OpenWrt – Blame information for rev 2
?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 <linux/clk.h> |
||
15 | #include <linux/of_mdio.h> |
||
16 | #include "ag71xx.h" |
||
17 | |||
18 | #define AG71XX_MDIO_RETRY 1000 |
||
19 | #define AG71XX_MDIO_DELAY 5 |
||
20 | |||
21 | static int bus_count; |
||
22 | |||
23 | static int ag71xx_mdio_wait_busy(struct ag71xx_mdio *am) |
||
24 | { |
||
25 | int i; |
||
26 | |||
27 | for (i = 0; i < AG71XX_MDIO_RETRY; i++) { |
||
28 | u32 busy; |
||
29 | |||
30 | udelay(AG71XX_MDIO_DELAY); |
||
31 | |||
32 | regmap_read(am->mii_regmap, AG71XX_REG_MII_IND, &busy); |
||
33 | if (!busy) |
||
34 | return 0; |
||
35 | |||
36 | udelay(AG71XX_MDIO_DELAY); |
||
37 | } |
||
38 | |||
39 | pr_err("%s: MDIO operation timed out\n", am->mii_bus->name); |
||
40 | |||
41 | return -ETIMEDOUT; |
||
42 | } |
||
43 | |||
44 | static int ag71xx_mdio_mii_read(struct mii_bus *bus, int addr, int reg) |
||
45 | { |
||
46 | struct ag71xx_mdio *am = bus->priv; |
||
47 | int err; |
||
48 | int ret; |
||
49 | |||
50 | err = ag71xx_mdio_wait_busy(am); |
||
51 | if (err) |
||
52 | return 0xffff; |
||
53 | |||
54 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CMD, MII_CMD_WRITE); |
||
55 | regmap_write(am->mii_regmap, AG71XX_REG_MII_ADDR, |
||
56 | ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); |
||
57 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CMD, MII_CMD_READ); |
||
58 | |||
59 | err = ag71xx_mdio_wait_busy(am); |
||
60 | if (err) |
||
61 | return 0xffff; |
||
62 | |||
63 | regmap_read(am->mii_regmap, AG71XX_REG_MII_STATUS, &ret); |
||
64 | ret &= 0xffff; |
||
65 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CMD, MII_CMD_WRITE); |
||
66 | |||
67 | DBG("mii_read: addr=%04x, reg=%04x, value=%04x\n", addr, reg, ret); |
||
68 | |||
69 | return ret; |
||
70 | } |
||
71 | |||
72 | static int ag71xx_mdio_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) |
||
73 | { |
||
74 | struct ag71xx_mdio *am = bus->priv; |
||
75 | |||
76 | DBG("mii_write: addr=%04x, reg=%04x, value=%04x\n", addr, reg, val); |
||
77 | |||
78 | regmap_write(am->mii_regmap, AG71XX_REG_MII_ADDR, |
||
79 | ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); |
||
80 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CTRL, val); |
||
81 | |||
82 | ag71xx_mdio_wait_busy(am); |
||
83 | |||
84 | return 0; |
||
85 | } |
||
86 | |||
87 | static const u32 ar71xx_mdio_div_table[] = { |
||
88 | 4, 4, 6, 8, 10, 14, 20, 28, |
||
89 | }; |
||
90 | |||
91 | static const u32 ar7240_mdio_div_table[] = { |
||
92 | 2, 2, 4, 6, 8, 12, 18, 26, 32, 40, 48, 56, 62, 70, 78, 96, |
||
93 | }; |
||
94 | |||
95 | static const u32 ar933x_mdio_div_table[] = { |
||
96 | 4, 4, 6, 8, 10, 14, 20, 28, 34, 42, 50, 58, 66, 74, 82, 98, |
||
97 | }; |
||
98 | |||
99 | static int ag71xx_mdio_get_divider(struct device_node *np, u32 *div) |
||
100 | { |
||
101 | struct clk *ref_clk = of_clk_get(np, 0); |
||
102 | unsigned long ref_clock; |
||
103 | u32 mdio_clock; |
||
104 | const u32 *table; |
||
105 | int ndivs, i; |
||
106 | |||
107 | if (IS_ERR(ref_clk)) |
||
108 | return -EINVAL; |
||
109 | |||
110 | ref_clock = clk_get_rate(ref_clk); |
||
111 | clk_put(ref_clk); |
||
112 | |||
113 | if(of_property_read_u32(np, "qca,mdio-max-frequency", &mdio_clock)) { |
||
114 | if (of_property_read_bool(np, "builtin-switch")) |
||
115 | mdio_clock = 5000000; |
||
116 | else |
||
117 | mdio_clock = 2000000; |
||
118 | } |
||
119 | |||
120 | if (of_device_is_compatible(np, "qca,ar9330-mdio") || |
||
121 | of_device_is_compatible(np, "qca,ar9340-mdio")) { |
||
122 | table = ar933x_mdio_div_table; |
||
123 | ndivs = ARRAY_SIZE(ar933x_mdio_div_table); |
||
124 | } else if (of_device_is_compatible(np, "qca,ar7240-mdio")) { |
||
125 | table = ar7240_mdio_div_table; |
||
126 | ndivs = ARRAY_SIZE(ar7240_mdio_div_table); |
||
127 | } else { |
||
128 | table = ar71xx_mdio_div_table; |
||
129 | ndivs = ARRAY_SIZE(ar71xx_mdio_div_table); |
||
130 | } |
||
131 | |||
132 | for (i = 0; i < ndivs; i++) { |
||
133 | unsigned long t; |
||
134 | |||
135 | t = ref_clock / table[i]; |
||
136 | if (t <= mdio_clock) { |
||
137 | *div = i; |
||
138 | return 0; |
||
139 | } |
||
140 | } |
||
141 | |||
142 | return -ENOENT; |
||
143 | } |
||
144 | |||
145 | static int ag71xx_mdio_reset(struct mii_bus *bus) |
||
146 | { |
||
147 | struct device_node *np = bus->dev.of_node; |
||
148 | struct ag71xx_mdio *am = bus->priv; |
||
149 | bool builtin_switch; |
||
150 | u32 t; |
||
151 | |||
152 | builtin_switch = of_property_read_bool(np, "builtin-switch"); |
||
153 | |||
154 | if (ag71xx_mdio_get_divider(np, &t)) { |
||
155 | if (of_device_is_compatible(np, "qca,ar9340-mdio")) |
||
156 | t = MII_CFG_CLK_DIV_58; |
||
157 | else if (builtin_switch) |
||
158 | t = MII_CFG_CLK_DIV_10; |
||
159 | else |
||
160 | t = MII_CFG_CLK_DIV_28; |
||
161 | } |
||
162 | |||
163 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CFG, t | MII_CFG_RESET); |
||
164 | udelay(100); |
||
165 | |||
166 | regmap_write(am->mii_regmap, AG71XX_REG_MII_CFG, t); |
||
167 | udelay(100); |
||
168 | |||
169 | return 0; |
||
170 | } |
||
171 | |||
172 | static int ag71xx_mdio_probe(struct platform_device *pdev) |
||
173 | { |
||
174 | struct device *amdev = &pdev->dev; |
||
175 | struct device_node *np = pdev->dev.of_node; |
||
176 | struct ag71xx_mdio *am; |
||
177 | struct mii_bus *mii_bus; |
||
178 | bool builtin_switch; |
||
179 | int i, err; |
||
180 | |||
181 | am = devm_kzalloc(amdev, sizeof(*am), GFP_KERNEL); |
||
182 | if (!am) |
||
183 | return -ENOMEM; |
||
184 | |||
185 | am->mii_regmap = syscon_regmap_lookup_by_phandle(np, "regmap"); |
||
186 | if (IS_ERR(am->mii_regmap)) |
||
187 | return PTR_ERR(am->mii_regmap); |
||
188 | |||
189 | mii_bus = devm_mdiobus_alloc(amdev); |
||
190 | if (!mii_bus) |
||
191 | return -ENOMEM; |
||
192 | |||
193 | am->mdio_reset = of_reset_control_get_exclusive(np, "mdio"); |
||
194 | builtin_switch = of_property_read_bool(np, "builtin-switch"); |
||
195 | |||
196 | mii_bus->name = "ag71xx_mdio"; |
||
197 | mii_bus->read = ag71xx_mdio_mii_read; |
||
198 | mii_bus->write = ag71xx_mdio_mii_write; |
||
199 | mii_bus->reset = ag71xx_mdio_reset; |
||
200 | mii_bus->priv = am; |
||
201 | mii_bus->parent = amdev; |
||
202 | snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s.%d", np->name, bus_count++); |
||
203 | |||
204 | if (!builtin_switch && |
||
205 | of_property_read_u32(np, "phy-mask", &mii_bus->phy_mask)) |
||
206 | mii_bus->phy_mask = 0; |
||
207 | |||
208 | for (i = 0; i < PHY_MAX_ADDR; i++) |
||
209 | mii_bus->irq[i] = PHY_POLL; |
||
210 | |||
211 | if (!IS_ERR(am->mdio_reset)) { |
||
212 | reset_control_assert(am->mdio_reset); |
||
213 | msleep(100); |
||
214 | reset_control_deassert(am->mdio_reset); |
||
215 | msleep(200); |
||
216 | } |
||
217 | |||
218 | err = of_mdiobus_register(mii_bus, np); |
||
219 | if (err) |
||
220 | return err; |
||
221 | |||
222 | am->mii_bus = mii_bus; |
||
223 | platform_set_drvdata(pdev, am); |
||
224 | |||
225 | return 0; |
||
226 | } |
||
227 | |||
228 | static int ag71xx_mdio_remove(struct platform_device *pdev) |
||
229 | { |
||
230 | struct ag71xx_mdio *am = platform_get_drvdata(pdev); |
||
231 | |||
232 | mdiobus_unregister(am->mii_bus); |
||
233 | return 0; |
||
234 | } |
||
235 | |||
236 | static const struct of_device_id ag71xx_mdio_match[] = { |
||
237 | { .compatible = "qca,ar7240-mdio" }, |
||
238 | { .compatible = "qca,ar9330-mdio" }, |
||
239 | { .compatible = "qca,ar9340-mdio" }, |
||
240 | { .compatible = "qca,ath79-mdio" }, |
||
241 | {} |
||
242 | }; |
||
243 | |||
244 | static struct platform_driver ag71xx_mdio_driver = { |
||
245 | .probe = ag71xx_mdio_probe, |
||
246 | .remove = ag71xx_mdio_remove, |
||
247 | .driver = { |
||
248 | .name = "ag71xx-mdio", |
||
249 | .of_match_table = ag71xx_mdio_match, |
||
250 | } |
||
251 | }; |
||
252 | |||
253 | module_platform_driver(ag71xx_mdio_driver); |
||
254 | MODULE_LICENSE("GPL"); |