Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at nocache-cleanup 457 lines 11 kB view raw
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 27struct 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 36static ptrdiff_t xpcs_mmio_addr_format(int dev, int reg) 37{ 38 return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg); 39} 40 41static u16 xpcs_mmio_addr_page(ptrdiff_t csr) 42{ 43 return FIELD_GET(0x1fff00, csr); 44} 45 46static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr) 47{ 48 return FIELD_GET(0xff, csr); 49} 50 51static 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)) & 0xffff; 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 82static 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 113static 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)) & 0xffff; 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 139static 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 165static 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 178static 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 191static 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 204static 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 218static 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 233static 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 278static 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_optional(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 298static 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 */ 337static 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 365err_clean_data: 366 mdiodev->dev.platform_data = NULL; 367 368 mdio_device_free(mdiodev); 369 370 return ret; 371} 372 373static int xpcs_plat_probe(struct platform_device *pdev) 374{ 375 struct dw_xpcs_plat *pxpcs; 376 int ret; 377 378 pxpcs = xpcs_plat_create_data(pdev); 379 if (IS_ERR(pxpcs)) 380 return PTR_ERR(pxpcs); 381 382 ret = xpcs_plat_init_res(pxpcs); 383 if (ret) 384 return ret; 385 386 ret = xpcs_plat_init_clk(pxpcs); 387 if (ret) 388 return ret; 389 390 ret = xpcs_plat_init_bus(pxpcs); 391 if (ret) 392 return ret; 393 394 ret = xpcs_plat_init_dev(pxpcs); 395 if (ret) 396 return ret; 397 398 return 0; 399} 400 401static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev) 402{ 403 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); 404 405 clk_disable_unprepare(pxpcs->cclk); 406 407 return 0; 408} 409 410static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev) 411{ 412 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev); 413 414 return clk_prepare_enable(pxpcs->cclk); 415} 416 417static const struct dev_pm_ops xpcs_plat_pm_ops = { 418 SET_RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend, 419 xpcs_plat_pm_runtime_resume, 420 NULL) 421}; 422 423DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE); 424DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G_ID); 425DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G_ID); 426DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G_ID); 427DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID); 428DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID); 429DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID); 430DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID); 431 432static const struct of_device_id xpcs_of_ids[] = { 433 { .compatible = "snps,dw-xpcs", .data = &xpcs_generic }, 434 { .compatible = "snps,dw-xpcs-gen1-3g", .data = &xpcs_pma_gen1_3g }, 435 { .compatible = "snps,dw-xpcs-gen2-3g", .data = &xpcs_pma_gen2_3g }, 436 { .compatible = "snps,dw-xpcs-gen2-6g", .data = &xpcs_pma_gen2_6g }, 437 { .compatible = "snps,dw-xpcs-gen4-3g", .data = &xpcs_pma_gen4_3g }, 438 { .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g }, 439 { .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g }, 440 { .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g }, 441 { /* sentinel */ }, 442}; 443MODULE_DEVICE_TABLE(of, xpcs_of_ids); 444 445static struct platform_driver xpcs_plat_driver = { 446 .probe = xpcs_plat_probe, 447 .driver = { 448 .name = "dwxpcs", 449 .pm = &xpcs_plat_pm_ops, 450 .of_match_table = xpcs_of_ids, 451 }, 452}; 453module_platform_driver(xpcs_plat_driver); 454 455MODULE_DESCRIPTION("Synopsys DesignWare XPCS platform device driver"); 456MODULE_AUTHOR("Serge Semin <fancer.lancer@gmail.com>"); 457MODULE_LICENSE("GPL");