]>
Commit | Line | Data |
---|---|---|
43b3cf66 IS |
1 | /* Applied Micro X-Gene SoC MDIO Driver |
2 | * | |
3 | * Copyright (c) 2016, Applied Micro Circuits Corporation | |
4 | * Author: Iyappan Subramanian <isubramanian@apm.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <linux/acpi.h> | |
21 | #include <linux/clk.h> | |
22 | #include <linux/device.h> | |
23 | #include <linux/efi.h> | |
24 | #include <linux/if_vlan.h> | |
25 | #include <linux/io.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/of_platform.h> | |
28 | #include <linux/of_net.h> | |
29 | #include <linux/of_mdio.h> | |
30 | #include <linux/prefetch.h> | |
31 | #include <linux/phy.h> | |
32 | #include <net/ip.h> | |
33 | #include "mdio-xgene.h" | |
34 | ||
35 | static bool xgene_mdio_status; | |
36 | ||
8ec7074a | 37 | u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr) |
43b3cf66 IS |
38 | { |
39 | void __iomem *addr, *rd, *cmd, *cmd_done; | |
40 | u32 done, rd_data = BUSY_MASK; | |
41 | u8 wait = 10; | |
42 | ||
8ec7074a QN |
43 | addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; |
44 | rd = pdata->mac_csr_addr + MAC_READ_REG_OFFSET; | |
45 | cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; | |
46 | cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; | |
43b3cf66 | 47 | |
8ec7074a | 48 | spin_lock(&pdata->mac_lock); |
43b3cf66 IS |
49 | iowrite32(rd_addr, addr); |
50 | iowrite32(XGENE_ENET_RD_CMD, cmd); | |
51 | ||
8ec7074a | 52 | while (!(done = ioread32(cmd_done)) && wait--) |
43b3cf66 | 53 | udelay(1); |
43b3cf66 | 54 | |
8ec7074a QN |
55 | if (done) |
56 | rd_data = ioread32(rd); | |
43b3cf66 | 57 | |
43b3cf66 | 58 | iowrite32(0, cmd); |
8ec7074a | 59 | spin_unlock(&pdata->mac_lock); |
43b3cf66 IS |
60 | |
61 | return rd_data; | |
62 | } | |
8ec7074a | 63 | EXPORT_SYMBOL(xgene_mdio_rd_mac); |
43b3cf66 | 64 | |
8ec7074a | 65 | void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data) |
43b3cf66 IS |
66 | { |
67 | void __iomem *addr, *wr, *cmd, *cmd_done; | |
68 | u8 wait = 10; | |
69 | u32 done; | |
70 | ||
8ec7074a QN |
71 | addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; |
72 | wr = pdata->mac_csr_addr + MAC_WRITE_REG_OFFSET; | |
73 | cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; | |
74 | cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; | |
43b3cf66 | 75 | |
8ec7074a | 76 | spin_lock(&pdata->mac_lock); |
43b3cf66 | 77 | iowrite32(wr_addr, addr); |
8ec7074a | 78 | iowrite32(data, wr); |
43b3cf66 IS |
79 | iowrite32(XGENE_ENET_WR_CMD, cmd); |
80 | ||
8ec7074a | 81 | while (!(done = ioread32(cmd_done)) && wait--) |
43b3cf66 | 82 | udelay(1); |
43b3cf66 IS |
83 | |
84 | if (!done) | |
85 | pr_err("MCX mac write failed, addr: 0x%04x\n", wr_addr); | |
86 | ||
87 | iowrite32(0, cmd); | |
8ec7074a | 88 | spin_unlock(&pdata->mac_lock); |
43b3cf66 | 89 | } |
8ec7074a | 90 | EXPORT_SYMBOL(xgene_mdio_wr_mac); |
43b3cf66 IS |
91 | |
92 | int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg) | |
93 | { | |
8ec7074a | 94 | struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; |
43b3cf66 IS |
95 | u32 data, done; |
96 | u8 wait = 10; | |
97 | ||
98 | data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); | |
8ec7074a QN |
99 | xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, data); |
100 | xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK); | |
43b3cf66 IS |
101 | do { |
102 | usleep_range(5, 10); | |
8ec7074a | 103 | done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); |
43b3cf66 IS |
104 | } while ((done & BUSY_MASK) && wait--); |
105 | ||
106 | if (done & BUSY_MASK) { | |
107 | dev_err(&bus->dev, "MII_MGMT read failed\n"); | |
108 | return -EBUSY; | |
109 | } | |
110 | ||
8ec7074a QN |
111 | data = xgene_mdio_rd_mac(pdata, MII_MGMT_STATUS_ADDR); |
112 | xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, 0); | |
43b3cf66 IS |
113 | |
114 | return data; | |
115 | } | |
116 | EXPORT_SYMBOL(xgene_mdio_rgmii_read); | |
117 | ||
118 | int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data) | |
119 | { | |
8ec7074a | 120 | struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; |
43b3cf66 IS |
121 | u32 val, done; |
122 | u8 wait = 10; | |
123 | ||
124 | val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); | |
8ec7074a | 125 | xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, val); |
43b3cf66 | 126 | |
8ec7074a | 127 | xgene_mdio_wr_mac(pdata, MII_MGMT_CONTROL_ADDR, data); |
43b3cf66 IS |
128 | do { |
129 | usleep_range(5, 10); | |
8ec7074a | 130 | done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); |
43b3cf66 IS |
131 | } while ((done & BUSY_MASK) && wait--); |
132 | ||
133 | if (done & BUSY_MASK) { | |
134 | dev_err(&bus->dev, "MII_MGMT write failed\n"); | |
135 | return -EBUSY; | |
136 | } | |
137 | ||
138 | return 0; | |
139 | } | |
140 | EXPORT_SYMBOL(xgene_mdio_rgmii_write); | |
141 | ||
142 | static u32 xgene_menet_rd_diag_csr(struct xgene_mdio_pdata *pdata, u32 offset) | |
143 | { | |
144 | return ioread32(pdata->diag_csr_addr + offset); | |
145 | } | |
146 | ||
147 | static void xgene_menet_wr_diag_csr(struct xgene_mdio_pdata *pdata, | |
148 | u32 offset, u32 val) | |
149 | { | |
150 | iowrite32(val, pdata->diag_csr_addr + offset); | |
151 | } | |
152 | ||
153 | static int xgene_enet_ecc_init(struct xgene_mdio_pdata *pdata) | |
154 | { | |
155 | u32 data; | |
156 | u8 wait = 10; | |
157 | ||
158 | xgene_menet_wr_diag_csr(pdata, MENET_CFG_MEM_RAM_SHUTDOWN_ADDR, 0x0); | |
159 | do { | |
160 | usleep_range(100, 110); | |
161 | data = xgene_menet_rd_diag_csr(pdata, MENET_BLOCK_MEM_RDY_ADDR); | |
162 | } while ((data != 0xffffffff) && wait--); | |
163 | ||
164 | if (data != 0xffffffff) { | |
165 | dev_err(pdata->dev, "Failed to release memory from shutdown\n"); | |
166 | return -ENODEV; | |
167 | } | |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
172 | static void xgene_gmac_reset(struct xgene_mdio_pdata *pdata) | |
173 | { | |
8ec7074a QN |
174 | xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, SOFT_RESET); |
175 | xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, 0); | |
43b3cf66 IS |
176 | } |
177 | ||
178 | static int xgene_mdio_reset(struct xgene_mdio_pdata *pdata) | |
179 | { | |
180 | int ret; | |
181 | ||
182 | if (pdata->dev->of_node) { | |
183 | clk_prepare_enable(pdata->clk); | |
184 | udelay(5); | |
185 | clk_disable_unprepare(pdata->clk); | |
186 | udelay(5); | |
187 | clk_prepare_enable(pdata->clk); | |
188 | udelay(5); | |
189 | } else { | |
190 | #ifdef CONFIG_ACPI | |
191 | acpi_evaluate_object(ACPI_HANDLE(pdata->dev), | |
192 | "_RST", NULL, NULL); | |
193 | #endif | |
194 | } | |
195 | ||
196 | ret = xgene_enet_ecc_init(pdata); | |
ab144360 AK |
197 | if (ret) { |
198 | if (pdata->dev->of_node) | |
199 | clk_disable_unprepare(pdata->clk); | |
43b3cf66 | 200 | return ret; |
ab144360 | 201 | } |
43b3cf66 IS |
202 | xgene_gmac_reset(pdata); |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static void xgene_enet_rd_mdio_csr(void __iomem *base_addr, | |
208 | u32 offset, u32 *val) | |
209 | { | |
210 | void __iomem *addr = base_addr + offset; | |
211 | ||
212 | *val = ioread32(addr); | |
213 | } | |
214 | ||
215 | static void xgene_enet_wr_mdio_csr(void __iomem *base_addr, | |
216 | u32 offset, u32 val) | |
217 | { | |
218 | void __iomem *addr = base_addr + offset; | |
219 | ||
220 | iowrite32(val, addr); | |
221 | } | |
222 | ||
223 | static int xgene_xfi_mdio_write(struct mii_bus *bus, int phy_id, | |
224 | int reg, u16 data) | |
225 | { | |
226 | void __iomem *addr = (void __iomem *)bus->priv; | |
227 | int timeout = 100; | |
228 | u32 status, val; | |
229 | ||
230 | val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg) | | |
231 | SET_VAL(HSTMIIMWRDAT, data); | |
4b72436d | 232 | xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); |
43b3cf66 IS |
233 | |
234 | val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_WRITE); | |
235 | xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); | |
236 | ||
237 | do { | |
238 | usleep_range(5, 10); | |
239 | xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); | |
240 | } while ((status & BUSY_MASK) && timeout--); | |
241 | ||
242 | xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | static int xgene_xfi_mdio_read(struct mii_bus *bus, int phy_id, int reg) | |
248 | { | |
249 | void __iomem *addr = (void __iomem *)bus->priv; | |
250 | u32 data, status, val; | |
251 | int timeout = 100; | |
252 | ||
253 | val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg); | |
254 | xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); | |
255 | ||
256 | val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_READ); | |
257 | xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); | |
258 | ||
259 | do { | |
260 | usleep_range(5, 10); | |
261 | xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); | |
262 | } while ((status & BUSY_MASK) && timeout--); | |
263 | ||
264 | if (status & BUSY_MASK) { | |
265 | pr_err("XGENET_MII_MGMT write failed\n"); | |
266 | return -EBUSY; | |
267 | } | |
268 | ||
269 | xgene_enet_rd_mdio_csr(addr, MIIMRD_FIELD_ADDR, &data); | |
270 | xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); | |
271 | ||
272 | return data; | |
273 | } | |
274 | ||
275 | struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr) | |
276 | { | |
277 | struct phy_device *phy_dev; | |
278 | ||
279 | phy_dev = get_phy_device(bus, phy_addr, false); | |
280 | if (!phy_dev || IS_ERR(phy_dev)) | |
281 | return NULL; | |
282 | ||
283 | if (phy_device_register(phy_dev)) | |
284 | phy_device_free(phy_dev); | |
285 | ||
286 | return phy_dev; | |
287 | } | |
288 | EXPORT_SYMBOL(xgene_enet_phy_register); | |
289 | ||
290 | #ifdef CONFIG_ACPI | |
291 | static acpi_status acpi_register_phy(acpi_handle handle, u32 lvl, | |
292 | void *context, void **ret) | |
293 | { | |
294 | struct mii_bus *mdio = context; | |
295 | struct acpi_device *adev; | |
296 | struct phy_device *phy_dev; | |
297 | const union acpi_object *obj; | |
298 | u32 phy_addr; | |
299 | ||
300 | if (acpi_bus_get_device(handle, &adev)) | |
301 | return AE_OK; | |
302 | ||
303 | if (acpi_dev_get_property(adev, "phy-channel", ACPI_TYPE_INTEGER, &obj)) | |
304 | return AE_OK; | |
305 | phy_addr = obj->integer.value; | |
306 | ||
307 | phy_dev = xgene_enet_phy_register(mdio, phy_addr); | |
308 | adev->driver_data = phy_dev; | |
309 | ||
310 | return AE_OK; | |
311 | } | |
312 | #endif | |
313 | ||
1f3d6209 AB |
314 | static const struct of_device_id xgene_mdio_of_match[] = { |
315 | { | |
316 | .compatible = "apm,xgene-mdio-rgmii", | |
317 | .data = (void *)XGENE_MDIO_RGMII | |
318 | }, | |
319 | { | |
320 | .compatible = "apm,xgene-mdio-xfi", | |
321 | .data = (void *)XGENE_MDIO_XFI | |
322 | }, | |
323 | {}, | |
324 | }; | |
325 | MODULE_DEVICE_TABLE(of, xgene_mdio_of_match); | |
326 | ||
327 | #ifdef CONFIG_ACPI | |
328 | static const struct acpi_device_id xgene_mdio_acpi_match[] = { | |
329 | { "APMC0D65", XGENE_MDIO_RGMII }, | |
330 | { "APMC0D66", XGENE_MDIO_XFI }, | |
331 | { } | |
332 | }; | |
333 | ||
334 | MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match); | |
335 | #endif | |
336 | ||
337 | ||
43b3cf66 IS |
338 | static int xgene_mdio_probe(struct platform_device *pdev) |
339 | { | |
340 | struct device *dev = &pdev->dev; | |
341 | struct mii_bus *mdio_bus; | |
342 | const struct of_device_id *of_id; | |
343 | struct resource *res; | |
344 | struct xgene_mdio_pdata *pdata; | |
345 | void __iomem *csr_base; | |
346 | int mdio_id = 0, ret = 0; | |
347 | ||
348 | of_id = of_match_device(xgene_mdio_of_match, &pdev->dev); | |
349 | if (of_id) { | |
350 | mdio_id = (enum xgene_mdio_id)of_id->data; | |
351 | } else { | |
352 | #ifdef CONFIG_ACPI | |
353 | const struct acpi_device_id *acpi_id; | |
354 | ||
355 | acpi_id = acpi_match_device(xgene_mdio_acpi_match, &pdev->dev); | |
356 | if (acpi_id) | |
357 | mdio_id = (enum xgene_mdio_id)acpi_id->driver_data; | |
358 | #endif | |
359 | } | |
360 | ||
361 | if (!mdio_id) | |
362 | return -ENODEV; | |
363 | ||
364 | pdata = devm_kzalloc(dev, sizeof(struct xgene_mdio_pdata), GFP_KERNEL); | |
365 | if (!pdata) | |
366 | return -ENOMEM; | |
367 | pdata->mdio_id = mdio_id; | |
368 | pdata->dev = dev; | |
369 | ||
370 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
371 | csr_base = devm_ioremap_resource(dev, res); | |
b2df430b | 372 | if (IS_ERR(csr_base)) |
43b3cf66 | 373 | return PTR_ERR(csr_base); |
43b3cf66 IS |
374 | pdata->mac_csr_addr = csr_base; |
375 | pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET; | |
376 | pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET; | |
377 | ||
8ec7074a QN |
378 | if (mdio_id == XGENE_MDIO_RGMII) |
379 | spin_lock_init(&pdata->mac_lock); | |
380 | ||
43b3cf66 IS |
381 | if (dev->of_node) { |
382 | pdata->clk = devm_clk_get(dev, NULL); | |
383 | if (IS_ERR(pdata->clk)) { | |
384 | dev_err(dev, "Unable to retrieve clk\n"); | |
385 | return PTR_ERR(pdata->clk); | |
386 | } | |
387 | } | |
388 | ||
389 | ret = xgene_mdio_reset(pdata); | |
390 | if (ret) | |
391 | return ret; | |
392 | ||
393 | mdio_bus = mdiobus_alloc(); | |
ab144360 AK |
394 | if (!mdio_bus) { |
395 | ret = -ENOMEM; | |
396 | goto out_clk; | |
397 | } | |
43b3cf66 IS |
398 | |
399 | mdio_bus->name = "APM X-Gene MDIO bus"; | |
400 | ||
401 | if (mdio_id == XGENE_MDIO_RGMII) { | |
402 | mdio_bus->read = xgene_mdio_rgmii_read; | |
403 | mdio_bus->write = xgene_mdio_rgmii_write; | |
8ec7074a | 404 | mdio_bus->priv = (void __force *)pdata; |
43b3cf66 IS |
405 | snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", |
406 | "xgene-mii-rgmii"); | |
407 | } else { | |
408 | mdio_bus->read = xgene_xfi_mdio_read; | |
409 | mdio_bus->write = xgene_xfi_mdio_write; | |
410 | mdio_bus->priv = (void __force *)pdata->mdio_csr_addr; | |
411 | snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", | |
412 | "xgene-mii-xfi"); | |
413 | } | |
414 | ||
415 | mdio_bus->parent = dev; | |
416 | platform_set_drvdata(pdev, pdata); | |
417 | ||
418 | if (dev->of_node) { | |
419 | ret = of_mdiobus_register(mdio_bus, dev->of_node); | |
420 | } else { | |
421 | #ifdef CONFIG_ACPI | |
422 | /* Mask out all PHYs from auto probing. */ | |
423 | mdio_bus->phy_mask = ~0; | |
424 | ret = mdiobus_register(mdio_bus); | |
425 | if (ret) | |
ab144360 | 426 | goto out_mdiobus; |
43b3cf66 IS |
427 | |
428 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), 1, | |
429 | acpi_register_phy, NULL, mdio_bus, NULL); | |
430 | #endif | |
431 | } | |
432 | ||
433 | if (ret) | |
ab144360 | 434 | goto out_mdiobus; |
43b3cf66 IS |
435 | |
436 | pdata->mdio_bus = mdio_bus; | |
437 | xgene_mdio_status = true; | |
438 | ||
439 | return 0; | |
440 | ||
ab144360 | 441 | out_mdiobus: |
43b3cf66 IS |
442 | mdiobus_free(mdio_bus); |
443 | ||
ab144360 AK |
444 | out_clk: |
445 | if (dev->of_node) | |
446 | clk_disable_unprepare(pdata->clk); | |
447 | ||
43b3cf66 IS |
448 | return ret; |
449 | } | |
450 | ||
451 | static int xgene_mdio_remove(struct platform_device *pdev) | |
452 | { | |
453 | struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev); | |
454 | struct mii_bus *mdio_bus = pdata->mdio_bus; | |
455 | struct device *dev = &pdev->dev; | |
456 | ||
457 | mdiobus_unregister(mdio_bus); | |
458 | mdiobus_free(mdio_bus); | |
459 | ||
440f895a CJ |
460 | if (dev->of_node) |
461 | clk_disable_unprepare(pdata->clk); | |
43b3cf66 IS |
462 | |
463 | return 0; | |
464 | } | |
465 | ||
43b3cf66 IS |
466 | static struct platform_driver xgene_mdio_driver = { |
467 | .driver = { | |
468 | .name = "xgene-mdio", | |
469 | .of_match_table = of_match_ptr(xgene_mdio_of_match), | |
470 | .acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match), | |
471 | }, | |
472 | .probe = xgene_mdio_probe, | |
473 | .remove = xgene_mdio_remove, | |
474 | }; | |
475 | ||
476 | module_platform_driver(xgene_mdio_driver); | |
477 | ||
478 | MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver"); | |
479 | MODULE_AUTHOR("Iyappan Subramanian <isubramanian@apm.com>"); | |
480 | MODULE_LICENSE("GPL"); |