This commit adds support for instantiating the Tegra PCIe controller from a device tree. Signed-off-by: Thierry Reding <thierry.reding@xxxxxxxxxxxxxxxxx> --- Changes in v3: - rewrite the DT binding and adapt driver correspondingly Changes in v2: - increase compile coverage by using the IS_ENABLED() macro - disable node by default .../bindings/pci/nvidia,tegra20-pcie.txt | 94 ++++++++++ arch/arm/boot/dts/tegra20.dtsi | 62 +++++++ arch/arm/mach-tegra/board-dt-tegra20.c | 7 +- arch/arm/mach-tegra/pcie.c | 195 +++++++++++++++++++++ 4 files changed, 353 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt new file mode 100644 index 0000000..b181d4c --- /dev/null +++ b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt @@ -0,0 +1,94 @@ +NVIDIA Tegra PCIe controller + +Required properties: +- compatible: "nvidia,tegra20-pcie" +- reg: physical base address and length of the controller's registers +- interrupts: the interrupt outputs of the controller +- pex-clk-supply: supply voltage for internal reference clock +- vdd-supply: power supply for controller (1.05V) +- ranges: describes the translation of addresses for root ports +- #address-cells: address representation for root ports (must be 3) + - cell 0 specifies the port index + - cell 1 denotes the address type + 0: root port register space + 1: PCI configuration space + 2: PCI extended configuration space + 3: downstream I/O + 4: non-prefetchable memory + 5: prefetchable memory + - cell 2 provides a number space that can include the size (should be 0) +- #size-cells: size representation for root ports (must be 1) + +Root ports are defined as subnodes of the PCIe controller node. + +Required properties: +- device_type: must be "pciex" +- reg: address and size of the port configuration registers +- #address-cells: must be 3 +- #size-cells: must be 2 +- ranges: sub-ranges distributed from the PCIe controller node +- nvidia,num-lanes: number of lanes to use for this port + +Example: + + pcie-controller { + compatible = "nvidia,tegra20-pcie"; + reg = <0x80003000 0x00000800 /* PADS registers */ + 0x80003800 0x00000200 /* AFI registers */ + 0x81000000 0x01000000 /* configuration space */ + 0x90000000 0x10000000>; /* extended configuration space */ + interrupts = <0 98 0x04 /* controller interrupt */ + 0 99 0x04>; /* MSI interrupt */ + status = "disabled"; + + ranges = <0 0 0 0x80000000 0x00001000 /* root port 0 */ + 0 1 0 0x81000000 0x00800000 /* port 0 config space */ + 0 2 0 0x90000000 0x08000000 /* port 0 ext config space */ + 0 3 0 0x82000000 0x00008000 /* port 0 downstream I/O */ + 0 4 0 0xa0000000 0x08000000 /* port 0 non-prefetchable memory */ + 0 5 0 0xb0000000 0x08000000 /* port 0 prefetchable memory */ + + 1 0 0 0x80001000 0x00001000 /* root port 1 */ + 1 1 0 0x81800000 0x00800000 /* port 1 config space */ + 1 2 0 0x98000000 0x08000000 /* port 1 ext config space */ + 1 3 0 0x82008000 0x00008000 /* port 1 downstream I/O */ + 1 4 0 0xa8000000 0x08000000 /* port 1 non-prefetchable memory */ + 1 5 0 0xb8000000 0x08000000>; /* port 1 prefetchable memory */ + + #address-cells = <3>; + #size-cells = <1>; + + pci@0 { + device_type = "pciex"; + reg = <0 0 0 0x1000>; + status = "disabled"; + + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x80000000 0 0 0 1 0 0 0x00800000 /* config space */ + 0x90000000 0 0 0 2 0 0 0x08000000 /* ext config space */ + 0x81000000 0 0 0 3 0 0 0x00008000 /* I/O */ + 0x82000000 0 0 0 4 0 0 0x08000000 /* non-prefetchable memory */ + 0xc2000000 0 0 0 5 0 0 0x08000000>; /* prefetchable memory */ + + nvidia,num-lanes = <2>; + }; + + pci@1 { + device_type = "pciex"; + reg = <1 0 0 0x1000>; + status = "disabled"; + + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x80000000 0 0 1 1 0 0 0x00800000 /* config space */ + 0x90000000 0 0 1 2 0 0 0x08000000 /* ext config space */ + 0x81000000 0 0 1 3 0 0 0x00008000 /* I/O */ + 0x82000000 0 0 1 4 0 0 0x08000000 /* non-prefetchable memory */ + 0xc2000000 0 0 1 5 0 0 0x08000000>; /* prefetchable memory */ + + nvidia,num-lanes = <2>; + }; + }; diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index a094c97..c886dff 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -199,6 +199,68 @@ #size-cells = <0>; }; + pcie-controller { + compatible = "nvidia,tegra20-pcie"; + reg = <0x80003000 0x00000800 /* PADS registers */ + 0x80003800 0x00000200 /* AFI registers */ + 0x81000000 0x01000000 /* configuration space */ + 0x90000000 0x10000000>; /* extended configuration space */ + interrupts = <0 98 0x04 /* controller interrupt */ + 0 99 0x04>; /* MSI interrupt */ + status = "disabled"; + + ranges = <0 0 0 0x80000000 0x00001000 /* root port 0 */ + 0 1 0 0x81000000 0x00800000 /* port 0 config space */ + 0 2 0 0x90000000 0x08000000 /* port 0 ext config space */ + 0 3 0 0x82000000 0x00010000 /* port 0 downstream I/O */ + 0 4 0 0xa0000000 0x08000000 /* port 0 non-prefetchable memory */ + 0 5 0 0xb0000000 0x08000000 /* port 0 prefetchable memory */ + + 1 0 0 0x80001000 0x00001000 /* root port 1 */ + 1 1 0 0x81800000 0x00800000 /* port 1 config space */ + 1 2 0 0x98000000 0x08000000 /* port 1 ext config space */ + 1 3 0 0x82010000 0x00010000 /* port 1 downstream I/O */ + 1 4 0 0xa8000000 0x08000000 /* port 1 non-prefetchable memory */ + 1 5 0 0xb8000000 0x08000000>; /* port 1 prefetchable memory */ + + #address-cells = <3>; + #size-cells = <1>; + + pci@0 { + device_type = "pciex"; + reg = <0 0 0 0x1000>; + status = "disabled"; + + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x80000000 0 0 0 1 0 0 0x00800000 /* config space */ + 0x90000000 0 0 0 2 0 0 0x08000000 /* ext config space */ + 0x81000000 0 0 0 3 0 0 0x00010000 /* I/O */ + 0x82000000 0 0 0 4 0 0 0x08000000 /* non-prefetchable memory */ + 0xc2000000 0 0 0 5 0 0 0x08000000>; /* prefetchable memory */ + + nvidia,num-lanes = <2>; + }; + + pci@1 { + device_type = "pciex"; + reg = <1 0 0 0x1000>; + status = "disabled"; + + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x80000000 0 0 1 1 0 0 0x00800000 /* config space */ + 0x90000000 0 0 1 2 0 0 0x08000000 /* ext config space */ + 0x81000000 0 0 1 3 0 0 0x00010000 /* I/O */ + 0x82000000 0 0 1 4 0 0 0x08000000 /* non-prefetchable memory */ + 0xc2000000 0 0 1 5 0 0 0x08000000>; /* prefetchable memory */ + + nvidia,num-lanes = <2>; + }; + }; + usb@c5000000 { compatible = "nvidia,tegra20-ehci", "usb-ehci"; reg = <0xc5000000 0x4000>; diff --git a/arch/arm/mach-tegra/board-dt-tegra20.c b/arch/arm/mach-tegra/board-dt-tegra20.c index a8a05c1..caa377a 100644 --- a/arch/arm/mach-tegra/board-dt-tegra20.c +++ b/arch/arm/mach-tegra/board-dt-tegra20.c @@ -40,6 +40,7 @@ #include <mach/iomap.h> #include <mach/irqs.h> +#include <mach/pci-tegra.h> #include "board.h" #include "board-harmony.h" @@ -114,11 +115,7 @@ static void __init tegra_dt_init(void) #ifdef CONFIG_MACH_TRIMSLICE static void __init trimslice_init(void) { - int ret; - - ret = tegra_pcie_init(true, true); - if (ret) - pr_err("tegra_pci_init() failed: %d\n", ret); + platform_device_register(&tegra_pcie_device); } #endif diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c index dab3479..2d00b1c 100644 --- a/arch/arm/mach-tegra/pcie.c +++ b/arch/arm/mach-tegra/pcie.c @@ -37,6 +37,10 @@ #include <linux/delay.h> #include <linux/export.h> #include <linux/msi.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/of_platform.h> +#include <linux/regulator/consumer.h> #include <asm/sizes.h> #include <asm/mach/irq.h> @@ -220,6 +224,9 @@ struct tegra_pcie { unsigned int num_ports; struct tegra_pcie_msi *msi; + + struct regulator *pex_clk_supply; + struct regulator *vdd_supply; }; struct tegra_pcie_port { @@ -1016,6 +1023,178 @@ static int tegra_pcie_disable_msi(struct tegra_pcie *pcie) return 0; } +static int tegra_pcie_dt_init(struct platform_device *pdev) +{ + struct tegra_pcie *pcie = platform_get_drvdata(pdev); + int err; + + if (!IS_ERR_OR_NULL(pcie->vdd_supply)) { + err = regulator_enable(pcie->vdd_supply); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable VDD regulator: %d\n", err); + return err; + } + } + + if (!IS_ERR_OR_NULL(pcie->pex_clk_supply)) { + err = regulator_enable(pcie->pex_clk_supply); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable pex-clk regulator: %d\n", + err); + return err; + } + } + + return 0; +} + +static int tegra_pcie_dt_exit(struct platform_device *pdev) +{ + struct tegra_pcie *pcie = platform_get_drvdata(pdev); + int err; + + if (!IS_ERR_OR_NULL(pcie->pex_clk_supply)) { + err = regulator_disable(pcie->pex_clk_supply); + if (err < 0) { + dev_err(&pdev->dev, + "failed to disable pex-clk regulator: %d\n", + err); + return err; + } + } + + if (!IS_ERR_OR_NULL(pcie->vdd_supply)) { + err = regulator_disable(pcie->vdd_supply); + if (err < 0) { + dev_err(&pdev->dev, + "failed to disable VDD regulator: %d\n", err); + return err; + } + } + + return 0; +} + +struct resource *of_parse_reg(struct device_node *np, unsigned int *countp) +{ + unsigned int count = 0, i; + struct resource *reg, res; + int err; + + while (of_address_to_resource(np, count, &res) == 0) + count++; + + reg = kzalloc(sizeof(*reg) * count, GFP_KERNEL); + if (!reg) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < count; i++) { + err = of_address_to_resource(np, i, ®[i]); + if (err < 0) { + kfree(reg); + return ERR_PTR(err); + } + } + + if (countp) + *countp = count; + + return reg; +} + +static int tegra_pcie_port_parse_dt(struct tegra_pcie *pcie, + struct device_node *node, + struct tegra_pcie_rp *port) +{ + const __be32 *values; + u32 value; + int err; + + values = of_get_property(node, "reg", NULL); + if (!values) + return -ENODEV; + + port->index = be32_to_cpup(values); + + port->resources = of_parse_reg(node, &port->num_resources); + if (!port->resources) + return -ENOMEM; + + port->ranges = of_pci_parse_ranges(node, &port->num_ranges); + if (!port->ranges) { + err = -ENOMEM; + goto free; + } + + err = of_property_read_u32(node, "nvidia,num-lanes", &value); + if (err < 0) + goto free; + + port->num_lanes = value; + + return 0; + +free: + kfree(port->ranges); + kfree(port->resources); + return err; +} + +static struct tegra_pcie_pdata *tegra_pcie_parse_dt(struct tegra_pcie *pcie) +{ + struct tegra_pcie_pdata *pdata; + struct device_node *child; + unsigned int i = 0; + size_t size; + int err; + + pdata = devm_kzalloc(pcie->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->init = tegra_pcie_dt_init; + pdata->exit = tegra_pcie_dt_exit; + + pcie->vdd_supply = devm_regulator_get(pcie->dev, "vdd"); + if (IS_ERR_OR_NULL(pcie->vdd_supply)) + return ERR_CAST(pcie->vdd_supply); + + pcie->pex_clk_supply = devm_regulator_get(pcie->dev, "pex-clk"); + if (IS_ERR_OR_NULL(pcie->pex_clk_supply)) + return ERR_CAST(pcie->pex_clk_supply); + + /* parse root port nodes */ + for_each_child_of_node(pcie->dev->of_node, child) { + if (of_device_is_available(child)) + pdata->num_ports++; + } + + size = pdata->num_ports * sizeof(*pdata->ports); + + pdata->ports = devm_kzalloc(pcie->dev, size, GFP_KERNEL); + if (!pdata->ports) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(pcie->dev->of_node, child) { + struct tegra_pcie_rp *port = &pdata->ports[i]; + + if (!of_device_is_available(child)) + continue; + + err = tegra_pcie_port_parse_dt(pcie, child, port); + if (err < 0) + return ERR_PTR(err); + + i++; + } + + pdata->num_ports = i; + + return pdata; +} + static unsigned long tegra_pcie_port_get_pex_ctrl(struct tegra_pcie_port *port) { unsigned long ret = 0; @@ -1193,6 +1372,14 @@ static int __devinit tegra_pcie_probe(struct platform_device *pdev) pcie->dev = &pdev->dev; + if (IS_ENABLED(CONFIG_OF)) { + if (!pdata && pdev->dev.of_node) { + pdata = tegra_pcie_parse_dt(pcie); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + } + if (!pdata) return -ENODEV; @@ -1280,10 +1467,18 @@ static int __devexit tegra_pcie_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id tegra_pcie_of_match[] = { + { .compatible = "nvidia,tegra20-pcie", }, + { }, +}; +#endif + static struct platform_driver tegra_pcie_driver = { .driver = { .name = "tegra-pcie", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_pcie_of_match), }, .probe = tegra_pcie_probe, .remove = __devexit_p(tegra_pcie_remove), -- 1.7.11.2 -- To unsubscribe from this list: send the line "unsubscribe linux-tegra" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html