diff options
Diffstat (limited to 'drivers/net/fsl_pq_mdio.c')
-rw-r--r-- | drivers/net/fsl_pq_mdio.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/drivers/net/fsl_pq_mdio.c b/drivers/net/fsl_pq_mdio.c new file mode 100644 index 000000000000..c434a156d7a9 --- /dev/null +++ b/drivers/net/fsl_pq_mdio.c | |||
@@ -0,0 +1,463 @@ | |||
1 | /* | ||
2 | * Freescale PowerQUICC Ethernet Driver -- MIIM bus implementation | ||
3 | * Provides Bus interface for MIIM regs | ||
4 | * | ||
5 | * Author: Andy Fleming <afleming@freescale.com> | ||
6 | * | ||
7 | * Copyright (c) 2002-2004,2008 Freescale Semiconductor, Inc. | ||
8 | * | ||
9 | * Based on gianfar_mii.c and ucc_geth_mii.c (Li Yang, Kim Phillips) | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify it | ||
12 | * under the terms of the GNU General Public License as published by the | ||
13 | * Free Software Foundation; either version 2 of the License, or (at your | ||
14 | * option) any later version. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/unistd.h> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/delay.h> | ||
26 | #include <linux/netdevice.h> | ||
27 | #include <linux/etherdevice.h> | ||
28 | #include <linux/skbuff.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <linux/mm.h> | ||
31 | #include <linux/module.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/crc32.h> | ||
34 | #include <linux/mii.h> | ||
35 | #include <linux/phy.h> | ||
36 | #include <linux/of.h> | ||
37 | #include <linux/of_platform.h> | ||
38 | |||
39 | #include <asm/io.h> | ||
40 | #include <asm/irq.h> | ||
41 | #include <asm/uaccess.h> | ||
42 | #include <asm/ucc.h> | ||
43 | |||
44 | #include "gianfar.h" | ||
45 | #include "fsl_pq_mdio.h" | ||
46 | |||
47 | /* | ||
48 | * Write value to the PHY at mii_id at register regnum, | ||
49 | * on the bus attached to the local interface, which may be different from the | ||
50 | * generic mdio bus (tied to a single interface), waiting until the write is | ||
51 | * done before returning. This is helpful in programming interfaces like | ||
52 | * the TBI which control interfaces like onchip SERDES and are always tied to | ||
53 | * the local mdio pins, which may not be the same as system mdio bus, used for | ||
54 | * controlling the external PHYs, for example. | ||
55 | */ | ||
56 | int fsl_pq_local_mdio_write(struct fsl_pq_mdio __iomem *regs, int mii_id, | ||
57 | int regnum, u16 value) | ||
58 | { | ||
59 | /* Set the PHY address and the register address we want to write */ | ||
60 | out_be32(®s->miimadd, (mii_id << 8) | regnum); | ||
61 | |||
62 | /* Write out the value we want */ | ||
63 | out_be32(®s->miimcon, value); | ||
64 | |||
65 | /* Wait for the transaction to finish */ | ||
66 | while (in_be32(®s->miimind) & MIIMIND_BUSY) | ||
67 | cpu_relax(); | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | /* | ||
73 | * Read the bus for PHY at addr mii_id, register regnum, and | ||
74 | * return the value. Clears miimcom first. All PHY operation | ||
75 | * done on the bus attached to the local interface, | ||
76 | * which may be different from the generic mdio bus | ||
77 | * This is helpful in programming interfaces like | ||
78 | * the TBI which, in turn, control interfaces like onchip SERDES | ||
79 | * and are always tied to the local mdio pins, which may not be the | ||
80 | * same as system mdio bus, used for controlling the external PHYs, for eg. | ||
81 | */ | ||
82 | int fsl_pq_local_mdio_read(struct fsl_pq_mdio __iomem *regs, | ||
83 | int mii_id, int regnum) | ||
84 | { | ||
85 | u16 value; | ||
86 | |||
87 | /* Set the PHY address and the register address we want to read */ | ||
88 | out_be32(®s->miimadd, (mii_id << 8) | regnum); | ||
89 | |||
90 | /* Clear miimcom, and then initiate a read */ | ||
91 | out_be32(®s->miimcom, 0); | ||
92 | out_be32(®s->miimcom, MII_READ_COMMAND); | ||
93 | |||
94 | /* Wait for the transaction to finish */ | ||
95 | while (in_be32(®s->miimind) & (MIIMIND_NOTVALID | MIIMIND_BUSY)) | ||
96 | cpu_relax(); | ||
97 | |||
98 | /* Grab the value of the register from miimstat */ | ||
99 | value = in_be32(®s->miimstat); | ||
100 | |||
101 | return value; | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Write value to the PHY at mii_id at register regnum, | ||
106 | * on the bus, waiting until the write is done before returning. | ||
107 | */ | ||
108 | int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum, u16 value) | ||
109 | { | ||
110 | struct fsl_pq_mdio __iomem *regs = (void __iomem *)bus->priv; | ||
111 | |||
112 | /* Write to the local MII regs */ | ||
113 | return(fsl_pq_local_mdio_write(regs, mii_id, regnum, value)); | ||
114 | } | ||
115 | |||
116 | /* | ||
117 | * Read the bus for PHY at addr mii_id, register regnum, and | ||
118 | * return the value. Clears miimcom first. | ||
119 | */ | ||
120 | int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum) | ||
121 | { | ||
122 | struct fsl_pq_mdio __iomem *regs = (void __iomem *)bus->priv; | ||
123 | |||
124 | /* Read the local MII regs */ | ||
125 | return(fsl_pq_local_mdio_read(regs, mii_id, regnum)); | ||
126 | } | ||
127 | |||
128 | /* Reset the MIIM registers, and wait for the bus to free */ | ||
129 | static int fsl_pq_mdio_reset(struct mii_bus *bus) | ||
130 | { | ||
131 | struct fsl_pq_mdio __iomem *regs = (void __iomem *)bus->priv; | ||
132 | unsigned int timeout = PHY_INIT_TIMEOUT; | ||
133 | |||
134 | mutex_lock(&bus->mdio_lock); | ||
135 | |||
136 | /* Reset the management interface */ | ||
137 | out_be32(®s->miimcfg, MIIMCFG_RESET); | ||
138 | |||
139 | /* Setup the MII Mgmt clock speed */ | ||
140 | out_be32(®s->miimcfg, MIIMCFG_INIT_VALUE); | ||
141 | |||
142 | /* Wait until the bus is free */ | ||
143 | while ((in_be32(®s->miimind) & MIIMIND_BUSY) && timeout--) | ||
144 | cpu_relax(); | ||
145 | |||
146 | mutex_unlock(&bus->mdio_lock); | ||
147 | |||
148 | if(timeout == 0) { | ||
149 | printk(KERN_ERR "%s: The MII Bus is stuck!\n", | ||
150 | bus->name); | ||
151 | return -EBUSY; | ||
152 | } | ||
153 | |||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /* Allocate an array which provides irq #s for each PHY on the given bus */ | ||
158 | static int *create_irq_map(struct device_node *np) | ||
159 | { | ||
160 | int *irqs; | ||
161 | int i; | ||
162 | struct device_node *child = NULL; | ||
163 | |||
164 | irqs = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL); | ||
165 | |||
166 | if (!irqs) | ||
167 | return NULL; | ||
168 | |||
169 | for (i = 0; i < PHY_MAX_ADDR; i++) | ||
170 | irqs[i] = PHY_POLL; | ||
171 | |||
172 | while ((child = of_get_next_child(np, child)) != NULL) { | ||
173 | int irq = irq_of_parse_and_map(child, 0); | ||
174 | const u32 *id; | ||
175 | |||
176 | if (irq == NO_IRQ) | ||
177 | continue; | ||
178 | |||
179 | id = of_get_property(child, "reg", NULL); | ||
180 | |||
181 | if (!id) | ||
182 | continue; | ||
183 | |||
184 | if (*id < PHY_MAX_ADDR && *id >= 0) | ||
185 | irqs[*id] = irq; | ||
186 | else | ||
187 | printk(KERN_WARNING "%s: " | ||
188 | "%d is not a valid PHY address\n", | ||
189 | np->full_name, *id); | ||
190 | } | ||
191 | |||
192 | return irqs; | ||
193 | } | ||
194 | |||
195 | void fsl_pq_mdio_bus_name(char *name, struct device_node *np) | ||
196 | { | ||
197 | const u32 *reg; | ||
198 | |||
199 | reg = of_get_property(np, "reg", NULL); | ||
200 | |||
201 | snprintf(name, MII_BUS_ID_SIZE, "%s@%x", np->name, reg ? *reg : 0); | ||
202 | } | ||
203 | |||
204 | /* Scan the bus in reverse, looking for an empty spot */ | ||
205 | static int fsl_pq_mdio_find_free(struct mii_bus *new_bus) | ||
206 | { | ||
207 | int i; | ||
208 | |||
209 | for (i = PHY_MAX_ADDR; i > 0; i--) { | ||
210 | u32 phy_id; | ||
211 | |||
212 | if (get_phy_id(new_bus, i, &phy_id)) | ||
213 | return -1; | ||
214 | |||
215 | if (phy_id == 0xffffffff) | ||
216 | break; | ||
217 | } | ||
218 | |||
219 | return i; | ||
220 | } | ||
221 | |||
222 | |||
223 | #ifdef CONFIG_GIANFAR | ||
224 | static u32 __iomem *get_gfar_tbipa(struct fsl_pq_mdio __iomem *regs) | ||
225 | { | ||
226 | struct gfar __iomem *enet_regs; | ||
227 | |||
228 | /* | ||
229 | * This is mildly evil, but so is our hardware for doing this. | ||
230 | * Also, we have to cast back to struct gfar because of | ||
231 | * definition weirdness done in gianfar.h. | ||
232 | */ | ||
233 | enet_regs = (struct gfar __iomem *) | ||
234 | ((char __iomem *)regs - offsetof(struct gfar, gfar_mii_regs)); | ||
235 | |||
236 | return &enet_regs->tbipa; | ||
237 | } | ||
238 | #endif | ||
239 | |||
240 | |||
241 | #ifdef CONFIG_UCC_GETH | ||
242 | static int get_ucc_id_for_range(u64 start, u64 end, u32 *ucc_id) | ||
243 | { | ||
244 | struct device_node *np = NULL; | ||
245 | int err = 0; | ||
246 | |||
247 | for_each_compatible_node(np, NULL, "ucc_geth") { | ||
248 | struct resource tempres; | ||
249 | |||
250 | err = of_address_to_resource(np, 0, &tempres); | ||
251 | if (err) | ||
252 | continue; | ||
253 | |||
254 | /* if our mdio regs fall within this UCC regs range */ | ||
255 | if ((start >= tempres.start) && (end <= tempres.end)) { | ||
256 | /* Find the id of the UCC */ | ||
257 | const u32 *id; | ||
258 | |||
259 | id = of_get_property(np, "cell-index", NULL); | ||
260 | if (!id) { | ||
261 | id = of_get_property(np, "device-id", NULL); | ||
262 | if (!id) | ||
263 | continue; | ||
264 | } | ||
265 | |||
266 | *ucc_id = *id; | ||
267 | |||
268 | return 0; | ||
269 | } | ||
270 | } | ||
271 | |||
272 | if (err) | ||
273 | return err; | ||
274 | else | ||
275 | return -EINVAL; | ||
276 | } | ||
277 | #endif | ||
278 | |||
279 | |||
280 | static int fsl_pq_mdio_probe(struct of_device *ofdev, | ||
281 | const struct of_device_id *match) | ||
282 | { | ||
283 | struct device_node *np = ofdev->node; | ||
284 | struct device_node *tbi; | ||
285 | struct fsl_pq_mdio __iomem *regs; | ||
286 | u32 __iomem *tbipa; | ||
287 | struct mii_bus *new_bus; | ||
288 | int tbiaddr = -1; | ||
289 | u64 addr, size; | ||
290 | int err = 0; | ||
291 | |||
292 | new_bus = mdiobus_alloc(); | ||
293 | if (NULL == new_bus) | ||
294 | return -ENOMEM; | ||
295 | |||
296 | new_bus->name = "Freescale PowerQUICC MII Bus", | ||
297 | new_bus->read = &fsl_pq_mdio_read, | ||
298 | new_bus->write = &fsl_pq_mdio_write, | ||
299 | new_bus->reset = &fsl_pq_mdio_reset, | ||
300 | fsl_pq_mdio_bus_name(new_bus->id, np); | ||
301 | |||
302 | /* Set the PHY base address */ | ||
303 | addr = of_translate_address(np, of_get_address(np, 0, &size, NULL)); | ||
304 | regs = ioremap(addr, size); | ||
305 | |||
306 | if (NULL == regs) { | ||
307 | err = -ENOMEM; | ||
308 | goto err_free_bus; | ||
309 | } | ||
310 | |||
311 | new_bus->priv = (void __force *)regs; | ||
312 | |||
313 | new_bus->irq = create_irq_map(np); | ||
314 | |||
315 | if (NULL == new_bus->irq) { | ||
316 | err = -ENOMEM; | ||
317 | goto err_unmap_regs; | ||
318 | } | ||
319 | |||
320 | new_bus->parent = &ofdev->dev; | ||
321 | dev_set_drvdata(&ofdev->dev, new_bus); | ||
322 | |||
323 | if (of_device_is_compatible(np, "fsl,gianfar-mdio") || | ||
324 | of_device_is_compatible(np, "gianfar")) { | ||
325 | #ifdef CONFIG_GIANFAR | ||
326 | tbipa = get_gfar_tbipa(regs); | ||
327 | #else | ||
328 | err = -ENODEV; | ||
329 | goto err_free_irqs; | ||
330 | #endif | ||
331 | } else if (of_device_is_compatible(np, "fsl,ucc-mdio") || | ||
332 | of_device_is_compatible(np, "ucc_geth_phy")) { | ||
333 | #ifdef CONFIG_UCC_GETH | ||
334 | u32 id; | ||
335 | |||
336 | tbipa = ®s->utbipar; | ||
337 | |||
338 | if ((err = get_ucc_id_for_range(addr, addr + size, &id))) | ||
339 | goto err_free_irqs; | ||
340 | |||
341 | ucc_set_qe_mux_mii_mng(id - 1); | ||
342 | #else | ||
343 | err = -ENODEV; | ||
344 | goto err_free_irqs; | ||
345 | #endif | ||
346 | } else { | ||
347 | err = -ENODEV; | ||
348 | goto err_free_irqs; | ||
349 | } | ||
350 | |||
351 | for_each_child_of_node(np, tbi) { | ||
352 | if (!strncmp(tbi->type, "tbi-phy", 8)) | ||
353 | break; | ||
354 | } | ||
355 | |||
356 | if (tbi) { | ||
357 | const u32 *prop = of_get_property(tbi, "reg", NULL); | ||
358 | |||
359 | if (prop) | ||
360 | tbiaddr = *prop; | ||
361 | } | ||
362 | |||
363 | if (tbiaddr == -1) { | ||
364 | out_be32(tbipa, 0); | ||
365 | |||
366 | tbiaddr = fsl_pq_mdio_find_free(new_bus); | ||
367 | } | ||
368 | |||
369 | /* | ||
370 | * We define TBIPA at 0 to be illegal, opting to fail for boards that | ||
371 | * have PHYs at 1-31, rather than change tbipa and rescan. | ||
372 | */ | ||
373 | if (tbiaddr == 0) { | ||
374 | err = -EBUSY; | ||
375 | |||
376 | goto err_free_irqs; | ||
377 | } | ||
378 | |||
379 | out_be32(tbipa, tbiaddr); | ||
380 | |||
381 | /* | ||
382 | * The TBIPHY-only buses will find PHYs at every address, | ||
383 | * so we mask them all but the TBI | ||
384 | */ | ||
385 | if (!of_device_is_compatible(np, "fsl,gianfar-mdio")) | ||
386 | new_bus->phy_mask = ~(1 << tbiaddr); | ||
387 | |||
388 | err = mdiobus_register(new_bus); | ||
389 | |||
390 | if (err) { | ||
391 | printk (KERN_ERR "%s: Cannot register as MDIO bus\n", | ||
392 | new_bus->name); | ||
393 | goto err_free_irqs; | ||
394 | } | ||
395 | |||
396 | return 0; | ||
397 | |||
398 | err_free_irqs: | ||
399 | kfree(new_bus->irq); | ||
400 | err_unmap_regs: | ||
401 | iounmap(regs); | ||
402 | err_free_bus: | ||
403 | kfree(new_bus); | ||
404 | |||
405 | return err; | ||
406 | } | ||
407 | |||
408 | |||
409 | static int fsl_pq_mdio_remove(struct of_device *ofdev) | ||
410 | { | ||
411 | struct device *device = &ofdev->dev; | ||
412 | struct mii_bus *bus = dev_get_drvdata(device); | ||
413 | |||
414 | mdiobus_unregister(bus); | ||
415 | |||
416 | dev_set_drvdata(device, NULL); | ||
417 | |||
418 | iounmap((void __iomem *)bus->priv); | ||
419 | bus->priv = NULL; | ||
420 | mdiobus_free(bus); | ||
421 | |||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | static struct of_device_id fsl_pq_mdio_match[] = { | ||
426 | { | ||
427 | .type = "mdio", | ||
428 | .compatible = "ucc_geth_phy", | ||
429 | }, | ||
430 | { | ||
431 | .type = "mdio", | ||
432 | .compatible = "gianfar", | ||
433 | }, | ||
434 | { | ||
435 | .compatible = "fsl,ucc-mdio", | ||
436 | }, | ||
437 | { | ||
438 | .compatible = "fsl,gianfar-tbi", | ||
439 | }, | ||
440 | { | ||
441 | .compatible = "fsl,gianfar-mdio", | ||
442 | }, | ||
443 | {}, | ||
444 | }; | ||
445 | |||
446 | static struct of_platform_driver fsl_pq_mdio_driver = { | ||
447 | .name = "fsl-pq_mdio", | ||
448 | .probe = fsl_pq_mdio_probe, | ||
449 | .remove = fsl_pq_mdio_remove, | ||
450 | .match_table = fsl_pq_mdio_match, | ||
451 | }; | ||
452 | |||
453 | int __init fsl_pq_mdio_init(void) | ||
454 | { | ||
455 | return of_register_platform_driver(&fsl_pq_mdio_driver); | ||
456 | } | ||
457 | |||
458 | void fsl_pq_mdio_exit(void) | ||
459 | { | ||
460 | of_unregister_platform_driver(&fsl_pq_mdio_driver); | ||
461 | } | ||
462 | subsys_initcall_sync(fsl_pq_mdio_init); | ||
463 | module_exit(fsl_pq_mdio_exit); | ||