430 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			430 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 943ebae781f519ecfecbfa1b997f15f59116e41d Mon Sep 17 00:00:00 2001
 | |
| From: Ray Jui <rjui@broadcom.com>
 | |
| Date: Fri, 4 Dec 2015 09:34:59 -0800
 | |
| Subject: [PATCH 2/5] PCI: iproc: Add PAXC interface support
 | |
| 
 | |
| Traditionally, all iProc PCIe root complexes use PAXB-based wrapper, with
 | |
| an integrated on-chip Serdes to support external endpoint devices.  On
 | |
| newer iProc platforms, a PAXC-based wrapper is introduced, for connection
 | |
| with internally emulated PCIe endpoint devices in the ASIC.
 | |
| 
 | |
| Add support for PAXC-based iProc PCIe root complex in the iProc PCIe core
 | |
| driver.  This change factors out common logic between PAXB and PAXC, and
 | |
| uses tables to store register offsets that are different between PAXB and
 | |
| PAXC.  This allows the driver to be scaled to support subsequent PAXC
 | |
| revisions in the future.
 | |
| 
 | |
| Signed-off-by: Ray Jui <rjui@broadcom.com>
 | |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
 | |
| Reviewed-by: Scott Branden <sbranden@broadcom.com>
 | |
| ---
 | |
|  drivers/pci/host/pcie-iproc-platform.c |  24 +++-
 | |
|  drivers/pci/host/pcie-iproc.c          | 202 +++++++++++++++++++++++++++------
 | |
|  drivers/pci/host/pcie-iproc.h          |  19 ++++
 | |
|  3 files changed, 205 insertions(+), 40 deletions(-)
 | |
| 
 | |
| --- a/drivers/pci/host/pcie-iproc-platform.c
 | |
| +++ b/drivers/pci/host/pcie-iproc-platform.c
 | |
| @@ -26,8 +26,21 @@
 | |
|  
 | |
|  #include "pcie-iproc.h"
 | |
|  
 | |
| +static const struct of_device_id iproc_pcie_of_match_table[] = {
 | |
| +	{
 | |
| +		.compatible = "brcm,iproc-pcie",
 | |
| +		.data = (int *)IPROC_PCIE_PAXB,
 | |
| +	}, {
 | |
| +		.compatible = "brcm,iproc-pcie-paxc",
 | |
| +		.data = (int *)IPROC_PCIE_PAXC,
 | |
| +	},
 | |
| +	{ /* sentinel */ }
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
 | |
| +
 | |
|  static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
 | |
|  {
 | |
| +	const struct of_device_id *of_id;
 | |
|  	struct iproc_pcie *pcie;
 | |
|  	struct device_node *np = pdev->dev.of_node;
 | |
|  	struct resource reg;
 | |
| @@ -35,11 +48,16 @@ static int iproc_pcie_pltfm_probe(struct
 | |
|  	LIST_HEAD(res);
 | |
|  	int ret;
 | |
|  
 | |
| +	of_id = of_match_device(iproc_pcie_of_match_table, &pdev->dev);
 | |
| +	if (!of_id)
 | |
| +		return -EINVAL;
 | |
| +
 | |
|  	pcie = devm_kzalloc(&pdev->dev, sizeof(struct iproc_pcie), GFP_KERNEL);
 | |
|  	if (!pcie)
 | |
|  		return -ENOMEM;
 | |
|  
 | |
|  	pcie->dev = &pdev->dev;
 | |
| +	pcie->type = (enum iproc_pcie_type)of_id->data;
 | |
|  	platform_set_drvdata(pdev, pcie);
 | |
|  
 | |
|  	ret = of_address_to_resource(np, 0, ®);
 | |
| @@ -114,12 +132,6 @@ static int iproc_pcie_pltfm_remove(struc
 | |
|  	return iproc_pcie_remove(pcie);
 | |
|  }
 | |
|  
 | |
| -static const struct of_device_id iproc_pcie_of_match_table[] = {
 | |
| -	{ .compatible = "brcm,iproc-pcie", },
 | |
| -	{ /* sentinel */ }
 | |
| -};
 | |
| -MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
 | |
| -
 | |
|  static struct platform_driver iproc_pcie_pltfm_driver = {
 | |
|  	.driver = {
 | |
|  		.name = "iproc-pcie",
 | |
| --- a/drivers/pci/host/pcie-iproc.c
 | |
| +++ b/drivers/pci/host/pcie-iproc.c
 | |
| @@ -30,20 +30,16 @@
 | |
|  
 | |
|  #include "pcie-iproc.h"
 | |
|  
 | |
| -#define CLK_CONTROL_OFFSET           0x000
 | |
|  #define EP_PERST_SOURCE_SELECT_SHIFT 2
 | |
|  #define EP_PERST_SOURCE_SELECT       BIT(EP_PERST_SOURCE_SELECT_SHIFT)
 | |
|  #define EP_MODE_SURVIVE_PERST_SHIFT  1
 | |
|  #define EP_MODE_SURVIVE_PERST        BIT(EP_MODE_SURVIVE_PERST_SHIFT)
 | |
|  #define RC_PCIE_RST_OUTPUT_SHIFT     0
 | |
|  #define RC_PCIE_RST_OUTPUT           BIT(RC_PCIE_RST_OUTPUT_SHIFT)
 | |
| +#define PAXC_RESET_MASK              0x7f
 | |
|  
 | |
| -#define CFG_IND_ADDR_OFFSET          0x120
 | |
|  #define CFG_IND_ADDR_MASK            0x00001ffc
 | |
|  
 | |
| -#define CFG_IND_DATA_OFFSET          0x124
 | |
| -
 | |
| -#define CFG_ADDR_OFFSET              0x1f8
 | |
|  #define CFG_ADDR_BUS_NUM_SHIFT       20
 | |
|  #define CFG_ADDR_BUS_NUM_MASK        0x0ff00000
 | |
|  #define CFG_ADDR_DEV_NUM_SHIFT       15
 | |
| @@ -55,12 +51,8 @@
 | |
|  #define CFG_ADDR_CFG_TYPE_SHIFT      0
 | |
|  #define CFG_ADDR_CFG_TYPE_MASK       0x00000003
 | |
|  
 | |
| -#define CFG_DATA_OFFSET              0x1fc
 | |
| -
 | |
| -#define SYS_RC_INTX_EN               0x330
 | |
|  #define SYS_RC_INTX_MASK             0xf
 | |
|  
 | |
| -#define PCIE_LINK_STATUS_OFFSET      0xf0c
 | |
|  #define PCIE_PHYLINKUP_SHIFT         3
 | |
|  #define PCIE_PHYLINKUP               BIT(PCIE_PHYLINKUP_SHIFT)
 | |
|  #define PCIE_DL_ACTIVE_SHIFT         2
 | |
| @@ -71,12 +63,54 @@
 | |
|  #define OARR_SIZE_CFG_SHIFT          1
 | |
|  #define OARR_SIZE_CFG                BIT(OARR_SIZE_CFG_SHIFT)
 | |
|  
 | |
| -#define OARR_LO(window)              (0xd20 + (window) * 8)
 | |
| -#define OARR_HI(window)              (0xd24 + (window) * 8)
 | |
| -#define OMAP_LO(window)              (0xd40 + (window) * 8)
 | |
| -#define OMAP_HI(window)              (0xd44 + (window) * 8)
 | |
| -
 | |
|  #define MAX_NUM_OB_WINDOWS           2
 | |
| +#define MAX_NUM_PAXC_PF              4
 | |
| +
 | |
| +#define IPROC_PCIE_REG_INVALID 0xffff
 | |
| +
 | |
| +enum iproc_pcie_reg {
 | |
| +	IPROC_PCIE_CLK_CTRL = 0,
 | |
| +	IPROC_PCIE_CFG_IND_ADDR,
 | |
| +	IPROC_PCIE_CFG_IND_DATA,
 | |
| +	IPROC_PCIE_CFG_ADDR,
 | |
| +	IPROC_PCIE_CFG_DATA,
 | |
| +	IPROC_PCIE_INTX_EN,
 | |
| +	IPROC_PCIE_OARR_LO,
 | |
| +	IPROC_PCIE_OARR_HI,
 | |
| +	IPROC_PCIE_OMAP_LO,
 | |
| +	IPROC_PCIE_OMAP_HI,
 | |
| +	IPROC_PCIE_LINK_STATUS,
 | |
| +};
 | |
| +
 | |
| +/* iProc PCIe PAXB registers */
 | |
| +static const u16 iproc_pcie_reg_paxb[] = {
 | |
| +	[IPROC_PCIE_CLK_CTRL]     = 0x000,
 | |
| +	[IPROC_PCIE_CFG_IND_ADDR] = 0x120,
 | |
| +	[IPROC_PCIE_CFG_IND_DATA] = 0x124,
 | |
| +	[IPROC_PCIE_CFG_ADDR]     = 0x1f8,
 | |
| +	[IPROC_PCIE_CFG_DATA]     = 0x1fc,
 | |
| +	[IPROC_PCIE_INTX_EN]      = 0x330,
 | |
| +	[IPROC_PCIE_OARR_LO]      = 0xd20,
 | |
| +	[IPROC_PCIE_OARR_HI]      = 0xd24,
 | |
| +	[IPROC_PCIE_OMAP_LO]      = 0xd40,
 | |
| +	[IPROC_PCIE_OMAP_HI]      = 0xd44,
 | |
| +	[IPROC_PCIE_LINK_STATUS]  = 0xf0c,
 | |
| +};
 | |
| +
 | |
| +/* iProc PCIe PAXC v1 registers */
 | |
| +static const u16 iproc_pcie_reg_paxc[] = {
 | |
| +	[IPROC_PCIE_CLK_CTRL]     = 0x000,
 | |
| +	[IPROC_PCIE_CFG_IND_ADDR] = 0x1f0,
 | |
| +	[IPROC_PCIE_CFG_IND_DATA] = 0x1f4,
 | |
| +	[IPROC_PCIE_CFG_ADDR]     = 0x1f8,
 | |
| +	[IPROC_PCIE_CFG_DATA]     = 0x1fc,
 | |
| +	[IPROC_PCIE_INTX_EN]      = IPROC_PCIE_REG_INVALID,
 | |
| +	[IPROC_PCIE_OARR_LO]      = IPROC_PCIE_REG_INVALID,
 | |
| +	[IPROC_PCIE_OARR_HI]      = IPROC_PCIE_REG_INVALID,
 | |
| +	[IPROC_PCIE_OMAP_LO]      = IPROC_PCIE_REG_INVALID,
 | |
| +	[IPROC_PCIE_OMAP_HI]      = IPROC_PCIE_REG_INVALID,
 | |
| +	[IPROC_PCIE_LINK_STATUS]  = IPROC_PCIE_REG_INVALID,
 | |
| +};
 | |
|  
 | |
|  static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
 | |
|  {
 | |
| @@ -91,6 +125,65 @@ static inline struct iproc_pcie *iproc_d
 | |
|  	return pcie;
 | |
|  }
 | |
|  
 | |
| +static inline bool iproc_pcie_reg_is_invalid(u16 reg_offset)
 | |
| +{
 | |
| +	return !!(reg_offset == IPROC_PCIE_REG_INVALID);
 | |
| +}
 | |
| +
 | |
| +static inline u16 iproc_pcie_reg_offset(struct iproc_pcie *pcie,
 | |
| +					enum iproc_pcie_reg reg)
 | |
| +{
 | |
| +	return pcie->reg_offsets[reg];
 | |
| +}
 | |
| +
 | |
| +static inline u32 iproc_pcie_read_reg(struct iproc_pcie *pcie,
 | |
| +				      enum iproc_pcie_reg reg)
 | |
| +{
 | |
| +	u16 offset = iproc_pcie_reg_offset(pcie, reg);
 | |
| +
 | |
| +	if (iproc_pcie_reg_is_invalid(offset))
 | |
| +		return 0;
 | |
| +
 | |
| +	return readl(pcie->base + offset);
 | |
| +}
 | |
| +
 | |
| +static inline void iproc_pcie_write_reg(struct iproc_pcie *pcie,
 | |
| +					enum iproc_pcie_reg reg, u32 val)
 | |
| +{
 | |
| +	u16 offset = iproc_pcie_reg_offset(pcie, reg);
 | |
| +
 | |
| +	if (iproc_pcie_reg_is_invalid(offset))
 | |
| +		return;
 | |
| +
 | |
| +	writel(val, pcie->base + offset);
 | |
| +}
 | |
| +
 | |
| +static inline void iproc_pcie_ob_write(struct iproc_pcie *pcie,
 | |
| +				       enum iproc_pcie_reg reg,
 | |
| +				       unsigned window, u32 val)
 | |
| +{
 | |
| +	u16 offset = iproc_pcie_reg_offset(pcie, reg);
 | |
| +
 | |
| +	if (iproc_pcie_reg_is_invalid(offset))
 | |
| +		return;
 | |
| +
 | |
| +	writel(val, pcie->base + offset + (window * 8));
 | |
| +}
 | |
| +
 | |
| +static inline bool iproc_pcie_device_is_valid(struct iproc_pcie *pcie,
 | |
| +					      unsigned int slot,
 | |
| +					      unsigned int fn)
 | |
| +{
 | |
| +	if (slot > 0)
 | |
| +		return false;
 | |
| +
 | |
| +	/* PAXC can only support limited number of functions */
 | |
| +	if (pcie->type == IPROC_PCIE_PAXC && fn >= MAX_NUM_PAXC_PF)
 | |
| +		return false;
 | |
| +
 | |
| +	return true;
 | |
| +}
 | |
| +
 | |
|  /**
 | |
|   * Note access to the configuration registers are protected at the higher layer
 | |
|   * by 'pci_lock' in drivers/pci/access.c
 | |
| @@ -104,28 +197,34 @@ static void __iomem *iproc_pcie_map_cfg_
 | |
|  	unsigned fn = PCI_FUNC(devfn);
 | |
|  	unsigned busno = bus->number;
 | |
|  	u32 val;
 | |
| +	u16 offset;
 | |
| +
 | |
| +	if (!iproc_pcie_device_is_valid(pcie, slot, fn))
 | |
| +		return NULL;
 | |
|  
 | |
|  	/* root complex access */
 | |
|  	if (busno == 0) {
 | |
| -		if (slot >= 1)
 | |
| +		iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_IND_ADDR,
 | |
| +				     where & CFG_IND_ADDR_MASK);
 | |
| +		offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_IND_DATA);
 | |
| +		if (iproc_pcie_reg_is_invalid(offset))
 | |
|  			return NULL;
 | |
| -		writel(where & CFG_IND_ADDR_MASK,
 | |
| -		       pcie->base + CFG_IND_ADDR_OFFSET);
 | |
| -		return (pcie->base + CFG_IND_DATA_OFFSET);
 | |
| +		else
 | |
| +			return (pcie->base + offset);
 | |
|  	}
 | |
|  
 | |
| -	if (fn > 1)
 | |
| -		return NULL;
 | |
| -
 | |
|  	/* EP device access */
 | |
|  	val = (busno << CFG_ADDR_BUS_NUM_SHIFT) |
 | |
|  		(slot << CFG_ADDR_DEV_NUM_SHIFT) |
 | |
|  		(fn << CFG_ADDR_FUNC_NUM_SHIFT) |
 | |
|  		(where & CFG_ADDR_REG_NUM_MASK) |
 | |
|  		(1 & CFG_ADDR_CFG_TYPE_MASK);
 | |
| -	writel(val, pcie->base + CFG_ADDR_OFFSET);
 | |
| -
 | |
| -	return (pcie->base + CFG_DATA_OFFSET);
 | |
| +	iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_ADDR, val);
 | |
| +	offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_DATA);
 | |
| +	if (iproc_pcie_reg_is_invalid(offset))
 | |
| +		return NULL;
 | |
| +	else
 | |
| +		return (pcie->base + offset);
 | |
|  }
 | |
|  
 | |
|  static struct pci_ops iproc_pcie_ops = {
 | |
| @@ -138,18 +237,29 @@ static void iproc_pcie_reset(struct ipro
 | |
|  {
 | |
|  	u32 val;
 | |
|  
 | |
| +	if (pcie->type == IPROC_PCIE_PAXC) {
 | |
| +		val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
 | |
| +		val &= ~PAXC_RESET_MASK;
 | |
| +		iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
 | |
| +		udelay(100);
 | |
| +		val |= PAXC_RESET_MASK;
 | |
| +		iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
 | |
| +		udelay(100);
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
|  	/*
 | |
|  	 * Select perst_b signal as reset source. Put the device into reset,
 | |
|  	 * and then bring it out of reset
 | |
|  	 */
 | |
| -	val = readl(pcie->base + CLK_CONTROL_OFFSET);
 | |
| +	val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
 | |
|  	val &= ~EP_PERST_SOURCE_SELECT & ~EP_MODE_SURVIVE_PERST &
 | |
|  		~RC_PCIE_RST_OUTPUT;
 | |
| -	writel(val, pcie->base + CLK_CONTROL_OFFSET);
 | |
| +	iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
 | |
|  	udelay(250);
 | |
|  
 | |
|  	val |= RC_PCIE_RST_OUTPUT;
 | |
| -	writel(val, pcie->base + CLK_CONTROL_OFFSET);
 | |
| +	iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
 | |
|  	msleep(100);
 | |
|  }
 | |
|  
 | |
| @@ -160,7 +270,14 @@ static int iproc_pcie_check_link(struct
 | |
|  	u16 pos, link_status;
 | |
|  	bool link_is_active = false;
 | |
|  
 | |
| -	val = readl(pcie->base + PCIE_LINK_STATUS_OFFSET);
 | |
| +	/*
 | |
| +	 * PAXC connects to emulated endpoint devices directly and does not
 | |
| +	 * have a Serdes.  Therefore skip the link detection logic here.
 | |
| +	 */
 | |
| +	if (pcie->type == IPROC_PCIE_PAXC)
 | |
| +		return 0;
 | |
| +
 | |
| +	val = iproc_pcie_read_reg(pcie, IPROC_PCIE_LINK_STATUS);
 | |
|  	if (!(val & PCIE_PHYLINKUP) || !(val & PCIE_DL_ACTIVE)) {
 | |
|  		dev_err(pcie->dev, "PHY or data link is INACTIVE!\n");
 | |
|  		return -ENODEV;
 | |
| @@ -221,7 +338,7 @@ static int iproc_pcie_check_link(struct
 | |
|  
 | |
|  static void iproc_pcie_enable(struct iproc_pcie *pcie)
 | |
|  {
 | |
| -	writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN);
 | |
| +	iproc_pcie_write_reg(pcie, IPROC_PCIE_INTX_EN, SYS_RC_INTX_MASK);
 | |
|  }
 | |
|  
 | |
|  /**
 | |
| @@ -272,11 +389,15 @@ static int iproc_pcie_setup_ob(struct ip
 | |
|  	axi_addr -= ob->axi_offset;
 | |
|  
 | |
|  	for (i = 0; i < MAX_NUM_OB_WINDOWS; i++) {
 | |
| -		writel(lower_32_bits(axi_addr) | OARR_VALID |
 | |
| -		       (ob->set_oarr_size ? 1 : 0), pcie->base + OARR_LO(i));
 | |
| -		writel(upper_32_bits(axi_addr), pcie->base + OARR_HI(i));
 | |
| -		writel(lower_32_bits(pci_addr), pcie->base + OMAP_LO(i));
 | |
| -		writel(upper_32_bits(pci_addr), pcie->base + OMAP_HI(i));
 | |
| +		iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_LO, i,
 | |
| +				    lower_32_bits(axi_addr) | OARR_VALID |
 | |
| +				    (ob->set_oarr_size ? 1 : 0));
 | |
| +		iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_HI, i,
 | |
| +				    upper_32_bits(axi_addr));
 | |
| +		iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_LO, i,
 | |
| +				    lower_32_bits(pci_addr));
 | |
| +		iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_HI, i,
 | |
| +				    upper_32_bits(pci_addr));
 | |
|  
 | |
|  		size -= ob->window_size;
 | |
|  		if (size == 0)
 | |
| @@ -340,6 +461,19 @@ int iproc_pcie_setup(struct iproc_pcie *
 | |
|  		goto err_exit_phy;
 | |
|  	}
 | |
|  
 | |
| +	switch (pcie->type) {
 | |
| +	case IPROC_PCIE_PAXB:
 | |
| +		pcie->reg_offsets = iproc_pcie_reg_paxb;
 | |
| +		break;
 | |
| +	case IPROC_PCIE_PAXC:
 | |
| +		pcie->reg_offsets = iproc_pcie_reg_paxc;
 | |
| +		break;
 | |
| +	default:
 | |
| +		dev_err(pcie->dev, "incompatible iProc PCIe interface\n");
 | |
| +		ret = -EINVAL;
 | |
| +		goto err_power_off_phy;
 | |
| +	}
 | |
| +
 | |
|  	iproc_pcie_reset(pcie);
 | |
|  
 | |
|  	if (pcie->need_ob_cfg) {
 | |
| --- a/drivers/pci/host/pcie-iproc.h
 | |
| +++ b/drivers/pci/host/pcie-iproc.h
 | |
| @@ -15,6 +15,20 @@
 | |
|  #define _PCIE_IPROC_H
 | |
|  
 | |
|  /**
 | |
| + * iProc PCIe interface type
 | |
| + *
 | |
| + * PAXB is the wrapper used in root complex that can be connected to an
 | |
| + * external endpoint device.
 | |
| + *
 | |
| + * PAXC is the wrapper used in root complex dedicated for internal emulated
 | |
| + * endpoint devices.
 | |
| + */
 | |
| +enum iproc_pcie_type {
 | |
| +	IPROC_PCIE_PAXB = 0,
 | |
| +	IPROC_PCIE_PAXC,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
|   * iProc PCIe outbound mapping
 | |
|   * @set_oarr_size: indicates the OARR size bit needs to be set
 | |
|   * @axi_offset: offset from the AXI address to the internal address used by
 | |
| @@ -29,7 +43,10 @@ struct iproc_pcie_ob {
 | |
|  
 | |
|  /**
 | |
|   * iProc PCIe device
 | |
| + *
 | |
|   * @dev: pointer to device data structure
 | |
| + * @type: iProc PCIe interface type
 | |
| + * @reg_offsets: register offsets
 | |
|   * @base: PCIe host controller I/O register base
 | |
|   * @sysdata: Per PCI controller data (ARM-specific)
 | |
|   * @root_bus: pointer to root bus
 | |
| @@ -41,6 +58,8 @@ struct iproc_pcie_ob {
 | |
|   */
 | |
|  struct iproc_pcie {
 | |
|  	struct device *dev;
 | |
| +	enum iproc_pcie_type type;
 | |
| +	const u16 *reg_offsets;
 | |
|  	void __iomem *base;
 | |
|  #ifdef CONFIG_ARM
 | |
|  	struct pci_sys_data sysdata;
 | 
