461 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Synopsys DesignWare XPCS platform device driver4 *5 * Copyright (C) 2024 Serge Semin6 */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 0xff26 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));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));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 else175 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 else188 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 else201 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 else215 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 else262 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(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 out321 * the MDIO and MCI/APB3 IO interfaces utilized for the DW XPCS CSRs322 * 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-bus334 * de-registration will effectively remove and destroy all the MDIO-devices335 * 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 looked349 * 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 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 376static 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 404static 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 413static 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 420static 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 426DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE);427DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G_ID);428DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G_ID);429DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G_ID);430DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID);431DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID);432DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID);433DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID);434 435static 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};446MODULE_DEVICE_TABLE(of, xpcs_of_ids);447 448static 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};456module_platform_driver(xpcs_plat_driver);457 458MODULE_DESCRIPTION("Synopsys DesignWare XPCS platform device driver");459MODULE_AUTHOR("Signed-off-by: Serge Semin <fancer.lancer@gmail.com>");460MODULE_LICENSE("GPL");461