Commit f5203a3d authored by Robert Hancock's avatar Robert Hancock Committed by David S. Miller

net: axienet: convert to phylink API

Convert this driver to use the phylink API rather than the legacy PHY
API. This allows for better support for SFP modules connected using a
1000BaseX or SGMII interface.
Signed-off-by: default avatarRobert Hancock <hancock@sedsystems.ca>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 28ef9ebd
...@@ -27,7 +27,7 @@ config XILINX_EMACLITE ...@@ -27,7 +27,7 @@ config XILINX_EMACLITE
config XILINX_AXI_EMAC config XILINX_AXI_EMAC
tristate "Xilinx 10/100/1000 AXI Ethernet support" tristate "Xilinx 10/100/1000 AXI Ethernet support"
depends on MICROBLAZE || X86 || ARM || COMPILE_TEST depends on MICROBLAZE || X86 || ARM || COMPILE_TEST
select PHYLIB select PHYLINK
---help--- ---help---
This driver supports the 10/100/1000 Ethernet from Xilinx for the This driver supports the 10/100/1000 Ethernet from Xilinx for the
AXI bus interface used in Xilinx Virtex FPGAs. AXI bus interface used in Xilinx Virtex FPGAs.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/if_vlan.h> #include <linux/if_vlan.h>
#include <linux/phylink.h>
/* Packet size info */ /* Packet size info */
#define XAE_HDR_SIZE 14 /* Size of Ethernet header */ #define XAE_HDR_SIZE 14 /* Size of Ethernet header */
...@@ -420,6 +421,9 @@ struct axienet_local { ...@@ -420,6 +421,9 @@ struct axienet_local {
/* Connection to PHY device */ /* Connection to PHY device */
struct device_node *phy_node; struct device_node *phy_node;
struct phylink *phylink;
struct phylink_config phylink_config;
/* Clock for AXI bus */ /* Clock for AXI bus */
struct clk *clk; struct clk *clk;
...@@ -439,7 +443,6 @@ struct axienet_local { ...@@ -439,7 +443,6 @@ struct axienet_local {
phy_interface_t phy_mode; phy_interface_t phy_mode;
u32 options; /* Current options word */ u32 options; /* Current options word */
u32 last_link;
u32 features; u32 features;
/* Buffer descriptors */ /* Buffer descriptors */
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
* Copyright (c) 2008-2009 Secret Lab Technologies Ltd. * Copyright (c) 2008-2009 Secret Lab Technologies Ltd.
* Copyright (c) 2010 - 2011 Michal Simek <monstr@monstr.eu> * Copyright (c) 2010 - 2011 Michal Simek <monstr@monstr.eu>
* Copyright (c) 2010 - 2011 PetaLogix * Copyright (c) 2010 - 2011 PetaLogix
* Copyright (c) 2019 SED Systems, a division of Calian Ltd.
* Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved.
* *
* This is a driver for the Xilinx Axi Ethernet which is used in the Virtex6 * This is a driver for the Xilinx Axi Ethernet which is used in the Virtex6
...@@ -519,63 +520,6 @@ static void axienet_device_reset(struct net_device *ndev) ...@@ -519,63 +520,6 @@ static void axienet_device_reset(struct net_device *ndev)
netif_trans_update(ndev); netif_trans_update(ndev);
} }
/**
* axienet_adjust_link - Adjust the PHY link speed/duplex.
* @ndev: Pointer to the net_device structure
*
* This function is called to change the speed and duplex setting after
* auto negotiation is done by the PHY. This is the function that gets
* registered with the PHY interface through the "of_phy_connect" call.
*/
static void axienet_adjust_link(struct net_device *ndev)
{
u32 emmc_reg;
u32 link_state;
u32 setspeed = 1;
struct axienet_local *lp = netdev_priv(ndev);
struct phy_device *phy = ndev->phydev;
link_state = phy->speed | (phy->duplex << 1) | phy->link;
if (lp->last_link != link_state) {
if ((phy->speed == SPEED_10) || (phy->speed == SPEED_100)) {
if (lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX)
setspeed = 0;
} else {
if ((phy->speed == SPEED_1000) &&
(lp->phy_mode == PHY_INTERFACE_MODE_MII))
setspeed = 0;
}
if (setspeed == 1) {
emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET);
emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK;
switch (phy->speed) {
case SPEED_1000:
emmc_reg |= XAE_EMMC_LINKSPD_1000;
break;
case SPEED_100:
emmc_reg |= XAE_EMMC_LINKSPD_100;
break;
case SPEED_10:
emmc_reg |= XAE_EMMC_LINKSPD_10;
break;
default:
dev_err(&ndev->dev, "Speed other than 10, 100 "
"or 1Gbps is not supported\n");
break;
}
axienet_iow(lp, XAE_EMMC_OFFSET, emmc_reg);
lp->last_link = link_state;
phy_print_status(phy);
} else {
netdev_err(ndev,
"Error setting Axi Ethernet mac speed\n");
}
}
}
/** /**
* axienet_start_xmit_done - Invoked once a transmit is completed by the * axienet_start_xmit_done - Invoked once a transmit is completed by the
* Axi DMA Tx channel. * Axi DMA Tx channel.
...@@ -956,7 +900,8 @@ static void axienet_dma_err_handler(unsigned long data); ...@@ -956,7 +900,8 @@ static void axienet_dma_err_handler(unsigned long data);
* Return: 0, on success. * Return: 0, on success.
* non-zero error value on failure * non-zero error value on failure
* *
* This is the driver open routine. It calls phy_start to start the PHY device. * This is the driver open routine. It calls phylink_start to start the
* PHY device.
* It also allocates interrupt service routines, enables the interrupt lines * It also allocates interrupt service routines, enables the interrupt lines
* and ISR handling. Axi Ethernet core is reset through Axi DMA core. Buffer * and ISR handling. Axi Ethernet core is reset through Axi DMA core. Buffer
* descriptors are initialized. * descriptors are initialized.
...@@ -965,7 +910,6 @@ static int axienet_open(struct net_device *ndev) ...@@ -965,7 +910,6 @@ static int axienet_open(struct net_device *ndev)
{ {
int ret; int ret;
struct axienet_local *lp = netdev_priv(ndev); struct axienet_local *lp = netdev_priv(ndev);
struct phy_device *phydev = NULL;
dev_dbg(&ndev->dev, "axienet_open()\n"); dev_dbg(&ndev->dev, "axienet_open()\n");
...@@ -983,16 +927,14 @@ static int axienet_open(struct net_device *ndev) ...@@ -983,16 +927,14 @@ static int axienet_open(struct net_device *ndev)
if (ret < 0) if (ret < 0)
return ret; return ret;
if (lp->phy_node) { ret = phylink_of_phy_connect(lp->phylink, lp->dev->of_node, 0);
phydev = of_phy_connect(lp->ndev, lp->phy_node, if (ret) {
axienet_adjust_link, 0, lp->phy_mode); dev_err(lp->dev, "phylink_of_phy_connect() failed: %d\n", ret);
return ret;
if (!phydev)
dev_err(lp->dev, "of_phy_connect() failed\n");
else
phy_start(phydev);
} }
phylink_start(lp->phylink);
/* Enable tasklets for Axi DMA error handling */ /* Enable tasklets for Axi DMA error handling */
tasklet_init(&lp->dma_err_tasklet, axienet_dma_err_handler, tasklet_init(&lp->dma_err_tasklet, axienet_dma_err_handler,
(unsigned long) lp); (unsigned long) lp);
...@@ -1022,8 +964,8 @@ static int axienet_open(struct net_device *ndev) ...@@ -1022,8 +964,8 @@ static int axienet_open(struct net_device *ndev)
err_rx_irq: err_rx_irq:
free_irq(lp->tx_irq, ndev); free_irq(lp->tx_irq, ndev);
err_tx_irq: err_tx_irq:
if (phydev) phylink_stop(lp->phylink);
phy_disconnect(phydev); phylink_disconnect_phy(lp->phylink);
tasklet_kill(&lp->dma_err_tasklet); tasklet_kill(&lp->dma_err_tasklet);
dev_err(lp->dev, "request_irq() failed\n"); dev_err(lp->dev, "request_irq() failed\n");
return ret; return ret;
...@@ -1035,7 +977,7 @@ static int axienet_open(struct net_device *ndev) ...@@ -1035,7 +977,7 @@ static int axienet_open(struct net_device *ndev)
* *
* Return: 0, on success. * Return: 0, on success.
* *
* This is the driver stop routine. It calls phy_disconnect to stop the PHY * This is the driver stop routine. It calls phylink_disconnect to stop the PHY
* device. It also removes the interrupt handlers and disables the interrupts. * device. It also removes the interrupt handlers and disables the interrupts.
* The Axi DMA Tx/Rx BDs are released. * The Axi DMA Tx/Rx BDs are released.
*/ */
...@@ -1047,6 +989,9 @@ static int axienet_stop(struct net_device *ndev) ...@@ -1047,6 +989,9 @@ static int axienet_stop(struct net_device *ndev)
dev_dbg(&ndev->dev, "axienet_close()\n"); dev_dbg(&ndev->dev, "axienet_close()\n");
phylink_stop(lp->phylink);
phylink_disconnect_phy(lp->phylink);
axienet_setoptions(ndev, lp->options & axienet_setoptions(ndev, lp->options &
~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN));
...@@ -1087,9 +1032,6 @@ static int axienet_stop(struct net_device *ndev) ...@@ -1087,9 +1032,6 @@ static int axienet_stop(struct net_device *ndev)
free_irq(lp->tx_irq, ndev); free_irq(lp->tx_irq, ndev);
free_irq(lp->rx_irq, ndev); free_irq(lp->rx_irq, ndev);
if (ndev->phydev)
phy_disconnect(ndev->phydev);
axienet_dma_bd_release(ndev); axienet_dma_bd_release(ndev);
return 0; return 0;
} }
...@@ -1294,12 +1236,9 @@ static void ...@@ -1294,12 +1236,9 @@ static void
axienet_ethtools_get_pauseparam(struct net_device *ndev, axienet_ethtools_get_pauseparam(struct net_device *ndev,
struct ethtool_pauseparam *epauseparm) struct ethtool_pauseparam *epauseparm)
{ {
u32 regval;
struct axienet_local *lp = netdev_priv(ndev); struct axienet_local *lp = netdev_priv(ndev);
epauseparm->autoneg = 0;
regval = axienet_ior(lp, XAE_FCC_OFFSET); phylink_ethtool_get_pauseparam(lp->phylink, epauseparm);
epauseparm->tx_pause = regval & XAE_FCC_FCTX_MASK;
epauseparm->rx_pause = regval & XAE_FCC_FCRX_MASK;
} }
/** /**
...@@ -1318,27 +1257,9 @@ static int ...@@ -1318,27 +1257,9 @@ static int
axienet_ethtools_set_pauseparam(struct net_device *ndev, axienet_ethtools_set_pauseparam(struct net_device *ndev,
struct ethtool_pauseparam *epauseparm) struct ethtool_pauseparam *epauseparm)
{ {
u32 regval = 0;
struct axienet_local *lp = netdev_priv(ndev); struct axienet_local *lp = netdev_priv(ndev);
if (netif_running(ndev)) { return phylink_ethtool_set_pauseparam(lp->phylink, epauseparm);
netdev_err(ndev,
"Please stop netif before applying configuration\n");
return -EFAULT;
}
regval = axienet_ior(lp, XAE_FCC_OFFSET);
if (epauseparm->tx_pause)
regval |= XAE_FCC_FCTX_MASK;
else
regval &= ~XAE_FCC_FCTX_MASK;
if (epauseparm->rx_pause)
regval |= XAE_FCC_FCRX_MASK;
else
regval &= ~XAE_FCC_FCRX_MASK;
axienet_iow(lp, XAE_FCC_OFFSET, regval);
return 0;
} }
/** /**
...@@ -1417,6 +1338,24 @@ static int axienet_ethtools_set_coalesce(struct net_device *ndev, ...@@ -1417,6 +1338,24 @@ static int axienet_ethtools_set_coalesce(struct net_device *ndev,
return 0; return 0;
} }
static int
axienet_ethtools_get_link_ksettings(struct net_device *ndev,
struct ethtool_link_ksettings *cmd)
{
struct axienet_local *lp = netdev_priv(ndev);
return phylink_ethtool_ksettings_get(lp->phylink, cmd);
}
static int
axienet_ethtools_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *cmd)
{
struct axienet_local *lp = netdev_priv(ndev);
return phylink_ethtool_ksettings_set(lp->phylink, cmd);
}
static const struct ethtool_ops axienet_ethtool_ops = { static const struct ethtool_ops axienet_ethtool_ops = {
.get_drvinfo = axienet_ethtools_get_drvinfo, .get_drvinfo = axienet_ethtools_get_drvinfo,
.get_regs_len = axienet_ethtools_get_regs_len, .get_regs_len = axienet_ethtools_get_regs_len,
...@@ -1428,8 +1367,141 @@ static const struct ethtool_ops axienet_ethtool_ops = { ...@@ -1428,8 +1367,141 @@ static const struct ethtool_ops axienet_ethtool_ops = {
.set_pauseparam = axienet_ethtools_set_pauseparam, .set_pauseparam = axienet_ethtools_set_pauseparam,
.get_coalesce = axienet_ethtools_get_coalesce, .get_coalesce = axienet_ethtools_get_coalesce,
.set_coalesce = axienet_ethtools_set_coalesce, .set_coalesce = axienet_ethtools_set_coalesce,
.get_link_ksettings = phy_ethtool_get_link_ksettings, .get_link_ksettings = axienet_ethtools_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings, .set_link_ksettings = axienet_ethtools_set_link_ksettings,
};
static void axienet_validate(struct phylink_config *config,
unsigned long *supported,
struct phylink_link_state *state)
{
struct net_device *ndev = to_net_dev(config->dev);
struct axienet_local *lp = netdev_priv(ndev);
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
/* Only support the mode we are configured for */
if (state->interface != PHY_INTERFACE_MODE_NA &&
state->interface != lp->phy_mode) {
netdev_warn(ndev, "Cannot use PHY mode %s, supported: %s\n",
phy_modes(state->interface),
phy_modes(lp->phy_mode));
bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
return;
}
phylink_set(mask, Autoneg);
phylink_set_port_modes(mask);
phylink_set(mask, Asym_Pause);
phylink_set(mask, Pause);
phylink_set(mask, 1000baseX_Full);
phylink_set(mask, 10baseT_Full);
phylink_set(mask, 100baseT_Full);
phylink_set(mask, 1000baseT_Full);
bitmap_and(supported, supported, mask,
__ETHTOOL_LINK_MODE_MASK_NBITS);
bitmap_and(state->advertising, state->advertising, mask,
__ETHTOOL_LINK_MODE_MASK_NBITS);
}
static int axienet_mac_link_state(struct phylink_config *config,
struct phylink_link_state *state)
{
struct net_device *ndev = to_net_dev(config->dev);
struct axienet_local *lp = netdev_priv(ndev);
u32 emmc_reg, fcc_reg;
state->interface = lp->phy_mode;
emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET);
if (emmc_reg & XAE_EMMC_LINKSPD_1000)
state->speed = SPEED_1000;
else if (emmc_reg & XAE_EMMC_LINKSPD_100)
state->speed = SPEED_100;
else
state->speed = SPEED_10;
state->pause = 0;
fcc_reg = axienet_ior(lp, XAE_FCC_OFFSET);
if (fcc_reg & XAE_FCC_FCTX_MASK)
state->pause |= MLO_PAUSE_TX;
if (fcc_reg & XAE_FCC_FCRX_MASK)
state->pause |= MLO_PAUSE_RX;
state->an_complete = 0;
state->duplex = 1;
return 1;
}
static void axienet_mac_an_restart(struct phylink_config *config)
{
/* Unsupported, do nothing */
}
static void axienet_mac_config(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
struct net_device *ndev = to_net_dev(config->dev);
struct axienet_local *lp = netdev_priv(ndev);
u32 emmc_reg, fcc_reg;
emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET);
emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK;
switch (state->speed) {
case SPEED_1000:
emmc_reg |= XAE_EMMC_LINKSPD_1000;
break;
case SPEED_100:
emmc_reg |= XAE_EMMC_LINKSPD_100;
break;
case SPEED_10:
emmc_reg |= XAE_EMMC_LINKSPD_10;
break;
default:
dev_err(&ndev->dev,
"Speed other than 10, 100 or 1Gbps is not supported\n");
break;
}
axienet_iow(lp, XAE_EMMC_OFFSET, emmc_reg);
fcc_reg = axienet_ior(lp, XAE_FCC_OFFSET);
if (state->pause & MLO_PAUSE_TX)
fcc_reg |= XAE_FCC_FCTX_MASK;
else
fcc_reg &= ~XAE_FCC_FCTX_MASK;
if (state->pause & MLO_PAUSE_RX)
fcc_reg |= XAE_FCC_FCRX_MASK;
else
fcc_reg &= ~XAE_FCC_FCRX_MASK;
axienet_iow(lp, XAE_FCC_OFFSET, fcc_reg);
}
static void axienet_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
/* nothing meaningful to do */
}
static void axienet_mac_link_up(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface,
struct phy_device *phy)
{
/* nothing meaningful to do */
}
static const struct phylink_mac_ops axienet_phylink_ops = {
.validate = axienet_validate,
.mac_link_state = axienet_mac_link_state,
.mac_an_restart = axienet_mac_an_restart,
.mac_config = axienet_mac_config,
.mac_link_down = axienet_mac_link_down,
.mac_link_up = axienet_mac_link_up,
}; };
/** /**
...@@ -1777,6 +1849,18 @@ static int axienet_probe(struct platform_device *pdev) ...@@ -1777,6 +1849,18 @@ static int axienet_probe(struct platform_device *pdev)
"error registering MDIO bus: %d\n", ret); "error registering MDIO bus: %d\n", ret);
} }
lp->phylink_config.dev = &ndev->dev;
lp->phylink_config.type = PHYLINK_NETDEV;
lp->phylink = phylink_create(&lp->phylink_config, pdev->dev.fwnode,
lp->phy_mode,
&axienet_phylink_ops);
if (IS_ERR(lp->phylink)) {
ret = PTR_ERR(lp->phylink);
dev_err(&pdev->dev, "phylink_create error (%i)\n", ret);
goto free_netdev;
}
ret = register_netdev(lp->ndev); ret = register_netdev(lp->ndev);
if (ret) { if (ret) {
dev_err(lp->dev, "register_netdev() error (%i)\n", ret); dev_err(lp->dev, "register_netdev() error (%i)\n", ret);
...@@ -1797,6 +1881,10 @@ static int axienet_remove(struct platform_device *pdev) ...@@ -1797,6 +1881,10 @@ static int axienet_remove(struct platform_device *pdev)
struct axienet_local *lp = netdev_priv(ndev); struct axienet_local *lp = netdev_priv(ndev);
unregister_netdev(ndev); unregister_netdev(ndev);
if (lp->phylink)
phylink_destroy(lp->phylink);
axienet_mdio_teardown(lp); axienet_mdio_teardown(lp);
if (lp->clk) if (lp->clk)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment