OpenWrt – Blame information for rev 3
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * B53 register access through MII registers |
||
3 | * |
||
4 | * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org> |
||
5 | * |
||
6 | * Permission to use, copy, modify, and/or distribute this software for any |
||
7 | * purpose with or without fee is hereby granted, provided that the above |
||
8 | * copyright notice and this permission notice appear in all copies. |
||
9 | * |
||
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||
15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||
16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||
17 | */ |
||
18 | |||
19 | #include <linux/kernel.h> |
||
20 | #include <linux/phy.h> |
||
21 | #include <linux/module.h> |
||
22 | |||
23 | #include "b53_priv.h" |
||
24 | |||
25 | #define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ |
||
26 | |||
27 | /* MII registers */ |
||
28 | #define REG_MII_PAGE 0x10 /* MII Page register */ |
||
29 | #define REG_MII_ADDR 0x11 /* MII Address register */ |
||
30 | #define REG_MII_DATA0 0x18 /* MII Data register 0 */ |
||
31 | #define REG_MII_DATA1 0x19 /* MII Data register 1 */ |
||
32 | #define REG_MII_DATA2 0x1a /* MII Data register 2 */ |
||
33 | #define REG_MII_DATA3 0x1b /* MII Data register 3 */ |
||
34 | |||
35 | #define REG_MII_PAGE_ENABLE BIT(0) |
||
36 | #define REG_MII_ADDR_WRITE BIT(0) |
||
37 | #define REG_MII_ADDR_READ BIT(1) |
||
38 | |||
39 | static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) |
||
40 | { |
||
41 | int i; |
||
42 | u16 v; |
||
43 | int ret; |
||
44 | struct mii_bus *bus = dev->priv; |
||
45 | |||
46 | if (dev->current_page != page) { |
||
47 | /* set page number */ |
||
48 | v = (page << 8) | REG_MII_PAGE_ENABLE; |
||
49 | ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); |
||
50 | if (ret) |
||
51 | return ret; |
||
52 | dev->current_page = page; |
||
53 | } |
||
54 | |||
55 | /* set register address */ |
||
56 | v = (reg << 8) | op; |
||
57 | ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); |
||
58 | if (ret) |
||
59 | return ret; |
||
60 | |||
61 | /* check if operation completed */ |
||
62 | for (i = 0; i < 5; ++i) { |
||
63 | v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); |
||
64 | if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) |
||
65 | break; |
||
66 | usleep_range(10, 100); |
||
67 | } |
||
68 | |||
69 | if (WARN_ON(i == 5)) |
||
70 | return -EIO; |
||
71 | |||
72 | return 0; |
||
73 | } |
||
74 | |||
75 | static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) |
||
76 | { |
||
77 | struct mii_bus *bus = dev->priv; |
||
78 | int ret; |
||
79 | |||
80 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); |
||
81 | if (ret) |
||
82 | return ret; |
||
83 | |||
84 | *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; |
||
85 | |||
86 | return 0; |
||
87 | } |
||
88 | |||
89 | static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) |
||
90 | { |
||
91 | struct mii_bus *bus = dev->priv; |
||
92 | int ret; |
||
93 | |||
94 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); |
||
95 | if (ret) |
||
96 | return ret; |
||
97 | |||
98 | *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); |
||
99 | |||
100 | return 0; |
||
101 | } |
||
102 | |||
103 | static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) |
||
104 | { |
||
105 | struct mii_bus *bus = dev->priv; |
||
106 | int ret; |
||
107 | |||
108 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); |
||
109 | if (ret) |
||
110 | return ret; |
||
111 | |||
112 | *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); |
||
113 | *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; |
||
114 | |||
115 | return 0; |
||
116 | } |
||
117 | |||
118 | static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) |
||
119 | { |
||
120 | struct mii_bus *bus = dev->priv; |
||
121 | u64 temp = 0; |
||
122 | int i; |
||
123 | int ret; |
||
124 | |||
125 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); |
||
126 | if (ret) |
||
127 | return ret; |
||
128 | |||
129 | for (i = 2; i >= 0; i--) { |
||
130 | temp <<= 16; |
||
131 | temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); |
||
132 | } |
||
133 | |||
134 | *val = temp; |
||
135 | |||
136 | return 0; |
||
137 | } |
||
138 | |||
139 | static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) |
||
140 | { |
||
141 | struct mii_bus *bus = dev->priv; |
||
142 | u64 temp = 0; |
||
143 | int i; |
||
144 | int ret; |
||
145 | |||
146 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); |
||
147 | if (ret) |
||
148 | return ret; |
||
149 | |||
150 | for (i = 3; i >= 0; i--) { |
||
151 | temp <<= 16; |
||
152 | temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); |
||
153 | } |
||
154 | |||
155 | *val = temp; |
||
156 | |||
157 | return 0; |
||
158 | } |
||
159 | |||
160 | static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) |
||
161 | { |
||
162 | struct mii_bus *bus = dev->priv; |
||
163 | int ret; |
||
164 | |||
165 | ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); |
||
166 | if (ret) |
||
167 | return ret; |
||
168 | |||
169 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); |
||
170 | } |
||
171 | |||
172 | static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, |
||
173 | u16 value) |
||
174 | { |
||
175 | struct mii_bus *bus = dev->priv; |
||
176 | int ret; |
||
177 | |||
178 | ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); |
||
179 | if (ret) |
||
180 | return ret; |
||
181 | |||
182 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); |
||
183 | } |
||
184 | |||
185 | static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, |
||
186 | u32 value) |
||
187 | { |
||
188 | struct mii_bus *bus = dev->priv; |
||
189 | unsigned int i; |
||
190 | u32 temp = value; |
||
191 | |||
192 | for (i = 0; i < 2; i++) { |
||
193 | int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, |
||
194 | temp & 0xffff); |
||
195 | if (ret) |
||
196 | return ret; |
||
197 | temp >>= 16; |
||
198 | } |
||
199 | |||
200 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); |
||
201 | |||
202 | } |
||
203 | |||
204 | static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, |
||
205 | u64 value) |
||
206 | { |
||
207 | struct mii_bus *bus = dev->priv; |
||
208 | unsigned i; |
||
209 | u64 temp = value; |
||
210 | |||
211 | for (i = 0; i < 3; i++) { |
||
212 | int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, |
||
213 | temp & 0xffff); |
||
214 | if (ret) |
||
215 | return ret; |
||
216 | temp >>= 16; |
||
217 | } |
||
218 | |||
219 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); |
||
220 | |||
221 | } |
||
222 | |||
223 | static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, |
||
224 | u64 value) |
||
225 | { |
||
226 | struct mii_bus *bus = dev->priv; |
||
227 | unsigned i; |
||
228 | u64 temp = value; |
||
229 | |||
230 | for (i = 0; i < 4; i++) { |
||
231 | int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, |
||
232 | temp & 0xffff); |
||
233 | if (ret) |
||
234 | return ret; |
||
235 | temp >>= 16; |
||
236 | } |
||
237 | |||
238 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); |
||
239 | } |
||
240 | |||
241 | static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg, |
||
242 | u16 *value) |
||
243 | { |
||
244 | struct mii_bus *bus = dev->priv; |
||
245 | |||
246 | *value = mdiobus_read(bus, addr, reg); |
||
247 | |||
248 | return 0; |
||
249 | } |
||
250 | |||
251 | static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg, |
||
252 | u16 value) |
||
253 | { |
||
254 | struct mii_bus *bus = dev->priv; |
||
255 | |||
256 | return mdiobus_write(bus, addr, reg, value); |
||
257 | } |
||
258 | |||
259 | static struct b53_io_ops b53_mdio_ops = { |
||
260 | .read8 = b53_mdio_read8, |
||
261 | .read16 = b53_mdio_read16, |
||
262 | .read32 = b53_mdio_read32, |
||
263 | .read48 = b53_mdio_read48, |
||
264 | .read64 = b53_mdio_read64, |
||
265 | .write8 = b53_mdio_write8, |
||
266 | .write16 = b53_mdio_write16, |
||
267 | .write32 = b53_mdio_write32, |
||
268 | .write48 = b53_mdio_write48, |
||
269 | .write64 = b53_mdio_write64, |
||
270 | .phy_read16 = b53_mdio_phy_read16, |
||
271 | .phy_write16 = b53_mdio_phy_write16, |
||
272 | }; |
||
273 | |||
274 | static int b53_phy_probe(struct phy_device *phydev) |
||
275 | { |
||
3 | office | 276 | struct b53_device dev; |
1 | office | 277 | int ret; |
278 | |||
279 | /* allow the generic phy driver to take over */ |
||
280 | if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0) |
||
281 | return -ENODEV; |
||
282 | |||
3 | office | 283 | dev.current_page = 0xff; |
284 | dev.priv = phydev->mdio.bus; |
||
285 | dev.ops = &b53_mdio_ops; |
||
286 | dev.pdata = NULL; |
||
287 | mutex_init(&dev.reg_mutex); |
||
1 | office | 288 | |
3 | office | 289 | ret = b53_switch_detect(&dev); |
1 | office | 290 | if (ret) |
291 | return ret; |
||
292 | |||
3 | office | 293 | if (is5325(&dev) || is5365(&dev)) |
1 | office | 294 | phydev->supported = SUPPORTED_100baseT_Full; |
295 | else |
||
296 | phydev->supported = SUPPORTED_1000baseT_Full; |
||
297 | |||
298 | phydev->advertising = phydev->supported; |
||
299 | |||
300 | return 0; |
||
301 | } |
||
302 | |||
303 | static int b53_phy_config_init(struct phy_device *phydev) |
||
304 | { |
||
3 | office | 305 | struct b53_device *dev; |
306 | int ret; |
||
1 | office | 307 | |
3 | office | 308 | dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus); |
309 | if (!dev) |
||
310 | return -ENOMEM; |
||
311 | |||
1 | office | 312 | /* we don't use page 0xff, so force a page set */ |
313 | dev->current_page = 0xff; |
||
314 | /* force the ethX as alias */ |
||
315 | dev->sw_dev.alias = phydev->attached_dev->name; |
||
316 | |||
3 | office | 317 | ret = b53_switch_register(dev); |
318 | if (ret) { |
||
319 | dev_err(dev->dev, "failed to register switch: %i\n", ret); |
||
320 | return ret; |
||
321 | } |
||
322 | |||
323 | phydev->priv = dev; |
||
324 | |||
1 | office | 325 | return 0; |
326 | } |
||
327 | |||
328 | static void b53_phy_remove(struct phy_device *phydev) |
||
329 | { |
||
330 | struct b53_device *priv = phydev->priv; |
||
331 | |||
332 | if (!priv) |
||
333 | return; |
||
334 | |||
335 | b53_switch_remove(priv); |
||
336 | |||
337 | phydev->priv = NULL; |
||
338 | } |
||
339 | |||
340 | static int b53_phy_config_aneg(struct phy_device *phydev) |
||
341 | { |
||
342 | return 0; |
||
343 | } |
||
344 | |||
345 | static int b53_phy_read_status(struct phy_device *phydev) |
||
346 | { |
||
347 | struct b53_device *priv = phydev->priv; |
||
348 | |||
349 | if (is5325(priv) || is5365(priv)) |
||
350 | phydev->speed = 100; |
||
351 | else |
||
352 | phydev->speed = 1000; |
||
353 | |||
354 | phydev->duplex = DUPLEX_FULL; |
||
355 | phydev->link = 1; |
||
356 | phydev->state = PHY_RUNNING; |
||
357 | |||
358 | netif_carrier_on(phydev->attached_dev); |
||
359 | phydev->adjust_link(phydev->attached_dev); |
||
360 | |||
361 | return 0; |
||
362 | } |
||
363 | |||
364 | /* BCM5325, BCM539x */ |
||
365 | static struct phy_driver b53_phy_driver_id1 = { |
||
366 | .phy_id = 0x0143bc00, |
||
367 | .name = "Broadcom B53 (1)", |
||
368 | .phy_id_mask = 0x1ffffc00, |
||
369 | .features = 0, |
||
370 | .probe = b53_phy_probe, |
||
371 | .remove = b53_phy_remove, |
||
372 | .config_aneg = b53_phy_config_aneg, |
||
373 | .config_init = b53_phy_config_init, |
||
374 | .read_status = b53_phy_read_status, |
||
375 | }; |
||
376 | |||
377 | /* BCM53125, BCM53128 */ |
||
378 | static struct phy_driver b53_phy_driver_id2 = { |
||
379 | .phy_id = 0x03625c00, |
||
380 | .name = "Broadcom B53 (2)", |
||
381 | .phy_id_mask = 0x1ffffc00, |
||
382 | .features = 0, |
||
383 | .probe = b53_phy_probe, |
||
384 | .remove = b53_phy_remove, |
||
385 | .config_aneg = b53_phy_config_aneg, |
||
386 | .config_init = b53_phy_config_init, |
||
387 | .read_status = b53_phy_read_status, |
||
388 | }; |
||
389 | |||
390 | /* BCM5365 */ |
||
391 | static struct phy_driver b53_phy_driver_id3 = { |
||
392 | .phy_id = 0x00406000, |
||
393 | .name = "Broadcom B53 (3)", |
||
394 | .phy_id_mask = 0x1ffffc00, |
||
395 | .features = 0, |
||
396 | .probe = b53_phy_probe, |
||
397 | .remove = b53_phy_remove, |
||
398 | .config_aneg = b53_phy_config_aneg, |
||
399 | .config_init = b53_phy_config_init, |
||
400 | .read_status = b53_phy_read_status, |
||
401 | }; |
||
402 | |||
403 | int __init b53_phy_driver_register(void) |
||
404 | { |
||
405 | int ret; |
||
406 | |||
407 | ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE); |
||
408 | if (ret) |
||
409 | return ret; |
||
410 | |||
411 | ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE); |
||
412 | if (ret) |
||
413 | goto err1; |
||
414 | |||
415 | ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE); |
||
416 | if (!ret) |
||
417 | return 0; |
||
418 | |||
419 | phy_driver_unregister(&b53_phy_driver_id2); |
||
420 | err1: |
||
421 | phy_driver_unregister(&b53_phy_driver_id1); |
||
422 | return ret; |
||
423 | } |
||
424 | |||
425 | void __exit b53_phy_driver_unregister(void) |
||
426 | { |
||
427 | phy_driver_unregister(&b53_phy_driver_id3); |
||
428 | phy_driver_unregister(&b53_phy_driver_id2); |
||
429 | phy_driver_unregister(&b53_phy_driver_id1); |
||
430 | } |
||
431 | |||
432 | module_init(b53_phy_driver_register); |
||
433 | module_exit(b53_phy_driver_unregister); |
||
434 | |||
435 | MODULE_DESCRIPTION("B53 MDIO access driver"); |
||
436 | MODULE_LICENSE("Dual BSD/GPL"); |