Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.8.
  1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
  2/*
  3 * Driver for the MDIO interface of Microsemi network switches.
  4 *
  5 * Author: Alexandre Belloni <alexandre.belloni@bootlin.com>
  6 * Copyright (c) 2017 Microsemi Corporation
  7 */
  8
  9#include <linux/kernel.h>
 10#include <linux/module.h>
 11#include <linux/phy.h>
 12#include <linux/platform_device.h>
 13#include <linux/bitops.h>
 14#include <linux/io.h>
 15#include <linux/iopoll.h>
 16#include <linux/of_mdio.h>
 17
 18#define MSCC_MIIM_REG_STATUS		0x0
 19#define		MSCC_MIIM_STATUS_STAT_BUSY	BIT(3)
 20#define MSCC_MIIM_REG_CMD		0x8
 21#define		MSCC_MIIM_CMD_OPR_WRITE		BIT(1)
 22#define		MSCC_MIIM_CMD_OPR_READ		BIT(2)
 23#define		MSCC_MIIM_CMD_WRDATA_SHIFT	4
 24#define		MSCC_MIIM_CMD_REGAD_SHIFT	20
 25#define		MSCC_MIIM_CMD_PHYAD_SHIFT	25
 26#define		MSCC_MIIM_CMD_VLD		BIT(31)
 27#define MSCC_MIIM_REG_DATA		0xC
 28#define		MSCC_MIIM_DATA_ERROR		(BIT(16) | BIT(17))
 29
 30#define MSCC_PHY_REG_PHY_CFG	0x0
 31#define		PHY_CFG_PHY_ENA		(BIT(0) | BIT(1) | BIT(2) | BIT(3))
 32#define		PHY_CFG_PHY_COMMON_RESET BIT(4)
 33#define		PHY_CFG_PHY_RESET	(BIT(5) | BIT(6) | BIT(7) | BIT(8))
 34#define MSCC_PHY_REG_PHY_STATUS	0x4
 35
 36struct mscc_miim_dev {
 37	void __iomem *regs;
 38	void __iomem *phy_regs;
 39};
 40
 41static int mscc_miim_wait_ready(struct mii_bus *bus)
 42{
 43	struct mscc_miim_dev *miim = bus->priv;
 44	u32 val;
 45
 46	readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val,
 47			   !(val & MSCC_MIIM_STATUS_STAT_BUSY), 100, 250000);
 48	if (val & MSCC_MIIM_STATUS_STAT_BUSY)
 49		return -ETIMEDOUT;
 50
 51	return 0;
 52}
 53
 54static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum)
 55{
 56	struct mscc_miim_dev *miim = bus->priv;
 57	u32 val;
 58	int ret;
 59
 60	ret = mscc_miim_wait_ready(bus);
 61	if (ret)
 62		goto out;
 63
 64	writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
 65	       (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ,
 66	       miim->regs + MSCC_MIIM_REG_CMD);
 67
 68	ret = mscc_miim_wait_ready(bus);
 69	if (ret)
 70		goto out;
 71
 72	val = readl(miim->regs + MSCC_MIIM_REG_DATA);
 73	if (val & MSCC_MIIM_DATA_ERROR) {
 74		ret = -EIO;
 75		goto out;
 76	}
 77
 78	ret = val & 0xFFFF;
 79out:
 80	return ret;
 81}
 82
 83static int mscc_miim_write(struct mii_bus *bus, int mii_id,
 84			   int regnum, u16 value)
 85{
 86	struct mscc_miim_dev *miim = bus->priv;
 87	int ret;
 88
 89	ret = mscc_miim_wait_ready(bus);
 90	if (ret < 0)
 91		goto out;
 92
 93	writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
 94	       (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) |
 95	       (value << MSCC_MIIM_CMD_WRDATA_SHIFT) |
 96	       MSCC_MIIM_CMD_OPR_WRITE,
 97	       miim->regs + MSCC_MIIM_REG_CMD);
 98
 99out:
100	return ret;
101}
102
103static int mscc_miim_reset(struct mii_bus *bus)
104{
105	struct mscc_miim_dev *miim = bus->priv;
106
107	if (miim->phy_regs) {
108		writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
109		writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
110		mdelay(500);
111	}
112
113	return 0;
114}
115
116static int mscc_miim_probe(struct platform_device *pdev)
117{
118	struct resource *res;
119	struct mii_bus *bus;
120	struct mscc_miim_dev *dev;
121	int ret;
122
123	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
124	if (!res)
125		return -ENODEV;
126
127	bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev));
128	if (!bus)
129		return -ENOMEM;
130
131	bus->name = "mscc_miim";
132	bus->read = mscc_miim_read;
133	bus->write = mscc_miim_write;
134	bus->reset = mscc_miim_reset;
135	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
136	bus->parent = &pdev->dev;
137
138	dev = bus->priv;
139	dev->regs = devm_ioremap_resource(&pdev->dev, res);
140	if (IS_ERR(dev->regs)) {
141		dev_err(&pdev->dev, "Unable to map MIIM registers\n");
142		return PTR_ERR(dev->regs);
143	}
144
145	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
146	if (res) {
147		dev->phy_regs = devm_ioremap_resource(&pdev->dev, res);
148		if (IS_ERR(dev->phy_regs)) {
149			dev_err(&pdev->dev, "Unable to map internal phy registers\n");
150			return PTR_ERR(dev->phy_regs);
151		}
152	}
153
154	ret = of_mdiobus_register(bus, pdev->dev.of_node);
155	if (ret < 0) {
156		dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
157		return ret;
158	}
159
160	platform_set_drvdata(pdev, bus);
161
162	return 0;
163}
164
165static int mscc_miim_remove(struct platform_device *pdev)
166{
167	struct mii_bus *bus = platform_get_drvdata(pdev);
168
169	mdiobus_unregister(bus);
170
171	return 0;
172}
173
174static const struct of_device_id mscc_miim_match[] = {
175	{ .compatible = "mscc,ocelot-miim" },
176	{ }
177};
178MODULE_DEVICE_TABLE(of, mscc_miim_match);
179
180static struct platform_driver mscc_miim_driver = {
181	.probe = mscc_miim_probe,
182	.remove = mscc_miim_remove,
183	.driver = {
184		.name = "mscc-miim",
185		.of_match_table = mscc_miim_match,
186	},
187};
188
189module_platform_driver(mscc_miim_driver);
190
191MODULE_DESCRIPTION("Microsemi MIIM driver");
192MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
193MODULE_LICENSE("Dual MIT/GPL");