OpenWrt – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Atheros AR71xx built-in ethernet mac driver |
||
3 | * |
||
4 | * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> |
||
5 | * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> |
||
6 | * |
||
7 | * Based on Atheros' AG7100 driver |
||
8 | * |
||
9 | * This program is free software; you can redistribute it and/or modify it |
||
10 | * under the terms of the GNU General Public License version 2 as published |
||
11 | * by the Free Software Foundation. |
||
12 | */ |
||
13 | |||
14 | #include "ag71xx.h" |
||
15 | |||
16 | #define AG71XX_MDIO_RETRY 1000 |
||
17 | #define AG71XX_MDIO_DELAY 5 |
||
18 | |||
19 | static inline void ag71xx_mdio_wr(struct ag71xx_mdio *am, unsigned reg, |
||
20 | u32 value) |
||
21 | { |
||
22 | void __iomem *r; |
||
23 | |||
24 | r = am->mdio_base + reg; |
||
25 | __raw_writel(value, r); |
||
26 | |||
27 | /* flush write */ |
||
28 | (void) __raw_readl(r); |
||
29 | } |
||
30 | |||
31 | static inline u32 ag71xx_mdio_rr(struct ag71xx_mdio *am, unsigned reg) |
||
32 | { |
||
33 | return __raw_readl(am->mdio_base + reg); |
||
34 | } |
||
35 | |||
36 | static void ag71xx_mdio_dump_regs(struct ag71xx_mdio *am) |
||
37 | { |
||
38 | DBG("%s: mii_cfg=%08x, mii_cmd=%08x, mii_addr=%08x\n", |
||
39 | am->mii_bus->name, |
||
40 | ag71xx_mdio_rr(am, AG71XX_REG_MII_CFG), |
||
41 | ag71xx_mdio_rr(am, AG71XX_REG_MII_CMD), |
||
42 | ag71xx_mdio_rr(am, AG71XX_REG_MII_ADDR)); |
||
43 | DBG("%s: mii_ctrl=%08x, mii_status=%08x, mii_ind=%08x\n", |
||
44 | am->mii_bus->name, |
||
45 | ag71xx_mdio_rr(am, AG71XX_REG_MII_CTRL), |
||
46 | ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS), |
||
47 | ag71xx_mdio_rr(am, AG71XX_REG_MII_IND)); |
||
48 | } |
||
49 | |||
50 | static int ag71xx_mdio_wait_busy(struct ag71xx_mdio *am) |
||
51 | { |
||
52 | int i; |
||
53 | |||
54 | for (i = 0; i < AG71XX_MDIO_RETRY; i++) { |
||
55 | u32 busy; |
||
56 | |||
57 | udelay(AG71XX_MDIO_DELAY); |
||
58 | |||
59 | busy = ag71xx_mdio_rr(am, AG71XX_REG_MII_IND); |
||
60 | if (!busy) |
||
61 | return 0; |
||
62 | |||
63 | udelay(AG71XX_MDIO_DELAY); |
||
64 | } |
||
65 | |||
66 | pr_err("%s: MDIO operation timed out\n", am->mii_bus->name); |
||
67 | |||
68 | return -ETIMEDOUT; |
||
69 | } |
||
70 | |||
71 | int ag71xx_mdio_mii_read(struct ag71xx_mdio *am, int addr, int reg) |
||
72 | { |
||
73 | int err; |
||
74 | int ret; |
||
75 | |||
76 | err = ag71xx_mdio_wait_busy(am); |
||
77 | if (err) |
||
78 | return 0xffff; |
||
79 | |||
80 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); |
||
81 | ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, |
||
82 | ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); |
||
83 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_READ); |
||
84 | |||
85 | err = ag71xx_mdio_wait_busy(am); |
||
86 | if (err) |
||
87 | return 0xffff; |
||
88 | |||
89 | ret = ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS) & 0xffff; |
||
90 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); |
||
91 | |||
92 | DBG("mii_read: addr=%04x, reg=%04x, value=%04x\n", addr, reg, ret); |
||
93 | |||
94 | return ret; |
||
95 | } |
||
96 | |||
97 | void ag71xx_mdio_mii_write(struct ag71xx_mdio *am, int addr, int reg, u16 val) |
||
98 | { |
||
99 | DBG("mii_write: addr=%04x, reg=%04x, value=%04x\n", addr, reg, val); |
||
100 | |||
101 | ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, |
||
102 | ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); |
||
103 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CTRL, val); |
||
104 | |||
105 | ag71xx_mdio_wait_busy(am); |
||
106 | } |
||
107 | |||
108 | static const u32 ar71xx_mdio_div_table[] = { |
||
109 | 4, 4, 6, 8, 10, 14, 20, 28, |
||
110 | }; |
||
111 | |||
112 | static const u32 ar7240_mdio_div_table[] = { |
||
113 | 2, 2, 4, 6, 8, 12, 18, 26, 32, 40, 48, 56, 62, 70, 78, 96, |
||
114 | }; |
||
115 | |||
116 | static const u32 ar933x_mdio_div_table[] = { |
||
117 | 4, 4, 6, 8, 10, 14, 20, 28, 34, 42, 50, 58, 66, 74, 82, 98, |
||
118 | }; |
||
119 | |||
120 | static int ag71xx_mdio_get_divider(struct ag71xx_mdio *am, u32 *div) |
||
121 | { |
||
122 | unsigned long ref_clock, mdio_clock; |
||
123 | const u32 *table; |
||
124 | int ndivs; |
||
125 | int i; |
||
126 | |||
127 | ref_clock = am->pdata->ref_clock; |
||
128 | mdio_clock = am->pdata->mdio_clock; |
||
129 | |||
130 | if (!ref_clock || !mdio_clock) |
||
131 | return -EINVAL; |
||
132 | |||
133 | if (am->pdata->is_ar9330 || am->pdata->is_ar934x) { |
||
134 | table = ar933x_mdio_div_table; |
||
135 | ndivs = ARRAY_SIZE(ar933x_mdio_div_table); |
||
136 | } else if (am->pdata->is_ar7240) { |
||
137 | table = ar7240_mdio_div_table; |
||
138 | ndivs = ARRAY_SIZE(ar7240_mdio_div_table); |
||
139 | } else { |
||
140 | table = ar71xx_mdio_div_table; |
||
141 | ndivs = ARRAY_SIZE(ar71xx_mdio_div_table); |
||
142 | } |
||
143 | |||
144 | for (i = 0; i < ndivs; i++) { |
||
145 | unsigned long t; |
||
146 | |||
147 | t = ref_clock / table[i]; |
||
148 | if (t <= mdio_clock) { |
||
149 | *div = i; |
||
150 | return 0; |
||
151 | } |
||
152 | } |
||
153 | |||
154 | dev_err(&am->mii_bus->dev, "no divider found for %lu/%lu\n", |
||
155 | ref_clock, mdio_clock); |
||
156 | return -ENOENT; |
||
157 | } |
||
158 | |||
159 | static int ag71xx_mdio_reset(struct mii_bus *bus) |
||
160 | { |
||
161 | struct ag71xx_mdio *am = bus->priv; |
||
162 | u32 t; |
||
163 | int err; |
||
164 | |||
165 | err = ag71xx_mdio_get_divider(am, &t); |
||
166 | if (err) { |
||
167 | /* fallback */ |
||
168 | if (am->pdata->is_ar7240) |
||
169 | t = MII_CFG_CLK_DIV_6; |
||
170 | else if (am->pdata->builtin_switch && !am->pdata->is_ar934x) |
||
171 | t = MII_CFG_CLK_DIV_10; |
||
172 | else if (!am->pdata->builtin_switch && am->pdata->is_ar934x) |
||
173 | t = MII_CFG_CLK_DIV_58; |
||
174 | else |
||
175 | t = MII_CFG_CLK_DIV_28; |
||
176 | } |
||
177 | |||
178 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t | MII_CFG_RESET); |
||
179 | udelay(100); |
||
180 | |||
181 | ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t); |
||
182 | udelay(100); |
||
183 | |||
184 | if (am->pdata->reset) |
||
185 | am->pdata->reset(bus); |
||
186 | |||
187 | return 0; |
||
188 | } |
||
189 | |||
190 | static int ag71xx_mdio_read(struct mii_bus *bus, int addr, int reg) |
||
191 | { |
||
192 | struct ag71xx_mdio *am = bus->priv; |
||
193 | |||
194 | if (am->pdata->builtin_switch) |
||
195 | return ar7240sw_phy_read(bus, addr, reg); |
||
196 | else |
||
197 | return ag71xx_mdio_mii_read(am, addr, reg); |
||
198 | } |
||
199 | |||
200 | static int ag71xx_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) |
||
201 | { |
||
202 | struct ag71xx_mdio *am = bus->priv; |
||
203 | |||
204 | if (am->pdata->builtin_switch) |
||
205 | ar7240sw_phy_write(bus, addr, reg, val); |
||
206 | else |
||
207 | ag71xx_mdio_mii_write(am, addr, reg, val); |
||
208 | return 0; |
||
209 | } |
||
210 | |||
211 | static int ag71xx_mdio_probe(struct platform_device *pdev) |
||
212 | { |
||
213 | struct ag71xx_mdio_platform_data *pdata; |
||
214 | struct ag71xx_mdio *am; |
||
215 | struct resource *res; |
||
216 | int i; |
||
217 | int err; |
||
218 | |||
219 | pdata = pdev->dev.platform_data; |
||
220 | if (!pdata) { |
||
221 | dev_err(&pdev->dev, "no platform data specified\n"); |
||
222 | return -EINVAL; |
||
223 | } |
||
224 | |||
225 | am = kzalloc(sizeof(*am), GFP_KERNEL); |
||
226 | if (!am) { |
||
227 | err = -ENOMEM; |
||
228 | goto err_out; |
||
229 | } |
||
230 | |||
231 | am->pdata = pdata; |
||
232 | |||
233 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
||
234 | if (!res) { |
||
235 | dev_err(&pdev->dev, "no iomem resource found\n"); |
||
236 | err = -ENXIO; |
||
237 | goto err_free_mdio; |
||
238 | } |
||
239 | |||
240 | am->mdio_base = ioremap_nocache(res->start, res->end - res->start + 1); |
||
241 | if (!am->mdio_base) { |
||
242 | dev_err(&pdev->dev, "unable to ioremap registers\n"); |
||
243 | err = -ENOMEM; |
||
244 | goto err_free_mdio; |
||
245 | } |
||
246 | |||
247 | am->mii_bus = mdiobus_alloc(); |
||
248 | if (am->mii_bus == NULL) { |
||
249 | err = -ENOMEM; |
||
250 | goto err_iounmap; |
||
251 | } |
||
252 | |||
253 | am->mii_bus->name = "ag71xx_mdio"; |
||
254 | am->mii_bus->read = ag71xx_mdio_read; |
||
255 | am->mii_bus->write = ag71xx_mdio_write; |
||
256 | am->mii_bus->reset = ag71xx_mdio_reset; |
||
257 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) |
||
258 | am->mii_bus->irq = am->mii_irq; |
||
259 | #endif |
||
260 | am->mii_bus->priv = am; |
||
261 | am->mii_bus->parent = &pdev->dev; |
||
262 | snprintf(am->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&pdev->dev)); |
||
263 | am->mii_bus->phy_mask = pdata->phy_mask; |
||
264 | |||
265 | for (i = 0; i < PHY_MAX_ADDR; i++) |
||
266 | am->mii_bus->irq[i] = PHY_POLL; |
||
267 | |||
268 | ag71xx_mdio_wr(am, AG71XX_REG_MAC_CFG1, 0); |
||
269 | |||
270 | err = mdiobus_register(am->mii_bus); |
||
271 | if (err) |
||
272 | goto err_free_bus; |
||
273 | |||
274 | ag71xx_mdio_dump_regs(am); |
||
275 | |||
276 | platform_set_drvdata(pdev, am); |
||
277 | return 0; |
||
278 | |||
279 | err_free_bus: |
||
280 | mdiobus_free(am->mii_bus); |
||
281 | err_iounmap: |
||
282 | iounmap(am->mdio_base); |
||
283 | err_free_mdio: |
||
284 | kfree(am); |
||
285 | err_out: |
||
286 | return err; |
||
287 | } |
||
288 | |||
289 | static int ag71xx_mdio_remove(struct platform_device *pdev) |
||
290 | { |
||
291 | struct ag71xx_mdio *am = platform_get_drvdata(pdev); |
||
292 | |||
293 | if (am) { |
||
294 | mdiobus_unregister(am->mii_bus); |
||
295 | mdiobus_free(am->mii_bus); |
||
296 | iounmap(am->mdio_base); |
||
297 | kfree(am); |
||
298 | platform_set_drvdata(pdev, NULL); |
||
299 | } |
||
300 | |||
301 | return 0; |
||
302 | } |
||
303 | |||
304 | static struct platform_driver ag71xx_mdio_driver = { |
||
305 | .probe = ag71xx_mdio_probe, |
||
306 | .remove = ag71xx_mdio_remove, |
||
307 | .driver = { |
||
308 | .name = "ag71xx-mdio", |
||
309 | } |
||
310 | }; |
||
311 | |||
312 | int __init ag71xx_mdio_driver_init(void) |
||
313 | { |
||
314 | return platform_driver_register(&ag71xx_mdio_driver); |
||
315 | } |
||
316 | |||
317 | void ag71xx_mdio_driver_exit(void) |
||
318 | { |
||
319 | platform_driver_unregister(&ag71xx_mdio_driver); |
||
320 | } |