brintos

brintos / linux-shallow public Read only

0
0
Text · 10.9 KiB · 629315f Raw
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