1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
From 38251bffc7552d62689e5fd6831f0aae1d842438 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 88/90] 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 46fa9bbff177..c217ea3bc703 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 319cd2b98545..2792b02278b9 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
@@ -697,6 +733,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;
@@ -828,6 +872,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;
@@ -856,6 +919,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:
@@ -996,6 +1078,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.
@@ -1007,6 +1098,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));
@@ -1198,11 +1291,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);
@@ -1240,6 +1351,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;
}
@@ -1805,6 +1935,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
@@ -1941,6 +2083,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.34.1
|