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