phy: Add MOXA MDIO driver
authorJonas Jensen <jonas.jensen@gmail.com>
Tue, 5 Nov 2013 15:55:01 +0000 (16:55 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 7 Nov 2013 20:37:09 +0000 (15:37 -0500)
The MOXA UC-711X hardware(s) has an ethernet controller that seem
to be developed internally. The IC used is "RTL8201CP".

This patch adds an MDIO driver which handles the MII bus.

Signed-off-by: Jonas Jensen <jonas.jensen@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/mdio-moxart.c [new file with mode: 0644]

index 342561ad3158d2660e2d08df349359530950ca0d..9b5d46c03eed34d244bf279ea13bf38ffebea009 100644 (file)
@@ -154,6 +154,13 @@ config MDIO_SUN4I
          interface units of the Allwinner SoC that have an EMAC (A10,
          A12, A10s, etc.)
 
+config MDIO_MOXART
+        tristate "MOXA ART MDIO interface support"
+        depends on ARCH_MOXART
+        help
+          This driver supports the MDIO interface found in the network
+          interface units of the MOXA ART SoC
+
 config MDIO_BUS_MUX
        tristate
        depends on OF_MDIO
index 23a2ab2e847e54d0d5b7ace4e4002f6c836c49e4..9013dfa12aa39ac7fe504734e6b3e62d7865b0d7 100644 (file)
@@ -31,3 +31,4 @@ obj-$(CONFIG_MDIO_BUS_MUX)    += mdio-mux.o
 obj-$(CONFIG_MDIO_BUS_MUX_GPIO)        += mdio-mux-gpio.o
 obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
 obj-$(CONFIG_MDIO_SUN4I)       += mdio-sun4i.o
+obj-$(CONFIG_MDIO_MOXART)      += mdio-moxart.o
diff --git a/drivers/net/phy/mdio-moxart.c b/drivers/net/phy/mdio-moxart.c
new file mode 100644 (file)
index 0000000..a5741cb
--- /dev/null
@@ -0,0 +1,201 @@
+/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver
+ *
+ * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#define REG_PHY_CTRL            0
+#define REG_PHY_WRITE_DATA      4
+
+/* REG_PHY_CTRL */
+#define MIIWR                   BIT(27) /* init write sequence (auto cleared)*/
+#define MIIRD                   BIT(26)
+#define REGAD_MASK              0x3e00000
+#define PHYAD_MASK              0x1f0000
+#define MIIRDATA_MASK           0xffff
+
+/* REG_PHY_WRITE_DATA */
+#define MIIWDATA_MASK           0xffff
+
+struct moxart_mdio_data {
+       void __iomem            *base;
+};
+
+static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+       struct moxart_mdio_data *data = bus->priv;
+       u32 ctrl = 0;
+       unsigned int count = 5;
+
+       dev_dbg(&bus->dev, "%s\n", __func__);
+
+       ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) |
+               ((regnum << 21) & REGAD_MASK);
+
+       writel(ctrl, data->base + REG_PHY_CTRL);
+
+       do {
+               ctrl = readl(data->base + REG_PHY_CTRL);
+
+               if (!(ctrl & MIIRD))
+                       return ctrl & MIIRDATA_MASK;
+
+               mdelay(10);
+               count--;
+       } while (count > 0);
+
+       dev_dbg(&bus->dev, "%s timed out\n", __func__);
+
+       return -ETIMEDOUT;
+}
+
+static int moxart_mdio_write(struct mii_bus *bus, int mii_id,
+                            int regnum, u16 value)
+{
+       struct moxart_mdio_data *data = bus->priv;
+       u32 ctrl = 0;
+       unsigned int count = 5;
+
+       dev_dbg(&bus->dev, "%s\n", __func__);
+
+       ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) |
+               ((regnum << 21) & REGAD_MASK);
+
+       value &= MIIWDATA_MASK;
+
+       writel(value, data->base + REG_PHY_WRITE_DATA);
+       writel(ctrl, data->base + REG_PHY_CTRL);
+
+       do {
+               ctrl = readl(data->base + REG_PHY_CTRL);
+
+               if (!(ctrl & MIIWR))
+                       return 0;
+
+               mdelay(10);
+               count--;
+       } while (count > 0);
+
+       dev_dbg(&bus->dev, "%s timed out\n", __func__);
+
+       return -ETIMEDOUT;
+}
+
+static int moxart_mdio_reset(struct mii_bus *bus)
+{
+       int data, i;
+
+       for (i = 0; i < PHY_MAX_ADDR; i++) {
+               data = moxart_mdio_read(bus, i, MII_BMCR);
+               if (data < 0)
+                       continue;
+
+               data |= BMCR_RESET;
+               if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0)
+                       continue;
+       }
+
+       return 0;
+}
+
+static int moxart_mdio_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct mii_bus *bus;
+       struct moxart_mdio_data *data;
+       struct resource *res;
+       int ret, i;
+
+       bus = mdiobus_alloc_size(sizeof(*data));
+       if (!bus)
+               return -ENOMEM;
+
+       bus->name = "MOXA ART Ethernet MII";
+       bus->read = &moxart_mdio_read;
+       bus->write = &moxart_mdio_write;
+       bus->reset = &moxart_mdio_reset;
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id);
+       bus->parent = &pdev->dev;
+
+       bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR,
+                       GFP_KERNEL);
+       if (!bus->irq) {
+               ret = -ENOMEM;
+               goto err_out_free_mdiobus;
+       }
+
+       /* Setting PHY_IGNORE_INTERRUPT here even if it has no effect,
+        * of_mdiobus_register() sets these PHY_POLL.
+        * Ideally, the interrupt from MAC controller could be used to
+        * detect link state changes, not polling, i.e. if there was
+        * a way phy_driver could set PHY_HAS_INTERRUPT but have that
+        * interrupt handled in ethernet drivercode.
+        */
+       for (i = 0; i < PHY_MAX_ADDR; i++)
+               bus->irq[i] = PHY_IGNORE_INTERRUPT;
+
+       data = bus->priv;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       data->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(data->base)) {
+               ret = PTR_ERR(data->base);
+               goto err_out_free_mdiobus;
+       }
+
+       ret = of_mdiobus_register(bus, np);
+       if (ret < 0)
+               goto err_out_free_mdiobus;
+
+       platform_set_drvdata(pdev, bus);
+
+       return 0;
+
+err_out_free_mdiobus:
+       mdiobus_free(bus);
+       return ret;
+}
+
+static int moxart_mdio_remove(struct platform_device *pdev)
+{
+       struct mii_bus *bus = platform_get_drvdata(pdev);
+
+       mdiobus_unregister(bus);
+       mdiobus_free(bus);
+
+       return 0;
+}
+
+static const struct of_device_id moxart_mdio_dt_ids[] = {
+       { .compatible = "moxa,moxart-mdio" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids);
+
+static struct platform_driver moxart_mdio_driver = {
+       .probe = moxart_mdio_probe,
+       .remove = moxart_mdio_remove,
+       .driver = {
+               .name = "moxart-mdio",
+               .of_match_table = moxart_mdio_dt_ids,
+       },
+};
+
+module_platform_driver(moxart_mdio_driver);
+
+MODULE_DESCRIPTION("MOXA ART MDIO interface driver");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
+MODULE_LICENSE("GPL");