0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/delay.h>
0013 #include <linux/kernel.h>
0014 #include <linux/module.h>
0015 #include <linux/mutex.h>
0016 #include <linux/of_address.h>
0017 #include <linux/of_mdio.h>
0018 #include <linux/phy.h>
0019 #include <linux/platform_device.h>
0020 #include <linux/regulator/consumer.h>
0021
0022 #define EMAC_MAC_MCMD_REG (0x00)
0023 #define EMAC_MAC_MADR_REG (0x04)
0024 #define EMAC_MAC_MWTD_REG (0x08)
0025 #define EMAC_MAC_MRDD_REG (0x0c)
0026 #define EMAC_MAC_MIND_REG (0x10)
0027 #define EMAC_MAC_SSRR_REG (0x14)
0028
0029 #define MDIO_TIMEOUT (msecs_to_jiffies(100))
0030
0031 struct sun4i_mdio_data {
0032 void __iomem *membase;
0033 struct regulator *regulator;
0034 };
0035
0036 static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
0037 {
0038 struct sun4i_mdio_data *data = bus->priv;
0039 unsigned long timeout_jiffies;
0040 int value;
0041
0042
0043 writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
0044
0045 writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
0046
0047
0048 timeout_jiffies = jiffies + MDIO_TIMEOUT;
0049 while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
0050 if (time_is_before_jiffies(timeout_jiffies))
0051 return -ETIMEDOUT;
0052 msleep(1);
0053 }
0054
0055
0056 writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
0057
0058 value = readl(data->membase + EMAC_MAC_MRDD_REG);
0059
0060 return value;
0061 }
0062
0063 static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
0064 u16 value)
0065 {
0066 struct sun4i_mdio_data *data = bus->priv;
0067 unsigned long timeout_jiffies;
0068
0069
0070 writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
0071
0072 writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
0073
0074
0075 timeout_jiffies = jiffies + MDIO_TIMEOUT;
0076 while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
0077 if (time_is_before_jiffies(timeout_jiffies))
0078 return -ETIMEDOUT;
0079 msleep(1);
0080 }
0081
0082
0083 writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
0084
0085 writel(value, data->membase + EMAC_MAC_MWTD_REG);
0086
0087 return 0;
0088 }
0089
0090 static int sun4i_mdio_probe(struct platform_device *pdev)
0091 {
0092 struct device_node *np = pdev->dev.of_node;
0093 struct mii_bus *bus;
0094 struct sun4i_mdio_data *data;
0095 int ret;
0096
0097 bus = mdiobus_alloc_size(sizeof(*data));
0098 if (!bus)
0099 return -ENOMEM;
0100
0101 bus->name = "sun4i_mii_bus";
0102 bus->read = &sun4i_mdio_read;
0103 bus->write = &sun4i_mdio_write;
0104 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
0105 bus->parent = &pdev->dev;
0106
0107 data = bus->priv;
0108 data->membase = devm_platform_ioremap_resource(pdev, 0);
0109 if (IS_ERR(data->membase)) {
0110 ret = PTR_ERR(data->membase);
0111 goto err_out_free_mdiobus;
0112 }
0113
0114 data->regulator = devm_regulator_get(&pdev->dev, "phy");
0115 if (IS_ERR(data->regulator)) {
0116 if (PTR_ERR(data->regulator) == -EPROBE_DEFER) {
0117 ret = -EPROBE_DEFER;
0118 goto err_out_free_mdiobus;
0119 }
0120
0121 dev_info(&pdev->dev, "no regulator found\n");
0122 data->regulator = NULL;
0123 } else {
0124 ret = regulator_enable(data->regulator);
0125 if (ret)
0126 goto err_out_free_mdiobus;
0127 }
0128
0129 ret = of_mdiobus_register(bus, np);
0130 if (ret < 0)
0131 goto err_out_disable_regulator;
0132
0133 platform_set_drvdata(pdev, bus);
0134
0135 return 0;
0136
0137 err_out_disable_regulator:
0138 if (data->regulator)
0139 regulator_disable(data->regulator);
0140 err_out_free_mdiobus:
0141 mdiobus_free(bus);
0142 return ret;
0143 }
0144
0145 static int sun4i_mdio_remove(struct platform_device *pdev)
0146 {
0147 struct mii_bus *bus = platform_get_drvdata(pdev);
0148 struct sun4i_mdio_data *data = bus->priv;
0149
0150 mdiobus_unregister(bus);
0151 if (data->regulator)
0152 regulator_disable(data->regulator);
0153 mdiobus_free(bus);
0154
0155 return 0;
0156 }
0157
0158 static const struct of_device_id sun4i_mdio_dt_ids[] = {
0159 { .compatible = "allwinner,sun4i-a10-mdio" },
0160
0161
0162 { .compatible = "allwinner,sun4i-mdio" },
0163 { }
0164 };
0165 MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids);
0166
0167 static struct platform_driver sun4i_mdio_driver = {
0168 .probe = sun4i_mdio_probe,
0169 .remove = sun4i_mdio_remove,
0170 .driver = {
0171 .name = "sun4i-mdio",
0172 .of_match_table = sun4i_mdio_dt_ids,
0173 },
0174 };
0175
0176 module_platform_driver(sun4i_mdio_driver);
0177
0178 MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver");
0179 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
0180 MODULE_LICENSE("GPL v2");