Commit | Line | Data |
---|---|---|
fc8f5ade TP |
1 | /* |
2 | * Driver for the MDIO interface of Marvell network interfaces. | |
3 | * | |
4 | * Since the MDIO interface of Marvell network interfaces is shared | |
5 | * between all network interfaces, having a single driver allows to | |
6 | * handle concurrent accesses properly (you may have four Ethernet | |
d4a0acb8 LB |
7 | * ports, but they in fact share the same SMI interface to access |
8 | * the MDIO bus). This driver is currently used by the mvneta and | |
9 | * mv643xx_eth drivers. | |
fc8f5ade TP |
10 | * |
11 | * Copyright (C) 2012 Marvell | |
12 | * | |
13 | * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> | |
14 | * | |
15 | * This file is licensed under the terms of the GNU General Public | |
16 | * License version 2. This program is licensed "as is" without any | |
17 | * warranty of any kind, whether express or implied. | |
18 | */ | |
19 | ||
14ef8b36 AT |
20 | #include <linux/clk.h> |
21 | #include <linux/delay.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/io.h> | |
fc8f5ade TP |
24 | #include <linux/kernel.h> |
25 | #include <linux/module.h> | |
c0ac08f5 | 26 | #include <linux/of_device.h> |
14ef8b36 | 27 | #include <linux/of_mdio.h> |
fc8f5ade | 28 | #include <linux/phy.h> |
fc8f5ade | 29 | #include <linux/platform_device.h> |
2ec98521 FF |
30 | #include <linux/sched.h> |
31 | #include <linux/wait.h> | |
fc8f5ade | 32 | |
2040ef2f AT |
33 | #define MVMDIO_SMI_DATA_SHIFT 0 |
34 | #define MVMDIO_SMI_PHY_ADDR_SHIFT 16 | |
35 | #define MVMDIO_SMI_PHY_REG_SHIFT 21 | |
36 | #define MVMDIO_SMI_READ_OPERATION BIT(26) | |
37 | #define MVMDIO_SMI_WRITE_OPERATION 0 | |
38 | #define MVMDIO_SMI_READ_VALID BIT(27) | |
39 | #define MVMDIO_SMI_BUSY BIT(28) | |
40 | #define MVMDIO_ERR_INT_CAUSE 0x007C | |
41 | #define MVMDIO_ERR_INT_SMI_DONE 0x00000010 | |
42 | #define MVMDIO_ERR_INT_MASK 0x0080 | |
fc8f5ade | 43 | |
c0ac08f5 AT |
44 | #define MVMDIO_XSMI_MGNT_REG 0x0 |
45 | #define MVMDIO_XSMI_PHYADDR_SHIFT 16 | |
46 | #define MVMDIO_XSMI_DEVADDR_SHIFT 21 | |
47 | #define MVMDIO_XSMI_WRITE_OPERATION (0x5 << 26) | |
48 | #define MVMDIO_XSMI_READ_OPERATION (0x7 << 26) | |
49 | #define MVMDIO_XSMI_READ_VALID BIT(29) | |
50 | #define MVMDIO_XSMI_BUSY BIT(30) | |
51 | #define MVMDIO_XSMI_ADDR_REG 0x8 | |
52 | ||
b70cd1c1 LB |
53 | /* |
54 | * SMI Timeout measurements: | |
55 | * - Kirkwood 88F6281 (Globalscale Dreamplug): 45us to 95us (Interrupt) | |
56 | * - Armada 370 (Globalscale Mirabox): 41us to 43us (Polled) | |
57 | */ | |
2040ef2f AT |
58 | #define MVMDIO_SMI_TIMEOUT 1000 /* 1000us = 1ms */ |
59 | #define MVMDIO_SMI_POLL_INTERVAL_MIN 45 | |
60 | #define MVMDIO_SMI_POLL_INTERVAL_MAX 55 | |
b70cd1c1 | 61 | |
c0ac08f5 AT |
62 | #define MVMDIO_XSMI_POLL_INTERVAL_MIN 150 |
63 | #define MVMDIO_XSMI_POLL_INTERVAL_MAX 160 | |
64 | ||
fc8f5ade | 65 | struct orion_mdio_dev { |
3712b717 | 66 | void __iomem *regs; |
4aabed69 | 67 | struct clk *clk[4]; |
2ec98521 FF |
68 | /* |
69 | * If we have access to the error interrupt pin (which is | |
70 | * somewhat misnamed as it not only reflects internal errors | |
71 | * but also reflects SMI completion), use that to wait for | |
72 | * SMI access completion instead of polling the SMI busy bit. | |
73 | */ | |
74 | int err_interrupt; | |
75 | wait_queue_head_t smi_busy_wait; | |
fc8f5ade TP |
76 | }; |
77 | ||
c0ac08f5 AT |
78 | enum orion_mdio_bus_type { |
79 | BUS_TYPE_SMI, | |
80 | BUS_TYPE_XSMI | |
81 | }; | |
82 | ||
b0b7fa4f AT |
83 | struct orion_mdio_ops { |
84 | int (*is_done)(struct orion_mdio_dev *); | |
19557966 AT |
85 | unsigned int poll_interval_min; |
86 | unsigned int poll_interval_max; | |
b0b7fa4f | 87 | }; |
2ec98521 | 88 | |
b07812f1 | 89 | /* Wait for the SMI unit to be ready for another operation |
fc8f5ade | 90 | */ |
b0b7fa4f AT |
91 | static int orion_mdio_wait_ready(const struct orion_mdio_ops *ops, |
92 | struct mii_bus *bus) | |
fc8f5ade TP |
93 | { |
94 | struct orion_mdio_dev *dev = bus->priv; | |
b70cd1c1 LB |
95 | unsigned long timeout = usecs_to_jiffies(MVMDIO_SMI_TIMEOUT); |
96 | unsigned long end = jiffies + timeout; | |
97 | int timedout = 0; | |
fc8f5ade | 98 | |
b70cd1c1 | 99 | while (1) { |
b0b7fa4f | 100 | if (ops->is_done(dev)) |
b70cd1c1 LB |
101 | return 0; |
102 | else if (timedout) | |
103 | break; | |
fc8f5ade | 104 | |
b70cd1c1 | 105 | if (dev->err_interrupt <= 0) { |
19557966 AT |
106 | usleep_range(ops->poll_interval_min, |
107 | ops->poll_interval_max); | |
fc8f5ade | 108 | |
b70cd1c1 LB |
109 | if (time_is_before_jiffies(end)) |
110 | ++timedout; | |
111 | } else { | |
1a1f20bc LB |
112 | /* wait_event_timeout does not guarantee a delay of at |
113 | * least one whole jiffie, so timeout must be no less | |
114 | * than two. | |
115 | */ | |
116 | if (timeout < 2) | |
117 | timeout = 2; | |
2ec98521 | 118 | wait_event_timeout(dev->smi_busy_wait, |
b0b7fa4f | 119 | ops->is_done(dev), timeout); |
b70cd1c1 LB |
120 | |
121 | ++timedout; | |
122 | } | |
fc8f5ade TP |
123 | } |
124 | ||
b70cd1c1 LB |
125 | dev_err(bus->parent, "Timeout: SMI busy for too long\n"); |
126 | return -ETIMEDOUT; | |
fc8f5ade TP |
127 | } |
128 | ||
b0b7fa4f AT |
129 | static int orion_mdio_smi_is_done(struct orion_mdio_dev *dev) |
130 | { | |
131 | return !(readl(dev->regs) & MVMDIO_SMI_BUSY); | |
132 | } | |
133 | ||
134 | static const struct orion_mdio_ops orion_mdio_smi_ops = { | |
135 | .is_done = orion_mdio_smi_is_done, | |
19557966 AT |
136 | .poll_interval_min = MVMDIO_SMI_POLL_INTERVAL_MIN, |
137 | .poll_interval_max = MVMDIO_SMI_POLL_INTERVAL_MAX, | |
b0b7fa4f AT |
138 | }; |
139 | ||
c0ac08f5 AT |
140 | static int orion_mdio_smi_read(struct mii_bus *bus, int mii_id, |
141 | int regnum) | |
fc8f5ade TP |
142 | { |
143 | struct orion_mdio_dev *dev = bus->priv; | |
fc8f5ade TP |
144 | u32 val; |
145 | int ret; | |
146 | ||
440ea776 AT |
147 | if (regnum & MII_ADDR_C45) |
148 | return -EOPNOTSUPP; | |
149 | ||
b0b7fa4f | 150 | ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus); |
839f46bb | 151 | if (ret < 0) |
0268b51e | 152 | return ret; |
fc8f5ade TP |
153 | |
154 | writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) | | |
155 | (regnum << MVMDIO_SMI_PHY_REG_SHIFT) | | |
156 | MVMDIO_SMI_READ_OPERATION), | |
3712b717 | 157 | dev->regs); |
fc8f5ade | 158 | |
b0b7fa4f | 159 | ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus); |
839f46bb | 160 | if (ret < 0) |
0268b51e | 161 | return ret; |
839f46bb LB |
162 | |
163 | val = readl(dev->regs); | |
164 | if (!(val & MVMDIO_SMI_READ_VALID)) { | |
165 | dev_err(bus->parent, "SMI bus read not valid\n"); | |
0268b51e | 166 | return -ENODEV; |
fc8f5ade TP |
167 | } |
168 | ||
0268b51e | 169 | return val & GENMASK(15, 0); |
fc8f5ade TP |
170 | } |
171 | ||
c0ac08f5 AT |
172 | static int orion_mdio_smi_write(struct mii_bus *bus, int mii_id, |
173 | int regnum, u16 value) | |
fc8f5ade TP |
174 | { |
175 | struct orion_mdio_dev *dev = bus->priv; | |
176 | int ret; | |
177 | ||
440ea776 AT |
178 | if (regnum & MII_ADDR_C45) |
179 | return -EOPNOTSUPP; | |
180 | ||
b0b7fa4f | 181 | ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus); |
526edcf5 | 182 | if (ret < 0) |
0268b51e | 183 | return ret; |
fc8f5ade TP |
184 | |
185 | writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) | | |
186 | (regnum << MVMDIO_SMI_PHY_REG_SHIFT) | | |
187 | MVMDIO_SMI_WRITE_OPERATION | | |
188 | (value << MVMDIO_SMI_DATA_SHIFT)), | |
3712b717 | 189 | dev->regs); |
fc8f5ade | 190 | |
0268b51e | 191 | return 0; |
fc8f5ade TP |
192 | } |
193 | ||
c0ac08f5 AT |
194 | static int orion_mdio_xsmi_is_done(struct orion_mdio_dev *dev) |
195 | { | |
196 | return !(readl(dev->regs + MVMDIO_XSMI_MGNT_REG) & MVMDIO_XSMI_BUSY); | |
197 | } | |
198 | ||
199 | static const struct orion_mdio_ops orion_mdio_xsmi_ops = { | |
200 | .is_done = orion_mdio_xsmi_is_done, | |
201 | .poll_interval_min = MVMDIO_XSMI_POLL_INTERVAL_MIN, | |
202 | .poll_interval_max = MVMDIO_XSMI_POLL_INTERVAL_MAX, | |
203 | }; | |
204 | ||
205 | static int orion_mdio_xsmi_read(struct mii_bus *bus, int mii_id, | |
206 | int regnum) | |
207 | { | |
208 | struct orion_mdio_dev *dev = bus->priv; | |
209 | u16 dev_addr = (regnum >> 16) & GENMASK(4, 0); | |
210 | int ret; | |
211 | ||
212 | if (!(regnum & MII_ADDR_C45)) | |
213 | return -EOPNOTSUPP; | |
214 | ||
215 | ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus); | |
216 | if (ret < 0) | |
217 | return ret; | |
218 | ||
219 | writel(regnum & GENMASK(15, 0), dev->regs + MVMDIO_XSMI_ADDR_REG); | |
220 | writel((mii_id << MVMDIO_XSMI_PHYADDR_SHIFT) | | |
221 | (dev_addr << MVMDIO_XSMI_DEVADDR_SHIFT) | | |
222 | MVMDIO_XSMI_READ_OPERATION, | |
223 | dev->regs + MVMDIO_XSMI_MGNT_REG); | |
224 | ||
225 | ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus); | |
226 | if (ret < 0) | |
227 | return ret; | |
228 | ||
229 | if (!(readl(dev->regs + MVMDIO_XSMI_MGNT_REG) & | |
230 | MVMDIO_XSMI_READ_VALID)) { | |
231 | dev_err(bus->parent, "XSMI bus read not valid\n"); | |
232 | return -ENODEV; | |
233 | } | |
234 | ||
235 | return readl(dev->regs + MVMDIO_XSMI_MGNT_REG) & GENMASK(15, 0); | |
236 | } | |
237 | ||
238 | static int orion_mdio_xsmi_write(struct mii_bus *bus, int mii_id, | |
239 | int regnum, u16 value) | |
240 | { | |
241 | struct orion_mdio_dev *dev = bus->priv; | |
242 | u16 dev_addr = (regnum >> 16) & GENMASK(4, 0); | |
243 | int ret; | |
244 | ||
245 | if (!(regnum & MII_ADDR_C45)) | |
246 | return -EOPNOTSUPP; | |
247 | ||
248 | ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus); | |
249 | if (ret < 0) | |
250 | return ret; | |
251 | ||
252 | writel(regnum & GENMASK(15, 0), dev->regs + MVMDIO_XSMI_ADDR_REG); | |
253 | writel((mii_id << MVMDIO_XSMI_PHYADDR_SHIFT) | | |
254 | (dev_addr << MVMDIO_XSMI_DEVADDR_SHIFT) | | |
255 | MVMDIO_XSMI_WRITE_OPERATION | value, | |
256 | dev->regs + MVMDIO_XSMI_MGNT_REG); | |
257 | ||
258 | return 0; | |
259 | } | |
260 | ||
2ec98521 FF |
261 | static irqreturn_t orion_mdio_err_irq(int irq, void *dev_id) |
262 | { | |
263 | struct orion_mdio_dev *dev = dev_id; | |
264 | ||
265 | if (readl(dev->regs + MVMDIO_ERR_INT_CAUSE) & | |
266 | MVMDIO_ERR_INT_SMI_DONE) { | |
267 | writel(~MVMDIO_ERR_INT_SMI_DONE, | |
268 | dev->regs + MVMDIO_ERR_INT_CAUSE); | |
269 | wake_up(&dev->smi_busy_wait); | |
270 | return IRQ_HANDLED; | |
271 | } | |
272 | ||
273 | return IRQ_NONE; | |
274 | } | |
275 | ||
03ce758e | 276 | static int orion_mdio_probe(struct platform_device *pdev) |
fc8f5ade | 277 | { |
c0ac08f5 | 278 | enum orion_mdio_bus_type type; |
7111b717 | 279 | struct resource *r; |
fc8f5ade TP |
280 | struct mii_bus *bus; |
281 | struct orion_mdio_dev *dev; | |
96cb4342 | 282 | int i, ret; |
fc8f5ade | 283 | |
c0ac08f5 AT |
284 | type = (enum orion_mdio_bus_type)of_device_get_match_data(&pdev->dev); |
285 | ||
7111b717 FF |
286 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
287 | if (!r) { | |
288 | dev_err(&pdev->dev, "No SMI register address given\n"); | |
289 | return -ENODEV; | |
290 | } | |
291 | ||
56ecd2cc EG |
292 | bus = devm_mdiobus_alloc_size(&pdev->dev, |
293 | sizeof(struct orion_mdio_dev)); | |
294 | if (!bus) | |
fc8f5ade | 295 | return -ENOMEM; |
fc8f5ade | 296 | |
c0ac08f5 AT |
297 | switch (type) { |
298 | case BUS_TYPE_SMI: | |
299 | bus->read = orion_mdio_smi_read; | |
300 | bus->write = orion_mdio_smi_write; | |
301 | break; | |
302 | case BUS_TYPE_XSMI: | |
303 | bus->read = orion_mdio_xsmi_read; | |
304 | bus->write = orion_mdio_xsmi_write; | |
305 | break; | |
306 | } | |
307 | ||
fc8f5ade | 308 | bus->name = "orion_mdio_bus"; |
fc8f5ade TP |
309 | snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", |
310 | dev_name(&pdev->dev)); | |
311 | bus->parent = &pdev->dev; | |
312 | ||
fc8f5ade | 313 | dev = bus->priv; |
3712b717 FF |
314 | dev->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); |
315 | if (!dev->regs) { | |
7111b717 | 316 | dev_err(&pdev->dev, "Unable to remap SMI register\n"); |
f814bfd7 | 317 | return -ENODEV; |
2ec98521 FF |
318 | } |
319 | ||
320 | init_waitqueue_head(&dev->smi_busy_wait); | |
321 | ||
d934423a APR |
322 | if (pdev->dev.of_node) { |
323 | for (i = 0; i < ARRAY_SIZE(dev->clk); i++) { | |
324 | dev->clk[i] = of_clk_get(pdev->dev.of_node, i); | |
325 | if (PTR_ERR(dev->clk[i]) == -EPROBE_DEFER) { | |
326 | ret = -EPROBE_DEFER; | |
327 | goto out_clk; | |
328 | } | |
329 | if (IS_ERR(dev->clk[i])) | |
330 | break; | |
331 | clk_prepare_enable(dev->clk[i]); | |
332 | } | |
333 | ||
334 | if (!IS_ERR(of_clk_get(pdev->dev.of_node, | |
335 | ARRAY_SIZE(dev->clk)))) | |
336 | dev_warn(&pdev->dev, | |
337 | "unsupported number of clocks, limiting to the first " | |
338 | __stringify(ARRAY_SIZE(dev->clk)) "\n"); | |
339 | } else { | |
340 | dev->clk[0] = clk_get(&pdev->dev, NULL); | |
341 | if (PTR_ERR(dev->clk[0]) == -EPROBE_DEFER) { | |
433a06d7 JM |
342 | ret = -EPROBE_DEFER; |
343 | goto out_clk; | |
344 | } | |
d934423a APR |
345 | if (!IS_ERR(dev->clk[0])) |
346 | clk_prepare_enable(dev->clk[0]); | |
96cb4342 | 347 | } |
3d604da1 | 348 | |
ea664b1b | 349 | |
fa2632f7 | 350 | dev->err_interrupt = platform_get_irq_optional(pdev, 0); |
a51e2c9d RK |
351 | if (dev->err_interrupt > 0 && |
352 | resource_size(r) < MVMDIO_ERR_INT_MASK + 4) { | |
353 | dev_err(&pdev->dev, | |
354 | "disabling interrupt, resource size is too small\n"); | |
355 | dev->err_interrupt = 0; | |
356 | } | |
39076b04 | 357 | if (dev->err_interrupt > 0) { |
2ec98521 FF |
358 | ret = devm_request_irq(&pdev->dev, dev->err_interrupt, |
359 | orion_mdio_err_irq, | |
360 | IRQF_SHARED, pdev->name, dev); | |
361 | if (ret) | |
362 | goto out_mdio; | |
363 | ||
364 | writel(MVMDIO_ERR_INT_SMI_DONE, | |
365 | dev->regs + MVMDIO_ERR_INT_MASK); | |
39076b04 | 366 | |
028fd76b CP |
367 | } else if (dev->err_interrupt == -EPROBE_DEFER) { |
368 | ret = -EPROBE_DEFER; | |
589bf32f | 369 | goto out_mdio; |
fc8f5ade TP |
370 | } |
371 | ||
00e798c7 | 372 | ret = of_mdiobus_register(bus, pdev->dev.of_node); |
fc8f5ade TP |
373 | if (ret < 0) { |
374 | dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); | |
2ec98521 | 375 | goto out_mdio; |
fc8f5ade TP |
376 | } |
377 | ||
378 | platform_set_drvdata(pdev, bus); | |
379 | ||
380 | return 0; | |
2ec98521 FF |
381 | |
382 | out_mdio: | |
37282485 RK |
383 | if (dev->err_interrupt > 0) |
384 | writel(0, dev->regs + MVMDIO_ERR_INT_MASK); | |
96cb4342 | 385 | |
433a06d7 | 386 | out_clk: |
96cb4342 RK |
387 | for (i = 0; i < ARRAY_SIZE(dev->clk); i++) { |
388 | if (IS_ERR(dev->clk[i])) | |
389 | break; | |
390 | clk_disable_unprepare(dev->clk[i]); | |
391 | clk_put(dev->clk[i]); | |
392 | } | |
393 | ||
2ec98521 | 394 | return ret; |
fc8f5ade TP |
395 | } |
396 | ||
03ce758e | 397 | static int orion_mdio_remove(struct platform_device *pdev) |
fc8f5ade TP |
398 | { |
399 | struct mii_bus *bus = platform_get_drvdata(pdev); | |
2ec98521 | 400 | struct orion_mdio_dev *dev = bus->priv; |
96cb4342 | 401 | int i; |
2ec98521 | 402 | |
7093a970 RK |
403 | if (dev->err_interrupt > 0) |
404 | writel(0, dev->regs + MVMDIO_ERR_INT_MASK); | |
fc8f5ade | 405 | mdiobus_unregister(bus); |
96cb4342 RK |
406 | |
407 | for (i = 0; i < ARRAY_SIZE(dev->clk); i++) { | |
408 | if (IS_ERR(dev->clk[i])) | |
409 | break; | |
410 | clk_disable_unprepare(dev->clk[i]); | |
411 | clk_put(dev->clk[i]); | |
412 | } | |
3d604da1 | 413 | |
fc8f5ade TP |
414 | return 0; |
415 | } | |
416 | ||
417 | static const struct of_device_id orion_mdio_match[] = { | |
c0ac08f5 AT |
418 | { .compatible = "marvell,orion-mdio", .data = (void *)BUS_TYPE_SMI }, |
419 | { .compatible = "marvell,xmdio", .data = (void *)BUS_TYPE_XSMI }, | |
fc8f5ade TP |
420 | { } |
421 | }; | |
422 | MODULE_DEVICE_TABLE(of, orion_mdio_match); | |
423 | ||
424 | static struct platform_driver orion_mdio_driver = { | |
425 | .probe = orion_mdio_probe, | |
03ce758e | 426 | .remove = orion_mdio_remove, |
fc8f5ade TP |
427 | .driver = { |
428 | .name = "orion-mdio", | |
429 | .of_match_table = orion_mdio_match, | |
430 | }, | |
431 | }; | |
432 | ||
433 | module_platform_driver(orion_mdio_driver); | |
434 | ||
435 | MODULE_DESCRIPTION("Marvell MDIO interface driver"); | |
436 | MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>"); | |
437 | MODULE_LICENSE("GPL"); | |
404b8bed | 438 | MODULE_ALIAS("platform:orion-mdio"); |