aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/fsl_pq_mdio.c
diff options
context:
space:
mode:
authorAndy Fleming <afleming@freescale.com>2009-02-04 19:42:12 -0500
committerDavid S. Miller <davem@davemloft.net>2009-02-04 19:42:35 -0500
commit1577ecef766650a57fceb171acee2b13cbfaf1d3 (patch)
treeb66df11d8815f95f815e65c3c3ffd32df81b8d43 /drivers/net/fsl_pq_mdio.c
parent9c46f6d42f1b5627c49a5906cb5b315ad8716ff0 (diff)
netdev: Merge UCC and gianfar MDIO bus drivers
The MDIO bus drivers for the UCC and gianfar ethernet controllers are essentially the same. There's no reason to duplicate that much code. Signed-off-by: Andy Fleming <afleming@freescale.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/fsl_pq_mdio.c')
-rw-r--r--drivers/net/fsl_pq_mdio.c463
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 */
56int 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(&regs->miimadd, (mii_id << 8) | regnum);
61
62 /* Write out the value we want */
63 out_be32(&regs->miimcon, value);
64
65 /* Wait for the transaction to finish */
66 while (in_be32(&regs->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 */
82int 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(&regs->miimadd, (mii_id << 8) | regnum);
89
90 /* Clear miimcom, and then initiate a read */
91 out_be32(&regs->miimcom, 0);
92 out_be32(&regs->miimcom, MII_READ_COMMAND);
93
94 /* Wait for the transaction to finish */
95 while (in_be32(&regs->miimind) & (MIIMIND_NOTVALID | MIIMIND_BUSY))
96 cpu_relax();
97
98 /* Grab the value of the register from miimstat */
99 value = in_be32(&regs->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 */
108int 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 */
120int 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 */
129static 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(&regs->miimcfg, MIIMCFG_RESET);
138
139 /* Setup the MII Mgmt clock speed */
140 out_be32(&regs->miimcfg, MIIMCFG_INIT_VALUE);
141
142 /* Wait until the bus is free */
143 while ((in_be32(&regs->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 */
158static 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
195void 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 */
205static 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
224static 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
242static 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
280static 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 = &regs->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
398err_free_irqs:
399 kfree(new_bus->irq);
400err_unmap_regs:
401 iounmap(regs);
402err_free_bus:
403 kfree(new_bus);
404
405 return err;
406}
407
408
409static 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
425static 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
446static 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
453int __init fsl_pq_mdio_init(void)
454{
455 return of_register_platform_driver(&fsl_pq_mdio_driver);
456}
457
458void fsl_pq_mdio_exit(void)
459{
460 of_unregister_platform_driver(&fsl_pq_mdio_driver);
461}
462subsys_initcall_sync(fsl_pq_mdio_init);
463module_exit(fsl_pq_mdio_exit);