Commit | Line | Data |
---|---|---|
1577ecef AF |
1 | /* |
2 | * Freescale PowerQUICC Ethernet Driver -- MIIM bus implementation | |
3 | * Provides Bus interface for MIIM regs | |
4 | * | |
5 | * Author: Andy Fleming <afleming@freescale.com> | |
1d2397d7 | 6 | * Modifier: Sandeep Gopalpet <sandeep.kumar@freescale.com> |
1577ecef | 7 | * |
1d2397d7 | 8 | * Copyright 2002-2004, 2008-2009 Freescale Semiconductor, Inc. |
1577ecef AF |
9 | * |
10 | * Based on gianfar_mii.c and ucc_geth_mii.c (Li Yang, Kim Phillips) | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify it | |
13 | * under the terms of the GNU General Public License as published by the | |
14 | * Free Software Foundation; either version 2 of the License, or (at your | |
15 | * option) any later version. | |
16 | * | |
17 | */ | |
18 | ||
19 | #include <linux/kernel.h> | |
20 | #include <linux/string.h> | |
21 | #include <linux/errno.h> | |
1577ecef | 22 | #include <linux/slab.h> |
1577ecef | 23 | #include <linux/delay.h> |
1577ecef | 24 | #include <linux/module.h> |
1577ecef | 25 | #include <linux/mii.h> |
22ae782f | 26 | #include <linux/of_address.h> |
324931ba | 27 | #include <linux/of_mdio.h> |
afae5ad7 | 28 | #include <linux/of_device.h> |
1577ecef AF |
29 | |
30 | #include <asm/io.h> | |
9a4cbd53 | 31 | #if IS_ENABLED(CONFIG_UCC_GETH) |
7aa1aa6e | 32 | #include <soc/fsl/qe/ucc.h> |
9a4cbd53 | 33 | #endif |
1577ecef AF |
34 | |
35 | #include "gianfar.h" | |
19bcd6c6 TT |
36 | |
37 | #define MIIMIND_BUSY 0x00000001 | |
38 | #define MIIMIND_NOTVALID 0x00000004 | |
39 | #define MIIMCFG_INIT_VALUE 0x00000007 | |
40 | #define MIIMCFG_RESET 0x80000000 | |
41 | ||
42 | #define MII_READ_COMMAND 0x00000001 | |
43 | ||
afae5ad7 TT |
44 | struct fsl_pq_mii { |
45 | u32 miimcfg; /* MII management configuration reg */ | |
46 | u32 miimcom; /* MII management command reg */ | |
47 | u32 miimadd; /* MII management address reg */ | |
48 | u32 miimcon; /* MII management control reg */ | |
49 | u32 miimstat; /* MII management status reg */ | |
50 | u32 miimind; /* MII management indication reg */ | |
51 | }; | |
52 | ||
19bcd6c6 TT |
53 | struct fsl_pq_mdio { |
54 | u8 res1[16]; | |
55 | u32 ieventm; /* MDIO Interrupt event register (for etsec2)*/ | |
56 | u32 imaskm; /* MDIO Interrupt mask register (for etsec2)*/ | |
57 | u8 res2[4]; | |
58 | u32 emapm; /* MDIO Event mapping register (for etsec2)*/ | |
59 | u8 res3[1280]; | |
afae5ad7 | 60 | struct fsl_pq_mii mii; |
19bcd6c6 TT |
61 | u8 res4[28]; |
62 | u32 utbipar; /* TBI phy address reg (only on UCC) */ | |
63 | u8 res5[2728]; | |
64 | } __packed; | |
1577ecef | 65 | |
59399c59 TT |
66 | /* Number of microseconds to wait for an MII register to respond */ |
67 | #define MII_TIMEOUT 1000 | |
68 | ||
b3319b10 AV |
69 | struct fsl_pq_mdio_priv { |
70 | void __iomem *map; | |
afae5ad7 TT |
71 | struct fsl_pq_mii __iomem *regs; |
72 | }; | |
73 | ||
74 | /* | |
75 | * Per-device-type data. Each type of device tree node that we support gets | |
76 | * one of these. | |
77 | * | |
78 | * @mii_offset: the offset of the MII registers within the memory map of the | |
79 | * node. Some nodes define only the MII registers, and some define the whole | |
80 | * MAC (which includes the MII registers). | |
81 | * | |
82 | * @get_tbipa: determines the address of the TBIPA register | |
83 | * | |
84 | * @ucc_configure: a special function for extra QE configuration | |
85 | */ | |
86 | struct fsl_pq_mdio_data { | |
87 | unsigned int mii_offset; /* offset of the MII registers */ | |
88 | uint32_t __iomem * (*get_tbipa)(void __iomem *p); | |
89 | void (*ucc_configure)(phys_addr_t start, phys_addr_t end); | |
b3319b10 AV |
90 | }; |
91 | ||
1577ecef | 92 | /* |
69cfb419 TT |
93 | * Write value to the PHY at mii_id at register regnum, on the bus attached |
94 | * to the local interface, which may be different from the generic mdio bus | |
95 | * (tied to a single interface), waiting until the write is done before | |
96 | * returning. This is helpful in programming interfaces like the TBI which | |
97 | * control interfaces like onchip SERDES and are always tied to the local | |
98 | * mdio pins, which may not be the same as system mdio bus, used for | |
1577ecef AF |
99 | * controlling the external PHYs, for example. |
100 | */ | |
69cfb419 TT |
101 | static int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum, |
102 | u16 value) | |
1577ecef | 103 | { |
69cfb419 | 104 | struct fsl_pq_mdio_priv *priv = bus->priv; |
afae5ad7 | 105 | struct fsl_pq_mii __iomem *regs = priv->regs; |
e4b081f5 | 106 | unsigned int timeout; |
59399c59 | 107 | |
1577ecef | 108 | /* Set the PHY address and the register address we want to write */ |
f5bbd262 | 109 | iowrite32be((mii_id << 8) | regnum, ®s->miimadd); |
1577ecef AF |
110 | |
111 | /* Write out the value we want */ | |
f5bbd262 | 112 | iowrite32be(value, ®s->miimcon); |
1577ecef AF |
113 | |
114 | /* Wait for the transaction to finish */ | |
e4b081f5 CM |
115 | timeout = MII_TIMEOUT; |
116 | while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) { | |
117 | cpu_relax(); | |
118 | timeout--; | |
119 | } | |
1577ecef | 120 | |
e4b081f5 | 121 | return timeout ? 0 : -ETIMEDOUT; |
1577ecef AF |
122 | } |
123 | ||
124 | /* | |
69cfb419 TT |
125 | * Read the bus for PHY at addr mii_id, register regnum, and return the value. |
126 | * Clears miimcom first. | |
127 | * | |
128 | * All PHY operation done on the bus attached to the local interface, which | |
129 | * may be different from the generic mdio bus. This is helpful in programming | |
130 | * interfaces like the TBI which, in turn, control interfaces like on-chip | |
131 | * SERDES and are always tied to the local mdio pins, which may not be the | |
1577ecef AF |
132 | * same as system mdio bus, used for controlling the external PHYs, for eg. |
133 | */ | |
69cfb419 | 134 | static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum) |
1577ecef | 135 | { |
69cfb419 | 136 | struct fsl_pq_mdio_priv *priv = bus->priv; |
afae5ad7 | 137 | struct fsl_pq_mii __iomem *regs = priv->regs; |
e4b081f5 | 138 | unsigned int timeout; |
69cfb419 | 139 | u16 value; |
1577ecef AF |
140 | |
141 | /* Set the PHY address and the register address we want to read */ | |
f5bbd262 | 142 | iowrite32be((mii_id << 8) | regnum, ®s->miimadd); |
1577ecef AF |
143 | |
144 | /* Clear miimcom, and then initiate a read */ | |
f5bbd262 CM |
145 | iowrite32be(0, ®s->miimcom); |
146 | iowrite32be(MII_READ_COMMAND, ®s->miimcom); | |
1577ecef | 147 | |
59399c59 | 148 | /* Wait for the transaction to finish, normally less than 100us */ |
e4b081f5 CM |
149 | timeout = MII_TIMEOUT; |
150 | while ((ioread32be(®s->miimind) & | |
151 | (MIIMIND_NOTVALID | MIIMIND_BUSY)) && timeout) { | |
152 | cpu_relax(); | |
153 | timeout--; | |
154 | } | |
155 | ||
156 | if (!timeout) | |
59399c59 | 157 | return -ETIMEDOUT; |
1577ecef AF |
158 | |
159 | /* Grab the value of the register from miimstat */ | |
f5bbd262 | 160 | value = ioread32be(®s->miimstat); |
1577ecef | 161 | |
afae5ad7 | 162 | dev_dbg(&bus->dev, "read %04x from address %x/%x\n", value, mii_id, regnum); |
1577ecef AF |
163 | return value; |
164 | } | |
165 | ||
1577ecef AF |
166 | /* Reset the MIIM registers, and wait for the bus to free */ |
167 | static int fsl_pq_mdio_reset(struct mii_bus *bus) | |
168 | { | |
69cfb419 | 169 | struct fsl_pq_mdio_priv *priv = bus->priv; |
afae5ad7 | 170 | struct fsl_pq_mii __iomem *regs = priv->regs; |
e4b081f5 | 171 | unsigned int timeout; |
1577ecef AF |
172 | |
173 | mutex_lock(&bus->mdio_lock); | |
174 | ||
175 | /* Reset the management interface */ | |
f5bbd262 | 176 | iowrite32be(MIIMCFG_RESET, ®s->miimcfg); |
1577ecef AF |
177 | |
178 | /* Setup the MII Mgmt clock speed */ | |
f5bbd262 | 179 | iowrite32be(MIIMCFG_INIT_VALUE, ®s->miimcfg); |
1577ecef AF |
180 | |
181 | /* Wait until the bus is free */ | |
e4b081f5 CM |
182 | timeout = MII_TIMEOUT; |
183 | while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) { | |
184 | cpu_relax(); | |
185 | timeout--; | |
186 | } | |
1577ecef AF |
187 | |
188 | mutex_unlock(&bus->mdio_lock); | |
189 | ||
e4b081f5 | 190 | if (!timeout) { |
5078ac79 | 191 | dev_err(&bus->dev, "timeout waiting for MII bus\n"); |
1577ecef AF |
192 | return -EBUSY; |
193 | } | |
194 | ||
195 | return 0; | |
196 | } | |
197 | ||
952c5ca1 | 198 | #if defined(CONFIG_GIANFAR) || defined(CONFIG_GIANFAR_MODULE) |
afae5ad7 | 199 | /* |
3bb35ac4 GF |
200 | * Return the TBIPA address, starting from the address |
201 | * of the mapped GFAR MDIO registers (struct gfar) | |
afae5ad7 TT |
202 | * This is mildly evil, but so is our hardware for doing this. |
203 | * Also, we have to cast back to struct gfar because of | |
204 | * definition weirdness done in gianfar.h. | |
205 | */ | |
3bb35ac4 | 206 | static uint32_t __iomem *get_gfar_tbipa_from_mdio(void __iomem *p) |
afae5ad7 TT |
207 | { |
208 | struct gfar __iomem *enet_regs = p; | |
1577ecef | 209 | |
afae5ad7 | 210 | return &enet_regs->tbipa; |
952c5ca1 | 211 | } |
1577ecef | 212 | |
3bb35ac4 GF |
213 | /* |
214 | * Return the TBIPA address, starting from the address | |
215 | * of the mapped GFAR MII registers (gfar_mii_regs[] within struct gfar) | |
216 | */ | |
217 | static uint32_t __iomem *get_gfar_tbipa_from_mii(void __iomem *p) | |
218 | { | |
219 | return get_gfar_tbipa_from_mdio(container_of(p, struct gfar, gfar_mii_regs)); | |
220 | } | |
221 | ||
afae5ad7 TT |
222 | /* |
223 | * Return the TBIPAR address for an eTSEC2 node | |
224 | */ | |
225 | static uint32_t __iomem *get_etsec_tbipa(void __iomem *p) | |
1577ecef | 226 | { |
afae5ad7 TT |
227 | return p; |
228 | } | |
229 | #endif | |
230 | ||
952c5ca1 | 231 | #if defined(CONFIG_UCC_GETH) || defined(CONFIG_UCC_GETH_MODULE) |
afae5ad7 | 232 | /* |
3bb35ac4 GF |
233 | * Return the TBIPAR address for a QE MDIO node, starting from the address |
234 | * of the mapped MII registers (struct fsl_pq_mii) | |
afae5ad7 TT |
235 | */ |
236 | static uint32_t __iomem *get_ucc_tbipa(void __iomem *p) | |
237 | { | |
3bb35ac4 | 238 | struct fsl_pq_mdio __iomem *mdio = container_of(p, struct fsl_pq_mdio, mii); |
afae5ad7 TT |
239 | |
240 | return &mdio->utbipar; | |
241 | } | |
242 | ||
243 | /* | |
244 | * Find the UCC node that controls the given MDIO node | |
245 | * | |
246 | * For some reason, the QE MDIO nodes are not children of the UCC devices | |
247 | * that control them. Therefore, we need to scan all UCC nodes looking for | |
248 | * the one that encompases the given MDIO node. We do this by comparing | |
249 | * physical addresses. The 'start' and 'end' addresses of the MDIO node are | |
250 | * passed, and the correct UCC node will cover the entire address range. | |
251 | * | |
252 | * This assumes that there is only one QE MDIO node in the entire device tree. | |
253 | */ | |
254 | static void ucc_configure(phys_addr_t start, phys_addr_t end) | |
255 | { | |
256 | static bool found_mii_master; | |
1577ecef | 257 | struct device_node *np = NULL; |
1577ecef | 258 | |
afae5ad7 TT |
259 | if (found_mii_master) |
260 | return; | |
1577ecef | 261 | |
afae5ad7 TT |
262 | for_each_compatible_node(np, NULL, "ucc_geth") { |
263 | struct resource res; | |
264 | const uint32_t *iprop; | |
265 | uint32_t id; | |
266 | int ret; | |
267 | ||
268 | ret = of_address_to_resource(np, 0, &res); | |
269 | if (ret < 0) { | |
270 | pr_debug("fsl-pq-mdio: no address range in node %s\n", | |
271 | np->full_name); | |
1577ecef | 272 | continue; |
afae5ad7 | 273 | } |
1577ecef AF |
274 | |
275 | /* if our mdio regs fall within this UCC regs range */ | |
afae5ad7 TT |
276 | if ((start < res.start) || (end > res.end)) |
277 | continue; | |
278 | ||
279 | iprop = of_get_property(np, "cell-index", NULL); | |
280 | if (!iprop) { | |
281 | iprop = of_get_property(np, "device-id", NULL); | |
282 | if (!iprop) { | |
283 | pr_debug("fsl-pq-mdio: no UCC ID in node %s\n", | |
284 | np->full_name); | |
285 | continue; | |
1577ecef | 286 | } |
afae5ad7 | 287 | } |
1577ecef | 288 | |
afae5ad7 | 289 | id = be32_to_cpup(iprop); |
1577ecef | 290 | |
afae5ad7 TT |
291 | /* |
292 | * cell-index and device-id for QE nodes are | |
293 | * numbered from 1, not 0. | |
294 | */ | |
295 | if (ucc_set_qe_mux_mii_mng(id - 1) < 0) { | |
296 | pr_debug("fsl-pq-mdio: invalid UCC ID in node %s\n", | |
297 | np->full_name); | |
298 | continue; | |
1577ecef | 299 | } |
afae5ad7 TT |
300 | |
301 | pr_debug("fsl-pq-mdio: setting node UCC%u to MII master\n", id); | |
302 | found_mii_master = true; | |
1577ecef | 303 | } |
afae5ad7 | 304 | } |
1577ecef | 305 | |
1577ecef | 306 | #endif |
afae5ad7 | 307 | |
94e5a2a8 | 308 | static const struct of_device_id fsl_pq_mdio_match[] = { |
afae5ad7 TT |
309 | #if defined(CONFIG_GIANFAR) || defined(CONFIG_GIANFAR_MODULE) |
310 | { | |
311 | .compatible = "fsl,gianfar-tbi", | |
312 | .data = &(struct fsl_pq_mdio_data) { | |
313 | .mii_offset = 0, | |
3bb35ac4 | 314 | .get_tbipa = get_gfar_tbipa_from_mii, |
afae5ad7 TT |
315 | }, |
316 | }, | |
317 | { | |
318 | .compatible = "fsl,gianfar-mdio", | |
319 | .data = &(struct fsl_pq_mdio_data) { | |
320 | .mii_offset = 0, | |
3bb35ac4 | 321 | .get_tbipa = get_gfar_tbipa_from_mii, |
afae5ad7 TT |
322 | }, |
323 | }, | |
324 | { | |
325 | .type = "mdio", | |
326 | .compatible = "gianfar", | |
327 | .data = &(struct fsl_pq_mdio_data) { | |
328 | .mii_offset = offsetof(struct fsl_pq_mdio, mii), | |
3bb35ac4 | 329 | .get_tbipa = get_gfar_tbipa_from_mdio, |
afae5ad7 TT |
330 | }, |
331 | }, | |
332 | { | |
333 | .compatible = "fsl,etsec2-tbi", | |
334 | .data = &(struct fsl_pq_mdio_data) { | |
335 | .mii_offset = offsetof(struct fsl_pq_mdio, mii), | |
336 | .get_tbipa = get_etsec_tbipa, | |
337 | }, | |
338 | }, | |
339 | { | |
340 | .compatible = "fsl,etsec2-mdio", | |
341 | .data = &(struct fsl_pq_mdio_data) { | |
342 | .mii_offset = offsetof(struct fsl_pq_mdio, mii), | |
343 | .get_tbipa = get_etsec_tbipa, | |
344 | }, | |
345 | }, | |
346 | #endif | |
347 | #if defined(CONFIG_UCC_GETH) || defined(CONFIG_UCC_GETH_MODULE) | |
348 | { | |
349 | .compatible = "fsl,ucc-mdio", | |
350 | .data = &(struct fsl_pq_mdio_data) { | |
351 | .mii_offset = 0, | |
352 | .get_tbipa = get_ucc_tbipa, | |
353 | .ucc_configure = ucc_configure, | |
354 | }, | |
355 | }, | |
356 | { | |
357 | /* Legacy UCC MDIO node */ | |
358 | .type = "mdio", | |
359 | .compatible = "ucc_geth_phy", | |
360 | .data = &(struct fsl_pq_mdio_data) { | |
361 | .mii_offset = 0, | |
362 | .get_tbipa = get_ucc_tbipa, | |
363 | .ucc_configure = ucc_configure, | |
364 | }, | |
365 | }, | |
366 | #endif | |
761743eb TT |
367 | /* No Kconfig option for Fman support yet */ |
368 | { | |
369 | .compatible = "fsl,fman-mdio", | |
370 | .data = &(struct fsl_pq_mdio_data) { | |
371 | .mii_offset = 0, | |
372 | /* Fman TBI operations are handled elsewhere */ | |
373 | }, | |
374 | }, | |
375 | ||
afae5ad7 TT |
376 | {}, |
377 | }; | |
378 | MODULE_DEVICE_TABLE(of, fsl_pq_mdio_match); | |
1577ecef | 379 | |
5078ac79 | 380 | static int fsl_pq_mdio_probe(struct platform_device *pdev) |
1577ecef | 381 | { |
afae5ad7 TT |
382 | const struct of_device_id *id = |
383 | of_match_device(fsl_pq_mdio_match, &pdev->dev); | |
384 | const struct fsl_pq_mdio_data *data = id->data; | |
5078ac79 | 385 | struct device_node *np = pdev->dev.of_node; |
afae5ad7 | 386 | struct resource res; |
1577ecef | 387 | struct device_node *tbi; |
b3319b10 | 388 | struct fsl_pq_mdio_priv *priv; |
1577ecef | 389 | struct mii_bus *new_bus; |
08d18f3b | 390 | int err; |
1577ecef | 391 | |
afae5ad7 TT |
392 | dev_dbg(&pdev->dev, "found %s compatible node\n", id->compatible); |
393 | ||
dd3b8a32 TT |
394 | new_bus = mdiobus_alloc_size(sizeof(*priv)); |
395 | if (!new_bus) | |
b3319b10 AV |
396 | return -ENOMEM; |
397 | ||
dd3b8a32 | 398 | priv = new_bus->priv; |
1577ecef | 399 | new_bus->name = "Freescale PowerQUICC MII Bus", |
5078ac79 TT |
400 | new_bus->read = &fsl_pq_mdio_read; |
401 | new_bus->write = &fsl_pq_mdio_write; | |
402 | new_bus->reset = &fsl_pq_mdio_reset; | |
1577ecef | 403 | |
afae5ad7 TT |
404 | err = of_address_to_resource(np, 0, &res); |
405 | if (err < 0) { | |
406 | dev_err(&pdev->dev, "could not obtain address information\n"); | |
dd3b8a32 | 407 | goto error; |
3b1fd3e5 AV |
408 | } |
409 | ||
69cfb419 | 410 | snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s@%llx", np->name, |
afae5ad7 | 411 | (unsigned long long)res.start); |
69cfb419 | 412 | |
afae5ad7 TT |
413 | priv->map = of_iomap(np, 0); |
414 | if (!priv->map) { | |
1577ecef | 415 | err = -ENOMEM; |
dd3b8a32 | 416 | goto error; |
1577ecef AF |
417 | } |
418 | ||
afae5ad7 TT |
419 | /* |
420 | * Some device tree nodes represent only the MII registers, and | |
421 | * others represent the MAC and MII registers. The 'mii_offset' field | |
422 | * contains the offset of the MII registers inside the mapped register | |
423 | * space. | |
424 | */ | |
425 | if (data->mii_offset > resource_size(&res)) { | |
426 | dev_err(&pdev->dev, "invalid register map\n"); | |
427 | err = -EINVAL; | |
dd3b8a32 | 428 | goto error; |
afae5ad7 TT |
429 | } |
430 | priv->regs = priv->map + data->mii_offset; | |
1577ecef | 431 | |
5078ac79 | 432 | new_bus->parent = &pdev->dev; |
a0e18600 | 433 | platform_set_drvdata(pdev, new_bus); |
1577ecef | 434 | |
afae5ad7 TT |
435 | if (data->get_tbipa) { |
436 | for_each_child_of_node(np, tbi) { | |
437 | if (strcmp(tbi->type, "tbi-phy") == 0) { | |
438 | dev_dbg(&pdev->dev, "found TBI PHY node %s\n", | |
439 | strrchr(tbi->full_name, '/') + 1); | |
440 | break; | |
441 | } | |
fbcc0e2c | 442 | } |
1577ecef | 443 | |
afae5ad7 TT |
444 | if (tbi) { |
445 | const u32 *prop = of_get_property(tbi, "reg", NULL); | |
446 | uint32_t __iomem *tbipa; | |
1577ecef | 447 | |
afae5ad7 TT |
448 | if (!prop) { |
449 | dev_err(&pdev->dev, | |
450 | "missing 'reg' property in node %s\n", | |
451 | tbi->full_name); | |
452 | err = -EBUSY; | |
453 | goto error; | |
454 | } | |
1577ecef | 455 | |
afae5ad7 | 456 | tbipa = data->get_tbipa(priv->map); |
1577ecef | 457 | |
3dd03e52 GF |
458 | /* |
459 | * Add consistency check to make sure TBI is contained | |
460 | * within the mapped range (not because we would get a | |
461 | * segfault, rather to catch bugs in computing TBI | |
462 | * address). Print error message but continue anyway. | |
463 | */ | |
464 | if ((void *)tbipa > priv->map + resource_size(&res) - 4) | |
8cde3e44 | 465 | dev_err(&pdev->dev, "invalid register map (should be at least 0x%04zx to contain TBI address)\n", |
3dd03e52 GF |
466 | ((void *)tbipa - priv->map) + 4); |
467 | ||
f5bbd262 | 468 | iowrite32be(be32_to_cpup(prop), tbipa); |
464b57da | 469 | } |
1577ecef AF |
470 | } |
471 | ||
afae5ad7 TT |
472 | if (data->ucc_configure) |
473 | data->ucc_configure(res.start, res.end); | |
474 | ||
324931ba | 475 | err = of_mdiobus_register(new_bus, np); |
1577ecef | 476 | if (err) { |
5078ac79 TT |
477 | dev_err(&pdev->dev, "cannot register %s as MDIO bus\n", |
478 | new_bus->name); | |
dd3b8a32 | 479 | goto error; |
1577ecef AF |
480 | } |
481 | ||
482 | return 0; | |
483 | ||
dd3b8a32 TT |
484 | error: |
485 | if (priv->map) | |
486 | iounmap(priv->map); | |
487 | ||
1577ecef | 488 | kfree(new_bus); |
dd3b8a32 | 489 | |
1577ecef AF |
490 | return err; |
491 | } | |
492 | ||
493 | ||
5078ac79 | 494 | static int fsl_pq_mdio_remove(struct platform_device *pdev) |
1577ecef | 495 | { |
5078ac79 | 496 | struct device *device = &pdev->dev; |
1577ecef | 497 | struct mii_bus *bus = dev_get_drvdata(device); |
b3319b10 | 498 | struct fsl_pq_mdio_priv *priv = bus->priv; |
1577ecef AF |
499 | |
500 | mdiobus_unregister(bus); | |
501 | ||
b3319b10 | 502 | iounmap(priv->map); |
1577ecef AF |
503 | mdiobus_free(bus); |
504 | ||
505 | return 0; | |
506 | } | |
507 | ||
74888760 | 508 | static struct platform_driver fsl_pq_mdio_driver = { |
4018294b GL |
509 | .driver = { |
510 | .name = "fsl-pq_mdio", | |
4018294b GL |
511 | .of_match_table = fsl_pq_mdio_match, |
512 | }, | |
1577ecef AF |
513 | .probe = fsl_pq_mdio_probe, |
514 | .remove = fsl_pq_mdio_remove, | |
1577ecef AF |
515 | }; |
516 | ||
db62f684 | 517 | module_platform_driver(fsl_pq_mdio_driver); |
1577ecef | 518 | |
26062897 | 519 | MODULE_LICENSE("GPL"); |