1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Synopsys DesignWare XPCS platform device driver 4 * 5 * Copyright (C) 2024 Serge Semin 6 */ 7 8 #include <linux/atomic.h> 9 #include <linux/bitfield.h> 10 #include <linux/clk.h> 11 #include <linux/device.h> 12 #include <linux/kernel.h> 13 #include <linux/mdio.h> 14 #include <linux/module.h> 15 #include <linux/pcs/pcs-xpcs.h> 16 #include <linux/phy.h> 17 #include <linux/platform_device.h> 18 #include <linux/pm_runtime.h> 19 #include <linux/property.h> 20 #include <linux/sizes.h> 21 22 #include "pcs-xpcs.h" 23 24 /* Page select register for the indirect MMIO CSRs access */ 25 #define DW_VR_CSR_VIEWPORT 0xff 26 27 struct dw_xpcs_plat { 28 struct platform_device *pdev; 29 struct mii_bus *bus; 30 bool reg_indir; 31 int reg_width; 32 void __iomem *reg_base; 33 struct clk *cclk; 34 }; 35 36 static ptrdiff_t xpcs_mmio_addr_format(int dev, int reg) 37 { 38 return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg); 39 } 40 41 static u16 xpcs_mmio_addr_page(ptrdiff_t csr) 42 { 43 return FIELD_GET(0x1fff00, csr); 44 } 45 46 static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr) 47 { 48 return FIELD_GET(0xff, csr); 49 } 50 51 static int xpcs_mmio_read_reg_indirect(struct dw_xpcs_plat *pxpcs, 52 int dev, int reg) 53 { 54 ptrdiff_t csr, ofs; 55 u16 page; 56 int ret; 57 58 csr = xpcs_mmio_addr_format(dev, reg); 59 page = xpcs_mmio_addr_page(csr); 60 ofs = xpcs_mmio_addr_offset(csr); 61 62 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev); 63 if (ret) 64 return ret; 65 66 switch (pxpcs->reg_width) { 67 case 4: 68 writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2)); 69 ret = readl(pxpcs->reg_base + (ofs << 2)); 70 break; 71 default: 72 writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1)); 73 ret = readw(pxpcs->reg_base + (ofs << 1)); 74 break; 75 } 76 77 pm_runtime_put(&pxpcs->pdev->dev); 78 79 return ret; 80 } 81 82 static int xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat *pxpcs, 83 int dev, int reg, u16 val) 84 { 85 ptrdiff_t csr, ofs; 86 u16 page; 87 int ret; 88 89 csr = xpcs_mmio_addr_format(dev, reg); 90 page = xpcs_mmio_addr_page(csr); 91 ofs = xpcs_mmio_addr_offset(csr); 92 93 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev); 94 if (ret) 95 return ret; 96 97 switch (pxpcs->reg_width) { 98 case 4: 99 writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2)); 100 writel(val, pxpcs->reg_base + (ofs << 2)); 101 break; 102 default: 103 writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1)); 104 writew(val, pxpcs->reg_base + (ofs << 1)); 105 break; 106 } 107 108 pm_runtime_put(&pxpcs->pdev->dev); 109 110 return 0; 111 } 112 113 static int xpcs_mmio_read_reg_direct(struct dw_xpcs_plat *pxpcs, 114 int dev, int reg) 115 { 116 ptrdiff_t csr; 117 int ret; 118 119 csr = xpcs_mmio_addr_format(dev, reg); 120 121 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev); 122 if (ret) 123 return ret; 124 125 switch (pxpcs->reg_width) { 126 case 4: 127 ret = readl(pxpcs->reg_base + (csr << 2)); 128 break; 129 default: 130 ret = readw(pxpcs->reg_base + (csr << 1)); 131 break; 132 } 133 134 pm_runtime_put(&pxpcs->pdev->dev); 135 136 return ret; 137 } 138 139 static int xpcs_mmio_write_reg_direct(struct dw_xpcs_plat *pxpcs, 140 int dev, int reg, u16 val) 141 { 142 ptrdiff_t csr; 143 int ret; 144 145 csr = xpcs_mmio_addr_format(dev, reg); 146 147 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev); 148 if (ret) 149 return ret; 150 151 switch (pxpcs->reg_width) { 152 case 4: 153 writel(val, pxpcs->reg_base + (csr << 2)); 154 break; 155 default: 156 writew(val, pxpcs->reg_base + (csr << 1)); 157 break; 158 } 159 160 pm_runtime_put(&pxpcs->pdev->dev); 161 162 return 0; 163 } 164 165 static int xpcs_mmio_read_c22(struct mii_bus *bus, int addr, int reg) 166 { 167 struct dw_xpcs_plat *pxpcs = bus->priv; 168 169 if (addr != 0) 170 return -ENODEV; 171 172 if (pxpcs->reg_indir) 173 return xpcs_mmio_read_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg); 174 else 175 return xpcs_mmio_read_reg_direct(pxpcs, MDIO_MMD_VEND2, reg); 176 } 177 178 static int xpcs_mmio_write_c22(struct mii_bus *bus, int addr, int reg, u16 val) 179 { 180 struct dw_xpcs_plat *pxpcs = bus->priv; 181 182 if (addr != 0) 183 return -ENODEV; 184 185 if (pxpcs->reg_indir) 186 return xpcs_mmio_write_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg, val); 187 else 188 return xpcs_mmio_write_reg_direct(pxpcs, MDIO_MMD_VEND2, reg, val); 189 } 190 191 static int xpcs_mmio_read_c45(struct mii_bus *bus, int addr, int dev, int reg) 192 { 193 struct dw_xpcs_plat *pxpcs = bus->priv; 194 195 if (addr != 0) 196 return -ENODEV; 197 198 if (pxpcs->reg_indir) 199 return xpcs_mmio_read_reg_indirect(pxpcs, dev, reg); 200 else 201 return xpcs_mmio_read_reg_direct(pxpcs, dev, reg); 202 } 203 204 static int xpcs_mmio_write_c45(struct mii_bus *bus, int addr, int dev, 205 int reg, u16 val) 206 { 207 struct dw_xpcs_plat *pxpcs = bus->priv; 208 209 if (addr != 0) 210 return -ENODEV; 211 212 if (pxpcs->reg_indir) 213 return xpcs_mmio_write_reg_indirect(pxpcs, dev, reg, val); 214 else 215 return xpcs_mmio_write_reg_direct(pxpcs, dev, reg, val); 216 } 217 218 static struct dw_xpcs_plat *xpcs_plat_create_data(struct platform_device *pdev) 219 { 220 struct dw_xpcs_plat *pxpcs; 221 222 pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL); 223 if (!pxpcs) 224 return ERR_PTR(-ENOMEM); 225 226 pxpcs->pdev = pdev; 227 228 dev_set_drvdata(&pdev->dev, pxpcs); 229 230 return pxpcs; 231 } 232 233 static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs) 234 { 235 struct platform_device *pdev = pxpcs->pdev; 236 struct device *dev = &pdev->dev; 237 resource_size_t spc_size; 238 struct resource *res; 239 240 if (!device_property_read_u32(dev, "reg-io-width", &pxpcs->reg_width)) { 241 if (pxpcs->reg_width != 2 && pxpcs->reg_width != 4) { 242 dev_err(dev, "Invalid reg-space data width\n"); 243 return -EINVAL; 244 } 245 } else { 246 pxpcs->reg_width = 2; 247 } 248 249 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "direct") ?: 250 platform_get_resource_byname(pdev, IORESOURCE_MEM, "indirect"); 251 if (!res) { 252 dev_err(dev, "No reg-space found\n"); 253 return -EINVAL; 254 } 255 256 if (!strcmp(res->name, "indirect")) 257 pxpcs->reg_indir = true; 258 259 if (pxpcs->reg_indir) 260 spc_size = pxpcs->reg_width * SZ_256; 261 else 262 spc_size = pxpcs->reg_width * SZ_2M; 263 264 if (resource_size(res) < spc_size) { 265 dev_err(dev, "Invalid reg-space size\n"); 266 return -EINVAL; 267 } 268 269 pxpcs->reg_base = devm_ioremap_resource(dev, res); 270 if (IS_ERR(pxpcs->reg_base)) { 271 dev_err(dev, "Failed to map reg-space\n"); 272 return PTR_ERR(pxpcs->reg_base); 273 } 274 275 return 0; 276 } 277 278 static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) 279 { 280 struct device *dev = &pxpcs->pdev->dev; 281 int ret; 282 283 pxpcs->cclk = devm_clk_get(dev, "csr"); 284 if (IS_ERR(pxpcs->cclk)) 285 return dev_err_probe(dev, PTR_ERR(pxpcs->cclk), 286 "Failed to get CSR clock\n"); 287 288 pm_runtime_set_active(dev); 289 ret = devm_pm_runtime_enable(dev); 290 if (ret) { 291 dev_err(dev, "Failed to enable runtime-PM\n"); 292 return ret; 293 } 294 295 return 0; 296 } 297 298 static int xpcs_plat_init_bus(struct dw_xpcs_plat *pxpcs) 299 { 300 struct device *dev = &pxpcs->pdev->dev; 301 static atomic_t id = ATOMIC_INIT(-1); 302 int ret; 303 304 pxpcs->bus = devm_mdiobus_alloc_size(dev, 0); 305 if (!pxpcs->bus) 306 return -ENOMEM; 307 308 pxpcs->bus->name = "DW XPCS MCI/APB3"; 309 pxpcs->bus->read = xpcs_mmio_read_c22; 310 pxpcs->bus->write = xpcs_mmio_write_c22; 311 pxpcs->bus->read_c45 = xpcs_mmio_read_c45; 312 pxpcs->bus->write_c45 = xpcs_mmio_write_c45; 313 pxpcs->bus->phy_mask = ~0; 314 pxpcs->bus->parent = dev; 315 pxpcs->bus->priv = pxpcs; 316 317 snprintf(pxpcs->bus->id, MII_BUS_ID_SIZE, 318 "dwxpcs-%x", atomic_inc_return(&id)); 319 320 /* MDIO-bus here serves as just a back-end engine abstracting out 321 * the MDIO and MCI/APB3 IO interfaces utilized for the DW XPCS CSRs 322 * access. 323 */ 324 ret = devm_mdiobus_register(dev, pxpcs->bus); 325 if (ret) { 326 dev_err(dev, "Failed to create MDIO bus\n"); 327 return ret; 328 } 329 330 return 0; 331 } 332 333 /* Note there is no need in the next function antagonist because the MDIO-bus 334 * de-registration will effectively remove and destroy all the MDIO-devices 335 * registered on the bus. 336 */ 337 static int xpcs_plat_init_dev(struct dw_xpcs_plat *pxpcs) 338 { 339 struct device *dev = &pxpcs->pdev->dev; 340 struct mdio_device *mdiodev; 341 int ret; 342 343 /* There is a single memory-mapped DW XPCS device */ 344 mdiodev = mdio_device_create(pxpcs->bus, 0); 345 if (IS_ERR(mdiodev)) 346 return PTR_ERR(mdiodev); 347 348 /* Associate the FW-node with the device structure so it can be looked 349 * up later. Make sure DD-core is aware of the OF-node being re-used. 350 */ 351 device_set_node(&mdiodev->dev, fwnode_handle_get(dev_fwnode(dev))); 352 mdiodev->dev.of_node_reused = true; 353 354 /* Pass the data further so the DW XPCS driver core could use it */ 355 mdiodev->dev.platform_data = (void *)device_get_match_data(dev); 356 357 ret = mdio_device_register(mdiodev); 358 if (ret) { 359 dev_err(dev, "Failed to register MDIO device\n"); 360 goto err_clean_data; 361 } 362 363 return 0; 364 365 err_clean_data: 366 mdiodev->dev.platform_data = NULL; 367 368 fwnode_handle_put(dev_fwnode(&mdiodev->dev)); 369 device_set_node(&mdiodev->dev, NULL); 370 371 mdio_device_free(mdiodev); 372 373 return ret; 374 } 375 376 static int xpcs_plat_probe(struct platform_device *pdev) 377 { 378 struct dw_xpcs_plat *pxpcs; 379 int ret; 380 381 pxpcs = xpcs_plat_create_data(pdev); 382 if (IS_ERR(pxpcs)) 383 return PTR_ERR(pxpcs); 384 385 ret = xpcs_plat_init_res(pxpcs); 386 if (ret) 387 return ret; 388 389 ret = xpcs_plat_init_clk(pxpcs); 390 if (ret) 391 return ret; 392 393 ret = xpcs_plat_init_bus(pxpcs); 394 if (ret) 395 return ret; 396 397 ret = xpcs_plat_init_dev(pxpcs); 398 if (ret) 399 return ret; 400 401 return 0; 402 } 403 404 static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev) 405 { 406 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); 407 408 clk_disable_unprepare(pxpcs->cclk); 409 410 return 0; 411 } 412 413 static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev) 414 { 415 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); 416 417 return clk_prepare_enable(pxpcs->cclk); 418 } 419 420 static const struct dev_pm_ops xpcs_plat_pm_ops = { 421 SET_RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend, 422 xpcs_plat_pm_runtime_resume, 423 NULL) 424 }; 425 426 DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE); 427 DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G_ID); 428 DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G_ID); 429 DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G_ID); 430 DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID); 431 DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID); 432 DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID); 433 DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID); 434 435 static const struct of_device_id xpcs_of_ids[] = { 436 { .compatible = "snps,dw-xpcs", .data = &xpcs_generic }, 437 { .compatible = "snps,dw-xpcs-gen1-3g", .data = &xpcs_pma_gen1_3g }, 438 { .compatible = "snps,dw-xpcs-gen2-3g", .data = &xpcs_pma_gen2_3g }, 439 { .compatible = "snps,dw-xpcs-gen2-6g", .data = &xpcs_pma_gen2_6g }, 440 { .compatible = "snps,dw-xpcs-gen4-3g", .data = &xpcs_pma_gen4_3g }, 441 { .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g }, 442 { .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g }, 443 { .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g }, 444 { /* sentinel */ }, 445 }; 446 MODULE_DEVICE_TABLE(of, xpcs_of_ids); 447 448 static struct platform_driver xpcs_plat_driver = { 449 .probe = xpcs_plat_probe, 450 .driver = { 451 .name = "dwxpcs", 452 .pm = &xpcs_plat_pm_ops, 453 .of_match_table = xpcs_of_ids, 454 }, 455 }; 456 module_platform_driver(xpcs_plat_driver); 457 458 MODULE_DESCRIPTION("Synopsys DesignWare XPCS platform device driver"); 459 MODULE_AUTHOR("Signed-off-by: Serge Semin <fancer.lancer@gmail.com>"); 460 MODULE_LICENSE("GPL"); 461