When using the fixed PHY with GENET (e.g. MOCA) the PHY link status can be determined from the internal link status captured by the MAC. This allows the PHY state machine to use the correct link state with the fixed PHY even if MAC link event interrupts are missed when the net device is opened. Fixes: 8d88c6ebb34c ("net: bcmgenet: enable MoCA link state change detection") Signed-off-by: Doug Berger <opendmb@gmail.com> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
581 lines
14 KiB
C
581 lines
14 KiB
C
/*
|
|
* Broadcom GENET MDIO routines
|
|
*
|
|
* Copyright (c) 2014-2017 Broadcom
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phy_fixed.h>
|
|
#include <linux/brcmphy.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/platform_data/bcmgenet.h>
|
|
#include <linux/platform_data/mdio-bcm-unimac.h>
|
|
|
|
#include "bcmgenet.h"
|
|
|
|
/* setup netdev link state when PHY link status change and
|
|
* update UMAC and RGMII block when link up
|
|
*/
|
|
void bcmgenet_mii_setup(struct net_device *dev)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
struct phy_device *phydev = dev->phydev;
|
|
u32 reg, cmd_bits = 0;
|
|
bool status_changed = false;
|
|
|
|
if (priv->old_link != phydev->link) {
|
|
status_changed = true;
|
|
priv->old_link = phydev->link;
|
|
}
|
|
|
|
if (phydev->link) {
|
|
/* check speed/duplex/pause changes */
|
|
if (priv->old_speed != phydev->speed) {
|
|
status_changed = true;
|
|
priv->old_speed = phydev->speed;
|
|
}
|
|
|
|
if (priv->old_duplex != phydev->duplex) {
|
|
status_changed = true;
|
|
priv->old_duplex = phydev->duplex;
|
|
}
|
|
|
|
if (priv->old_pause != phydev->pause) {
|
|
status_changed = true;
|
|
priv->old_pause = phydev->pause;
|
|
}
|
|
|
|
/* done if nothing has changed */
|
|
if (!status_changed)
|
|
return;
|
|
|
|
/* speed */
|
|
if (phydev->speed == SPEED_1000)
|
|
cmd_bits = UMAC_SPEED_1000;
|
|
else if (phydev->speed == SPEED_100)
|
|
cmd_bits = UMAC_SPEED_100;
|
|
else
|
|
cmd_bits = UMAC_SPEED_10;
|
|
cmd_bits <<= CMD_SPEED_SHIFT;
|
|
|
|
/* duplex */
|
|
if (phydev->duplex != DUPLEX_FULL)
|
|
cmd_bits |= CMD_HD_EN;
|
|
|
|
/* pause capability */
|
|
if (!phydev->pause)
|
|
cmd_bits |= CMD_RX_PAUSE_IGNORE | CMD_TX_PAUSE_IGNORE;
|
|
|
|
/*
|
|
* Program UMAC and RGMII block based on established
|
|
* link speed, duplex, and pause. The speed set in
|
|
* umac->cmd tell RGMII block which clock to use for
|
|
* transmit -- 25MHz(100Mbps) or 125MHz(1Gbps).
|
|
* Receive clock is provided by the PHY.
|
|
*/
|
|
reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL);
|
|
reg &= ~OOB_DISABLE;
|
|
reg |= RGMII_LINK;
|
|
bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL);
|
|
|
|
reg = bcmgenet_umac_readl(priv, UMAC_CMD);
|
|
reg &= ~((CMD_SPEED_MASK << CMD_SPEED_SHIFT) |
|
|
CMD_HD_EN |
|
|
CMD_RX_PAUSE_IGNORE | CMD_TX_PAUSE_IGNORE);
|
|
reg |= cmd_bits;
|
|
bcmgenet_umac_writel(priv, reg, UMAC_CMD);
|
|
} else {
|
|
/* done if nothing has changed */
|
|
if (!status_changed)
|
|
return;
|
|
|
|
/* needed for MoCA fixed PHY to reflect correct link status */
|
|
netif_carrier_off(dev);
|
|
}
|
|
|
|
phy_print_status(phydev);
|
|
}
|
|
|
|
|
|
static int bcmgenet_fixed_phy_link_update(struct net_device *dev,
|
|
struct fixed_phy_status *status)
|
|
{
|
|
struct bcmgenet_priv *priv;
|
|
u32 reg;
|
|
|
|
if (dev && dev->phydev && status) {
|
|
priv = netdev_priv(dev);
|
|
reg = bcmgenet_umac_readl(priv, UMAC_MODE);
|
|
status->link = !!(reg & MODE_LINK_STATUS);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bcmgenet_phy_power_set(struct net_device *dev, bool enable)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
u32 reg = 0;
|
|
|
|
/* EXT_GPHY_CTRL is only valid for GENETv4 and onward */
|
|
if (GENET_IS_V4(priv)) {
|
|
reg = bcmgenet_ext_readl(priv, EXT_GPHY_CTRL);
|
|
if (enable) {
|
|
reg &= ~EXT_CK25_DIS;
|
|
bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL);
|
|
mdelay(1);
|
|
|
|
reg &= ~(EXT_CFG_IDDQ_BIAS | EXT_CFG_PWR_DOWN);
|
|
reg |= EXT_GPHY_RESET;
|
|
bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL);
|
|
mdelay(1);
|
|
|
|
reg &= ~EXT_GPHY_RESET;
|
|
} else {
|
|
reg |= EXT_CFG_IDDQ_BIAS | EXT_CFG_PWR_DOWN |
|
|
EXT_GPHY_RESET;
|
|
bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL);
|
|
mdelay(1);
|
|
reg |= EXT_CK25_DIS;
|
|
}
|
|
bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL);
|
|
udelay(60);
|
|
} else {
|
|
mdelay(1);
|
|
}
|
|
}
|
|
|
|
static void bcmgenet_moca_phy_setup(struct bcmgenet_priv *priv)
|
|
{
|
|
u32 reg;
|
|
|
|
if (!GENET_IS_V5(priv)) {
|
|
/* Speed settings are set in bcmgenet_mii_setup() */
|
|
reg = bcmgenet_sys_readl(priv, SYS_PORT_CTRL);
|
|
reg |= LED_ACT_SOURCE_MAC;
|
|
bcmgenet_sys_writel(priv, reg, SYS_PORT_CTRL);
|
|
}
|
|
|
|
if (priv->hw_params->flags & GENET_HAS_MOCA_LINK_DET)
|
|
fixed_phy_set_link_update(priv->dev->phydev,
|
|
bcmgenet_fixed_phy_link_update);
|
|
}
|
|
|
|
int bcmgenet_mii_config(struct net_device *dev, bool init)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
struct phy_device *phydev = dev->phydev;
|
|
struct device *kdev = &priv->pdev->dev;
|
|
const char *phy_name = NULL;
|
|
u32 id_mode_dis = 0;
|
|
u32 port_ctrl;
|
|
u32 reg;
|
|
|
|
priv->ext_phy = !priv->internal_phy &&
|
|
(priv->phy_interface != PHY_INTERFACE_MODE_MOCA);
|
|
|
|
switch (priv->phy_interface) {
|
|
case PHY_INTERFACE_MODE_INTERNAL:
|
|
case PHY_INTERFACE_MODE_MOCA:
|
|
/* Irrespective of the actually configured PHY speed (100 or
|
|
* 1000) GENETv4 only has an internal GPHY so we will just end
|
|
* up masking the Gigabit features from what we support, not
|
|
* switching to the EPHY
|
|
*/
|
|
if (GENET_IS_V4(priv))
|
|
port_ctrl = PORT_MODE_INT_GPHY;
|
|
else
|
|
port_ctrl = PORT_MODE_INT_EPHY;
|
|
|
|
bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL);
|
|
|
|
if (priv->internal_phy) {
|
|
phy_name = "internal PHY";
|
|
} else if (priv->phy_interface == PHY_INTERFACE_MODE_MOCA) {
|
|
phy_name = "MoCA";
|
|
bcmgenet_moca_phy_setup(priv);
|
|
}
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_MII:
|
|
phy_name = "external MII";
|
|
phydev->supported &= PHY_BASIC_FEATURES;
|
|
bcmgenet_sys_writel(priv,
|
|
PORT_MODE_EXT_EPHY, SYS_PORT_CTRL);
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_REVMII:
|
|
phy_name = "external RvMII";
|
|
/* of_mdiobus_register took care of reading the 'max-speed'
|
|
* PHY property for us, effectively limiting the PHY supported
|
|
* capabilities, use that knowledge to also configure the
|
|
* Reverse MII interface correctly.
|
|
*/
|
|
if ((dev->phydev->supported & PHY_BASIC_FEATURES) ==
|
|
PHY_BASIC_FEATURES)
|
|
port_ctrl = PORT_MODE_EXT_RVMII_25;
|
|
else
|
|
port_ctrl = PORT_MODE_EXT_RVMII_50;
|
|
bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL);
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
/* RGMII_NO_ID: TXC transitions at the same time as TXD
|
|
* (requires PCB or receiver-side delay)
|
|
* RGMII: Add 2ns delay on TXC (90 degree shift)
|
|
*
|
|
* ID is implicitly disabled for 100Mbps (RG)MII operation.
|
|
*/
|
|
id_mode_dis = BIT(16);
|
|
/* fall through */
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
if (id_mode_dis)
|
|
phy_name = "external RGMII (no delay)";
|
|
else
|
|
phy_name = "external RGMII (TX delay)";
|
|
bcmgenet_sys_writel(priv,
|
|
PORT_MODE_EXT_GPHY, SYS_PORT_CTRL);
|
|
break;
|
|
default:
|
|
dev_err(kdev, "unknown phy mode: %d\n", priv->phy_interface);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* This is an external PHY (xMII), so we need to enable the RGMII
|
|
* block for the interface to work
|
|
*/
|
|
if (priv->ext_phy) {
|
|
reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL);
|
|
reg |= RGMII_MODE_EN | id_mode_dis;
|
|
bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL);
|
|
}
|
|
|
|
if (init)
|
|
dev_info(kdev, "configuring instance for %s\n", phy_name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bcmgenet_mii_probe(struct net_device *dev)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
struct device_node *dn = priv->pdev->dev.of_node;
|
|
struct phy_device *phydev;
|
|
u32 phy_flags;
|
|
int ret;
|
|
|
|
/* Communicate the integrated PHY revision */
|
|
phy_flags = priv->gphy_rev;
|
|
|
|
/* Initialize link state variables that bcmgenet_mii_setup() uses */
|
|
priv->old_link = -1;
|
|
priv->old_speed = -1;
|
|
priv->old_duplex = -1;
|
|
priv->old_pause = -1;
|
|
|
|
if (dn) {
|
|
phydev = of_phy_connect(dev, priv->phy_dn, bcmgenet_mii_setup,
|
|
phy_flags, priv->phy_interface);
|
|
if (!phydev) {
|
|
pr_err("could not attach to PHY\n");
|
|
return -ENODEV;
|
|
}
|
|
} else {
|
|
phydev = dev->phydev;
|
|
phydev->dev_flags = phy_flags;
|
|
|
|
ret = phy_connect_direct(dev, phydev, bcmgenet_mii_setup,
|
|
priv->phy_interface);
|
|
if (ret) {
|
|
pr_err("could not attach to PHY\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
/* Configure port multiplexer based on what the probed PHY device since
|
|
* reading the 'max-speed' property determines the maximum supported
|
|
* PHY speed which is needed for bcmgenet_mii_config() to configure
|
|
* things appropriately.
|
|
*/
|
|
ret = bcmgenet_mii_config(dev, true);
|
|
if (ret) {
|
|
phy_disconnect(dev->phydev);
|
|
return ret;
|
|
}
|
|
|
|
phydev->advertising = phydev->supported;
|
|
|
|
/* The internal PHY has its link interrupts routed to the
|
|
* Ethernet MAC ISRs
|
|
*/
|
|
if (priv->internal_phy)
|
|
dev->phydev->irq = PHY_IGNORE_INTERRUPT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct device_node *bcmgenet_mii_of_find_mdio(struct bcmgenet_priv *priv)
|
|
{
|
|
struct device_node *dn = priv->pdev->dev.of_node;
|
|
struct device *kdev = &priv->pdev->dev;
|
|
char *compat;
|
|
|
|
compat = kasprintf(GFP_KERNEL, "brcm,genet-mdio-v%d", priv->version);
|
|
if (!compat)
|
|
return NULL;
|
|
|
|
priv->mdio_dn = of_find_compatible_node(dn, NULL, compat);
|
|
kfree(compat);
|
|
if (!priv->mdio_dn) {
|
|
dev_err(kdev, "unable to find MDIO bus node\n");
|
|
return NULL;
|
|
}
|
|
|
|
return priv->mdio_dn;
|
|
}
|
|
|
|
static void bcmgenet_mii_pdata_init(struct bcmgenet_priv *priv,
|
|
struct unimac_mdio_pdata *ppd)
|
|
{
|
|
struct device *kdev = &priv->pdev->dev;
|
|
struct bcmgenet_platform_data *pd = kdev->platform_data;
|
|
|
|
if (pd->phy_interface != PHY_INTERFACE_MODE_MOCA && pd->mdio_enabled) {
|
|
/*
|
|
* Internal or external PHY with MDIO access
|
|
*/
|
|
if (pd->phy_address >= 0 && pd->phy_address < PHY_MAX_ADDR)
|
|
ppd->phy_mask = 1 << pd->phy_address;
|
|
else
|
|
ppd->phy_mask = 0;
|
|
}
|
|
}
|
|
|
|
static int bcmgenet_mii_wait(void *wait_func_data)
|
|
{
|
|
struct bcmgenet_priv *priv = wait_func_data;
|
|
|
|
wait_event_timeout(priv->wq,
|
|
!(bcmgenet_umac_readl(priv, UMAC_MDIO_CMD)
|
|
& MDIO_START_BUSY),
|
|
HZ / 100);
|
|
return 0;
|
|
}
|
|
|
|
static int bcmgenet_mii_register(struct bcmgenet_priv *priv)
|
|
{
|
|
struct platform_device *pdev = priv->pdev;
|
|
struct bcmgenet_platform_data *pdata = pdev->dev.platform_data;
|
|
struct device_node *dn = pdev->dev.of_node;
|
|
struct unimac_mdio_pdata ppd;
|
|
struct platform_device *ppdev;
|
|
struct resource *pres, res;
|
|
int id, ret;
|
|
|
|
pres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
memset(&res, 0, sizeof(res));
|
|
memset(&ppd, 0, sizeof(ppd));
|
|
|
|
ppd.wait_func = bcmgenet_mii_wait;
|
|
ppd.wait_func_data = priv;
|
|
ppd.bus_name = "bcmgenet MII bus";
|
|
|
|
/* Unimac MDIO bus controller starts at UniMAC offset + MDIO_CMD
|
|
* and is 2 * 32-bits word long, 8 bytes total.
|
|
*/
|
|
res.start = pres->start + GENET_UMAC_OFF + UMAC_MDIO_CMD;
|
|
res.end = res.start + 8;
|
|
res.flags = IORESOURCE_MEM;
|
|
|
|
if (dn)
|
|
id = of_alias_get_id(dn, "eth");
|
|
else
|
|
id = pdev->id;
|
|
|
|
ppdev = platform_device_alloc(UNIMAC_MDIO_DRV_NAME, id);
|
|
if (!ppdev)
|
|
return -ENOMEM;
|
|
|
|
/* Retain this platform_device pointer for later cleanup */
|
|
priv->mii_pdev = ppdev;
|
|
ppdev->dev.parent = &pdev->dev;
|
|
ppdev->dev.of_node = bcmgenet_mii_of_find_mdio(priv);
|
|
if (pdata)
|
|
bcmgenet_mii_pdata_init(priv, &ppd);
|
|
|
|
ret = platform_device_add_resources(ppdev, &res, 1);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = platform_device_add_data(ppdev, &ppd, sizeof(ppd));
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = platform_device_add(ppdev);
|
|
if (ret)
|
|
goto out;
|
|
|
|
return 0;
|
|
out:
|
|
platform_device_put(ppdev);
|
|
return ret;
|
|
}
|
|
|
|
static int bcmgenet_mii_of_init(struct bcmgenet_priv *priv)
|
|
{
|
|
struct device_node *dn = priv->pdev->dev.of_node;
|
|
struct device *kdev = &priv->pdev->dev;
|
|
struct phy_device *phydev;
|
|
int phy_mode;
|
|
int ret;
|
|
|
|
/* Fetch the PHY phandle */
|
|
priv->phy_dn = of_parse_phandle(dn, "phy-handle", 0);
|
|
|
|
/* In the case of a fixed PHY, the DT node associated
|
|
* to the PHY is the Ethernet MAC DT node.
|
|
*/
|
|
if (!priv->phy_dn && of_phy_is_fixed_link(dn)) {
|
|
ret = of_phy_register_fixed_link(dn);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->phy_dn = of_node_get(dn);
|
|
}
|
|
|
|
/* Get the link mode */
|
|
phy_mode = of_get_phy_mode(dn);
|
|
if (phy_mode < 0) {
|
|
dev_err(kdev, "invalid PHY mode property\n");
|
|
return phy_mode;
|
|
}
|
|
|
|
priv->phy_interface = phy_mode;
|
|
|
|
/* We need to specifically look up whether this PHY interface is internal
|
|
* or not *before* we even try to probe the PHY driver over MDIO as we
|
|
* may have shut down the internal PHY for power saving purposes.
|
|
*/
|
|
if (priv->phy_interface == PHY_INTERFACE_MODE_INTERNAL)
|
|
priv->internal_phy = true;
|
|
|
|
/* Make sure we initialize MoCA PHYs with a link down */
|
|
if (phy_mode == PHY_INTERFACE_MODE_MOCA) {
|
|
phydev = of_phy_find_device(dn);
|
|
if (phydev) {
|
|
phydev->link = 0;
|
|
put_device(&phydev->mdio.dev);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcmgenet_mii_pd_init(struct bcmgenet_priv *priv)
|
|
{
|
|
struct device *kdev = &priv->pdev->dev;
|
|
struct bcmgenet_platform_data *pd = kdev->platform_data;
|
|
char phy_name[MII_BUS_ID_SIZE + 3];
|
|
char mdio_bus_id[MII_BUS_ID_SIZE];
|
|
struct phy_device *phydev;
|
|
|
|
snprintf(mdio_bus_id, MII_BUS_ID_SIZE, "%s-%d",
|
|
UNIMAC_MDIO_DRV_NAME, priv->pdev->id);
|
|
|
|
if (pd->phy_interface != PHY_INTERFACE_MODE_MOCA && pd->mdio_enabled) {
|
|
snprintf(phy_name, MII_BUS_ID_SIZE, PHY_ID_FMT,
|
|
mdio_bus_id, pd->phy_address);
|
|
|
|
/*
|
|
* Internal or external PHY with MDIO access
|
|
*/
|
|
phydev = phy_attach(priv->dev, phy_name, pd->phy_interface);
|
|
if (!phydev) {
|
|
dev_err(kdev, "failed to register PHY device\n");
|
|
return -ENODEV;
|
|
}
|
|
} else {
|
|
/*
|
|
* MoCA port or no MDIO access.
|
|
* Use fixed PHY to represent the link layer.
|
|
*/
|
|
struct fixed_phy_status fphy_status = {
|
|
.link = 1,
|
|
.speed = pd->phy_speed,
|
|
.duplex = pd->phy_duplex,
|
|
.pause = 0,
|
|
.asym_pause = 0,
|
|
};
|
|
|
|
phydev = fixed_phy_register(PHY_POLL, &fphy_status, -1, NULL);
|
|
if (!phydev || IS_ERR(phydev)) {
|
|
dev_err(kdev, "failed to register fixed PHY device\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Make sure we initialize MoCA PHYs with a link down */
|
|
phydev->link = 0;
|
|
|
|
}
|
|
|
|
priv->phy_interface = pd->phy_interface;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcmgenet_mii_bus_init(struct bcmgenet_priv *priv)
|
|
{
|
|
struct device_node *dn = priv->pdev->dev.of_node;
|
|
|
|
if (dn)
|
|
return bcmgenet_mii_of_init(priv);
|
|
else
|
|
return bcmgenet_mii_pd_init(priv);
|
|
}
|
|
|
|
int bcmgenet_mii_init(struct net_device *dev)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
int ret;
|
|
|
|
ret = bcmgenet_mii_register(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = bcmgenet_mii_bus_init(priv);
|
|
if (ret)
|
|
goto out;
|
|
|
|
return 0;
|
|
|
|
out:
|
|
bcmgenet_mii_exit(dev);
|
|
return ret;
|
|
}
|
|
|
|
void bcmgenet_mii_exit(struct net_device *dev)
|
|
{
|
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
|
struct device_node *dn = priv->pdev->dev.of_node;
|
|
|
|
if (of_phy_is_fixed_link(dn))
|
|
of_phy_deregister_fixed_link(dn);
|
|
of_node_put(priv->phy_dn);
|
|
platform_device_unregister(priv->mii_pdev);
|
|
}
|