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