diff options
Diffstat (limited to 'drivers/pci')
26 files changed, 3267 insertions, 142 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index df141420c902..9747c1ec8c74 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -134,3 +134,4 @@ config PCI_HYPERV source "drivers/pci/hotplug/Kconfig" source "drivers/pci/dwc/Kconfig" source "drivers/pci/host/Kconfig" +source "drivers/pci/endpoint/Kconfig" diff --git a/drivers/pci/dwc/Kconfig b/drivers/pci/dwc/Kconfig index dfb8a69afc28..b7e15526d676 100644 --- a/drivers/pci/dwc/Kconfig +++ b/drivers/pci/dwc/Kconfig @@ -9,16 +9,44 @@ config PCIE_DW_HOST depends on PCI_MSI_IRQ_DOMAIN select PCIE_DW +config PCIE_DW_EP + bool + depends on PCI_ENDPOINT + select PCIE_DW + config PCI_DRA7XX bool "TI DRA7xx PCIe controller" - depends on PCI + depends on (PCI && PCI_MSI_IRQ_DOMAIN) || PCI_ENDPOINT depends on OF && HAS_IOMEM && TI_PIPE3 + help + Enables support for the PCIe controller in the DRA7xx SoC. There + are two instances of PCIe controller in DRA7xx. This controller can + work either as EP or RC. In order to enable host-specific features + PCI_DRA7XX_HOST must be selected and in order to enable device- + specific features PCI_DRA7XX_EP must be selected. This uses + the Designware core. + +if PCI_DRA7XX + +config PCI_DRA7XX_HOST + bool "PCI DRA7xx Host Mode" + depends on PCI depends on PCI_MSI_IRQ_DOMAIN select PCIE_DW_HOST + default y help - Enables support for the PCIe controller in the DRA7xx SoC. There - are two instances of PCIe controller in DRA7xx. This controller can - act both as EP and RC. This reuses the Designware core. + Enables support for the PCIe controller in the DRA7xx SoC to work in + host mode. + +config PCI_DRA7XX_EP + bool "PCI DRA7xx Endpoint Mode" + depends on PCI_ENDPOINT + select PCIE_DW_EP + help + Enables support for the PCIe controller in the DRA7xx SoC to work in + endpoint mode. + +endif config PCIE_DW_PLAT bool "Platform bus based DesignWare PCIe Controller" @@ -89,6 +117,7 @@ config PCI_HISI depends on PCI_MSI_IRQ_DOMAIN select PCIEPORTBUS select PCIE_DW_HOST + select PCI_HOST_COMMON help Say Y here if you want PCIe controller support on HiSilicon Hip05 and Hip06 SoCs diff --git a/drivers/pci/dwc/Makefile b/drivers/pci/dwc/Makefile index a2df13c28798..f31a8596442a 100644 --- a/drivers/pci/dwc/Makefile +++ b/drivers/pci/dwc/Makefile @@ -1,7 +1,10 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o +obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o -obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o +ifneq ($(filter y,$(CONFIG_PCI_DRA7XX_HOST) $(CONFIG_PCI_DRA7XX_EP)),) + obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o +endif obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o obj-$(CONFIG_PCI_IMX6) += pci-imx6.o obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o diff --git a/drivers/pci/dwc/pci-dra7xx.c b/drivers/pci/dwc/pci-dra7xx.c index 0984baff07e3..8decf46cf525 100644 --- a/drivers/pci/dwc/pci-dra7xx.c +++ b/drivers/pci/dwc/pci-dra7xx.c @@ -10,12 +10,14 @@ * published by the Free Software Foundation. */ +#include <linux/delay.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/of_pci.h> #include <linux/pci.h> @@ -24,6 +26,8 @@ #include <linux/pm_runtime.h> #include <linux/resource.h> #include <linux/types.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> #include "pcie-designware.h" @@ -57,6 +61,11 @@ #define MSI BIT(4) #define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD) +#define PCIECTRL_TI_CONF_DEVICE_TYPE 0x0100 +#define DEVICE_TYPE_EP 0x0 +#define DEVICE_TYPE_LEG_EP 0x1 +#define DEVICE_TYPE_RC 0x4 + #define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104 #define LTSSM_EN 0x1 @@ -66,6 +75,13 @@ #define EXP_CAP_ID_OFFSET 0x70 +#define PCIECTRL_TI_CONF_INTX_ASSERT 0x0124 +#define PCIECTRL_TI_CONF_INTX_DEASSERT 0x0128 + +#define PCIECTRL_TI_CONF_MSI_XMT 0x012c +#define MSI_REQ_GRANT BIT(0) +#define MSI_VECTOR_SHIFT 7 + struct dra7xx_pcie { struct dw_pcie *pci; void __iomem *base; /* DT ti_conf */ @@ -73,6 +89,11 @@ struct dra7xx_pcie { struct phy **phy; int link_gen; struct irq_domain *irq_domain; + enum dw_pcie_device_mode mode; +}; + +struct dra7xx_pcie_of_data { + enum dw_pcie_device_mode mode; }; #define to_dra7xx_pcie(x) dev_get_drvdata((x)->dev) @@ -88,6 +109,11 @@ static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset, writel(value, pcie->base + offset); } +static u64 dra7xx_pcie_cpu_addr_fixup(u64 pci_addr) +{ + return pci_addr & DRA7XX_CPU_TO_BUS_ADDR; +} + static int dra7xx_pcie_link_up(struct dw_pcie *pci) { struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); @@ -96,9 +122,19 @@ static int dra7xx_pcie_link_up(struct dw_pcie *pci) return !!(reg & LINK_UP); } -static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx) +static void dra7xx_pcie_stop_link(struct dw_pcie *pci) { - struct dw_pcie *pci = dra7xx->pci; + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); + u32 reg; + + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); + reg &= ~LTSSM_EN; + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); +} + +static int dra7xx_pcie_establish_link(struct dw_pcie *pci) +{ + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); struct device *dev = pci->dev; u32 reg; u32 exp_cap_off = EXP_CAP_ID_OFFSET; @@ -132,34 +168,42 @@ static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx) reg |= LTSSM_EN; dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); - return dw_pcie_wait_for_link(pci); + return 0; } -static void dra7xx_pcie_enable_interrupts(struct dra7xx_pcie *dra7xx) +static void dra7xx_pcie_enable_msi_interrupts(struct dra7xx_pcie *dra7xx) { - dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, - ~INTERRUPTS); - dra7xx_pcie_writel(dra7xx, - PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN, INTERRUPTS); dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, ~LEG_EP_INTERRUPTS & ~MSI); - dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, + + dra7xx_pcie_writel(dra7xx, + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, MSI | LEG_EP_INTERRUPTS); } +static void dra7xx_pcie_enable_wrapper_interrupts(struct dra7xx_pcie *dra7xx) +{ + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, + ~INTERRUPTS); + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN, + INTERRUPTS); +} + +static void dra7xx_pcie_enable_interrupts(struct dra7xx_pcie *dra7xx) +{ + dra7xx_pcie_enable_wrapper_interrupts(dra7xx); + dra7xx_pcie_enable_msi_interrupts(dra7xx); +} + static void dra7xx_pcie_host_init(struct pcie_port *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); - pp->io_base &= DRA7XX_CPU_TO_BUS_ADDR; - pp->mem_base &= DRA7XX_CPU_TO_BUS_ADDR; - pp->cfg0_base &= DRA7XX_CPU_TO_BUS_ADDR; - pp->cfg1_base &= DRA7XX_CPU_TO_BUS_ADDR; - dw_pcie_setup_rc(pp); - dra7xx_pcie_establish_link(dra7xx); + dra7xx_pcie_establish_link(pci); + dw_pcie_wait_for_link(pci); dw_pcie_msi_init(pp); dra7xx_pcie_enable_interrupts(dra7xx); } @@ -237,6 +281,7 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) struct dra7xx_pcie *dra7xx = arg; struct dw_pcie *pci = dra7xx->pci; struct device *dev = pci->dev; + struct dw_pcie_ep *ep = &pci->ep; u32 reg; reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN); @@ -273,8 +318,11 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) if (reg & LINK_REQ_RST) dev_dbg(dev, "Link Request Reset\n"); - if (reg & LINK_UP_EVT) + if (reg & LINK_UP_EVT) { + if (dra7xx->mode == DW_PCIE_EP_TYPE) + dw_pcie_ep_linkup(ep); dev_dbg(dev, "Link-up state change\n"); + } if (reg & CFG_BME_EVT) dev_dbg(dev, "CFG 'Bus Master Enable' change\n"); @@ -287,6 +335,94 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) return IRQ_HANDLED; } +static void dra7xx_pcie_ep_init(struct dw_pcie_ep *ep) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); + + dra7xx_pcie_enable_wrapper_interrupts(dra7xx); +} + +static void dra7xx_pcie_raise_legacy_irq(struct dra7xx_pcie *dra7xx) +{ + dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_ASSERT, 0x1); + mdelay(1); + dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_DEASSERT, 0x1); +} + +static void dra7xx_pcie_raise_msi_irq(struct dra7xx_pcie *dra7xx, + u8 interrupt_num) +{ + u32 reg; + + reg = (interrupt_num - 1) << MSI_VECTOR_SHIFT; + reg |= MSI_REQ_GRANT; + dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_MSI_XMT, reg); +} + +static int dra7xx_pcie_raise_irq(struct dw_pcie_ep *ep, + enum pci_epc_irq_type type, u8 interrupt_num) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci); + + switch (type) { + case PCI_EPC_IRQ_LEGACY: + dra7xx_pcie_raise_legacy_irq(dra7xx); + break; + case PCI_EPC_IRQ_MSI: + dra7xx_pcie_raise_msi_irq(dra7xx, interrupt_num); + break; + default: + dev_err(pci->dev, "UNKNOWN IRQ type\n"); + } + + return 0; +} + +static struct dw_pcie_ep_ops pcie_ep_ops = { + .ep_init = dra7xx_pcie_ep_init, + .raise_irq = dra7xx_pcie_raise_irq, +}; + +static int __init dra7xx_add_pcie_ep(struct dra7xx_pcie *dra7xx, + struct platform_device *pdev) +{ + int ret; + struct dw_pcie_ep *ep; + struct resource *res; + struct device *dev = &pdev->dev; + struct dw_pcie *pci = dra7xx->pci; + + ep = &pci->ep; + ep->ops = &pcie_ep_ops; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics"); + pci->dbi_base = devm_ioremap(dev, res->start, resource_size(res)); + if (!pci->dbi_base) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics2"); + pci->dbi_base2 = devm_ioremap(dev, res->start, resource_size(res)); + if (!pci->dbi_base2) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space"); + if (!res) + return -EINVAL; + + ep->phys_base = res->start; + ep->addr_size = resource_size(res); + + ret = dw_pcie_ep_init(ep); + if (ret) { + dev_err(dev, "failed to initialize endpoint\n"); + return ret; + } + + return 0; +} + static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx, struct platform_device *pdev) { @@ -329,6 +465,9 @@ static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx, } static const struct dw_pcie_ops dw_pcie_ops = { + .cpu_addr_fixup = dra7xx_pcie_cpu_addr_fixup, + .start_link = dra7xx_pcie_establish_link, + .stop_link = dra7xx_pcie_stop_link, .link_up = dra7xx_pcie_link_up, }; @@ -371,6 +510,68 @@ err_phy: return ret; } +static const struct dra7xx_pcie_of_data dra7xx_pcie_rc_of_data = { + .mode = DW_PCIE_RC_TYPE, +}; + +static const struct dra7xx_pcie_of_data dra7xx_pcie_ep_of_data = { + .mode = DW_PCIE_EP_TYPE, +}; + +static const struct of_device_id of_dra7xx_pcie_match[] = { + { + .compatible = "ti,dra7-pcie", + .data = &dra7xx_pcie_rc_of_data, + }, + { + .compatible = "ti,dra7-pcie-ep", + .data = &dra7xx_pcie_ep_of_data, + }, + {}, +}; + +/* + * dra7xx_pcie_ep_unaligned_memaccess: workaround for AM572x/AM571x Errata i870 + * @dra7xx: the dra7xx device where the workaround should be applied + * + * Access to the PCIe slave port that are not 32-bit aligned will result + * in incorrect mapping to TLP Address and Byte enable fields. Therefore, + * byte and half-word accesses are not possible to byte offset 0x1, 0x2, or + * 0x3. + * + * To avoid this issue set PCIE_SS1_AXI2OCP_LEGACY_MODE_ENABLE to 1. + */ +static int dra7xx_pcie_ep_unaligned_memaccess(struct device *dev) +{ + int ret; + struct device_node *np = dev->of_node; + struct of_phandle_args args; + struct regmap *regmap; + + regmap = syscon_regmap_lookup_by_phandle(np, + "ti,syscon-unaligned-access"); + if (IS_ERR(regmap)) { + dev_dbg(dev, "can't get ti,syscon-unaligned-access\n"); + return -EINVAL; + } + + ret = of_parse_phandle_with_fixed_args(np, "ti,syscon-unaligned-access", + 2, 0, &args); + if (ret) { + dev_err(dev, "failed to parse ti,syscon-unaligned-access\n"); + return ret; + } + + ret = regmap_update_bits(regmap, args.args[0], args.args[1], + args.args[1]); + if (ret) + dev_err(dev, "failed to enable unaligned access\n"); + + of_node_put(args.np); + + return ret; +} + static int __init dra7xx_pcie_probe(struct platform_device *pdev) { u32 reg; @@ -388,6 +589,16 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev) struct device_node *np = dev->of_node; char name[10]; struct gpio_desc *reset; + const struct of_device_id *match; + const struct dra7xx_pcie_of_data *data; + enum dw_pcie_device_mode mode; + + match = of_match_device(of_match_ptr(of_dra7xx_pcie_match), dev); + if (!match) + return -EINVAL; + + data = (struct dra7xx_pcie_of_data *)match->data; + mode = (enum dw_pcie_device_mode)data->mode; dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL); if (!dra7xx) @@ -409,13 +620,6 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev) return -EINVAL; } - ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler, - IRQF_SHARED, "dra7xx-pcie-main", dra7xx); - if (ret) { - dev_err(dev, "failed to request irq\n"); - return ret; - } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ti_conf"); base = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!base) @@ -473,9 +677,37 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev) if (dra7xx->link_gen < 0 || dra7xx->link_gen > 2) dra7xx->link_gen = 2; - ret = dra7xx_add_pcie_port(dra7xx, pdev); - if (ret < 0) + switch (mode) { + case DW_PCIE_RC_TYPE: + dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE, + DEVICE_TYPE_RC); + ret = dra7xx_add_pcie_port(dra7xx, pdev); + if (ret < 0) + goto err_gpio; + break; + case DW_PCIE_EP_TYPE: + dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE, + DEVICE_TYPE_EP); + + ret = dra7xx_pcie_ep_unaligned_memaccess(dev); + if (ret) + goto err_gpio; + + ret = dra7xx_add_pcie_ep(dra7xx, pdev); + if (ret < 0) + goto err_gpio; + break; + default: + dev_err(dev, "INVALID device type %d\n", mode); + } + dra7xx->mode = mode; + + ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler, + IRQF_SHARED, "dra7xx-pcie-main", dra7xx); + if (ret) { + dev_err(dev, "failed to request irq\n"); goto err_gpio; + } return 0; @@ -496,6 +728,9 @@ static int dra7xx_pcie_suspend(struct device *dev) struct dw_pcie *pci = dra7xx->pci; u32 val; + if (dra7xx->mode != DW_PCIE_RC_TYPE) + return 0; + /* clear MSE */ val = dw_pcie_readl_dbi(pci, PCI_COMMAND); val &= ~PCI_COMMAND_MEMORY; @@ -510,6 +745,9 @@ static int dra7xx_pcie_resume(struct device *dev) struct dw_pcie *pci = dra7xx->pci; u32 val; + if (dra7xx->mode != DW_PCIE_RC_TYPE) + return 0; + /* set MSE */ val = dw_pcie_readl_dbi(pci, PCI_COMMAND); val |= PCI_COMMAND_MEMORY; @@ -548,11 +786,6 @@ static const struct dev_pm_ops dra7xx_pcie_pm_ops = { dra7xx_pcie_resume_noirq) }; -static const struct of_device_id of_dra7xx_pcie_match[] = { - { .compatible = "ti,dra7-pcie", }, - {}, -}; - static struct platform_driver dra7xx_pcie_driver = { .driver = { .name = "dra7-pcie", diff --git a/drivers/pci/dwc/pci-exynos.c b/drivers/pci/dwc/pci-exynos.c index 993b650ef275..546082ad5a3f 100644 --- a/drivers/pci/dwc/pci-exynos.c +++ b/drivers/pci/dwc/pci-exynos.c @@ -132,10 +132,6 @@ static int exynos5440_pcie_get_mem_resources(struct platform_device *pdev, struct device *dev = pci->dev; struct resource *res; - /* If using the PHY framework, doesn't need to get other resource */ - if (ep->using_phy) - return 0; - ep->mem_res = devm_kzalloc(dev, sizeof(*ep->mem_res), GFP_KERNEL); if (!ep->mem_res) return -ENOMEM; @@ -145,6 +141,10 @@ static int exynos5440_pcie_get_mem_resources(struct platform_device *pdev, if (IS_ERR(ep->mem_res->elbi_base)) return PTR_ERR(ep->mem_res->elbi_base); + /* If using the PHY framework, doesn't need to get other resource */ + if (ep->using_phy) + return 0; + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ep->mem_res->phy_base = devm_ioremap_resource(dev, res); if (IS_ERR(ep->mem_res->phy_base)) @@ -521,23 +521,25 @@ static void exynos_pcie_enable_interrupts(struct exynos_pcie *ep) exynos_pcie_msi_init(ep); } -static u32 exynos_pcie_readl_dbi(struct dw_pcie *pci, u32 reg) +static u32 exynos_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, + u32 reg, size_t size) { struct exynos_pcie *ep = to_exynos_pcie(pci); u32 val; exynos_pcie_sideband_dbi_r_mode(ep, true); - val = readl(pci->dbi_base + reg); + dw_pcie_read(base + reg, size, &val); exynos_pcie_sideband_dbi_r_mode(ep, false); return val; } -static void exynos_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val) +static void exynos_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, + u32 reg, size_t size, u32 val) { struct exynos_pcie *ep = to_exynos_pcie(pci); exynos_pcie_sideband_dbi_w_mode(ep, true); - writel(val, pci->dbi_base + reg); + dw_pcie_write(base + reg, size, val); exynos_pcie_sideband_dbi_w_mode(ep, false); } @@ -644,8 +646,8 @@ static int __init exynos_add_pcie_port(struct exynos_pcie *ep, } static const struct dw_pcie_ops dw_pcie_ops = { - .readl_dbi = exynos_pcie_readl_dbi, - .writel_dbi = exynos_pcie_writel_dbi, + .read_dbi = exynos_pcie_read_dbi, + .write_dbi = exynos_pcie_write_dbi, .link_up = exynos_pcie_link_up, }; diff --git a/drivers/pci/dwc/pcie-artpec6.c b/drivers/pci/dwc/pcie-artpec6.c index fcd3ef845883..5b3b3afc0edb 100644 --- a/drivers/pci/dwc/pcie-artpec6.c +++ b/drivers/pci/dwc/pcie-artpec6.c @@ -78,6 +78,11 @@ static void artpec6_pcie_writel(struct artpec6_pcie *artpec6_pcie, u32 offset, u regmap_write(artpec6_pcie->regmap, offset, val); } +static u64 artpec6_pcie_cpu_addr_fixup(u64 pci_addr) +{ + return pci_addr & ARTPEC6_CPU_TO_BUS_ADDR; +} + static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie) { struct dw_pcie *pci = artpec6_pcie->pci; @@ -142,11 +147,6 @@ static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie) */ dw_pcie_writel_dbi(pci, MISC_CONTROL_1_OFF, DBI_RO_WR_EN); - pp->io_base &= ARTPEC6_CPU_TO_BUS_ADDR; - pp->mem_base &= ARTPEC6_CPU_TO_BUS_ADDR; - pp->cfg0_base &= ARTPEC6_CPU_TO_BUS_ADDR; - pp->cfg1_base &= ARTPEC6_CPU_TO_BUS_ADDR; - /* setup root complex */ dw_pcie_setup_rc(pp); @@ -234,6 +234,10 @@ static int artpec6_add_pcie_port(struct artpec6_pcie *artpec6_pcie, return 0; } +static const struct dw_pcie_ops dw_pcie_ops = { + .cpu_addr_fixup = artpec6_pcie_cpu_addr_fixup, +}; + static int artpec6_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -252,6 +256,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev) return -ENOMEM; pci->dev = dev; + pci->ops = &dw_pcie_ops; artpec6_pcie->pci = pci; diff --git a/drivers/pci/dwc/pcie-designware-ep.c b/drivers/pci/dwc/pcie-designware-ep.c new file mode 100644 index 000000000000..398406393f37 --- /dev/null +++ b/drivers/pci/dwc/pcie-designware-ep.c @@ -0,0 +1,342 @@ +/** + * Synopsys Designware PCIe Endpoint controller driver + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/of.h> + +#include "pcie-designware.h" +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> + +void dw_pcie_ep_linkup(struct dw_pcie_ep *ep) +{ + struct pci_epc *epc = ep->epc; + + pci_epc_linkup(epc); +} + +static void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar) +{ + u32 reg; + + reg = PCI_BASE_ADDRESS_0 + (4 * bar); + dw_pcie_writel_dbi2(pci, reg, 0x0); + dw_pcie_writel_dbi(pci, reg, 0x0); +} + +static int dw_pcie_ep_write_header(struct pci_epc *epc, + struct pci_epf_header *hdr) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, hdr->vendorid); + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, hdr->deviceid); + dw_pcie_writeb_dbi(pci, PCI_REVISION_ID, hdr->revid); + dw_pcie_writeb_dbi(pci, PCI_CLASS_PROG, hdr->progif_code); + dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, + hdr->subclass_code | hdr->baseclass_code << 8); + dw_pcie_writeb_dbi(pci, PCI_CACHE_LINE_SIZE, + hdr->cache_line_size); + dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_VENDOR_ID, + hdr->subsys_vendor_id); + dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_ID, hdr->subsys_id); + dw_pcie_writeb_dbi(pci, PCI_INTERRUPT_PIN, + hdr->interrupt_pin); + + return 0; +} + +static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, enum pci_barno bar, + dma_addr_t cpu_addr, + enum dw_pcie_as_type as_type) +{ + int ret; + u32 free_win; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + free_win = find_first_zero_bit(&ep->ib_window_map, + sizeof(ep->ib_window_map)); + if (free_win >= ep->num_ib_windows) { + dev_err(pci->dev, "no free inbound window\n"); + return -EINVAL; + } + + ret = dw_pcie_prog_inbound_atu(pci, free_win, bar, cpu_addr, + as_type); + if (ret < 0) { + dev_err(pci->dev, "Failed to program IB window\n"); + return ret; + } + + ep->bar_to_atu[bar] = free_win; + set_bit(free_win, &ep->ib_window_map); + + return 0; +} + +static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, phys_addr_t phys_addr, + u64 pci_addr, size_t size) +{ + u32 free_win; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + free_win = find_first_zero_bit(&ep->ob_window_map, + sizeof(ep->ob_window_map)); + if (free_win >= ep->num_ob_windows) { + dev_err(pci->dev, "no free outbound window\n"); + return -EINVAL; + } + + dw_pcie_prog_outbound_atu(pci, free_win, PCIE_ATU_TYPE_MEM, + phys_addr, pci_addr, size); + + set_bit(free_win, &ep->ob_window_map); + ep->outbound_addr[free_win] = phys_addr; + + return 0; +} + +static void dw_pcie_ep_clear_bar(struct pci_epc *epc, enum pci_barno bar) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + u32 atu_index = ep->bar_to_atu[bar]; + + dw_pcie_ep_reset_bar(pci, bar); + + dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_INBOUND); + clear_bit(atu_index, &ep->ib_window_map); +} + +static int dw_pcie_ep_set_bar(struct pci_epc *epc, enum pci_barno bar, + dma_addr_t bar_phys, size_t size, int flags) +{ + int ret; + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + enum dw_pcie_as_type as_type; + u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar); + + if (!(flags & PCI_BASE_ADDRESS_SPACE)) + as_type = DW_PCIE_AS_MEM; + else + as_type = DW_PCIE_AS_IO; + + ret = dw_pcie_ep_inbound_atu(ep, bar, bar_phys, as_type); + if (ret) + return ret; + + dw_pcie_writel_dbi2(pci, reg, size - 1); + dw_pcie_writel_dbi(pci, reg, flags); + + return 0; +} + +static int dw_pcie_find_index(struct dw_pcie_ep *ep, phys_addr_t addr, + u32 *atu_index) +{ + u32 index; + + for (index = 0; index < ep->num_ob_windows; index++) { + if (ep->outbound_addr[index] != addr) + continue; + *atu_index = index; + return 0; + } + + return -EINVAL; +} + +static void dw_pcie_ep_unmap_addr(struct pci_epc *epc, phys_addr_t addr) +{ + int ret; + u32 atu_index; + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + ret = dw_pcie_find_index(ep, addr, &atu_index); + if (ret < 0) + return; + + dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_OUTBOUND); + clear_bit(atu_index, &ep->ob_window_map); +} + +static int dw_pcie_ep_map_addr(struct pci_epc *epc, phys_addr_t addr, + u64 pci_addr, size_t size) +{ + int ret; + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + ret = dw_pcie_ep_outbound_atu(ep, addr, pci_addr, size); + if (ret) { + dev_err(pci->dev, "failed to enable address\n"); + return ret; + } + + return 0; +} + +static int dw_pcie_ep_get_msi(struct pci_epc *epc) +{ + int val; + u32 lower_addr; + u32 upper_addr; + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + val = dw_pcie_readb_dbi(pci, MSI_MESSAGE_CONTROL); + val = (val & MSI_CAP_MME_MASK) >> MSI_CAP_MME_SHIFT; + + lower_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_L32); + upper_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_U32); + + if (!(lower_addr || upper_addr)) + return -EINVAL; + + return val; +} + +static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 encode_int) +{ + int val; + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + val = (encode_int << MSI_CAP_MMC_SHIFT); + dw_pcie_writew_dbi(pci, MSI_MESSAGE_CONTROL, val); + + return 0; +} + +static int dw_pcie_ep_raise_irq(struct pci_epc *epc, + enum pci_epc_irq_type type, u8 interrupt_num) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + + if (!ep->ops->raise_irq) + return -EINVAL; + + return ep->ops->raise_irq(ep, type, interrupt_num); +} + +static void dw_pcie_ep_stop(struct pci_epc *epc) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + if (!pci->ops->stop_link) + return; + + pci->ops->stop_link(pci); +} + +static int dw_pcie_ep_start(struct pci_epc *epc) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + if (!pci->ops->start_link) + return -EINVAL; + + return pci->ops->start_link(pci); +} + +static const struct pci_epc_ops epc_ops = { + .write_header = dw_pcie_ep_write_header, + .set_bar = dw_pcie_ep_set_bar, + .clear_bar = dw_pcie_ep_clear_bar, + .map_addr = dw_pcie_ep_map_addr, + .unmap_addr = dw_pcie_ep_unmap_addr, + .set_msi = dw_pcie_ep_set_msi, + .get_msi = dw_pcie_ep_get_msi, + .raise_irq = dw_pcie_ep_raise_irq, + .start = dw_pcie_ep_start, + .stop = dw_pcie_ep_stop, +}; + +void dw_pcie_ep_exit(struct dw_pcie_ep *ep) +{ + struct pci_epc *epc = ep->epc; + + pci_epc_mem_exit(epc); +} + +int dw_pcie_ep_init(struct dw_pcie_ep *ep) +{ + int ret; + void *addr; + enum pci_barno bar; + struct pci_epc *epc; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct device *dev = pci->dev; + struct device_node *np = dev->of_node; + + if (!pci->dbi_base || !pci->dbi_base2) { + dev_err(dev, "dbi_base/deb_base2 is not populated\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "num-ib-windows", &ep->num_ib_windows); + if (ret < 0) { + dev_err(dev, "unable to read *num-ib-windows* property\n"); + return ret; + } + + ret = of_property_read_u32(np, "num-ob-windows", &ep->num_ob_windows); + if (ret < 0) { + dev_err(dev, "unable to read *num-ob-windows* property\n"); + return ret; + } + + addr = devm_kzalloc(dev, sizeof(phys_addr_t) * ep->num_ob_windows, + GFP_KERNEL); + if (!addr) + return -ENOMEM; + ep->outbound_addr = addr; + + for (bar = BAR_0; bar <= BAR_5; bar++) + dw_pcie_ep_reset_bar(pci, bar); + + if (ep->ops->ep_init) + ep->ops->ep_init(ep); + + epc = devm_pci_epc_create(dev, &epc_ops); + if (IS_ERR(epc)) { + dev_err(dev, "failed to create epc device\n"); + return PTR_ERR(epc); + } + + ret = of_property_read_u8(np, "max-functions", &epc->max_functions); + if (ret < 0) + epc->max_functions = 1; + + ret = pci_epc_mem_init(epc, ep->phys_base, ep->addr_size); + if (ret < 0) { + dev_err(dev, "Failed to initialize address space\n"); + return ret; + } + + ep->epc = epc; + epc_set_drvdata(epc, ep); + dw_pcie_setup(pci); + + return 0; +} diff --git a/drivers/pci/dwc/pcie-designware-host.c b/drivers/pci/dwc/pcie-designware-host.c index 5ba334938b52..aece2962defc 100644 --- a/drivers/pci/dwc/pcie-designware-host.c +++ b/drivers/pci/dwc/pcie-designware-host.c @@ -56,24 +56,25 @@ static struct irq_chip dw_msi_irq_chip = { /* MSI int handler */ irqreturn_t dw_handle_msi_irq(struct pcie_port *pp) { - unsigned long val; + u32 val; int i, pos, irq; irqreturn_t ret = IRQ_NONE; for (i = 0; i < MAX_MSI_CTRLS; i++) { dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, - (u32 *)&val); - if (val) { - ret = IRQ_HANDLED; - pos = 0; - while ((pos = find_next_bit(&val, 32, pos)) != 32) { - irq = irq_find_mapping(pp->irq_domain, - i * 32 + pos); - dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + - i * 12, 4, 1 << pos); - generic_handle_irq(irq); - pos++; - } + &val); + if (!val) + continue; + + ret = IRQ_HANDLED; + pos = 0; + while ((pos = find_next_bit((unsigned long *) &val, 32, + pos)) != 32) { + irq = irq_find_mapping(pp->irq_domain, i * 32 + pos); + dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, + 4, 1 << pos); + generic_handle_irq(irq); + pos++; } } diff --git a/drivers/pci/dwc/pcie-designware-plat.c b/drivers/pci/dwc/pcie-designware-plat.c index b6c832ba39dd..f20d494922ab 100644 --- a/drivers/pci/dwc/pcie-designware-plat.c +++ b/drivers/pci/dwc/pcie-designware-plat.c @@ -86,6 +86,9 @@ static int dw_plat_add_pcie_port(struct pcie_port *pp, return 0; } +static const struct dw_pcie_ops dw_pcie_ops = { +}; + static int dw_plat_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -103,6 +106,7 @@ static int dw_plat_pcie_probe(struct platform_device *pdev) return -ENOMEM; pci->dev = dev; + pci->ops = &dw_pcie_ops; dw_plat_pcie->pci = pci; diff --git a/drivers/pci/dwc/pcie-designware.c b/drivers/pci/dwc/pcie-designware.c index 7e1fb7d6643c..0e03af279259 100644 --- a/drivers/pci/dwc/pcie-designware.c +++ b/drivers/pci/dwc/pcie-designware.c @@ -61,91 +61,253 @@ int dw_pcie_write(void __iomem *addr, int size, u32 val) return PCIBIOS_SUCCESSFUL; } -u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg) +u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg, + size_t size) { - if (pci->ops->readl_dbi) - return pci->ops->readl_dbi(pci, reg); + int ret; + u32 val; - return readl(pci->dbi_base + reg); + if (pci->ops->read_dbi) + return pci->ops->read_dbi(pci, base, reg, size); + + ret = dw_pcie_read(base + reg, size, &val); + if (ret) + dev_err(pci->dev, "read DBI address failed\n"); + + return val; } -void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val) +void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg, + size_t size, u32 val) { - if (pci->ops->writel_dbi) - pci->ops->writel_dbi(pci, reg, val); - else - writel(val, pci->dbi_base + reg); + int ret; + + if (pci->ops->write_dbi) { + pci->ops->write_dbi(pci, base, reg, size, val); + return; + } + + ret = dw_pcie_write(base + reg, size, val); + if (ret) + dev_err(pci->dev, "write DBI address failed\n"); } -static u32 dw_pcie_readl_unroll(struct dw_pcie *pci, u32 index, u32 reg) +static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg) { u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); return dw_pcie_readl_dbi(pci, offset + reg); } -static void dw_pcie_writel_unroll(struct dw_pcie *pci, u32 index, u32 reg, - u32 val) +static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg, + u32 val) { u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); dw_pcie_writel_dbi(pci, offset + reg, val); } +void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, int index, int type, + u64 cpu_addr, u64 pci_addr, u32 size) +{ + u32 retries, val; + + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE, + lower_32_bits(cpu_addr)); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE, + upper_32_bits(cpu_addr)); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LIMIT, + lower_32_bits(cpu_addr + size - 1)); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, + lower_32_bits(pci_addr)); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, + upper_32_bits(pci_addr)); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, + type); + dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, + PCIE_ATU_ENABLE); + + /* + * Make sure ATU enable takes effect before any subsequent config + * and I/O accesses. + */ + for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { + val = dw_pcie_readl_ob_unroll(pci, index, + PCIE_ATU_UNR_REGION_CTRL2); + if (val & PCIE_ATU_ENABLE) + return; + + usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX); + } + dev_err(pci->dev, "outbound iATU is not being enabled\n"); +} + void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type, u64 cpu_addr, u64 pci_addr, u32 size) { u32 retries, val; + if (pci->ops->cpu_addr_fixup) + cpu_addr = pci->ops->cpu_addr_fixup(cpu_addr); + if (pci->iatu_unroll_enabled) { - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE, - lower_32_bits(cpu_addr)); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE, - upper_32_bits(cpu_addr)); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LIMIT, - lower_32_bits(cpu_addr + size - 1)); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, - lower_32_bits(pci_addr)); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, - upper_32_bits(pci_addr)); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, - type); - dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, - PCIE_ATU_ENABLE); - } else { - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, - PCIE_ATU_REGION_OUTBOUND | index); - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE, - lower_32_bits(cpu_addr)); - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE, - upper_32_bits(cpu_addr)); - dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT, - lower_32_bits(cpu_addr + size - 1)); - dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, - lower_32_bits(pci_addr)); - dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, - upper_32_bits(pci_addr)); - dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type); - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE); + dw_pcie_prog_outbound_atu_unroll(pci, index, type, cpu_addr, + pci_addr, size); + return; } + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, + PCIE_ATU_REGION_OUTBOUND | index); + dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE, + lower_32_bits(cpu_addr)); + dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE, + upper_32_bits(cpu_addr)); + dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT, + lower_32_bits(cpu_addr + size - 1)); + dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, + lower_32_bits(pci_addr)); + dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, + upper_32_bits(pci_addr)); + dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type); + dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE); + /* * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { - if (pci->iatu_unroll_enabled) - val = dw_pcie_readl_unroll(pci, index, - PCIE_ATU_UNR_REGION_CTRL2); - else - val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2); - + val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2); if (val == PCIE_ATU_ENABLE) return; usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX); } - dev_err(pci->dev, "iATU is not being enabled\n"); + dev_err(pci->dev, "outbound iATU is not being enabled\n"); +} + +static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg) +{ + u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index); + + return dw_pcie_readl_dbi(pci, offset + reg); +} + +static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg, + u32 val) +{ + u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index); + + dw_pcie_writel_dbi(pci, offset + reg, val); +} + +int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, int index, int bar, + u64 cpu_addr, enum dw_pcie_as_type as_type) +{ + int type; + u32 retries, val; + + dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, + lower_32_bits(cpu_addr)); + dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, + upper_32_bits(cpu_addr)); + + switch (as_type) { + case DW_PCIE_AS_MEM: + type = PCIE_ATU_TYPE_MEM; + break; + case DW_PCIE_AS_IO: + type = PCIE_ATU_TYPE_IO; + break; + default: + return -EINVAL; + } + + dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type); + dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, + PCIE_ATU_ENABLE | + PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); + + /* + * Make sure ATU enable takes effect before any subsequent config + * and I/O accesses. + */ + for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { + val = dw_pcie_readl_ib_unroll(pci, index, + PCIE_ATU_UNR_REGION_CTRL2); + if (val & PCIE_ATU_ENABLE) + return 0; + + usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX); + } + dev_err(pci->dev, "inbound iATU is not being enabled\n"); + + return -EBUSY; +} + +int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar, + u64 cpu_addr, enum dw_pcie_as_type as_type) +{ + int type; + u32 retries, val; + + if (pci->iatu_unroll_enabled) + return dw_pcie_prog_inbound_atu_unroll(pci, index, bar, + cpu_addr, as_type); + + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND | + index); + dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr)); + dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr)); + + switch (as_type) { + case DW_PCIE_AS_MEM: + type = PCIE_ATU_TYPE_MEM; + break; + case DW_PCIE_AS_IO: + type = PCIE_ATU_TYPE_IO; + break; + default: + return -EINVAL; + } + + dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type); + dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE + | PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); + + /* + * Make sure ATU enable takes effect before any subsequent config + * and I/O accesses. + */ + for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { + val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2); + if (val & PCIE_ATU_ENABLE) + return 0; + + usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX); + } + dev_err(pci->dev, "inbound iATU is not being enabled\n"); + + return -EBUSY; +} + +void dw_pcie_disable_atu(struct dw_pcie *pci, int index, + enum dw_pcie_region_type type) +{ + int region; + + switch (type) { + case DW_PCIE_REGION_INBOUND: + region = PCIE_ATU_REGION_INBOUND; + break; + case DW_PCIE_REGION_OUTBOUND: + region = PCIE_ATU_REGION_OUTBOUND; + break; + default: + return; + } + + dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index); + dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~PCIE_ATU_ENABLE); } int dw_pcie_wait_for_link(struct dw_pcie *pci) diff --git a/drivers/pci/dwc/pcie-designware.h b/drivers/pci/dwc/pcie-designware.h index cd3b8713fe50..c6a840575796 100644 --- a/drivers/pci/dwc/pcie-designware.h +++ b/drivers/pci/dwc/pcie-designware.h @@ -18,6 +18,9 @@ #include <linux/msi.h> #include <linux/pci.h> +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> + /* Parameters for the waiting for link up routine */ #define LINK_WAIT_MAX_RETRIES 10 #define LINK_WAIT_USLEEP_MIN 90000 @@ -89,6 +92,16 @@ #define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \ ((0x3 << 20) | ((region) << 9)) +#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \ + ((0x3 << 20) | ((region) << 9) | (0x1 << 8)) + +#define MSI_MESSAGE_CONTROL 0x52 +#define MSI_CAP_MMC_SHIFT 1 +#define MSI_CAP_MME_SHIFT 4 +#define MSI_CAP_MME_MASK (7 << MSI_CAP_MME_SHIFT) +#define MSI_MESSAGE_ADDR_L32 0x54 +#define MSI_MESSAGE_ADDR_U32 0x58 + /* * Maximum number of MSI IRQs can be 256 per controller. But keep * it 32 as of now. Probably we will never need more than 32. If needed, @@ -99,6 +112,20 @@ struct pcie_port; struct dw_pcie; +struct dw_pcie_ep; + +enum dw_pcie_region_type { + DW_PCIE_REGION_UNKNOWN, + DW_PCIE_REGION_INBOUND, + DW_PCIE_REGION_OUTBOUND, +}; + +enum dw_pcie_device_mode { + DW_PCIE_UNKNOWN_TYPE, + DW_PCIE_EP_TYPE, + DW_PCIE_LEG_EP_TYPE, + DW_PCIE_RC_TYPE, +}; struct dw_pcie_host_ops { int (*rd_own_conf)(struct pcie_port *pp, int where, int size, u32 *val); @@ -142,35 +169,116 @@ struct pcie_port { DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS); }; +enum dw_pcie_as_type { + DW_PCIE_AS_UNKNOWN, + DW_PCIE_AS_MEM, + DW_PCIE_AS_IO, +}; + +struct dw_pcie_ep_ops { + void (*ep_init)(struct dw_pcie_ep *ep); + int (*raise_irq)(struct dw_pcie_ep *ep, enum pci_epc_irq_type type, + u8 interrupt_num); +}; + +struct dw_pcie_ep { + struct pci_epc *epc; + struct dw_pcie_ep_ops *ops; + phys_addr_t phys_base; + size_t addr_size; + u8 bar_to_atu[6]; + phys_addr_t *outbound_addr; + unsigned long ib_window_map; + unsigned long ob_window_map; + u32 num_ib_windows; + u32 num_ob_windows; +}; + struct dw_pcie_ops { - u32 (*readl_dbi)(struct dw_pcie *pcie, u32 reg); - void (*writel_dbi)(struct dw_pcie *pcie, u32 reg, u32 val); + u64 (*cpu_addr_fixup)(u64 cpu_addr); + u32 (*read_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg, + size_t size); + void (*write_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg, + size_t size, u32 val); int (*link_up)(struct dw_pcie *pcie); + int (*start_link)(struct dw_pcie *pcie); + void (*stop_link)(struct dw_pcie *pcie); }; struct dw_pcie { struct device *dev; void __iomem *dbi_base; + void __iomem *dbi_base2; u32 num_viewport; u8 iatu_unroll_enabled; struct pcie_port pp; + struct dw_pcie_ep ep; const struct dw_pcie_ops *ops; }; #define to_dw_pcie_from_pp(port) container_of((port), struct dw_pcie, pp) +#define to_dw_pcie_from_ep(endpoint) \ + container_of((endpoint), struct dw_pcie, ep) + int dw_pcie_read(void __iomem *addr, int size, u32 *val); int dw_pcie_write(void __iomem *addr, int size, u32 val); -u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg); -void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val); +u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg, + size_t size); +void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg, + size_t size, u32 val); int dw_pcie_link_up(struct dw_pcie *pci); int dw_pcie_wait_for_link(struct dw_pcie *pci); void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type, u64 cpu_addr, u64 pci_addr, u32 size); +int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar, + u64 cpu_addr, enum dw_pcie_as_type as_type); +void dw_pcie_disable_atu(struct dw_pcie *pci, int index, + enum dw_pcie_region_type type); void dw_pcie_setup(struct dw_pcie *pci); +static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val) +{ + __dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x4, val); +} + +static inline u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg) +{ + return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x4); +} + +static inline void dw_pcie_writew_dbi(struct dw_pcie *pci, u32 reg, u16 val) +{ + __dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x2, val); +} + +static inline u16 dw_pcie_readw_dbi(struct dw_pcie *pci, u32 reg) +{ + return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x2); +} + +static inline void dw_pcie_writeb_dbi(struct dw_pcie *pci, u32 reg, u8 val) +{ + __dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x1, val); +} + +static inline u8 dw_pcie_readb_dbi(struct dw_pcie *pci, u32 reg) +{ + return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x1); +} + +static inline void dw_pcie_writel_dbi2(struct dw_pcie *pci, u32 reg, u32 val) +{ + __dw_pcie_write_dbi(pci, pci->dbi_base2, reg, 0x4, val); +} + +static inline u32 dw_pcie_readl_dbi2(struct dw_pcie *pci, u32 reg) +{ + return __dw_pcie_read_dbi(pci, pci->dbi_base2, reg, 0x4); +} + #ifdef CONFIG_PCIE_DW_HOST irqreturn_t dw_handle_msi_irq(struct pcie_port *pp); void dw_pcie_msi_init(struct pcie_port *pp); @@ -195,4 +303,23 @@ static inline int dw_pcie_host_init(struct pcie_port *pp) return 0; } #endif + +#ifdef CONFIG_PCIE_DW_EP +void dw_pcie_ep_linkup(struct dw_pcie_ep *ep); +int dw_pcie_ep_init(struct dw_pcie_ep *ep); +void dw_pcie_ep_exit(struct dw_pcie_ep *ep); +#else +static inline void dw_pcie_ep_linkup(struct dw_pcie_ep *ep) +{ +} + +static inline int dw_pcie_ep_init(struct dw_pcie_ep *ep) +{ + return 0; +} + +static inline void dw_pcie_ep_exit(struct dw_pcie_ep *ep) +{ +} +#endif #endif /* _PCIE_DESIGNWARE_H */ diff --git a/drivers/pci/endpoint/Kconfig b/drivers/pci/endpoint/Kconfig new file mode 100644 index 000000000000..c23f146fb5a6 --- /dev/null +++ b/drivers/pci/endpoint/Kconfig @@ -0,0 +1,31 @@ +# +# PCI Endpoint Support +# + +menu "PCI Endpoint" + +config PCI_ENDPOINT + bool "PCI Endpoint Support" + help + Enable this configuration option to support configurable PCI + endpoint. This should be enabled if the platform has a PCI + controller that can operate in endpoint mode. + + Enabling this option will build the endpoint library, which + includes endpoint controller library and endpoint function + library. + + If in doubt, say "N" to disable Endpoint support. + +config PCI_ENDPOINT_CONFIGFS + bool "PCI Endpoint Configfs Support" + depends on PCI_ENDPOINT + select CONFIGFS_FS + help + This will enable the configfs entry that can be used to + configure the endpoint function and used to bind the + function with a endpoint controller. + +source "drivers/pci/endpoint/functions/Kconfig" + +endmenu diff --git a/drivers/pci/endpoint/Makefile b/drivers/pci/endpoint/Makefile new file mode 100644 index 000000000000..1041f80a4645 --- /dev/null +++ b/drivers/pci/endpoint/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for PCI Endpoint Support +# + +obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o +obj-$(CONFIG_PCI_ENDPOINT) += pci-epc-core.o pci-epf-core.o\ + pci-epc-mem.o functions/ diff --git a/drivers/pci/endpoint/functions/Kconfig b/drivers/pci/endpoint/functions/Kconfig new file mode 100644 index 000000000000..175edad42d2f --- /dev/null +++ b/drivers/pci/endpoint/functions/Kconfig @@ -0,0 +1,12 @@ +# +# PCI Endpoint Functions +# + +config PCI_EPF_TEST + tristate "PCI Endpoint Test driver" + depends on PCI_ENDPOINT + help + Enable this configuration option to enable the test driver + for PCI Endpoint. + + If in doubt, say "N" to disable Endpoint test driver. diff --git a/drivers/pci/endpoint/functions/Makefile b/drivers/pci/endpoint/functions/Makefile new file mode 100644 index 000000000000..6d94a4801838 --- /dev/null +++ b/drivers/pci/endpoint/functions/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for PCI Endpoint Functions +# + +obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c new file mode 100644 index 000000000000..53fff8030337 --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -0,0 +1,510 @@ +/** + * Test driver to test endpoint functionality + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> +#include <linux/random.h> + +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> +#include <linux/pci_regs.h> + +#define COMMAND_RAISE_LEGACY_IRQ BIT(0) +#define COMMAND_RAISE_MSI_IRQ BIT(1) +#define MSI_NUMBER_SHIFT 2 +#define MSI_NUMBER_MASK (0x3f << MSI_NUMBER_SHIFT) +#define COMMAND_READ BIT(8) +#define COMMAND_WRITE BIT(9) +#define COMMAND_COPY BIT(10) + +#define STATUS_READ_SUCCESS BIT(0) +#define STATUS_READ_FAIL BIT(1) +#define STATUS_WRITE_SUCCESS BIT(2) +#define STATUS_WRITE_FAIL BIT(3) +#define STATUS_COPY_SUCCESS BIT(4) +#define STATUS_COPY_FAIL BIT(5) +#define STATUS_IRQ_RAISED BIT(6) +#define STATUS_SRC_ADDR_INVALID BIT(7) +#define STATUS_DST_ADDR_INVALID BIT(8) + +#define TIMER_RESOLUTION 1 + +static struct workqueue_struct *kpcitest_workqueue; + +struct pci_epf_test { + void *reg[6]; + struct pci_epf *epf; + struct delayed_work cmd_handler; +}; + +struct pci_epf_test_reg { + u32 magic; + u32 command; + u32 status; + u64 src_addr; + u64 dst_addr; + u32 size; + u32 checksum; +} __packed; + +static struct pci_epf_header test_header = { + .vendorid = PCI_ANY_ID, + .deviceid = PCI_ANY_ID, + .baseclass_code = PCI_CLASS_OTHERS, + .interrupt_pin = PCI_INTERRUPT_INTA, +}; + +static int bar_size[] = { 512, 1024, 16384, 131072, 1048576 }; + +static int pci_epf_test_copy(struct pci_epf_test *epf_test) +{ + int ret; + void __iomem *src_addr; + void __iomem *dst_addr; + phys_addr_t src_phys_addr; + phys_addr_t dst_phys_addr; + struct pci_epf *epf = epf_test->epf; + struct device *dev = &epf->dev; + struct pci_epc *epc = epf->epc; + struct pci_epf_test_reg *reg = epf_test->reg[0]; + + src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size); + if (!src_addr) { + dev_err(dev, "failed to allocate source address\n"); + reg->status = STATUS_SRC_ADDR_INVALID; + ret = -ENOMEM; + goto err; + } + + ret = pci_epc_map_addr(epc, src_phys_addr, reg->src_addr, reg->size); + if (ret) { + dev_err(dev, "failed to map source address\n"); + reg->status = STATUS_SRC_ADDR_INVALID; + goto err_src_addr; + } + + dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size); + if (!dst_addr) { + dev_err(dev, "failed to allocate destination address\n"); + reg->status = STATUS_DST_ADDR_INVALID; + ret = -ENOMEM; + goto err_src_map_addr; + } + + ret = pci_epc_map_addr(epc, dst_phys_addr, reg->dst_addr, reg->size); + if (ret) { + dev_err(dev, "failed to map destination address\n"); + reg->status = STATUS_DST_ADDR_INVALID; + goto err_dst_addr; + } + + memcpy(dst_addr, src_addr, reg->size); + + pci_epc_unmap_addr(epc, dst_phys_addr); + +err_dst_addr: + pci_epc_mem_free_addr(epc, dst_phys_addr, dst_addr, reg->size); + +err_src_map_addr: + pci_epc_unmap_addr(epc, src_phys_addr); + +err_src_addr: + pci_epc_mem_free_addr(epc, src_phys_addr, src_addr, reg->size); + +err: + return ret; +} + +static int pci_epf_test_read(struct pci_epf_test *epf_test) +{ + int ret; + void __iomem *src_addr; + void *buf; + u32 crc32; + phys_addr_t phys_addr; + struct pci_epf *epf = epf_test->epf; + struct device *dev = &epf->dev; + struct pci_epc *epc = epf->epc; + struct pci_epf_test_reg *reg = epf_test->reg[0]; + + src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size); + if (!src_addr) { + dev_err(dev, "failed to allocate address\n"); + reg->status = STATUS_SRC_ADDR_INVALID; + ret = -ENOMEM; + goto err; + } + + ret = pci_epc_map_addr(epc, phys_addr, reg->src_addr, reg->size); + if (ret) { + dev_err(dev, "failed to map address\n"); + reg->status = STATUS_SRC_ADDR_INVALID; + goto err_addr; + } + + buf = kzalloc(reg->size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_map_addr; + } + + memcpy(buf, src_addr, reg->size); + + crc32 = crc32_le(~0, buf, reg->size); + if (crc32 != reg->checksum) + ret = -EIO; + + kfree(buf); + +err_map_addr: + pci_epc_unmap_addr(epc, phys_addr); + +err_addr: + pci_epc_mem_free_addr(epc, phys_addr, src_addr, reg->size); + +err: + return ret; +} + +static int pci_epf_test_write(struct pci_epf_test *epf_test) +{ + int ret; + void __iomem *dst_addr; + void *buf; + phys_addr_t phys_addr; + struct pci_epf *epf = epf_test->epf; + struct device *dev = &epf->dev; + struct pci_epc *epc = epf->epc; + struct pci_epf_test_reg *reg = epf_test->reg[0]; + + dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size); + if (!dst_addr) { + dev_err(dev, "failed to allocate address\n"); + reg->status = STATUS_DST_ADDR_INVALID; + ret = -ENOMEM; + goto err; + } + + ret = pci_epc_map_addr(epc, phys_addr, reg->dst_addr, reg->size); + if (ret) { + dev_err(dev, "failed to map address\n"); + reg->status = STATUS_DST_ADDR_INVALID; + goto err_addr; + } + + buf = kzalloc(reg->size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_map_addr; + } + + get_random_bytes(buf, reg->size); + reg->checksum = crc32_le(~0, buf, reg->size); + + memcpy(dst_addr, buf, reg->size); + + /* + * wait 1ms inorder for the write to complete. Without this delay L3 + * error in observed in the host system. + */ + mdelay(1); + + kfree(buf); + +err_map_addr: + pci_epc_unmap_addr(epc, phys_addr); + +err_addr: + pci_epc_mem_free_addr(epc, phys_addr, dst_addr, reg->size); + +err: + return ret; +} + +static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test) +{ + u8 irq; + u8 msi_count; + struct pci_epf *epf = epf_test->epf; + struct pci_epc *epc = epf->epc; + struct pci_epf_test_reg *reg = epf_test->reg[0]; + + reg->status |= STATUS_IRQ_RAISED; + msi_count = pci_epc_get_msi(epc); + irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT; + if (irq > msi_count || msi_count <= 0) + pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0); + else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq); +} + +static void pci_epf_test_cmd_handler(struct work_struct *work) +{ + int ret; + u8 irq; + u8 msi_count; + struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test, + cmd_handler.work); + struct pci_epf *epf = epf_test->epf; + struct pci_epc *epc = epf->epc; + struct pci_epf_test_reg *reg = epf_test->reg[0]; + + if (!reg->command) + goto reset_handler; + + if (reg->command & COMMAND_RAISE_LEGACY_IRQ) { + reg->status = STATUS_IRQ_RAISED; + pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0); + goto reset_handler; + } + + if (reg->command & COMMAND_WRITE) { + ret = pci_epf_test_write(epf_test); + if (ret) + reg->status |= STATUS_WRITE_FAIL; + else + reg->status |= STATUS_WRITE_SUCCESS; + pci_epf_test_raise_irq(epf_test); + goto reset_handler; + } + + if (reg->command & COMMAND_READ) { + ret = pci_epf_test_read(epf_test); + if (!ret) + reg->status |= STATUS_READ_SUCCESS; + else + reg->status |= STATUS_READ_FAIL; + pci_epf_test_raise_irq(epf_test); + goto reset_handler; + } + + if (reg->command & COMMAND_COPY) { + ret = pci_epf_test_copy(epf_test); + if (!ret) + reg->status |= STATUS_COPY_SUCCESS; + else + reg->status |= STATUS_COPY_FAIL; + pci_epf_test_raise_irq(epf_test); + goto reset_handler; + } + + if (reg->command & COMMAND_RAISE_MSI_IRQ) { + msi_count = pci_epc_get_msi(epc); + irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT; + if (irq > msi_count || msi_count <= 0) + goto reset_handler; + reg->status = STATUS_IRQ_RAISED; + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq); + goto reset_handler; + } + +reset_handler: + reg->command = 0; + + queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, + msecs_to_jiffies(1)); +} + +static void pci_epf_test_linkup(struct pci_epf *epf) +{ + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + + queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, + msecs_to_jiffies(1)); +} + +static void pci_epf_test_unbind(struct pci_epf *epf) +{ + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + int bar; + + cancel_delayed_work(&epf_test->cmd_handler); + pci_epc_stop(epc); + for (bar = BAR_0; bar <= BAR_5; bar++) { + if (epf_test->reg[bar]) { + pci_epf_free_space(epf, epf_test->reg[bar], bar); + pci_epc_clear_bar(epc, bar); + } + } +} + +static int pci_epf_test_set_bar(struct pci_epf *epf) +{ + int flags; + int bar; + int ret; + struct pci_epf_bar *epf_bar; + struct pci_epc *epc = epf->epc; + struct device *dev = &epf->dev; + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + + flags = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_32; + if (sizeof(dma_addr_t) == 0x8) + flags |= PCI_BASE_ADDRESS_MEM_TYPE_64; + + for (bar = BAR_0; bar <= BAR_5; bar++) { + epf_bar = &epf->bar[bar]; + ret = pci_epc_set_bar(epc, bar, epf_bar->phys_addr, + epf_bar->size, flags); + if (ret) { + pci_epf_free_space(epf, epf_test->reg[bar], bar); + dev_err(dev, "failed to set BAR%d\n", bar); + if (bar == BAR_0) + return ret; + } + } + + return 0; +} + +static int pci_epf_test_alloc_space(struct pci_epf *epf) +{ + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + struct device *dev = &epf->dev; + void *base; + int bar; + + base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg), + BAR_0); + if (!base) { + dev_err(dev, "failed to allocated register space\n"); + return -ENOMEM; + } + epf_test->reg[0] = base; + + for (bar = BAR_1; bar <= BAR_5; bar++) { + base = pci_epf_alloc_space(epf, bar_size[bar - 1], bar); + if (!base) + dev_err(dev, "failed to allocate space for BAR%d\n", + bar); + epf_test->reg[bar] = base; + } + + return 0; +} + +static int pci_epf_test_bind(struct pci_epf *epf) +{ + int ret; + struct pci_epf_header *header = epf->header; + struct pci_epc *epc = epf->epc; + struct device *dev = &epf->dev; + + if (WARN_ON_ONCE(!epc)) + return -EINVAL; + + ret = pci_epc_write_header(epc, header); + if (ret) { + dev_err(dev, "configuration header write failed\n"); + return ret; + } + + ret = pci_epf_test_alloc_space(epf); + if (ret) + return ret; + + ret = pci_epf_test_set_bar(epf); + if (ret) + return ret; + + ret = pci_epc_set_msi(epc, epf->msi_interrupts); + if (ret) + return ret; + + return 0; +} + +static int pci_epf_test_probe(struct pci_epf *epf) +{ + struct pci_epf_test *epf_test; + struct device *dev = &epf->dev; + + epf_test = devm_kzalloc(dev, sizeof(*epf_test), GFP_KERNEL); + if (!epf_test) + return -ENOMEM; + + epf->header = &test_header; + epf_test->epf = epf; + + INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler); + + epf_set_drvdata(epf, epf_test); + return 0; +} + +static int pci_epf_test_remove(struct pci_epf *epf) +{ + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + + kfree(epf_test); + return 0; +} + +static struct pci_epf_ops ops = { + .unbind = pci_epf_test_unbind, + .bind = pci_epf_test_bind, + .linkup = pci_epf_test_linkup, +}; + +static const struct pci_epf_device_id pci_epf_test_ids[] = { + { + .name = "pci_epf_test", + }, + {}, +}; + +static struct pci_epf_driver test_driver = { + .driver.name = "pci_epf_test", + .probe = pci_epf_test_probe, + .remove = pci_epf_test_remove, + .id_table = pci_epf_test_ids, + .ops = &ops, + .owner = THIS_MODULE, +}; + +static int __init pci_epf_test_init(void) +{ + int ret; + + kpcitest_workqueue = alloc_workqueue("kpcitest", + WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); + ret = pci_epf_register_driver(&test_driver); + if (ret) { + pr_err("failed to register pci epf test driver --> %d\n", ret); + return ret; + } + + return 0; +} +module_init(pci_epf_test_init); + +static void __exit pci_epf_test_exit(void) +{ + pci_epf_unregister_driver(&test_driver); +} +module_exit(pci_epf_test_exit); + +MODULE_DESCRIPTION("PCI EPF TEST DRIVER"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-ep-cfs.c b/drivers/pci/endpoint/pci-ep-cfs.c new file mode 100644 index 000000000000..424fdd6ed1ca --- /dev/null +++ b/drivers/pci/endpoint/pci-ep-cfs.c @@ -0,0 +1,509 @@ +/** + * configfs to configure the PCI endpoint + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> +#include <linux/pci-ep-cfs.h> + +static struct config_group *functions_group; +static struct config_group *controllers_group; + +struct pci_epf_group { + struct config_group group; + struct pci_epf *epf; +}; + +struct pci_epc_group { + struct config_group group; + struct pci_epc *epc; + bool start; + unsigned long function_num_map; +}; + +static inline struct pci_epf_group *to_pci_epf_group(struct config_item *item) +{ + return container_of(to_config_group(item), struct pci_epf_group, group); +} + +static inline struct pci_epc_group *to_pci_epc_group(struct config_item *item) +{ + return container_of(to_config_group(item), struct pci_epc_group, group); +} + +static ssize_t pci_epc_start_store(struct config_item *item, const char *page, + size_t len) +{ + int ret; + bool start; + struct pci_epc *epc; + struct pci_epc_group *epc_group = to_pci_epc_group(item); + + epc = epc_group->epc; + + ret = kstrtobool(page, &start); + if (ret) + return ret; + + if (!start) { + pci_epc_stop(epc); + return len; + } + + ret = pci_epc_start(epc); + if (ret) { + dev_err(&epc->dev, "failed to start endpoint controller\n"); + return -EINVAL; + } + + epc_group->start = start; + + return len; +} + +static ssize_t pci_epc_start_show(struct config_item *item, char *page) +{ + return sprintf(page, "%d\n", + to_pci_epc_group(item)->start); +} + +CONFIGFS_ATTR(pci_epc_, start); + +static struct configfs_attribute *pci_epc_attrs[] = { + &pci_epc_attr_start, + NULL, +}; + +static int pci_epc_epf_link(struct config_item *epc_item, + struct config_item *epf_item) +{ + int ret; + u32 func_no = 0; + struct pci_epc *epc; + struct pci_epf *epf; + struct pci_epf_group *epf_group = to_pci_epf_group(epf_item); + struct pci_epc_group *epc_group = to_pci_epc_group(epc_item); + + epc = epc_group->epc; + epf = epf_group->epf; + ret = pci_epc_add_epf(epc, epf); + if (ret) + goto err_add_epf; + + func_no = find_first_zero_bit(&epc_group->function_num_map, + sizeof(epc_group->function_num_map)); + set_bit(func_no, &epc_group->function_num_map); + epf->func_no = func_no; + + ret = pci_epf_bind(epf); + if (ret) + goto err_epf_bind; + + return 0; + +err_epf_bind: + pci_epc_remove_epf(epc, epf); + +err_add_epf: + clear_bit(func_no, &epc_group->function_num_map); + + return ret; +} + +static void pci_epc_epf_unlink(struct config_item *epc_item, + struct config_item *epf_item) +{ + struct pci_epc *epc; + struct pci_epf *epf; + struct pci_epf_group *epf_group = to_pci_epf_group(epf_item); + struct pci_epc_group *epc_group = to_pci_epc_group(epc_item); + + WARN_ON_ONCE(epc_group->start); + + epc = epc_group->epc; + epf = epf_group->epf; + clear_bit(epf->func_no, &epc_group->function_num_map); + pci_epf_unbind(epf); + pci_epc_remove_epf(epc, epf); +} + +static struct configfs_item_operations pci_epc_item_ops = { + .allow_link = pci_epc_epf_link, + .drop_link = pci_epc_epf_unlink, +}; + +static struct config_item_type pci_epc_type = { + .ct_item_ops = &pci_epc_item_ops, + .ct_attrs = pci_epc_attrs, + .ct_owner = THIS_MODULE, +}; + +struct config_group *pci_ep_cfs_add_epc_group(const char *name) +{ + int ret; + struct pci_epc *epc; + struct config_group *group; + struct pci_epc_group *epc_group; + + epc_group = kzalloc(sizeof(*epc_group), GFP_KERNEL); + if (!epc_group) { + ret = -ENOMEM; + goto err; + } + + group = &epc_group->group; + + config_group_init_type_name(group, name, &pci_epc_type); + ret = configfs_register_group(controllers_group, group); + if (ret) { + pr_err("failed to register configfs group for %s\n", name); + goto err_register_group; + } + + epc = pci_epc_get(name); + if (IS_ERR(epc)) { + ret = PTR_ERR(epc); + goto err_epc_get; + } + + epc_group->epc = epc; + + return group; + +err_epc_get: + configfs_unregister_group(group); + +err_register_group: + kfree(epc_group); + +err: + return ERR_PTR(ret); +} +EXPORT_SYMBOL(pci_ep_cfs_add_epc_group); + +void pci_ep_cfs_remove_epc_group(struct config_group *group) +{ + struct pci_epc_group *epc_group; + + if (!group) + return; + + epc_group = container_of(group, struct pci_epc_group, group); + pci_epc_put(epc_group->epc); + configfs_unregister_group(&epc_group->group); + kfree(epc_group); +} +EXPORT_SYMBOL(pci_ep_cfs_remove_epc_group); + +#define PCI_EPF_HEADER_R(_name) \ +static ssize_t pci_epf_##_name##_show(struct config_item *item, char *page) \ +{ \ + struct pci_epf *epf = to_pci_epf_group(item)->epf; \ + if (WARN_ON_ONCE(!epf->header)) \ + return -EINVAL; \ + return sprintf(page, "0x%04x\n", epf->header->_name); \ +} + +#define PCI_EPF_HEADER_W_u32(_name) \ +static ssize_t pci_epf_##_name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + u32 val; \ + int ret; \ + struct pci_epf *epf = to_pci_epf_group(item)->epf; \ + if (WARN_ON_ONCE(!epf->header)) \ + return -EINVAL; \ + ret = kstrtou32(page, 0, &val); \ + if (ret) \ + return ret; \ + epf->header->_name = val; \ + return len; \ +} + +#define PCI_EPF_HEADER_W_u16(_name) \ +static ssize_t pci_epf_##_name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + u16 val; \ + int ret; \ + struct pci_epf *epf = to_pci_epf_group(item)->epf; \ + if (WARN_ON_ONCE(!epf->header)) \ + return -EINVAL; \ + ret = kstrtou16(page, 0, &val); \ + if (ret) \ + return ret; \ + epf->header->_name = val; \ + return len; \ +} + +#define PCI_EPF_HEADER_W_u8(_name) \ +static ssize_t pci_epf_##_name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + u8 val; \ + int ret; \ + struct pci_epf *epf = to_pci_epf_group(item)->epf; \ + if (WARN_ON_ONCE(!epf->header)) \ + return -EINVAL; \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + return ret; \ + epf->header->_name = val; \ + return len; \ +} + +static ssize_t pci_epf_msi_interrupts_store(struct config_item *item, + const char *page, size_t len) +{ + u8 val; + int ret; + + ret = kstrtou8(page, 0, &val); + if (ret) + return ret; + + to_pci_epf_group(item)->epf->msi_interrupts = val; + + return len; +} + +static ssize_t pci_epf_msi_interrupts_show(struct config_item *item, + char *page) +{ + return sprintf(page, "%d\n", + to_pci_epf_group(item)->epf->msi_interrupts); +} + +PCI_EPF_HEADER_R(vendorid) +PCI_EPF_HEADER_W_u16(vendorid) + +PCI_EPF_HEADER_R(deviceid) +PCI_EPF_HEADER_W_u16(deviceid) + +PCI_EPF_HEADER_R(revid) +PCI_EPF_HEADER_W_u8(revid) + +PCI_EPF_HEADER_R(progif_code) +PCI_EPF_HEADER_W_u8(progif_code) + +PCI_EPF_HEADER_R(subclass_code) +PCI_EPF_HEADER_W_u8(subclass_code) + +PCI_EPF_HEADER_R(baseclass_code) +PCI_EPF_HEADER_W_u8(baseclass_code) + +PCI_EPF_HEADER_R(cache_line_size) +PCI_EPF_HEADER_W_u8(cache_line_size) + +PCI_EPF_HEADER_R(subsys_vendor_id) +PCI_EPF_HEADER_W_u16(subsys_vendor_id) + +PCI_EPF_HEADER_R(subsys_id) +PCI_EPF_HEADER_W_u16(subsys_id) + +PCI_EPF_HEADER_R(interrupt_pin) +PCI_EPF_HEADER_W_u8(interrupt_pin) + +CONFIGFS_ATTR(pci_epf_, vendorid); +CONFIGFS_ATTR(pci_epf_, deviceid); +CONFIGFS_ATTR(pci_epf_, revid); +CONFIGFS_ATTR(pci_epf_, progif_code); +CONFIGFS_ATTR(pci_epf_, subclass_code); +CONFIGFS_ATTR(pci_epf_, baseclass_code); +CONFIGFS_ATTR(pci_epf_, cache_line_size); +CONFIGFS_ATTR(pci_epf_, subsys_vendor_id); +CONFIGFS_ATTR(pci_epf_, subsys_id); +CONFIGFS_ATTR(pci_epf_, interrupt_pin); +CONFIGFS_ATTR(pci_epf_, msi_interrupts); + +static struct configfs_attribute *pci_epf_attrs[] = { + &pci_epf_attr_vendorid, + &pci_epf_attr_deviceid, + &pci_epf_attr_revid, + &pci_epf_attr_progif_code, + &pci_epf_attr_subclass_code, + &pci_epf_attr_baseclass_code, + &pci_epf_attr_cache_line_size, + &pci_epf_attr_subsys_vendor_id, + &pci_epf_attr_subsys_id, + &pci_epf_attr_interrupt_pin, + &pci_epf_attr_msi_interrupts, + NULL, +}; + +static void pci_epf_release(struct config_item *item) +{ + struct pci_epf_group *epf_group = to_pci_epf_group(item); + + pci_epf_destroy(epf_group->epf); + kfree(epf_group); +} + +static struct configfs_item_operations pci_epf_ops = { + .release = pci_epf_release, +}; + +static struct config_item_type pci_epf_type = { + .ct_item_ops = &pci_epf_ops, + .ct_attrs = pci_epf_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *pci_epf_make(struct config_group *group, + const char *name) +{ + struct pci_epf_group *epf_group; + struct pci_epf *epf; + + epf_group = kzalloc(sizeof(*epf_group), GFP_KERNEL); + if (!epf_group) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&epf_group->group, name, &pci_epf_type); + + epf = pci_epf_create(group->cg_item.ci_name); + if (IS_ERR(epf)) { + pr_err("failed to create endpoint function device\n"); + return ERR_PTR(-EINVAL); + } + + epf_group->epf = epf; + + return &epf_group->group; +} + +static void pci_epf_drop(struct config_group *group, struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations pci_epf_group_ops = { + .make_group = &pci_epf_make, + .drop_item = &pci_epf_drop, +}; + +static struct config_item_type pci_epf_group_type = { + .ct_group_ops = &pci_epf_group_ops, + .ct_owner = THIS_MODULE, +}; + +struct config_group *pci_ep_cfs_add_epf_group(const char *name) +{ + struct config_group *group; + + group = configfs_register_default_group(functions_group, name, + &pci_epf_group_type); + if (IS_ERR(group)) + pr_err("failed to register configfs group for %s function\n", + name); + + return group; +} +EXPORT_SYMBOL(pci_ep_cfs_add_epf_group); + +void pci_ep_cfs_remove_epf_group(struct config_group *group) +{ + if (IS_ERR_OR_NULL(group)) + return; + + configfs_unregister_default_group(group); +} +EXPORT_SYMBOL(pci_ep_cfs_remove_epf_group); + +static struct config_item_type pci_functions_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type pci_controllers_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type pci_ep_type = { + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem pci_ep_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "pci_ep", + .ci_type = &pci_ep_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(pci_ep_cfs_subsys.su_mutex), +}; + +static int __init pci_ep_cfs_init(void) +{ + int ret; + struct config_group *root = &pci_ep_cfs_subsys.su_group; + + config_group_init(root); + + ret = configfs_register_subsystem(&pci_ep_cfs_subsys); + if (ret) { + pr_err("Error %d while registering subsystem %s\n", + ret, root->cg_item.ci_namebuf); + goto err; + } + + functions_group = configfs_register_default_group(root, "functions", + &pci_functions_type); + if (IS_ERR(functions_group)) { + ret = PTR_ERR(functions_group); + pr_err("Error %d while registering functions group\n", + ret); + goto err_functions_group; + } + + controllers_group = + configfs_register_default_group(root, "controllers", + &pci_controllers_type); + if (IS_ERR(controllers_group)) { + ret = PTR_ERR(controllers_group); + pr_err("Error %d while registering controllers group\n", + ret); + goto err_controllers_group; + } + + return 0; + +err_controllers_group: + configfs_unregister_default_group(functions_group); + +err_functions_group: + configfs_unregister_subsystem(&pci_ep_cfs_subsys); + +err: + return ret; +} +module_init(pci_ep_cfs_init); + +static void __exit pci_ep_cfs_exit(void) +{ + configfs_unregister_default_group(controllers_group); + configfs_unregister_default_group(functions_group); + configfs_unregister_subsystem(&pci_ep_cfs_subsys); +} +module_exit(pci_ep_cfs_exit); + +MODULE_DESCRIPTION("PCI EP CONFIGFS"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c new file mode 100644 index 000000000000..caa7be10e473 --- /dev/null +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -0,0 +1,580 @@ +/** + * PCI Endpoint *Controller* (EPC) library + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> +#include <linux/pci-ep-cfs.h> + +static struct class *pci_epc_class; + +static void devm_pci_epc_release(struct device *dev, void *res) +{ + struct pci_epc *epc = *(struct pci_epc **)res; + + pci_epc_destroy(epc); +} + +static int devm_pci_epc_match(struct device *dev, void *res, void *match_data) +{ + struct pci_epc **epc = res; + + return *epc == match_data; +} + +/** + * pci_epc_put() - release the PCI endpoint controller + * @epc: epc returned by pci_epc_get() + * + * release the refcount the caller obtained by invoking pci_epc_get() + */ +void pci_epc_put(struct pci_epc *epc) +{ + if (!epc || IS_ERR(epc)) + return; + + module_put(epc->ops->owner); + put_device(&epc->dev); +} +EXPORT_SYMBOL_GPL(pci_epc_put); + +/** + * pci_epc_get() - get the PCI endpoint controller + * @epc_name: device name of the endpoint controller + * + * Invoke to get struct pci_epc * corresponding to the device name of the + * endpoint controller + */ +struct pci_epc *pci_epc_get(const char *epc_name) +{ + int ret = -EINVAL; + struct pci_epc *epc; + struct device *dev; + struct class_dev_iter iter; + + class_dev_iter_init(&iter, pci_epc_class, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) { + if (strcmp(epc_name, dev_name(dev))) + continue; + + epc = to_pci_epc(dev); + if (!try_module_get(epc->ops->owner)) { + ret = -EINVAL; + goto err; + } + + class_dev_iter_exit(&iter); + get_device(&epc->dev); + return epc; + } + +err: + class_dev_iter_exit(&iter); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(pci_epc_get); + +/** + * pci_epc_stop() - stop the PCI link + * @epc: the link of the EPC device that has to be stopped + * + * Invoke to stop the PCI link + */ +void pci_epc_stop(struct pci_epc *epc) +{ + unsigned long flags; + + if (IS_ERR(epc) || !epc->ops->stop) + return; + + spin_lock_irqsave(&epc->lock, flags); + epc->ops->stop(epc); + spin_unlock_irqrestore(&epc->lock, flags); +} +EXPORT_SYMBOL_GPL(pci_epc_stop); + +/** + * pci_epc_start() - start the PCI link + * @epc: the link of *this* EPC device has to be started + * + * Invoke to start the PCI link + */ +int pci_epc_start(struct pci_epc *epc) +{ + int ret; + unsigned long flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->start) + return 0; + + spin_lock_irqsave(&epc->lock, flags); + ret = epc->ops->start(epc); + spin_unlock_irqrestore(&epc->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_start); + +/** + * pci_epc_raise_irq() - interrupt the host system + * @epc: the EPC device which has to interrupt the host + * @type: specify the type of interrupt; legacy or MSI + * @interrupt_num: the MSI interrupt number + * + * Invoke to raise an MSI or legacy interrupt + */ +int pci_epc_raise_irq(struct pci_epc *epc, enum pci_epc_irq_type type, + u8 interrupt_num) +{ + int ret; + unsigned long flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->raise_irq) + return 0; + + spin_lock_irqsave(&epc->lock, flags); + ret = epc->ops->raise_irq(epc, type, interrupt_num); + spin_unlock_irqrestore(&epc->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_raise_irq); + +/** + * pci_epc_get_msi() - get the number of MSI interrupt numbers allocated + * @epc: the EPC device to which MSI interrupts was requested + * + * Invoke to get the number of MSI interrupts allocated by the RC + */ +int pci_epc_get_msi(struct pci_epc *epc) +{ + int interrupt; + unsigned long flags; + + if (IS_ERR(epc)) + return 0; + + if (!epc->ops->get_msi) + return 0; + + spin_lock_irqsave(&epc->lock, flags); + interrupt = epc->ops->get_msi(epc); + spin_unlock_irqrestore(&epc->lock, flags); + + if (interrupt < 0) + return 0; + + interrupt = 1 << interrupt; + + return interrupt; +} +EXPORT_SYMBOL_GPL(pci_epc_get_msi); + +/** + * pci_epc_set_msi() - set the number of MSI interrupt numbers required + * @epc: the EPC device on which MSI has to be configured + * @interrupts: number of MSI interrupts required by the EPF + * + * Invoke to set the required number of MSI interrupts. + */ +int pci_epc_set_msi(struct pci_epc *epc, u8 interrupts) +{ + int ret; + u8 encode_int; + unsigned long flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->set_msi) + return 0; + + encode_int = order_base_2(interrupts); + + spin_lock_irqsave(&epc->lock, flags); + ret = epc->ops->set_msi(epc, encode_int); + spin_unlock_irqrestore(&epc->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_set_msi); + +/** + * pci_epc_unmap_addr() - unmap CPU address from PCI address + * @epc: the EPC device on which address is allocated + * @phys_addr: physical address of the local system + * + * Invoke to unmap the CPU address from PCI address. + */ +void pci_epc_unmap_addr(struct pci_epc *epc, phys_addr_t phys_addr) +{ + unsigned long flags; + + if (IS_ERR(epc)) + return; + + if (!epc->ops->unmap_addr) + return; + + spin_lock_irqsave(&epc->lock, flags); + epc->ops->unmap_addr(epc, phys_addr); + spin_unlock_irqrestore(&epc->lock, flags); +} +EXPORT_SYMBOL_GPL(pci_epc_unmap_addr); + +/** + * pci_epc_map_addr() - map CPU address to PCI address + * @epc: the EPC device on which address is allocated + * @phys_addr: physical address of the local system + * @pci_addr: PCI address to which the physical address should be mapped + * @size: the size of the allocation + * + * Invoke to map CPU address with PCI address. + */ +int pci_epc_map_addr(struct pci_epc *epc, phys_addr_t phys_addr, + u64 pci_addr, size_t size) +{ + int ret; + unsigned long flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->map_addr) + return 0; + + spin_lock_irqsave(&epc->lock, flags); + ret = epc->ops->map_addr(epc, phys_addr, pci_addr, size); + spin_unlock_irqrestore(&epc->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_map_addr); + +/** + * pci_epc_clear_bar() - reset the BAR + * @epc: the EPC device for which the BAR has to be cleared + * @bar: the BAR number that has to be reset + * + * Invoke to reset the BAR of the endpoint device. + */ +void pci_epc_clear_bar(struct pci_epc *epc, int bar) +{ + unsigned long flags; + + if (IS_ERR(epc)) + return; + + if (!epc->ops->clear_bar) + return; + + spin_lock_irqsave(&epc->lock, flags); + epc->ops->clear_bar(epc, bar); + spin_unlock_irqrestore(&epc->lock, flags); +} +EXPORT_SYMBOL_GPL(pci_epc_clear_bar); + +/** + * pci_epc_set_bar() - configure BAR in order for host to assign PCI addr space + * @epc: the EPC device on which BAR has to be configured + * @bar: the BAR number that has to be configured + * @size: the size of the addr space + * @flags: specify memory allocation/io allocation/32bit address/64 bit address + * + * Invoke to configure the BAR of the endpoint device. + */ +int pci_epc_set_bar(struct pci_epc *epc, enum pci_barno bar, + dma_addr_t bar_phys, size_t size, int flags) +{ + int ret; + unsigned long irq_flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->set_bar) + return 0; + + spin_lock_irqsave(&epc->lock, irq_flags); + ret = epc->ops->set_bar(epc, bar, bar_phys, size, flags); + spin_unlock_irqrestore(&epc->lock, irq_flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_set_bar); + +/** + * pci_epc_write_header() - write standard configuration header + * @epc: the EPC device to which the configuration header should be written + * @header: standard configuration header fields + * + * Invoke to write the configuration header to the endpoint controller. Every + * endpoint controller will have a dedicated location to which the standard + * configuration header would be written. The callback function should write + * the header fields to this dedicated location. + */ +int pci_epc_write_header(struct pci_epc *epc, struct pci_epf_header *header) +{ + int ret; + unsigned long flags; + + if (IS_ERR(epc)) + return -EINVAL; + + if (!epc->ops->write_header) + return 0; + + spin_lock_irqsave(&epc->lock, flags); + ret = epc->ops->write_header(epc, header); + spin_unlock_irqrestore(&epc->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_write_header); + +/** + * pci_epc_add_epf() - bind PCI endpoint function to an endpoint controller + * @epc: the EPC device to which the endpoint function should be added + * @epf: the endpoint function to be added + * + * A PCI endpoint device can have one or more functions. In the case of PCIe, + * the specification allows up to 8 PCIe endpoint functions. Invoke + * pci_epc_add_epf() to add a PCI endpoint function to an endpoint controller. + */ +int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf) +{ + unsigned long flags; + + if (epf->epc) + return -EBUSY; + + if (IS_ERR(epc)) + return -EINVAL; + + if (epf->func_no > epc->max_functions - 1) + return -EINVAL; + + epf->epc = epc; + dma_set_coherent_mask(&epf->dev, epc->dev.coherent_dma_mask); + epf->dev.dma_mask = epc->dev.dma_mask; + + spin_lock_irqsave(&epc->lock, flags); + list_add_tail(&epf->list, &epc->pci_epf); + spin_unlock_irqrestore(&epc->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pci_epc_add_epf); + +/** + * pci_epc_remove_epf() - remove PCI endpoint function from endpoint controller + * @epc: the EPC device from which the endpoint function should be removed + * @epf: the endpoint function to be removed + * + * Invoke to remove PCI endpoint function from the endpoint controller. + */ +void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf) +{ + unsigned long flags; + + if (!epc || IS_ERR(epc)) + return; + + spin_lock_irqsave(&epc->lock, flags); + list_del(&epf->list); + spin_unlock_irqrestore(&epc->lock, flags); +} +EXPORT_SYMBOL_GPL(pci_epc_remove_epf); + +/** + * pci_epc_linkup() - Notify the EPF device that EPC device has established a + * connection with the Root Complex. + * @epc: the EPC device which has established link with the host + * + * Invoke to Notify the EPF device that the EPC device has established a + * connection with the Root Complex. + */ +void pci_epc_linkup(struct pci_epc *epc) +{ + unsigned long flags; + struct pci_epf *epf; + + if (!epc || IS_ERR(epc)) + return; + + spin_lock_irqsave(&epc->lock, flags); + list_for_each_entry(epf, &epc->pci_epf, list) + pci_epf_linkup(epf); + spin_unlock_irqrestore(&epc->lock, flags); +} +EXPORT_SYMBOL_GPL(pci_epc_linkup); + +/** + * pci_epc_destroy() - destroy the EPC device + * @epc: the EPC device that has to be destroyed + * + * Invoke to destroy the PCI EPC device + */ +void pci_epc_destroy(struct pci_epc *epc) +{ + pci_ep_cfs_remove_epc_group(epc->group); + device_unregister(&epc->dev); + kfree(epc); +} +EXPORT_SYMBOL_GPL(pci_epc_destroy); + +/** + * devm_pci_epc_destroy() - destroy the EPC device + * @dev: device that wants to destroy the EPC + * @epc: the EPC device that has to be destroyed + * + * Invoke to destroy the devres associated with this + * pci_epc and destroy the EPC device. + */ +void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc) +{ + int r; + + r = devres_destroy(dev, devm_pci_epc_release, devm_pci_epc_match, + epc); + dev_WARN_ONCE(dev, r, "couldn't find PCI EPC resource\n"); +} +EXPORT_SYMBOL_GPL(devm_pci_epc_destroy); + +/** + * __pci_epc_create() - create a new endpoint controller (EPC) device + * @dev: device that is creating the new EPC + * @ops: function pointers for performing EPC operations + * @owner: the owner of the module that creates the EPC device + * + * Invoke to create a new EPC device and add it to pci_epc class. + */ +struct pci_epc * +__pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, + struct module *owner) +{ + int ret; + struct pci_epc *epc; + + if (WARN_ON(!dev)) { + ret = -EINVAL; + goto err_ret; + } + + epc = kzalloc(sizeof(*epc), GFP_KERNEL); + if (!epc) { + ret = -ENOMEM; + goto err_ret; + } + + spin_lock_init(&epc->lock); + INIT_LIST_HEAD(&epc->pci_epf); + + device_initialize(&epc->dev); + dma_set_coherent_mask(&epc->dev, dev->coherent_dma_mask); + epc->dev.class = pci_epc_class; + epc->dev.dma_mask = dev->dma_mask; + epc->ops = ops; + + ret = dev_set_name(&epc->dev, "%s", dev_name(dev)); + if (ret) + goto put_dev; + + ret = device_add(&epc->dev); + if (ret) + goto put_dev; + + epc->group = pci_ep_cfs_add_epc_group(dev_name(dev)); + + return epc; + +put_dev: + put_device(&epc->dev); + kfree(epc); + +err_ret: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(__pci_epc_create); + +/** + * __devm_pci_epc_create() - create a new endpoint controller (EPC) device + * @dev: device that is creating the new EPC + * @ops: function pointers for performing EPC operations + * @owner: the owner of the module that creates the EPC device + * + * Invoke to create a new EPC device and add it to pci_epc class. + * While at that, it also associates the device with the pci_epc using devres. + * On driver detach, release function is invoked on the devres data, + * then, devres data is freed. + */ +struct pci_epc * +__devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, + struct module *owner) +{ + struct pci_epc **ptr, *epc; + + ptr = devres_alloc(devm_pci_epc_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + epc = __pci_epc_create(dev, ops, owner); + if (!IS_ERR(epc)) { + *ptr = epc; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return epc; +} +EXPORT_SYMBOL_GPL(__devm_pci_epc_create); + +static int __init pci_epc_init(void) +{ + pci_epc_class = class_create(THIS_MODULE, "pci_epc"); + if (IS_ERR(pci_epc_class)) { + pr_err("failed to create pci epc class --> %ld\n", + PTR_ERR(pci_epc_class)); + return PTR_ERR(pci_epc_class); + } + + return 0; +} +module_init(pci_epc_init); + +static void __exit pci_epc_exit(void) +{ + class_destroy(pci_epc_class); +} +module_exit(pci_epc_exit); + +MODULE_DESCRIPTION("PCI EPC Library"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epc-mem.c b/drivers/pci/endpoint/pci-epc-mem.c new file mode 100644 index 000000000000..3a94cc1caf22 --- /dev/null +++ b/drivers/pci/endpoint/pci-epc-mem.c @@ -0,0 +1,143 @@ +/** + * PCI Endpoint *Controller* Address Space Management + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/pci-epc.h> + +/** + * pci_epc_mem_init() - initialize the pci_epc_mem structure + * @epc: the EPC device that invoked pci_epc_mem_init + * @phys_base: the physical address of the base + * @size: the size of the address space + * + * Invoke to initialize the pci_epc_mem structure used by the + * endpoint functions to allocate mapped PCI address. + */ +int pci_epc_mem_init(struct pci_epc *epc, phys_addr_t phys_base, size_t size) +{ + int ret; + struct pci_epc_mem *mem; + unsigned long *bitmap; + int pages = size >> PAGE_SHIFT; + int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long); + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) { + ret = -ENOMEM; + goto err; + } + + bitmap = kzalloc(bitmap_size, GFP_KERNEL); + if (!bitmap) { + ret = -ENOMEM; + goto err_mem; + } + + mem->bitmap = bitmap; + mem->phys_base = phys_base; + mem->pages = pages; + mem->size = size; + + epc->mem = mem; + + return 0; + +err_mem: + kfree(mem); + +err: +return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_mem_init); + +/** + * pci_epc_mem_exit() - cleanup the pci_epc_mem structure + * @epc: the EPC device that invoked pci_epc_mem_exit + * + * Invoke to cleanup the pci_epc_mem structure allocated in + * pci_epc_mem_init(). + */ +void pci_epc_mem_exit(struct pci_epc *epc) +{ + struct pci_epc_mem *mem = epc->mem; + + epc->mem = NULL; + kfree(mem->bitmap); + kfree(mem); +} +EXPORT_SYMBOL_GPL(pci_epc_mem_exit); + +/** + * pci_epc_mem_alloc_addr() - allocate memory address from EPC addr space + * @epc: the EPC device on which memory has to be allocated + * @phys_addr: populate the allocated physical address here + * @size: the size of the address space that has to be allocated + * + * Invoke to allocate memory address from the EPC address space. This + * is usually done to map the remote RC address into the local system. + */ +void __iomem *pci_epc_mem_alloc_addr(struct pci_epc *epc, + phys_addr_t *phys_addr, size_t size) +{ + int pageno; + void __iomem *virt_addr; + struct pci_epc_mem *mem = epc->mem; + int order = get_order(size); + + pageno = bitmap_find_free_region(mem->bitmap, mem->pages, order); + if (pageno < 0) + return NULL; + + *phys_addr = mem->phys_base + (pageno << PAGE_SHIFT); + virt_addr = ioremap(*phys_addr, size); + if (!virt_addr) + bitmap_release_region(mem->bitmap, pageno, order); + + return virt_addr; +} +EXPORT_SYMBOL_GPL(pci_epc_mem_alloc_addr); + +/** + * pci_epc_mem_free_addr() - free the allocated memory address + * @epc: the EPC device on which memory was allocated + * @phys_addr: the allocated physical address + * @virt_addr: virtual address of the allocated mem space + * @size: the size of the allocated address space + * + * Invoke to free the memory allocated using pci_epc_mem_alloc_addr. + */ +void pci_epc_mem_free_addr(struct pci_epc *epc, phys_addr_t phys_addr, + void __iomem *virt_addr, size_t size) +{ + int pageno; + int order = get_order(size); + struct pci_epc_mem *mem = epc->mem; + + iounmap(virt_addr); + pageno = (phys_addr - mem->phys_base) >> PAGE_SHIFT; + bitmap_release_region(mem->bitmap, pageno, order); +} +EXPORT_SYMBOL_GPL(pci_epc_mem_free_addr); + +MODULE_DESCRIPTION("PCI EPC Address Space Management"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c new file mode 100644 index 000000000000..6877d6a5bcc9 --- /dev/null +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -0,0 +1,359 @@ +/** + * PCI Endpoint *Function* (EPF) library + * + * Copyright (C) 2017 Texas Instruments + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/pci-epc.h> +#include <linux/pci-epf.h> +#include <linux/pci-ep-cfs.h> + +static struct bus_type pci_epf_bus_type; +static struct device_type pci_epf_type; + +/** + * pci_epf_linkup() - Notify the function driver that EPC device has + * established a connection with the Root Complex. + * @epf: the EPF device bound to the EPC device which has established + * the connection with the host + * + * Invoke to notify the function driver that EPC device has established + * a connection with the Root Complex. + */ +void pci_epf_linkup(struct pci_epf *epf) +{ + if (!epf->driver) { + dev_WARN(&epf->dev, "epf device not bound to driver\n"); + return; + } + + epf->driver->ops->linkup(epf); +} +EXPORT_SYMBOL_GPL(pci_epf_linkup); + +/** + * pci_epf_unbind() - Notify the function driver that the binding between the + * EPF device and EPC device has been lost + * @epf: the EPF device which has lost the binding with the EPC device + * + * Invoke to notify the function driver that the binding between the EPF device + * and EPC device has been lost. + */ +void pci_epf_unbind(struct pci_epf *epf) +{ + if (!epf->driver) { + dev_WARN(&epf->dev, "epf device not bound to driver\n"); + return; + } + + epf->driver->ops->unbind(epf); + module_put(epf->driver->owner); +} +EXPORT_SYMBOL_GPL(pci_epf_unbind); + +/** + * pci_epf_bind() - Notify the function driver that the EPF device has been + * bound to a EPC device + * @epf: the EPF device which has been bound to the EPC device + * + * Invoke to notify the function driver that it has been bound to a EPC device + */ +int pci_epf_bind(struct pci_epf *epf) +{ + if (!epf->driver) { + dev_WARN(&epf->dev, "epf device not bound to driver\n"); + return -EINVAL; + } + + if (!try_module_get(epf->driver->owner)) + return -EAGAIN; + + return epf->driver->ops->bind(epf); +} +EXPORT_SYMBOL_GPL(pci_epf_bind); + +/** + * pci_epf_free_space() - free the allocated PCI EPF register space + * @addr: the virtual address of the PCI EPF register space + * @bar: the BAR number corresponding to the register space + * + * Invoke to free the allocated PCI EPF register space. + */ +void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar) +{ + struct device *dev = &epf->dev; + + if (!addr) + return; + + dma_free_coherent(dev, epf->bar[bar].size, addr, + epf->bar[bar].phys_addr); + + epf->bar[bar].phys_addr = 0; + epf->bar[bar].size = 0; +} +EXPORT_SYMBOL_GPL(pci_epf_free_space); + +/** + * pci_epf_alloc_space() - allocate memory for the PCI EPF register space + * @size: the size of the memory that has to be allocated + * @bar: the BAR number corresponding to the allocated register space + * + * Invoke to allocate memory for the PCI EPF register space. + */ +void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar) +{ + void *space; + struct device *dev = &epf->dev; + dma_addr_t phys_addr; + + if (size < 128) + size = 128; + size = roundup_pow_of_two(size); + + space = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL); + if (!space) { + dev_err(dev, "failed to allocate mem space\n"); + return NULL; + } + + epf->bar[bar].phys_addr = phys_addr; + epf->bar[bar].size = size; + + return space; +} +EXPORT_SYMBOL_GPL(pci_epf_alloc_space); + +/** + * pci_epf_unregister_driver() - unregister the PCI EPF driver + * @driver: the PCI EPF driver that has to be unregistered + * + * Invoke to unregister the PCI EPF driver. + */ +void pci_epf_unregister_driver(struct pci_epf_driver *driver) +{ + pci_ep_cfs_remove_epf_group(driver->group); + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(pci_epf_unregister_driver); + +/** + * __pci_epf_register_driver() - register a new PCI EPF driver + * @driver: structure representing PCI EPF driver + * @owner: the owner of the module that registers the PCI EPF driver + * + * Invoke to register a new PCI EPF driver. + */ +int __pci_epf_register_driver(struct pci_epf_driver *driver, + struct module *owner) +{ + int ret; + + if (!driver->ops) + return -EINVAL; + + if (!driver->ops->bind || !driver->ops->unbind || !driver->ops->linkup) + return -EINVAL; + + driver->driver.bus = &pci_epf_bus_type; + driver->driver.owner = owner; + + ret = driver_register(&driver->driver); + if (ret) + return ret; + + driver->group = pci_ep_cfs_add_epf_group(driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(__pci_epf_register_driver); + +/** + * pci_epf_destroy() - destroy the created PCI EPF device + * @epf: the PCI EPF device that has to be destroyed. + * + * Invoke to destroy the PCI EPF device created by invoking pci_epf_create(). + */ +void pci_epf_destroy(struct pci_epf *epf) +{ + device_unregister(&epf->dev); +} +EXPORT_SYMBOL_GPL(pci_epf_destroy); + +/** + * pci_epf_create() - create a new PCI EPF device + * @name: the name of the PCI EPF device. This name will be used to bind the + * the EPF device to a EPF driver + * + * Invoke to create a new PCI EPF device by providing the name of the function + * device. + */ +struct pci_epf *pci_epf_create(const char *name) +{ + int ret; + struct pci_epf *epf; + struct device *dev; + char *func_name; + char *buf; + + epf = kzalloc(sizeof(*epf), GFP_KERNEL); + if (!epf) { + ret = -ENOMEM; + goto err_ret; + } + + buf = kstrdup(name, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto free_epf; + } + + func_name = buf; + buf = strchrnul(buf, '.'); + *buf = '\0'; + + epf->name = kstrdup(func_name, GFP_KERNEL); + if (!epf->name) { + ret = -ENOMEM; + goto free_func_name; + } + + dev = &epf->dev; + device_initialize(dev); + dev->bus = &pci_epf_bus_type; + dev->type = &pci_epf_type; + + ret = dev_set_name(dev, "%s", name); + if (ret) + goto put_dev; + + ret = device_add(dev); + if (ret) + goto put_dev; + + kfree(func_name); + return epf; + +put_dev: + put_device(dev); + kfree(epf->name); + +free_func_name: + kfree(func_name); + +free_epf: + kfree(epf); + +err_ret: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(pci_epf_create); + +static void pci_epf_dev_release(struct device *dev) +{ + struct pci_epf *epf = to_pci_epf(dev); + + kfree(epf->name); + kfree(epf); +} + +static struct device_type pci_epf_type = { + .release = pci_epf_dev_release, +}; + +static int +pci_epf_match_id(const struct pci_epf_device_id *id, const struct pci_epf *epf) +{ + while (id->name[0]) { + if (strcmp(epf->name, id->name) == 0) + return true; + id++; + } + + return false; +} + +static int pci_epf_device_match(struct device *dev, struct device_driver *drv) +{ + struct pci_epf *epf = to_pci_epf(dev); + struct pci_epf_driver *driver = to_pci_epf_driver(drv); + + if (driver->id_table) + return pci_epf_match_id(driver->id_table, epf); + + return !strcmp(epf->name, drv->name); +} + +static int pci_epf_device_probe(struct device *dev) +{ + struct pci_epf *epf = to_pci_epf(dev); + struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver); + + if (!driver->probe) + return -ENODEV; + + epf->driver = driver; + + return driver->probe(epf); +} + +static int pci_epf_device_remove(struct device *dev) +{ + int ret; + struct pci_epf *epf = to_pci_epf(dev); + struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver); + + ret = driver->remove(epf); + epf->driver = NULL; + + return ret; +} + +static struct bus_type pci_epf_bus_type = { + .name = "pci-epf", + .match = pci_epf_device_match, + .probe = pci_epf_device_probe, + .remove = pci_epf_device_remove, +}; + +static int __init pci_epf_init(void) +{ + int ret; + + ret = bus_register(&pci_epf_bus_type); + if (ret) { + pr_err("failed to register pci epf bus --> %d\n", ret); + return ret; + } + + return 0; +} +module_init(pci_epf_init); + +static void __exit pci_epf_exit(void) +{ + bus_unregister(&pci_epf_bus_type); +} +module_exit(pci_epf_exit); + +MODULE_DESCRIPTION("PCI EPF Library"); +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/host/pci-thunder-pem.c index 52b5bdccf5f0..6e031b522529 100644 --- a/drivers/pci/host/pci-thunder-pem.c +++ b/drivers/pci/host/pci-thunder-pem.c @@ -14,6 +14,7 @@ * Copyright (C) 2015 - 2016 Cavium, Inc. */ +#include <linux/bitfield.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/of_address.h> @@ -334,6 +335,49 @@ static int thunder_pem_init(struct device *dev, struct pci_config_window *cfg, #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) +#define PEM_RES_BASE 0x87e0c0000000UL +#define PEM_NODE_MASK GENMASK(45, 44) +#define PEM_INDX_MASK GENMASK(26, 24) +#define PEM_MIN_DOM_IN_NODE 4 +#define PEM_MAX_DOM_IN_NODE 10 + +static void thunder_pem_reserve_range(struct device *dev, int seg, + struct resource *r) +{ + resource_size_t start = r->start, end = r->end; + struct resource *res; + const char *regionid; + + regionid = kasprintf(GFP_KERNEL, "PEM RC:%d", seg); + if (!regionid) + return; + + res = request_mem_region(start, end - start + 1, regionid); + if (res) + res->flags &= ~IORESOURCE_BUSY; + else + kfree(regionid); + + dev_info(dev, "%pR %s reserved\n", r, + res ? "has been" : "could not be"); +} + +static void thunder_pem_legacy_fw(struct acpi_pci_root *root, + struct resource *res_pem) +{ + int node = acpi_get_node(root->device->handle); + int index; + + if (node == NUMA_NO_NODE) + node = 0; + + index = root->segment - PEM_MIN_DOM_IN_NODE; + index -= node * PEM_MAX_DOM_IN_NODE; + res_pem->start = PEM_RES_BASE | FIELD_PREP(PEM_NODE_MASK, node) | + FIELD_PREP(PEM_INDX_MASK, index); + res_pem->flags = IORESOURCE_MEM; +} + static int thunder_pem_acpi_init(struct pci_config_window *cfg) { struct device *dev = cfg->parent; @@ -346,10 +390,24 @@ static int thunder_pem_acpi_init(struct pci_config_window *cfg) if (!res_pem) return -ENOMEM; - ret = acpi_get_rc_resources(dev, "THRX0002", root->segment, res_pem); + ret = acpi_get_rc_resources(dev, "CAVA02B", root->segment, res_pem); + + /* + * If we fail to gather resources it means that we run with old + * FW where we need to calculate PEM-specific resources manually. + */ if (ret) { - dev_err(dev, "can't get rc base address\n"); - return ret; + thunder_pem_legacy_fw(root, res_pem); + /* + * Reserve 64K size PEM specific resources. The full 16M range + * size is required for thunder_pem_init() call. + */ + res_pem->end = res_pem->start + SZ_64K - 1; + thunder_pem_reserve_range(dev, root->segment, res_pem); + res_pem->end = res_pem->start + SZ_16M - 1; + + /* Reserve PCI configuration space as well. */ + thunder_pem_reserve_range(dev, root->segment, &cfg->res); } return thunder_pem_init(dev, cfg, res_pem); diff --git a/drivers/pci/host/pcie-iproc-bcma.c b/drivers/pci/host/pcie-iproc-bcma.c index bd4c9ec25edc..384c27e664fe 100644 --- a/drivers/pci/host/pcie-iproc-bcma.c +++ b/drivers/pci/host/pcie-iproc-bcma.c @@ -44,8 +44,7 @@ static int iproc_pcie_bcma_probe(struct bcma_device *bdev) { struct device *dev = &bdev->dev; struct iproc_pcie *pcie; - LIST_HEAD(res); - struct resource res_mem; + LIST_HEAD(resources); int ret; pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); @@ -63,22 +62,23 @@ static int iproc_pcie_bcma_probe(struct bcma_device *bdev) pcie->base_addr = bdev->addr; - res_mem.start = bdev->addr_s[0]; - res_mem.end = bdev->addr_s[0] + SZ_128M - 1; - res_mem.name = "PCIe MEM space"; - res_mem.flags = IORESOURCE_MEM; - pci_add_resource(&res, &res_mem); + pcie->mem.start = bdev->addr_s[0]; + pcie->mem.end = bdev->addr_s[0] + SZ_128M - 1; + pcie->mem.name = "PCIe MEM space"; + pcie->mem.flags = IORESOURCE_MEM; + pci_add_resource(&resources, &pcie->mem); pcie->map_irq = iproc_pcie_bcma_map_irq; - ret = iproc_pcie_setup(pcie, &res); - if (ret) + ret = iproc_pcie_setup(pcie, &resources); + if (ret) { dev_err(dev, "PCIe controller setup failed\n"); - - pci_free_resource_list(&res); + pci_free_resource_list(&resources); + return ret; + } bcma_set_drvdata(bdev, pcie); - return ret; + return 0; } static void iproc_pcie_bcma_remove(struct bcma_device *bdev) diff --git a/drivers/pci/host/pcie-iproc-platform.c b/drivers/pci/host/pcie-iproc-platform.c index f4909bb0b2ad..8c6a327ca6cd 100644 --- a/drivers/pci/host/pcie-iproc-platform.c +++ b/drivers/pci/host/pcie-iproc-platform.c @@ -51,7 +51,7 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev) struct device_node *np = dev->of_node; struct resource reg; resource_size_t iobase = 0; - LIST_HEAD(res); + LIST_HEAD(resources); int ret; pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); @@ -96,10 +96,10 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev) pcie->phy = NULL; } - ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &res, &iobase); + ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &resources, + &iobase); if (ret) { - dev_err(dev, - "unable to get PCI host bridge resources\n"); + dev_err(dev, "unable to get PCI host bridge resources\n"); return ret; } @@ -112,14 +112,15 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev) pcie->map_irq = of_irq_parse_and_map_pci; } - ret = iproc_pcie_setup(pcie, &res); - if (ret) + ret = iproc_pcie_setup(pcie, &resources); + if (ret) { dev_err(dev, "PCIe controller setup failed\n"); - - pci_free_resource_list(&res); + pci_free_resource_list(&resources); + return ret; + } platform_set_drvdata(pdev, pcie); - return ret; + return 0; } static int iproc_pcie_pltfm_remove(struct platform_device *pdev) diff --git a/drivers/pci/host/pcie-iproc.h b/drivers/pci/host/pcie-iproc.h index 04fed8e907f1..0bbe2ea44f3e 100644 --- a/drivers/pci/host/pcie-iproc.h +++ b/drivers/pci/host/pcie-iproc.h @@ -90,6 +90,7 @@ struct iproc_pcie { #ifdef CONFIG_ARM struct pci_sys_data sysdata; #endif + struct resource mem; struct pci_bus *root_bus; struct phy *phy; int (*map_irq)(const struct pci_dev *, u8, u8); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 973472c23d89..1dfa10cc566b 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -478,7 +478,7 @@ static void aspm_calc_l1ss_info(struct pcie_link_state *link, static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) { - struct pci_dev *child, *parent = link->pdev; + struct pci_dev *child = link->downstream, *parent = link->pdev; struct pci_bus *linkbus = parent->subordinate; struct aspm_register_info upreg, dwreg; @@ -491,9 +491,7 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) /* Get upstream/downstream components' register state */ pcie_get_aspm_reg(parent, &upreg); - child = pci_function_0(linkbus); pcie_get_aspm_reg(child, &dwreg); - link->downstream = child; /* * If ASPM not supported, don't mess with the clocks and link, @@ -800,6 +798,7 @@ static struct pcie_link_state *alloc_pcie_link_state(struct pci_dev *pdev) INIT_LIST_HEAD(&link->children); INIT_LIST_HEAD(&link->link); link->pdev = pdev; + link->downstream = pci_function_0(pdev->subordinate); /* * Root Ports and PCI/PCI-X to PCIe Bridges are roots of PCIe diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index f754453fe754..673683660b5c 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2174,6 +2174,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005d, quirk_blacklist_vpd); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005f, quirk_blacklist_vpd); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATTANSIC, PCI_ANY_ID, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_QLOGIC, 0x2261, quirk_blacklist_vpd); /* * For Broadcom 5706, 5708, 5709 rev. A nics, any read beyond the |