Merge tag 'trace-printf-v6.13' of git://git.kernel.org/pub/scm/linux/kernel/git/trace...
[drm/drm-misc.git] / drivers / net / pcs / pcs-xpcs-plat.c
blob629315f1e57cb32e6df20b7378be8a59a932709c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Synopsys DesignWare XPCS platform device driver
5 * Copyright (C) 2024 Serge Semin
6 */
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>
22 #include "pcs-xpcs.h"
24 /* Page select register for the indirect MMIO CSRs access */
25 #define DW_VR_CSR_VIEWPORT 0xff
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;
36 static ptrdiff_t xpcs_mmio_addr_format(int dev, int reg)
38 return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg);
41 static u16 xpcs_mmio_addr_page(ptrdiff_t csr)
43 return FIELD_GET(0x1fff00, csr);
46 static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr)
48 return FIELD_GET(0xff, csr);
51 static int xpcs_mmio_read_reg_indirect(struct dw_xpcs_plat *pxpcs,
52 int dev, int reg)
54 ptrdiff_t csr, ofs;
55 u16 page;
56 int ret;
58 csr = xpcs_mmio_addr_format(dev, reg);
59 page = xpcs_mmio_addr_page(csr);
60 ofs = xpcs_mmio_addr_offset(csr);
62 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
63 if (ret)
64 return ret;
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;
77 pm_runtime_put(&pxpcs->pdev->dev);
79 return ret;
82 static int xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat *pxpcs,
83 int dev, int reg, u16 val)
85 ptrdiff_t csr, ofs;
86 u16 page;
87 int ret;
89 csr = xpcs_mmio_addr_format(dev, reg);
90 page = xpcs_mmio_addr_page(csr);
91 ofs = xpcs_mmio_addr_offset(csr);
93 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
94 if (ret)
95 return ret;
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;
108 pm_runtime_put(&pxpcs->pdev->dev);
110 return 0;
113 static int xpcs_mmio_read_reg_direct(struct dw_xpcs_plat *pxpcs,
114 int dev, int reg)
116 ptrdiff_t csr;
117 int ret;
119 csr = xpcs_mmio_addr_format(dev, reg);
121 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
122 if (ret)
123 return ret;
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;
134 pm_runtime_put(&pxpcs->pdev->dev);
136 return ret;
139 static int xpcs_mmio_write_reg_direct(struct dw_xpcs_plat *pxpcs,
140 int dev, int reg, u16 val)
142 ptrdiff_t csr;
143 int ret;
145 csr = xpcs_mmio_addr_format(dev, reg);
147 ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
148 if (ret)
149 return ret;
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;
160 pm_runtime_put(&pxpcs->pdev->dev);
162 return 0;
165 static int xpcs_mmio_read_c22(struct mii_bus *bus, int addr, int reg)
167 struct dw_xpcs_plat *pxpcs = bus->priv;
169 if (addr != 0)
170 return -ENODEV;
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);
178 static int xpcs_mmio_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
180 struct dw_xpcs_plat *pxpcs = bus->priv;
182 if (addr != 0)
183 return -ENODEV;
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);
191 static int xpcs_mmio_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
193 struct dw_xpcs_plat *pxpcs = bus->priv;
195 if (addr != 0)
196 return -ENODEV;
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);
204 static int xpcs_mmio_write_c45(struct mii_bus *bus, int addr, int dev,
205 int reg, u16 val)
207 struct dw_xpcs_plat *pxpcs = bus->priv;
209 if (addr != 0)
210 return -ENODEV;
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);
218 static struct dw_xpcs_plat *xpcs_plat_create_data(struct platform_device *pdev)
220 struct dw_xpcs_plat *pxpcs;
222 pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL);
223 if (!pxpcs)
224 return ERR_PTR(-ENOMEM);
226 pxpcs->pdev = pdev;
228 dev_set_drvdata(&pdev->dev, pxpcs);
230 return pxpcs;
233 static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs)
235 struct platform_device *pdev = pxpcs->pdev;
236 struct device *dev = &pdev->dev;
237 resource_size_t spc_size;
238 struct resource *res;
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;
245 } else {
246 pxpcs->reg_width = 2;
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;
256 if (!strcmp(res->name, "indirect"))
257 pxpcs->reg_indir = true;
259 if (pxpcs->reg_indir)
260 spc_size = pxpcs->reg_width * SZ_256;
261 else
262 spc_size = pxpcs->reg_width * SZ_2M;
264 if (resource_size(res) < spc_size) {
265 dev_err(dev, "Invalid reg-space size\n");
266 return -EINVAL;
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);
275 return 0;
278 static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs)
280 struct device *dev = &pxpcs->pdev->dev;
281 int ret;
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");
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;
295 return 0;
298 static int xpcs_plat_init_bus(struct dw_xpcs_plat *pxpcs)
300 struct device *dev = &pxpcs->pdev->dev;
301 static atomic_t id = ATOMIC_INIT(-1);
302 int ret;
304 pxpcs->bus = devm_mdiobus_alloc_size(dev, 0);
305 if (!pxpcs->bus)
306 return -ENOMEM;
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;
317 snprintf(pxpcs->bus->id, MII_BUS_ID_SIZE,
318 "dwxpcs-%x", atomic_inc_return(&id));
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.
324 ret = devm_mdiobus_register(dev, pxpcs->bus);
325 if (ret) {
326 dev_err(dev, "Failed to create MDIO bus\n");
327 return ret;
330 return 0;
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.
337 static int xpcs_plat_init_dev(struct dw_xpcs_plat *pxpcs)
339 struct device *dev = &pxpcs->pdev->dev;
340 struct mdio_device *mdiodev;
341 int ret;
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);
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.
351 device_set_node(&mdiodev->dev, fwnode_handle_get(dev_fwnode(dev)));
352 mdiodev->dev.of_node_reused = true;
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);
357 ret = mdio_device_register(mdiodev);
358 if (ret) {
359 dev_err(dev, "Failed to register MDIO device\n");
360 goto err_clean_data;
363 return 0;
365 err_clean_data:
366 mdiodev->dev.platform_data = NULL;
368 fwnode_handle_put(dev_fwnode(&mdiodev->dev));
369 device_set_node(&mdiodev->dev, NULL);
371 mdio_device_free(mdiodev);
373 return ret;
376 static int xpcs_plat_probe(struct platform_device *pdev)
378 struct dw_xpcs_plat *pxpcs;
379 int ret;
381 pxpcs = xpcs_plat_create_data(pdev);
382 if (IS_ERR(pxpcs))
383 return PTR_ERR(pxpcs);
385 ret = xpcs_plat_init_res(pxpcs);
386 if (ret)
387 return ret;
389 ret = xpcs_plat_init_clk(pxpcs);
390 if (ret)
391 return ret;
393 ret = xpcs_plat_init_bus(pxpcs);
394 if (ret)
395 return ret;
397 ret = xpcs_plat_init_dev(pxpcs);
398 if (ret)
399 return ret;
401 return 0;
404 static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev)
406 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
408 clk_disable_unprepare(pxpcs->cclk);
410 return 0;
413 static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev)
415 struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
417 return clk_prepare_enable(pxpcs->cclk);
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)
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);
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 */ },
446 MODULE_DEVICE_TABLE(of, xpcs_of_ids);
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,
456 module_platform_driver(xpcs_plat_driver);
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");