Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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));
 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	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(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	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");