| /* 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"); |