net: phy: add generic UniMAC MDIO bus driver

Add a generic UniMAC MDIO bus driver and its Device Tree binding, which
can be used by the BCMGENET driver as-is, and the upcoming Starfighter 2
Ethernet switch MDIO bus controller.

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Florian Fainelli 2014-08-27 17:04:47 -07:00 committed by David S. Miller
parent 3e8a72d1da
commit 2ba1b163c9
4 changed files with 260 additions and 0 deletions

View file

@ -0,0 +1,39 @@
* Broadcom UniMAC MDIO bus controller
Required properties:
- compatible: should one from "brcm,genet-mdio-v1", "brcm,genet-mdio-v2",
"brcm,genet-mdio-v3", "brcm,genet-mdio-v4" or "brcm,unimac-mdio"
- reg: address and length of the regsiter set for the device, first one is the
base register, and the second one is optional and for indirect accesses to
larger than 16-bits MDIO transactions
- reg-names: name(s) of the register must be "mdio" and optional "mdio_indir_rw"
- #size-cells: must be 1
- #address-cells: must be 0
Optional properties:
- interrupts: must be one if the interrupt is shared with the Ethernet MAC or
Ethernet switch this MDIO block is integrated from, or must be two, if there
are two separate interrupts, first one must be "mdio done" and second must be
for "mdio error"
- interrupt-names: must be "mdio_done_error" when there is a share interrupt fed
to this hardware block, or must be "mdio_done" for the first interrupt and
"mdio_error" for the second when there are separate interrupts
Child nodes of this MDIO bus controller node are standard Ethernet PHY device
nodes as described in Documentation/devicetree/bindings/net/phy.txt
Example:
mdio@403c0 {
compatible = "brcm,unimac-mdio";
reg = <0x403c0 0x8 0x40300 0x18>;
reg-names = "mdio", "mdio_indir_rw";
#size-cells = <1>;
#address-cells = <0>;
...
phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <0>;
};
};

View file

@ -205,6 +205,14 @@ config MDIO_BUS_MUX_MMIOREG
Currently, only 8-bit registers are supported.
config MDIO_BCM_UNIMAC
tristate "Broadcom UniMAC MDIO bus controller"
help
This module provides a driver for the Broadcom UniMAC MDIO busses.
This hardware can be found in the Broadcom GENET Ethernet MAC
controllers as well as some Broadcom Ethernet switches such as the
Starfighter 2 switches.
endif # PHYLIB
config MICREL_KS8995MA

View file

@ -34,3 +34,4 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o
obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o

View file

@ -0,0 +1,212 @@
/*
* Broadcom UniMAC MDIO bus controller driver
*
* Copyright (C) 2014, Broadcom Corporation
*
* 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/kernel.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_mdio.h>
#define MDIO_CMD 0x00
#define MDIO_START_BUSY (1 << 29)
#define MDIO_READ_FAIL (1 << 28)
#define MDIO_RD (2 << 26)
#define MDIO_WR (1 << 26)
#define MDIO_PMD_SHIFT 21
#define MDIO_PMD_MASK 0x1F
#define MDIO_REG_SHIFT 16
#define MDIO_REG_MASK 0x1F
#define MDIO_CFG 0x04
#define MDIO_C22 (1 << 0)
#define MDIO_C45 0
#define MDIO_CLK_DIV_SHIFT 4
#define MDIO_CLK_DIV_MASK 0x3F
#define MDIO_SUPP_PREAMBLE (1 << 12)
struct unimac_mdio_priv {
struct mii_bus *mii_bus;
void __iomem *base;
};
static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
{
u32 reg;
reg = __raw_readl(priv->base + MDIO_CMD);
reg |= MDIO_START_BUSY;
__raw_writel(reg, priv->base + MDIO_CMD);
}
static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv)
{
return __raw_readl(priv->base + MDIO_CMD) & MDIO_START_BUSY;
}
static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
{
struct unimac_mdio_priv *priv = bus->priv;
unsigned int timeout = 1000;
u32 cmd;
/* Prepare the read operation */
cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT);
__raw_writel(cmd, priv->base + MDIO_CMD);
/* Start MDIO transaction */
unimac_mdio_start(priv);
do {
if (!unimac_mdio_busy(priv))
break;
usleep_range(1000, 2000);
} while (timeout--);
if (!timeout)
return -ETIMEDOUT;
cmd = __raw_readl(priv->base + MDIO_CMD);
if (cmd & MDIO_READ_FAIL)
return -EIO;
return cmd & 0xffff;
}
static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
int reg, u16 val)
{
struct unimac_mdio_priv *priv = bus->priv;
unsigned int timeout = 1000;
u32 cmd;
/* Prepare the write operation */
cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
(reg << MDIO_REG_SHIFT) | (0xffff & val);
__raw_writel(cmd, priv->base + MDIO_CMD);
unimac_mdio_start(priv);
do {
if (!unimac_mdio_busy(priv))
break;
usleep_range(1000, 2000);
} while (timeout--);
if (!timeout)
return -ETIMEDOUT;
return 0;
}
static int unimac_mdio_probe(struct platform_device *pdev)
{
struct unimac_mdio_priv *priv;
struct device_node *np;
struct mii_bus *bus;
struct resource *r;
int ret;
np = pdev->dev.of_node;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* Just ioremap, as this MDIO block is usually integrated into an
* Ethernet MAC controller register range
*/
priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
if (!priv->base) {
dev_err(&pdev->dev, "failed to remap register\n");
return -ENOMEM;
}
priv->mii_bus = mdiobus_alloc();
if (!priv->mii_bus)
return -ENOMEM;
bus = priv->mii_bus;
bus->priv = priv;
bus->name = "unimac MII bus";
bus->parent = &pdev->dev;
bus->read = unimac_mdio_read;
bus->write = unimac_mdio_write;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
if (!bus->irq) {
ret = -ENOMEM;
goto out_mdio_free;
}
ret = of_mdiobus_register(bus, np);
if (ret) {
dev_err(&pdev->dev, "MDIO bus registration failed\n");
goto out_mdio_irq;
}
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base);
return 0;
out_mdio_irq:
kfree(bus->irq);
out_mdio_free:
mdiobus_free(bus);
return ret;
}
static int unimac_mdio_remove(struct platform_device *pdev)
{
struct unimac_mdio_priv *priv = platform_get_drvdata(pdev);
mdiobus_unregister(priv->mii_bus);
kfree(priv->mii_bus->irq);
mdiobus_free(priv->mii_bus);
return 0;
}
static struct of_device_id unimac_mdio_ids[] = {
{ .compatible = "brcm,genet-mdio-v4", },
{ .compatible = "brcm,genet-mdio-v3", },
{ .compatible = "brcm,genet-mdio-v2", },
{ .compatible = "brcm,genet-mdio-v1", },
{ .compatible = "brcm,unimac-mdio", },
};
static struct platform_driver unimac_mdio_driver = {
.driver = {
.name = "unimac-mdio",
.owner = THIS_MODULE,
.of_match_table = unimac_mdio_ids,
},
.probe = unimac_mdio_probe,
.remove = unimac_mdio_remove,
};
module_platform_driver(unimac_mdio_driver);
MODULE_AUTHOR("Broadcom Corporation");
MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:unimac-mdio");