From 527312a74d9d85ba9520c8cb2979004f6d23c4da Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 29 Nov 2016 10:13:46 +0000 Subject: [PATCH] mvebu/clearfog pcie updates Signed-off-by: Russell King --- drivers/pci/controller/pci-mvebu.c | 112 ++++++++++++++++++++++++++++- drivers/pci/pci-bridge-emul.c | 83 ++++++++++++--------- drivers/pci/pci-bridge-emul.h | 15 ++++ drivers/pci/pcie/aspm.c | 6 ++ drivers/pci/pcie/portdrv_core.c | 2 + 5 files changed, 184 insertions(+), 34 deletions(-) --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -52,7 +52,14 @@ PCIE_CONF_ADDR_EN) #define PCIE_CONF_DATA_OFF 0x18fc #define PCIE_MASK_OFF 0x1910 +#define PCIE_MASK_PM_PME BIT(28) #define PCIE_MASK_ENABLE_INTS 0x0f000000 +#define PCIE_MASK_ERR_COR BIT(18) +#define PCIE_MASK_ERR_NONFATAL BIT(17) +#define PCIE_MASK_ERR_FATAL BIT(16) +#define PCIE_MASK_FERR_DET BIT(10) +#define PCIE_MASK_NFERR_DET BIT(9) +#define PCIE_MASK_CORERR_DET BIT(8) #define PCIE_CTRL_OFF 0x1a00 #define PCIE_CTRL_X1_MODE 0x0001 #define PCIE_STAT_OFF 0x1a04 @@ -430,6 +437,54 @@ static void mvebu_pcie_handle_membase_ch &port->memwin); } +static void mvebu_pcie_handle_irq_change(struct mvebu_pcie_port *port) +{ + u32 reg, old; + u16 devctl, rtctl; + + /* + * Errors from downstream devices: + * bridge control register SERR: enables reception of errors + * Errors from this device, or received errors: + * command SERR: enables ERR_NONFATAL and ERR_FATAL messages + * => when enabled, these conditions also flag SERR in status register + * devctl CERE: enables ERR_CORR messages + * devctl NFERE: enables ERR_NONFATAL messages + * devctl FERE: enables ERR_FATAL messages + * Enabled messages then have three paths: + * 1. rtctl: enables system error indication + * 2. root error status register updated + * 3. root error command register: forwarding via MSI + */ + old = mvebu_readl(port, PCIE_MASK_OFF); + reg = old & ~(PCIE_MASK_PM_PME | PCIE_MASK_FERR_DET | + PCIE_MASK_NFERR_DET | PCIE_MASK_CORERR_DET | + PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL | + PCIE_MASK_ERR_FATAL); + + devctl = port->bridge.pcie_conf.devctl; + if (devctl & PCI_EXP_DEVCTL_FERE) + reg |= PCIE_MASK_FERR_DET | PCIE_MASK_ERR_FATAL; + if (devctl & PCI_EXP_DEVCTL_NFERE) + reg |= PCIE_MASK_NFERR_DET | PCIE_MASK_ERR_NONFATAL; + if (devctl & PCI_EXP_DEVCTL_CERE) + reg |= PCIE_MASK_CORERR_DET | PCIE_MASK_ERR_COR; + if (port->bridge.conf.command & PCI_COMMAND_SERR) + reg |= PCIE_MASK_FERR_DET | PCIE_MASK_NFERR_DET | + PCIE_MASK_ERR_FATAL | PCIE_MASK_ERR_NONFATAL; + + if (!(port->bridge.conf.bridgectrl & PCI_BRIDGE_CTL_SERR)) + reg &= ~(PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL | + PCIE_MASK_ERR_FATAL); + + rtctl = port->bridge.pcie_conf.rootctl; + if (rtctl & PCI_EXP_RTCTL_PMEIE) + reg |= PCIE_MASK_PM_PME; + + if (old != reg) + mvebu_writel(port, reg, PCIE_MASK_OFF); +} + static pci_bridge_emul_read_status_t mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge, int reg, u32 *value) @@ -475,6 +530,30 @@ mvebu_pci_bridge_emul_pcie_conf_read(str return PCI_BRIDGE_EMUL_HANDLED; } +static pci_bridge_emul_read_status_t +mvebu_pci_bridge_emul_pcie_ext_read(struct pci_bridge_emul *bridge, + int reg, u32 *value) +{ + struct mvebu_pcie_port *port = bridge->data; + + switch (reg) { + case 0x00 ... 0x28: + *value = mvebu_readl(port, 0x100 + (reg & ~3)); + break; + + case PCI_ERR_ROOT_COMMAND: + case PCI_ERR_ROOT_STATUS: + case PCI_ERR_ROOT_ERR_SRC: + *value = 0; + break; + + default: + return PCI_BRIDGE_EMUL_NOT_HANDLED; + } + + return PCI_BRIDGE_EMUL_HANDLED; +} + static void mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge, int reg, u32 old, u32 new, u32 mask) @@ -492,7 +571,8 @@ mvebu_pci_bridge_emul_base_conf_write(st mvebu_pcie_handle_iobase_change(port); if ((old ^ new) & PCI_COMMAND_MEMORY) mvebu_pcie_handle_membase_change(port); - + if ((old ^ new) & PCI_COMMAND_SERR) + mvebu_pcie_handle_irq_change(port); break; } @@ -515,6 +595,11 @@ mvebu_pci_bridge_emul_base_conf_write(st mvebu_pcie_handle_iobase_change(port); break; + case PCI_INTERRUPT_LINE: + if (((old ^ new) >> 16) & PCI_BRIDGE_CTL_SERR) + mvebu_pcie_handle_irq_change(port); + break; + case PCI_PRIMARY_BUS: mvebu_pcie_set_local_bus_nr(port, conf->secondary_bus); break; @@ -532,6 +617,10 @@ mvebu_pci_bridge_emul_pcie_conf_write(st switch (reg) { case PCI_EXP_DEVCTL: + if ((new ^ old) & (PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_URRE)) + mvebu_pcie_handle_irq_change(port); + /* * Armada370 data says these bits must always * be zero when in root complex mode. @@ -557,6 +646,25 @@ mvebu_pci_bridge_emul_pcie_conf_write(st case PCI_EXP_RTSTA: mvebu_writel(port, new, PCIE_RC_RTSTA); break; + + case PCI_EXP_RTCTL: + if ((new ^ old) & (PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE | + PCI_EXP_RTCTL_SEFEE | PCI_EXP_RTCTL_PMEIE)) + mvebu_pcie_handle_irq_change(port); + break; + } +} + +static void +mvebu_pci_bridge_emul_pcie_ext_write(struct pci_bridge_emul *bridge, + int reg, u32 old, u32 new, u32 mask) +{ + struct mvebu_pcie_port *port = bridge->data; + + switch (reg) { + case 0x00 ... 0x28: + mvebu_writel(port, new, 0x100 + (reg & ~3)); + break; } } @@ -564,6 +672,8 @@ static struct pci_bridge_emul_ops mvebu_ .write_base = mvebu_pci_bridge_emul_base_conf_write, .read_pcie = mvebu_pci_bridge_emul_pcie_conf_read, .write_pcie = mvebu_pci_bridge_emul_pcie_conf_write, + .read_ext = mvebu_pci_bridge_emul_pcie_ext_read, + .write_ext = mvebu_pci_bridge_emul_pcie_ext_write, }; /* --- a/drivers/pci/pci-bridge-emul.c +++ b/drivers/pci/pci-bridge-emul.c @@ -151,6 +151,7 @@ static const struct pci_bridge_reg_behav .rw = (GENMASK(7, 0) | ((PCI_BRIDGE_CTL_PARITY | PCI_BRIDGE_CTL_SERR | + /* NOTE: PCIe does not allow ISA, VGA, MASTER_ABORT */ PCI_BRIDGE_CTL_ISA | PCI_BRIDGE_CTL_VGA | PCI_BRIDGE_CTL_MASTER_ABORT | @@ -264,6 +265,7 @@ int pci_bridge_emul_init(struct pci_brid bridge->conf.header_type = PCI_HEADER_TYPE_BRIDGE; bridge->conf.cache_line_size = 0x10; bridge->conf.status = cpu_to_le16(PCI_STATUS_CAP_LIST); + bridge->conf.bridgectrl = cpu_to_le16(PCI_BRIDGE_CTL_SERR); bridge->pci_regs_behavior = kmemdup(pci_regs_behavior, sizeof(pci_regs_behavior), GFP_KERNEL); @@ -323,25 +325,26 @@ int pci_bridge_emul_conf_read(struct pci __le32 *cfgspace; const struct pci_bridge_reg_behavior *behavior; - if (bridge->has_pcie && reg >= PCI_CAP_PCIE_END) { - *value = 0; - return PCIBIOS_SUCCESSFUL; - } - - if (!bridge->has_pcie && reg >= PCI_BRIDGE_CONF_END) { + if (reg < PCI_CAP_PCIE_START) { + read_op = bridge->ops->read_base; + cfgspace = (__le32 *) &bridge->conf; + behavior = bridge->pci_regs_behavior; + } else if (!bridge->has_pcie) { *value = 0; return PCIBIOS_SUCCESSFUL; - } - - if (bridge->has_pcie && reg >= PCI_CAP_PCIE_START) { + } else if (reg < PCI_CAP_PCIE_END) { reg -= PCI_CAP_PCIE_START; read_op = bridge->ops->read_pcie; cfgspace = (__le32 *) &bridge->pcie_conf; behavior = bridge->pcie_cap_regs_behavior; + } else if (reg < 0x100) { + *value = 0; + return PCIBIOS_SUCCESSFUL; } else { - read_op = bridge->ops->read_base; - cfgspace = (__le32 *) &bridge->conf; - behavior = bridge->pci_regs_behavior; + reg -= 0x100; + read_op = bridge->ops->read_ext; + cfgspace = NULL; + behavior = NULL; } if (read_op) @@ -349,15 +352,20 @@ int pci_bridge_emul_conf_read(struct pci else ret = PCI_BRIDGE_EMUL_NOT_HANDLED; - if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED) - *value = le32_to_cpu(cfgspace[reg / 4]); + if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED) { + if (cfgspace) + *value = le32_to_cpu(cfgspace[reg / 4]); + else + *value = 0; + } /* * Make sure we never return any reserved bit with a value * different from 0. */ - *value &= behavior[reg / 4].ro | behavior[reg / 4].rw | - behavior[reg / 4].w1c; + if (behavior) + *value &= behavior[reg / 4].ro | behavior[reg / 4].rw | + behavior[reg / 4].w1c; if (size == 1) *value = (*value >> (8 * (where & 3))) & 0xff; @@ -385,12 +393,6 @@ int pci_bridge_emul_conf_write(struct pc __le32 *cfgspace; const struct pci_bridge_reg_behavior *behavior; - if (bridge->has_pcie && reg >= PCI_CAP_PCIE_END) - return PCIBIOS_SUCCESSFUL; - - if (!bridge->has_pcie && reg >= PCI_BRIDGE_CONF_END) - return PCIBIOS_SUCCESSFUL; - shift = (where & 0x3) * 8; if (size == 4) @@ -413,28 +413,44 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where, if (ret != PCIBIOS_SUCCESSFUL) return ret; - if (bridge->has_pcie && reg >= PCI_CAP_PCIE_START) { - reg -= PCI_CAP_PCIE_START; - write_op = bridge->ops->write_pcie; - cfgspace = (__le32 *) &bridge->pcie_conf; - behavior = bridge->pcie_cap_regs_behavior; - } else { + + if (reg < PCI_CAP_PCIE_START) { write_op = bridge->ops->write_base; cfgspace = (__le32 *) &bridge->conf; behavior = bridge->pci_regs_behavior; + } else if (!bridge->has_pcie) { + return PCIBIOS_SUCCESSFUL; + } else if (reg < PCI_CAP_PCIE_END) { + reg -= PCI_CAP_PCIE_START; + write_op = bridge->ops->write_pcie; + cfgspace = (__le32 *) &bridge->pcie_conf; + behavior = bridge->pcie_cap_regs_behavior; + } else if (reg < 0x100) { + return PCIBIOS_SUCCESSFUL; + } else { + reg -= 0x100; + write_op = bridge->ops->write_ext; + cfgspace = NULL; + behavior = NULL; + } + + if (behavior) { + /* Keep all bits, except the RW bits */ + new = old & (~mask | ~behavior[reg / 4].rw); + + /* Update the value of the RW bits */ + new |= (value << shift) & (behavior[reg / 4].rw & mask); + + /* Clear the W1C bits */ + new &= ~((value << shift) & (behavior[reg / 4].w1c & mask)); + } else { + new = old & ~mask; + new |= (value << shift) & mask; } - - /* Keep all bits, except the RW bits */ - new = old & (~mask | ~behavior[reg / 4].rw); - - /* Update the value of the RW bits */ - new |= (value << shift) & (behavior[reg / 4].rw & mask); - - /* Clear the W1C bits */ - new &= ~((value << shift) & (behavior[reg / 4].w1c & mask)); - - /* Save the new value with the cleared W1C bits into the cfgspace */ - cfgspace[reg / 4] = cpu_to_le32(new); + + /* Save the new value with the cleared W1C bits into the cfgspace */ + if (cfgspace) + cfgspace[reg / 4] = cpu_to_le32(new); /* * Clear the W1C bits not specified by the write mask, so that the --- a/drivers/pci/pci-bridge-emul.h +++ b/drivers/pci/pci-bridge-emul.h @@ -90,6 +90,14 @@ struct pci_bridge_emul_ops { */ pci_bridge_emul_read_status_t (*read_pcie)(struct pci_bridge_emul *bridge, int reg, u32 *value); + + /* + * Same as ->read_base(), except it is for reading from the + * PCIe extended capability configuration space. + */ + pci_bridge_emul_read_status_t (*read_ext)(struct pci_bridge_emul *bridge, + int reg, u32 *value); + /* * Called when writing to the regular PCI bridge configuration * space. old is the current value, new is the new value being @@ -105,6 +113,13 @@ struct pci_bridge_emul_ops { */ void (*write_pcie)(struct pci_bridge_emul *bridge, int reg, u32 old, u32 new, u32 mask); + + /* + * Same as ->write_base(), except it is for writing from the + * PCIe extended capability configuration space. + */ + void (*write_ext)(struct pci_bridge_emul *bridge, int reg, + u32 old, u32 new, u32 mask); }; struct pci_bridge_reg_behavior; --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -578,6 +578,12 @@ static void pcie_aspm_cap_init(struct pc pcie_capability_read_dword(child, PCI_EXP_LNKCAP, &child_lnkcap); pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &parent_lnkctl); pcie_capability_read_word(child, PCI_EXP_LNKCTL, &child_lnkctl); +dev_info(&parent->dev, "up support %x enabled %x\n", + (parent_lnkcap & PCI_EXP_LNKCAP_ASPMS) >> 10, + !!(parent_lnkctl & PCI_EXP_LNKCTL_ASPMC)); +dev_info(&parent->dev, "dn support %x enabled %x\n", + (child_lnkcap & PCI_EXP_LNKCAP_ASPMS) >> 10, + !!(child_lnkctl & PCI_EXP_LNKCTL_ASPMC)); /* * Setup L0s state --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -325,6 +325,7 @@ int pcie_port_device_register(struct pci /* Get and check PCI Express port services */ capabilities = get_port_device_capability(dev); +dev_info(&dev->dev, "PCIe capabilities: 0x%x\n", capabilities); if (!capabilities) return 0; @@ -337,6 +338,7 @@ int pcie_port_device_register(struct pci * if that is to be used. */ status = pcie_init_service_irqs(dev, irqs, capabilities); +dev_info(&dev->dev, "init_service_irqs: %d\n", status); if (status) { capabilities &= PCIE_PORT_SERVICE_HP; if (!capabilities)