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