Message ID | 1403719007-9352-4-git-send-email-kishon@ti.com (mailing list archive) |
---|---|
State | New, archived |
Delegated to: | Bjorn Helgaas |
Headers | show |
Hi Arnd, Tony, other dt guys, On Wednesday 25 June 2014 11:26 PM, Kishon Vijay Abraham I wrote: > Added support for pcie controller in dra7xx. This driver re-uses > the designware core code that is already present in kernel. > Are you okay with this patch? an you give your Acked-by? Thanks Kishon > Cc: Jason Gunthorpe <jgunthorpe@obsidianresearch.com> > Cc: Bjorn Helgaas <bhelgaas@google.com> > Cc: Mohit Kumar <mohit.kumar@st.com> > Cc: Jingoo Han <jg1.han@samsung.com> > Cc: Marek Vasut <marex@denx.de> > Cc: Arnd Bergmann <arnd@arndb.de> > Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com> > --- > Documentation/devicetree/bindings/pci/ti-pci.txt | 59 +++ > drivers/pci/host/Kconfig | 10 + > drivers/pci/host/Makefile | 1 + > drivers/pci/host/pci-dra7xx.c | 458 ++++++++++++++++++++++ > 4 files changed, 528 insertions(+) > create mode 100644 Documentation/devicetree/bindings/pci/ti-pci.txt > create mode 100644 drivers/pci/host/pci-dra7xx.c > > diff --git a/Documentation/devicetree/bindings/pci/ti-pci.txt b/Documentation/devicetree/bindings/pci/ti-pci.txt > new file mode 100644 > index 0000000..3d21791 > --- /dev/null > +++ b/Documentation/devicetree/bindings/pci/ti-pci.txt > @@ -0,0 +1,59 @@ > +TI PCI Controllers > + > +PCIe Designware Controller > + - compatible: Should be "ti,dra7-pcie"" > + - reg : Two register ranges as listed in the reg-names property > + - reg-names : The first entry must be "ti-conf" for the TI specific registers > + The second entry must be "rc-dbics" for the designware pcie > + registers > + The third entry must be "config" for the PCIe configuration space > + - phys : list of PHY specifiers (used by generic PHY framework) > + - phy-names : must be "pcie-phy0", "pcie-phy1", "pcie-phyN".. based on the > + number of PHYs as specified in *phys* property. > + - ti,hwmods : Name of the hwmod associated to the pcie, "pcie<X>", > + where <X> is the instance number of the pcie from the HW spec. > + - interrupts : Two interrupt entries must be specified. The first one is for > + main interrupt line and the second for MSI interrupt line. > + - #address-cells, > + #size-cells, > + #interrupt-cells, > + device_type, > + ranges, > + num-lanes, > + interrupt-map-mask, > + interrupt-map : as specified in ../designware-pcie.txt > + > +Example: > +axi { > + compatible = "simple-bus"; > + #size-cells = <1>; > + #address-cells = <1>; > + ranges = <0x51000000 0x51000000 0x3000 > + 0x0 0x20000000 0x10000000>; > + pcie@51000000 { > + compatible = "ti,dra7-pcie"; > + reg = <0x51000000 0x2000>, <0x51002000 0x14c>, <0x1000 0x2000>; > + reg-names = "rc_dbics", "ti_conf", "config"; > + interrupts = <0 232 0x4>, <0 233 0x4>; > + #address-cells = <3>; > + #size-cells = <2>; > + device_type = "pci"; > + ranges = <0x81000000 0 0 0x03000 0 0x00010000 > + 0x82000000 0 0x20013000 0x13000 0 0xffed000>; > + #interrupt-cells = <1>; > + num-lanes = <1>; > + ti,hwmods = "pcie1"; > + phys = <&pcie1_phy>; > + phy-names = "pcie-phy0"; > + interrupt-map-mask = <0 0 0 7>; > + interrupt-map = <0 0 0 1 &pcie_intc 1>, > + <0 0 0 2 &pcie_intc 2>, > + <0 0 0 3 &pcie_intc 3>, > + <0 0 0 4 &pcie_intc 4>; > + pcie_intc: interrupt-controller { > + interrupt-controller; > + #address-cells = <0>; > + #interrupt-cells = <1>; > + }; > + }; > +}; > diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig > index 21df477..22117b0 100644 > --- a/drivers/pci/host/Kconfig > +++ b/drivers/pci/host/Kconfig > @@ -1,6 +1,16 @@ > menu "PCI host controller drivers" > depends on PCI > > +config PCI_DRA7XX > + bool "TI DRA7xx PCIe controller" > + select PCIE_DW > + depends on OF && HAS_IOMEM && TI_PIPE3 > + help > + Enables support for the PCIE controller present in DRA7xx SoC. There > + are two instances of PCIE controller in DRA7xx. This controller can > + act both as EP and RC. This reuses the same Designware core as used > + by other SoCs. > + > config PCI_MVEBU > bool "Marvell EBU PCIe controller" > depends on ARCH_MVEBU || ARCH_DOVE || ARCH_KIRKWOOD > diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile > index 611ba4b..c42844d 100644 > --- a/drivers/pci/host/Makefile > +++ b/drivers/pci/host/Makefile > @@ -1,4 +1,5 @@ > obj-$(CONFIG_PCIE_DW) += pcie-designware.o > +obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o > obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o > obj-$(CONFIG_PCI_IMX6) += pci-imx6.o > obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o > diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c > new file mode 100644 > index 0000000..52b34fe > --- /dev/null > +++ b/drivers/pci/host/pci-dra7xx.c > @@ -0,0 +1,458 @@ > +/* > + * pcie-dra7xx - PCIe controller driver for TI DRA7xx SoCs > + * > + * Copyright (C) 2013-2014 Texas Instruments Incorporated - http://www.ti.com > + * > + * Authors: 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 as > + * 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/module.h> > +#include <linux/pci.h> > +#include <linux/phy/phy.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/resource.h> > +#include <linux/types.h> > + > +#include "pcie-designware.h" > + > +/* PCIe controller wrapper DRA7XX configuration registers */ > + > +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN 0x0024 > +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN 0x0028 > +#define ERR_SYS BIT(0) > +#define ERR_FATAL BIT(1) > +#define ERR_NONFATAL BIT(2) > +#define ERR_COR BIT(3) > +#define ERR_AXI BIT(4) > +#define ERR_ECRC BIT(5) > +#define PME_TURN_OFF BIT(8) > +#define PME_TO_ACK BIT(9) > +#define PM_PME BIT(10) > +#define LINK_REQ_RST BIT(11) > +#define LINK_UP_EVT BIT(12) > +#define CFG_BME_EVT BIT(13) > +#define CFG_MSE_EVT BIT(14) > +#define INTERRUPTS (ERR_SYS | ERR_FATAL | ERR_NONFATAL | ERR_COR | ERR_AXI | \ > + ERR_ECRC | PME_TURN_OFF | PME_TO_ACK | PM_PME | \ > + LINK_REQ_RST | LINK_UP_EVT | CFG_BME_EVT | CFG_MSE_EVT) > + > +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI 0x0034 > +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI 0x0038 > +#define INTA BIT(0) > +#define INTB BIT(1) > +#define INTC BIT(2) > +#define INTD BIT(3) > +#define MSI BIT(4) > +#define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD) > + > +#define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104 > +#define LTSSM_EN 0x1 > + > +#define PCIECTRL_DRA7XX_CONF_PHY_CS 0x010C > +#define LINK_UP BIT(16) > + > +struct dra7xx_pcie { > + void __iomem *base; > + struct phy **phy; > + int phy_count; > + struct device *dev; > + struct pcie_port pp; > +}; > + > +#define to_dra7xx_pcie(x) container_of((x), struct dra7xx_pcie, pp) > + > +static inline u32 dra7xx_pcie_readl(struct dra7xx_pcie *pcie, u32 offset) > +{ > + return readl(pcie->base + offset); > +} > + > +static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset, > + u32 value) > +{ > + writel(value, pcie->base + offset); > +} > + > +static int dra7xx_pcie_link_up(struct pcie_port *pp) > +{ > + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); > + u32 reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); > + > + return !!(reg & LINK_UP); > +} > + > +static int dra7xx_pcie_establish_link(struct pcie_port *pp) > +{ > + u32 reg; > + unsigned int retries = 1000; > + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); > + > + if (dw_pcie_link_up(pp)) { > + dev_err(pp->dev, "link is already up\n"); > + return 0; > + } > + > + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); > + reg |= LTSSM_EN; > + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); > + > + while (retries--) { > + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); > + if (reg & LINK_UP) > + break; > + usleep_range(10, 20); > + } > + > + if (retries == 0) { > + dev_err(pp->dev, "link is not up\n"); > + return -ETIMEDOUT; > + } > + > + return 0; > +} > + > +static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) > +{ > + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); > + > + 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); > + > + if (IS_ENABLED(CONFIG_PCI_MSI)) > + dra7xx_pcie_writel(dra7xx, > + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, MSI); > + else > + dra7xx_pcie_writel(dra7xx, > + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, > + LEG_EP_INTERRUPTS); > +} > + > +static void dra7xx_pcie_host_init(struct pcie_port *pp) > +{ > + dw_pcie_setup_rc(pp); > + dra7xx_pcie_establish_link(pp); > + if (IS_ENABLED(CONFIG_PCI_MSI)) > + dw_pcie_msi_init(pp); > + dra7xx_pcie_enable_interrupts(pp); > +} > + > +static struct pcie_host_ops dra7xx_pcie_host_ops = { > + .link_up = dra7xx_pcie_link_up, > + .host_init = dra7xx_pcie_host_init, > +}; > + > +static int dra7xx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, > + irq_hw_number_t hwirq) > +{ > + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); > + irq_set_chip_data(irq, domain->host_data); > + set_irq_flags(irq, IRQF_VALID); > + > + return 0; > +} > + > +static const struct irq_domain_ops intx_domain_ops = { > + .map = dra7xx_pcie_intx_map, > +}; > + > +static int dra7xx_pcie_init_irq_domain(struct pcie_port *pp) > +{ > + struct device *dev = pp->dev; > + struct device_node *node = dev->of_node; > + struct device_node *pcie_intc_node = of_get_next_child(node, NULL); > + > + if (!pcie_intc_node) { > + dev_err(dev, "No PCIe Intc node found\n"); > + return PTR_ERR(pcie_intc_node); > + } > + > + pp->irq_domain = irq_domain_add_linear(pcie_intc_node, 4, > + &intx_domain_ops, pp); > + if (!pp->irq_domain) { > + dev_err(dev, "Failed to get a INTx IRQ domain\n"); > + return PTR_ERR(pp->irq_domain); > + } > + > + return 0; > +} > + > +static irqreturn_t dra7xx_pcie_msi_irq_handler(int irq, void *arg) > +{ > + struct pcie_port *pp = arg; > + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); > + u32 reg; > + > + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI); > + > + switch (reg) { > + case MSI: > + dw_handle_msi_irq(pp); > + break; > + case INTA: > + case INTB: > + case INTC: > + case INTD: > + generic_handle_irq(irq_find_mapping(pp->irq_domain, ffs(reg))); > + break; > + } > + > + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, reg); > + > + return IRQ_HANDLED; > +} > + > + > +static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) > +{ > + struct dra7xx_pcie *dra7xx = arg; > + u32 reg; > + > + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN); > + > + if (reg & ERR_SYS) > + dev_dbg(dra7xx->dev, "System Error\n"); > + > + if (reg & ERR_FATAL) > + dev_dbg(dra7xx->dev, "Fatal Error\n"); > + > + if (reg & ERR_NONFATAL) > + dev_dbg(dra7xx->dev, "Non Fatal Error\n"); > + > + if (reg & ERR_COR) > + dev_dbg(dra7xx->dev, "Correctable Error\n"); > + > + if (reg & ERR_AXI) > + dev_dbg(dra7xx->dev, "AXI tag lookup fatal Error\n"); > + > + if (reg & ERR_ECRC) > + dev_dbg(dra7xx->dev, "ECRC Error\n"); > + > + if (reg & PME_TURN_OFF) > + dev_dbg(dra7xx->dev, > + "Power Management Event Turn-Off message received\n"); > + > + if (reg & PME_TO_ACK) > + dev_dbg(dra7xx->dev, > + "Power Management Turn-Off Ack message received\n"); > + > + if (reg & PM_PME) > + dev_dbg(dra7xx->dev, > + "PM Power Management Event message received\n"); > + > + if (reg & LINK_REQ_RST) > + dev_dbg(dra7xx->dev, "Link Request Reset\n"); > + > + if (reg & LINK_UP_EVT) > + dev_dbg(dra7xx->dev, "Link-up state change\n"); > + > + if (reg & CFG_BME_EVT) > + dev_dbg(dra7xx->dev, "CFG 'Bus Master Enable' change\n"); > + > + if (reg & CFG_MSE_EVT) > + dev_dbg(dra7xx->dev, "CFG 'Memory Space Enable' change\n"); > + > + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, reg); > + > + return IRQ_HANDLED; > +} > + > +static int add_pcie_port(struct dra7xx_pcie *dra7xx, > + struct platform_device *pdev) > +{ > + int ret; > + struct pcie_port *pp; > + struct resource *res; > + struct device *dev = &pdev->dev; > + > + pp = &dra7xx->pp; > + pp->dev = dev; > + pp->ops = &dra7xx_pcie_host_ops; > + > + pp->irq = platform_get_irq(pdev, 1); > + if (pp->irq < 0) { > + dev_err(dev, "missing IRQ resource\n"); > + return -EINVAL; > + } > + > + ret = devm_request_irq(&pdev->dev, pp->irq, > + dra7xx_pcie_msi_irq_handler, IRQF_SHARED, > + "dra7-pcie-msi", pp); > + if (ret) { > + dev_err(&pdev->dev, "failed to request irq\n"); > + return ret; > + } > + > + if (!IS_ENABLED(CONFIG_PCI_MSI)) { > + ret = dra7xx_pcie_init_irq_domain(pp); > + if (ret < 0) > + return ret; > + } > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbics"); > + pp->dbi_base = devm_ioremap(dev, res->start, resource_size(res)); > + if (!pp->dbi_base) > + return -ENOMEM; > + > + ret = dw_pcie_host_init(pp); > + if (ret) { > + dev_err(dra7xx->dev, "failed to initialize host\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int __init dra7xx_pcie_probe(struct platform_device *pdev) > +{ > + u32 reg; > + int ret; > + int irq; > + int i; > + int phy_count; > + struct phy **phy; > + void __iomem *base; > + struct resource *res; > + struct dra7xx_pcie *dra7xx; > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + char name[10]; > + > + dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL); > + if (!dra7xx) > + return -ENOMEM; > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + dev_err(dev, "missing IRQ resource\n"); > + 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) > + return -ENOMEM; > + > + phy_count = of_property_count_strings(np, "phy-names"); > + if (phy_count < 0) { > + dev_err(dev, "unable to find the strings\n"); > + return phy_count; > + } > + > + phy = devm_kzalloc(dev, sizeof(*phy) * phy_count, GFP_KERNEL); > + if (!phy) > + return -ENOMEM; > + > + for (i = 0; i < phy_count; i++) { > + snprintf(name, sizeof(name), "pcie-phy%d", i); > + phy[i] = devm_phy_get(dev, name); > + if (IS_ERR(phy[i])) > + return PTR_ERR(phy[i]); > + > + ret = phy_init(phy[i]); > + if (ret < 0) > + goto err_phy; > + > + ret = phy_power_on(phy[i]); > + if (ret < 0) { > + phy_exit(phy[i]); > + goto err_phy; > + } > + } > + > + dra7xx->base = base; > + dra7xx->phy = phy; > + dra7xx->dev = dev; > + dra7xx->phy_count = phy_count; > + > + pm_runtime_enable(dev); > + ret = pm_runtime_get_sync(dev); > + if (IS_ERR_VALUE(ret)) { > + dev_err(dev, "pm_runtime_get_sync failed\n"); > + goto err_phy; > + } > + > + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); > + reg &= ~LTSSM_EN; > + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); > + > + platform_set_drvdata(pdev, dra7xx); > + > + ret = add_pcie_port(dra7xx, pdev); > + if (ret < 0) > + goto err_add_port; > + > + return 0; > + > +err_add_port: > + pm_runtime_put(dev); > + pm_runtime_disable(dev); > + > +err_phy: > + while (--i >= 0) { > + phy_power_off(phy[i]); > + phy_exit(phy[i]); > + } > + > + return ret; > +} > + > +static int __exit dra7xx_pcie_remove(struct platform_device *pdev) > +{ > + struct dra7xx_pcie *dra7xx = platform_get_drvdata(pdev); > + struct pcie_port *pp = &dra7xx->pp; > + struct device *dev = &pdev->dev; > + int count = dra7xx->phy_count; > + > + if (pp->irq_domain) > + irq_domain_remove(pp->irq_domain); > + pm_runtime_put(dev); > + pm_runtime_disable(dev); > + while (count--) { > + phy_power_off(dra7xx->phy[count]); > + phy_exit(dra7xx->phy[count]); > + } > + > + return 0; > +} > + > +static const struct of_device_id of_dra7xx_pcie_match[] = { > + { .compatible = "ti,dra7-pcie", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, of_dra7xx_pcie_match); > + > +static struct platform_driver dra7xx_pcie_driver = { > + .remove = __exit_p(dra7xx_pcie_remove), > + .driver = { > + .name = "dra7-pcie", > + .owner = THIS_MODULE, > + .of_match_table = of_dra7xx_pcie_match, > + }, > +}; > + > +module_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); > + > +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); > +MODULE_DESCRIPTION("TI PCIe controller driver"); > +MODULE_LICENSE("GPL v2"); > -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
+Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala On Tuesday 08 July 2014 06:04 PM, Kishon Vijay Abraham I wrote: > Hi Arnd, Tony, other dt guys, > > On Wednesday 25 June 2014 11:26 PM, Kishon Vijay Abraham I wrote: >> Added support for pcie controller in dra7xx. This driver re-uses >> the designware core code that is already present in kernel. >> > > Are you okay with this patch? an you give your Acked-by? > > Thanks > Kishon >> Cc: Jason Gunthorpe <jgunthorpe@obsidianresearch.com> >> Cc: Bjorn Helgaas <bhelgaas@google.com> >> Cc: Mohit Kumar <mohit.kumar@st.com> >> Cc: Jingoo Han <jg1.han@samsung.com> >> Cc: Marek Vasut <marex@denx.de> >> Cc: Arnd Bergmann <arnd@arndb.de> >> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com> >> --- >> Documentation/devicetree/bindings/pci/ti-pci.txt | 59 +++ >> drivers/pci/host/Kconfig | 10 + >> drivers/pci/host/Makefile | 1 + >> drivers/pci/host/pci-dra7xx.c | 458 ++++++++++++++++++++++ >> 4 files changed, 528 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/pci/ti-pci.txt >> create mode 100644 drivers/pci/host/pci-dra7xx.c >> >> diff --git a/Documentation/devicetree/bindings/pci/ti-pci.txt b/Documentation/devicetree/bindings/pci/ti-pci.txt >> new file mode 100644 >> index 0000000..3d21791 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/pci/ti-pci.txt >> @@ -0,0 +1,59 @@ >> +TI PCI Controllers >> + >> +PCIe Designware Controller >> + - compatible: Should be "ti,dra7-pcie"" >> + - reg : Two register ranges as listed in the reg-names property >> + - reg-names : The first entry must be "ti-conf" for the TI specific registers >> + The second entry must be "rc-dbics" for the designware pcie >> + registers >> + The third entry must be "config" for the PCIe configuration space >> + - phys : list of PHY specifiers (used by generic PHY framework) >> + - phy-names : must be "pcie-phy0", "pcie-phy1", "pcie-phyN".. based on the >> + number of PHYs as specified in *phys* property. >> + - ti,hwmods : Name of the hwmod associated to the pcie, "pcie<X>", >> + where <X> is the instance number of the pcie from the HW spec. >> + - interrupts : Two interrupt entries must be specified. The first one is for >> + main interrupt line and the second for MSI interrupt line. >> + - #address-cells, >> + #size-cells, >> + #interrupt-cells, >> + device_type, >> + ranges, >> + num-lanes, >> + interrupt-map-mask, >> + interrupt-map : as specified in ../designware-pcie.txt >> + >> +Example: >> +axi { >> + compatible = "simple-bus"; >> + #size-cells = <1>; >> + #address-cells = <1>; >> + ranges = <0x51000000 0x51000000 0x3000 >> + 0x0 0x20000000 0x10000000>; >> + pcie@51000000 { >> + compatible = "ti,dra7-pcie"; >> + reg = <0x51000000 0x2000>, <0x51002000 0x14c>, <0x1000 0x2000>; >> + reg-names = "rc_dbics", "ti_conf", "config"; >> + interrupts = <0 232 0x4>, <0 233 0x4>; >> + #address-cells = <3>; >> + #size-cells = <2>; >> + device_type = "pci"; >> + ranges = <0x81000000 0 0 0x03000 0 0x00010000 >> + 0x82000000 0 0x20013000 0x13000 0 0xffed000>; >> + #interrupt-cells = <1>; >> + num-lanes = <1>; >> + ti,hwmods = "pcie1"; >> + phys = <&pcie1_phy>; >> + phy-names = "pcie-phy0"; >> + interrupt-map-mask = <0 0 0 7>; >> + interrupt-map = <0 0 0 1 &pcie_intc 1>, >> + <0 0 0 2 &pcie_intc 2>, >> + <0 0 0 3 &pcie_intc 3>, >> + <0 0 0 4 &pcie_intc 4>; >> + pcie_intc: interrupt-controller { >> + interrupt-controller; >> + #address-cells = <0>; >> + #interrupt-cells = <1>; >> + }; >> + }; >> +}; >> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig >> index 21df477..22117b0 100644 >> --- a/drivers/pci/host/Kconfig >> +++ b/drivers/pci/host/Kconfig >> @@ -1,6 +1,16 @@ >> menu "PCI host controller drivers" >> depends on PCI >> >> +config PCI_DRA7XX >> + bool "TI DRA7xx PCIe controller" >> + select PCIE_DW >> + depends on OF && HAS_IOMEM && TI_PIPE3 >> + help >> + Enables support for the PCIE controller present in DRA7xx SoC. There >> + are two instances of PCIE controller in DRA7xx. This controller can >> + act both as EP and RC. This reuses the same Designware core as used >> + by other SoCs. >> + >> config PCI_MVEBU >> bool "Marvell EBU PCIe controller" >> depends on ARCH_MVEBU || ARCH_DOVE || ARCH_KIRKWOOD >> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile >> index 611ba4b..c42844d 100644 >> --- a/drivers/pci/host/Makefile >> +++ b/drivers/pci/host/Makefile >> @@ -1,4 +1,5 @@ >> obj-$(CONFIG_PCIE_DW) += pcie-designware.o >> +obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o >> obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o >> obj-$(CONFIG_PCI_IMX6) += pci-imx6.o >> obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o >> diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c >> new file mode 100644 >> index 0000000..52b34fe >> --- /dev/null >> +++ b/drivers/pci/host/pci-dra7xx.c >> @@ -0,0 +1,458 @@ >> +/* >> + * pcie-dra7xx - PCIe controller driver for TI DRA7xx SoCs >> + * >> + * Copyright (C) 2013-2014 Texas Instruments Incorporated - http://www.ti.com >> + * >> + * Authors: 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 as >> + * 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/module.h> >> +#include <linux/pci.h> >> +#include <linux/phy/phy.h> >> +#include <linux/platform_device.h> >> +#include <linux/pm_runtime.h> >> +#include <linux/resource.h> >> +#include <linux/types.h> >> + >> +#include "pcie-designware.h" >> + >> +/* PCIe controller wrapper DRA7XX configuration registers */ >> + >> +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN 0x0024 >> +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN 0x0028 >> +#define ERR_SYS BIT(0) >> +#define ERR_FATAL BIT(1) >> +#define ERR_NONFATAL BIT(2) >> +#define ERR_COR BIT(3) >> +#define ERR_AXI BIT(4) >> +#define ERR_ECRC BIT(5) >> +#define PME_TURN_OFF BIT(8) >> +#define PME_TO_ACK BIT(9) >> +#define PM_PME BIT(10) >> +#define LINK_REQ_RST BIT(11) >> +#define LINK_UP_EVT BIT(12) >> +#define CFG_BME_EVT BIT(13) >> +#define CFG_MSE_EVT BIT(14) >> +#define INTERRUPTS (ERR_SYS | ERR_FATAL | ERR_NONFATAL | ERR_COR | ERR_AXI | \ >> + ERR_ECRC | PME_TURN_OFF | PME_TO_ACK | PM_PME | \ >> + LINK_REQ_RST | LINK_UP_EVT | CFG_BME_EVT | CFG_MSE_EVT) >> + >> +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI 0x0034 >> +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI 0x0038 >> +#define INTA BIT(0) >> +#define INTB BIT(1) >> +#define INTC BIT(2) >> +#define INTD BIT(3) >> +#define MSI BIT(4) >> +#define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD) >> + >> +#define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104 >> +#define LTSSM_EN 0x1 >> + >> +#define PCIECTRL_DRA7XX_CONF_PHY_CS 0x010C >> +#define LINK_UP BIT(16) >> + >> +struct dra7xx_pcie { >> + void __iomem *base; >> + struct phy **phy; >> + int phy_count; >> + struct device *dev; >> + struct pcie_port pp; >> +}; >> + >> +#define to_dra7xx_pcie(x) container_of((x), struct dra7xx_pcie, pp) >> + >> +static inline u32 dra7xx_pcie_readl(struct dra7xx_pcie *pcie, u32 offset) >> +{ >> + return readl(pcie->base + offset); >> +} >> + >> +static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset, >> + u32 value) >> +{ >> + writel(value, pcie->base + offset); >> +} >> + >> +static int dra7xx_pcie_link_up(struct pcie_port *pp) >> +{ >> + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); >> + u32 reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); >> + >> + return !!(reg & LINK_UP); >> +} >> + >> +static int dra7xx_pcie_establish_link(struct pcie_port *pp) >> +{ >> + u32 reg; >> + unsigned int retries = 1000; >> + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); >> + >> + if (dw_pcie_link_up(pp)) { >> + dev_err(pp->dev, "link is already up\n"); >> + return 0; >> + } >> + >> + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); >> + reg |= LTSSM_EN; >> + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); >> + >> + while (retries--) { >> + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); >> + if (reg & LINK_UP) >> + break; >> + usleep_range(10, 20); >> + } >> + >> + if (retries == 0) { >> + dev_err(pp->dev, "link is not up\n"); >> + return -ETIMEDOUT; >> + } >> + >> + return 0; >> +} >> + >> +static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) >> +{ >> + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); >> + >> + 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); >> + >> + if (IS_ENABLED(CONFIG_PCI_MSI)) >> + dra7xx_pcie_writel(dra7xx, >> + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, MSI); >> + else >> + dra7xx_pcie_writel(dra7xx, >> + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, >> + LEG_EP_INTERRUPTS); >> +} >> + >> +static void dra7xx_pcie_host_init(struct pcie_port *pp) >> +{ >> + dw_pcie_setup_rc(pp); >> + dra7xx_pcie_establish_link(pp); >> + if (IS_ENABLED(CONFIG_PCI_MSI)) >> + dw_pcie_msi_init(pp); >> + dra7xx_pcie_enable_interrupts(pp); >> +} >> + >> +static struct pcie_host_ops dra7xx_pcie_host_ops = { >> + .link_up = dra7xx_pcie_link_up, >> + .host_init = dra7xx_pcie_host_init, >> +}; >> + >> +static int dra7xx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, >> + irq_hw_number_t hwirq) >> +{ >> + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); >> + irq_set_chip_data(irq, domain->host_data); >> + set_irq_flags(irq, IRQF_VALID); >> + >> + return 0; >> +} >> + >> +static const struct irq_domain_ops intx_domain_ops = { >> + .map = dra7xx_pcie_intx_map, >> +}; >> + >> +static int dra7xx_pcie_init_irq_domain(struct pcie_port *pp) >> +{ >> + struct device *dev = pp->dev; >> + struct device_node *node = dev->of_node; >> + struct device_node *pcie_intc_node = of_get_next_child(node, NULL); >> + >> + if (!pcie_intc_node) { >> + dev_err(dev, "No PCIe Intc node found\n"); >> + return PTR_ERR(pcie_intc_node); >> + } >> + >> + pp->irq_domain = irq_domain_add_linear(pcie_intc_node, 4, >> + &intx_domain_ops, pp); >> + if (!pp->irq_domain) { >> + dev_err(dev, "Failed to get a INTx IRQ domain\n"); >> + return PTR_ERR(pp->irq_domain); >> + } >> + >> + return 0; >> +} >> + >> +static irqreturn_t dra7xx_pcie_msi_irq_handler(int irq, void *arg) >> +{ >> + struct pcie_port *pp = arg; >> + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); >> + u32 reg; >> + >> + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI); >> + >> + switch (reg) { >> + case MSI: >> + dw_handle_msi_irq(pp); >> + break; >> + case INTA: >> + case INTB: >> + case INTC: >> + case INTD: >> + generic_handle_irq(irq_find_mapping(pp->irq_domain, ffs(reg))); >> + break; >> + } >> + >> + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, reg); >> + >> + return IRQ_HANDLED; >> +} >> + >> + >> +static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) >> +{ >> + struct dra7xx_pcie *dra7xx = arg; >> + u32 reg; >> + >> + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN); >> + >> + if (reg & ERR_SYS) >> + dev_dbg(dra7xx->dev, "System Error\n"); >> + >> + if (reg & ERR_FATAL) >> + dev_dbg(dra7xx->dev, "Fatal Error\n"); >> + >> + if (reg & ERR_NONFATAL) >> + dev_dbg(dra7xx->dev, "Non Fatal Error\n"); >> + >> + if (reg & ERR_COR) >> + dev_dbg(dra7xx->dev, "Correctable Error\n"); >> + >> + if (reg & ERR_AXI) >> + dev_dbg(dra7xx->dev, "AXI tag lookup fatal Error\n"); >> + >> + if (reg & ERR_ECRC) >> + dev_dbg(dra7xx->dev, "ECRC Error\n"); >> + >> + if (reg & PME_TURN_OFF) >> + dev_dbg(dra7xx->dev, >> + "Power Management Event Turn-Off message received\n"); >> + >> + if (reg & PME_TO_ACK) >> + dev_dbg(dra7xx->dev, >> + "Power Management Turn-Off Ack message received\n"); >> + >> + if (reg & PM_PME) >> + dev_dbg(dra7xx->dev, >> + "PM Power Management Event message received\n"); >> + >> + if (reg & LINK_REQ_RST) >> + dev_dbg(dra7xx->dev, "Link Request Reset\n"); >> + >> + if (reg & LINK_UP_EVT) >> + dev_dbg(dra7xx->dev, "Link-up state change\n"); >> + >> + if (reg & CFG_BME_EVT) >> + dev_dbg(dra7xx->dev, "CFG 'Bus Master Enable' change\n"); >> + >> + if (reg & CFG_MSE_EVT) >> + dev_dbg(dra7xx->dev, "CFG 'Memory Space Enable' change\n"); >> + >> + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, reg); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int add_pcie_port(struct dra7xx_pcie *dra7xx, >> + struct platform_device *pdev) >> +{ >> + int ret; >> + struct pcie_port *pp; >> + struct resource *res; >> + struct device *dev = &pdev->dev; >> + >> + pp = &dra7xx->pp; >> + pp->dev = dev; >> + pp->ops = &dra7xx_pcie_host_ops; >> + >> + pp->irq = platform_get_irq(pdev, 1); >> + if (pp->irq < 0) { >> + dev_err(dev, "missing IRQ resource\n"); >> + return -EINVAL; >> + } >> + >> + ret = devm_request_irq(&pdev->dev, pp->irq, >> + dra7xx_pcie_msi_irq_handler, IRQF_SHARED, >> + "dra7-pcie-msi", pp); >> + if (ret) { >> + dev_err(&pdev->dev, "failed to request irq\n"); >> + return ret; >> + } >> + >> + if (!IS_ENABLED(CONFIG_PCI_MSI)) { >> + ret = dra7xx_pcie_init_irq_domain(pp); >> + if (ret < 0) >> + return ret; >> + } >> + >> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbics"); >> + pp->dbi_base = devm_ioremap(dev, res->start, resource_size(res)); >> + if (!pp->dbi_base) >> + return -ENOMEM; >> + >> + ret = dw_pcie_host_init(pp); >> + if (ret) { >> + dev_err(dra7xx->dev, "failed to initialize host\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int __init dra7xx_pcie_probe(struct platform_device *pdev) >> +{ >> + u32 reg; >> + int ret; >> + int irq; >> + int i; >> + int phy_count; >> + struct phy **phy; >> + void __iomem *base; >> + struct resource *res; >> + struct dra7xx_pcie *dra7xx; >> + struct device *dev = &pdev->dev; >> + struct device_node *np = dev->of_node; >> + char name[10]; >> + >> + dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL); >> + if (!dra7xx) >> + return -ENOMEM; >> + >> + irq = platform_get_irq(pdev, 0); >> + if (irq < 0) { >> + dev_err(dev, "missing IRQ resource\n"); >> + 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) >> + return -ENOMEM; >> + >> + phy_count = of_property_count_strings(np, "phy-names"); >> + if (phy_count < 0) { >> + dev_err(dev, "unable to find the strings\n"); >> + return phy_count; >> + } >> + >> + phy = devm_kzalloc(dev, sizeof(*phy) * phy_count, GFP_KERNEL); >> + if (!phy) >> + return -ENOMEM; >> + >> + for (i = 0; i < phy_count; i++) { >> + snprintf(name, sizeof(name), "pcie-phy%d", i); >> + phy[i] = devm_phy_get(dev, name); >> + if (IS_ERR(phy[i])) >> + return PTR_ERR(phy[i]); >> + >> + ret = phy_init(phy[i]); >> + if (ret < 0) >> + goto err_phy; >> + >> + ret = phy_power_on(phy[i]); >> + if (ret < 0) { >> + phy_exit(phy[i]); >> + goto err_phy; >> + } >> + } >> + >> + dra7xx->base = base; >> + dra7xx->phy = phy; >> + dra7xx->dev = dev; >> + dra7xx->phy_count = phy_count; >> + >> + pm_runtime_enable(dev); >> + ret = pm_runtime_get_sync(dev); >> + if (IS_ERR_VALUE(ret)) { >> + dev_err(dev, "pm_runtime_get_sync failed\n"); >> + goto err_phy; >> + } >> + >> + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); >> + reg &= ~LTSSM_EN; >> + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); >> + >> + platform_set_drvdata(pdev, dra7xx); >> + >> + ret = add_pcie_port(dra7xx, pdev); >> + if (ret < 0) >> + goto err_add_port; >> + >> + return 0; >> + >> +err_add_port: >> + pm_runtime_put(dev); >> + pm_runtime_disable(dev); >> + >> +err_phy: >> + while (--i >= 0) { >> + phy_power_off(phy[i]); >> + phy_exit(phy[i]); >> + } >> + >> + return ret; >> +} >> + >> +static int __exit dra7xx_pcie_remove(struct platform_device *pdev) >> +{ >> + struct dra7xx_pcie *dra7xx = platform_get_drvdata(pdev); >> + struct pcie_port *pp = &dra7xx->pp; >> + struct device *dev = &pdev->dev; >> + int count = dra7xx->phy_count; >> + >> + if (pp->irq_domain) >> + irq_domain_remove(pp->irq_domain); >> + pm_runtime_put(dev); >> + pm_runtime_disable(dev); >> + while (count--) { >> + phy_power_off(dra7xx->phy[count]); >> + phy_exit(dra7xx->phy[count]); >> + } >> + >> + return 0; >> +} >> + >> +static const struct of_device_id of_dra7xx_pcie_match[] = { >> + { .compatible = "ti,dra7-pcie", }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, of_dra7xx_pcie_match); >> + >> +static struct platform_driver dra7xx_pcie_driver = { >> + .remove = __exit_p(dra7xx_pcie_remove), >> + .driver = { >> + .name = "dra7-pcie", >> + .owner = THIS_MODULE, >> + .of_match_table = of_dra7xx_pcie_match, >> + }, >> +}; >> + >> +module_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); >> + >> +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); >> +MODULE_DESCRIPTION("TI PCIe controller driver"); >> +MODULE_LICENSE("GPL v2"); >> > -- > To unsubscribe from this list: send the line "unsubscribe linux-pci" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html > -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/devicetree/bindings/pci/ti-pci.txt b/Documentation/devicetree/bindings/pci/ti-pci.txt new file mode 100644 index 0000000..3d21791 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/ti-pci.txt @@ -0,0 +1,59 @@ +TI PCI Controllers + +PCIe Designware Controller + - compatible: Should be "ti,dra7-pcie"" + - reg : Two register ranges as listed in the reg-names property + - reg-names : The first entry must be "ti-conf" for the TI specific registers + The second entry must be "rc-dbics" for the designware pcie + registers + The third entry must be "config" for the PCIe configuration space + - phys : list of PHY specifiers (used by generic PHY framework) + - phy-names : must be "pcie-phy0", "pcie-phy1", "pcie-phyN".. based on the + number of PHYs as specified in *phys* property. + - ti,hwmods : Name of the hwmod associated to the pcie, "pcie<X>", + where <X> is the instance number of the pcie from the HW spec. + - interrupts : Two interrupt entries must be specified. The first one is for + main interrupt line and the second for MSI interrupt line. + - #address-cells, + #size-cells, + #interrupt-cells, + device_type, + ranges, + num-lanes, + interrupt-map-mask, + interrupt-map : as specified in ../designware-pcie.txt + +Example: +axi { + compatible = "simple-bus"; + #size-cells = <1>; + #address-cells = <1>; + ranges = <0x51000000 0x51000000 0x3000 + 0x0 0x20000000 0x10000000>; + pcie@51000000 { + compatible = "ti,dra7-pcie"; + reg = <0x51000000 0x2000>, <0x51002000 0x14c>, <0x1000 0x2000>; + reg-names = "rc_dbics", "ti_conf", "config"; + interrupts = <0 232 0x4>, <0 233 0x4>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + ranges = <0x81000000 0 0 0x03000 0 0x00010000 + 0x82000000 0 0x20013000 0x13000 0 0xffed000>; + #interrupt-cells = <1>; + num-lanes = <1>; + ti,hwmods = "pcie1"; + phys = <&pcie1_phy>; + phy-names = "pcie-phy0"; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &pcie_intc 1>, + <0 0 0 2 &pcie_intc 2>, + <0 0 0 3 &pcie_intc 3>, + <0 0 0 4 &pcie_intc 4>; + pcie_intc: interrupt-controller { + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <1>; + }; + }; +}; diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 21df477..22117b0 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -1,6 +1,16 @@ menu "PCI host controller drivers" depends on PCI +config PCI_DRA7XX + bool "TI DRA7xx PCIe controller" + select PCIE_DW + depends on OF && HAS_IOMEM && TI_PIPE3 + help + Enables support for the PCIE controller present in DRA7xx SoC. There + are two instances of PCIE controller in DRA7xx. This controller can + act both as EP and RC. This reuses the same Designware core as used + by other SoCs. + config PCI_MVEBU bool "Marvell EBU PCIe controller" depends on ARCH_MVEBU || ARCH_DOVE || ARCH_KIRKWOOD diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 611ba4b..c42844d 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o +obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o obj-$(CONFIG_PCI_IMX6) += pci-imx6.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c new file mode 100644 index 0000000..52b34fe --- /dev/null +++ b/drivers/pci/host/pci-dra7xx.c @@ -0,0 +1,458 @@ +/* + * pcie-dra7xx - PCIe controller driver for TI DRA7xx SoCs + * + * Copyright (C) 2013-2014 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: 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 as + * 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/module.h> +#include <linux/pci.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/resource.h> +#include <linux/types.h> + +#include "pcie-designware.h" + +/* PCIe controller wrapper DRA7XX configuration registers */ + +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN 0x0024 +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN 0x0028 +#define ERR_SYS BIT(0) +#define ERR_FATAL BIT(1) +#define ERR_NONFATAL BIT(2) +#define ERR_COR BIT(3) +#define ERR_AXI BIT(4) +#define ERR_ECRC BIT(5) +#define PME_TURN_OFF BIT(8) +#define PME_TO_ACK BIT(9) +#define PM_PME BIT(10) +#define LINK_REQ_RST BIT(11) +#define LINK_UP_EVT BIT(12) +#define CFG_BME_EVT BIT(13) +#define CFG_MSE_EVT BIT(14) +#define INTERRUPTS (ERR_SYS | ERR_FATAL | ERR_NONFATAL | ERR_COR | ERR_AXI | \ + ERR_ECRC | PME_TURN_OFF | PME_TO_ACK | PM_PME | \ + LINK_REQ_RST | LINK_UP_EVT | CFG_BME_EVT | CFG_MSE_EVT) + +#define PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI 0x0034 +#define PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI 0x0038 +#define INTA BIT(0) +#define INTB BIT(1) +#define INTC BIT(2) +#define INTD BIT(3) +#define MSI BIT(4) +#define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD) + +#define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104 +#define LTSSM_EN 0x1 + +#define PCIECTRL_DRA7XX_CONF_PHY_CS 0x010C +#define LINK_UP BIT(16) + +struct dra7xx_pcie { + void __iomem *base; + struct phy **phy; + int phy_count; + struct device *dev; + struct pcie_port pp; +}; + +#define to_dra7xx_pcie(x) container_of((x), struct dra7xx_pcie, pp) + +static inline u32 dra7xx_pcie_readl(struct dra7xx_pcie *pcie, u32 offset) +{ + return readl(pcie->base + offset); +} + +static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset, + u32 value) +{ + writel(value, pcie->base + offset); +} + +static int dra7xx_pcie_link_up(struct pcie_port *pp) +{ + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); + u32 reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); + + return !!(reg & LINK_UP); +} + +static int dra7xx_pcie_establish_link(struct pcie_port *pp) +{ + u32 reg; + unsigned int retries = 1000; + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); + + if (dw_pcie_link_up(pp)) { + dev_err(pp->dev, "link is already up\n"); + return 0; + } + + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); + reg |= LTSSM_EN; + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); + + while (retries--) { + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_PHY_CS); + if (reg & LINK_UP) + break; + usleep_range(10, 20); + } + + if (retries == 0) { + dev_err(pp->dev, "link is not up\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) +{ + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); + + 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); + + if (IS_ENABLED(CONFIG_PCI_MSI)) + dra7xx_pcie_writel(dra7xx, + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, MSI); + else + dra7xx_pcie_writel(dra7xx, + PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI, + LEG_EP_INTERRUPTS); +} + +static void dra7xx_pcie_host_init(struct pcie_port *pp) +{ + dw_pcie_setup_rc(pp); + dra7xx_pcie_establish_link(pp); + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(pp); + dra7xx_pcie_enable_interrupts(pp); +} + +static struct pcie_host_ops dra7xx_pcie_host_ops = { + .link_up = dra7xx_pcie_link_up, + .host_init = dra7xx_pcie_host_init, +}; + +static int dra7xx_pcie_intx_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + set_irq_flags(irq, IRQF_VALID); + + return 0; +} + +static const struct irq_domain_ops intx_domain_ops = { + .map = dra7xx_pcie_intx_map, +}; + +static int dra7xx_pcie_init_irq_domain(struct pcie_port *pp) +{ + struct device *dev = pp->dev; + struct device_node *node = dev->of_node; + struct device_node *pcie_intc_node = of_get_next_child(node, NULL); + + if (!pcie_intc_node) { + dev_err(dev, "No PCIe Intc node found\n"); + return PTR_ERR(pcie_intc_node); + } + + pp->irq_domain = irq_domain_add_linear(pcie_intc_node, 4, + &intx_domain_ops, pp); + if (!pp->irq_domain) { + dev_err(dev, "Failed to get a INTx IRQ domain\n"); + return PTR_ERR(pp->irq_domain); + } + + return 0; +} + +static irqreturn_t dra7xx_pcie_msi_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); + u32 reg; + + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI); + + switch (reg) { + case MSI: + dw_handle_msi_irq(pp); + break; + case INTA: + case INTB: + case INTC: + case INTD: + generic_handle_irq(irq_find_mapping(pp->irq_domain, ffs(reg))); + break; + } + + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI, reg); + + return IRQ_HANDLED; +} + + +static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) +{ + struct dra7xx_pcie *dra7xx = arg; + u32 reg; + + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN); + + if (reg & ERR_SYS) + dev_dbg(dra7xx->dev, "System Error\n"); + + if (reg & ERR_FATAL) + dev_dbg(dra7xx->dev, "Fatal Error\n"); + + if (reg & ERR_NONFATAL) + dev_dbg(dra7xx->dev, "Non Fatal Error\n"); + + if (reg & ERR_COR) + dev_dbg(dra7xx->dev, "Correctable Error\n"); + + if (reg & ERR_AXI) + dev_dbg(dra7xx->dev, "AXI tag lookup fatal Error\n"); + + if (reg & ERR_ECRC) + dev_dbg(dra7xx->dev, "ECRC Error\n"); + + if (reg & PME_TURN_OFF) + dev_dbg(dra7xx->dev, + "Power Management Event Turn-Off message received\n"); + + if (reg & PME_TO_ACK) + dev_dbg(dra7xx->dev, + "Power Management Turn-Off Ack message received\n"); + + if (reg & PM_PME) + dev_dbg(dra7xx->dev, + "PM Power Management Event message received\n"); + + if (reg & LINK_REQ_RST) + dev_dbg(dra7xx->dev, "Link Request Reset\n"); + + if (reg & LINK_UP_EVT) + dev_dbg(dra7xx->dev, "Link-up state change\n"); + + if (reg & CFG_BME_EVT) + dev_dbg(dra7xx->dev, "CFG 'Bus Master Enable' change\n"); + + if (reg & CFG_MSE_EVT) + dev_dbg(dra7xx->dev, "CFG 'Memory Space Enable' change\n"); + + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN, reg); + + return IRQ_HANDLED; +} + +static int add_pcie_port(struct dra7xx_pcie *dra7xx, + struct platform_device *pdev) +{ + int ret; + struct pcie_port *pp; + struct resource *res; + struct device *dev = &pdev->dev; + + pp = &dra7xx->pp; + pp->dev = dev; + pp->ops = &dra7xx_pcie_host_ops; + + pp->irq = platform_get_irq(pdev, 1); + if (pp->irq < 0) { + dev_err(dev, "missing IRQ resource\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, pp->irq, + dra7xx_pcie_msi_irq_handler, IRQF_SHARED, + "dra7-pcie-msi", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + + if (!IS_ENABLED(CONFIG_PCI_MSI)) { + ret = dra7xx_pcie_init_irq_domain(pp); + if (ret < 0) + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbics"); + pp->dbi_base = devm_ioremap(dev, res->start, resource_size(res)); + if (!pp->dbi_base) + return -ENOMEM; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dra7xx->dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +static int __init dra7xx_pcie_probe(struct platform_device *pdev) +{ + u32 reg; + int ret; + int irq; + int i; + int phy_count; + struct phy **phy; + void __iomem *base; + struct resource *res; + struct dra7xx_pcie *dra7xx; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + char name[10]; + + dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL); + if (!dra7xx) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "missing IRQ resource\n"); + 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) + return -ENOMEM; + + phy_count = of_property_count_strings(np, "phy-names"); + if (phy_count < 0) { + dev_err(dev, "unable to find the strings\n"); + return phy_count; + } + + phy = devm_kzalloc(dev, sizeof(*phy) * phy_count, GFP_KERNEL); + if (!phy) + return -ENOMEM; + + for (i = 0; i < phy_count; i++) { + snprintf(name, sizeof(name), "pcie-phy%d", i); + phy[i] = devm_phy_get(dev, name); + if (IS_ERR(phy[i])) + return PTR_ERR(phy[i]); + + ret = phy_init(phy[i]); + if (ret < 0) + goto err_phy; + + ret = phy_power_on(phy[i]); + if (ret < 0) { + phy_exit(phy[i]); + goto err_phy; + } + } + + dra7xx->base = base; + dra7xx->phy = phy; + dra7xx->dev = dev; + dra7xx->phy_count = phy_count; + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (IS_ERR_VALUE(ret)) { + dev_err(dev, "pm_runtime_get_sync failed\n"); + goto err_phy; + } + + reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD); + reg &= ~LTSSM_EN; + dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); + + platform_set_drvdata(pdev, dra7xx); + + ret = add_pcie_port(dra7xx, pdev); + if (ret < 0) + goto err_add_port; + + return 0; + +err_add_port: + pm_runtime_put(dev); + pm_runtime_disable(dev); + +err_phy: + while (--i >= 0) { + phy_power_off(phy[i]); + phy_exit(phy[i]); + } + + return ret; +} + +static int __exit dra7xx_pcie_remove(struct platform_device *pdev) +{ + struct dra7xx_pcie *dra7xx = platform_get_drvdata(pdev); + struct pcie_port *pp = &dra7xx->pp; + struct device *dev = &pdev->dev; + int count = dra7xx->phy_count; + + if (pp->irq_domain) + irq_domain_remove(pp->irq_domain); + pm_runtime_put(dev); + pm_runtime_disable(dev); + while (count--) { + phy_power_off(dra7xx->phy[count]); + phy_exit(dra7xx->phy[count]); + } + + return 0; +} + +static const struct of_device_id of_dra7xx_pcie_match[] = { + { .compatible = "ti,dra7-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_dra7xx_pcie_match); + +static struct platform_driver dra7xx_pcie_driver = { + .remove = __exit_p(dra7xx_pcie_remove), + .driver = { + .name = "dra7-pcie", + .owner = THIS_MODULE, + .of_match_table = of_dra7xx_pcie_match, + }, +}; + +module_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); + +MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); +MODULE_DESCRIPTION("TI PCIe controller driver"); +MODULE_LICENSE("GPL v2");
Added support for pcie controller in dra7xx. This driver re-uses the designware core code that is already present in kernel. Cc: Jason Gunthorpe <jgunthorpe@obsidianresearch.com> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Mohit Kumar <mohit.kumar@st.com> Cc: Jingoo Han <jg1.han@samsung.com> Cc: Marek Vasut <marex@denx.de> Cc: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com> --- Documentation/devicetree/bindings/pci/ti-pci.txt | 59 +++ drivers/pci/host/Kconfig | 10 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-dra7xx.c | 458 ++++++++++++++++++++++ 4 files changed, 528 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/ti-pci.txt create mode 100644 drivers/pci/host/pci-dra7xx.c