brintos

brintos / linux-shallow public Read only

0
0
Text · 8.7 KiB · 1ce7d67 Raw
353 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2016 Broadcom4 */5#include <linux/align.h>6#include <linux/clk.h>7#include <linux/delay.h>8#include <linux/device.h>9#include <linux/iopoll.h>10#include <linux/mdio-mux.h>11#include <linux/module.h>12#include <linux/of_mdio.h>13#include <linux/phy.h>14#include <linux/platform_device.h>15#include <linux/sizes.h>16 17#define MDIO_RATE_ADJ_EXT_OFFSET	0x00018#define MDIO_RATE_ADJ_INT_OFFSET	0x00419#define MDIO_RATE_ADJ_DIVIDENT_SHIFT	1620 21#define MDIO_SCAN_CTRL_OFFSET		0x00822#define MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR	2823 24#define MDIO_PARAM_OFFSET		0x23c25#define MDIO_PARAM_MIIM_CYCLE		2926#define MDIO_PARAM_INTERNAL_SEL		2527#define MDIO_PARAM_BUS_ID		2228#define MDIO_PARAM_C45_SEL		2129#define MDIO_PARAM_PHY_ID		1630#define MDIO_PARAM_PHY_DATA		031 32#define MDIO_READ_OFFSET		0x24033#define MDIO_READ_DATA_MASK		0xffff34#define MDIO_ADDR_OFFSET		0x24435 36#define MDIO_CTRL_OFFSET		0x24837#define MDIO_CTRL_WRITE_OP		0x138#define MDIO_CTRL_READ_OP		0x239 40#define MDIO_STAT_OFFSET		0x24c41#define MDIO_STAT_DONE			142 43#define BUS_MAX_ADDR			3244#define EXT_BUS_START_ADDR		1645 46#define MDIO_REG_ADDR_SPACE_SIZE	0x25047 48#define MDIO_OPERATING_FREQUENCY	1100000049#define MDIO_RATE_ADJ_DIVIDENT		150 51struct iproc_mdiomux_desc {52	void *mux_handle;53	void __iomem *base;54	struct device *dev;55	struct mii_bus *mii_bus;56	struct clk *core_clk;57};58 59static void mdio_mux_iproc_config(struct iproc_mdiomux_desc *md)60{61	u32 divisor;62	u32 val;63 64	/* Disable external mdio master access */65	val = readl(md->base + MDIO_SCAN_CTRL_OFFSET);66	val |= BIT(MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR);67	writel(val, md->base + MDIO_SCAN_CTRL_OFFSET);68 69	if (md->core_clk) {70		/* use rate adjust regs to derive the mdio's operating71		 * frequency from the specified core clock72		 */73		divisor = clk_get_rate(md->core_clk) / MDIO_OPERATING_FREQUENCY;74		divisor = divisor / (MDIO_RATE_ADJ_DIVIDENT + 1);75		val = divisor;76		val |= MDIO_RATE_ADJ_DIVIDENT << MDIO_RATE_ADJ_DIVIDENT_SHIFT;77		writel(val, md->base + MDIO_RATE_ADJ_EXT_OFFSET);78		writel(val, md->base + MDIO_RATE_ADJ_INT_OFFSET);79	}80}81 82static int iproc_mdio_wait_for_idle(void __iomem *base, bool result)83{84	u32 val;85 86	return readl_poll_timeout(base + MDIO_STAT_OFFSET, val,87				  (val & MDIO_STAT_DONE) == result,88				  2000, 1000000);89}90 91/* start_miim_ops- Program and start MDIO transaction over mdio bus.92 * @base: Base address93 * @phyid: phyid of the selected bus.94 * @reg: register offset to be read/written.95 * @val :0 if read op else value to be written in @reg;96 * @op: Operation that need to be carried out.97 *      MDIO_CTRL_READ_OP: Read transaction.98 *      MDIO_CTRL_WRITE_OP: Write transaction.99 *100 * Return value: Successful Read operation returns read reg values and write101 *      operation returns 0. Failure operation returns negative error code.102 */103static int start_miim_ops(void __iomem *base, bool c45,104			  u16 phyid, u32 reg, u16 val, u32 op)105{106	u32 param;107	int ret;108 109	writel(0, base + MDIO_CTRL_OFFSET);110	ret = iproc_mdio_wait_for_idle(base, 0);111	if (ret)112		goto err;113 114	param = readl(base + MDIO_PARAM_OFFSET);115	param |= phyid << MDIO_PARAM_PHY_ID;116	param |= val << MDIO_PARAM_PHY_DATA;117	if (c45)118		param |= BIT(MDIO_PARAM_C45_SEL);119 120	writel(param, base + MDIO_PARAM_OFFSET);121 122	writel(reg, base + MDIO_ADDR_OFFSET);123 124	writel(op, base + MDIO_CTRL_OFFSET);125 126	ret = iproc_mdio_wait_for_idle(base, 1);127	if (ret)128		goto err;129 130	if (op == MDIO_CTRL_READ_OP)131		ret = readl(base + MDIO_READ_OFFSET) & MDIO_READ_DATA_MASK;132err:133	return ret;134}135 136static int iproc_mdiomux_read_c22(struct mii_bus *bus, int phyid, int reg)137{138	struct iproc_mdiomux_desc *md = bus->priv;139	int ret;140 141	ret = start_miim_ops(md->base, false, phyid, reg, 0, MDIO_CTRL_READ_OP);142	if (ret < 0)143		dev_err(&bus->dev, "mdiomux c22 read operation failed!!!");144 145	return ret;146}147 148static int iproc_mdiomux_read_c45(struct mii_bus *bus, int phyid, int devad,149				  int reg)150{151	struct iproc_mdiomux_desc *md = bus->priv;152	int ret;153 154	ret = start_miim_ops(md->base, true, phyid, reg | devad << 16, 0,155			     MDIO_CTRL_READ_OP);156	if (ret < 0)157		dev_err(&bus->dev, "mdiomux read c45 operation failed!!!");158 159	return ret;160}161 162static int iproc_mdiomux_write_c22(struct mii_bus *bus,163				   int phyid, int reg, u16 val)164{165	struct iproc_mdiomux_desc *md = bus->priv;166	int ret;167 168	/* Write val at reg offset */169	ret = start_miim_ops(md->base, false, phyid, reg, val,170			     MDIO_CTRL_WRITE_OP);171	if (ret < 0)172		dev_err(&bus->dev, "mdiomux write c22 operation failed!!!");173 174	return ret;175}176 177static int iproc_mdiomux_write_c45(struct mii_bus *bus,178				   int phyid, int devad, int reg, u16 val)179{180	struct iproc_mdiomux_desc *md = bus->priv;181	int ret;182 183	/* Write val at reg offset */184	ret = start_miim_ops(md->base, true, phyid, reg | devad << 16, val,185			     MDIO_CTRL_WRITE_OP);186	if (ret < 0)187		dev_err(&bus->dev, "mdiomux write c45 operation failed!!!");188 189	return ret;190}191 192static int mdio_mux_iproc_switch_fn(int current_child, int desired_child,193				    void *data)194{195	struct iproc_mdiomux_desc *md = data;196	u32 param, bus_id;197	bool bus_dir;198 199	/* select bus and its properties */200	bus_dir = (desired_child < EXT_BUS_START_ADDR);201	bus_id = bus_dir ? desired_child : (desired_child - EXT_BUS_START_ADDR);202 203	param = (bus_dir ? 1 : 0) << MDIO_PARAM_INTERNAL_SEL;204	param |= (bus_id << MDIO_PARAM_BUS_ID);205 206	writel(param, md->base + MDIO_PARAM_OFFSET);207	return 0;208}209 210static int mdio_mux_iproc_probe(struct platform_device *pdev)211{212	struct iproc_mdiomux_desc *md;213	struct mii_bus *bus;214	struct resource *res;215	int rc;216 217	md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL);218	if (!md)219		return -ENOMEM;220	md->dev = &pdev->dev;221 222	md->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);223	if (IS_ERR(md->base))224		return PTR_ERR(md->base);225	if (!IS_ALIGNED(res->start, SZ_4K)) {226		/* For backward compatibility in case the227		 * base address is specified with an offset.228		 */229		dev_info(&pdev->dev, "fix base address in dt-blob\n");230		res->start = ALIGN_DOWN(res->start, SZ_4K);231		res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1;232	}233 234	md->mii_bus = devm_mdiobus_alloc(&pdev->dev);235	if (!md->mii_bus) {236		dev_err(&pdev->dev, "mdiomux bus alloc failed\n");237		return -ENOMEM;238	}239 240	md->core_clk = devm_clk_get(&pdev->dev, NULL);241	if (md->core_clk == ERR_PTR(-ENOENT) ||242	    md->core_clk == ERR_PTR(-EINVAL))243		md->core_clk = NULL;244	else if (IS_ERR(md->core_clk))245		return PTR_ERR(md->core_clk);246 247	rc = clk_prepare_enable(md->core_clk);248	if (rc) {249		dev_err(&pdev->dev, "failed to enable core clk\n");250		return rc;251	}252 253	bus = md->mii_bus;254	bus->priv = md;255	bus->name = "iProc MDIO mux bus";256	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);257	bus->parent = &pdev->dev;258	bus->read = iproc_mdiomux_read_c22;259	bus->write = iproc_mdiomux_write_c22;260	bus->read_c45 = iproc_mdiomux_read_c45;261	bus->write_c45 = iproc_mdiomux_write_c45;262 263	bus->phy_mask = ~0;264	bus->dev.of_node = pdev->dev.of_node;265	rc = mdiobus_register(bus);266	if (rc) {267		dev_err(&pdev->dev, "mdiomux registration failed\n");268		goto out_clk;269	}270 271	platform_set_drvdata(pdev, md);272 273	rc = mdio_mux_init(md->dev, md->dev->of_node, mdio_mux_iproc_switch_fn,274			   &md->mux_handle, md, md->mii_bus);275	if (rc) {276		dev_info(md->dev, "mdiomux initialization failed\n");277		goto out_register;278	}279 280	mdio_mux_iproc_config(md);281 282	dev_info(md->dev, "iProc mdiomux registered\n");283	return 0;284 285out_register:286	mdiobus_unregister(bus);287out_clk:288	clk_disable_unprepare(md->core_clk);289	return rc;290}291 292static void mdio_mux_iproc_remove(struct platform_device *pdev)293{294	struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev);295 296	mdio_mux_uninit(md->mux_handle);297	mdiobus_unregister(md->mii_bus);298	clk_disable_unprepare(md->core_clk);299}300 301#ifdef CONFIG_PM_SLEEP302static int mdio_mux_iproc_suspend(struct device *dev)303{304	struct iproc_mdiomux_desc *md = dev_get_drvdata(dev);305 306	clk_disable_unprepare(md->core_clk);307 308	return 0;309}310 311static int mdio_mux_iproc_resume(struct device *dev)312{313	struct iproc_mdiomux_desc *md = dev_get_drvdata(dev);314	int rc;315 316	rc = clk_prepare_enable(md->core_clk);317	if (rc) {318		dev_err(md->dev, "failed to enable core clk\n");319		return rc;320	}321	mdio_mux_iproc_config(md);322 323	return 0;324}325#endif326 327static SIMPLE_DEV_PM_OPS(mdio_mux_iproc_pm_ops,328			 mdio_mux_iproc_suspend, mdio_mux_iproc_resume);329 330static const struct of_device_id mdio_mux_iproc_match[] = {331	{332		.compatible = "brcm,mdio-mux-iproc",333	},334	{},335};336MODULE_DEVICE_TABLE(of, mdio_mux_iproc_match);337 338static struct platform_driver mdiomux_iproc_driver = {339	.driver = {340		.name		= "mdio-mux-iproc",341		.of_match_table = mdio_mux_iproc_match,342		.pm		= &mdio_mux_iproc_pm_ops,343	},344	.probe		= mdio_mux_iproc_probe,345	.remove_new	= mdio_mux_iproc_remove,346};347 348module_platform_driver(mdiomux_iproc_driver);349 350MODULE_DESCRIPTION("iProc MDIO Mux Bus Driver");351MODULE_AUTHOR("Pramod Kumar <pramod.kumar@broadcom.com>");352MODULE_LICENSE("GPL v2");353