1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
3 * Driver for the MDIO interface of Microsemi network switches.
5 * Author: Alexandre Belloni <alexandre.belloni@bootlin.com>
6 * Copyright (c) 2017 Microsemi Corporation
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>
15 #include <linux/iopoll.h>
16 #include <linux/of_mdio.h>
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))
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
36 struct mscc_miim_dev
{
38 void __iomem
*phy_regs
;
41 static int mscc_miim_wait_ready(struct mii_bus
*bus
)
43 struct mscc_miim_dev
*miim
= bus
->priv
;
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
)
54 static int mscc_miim_read(struct mii_bus
*bus
, int mii_id
, int regnum
)
56 struct mscc_miim_dev
*miim
= bus
->priv
;
60 ret
= mscc_miim_wait_ready(bus
);
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
);
68 ret
= mscc_miim_wait_ready(bus
);
72 val
= readl(miim
->regs
+ MSCC_MIIM_REG_DATA
);
73 if (val
& MSCC_MIIM_DATA_ERROR
) {
83 static int mscc_miim_write(struct mii_bus
*bus
, int mii_id
,
84 int regnum
, u16 value
)
86 struct mscc_miim_dev
*miim
= bus
->priv
;
89 ret
= mscc_miim_wait_ready(bus
);
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
);
103 static int mscc_miim_reset(struct mii_bus
*bus
)
105 struct mscc_miim_dev
*miim
= bus
->priv
;
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
);
116 static int mscc_miim_probe(struct platform_device
*pdev
)
118 struct resource
*res
;
120 struct mscc_miim_dev
*dev
;
123 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
127 bus
= devm_mdiobus_alloc_size(&pdev
->dev
, sizeof(*dev
));
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
;
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
);
145 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 1);
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
);
154 ret
= of_mdiobus_register(bus
, pdev
->dev
.of_node
);
156 dev_err(&pdev
->dev
, "Cannot register MDIO bus (%d)\n", ret
);
160 platform_set_drvdata(pdev
, bus
);
165 static int mscc_miim_remove(struct platform_device
*pdev
)
167 struct mii_bus
*bus
= platform_get_drvdata(pdev
);
169 mdiobus_unregister(bus
);
174 static const struct of_device_id mscc_miim_match
[] = {
175 { .compatible
= "mscc,ocelot-miim" },
178 MODULE_DEVICE_TABLE(of
, mscc_miim_match
);
180 static struct platform_driver mscc_miim_driver
= {
181 .probe
= mscc_miim_probe
,
182 .remove
= mscc_miim_remove
,
185 .of_match_table
= mscc_miim_match
,
189 module_platform_driver(mscc_miim_driver
);
191 MODULE_DESCRIPTION("Microsemi MIIM driver");
192 MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
193 MODULE_LICENSE("Dual MIT/GPL");