Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: Add MDIO bus driver for the Allwinner EMAC

This patch adds a separate driver for the MDIO interface of the
Allwinner ethernet controllers.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Maxime Ripard and committed by
David S. Miller
4bdcb1dd 49220505

+231
+26
Documentation/devicetree/bindings/net/allwinner,sun4i-mdio.txt
··· 1 + * Allwinner A10 MDIO Ethernet Controller interface 2 + 3 + Required properties: 4 + - compatible: should be "allwinner,sun4i-mdio". 5 + - reg: address and length of the register set for the device. 6 + 7 + Optional properties: 8 + - phy-supply: phandle to a regulator if the PHY needs one 9 + 10 + Example at the SoC level: 11 + mdio@01c0b080 { 12 + compatible = "allwinner,sun4i-mdio"; 13 + reg = <0x01c0b080 0x14>; 14 + #address-cells = <1>; 15 + #size-cells = <0>; 16 + }; 17 + 18 + And at the board level: 19 + 20 + mdio@01c0b080 { 21 + phy-supply = <&reg_emac_3v3>; 22 + 23 + phy0: ethernet-phy@0 { 24 + reg = <0>; 25 + }; 26 + };
+10
drivers/net/phy/Kconfig
··· 144 144 145 145 If in doubt, say Y. 146 146 147 + config MDIO_SUN4I 148 + tristate "Allwinner sun4i MDIO interface support" 149 + depends on ARCH_SUNXI 150 + select REGULATOR 151 + select REGULATOR_FIXED_VOLTAGE 152 + help 153 + This driver supports the MDIO interface found in the network 154 + interface units of the Allwinner SoC that have an EMAC (A10, 155 + A12, A10s, etc.) 156 + 147 157 config MDIO_BUS_MUX 148 158 tristate 149 159 depends on OF_MDIO
+1
drivers/net/phy/Makefile
··· 30 30 obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o 31 31 obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o 32 32 obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o 33 + obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
+194
drivers/net/phy/mdio-sun4i.c
··· 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/init.h> 17 + #include <linux/kernel.h> 18 + #include <linux/module.h> 19 + #include <linux/mutex.h> 20 + #include <linux/of_address.h> 21 + #include <linux/of_mdio.h> 22 + #include <linux/phy.h> 23 + #include <linux/platform_device.h> 24 + #include <linux/regulator/consumer.h> 25 + 26 + #define EMAC_MAC_MCMD_REG (0x00) 27 + #define EMAC_MAC_MADR_REG (0x04) 28 + #define EMAC_MAC_MWTD_REG (0x08) 29 + #define EMAC_MAC_MRDD_REG (0x0c) 30 + #define EMAC_MAC_MIND_REG (0x10) 31 + #define EMAC_MAC_SSRR_REG (0x14) 32 + 33 + #define MDIO_TIMEOUT (msecs_to_jiffies(100)) 34 + 35 + struct sun4i_mdio_data { 36 + void __iomem *membase; 37 + struct regulator *regulator; 38 + }; 39 + 40 + static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum) 41 + { 42 + struct sun4i_mdio_data *data = bus->priv; 43 + unsigned long start_jiffies; 44 + int value; 45 + 46 + /* issue the phy address and reg */ 47 + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); 48 + /* pull up the phy io line */ 49 + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); 50 + 51 + /* Wait read complete */ 52 + start_jiffies = jiffies; 53 + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { 54 + if (time_after(start_jiffies, 55 + start_jiffies + MDIO_TIMEOUT)) 56 + return -ETIMEDOUT; 57 + msleep(1); 58 + } 59 + 60 + /* push down the phy io line */ 61 + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); 62 + /* and read data */ 63 + value = readl(data->membase + EMAC_MAC_MRDD_REG); 64 + 65 + return value; 66 + } 67 + 68 + static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum, 69 + u16 value) 70 + { 71 + struct sun4i_mdio_data *data = bus->priv; 72 + unsigned long start_jiffies; 73 + 74 + /* issue the phy address and reg */ 75 + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); 76 + /* pull up the phy io line */ 77 + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); 78 + 79 + /* Wait read complete */ 80 + start_jiffies = jiffies; 81 + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { 82 + if (time_after(start_jiffies, 83 + start_jiffies + MDIO_TIMEOUT)) 84 + return -ETIMEDOUT; 85 + msleep(1); 86 + } 87 + 88 + /* push down the phy io line */ 89 + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); 90 + /* and write data */ 91 + writel(value, data->membase + EMAC_MAC_MWTD_REG); 92 + 93 + return 0; 94 + } 95 + 96 + static int sun4i_mdio_reset(struct mii_bus *bus) 97 + { 98 + return 0; 99 + } 100 + 101 + static int sun4i_mdio_probe(struct platform_device *pdev) 102 + { 103 + struct device_node *np = pdev->dev.of_node; 104 + struct mii_bus *bus; 105 + struct sun4i_mdio_data *data; 106 + int ret, i; 107 + 108 + bus = mdiobus_alloc_size(sizeof(*data)); 109 + if (!bus) 110 + return -ENOMEM; 111 + 112 + bus->name = "sun4i_mii_bus"; 113 + bus->read = &sun4i_mdio_read; 114 + bus->write = &sun4i_mdio_write; 115 + bus->reset = &sun4i_mdio_reset; 116 + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); 117 + bus->parent = &pdev->dev; 118 + 119 + bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); 120 + if (!bus->irq) { 121 + ret = -ENOMEM; 122 + goto err_out_free_mdiobus; 123 + } 124 + 125 + for (i = 0; i < PHY_MAX_ADDR; i++) 126 + bus->irq[i] = PHY_POLL; 127 + 128 + data = bus->priv; 129 + data->membase = of_iomap(np, 0); 130 + if (!data->membase) { 131 + ret = -ENOMEM; 132 + goto err_out_free_mdio_irq; 133 + } 134 + 135 + data->regulator = devm_regulator_get(&pdev->dev, "phy"); 136 + if (IS_ERR(data->regulator)) { 137 + if (PTR_ERR(data->regulator) == -EPROBE_DEFER) 138 + return -EPROBE_DEFER; 139 + 140 + dev_info(&pdev->dev, "no regulator found\n"); 141 + } else { 142 + ret = regulator_enable(data->regulator); 143 + if (ret) 144 + goto err_out_free_mdio_irq; 145 + } 146 + 147 + ret = of_mdiobus_register(bus, np); 148 + if (ret < 0) 149 + goto err_out_disable_regulator; 150 + 151 + platform_set_drvdata(pdev, bus); 152 + 153 + return 0; 154 + 155 + err_out_disable_regulator: 156 + regulator_disable(data->regulator); 157 + err_out_free_mdio_irq: 158 + kfree(bus->irq); 159 + err_out_free_mdiobus: 160 + mdiobus_free(bus); 161 + return ret; 162 + } 163 + 164 + static int sun4i_mdio_remove(struct platform_device *pdev) 165 + { 166 + struct mii_bus *bus = platform_get_drvdata(pdev); 167 + 168 + mdiobus_unregister(bus); 169 + kfree(bus->irq); 170 + mdiobus_free(bus); 171 + 172 + return 0; 173 + } 174 + 175 + static const struct of_device_id sun4i_mdio_dt_ids[] = { 176 + { .compatible = "allwinner,sun4i-mdio" }, 177 + { } 178 + }; 179 + MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids); 180 + 181 + static struct platform_driver sun4i_mdio_driver = { 182 + .probe = sun4i_mdio_probe, 183 + .remove = sun4i_mdio_remove, 184 + .driver = { 185 + .name = "sun4i-mdio", 186 + .of_match_table = sun4i_mdio_dt_ids, 187 + }, 188 + }; 189 + 190 + module_platform_driver(sun4i_mdio_driver); 191 + 192 + MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver"); 193 + MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 194 + MODULE_LICENSE("GPL");