18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT) 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for the MDIO interface of Microsemi network switches. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Author: Alexandre Belloni <alexandre.belloni@bootlin.com> 68c2ecf20Sopenharmony_ci * Copyright (c) 2017 Microsemi Corporation 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/bitops.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/of_mdio.h> 158c2ecf20Sopenharmony_ci#include <linux/phy.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define MSCC_MIIM_REG_STATUS 0x0 198c2ecf20Sopenharmony_ci#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) 208c2ecf20Sopenharmony_ci#define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) 218c2ecf20Sopenharmony_ci#define MSCC_MIIM_REG_CMD 0x8 228c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_OPR_WRITE BIT(1) 238c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_OPR_READ BIT(2) 248c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_WRDATA_SHIFT 4 258c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_REGAD_SHIFT 20 268c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_PHYAD_SHIFT 25 278c2ecf20Sopenharmony_ci#define MSCC_MIIM_CMD_VLD BIT(31) 288c2ecf20Sopenharmony_ci#define MSCC_MIIM_REG_DATA 0xC 298c2ecf20Sopenharmony_ci#define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define MSCC_PHY_REG_PHY_CFG 0x0 328c2ecf20Sopenharmony_ci#define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3)) 338c2ecf20Sopenharmony_ci#define PHY_CFG_PHY_COMMON_RESET BIT(4) 348c2ecf20Sopenharmony_ci#define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8)) 358c2ecf20Sopenharmony_ci#define MSCC_PHY_REG_PHY_STATUS 0x4 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct mscc_miim_dev { 388c2ecf20Sopenharmony_ci void __iomem *regs; 398c2ecf20Sopenharmony_ci void __iomem *phy_regs; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci/* When high resolution timers aren't built-in: we can't use usleep_range() as 438c2ecf20Sopenharmony_ci * we would sleep way too long. Use udelay() instead. 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ci#define mscc_readl_poll_timeout(addr, val, cond, delay_us, timeout_us) \ 468c2ecf20Sopenharmony_ci({ \ 478c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ 488c2ecf20Sopenharmony_ci readl_poll_timeout_atomic(addr, val, cond, delay_us, \ 498c2ecf20Sopenharmony_ci timeout_us); \ 508c2ecf20Sopenharmony_ci readl_poll_timeout(addr, val, cond, delay_us, timeout_us); \ 518c2ecf20Sopenharmony_ci}) 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic int mscc_miim_wait_ready(struct mii_bus *bus) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct mscc_miim_dev *miim = bus->priv; 568c2ecf20Sopenharmony_ci u32 val; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, 598c2ecf20Sopenharmony_ci !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, 608c2ecf20Sopenharmony_ci 10000); 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic int mscc_miim_wait_pending(struct mii_bus *bus) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci struct mscc_miim_dev *miim = bus->priv; 668c2ecf20Sopenharmony_ci u32 val; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, 698c2ecf20Sopenharmony_ci !(val & MSCC_MIIM_STATUS_STAT_PENDING), 708c2ecf20Sopenharmony_ci 50, 10000); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct mscc_miim_dev *miim = bus->priv; 768c2ecf20Sopenharmony_ci u32 val; 778c2ecf20Sopenharmony_ci int ret; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (regnum & MII_ADDR_C45) 808c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci ret = mscc_miim_wait_pending(bus); 838c2ecf20Sopenharmony_ci if (ret) 848c2ecf20Sopenharmony_ci goto out; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | 878c2ecf20Sopenharmony_ci (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ, 888c2ecf20Sopenharmony_ci miim->regs + MSCC_MIIM_REG_CMD); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci ret = mscc_miim_wait_ready(bus); 918c2ecf20Sopenharmony_ci if (ret) 928c2ecf20Sopenharmony_ci goto out; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci val = readl(miim->regs + MSCC_MIIM_REG_DATA); 958c2ecf20Sopenharmony_ci if (val & MSCC_MIIM_DATA_ERROR) { 968c2ecf20Sopenharmony_ci ret = -EIO; 978c2ecf20Sopenharmony_ci goto out; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci ret = val & 0xFFFF; 1018c2ecf20Sopenharmony_ciout: 1028c2ecf20Sopenharmony_ci return ret; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int mscc_miim_write(struct mii_bus *bus, int mii_id, 1068c2ecf20Sopenharmony_ci int regnum, u16 value) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct mscc_miim_dev *miim = bus->priv; 1098c2ecf20Sopenharmony_ci int ret; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (regnum & MII_ADDR_C45) 1128c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci ret = mscc_miim_wait_pending(bus); 1158c2ecf20Sopenharmony_ci if (ret < 0) 1168c2ecf20Sopenharmony_ci goto out; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | 1198c2ecf20Sopenharmony_ci (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | 1208c2ecf20Sopenharmony_ci (value << MSCC_MIIM_CMD_WRDATA_SHIFT) | 1218c2ecf20Sopenharmony_ci MSCC_MIIM_CMD_OPR_WRITE, 1228c2ecf20Sopenharmony_ci miim->regs + MSCC_MIIM_REG_CMD); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ciout: 1258c2ecf20Sopenharmony_ci return ret; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int mscc_miim_reset(struct mii_bus *bus) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct mscc_miim_dev *miim = bus->priv; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (miim->phy_regs) { 1338c2ecf20Sopenharmony_ci writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); 1348c2ecf20Sopenharmony_ci writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); 1358c2ecf20Sopenharmony_ci mdelay(500); 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int mscc_miim_probe(struct platform_device *pdev) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci struct resource *res; 1448c2ecf20Sopenharmony_ci struct mii_bus *bus; 1458c2ecf20Sopenharmony_ci struct mscc_miim_dev *dev; 1468c2ecf20Sopenharmony_ci int ret; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1498c2ecf20Sopenharmony_ci if (!res) 1508c2ecf20Sopenharmony_ci return -ENODEV; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev)); 1538c2ecf20Sopenharmony_ci if (!bus) 1548c2ecf20Sopenharmony_ci return -ENOMEM; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci bus->name = "mscc_miim"; 1578c2ecf20Sopenharmony_ci bus->read = mscc_miim_read; 1588c2ecf20Sopenharmony_ci bus->write = mscc_miim_write; 1598c2ecf20Sopenharmony_ci bus->reset = mscc_miim_reset; 1608c2ecf20Sopenharmony_ci snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); 1618c2ecf20Sopenharmony_ci bus->parent = &pdev->dev; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci dev = bus->priv; 1648c2ecf20Sopenharmony_ci dev->regs = devm_ioremap_resource(&pdev->dev, res); 1658c2ecf20Sopenharmony_ci if (IS_ERR(dev->regs)) { 1668c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to map MIIM registers\n"); 1678c2ecf20Sopenharmony_ci return PTR_ERR(dev->regs); 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 1718c2ecf20Sopenharmony_ci if (res) { 1728c2ecf20Sopenharmony_ci dev->phy_regs = devm_ioremap_resource(&pdev->dev, res); 1738c2ecf20Sopenharmony_ci if (IS_ERR(dev->phy_regs)) { 1748c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to map internal phy registers\n"); 1758c2ecf20Sopenharmony_ci return PTR_ERR(dev->phy_regs); 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = of_mdiobus_register(bus, pdev->dev.of_node); 1808c2ecf20Sopenharmony_ci if (ret < 0) { 1818c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); 1828c2ecf20Sopenharmony_ci return ret; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, bus); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic int mscc_miim_remove(struct platform_device *pdev) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct mii_bus *bus = platform_get_drvdata(pdev); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci mdiobus_unregister(bus); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic const struct of_device_id mscc_miim_match[] = { 2008c2ecf20Sopenharmony_ci { .compatible = "mscc,ocelot-miim" }, 2018c2ecf20Sopenharmony_ci { } 2028c2ecf20Sopenharmony_ci}; 2038c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mscc_miim_match); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic struct platform_driver mscc_miim_driver = { 2068c2ecf20Sopenharmony_ci .probe = mscc_miim_probe, 2078c2ecf20Sopenharmony_ci .remove = mscc_miim_remove, 2088c2ecf20Sopenharmony_ci .driver = { 2098c2ecf20Sopenharmony_ci .name = "mscc-miim", 2108c2ecf20Sopenharmony_ci .of_match_table = mscc_miim_match, 2118c2ecf20Sopenharmony_ci }, 2128c2ecf20Sopenharmony_ci}; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cimodule_platform_driver(mscc_miim_driver); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Microsemi MIIM driver"); 2178c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>"); 2188c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual MIT/GPL"); 219