Skip to content

Commit 542671f

Browse files
alexandrebellonidavem330
authored andcommitted
net: phy: mscc-miim: Add MDIO driver
Add a driver for the Microsemi MII Management controller (MIIM) found on Microsemi SoCs. On Ocelot, there are two controllers, one is connected to the internal PHYs, the other one can communicate with external PHYs. Reviewed-by: Andrew Lunn <[email protected]> Signed-off-by: Alexandre Belloni <[email protected]> Reviewed-by: Florian Fainelli <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent cd1436a commit 542671f

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

drivers/net/phy/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ config MDIO_MOXART
123123
This driver supports the MDIO interface found in the network
124124
interface units of the MOXA ART SoC
125125

126+
config MDIO_MSCC_MIIM
127+
tristate "Microsemi MIIM interface support"
128+
depends on HAS_IOMEM
129+
help
130+
This driver supports the MIIM (MDIO) interface found in the network
131+
switches of the Microsemi SoCs
132+
126133
config MDIO_OCTEON
127134
tristate "Octeon and some ThunderX SOCs MDIO buses"
128135
depends on 64BIT

drivers/net/phy/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
3434
obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o
3535
obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o
3636
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
37+
obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
3738
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
3839
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
3940
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o

drivers/net/phy/mdio-mscc-miim.c

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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 <[email protected]>
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+
36+
struct mscc_miim_dev {
37+
void __iomem *regs;
38+
void __iomem *phy_regs;
39+
};
40+
41+
static 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+
54+
static 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;
79+
out:
80+
return ret;
81+
}
82+
83+
static 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+
99+
out:
100+
return ret;
101+
}
102+
103+
static 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+
116+
static 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+
if (pdev->dev.of_node)
155+
ret = of_mdiobus_register(bus, pdev->dev.of_node);
156+
else
157+
ret = mdiobus_register(bus);
158+
159+
if (ret < 0) {
160+
dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
161+
return ret;
162+
}
163+
164+
platform_set_drvdata(pdev, bus);
165+
166+
return 0;
167+
}
168+
169+
static int mscc_miim_remove(struct platform_device *pdev)
170+
{
171+
struct mii_bus *bus = platform_get_drvdata(pdev);
172+
173+
mdiobus_unregister(bus);
174+
175+
return 0;
176+
}
177+
178+
static const struct of_device_id mscc_miim_match[] = {
179+
{ .compatible = "mscc,ocelot-miim" },
180+
{ }
181+
};
182+
MODULE_DEVICE_TABLE(of, mscc_miim_match);
183+
184+
static struct platform_driver mscc_miim_driver = {
185+
.probe = mscc_miim_probe,
186+
.remove = mscc_miim_remove,
187+
.driver = {
188+
.name = "mscc-miim",
189+
.of_match_table = mscc_miim_match,
190+
},
191+
};
192+
193+
module_platform_driver(mscc_miim_driver);
194+
195+
MODULE_DESCRIPTION("Microsemi MIIM driver");
196+
MODULE_AUTHOR("Alexandre Belloni <[email protected]>");
197+
MODULE_LICENSE("Dual MIT/GPL");

0 commit comments

Comments
 (0)