2013-04-03 14:45:52

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 00/12] Rewrite Tegra PCIe driver

This patch series contains an almost complete rewrite of the Tegra PCIe
driver. The code is moved to the drivers/pci/host directory and turned
into a proper platform driver, adding MSI and DT support while at it.
Other PCI host controller drivers can be added to that directory in an
attempt to make it easier to factor out common code.

Patches 1 to 3 add generic OF helpers to parse a PCI node's ranges
property as well as obtain the device and function numbers from a node's
reg property.

Patch 4 introduces a new MSI chip infrastructure which is required to
make multiple platform-specific implementations of the MSI support
functions coexist peacefully.

Patches 5 and 6 move some of the Tegra specific code around to allow it
to be used from outside the arch/arm/mach-tegra directory.

Patch 7 moves the Tegra PCIe controller driver to the drivers/pci/host
directory and turns it into a proper platform driver. It also adds MSI
(based on patches by NVIDIA) and DT support.

Patches 8 to 11 add device tree based probing for the TEC, Harmony and
TrimSlice boards. Finally the default configuration for Tegra is updated
in patch 12.

Thierry

Andrew Murray (1):
of/pci: Provide support for parsing PCI DT ranges property

Thierry Reding (11):
of/pci: Add of_pci_get_devfn() function
of/pci: Add of_pci_parse_bus_range() function
PCI: Introduce new MSI chip infrastructure
ARM: tegra: Move tegra_pcie_xclk_clamp() to PMC
ARM: tegra: Move pmc.h to include/linux/tegra-pmc.h
PCI: tegra: Move PCIe driver to drivers/pci/host
ARM: tegra: tamonten: Add PCIe support
ARM: tegra: tec: Add PCIe support
ARM: tegra: harmony: Initialize PCIe from DT
ARM: tegra: trimslice: Initialize PCIe from DT
ARM: tegra: Update default configuration (PCIe)

.../bindings/pci/nvidia,tegra20-pcie.txt | 123 ++
arch/arm/boot/dts/tegra20-harmony.dts | 20 +-
arch/arm/boot/dts/tegra20-tamonten.dtsi | 17 +-
arch/arm/boot/dts/tegra20-tec.dts | 8 +
arch/arm/boot/dts/tegra20-trimslice.dts | 28 +
arch/arm/boot/dts/tegra20.dtsi | 53 +
arch/arm/configs/tegra_defconfig | 4 +-
arch/arm/mach-tegra/Kconfig | 7 +-
arch/arm/mach-tegra/Makefile | 3 -
arch/arm/mach-tegra/board-harmony-pcie.c | 89 --
arch/arm/mach-tegra/board.h | 8 -
arch/arm/mach-tegra/iomap.h | 3 -
arch/arm/mach-tegra/pcie.c | 886 -----------
arch/arm/mach-tegra/pmc.c | 16 +
arch/arm/mach-tegra/tegra.c | 24 -
arch/microblaze/pci/pci-common.c | 110 +-
arch/mips/pci/pci.c | 50 +-
arch/powerpc/kernel/pci-common.c | 99 +-
drivers/of/address.c | 63 +
drivers/of/of_pci.c | 59 +-
drivers/pci/Kconfig | 2 +
drivers/pci/Makefile | 3 +
drivers/pci/host/Kconfig | 8 +
drivers/pci/host/Makefile | 1 +
drivers/pci/host/pci-tegra.c | 1560 ++++++++++++++++++++
drivers/pci/msi.c | 35 +-
drivers/pci/probe.c | 1 +
include/linux/msi.h | 10 +
include/linux/of_address.h | 42 +
include/linux/of_pci.h | 2 +
include/linux/pci.h | 1 +
include/linux/tegra-pmc.h | 23 +
32 files changed, 2155 insertions(+), 1203 deletions(-)
create mode 100644 Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
delete mode 100644 arch/arm/mach-tegra/board-harmony-pcie.c
delete mode 100644 arch/arm/mach-tegra/pcie.c
create mode 100644 drivers/pci/host/Kconfig
create mode 100644 drivers/pci/host/Makefile
create mode 100644 drivers/pci/host/pci-tegra.c
create mode 100644 include/linux/tegra-pmc.h

--
1.8.2


2013-04-03 14:45:39

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 01/12] of/pci: Provide support for parsing PCI DT ranges property

From: Andrew Murray <[email protected]>

This patch factors out common implementation patterns to reduce overall kernel
code and provide a means for host bridge drivers to directly obtain struct
resources from the DT's ranges property without relying on architecture specific
DT handling. This will make it easier to write archiecture independent host bridge
drivers and mitigate against further duplication of DT parsing code.

This patch can be used in the following way:

struct of_pci_range_parser parser;
struct of_pci_range range;

if (of_pci_range_parser(&parser, np))
; //no ranges property

for_each_of_pci_range(&parser, &range) {

/*
directly access properties of the address range, e.g.:
range.pci_space, range.pci_addr, range.cpu_addr,
range.size, range.flags

alternatively obtain a struct resource, e.g.:
struct resource res;
of_pci_range_to_resource(&range, np, &res);
*/
}

Additionally the implementation takes care of adjacent ranges and merges them
into a single range (as was the case with powerpc and microblaze).

The modifications to microblaze, mips and powerpc have not been tested.

Signed-off-by: Andrew Murray <[email protected]>
Signed-off-by: Liviu Dudau <[email protected]>
Signed-off-by: Thomas Petazzoni <[email protected]>
Signed-off-by: Thierry Reding <[email protected]>
---
arch/microblaze/pci/pci-common.c | 110 ++++++++++++++-------------------------
arch/mips/pci/pci.c | 50 ++++++------------
arch/powerpc/kernel/pci-common.c | 99 ++++++++++++-----------------------
drivers/of/address.c | 63 ++++++++++++++++++++++
include/linux/of_address.h | 42 +++++++++++++++
5 files changed, 194 insertions(+), 170 deletions(-)

diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c
index bdb8ea1..11dd9ac 100644
--- a/arch/microblaze/pci/pci-common.c
+++ b/arch/microblaze/pci/pci-common.c
@@ -657,67 +657,43 @@ void pci_resource_to_user(const struct pci_dev *dev, int bar,
void pci_process_bridge_OF_ranges(struct pci_controller *hose,
struct device_node *dev, int primary)
{
- const u32 *ranges;
- int rlen;
- int pna = of_n_addr_cells(dev);
- int np = pna + 5;
int memno = 0, isa_hole = -1;
- u32 pci_space;
- unsigned long long pci_addr, cpu_addr, pci_next, cpu_next, size;
unsigned long long isa_mb = 0;
struct resource *res;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ u32 res_type;

pr_info("PCI host bridge %s %s ranges:\n",
dev->full_name, primary ? "(primary)" : "");

- /* Get ranges property */
- ranges = of_get_property(dev, "ranges", &rlen);
- if (ranges == NULL)
+ /* Check for ranges property */
+ if (of_pci_range_parser(&parser, dev))
return;

- /* Parse it */
pr_debug("Parsing ranges property...\n");
- while ((rlen -= np * 4) >= 0) {
+ for_each_of_pci_range(&parser, &range) {
/* Read next ranges element */
- pci_space = ranges[0];
- pci_addr = of_read_number(ranges + 1, 2);
- cpu_addr = of_translate_address(dev, ranges + 3);
- size = of_read_number(ranges + pna + 3, 2);
-
- pr_debug("pci_space: 0x%08x pci_addr:0x%016llx ",
- pci_space, pci_addr);
- pr_debug("cpu_addr:0x%016llx size:0x%016llx\n",
- cpu_addr, size);
-
- ranges += np;
+ pr_debug("pci_space: 0x%08x pci_addr: 0x%016llx ",
+ range.pci_space, range.pci_addr);
+ pr_debug("cpu_addr: 0x%016llx size: 0x%016llx\n",
+ range.cpu_addr, range.size);

/* If we failed translation or got a zero-sized region
* (some FW try to feed us with non sensical zero sized regions
* such as power3 which look like some kind of attempt
* at exposing the VGA memory hole)
*/
- if (cpu_addr == OF_BAD_ADDR || size == 0)
+ if (range.cpu_addr == OF_BAD_ADDR || range.size == 0)
continue;

- /* Now consume following elements while they are contiguous */
- for (; rlen >= np * sizeof(u32);
- ranges += np, rlen -= np * 4) {
- if (ranges[0] != pci_space)
- break;
- pci_next = of_read_number(ranges + 1, 2);
- cpu_next = of_translate_address(dev, ranges + 3);
- if (pci_next != pci_addr + size ||
- cpu_next != cpu_addr + size)
- break;
- size += of_read_number(ranges + pna + 3, 2);
- }
-
/* Act based on address space type */
res = NULL;
- switch ((pci_space >> 24) & 0x3) {
- case 1: /* PCI IO space */
+ res_type = range.flags & IORESOURCE_TYPE_BITS;
+ if (res_type == IORESOURCE_IO) {
pr_info(" IO 0x%016llx..0x%016llx -> 0x%016llx\n",
- cpu_addr, cpu_addr + size - 1, pci_addr);
+ range.cpu_addr, range.cpu_addr + range.size - 1,
+ range.pci_addr);

/* We support only one IO range */
if (hose->pci_io_size) {
@@ -725,11 +701,12 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
continue;
}
/* On 32 bits, limit I/O space to 16MB */
- if (size > 0x01000000)
- size = 0x01000000;
+ if (range.size > 0x01000000)
+ range.size = 0x01000000;

/* 32 bits needs to map IOs here */
- hose->io_base_virt = ioremap(cpu_addr, size);
+ hose->io_base_virt = ioremap(range.cpu_addr,
+ range.size);

/* Expect trouble if pci_addr is not 0 */
if (primary)
@@ -738,19 +715,18 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
/* pci_io_size and io_base_phys always represent IO
* space starting at 0 so we factor in pci_addr
*/
- hose->pci_io_size = pci_addr + size;
- hose->io_base_phys = cpu_addr - pci_addr;
+ hose->pci_io_size = range.pci_addr + range.size;
+ hose->io_base_phys = range.cpu_addr - range.pci_addr;

/* Build resource */
res = &hose->io_resource;
- res->flags = IORESOURCE_IO;
- res->start = pci_addr;
- break;
- case 2: /* PCI Memory space */
- case 3: /* PCI 64 bits Memory space */
+ range.cpu_addr = range.pci_addr;
+ } else if (res_type == IORESOURCE_MEM) {
pr_info(" MEM 0x%016llx..0x%016llx -> 0x%016llx %s\n",
- cpu_addr, cpu_addr + size - 1, pci_addr,
- (pci_space & 0x40000000) ? "Prefetch" : "");
+ range.cpu_addr, range.cpu_addr + range.size - 1,
+ range.pci_addr,
+ (range.pci_space & 0x40000000) ?
+ "Prefetch" : "");

/* We support only 3 memory ranges */
if (memno >= 3) {
@@ -758,13 +734,13 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
continue;
}
/* Handles ISA memory hole space here */
- if (pci_addr == 0) {
+ if (range.pci_addr == 0) {
isa_mb = cpu_addr;
isa_hole = memno;
if (primary || isa_mem_base == 0)
- isa_mem_base = cpu_addr;
- hose->isa_mem_phys = cpu_addr;
- hose->isa_mem_size = size;
+ isa_mem_base = range.cpu_addr;
+ hose->isa_mem_phys = range.cpu_addr;
+ hose->isa_mem_size = range.size;
}

/* We get the PCI/Mem offset from the first range or
@@ -772,30 +748,22 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
* hole. If they don't match, bugger.
*/
if (memno == 0 ||
- (isa_hole >= 0 && pci_addr != 0 &&
+ (isa_hole >= 0 && range.pci_addr != 0 &&
hose->pci_mem_offset == isa_mb))
- hose->pci_mem_offset = cpu_addr - pci_addr;
- else if (pci_addr != 0 &&
- hose->pci_mem_offset != cpu_addr - pci_addr) {
+ hose->pci_mem_offset = range.cpu_addr
+ - range.pci_addr;
+ else if (range.pci_addr != 0 &&
+ hose->pci_mem_offset != range.cpu_addr
+ - range.pci_addr) {
pr_info(" \\--> Skipped (offset mismatch) !\n");
continue;
}

/* Build resource */
res = &hose->mem_resources[memno++];
- res->flags = IORESOURCE_MEM;
- if (pci_space & 0x40000000)
- res->flags |= IORESOURCE_PREFETCH;
- res->start = cpu_addr;
- break;
- }
- if (res != NULL) {
- res->name = dev->full_name;
- res->end = res->start + size - 1;
- res->parent = NULL;
- res->sibling = NULL;
- res->child = NULL;
}
+ if (res != NULL)
+ of_pci_range_to_resource(&range, dev, res);
}

/* If there's an ISA hole and the pci_mem_offset is -not- matching
diff --git a/arch/mips/pci/pci.c b/arch/mips/pci/pci.c
index 0872f12..bee49a4 100644
--- a/arch/mips/pci/pci.c
+++ b/arch/mips/pci/pci.c
@@ -122,51 +122,33 @@ static void pcibios_scanbus(struct pci_controller *hose)
#ifdef CONFIG_OF
void pci_load_of_ranges(struct pci_controller *hose, struct device_node *node)
{
- const __be32 *ranges;
- int rlen;
- int pna = of_n_addr_cells(node);
- int np = pna + 5;
+ struct of_pci_range_range range;
+ struct of_pci_range_parser parser;
+ u32 res_type;

pr_info("PCI host bridge %s ranges:\n", node->full_name);
- ranges = of_get_property(node, "ranges", &rlen);
- if (ranges == NULL)
- return;
hose->of_node = node;

- while ((rlen -= np * 4) >= 0) {
- u32 pci_space;
+ if (of_pci_range_parser(&parser, node))
+ return;
+
+ for_each_of_pci_range(&parser, &range) {
struct resource *res = NULL;
- u64 addr, size;
-
- pci_space = be32_to_cpup(&ranges[0]);
- addr = of_translate_address(node, ranges + 3);
- size = of_read_number(ranges + pna + 3, 2);
- ranges += np;
- switch ((pci_space >> 24) & 0x3) {
- case 1: /* PCI IO space */
+
+ res_type = range.flags & IORESOURCE_TYPE_BITS;
+ if (res_type == IORESOURCE_IO) {
pr_info(" IO 0x%016llx..0x%016llx\n",
- addr, addr + size - 1);
+ range.addr, range.addr + range.size - 1);
hose->io_map_base =
- (unsigned long)ioremap(addr, size);
+ (unsigned long)ioremap(range.addr, range.size);
res = hose->io_resource;
- res->flags = IORESOURCE_IO;
- break;
- case 2: /* PCI Memory space */
- case 3: /* PCI 64 bits Memory space */
+ } else if (res_type == IORESOURCE_MEM) {
pr_info(" MEM 0x%016llx..0x%016llx\n",
- addr, addr + size - 1);
+ range.addr, range.addr + range.size - 1);
res = hose->mem_resource;
- res->flags = IORESOURCE_MEM;
- break;
- }
- if (res != NULL) {
- res->start = addr;
- res->name = node->full_name;
- res->end = res->start + size - 1;
- res->parent = NULL;
- res->sibling = NULL;
- res->child = NULL;
}
+ if (res != NULL)
+ of_pci_range_to_resource(&range, node, res);
}
}
#endif
diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c
index fa12ae4..878d530 100644
--- a/arch/powerpc/kernel/pci-common.c
+++ b/arch/powerpc/kernel/pci-common.c
@@ -676,61 +676,37 @@ void pci_resource_to_user(const struct pci_dev *dev, int bar,
void pci_process_bridge_OF_ranges(struct pci_controller *hose,
struct device_node *dev, int primary)
{
- const u32 *ranges;
- int rlen;
- int pna = of_n_addr_cells(dev);
- int np = pna + 5;
int memno = 0, isa_hole = -1;
- u32 pci_space;
- unsigned long long pci_addr, cpu_addr, pci_next, cpu_next, size;
unsigned long long isa_mb = 0;
struct resource *res;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ u32 res_type;

printk(KERN_INFO "PCI host bridge %s %s ranges:\n",
dev->full_name, primary ? "(primary)" : "");

- /* Get ranges property */
- ranges = of_get_property(dev, "ranges", &rlen);
- if (ranges == NULL)
+ /* Check for ranges property */
+ if (of_pci_range_parser(&parser, dev))
return;

- /* Parse it */
- while ((rlen -= np * 4) >= 0) {
- /* Read next ranges element */
- pci_space = ranges[0];
- pci_addr = of_read_number(ranges + 1, 2);
- cpu_addr = of_translate_address(dev, ranges + 3);
- size = of_read_number(ranges + pna + 3, 2);
- ranges += np;
-
+ for_each_of_pci_range(&parser, &range) {
/* If we failed translation or got a zero-sized region
* (some FW try to feed us with non sensical zero sized regions
* such as power3 which look like some kind of attempt at exposing
* the VGA memory hole)
*/
- if (cpu_addr == OF_BAD_ADDR || size == 0)
+ if (range.cpu_addr == OF_BAD_ADDR || range.size == 0)
continue;

- /* Now consume following elements while they are contiguous */
- for (; rlen >= np * sizeof(u32);
- ranges += np, rlen -= np * 4) {
- if (ranges[0] != pci_space)
- break;
- pci_next = of_read_number(ranges + 1, 2);
- cpu_next = of_translate_address(dev, ranges + 3);
- if (pci_next != pci_addr + size ||
- cpu_next != cpu_addr + size)
- break;
- size += of_read_number(ranges + pna + 3, 2);
- }
-
/* Act based on address space type */
res = NULL;
- switch ((pci_space >> 24) & 0x3) {
- case 1: /* PCI IO space */
+ res_type = range.flags & IORESOURCE_TYPE_BITS;
+ if (res_type == IORESOURCE_IO) {
printk(KERN_INFO
" IO 0x%016llx..0x%016llx -> 0x%016llx\n",
- cpu_addr, cpu_addr + size - 1, pci_addr);
+ range.cpu_addr, range.cpu_addr + range.size - 1,
+ range.pci_addr);

/* We support only one IO range */
if (hose->pci_io_size) {
@@ -740,11 +716,12 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
}
#ifdef CONFIG_PPC32
/* On 32 bits, limit I/O space to 16MB */
- if (size > 0x01000000)
- size = 0x01000000;
+ if (range.size > 0x01000000)
+ range.size = 0x01000000;

/* 32 bits needs to map IOs here */
- hose->io_base_virt = ioremap(cpu_addr, size);
+ hose->io_base_virt = ioremap(range.cpu_addr,
+ range.size);

/* Expect trouble if pci_addr is not 0 */
if (primary)
@@ -754,20 +731,19 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
/* pci_io_size and io_base_phys always represent IO
* space starting at 0 so we factor in pci_addr
*/
- hose->pci_io_size = pci_addr + size;
- hose->io_base_phys = cpu_addr - pci_addr;
+ hose->pci_io_size = range.pci_addr + range.size;
+ hose->io_base_phys = range.cpu_addr - range.pci_addr;

/* Build resource */
res = &hose->io_resource;
- res->flags = IORESOURCE_IO;
- res->start = pci_addr;
- break;
- case 2: /* PCI Memory space */
- case 3: /* PCI 64 bits Memory space */
+ range.cpu_addr = range.pci_addr;
+ } else if (res_type == IORESOURCE_MEM) {
printk(KERN_INFO
" MEM 0x%016llx..0x%016llx -> 0x%016llx %s\n",
- cpu_addr, cpu_addr + size - 1, pci_addr,
- (pci_space & 0x40000000) ? "Prefetch" : "");
+ range.cpu_addr, range.cpu_addr + range.size - 1,
+ range.pci_addr,
+ (range.pci_space & 0x40000000) ?
+ "Prefetch" : "");

/* We support only 3 memory ranges */
if (memno >= 3) {
@@ -776,13 +752,13 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
continue;
}
/* Handles ISA memory hole space here */
- if (pci_addr == 0) {
+ if (range.pci_addr == 0) {
isa_mb = cpu_addr;
isa_hole = memno;
if (primary || isa_mem_base == 0)
- isa_mem_base = cpu_addr;
- hose->isa_mem_phys = cpu_addr;
- hose->isa_mem_size = size;
+ isa_mem_base = range.cpu_addr;
+ hose->isa_mem_phys = range.cpu_addr;
+ hose->isa_mem_size = range.size;
}

/* We get the PCI/Mem offset from the first range or
@@ -790,11 +766,13 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,
* hole. If they don't match, bugger.
*/
if (memno == 0 ||
- (isa_hole >= 0 && pci_addr != 0 &&
+ (isa_hole >= 0 && range.pci_addr != 0 &&
hose->pci_mem_offset == isa_mb))
- hose->pci_mem_offset = cpu_addr - pci_addr;
+ hose->pci_mem_offset = range.cpu_addr -
+ range.pci_addr;
else if (pci_addr != 0 &&
- hose->pci_mem_offset != cpu_addr - pci_addr) {
+ hose->pci_mem_offset != range.cpu_addr -
+ range.pci_addr) {
printk(KERN_INFO
" \\--> Skipped (offset mismatch) !\n");
continue;
@@ -802,19 +780,10 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose,

/* Build resource */
res = &hose->mem_resources[memno++];
- res->flags = IORESOURCE_MEM;
- if (pci_space & 0x40000000)
- res->flags |= IORESOURCE_PREFETCH;
- res->start = cpu_addr;
break;
}
- if (res != NULL) {
- res->name = dev->full_name;
- res->end = res->start + size - 1;
- res->parent = NULL;
- res->sibling = NULL;
- res->child = NULL;
- }
+ if (res != NULL)
+ of_pci_range_to_resource(&range, dev, res);
}

/* If there's an ISA hole and the pci_mem_offset is -not- matching
diff --git a/drivers/of/address.c b/drivers/of/address.c
index 04da786..e87f45e 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -227,6 +227,69 @@ int of_pci_address_to_resource(struct device_node *dev, int bar,
return __of_address_to_resource(dev, addrp, size, flags, NULL, r);
}
EXPORT_SYMBOL_GPL(of_pci_address_to_resource);
+
+int of_pci_range_parser(struct of_pci_range_parser *parser,
+ struct device_node *node)
+{
+ const int na = 3, ns = 2;
+ int rlen;
+
+ parser->node = node;
+ parser->pna = of_n_addr_cells(node);
+ parser->np = parser->pna + na + ns;
+
+ parser->range = of_get_property(node, "ranges", &rlen);
+ if (parser->range == NULL)
+ return -ENOENT;
+
+ parser->end = parser->range + rlen / sizeof(__be32);
+
+ return 0;
+}
+
+struct of_pci_range *of_pci_process_ranges(struct of_pci_range_parser *parser,
+ struct of_pci_range *range)
+{
+ const int na = 3, ns = 2;
+
+ if (!parser->range || parser->range + parser->np > parser->end)
+ return NULL;
+
+ range->pci_space = be32_to_cpup(parser->range);
+ range->flags = of_bus_pci_get_flags(parser->range);
+ range->pci_addr = of_read_number(parser->range + 1, ns);
+ range->cpu_addr = of_translate_address(parser->node,
+ parser->range + na);
+ range->size = of_read_number(parser->range + parser->pna + na, ns);
+
+ parser->range += parser->np;
+
+ /* Now consume following elements while they are contiguous */
+ while (parser->range + parser->np <= parser->end) {
+ u32 flags, pci_space;
+ u64 pci_addr, cpu_addr, size;
+
+ pci_space = be32_to_cpup(parser->range);
+ flags = of_bus_pci_get_flags(parser->range);
+ pci_addr = of_read_number(parser->range + 1, ns);
+ cpu_addr = of_translate_address(parser->node,
+ parser->range + na);
+ size = of_read_number(parser->range + parser->pna + na, ns);
+
+ if (flags != range->flags)
+ break;
+ if (pci_addr != range->pci_addr + range->size ||
+ cpu_addr != range->cpu_addr + range->size)
+ break;
+
+ range->size += size;
+ parser->range += parser->np;
+ }
+
+ return range;
+}
+EXPORT_SYMBOL_GPL(of_pci_process_ranges);
+
#endif /* CONFIG_PCI */

/*
diff --git a/include/linux/of_address.h b/include/linux/of_address.h
index 0506eb5..c7003a3 100644
--- a/include/linux/of_address.h
+++ b/include/linux/of_address.h
@@ -4,6 +4,32 @@
#include <linux/errno.h>
#include <linux/of.h>

+struct of_pci_range_parser {
+ struct device_node *node;
+ const __be32 *range, *end;
+ int np, pna;
+};
+
+struct of_pci_range {
+ u32 pci_space;
+ u64 pci_addr;
+ u64 cpu_addr;
+ u64 size;
+ u32 flags;
+};
+
+#define for_each_of_pci_range(parser, range) \
+ for (; of_pci_process_ranges(parser, range);)
+
+#define of_pci_range_to_resource(range, np, res) \
+ do { \
+ (res)->flags = (range)->flags; \
+ (res)->start = (range)->cpu_addr; \
+ (res)->end = (range)->cpu_addr + (range)->size - 1; \
+ (res)->parent = (res)->child = (res)->sibling = NULL; \
+ (res)->name = (np)->full_name; \
+ } while (0)
+
#ifdef CONFIG_OF_ADDRESS
extern u64 of_translate_address(struct device_node *np, const __be32 *addr);
extern bool of_can_translate_address(struct device_node *dev);
@@ -27,6 +53,10 @@ static inline unsigned long pci_address_to_pio(phys_addr_t addr) { return -1; }
#define pci_address_to_pio pci_address_to_pio
#endif

+int of_pci_range_parser(struct of_pci_range_parser *parser,
+ struct device_node *node);
+struct of_pci_range *of_pci_process_ranges(struct of_pci_range_parser *parser,
+ struct of_pci_range *range);
#else /* CONFIG_OF_ADDRESS */
#ifndef of_address_to_resource
static inline int of_address_to_resource(struct device_node *dev, int index,
@@ -53,6 +83,18 @@ static inline const __be32 *of_get_address(struct device_node *dev, int index,
{
return NULL;
}
+
+int of_pci_range_parser(struct of_pci_range_parser *parser,
+ struct device_noed *node)
+{
+ return -1;
+}
+
+struct of_pci_range *of_pci_process_ranges(struct of_pci_range_parser *parser,
+ struct of_pci_range *range)
+{
+ return NULL;
+}
#endif /* CONFIG_OF_ADDRESS */


--
1.8.2

2013-04-03 14:45:45

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 02/12] of/pci: Add of_pci_get_devfn() function

This function can be used to parse the device and function number from a
standard 5-cell PCI resource. PCI_SLOT() and PCI_FUNC() can be used on
the returned value obtain the device and function numbers respectively.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- rename devfn and err variables for clarity

drivers/of/of_pci.c | 34 +++++++++++++++++++++++++++++-----
include/linux/of_pci.h | 1 +
2 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c
index 13e37e2..4dd7b9b 100644
--- a/drivers/of/of_pci.c
+++ b/drivers/of/of_pci.c
@@ -5,14 +5,15 @@
#include <asm/prom.h>

static inline int __of_pci_pci_compare(struct device_node *node,
- unsigned int devfn)
+ unsigned int data)
{
- unsigned int size;
- const __be32 *reg = of_get_property(node, "reg", &size);
+ int devfn;

- if (!reg || size < 5 * sizeof(__be32))
+ devfn = of_pci_get_devfn(node);
+ if (devfn < 0)
return 0;
- return ((be32_to_cpup(&reg[0]) >> 8) & 0xff) == devfn;
+
+ return devfn == data;
}

struct device_node *of_pci_find_child_device(struct device_node *parent,
@@ -40,3 +41,26 @@ struct device_node *of_pci_find_child_device(struct device_node *parent,
return NULL;
}
EXPORT_SYMBOL_GPL(of_pci_find_child_device);
+
+/**
+ * of_pci_get_devfn() - Get device and function numbers for a device node
+ * @np: device node
+ *
+ * Parses a standard 5-cell PCI resource and returns an 8-bit value that can
+ * be passed to the PCI_SLOT() and PCI_FUNC() macros to extract the device
+ * and function numbers respectively. On error a negative error code is
+ * returned.
+ */
+int of_pci_get_devfn(struct device_node *np)
+{
+ unsigned int size;
+ const __be32 *reg;
+
+ reg = of_get_property(np, "reg", &size);
+
+ if (!reg || size < 5 * sizeof(__be32))
+ return -EINVAL;
+
+ return (be32_to_cpup(reg) >> 8) & 0xff;
+}
+EXPORT_SYMBOL_GPL(of_pci_get_devfn);
diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h
index bb115de..91ec484 100644
--- a/include/linux/of_pci.h
+++ b/include/linux/of_pci.h
@@ -10,5 +10,6 @@ int of_irq_map_pci(const struct pci_dev *pdev, struct of_irq *out_irq);
struct device_node;
struct device_node *of_pci_find_child_device(struct device_node *parent,
unsigned int devfn);
+int of_pci_get_devfn(struct device_node *np);

#endif
--
1.8.2

2013-04-03 14:46:09

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 11/12] ARM: tegra: trimslice: Initialize PCIe from DT

With the device tree support in place, probe the PCIe controller from
the device tree and remove the corresponding workaround in the board
file.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- add missing dummy regulator nodes by Stephen Warren
- rename port 0 DT node and disable unused port 1

arch/arm/boot/dts/tegra20-trimslice.dts | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20-trimslice.dts b/arch/arm/boot/dts/tegra20-trimslice.dts
index 955bf49..dc081c3 100644
--- a/arch/arm/boot/dts/tegra20-trimslice.dts
+++ b/arch/arm/boot/dts/tegra20-trimslice.dts
@@ -300,6 +300,16 @@
};
};

+ pcie-controller {
+ status = "okay";
+ pex-clk-supply = <&pci_clk_reg>;
+ vdd-supply = <&pci_vdd_reg>;
+
+ pci@1,0 {
+ status = "okay";
+ };
+ };
+
usb@c5000000 {
status = "okay";
nvidia,vbus-gpio = <&gpio 170 0>; /* gpio PV2 */
@@ -357,6 +367,24 @@
regulator-max-microvolt = <1800000>;
regulator-always-on;
};
+
+ pci_clk_reg: regulator@2 {
+ compatible = "regulator-fixed";
+ reg = <2>;
+ regulator-name = "pci_clk";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ };
+
+ pci_vdd_reg: regulator@3 {
+ compatible = "regulator-fixed";
+ reg = <3>;
+ regulator-name = "pci_vdd";
+ regulator-min-microvolt = <1050000>;
+ regulator-max-microvolt = <1050000>;
+ regulator-always-on;
+ };
};

sound {
--
1.8.2

2013-04-03 14:46:07

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 03/12] of/pci: Add of_pci_parse_bus_range() function

This function can be used to parse a bus-range property as specified by
device nodes representing PCI bridges.

Signed-off-by: Thierry Reding <[email protected]>
---
drivers/of/of_pci.c | 25 +++++++++++++++++++++++++
include/linux/of_pci.h | 1 +
2 files changed, 26 insertions(+)

diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c
index 4dd7b9b..42c687a 100644
--- a/drivers/of/of_pci.c
+++ b/drivers/of/of_pci.c
@@ -64,3 +64,28 @@ int of_pci_get_devfn(struct device_node *np)
return (be32_to_cpup(reg) >> 8) & 0xff;
}
EXPORT_SYMBOL_GPL(of_pci_get_devfn);
+
+/**
+ * of_pci_parse_bus_range() - parse the bus-range property of a PCI device
+ * @node: device node
+ * @res: address to a struct resource to return the bus-range
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int of_pci_parse_bus_range(struct device_node *node, struct resource *res)
+{
+ const __be32 *values;
+ int len;
+
+ values = of_get_property(node, "bus-range", &len);
+ if (!values || len < sizeof(*values) * 2)
+ return -EINVAL;
+
+ res->name = node->name;
+ res->start = be32_to_cpup(values++);
+ res->end = be32_to_cpup(values);
+ res->flags = IORESOURCE_BUS;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_pci_parse_bus_range);
diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h
index 91ec484..7a04826 100644
--- a/include/linux/of_pci.h
+++ b/include/linux/of_pci.h
@@ -11,5 +11,6 @@ struct device_node;
struct device_node *of_pci_find_child_device(struct device_node *parent,
unsigned int devfn);
int of_pci_get_devfn(struct device_node *np);
+int of_pci_parse_bus_range(struct device_node *node, struct resource *res);

#endif
--
1.8.2

2013-04-03 14:48:24

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
directory. The motivation is to collect various host controller drivers
in the same location in order to facilitate refactoring.

The Tegra PCIe driver has been largely rewritten, both in order to turn
it into a proper platform driver and to add MSI (based on code by
Krishna Kishore <[email protected]>) as well as device tree support.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- remove Harmony and TrimSlice leftovers
- use symbolic names for interrupt codes
- fix root port reset sequence
- depend on ARCH_TEGRA
- only enable configured root ports
- make regulators mandatory
- fix build with !PCI_MSI
- derive XBAR configuration from nvidia,num-lanes property
- remap configuration space on a per-bus basis

Changes in v3:
- move Tegra30 specific bits into a follow-up patch
- update DT binding to be more spec-conformant
- update for recent OF ranges parser changes
- use new MSI chip infrastructure

.../bindings/pci/nvidia,tegra20-pcie.txt | 123 ++
arch/arm/boot/dts/tegra20.dtsi | 53 +
arch/arm/mach-tegra/Kconfig | 7 +-
arch/arm/mach-tegra/Makefile | 3 -
arch/arm/mach-tegra/board-harmony-pcie.c | 89 --
arch/arm/mach-tegra/board.h | 8 -
arch/arm/mach-tegra/iomap.h | 3 -
arch/arm/mach-tegra/pcie.c | 864 -----------
arch/arm/mach-tegra/tegra.c | 24 -
drivers/pci/Kconfig | 2 +
drivers/pci/Makefile | 3 +
drivers/pci/host/Kconfig | 8 +
drivers/pci/host/Makefile | 1 +
drivers/pci/host/pci-tegra.c | 1560 ++++++++++++++++++++
14 files changed, 1752 insertions(+), 996 deletions(-)
create mode 100644 Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
delete mode 100644 arch/arm/mach-tegra/board-harmony-pcie.c
delete mode 100644 arch/arm/mach-tegra/pcie.c
create mode 100644 drivers/pci/host/Kconfig
create mode 100644 drivers/pci/host/Makefile
create mode 100644 drivers/pci/host/pci-tegra.c

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..55f6248
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
@@ -0,0 +1,123 @@
+NVIDIA Tegra PCIe controller
+
+Required properties:
+- compatible: "nvidia,tegra20-pcie"
+- device_type: must be "pci"
+- reg: physical base address and length of the controller's registers
+- reg-names: list of names to identify entries in the reg property
+- interrupts: the interrupt outputs of the controller
+- interrupt-names: list of names to identify interrupts
+- pex-clk-supply: supply voltage for internal reference clock
+- vdd-supply: power supply for controller (1.05V)
+- bus-range: range of bus numbers associated with this controller
+- #address-cells: address representation for root ports (must be 3)
+ - cell 0 specifies the bus and device numbers of the root port:
+ [23:16]: bus number
+ [15:11]: device number
+ - cell 1 denotes the upper 32 address bits and should be 0
+ - cell 2 contains the lower 32 address bits and is used to translate to the
+ CPU address space
+- #size-cells: size representation for root ports (must be 2)
+- ranges: describes the translation of addresses for root ports
+- clocks: the clock inputs of the controller
+- clock-names: list of names to identify clocks
+
+Root ports are defined as subnodes of the PCIe controller node.
+
+Required properties:
+- device_type: must be "pci"
+- assigned-addresses: address and size of the port configuration registers
+- reg: PCI bus address of the root port
+- #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:
+
+SoC DTSI:
+
+ pcie-controller {
+ compatible = "nvidia,tegra20-pcie";
+ device_type = "pci";
+ reg = <0x80003000 0x00000800 /* PADS registers */
+ 0x80003800 0x00000200 /* AFI registers */
+ 0x90000000 0x10000000>; /* configuration space */
+ reg-names = "pads", "afi", "cs";
+ interrupts = <0 98 0x04 /* controller interrupt */
+ 0 99 0x04>; /* MSI interrupt */
+ interrupt-names = "intr", "msi";
+
+ bus-range = <0x00 0xff>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ranges = <0x82000000 0 0x80000000 0x80000000 0 0x00001000 /* port 0 registers */
+ 0x82000000 0 0x80001000 0x80001000 0 0x00001000 /* port 1 registers */
+ 0x81000000 0 0 0x82000000 0 0x00010000 /* downstream I/O */
+ 0x82000000 0 0xa0000000 0xa0000000 0 0x10000000 /* non-prefetchable memory */
+ 0xc2000000 0 0xb0000000 0xb0000000 0 0x10000000>; /* prefetchable memory */
+
+ clocks = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 74>,
+ <&tegra_car 118>;
+ clock-names = "pex", "afi", "pcie_xclk", "pll_e";
+ status = "disabled";
+
+ pci@1,0 {
+ device_type = "pci";
+ assigned-addresses = <0x82000800 0 0x80000000 0 0x1000>;
+ reg = <0x000800 0 0 0 0>;
+ status = "disabled";
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ranges;
+
+ nvidia,num-lanes = <2>;
+ };
+
+ pci@2,0 {
+ device_type = "pci";
+ assigned-addresses = <0x82001000 0 0x80001000 0 0x1000>;
+ reg = <0x001000 0 0 0 0>;
+ status = "disabled";
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ranges;
+
+ nvidia,num-lanes = <2>;
+ };
+ };
+
+
+Board DTS:
+
+ pcie-controller {
+ status = "okay";
+
+ vdd-supply = <&pci_vdd_reg>;
+ pex-clk-supply = <&pci_clk_reg>;
+
+ /* root port 00:01.0 */
+ pci@1,0 {
+ status = "okay";
+
+ /* bridge 01:00.0 */
+ pci@0,0 {
+ reg = <0x010000 0 0 0 0>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ device_type = "pci";
+
+ /* endpoint 02:00.0 */
+ pci@0,0 {
+ reg = <0x020000 0 0 0 0>;
+ };
+ };
+ };
+ };
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 431a5e0..ec111b7 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -451,6 +451,59 @@
#size-cells = <0>;
};

+ pcie-controller {
+ compatible = "nvidia,tegra20-pcie";
+ device_type = "pci";
+ reg = <0x80003000 0x00000800 /* PADS registers */
+ 0x80003800 0x00000200 /* AFI registers */
+ 0x90000000 0x10000000>; /* configuration space */
+ reg-names = "pads", "afi", "cs";
+ interrupts = <0 98 0x04 /* controller interrupt */
+ 0 99 0x04>; /* MSI interrupt */
+ interrupt-names = "intr", "msi";
+
+ bus-range = <0x00 0xff>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ranges = <0x82000000 0 0x80000000 0x80000000 0 0x00001000 /* port 0 registers */
+ 0x82000000 0 0x80001000 0x80001000 0 0x00001000 /* port 1 registers */
+ 0x81000000 0 0 0x82000000 0 0x00010000 /* downstream I/O */
+ 0x82000000 0 0xa0000000 0xa0000000 0 0x10000000 /* non-prefetchable memory */
+ 0xc2000000 0 0xb0000000 0xb0000000 0 0x10000000>; /* prefetchable memory */
+
+ clocks = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 74>,
+ <&tegra_car 118>;
+ clock-names = "pex", "afi", "pcie_xclk", "pll_e";
+ status = "disabled";
+
+ pci@1,0 {
+ device_type = "pci";
+ assigned-addresses = <0x82000800 0 0x80000000 0 0x1000>;
+ reg = <0x000800 0 0 0 0>;
+ status = "disabled";
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+
+ nvidia,num-lanes = <2>;
+ };
+
+ pci@2,0 {
+ device_type = "pci";
+ assigned-addresses = <0x82001000 0 0x80001000 0 0x1000>;
+ reg = <0x001000 0 0 0 0>;
+ status = "disabled";
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+
+ nvidia,num-lanes = <2>;
+ };
+ };
+
usb@c5000000 {
compatible = "nvidia,tegra20-ehci", "usb-ehci";
reg = <0xc5000000 0x4000>;
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 661ba32..8c9b6d2 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -13,6 +13,8 @@ config ARCH_TEGRA
select SOC_BUS
select SPARSE_IRQ
select USE_OF
+ select MIGHT_HAVE_PCI
+ select ARCH_SUPPORTS_MSI
help
This enables support for NVIDIA Tegra based systems.

@@ -68,11 +70,6 @@ config ARCH_TEGRA_114_SOC
Support for NVIDIA Tegra T114 processor family, based on the
ARM CortexA15MP CPU

-config TEGRA_PCI
- bool "PCI Express support"
- depends on ARCH_TEGRA_2x_SOC
- select PCI
-
config TEGRA_AHB
bool "Enable AHB driver for NVIDIA Tegra SoCs"
default y
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index e40326d..ba89d7c 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -26,13 +26,10 @@ endif
obj-$(CONFIG_SMP) += platsmp.o headsmp.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
obj-$(CONFIG_CPU_FREQ) += cpu-tegra.o
-obj-$(CONFIG_TEGRA_PCI) += pcie.o

obj-$(CONFIG_ARCH_TEGRA_114_SOC) += tegra114_speedo.o
ifeq ($(CONFIG_CPU_IDLE),y)
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += cpuidle-tegra114.o
endif

-obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += board-harmony-pcie.o
-
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += board-paz00.o
diff --git a/arch/arm/mach-tegra/board-harmony-pcie.c b/arch/arm/mach-tegra/board-harmony-pcie.c
deleted file mode 100644
index 035b240..0000000
--- a/arch/arm/mach-tegra/board-harmony-pcie.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * arch/arm/mach-tegra/board-harmony-pcie.c
- *
- * Copyright (C) 2010 CompuLab, Ltd.
- * Mike Rapoport <[email protected]>
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * 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.
- *
- */
-
-#include <linux/kernel.h>
-#include <linux/gpio.h>
-#include <linux/err.h>
-#include <linux/of_gpio.h>
-#include <linux/regulator/consumer.h>
-
-#include <asm/mach-types.h>
-
-#include "board.h"
-
-#ifdef CONFIG_TEGRA_PCI
-
-int __init harmony_pcie_init(void)
-{
- struct device_node *np;
- int en_vdd_1v05;
- struct regulator *regulator = NULL;
- int err;
-
- np = of_find_node_by_path("/regulators/regulator@3");
- if (!np) {
- pr_err("%s: of_find_node_by_path failed\n", __func__);
- return -ENODEV;
- }
-
- en_vdd_1v05 = of_get_named_gpio(np, "gpio", 0);
- if (en_vdd_1v05 < 0) {
- pr_err("%s: of_get_named_gpio failed: %d\n", __func__,
- en_vdd_1v05);
- return en_vdd_1v05;
- }
-
- err = gpio_request(en_vdd_1v05, "EN_VDD_1V05");
- if (err) {
- pr_err("%s: gpio_request failed: %d\n", __func__, err);
- return err;
- }
-
- gpio_direction_output(en_vdd_1v05, 1);
-
- regulator = regulator_get(NULL, "vdd_ldo0,vddio_pex_clk");
- if (IS_ERR(regulator)) {
- err = PTR_ERR(regulator);
- pr_err("%s: regulator_get failed: %d\n", __func__, err);
- goto err_reg;
- }
-
- err = regulator_enable(regulator);
- if (err) {
- pr_err("%s: regulator_enable failed: %d\n", __func__, err);
- goto err_en;
- }
-
- err = tegra_pcie_init(true, true);
- if (err) {
- pr_err("%s: tegra_pcie_init failed: %d\n", __func__, err);
- goto err_pcie;
- }
-
- return 0;
-
-err_pcie:
- regulator_disable(regulator);
-err_en:
- regulator_put(regulator);
-err_reg:
- gpio_free(en_vdd_1v05);
-
- return err;
-}
-
-#endif
diff --git a/arch/arm/mach-tegra/board.h b/arch/arm/mach-tegra/board.h
index 1787327..ea9e773 100644
--- a/arch/arm/mach-tegra/board.h
+++ b/arch/arm/mach-tegra/board.h
@@ -30,7 +30,6 @@ void __init tegra_init_early(void);
void __init tegra_map_common_io(void);
void __init tegra_init_irq(void);
void __init tegra_dt_init_irq(void);
-int __init tegra_pcie_init(bool init_port0, bool init_port1);

void tegra_init_late(void);

@@ -47,13 +46,6 @@ int __init tegra_powergate_debugfs_init(void);
static inline int tegra_powergate_debugfs_init(void) { return 0; }
#endif

-int __init harmony_regulator_init(void);
-#ifdef CONFIG_TEGRA_PCI
-int __init harmony_pcie_init(void);
-#else
-static inline int harmony_pcie_init(void) { return 0; }
-#endif
-
void __init tegra_paz00_wifikill_init(void);

#endif
diff --git a/arch/arm/mach-tegra/iomap.h b/arch/arm/mach-tegra/iomap.h
index 399fbca..d228421 100644
--- a/arch/arm/mach-tegra/iomap.h
+++ b/arch/arm/mach-tegra/iomap.h
@@ -278,9 +278,6 @@
#define IO_APB_VIRT IOMEM(0xFE300000)
#define IO_APB_SIZE SZ_1M

-#define TEGRA_PCIE_BASE 0x80000000
-#define TEGRA_PCIE_IO_BASE (TEGRA_PCIE_BASE + SZ_4M)
-
#define IO_TO_VIRT_BETWEEN(p, st, sz) ((p) >= (st) && (p) < ((st) + (sz)))
#define IO_TO_VIRT_XLATE(p, pst, vst) (((p) - (pst) + (vst)))

diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
deleted file mode 100644
index 6c1989b..0000000
--- a/arch/arm/mach-tegra/pcie.c
+++ /dev/null
@@ -1,864 +0,0 @@
-/*
- * arch/arm/mach-tegra/pci.c
- *
- * PCIe host controller driver for TEGRA(2) SOCs
- *
- * Copyright (c) 2010, CompuLab, Ltd.
- * Author: Mike Rapoport <[email protected]>
- *
- * Based on NVIDIA PCIe driver
- * Copyright (c) 2008-2009, NVIDIA Corporation.
- *
- * Bits taken from arch/arm/mach-dove/pcie.c
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <linux/kernel.h>
-#include <linux/pci.h>
-#include <linux/interrupt.h>
-#include <linux/irq.h>
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/export.h>
-#include <linux/clk/tegra.h>
-#include <linux/tegra-powergate.h>
-
-#include <asm/sizes.h>
-#include <asm/mach/pci.h>
-
-#include "board.h"
-#include "iomap.h"
-#include "pmc.h"
-
-/* Hack - need to parse this from DT */
-#define INT_PCIE_INTR 130
-
-/* register definitions */
-#define AFI_OFFSET 0x3800
-#define PADS_OFFSET 0x3000
-#define RP0_OFFSET 0x0000
-#define RP1_OFFSET 0x1000
-
-#define AFI_AXI_BAR0_SZ 0x00
-#define AFI_AXI_BAR1_SZ 0x04
-#define AFI_AXI_BAR2_SZ 0x08
-#define AFI_AXI_BAR3_SZ 0x0c
-#define AFI_AXI_BAR4_SZ 0x10
-#define AFI_AXI_BAR5_SZ 0x14
-
-#define AFI_AXI_BAR0_START 0x18
-#define AFI_AXI_BAR1_START 0x1c
-#define AFI_AXI_BAR2_START 0x20
-#define AFI_AXI_BAR3_START 0x24
-#define AFI_AXI_BAR4_START 0x28
-#define AFI_AXI_BAR5_START 0x2c
-
-#define AFI_FPCI_BAR0 0x30
-#define AFI_FPCI_BAR1 0x34
-#define AFI_FPCI_BAR2 0x38
-#define AFI_FPCI_BAR3 0x3c
-#define AFI_FPCI_BAR4 0x40
-#define AFI_FPCI_BAR5 0x44
-
-#define AFI_CACHE_BAR0_SZ 0x48
-#define AFI_CACHE_BAR0_ST 0x4c
-#define AFI_CACHE_BAR1_SZ 0x50
-#define AFI_CACHE_BAR1_ST 0x54
-
-#define AFI_MSI_BAR_SZ 0x60
-#define AFI_MSI_FPCI_BAR_ST 0x64
-#define AFI_MSI_AXI_BAR_ST 0x68
-
-#define AFI_CONFIGURATION 0xac
-#define AFI_CONFIGURATION_EN_FPCI (1 << 0)
-
-#define AFI_FPCI_ERROR_MASKS 0xb0
-
-#define AFI_INTR_MASK 0xb4
-#define AFI_INTR_MASK_INT_MASK (1 << 0)
-#define AFI_INTR_MASK_MSI_MASK (1 << 8)
-
-#define AFI_INTR_CODE 0xb8
-#define AFI_INTR_CODE_MASK 0xf
-#define AFI_INTR_MASTER_ABORT 4
-#define AFI_INTR_LEGACY 6
-
-#define AFI_INTR_SIGNATURE 0xbc
-#define AFI_SM_INTR_ENABLE 0xc4
-
-#define AFI_AFI_INTR_ENABLE 0xc8
-#define AFI_INTR_EN_INI_SLVERR (1 << 0)
-#define AFI_INTR_EN_INI_DECERR (1 << 1)
-#define AFI_INTR_EN_TGT_SLVERR (1 << 2)
-#define AFI_INTR_EN_TGT_DECERR (1 << 3)
-#define AFI_INTR_EN_TGT_WRERR (1 << 4)
-#define AFI_INTR_EN_DFPCI_DECERR (1 << 5)
-#define AFI_INTR_EN_AXI_DECERR (1 << 6)
-#define AFI_INTR_EN_FPCI_TIMEOUT (1 << 7)
-
-#define AFI_PCIE_CONFIG 0x0f8
-#define AFI_PCIE_CONFIG_PCIEC0_DISABLE_DEVICE (1 << 1)
-#define AFI_PCIE_CONFIG_PCIEC1_DISABLE_DEVICE (1 << 2)
-#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK (0xf << 20)
-#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE (0x0 << 20)
-#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL (0x1 << 20)
-
-#define AFI_FUSE 0x104
-#define AFI_FUSE_PCIE_T0_GEN2_DIS (1 << 2)
-
-#define AFI_PEX0_CTRL 0x110
-#define AFI_PEX1_CTRL 0x118
-#define AFI_PEX_CTRL_RST (1 << 0)
-#define AFI_PEX_CTRL_REFCLK_EN (1 << 3)
-
-#define RP_VEND_XP 0x00000F00
-#define RP_VEND_XP_DL_UP (1 << 30)
-
-#define RP_LINK_CONTROL_STATUS 0x00000090
-#define RP_LINK_CONTROL_STATUS_LINKSTAT_MASK 0x3fff0000
-
-#define PADS_CTL_SEL 0x0000009C
-
-#define PADS_CTL 0x000000A0
-#define PADS_CTL_IDDQ_1L (1 << 0)
-#define PADS_CTL_TX_DATA_EN_1L (1 << 6)
-#define PADS_CTL_RX_DATA_EN_1L (1 << 10)
-
-#define PADS_PLL_CTL 0x000000B8
-#define PADS_PLL_CTL_RST_B4SM (1 << 1)
-#define PADS_PLL_CTL_LOCKDET (1 << 8)
-#define PADS_PLL_CTL_REFCLK_MASK (0x3 << 16)
-#define PADS_PLL_CTL_REFCLK_INTERNAL_CML (0 << 16)
-#define PADS_PLL_CTL_REFCLK_INTERNAL_CMOS (1 << 16)
-#define PADS_PLL_CTL_REFCLK_EXTERNAL (2 << 16)
-#define PADS_PLL_CTL_TXCLKREF_MASK (0x1 << 20)
-#define PADS_PLL_CTL_TXCLKREF_DIV10 (0 << 20)
-#define PADS_PLL_CTL_TXCLKREF_DIV5 (1 << 20)
-
-/*
- * Tegra2 defines 1GB in the AXI address map for PCIe.
- *
- * That address space is split into different regions, with sizes and
- * offsets as follows:
- *
- * 0x80000000 - 0x80003fff - PCI controller registers
- * 0x80004000 - 0x80103fff - PCI configuration space
- * 0x80104000 - 0x80203fff - PCI extended configuration space
- * 0x80203fff - 0x803fffff - unused
- * 0x80400000 - 0x8040ffff - downstream IO
- * 0x80410000 - 0x8fffffff - unused
- * 0x90000000 - 0x9fffffff - non-prefetchable memory
- * 0xa0000000 - 0xbfffffff - prefetchable memory
- */
-#define PCIE_REGS_SZ SZ_16K
-#define PCIE_CFG_OFF PCIE_REGS_SZ
-#define PCIE_CFG_SZ SZ_1M
-#define PCIE_EXT_CFG_OFF (PCIE_CFG_SZ + PCIE_CFG_OFF)
-#define PCIE_EXT_CFG_SZ SZ_1M
-#define PCIE_IOMAP_SZ (PCIE_REGS_SZ + PCIE_CFG_SZ + PCIE_EXT_CFG_SZ)
-
-#define MEM_BASE_0 (TEGRA_PCIE_BASE + SZ_256M)
-#define MEM_SIZE_0 SZ_128M
-#define MEM_BASE_1 (MEM_BASE_0 + MEM_SIZE_0)
-#define MEM_SIZE_1 SZ_128M
-#define PREFETCH_MEM_BASE_0 (MEM_BASE_1 + MEM_SIZE_1)
-#define PREFETCH_MEM_SIZE_0 SZ_128M
-#define PREFETCH_MEM_BASE_1 (PREFETCH_MEM_BASE_0 + PREFETCH_MEM_SIZE_0)
-#define PREFETCH_MEM_SIZE_1 SZ_128M
-
-#define PCIE_CONF_BUS(b) ((b) << 16)
-#define PCIE_CONF_DEV(d) ((d) << 11)
-#define PCIE_CONF_FUNC(f) ((f) << 8)
-#define PCIE_CONF_REG(r) \
- (((r) & ~0x3) | (((r) < 256) ? PCIE_CFG_OFF : PCIE_EXT_CFG_OFF))
-
-struct tegra_pcie_port {
- int index;
- u8 root_bus_nr;
- void __iomem *base;
-
- bool link_up;
-
- char mem_space_name[16];
- char prefetch_space_name[20];
- struct resource res[2];
-};
-
-struct tegra_pcie_info {
- struct tegra_pcie_port port[2];
- int num_ports;
-
- void __iomem *regs;
- struct resource res_mmio;
-
- struct clk *pex_clk;
- struct clk *afi_clk;
- struct clk *pcie_xclk;
- struct clk *pll_e;
-};
-
-static struct tegra_pcie_info tegra_pcie;
-
-static inline void afi_writel(u32 value, unsigned long offset)
-{
- writel(value, offset + AFI_OFFSET + tegra_pcie.regs);
-}
-
-static inline u32 afi_readl(unsigned long offset)
-{
- return readl(offset + AFI_OFFSET + tegra_pcie.regs);
-}
-
-static inline void pads_writel(u32 value, unsigned long offset)
-{
- writel(value, offset + PADS_OFFSET + tegra_pcie.regs);
-}
-
-static inline u32 pads_readl(unsigned long offset)
-{
- return readl(offset + PADS_OFFSET + tegra_pcie.regs);
-}
-
-static struct tegra_pcie_port *bus_to_port(int bus)
-{
- int i;
-
- for (i = tegra_pcie.num_ports - 1; i >= 0; i--) {
- int rbus = tegra_pcie.port[i].root_bus_nr;
- if (rbus != -1 && rbus == bus)
- break;
- }
-
- return i >= 0 ? tegra_pcie.port + i : NULL;
-}
-
-static int tegra_pcie_read_conf(struct pci_bus *bus, unsigned int devfn,
- int where, int size, u32 *val)
-{
- struct tegra_pcie_port *pp = bus_to_port(bus->number);
- void __iomem *addr;
-
- if (pp) {
- if (devfn != 0) {
- *val = 0xffffffff;
- return PCIBIOS_DEVICE_NOT_FOUND;
- }
-
- addr = pp->base + (where & ~0x3);
- } else {
- addr = tegra_pcie.regs + (PCIE_CONF_BUS(bus->number) +
- PCIE_CONF_DEV(PCI_SLOT(devfn)) +
- PCIE_CONF_FUNC(PCI_FUNC(devfn)) +
- PCIE_CONF_REG(where));
- }
-
- *val = readl(addr);
-
- if (size == 1)
- *val = (*val >> (8 * (where & 3))) & 0xff;
- else if (size == 2)
- *val = (*val >> (8 * (where & 3))) & 0xffff;
-
- return PCIBIOS_SUCCESSFUL;
-}
-
-static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn,
- int where, int size, u32 val)
-{
- struct tegra_pcie_port *pp = bus_to_port(bus->number);
- void __iomem *addr;
-
- u32 mask;
- u32 tmp;
-
- if (pp) {
- if (devfn != 0)
- return PCIBIOS_DEVICE_NOT_FOUND;
-
- addr = pp->base + (where & ~0x3);
- } else {
- addr = tegra_pcie.regs + (PCIE_CONF_BUS(bus->number) +
- PCIE_CONF_DEV(PCI_SLOT(devfn)) +
- PCIE_CONF_FUNC(PCI_FUNC(devfn)) +
- PCIE_CONF_REG(where));
- }
-
- if (size == 4) {
- writel(val, addr);
- return PCIBIOS_SUCCESSFUL;
- }
-
- if (size == 2)
- mask = ~(0xffff << ((where & 0x3) * 8));
- else if (size == 1)
- mask = ~(0xff << ((where & 0x3) * 8));
- else
- return PCIBIOS_BAD_REGISTER_NUMBER;
-
- tmp = readl(addr) & mask;
- tmp |= val << ((where & 0x3) * 8);
- writel(tmp, addr);
-
- return PCIBIOS_SUCCESSFUL;
-}
-
-static struct pci_ops tegra_pcie_ops = {
- .read = tegra_pcie_read_conf,
- .write = tegra_pcie_write_conf,
-};
-
-static void tegra_pcie_fixup_bridge(struct pci_dev *dev)
-{
- u16 reg;
-
- if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) {
- pci_read_config_word(dev, PCI_COMMAND, &reg);
- reg |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
- PCI_COMMAND_MASTER | PCI_COMMAND_SERR);
- pci_write_config_word(dev, PCI_COMMAND, reg);
- }
-}
-DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_fixup_bridge);
-
-/* Tegra PCIE root complex wrongly reports device class */
-static void tegra_pcie_fixup_class(struct pci_dev *dev)
-{
- dev->class = PCI_CLASS_BRIDGE_PCI << 8;
-}
-DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf0, tegra_pcie_fixup_class);
-DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf1, tegra_pcie_fixup_class);
-
-/* Tegra PCIE requires relaxed ordering */
-static void tegra_pcie_relax_enable(struct pci_dev *dev)
-{
- pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN);
-}
-DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_relax_enable);
-
-static int tegra_pcie_setup(int nr, struct pci_sys_data *sys)
-{
- struct tegra_pcie_port *pp;
-
- if (nr >= tegra_pcie.num_ports)
- return 0;
-
- pp = tegra_pcie.port + nr;
- pp->root_bus_nr = sys->busnr;
-
- pci_ioremap_io(nr * SZ_64K, TEGRA_PCIE_IO_BASE);
-
- /*
- * IORESOURCE_MEM
- */
- snprintf(pp->mem_space_name, sizeof(pp->mem_space_name),
- "PCIe %d MEM", pp->index);
- pp->mem_space_name[sizeof(pp->mem_space_name) - 1] = 0;
- pp->res[0].name = pp->mem_space_name;
- if (pp->index == 0) {
- pp->res[0].start = MEM_BASE_0;
- pp->res[0].end = pp->res[0].start + MEM_SIZE_0 - 1;
- } else {
- pp->res[0].start = MEM_BASE_1;
- pp->res[0].end = pp->res[0].start + MEM_SIZE_1 - 1;
- }
- pp->res[0].flags = IORESOURCE_MEM;
- if (request_resource(&iomem_resource, &pp->res[0]))
- panic("Request PCIe Memory resource failed\n");
- pci_add_resource_offset(&sys->resources, &pp->res[0], sys->mem_offset);
-
- /*
- * IORESOURCE_MEM | IORESOURCE_PREFETCH
- */
- snprintf(pp->prefetch_space_name, sizeof(pp->prefetch_space_name),
- "PCIe %d PREFETCH MEM", pp->index);
- pp->prefetch_space_name[sizeof(pp->prefetch_space_name) - 1] = 0;
- pp->res[1].name = pp->prefetch_space_name;
- if (pp->index == 0) {
- pp->res[1].start = PREFETCH_MEM_BASE_0;
- pp->res[1].end = pp->res[1].start + PREFETCH_MEM_SIZE_0 - 1;
- } else {
- pp->res[1].start = PREFETCH_MEM_BASE_1;
- pp->res[1].end = pp->res[1].start + PREFETCH_MEM_SIZE_1 - 1;
- }
- pp->res[1].flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
- if (request_resource(&iomem_resource, &pp->res[1]))
- panic("Request PCIe Prefetch Memory resource failed\n");
- pci_add_resource_offset(&sys->resources, &pp->res[1], sys->mem_offset);
-
- return 1;
-}
-
-static int tegra_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
-{
- return INT_PCIE_INTR;
-}
-
-static struct pci_bus __init *tegra_pcie_scan_bus(int nr,
- struct pci_sys_data *sys)
-{
- struct tegra_pcie_port *pp;
-
- if (nr >= tegra_pcie.num_ports)
- return NULL;
-
- pp = tegra_pcie.port + nr;
- pp->root_bus_nr = sys->busnr;
-
- return pci_scan_root_bus(NULL, sys->busnr, &tegra_pcie_ops, sys,
- &sys->resources);
-}
-
-static struct hw_pci tegra_pcie_hw __initdata = {
- .nr_controllers = 2,
- .setup = tegra_pcie_setup,
- .scan = tegra_pcie_scan_bus,
- .map_irq = tegra_pcie_map_irq,
-};
-
-
-static irqreturn_t tegra_pcie_isr(int irq, void *arg)
-{
- const char *err_msg[] = {
- "Unknown",
- "AXI slave error",
- "AXI decode error",
- "Target abort",
- "Master abort",
- "Invalid write",
- "Response decoding error",
- "AXI response decoding error",
- "Transcation timeout",
- };
-
- u32 code, signature;
-
- code = afi_readl(AFI_INTR_CODE) & AFI_INTR_CODE_MASK;
- signature = afi_readl(AFI_INTR_SIGNATURE);
- afi_writel(0, AFI_INTR_CODE);
-
- if (code == AFI_INTR_LEGACY)
- return IRQ_NONE;
-
- if (code >= ARRAY_SIZE(err_msg))
- code = 0;
-
- /*
- * do not pollute kernel log with master abort reports since they
- * happen a lot during enumeration
- */
- if (code == AFI_INTR_MASTER_ABORT)
- pr_debug("PCIE: %s, signature: %08x\n", err_msg[code], signature);
- else
- pr_err("PCIE: %s, signature: %08x\n", err_msg[code], signature);
-
- return IRQ_HANDLED;
-}
-
-static void tegra_pcie_setup_translations(void)
-{
- u32 fpci_bar;
- u32 size;
- u32 axi_address;
-
- /* Bar 0: config Bar */
- fpci_bar = ((u32)0xfdff << 16);
- size = PCIE_CFG_SZ;
- axi_address = TEGRA_PCIE_BASE + PCIE_CFG_OFF;
- afi_writel(axi_address, AFI_AXI_BAR0_START);
- afi_writel(size >> 12, AFI_AXI_BAR0_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR0);
-
- /* Bar 1: extended config Bar */
- fpci_bar = ((u32)0xfe1 << 20);
- size = PCIE_EXT_CFG_SZ;
- axi_address = TEGRA_PCIE_BASE + PCIE_EXT_CFG_OFF;
- afi_writel(axi_address, AFI_AXI_BAR1_START);
- afi_writel(size >> 12, AFI_AXI_BAR1_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR1);
-
- /* Bar 2: downstream IO bar */
- fpci_bar = ((__u32)0xfdfc << 16);
- size = SZ_128K;
- axi_address = TEGRA_PCIE_IO_BASE;
- afi_writel(axi_address, AFI_AXI_BAR2_START);
- afi_writel(size >> 12, AFI_AXI_BAR2_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR2);
-
- /* Bar 3: prefetchable memory BAR */
- fpci_bar = (((PREFETCH_MEM_BASE_0 >> 12) & 0x0fffffff) << 4) | 0x1;
- size = PREFETCH_MEM_SIZE_0 + PREFETCH_MEM_SIZE_1;
- axi_address = PREFETCH_MEM_BASE_0;
- afi_writel(axi_address, AFI_AXI_BAR3_START);
- afi_writel(size >> 12, AFI_AXI_BAR3_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR3);
-
- /* Bar 4: non prefetchable memory BAR */
- fpci_bar = (((MEM_BASE_0 >> 12) & 0x0FFFFFFF) << 4) | 0x1;
- size = MEM_SIZE_0 + MEM_SIZE_1;
- axi_address = MEM_BASE_0;
- afi_writel(axi_address, AFI_AXI_BAR4_START);
- afi_writel(size >> 12, AFI_AXI_BAR4_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR4);
-
- /* Bar 5: NULL out the remaining BAR as it is not used */
- fpci_bar = 0;
- size = 0;
- axi_address = 0;
- afi_writel(axi_address, AFI_AXI_BAR5_START);
- afi_writel(size >> 12, AFI_AXI_BAR5_SZ);
- afi_writel(fpci_bar, AFI_FPCI_BAR5);
-
- /* map all upstream transactions as uncached */
- afi_writel(PHYS_OFFSET, AFI_CACHE_BAR0_ST);
- afi_writel(0, AFI_CACHE_BAR0_SZ);
- afi_writel(0, AFI_CACHE_BAR1_ST);
- afi_writel(0, AFI_CACHE_BAR1_SZ);
-
- /* No MSI */
- afi_writel(0, AFI_MSI_FPCI_BAR_ST);
- afi_writel(0, AFI_MSI_BAR_SZ);
- afi_writel(0, AFI_MSI_AXI_BAR_ST);
- afi_writel(0, AFI_MSI_BAR_SZ);
-}
-
-static int tegra_pcie_enable_controller(void)
-{
- u32 val, reg;
- int i, timeout;
-
- /* Enable slot clock and pulse the reset signals */
- for (i = 0, reg = AFI_PEX0_CTRL; i < 2; i++, reg += 0x8) {
- val = afi_readl(reg) | AFI_PEX_CTRL_REFCLK_EN;
- afi_writel(val, reg);
- val &= ~AFI_PEX_CTRL_RST;
- afi_writel(val, reg);
-
- val = afi_readl(reg) | AFI_PEX_CTRL_RST;
- afi_writel(val, reg);
- }
-
- /* Enable dual controller and both ports */
- val = afi_readl(AFI_PCIE_CONFIG);
- val &= ~(AFI_PCIE_CONFIG_PCIEC0_DISABLE_DEVICE |
- AFI_PCIE_CONFIG_PCIEC1_DISABLE_DEVICE |
- AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK);
- val |= AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
- afi_writel(val, AFI_PCIE_CONFIG);
-
- val = afi_readl(AFI_FUSE) & ~AFI_FUSE_PCIE_T0_GEN2_DIS;
- afi_writel(val, AFI_FUSE);
-
- /* Initialze internal PHY, enable up to 16 PCIE lanes */
- pads_writel(0x0, PADS_CTL_SEL);
-
- /* override IDDQ to 1 on all 4 lanes */
- val = pads_readl(PADS_CTL) | PADS_CTL_IDDQ_1L;
- pads_writel(val, PADS_CTL);
-
- /*
- * set up PHY PLL inputs select PLLE output as refclock,
- * set TX ref sel to div10 (not div5)
- */
- val = pads_readl(PADS_PLL_CTL);
- val &= ~(PADS_PLL_CTL_REFCLK_MASK | PADS_PLL_CTL_TXCLKREF_MASK);
- val |= (PADS_PLL_CTL_REFCLK_INTERNAL_CML | PADS_PLL_CTL_TXCLKREF_DIV10);
- pads_writel(val, PADS_PLL_CTL);
-
- /* take PLL out of reset */
- val = pads_readl(PADS_PLL_CTL) | PADS_PLL_CTL_RST_B4SM;
- pads_writel(val, PADS_PLL_CTL);
-
- /*
- * Hack, set the clock voltage to the DEFAULT provided by hw folks.
- * This doesn't exist in the documentation
- */
- pads_writel(0xfa5cfa5c, 0xc8);
-
- /* Wait for the PLL to lock */
- timeout = 300;
- do {
- val = pads_readl(PADS_PLL_CTL);
- usleep_range(1000, 1000);
- if (--timeout == 0) {
- pr_err("Tegra PCIe error: timeout waiting for PLL\n");
- return -EBUSY;
- }
- } while (!(val & PADS_PLL_CTL_LOCKDET));
-
- /* turn off IDDQ override */
- val = pads_readl(PADS_CTL) & ~PADS_CTL_IDDQ_1L;
- pads_writel(val, PADS_CTL);
-
- /* enable TX/RX data */
- val = pads_readl(PADS_CTL);
- val |= (PADS_CTL_TX_DATA_EN_1L | PADS_CTL_RX_DATA_EN_1L);
- pads_writel(val, PADS_CTL);
-
- /* Take the PCIe interface module out of reset */
- tegra_periph_reset_deassert(tegra_pcie.pcie_xclk);
-
- /* Finally enable PCIe */
- val = afi_readl(AFI_CONFIGURATION) | AFI_CONFIGURATION_EN_FPCI;
- afi_writel(val, AFI_CONFIGURATION);
-
- val = (AFI_INTR_EN_INI_SLVERR | AFI_INTR_EN_INI_DECERR |
- AFI_INTR_EN_TGT_SLVERR | AFI_INTR_EN_TGT_DECERR |
- AFI_INTR_EN_TGT_WRERR | AFI_INTR_EN_DFPCI_DECERR);
- afi_writel(val, AFI_AFI_INTR_ENABLE);
- afi_writel(0xffffffff, AFI_SM_INTR_ENABLE);
-
- /* FIXME: No MSI for now, only INT */
- afi_writel(AFI_INTR_MASK_INT_MASK, AFI_INTR_MASK);
-
- /* Disable all execptions */
- afi_writel(0, AFI_FPCI_ERROR_MASKS);
-
- return 0;
-}
-
-static void tegra_pcie_power_off(void)
-{
- tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
- tegra_periph_reset_assert(tegra_pcie.afi_clk);
- tegra_periph_reset_assert(tegra_pcie.pex_clk);
-
- tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
- tegra_pmc_pcie_xclk_clamp(true);
-}
-
-static int tegra_pcie_power_regate(void)
-{
- int err;
-
- tegra_pcie_power_off();
-
- tegra_pmc_pcie_xclk_clamp(true);
-
- tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
- tegra_periph_reset_assert(tegra_pcie.afi_clk);
-
- err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
- tegra_pcie.pex_clk);
- if (err) {
- pr_err("PCIE: powerup sequence failed: %d\n", err);
- return err;
- }
-
- tegra_periph_reset_deassert(tegra_pcie.afi_clk);
-
- tegra_pmc_pcie_xclk_clamp(false);
-
- clk_prepare_enable(tegra_pcie.afi_clk);
- clk_prepare_enable(tegra_pcie.pex_clk);
- return clk_prepare_enable(tegra_pcie.pll_e);
-}
-
-static int tegra_pcie_clocks_get(void)
-{
- int err;
-
- tegra_pcie.pex_clk = clk_get(NULL, "pex");
- if (IS_ERR(tegra_pcie.pex_clk))
- return PTR_ERR(tegra_pcie.pex_clk);
-
- tegra_pcie.afi_clk = clk_get(NULL, "afi");
- if (IS_ERR(tegra_pcie.afi_clk)) {
- err = PTR_ERR(tegra_pcie.afi_clk);
- goto err_afi_clk;
- }
-
- tegra_pcie.pcie_xclk = clk_get(NULL, "pcie_xclk");
- if (IS_ERR(tegra_pcie.pcie_xclk)) {
- err = PTR_ERR(tegra_pcie.pcie_xclk);
- goto err_pcie_xclk;
- }
-
- tegra_pcie.pll_e = clk_get_sys(NULL, "pll_e");
- if (IS_ERR(tegra_pcie.pll_e)) {
- err = PTR_ERR(tegra_pcie.pll_e);
- goto err_pll_e;
- }
-
- return 0;
-
-err_pll_e:
- clk_put(tegra_pcie.pcie_xclk);
-err_pcie_xclk:
- clk_put(tegra_pcie.afi_clk);
-err_afi_clk:
- clk_put(tegra_pcie.pex_clk);
-
- return err;
-}
-
-static void tegra_pcie_clocks_put(void)
-{
- clk_put(tegra_pcie.pll_e);
- clk_put(tegra_pcie.pcie_xclk);
- clk_put(tegra_pcie.afi_clk);
- clk_put(tegra_pcie.pex_clk);
-}
-
-static int __init tegra_pcie_get_resources(void)
-{
- int err;
-
- err = tegra_pcie_clocks_get();
- if (err) {
- pr_err("PCIE: failed to get clocks: %d\n", err);
- return err;
- }
-
- err = tegra_pcie_power_regate();
- if (err) {
- pr_err("PCIE: failed to power up: %d\n", err);
- goto err_pwr_on;
- }
-
- tegra_pcie.regs = ioremap_nocache(TEGRA_PCIE_BASE, PCIE_IOMAP_SZ);
- if (tegra_pcie.regs == NULL) {
- pr_err("PCIE: Failed to map PCI/AFI registers\n");
- err = -ENOMEM;
- goto err_map_reg;
- }
-
- err = request_irq(INT_PCIE_INTR, tegra_pcie_isr,
- IRQF_SHARED, "PCIE", &tegra_pcie);
- if (err) {
- pr_err("PCIE: Failed to register IRQ: %d\n", err);
- goto err_req_io;
- }
- set_irq_flags(INT_PCIE_INTR, IRQF_VALID);
-
- return 0;
-
-err_req_io:
- iounmap(tegra_pcie.regs);
-err_map_reg:
- tegra_pcie_power_off();
-err_pwr_on:
- tegra_pcie_clocks_put();
-
- return err;
-}
-
-/*
- * FIXME: If there are no PCIe cards attached, then calling this function
- * can result in the increase of the bootup time as there are big timeout
- * loops.
- */
-#define TEGRA_PCIE_LINKUP_TIMEOUT 200 /* up to 1.2 seconds */
-static bool tegra_pcie_check_link(struct tegra_pcie_port *pp, int idx,
- u32 reset_reg)
-{
- u32 reg;
- int retries = 3;
- int timeout;
-
- do {
- timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
- while (timeout) {
- reg = readl(pp->base + RP_VEND_XP);
-
- if (reg & RP_VEND_XP_DL_UP)
- break;
-
- mdelay(1);
- timeout--;
- }
-
- if (!timeout) {
- pr_err("PCIE: port %d: link down, retrying\n", idx);
- goto retry;
- }
-
- timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
- while (timeout) {
- reg = readl(pp->base + RP_LINK_CONTROL_STATUS);
-
- if (reg & 0x20000000)
- return true;
-
- mdelay(1);
- timeout--;
- }
-
-retry:
- /* Pulse the PEX reset */
- reg = afi_readl(reset_reg) | AFI_PEX_CTRL_RST;
- afi_writel(reg, reset_reg);
- mdelay(1);
- reg = afi_readl(reset_reg) & ~AFI_PEX_CTRL_RST;
- afi_writel(reg, reset_reg);
-
- retries--;
- } while (retries);
-
- return false;
-}
-
-static void __init tegra_pcie_add_port(int index, u32 offset, u32 reset_reg)
-{
- struct tegra_pcie_port *pp;
-
- pp = tegra_pcie.port + tegra_pcie.num_ports;
-
- pp->index = -1;
- pp->base = tegra_pcie.regs + offset;
- pp->link_up = tegra_pcie_check_link(pp, index, reset_reg);
-
- if (!pp->link_up) {
- pp->base = NULL;
- printk(KERN_INFO "PCIE: port %d: link down, ignoring\n", index);
- return;
- }
-
- tegra_pcie.num_ports++;
- pp->index = index;
- pp->root_bus_nr = -1;
- memset(pp->res, 0, sizeof(pp->res));
-}
-
-int __init tegra_pcie_init(bool init_port0, bool init_port1)
-{
- int err;
-
- if (!(init_port0 || init_port1))
- return -ENODEV;
-
- pcibios_min_mem = 0;
-
- err = tegra_pcie_get_resources();
- if (err)
- return err;
-
- err = tegra_pcie_enable_controller();
- if (err)
- return err;
-
- /* setup the AFI address translations */
- tegra_pcie_setup_translations();
-
- if (init_port0)
- tegra_pcie_add_port(0, RP0_OFFSET, AFI_PEX0_CTRL);
-
- if (init_port1)
- tegra_pcie_add_port(1, RP1_OFFSET, AFI_PEX1_CTRL);
-
- pci_common_init(&tegra_pcie_hw);
-
- return 0;
-}
diff --git a/arch/arm/mach-tegra/tegra.c b/arch/arm/mach-tegra/tegra.c
index 9cf1ab1..b339c08 100644
--- a/arch/arm/mach-tegra/tegra.c
+++ b/arch/arm/mach-tegra/tegra.c
@@ -113,28 +113,6 @@ out:
tegra20_auxdata_lookup, parent);
}

-static void __init trimslice_init(void)
-{
-#ifdef CONFIG_TEGRA_PCI
- int ret;
-
- ret = tegra_pcie_init(true, true);
- if (ret)
- pr_err("tegra_pci_init() failed: %d\n", ret);
-#endif
-}
-
-static void __init harmony_init(void)
-{
-#ifdef CONFIG_TEGRA_PCI
- int ret;
-
- ret = harmony_pcie_init();
- if (ret)
- pr_err("harmony_pcie_init() failed: %d\n", ret);
-#endif
-}
-
static void __init paz00_init(void)
{
if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC))
@@ -145,8 +123,6 @@ static struct {
char *machine;
void (*init)(void);
} board_init_funcs[] = {
- { "compulab,trimslice", trimslice_init },
- { "nvidia,harmony", harmony_init },
{ "compal,paz00", paz00_init },
};

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 6d51aa6..ac45398 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -119,3 +119,5 @@ config PCI_IOAPIC
config PCI_LABEL
def_bool y if (DMI || ACPI)
select NLS
+
+source "drivers/pci/host/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 0c3efcf..6ebf5bf 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -67,3 +67,6 @@ obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_OF) += of.o

ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG
+
+# PCI host controller drivers
+obj-y += host/
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
new file mode 100644
index 0000000..b818b24
--- /dev/null
+++ b/drivers/pci/host/Kconfig
@@ -0,0 +1,8 @@
+menu "PCI host controller drivers"
+ depends on PCI
+
+config PCI_TEGRA
+ bool "NVIDIA Tegra PCIe controller"
+ depends on ARCH_TEGRA
+
+endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
new file mode 100644
index 0000000..bf3adff
--- /dev/null
+++ b/drivers/pci/host/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
new file mode 100644
index 0000000..6e17fc2
--- /dev/null
+++ b/drivers/pci/host/pci-tegra.c
@@ -0,0 +1,1560 @@
+/*
+ * PCIe host controller driver for TEGRA(2) SOCs
+ *
+ * Copyright (c) 2010, CompuLab, Ltd.
+ * Author: Mike Rapoport <[email protected]>
+ *
+ * Based on NVIDIA PCIe driver
+ * Copyright (c) 2008-2009, NVIDIA Corporation.
+ *
+ * Bits taken from arch/arm/mach-dove/pcie.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/clk.h>
+#include <linux/clk/tegra.h>
+#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/tegra-pmc.h>
+#include <linux/tegra-powergate.h>
+#include <linux/vmalloc.h>
+#include <linux/regulator/consumer.h>
+
+#include <asm/sizes.h>
+#include <asm/mach/irq.h>
+#include <asm/mach/map.h>
+#include <asm/mach/pci.h>
+
+#define INT_PCI_MSI_NR (8 * 32)
+#define TEGRA_MAX_PORTS 2
+
+/* register definitions */
+
+#define AFI_AXI_BAR0_SZ 0x00
+#define AFI_AXI_BAR1_SZ 0x04
+#define AFI_AXI_BAR2_SZ 0x08
+#define AFI_AXI_BAR3_SZ 0x0c
+#define AFI_AXI_BAR4_SZ 0x10
+#define AFI_AXI_BAR5_SZ 0x14
+
+#define AFI_AXI_BAR0_START 0x18
+#define AFI_AXI_BAR1_START 0x1c
+#define AFI_AXI_BAR2_START 0x20
+#define AFI_AXI_BAR3_START 0x24
+#define AFI_AXI_BAR4_START 0x28
+#define AFI_AXI_BAR5_START 0x2c
+
+#define AFI_FPCI_BAR0 0x30
+#define AFI_FPCI_BAR1 0x34
+#define AFI_FPCI_BAR2 0x38
+#define AFI_FPCI_BAR3 0x3c
+#define AFI_FPCI_BAR4 0x40
+#define AFI_FPCI_BAR5 0x44
+
+#define AFI_CACHE_BAR0_SZ 0x48
+#define AFI_CACHE_BAR0_ST 0x4c
+#define AFI_CACHE_BAR1_SZ 0x50
+#define AFI_CACHE_BAR1_ST 0x54
+
+#define AFI_MSI_BAR_SZ 0x60
+#define AFI_MSI_FPCI_BAR_ST 0x64
+#define AFI_MSI_AXI_BAR_ST 0x68
+
+#define AFI_MSI_VEC0 0x6c
+#define AFI_MSI_VEC1 0x70
+#define AFI_MSI_VEC2 0x74
+#define AFI_MSI_VEC3 0x78
+#define AFI_MSI_VEC4 0x7c
+#define AFI_MSI_VEC5 0x80
+#define AFI_MSI_VEC6 0x84
+#define AFI_MSI_VEC7 0x88
+
+#define AFI_MSI_EN_VEC0 0x8c
+#define AFI_MSI_EN_VEC1 0x90
+#define AFI_MSI_EN_VEC2 0x94
+#define AFI_MSI_EN_VEC3 0x98
+#define AFI_MSI_EN_VEC4 0x9c
+#define AFI_MSI_EN_VEC5 0xa0
+#define AFI_MSI_EN_VEC6 0xa4
+#define AFI_MSI_EN_VEC7 0xa8
+
+#define AFI_CONFIGURATION 0xac
+#define AFI_CONFIGURATION_EN_FPCI (1 << 0)
+
+#define AFI_FPCI_ERROR_MASKS 0xb0
+
+#define AFI_INTR_MASK 0xb4
+#define AFI_INTR_MASK_INT_MASK (1 << 0)
+#define AFI_INTR_MASK_MSI_MASK (1 << 8)
+
+#define AFI_INTR_CODE 0xb8
+#define AFI_INTR_CODE_MASK 0xf
+#define AFI_INTR_AXI_SLAVE_ERROR 1
+#define AFI_INTR_AXI_DECODE_ERROR 2
+#define AFI_INTR_TARGET_ABORT 3
+#define AFI_INTR_MASTER_ABORT 4
+#define AFI_INTR_INVALID_WRITE 5
+#define AFI_INTR_LEGACY 6
+#define AFI_INTR_FPCI_DECODE_ERROR 7
+
+#define AFI_INTR_SIGNATURE 0xbc
+#define AFI_UPPER_FPCI_ADDRESS 0xc0
+#define AFI_SM_INTR_ENABLE 0xc4
+#define AFI_SM_INTR_INTA_ASSERT (1 << 0)
+#define AFI_SM_INTR_INTB_ASSERT (1 << 1)
+#define AFI_SM_INTR_INTC_ASSERT (1 << 2)
+#define AFI_SM_INTR_INTD_ASSERT (1 << 3)
+#define AFI_SM_INTR_INTA_DEASSERT (1 << 4)
+#define AFI_SM_INTR_INTB_DEASSERT (1 << 5)
+#define AFI_SM_INTR_INTC_DEASSERT (1 << 6)
+#define AFI_SM_INTR_INTD_DEASSERT (1 << 7)
+
+#define AFI_AFI_INTR_ENABLE 0xc8
+#define AFI_INTR_EN_INI_SLVERR (1 << 0)
+#define AFI_INTR_EN_INI_DECERR (1 << 1)
+#define AFI_INTR_EN_TGT_SLVERR (1 << 2)
+#define AFI_INTR_EN_TGT_DECERR (1 << 3)
+#define AFI_INTR_EN_TGT_WRERR (1 << 4)
+#define AFI_INTR_EN_DFPCI_DECERR (1 << 5)
+#define AFI_INTR_EN_AXI_DECERR (1 << 6)
+#define AFI_INTR_EN_FPCI_TIMEOUT (1 << 7)
+
+#define AFI_PCIE_CONFIG 0x0f8
+#define AFI_PCIE_CONFIG_PCIE_DISABLE(x) (1 << ((x) + 1))
+#define AFI_PCIE_CONFIG_PCIE_DISABLE_ALL 0xe
+#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK (0xf << 20)
+#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE (0x0 << 20)
+#define AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL (0x1 << 20)
+
+#define AFI_FUSE 0x104
+#define AFI_FUSE_PCIE_T0_GEN2_DIS (1 << 2)
+
+#define AFI_PEX0_CTRL 0x110
+#define AFI_PEX1_CTRL 0x118
+#define AFI_PEX_CTRL_RST (1 << 0)
+#define AFI_PEX_CTRL_REFCLK_EN (1 << 3)
+
+#define RP_VEND_XP 0x00000F00
+#define RP_VEND_XP_DL_UP (1 << 30)
+
+#define RP_LINK_CONTROL_STATUS 0x00000090
+#define RP_LINK_CONTROL_STATUS_LINKSTAT_MASK 0x3fff0000
+
+#define PADS_CTL_SEL 0x0000009C
+
+#define PADS_CTL 0x000000A0
+#define PADS_CTL_IDDQ_1L (1 << 0)
+#define PADS_CTL_TX_DATA_EN_1L (1 << 6)
+#define PADS_CTL_RX_DATA_EN_1L (1 << 10)
+
+#define PADS_PLL_CTL 0x000000B8
+#define PADS_PLL_CTL_RST_B4SM (1 << 1)
+#define PADS_PLL_CTL_LOCKDET (1 << 8)
+#define PADS_PLL_CTL_REFCLK_MASK (0x3 << 16)
+#define PADS_PLL_CTL_REFCLK_INTERNAL_CML (0 << 16)
+#define PADS_PLL_CTL_REFCLK_INTERNAL_CMOS (1 << 16)
+#define PADS_PLL_CTL_REFCLK_EXTERNAL (2 << 16)
+#define PADS_PLL_CTL_TXCLKREF_MASK (0x1 << 20)
+#define PADS_PLL_CTL_TXCLKREF_DIV10 (0 << 20)
+#define PADS_PLL_CTL_TXCLKREF_DIV5 (1 << 20)
+
+struct tegra_msi {
+ DECLARE_BITMAP(used, INT_PCI_MSI_NR);
+ struct irq_domain *domain;
+ struct msi_chip chip;
+ unsigned long pages;
+ struct mutex lock;
+ int irq;
+};
+
+static inline struct tegra_msi *to_tegra_msi(struct msi_chip *chip)
+{
+ return container_of(chip, struct tegra_msi, chip);
+}
+
+struct tegra_pcie {
+ struct device *dev;
+
+ void __iomem *pads;
+ void __iomem *afi;
+ int irq;
+
+ struct list_head busses;
+ struct resource *cs;
+
+ struct resource io;
+ struct resource mem;
+ struct resource prefetch;
+ struct resource busn;
+
+ struct clk *pex_clk;
+ struct clk *afi_clk;
+ struct clk *pcie_xclk;
+ struct clk *pll_e;
+
+ struct tegra_msi msi;
+
+ struct list_head ports;
+ unsigned int num_ports;
+ u32 xbar_config;
+
+ struct regulator *pex_clk_supply;
+ struct regulator *vdd_supply;
+};
+
+struct tegra_pcie_port {
+ struct tegra_pcie *pcie;
+ struct list_head list;
+ struct resource regs;
+ void __iomem *base;
+ unsigned int index;
+ unsigned int lanes;
+};
+
+struct tegra_pcie_bus {
+ struct vm_struct *area;
+ struct list_head list;
+ unsigned int nr;
+};
+
+static inline struct tegra_pcie *sys_to_pcie(struct pci_sys_data *sys)
+{
+ return sys->private_data;
+}
+
+static inline void afi_writel(struct tegra_pcie *pcie, u32 value,
+ unsigned long offset)
+{
+ writel(value, pcie->afi + offset);
+}
+
+static inline u32 afi_readl(struct tegra_pcie *pcie, unsigned long offset)
+{
+ return readl(pcie->afi + offset);
+}
+
+static inline void pads_writel(struct tegra_pcie *pcie, u32 value,
+ unsigned long offset)
+{
+ writel(value, pcie->pads + offset);
+}
+
+static inline u32 pads_readl(struct tegra_pcie *pcie, unsigned long offset)
+{
+ return readl(pcie->pads + offset);
+}
+
+/*
+ * The configuration space mapping on Tegra is somewhat similar to the ECAM
+ * defined by PCIe. However it deviates a bit in how the 4 bits for extended
+ * register accesses are mapped:
+ *
+ * [27:24] extended register number
+ * [23:16] bus number
+ * [15:11] device number
+ * [10: 8] function number
+ * [ 7: 0] register number
+ *
+ * Mapping the whole extended configuration space would required 256 MiB of
+ * virtual address space, only a small part of which will actually be used.
+ * To work around this, a 1 MiB of virtual addresses are allocated per bus
+ * when the bus is first accessed. When the physical range is mapped, the
+ * the bus number bits are hidden so that the extended register number bits
+ * appear as bits [19:16]. Therefore the virtual mapping looks like this:
+ *
+ * [19:16] extended register number
+ * [15:11] device number
+ * [10: 8] function number
+ * [ 7: 0] register number
+ *
+ * This is achieved by stitching together 16 chunks of 64 KiB of physical
+ * address space via the MMU.
+ */
+static unsigned long tegra_pcie_conf_offset(unsigned int devfn, int where)
+{
+ return ((where & 0xf00) << 8) | (PCI_SLOT(devfn) << 11) |
+ (PCI_FUNC(devfn) << 8) | (where & 0xfc);
+}
+
+static struct tegra_pcie_bus *tegra_pcie_bus_alloc(struct tegra_pcie *pcie,
+ unsigned int busnr)
+{
+ pgprot_t prot = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_XN |
+ L_PTE_MT_DEV_SHARED | L_PTE_SHARED;
+ phys_addr_t cs = pcie->cs->start;
+ struct tegra_pcie_bus *bus;
+ unsigned int i;
+ int err;
+
+ bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+ if (!bus)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&bus->list);
+ bus->nr = busnr;
+
+ /* allocate 1 MiB of virtual addresses */
+ bus->area = get_vm_area(SZ_1M, VM_IOREMAP);
+ if (!bus->area) {
+ err = -ENOMEM;
+ goto free;
+ }
+
+ /* map each of the 16 chunks of 64 KiB each */
+ for (i = 0; i < 16; i++) {
+ unsigned long virt = (unsigned long)bus->area->addr +
+ i * SZ_64K;
+ phys_addr_t phys = cs + i * SZ_1M + busnr * SZ_64K;
+
+ err = ioremap_page_range(virt, virt + SZ_64K, phys, prot);
+ if (err < 0) {
+ dev_err(pcie->dev, "ioremap_page_range() failed: %d\n",
+ err);
+ goto unmap;
+ }
+ }
+
+ return bus;
+
+unmap:
+ vunmap(bus->area->addr);
+free:
+ kfree(bus);
+ return ERR_PTR(err);
+}
+
+/*
+ * Look up a virtual address mapping for the specified bus number. If no such
+ * mapping existis, try to create one.
+ */
+static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie,
+ unsigned int busnr)
+{
+ struct tegra_pcie_bus *bus;
+
+ list_for_each_entry(bus, &pcie->busses, list)
+ if (bus->nr == busnr)
+ return bus->area->addr;
+
+ bus = tegra_pcie_bus_alloc(pcie, busnr);
+ if (IS_ERR(bus))
+ return NULL;
+
+ list_add_tail(&bus->list, &pcie->busses);
+
+ return bus->area->addr;
+}
+
+static int tegra_pcie_read_conf(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 *value)
+{
+ struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata);
+ void __iomem *addr = NULL;
+
+ if (bus->number == 0) {
+ unsigned int slot = PCI_SLOT(devfn);
+ struct tegra_pcie_port *port;
+
+ list_for_each_entry(port, &pcie->ports, list) {
+ if (port->index + 1 == slot) {
+ addr = port->base + (where & ~3);
+ break;
+ }
+ }
+ } else {
+ addr = tegra_pcie_bus_map(pcie, bus->number);
+ if (!addr) {
+ dev_err(pcie->dev,
+ "failed to map cfg. space for bus %u\n",
+ bus->number);
+ *value = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ addr += tegra_pcie_conf_offset(devfn, where);
+ }
+
+ if (!addr) {
+ *value = 0xffffffff;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ *value = readl(addr);
+
+ if (size == 1)
+ *value = (*value >> (8 * (where & 3))) & 0xff;
+ else if (size == 2)
+ *value = (*value >> (8 * (where & 3))) & 0xffff;
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 value)
+{
+ struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata);
+ void __iomem *addr = NULL;
+ u32 mask, tmp;
+
+ if (bus->number == 0) {
+ unsigned int slot = PCI_SLOT(devfn);
+ struct tegra_pcie_port *port;
+
+ list_for_each_entry(port, &pcie->ports, list) {
+ if (port->index + 1 == slot) {
+ addr = port->base + (where & ~3);
+ break;
+ }
+ }
+ } else {
+ addr = tegra_pcie_bus_map(pcie, bus->number);
+ if (!addr) {
+ dev_err(pcie->dev,
+ "failed to map cfg. space for bus %u\n",
+ bus->number);
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ addr += tegra_pcie_conf_offset(devfn, where);
+ }
+
+ if (size == 4) {
+ writel(value, addr);
+ return PCIBIOS_SUCCESSFUL;
+ }
+
+ if (size == 2)
+ mask = ~(0xffff << ((where & 0x3) * 8));
+ else if (size == 1)
+ mask = ~(0xff << ((where & 0x3) * 8));
+ else
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+
+ tmp = readl(addr) & mask;
+ tmp |= value << ((where & 0x3) * 8);
+ writel(tmp, addr);
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops tegra_pcie_ops = {
+ .read = tegra_pcie_read_conf,
+ .write = tegra_pcie_write_conf,
+};
+
+static unsigned long tegra_pcie_port_get_pex_ctrl(struct tegra_pcie_port *port)
+{
+ unsigned long ret = 0;
+
+ switch (port->index) {
+ case 0:
+ ret = AFI_PEX0_CTRL;
+ break;
+
+ case 1:
+ ret = AFI_PEX1_CTRL;
+ break;
+ }
+
+ return ret;
+}
+
+static void tegra_pcie_port_reset(struct tegra_pcie_port *port)
+{
+ unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+ unsigned long value;
+
+ /* pulse reset signal */
+ value = afi_readl(port->pcie, ctrl);
+ value &= ~AFI_PEX_CTRL_RST;
+ afi_writel(port->pcie, value, ctrl);
+
+ usleep_range(1000, 1000);
+
+ value = afi_readl(port->pcie, ctrl);
+ value |= AFI_PEX_CTRL_RST;
+ afi_writel(port->pcie, value, ctrl);
+}
+
+static void tegra_pcie_port_enable(struct tegra_pcie_port *port)
+{
+ unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+ unsigned long value;
+
+ /* enable reference clock */
+ value = afi_readl(port->pcie, ctrl);
+ value |= AFI_PEX_CTRL_REFCLK_EN;
+ afi_writel(port->pcie, value, ctrl);
+
+ tegra_pcie_port_reset(port);
+}
+
+static void tegra_pcie_port_disable(struct tegra_pcie_port *port)
+{
+ unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
+ unsigned long value;
+
+ /* assert port reset */
+ value = afi_readl(port->pcie, ctrl);
+ value &= ~AFI_PEX_CTRL_RST;
+ afi_writel(port->pcie, value, ctrl);
+
+ /* disable reference clock */
+ value = afi_readl(port->pcie, ctrl);
+ value &= ~AFI_PEX_CTRL_REFCLK_EN;
+ afi_writel(port->pcie, value, ctrl);
+}
+
+static void tegra_pcie_port_free(struct tegra_pcie_port *port)
+{
+ struct tegra_pcie *pcie = port->pcie;
+
+ devm_iounmap(pcie->dev, port->base);
+ devm_release_mem_region(pcie->dev, port->regs.start,
+ resource_size(&port->regs));
+ list_del(&port->list);
+ devm_kfree(pcie->dev, port);
+}
+
+static void tegra_pcie_fixup_bridge(struct pci_dev *dev)
+{
+ u16 reg;
+
+ if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) {
+ pci_read_config_word(dev, PCI_COMMAND, &reg);
+ reg |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SERR);
+ pci_write_config_word(dev, PCI_COMMAND, reg);
+ }
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_fixup_bridge);
+
+/* Tegra PCIE root complex wrongly reports device class */
+static void tegra_pcie_fixup_class(struct pci_dev *dev)
+{
+ dev->class = PCI_CLASS_BRIDGE_PCI << 8;
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf0, tegra_pcie_fixup_class);
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NVIDIA, 0x0bf1, tegra_pcie_fixup_class);
+
+/* Tegra PCIE requires relaxed ordering */
+static void tegra_pcie_relax_enable(struct pci_dev *dev)
+{
+ pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN);
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, tegra_pcie_relax_enable);
+
+static int tegra_pcie_setup(int nr, struct pci_sys_data *sys)
+{
+ struct tegra_pcie *pcie = sys_to_pcie(sys);
+
+ pci_add_resource_offset(&sys->resources, &pcie->mem, sys->mem_offset);
+ pci_add_resource_offset(&sys->resources, &pcie->prefetch, sys->mem_offset);
+ pci_add_resource(&sys->resources, &pcie->busn);
+
+ pci_ioremap_io(nr * SZ_64K, pcie->io.start);
+
+ return 1;
+}
+
+static int tegra_pcie_map_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
+{
+ struct tegra_pcie *pcie = sys_to_pcie(pdev->bus->sysdata);
+
+ return pcie->irq;
+}
+
+static struct pci_bus *tegra_pcie_scan_bus(int nr, struct pci_sys_data *sys)
+{
+ struct tegra_pcie *pcie = sys_to_pcie(sys);
+ struct pci_host_bridge_window *window;
+ struct pci_bus *bus;
+ bool found = false;
+
+ list_for_each_entry(window, &sys->resources, list) {
+ if (window->res->flags & IORESOURCE_BUS) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ dev_err(pcie->dev, "no bus resource found!\n");
+
+ bus = pci_create_root_bus(pcie->dev, sys->busnr, &tegra_pcie_ops,
+ sys, &sys->resources);
+ if (!bus)
+ return NULL;
+
+ if (IS_ENABLED(CONFIG_PCI_MSI))
+ bus->msi = &pcie->msi.chip;
+
+ pci_scan_child_bus(bus);
+
+ return bus;
+}
+
+static irqreturn_t tegra_pcie_isr(int irq, void *arg)
+{
+ const char *err_msg[] = {
+ "Unknown",
+ "AXI slave error",
+ "AXI decode error",
+ "Target abort",
+ "Master abort",
+ "Invalid write",
+ "Response decoding error",
+ "AXI response decoding error",
+ "Transaction timeout",
+ };
+ struct tegra_pcie *pcie = arg;
+ u32 code, signature;
+
+ code = afi_readl(pcie, AFI_INTR_CODE) & AFI_INTR_CODE_MASK;
+ signature = afi_readl(pcie, AFI_INTR_SIGNATURE);
+ afi_writel(pcie, 0, AFI_INTR_CODE);
+
+ if (code == AFI_INTR_LEGACY)
+ return IRQ_NONE;
+
+ if (code >= ARRAY_SIZE(err_msg))
+ code = 0;
+
+ /*
+ * do not pollute kernel log with master abort reports since they
+ * happen a lot during enumeration
+ */
+ if (code == AFI_INTR_MASTER_ABORT)
+ dev_dbg(pcie->dev, "%s, signature: %08x\n", err_msg[code],
+ signature);
+ else
+ dev_err(pcie->dev, "%s, signature: %08x\n", err_msg[code],
+ signature);
+
+ if (code == AFI_INTR_TARGET_ABORT || code == AFI_INTR_MASTER_ABORT ||
+ code == AFI_INTR_FPCI_DECODE_ERROR) {
+ u32 fpci = afi_readl(pcie, AFI_UPPER_FPCI_ADDRESS) & 0xff;
+ u64 address = (u64)fpci << 32 | (signature & 0xfffffffc);
+
+ if (code == AFI_INTR_MASTER_ABORT)
+ dev_dbg(pcie->dev, " FPCI address: %10llx\n", address);
+ else
+ dev_err(pcie->dev, " FPCI address: %10llx\n", address);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * FPCI map is as follows:
+ * - 0xfdfc000000: I/O space
+ * - 0xfdfe000000: type 0 configuration space
+ * - 0xfdff000000: type 1 configuration space
+ * - 0xfe00000000: type 0 extended configuration space
+ * - 0xfe10000000: type 1 extended configuration space
+ */
+static void tegra_pcie_setup_translations(struct tegra_pcie *pcie)
+{
+ u32 fpci_bar, size, axi_address;
+
+ /* Bar 0: type 1 extended configuration space */
+ fpci_bar = 0xfe100000;
+ size = resource_size(pcie->cs);
+ axi_address = pcie->cs->start;
+ afi_writel(pcie, axi_address, AFI_AXI_BAR0_START);
+ afi_writel(pcie, size >> 12, AFI_AXI_BAR0_SZ);
+ afi_writel(pcie, fpci_bar, AFI_FPCI_BAR0);
+
+ /* Bar 1: downstream IO bar */
+ fpci_bar = 0xfdfc0000;
+ size = resource_size(&pcie->io);
+ axi_address = pcie->io.start;
+ afi_writel(pcie, axi_address, AFI_AXI_BAR1_START);
+ afi_writel(pcie, size >> 12, AFI_AXI_BAR1_SZ);
+ afi_writel(pcie, fpci_bar, AFI_FPCI_BAR1);
+
+ /* Bar 2: prefetchable memory BAR */
+ fpci_bar = (((pcie->prefetch.start >> 12) & 0x0fffffff) << 4) | 0x1;
+ size = resource_size(&pcie->prefetch);
+ axi_address = pcie->prefetch.start;
+ afi_writel(pcie, axi_address, AFI_AXI_BAR2_START);
+ afi_writel(pcie, size >> 12, AFI_AXI_BAR2_SZ);
+ afi_writel(pcie, fpci_bar, AFI_FPCI_BAR2);
+
+ /* Bar 3: non prefetchable memory BAR */
+ fpci_bar = (((pcie->mem.start >> 12) & 0x0fffffff) << 4) | 0x1;
+ size = resource_size(&pcie->mem);
+ axi_address = pcie->mem.start;
+ afi_writel(pcie, axi_address, AFI_AXI_BAR3_START);
+ afi_writel(pcie, size >> 12, AFI_AXI_BAR3_SZ);
+ afi_writel(pcie, fpci_bar, AFI_FPCI_BAR3);
+
+ /* NULL out the remaining BARs as they are not used */
+ afi_writel(pcie, 0, AFI_AXI_BAR4_START);
+ afi_writel(pcie, 0, AFI_AXI_BAR4_SZ);
+ afi_writel(pcie, 0, AFI_FPCI_BAR4);
+
+ afi_writel(pcie, 0, AFI_AXI_BAR5_START);
+ afi_writel(pcie, 0, AFI_AXI_BAR5_SZ);
+ afi_writel(pcie, 0, AFI_FPCI_BAR5);
+
+ /* map all upstream transactions as uncached */
+ afi_writel(pcie, PHYS_OFFSET, AFI_CACHE_BAR0_ST);
+ afi_writel(pcie, 0, AFI_CACHE_BAR0_SZ);
+ afi_writel(pcie, 0, AFI_CACHE_BAR1_ST);
+ afi_writel(pcie, 0, AFI_CACHE_BAR1_SZ);
+
+ /* MSI translations are setup only when needed */
+ afi_writel(pcie, 0, AFI_MSI_FPCI_BAR_ST);
+ afi_writel(pcie, 0, AFI_MSI_BAR_SZ);
+ afi_writel(pcie, 0, AFI_MSI_AXI_BAR_ST);
+ afi_writel(pcie, 0, AFI_MSI_BAR_SZ);
+}
+
+static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
+{
+ struct tegra_pcie_port *port;
+ unsigned int timeout;
+ unsigned long value;
+
+ /* configure mode and disable all ports */
+ value = afi_readl(pcie, AFI_PCIE_CONFIG);
+ value &= ~AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_MASK;
+ value |= AFI_PCIE_CONFIG_PCIE_DISABLE_ALL | pcie->xbar_config;
+
+ list_for_each_entry(port, &pcie->ports, list)
+ value &= ~AFI_PCIE_CONFIG_PCIE_DISABLE(port->index);
+
+ afi_writel(pcie, value, AFI_PCIE_CONFIG);
+
+ value = afi_readl(pcie, AFI_FUSE);
+ value &= ~AFI_FUSE_PCIE_T0_GEN2_DIS;
+ afi_writel(pcie, value, AFI_FUSE);
+
+ /* initialze internal PHY, enable up to 16 PCIE lanes */
+ pads_writel(pcie, 0x0, PADS_CTL_SEL);
+
+ /* override IDDQ to 1 on all 4 lanes */
+ value = pads_readl(pcie, PADS_CTL);
+ value |= PADS_CTL_IDDQ_1L;
+ pads_writel(pcie, value, PADS_CTL);
+
+ /*
+ * Set up PHY PLL inputs select PLLE output as refclock,
+ * set TX ref sel to div10 (not div5).
+ */
+ value = pads_readl(pcie, PADS_PLL_CTL);
+ value &= ~(PADS_PLL_CTL_REFCLK_MASK | PADS_PLL_CTL_TXCLKREF_MASK);
+ value |= (PADS_PLL_CTL_REFCLK_INTERNAL_CML | PADS_PLL_CTL_TXCLKREF_DIV10);
+ pads_writel(pcie, value, PADS_PLL_CTL);
+
+ /* take PLL out of reset */
+ value = pads_readl(pcie, PADS_PLL_CTL);
+ value |= PADS_PLL_CTL_RST_B4SM;
+ pads_writel(pcie, value, PADS_PLL_CTL);
+
+ /*
+ * Hack, set the clock voltage to the DEFAULT provided by hw folks.
+ * This doesn't exist in the documentation.
+ */
+ pads_writel(pcie, 0xfa5cfa5c, 0xc8);
+
+ /* wait for the PLL to lock */
+ timeout = 300;
+ do {
+ value = pads_readl(pcie, PADS_PLL_CTL);
+ usleep_range(1000, 1000);
+ if (--timeout == 0) {
+ pr_err("Tegra PCIe error: timeout waiting for PLL\n");
+ return -EBUSY;
+ }
+ } while (!(value & PADS_PLL_CTL_LOCKDET));
+
+ /* turn off IDDQ override */
+ value = pads_readl(pcie, PADS_CTL);
+ value &= ~PADS_CTL_IDDQ_1L;
+ pads_writel(pcie, value, PADS_CTL);
+
+ /* enable TX/RX data */
+ value = pads_readl(pcie, PADS_CTL);
+ value |= PADS_CTL_TX_DATA_EN_1L | PADS_CTL_RX_DATA_EN_1L;
+ pads_writel(pcie, value, PADS_CTL);
+
+ /* take the PCIe interface module out of reset */
+ tegra_periph_reset_deassert(pcie->pcie_xclk);
+
+ /* finally enable PCIe */
+ value = afi_readl(pcie, AFI_CONFIGURATION);
+ value |= AFI_CONFIGURATION_EN_FPCI;
+ afi_writel(pcie, value, AFI_CONFIGURATION);
+
+ value = AFI_INTR_EN_INI_SLVERR | AFI_INTR_EN_INI_DECERR |
+ AFI_INTR_EN_TGT_SLVERR | AFI_INTR_EN_TGT_DECERR |
+ AFI_INTR_EN_TGT_WRERR | AFI_INTR_EN_DFPCI_DECERR;
+ afi_writel(pcie, value, AFI_AFI_INTR_ENABLE);
+ afi_writel(pcie, 0xffffffff, AFI_SM_INTR_ENABLE);
+
+ /* don't enable MSI for now, only when needed */
+ afi_writel(pcie, AFI_INTR_MASK_INT_MASK, AFI_INTR_MASK);
+
+ /* disable all exceptions */
+ afi_writel(pcie, 0, AFI_FPCI_ERROR_MASKS);
+
+ return 0;
+}
+
+static void tegra_pcie_power_off(struct tegra_pcie *pcie)
+{
+ int err;
+
+ /* TODO: disable and unprepare clocks? */
+
+ tegra_periph_reset_assert(pcie->pcie_xclk);
+ tegra_periph_reset_assert(pcie->afi_clk);
+ tegra_periph_reset_assert(pcie->pex_clk);
+
+ tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+ tegra_pmc_pcie_xclk_clamp(true);
+
+ err = regulator_disable(pcie->pex_clk_supply);
+ if (err < 0)
+ dev_err(pcie->dev, "failed to disable pex-clk regulator: %d\n",
+ err);
+
+ err = regulator_disable(pcie->vdd_supply);
+ if (err < 0)
+ dev_err(pcie->dev, "failed to disable VDD regulator: %d\n",
+ err);
+}
+
+static int tegra_pcie_power_on(struct tegra_pcie *pcie)
+{
+ int err;
+
+ tegra_periph_reset_assert(pcie->pcie_xclk);
+ tegra_periph_reset_assert(pcie->afi_clk);
+ tegra_periph_reset_assert(pcie->pex_clk);
+
+ tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
+ tegra_pmc_pcie_xclk_clamp(true);
+
+ /* enable regulators */
+ err = regulator_enable(pcie->vdd_supply);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to enable VDD regulator: %d\n", err);
+ return err;
+ }
+
+ err = regulator_enable(pcie->pex_clk_supply);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to enable pex-clk regulator: %d\n",
+ err);
+ return err;
+ }
+
+ err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_PCIE,
+ pcie->pex_clk);
+ if (err) {
+ dev_err(pcie->dev, "powerup sequence failed: %d\n", err);
+ return err;
+ }
+
+ tegra_periph_reset_deassert(pcie->afi_clk);
+
+ tegra_pmc_pcie_xclk_clamp(false);
+
+ err = clk_prepare_enable(pcie->afi_clk);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to enable AFI clock: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(pcie->pll_e);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to enable PLLE clock: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra_pcie_clocks_get(struct tegra_pcie *pcie)
+{
+ pcie->pex_clk = devm_clk_get(pcie->dev, "pex");
+ if (IS_ERR(pcie->pex_clk))
+ return PTR_ERR(pcie->pex_clk);
+
+ pcie->afi_clk = devm_clk_get(pcie->dev, "afi");
+ if (IS_ERR(pcie->afi_clk))
+ return PTR_ERR(pcie->afi_clk);
+
+ pcie->pcie_xclk = devm_clk_get(pcie->dev, "pcie_xclk");
+ if (IS_ERR(pcie->pcie_xclk))
+ return PTR_ERR(pcie->pcie_xclk);
+
+ pcie->pll_e = devm_clk_get(pcie->dev, "pll_e");
+ if (IS_ERR(pcie->pll_e))
+ return PTR_ERR(pcie->pll_e);
+
+ return 0;
+}
+
+static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
+{
+ struct platform_device *pdev = to_platform_device(pcie->dev);
+ struct resource *pads, *afi, *res;
+ int err;
+
+ err = tegra_pcie_clocks_get(pcie);
+ if (err) {
+ dev_err(&pdev->dev, "failed to get clocks: %d\n", err);
+ return err;
+ }
+
+ err = tegra_pcie_power_on(pcie);
+ if (err) {
+ dev_err(&pdev->dev, "failed to power up: %d\n", err);
+ return err;
+ }
+
+ /* request and remap controller registers */
+ pads = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pads");
+ if (!pads) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ afi = platform_get_resource_byname(pdev, IORESOURCE_MEM, "afi");
+ if (!afi) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ pcie->pads = devm_request_and_ioremap(&pdev->dev, pads);
+ if (!pcie->pads) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ pcie->afi = devm_request_and_ioremap(&pdev->dev, afi);
+ if (!pcie->afi) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ /* request and remap configuration space */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cs");
+ if (!res) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ pcie->cs = devm_request_mem_region(pcie->dev, res->start,
+ resource_size(res), res->name);
+ if (!pcie->cs) {
+ err = -EADDRNOTAVAIL;
+ goto poweroff;
+ }
+
+ /* request interrupt */
+ err = platform_get_irq_byname(pdev, "intr");
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
+ goto poweroff;
+ }
+
+ pcie->irq = err;
+
+ err = devm_request_irq(&pdev->dev, pcie->irq, tegra_pcie_isr,
+ IRQF_SHARED, "PCIE", pcie);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register IRQ: %d\n", err);
+ goto poweroff;
+ }
+
+ return 0;
+
+poweroff:
+ tegra_pcie_power_off(pcie);
+ return err;
+}
+
+static int tegra_pcie_put_resources(struct tegra_pcie *pcie)
+{
+ tegra_pcie_power_off(pcie);
+ return 0;
+}
+
+static int tegra_msi_alloc(struct tegra_msi *chip)
+{
+ int msi;
+
+ mutex_lock(&chip->lock);
+
+ msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
+ if (msi < INT_PCI_MSI_NR)
+ set_bit(msi, chip->used);
+ else
+ msi = -ENOSPC;
+
+ mutex_unlock(&chip->lock);
+
+ return msi;
+}
+
+static void tegra_msi_free(struct tegra_msi *chip, unsigned long irq)
+{
+ struct device *dev = chip->chip.dev;
+
+ mutex_lock(&chip->lock);
+
+ if (!test_bit(irq, chip->used))
+ dev_err(dev, "trying to free unused MSI#%lu\n", irq);
+ else
+ clear_bit(irq, chip->used);
+
+ mutex_unlock(&chip->lock);
+}
+
+static irqreturn_t tegra_pcie_msi_irq(int irq, void *data)
+{
+ struct tegra_pcie *pcie = data;
+ struct tegra_msi *msi = &pcie->msi;
+ unsigned int i;
+
+ for (i = 0; i < 8; i++) {
+ unsigned long reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4);
+
+ while (reg) {
+ unsigned int offset = find_first_bit(&reg, 32);
+ unsigned int index = i * 32 + offset;
+ unsigned int irq;
+
+ /* clear the interrupt */
+ afi_writel(pcie, 1 << offset, AFI_MSI_VEC0 + i * 4);
+
+ irq = irq_find_mapping(msi->domain, index);
+ if (irq) {
+ if (test_bit(index, msi->used))
+ generic_handle_irq(irq);
+ else
+ dev_info(pcie->dev, "unhandled MSI\n");
+ } else {
+ /*
+ * that's weird who triggered this?
+ * just clear it
+ */
+ dev_info(pcie->dev, "unexpected MSI\n");
+ }
+
+ /* see if there's any more pending in this vector */
+ reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int tegra_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev,
+ struct msi_desc *desc)
+{
+ struct tegra_msi *msi = to_tegra_msi(chip);
+ struct tegra_pcie *pcie = container_of(chip, struct tegra_pcie, msi.chip);
+ struct msi_msg msg;
+ unsigned int irq;
+ int hwirq;
+
+ hwirq = tegra_msi_alloc(msi);
+ if (hwirq < 0)
+ return hwirq;
+
+ irq = irq_create_mapping(msi->domain, hwirq);
+ if (!irq)
+ return -EINVAL;
+
+ irq_set_msi_desc(irq, desc);
+
+ msg.address_lo = afi_readl(pcie, AFI_MSI_AXI_BAR_ST);
+ /* 32 bit address only */
+ msg.address_hi = 0;
+ msg.data = hwirq;
+
+ write_msi_msg(irq, &msg);
+
+ return 0;
+}
+
+static void tegra_msi_teardown_irq(struct msi_chip *chip, unsigned int irq)
+{
+ struct tegra_msi *msi = to_tegra_msi(chip);
+ struct irq_data *d = irq_get_irq_data(irq);
+
+ tegra_msi_free(msi, d->hwirq);
+}
+
+static struct irq_chip tegra_msi_irq_chip = {
+ .name = "Tegra PCIe MSI",
+ .irq_enable = unmask_msi_irq,
+ .irq_disable = mask_msi_irq,
+ .irq_mask = mask_msi_irq,
+ .irq_unmask = unmask_msi_irq,
+};
+
+static int tegra_msi_map(struct irq_domain *domain, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &tegra_msi_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 msi_domain_ops = {
+ .map = tegra_msi_map,
+};
+
+static int tegra_pcie_enable_msi(struct tegra_pcie *pcie)
+{
+ struct platform_device *pdev = to_platform_device(pcie->dev);
+ struct tegra_msi *msi = &pcie->msi;
+ unsigned long base;
+ int err;
+ u32 reg;
+
+ mutex_init(&msi->lock);
+
+ msi->chip.dev = pcie->dev;
+ msi->chip.setup_irq = tegra_msi_setup_irq;
+ msi->chip.teardown_irq = tegra_msi_teardown_irq;
+
+ msi->domain = irq_domain_add_linear(pcie->dev->of_node, INT_PCI_MSI_NR,
+ &msi_domain_ops, &msi->chip);
+ if (!msi->domain) {
+ dev_err(&pdev->dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+ err = platform_get_irq_byname(pdev, "msi");
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
+ goto err;
+ }
+
+ msi->irq = err;
+
+ err = devm_request_irq(&pdev->dev, msi->irq, tegra_pcie_msi_irq,
+ 0, tegra_msi_irq_chip.name, pcie);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
+ goto err;
+ }
+
+ /* setup AFI/FPCI range */
+ msi->pages = __get_free_pages(GFP_KERNEL, 3);
+ base = virt_to_phys((void *)msi->pages);
+
+ afi_writel(pcie, base, AFI_MSI_FPCI_BAR_ST);
+ afi_writel(pcie, base, AFI_MSI_AXI_BAR_ST);
+ /* this register is in 4K increments */
+ afi_writel(pcie, 1, AFI_MSI_BAR_SZ);
+
+ /* enable all MSI vectors */
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC0);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC1);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC2);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC3);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC4);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC5);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC6);
+ afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC7);
+
+ /* and unmask the MSI interrupt */
+ reg = afi_readl(pcie, AFI_INTR_MASK);
+ reg |= AFI_INTR_MASK_MSI_MASK;
+ afi_writel(pcie, reg, AFI_INTR_MASK);
+
+ return 0;
+
+err:
+ irq_domain_remove(msi->domain);
+ return err;
+}
+
+static int tegra_pcie_disable_msi(struct tegra_pcie *pcie)
+{
+ struct tegra_msi *msi = &pcie->msi;
+ unsigned int i, irq;
+ u32 value;
+
+ /* mask the MSI interrupt */
+ value = afi_readl(pcie, AFI_INTR_MASK);
+ value &= ~AFI_INTR_MASK_MSI_MASK;
+ afi_writel(pcie, value, AFI_INTR_MASK);
+
+ /* disable all MSI vectors */
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC0);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC1);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC2);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC3);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC4);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC5);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC6);
+ afi_writel(pcie, 0, AFI_MSI_EN_VEC7);
+
+ free_pages(msi->pages, 3);
+
+ for (i = 0; i < INT_PCI_MSI_NR; i++) {
+ irq = irq_find_mapping(msi->domain, i);
+ if (irq > 0)
+ irq_dispose_mapping(irq);
+ }
+
+ irq_domain_remove(msi->domain);
+
+ return 0;
+}
+
+static u32 tegra_pcie_get_xbar_config(struct tegra_pcie *pcie, u32 lanes)
+{
+ struct device_node *np = pcie->dev->of_node;
+
+ switch (lanes) {
+ case 0x00000004:
+ dev_info(pcie->dev, "single-mode configuration\n");
+ return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE;
+
+ case 0x00000202:
+ dev_info(pcie->dev, "dual-mode configuration\n");
+ return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
+ }
+
+ return 0;
+}
+
+static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)
+{
+ struct device_node *np = pcie->dev->of_node, *port;
+ struct of_pci_range_parser parser;
+ struct of_pci_range range;
+ struct resource res;
+ u32 lanes = 0;
+ int err;
+
+ if (of_pci_range_parser(&parser, np)) {
+ dev_err(pcie->dev, "missing \"ranges\" property\n");
+ return -EINVAL;
+ }
+
+ pcie->vdd_supply = devm_regulator_get(pcie->dev, "vdd");
+ if (IS_ERR(pcie->vdd_supply))
+ return PTR_ERR(pcie->vdd_supply);
+
+ pcie->pex_clk_supply = devm_regulator_get(pcie->dev, "pex-clk");
+ if (IS_ERR(pcie->pex_clk_supply))
+ return PTR_ERR(pcie->pex_clk_supply);
+
+ for_each_of_pci_range(&parser, &range) {
+ of_pci_range_to_resource(&range, np, &res);
+
+ switch (res.flags & IORESOURCE_TYPE_BITS) {
+ case IORESOURCE_IO:
+ memcpy(&pcie->io, &res, sizeof(res));
+ pcie->io.name = "I/O";
+ break;
+
+ case IORESOURCE_MEM:
+ if (res.flags & IORESOURCE_PREFETCH) {
+ memcpy(&pcie->prefetch, &res, sizeof(res));
+ pcie->prefetch.name = "PREFETCH";
+ } else {
+ memcpy(&pcie->mem, &res, sizeof(res));
+ pcie->mem.name = "MEM";
+ }
+ break;
+ }
+ }
+
+ err = of_pci_parse_bus_range(np, &pcie->busn);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to parse ranges property: %d\n",
+ err);
+ pcie->busn.name = np->name;
+ pcie->busn.start = 0;
+ pcie->busn.end = 0xff;
+ pcie->busn.flags = IORESOURCE_BUS;
+ }
+
+ /* parse root ports */
+ for_each_child_of_node(np, port) {
+ struct tegra_pcie_port *rp;
+ unsigned int index;
+ u32 value;
+
+ err = of_pci_get_devfn(port);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to parse address: %d\n",
+ err);
+ return err;
+ }
+
+ index = PCI_SLOT(err);
+
+ if (index < 1 || index > TEGRA_MAX_PORTS) {
+ dev_err(pcie->dev, "invalid port number: %d\n", index);
+ return -EINVAL;
+ }
+
+ index--;
+
+ err = of_property_read_u32(port, "nvidia,num-lanes", &value);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to parse # of lanes: %d\n",
+ err);
+ return err;
+ }
+
+ if (value > 16) {
+ dev_err(pcie->dev, "invalid # of lanes: %u\n", value);
+ return -EINVAL;
+ }
+
+ lanes |= value << (index << 3);
+
+ if (!of_device_is_available(port))
+ continue;
+
+ rp = devm_kzalloc(pcie->dev, sizeof(*rp), GFP_KERNEL);
+ if (!rp)
+ return -ENOMEM;
+
+ err = of_address_to_resource(port, 0, &rp->regs);
+ if (err < 0) {
+ dev_err(pcie->dev, "failed to parse address: %d\n",
+ err);
+ return err;
+ }
+
+ INIT_LIST_HEAD(&rp->list);
+ rp->index = index;
+ rp->lanes = value;
+ rp->pcie = pcie;
+
+ rp->base = devm_request_and_ioremap(pcie->dev, &rp->regs);
+ if (!rp->base)
+ return -EADDRNOTAVAIL;
+
+ list_add_tail(&rp->list, &pcie->ports);
+ }
+
+ pcie->xbar_config = tegra_pcie_get_xbar_config(pcie, lanes);
+ if (!pcie->xbar_config) {
+ dev_err(pcie->dev, "invalid lane configuration\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * FIXME: If there are no PCIe cards attached, then calling this function
+ * can result in the increase of the bootup time as there are big timeout
+ * loops.
+ */
+#define TEGRA_PCIE_LINKUP_TIMEOUT 200 /* up to 1.2 seconds */
+static bool tegra_pcie_port_check_link(struct tegra_pcie_port *port)
+{
+ unsigned int retries = 3;
+ unsigned long value;
+
+ do {
+ unsigned int timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
+
+ do {
+ value = readl(port->base + RP_VEND_XP);
+
+ if (value & RP_VEND_XP_DL_UP)
+ break;
+
+ usleep_range(1000, 1000);
+ } while (--timeout);
+
+ if (!timeout) {
+ dev_err(port->pcie->dev, "link %u down, retrying\n",
+ port->index);
+ goto retry;
+ }
+
+ timeout = TEGRA_PCIE_LINKUP_TIMEOUT;
+
+ do {
+ value = readl(port->base + RP_LINK_CONTROL_STATUS);
+
+ if (value & 0x20000000)
+ return true;
+
+ usleep_range(1000, 1000);
+ } while (--timeout);
+
+retry:
+ tegra_pcie_port_reset(port);
+ } while (--retries);
+
+ return false;
+}
+
+static int tegra_pcie_enable(struct tegra_pcie *pcie)
+{
+ struct tegra_pcie_port *port, *tmp;
+ struct hw_pci hw;
+
+ list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
+ dev_info(pcie->dev, "probing port %u, using %u lanes\n",
+ port->index, port->lanes);
+
+ tegra_pcie_port_enable(port);
+
+ if (tegra_pcie_port_check_link(port))
+ continue;
+
+ dev_info(pcie->dev, "link %u down, ignoring\n", port->index);
+
+ tegra_pcie_port_disable(port);
+ tegra_pcie_port_free(port);
+ }
+
+ memset(&hw, 0, sizeof(hw));
+
+ hw.nr_controllers = 1;
+ hw.private_data = (void **)&pcie;
+ hw.setup = tegra_pcie_setup;
+ hw.scan = tegra_pcie_scan_bus;
+ hw.map_irq = tegra_pcie_map_irq;
+
+ pci_common_init(&hw);
+
+ return 0;
+}
+
+static int tegra_pcie_probe(struct platform_device *pdev)
+{
+ struct tegra_pcie *pcie;
+ int err;
+
+ pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&pcie->busses);
+ INIT_LIST_HEAD(&pcie->ports);
+ pcie->dev = &pdev->dev;
+
+ err = tegra_pcie_parse_dt(pcie);
+ if (err < 0)
+ return err;
+
+ pcibios_min_mem = 0;
+
+ err = tegra_pcie_get_resources(pcie);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to request resources: %d\n", err);
+ return err;
+ }
+
+ err = tegra_pcie_enable_controller(pcie);
+ if (err)
+ goto put_resources;
+
+ /* setup the AFI address translations */
+ tegra_pcie_setup_translations(pcie);
+
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
+ err = tegra_pcie_enable_msi(pcie);
+ if (err < 0) {
+ dev_err(&pdev->dev,
+ "failed to enable MSI support: %d\n",
+ err);
+ goto put_resources;
+ }
+ }
+
+ err = tegra_pcie_enable(pcie);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to enable PCIe ports: %d\n", err);
+ goto disable_msi;
+ }
+
+ platform_set_drvdata(pdev, pcie);
+ return 0;
+
+disable_msi:
+ if (IS_ENABLED(CONFIG_PCI_MSI))
+ tegra_pcie_disable_msi(pcie);
+put_resources:
+ tegra_pcie_put_resources(pcie);
+ return err;
+}
+
+static int tegra_pcie_remove(struct platform_device *pdev)
+{
+ struct tegra_pcie *pcie = platform_get_drvdata(pdev);
+ struct tegra_pcie_bus *bus;
+ int err;
+
+ list_for_each_entry(bus, &pcie->busses, list) {
+ vunmap(bus->area->addr);
+ kfree(bus);
+ }
+
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
+ err = tegra_pcie_disable_msi(pcie);
+ if (err < 0)
+ return err;
+ }
+
+ err = tegra_pcie_put_resources(pcie);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static const struct of_device_id tegra_pcie_of_match[] = {
+ { .compatible = "nvidia,tegra20-pcie", },
+ { },
+};
+
+static struct platform_driver tegra_pcie_driver = {
+ .driver = {
+ .name = "tegra-pcie",
+ .owner = THIS_MODULE,
+ .of_match_table = tegra_pcie_of_match,
+ },
+ .probe = tegra_pcie_probe,
+ .remove = tegra_pcie_remove,
+};
+module_platform_driver(tegra_pcie_driver);
--
1.8.2

2013-04-03 14:46:03

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 06/12] ARM: tegra: Move pmc.h to include/linux/tegra-pmc.h

In preparation for moving the PCIe driver into the drivers/pci/host
directory, this header, which contains prototypes that are required by
the PCIe driver, needs to be moved to a globally visible location.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- keep tegra_pmc_init() prototype in arch/arm/mach-tegra/pmc.h
- move pmc.h to includex/linux/tegra-pmc.h

Changes in v3:
- update subject to match new location

arch/arm/mach-tegra/pmc.h | 1 -
include/linux/tegra-pmc.h | 23 +++++++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
create mode 100644 include/linux/tegra-pmc.h

diff --git a/arch/arm/mach-tegra/pmc.h b/arch/arm/mach-tegra/pmc.h
index c778451..7d44710 100644
--- a/arch/arm/mach-tegra/pmc.h
+++ b/arch/arm/mach-tegra/pmc.h
@@ -23,6 +23,5 @@ int tegra_pmc_cpu_power_on(int cpuid);
int tegra_pmc_cpu_remove_clamping(int cpuid);

void tegra_pmc_init(void);
-void tegra_pmc_pcie_xclk_clamp(bool clamp);

#endif
diff --git a/include/linux/tegra-pmc.h b/include/linux/tegra-pmc.h
new file mode 100644
index 0000000..b6296bf
--- /dev/null
+++ b/include/linux/tegra-pmc.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/>.
+ *
+ */
+
+#ifndef __LINUX_TEGRA_PMC_H
+#define __LINUX_TEGRA_PMC_H
+
+void tegra_pmc_pcie_xclk_clamp(bool clamp);
+
+#endif
--
1.8.2

2013-04-03 14:46:00

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 09/12] ARM: tegra: tec: Add PCIe support

Enable the first PCIe root port which is connected to an FPGA on the
Tamonten Evaluation Carrier and add device nodes for each of the PCI
endpoints available in the standard configuration.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- remove duplicate nodes and properties defined in tegra20-tamonten.dtsi

Changes in v3:
- remove PCIe endpoint device tree nodes, all devices can be discovered
via standard PCI enumeration

arch/arm/boot/dts/tegra20-tec.dts | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/arch/arm/boot/dts/tegra20-tec.dts b/arch/arm/boot/dts/tegra20-tec.dts
index 1ae37ba..e396cb2 100644
--- a/arch/arm/boot/dts/tegra20-tec.dts
+++ b/arch/arm/boot/dts/tegra20-tec.dts
@@ -64,6 +64,14 @@
};
};

+ pcie-controller {
+ status = "okay";
+
+ pci@1,0 {
+ status = "okay";
+ };
+ };
+
sound {
compatible = "ad,tegra-audio-wm8903-tec",
"nvidia,tegra-audio-wm8903";
--
1.8.2

2013-04-03 14:49:09

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 04/12] PCI: Introduce new MSI chip infrastructure

The new struct msi_chip is used to associated an MSI controller with a
PCI bus. It is automatically handed down from the root to its children
during bus enumeration.

This patch provides default (weak) implementations for the architecture-
specific MSI functions (arch_setup_msi_irq(), arch_teardown_msi_irq()
and arch_msi_check_device()) which check if a PCI device's bus has an
attached MSI chip and forward the call appropriately.

Signed-off-by: Thierry Reding <[email protected]>
---
drivers/pci/msi.c | 35 +++++++++++++++++++++++++++++++----
drivers/pci/probe.c | 1 +
include/linux/msi.h | 10 ++++++++++
include/linux/pci.h | 1 +
4 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c
index 00cc78c..fce3549 100644
--- a/drivers/pci/msi.c
+++ b/drivers/pci/msi.c
@@ -26,14 +26,41 @@

static int pci_msi_enable = 1;

-/* Arch hooks */
+int __weak arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc)
+{
+ struct msi_chip *chip = dev->bus->msi;
+
+ if (chip && chip->setup_irq) {
+ int err;
+
+ err = chip->setup_irq(chip, dev, desc);
+ if (err < 0)
+ return err;
+
+ irq_set_chip_data(desc->irq, chip);
+ return err;
+ }
+
+ return -EINVAL;
+}

-#ifndef arch_msi_check_device
-int arch_msi_check_device(struct pci_dev *dev, int nvec, int type)
+void __weak arch_teardown_msi_irq(unsigned int irq)
{
+ struct msi_chip *chip = irq_get_chip_data(irq);
+
+ if (chip && chip->teardown_irq)
+ chip->teardown_irq(chip, irq);
+}
+
+int __weak arch_msi_check_device(struct pci_dev *dev, int nvec, int type)
+{
+ struct msi_chip *chip = dev->bus->msi;
+
+ if (chip && chip->check_device)
+ return chip->check_device(chip, dev, nvec, type);
+
return 0;
}
-#endif

#ifndef arch_setup_msi_irqs
# define arch_setup_msi_irqs default_setup_msi_irqs
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b494066..9307550 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -634,6 +634,7 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,

child->parent = parent;
child->ops = parent->ops;
+ child->msi = parent->msi;
child->sysdata = parent->sysdata;
child->bus_flags = parent->bus_flags;

diff --git a/include/linux/msi.h b/include/linux/msi.h
index ce93a34..ea4a5be 100644
--- a/include/linux/msi.h
+++ b/include/linux/msi.h
@@ -58,5 +58,15 @@ extern int arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type);
extern void arch_teardown_msi_irqs(struct pci_dev *dev);
extern int arch_msi_check_device(struct pci_dev* dev, int nvec, int type);

+struct msi_chip {
+ struct module *owner;
+ struct device *dev;
+
+ int (*setup_irq)(struct msi_chip *chip, struct pci_dev *dev,
+ struct msi_desc *desc);
+ void (*teardown_irq)(struct msi_chip *chip, unsigned int irq);
+ int (*check_device)(struct msi_chip *chip, struct pci_dev *dev,
+ int nvec, int type);
+};

#endif /* LINUX_MSI_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index f42cdc6..7e44085 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -431,6 +431,7 @@ struct pci_bus {
struct resource busn_res; /* bus numbers routed to this bus */

struct pci_ops *ops; /* configuration access functions */
+ struct msi_chip *msi; /* MSI controller */
void *sysdata; /* hook for sys-specific extension */
struct proc_dir_entry *procdir; /* directory entry in /proc/bus/pci */

--
1.8.2

2013-04-03 14:50:14

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 05/12] ARM: tegra: Move tegra_pcie_xclk_clamp() to PMC

The PMC code already accesses to PMC registers so it makes sense to
move this function there as well. While at it, rename the function to
tegra_pmc_pcie_xclk_clamp() for consistency.

Signed-off-by: Thierry Reding <[email protected]>
Acked-by: Stephen Warren <[email protected]>
---
arch/arm/mach-tegra/pcie.c | 30 ++++--------------------------
arch/arm/mach-tegra/pmc.c | 16 ++++++++++++++++
arch/arm/mach-tegra/pmc.h | 1 +
3 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/arch/arm/mach-tegra/pcie.c b/arch/arm/mach-tegra/pcie.c
index 46144a1..6c1989b 100644
--- a/arch/arm/mach-tegra/pcie.c
+++ b/arch/arm/mach-tegra/pcie.c
@@ -41,6 +41,7 @@

#include "board.h"
#include "iomap.h"
+#include "pmc.h"

/* Hack - need to parse this from DT */
#define INT_PCIE_INTR 130
@@ -147,17 +148,6 @@
#define PADS_PLL_CTL_TXCLKREF_DIV10 (0 << 20)
#define PADS_PLL_CTL_TXCLKREF_DIV5 (1 << 20)

-/* PMC access is required for PCIE xclk (un)clamping */
-#define PMC_SCRATCH42 0x144
-#define PMC_SCRATCH42_PCX_CLAMP (1 << 0)
-
-static void __iomem *reg_pmc_base = IO_ADDRESS(TEGRA_PMC_BASE);
-
-#define pmc_writel(value, reg) \
- __raw_writel(value, reg_pmc_base + (reg))
-#define pmc_readl(reg) \
- __raw_readl(reg_pmc_base + (reg))
-
/*
* Tegra2 defines 1GB in the AXI address map for PCIe.
*
@@ -639,18 +629,6 @@ static int tegra_pcie_enable_controller(void)
return 0;
}

-static void tegra_pcie_xclk_clamp(bool clamp)
-{
- u32 reg;
-
- reg = pmc_readl(PMC_SCRATCH42) & ~PMC_SCRATCH42_PCX_CLAMP;
-
- if (clamp)
- reg |= PMC_SCRATCH42_PCX_CLAMP;
-
- pmc_writel(reg, PMC_SCRATCH42);
-}
-
static void tegra_pcie_power_off(void)
{
tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
@@ -658,7 +636,7 @@ static void tegra_pcie_power_off(void)
tegra_periph_reset_assert(tegra_pcie.pex_clk);

tegra_powergate_power_off(TEGRA_POWERGATE_PCIE);
- tegra_pcie_xclk_clamp(true);
+ tegra_pmc_pcie_xclk_clamp(true);
}

static int tegra_pcie_power_regate(void)
@@ -667,7 +645,7 @@ static int tegra_pcie_power_regate(void)

tegra_pcie_power_off();

- tegra_pcie_xclk_clamp(true);
+ tegra_pmc_pcie_xclk_clamp(true);

tegra_periph_reset_assert(tegra_pcie.pcie_xclk);
tegra_periph_reset_assert(tegra_pcie.afi_clk);
@@ -681,7 +659,7 @@ static int tegra_pcie_power_regate(void)

tegra_periph_reset_deassert(tegra_pcie.afi_clk);

- tegra_pcie_xclk_clamp(false);
+ tegra_pmc_pcie_xclk_clamp(false);

clk_prepare_enable(tegra_pcie.afi_clk);
clk_prepare_enable(tegra_pcie.pex_clk);
diff --git a/arch/arm/mach-tegra/pmc.c b/arch/arm/mach-tegra/pmc.c
index b30e921..f82284c 100644
--- a/arch/arm/mach-tegra/pmc.c
+++ b/arch/arm/mach-tegra/pmc.c
@@ -44,6 +44,10 @@ static DEFINE_SPINLOCK(tegra_powergate_lock);
static void __iomem *tegra_pmc_base;
static bool tegra_pmc_invert_interrupt;

+/* PMC access is required for PCIE xclk (un)clamping */
+#define PMC_SCRATCH42 0x144
+#define PMC_SCRATCH42_PCX_CLAMP (1 << 0)
+
static inline u32 tegra_pmc_readl(u32 reg)
{
return readl(tegra_pmc_base + reg);
@@ -166,3 +170,15 @@ void __init tegra_pmc_init(void)
val &= ~PMC_CTRL_INTR_LOW;
tegra_pmc_writel(val, PMC_CTRL);
}
+
+void tegra_pmc_pcie_xclk_clamp(bool clamp)
+{
+ u32 reg;
+
+ reg = tegra_pmc_readl(PMC_SCRATCH42) & ~PMC_SCRATCH42_PCX_CLAMP;
+
+ if (clamp)
+ reg |= PMC_SCRATCH42_PCX_CLAMP;
+
+ tegra_pmc_writel(reg, PMC_SCRATCH42);
+}
diff --git a/arch/arm/mach-tegra/pmc.h b/arch/arm/mach-tegra/pmc.h
index 7d44710..c778451 100644
--- a/arch/arm/mach-tegra/pmc.h
+++ b/arch/arm/mach-tegra/pmc.h
@@ -23,5 +23,6 @@ int tegra_pmc_cpu_power_on(int cpuid);
int tegra_pmc_cpu_remove_clamping(int cpuid);

void tegra_pmc_init(void);
+void tegra_pmc_pcie_xclk_clamp(bool clamp);

#endif
--
1.8.2

2013-04-03 14:51:06

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 08/12] ARM: tegra: tamonten: Add PCIe support

Add properties common to all Tamonten-derived boards to the Tamonten
DTSI and add the fixed 1.05 V regulator.

Signed-off-by: Thierry Reding <[email protected]>
---
Changes in v2:
- add properties common to all Tamonten boards
- add fixed 1.05 V regulator

arch/arm/boot/dts/tegra20-tamonten.dtsi | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/arch/arm/boot/dts/tegra20-tamonten.dtsi b/arch/arm/boot/dts/tegra20-tamonten.dtsi
index 08cc557..020f7bc 100644
--- a/arch/arm/boot/dts/tegra20-tamonten.dtsi
+++ b/arch/arm/boot/dts/tegra20-tamonten.dtsi
@@ -438,7 +438,7 @@
regulator-always-on;
};

- ldo0 {
+ pci_clk_reg: ldo0 {
regulator-name = "vdd_ldo0,vddio_pex_clk";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
@@ -533,6 +533,11 @@
nvidia,invert-interrupt;
};

+ pcie-controller {
+ pex-clk-supply = <&pci_clk_reg>;
+ vdd-supply = <&pci_vdd_reg>;
+ };
+
usb@c5008000 {
status = "okay";
};
@@ -558,5 +563,15 @@
regulator-max-microvolt = <5000000>;
regulator-always-on;
};
+
+ pci_vdd_reg: regulator@1 {
+ compatible = "regulator-fixed";
+ reg = <1>;
+ regulator-name = "vdd_1v05";
+ regulator-min-microvolt = <1050000>;
+ regulator-max-microvolt = <1050000>;
+ gpio = <&pmic 2 0>;
+ enable-active-high;
+ };
};
};
--
1.8.2

2013-04-03 14:51:04

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 12/12] ARM: tegra: Update default configuration (PCIe)

Enable PCI and MSI support as well as the new Tegra PCIe controller
driver.

Signed-off-by: Thierry Reding <[email protected]>
---
arch/arm/configs/tegra_defconfig | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/arch/arm/configs/tegra_defconfig b/arch/arm/configs/tegra_defconfig
index 2b42a2c..6cedaa1 100644
--- a/arch/arm/configs/tegra_defconfig
+++ b/arch/arm/configs/tegra_defconfig
@@ -26,8 +26,10 @@ CONFIG_GPIO_PCA953X=y
CONFIG_ARCH_TEGRA_2x_SOC=y
CONFIG_ARCH_TEGRA_3x_SOC=y
CONFIG_ARCH_TEGRA_114_SOC=y
-CONFIG_TEGRA_PCI=y
CONFIG_TEGRA_EMC_SCALING_ENABLE=y
+CONFIG_PCI=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_TEGRA=y
CONFIG_SMP=y
CONFIG_PREEMPT=y
CONFIG_AEABI=y
--
1.8.2

2013-04-03 14:52:01

by Thierry Reding

[permalink] [raw]
Subject: [PATCH v3 10/12] ARM: tegra: harmony: Initialize PCIe from DT

With the device tree support in place, probe the PCIe controller from
the device tree and remove the corresponding workaround in the board
file.

Signed-off-by: Thierry Reding <[email protected]>
---
arch/arm/boot/dts/tegra20-harmony.dts | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/arch/arm/boot/dts/tegra20-harmony.dts b/arch/arm/boot/dts/tegra20-harmony.dts
index 5fb0888..8799d7a 100644
--- a/arch/arm/boot/dts/tegra20-harmony.dts
+++ b/arch/arm/boot/dts/tegra20-harmony.dts
@@ -334,7 +334,7 @@
regulator-always-on;
};

- ldo0 {
+ pci_clk_reg: ldo0 {
regulator-name = "vdd_ldo0,vddio_pex_clk";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
@@ -418,6 +418,20 @@
nvidia,invert-interrupt;
};

+ pcie-controller {
+ pex-clk-supply = <&pci_clk_reg>;
+ vdd-supply = <&pci_vdd_reg>;
+ status = "okay";
+
+ pci@1,0 {
+ status = "okay";
+ };
+
+ pci@2,0 {
+ status = "okay";
+ };
+ };
+
usb@c5000000 {
status = "okay";
};
@@ -601,7 +615,7 @@
enable-active-high;
};

- regulator@3 {
+ pci_vdd_reg: regulator@3 {
compatible = "regulator-fixed";
reg = <3>;
regulator-name = "vdd_1v05";
@@ -609,8 +623,6 @@
regulator-max-microvolt = <1050000>;
gpio = <&pmic 2 0>;
enable-active-high;
- /* Hack until board-harmony-pcie.c is removed */
- status = "disabled";
};

regulator@4 {
--
1.8.2

2013-04-04 21:29:01

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 04/03/2013 08:45 AM, Thierry Reding wrote:
> Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
> directory. The motivation is to collect various host controller drivers
> in the same location in order to facilitate refactoring.
>
> The Tegra PCIe driver has been largely rewritten, both in order to turn
> it into a proper platform driver and to add MSI (based on code by
> Krishna Kishore <[email protected]>) as well as device tree support.

> arch/arm/boot/dts/tegra20.dtsi | 53 +

I guess this has to touch both the driver and the dtsi file so that
bisectabilty isn't broken? I guess that's OK.

> diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt

> +Required properties:

> +- interrupts: the interrupt outputs of the controller
> +- interrupt-names: list of names to identify interrupts

The specification part of this file should define which interrupt
outputs must be included in this list, and the order they must appear in
the list. I believe that the entries in the interrupts property must
have a defined order, so I'm not sure whether interrupt-names is useful
here?

> +- ranges: describes the translation of addresses for root ports

Shouldn't there be some discussion re: how the range are expected to be
set up so that everything works? We shouldn't expect people to just
blindly copy the example without any way of understanding it.

> +- clocks: the clock inputs of the controller
> +- clock-names: list of names to identify clocks

The specification part of this file should define which clocks are
required, and not rely on the example below to do this. I would suggest
re-writing this as:

- clocks : Must contain an entry for each entry in clock-names.
- clock-names : Must include the following entries:
"pex" (The Tegra clock of that name)
"afi" (The Tegra clock of that name)
"pcie_xclk" (The Tegra clock of that name)
"pll_e" (The Tegra clock of that name)

> +Root ports are defined as subnodes of the PCIe controller node.
> +
> +Required properties:
...
> +- ranges: sub-ranges distributed from the PCIe controller node

Here, I think it's worth mentioning that an empty ranges is all that's
required.

> +Board DTS:
> +
> + pcie-controller {
> + status = "okay";
> +
> + vdd-supply = <&pci_vdd_reg>;
> + pex-clk-supply = <&pci_clk_reg>;
> +
> + /* root port 00:01.0 */
> + pci@1,0 {
> + status = "okay";

Is it worth putting a comment here stating that the explicit devices
included below in this example are entirely optional?

> diff --git a/arch/arm/mach-tegra/board-harmony-pcie.c b/arch/arm/mach-tegra/board-harmony-pcie.c
> deleted file mode 100644

Hmmm. Is this patch meant to include a change to tegra20-harmony.dtsi to
hook up all the regulators through device tree? Same for TrimSlice?

> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile

I think the creation of those files should be a separate patch, and
hopefully get into 3.10 to remove any dependencies. Otherwise, 3 or 4
different patches are all going to try and do the same thing. Didn't the
Marvell series split out the creation of drivers/pci/host/ into a
separate patch that's suitable for this, and could go into 3.10?

> diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c

I'll review that file separately later.

2013-04-04 21:30:07

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 04/04/2013 03:28 PM, Stephen Warren wrote:
> On 04/03/2013 08:45 AM, Thierry Reding wrote:
>> Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
>> directory. The motivation is to collect various host controller drivers
>> in the same location in order to facilitate refactoring.
>>
>> The Tegra PCIe driver has been largely rewritten, both in order to turn
>> it into a proper platform driver and to add MSI (based on code by
>> Krishna Kishore <[email protected]>) as well as device tree support.
>
>> arch/arm/boot/dts/tegra20.dtsi | 53 +
>
> I guess this has to touch both the driver and the dtsi file so that
> bisectabilty isn't broken? I guess that's OK.

Actually, can't you move all the *.dts/*.dtsi changes to the start of
the series, then put the driver conversion patch last, to avoid any
bisectability issues and still keep code and DT changes separate?

2013-04-05 06:04:36

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On Thu, Apr 04, 2013 at 03:30:01PM -0600, Stephen Warren wrote:
> On 04/04/2013 03:28 PM, Stephen Warren wrote:
> > On 04/03/2013 08:45 AM, Thierry Reding wrote:
> >> Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
> >> directory. The motivation is to collect various host controller drivers
> >> in the same location in order to facilitate refactoring.
> >>
> >> The Tegra PCIe driver has been largely rewritten, both in order to turn
> >> it into a proper platform driver and to add MSI (based on code by
> >> Krishna Kishore <[email protected]>) as well as device tree support.
> >
> >> arch/arm/boot/dts/tegra20.dtsi | 53 +
> >
> > I guess this has to touch both the driver and the dtsi file so that
> > bisectabilty isn't broken? I guess that's OK.
>
> Actually, can't you move all the *.dts/*.dtsi changes to the start of
> the series, then put the driver conversion patch last, to avoid any
> bisectability issues and still keep code and DT changes separate?

I'm not sure if that'll work. There's this oddity in the Harmony DTS
where the regulator@3 is disabled with a comment that this is a hack
required until board-harmony-pcie.c is removed. If we change the DTS
before the driver rewrite I think that would break requesting the GPIO
in the board file.

A possible workaround I can think of is accessing the regulator from
board-harmony-pcie.c instead of the GPIO directly. But perhaps such an
attempt already failed (deferred probing getting in the way?).

Since it looks like this series will not make it into 3.10, maybe we
should try and at least get these things ironed out so that the merge
can be done more easily in 3.11. One big step in that direction would
obviously be if we could get the DTS changes merged already which, as
you point out, should be independent of the driver rewrite itself.

Thierry


Attachments:
(No filename) (1.83 kB)
(No filename) (836.00 B)
Download all attachments

2013-04-05 07:37:15

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On Thu, Apr 04, 2013 at 03:28:54PM -0600, Stephen Warren wrote:
[...]
> > diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
>
> > +Required properties:
>
> > +- interrupts: the interrupt outputs of the controller
> > +- interrupt-names: list of names to identify interrupts
>
> The specification part of this file should define which interrupt
> outputs must be included in this list, and the order they must appear in
> the list.

Okay, I can add such a description similar to what you propose below for
the clocks and clock-names properties. Something like this:

- interrupts: A list of interrupt outputs of the controller. Must contain an
entry for each entry in the interrupt-names property.
- interrupt-names: Must include the following entries:
"intr": The Tegra interrupt that is asserted for controller interrupts
"msi": The Tegra interrupt that is asserted when an MSI is received

Would that be acceptable?

I should probably update the reg properties in a similar way. Maybe like
so:

- reg: A list of physical base address and length for each set of controller
registers. Must contain an entry for each entry in the reg-names property.
- reg-names: Must include the following entries:
"pads": PADS registers
"afi": AFI registers
"cs": configuration space region

> I believe that the entries in the interrupts property must
> have a defined order, so I'm not sure whether interrupt-names is useful
> here?

Actually the interrupt-names property is there specifically so that the
order doesn't matter. The driver uses platform_get_irq_byname(), using
"intr" and "msi" respectively so that they don't have to appear in a
specific order. I did this so that it is more in line with properties
such as clocks and reg.

> > +- ranges: describes the translation of addresses for root ports
>
> Shouldn't there be some discussion re: how the range are expected to be
> set up so that everything works? We shouldn't expect people to just
> blindly copy the example without any way of understanding it.

Possibly. I wouldn't like to go too much into the details, though, since
the ranges property is not only rather complex but also well documented
in other places. But I could add some explanation about which entries
are expected and how they work together. In this case even order is
important. The port register entries need to be listed before the
non-prefetchable memory window entry, otherwise the ranges parser gets
confused. How does the following sound?

- ranges: Describes the translation of addresses for root ports and standard
PCI regions. The entries must be 6 cells each, where the first three cells
correspond to the address as described for the #address-cells property
above, the fourth cell is the physical CPU address to translate to and the
fifth and six cells are as described for the #size-cells property above.
- The first two entries are expected to translate the addresses for the root
port registers, which are referenced by the assigned-addresses property of
the root port nodes (see below).
- The remaining entries setup the mapping for the standard I/O, memory and
prefetchable PCI regions. The first cell determines the type of region
that is setup:
- 0x81000000: I/O memory region
- 0x82000000: non-prefetchable memory region
- 0xc2000000: prefetchable memory region
Please refer to the standard PCI bus binding document for a more detailed
explanation.

> > +- clocks: the clock inputs of the controller
> > +- clock-names: list of names to identify clocks
>
> The specification part of this file should define which clocks are
> required, and not rely on the example below to do this. I would suggest
> re-writing this as:
>
> - clocks : Must contain an entry for each entry in clock-names.
> - clock-names : Must include the following entries:
> "pex" (The Tegra clock of that name)
> "afi" (The Tegra clock of that name)
> "pcie_xclk" (The Tegra clock of that name)
> "pll_e" (The Tegra clock of that name)

Okay, sounds good. Although the Tegra clocks are named somewhat
differently. "pex" is named "pcie" and "pcie_xclk" is "unassigned" (!)
according to the nvidia,tegra20-car.txt binding document. Perhaps I
should update the binding document to replace unassigned with pcie_xclk
for clock 74. And maybe rename pex in the PCIe binding to pcie to match
the CAR binding?

> > +Root ports are defined as subnodes of the PCIe controller node.
> > +
> > +Required properties:
> ...
> > +- ranges: sub-ranges distributed from the PCIe controller node
>
> Here, I think it's worth mentioning that an empty ranges is all that's
> required.

Yes, that's a good idea.

> > +Board DTS:
> > +
> > + pcie-controller {
> > + status = "okay";
> > +
> > + vdd-supply = <&pci_vdd_reg>;
> > + pex-clk-supply = <&pci_clk_reg>;
> > +
> > + /* root port 00:01.0 */
> > + pci@1,0 {
> > + status = "okay";
>
> Is it worth putting a comment here stating that the explicit devices
> included below in this example are entirely optional?

Would it be acceptable to annotate the 01:00.0 bridge with "optional"?
Like so:

pcie-controller {
...
pci@1,0 {
...
/* bridge 01:00.0 (optional) */
pci@0,0 {
...
};
};
};

Alternatively I could add something like below:

---snip---

Note that devices on the PCI bus are dynamically discovered using PCI's bus
enumeration and therefore don't need corresponding device nodes in DT. However
if a device on the PCI bus provides a non-probeable bus such as I2C or SPI,
device nodes need to be added in order to allow the bus' children to be
instantiated at the proper location in the operating system's device tree (as
illustrated by the optional nodes in the example above).

---snip---

Maybe I'll just do both.

> > diff --git a/arch/arm/mach-tegra/board-harmony-pcie.c b/arch/arm/mach-tegra/board-harmony-pcie.c
> > deleted file mode 100644
>
> Hmmm. Is this patch meant to include a change to tegra20-harmony.dtsi to
> hook up all the regulators through device tree? Same for TrimSlice?

As discussed above I think it'd be preferrable, in order to keep
dependencies simpler, to separate the changes into different patches.
Aside from the oddity on Harmony I don't think there's anything that
prevents that.

> > diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> > diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> > diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
> > diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>
> I think the creation of those files should be a separate patch, and
> hopefully get into 3.10 to remove any dependencies. Otherwise, 3 or 4
> different patches are all going to try and do the same thing. Didn't the
> Marvell series split out the creation of drivers/pci/host/ into a
> separate patch that's suitable for this, and could go into 3.10?

At the time when I wrote the patch to move the driver to drivers/pci,
there were no other drivers so I didn't think it necessary to split out
those changes. I had also been overly optimistic that the patches could
be merged at that point. Meanwhile there are a few more drivers that
will go into that directory, so yes they should go into a separate patch
like Thomas has done. If that makes it into 3.10, all the better.

> > diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
>
> I'll review that file separately later.

Okay, thanks for reviewing.

Thierry


Attachments:
(No filename) (7.38 kB)
(No filename) (836.00 B)
Download all attachments

2013-04-05 07:54:04

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

NVIDIA Tegra PCIe controller

Required properties:
- compatible: "nvidia,tegra20-pcie"
- device_type: Must be "pci"
- reg: A list of physical base address and length for each set of controller
registers. Must contain an entry for each entry in the reg-names property.
- reg-names: Must include the following entries:
"pads": PADS registers
"afi": AFI registers
"cs": configuration space region
- interrupts: A list of interrupt outputs of the controller. Must contain an
entry for each entry in the interrupt-names property.
- interrupt-names: Must include the following entries:
"intr": The Tegra interrupt that is asserted for controller interrupts
"msi": The Tegra interrupt that is asserted when an MSI is received
- pex-clk-supply: Supply voltage for internal reference clock
- vdd-supply: Power supply for controller (1.05V)
- bus-range: Range of bus numbers associated with this controller
- #address-cells: Address representation for root ports (must be 3)
- cell 0 specifies the bus and device numbers of the root port:
[23:16]: bus number
[15:11]: device number
- cell 1 denotes the upper 32 address bits and should be 0
- cell 2 contains the lower 32 address bits and is used to translate to the
CPU address space
- #size-cells: Size representation for root ports (must be 2)
- ranges: Describes the translation of addresses for root ports and standard
PCI regions. The entries must be 6 cells each, where the first three cells
correspond to the address as described for the #address-cells property
above, the fourth cell is the physical CPU address to translate to and the
fifth and six cells are as described for the #size-cells property above.
- The first two entries are expected to translate the addresses for the root
port registers, which are referenced by the assigned-addresses property of
the root port nodes (see below).
- The remaining entries setup the mapping for the standard I/O, memory and
prefetchable PCI regions. The first cell determines the type of region
that is setup:
- 0x81000000: I/O memory region
- 0x82000000: non-prefetchable memory region
- 0xc2000000: prefetchable memory region
Please refer to the standard PCI bus binding document for a more detailed
explanation.
- clocks: List of clock inputs of the controller. Must contain an entry for
each entry in the clock-names property.
- clock-names: Must include the following entries:
"pex": The Tegra clock of that name
"afi": The Tegra clock of that name
"pcie_xclk": The Tegra clock of that name
"pll_e": The Tegra clock of that name

Root ports are defined as subnodes of the PCIe controller node.

Required properties:
- device_type: Must be "pci"
- assigned-addresses: Address and size of the port configuration registers
- reg: PCI bus address of the root port
- #address-cells: Must be 3
- #size-cells: Must be 2
- ranges: Sub-ranges distributed from the PCIe controller node. An empty
property is sufficient.
- nvidia,num-lanes: Number of lanes to use for this port. Valid combinations
are:
- Root port 0 uses 4 lanes, root port 1 is unused.
- Both root ports use 2 lanes.

Example:

SoC DTSI:

pcie-controller {
compatible = "nvidia,tegra20-pcie";
device_type = "pci";
reg = <0x80003000 0x00000800 /* PADS registers */
0x80003800 0x00000200 /* AFI registers */
0x90000000 0x10000000>; /* configuration space */
reg-names = "pads", "afi", "cs";
interrupts = <0 98 0x04 /* controller interrupt */
0 99 0x04>; /* MSI interrupt */
interrupt-names = "intr", "msi";

bus-range = <0x00 0xff>;
#address-cells = <3>;
#size-cells = <2>;

ranges = <0x82000000 0 0x80000000 0x80000000 0 0x00001000 /* port 0 registers */
0x82000000 0 0x80001000 0x80001000 0 0x00001000 /* port 1 registers */
0x81000000 0 0 0x82000000 0 0x00010000 /* downstream I/O */
0x82000000 0 0xa0000000 0xa0000000 0 0x10000000 /* non-prefetchable memory */
0xc2000000 0 0xb0000000 0xb0000000 0 0x10000000>; /* prefetchable memory */

clocks = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 74>,
<&tegra_car 118>;
clock-names = "pex", "afi", "pcie_xclk", "pll_e";
status = "disabled";

pci@1,0 {
device_type = "pci";
assigned-addresses = <0x82000800 0 0x80000000 0 0x1000>;
reg = <0x000800 0 0 0 0>;
status = "disabled";

#address-cells = <3>;
#size-cells = <2>;

ranges;

nvidia,num-lanes = <2>;
};

pci@2,0 {
device_type = "pci";
assigned-addresses = <0x82001000 0 0x80001000 0 0x1000>;
reg = <0x001000 0 0 0 0>;
status = "disabled";

#address-cells = <3>;
#size-cells = <2>;

ranges;

nvidia,num-lanes = <2>;
};
};


Board DTS:

pcie-controller {
status = "okay";

vdd-supply = <&pci_vdd_reg>;
pex-clk-supply = <&pci_clk_reg>;

/* root port 00:01.0 */
pci@1,0 {
status = "okay";

/* bridge 01:00.0 (optional) */
pci@0,0 {
reg = <0x010000 0 0 0 0>;

#address-cells = <3>;
#size-cells = <2>;

device_type = "pci";

/* endpoint 02:00.0 */
pci@0,0 {
reg = <0x020000 0 0 0 0>;
};
};
};
};

Note that devices on the PCI bus are dynamically discovered using PCI's bus
enumeration and therefore don't need corresponding device nodes in DT. However
if a device on the PCI bus provides a non-probeable bus such as I2C or SPI,
device nodes need to be added in order to allow the bus' children to be
instantiated at the proper location in the operating system's device tree (as
illustrated by the optional nodes in the example above).


Attachments:
(No filename) (0.00 B)
(No filename) (836.00 B)
Download all attachments

2013-04-10 22:47:00

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 04/05/2013 12:03 AM, Thierry Reding wrote:
> On Thu, Apr 04, 2013 at 03:30:01PM -0600, Stephen Warren wrote:
>> On 04/04/2013 03:28 PM, Stephen Warren wrote:
>>> On 04/03/2013 08:45 AM, Thierry Reding wrote:
>>>> Move the PCIe driver from arch/arm/mach-tegra into the
>>>> drivers/pci/host directory. The motivation is to collect
>>>> various host controller drivers in the same location in order
>>>> to facilitate refactoring.
>>>>
>>>> The Tegra PCIe driver has been largely rewritten, both in
>>>> order to turn it into a proper platform driver and to add MSI
>>>> (based on code by Krishna Kishore <[email protected]>) as
>>>> well as device tree support.
>>>
>>>> arch/arm/boot/dts/tegra20.dtsi | 53 +
>>>
>>> I guess this has to touch both the driver and the dtsi file so
>>> that bisectabilty isn't broken? I guess that's OK.
>>
>> Actually, can't you move all the *.dts/*.dtsi changes to the
>> start of the series, then put the driver conversion patch last,
>> to avoid any bisectability issues and still keep code and DT
>> changes separate?
>
> I'm not sure if that'll work. There's this oddity in the Harmony
> DTS where the regulator@3 is disabled with a comment that this is a
> hack required until board-harmony-pcie.c is removed. If we change
> the DTS before the driver rewrite I think that would break
> requesting the GPIO in the board file.

Hmm. Well I guess that PCIe doesn't really work right now anyway. I
mean the enumeration works, but actual instantiation of the Linux
devices doesn't due to the resource conflict issues, which your new
driver works around.

As such, it might be simplest to just submit all the following in a
single kernel version:

* Delete old Tegra PCI support.
* Add new driver (add not move); this would just touch drivers/pci/host.
* Add all the required new DT content.

That would be basically what you've already posted, except that the DT
changes in this patch can be moved later, and all the arch/arm/*.c
patches can happen earlier if you want.

Does that sound reasonable?

I think you can do:

* All the DT changes first, except the one-line change to enable the
regulator. DT additions for the new PCIe controller would be
status=disabled.

* Add/move the new PCI.

We'd probably still want all this to go through a single tree so (i.e.
the Tegra tree, and not submit the drivers/pci/host addition through
the PCI tree) to avoid ever having 2 drivers at once for the Tegra PCI
controller.

2013-04-10 23:05:48

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 04/05/2013 01:37 AM, Thierry Reding wrote:
> On Thu, Apr 04, 2013 at 03:28:54PM -0600, Stephen Warren wrote:
> [...]
>>> diff --git
>>> a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
>>> b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
>>
>>>
>>>
+Required properties:
>>
>>> +- interrupts: the interrupt outputs of the controller +-
>>> interrupt-names: list of names to identify interrupts
>>
>> The specification part of this file should define which
>> interrupt outputs must be included in this list, and the order
>> they must appear in the list.
...
>> I believe that the entries in the interrupts property must have a
>> defined order, so I'm not sure whether interrupt-names is useful
>> here?
>
> Actually the interrupt-names property is there specifically so that
> the order doesn't matter. The driver uses
> platform_get_irq_byname(), using "intr" and "msi" respectively so
> that they don't have to appear in a specific order. I did this so
> that it is more in line with properties such as clocks and reg.

I believe that reg and interrupts must be possible to interpret
without resorting to reg-names and interrupt-names. In other words,
one must always define a specific order for those properties, and the
-names properties must follow that order. This is different than some
newer bindings where the order is arbitrary and based on whatever
random order the entries appear in *-names.

Given that, I'm not actually convinced that reg-names or
interrupt-names actually make any sense...

For background, see:
http://www.spinics.net/lists/linux-samsung-soc/msg16704.html

>>> +- ranges: describes the translation of addresses for root
>>> ports
>>
>> Shouldn't there be some discussion re: how the range are expected
>> to be set up so that everything works? We shouldn't expect people
>> to just blindly copy the example without any way of understanding
>> it.
>
> Possibly. I wouldn't like to go too much into the details, though,
> since the ranges property is not only rather complex but also well
> documented in other places.

The complexity is exactly why this needs to be fully documented. It's
very important to document not only what needs to be there, but also
the rationale behind the choice of exactly what and how to represent
in ranges. This is necessary so that (a) everyone who participated in
the discussion of that ranges definition can ensure that the final
result and rationale is what was expected (b) if somebody else extends
the binding for a new Tegra SoC in the future, they will have enough
information to fully respect the rationale behind the existing binding.

Is ranges really well-documented elsewhere? The raw property certainly
is (although a link would be useful), but I'm not convinced that the
concept of what each cell in the PCIe controller's own internal
address space means actually is documented. Otherwise, why would there
have been such a discussion on this topic?

> But I could add some explanation about which entries are expected
> and how they work together. In this case even order is important.
> The port register entries need to be listed before the
> non-prefetchable memory window entry, otherwise the ranges parser
> gets confused. How does the following sound?
>
> - ranges: Describes the translation of addresses for root ports and
> standard PCI regions. The entries must be 6 cells each, where the
> first three cells correspond to the address as described for the
> #address-cells property above, the fourth cell is the physical CPU
> address to translate to and the fifth and six cells are as
> described for the #size-cells property above. - The first two
> entries are expected to translate the addresses for the root port
> registers, which are referenced by the assigned-addresses property
> of the root port nodes (see below). - The remaining entries setup
> the mapping for the standard I/O, memory and

s/setup/set up/

> prefetchable PCI regions. The first cell determines the type of
> region

I assume this definition of "the first cell determines ..." only
applies to these remaining entries, and not the first two entries.
Perhaps prefix that sentence with "For these entries, "?

> that is setup: - 0x81000000: I/O memory region - 0x82000000:
> non-prefetchable memory region - 0xc2000000: prefetchable memory
> region Please refer to the standard PCI bus binding document for a
> more detailed explanation.

That looks OK, yes.


>>> +- clocks: the clock inputs of the controller +- clock-names:
>>> list of names to identify clocks
>>
>> The specification part of this file should define which clocks
>> are required, and not rely on the example below to do this. I
>> would suggest re-writing this as:
>>
>> - clocks : Must contain an entry for each entry in clock-names. -
>> clock-names : Must include the following entries: "pex" (The
>> Tegra clock of that name) "afi" (The Tegra clock of that name)
>> "pcie_xclk" (The Tegra clock of that name) "pll_e" (The Tegra
>> clock of that name)
>
> Okay, sounds good. Although the Tegra clocks are named somewhat
> differently.

Hmm. That's true. Perhaps replace "(The Tegra clock of that name)"
with some more logical-level description of what those clocks are used
for by the module.

> "pex" is named "pcie" and "pcie_xclk" is "unassigned" (!) according
> to the nvidia,tegra20-car.txt binding document. Perhaps I should
> update the binding document to replace unassigned with pcie_xclk
> for clock 74.

The CAR binding shouldn't change for now at least. The clock IDs there
were chosen to match /both/ the CAR clock enable registers /and/ the
CAR module reset registers. Where there isn't an exact 1:1
correspondance between those two sets of registers, that clock ID was
not used, and instead a clock ID >96 was chosen. This was done to
hightlight the lack of 1:1 correspondence. See the explanatory text
before the list, in the CAR binding document.

> And maybe rename pex in the PCIe binding to pcie to match the CAR
> binding?

The PCIe binding should really name its resources based on what makes
sense for the PCIe HW in isolation. pex is probably fine here,
especially if it happens to match some PCIe signal.

>>> +Board DTS: + + pcie-controller { + status = "okay"; + +
>>> vdd-supply = <&pci_vdd_reg>; + pex-clk-supply =
>>> <&pci_clk_reg>; + + /* root port 00:01.0 */ + pci@1,0 { +
>>> status = "okay";
>>
>> Is it worth putting a comment here stating that the explicit
>> devices included below in this example are entirely optional?
>
> Would it be acceptable to annotate the 01:00.0 bridge with
> "optional"? Like so:
>
> pcie-controller { ... pci@1,0 { ... /* bridge 01:00.0 (optional)
> */ pci@0,0 {

Yes, that'd be fine.

> Alternatively I could add something like below:
>
> ---snip---
>
> Note that devices on the PCI bus are dynamically discovered using
> PCI's bus enumeration and therefore don't need corresponding device
> nodes in DT. However if a device on the PCI bus provides a
> non-probeable bus such as I2C or SPI, device nodes need to be added
> in order to allow the bus' children to be instantiated at the
> proper location in the operating system's device tree (as
> illustrated by the optional nodes in the example above).
>
> ---snip---
>
> Maybe I'll just do both.

Yes, that explanatory text is very useful & good.

2013-04-15 18:28:22

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 04/03/2013 08:45 AM, Thierry Reding wrote:
> Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
> directory. The motivation is to collect various host controller drivers
> in the same location in order to facilitate refactoring.
>
> The Tegra PCIe driver has been largely rewritten, both in order to turn
> it into a proper platform driver and to add MSI (based on code by
> Krishna Kishore <[email protected]>) as well as device tree support.

> diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c

> +struct tegra_msi {
> + DECLARE_BITMAP(used, INT_PCI_MSI_NR);
> + struct irq_domain *domain;
> + struct msi_chip chip;

Nit: You could move that to be the first field in the struct, then
to_tegra_msi() would end up being a no-op, which might save a byte or two.

> +static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn,
> + int where, int size, u32 value)
...
> + if (bus->number == 0) {
> + unsigned int slot = PCI_SLOT(devfn);
> + struct tegra_pcie_port *port;
> +
> + list_for_each_entry(port, &pcie->ports, list) {
> + if (port->index + 1 == slot) {
> + addr = port->base + (where & ~3);
> + break;
> + }
> + }
> + } else {
> + addr = tegra_pcie_bus_map(pcie, bus->number);
> + if (!addr) {
> + dev_err(pcie->dev,
> + "failed to map cfg. space for bus %u\n",
> + bus->number);
> + return PCIBIOS_DEVICE_NOT_FOUND;
> + }
> +
> + addr += tegra_pcie_conf_offset(devfn, where);
> + }

It seems like that chunk of code could be shared between
tegra_pcie_read_conf() and tegra_pcie_write_conf().

Also, tegra_pcie_read_conf() checks again for addr == NULL and returns
if so. read and write should be consistent.

> +static void tegra_pcie_port_free(struct tegra_pcie_port *port)
> +{
> + struct tegra_pcie *pcie = port->pcie;
> +
> + devm_iounmap(pcie->dev, port->base);
> + devm_release_mem_region(pcie->dev, port->regs.start,
> + resource_size(&port->regs));
> + list_del(&port->list);
> + devm_kfree(pcie->dev, port);
> +}

Do ports get allocated and freed separately from the host controller,
such that it's actually worth manually calling the
devm_iounmap/release/free functions?

> +static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
...
> + /* disable all exceptions */
> + afi_writel(pcie, 0, AFI_FPCI_ERROR_MASKS);

Is that a good idea?

> +static int tegra_pcie_get_resources(struct tegra_pcie *pcie)

> + err = devm_request_irq(&pdev->dev, pcie->irq, tegra_pcie_isr,
> + IRQF_SHARED, "PCIE", pcie);

devm_request_irq and IRQF_SHARED sounds like a bad combination; what if
the IRQ goes off after this driver's remove() is called, but before the
driver core devm cleanup runs?

> +static irqreturn_t tegra_pcie_msi_irq(int irq, void *data)
...
> + return IRQ_HANDLED;

Shouldn't this function return IRQ_NONE if no MSI status bits were found
set?

> +static int tegra_pcie_enable_msi(struct tegra_pcie *pcie)

> + /* setup AFI/FPCI range */
> + msi->pages = __get_free_pages(GFP_KERNEL, 3);

If tegra_msi_setup_irq() hard-codes the MSI address as msi->pages, then
I expect you can get away with a single page here. Of course, perhaps
tegra_msi_setup_irq() is supposed to give a different address to every
MSI client? Even so, 256 clients * 4 bytes is still less than 1 page.

> +static u32 tegra_pcie_get_xbar_config(struct tegra_pcie *pcie, u32 lanes)
> +{
> + struct device_node *np = pcie->dev->of_node;
> +
> + switch (lanes) {
> + case 0x00000004:
> + dev_info(pcie->dev, "single-mode configuration\n");
> + return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE;
> +
> + case 0x00000202:
> + dev_info(pcie->dev, "dual-mode configuration\n");
> + return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
> + }
> +
> + return 0;

Shouldn't that return an error, and dev_err() about it? If not, then I
think using one of the #defines for the default would make the result a
lot more obvious.

> +static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)

> + pcie->xbar_config = tegra_pcie_get_xbar_config(pcie, lanes);
> + if (!pcie->xbar_config) {
> + dev_err(pcie->dev, "invalid lane configuration\n");
> + return -EINVAL;
> + }

Oh, but AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE==0, which is valid...

> +static bool tegra_pcie_port_check_link(struct tegra_pcie_port *port)
...
> + if (value & 0x20000000)
> + return true;

Can we use a #define for 0x20000000?

> +static int tegra_pcie_enable(struct tegra_pcie *pcie)
> +{
> + struct tegra_pcie_port *port, *tmp;
> + struct hw_pci hw;
> +
> + list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
> + dev_info(pcie->dev, "probing port %u, using %u lanes\n",
> + port->index, port->lanes);
> +
> + tegra_pcie_port_enable(port);
> +
> + if (tegra_pcie_port_check_link(port))
> + continue;
> +
> + dev_info(pcie->dev, "link %u down, ignoring\n", port->index);
> +
> + tegra_pcie_port_disable(port);
> + tegra_pcie_port_free(port);
> + }

Why is that needed; when would a port get enabled if it was already
enabled, and if it was already enabled, wouldn't you want this function
to be a no-op rather than destroying everything and starting again?

> +static int tegra_pcie_probe(struct platform_device *pdev)
...
> + pcibios_min_mem = 0;

What does that mean/do? I wonder if that should be set to 0x80000000 by
the Tegra30 patches?

2013-06-12 12:30:07

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On Mon, Apr 15, 2013 at 12:28:12PM -0600, Stephen Warren wrote:
> On 04/03/2013 08:45 AM, Thierry Reding wrote:
> > Move the PCIe driver from arch/arm/mach-tegra into the drivers/pci/host
> > directory. The motivation is to collect various host controller drivers
> > in the same location in order to facilitate refactoring.
> >
> > The Tegra PCIe driver has been largely rewritten, both in order to turn
> > it into a proper platform driver and to add MSI (based on code by
> > Krishna Kishore <[email protected]>) as well as device tree support.
>
> > diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
>
> > +struct tegra_msi {
> > + DECLARE_BITMAP(used, INT_PCI_MSI_NR);
> > + struct irq_domain *domain;
> > + struct msi_chip chip;
>
> Nit: You could move that to be the first field in the struct, then
> to_tegra_msi() would end up being a no-op, which might save a byte or two.

Done.

> > +static int tegra_pcie_write_conf(struct pci_bus *bus, unsigned int devfn,
> > + int where, int size, u32 value)
> ...
> > + if (bus->number == 0) {
> > + unsigned int slot = PCI_SLOT(devfn);
> > + struct tegra_pcie_port *port;
> > +
> > + list_for_each_entry(port, &pcie->ports, list) {
> > + if (port->index + 1 == slot) {
> > + addr = port->base + (where & ~3);
> > + break;
> > + }
> > + }
> > + } else {
> > + addr = tegra_pcie_bus_map(pcie, bus->number);
> > + if (!addr) {
> > + dev_err(pcie->dev,
> > + "failed to map cfg. space for bus %u\n",
> > + bus->number);
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > + }
> > +
> > + addr += tegra_pcie_conf_offset(devfn, where);
> > + }
>
> It seems like that chunk of code could be shared between
> tegra_pcie_read_conf() and tegra_pcie_write_conf().
>
> Also, tegra_pcie_read_conf() checks again for addr == NULL and returns
> if so. read and write should be consistent.

Done.

> > +static void tegra_pcie_port_free(struct tegra_pcie_port *port)
> > +{
> > + struct tegra_pcie *pcie = port->pcie;
> > +
> > + devm_iounmap(pcie->dev, port->base);
> > + devm_release_mem_region(pcie->dev, port->regs.start,
> > + resource_size(&port->regs));
> > + list_del(&port->list);
> > + devm_kfree(pcie->dev, port);
> > +}
>
> Do ports get allocated and freed separately from the host controller,
> such that it's actually worth manually calling the
> devm_iounmap/release/free functions?

Yes, they can be freed separately. When a link is down and hence no
device is connected to the port, tegra_pcie_enable() will disable and
free that port.

That's in fact the only place where this is called because we don't
actually support removing the controller. I don't know of any ARM
architecture support to do that (i.e. there is no counterpart to
pci_common_init()).

> > +static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
> ...
> > + /* disable all exceptions */
> > + afi_writel(pcie, 0, AFI_FPCI_ERROR_MASKS);
>
> Is that a good idea?

I have no idea what that register means. It's one of the undocumented
registers. Note that I also didn't change this, it's been there since
the beginning.

It's probably worth finding out about the register's meaning and about
how to catch these exceptions at runtime, though. I'm adding Jay on Cc
maybe he can research internally.

> > +static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
>
> > + err = devm_request_irq(&pdev->dev, pcie->irq, tegra_pcie_isr,
> > + IRQF_SHARED, "PCIE", pcie);
>
> devm_request_irq and IRQF_SHARED sounds like a bad combination; what if
> the IRQ goes off after this driver's remove() is called, but before the
> driver core devm cleanup runs?

You're right. Changed to request_irq() in tegra_pcie_get_resources() and
added free_irq() to tegra_pcie_put_resources().

> > +static irqreturn_t tegra_pcie_msi_irq(int irq, void *data)
> ...
> > + return IRQ_HANDLED;
>
> Shouldn't this function return IRQ_NONE if no MSI status bits were found
> set?

The IRQ isn't marked IRQF_SHARED, so I don't think this is needed.

> > +static int tegra_pcie_enable_msi(struct tegra_pcie *pcie)
>
> > + /* setup AFI/FPCI range */
> > + msi->pages = __get_free_pages(GFP_KERNEL, 3);
>
> If tegra_msi_setup_irq() hard-codes the MSI address as msi->pages, then
> I expect you can get away with a single page here. Of course, perhaps
> tegra_msi_setup_irq() is supposed to give a different address to every
> MSI client? Even so, 256 clients * 4 bytes is still less than 1 page.

Yes, I'll try setting the order to 0 and see if that still works. I
don't have a setup with more than one MSI client so test results may not
be very reliable, though.

> > +static u32 tegra_pcie_get_xbar_config(struct tegra_pcie *pcie, u32 lanes)
> > +{
> > + struct device_node *np = pcie->dev->of_node;
> > +
> > + switch (lanes) {
> > + case 0x00000004:
> > + dev_info(pcie->dev, "single-mode configuration\n");
> > + return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE;
> > +
> > + case 0x00000202:
> > + dev_info(pcie->dev, "dual-mode configuration\n");
> > + return AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_DUAL;
> > + }
> > +
> > + return 0;
>
> Shouldn't that return an error, and dev_err() about it? If not, then I
> think using one of the #defines for the default would make the result a
> lot more obvious.

I've changed tegra_pcie_get_xbar_config() to return -EINVAL if an
invalid combination of lanes is encountered and 0 otherwise, passing
back the corresponding define in an output parameter. I think it's
reasonable to fail if an invalid combination is provided instead of
assuming that some default will work.

> > +static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)
>
> > + pcie->xbar_config = tegra_pcie_get_xbar_config(pcie, lanes);
> > + if (!pcie->xbar_config) {
> > + dev_err(pcie->dev, "invalid lane configuration\n");
> > + return -EINVAL;
> > + }
>
> Oh, but AFI_PCIE_CONFIG_SM2TMS0_XBAR_CONFIG_SINGLE==0, which is valid...

Fixed by the above change.

> > +static bool tegra_pcie_port_check_link(struct tegra_pcie_port *port)
> ...
> > + if (value & 0x20000000)
> > + return true;
>
> Can we use a #define for 0x20000000?

Yes, we could use something like RP_LINK_CONTROL_STATUS_DL_LINK_ACTIVE
from the TRM, though it's rather long...

> > +static int tegra_pcie_enable(struct tegra_pcie *pcie)
> > +{
> > + struct tegra_pcie_port *port, *tmp;
> > + struct hw_pci hw;
> > +
> > + list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
> > + dev_info(pcie->dev, "probing port %u, using %u lanes\n",
> > + port->index, port->lanes);
> > +
> > + tegra_pcie_port_enable(port);
> > +
> > + if (tegra_pcie_port_check_link(port))
> > + continue;
> > +
> > + dev_info(pcie->dev, "link %u down, ignoring\n", port->index);
> > +
> > + tegra_pcie_port_disable(port);
> > + tegra_pcie_port_free(port);
> > + }
>
> Why is that needed; when would a port get enabled if it was already
> enabled, and if it was already enabled, wouldn't you want this function
> to be a no-op rather than destroying everything and starting again?

I'm not sure I understand what you're saying. The intent of this
function is to enable a port, check that there's a link and disable the
port otherwise. That way we don't keep ports around where nothing is
connected.

> > +static int tegra_pcie_probe(struct platform_device *pdev)
> ...
> > + pcibios_min_mem = 0;
>
> What does that mean/do? I wonder if that should be set to 0x80000000 by
> the Tegra30 patches?

ARM defines PCIBIOS_MIN_MEM to that variable. That macro in turn is only
used by pci_bus_alloc_resource() AFAICT, which uses it to override the
start of a resource when allocating if res->start == 0. As such it
designates a lower-bound of valid PCI memory addresses, so 0 on Tegra20
and 0x80000000 on Tegra30 don't seem like good values. Maybe we need to
set them to the lowest of the prefetchable and non-prefetchable memory
areas as defined in the DT?

It doesn't currently seem to matter at all, though, since we never pass
in a range that's 0, so the start address of resources can never be 0
and therefore PCIBIOS_MIN_MEM is never used.

Thierry


Attachments:
(No filename) (7.91 kB)
(No filename) (836.00 B)
Download all attachments

2013-06-12 16:09:16

by Stephen Warren

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On 06/12/2013 06:30 AM, Thierry Reding wrote:
> On Mon, Apr 15, 2013 at 12:28:12PM -0600, Stephen Warren wrote:
>> On 04/03/2013 08:45 AM, Thierry Reding wrote:
>>> Move the PCIe driver from arch/arm/mach-tegra into the
>>> drivers/pci/host directory. The motivation is to collect
>>> various host controller drivers in the same location in order
>>> to facilitate refactoring.
>>>
>>> The Tegra PCIe driver has been largely rewritten, both in order
>>> to turn it into a proper platform driver and to add MSI (based
>>> on code by Krishna Kishore <[email protected]>) as well as
>>> device tree support.
...
>>> +static irqreturn_t tegra_pcie_msi_irq(int irq, void *data)
>> ...
>>> + return IRQ_HANDLED;
>>
>> Shouldn't this function return IRQ_NONE if no MSI status bits
>> were found set?
>
> The IRQ isn't marked IRQF_SHARED, so I don't think this is needed.

Isn't it still useful to detect unexpected/stuck interrupts?

>>> +static int tegra_pcie_enable(struct tegra_pcie *pcie)
...
>>
>> Why is that needed; when would a port get enabled if it was
>> already enabled, and if it was already enabled, wouldn't you want
>> this function to be a no-op rather than destroying everything and
>> starting again?
>
> I'm not sure I understand what you're saying. The intent of this
> function is to enable a port, check that there's a link and disable
> the port otherwise. That way we don't keep ports around where
> nothing is connected.

Hmm. I have no idea either now:-( The body of tegra_pcie_enable()
looks fine.

>>> +static int tegra_pcie_probe(struct platform_device *pdev)
>> ...
>>> + pcibios_min_mem = 0;
>>
>> What does that mean/do? I wonder if that should be set to
>> 0x80000000 by the Tegra30 patches?
>
> ARM defines PCIBIOS_MIN_MEM to that variable. That macro in turn is
> only used by pci_bus_alloc_resource() AFAICT, which uses it to
> override the start of a resource when allocating if res->start ==
> 0. As such it designates a lower-bound of valid PCI memory
> addresses, so 0 on Tegra20 and 0x80000000 on Tegra30 don't seem
> like good values. Maybe we need to set them to the lowest of the
> prefetchable and non-prefetchable memory areas as defined in the
> DT?
>
> It doesn't currently seem to matter at all, though, since we never
> pass in a range that's 0, so the start address of resources can
> never be 0 and therefore PCIBIOS_MIN_MEM is never used.

Hmmm. I guess ignore it then. If the value won't ever be used, 0 is as
good a value as any?

2013-06-12 21:23:16

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 07/12] PCI: tegra: Move PCIe driver to drivers/pci/host

On Wed, Jun 12, 2013 at 10:09:10AM -0600, Stephen Warren wrote:
> On 06/12/2013 06:30 AM, Thierry Reding wrote:
> > On Mon, Apr 15, 2013 at 12:28:12PM -0600, Stephen Warren wrote:
> >> On 04/03/2013 08:45 AM, Thierry Reding wrote:
> >>> Move the PCIe driver from arch/arm/mach-tegra into the
> >>> drivers/pci/host directory. The motivation is to collect
> >>> various host controller drivers in the same location in order
> >>> to facilitate refactoring.
> >>>
> >>> The Tegra PCIe driver has been largely rewritten, both in order
> >>> to turn it into a proper platform driver and to add MSI (based
> >>> on code by Krishna Kishore <[email protected]>) as well as
> >>> device tree support.
> ...
> >>> +static irqreturn_t tegra_pcie_msi_irq(int irq, void *data)
> >> ...
> >>> + return IRQ_HANDLED;
> >>
> >> Shouldn't this function return IRQ_NONE if no MSI status bits
> >> were found set?
> >
> > The IRQ isn't marked IRQF_SHARED, so I don't think this is needed.
>
> Isn't it still useful to detect unexpected/stuck interrupts?

Yes, you're right. I can't think of a nicer way to do it, though, so
I'll go with a processed IRQ counter or a flag.

> >>> +static int tegra_pcie_probe(struct platform_device *pdev)
> >> ...
> >>> + pcibios_min_mem = 0;
> >>
> >> What does that mean/do? I wonder if that should be set to
> >> 0x80000000 by the Tegra30 patches?
> >
> > ARM defines PCIBIOS_MIN_MEM to that variable. That macro in turn is
> > only used by pci_bus_alloc_resource() AFAICT, which uses it to
> > override the start of a resource when allocating if res->start ==
> > 0. As such it designates a lower-bound of valid PCI memory
> > addresses, so 0 on Tegra20 and 0x80000000 on Tegra30 don't seem
> > like good values. Maybe we need to set them to the lowest of the
> > prefetchable and non-prefetchable memory areas as defined in the
> > DT?
> >
> > It doesn't currently seem to matter at all, though, since we never
> > pass in a range that's 0, so the start address of resources can
> > never be 0 and therefore PCIBIOS_MIN_MEM is never used.
>
> Hmmm. I guess ignore it then. If the value won't ever be used, 0 is as
> good a value as any?

Alright, I'll do that then.

Thierry


Attachments:
(No filename) (2.15 kB)
(No filename) (836.00 B)
Download all attachments