linux/drivers/net/mdio/mdio-moxart.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver
   3 *
   4 * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com>
   5 */
   6
   7#include <linux/delay.h>
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/mutex.h>
  11#include <linux/of_address.h>
  12#include <linux/of_mdio.h>
  13#include <linux/phy.h>
  14#include <linux/platform_device.h>
  15
  16#define REG_PHY_CTRL            0
  17#define REG_PHY_WRITE_DATA      4
  18
  19/* REG_PHY_CTRL */
  20#define MIIWR                   BIT(27) /* init write sequence (auto cleared)*/
  21#define MIIRD                   BIT(26)
  22#define REGAD_MASK              0x3e00000
  23#define PHYAD_MASK              0x1f0000
  24#define MIIRDATA_MASK           0xffff
  25
  26/* REG_PHY_WRITE_DATA */
  27#define MIIWDATA_MASK           0xffff
  28
  29struct moxart_mdio_data {
  30        void __iomem            *base;
  31};
  32
  33static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
  34{
  35        struct moxart_mdio_data *data = bus->priv;
  36        u32 ctrl = 0;
  37        unsigned int count = 5;
  38
  39        dev_dbg(&bus->dev, "%s\n", __func__);
  40
  41        ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) |
  42                ((regnum << 21) & REGAD_MASK);
  43
  44        writel(ctrl, data->base + REG_PHY_CTRL);
  45
  46        do {
  47                ctrl = readl(data->base + REG_PHY_CTRL);
  48
  49                if (!(ctrl & MIIRD))
  50                        return ctrl & MIIRDATA_MASK;
  51
  52                mdelay(10);
  53                count--;
  54        } while (count > 0);
  55
  56        dev_dbg(&bus->dev, "%s timed out\n", __func__);
  57
  58        return -ETIMEDOUT;
  59}
  60
  61static int moxart_mdio_write(struct mii_bus *bus, int mii_id,
  62                             int regnum, u16 value)
  63{
  64        struct moxart_mdio_data *data = bus->priv;
  65        u32 ctrl = 0;
  66        unsigned int count = 5;
  67
  68        dev_dbg(&bus->dev, "%s\n", __func__);
  69
  70        ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) |
  71                ((regnum << 21) & REGAD_MASK);
  72
  73        value &= MIIWDATA_MASK;
  74
  75        writel(value, data->base + REG_PHY_WRITE_DATA);
  76        writel(ctrl, data->base + REG_PHY_CTRL);
  77
  78        do {
  79                ctrl = readl(data->base + REG_PHY_CTRL);
  80
  81                if (!(ctrl & MIIWR))
  82                        return 0;
  83
  84                mdelay(10);
  85                count--;
  86        } while (count > 0);
  87
  88        dev_dbg(&bus->dev, "%s timed out\n", __func__);
  89
  90        return -ETIMEDOUT;
  91}
  92
  93static int moxart_mdio_reset(struct mii_bus *bus)
  94{
  95        int data, i;
  96
  97        for (i = 0; i < PHY_MAX_ADDR; i++) {
  98                data = moxart_mdio_read(bus, i, MII_BMCR);
  99                if (data < 0)
 100                        continue;
 101
 102                data |= BMCR_RESET;
 103                if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0)
 104                        continue;
 105        }
 106
 107        return 0;
 108}
 109
 110static int moxart_mdio_probe(struct platform_device *pdev)
 111{
 112        struct device_node *np = pdev->dev.of_node;
 113        struct mii_bus *bus;
 114        struct moxart_mdio_data *data;
 115        int ret, i;
 116
 117        bus = mdiobus_alloc_size(sizeof(*data));
 118        if (!bus)
 119                return -ENOMEM;
 120
 121        bus->name = "MOXA ART Ethernet MII";
 122        bus->read = &moxart_mdio_read;
 123        bus->write = &moxart_mdio_write;
 124        bus->reset = &moxart_mdio_reset;
 125        snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id);
 126        bus->parent = &pdev->dev;
 127
 128        /* Setting PHY_MAC_INTERRUPT here even if it has no effect,
 129         * of_mdiobus_register() sets these PHY_POLL.
 130         * Ideally, the interrupt from MAC controller could be used to
 131         * detect link state changes, not polling, i.e. if there was
 132         * a way phy_driver could set PHY_HAS_INTERRUPT but have that
 133         * interrupt handled in ethernet drivercode.
 134         */
 135        for (i = 0; i < PHY_MAX_ADDR; i++)
 136                bus->irq[i] = PHY_MAC_INTERRUPT;
 137
 138        data = bus->priv;
 139        data->base = devm_platform_ioremap_resource(pdev, 0);
 140        if (IS_ERR(data->base)) {
 141                ret = PTR_ERR(data->base);
 142                goto err_out_free_mdiobus;
 143        }
 144
 145        ret = of_mdiobus_register(bus, np);
 146        if (ret < 0)
 147                goto err_out_free_mdiobus;
 148
 149        platform_set_drvdata(pdev, bus);
 150
 151        return 0;
 152
 153err_out_free_mdiobus:
 154        mdiobus_free(bus);
 155        return ret;
 156}
 157
 158static int moxart_mdio_remove(struct platform_device *pdev)
 159{
 160        struct mii_bus *bus = platform_get_drvdata(pdev);
 161
 162        mdiobus_unregister(bus);
 163        mdiobus_free(bus);
 164
 165        return 0;
 166}
 167
 168static const struct of_device_id moxart_mdio_dt_ids[] = {
 169        { .compatible = "moxa,moxart-mdio" },
 170        { }
 171};
 172MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids);
 173
 174static struct platform_driver moxart_mdio_driver = {
 175        .probe = moxart_mdio_probe,
 176        .remove = moxart_mdio_remove,
 177        .driver = {
 178                .name = "moxart-mdio",
 179                .of_match_table = moxart_mdio_dt_ids,
 180        },
 181};
 182
 183module_platform_driver(moxart_mdio_driver);
 184
 185MODULE_DESCRIPTION("MOXA ART MDIO interface driver");
 186MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
 187MODULE_LICENSE("GPL v2");
 188