1 // SPDX-License-Identifier: GPL-2.0
3 * Synopsys DesignWare XPCS platform device driver
5 * Copyright (C) 2024 Serge Semin
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>
24 /* Page select register for the indirect MMIO CSRs access */
25 #define DW_VR_CSR_VIEWPORT 0xff
28 struct platform_device
*pdev
;
32 void __iomem
*reg_base
;
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
,
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
);
66 switch (pxpcs
->reg_width
) {
68 writel(page
, pxpcs
->reg_base
+ (DW_VR_CSR_VIEWPORT
<< 2));
69 ret
= readl(pxpcs
->reg_base
+ (ofs
<< 2));
72 writew(page
, pxpcs
->reg_base
+ (DW_VR_CSR_VIEWPORT
<< 1));
73 ret
= readw(pxpcs
->reg_base
+ (ofs
<< 1));
77 pm_runtime_put(&pxpcs
->pdev
->dev
);
82 static int xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat
*pxpcs
,
83 int dev
, int reg
, u16 val
)
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
);
97 switch (pxpcs
->reg_width
) {
99 writel(page
, pxpcs
->reg_base
+ (DW_VR_CSR_VIEWPORT
<< 2));
100 writel(val
, pxpcs
->reg_base
+ (ofs
<< 2));
103 writew(page
, pxpcs
->reg_base
+ (DW_VR_CSR_VIEWPORT
<< 1));
104 writew(val
, pxpcs
->reg_base
+ (ofs
<< 1));
108 pm_runtime_put(&pxpcs
->pdev
->dev
);
113 static int xpcs_mmio_read_reg_direct(struct dw_xpcs_plat
*pxpcs
,
119 csr
= xpcs_mmio_addr_format(dev
, reg
);
121 ret
= pm_runtime_resume_and_get(&pxpcs
->pdev
->dev
);
125 switch (pxpcs
->reg_width
) {
127 ret
= readl(pxpcs
->reg_base
+ (csr
<< 2));
130 ret
= readw(pxpcs
->reg_base
+ (csr
<< 1));
134 pm_runtime_put(&pxpcs
->pdev
->dev
);
139 static int xpcs_mmio_write_reg_direct(struct dw_xpcs_plat
*pxpcs
,
140 int dev
, int reg
, u16 val
)
145 csr
= xpcs_mmio_addr_format(dev
, reg
);
147 ret
= pm_runtime_resume_and_get(&pxpcs
->pdev
->dev
);
151 switch (pxpcs
->reg_width
) {
153 writel(val
, pxpcs
->reg_base
+ (csr
<< 2));
156 writew(val
, pxpcs
->reg_base
+ (csr
<< 1));
160 pm_runtime_put(&pxpcs
->pdev
->dev
);
165 static int xpcs_mmio_read_c22(struct mii_bus
*bus
, int addr
, int reg
)
167 struct dw_xpcs_plat
*pxpcs
= bus
->priv
;
172 if (pxpcs
->reg_indir
)
173 return xpcs_mmio_read_reg_indirect(pxpcs
, MDIO_MMD_VEND2
, reg
);
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
;
185 if (pxpcs
->reg_indir
)
186 return xpcs_mmio_write_reg_indirect(pxpcs
, MDIO_MMD_VEND2
, reg
, val
);
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
;
198 if (pxpcs
->reg_indir
)
199 return xpcs_mmio_read_reg_indirect(pxpcs
, dev
, reg
);
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
,
207 struct dw_xpcs_plat
*pxpcs
= bus
->priv
;
212 if (pxpcs
->reg_indir
)
213 return xpcs_mmio_write_reg_indirect(pxpcs
, dev
, reg
, val
);
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
);
224 return ERR_PTR(-ENOMEM
);
228 dev_set_drvdata(&pdev
->dev
, 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");
246 pxpcs
->reg_width
= 2;
249 res
= platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "direct") ?:
250 platform_get_resource_byname(pdev
, IORESOURCE_MEM
, "indirect");
252 dev_err(dev
, "No reg-space found\n");
256 if (!strcmp(res
->name
, "indirect"))
257 pxpcs
->reg_indir
= true;
259 if (pxpcs
->reg_indir
)
260 spc_size
= pxpcs
->reg_width
* SZ_256
;
262 spc_size
= pxpcs
->reg_width
* SZ_2M
;
264 if (resource_size(res
) < spc_size
) {
265 dev_err(dev
, "Invalid reg-space size\n");
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
);
278 static int xpcs_plat_init_clk(struct dw_xpcs_plat
*pxpcs
)
280 struct device
*dev
= &pxpcs
->pdev
->dev
;
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
);
291 dev_err(dev
, "Failed to enable runtime-PM\n");
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);
304 pxpcs
->bus
= devm_mdiobus_alloc_size(dev
, 0);
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
324 ret
= devm_mdiobus_register(dev
, pxpcs
->bus
);
326 dev_err(dev
, "Failed to create MDIO bus\n");
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
;
343 /* There is a single memory-mapped DW XPCS device */
344 mdiodev
= mdio_device_create(pxpcs
->bus
, 0);
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
);
359 dev_err(dev
, "Failed to register MDIO device\n");
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
);
376 static int xpcs_plat_probe(struct platform_device
*pdev
)
378 struct dw_xpcs_plat
*pxpcs
;
381 pxpcs
= xpcs_plat_create_data(pdev
);
383 return PTR_ERR(pxpcs
);
385 ret
= xpcs_plat_init_res(pxpcs
);
389 ret
= xpcs_plat_init_clk(pxpcs
);
393 ret
= xpcs_plat_init_bus(pxpcs
);
397 ret
= xpcs_plat_init_dev(pxpcs
);
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
);
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
,
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
},
446 MODULE_DEVICE_TABLE(of
, xpcs_of_ids
);
448 static struct platform_driver xpcs_plat_driver
= {
449 .probe
= xpcs_plat_probe
,
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");