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 29 struct moxart_mdio_data { 30 void __iomem *base; 31 }; 32 33 static 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 61 static 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 93 static 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 110 static 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_IGNORE_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_IGNORE_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 153 err_out_free_mdiobus: 154 mdiobus_free(bus); 155 return ret; 156 } 157 158 static 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 168 static const struct of_device_id moxart_mdio_dt_ids[] = { 169 { .compatible = "moxa,moxart-mdio" }, 170 { } 171 }; 172 MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids); 173 174 static 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 183 module_platform_driver(moxart_mdio_driver); 184 185 MODULE_DESCRIPTION("MOXA ART MDIO interface driver"); 186 MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>"); 187 MODULE_LICENSE("GPL v2"); 188