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