1/* 2 * Allwinner EMAC MDIO interface driver 3 * 4 * Copyright 2012-2013 Stefan Roese <sr@denx.de> 5 * Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com> 6 * 7 * Based on the Linux driver provided by Allwinner: 8 * Copyright (C) 1997 Sten Wang 9 * 10 * This file is licensed under the terms of the GNU General Public 11 * License version 2. This program is licensed "as is" without any 12 * warranty of any kind, whether express or implied. 13 */ 14 15#include <linux/delay.h> 16#include <linux/kernel.h> 17#include <linux/module.h> 18#include <linux/mutex.h> 19#include <linux/of_address.h> 20#include <linux/of_mdio.h> 21#include <linux/phy.h> 22#include <linux/platform_device.h> 23#include <linux/regulator/consumer.h> 24 25#define EMAC_MAC_MCMD_REG (0x00) 26#define EMAC_MAC_MADR_REG (0x04) 27#define EMAC_MAC_MWTD_REG (0x08) 28#define EMAC_MAC_MRDD_REG (0x0c) 29#define EMAC_MAC_MIND_REG (0x10) 30#define EMAC_MAC_SSRR_REG (0x14) 31 32#define MDIO_TIMEOUT (msecs_to_jiffies(100)) 33 34struct sun4i_mdio_data { 35 void __iomem *membase; 36 struct regulator *regulator; 37}; 38 39static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum) 40{ 41 struct sun4i_mdio_data *data = bus->priv; 42 unsigned long timeout_jiffies; 43 int value; 44 45 /* issue the phy address and reg */ 46 writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); 47 /* pull up the phy io line */ 48 writel(0x1, data->membase + EMAC_MAC_MCMD_REG); 49 50 /* Wait read complete */ 51 timeout_jiffies = jiffies + MDIO_TIMEOUT; 52 while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { 53 if (time_is_before_jiffies(timeout_jiffies)) 54 return -ETIMEDOUT; 55 msleep(1); 56 } 57 58 /* push down the phy io line */ 59 writel(0x0, data->membase + EMAC_MAC_MCMD_REG); 60 /* and read data */ 61 value = readl(data->membase + EMAC_MAC_MRDD_REG); 62 63 return value; 64} 65 66static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum, 67 u16 value) 68{ 69 struct sun4i_mdio_data *data = bus->priv; 70 unsigned long timeout_jiffies; 71 72 /* issue the phy address and reg */ 73 writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); 74 /* pull up the phy io line */ 75 writel(0x1, data->membase + EMAC_MAC_MCMD_REG); 76 77 /* Wait read complete */ 78 timeout_jiffies = jiffies + MDIO_TIMEOUT; 79 while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { 80 if (time_is_before_jiffies(timeout_jiffies)) 81 return -ETIMEDOUT; 82 msleep(1); 83 } 84 85 /* push down the phy io line */ 86 writel(0x0, data->membase + EMAC_MAC_MCMD_REG); 87 /* and write data */ 88 writel(value, data->membase + EMAC_MAC_MWTD_REG); 89 90 return 0; 91} 92 93static int sun4i_mdio_probe(struct platform_device *pdev) 94{ 95 struct device_node *np = pdev->dev.of_node; 96 struct mii_bus *bus; 97 struct sun4i_mdio_data *data; 98 struct resource *res; 99 int ret, i; 100 101 bus = mdiobus_alloc_size(sizeof(*data)); 102 if (!bus) 103 return -ENOMEM; 104 105 bus->name = "sun4i_mii_bus"; 106 bus->read = &sun4i_mdio_read; 107 bus->write = &sun4i_mdio_write; 108 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); 109 bus->parent = &pdev->dev; 110 111 bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR, 112 GFP_KERNEL); 113 if (!bus->irq) { 114 ret = -ENOMEM; 115 goto err_out_free_mdiobus; 116 } 117 118 for (i = 0; i < PHY_MAX_ADDR; i++) 119 bus->irq[i] = PHY_POLL; 120 121 data = bus->priv; 122 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 123 data->membase = devm_ioremap_resource(&pdev->dev, res); 124 if (IS_ERR(data->membase)) { 125 ret = PTR_ERR(data->membase); 126 goto err_out_free_mdiobus; 127 } 128 129 data->regulator = devm_regulator_get(&pdev->dev, "phy"); 130 if (IS_ERR(data->regulator)) { 131 if (PTR_ERR(data->regulator) == -EPROBE_DEFER) 132 return -EPROBE_DEFER; 133 134 dev_info(&pdev->dev, "no regulator found\n"); 135 } else { 136 ret = regulator_enable(data->regulator); 137 if (ret) 138 goto err_out_free_mdiobus; 139 } 140 141 ret = of_mdiobus_register(bus, np); 142 if (ret < 0) 143 goto err_out_disable_regulator; 144 145 platform_set_drvdata(pdev, bus); 146 147 return 0; 148 149err_out_disable_regulator: 150 regulator_disable(data->regulator); 151err_out_free_mdiobus: 152 mdiobus_free(bus); 153 return ret; 154} 155 156static int sun4i_mdio_remove(struct platform_device *pdev) 157{ 158 struct mii_bus *bus = platform_get_drvdata(pdev); 159 160 mdiobus_unregister(bus); 161 mdiobus_free(bus); 162 163 return 0; 164} 165 166static const struct of_device_id sun4i_mdio_dt_ids[] = { 167 { .compatible = "allwinner,sun4i-a10-mdio" }, 168 169 /* Deprecated */ 170 { .compatible = "allwinner,sun4i-mdio" }, 171 { } 172}; 173MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids); 174 175static struct platform_driver sun4i_mdio_driver = { 176 .probe = sun4i_mdio_probe, 177 .remove = sun4i_mdio_remove, 178 .driver = { 179 .name = "sun4i-mdio", 180 .of_match_table = sun4i_mdio_dt_ids, 181 }, 182}; 183 184module_platform_driver(sun4i_mdio_driver); 185 186MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver"); 187MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 188MODULE_LICENSE("GPL"); 189