9
0
Fork 0
barebox/drivers/net/phy/mdio-mvebu.c

154 lines
3.6 KiB
C

/*
* Marvell MVEBU SoC MDIO interface driver
*
* (C) Copyright 2014
* Pengutronix, Michael Grzeschik <mgr@pengutronix.de>
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
*
* based on mvmdio driver from Linux
* Copyright (C) 2012 Marvell
* Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
*
* 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 <clock.h>
#include <common.h>
#include <driver.h>
#include <init.h>
#include <io.h>
#include <of.h>
#include <linux/clk.h>
#include <linux/phy.h>
#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.dev.device_node = dev->device_node;
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);