|  | /* | 
|  | * Allwinner EMAC MDIO interface driver | 
|  | * | 
|  | * Copyright 2012-2013 Stefan Roese <sr@denx.de> | 
|  | * Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com> | 
|  | * | 
|  | * Based on the Linux driver provided by Allwinner: | 
|  | * Copyright (C) 1997  Sten Wang | 
|  | * | 
|  | * This file is licensed under the terms of the GNU General Public | 
|  | * License version 2. This program is licensed "as is" without any | 
|  | * warranty of any kind, whether express or implied. | 
|  | */ | 
|  |  | 
|  | #include <linux/delay.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/of_mdio.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/regulator/consumer.h> | 
|  |  | 
|  | #define EMAC_MAC_MCMD_REG	(0x00) | 
|  | #define EMAC_MAC_MADR_REG	(0x04) | 
|  | #define EMAC_MAC_MWTD_REG	(0x08) | 
|  | #define EMAC_MAC_MRDD_REG	(0x0c) | 
|  | #define EMAC_MAC_MIND_REG	(0x10) | 
|  | #define EMAC_MAC_SSRR_REG	(0x14) | 
|  |  | 
|  | #define MDIO_TIMEOUT		(msecs_to_jiffies(100)) | 
|  |  | 
|  | struct sun4i_mdio_data { | 
|  | void __iomem		*membase; | 
|  | struct regulator	*regulator; | 
|  | }; | 
|  |  | 
|  | static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum) | 
|  | { | 
|  | struct sun4i_mdio_data *data = bus->priv; | 
|  | unsigned long timeout_jiffies; | 
|  | int value; | 
|  |  | 
|  | /* issue the phy address and reg */ | 
|  | writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); | 
|  | /* pull up the phy io line */ | 
|  | writel(0x1, data->membase + EMAC_MAC_MCMD_REG); | 
|  |  | 
|  | /* Wait read complete */ | 
|  | timeout_jiffies = jiffies + MDIO_TIMEOUT; | 
|  | while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { | 
|  | if (time_is_before_jiffies(timeout_jiffies)) | 
|  | return -ETIMEDOUT; | 
|  | msleep(1); | 
|  | } | 
|  |  | 
|  | /* push down the phy io line */ | 
|  | writel(0x0, data->membase + EMAC_MAC_MCMD_REG); | 
|  | /* and read data */ | 
|  | value = readl(data->membase + EMAC_MAC_MRDD_REG); | 
|  |  | 
|  | return value; | 
|  | } | 
|  |  | 
|  | static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum, | 
|  | u16 value) | 
|  | { | 
|  | struct sun4i_mdio_data *data = bus->priv; | 
|  | unsigned long timeout_jiffies; | 
|  |  | 
|  | /* issue the phy address and reg */ | 
|  | writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); | 
|  | /* pull up the phy io line */ | 
|  | writel(0x1, data->membase + EMAC_MAC_MCMD_REG); | 
|  |  | 
|  | /* Wait read complete */ | 
|  | timeout_jiffies = jiffies + MDIO_TIMEOUT; | 
|  | while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { | 
|  | if (time_is_before_jiffies(timeout_jiffies)) | 
|  | return -ETIMEDOUT; | 
|  | msleep(1); | 
|  | } | 
|  |  | 
|  | /* push down the phy io line */ | 
|  | writel(0x0, data->membase + EMAC_MAC_MCMD_REG); | 
|  | /* and write data */ | 
|  | writel(value, data->membase + EMAC_MAC_MWTD_REG); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sun4i_mdio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device_node *np = pdev->dev.of_node; | 
|  | struct mii_bus *bus; | 
|  | struct sun4i_mdio_data *data; | 
|  | struct resource *res; | 
|  | int ret; | 
|  |  | 
|  | bus = mdiobus_alloc_size(sizeof(*data)); | 
|  | if (!bus) | 
|  | return -ENOMEM; | 
|  |  | 
|  | bus->name = "sun4i_mii_bus"; | 
|  | bus->read = &sun4i_mdio_read; | 
|  | bus->write = &sun4i_mdio_write; | 
|  | snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); | 
|  | bus->parent = &pdev->dev; | 
|  |  | 
|  | data = bus->priv; | 
|  | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | data->membase = devm_ioremap_resource(&pdev->dev, res); | 
|  | if (IS_ERR(data->membase)) { | 
|  | ret = PTR_ERR(data->membase); | 
|  | goto err_out_free_mdiobus; | 
|  | } | 
|  |  | 
|  | data->regulator = devm_regulator_get(&pdev->dev, "phy"); | 
|  | if (IS_ERR(data->regulator)) { | 
|  | if (PTR_ERR(data->regulator) == -EPROBE_DEFER) { | 
|  | ret = -EPROBE_DEFER; | 
|  | goto err_out_free_mdiobus; | 
|  | } | 
|  |  | 
|  | dev_info(&pdev->dev, "no regulator found\n"); | 
|  | data->regulator = NULL; | 
|  | } else { | 
|  | ret = regulator_enable(data->regulator); | 
|  | if (ret) | 
|  | goto err_out_free_mdiobus; | 
|  | } | 
|  |  | 
|  | ret = of_mdiobus_register(bus, np); | 
|  | if (ret < 0) | 
|  | goto err_out_disable_regulator; | 
|  |  | 
|  | platform_set_drvdata(pdev, bus); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_out_disable_regulator: | 
|  | if (data->regulator) | 
|  | regulator_disable(data->regulator); | 
|  | err_out_free_mdiobus: | 
|  | mdiobus_free(bus); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int sun4i_mdio_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct mii_bus *bus = platform_get_drvdata(pdev); | 
|  |  | 
|  | mdiobus_unregister(bus); | 
|  | mdiobus_free(bus); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id sun4i_mdio_dt_ids[] = { | 
|  | { .compatible = "allwinner,sun4i-a10-mdio" }, | 
|  |  | 
|  | /* Deprecated */ | 
|  | { .compatible = "allwinner,sun4i-mdio" }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids); | 
|  |  | 
|  | static struct platform_driver sun4i_mdio_driver = { | 
|  | .probe = sun4i_mdio_probe, | 
|  | .remove = sun4i_mdio_remove, | 
|  | .driver = { | 
|  | .name = "sun4i-mdio", | 
|  | .of_match_table = sun4i_mdio_dt_ids, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(sun4i_mdio_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver"); | 
|  | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | 
|  | MODULE_LICENSE("GPL"); |