1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Applied Micro X-Gene SoC MDIO Driver |
3 | * |
4 | * Copyright (c) 2016, Applied Micro Circuits Corporation |
5 | * Author: Iyappan Subramanian <isubramanian@apm.com> |
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/mdio/mdio-xgene.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/of_mdio.h> |
18 | #include <linux/of_net.h> |
19 | #include <linux/phy.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/prefetch.h> |
22 | #include <linux/property.h> |
23 | #include <net/ip.h> |
24 | |
25 | u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr) |
26 | { |
27 | void __iomem *addr, *rd, *cmd, *cmd_done; |
28 | u32 done, rd_data = BUSY_MASK; |
29 | u8 wait = 10; |
30 | |
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; |
35 | |
36 | spin_lock(lock: &pdata->mac_lock); |
37 | iowrite32(rd_addr, addr); |
38 | iowrite32(XGENE_ENET_RD_CMD, cmd); |
39 | |
40 | while (!(done = ioread32(cmd_done)) && wait--) |
41 | udelay(1); |
42 | |
43 | if (done) |
44 | rd_data = ioread32(rd); |
45 | |
46 | iowrite32(0, cmd); |
47 | spin_unlock(lock: &pdata->mac_lock); |
48 | |
49 | return rd_data; |
50 | } |
51 | EXPORT_SYMBOL(xgene_mdio_rd_mac); |
52 | |
53 | void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data) |
54 | { |
55 | void __iomem *addr, *wr, *cmd, *cmd_done; |
56 | u8 wait = 10; |
57 | u32 done; |
58 | |
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; |
63 | |
64 | spin_lock(lock: &pdata->mac_lock); |
65 | iowrite32(wr_addr, addr); |
66 | iowrite32(data, wr); |
67 | iowrite32(XGENE_ENET_WR_CMD, cmd); |
68 | |
69 | while (!(done = ioread32(cmd_done)) && wait--) |
70 | udelay(1); |
71 | |
72 | if (!done) |
73 | pr_err("MCX mac write failed, addr: 0x%04x\n" , wr_addr); |
74 | |
75 | iowrite32(0, cmd); |
76 | spin_unlock(lock: &pdata->mac_lock); |
77 | } |
78 | EXPORT_SYMBOL(xgene_mdio_wr_mac); |
79 | |
80 | int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg) |
81 | { |
82 | struct xgene_mdio_pdata *pdata = bus->priv; |
83 | u32 data, done; |
84 | u8 wait = 10; |
85 | |
86 | data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); |
87 | xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, data); |
88 | xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK); |
89 | do { |
90 | usleep_range(min: 5, max: 10); |
91 | done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); |
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 | |
99 | data = xgene_mdio_rd_mac(pdata, MII_MGMT_STATUS_ADDR); |
100 | xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, 0); |
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 | { |
108 | struct xgene_mdio_pdata *pdata = bus->priv; |
109 | u32 val, done; |
110 | u8 wait = 10; |
111 | |
112 | val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); |
113 | xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, val); |
114 | |
115 | xgene_mdio_wr_mac(pdata, MII_MGMT_CONTROL_ADDR, data); |
116 | do { |
117 | usleep_range(min: 5, max: 10); |
118 | done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); |
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, val: 0x0); |
147 | do { |
148 | usleep_range(min: 100, max: 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 | { |
162 | xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, SOFT_RESET); |
163 | xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, 0); |
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(clk: pdata->clk); |
172 | udelay(5); |
173 | clk_disable_unprepare(clk: pdata->clk); |
174 | udelay(5); |
175 | clk_prepare_enable(clk: pdata->clk); |
176 | udelay(5); |
177 | } else { |
178 | #ifdef CONFIG_ACPI |
179 | acpi_evaluate_object(ACPI_HANDLE(pdata->dev), |
180 | pathname: "_RST" , NULL, NULL); |
181 | #endif |
182 | } |
183 | |
184 | ret = xgene_enet_ecc_init(pdata); |
185 | if (ret) { |
186 | if (pdata->dev->of_node) |
187 | clk_disable_unprepare(clk: pdata->clk); |
188 | return ret; |
189 | } |
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); |
220 | xgene_enet_wr_mdio_csr(base_addr: addr, MIIM_FIELD_ADDR, val); |
221 | |
222 | val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_WRITE); |
223 | xgene_enet_wr_mdio_csr(base_addr: addr, MIIM_COMMAND_ADDR, val); |
224 | |
225 | do { |
226 | usleep_range(min: 5, max: 10); |
227 | xgene_enet_rd_mdio_csr(base_addr: addr, MIIM_INDICATOR_ADDR, val: &status); |
228 | } while ((status & BUSY_MASK) && timeout--); |
229 | |
230 | xgene_enet_wr_mdio_csr(base_addr: addr, MIIM_COMMAND_ADDR, val: 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(base_addr: addr, MIIM_FIELD_ADDR, val); |
243 | |
244 | val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_READ); |
245 | xgene_enet_wr_mdio_csr(base_addr: addr, MIIM_COMMAND_ADDR, val); |
246 | |
247 | do { |
248 | usleep_range(min: 5, max: 10); |
249 | xgene_enet_rd_mdio_csr(base_addr: addr, MIIM_INDICATOR_ADDR, val: &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(base_addr: addr, MIIMRD_FIELD_ADDR, val: &data); |
258 | xgene_enet_wr_mdio_csr(base_addr: addr, MIIM_COMMAND_ADDR, val: 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, addr: phy_addr, is_c45: false); |
268 | if (!phy_dev || IS_ERR(ptr: phy_dev)) |
269 | return NULL; |
270 | |
271 | if (phy_device_register(phy: phy_dev)) |
272 | phy_device_free(phydev: 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 | adev = acpi_fetch_acpi_dev(handle); |
289 | if (!adev) |
290 | return AE_OK; |
291 | |
292 | if (acpi_dev_get_property(adev, name: "phy-channel" , ACPI_TYPE_INTEGER, obj: &obj)) |
293 | return AE_OK; |
294 | phy_addr = obj->integer.value; |
295 | |
296 | phy_dev = xgene_enet_phy_register(mdio, phy_addr); |
297 | adev->driver_data = phy_dev; |
298 | |
299 | return AE_OK; |
300 | } |
301 | #endif |
302 | |
303 | static const struct of_device_id xgene_mdio_of_match[] = { |
304 | { |
305 | .compatible = "apm,xgene-mdio-rgmii" , |
306 | .data = (void *)XGENE_MDIO_RGMII |
307 | }, |
308 | { |
309 | .compatible = "apm,xgene-mdio-xfi" , |
310 | .data = (void *)XGENE_MDIO_XFI |
311 | }, |
312 | {}, |
313 | }; |
314 | MODULE_DEVICE_TABLE(of, xgene_mdio_of_match); |
315 | |
316 | #ifdef CONFIG_ACPI |
317 | static const struct acpi_device_id xgene_mdio_acpi_match[] = { |
318 | { "APMC0D65" , XGENE_MDIO_RGMII }, |
319 | { "APMC0D66" , XGENE_MDIO_XFI }, |
320 | { } |
321 | }; |
322 | |
323 | MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match); |
324 | #endif |
325 | |
326 | |
327 | static int xgene_mdio_probe(struct platform_device *pdev) |
328 | { |
329 | struct device *dev = &pdev->dev; |
330 | struct mii_bus *mdio_bus; |
331 | struct xgene_mdio_pdata *pdata; |
332 | void __iomem *csr_base; |
333 | int mdio_id = 0, ret = 0; |
334 | |
335 | mdio_id = (uintptr_t)device_get_match_data(dev: &pdev->dev); |
336 | if (!mdio_id) |
337 | return -ENODEV; |
338 | |
339 | pdata = devm_kzalloc(dev, size: sizeof(struct xgene_mdio_pdata), GFP_KERNEL); |
340 | if (!pdata) |
341 | return -ENOMEM; |
342 | pdata->mdio_id = mdio_id; |
343 | pdata->dev = dev; |
344 | |
345 | csr_base = devm_platform_ioremap_resource(pdev, index: 0); |
346 | if (IS_ERR(ptr: csr_base)) |
347 | return PTR_ERR(ptr: csr_base); |
348 | pdata->mac_csr_addr = csr_base; |
349 | pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET; |
350 | pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET; |
351 | |
352 | if (mdio_id == XGENE_MDIO_RGMII) |
353 | spin_lock_init(&pdata->mac_lock); |
354 | |
355 | if (dev->of_node) { |
356 | pdata->clk = devm_clk_get(dev, NULL); |
357 | if (IS_ERR(ptr: pdata->clk)) { |
358 | dev_err(dev, "Unable to retrieve clk\n" ); |
359 | return PTR_ERR(ptr: pdata->clk); |
360 | } |
361 | } |
362 | |
363 | ret = xgene_mdio_reset(pdata); |
364 | if (ret) |
365 | return ret; |
366 | |
367 | mdio_bus = mdiobus_alloc(); |
368 | if (!mdio_bus) { |
369 | ret = -ENOMEM; |
370 | goto out_clk; |
371 | } |
372 | |
373 | mdio_bus->name = "APM X-Gene MDIO bus" ; |
374 | |
375 | if (mdio_id == XGENE_MDIO_RGMII) { |
376 | mdio_bus->read = xgene_mdio_rgmii_read; |
377 | mdio_bus->write = xgene_mdio_rgmii_write; |
378 | mdio_bus->priv = (void __force *)pdata; |
379 | snprintf(buf: mdio_bus->id, MII_BUS_ID_SIZE, fmt: "%s" , |
380 | "xgene-mii-rgmii" ); |
381 | } else { |
382 | mdio_bus->read = xgene_xfi_mdio_read; |
383 | mdio_bus->write = xgene_xfi_mdio_write; |
384 | mdio_bus->priv = (void __force *)pdata->mdio_csr_addr; |
385 | snprintf(buf: mdio_bus->id, MII_BUS_ID_SIZE, fmt: "%s" , |
386 | "xgene-mii-xfi" ); |
387 | } |
388 | |
389 | mdio_bus->parent = dev; |
390 | platform_set_drvdata(pdev, data: pdata); |
391 | |
392 | if (dev->of_node) { |
393 | ret = of_mdiobus_register(mdio: mdio_bus, np: dev->of_node); |
394 | } else { |
395 | #ifdef CONFIG_ACPI |
396 | /* Mask out all PHYs from auto probing. */ |
397 | mdio_bus->phy_mask = ~0; |
398 | ret = mdiobus_register(mdio_bus); |
399 | if (ret) |
400 | goto out_mdiobus; |
401 | |
402 | acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), max_depth: 1, |
403 | descending_callback: acpi_register_phy, NULL, context: mdio_bus, NULL); |
404 | #endif |
405 | } |
406 | |
407 | if (ret) |
408 | goto out_mdiobus; |
409 | |
410 | pdata->mdio_bus = mdio_bus; |
411 | |
412 | return 0; |
413 | |
414 | out_mdiobus: |
415 | mdiobus_free(bus: mdio_bus); |
416 | |
417 | out_clk: |
418 | if (dev->of_node) |
419 | clk_disable_unprepare(clk: pdata->clk); |
420 | |
421 | return ret; |
422 | } |
423 | |
424 | static void xgene_mdio_remove(struct platform_device *pdev) |
425 | { |
426 | struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev); |
427 | struct mii_bus *mdio_bus = pdata->mdio_bus; |
428 | struct device *dev = &pdev->dev; |
429 | |
430 | mdiobus_unregister(bus: mdio_bus); |
431 | mdiobus_free(bus: mdio_bus); |
432 | |
433 | if (dev->of_node) |
434 | clk_disable_unprepare(clk: pdata->clk); |
435 | } |
436 | |
437 | static struct platform_driver xgene_mdio_driver = { |
438 | .driver = { |
439 | .name = "xgene-mdio" , |
440 | .of_match_table = xgene_mdio_of_match, |
441 | .acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match), |
442 | }, |
443 | .probe = xgene_mdio_probe, |
444 | .remove_new = xgene_mdio_remove, |
445 | }; |
446 | |
447 | module_platform_driver(xgene_mdio_driver); |
448 | |
449 | MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver" ); |
450 | MODULE_AUTHOR("Iyappan Subramanian <isubramanian@apm.com>" ); |
451 | MODULE_LICENSE("GPL" ); |
452 | |