diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 260774e26..02e1e83c0 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -33,6 +33,14 @@ config SMSC_PHY ---help--- Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs +comment "MII bus device drivers" + +config MDIO_MVEBU + bool "Driver for MVEBU SoC MDIO bus" + depends on ARCH_MVEBU + ---help--- + Driver for the MDIO bus found on Marvell EBU SoCs. + endif endmenu diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a00cc76ef..7f8277f1d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_LXT_PHY) += lxt.o obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_SMSC_PHY) += smsc.o + +obj-$(CONFIG_MDIO_MVEBU) += mdio-mvebu.o diff --git a/drivers/net/phy/mdio-mvebu.c b/drivers/net/phy/mdio-mvebu.c new file mode 100644 index 000000000..f8b492a24 --- /dev/null +++ b/drivers/net/phy/mdio-mvebu.c @@ -0,0 +1,152 @@ +/* + * Marvell MVEBU SoC MDIO interface driver + * + * (C) Copyright 2014 + * Pengutronix, Michael Grzeschik + * Sebastian Hesselbarth + * + * based on mvmdio driver from Linux + * Copyright (C) 2012 Marvell + * Thomas Petazzoni + * + * Since the MDIO interface of Marvell network interfaces is shared + * between all network interfaces, having a single driver allows to + * handle concurrent accesses properly (you may have four Ethernet + * ports, but they in fact share the same SMI interface to access + * the MDIO bus). This driver is currently used by the mvneta and + * mv643xx_eth drivers. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define SMI_DATA_SHIFT 0 +#define SMI_PHY_ADDR_SHIFT 16 +#define SMI_PHY_REG_SHIFT 21 +#define SMI_READ_OPERATION BIT(26) +#define SMI_WRITE_OPERATION 0 +#define SMI_READ_VALID BIT(27) +#define SMI_BUSY BIT(28) +#define ERR_INT_CAUSE 0x007C +#define ERR_INT_SMI_DONE BIT(4) +#define ERR_INT_MASK BIT(7) + +struct mdio_priv { + struct mii_bus miibus; + void __iomem *regs; + struct clk *clk; +}; + +#define SMI_POLL_TIMEOUT (10 * MSECOND) + +static int mvebu_mdio_wait_ready(struct mdio_priv *priv) +{ + int ret = wait_on_timeout(SMI_POLL_TIMEOUT, + (readl(priv->regs) & SMI_BUSY) == 0); + + if (ret) + dev_err(&priv->miibus.dev, "timeout, SMI busy for too long\n"); + + return ret; +} + +static int mvebu_mdio_read(struct mii_bus *bus, int addr, int reg) +{ + struct mdio_priv *priv = bus->priv; + u32 smi; + int ret; + + ret = mvebu_mdio_wait_ready(priv); + if (ret) + return ret; + + smi = SMI_READ_OPERATION; + smi |= (addr << SMI_PHY_ADDR_SHIFT) | (reg << SMI_PHY_REG_SHIFT); + writel(smi, priv->regs); + + ret = mvebu_mdio_wait_ready(priv); + if (ret) + return ret; + + smi = readl(priv->regs); + if ((smi & SMI_READ_VALID) == 0) { + dev_err(&bus->dev, "SMI bus read not valid\n"); + return -ENODEV; + } + + return smi & 0xFFFF; +} + +static int mvebu_mdio_write(struct mii_bus *bus, int addr, int reg, u16 data) +{ + struct mdio_priv *priv = bus->priv; + u32 smi; + int ret; + + ret = mvebu_mdio_wait_ready(priv); + if (ret) + return ret; + + smi = SMI_WRITE_OPERATION; + smi |= (addr << SMI_PHY_ADDR_SHIFT) | (reg << SMI_PHY_REG_SHIFT); + smi |= data << SMI_DATA_SHIFT; + writel(smi, priv->regs); + + return 0; +} + +static int mvebu_mdio_probe(struct device_d *dev) +{ + struct mdio_priv *priv; + + priv = xzalloc(sizeof(*priv)); + dev->priv = priv; + + priv->regs = dev_get_mem_region(dev, 0); + if (!priv->regs) + return -ENOMEM; + + priv->clk = clk_get(dev, NULL); + if (!IS_ERR(priv->clk)) + clk_enable(priv->clk); + + priv->miibus.priv = priv; + priv->miibus.parent = dev; + priv->miibus.read = mvebu_mdio_read; + priv->miibus.write = mvebu_mdio_write; + + return mdiobus_register(&priv->miibus); +} + +static void mvebu_mdio_remove(struct device_d *dev) +{ + struct mdio_priv *priv = dev->priv; + + mdiobus_unregister(&priv->miibus); + + if (!IS_ERR(priv->clk)) + clk_disable(priv->clk); +} + +static struct of_device_id mvebu_mdio_dt_ids[] = { + { .compatible = "marvell,orion-mdio" }, + { } +}; + +static struct driver_d mvebu_mdio_driver = { + .name = "mvebu-mdio", + .probe = mvebu_mdio_probe, + .remove = mvebu_mdio_remove, + .of_compatible = DRV_OF_COMPAT(mvebu_mdio_dt_ids), +}; +device_platform_driver(mvebu_mdio_driver);