mdio-mscc-miim.c (9636B)
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/clk.h> 11#include <linux/io.h> 12#include <linux/iopoll.h> 13#include <linux/kernel.h> 14#include <linux/mdio/mdio-mscc-miim.h> 15#include <linux/module.h> 16#include <linux/of_mdio.h> 17#include <linux/phy.h> 18#include <linux/platform_device.h> 19#include <linux/property.h> 20#include <linux/regmap.h> 21 22#define MSCC_MIIM_REG_STATUS 0x0 23#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) 24#define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) 25#define MSCC_MIIM_REG_CMD 0x8 26#define MSCC_MIIM_CMD_OPR_WRITE BIT(1) 27#define MSCC_MIIM_CMD_OPR_READ BIT(2) 28#define MSCC_MIIM_CMD_WRDATA_SHIFT 4 29#define MSCC_MIIM_CMD_REGAD_SHIFT 20 30#define MSCC_MIIM_CMD_PHYAD_SHIFT 25 31#define MSCC_MIIM_CMD_VLD BIT(31) 32#define MSCC_MIIM_REG_DATA 0xC 33#define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) 34#define MSCC_MIIM_REG_CFG 0x10 35#define MSCC_MIIM_CFG_PRESCALE_MASK GENMASK(7, 0) 36 37#define MSCC_PHY_REG_PHY_CFG 0x0 38#define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3)) 39#define PHY_CFG_PHY_COMMON_RESET BIT(4) 40#define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8)) 41#define MSCC_PHY_REG_PHY_STATUS 0x4 42 43#define LAN966X_CUPHY_COMMON_CFG 0x0 44#define CUPHY_COMMON_CFG_RESET_N BIT(0) 45 46struct mscc_miim_info { 47 unsigned int phy_reset_offset; 48 unsigned int phy_reset_bits; 49}; 50 51struct mscc_miim_dev { 52 struct regmap *regs; 53 int mii_status_offset; 54 struct regmap *phy_regs; 55 const struct mscc_miim_info *info; 56 struct clk *clk; 57 u32 bus_freq; 58}; 59 60/* When high resolution timers aren't built-in: we can't use usleep_range() as 61 * we would sleep way too long. Use udelay() instead. 62 */ 63#define mscc_readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us)\ 64({ \ 65 if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ 66 readx_poll_timeout_atomic(op, addr, val, cond, delay_us, \ 67 timeout_us); \ 68 readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us); \ 69}) 70 71static int mscc_miim_status(struct mii_bus *bus) 72{ 73 struct mscc_miim_dev *miim = bus->priv; 74 int val, ret; 75 76 ret = regmap_read(miim->regs, 77 MSCC_MIIM_REG_STATUS + miim->mii_status_offset, &val); 78 if (ret < 0) { 79 WARN_ONCE(1, "mscc miim status read error %d\n", ret); 80 return ret; 81 } 82 83 return val; 84} 85 86static int mscc_miim_wait_ready(struct mii_bus *bus) 87{ 88 u32 val; 89 90 return mscc_readx_poll_timeout(mscc_miim_status, bus, val, 91 !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, 92 10000); 93} 94 95static int mscc_miim_wait_pending(struct mii_bus *bus) 96{ 97 u32 val; 98 99 return mscc_readx_poll_timeout(mscc_miim_status, bus, val, 100 !(val & MSCC_MIIM_STATUS_STAT_PENDING), 101 50, 10000); 102} 103 104static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum) 105{ 106 struct mscc_miim_dev *miim = bus->priv; 107 u32 val; 108 int ret; 109 110 if (regnum & MII_ADDR_C45) 111 return -EOPNOTSUPP; 112 113 ret = mscc_miim_wait_pending(bus); 114 if (ret) 115 goto out; 116 117 ret = regmap_write(miim->regs, 118 MSCC_MIIM_REG_CMD + miim->mii_status_offset, 119 MSCC_MIIM_CMD_VLD | 120 (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | 121 (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | 122 MSCC_MIIM_CMD_OPR_READ); 123 124 if (ret < 0) { 125 WARN_ONCE(1, "mscc miim write cmd reg error %d\n", ret); 126 goto out; 127 } 128 129 ret = mscc_miim_wait_ready(bus); 130 if (ret) 131 goto out; 132 133 ret = regmap_read(miim->regs, 134 MSCC_MIIM_REG_DATA + miim->mii_status_offset, &val); 135 if (ret < 0) { 136 WARN_ONCE(1, "mscc miim read data reg error %d\n", ret); 137 goto out; 138 } 139 140 if (val & MSCC_MIIM_DATA_ERROR) { 141 ret = -EIO; 142 goto out; 143 } 144 145 ret = val & 0xFFFF; 146out: 147 return ret; 148} 149 150static int mscc_miim_write(struct mii_bus *bus, int mii_id, 151 int regnum, u16 value) 152{ 153 struct mscc_miim_dev *miim = bus->priv; 154 int ret; 155 156 if (regnum & MII_ADDR_C45) 157 return -EOPNOTSUPP; 158 159 ret = mscc_miim_wait_pending(bus); 160 if (ret < 0) 161 goto out; 162 163 ret = regmap_write(miim->regs, 164 MSCC_MIIM_REG_CMD + miim->mii_status_offset, 165 MSCC_MIIM_CMD_VLD | 166 (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | 167 (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | 168 (value << MSCC_MIIM_CMD_WRDATA_SHIFT) | 169 MSCC_MIIM_CMD_OPR_WRITE); 170 171 if (ret < 0) 172 WARN_ONCE(1, "mscc miim write error %d\n", ret); 173out: 174 return ret; 175} 176 177static int mscc_miim_reset(struct mii_bus *bus) 178{ 179 struct mscc_miim_dev *miim = bus->priv; 180 unsigned int offset, bits; 181 int ret; 182 183 if (!miim->phy_regs) 184 return 0; 185 186 offset = miim->info->phy_reset_offset; 187 bits = miim->info->phy_reset_bits; 188 189 ret = regmap_update_bits(miim->phy_regs, offset, bits, 0); 190 if (ret < 0) { 191 WARN_ONCE(1, "mscc reset set error %d\n", ret); 192 return ret; 193 } 194 195 ret = regmap_update_bits(miim->phy_regs, offset, bits, bits); 196 if (ret < 0) { 197 WARN_ONCE(1, "mscc reset clear error %d\n", ret); 198 return ret; 199 } 200 201 mdelay(500); 202 203 return 0; 204} 205 206static const struct regmap_config mscc_miim_regmap_config = { 207 .reg_bits = 32, 208 .val_bits = 32, 209 .reg_stride = 4, 210}; 211 212static const struct regmap_config mscc_miim_phy_regmap_config = { 213 .reg_bits = 32, 214 .val_bits = 32, 215 .reg_stride = 4, 216 .name = "phy", 217}; 218 219int mscc_miim_setup(struct device *dev, struct mii_bus **pbus, const char *name, 220 struct regmap *mii_regmap, int status_offset) 221{ 222 struct mscc_miim_dev *miim; 223 struct mii_bus *bus; 224 225 bus = devm_mdiobus_alloc_size(dev, sizeof(*miim)); 226 if (!bus) 227 return -ENOMEM; 228 229 bus->name = name; 230 bus->read = mscc_miim_read; 231 bus->write = mscc_miim_write; 232 bus->reset = mscc_miim_reset; 233 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); 234 bus->parent = dev; 235 236 miim = bus->priv; 237 238 *pbus = bus; 239 240 miim->regs = mii_regmap; 241 miim->mii_status_offset = status_offset; 242 243 *pbus = bus; 244 245 return 0; 246} 247EXPORT_SYMBOL(mscc_miim_setup); 248 249static int mscc_miim_clk_set(struct mii_bus *bus) 250{ 251 struct mscc_miim_dev *miim = bus->priv; 252 unsigned long rate; 253 u32 div; 254 255 /* Keep the current settings */ 256 if (!miim->bus_freq) 257 return 0; 258 259 rate = clk_get_rate(miim->clk); 260 261 div = DIV_ROUND_UP(rate, 2 * miim->bus_freq) - 1; 262 if (div == 0 || div & ~MSCC_MIIM_CFG_PRESCALE_MASK) { 263 dev_err(&bus->dev, "Incorrect MDIO clock frequency\n"); 264 return -EINVAL; 265 } 266 267 return regmap_update_bits(miim->regs, MSCC_MIIM_REG_CFG, 268 MSCC_MIIM_CFG_PRESCALE_MASK, div); 269} 270 271static int mscc_miim_probe(struct platform_device *pdev) 272{ 273 struct regmap *mii_regmap, *phy_regmap = NULL; 274 struct device_node *np = pdev->dev.of_node; 275 struct device *dev = &pdev->dev; 276 void __iomem *regs, *phy_regs; 277 struct mscc_miim_dev *miim; 278 struct resource *res; 279 struct mii_bus *bus; 280 int ret; 281 282 regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 283 if (IS_ERR(regs)) { 284 dev_err(dev, "Unable to map MIIM registers\n"); 285 return PTR_ERR(regs); 286 } 287 288 mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config); 289 290 if (IS_ERR(mii_regmap)) { 291 dev_err(dev, "Unable to create MIIM regmap\n"); 292 return PTR_ERR(mii_regmap); 293 } 294 295 /* This resource is optional */ 296 res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 297 if (res) { 298 phy_regs = devm_ioremap_resource(dev, res); 299 if (IS_ERR(phy_regs)) { 300 dev_err(dev, "Unable to map internal phy registers\n"); 301 return PTR_ERR(phy_regs); 302 } 303 304 phy_regmap = devm_regmap_init_mmio(dev, phy_regs, 305 &mscc_miim_phy_regmap_config); 306 if (IS_ERR(phy_regmap)) { 307 dev_err(dev, "Unable to create phy register regmap\n"); 308 return PTR_ERR(phy_regmap); 309 } 310 } 311 312 ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0); 313 if (ret < 0) { 314 dev_err(dev, "Unable to setup the MDIO bus\n"); 315 return ret; 316 } 317 318 miim = bus->priv; 319 miim->phy_regs = phy_regmap; 320 321 miim->info = device_get_match_data(dev); 322 if (!miim->info) 323 return -EINVAL; 324 325 miim->clk = devm_clk_get_optional(dev, NULL); 326 if (IS_ERR(miim->clk)) 327 return PTR_ERR(miim->clk); 328 329 of_property_read_u32(np, "clock-frequency", &miim->bus_freq); 330 331 if (miim->bus_freq && !miim->clk) { 332 dev_err(dev, "cannot use clock-frequency without a clock\n"); 333 return -EINVAL; 334 } 335 336 ret = clk_prepare_enable(miim->clk); 337 if (ret) 338 return ret; 339 340 ret = mscc_miim_clk_set(bus); 341 if (ret) 342 goto out_disable_clk; 343 344 ret = of_mdiobus_register(bus, np); 345 if (ret < 0) { 346 dev_err(dev, "Cannot register MDIO bus (%d)\n", ret); 347 goto out_disable_clk; 348 } 349 350 platform_set_drvdata(pdev, bus); 351 352 return 0; 353 354out_disable_clk: 355 clk_disable_unprepare(miim->clk); 356 return ret; 357} 358 359static int mscc_miim_remove(struct platform_device *pdev) 360{ 361 struct mii_bus *bus = platform_get_drvdata(pdev); 362 struct mscc_miim_dev *miim = bus->priv; 363 364 clk_disable_unprepare(miim->clk); 365 mdiobus_unregister(bus); 366 367 return 0; 368} 369 370static const struct mscc_miim_info mscc_ocelot_miim_info = { 371 .phy_reset_offset = MSCC_PHY_REG_PHY_CFG, 372 .phy_reset_bits = PHY_CFG_PHY_ENA | PHY_CFG_PHY_COMMON_RESET | 373 PHY_CFG_PHY_RESET, 374}; 375 376static const struct mscc_miim_info microchip_lan966x_miim_info = { 377 .phy_reset_offset = LAN966X_CUPHY_COMMON_CFG, 378 .phy_reset_bits = CUPHY_COMMON_CFG_RESET_N, 379}; 380 381static const struct of_device_id mscc_miim_match[] = { 382 { 383 .compatible = "mscc,ocelot-miim", 384 .data = &mscc_ocelot_miim_info 385 }, { 386 .compatible = "microchip,lan966x-miim", 387 .data = µchip_lan966x_miim_info 388 }, 389 { } 390}; 391MODULE_DEVICE_TABLE(of, mscc_miim_match); 392 393static struct platform_driver mscc_miim_driver = { 394 .probe = mscc_miim_probe, 395 .remove = mscc_miim_remove, 396 .driver = { 397 .name = "mscc-miim", 398 .of_match_table = mscc_miim_match, 399 }, 400}; 401 402module_platform_driver(mscc_miim_driver); 403 404MODULE_DESCRIPTION("Microsemi MIIM driver"); 405MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>"); 406MODULE_LICENSE("Dual MIT/GPL");