linux/drivers/net/ethernet/marvell/mvmdio.c
<<
>>
Prefs
   1/*
   2 * Driver for the MDIO interface of Marvell network interfaces.
   3 *
   4 * Since the MDIO interface of Marvell network interfaces is shared
   5 * between all network interfaces, having a single driver allows to
   6 * handle concurrent accesses properly (you may have four Ethernet
   7 * ports, but they in fact share the same SMI interface to access
   8 * the MDIO bus). This driver is currently used by the mvneta and
   9 * mv643xx_eth drivers.
  10 *
  11 * Copyright (C) 2012 Marvell
  12 *
  13 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
  14 *
  15 * This file is licensed under the terms of the GNU General Public
  16 * License version 2. This program is licensed "as is" without any
  17 * warranty of any kind, whether express or implied.
  18 */
  19
  20#include <linux/kernel.h>
  21#include <linux/module.h>
  22#include <linux/mutex.h>
  23#include <linux/phy.h>
  24#include <linux/interrupt.h>
  25#include <linux/platform_device.h>
  26#include <linux/delay.h>
  27#include <linux/io.h>
  28#include <linux/clk.h>
  29#include <linux/of_mdio.h>
  30#include <linux/sched.h>
  31#include <linux/wait.h>
  32
  33#define MVMDIO_SMI_DATA_SHIFT              0
  34#define MVMDIO_SMI_PHY_ADDR_SHIFT          16
  35#define MVMDIO_SMI_PHY_REG_SHIFT           21
  36#define MVMDIO_SMI_READ_OPERATION          BIT(26)
  37#define MVMDIO_SMI_WRITE_OPERATION         0
  38#define MVMDIO_SMI_READ_VALID              BIT(27)
  39#define MVMDIO_SMI_BUSY                    BIT(28)
  40#define MVMDIO_ERR_INT_CAUSE               0x007C
  41#define  MVMDIO_ERR_INT_SMI_DONE           0x00000010
  42#define MVMDIO_ERR_INT_MASK                0x0080
  43
  44/*
  45 * SMI Timeout measurements:
  46 * - Kirkwood 88F6281 (Globalscale Dreamplug): 45us to 95us (Interrupt)
  47 * - Armada 370       (Globalscale Mirabox):   41us to 43us (Polled)
  48 */
  49#define MVMDIO_SMI_TIMEOUT                 1000 /* 1000us = 1ms */
  50#define MVMDIO_SMI_POLL_INTERVAL_MIN       45
  51#define MVMDIO_SMI_POLL_INTERVAL_MAX       55
  52
  53struct orion_mdio_dev {
  54        struct mutex lock;
  55        void __iomem *regs;
  56        struct clk *clk;
  57        /*
  58         * If we have access to the error interrupt pin (which is
  59         * somewhat misnamed as it not only reflects internal errors
  60         * but also reflects SMI completion), use that to wait for
  61         * SMI access completion instead of polling the SMI busy bit.
  62         */
  63        int err_interrupt;
  64        wait_queue_head_t smi_busy_wait;
  65};
  66
  67static int orion_mdio_smi_is_done(struct orion_mdio_dev *dev)
  68{
  69        return !(readl(dev->regs) & MVMDIO_SMI_BUSY);
  70}
  71
  72/* Wait for the SMI unit to be ready for another operation
  73 */
  74static int orion_mdio_wait_ready(struct mii_bus *bus)
  75{
  76        struct orion_mdio_dev *dev = bus->priv;
  77        unsigned long timeout = usecs_to_jiffies(MVMDIO_SMI_TIMEOUT);
  78        unsigned long end = jiffies + timeout;
  79        int timedout = 0;
  80
  81        while (1) {
  82                if (orion_mdio_smi_is_done(dev))
  83                        return 0;
  84                else if (timedout)
  85                        break;
  86
  87                if (dev->err_interrupt <= 0) {
  88                        usleep_range(MVMDIO_SMI_POLL_INTERVAL_MIN,
  89                                     MVMDIO_SMI_POLL_INTERVAL_MAX);
  90
  91                        if (time_is_before_jiffies(end))
  92                                ++timedout;
  93                } else {
  94                        /* wait_event_timeout does not guarantee a delay of at
  95                         * least one whole jiffie, so timeout must be no less
  96                         * than two.
  97                         */
  98                        if (timeout < 2)
  99                                timeout = 2;
 100                        wait_event_timeout(dev->smi_busy_wait,
 101                                           orion_mdio_smi_is_done(dev),
 102                                           timeout);
 103
 104                        ++timedout;
 105                }
 106        }
 107
 108        dev_err(bus->parent, "Timeout: SMI busy for too long\n");
 109        return  -ETIMEDOUT;
 110}
 111
 112static int orion_mdio_read(struct mii_bus *bus, int mii_id,
 113                           int regnum)
 114{
 115        struct orion_mdio_dev *dev = bus->priv;
 116        u32 val;
 117        int ret;
 118
 119        mutex_lock(&dev->lock);
 120
 121        ret = orion_mdio_wait_ready(bus);
 122        if (ret < 0)
 123                goto out;
 124
 125        writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) |
 126                (regnum << MVMDIO_SMI_PHY_REG_SHIFT)  |
 127                MVMDIO_SMI_READ_OPERATION),
 128               dev->regs);
 129
 130        ret = orion_mdio_wait_ready(bus);
 131        if (ret < 0)
 132                goto out;
 133
 134        val = readl(dev->regs);
 135        if (!(val & MVMDIO_SMI_READ_VALID)) {
 136                dev_err(bus->parent, "SMI bus read not valid\n");
 137                ret = -ENODEV;
 138                goto out;
 139        }
 140
 141        ret = val & 0xFFFF;
 142out:
 143        mutex_unlock(&dev->lock);
 144        return ret;
 145}
 146
 147static int orion_mdio_write(struct mii_bus *bus, int mii_id,
 148                            int regnum, u16 value)
 149{
 150        struct orion_mdio_dev *dev = bus->priv;
 151        int ret;
 152
 153        mutex_lock(&dev->lock);
 154
 155        ret = orion_mdio_wait_ready(bus);
 156        if (ret < 0)
 157                goto out;
 158
 159        writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) |
 160                (regnum << MVMDIO_SMI_PHY_REG_SHIFT)  |
 161                MVMDIO_SMI_WRITE_OPERATION            |
 162                (value << MVMDIO_SMI_DATA_SHIFT)),
 163               dev->regs);
 164
 165out:
 166        mutex_unlock(&dev->lock);
 167        return ret;
 168}
 169
 170static irqreturn_t orion_mdio_err_irq(int irq, void *dev_id)
 171{
 172        struct orion_mdio_dev *dev = dev_id;
 173
 174        if (readl(dev->regs + MVMDIO_ERR_INT_CAUSE) &
 175                        MVMDIO_ERR_INT_SMI_DONE) {
 176                writel(~MVMDIO_ERR_INT_SMI_DONE,
 177                                dev->regs + MVMDIO_ERR_INT_CAUSE);
 178                wake_up(&dev->smi_busy_wait);
 179                return IRQ_HANDLED;
 180        }
 181
 182        return IRQ_NONE;
 183}
 184
 185static int orion_mdio_probe(struct platform_device *pdev)
 186{
 187        struct resource *r;
 188        struct mii_bus *bus;
 189        struct orion_mdio_dev *dev;
 190        int i, ret;
 191
 192        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 193        if (!r) {
 194                dev_err(&pdev->dev, "No SMI register address given\n");
 195                return -ENODEV;
 196        }
 197
 198        bus = devm_mdiobus_alloc_size(&pdev->dev,
 199                                      sizeof(struct orion_mdio_dev));
 200        if (!bus)
 201                return -ENOMEM;
 202
 203        bus->name = "orion_mdio_bus";
 204        bus->read = orion_mdio_read;
 205        bus->write = orion_mdio_write;
 206        snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii",
 207                 dev_name(&pdev->dev));
 208        bus->parent = &pdev->dev;
 209
 210        bus->irq = devm_kmalloc_array(&pdev->dev, PHY_MAX_ADDR, sizeof(int),
 211                                      GFP_KERNEL);
 212        if (!bus->irq)
 213                return -ENOMEM;
 214
 215        for (i = 0; i < PHY_MAX_ADDR; i++)
 216                bus->irq[i] = PHY_POLL;
 217
 218        dev = bus->priv;
 219        dev->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
 220        if (!dev->regs) {
 221                dev_err(&pdev->dev, "Unable to remap SMI register\n");
 222                ret = -ENODEV;
 223                goto out_mdio;
 224        }
 225
 226        init_waitqueue_head(&dev->smi_busy_wait);
 227
 228        dev->clk = devm_clk_get(&pdev->dev, NULL);
 229        if (!IS_ERR(dev->clk))
 230                clk_prepare_enable(dev->clk);
 231
 232        dev->err_interrupt = platform_get_irq(pdev, 0);
 233        if (dev->err_interrupt > 0) {
 234                ret = devm_request_irq(&pdev->dev, dev->err_interrupt,
 235                                        orion_mdio_err_irq,
 236                                        IRQF_SHARED, pdev->name, dev);
 237                if (ret)
 238                        goto out_mdio;
 239
 240                writel(MVMDIO_ERR_INT_SMI_DONE,
 241                        dev->regs + MVMDIO_ERR_INT_MASK);
 242
 243        } else if (dev->err_interrupt == -EPROBE_DEFER) {
 244                return -EPROBE_DEFER;
 245        }
 246
 247        mutex_init(&dev->lock);
 248
 249        if (pdev->dev.of_node)
 250                ret = of_mdiobus_register(bus, pdev->dev.of_node);
 251        else
 252                ret = mdiobus_register(bus);
 253        if (ret < 0) {
 254                dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
 255                goto out_mdio;
 256        }
 257
 258        platform_set_drvdata(pdev, bus);
 259
 260        return 0;
 261
 262out_mdio:
 263        if (!IS_ERR(dev->clk))
 264                clk_disable_unprepare(dev->clk);
 265        return ret;
 266}
 267
 268static int orion_mdio_remove(struct platform_device *pdev)
 269{
 270        struct mii_bus *bus = platform_get_drvdata(pdev);
 271        struct orion_mdio_dev *dev = bus->priv;
 272
 273        writel(0, dev->regs + MVMDIO_ERR_INT_MASK);
 274        mdiobus_unregister(bus);
 275        if (!IS_ERR(dev->clk))
 276                clk_disable_unprepare(dev->clk);
 277
 278        return 0;
 279}
 280
 281static const struct of_device_id orion_mdio_match[] = {
 282        { .compatible = "marvell,orion-mdio" },
 283        { }
 284};
 285MODULE_DEVICE_TABLE(of, orion_mdio_match);
 286
 287static struct platform_driver orion_mdio_driver = {
 288        .probe = orion_mdio_probe,
 289        .remove = orion_mdio_remove,
 290        .driver = {
 291                .name = "orion-mdio",
 292                .of_match_table = orion_mdio_match,
 293        },
 294};
 295
 296module_platform_driver(orion_mdio_driver);
 297
 298MODULE_DESCRIPTION("Marvell MDIO interface driver");
 299MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>");
 300MODULE_LICENSE("GPL");
 301MODULE_ALIAS("platform:orion-mdio");
 302