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 | ||
37 | static u32 xgene_enet_rd_mac(void __iomem *base_addr, u32 rd_addr) | |
38 | { | |
39 | void __iomem *addr, *rd, *cmd, *cmd_done; | |
40 | u32 done, rd_data = BUSY_MASK; | |
41 | u8 wait = 10; | |
42 | ||
43 | addr = base_addr + MAC_ADDR_REG_OFFSET; | |
44 | rd = base_addr + MAC_READ_REG_OFFSET; | |
45 | cmd = base_addr + MAC_COMMAND_REG_OFFSET; | |
46 | cmd_done = base_addr + MAC_COMMAND_DONE_REG_OFFSET; | |
47 | ||
48 | iowrite32(rd_addr, addr); | |
49 | iowrite32(XGENE_ENET_RD_CMD, cmd); | |
50 | ||
51 | while (wait--) { | |
52 | done = ioread32(cmd_done); | |
53 | if (done) | |
54 | break; | |
55 | udelay(1); | |
56 | } | |
57 | ||
58 | if (!done) | |
59 | return rd_data; | |
60 | ||
61 | rd_data = ioread32(rd); | |
62 | iowrite32(0, cmd); | |
63 | ||
64 | return rd_data; | |
65 | } | |
66 | ||
67 | static void xgene_enet_wr_mac(void __iomem *base_addr, u32 wr_addr, u32 wr_data) | |
68 | { | |
69 | void __iomem *addr, *wr, *cmd, *cmd_done; | |
70 | u8 wait = 10; | |
71 | u32 done; | |
72 | ||
73 | addr = base_addr + MAC_ADDR_REG_OFFSET; | |
74 | wr = base_addr + MAC_WRITE_REG_OFFSET; | |
75 | cmd = base_addr + MAC_COMMAND_REG_OFFSET; | |
76 | cmd_done = base_addr + MAC_COMMAND_DONE_REG_OFFSET; | |
77 | ||
78 | iowrite32(wr_addr, addr); | |
79 | iowrite32(wr_data, wr); | |
80 | iowrite32(XGENE_ENET_WR_CMD, cmd); | |
81 | ||
82 | while (wait--) { | |
83 | done = ioread32(cmd_done); | |
84 | if (done) | |
85 | break; | |
86 | udelay(1); | |
87 | } | |
88 | ||
89 | if (!done) | |
90 | pr_err("MCX mac write failed, addr: 0x%04x\n", wr_addr); | |
91 | ||
92 | iowrite32(0, cmd); | |
93 | } | |
94 | ||
95 | int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg) | |
96 | { | |
97 | void __iomem *addr = (void __iomem *)bus->priv; | |
98 | u32 data, done; | |
99 | u8 wait = 10; | |
100 | ||
101 | data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); | |
102 | xgene_enet_wr_mac(addr, MII_MGMT_ADDRESS_ADDR, data); | |
103 | xgene_enet_wr_mac(addr, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK); | |
104 | do { | |
105 | usleep_range(5, 10); | |
106 | done = xgene_enet_rd_mac(addr, MII_MGMT_INDICATORS_ADDR); | |
107 | } while ((done & BUSY_MASK) && wait--); | |
108 | ||
109 | if (done & BUSY_MASK) { | |
110 | dev_err(&bus->dev, "MII_MGMT read failed\n"); | |
111 | return -EBUSY; | |
112 | } | |
113 | ||
114 | data = xgene_enet_rd_mac(addr, MII_MGMT_STATUS_ADDR); | |
115 | xgene_enet_wr_mac(addr, MII_MGMT_COMMAND_ADDR, 0); | |
116 | ||
117 | return data; | |
118 | } | |
119 | EXPORT_SYMBOL(xgene_mdio_rgmii_read); | |
120 | ||
121 | int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data) | |
122 | { | |
123 | void __iomem *addr = (void __iomem *)bus->priv; | |
124 | u32 val, done; | |
125 | u8 wait = 10; | |
126 | ||
127 | val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); | |
128 | xgene_enet_wr_mac(addr, MII_MGMT_ADDRESS_ADDR, val); | |
129 | ||
130 | xgene_enet_wr_mac(addr, MII_MGMT_CONTROL_ADDR, data); | |
131 | do { | |
132 | usleep_range(5, 10); | |
133 | done = xgene_enet_rd_mac(addr, MII_MGMT_INDICATORS_ADDR); | |
134 | } while ((done & BUSY_MASK) && wait--); | |
135 | ||
136 | if (done & BUSY_MASK) { | |
137 | dev_err(&bus->dev, "MII_MGMT write failed\n"); | |
138 | return -EBUSY; | |
139 | } | |
140 | ||
141 | return 0; | |
142 | } | |
143 | EXPORT_SYMBOL(xgene_mdio_rgmii_write); | |
144 | ||
145 | static u32 xgene_menet_rd_diag_csr(struct xgene_mdio_pdata *pdata, u32 offset) | |
146 | { | |
147 | return ioread32(pdata->diag_csr_addr + offset); | |
148 | } | |
149 | ||
150 | static void xgene_menet_wr_diag_csr(struct xgene_mdio_pdata *pdata, | |
151 | u32 offset, u32 val) | |
152 | { | |
153 | iowrite32(val, pdata->diag_csr_addr + offset); | |
154 | } | |
155 | ||
156 | static int xgene_enet_ecc_init(struct xgene_mdio_pdata *pdata) | |
157 | { | |
158 | u32 data; | |
159 | u8 wait = 10; | |
160 | ||
161 | xgene_menet_wr_diag_csr(pdata, MENET_CFG_MEM_RAM_SHUTDOWN_ADDR, 0x0); | |
162 | do { | |
163 | usleep_range(100, 110); | |
164 | data = xgene_menet_rd_diag_csr(pdata, MENET_BLOCK_MEM_RDY_ADDR); | |
165 | } while ((data != 0xffffffff) && wait--); | |
166 | ||
167 | if (data != 0xffffffff) { | |
168 | dev_err(pdata->dev, "Failed to release memory from shutdown\n"); | |
169 | return -ENODEV; | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static void xgene_gmac_reset(struct xgene_mdio_pdata *pdata) | |
176 | { | |
177 | xgene_enet_wr_mac(pdata->mac_csr_addr, MAC_CONFIG_1_ADDR, SOFT_RESET); | |
178 | xgene_enet_wr_mac(pdata->mac_csr_addr, MAC_CONFIG_1_ADDR, 0); | |
179 | } | |
180 | ||
181 | static int xgene_mdio_reset(struct xgene_mdio_pdata *pdata) | |
182 | { | |
183 | int ret; | |
184 | ||
185 | if (pdata->dev->of_node) { | |
186 | clk_prepare_enable(pdata->clk); | |
187 | udelay(5); | |
188 | clk_disable_unprepare(pdata->clk); | |
189 | udelay(5); | |
190 | clk_prepare_enable(pdata->clk); | |
191 | udelay(5); | |
192 | } else { | |
193 | #ifdef CONFIG_ACPI | |
194 | acpi_evaluate_object(ACPI_HANDLE(pdata->dev), | |
195 | "_RST", NULL, NULL); | |
196 | #endif | |
197 | } | |
198 | ||
199 | ret = xgene_enet_ecc_init(pdata); | |
200 | if (ret) | |
201 | return ret; | |
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); | |
232 | xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, data); | |
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 | ||
314 | static int xgene_mdio_probe(struct platform_device *pdev) | |
315 | { | |
316 | struct device *dev = &pdev->dev; | |
317 | struct mii_bus *mdio_bus; | |
318 | const struct of_device_id *of_id; | |
319 | struct resource *res; | |
320 | struct xgene_mdio_pdata *pdata; | |
321 | void __iomem *csr_base; | |
322 | int mdio_id = 0, ret = 0; | |
323 | ||
324 | of_id = of_match_device(xgene_mdio_of_match, &pdev->dev); | |
325 | if (of_id) { | |
326 | mdio_id = (enum xgene_mdio_id)of_id->data; | |
327 | } else { | |
328 | #ifdef CONFIG_ACPI | |
329 | const struct acpi_device_id *acpi_id; | |
330 | ||
331 | acpi_id = acpi_match_device(xgene_mdio_acpi_match, &pdev->dev); | |
332 | if (acpi_id) | |
333 | mdio_id = (enum xgene_mdio_id)acpi_id->driver_data; | |
334 | #endif | |
335 | } | |
336 | ||
337 | if (!mdio_id) | |
338 | return -ENODEV; | |
339 | ||
340 | pdata = devm_kzalloc(dev, sizeof(struct xgene_mdio_pdata), GFP_KERNEL); | |
341 | if (!pdata) | |
342 | return -ENOMEM; | |
343 | pdata->mdio_id = mdio_id; | |
344 | pdata->dev = dev; | |
345 | ||
346 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
347 | csr_base = devm_ioremap_resource(dev, res); | |
b2df430b | 348 | if (IS_ERR(csr_base)) |
43b3cf66 | 349 | return PTR_ERR(csr_base); |
43b3cf66 IS |
350 | pdata->mac_csr_addr = csr_base; |
351 | pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET; | |
352 | pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET; | |
353 | ||
354 | if (dev->of_node) { | |
355 | pdata->clk = devm_clk_get(dev, NULL); | |
356 | if (IS_ERR(pdata->clk)) { | |
357 | dev_err(dev, "Unable to retrieve clk\n"); | |
358 | return PTR_ERR(pdata->clk); | |
359 | } | |
360 | } | |
361 | ||
362 | ret = xgene_mdio_reset(pdata); | |
363 | if (ret) | |
364 | return ret; | |
365 | ||
366 | mdio_bus = mdiobus_alloc(); | |
367 | if (!mdio_bus) | |
368 | return -ENOMEM; | |
369 | ||
370 | mdio_bus->name = "APM X-Gene MDIO bus"; | |
371 | ||
372 | if (mdio_id == XGENE_MDIO_RGMII) { | |
373 | mdio_bus->read = xgene_mdio_rgmii_read; | |
374 | mdio_bus->write = xgene_mdio_rgmii_write; | |
375 | mdio_bus->priv = (void __force *)pdata->mac_csr_addr; | |
376 | snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", | |
377 | "xgene-mii-rgmii"); | |
378 | } else { | |
379 | mdio_bus->read = xgene_xfi_mdio_read; | |
380 | mdio_bus->write = xgene_xfi_mdio_write; | |
381 | mdio_bus->priv = (void __force *)pdata->mdio_csr_addr; | |
382 | snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", | |
383 | "xgene-mii-xfi"); | |
384 | } | |
385 | ||
386 | mdio_bus->parent = dev; | |
387 | platform_set_drvdata(pdev, pdata); | |
388 | ||
389 | if (dev->of_node) { | |
390 | ret = of_mdiobus_register(mdio_bus, dev->of_node); | |
391 | } else { | |
392 | #ifdef CONFIG_ACPI | |
393 | /* Mask out all PHYs from auto probing. */ | |
394 | mdio_bus->phy_mask = ~0; | |
395 | ret = mdiobus_register(mdio_bus); | |
396 | if (ret) | |
397 | goto out; | |
398 | ||
399 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), 1, | |
400 | acpi_register_phy, NULL, mdio_bus, NULL); | |
401 | #endif | |
402 | } | |
403 | ||
404 | if (ret) | |
405 | goto out; | |
406 | ||
407 | pdata->mdio_bus = mdio_bus; | |
408 | xgene_mdio_status = true; | |
409 | ||
410 | return 0; | |
411 | ||
412 | out: | |
413 | mdiobus_free(mdio_bus); | |
414 | ||
415 | return ret; | |
416 | } | |
417 | ||
418 | static int xgene_mdio_remove(struct platform_device *pdev) | |
419 | { | |
420 | struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev); | |
421 | struct mii_bus *mdio_bus = pdata->mdio_bus; | |
422 | struct device *dev = &pdev->dev; | |
423 | ||
424 | mdiobus_unregister(mdio_bus); | |
425 | mdiobus_free(mdio_bus); | |
426 | ||
427 | if (dev->of_node) { | |
428 | if (IS_ERR(pdata->clk)) | |
429 | clk_disable_unprepare(pdata->clk); | |
430 | } | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
435 | #ifdef CONFIG_OF | |
436 | static const struct of_device_id xgene_mdio_of_match[] = { | |
437 | { | |
438 | .compatible = "apm,xgene-mdio-rgmii", | |
439 | .data = (void *)XGENE_MDIO_RGMII | |
440 | }, | |
441 | { | |
442 | .compatible = "apm,xgene-mdio-xfi", | |
443 | .data = (void *)XGENE_MDIO_XFI | |
444 | }, | |
445 | {}, | |
446 | }; | |
447 | ||
448 | MODULE_DEVICE_TABLE(of, xgene_mdio_of_match); | |
449 | #endif | |
450 | ||
451 | #ifdef CONFIG_ACPI | |
452 | static const struct acpi_device_id xgene_mdio_acpi_match[] = { | |
453 | { "APMC0D65", XGENE_MDIO_RGMII }, | |
454 | { "APMC0D66", XGENE_MDIO_XFI }, | |
455 | { } | |
456 | }; | |
457 | ||
458 | MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match); | |
459 | #endif | |
460 | ||
461 | static struct platform_driver xgene_mdio_driver = { | |
462 | .driver = { | |
463 | .name = "xgene-mdio", | |
464 | .of_match_table = of_match_ptr(xgene_mdio_of_match), | |
465 | .acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match), | |
466 | }, | |
467 | .probe = xgene_mdio_probe, | |
468 | .remove = xgene_mdio_remove, | |
469 | }; | |
470 | ||
471 | module_platform_driver(xgene_mdio_driver); | |
472 | ||
473 | MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver"); | |
474 | MODULE_AUTHOR("Iyappan Subramanian <isubramanian@apm.com>"); | |
475 | MODULE_LICENSE("GPL"); |