xref: /linux/drivers/net/pcs/pcs-xpcs-plat.c (revision f6bb3e9d)
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 
xpcs_mmio_addr_format(int dev,int reg)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 
xpcs_mmio_addr_page(ptrdiff_t csr)41 static u16 xpcs_mmio_addr_page(ptrdiff_t csr)
42 {
43 	return FIELD_GET(0x1fff00, csr);
44 }
45 
xpcs_mmio_addr_offset(ptrdiff_t csr)46 static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr)
47 {
48 	return FIELD_GET(0xff, csr);
49 }
50 
xpcs_mmio_read_reg_indirect(struct dw_xpcs_plat * pxpcs,int dev,int reg)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 
xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat * pxpcs,int dev,int reg,u16 val)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 
xpcs_mmio_read_reg_direct(struct dw_xpcs_plat * pxpcs,int dev,int reg)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 
xpcs_mmio_write_reg_direct(struct dw_xpcs_plat * pxpcs,int dev,int reg,u16 val)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 
xpcs_mmio_read_c22(struct mii_bus * bus,int addr,int reg)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 
xpcs_mmio_write_c22(struct mii_bus * bus,int addr,int reg,u16 val)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 
xpcs_mmio_read_c45(struct mii_bus * bus,int addr,int dev,int reg)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 
xpcs_mmio_write_c45(struct mii_bus * bus,int addr,int dev,int reg,u16 val)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 
xpcs_plat_create_data(struct platform_device * pdev)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 
xpcs_plat_init_res(struct dw_xpcs_plat * pxpcs)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 
xpcs_plat_init_clk(struct dw_xpcs_plat * pxpcs)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 
xpcs_plat_init_bus(struct dw_xpcs_plat * pxpcs)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  */
xpcs_plat_init_dev(struct dw_xpcs_plat * pxpcs)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 
xpcs_plat_probe(struct platform_device * pdev)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 
xpcs_plat_pm_runtime_suspend(struct device * dev)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 
xpcs_plat_pm_runtime_resume(struct device * dev)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