aboutsummaryrefslogtreecommitdiff
path: root/nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch')
-rw-r--r--nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch297
1 files changed, 297 insertions, 0 deletions
diff --git a/nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch b/nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
new file mode 100644
index 0000000..128e012
--- /dev/null
+++ b/nixos/modules/omnia-kernel-patches/0045-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
@@ -0,0 +1,297 @@
+From 6130515a14b0ef390507edc5b232996a1bcfccbc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 17 Sep 2021 14:53:11 +0200
+Subject: [PATCH 45/53] PCI: mvebu: Add support for PCI_EXP_SLTSTA_DLLSC via
+ hot plug interrupt
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If link up/down state is changed in mvebu_pcie_link_up() then trigger
+hot plug interrupt with DLLSC state change.
+
+Also triggers hot plug interrupt when mvebu triggers Link Failure interrupt
+which indicates that link was changed from active state or when mvebu
+triggers TxReq No Link interrupt which indicates that link is down while
+trying to transmit PCIe transaction.
+
+And this hot plug interrupt also when explicit Link Disable or PCIe Host
+Reset is issued as mvebu does not trigger Link Failure when dropping to
+Detect via Hot Reset or Link Disable.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/Kconfig | 3 +
+ drivers/pci/controller/pci-mvebu.c | 147 ++++++++++++++++++++++++++++-
+ 2 files changed, 149 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
+index d1c5fcf00a8a..8da2efdc5177 100644
+--- a/drivers/pci/controller/Kconfig
++++ b/drivers/pci/controller/Kconfig
+@@ -10,6 +10,9 @@ config PCI_MVEBU
+ depends on ARM
+ depends on OF
+ select PCI_BRIDGE_EMUL
++ select PCIEPORTBUS
++ select HOTPLUG_PCI
++ select HOTPLUG_PCI_PCIE
+ help
+ Add support for Marvell EBU PCIe controller. This PCIe controller
+ is used on 32-bit Marvell ARM SoCs: Dove, Kirkwood, Armada 370,
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index ddd5ba8b265e..634ca84cfda2 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -56,12 +56,14 @@
+ #define PCIE_CONF_DATA_OFF 0x18fc
+ #define PCIE_INT_CAUSE_OFF 0x1900
+ #define PCIE_INT_UNMASK_OFF 0x1910
++#define PCIE_INT_TXREQ_NOLINK BIT(0)
+ #define PCIE_INT_DET_COR BIT(8)
+ #define PCIE_INT_DET_NONFATAL BIT(9)
+ #define PCIE_INT_DET_FATAL BIT(10)
+ #define PCIE_INT_ERR_FATAL BIT(16)
+ #define PCIE_INT_ERR_NONFATAL BIT(17)
+ #define PCIE_INT_ERR_COR BIT(18)
++#define PCIE_INT_LINK_FAIL BIT(23)
+ #define PCIE_INT_INTX(i) BIT(24+i)
+ #define PCIE_INT_PM_PME BIT(28)
+ #define PCIE_INT_DET_MASK (PCIE_INT_DET_COR | PCIE_INT_DET_NONFATAL | PCIE_INT_DET_FATAL)
+@@ -134,6 +136,8 @@ struct mvebu_pcie_port {
+ int error_irq;
+ int intx_irq;
+ bool pme_pending;
++ struct timer_list link_irq_timer;
++ bool link_was_up;
+ };
+
+ static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
+@@ -153,7 +157,26 @@ static inline bool mvebu_has_ioport(struct mvebu_pcie_port *port)
+
+ static bool mvebu_pcie_link_up(struct mvebu_pcie_port *port)
+ {
+- return !(mvebu_readl(port, PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
++ bool link_is_up;
++ u16 slotsta;
++
++ link_is_up = !(mvebu_readl(port, PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
++
++ if (link_is_up != port->link_was_up) {
++ port->link_was_up = link_is_up;
++ /*
++ * Link IRQ timer/handler is available only when "error"
++ * interrupt was specified in DT.
++ */
++ if (port->error_irq > 0) {
++ slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++ port->bridge.pcie_conf.slotsta =
++ cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++ mod_timer(&port->link_irq_timer, jiffies + 1);
++ }
++ }
++
++ return link_is_up;
+ }
+
+ static u8 mvebu_pcie_get_local_bus_nr(struct mvebu_pcie_port *port)
+@@ -346,6 +369,19 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ }
+
++ /*
++ * Unmask No Link and Link Failure interrupts to process Link Down
++ * events. These events are reported as Data Link Layer State Changed
++ * notification via Hot Plug Interrupt. Other parts of Link change
++ * events are available only when "error" interrupt was specified in DT.
++ * So enable these interrupts under same conditions.
++ */
++ if (port->error_irq > 0) {
++ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++ unmask |= PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL;
++ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++ }
++
+ /*
+ * Fallback code when "intx" interrupt was not specified in DT:
+ * Unmask all legacy INTx interrupts as driver does not provide a way
+@@ -692,6 +728,14 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ val |= slotctl & PCI_EXP_SLTCTL_ASPL_DISABLE;
+ else if (!(mvebu_readl(port, PCIE_SSPL_OFF) & PCIE_SSPL_ENABLE))
+ val |= PCI_EXP_SLTCTL_ASPL_DISABLE;
++ /*
++ * HPIE and DLLSCE bits are stored only in emulated config
++ * space buffer and are supported only when or "error" interrupt
++ * was specified in DT.
++ */
++ if (port->error_irq > 0)
++ val |= slotctl & (PCI_EXP_SLTCTL_HPIE |
++ PCI_EXP_SLTCTL_DLLSCE);
+ /* This callback is 32-bit and in high bits is slot status. */
+ val |= slotsta << 16;
+ *value = val;
+@@ -823,6 +867,25 @@ mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge,
+ else
+ ctrl &= ~PCIE_CTRL_MASTER_HOT_RESET;
+ mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
++ /*
++ * When dropping to Detect via Hot Reset, Disable Link
++ * or Loopback states, the Link Failure interrupt is not
++ * asserted. So when setting Secondary Bus Reset / Hot
++ * Reset bit, call link IRQ timer/handler manually.
++ */
++ if ((ctrl & PCIE_CTRL_MASTER_HOT_RESET) && port->link_was_up) {
++ port->link_was_up = false;
++ /*
++ * Link IRQ timer/handler is available only when
++ * "error" interrupt was specified in DT.
++ */
++ if (port->error_irq > 0) {
++ u16 slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++ port->bridge.pcie_conf.slotsta =
++ cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++ mod_timer(&port->link_irq_timer, jiffies + 1);
++ }
++ }
+ }
+ break;
+
+@@ -851,6 +914,25 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ new &= ~PCI_EXP_LNKCTL_CLKREQ_EN;
+
+ mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
++ /*
++ * When dropping to Detect via Hot Reset, Disable Link
++ * or Loopback states, the Link Failure interrupt is not
++ * asserted. So when setting Link Disable bit, call link
++ * IRQ timer/handler manually.
++ */
++ if ((new & PCI_EXP_LNKCTL_LD) && port->link_was_up) {
++ port->link_was_up = false;
++ /*
++ * Link IRQ timer/handler is available only when
++ * "error" interrupt was specified in DT.
++ */
++ if (port->error_irq > 0) {
++ u16 slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++ port->bridge.pcie_conf.slotsta =
++ cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++ mod_timer(&port->link_irq_timer, jiffies + 1);
++ }
++ }
+ break;
+
+ case PCI_EXP_SLTCTL:
+@@ -991,6 +1073,15 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver | PCI_EXP_FLAGS_SLOT);
+
+ /*
++ * When "error" interrupt was specified in DT then driver is able to
++ * deliver Data Link Layer State Change interrupt. So in this case mark
++ * bridge as Hot Plug Capable as this is the way how to enable
++ * delivering of Data Link Layer State Change interrupts.
++ *
++ * No Command Completed Support is set because bridge does not support
++ * Command Completed Interrupt. Every command is executed immediately
++ * without any delay.
++ *
+ * Set Presence Detect State bit permanently as there is no support for
+ * unplugging PCIe card from the slot. Assume that PCIe card is always
+ * connected in slot.
+@@ -1002,6 +1093,8 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ * Also set correct slot power limit.
+ */
+ bridge->pcie_conf.slotcap = cpu_to_le32(
++ PCI_EXP_SLTCAP_NCCS |
++ (port->error_irq > 0 ? PCI_EXP_SLTCAP_HPC : 0) |
+ FIELD_PREP(PCI_EXP_SLTCAP_SPLV, port->slot_power_limit_value) |
+ FIELD_PREP(PCI_EXP_SLTCAP_SPLS, port->slot_power_limit_scale) |
+ FIELD_PREP(PCI_EXP_SLTCAP_PSN, port->port+1));
+@@ -1191,11 +1284,29 @@ static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+ return 0;
+ }
+
++static void mvebu_pcie_link_irq_handler(struct timer_list *timer)
++{
++ struct mvebu_pcie_port *port = from_timer(port, timer, link_irq_timer);
++ struct device *dev = &port->pcie->pdev->dev;
++ u16 slotctl;
++
++ dev_info(dev, "%s: link %s\n", port->name, port->link_was_up ? "up" : "down");
++
++ slotctl = le16_to_cpu(port->bridge.pcie_conf.slotctl);
++ if (!(slotctl & PCI_EXP_SLTCTL_DLLSCE) ||
++ !(slotctl & PCI_EXP_SLTCTL_HPIE))
++ return;
++
++ if (generic_handle_domain_irq(port->rp_irq_domain, 0) == -EINVAL)
++ dev_err_ratelimited(dev, "unhandled HP IRQ\n");
++}
++
+ static irqreturn_t mvebu_pcie_error_irq_handler(int irq, void *arg)
+ {
+ struct mvebu_pcie_port *port = arg;
+ struct device *dev = &port->pcie->pdev->dev;
+ u32 cause, unmask, status;
++ u16 slotsta;
+
+ cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+@@ -1233,6 +1344,25 @@ static irqreturn_t mvebu_pcie_error_irq_handler(int irq, void *arg)
+ dev_err_ratelimited(dev, "unhandled ERR IRQ\n");
+ }
+
++ /* Process No Link and Link Failure interrupts as HP IRQ */
++ if (status & (PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL)) {
++ mvebu_writel(port,
++ ~(PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL),
++ PCIE_INT_CAUSE_OFF);
++ if (port->link_was_up) {
++ port->link_was_up = false;
++ slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++ port->bridge.pcie_conf.slotsta =
++ cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++ /*
++ * Deactivate timer and call mvebu_pcie_link_irq_handler()
++ * function directly as we are in the interrupt context.
++ */
++ del_timer_sync(&port->link_irq_timer);
++ mvebu_pcie_link_irq_handler(&port->link_irq_timer);
++ }
++ }
++
+ return status ? IRQ_HANDLED : IRQ_NONE;
+ }
+
+@@ -1796,6 +1926,18 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ }
+ }
+
++ /*
++ * Function mvebu_pcie_link_irq_handler() calls function
++ * generic_handle_irq() and it expects local IRQs to be disabled
++ * as normally generic_handle_irq() is called from the interrupt
++ * context. So use TIMER_IRQSAFE flag for this link_irq_timer.
++ * Available only if "or "error" interrupt was specified.
++ */
++ if (port->error_irq > 0)
++ timer_setup(&port->link_irq_timer,
++ mvebu_pcie_link_irq_handler,
++ TIMER_IRQSAFE);
++
+ /*
+ * PCIe topology exported by mvebu hw is quite complicated. In
+ * reality has something like N fully independent host bridges
+@@ -1932,6 +2074,9 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ irq_domain_remove(port->rp_irq_domain);
+ }
+
++ if (port->error_irq > 0)
++ del_timer_sync(&port->link_irq_timer);
++
+ /* Free config space for emulated root bridge. */
+ pci_bridge_emul_cleanup(&port->bridge);
+
+--
+2.37.3
+