linux/drivers/net/mdio/mdio-mscc-miim.c
<<
>>
Prefs
   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        ret = mscc_miim_wait_pending(bus);
  80        if (ret)
  81                goto out;
  82
  83        writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
  84               (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ,
  85               miim->regs + MSCC_MIIM_REG_CMD);
  86
  87        ret = mscc_miim_wait_ready(bus);
  88        if (ret)
  89                goto out;
  90
  91        val = readl(miim->regs + MSCC_MIIM_REG_DATA);
  92        if (val & MSCC_MIIM_DATA_ERROR) {
  93                ret = -EIO;
  94                goto out;
  95        }
  96
  97        ret = val & 0xFFFF;
  98out:
  99        return ret;
 100}
 101
 102static int mscc_miim_write(struct mii_bus *bus, int mii_id,
 103                           int regnum, u16 value)
 104{
 105        struct mscc_miim_dev *miim = bus->priv;
 106        int ret;
 107
 108        ret = mscc_miim_wait_pending(bus);
 109        if (ret < 0)
 110                goto out;
 111
 112        writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
 113               (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) |
 114               (value << MSCC_MIIM_CMD_WRDATA_SHIFT) |
 115               MSCC_MIIM_CMD_OPR_WRITE,
 116               miim->regs + MSCC_MIIM_REG_CMD);
 117
 118out:
 119        return ret;
 120}
 121
 122static int mscc_miim_reset(struct mii_bus *bus)
 123{
 124        struct mscc_miim_dev *miim = bus->priv;
 125
 126        if (miim->phy_regs) {
 127                writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
 128                writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
 129                mdelay(500);
 130        }
 131
 132        return 0;
 133}
 134
 135static int mscc_miim_probe(struct platform_device *pdev)
 136{
 137        struct mscc_miim_dev *dev;
 138        struct resource *res;
 139        struct mii_bus *bus;
 140        int ret;
 141
 142        bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev));
 143        if (!bus)
 144                return -ENOMEM;
 145
 146        bus->name = "mscc_miim";
 147        bus->read = mscc_miim_read;
 148        bus->write = mscc_miim_write;
 149        bus->reset = mscc_miim_reset;
 150        snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
 151        bus->parent = &pdev->dev;
 152
 153        dev = bus->priv;
 154        dev->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
 155        if (IS_ERR(dev->regs)) {
 156                dev_err(&pdev->dev, "Unable to map MIIM registers\n");
 157                return PTR_ERR(dev->regs);
 158        }
 159
 160        /* This resource is optional */
 161        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 162        if (res) {
 163                dev->phy_regs = devm_ioremap_resource(&pdev->dev, res);
 164                if (IS_ERR(dev->phy_regs)) {
 165                        dev_err(&pdev->dev, "Unable to map internal phy registers\n");
 166                        return PTR_ERR(dev->phy_regs);
 167                }
 168        }
 169
 170        ret = of_mdiobus_register(bus, pdev->dev.of_node);
 171        if (ret < 0) {
 172                dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
 173                return ret;
 174        }
 175
 176        platform_set_drvdata(pdev, bus);
 177
 178        return 0;
 179}
 180
 181static int mscc_miim_remove(struct platform_device *pdev)
 182{
 183        struct mii_bus *bus = platform_get_drvdata(pdev);
 184
 185        mdiobus_unregister(bus);
 186
 187        return 0;
 188}
 189
 190static const struct of_device_id mscc_miim_match[] = {
 191        { .compatible = "mscc,ocelot-miim" },
 192        { }
 193};
 194MODULE_DEVICE_TABLE(of, mscc_miim_match);
 195
 196static struct platform_driver mscc_miim_driver = {
 197        .probe = mscc_miim_probe,
 198        .remove = mscc_miim_remove,
 199        .driver = {
 200                .name = "mscc-miim",
 201                .of_match_table = mscc_miim_match,
 202        },
 203};
 204
 205module_platform_driver(mscc_miim_driver);
 206
 207MODULE_DESCRIPTION("Microsemi MIIM driver");
 208MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
 209MODULE_LICENSE("Dual MIT/GPL");
 210