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@xxxxxxxxxxxxxxxxxxxx> > Cc: Bjorn Helgaas <bhelgaas@xxxxxxxxxx> > Cc: Mohit Kumar <mohit.kumar@xxxxxx> > Cc: Jingoo Han <jg1.han@xxxxxxxxxxx> > Cc: Marek Vasut <marex@xxxxxxx> > Cc: Arnd Bergmann <arnd@xxxxxxxx> > Signed-off-by: Kishon Vijay Abraham I <kishon@xxxxxx> > --- > 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@xxxxxx> > + * > + * 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@xxxxxx>"); > +MODULE_DESCRIPTION("TI PCIe controller driver"); > +MODULE_LICENSE("GPL v2"); > -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html