[RFC PATCH 1/3] pci: APM X-Gene PCIe controller driver

Tanmay Inamdar tinamdar at apm.com
Mon Jan 6 21:41:34 EST 2014


Thanks for your comments. Please see some inline replies.

On Fri, Jan 3, 2014 at 4:07 AM, Arnd Bergmann <arnd at arndb.de> wrote:
> On Monday 23 December 2013, Tanmay Inamdar wrote:
>> This patch adds the AppliedMicro X-gene SOC PCIe controller driver.
>> APM X-Gene PCIe controller supports maximum upto 8 lanes and
>> GEN3 speed. X-Gene has maximum 5 PCIe ports supported.
>>
>> Signed-off-by: Tanmay Inamdar <tinamdar at apm.com>
>> ---
>>  drivers/pci/host/Kconfig      |    5 +
>>  drivers/pci/host/Makefile     |    1 +
>>  drivers/pci/host/pcie-xgene.c | 1017 +++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 1023 insertions(+)
>>  create mode 100644 drivers/pci/host/pcie-xgene.c
>>
>> diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
>> index 47d46c6..6d8fcbc 100644
>> --- a/drivers/pci/host/Kconfig
>> +++ b/drivers/pci/host/Kconfig
>> @@ -33,4 +33,9 @@ config PCI_RCAR_GEN2
>>         There are 3 internal PCI controllers available with a single
>>         built-in EHCI/OHCI host controller present on each one.
>>
>> +config PCI_XGENE
>> +     bool "X-Gene PCIe controller"
>> +     depends on ARCH_XGENE
>> +     depends on OF
>
> Please add a help text here.

ok

>
>> +#ifdef CONFIG_ARM64
>> +#include <asm/pcibios.h>
>> +#else
>> +#include <asm/mach/pci.h>
>> +#endif
>
> What is !ARM64 case? Is this for PowerPC or ARM? Since you depend on
> ARCH_XGENE in Kconfig I guess neither case can actually happen,
> so you can remove the #ifdef.

ok

>
>> +#define CFG_CONSTANTS_31_00          0x2000
>> +#define CFG_CONSTANTS_63_32          0x2004
>> +#define CFG_CONSTANTS_159_128                0x2010
>> +#define CFG_CONSTANTS_415_384           0x2030
>
> These macros do not seem helpful. If you don't have meaningful register
> names, just don't provide any and address the registers by index.

ok

>
>> +#define ENABLE_L1S_POWER_MGMT_SET(dst, src)     (((dst) & ~0x02000000) | \
>> +                                             (((u32)(src) << 0x19) & \
>> +                                             0x02000000))
>
> Makes this an inline function, or open-code it in the caller if there
> is only one.
>

ok

>> +#ifdef CONFIG_64BIT
>> +#define pci_io_offset(s)             (s & 0xff00000000)
>> +#else
>> +#define pci_io_offset(s)             (s & 0x00000000)
>> +#endif /* CONFIG_64BIT */
>
> Why is this needed? The I/O space can never be over 0xffffffff,
> or in practice 0xffff. My best guess is that you have a bug in the
> function parsing your ranges property, or in the property value.

I will recheck the logic.

>
>> +static inline struct xgene_pcie_port *
>> +xgene_pcie_sys_to_port(struct pci_sys_data *sys)
>> +{
>> +     return (struct xgene_pcie_port *)sys->private_data;
>> +}
>
> You shouldn't need the cast, or the accessor function, since private_data
> is already a void pointer.

got it.

>
>> +/* IO ports are memory mapped */
>> +void __iomem *__pci_ioport_map(struct pci_dev *dev, unsigned long port,
>> +                            unsigned int nr)
>> +{
>> +     return devm_ioremap_nocache(&dev->dev, port, nr);
>> +}
>
> This can't be in the host driver, since you can have only one instance
> of the function in the system, but you probably want multiple host
> drivers in a multiplatform kernel on ARM64.

You are right. It will fail multiplatform kernel.

>
> Also, the implementation is wrong since the I/O port range already needs
> to be ioremapped in order for inb/outb to work. There is already a
> generic implementation of this in include/asm-generic/iomap.h, which
> correctly calls ioport_map. Make sure that arm64 uses this implementation
> and provides an ioport_map() function like
>
> static inline void __iomem *ioport_map(unsigned long port, unsigned int nr)
> {
>         return PCI_IOBASE + port;
> }

For X-Gene, IO regions are memory mapped IO regions. So I am not sure
if 'ioport_map'
would work.

>
>> +/* PCIE Out/In to CSR */
>> +static inline void xgene_pcie_out32(void *addr, u32 val)
>> +{
>> +     pr_debug("pcie csr wr: 0x%llx 0x%08x\n", (phys_addr_t)addr, val);
>> +     writel(val, addr);
>> +}
>> +
>> +static inline void xgene_pcie_in32(void *addr, u32 *val)
>> +{
>> +     *val = readl(addr);
>> +     pr_debug("pcie csr rd: 0x%llx 0x%08x\n", (phys_addr_t)addr, *val);
>> +}
>
> These add no value, just remove them. If your code is so buggy that
> you need to print every register access to the debug log, we don't
> want it ;-)

Yep. I will remove it.

>
>> +static inline void xgene_pcie_cfg_out16(void *addr, u16 val)
>> +{
>> +     phys_addr_t temp_addr = (phys_addr_t) addr & ~0x3;
>> +     u32 val32 = readl((void *)temp_addr);
>> +
>> +     switch ((phys_addr_t) addr & 0x3) {
>> +     case 2:
>> +             val32 &= ~0xFFFF0000;
>> +             val32 |= (u32) val << 16;
>> +             break;
>> +     case 0:
>> +     default:
>> +             val32 &= ~0xFFFF;
>> +             val32 |= val;
>> +             break;
>> +     }
>> +     writel(val32, (void *)temp_addr);
>> +}
>
> Isn't there a generic version of this? If not, should there be one?
> Maybe Bjorn can comment.
>
> Also, all the typecasts are wrong. Please think about what types
> you really want and fix them.

ok

>
>> +static void xgene_pcie_set_rtdid_reg(struct pci_bus *bus, uint devfn)
>> +{
>> +     struct xgene_pcie_port *port = xgene_pcie_bus_to_port(bus);
>> +     unsigned int b, d, f;
>> +     u32 rtdid_val = 0;
>> +
>> +     b = bus->number;
>> +     d = PCI_SLOT(devfn);
>> +     f = PCI_FUNC(devfn);
>> +
>> +     if (bus->number == port->first_busno)
>> +             rtdid_val = (b << 24) | (d << 19) | (f << 16);
>> +     else if (bus->number >= (port->first_busno + 1))
>> +             rtdid_val = (port->first_busno << 24) |
>> +             (b << 8) | (d << 3) | f;
>> +
>> +     xgene_pcie_out32(port->csr_base + RTDID, rtdid_val);
>> +     /* read the register back to ensure flush */
>> +     xgene_pcie_in32(port->csr_base + RTDID, &rtdid_val);
>> +}
>
> What is an 'rtdid'? Maybe add some comments

RTDID should be set with correct bdf to access the EP config space. I
will add comments.

>
>> +static void xgene_pcie_setup_lanes(struct xgene_pcie_port *port)
>> +{
>> +     void *csr_base = port->csr_base;
>> +     u32 val;
>> +
> ...
>> +static void xgene_pcie_setup_link(struct xgene_pcie_port *port)
>> +{
>> +     void *csr_base = port->csr_base;
>> +     u32 val;
>> +
>
> Don't these belong into the PHY driver? Can the setup be done in the
> firmware instead so we don't have to bother with it in Linux?
> Presumably you already need PCI support at boot time already if
> you want to boot from a PCI device.

They do look like phy setup functions but they are part of PCIe core
register space.

>
>> +static void xgene_pcie_config_pims(void *csr_base, u32 addr,
>> +                                u64 pim, resource_size_t size)
>> +{
>> +     u32 val;
>> +
>> +     xgene_pcie_out32(csr_base + addr, lower_32_bits(pim));
>> +     val = upper_32_bits(pim) | EN_COHERENCY;
>> +     xgene_pcie_out32(csr_base + addr + 0x04, val);
>> +     xgene_pcie_out32(csr_base + addr + 0x08, 0x0);
>> +     xgene_pcie_out32(csr_base + addr + 0x0c, 0x0);
>> +     val = lower_32_bits(size);
>> +     xgene_pcie_out32(csr_base + addr + 0x10, val);
>> +     val = upper_32_bits(size);
>> +     xgene_pcie_out32(csr_base + addr + 0x14, val);
>> +}
>
> I suspect this is for programming the inbound translation window for
> DMA transactions (maybe add a comment?), and the second 64-bit word is
> for the bus-side address. Are you sure you want a translation starting
> at zero, rather than an identity-mapping like this?

Actually it is an unused sub-region. I will remove setting to 0. It
defaults to 0 anyways.

>
>         xgene_pcie_out32(csr_base + addr, lower_32_bits(pim));
>         val = upper_32_bits(pim) | EN_COHERENCY;
>         xgene_pcie_out32(csr_base + addr + 0x04, val);
>         xgene_pcie_out32(csr_base + addr + 0x08, pim & 0xffffffff);
>         xgene_pcie_out32(csr_base + addr + 0x0c, pim >> 32);
>
>> +static void xgene_pcie_setup_port(struct xgene_pcie_port *port)
>> +{
>> +     int type = port->type;
>> +
>> +     xgene_pcie_program_core(port->csr_base);
>> +     if (type == PTYPE_ROOT_PORT)
>> +             xgene_pcie_setup_root_complex(port);
>> +     else
>> +             xgene_pcie_setup_endpoint(port);
>> +}
>
> We don't really have infrastructure for PCIe endpoint devices in Linux,
> or in the generic DT binding for PCI hosts. We probably really want to
> add that in the future, but until we have decided on how to do this,
> please remove all code related to endpoint mode.

ok.

>
>> +struct device_node *pcibios_get_phb_of_node(struct pci_bus *bus)
>> +{
>> +     struct xgene_pcie_port *port = xgene_pcie_bus_to_port(bus);
>> +
>> +     return of_node_get(port->node);
>> +}
>
> Another pointless wrapper to remove.

If I remove this, then we get a failure while parsing irqs
"pci 0000:00:00.0: of_irq_parse_pci() failed with rc=-22"

>
>> +static void xgene_pcie_fixup_bridge(struct pci_dev *dev)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
>> +             dev->resource[i].start = dev->resource[i].end = 0;
>> +             dev->resource[i].flags = 0;
>> +     }
>> +}
>> +DECLARE_PCI_FIXUP_HEADER(XGENE_PCIE_VENDORID, XGENE_PCIE_BRIDGE_DEVICEID,
>> +                      xgene_pcie_fixup_bridge);
>
> Please add a comment to describe exactly what bug you are working around,
> and what devices are affected.

ok

>
>> +/*
>> + * read configuration values from DTS
>> + */
>> +static int xgene_pcie_read_dts_config(struct xgene_pcie_port *port)
>> +{
>> +     struct device_node *np = port->node;
>> +     struct resource csr_res;
>> +     u32 val32;
>> +     int ret;
>> +     const u8 *val;
>> +
>> +     val = of_get_property(np, "device_type", NULL);
>> +     if ((val != NULL) && !strcmp(val, "ep"))
>> +             port->type = PTYPE_ENDPOINT;
>> +     else
>> +             port->type = PTYPE_ROOT_PORT;
>
> "ep" is not a valid device_type for all I know.

Will remove all EP stuff.

>
>> +     ret = of_property_read_u32(np, "link_speed", &val32);
>> +     if (ret == 0)
>> +             port->link_speed = val32;
>> +     else
>> +             port->link_speed = PCIE_GEN3;
>
> I guess this should be an argument to the phy node. Isn't it the phy
> that needs to know the link speed rather than the host?

yes. some part of it still resides in the core. However I will make it
to GEN3 by default.

>
>> +static int xgene_pcie_alloc_ep_mem(struct xgene_pcie_port *port)
>> +{
>> +     struct xgene_pcie_ep_info *ep = &port->ep_info;
>> +
>> +     ep->reg_virt = dma_alloc_coherent(port->dev, XGENE_PCIE_EP_MEM_SIZE,
>> +                                       &ep->reg_phys, GFP_KERNEL);
>> +     if (ep->reg_virt == NULL)
>> +             return -ENOMEM;
>> +
>> +     dev_info(port->dev, "EP: Virt - %p Phys - 0x%llx Size - 0x%x\n",
>> +              ep->reg_virt, (u64) ep->reg_phys, XGENE_PCIE_EP_MEM_SIZE);
>> +     return 0;
>> +}
>
> remove endpoint stuff for now.

ok

>
>> +static int xgene_pcie_populate_inbound_regions(struct xgene_pcie_port *port)
>> +{
>> +     struct resource *msi_res = &port->res[XGENE_MSI];
>> +     phys_addr_t ddr_size = memblock_phys_mem_size();
>> +     phys_addr_t ddr_base = memblock_start_of_DRAM();
>
> This looks fragile. What about discontiguous memory? It's probably better to
> leave this setup to the firmware, which already has to do it.

Idea is to map whole RAM. The memory controller in X-Gene does not
allow holes or
discontinuity in RAM.

>
>> +static int xgene_pcie_parse_map_ranges(struct xgene_pcie_port *port)
>> +{
>> +     struct device_node *np = port->node;
>> +     struct of_pci_range range;
>> +     struct of_pci_range_parser parser;
>> +     struct device *dev = port->dev;
>> +     u32 cfg_map_done = 0;
>> +     int ret;
>> +
>> +     if (of_pci_range_parser_init(&parser, np)) {
>> +             dev_err(dev, "missing ranges property\n");
>> +             return -EINVAL;
>> +     }
>> +
>> +     /* Get the I/O, memory, config ranges from DT */
>> +     for_each_of_pci_range(&parser, &range) {
>> +             struct resource *res = NULL;
>> +             u64 restype = range.flags & IORESOURCE_TYPE_BITS;
>> +             u64 end = range.cpu_addr + range.size - 1;
>> +             dev_dbg(port->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n",
>> +                     range.flags, range.cpu_addr, end, range.pci_addr);
>> +
>> +             switch (restype) {
>> +             case IORESOURCE_IO:
>> +                     res = &port->res[XGENE_IO];
>> +                     of_pci_range_to_resource(&range, np, res);
>> +                     xgene_pcie_setup_ob_reg(port->csr_base, OMR1BARL,
>> +                                             XGENE_IO, res);
>> +                     break;
>> +             case IORESOURCE_MEM:
>> +                     res = &port->res[XGENE_MEM];
>> +                     of_pci_range_to_resource(&range, np, res);
>> +                     xgene_pcie_setup_ob_reg(port->csr_base, OMR2BARL,
>> +                                             XGENE_MEM, res);
>> +                     break;
>
> You also need to read out the pci_addr field from the range struct in order
> to set up the io_offset and mem_offset and the translation windows.
> Don't assume that they start at zero.

ok.

>
>> +             case 0:
>> +                     if (!cfg_map_done) {
>> +                             /* config region */
>> +                             if (port->type == PTYPE_ROOT_PORT) {
>> +                                     ret = xgene_pcie_map_cfg(port, &range);
>> +                                     if (ret)
>> +                                             return ret;
>> +                             }
>> +                             cfg_map_done = 1;
>> +                     } else {
>> +                             /* msi region */
>> +                             res = &port->res[XGENE_MSI];
>> +                             of_pci_range_to_resource(&range, np, res);
>> +                     }
>> +                     break;
>
> Don't make assumptions about the order of the ranges property. Also, neither
> the MSI register nor the config space should be in the ranges.

ok.
>
>> +static int xgene_pcie_setup(int nr, struct pci_sys_data *sys)
>> +{
>> +     struct xgene_pcie_port *pp = xgene_pcie_sys_to_port(sys);
>> +
>> +     if (pp == NULL)
>> +             return 0;
>> +
>> +     if (pp->type == PTYPE_ENDPOINT)
>> +             return 0;
>> +
>> +     sys->io_offset = pci_io_offset(pp->res[XGENE_IO].start);
>
> Normally we want io_offset to be zero, i.e. have every Bus I/O space
> window get translated to the same Linux I/O space address, i.e.
> the number you pass into pci_ioremap_io(). The code here assumes
> that the Bus I/O address is zero instead and the io_offset adjusts
> for that, which is a bit confusing. Please change it to read
> the actual values from the ranges property.

I will recheck the logic.

>
>> +     sys->mem_offset = pci_io_offset(pp->res[XGENE_MEM].start);
>> +
>> +     BUG_ON(request_resource(&iomem_resource, &pp->res[XGENE_IO]) ||
>> +            request_resource(&iomem_resource, &pp->res[XGENE_MEM]));
>> +
>> +     pci_add_resource_offset(&sys->resources, &pp->res[XGENE_MEM],
>> +                             sys->mem_offset);
>> +     pci_add_resource_offset(&sys->resources, &pp->res[XGENE_IO],
>> +                             sys->io_offset);
>
> &pp->res[XGENE_IO] is in memory space, while the argument you want here
> is in I/O space.
>
>> +static int xgene_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
>> +{
>> +     return of_irq_parse_and_map_pci(dev, slot, pin);
>> +}
>
> Just use the function directly and remove the wrapper.

got it.

>
>         Arnd



More information about the linux-arm-kernel mailing list