Commit | Line | Data |
---|---|---|
967dd82f FF |
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 | #include <linux/delay.h> | |
23 | #include <linux/brcmphy.h> | |
24 | #include <linux/rtnetlink.h> | |
25 | #include <net/dsa.h> | |
26 | ||
27 | #include "b53_priv.h" | |
28 | ||
29 | /* MII registers */ | |
30 | #define REG_MII_PAGE 0x10 /* MII Page register */ | |
31 | #define REG_MII_ADDR 0x11 /* MII Address register */ | |
32 | #define REG_MII_DATA0 0x18 /* MII Data register 0 */ | |
33 | #define REG_MII_DATA1 0x19 /* MII Data register 1 */ | |
34 | #define REG_MII_DATA2 0x1a /* MII Data register 2 */ | |
35 | #define REG_MII_DATA3 0x1b /* MII Data register 3 */ | |
36 | ||
37 | #define REG_MII_PAGE_ENABLE BIT(0) | |
38 | #define REG_MII_ADDR_WRITE BIT(0) | |
39 | #define REG_MII_ADDR_READ BIT(1) | |
40 | ||
41 | static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) | |
42 | { | |
43 | int i; | |
44 | u16 v; | |
45 | int ret; | |
46 | struct mii_bus *bus = dev->priv; | |
47 | ||
48 | if (dev->current_page != page) { | |
49 | /* set page number */ | |
50 | v = (page << 8) | REG_MII_PAGE_ENABLE; | |
51 | ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
52 | REG_MII_PAGE, v); | |
53 | if (ret) | |
54 | return ret; | |
55 | dev->current_page = page; | |
56 | } | |
57 | ||
58 | /* set register address */ | |
59 | v = (reg << 8) | op; | |
60 | ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_ADDR, v); | |
61 | if (ret) | |
62 | return ret; | |
63 | ||
64 | /* check if operation completed */ | |
65 | for (i = 0; i < 5; ++i) { | |
66 | v = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
67 | REG_MII_ADDR); | |
68 | if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) | |
69 | break; | |
70 | usleep_range(10, 100); | |
71 | } | |
72 | ||
73 | if (WARN_ON(i == 5)) | |
74 | return -EIO; | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) | |
80 | { | |
81 | struct mii_bus *bus = dev->priv; | |
82 | int ret; | |
83 | ||
84 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); | |
85 | if (ret) | |
86 | return ret; | |
87 | ||
88 | *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
89 | REG_MII_DATA0) & 0xff; | |
90 | ||
91 | return 0; | |
92 | } | |
93 | ||
94 | static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) | |
95 | { | |
96 | struct mii_bus *bus = dev->priv; | |
97 | int ret; | |
98 | ||
99 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); | |
100 | if (ret) | |
101 | return ret; | |
102 | ||
103 | *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_DATA0); | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) | |
109 | { | |
110 | struct mii_bus *bus = dev->priv; | |
111 | int ret; | |
112 | ||
113 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); | |
114 | if (ret) | |
115 | return ret; | |
116 | ||
117 | *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_DATA0); | |
118 | *val |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
119 | REG_MII_DATA1) << 16; | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) | |
125 | { | |
126 | struct mii_bus *bus = dev->priv; | |
127 | u64 temp = 0; | |
128 | int i; | |
129 | int ret; | |
130 | ||
131 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); | |
132 | if (ret) | |
133 | return ret; | |
134 | ||
135 | for (i = 2; i >= 0; i--) { | |
136 | temp <<= 16; | |
137 | temp |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
138 | REG_MII_DATA0 + i); | |
139 | } | |
140 | ||
141 | *val = temp; | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) | |
147 | { | |
148 | struct mii_bus *bus = dev->priv; | |
149 | u64 temp = 0; | |
150 | int i; | |
151 | int ret; | |
152 | ||
153 | ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
157 | for (i = 3; i >= 0; i--) { | |
158 | temp <<= 16; | |
159 | temp |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
160 | REG_MII_DATA0 + i); | |
161 | } | |
162 | ||
163 | *val = temp; | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
168 | static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) | |
169 | { | |
170 | struct mii_bus *bus = dev->priv; | |
171 | int ret; | |
172 | ||
173 | ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
174 | REG_MII_DATA0, value); | |
175 | if (ret) | |
176 | return ret; | |
177 | ||
178 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); | |
179 | } | |
180 | ||
181 | static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, | |
182 | u16 value) | |
183 | { | |
184 | struct mii_bus *bus = dev->priv; | |
185 | int ret; | |
186 | ||
187 | ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
188 | REG_MII_DATA0, value); | |
189 | if (ret) | |
190 | return ret; | |
191 | ||
192 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); | |
193 | } | |
194 | ||
195 | static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, | |
196 | u32 value) | |
197 | { | |
198 | struct mii_bus *bus = dev->priv; | |
199 | unsigned int i; | |
200 | u32 temp = value; | |
201 | ||
202 | for (i = 0; i < 2; i++) { | |
203 | int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
204 | REG_MII_DATA0 + i, | |
205 | temp & 0xffff); | |
206 | if (ret) | |
207 | return ret; | |
208 | temp >>= 16; | |
209 | } | |
210 | ||
211 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); | |
212 | } | |
213 | ||
214 | static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, | |
215 | u64 value) | |
216 | { | |
217 | struct mii_bus *bus = dev->priv; | |
218 | unsigned int i; | |
219 | u64 temp = value; | |
220 | ||
221 | for (i = 0; i < 3; i++) { | |
222 | int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
223 | REG_MII_DATA0 + i, | |
224 | temp & 0xffff); | |
225 | if (ret) | |
226 | return ret; | |
227 | temp >>= 16; | |
228 | } | |
229 | ||
230 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); | |
231 | } | |
232 | ||
233 | static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, | |
234 | u64 value) | |
235 | { | |
236 | struct mii_bus *bus = dev->priv; | |
237 | unsigned int i; | |
238 | u64 temp = value; | |
239 | ||
240 | for (i = 0; i < 4; i++) { | |
241 | int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, | |
242 | REG_MII_DATA0 + i, | |
243 | temp & 0xffff); | |
244 | if (ret) | |
245 | return ret; | |
246 | temp >>= 16; | |
247 | } | |
248 | ||
249 | return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); | |
250 | } | |
251 | ||
252 | static int b53_mdio_phy_read16(struct b53_device *dev, int addr, int reg, | |
253 | u16 *value) | |
254 | { | |
255 | struct mii_bus *bus = dev->priv; | |
256 | ||
257 | *value = mdiobus_read_nested(bus, addr, reg); | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
262 | static int b53_mdio_phy_write16(struct b53_device *dev, int addr, int reg, | |
263 | u16 value) | |
264 | { | |
265 | struct mii_bus *bus = dev->bus; | |
266 | ||
267 | return mdiobus_write_nested(bus, addr, reg, value); | |
268 | } | |
269 | ||
0dff88d3 | 270 | static const struct b53_io_ops b53_mdio_ops = { |
967dd82f FF |
271 | .read8 = b53_mdio_read8, |
272 | .read16 = b53_mdio_read16, | |
273 | .read32 = b53_mdio_read32, | |
274 | .read48 = b53_mdio_read48, | |
275 | .read64 = b53_mdio_read64, | |
276 | .write8 = b53_mdio_write8, | |
277 | .write16 = b53_mdio_write16, | |
278 | .write32 = b53_mdio_write32, | |
279 | .write48 = b53_mdio_write48, | |
280 | .write64 = b53_mdio_write64, | |
281 | .phy_read16 = b53_mdio_phy_read16, | |
282 | .phy_write16 = b53_mdio_phy_write16, | |
283 | }; | |
284 | ||
285 | #define B53_BRCM_OUI_1 0x0143bc00 | |
286 | #define B53_BRCM_OUI_2 0x03625c00 | |
287 | #define B53_BRCM_OUI_3 0x00406000 | |
288 | ||
289 | static int b53_mdio_probe(struct mdio_device *mdiodev) | |
290 | { | |
291 | struct b53_device *dev; | |
292 | u32 phy_id; | |
293 | int ret; | |
294 | ||
295 | /* allow the generic PHY driver to take over the non-management MDIO | |
296 | * addresses | |
297 | */ | |
298 | if (mdiodev->addr != BRCM_PSEUDO_PHY_ADDR && mdiodev->addr != 0) { | |
299 | dev_err(&mdiodev->dev, "leaving address %d to PHY\n", | |
300 | mdiodev->addr); | |
301 | return -ENODEV; | |
302 | } | |
303 | ||
304 | /* read the first port's id */ | |
305 | phy_id = mdiobus_read(mdiodev->bus, 0, 2) << 16; | |
306 | phy_id |= mdiobus_read(mdiodev->bus, 0, 3); | |
307 | ||
308 | /* BCM5325, BCM539x (OUI_1) | |
309 | * BCM53125, BCM53128 (OUI_2) | |
310 | * BCM5365 (OUI_3) | |
311 | */ | |
312 | if ((phy_id & 0xfffffc00) != B53_BRCM_OUI_1 && | |
313 | (phy_id & 0xfffffc00) != B53_BRCM_OUI_2 && | |
314 | (phy_id & 0xfffffc00) != B53_BRCM_OUI_3) { | |
315 | dev_err(&mdiodev->dev, "Unsupported device: 0x%08x\n", phy_id); | |
316 | return -ENODEV; | |
317 | } | |
318 | ||
0830c980 FF |
319 | /* First probe will come from SWITCH_MDIO controller on the 7445D0 |
320 | * switch, which will conflict with the 7445 integrated switch | |
321 | * pseudo-phy (we end-up programming both). In that case, we return | |
322 | * -EPROBE_DEFER for the first time we get here, and wait until we come | |
323 | * back with the slave MDIO bus which has the correct indirection | |
324 | * layer setup | |
325 | */ | |
326 | if (of_machine_is_compatible("brcm,bcm7445d0") && | |
327 | strcmp(mdiodev->bus->name, "sf2 slave mii")) | |
328 | return -EPROBE_DEFER; | |
329 | ||
967dd82f FF |
330 | dev = b53_switch_alloc(&mdiodev->dev, &b53_mdio_ops, mdiodev->bus); |
331 | if (!dev) | |
332 | return -ENOMEM; | |
333 | ||
334 | /* we don't use page 0xff, so force a page set */ | |
335 | dev->current_page = 0xff; | |
336 | dev->bus = mdiodev->bus; | |
337 | ||
338 | dev_set_drvdata(&mdiodev->dev, dev); | |
339 | ||
340 | ret = b53_switch_register(dev); | |
341 | if (ret) { | |
342 | dev_err(&mdiodev->dev, "failed to register switch: %i\n", ret); | |
343 | return ret; | |
344 | } | |
345 | ||
346 | return ret; | |
347 | } | |
348 | ||
349 | static void b53_mdio_remove(struct mdio_device *mdiodev) | |
350 | { | |
351 | struct b53_device *dev = dev_get_drvdata(&mdiodev->dev); | |
352 | struct dsa_switch *ds = dev->ds; | |
353 | ||
354 | dsa_unregister_switch(ds); | |
355 | } | |
356 | ||
357 | static const struct of_device_id b53_of_match[] = { | |
358 | { .compatible = "brcm,bcm5325" }, | |
359 | { .compatible = "brcm,bcm53115" }, | |
360 | { .compatible = "brcm,bcm53125" }, | |
361 | { .compatible = "brcm,bcm53128" }, | |
362 | { .compatible = "brcm,bcm5365" }, | |
363 | { .compatible = "brcm,bcm5395" }, | |
364 | { .compatible = "brcm,bcm5397" }, | |
365 | { .compatible = "brcm,bcm5398" }, | |
366 | { /* sentinel */ }, | |
367 | }; | |
368 | MODULE_DEVICE_TABLE(of, b53_of_match); | |
369 | ||
370 | static struct mdio_driver b53_mdio_driver = { | |
371 | .probe = b53_mdio_probe, | |
372 | .remove = b53_mdio_remove, | |
373 | .mdiodrv.driver = { | |
374 | .name = "bcm53xx", | |
375 | .of_match_table = b53_of_match, | |
376 | }, | |
377 | }; | |
378 | ||
379 | static int __init b53_mdio_driver_register(void) | |
380 | { | |
381 | return mdio_driver_register(&b53_mdio_driver); | |
382 | } | |
383 | module_init(b53_mdio_driver_register); | |
384 | ||
385 | static void __exit b53_mdio_driver_unregister(void) | |
386 | { | |
387 | mdio_driver_unregister(&b53_mdio_driver); | |
388 | } | |
389 | module_exit(b53_mdio_driver_unregister); | |
390 | ||
391 | MODULE_DESCRIPTION("B53 MDIO access driver"); | |
392 | MODULE_LICENSE("Dual BSD/GPL"); |