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