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