From bd6728d5951b7ddb55cbfdaa5e443d92f2989679 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 29 Nov 2016 10:13:46 +0000 Subject: 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 | 2 + drivers/pci/pcie/portdrv_core.c | 2 + 5 files changed, 180 insertions(+), 34 deletions(-) diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index c39978b750ec..b4fb20835573 100644 --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -53,7 +53,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 @@ -432,6 +439,54 @@ static void mvebu_pcie_handle_membase_change(struct mvebu_pcie_port *port) &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) @@ -477,6 +532,30 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge, 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) @@ -494,7 +573,8 @@ mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge, 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; } @@ -517,6 +597,11 @@ mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge, 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; @@ -534,6 +619,10 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge, 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. @@ -559,6 +648,25 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge, 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; } } @@ -566,6 +674,8 @@ static struct pci_bridge_emul_ops mvebu_pci_bridge_emul_ops = { .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, }; /* diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c index ccf26d12ec61..c124b7ff1e22 100644 --- a/drivers/pci/pci-bridge-emul.c +++ b/drivers/pci/pci-bridge-emul.c @@ -151,6 +151,7 @@ static const struct pci_bridge_reg_behavior pci_regs_behavior[] = { .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_bridge_emul *bridge, 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); @@ -321,25 +323,26 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where, __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) @@ -347,15 +350,20 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where, 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; @@ -382,12 +390,6 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where, __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) @@ -403,27 +405,42 @@ 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) { + 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 { - write_op = bridge->ops->write_base; - cfgspace = (__le32 *) &bridge->conf; - behavior = bridge->pci_regs_behavior; + reg -= 0x100; + write_op = bridge->ops->write_ext; + cfgspace = NULL; + behavior = NULL; } - /* Keep all bits, except the RW bits */ - new = old & (~mask | ~behavior[reg / 4].rw); + 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); + /* 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)); + /* Clear the W1C bits */ + new &= ~((value << shift) & (behavior[reg / 4].w1c & mask)); + } else { + new = old & ~mask; + new |= (value << shift) & mask; + } - cfgspace[reg / 4] = cpu_to_le32(new); + if (cfgspace) + cfgspace[reg / 4] = cpu_to_le32(new); if (write_op) write_op(bridge, reg, old, new, mask); diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h index b31883022a8e..5f64560aaa26 100644 --- 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; diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 253c30cc1967..3edbe276e626 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -576,6 +576,8 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) */ pcie_get_aspm_reg(parent, &upreg); pcie_get_aspm_reg(child, &dwreg); +dev_info(&parent->dev, "up support %x enabled %x\n", upreg.support, upreg.enabled); +dev_info(&parent->dev, "dn support %x enabled %x\n", dwreg.support, dwreg.enabled); /* * Setup L0s state diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 50a9522ab07d..105ebc7a7987 100644 --- 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_dev *dev) /* 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_dev *dev) * 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) -- cgit