|  | /* Copyright (c) 2014 Linaro Ltd. | 
|  | * Copyright (c) 2014 Hisilicon Limited. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License as published by | 
|  | * the Free Software Foundation; either version 2 of the License, or | 
|  | * (at your option) any later version. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/of_mdio.h> | 
|  | #include <linux/delay.h> | 
|  |  | 
|  | #define MDIO_CMD_REG		0x0 | 
|  | #define MDIO_ADDR_REG		0x4 | 
|  | #define MDIO_WDATA_REG		0x8 | 
|  | #define MDIO_RDATA_REG		0xc | 
|  | #define MDIO_STA_REG		0x10 | 
|  |  | 
|  | #define MDIO_START		BIT(14) | 
|  | #define MDIO_R_VALID		BIT(1) | 
|  | #define MDIO_READ	        (BIT(12) | BIT(11) | MDIO_START) | 
|  | #define MDIO_WRITE	        (BIT(12) | BIT(10) | MDIO_START) | 
|  |  | 
|  | struct hip04_mdio_priv { | 
|  | void __iomem *base; | 
|  | }; | 
|  |  | 
|  | #define WAIT_TIMEOUT 10 | 
|  | static int hip04_mdio_wait_ready(struct mii_bus *bus) | 
|  | { | 
|  | struct hip04_mdio_priv *priv = bus->priv; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; readl_relaxed(priv->base + MDIO_CMD_REG) & MDIO_START; i++) { | 
|  | if (i == WAIT_TIMEOUT) | 
|  | return -ETIMEDOUT; | 
|  | msleep(20); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int hip04_mdio_read(struct mii_bus *bus, int mii_id, int regnum) | 
|  | { | 
|  | struct hip04_mdio_priv *priv = bus->priv; | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | ret = hip04_mdio_wait_ready(bus); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | val = regnum | (mii_id << 5) | MDIO_READ; | 
|  | writel_relaxed(val, priv->base + MDIO_CMD_REG); | 
|  |  | 
|  | ret = hip04_mdio_wait_ready(bus); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | val = readl_relaxed(priv->base + MDIO_STA_REG); | 
|  | if (val & MDIO_R_VALID) { | 
|  | dev_err(bus->parent, "SMI bus read not valid\n"); | 
|  | ret = -ENODEV; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | val = readl_relaxed(priv->base + MDIO_RDATA_REG); | 
|  | ret = val & 0xFFFF; | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int hip04_mdio_write(struct mii_bus *bus, int mii_id, | 
|  | int regnum, u16 value) | 
|  | { | 
|  | struct hip04_mdio_priv *priv = bus->priv; | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | ret = hip04_mdio_wait_ready(bus); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | writel_relaxed(value, priv->base + MDIO_WDATA_REG); | 
|  | val = regnum | (mii_id << 5) | MDIO_WRITE; | 
|  | writel_relaxed(val, priv->base + MDIO_CMD_REG); | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int hip04_mdio_reset(struct mii_bus *bus) | 
|  | { | 
|  | int temp, i; | 
|  |  | 
|  | for (i = 0; i < PHY_MAX_ADDR; i++) { | 
|  | hip04_mdio_write(bus, i, 22, 0); | 
|  | temp = hip04_mdio_read(bus, i, MII_BMCR); | 
|  | if (temp < 0) | 
|  | continue; | 
|  |  | 
|  | temp |= BMCR_RESET; | 
|  | if (hip04_mdio_write(bus, i, MII_BMCR, temp) < 0) | 
|  | continue; | 
|  | } | 
|  |  | 
|  | mdelay(500); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int hip04_mdio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct resource *r; | 
|  | struct mii_bus *bus; | 
|  | struct hip04_mdio_priv *priv; | 
|  | int ret; | 
|  |  | 
|  | bus = mdiobus_alloc_size(sizeof(struct hip04_mdio_priv)); | 
|  | if (!bus) { | 
|  | dev_err(&pdev->dev, "Cannot allocate MDIO bus\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | bus->name = "hip04_mdio_bus"; | 
|  | bus->read = hip04_mdio_read; | 
|  | bus->write = hip04_mdio_write; | 
|  | bus->reset = hip04_mdio_reset; | 
|  | snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); | 
|  | bus->parent = &pdev->dev; | 
|  | priv = bus->priv; | 
|  |  | 
|  | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | priv->base = devm_ioremap_resource(&pdev->dev, r); | 
|  | if (IS_ERR(priv->base)) { | 
|  | ret = PTR_ERR(priv->base); | 
|  | goto out_mdio; | 
|  | } | 
|  |  | 
|  | ret = of_mdiobus_register(bus, pdev->dev.of_node); | 
|  | if (ret < 0) { | 
|  | dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); | 
|  | goto out_mdio; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, bus); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_mdio: | 
|  | mdiobus_free(bus); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int hip04_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 hip04_mdio_match[] = { | 
|  | { .compatible = "hisilicon,hip04-mdio" }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, hip04_mdio_match); | 
|  |  | 
|  | static struct platform_driver hip04_mdio_driver = { | 
|  | .probe = hip04_mdio_probe, | 
|  | .remove = hip04_mdio_remove, | 
|  | .driver = { | 
|  | .name = "hip04-mdio", | 
|  | .owner = THIS_MODULE, | 
|  | .of_match_table = hip04_mdio_match, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(hip04_mdio_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("HISILICON P04 MDIO interface driver"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_ALIAS("platform:hip04-mdio"); |