Staging: octeon-ethernet: Convert to use PHY Abstraction Layer.

The octeon-ethernet driver shares an mdio bus with the octeon-mgmt
driver.  Here we convert the octeon-ethernet driver to use the PHY
Abstraction Layer.

Signed-off-by: David Daney <ddaney@caviumnetworks.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
This commit is contained in:
David Daney 2009-10-14 12:04:42 -07:00 committed by Ralf Baechle
parent d6aa60a10b
commit f6ed1b3b35
9 changed files with 125 additions and 281 deletions

View file

@ -1,7 +1,8 @@
config OCTEON_ETHERNET config OCTEON_ETHERNET
tristate "Cavium Networks Octeon Ethernet support" tristate "Cavium Networks Octeon Ethernet support"
depends on CPU_CAVIUM_OCTEON depends on CPU_CAVIUM_OCTEON
select MII select PHYLIB
select MDIO_OCTEON
help help
This driver supports the builtin ethernet ports on Cavium This driver supports the builtin ethernet ports on Cavium
Networks' products in the Octeon family. This driver supports the Networks' products in the Octeon family. This driver supports the

View file

@ -26,7 +26,8 @@
**********************************************************************/ **********************************************************************/
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/mii.h> #include <linux/phy.h>
#include <net/dst.h> #include <net/dst.h>
#include <asm/octeon/octeon.h> #include <asm/octeon/octeon.h>
@ -34,86 +35,12 @@
#include "ethernet-defines.h" #include "ethernet-defines.h"
#include "octeon-ethernet.h" #include "octeon-ethernet.h"
#include "ethernet-mdio.h" #include "ethernet-mdio.h"
#include "ethernet-util.h"
#include "cvmx-helper-board.h" #include "cvmx-helper-board.h"
#include "cvmx-smix-defs.h" #include "cvmx-smix-defs.h"
DECLARE_MUTEX(mdio_sem);
/**
* Perform an MII read. Called by the generic MII routines
*
* @dev: Device to perform read for
* @phy_id: The MII phy id
* @location: Register location to read
* Returns Result from the read or zero on failure
*/
static int cvm_oct_mdio_read(struct net_device *dev, int phy_id, int location)
{
union cvmx_smix_cmd smi_cmd;
union cvmx_smix_rd_dat smi_rd;
smi_cmd.u64 = 0;
smi_cmd.s.phy_op = 1;
smi_cmd.s.phy_adr = phy_id;
smi_cmd.s.reg_adr = location;
cvmx_write_csr(CVMX_SMIX_CMD(0), smi_cmd.u64);
do {
if (!in_interrupt())
yield();
smi_rd.u64 = cvmx_read_csr(CVMX_SMIX_RD_DAT(0));
} while (smi_rd.s.pending);
if (smi_rd.s.val)
return smi_rd.s.dat;
else
return 0;
}
static int cvm_oct_mdio_dummy_read(struct net_device *dev, int phy_id,
int location)
{
return 0xffff;
}
/**
* Perform an MII write. Called by the generic MII routines
*
* @dev: Device to perform write for
* @phy_id: The MII phy id
* @location: Register location to write
* @val: Value to write
*/
static void cvm_oct_mdio_write(struct net_device *dev, int phy_id, int location,
int val)
{
union cvmx_smix_cmd smi_cmd;
union cvmx_smix_wr_dat smi_wr;
smi_wr.u64 = 0;
smi_wr.s.dat = val;
cvmx_write_csr(CVMX_SMIX_WR_DAT(0), smi_wr.u64);
smi_cmd.u64 = 0;
smi_cmd.s.phy_op = 0;
smi_cmd.s.phy_adr = phy_id;
smi_cmd.s.reg_adr = location;
cvmx_write_csr(CVMX_SMIX_CMD(0), smi_cmd.u64);
do {
if (!in_interrupt())
yield();
smi_wr.u64 = cvmx_read_csr(CVMX_SMIX_WR_DAT(0));
} while (smi_wr.s.pending);
}
static void cvm_oct_mdio_dummy_write(struct net_device *dev, int phy_id,
int location, int val)
{
}
static void cvm_oct_get_drvinfo(struct net_device *dev, static void cvm_oct_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info) struct ethtool_drvinfo *info)
{ {
@ -125,49 +52,37 @@ static void cvm_oct_get_drvinfo(struct net_device *dev,
static int cvm_oct_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) static int cvm_oct_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{ {
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
int ret;
down(&mdio_sem); if (priv->phydev)
ret = mii_ethtool_gset(&priv->mii_info, cmd); return phy_ethtool_gset(priv->phydev, cmd);
up(&mdio_sem);
return ret; return -EINVAL;
} }
static int cvm_oct_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) static int cvm_oct_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{ {
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
int ret;
down(&mdio_sem); if (!capable(CAP_NET_ADMIN))
ret = mii_ethtool_sset(&priv->mii_info, cmd); return -EPERM;
up(&mdio_sem);
return ret; if (priv->phydev)
return phy_ethtool_sset(priv->phydev, cmd);
return -EINVAL;
} }
static int cvm_oct_nway_reset(struct net_device *dev) static int cvm_oct_nway_reset(struct net_device *dev)
{ {
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
int ret;
down(&mdio_sem); if (!capable(CAP_NET_ADMIN))
ret = mii_nway_restart(&priv->mii_info); return -EPERM;
up(&mdio_sem);
return ret; if (priv->phydev)
} return phy_start_aneg(priv->phydev);
static u32 cvm_oct_get_link(struct net_device *dev) return -EINVAL;
{
struct octeon_ethernet *priv = netdev_priv(dev);
u32 ret;
down(&mdio_sem);
ret = mii_link_ok(&priv->mii_info);
up(&mdio_sem);
return ret;
} }
const struct ethtool_ops cvm_oct_ethtool_ops = { const struct ethtool_ops cvm_oct_ethtool_ops = {
@ -175,7 +90,7 @@ const struct ethtool_ops cvm_oct_ethtool_ops = {
.get_settings = cvm_oct_get_settings, .get_settings = cvm_oct_get_settings,
.set_settings = cvm_oct_set_settings, .set_settings = cvm_oct_set_settings,
.nway_reset = cvm_oct_nway_reset, .nway_reset = cvm_oct_nway_reset,
.get_link = cvm_oct_get_link, .get_link = ethtool_op_get_link,
.get_sg = ethtool_op_get_sg, .get_sg = ethtool_op_get_sg,
.get_tx_csum = ethtool_op_get_tx_csum, .get_tx_csum = ethtool_op_get_tx_csum,
}; };
@ -191,41 +106,78 @@ const struct ethtool_ops cvm_oct_ethtool_ops = {
int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{ {
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
struct mii_ioctl_data *data = if_mii(rq);
unsigned int duplex_chg;
int ret;
down(&mdio_sem); if (!netif_running(dev))
ret = generic_mii_ioctl(&priv->mii_info, data, cmd, &duplex_chg); return -EINVAL;
up(&mdio_sem);
return ret; if (!priv->phydev)
return -EINVAL;
return phy_mii_ioctl(priv->phydev, if_mii(rq), cmd);
} }
static void cvm_oct_adjust_link(struct net_device *dev)
{
struct octeon_ethernet *priv = netdev_priv(dev);
cvmx_helper_link_info_t link_info;
if (priv->last_link != priv->phydev->link) {
priv->last_link = priv->phydev->link;
link_info.u64 = 0;
link_info.s.link_up = priv->last_link ? 1 : 0;
link_info.s.full_duplex = priv->phydev->duplex ? 1 : 0;
link_info.s.speed = priv->phydev->speed;
cvmx_helper_link_set( priv->port, link_info);
if (priv->last_link) {
netif_carrier_on(dev);
if (priv->queue != -1)
DEBUGPRINT("%s: %u Mbps %s duplex, "
"port %2d, queue %2d\n",
dev->name, priv->phydev->speed,
priv->phydev->duplex ?
"Full" : "Half",
priv->port, priv->queue);
else
DEBUGPRINT("%s: %u Mbps %s duplex, "
"port %2d, POW\n",
dev->name, priv->phydev->speed,
priv->phydev->duplex ?
"Full" : "Half",
priv->port);
} else {
netif_carrier_off(dev);
DEBUGPRINT("%s: Link down\n", dev->name);
}
}
}
/** /**
* Setup the MDIO device structures * Setup the PHY
* *
* @dev: Device to setup * @dev: Device to setup
* *
* Returns Zero on success, negative on failure * Returns Zero on success, negative on failure
*/ */
int cvm_oct_mdio_setup_device(struct net_device *dev) int cvm_oct_phy_setup_device(struct net_device *dev)
{ {
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
int phy_id = cvmx_helper_board_get_mii_address(priv->port);
if (phy_id != -1) { int phy_addr = cvmx_helper_board_get_mii_address(priv->port);
priv->mii_info.dev = dev; if (phy_addr != -1) {
priv->mii_info.phy_id = phy_id; char phy_id[20];
priv->mii_info.phy_id_mask = 0xff;
priv->mii_info.supports_gmii = 1; snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, "0", phy_addr);
priv->mii_info.reg_num_mask = 0x1f;
priv->mii_info.mdio_read = cvm_oct_mdio_read; priv->phydev = phy_connect(dev, phy_id, cvm_oct_adjust_link, 0,
priv->mii_info.mdio_write = cvm_oct_mdio_write; PHY_INTERFACE_MODE_GMII);
} else {
/* Supply dummy MDIO routines so the kernel won't crash if (IS_ERR(priv->phydev)) {
if the user tries to read them */ priv->phydev = NULL;
priv->mii_info.mdio_read = cvm_oct_mdio_dummy_read; return -1;
priv->mii_info.mdio_write = cvm_oct_mdio_dummy_write; }
priv->last_link = 0;
phy_start_aneg(priv->phydev);
} }
return 0; return 0;
} }

View file

@ -43,4 +43,4 @@
extern const struct ethtool_ops cvm_oct_ethtool_ops; extern const struct ethtool_ops cvm_oct_ethtool_ops;
int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
int cvm_oct_mdio_setup_device(struct net_device *dev); int cvm_oct_phy_setup_device(struct net_device *dev);

View file

@ -25,7 +25,6 @@
* Contact Cavium Networks for more information * Contact Cavium Networks for more information
**********************************************************************/ **********************************************************************/
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/mii.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
#include <net/dst.h> #include <net/dst.h>
@ -38,112 +37,6 @@
#include "cvmx-helper.h" #include "cvmx-helper.h"
#include "cvmx-pip.h" #include "cvmx-pip.h"
static unsigned long long cvm_oct_stats_read_switch(struct net_device *dev,
int phy_id, int offset)
{
struct octeon_ethernet *priv = netdev_priv(dev);
priv->mii_info.mdio_write(dev, phy_id, 0x1d, 0xcc00 | offset);
return ((uint64_t) priv->mii_info.
mdio_read(dev, phy_id,
0x1e) << 16) | (uint64_t) priv->mii_info.
mdio_read(dev, phy_id, 0x1f);
}
static int cvm_oct_stats_switch_show(struct seq_file *m, void *v)
{
static const int ports[] = { 0, 1, 2, 3, 9, -1 };
struct net_device *dev = cvm_oct_device[0];
int index = 0;
while (ports[index] != -1) {
/* Latch port */
struct octeon_ethernet *priv = netdev_priv(dev);
priv->mii_info.mdio_write(dev, 0x1b, 0x1d,
0xdc00 | ports[index]);
seq_printf(m, "\nSwitch Port %d\n", ports[index]);
seq_printf(m, "InGoodOctets: %12llu\t"
"OutOctets: %12llu\t"
"64 Octets: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b,
0x00) |
(cvm_oct_stats_read_switch(dev, 0x1b, 0x01) << 32),
cvm_oct_stats_read_switch(dev, 0x1b,
0x0E) |
(cvm_oct_stats_read_switch(dev, 0x1b, 0x0F) << 32),
cvm_oct_stats_read_switch(dev, 0x1b, 0x08));
seq_printf(m, "InBadOctets: %12llu\t"
"OutUnicast: %12llu\t"
"65-127 Octets: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x02),
cvm_oct_stats_read_switch(dev, 0x1b, 0x10),
cvm_oct_stats_read_switch(dev, 0x1b, 0x09));
seq_printf(m, "InUnicast: %12llu\t"
"OutBroadcasts: %12llu\t"
"128-255 Octets: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x04),
cvm_oct_stats_read_switch(dev, 0x1b, 0x13),
cvm_oct_stats_read_switch(dev, 0x1b, 0x0A));
seq_printf(m, "InBroadcasts: %12llu\t"
"OutMulticasts: %12llu\t"
"256-511 Octets: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x06),
cvm_oct_stats_read_switch(dev, 0x1b, 0x12),
cvm_oct_stats_read_switch(dev, 0x1b, 0x0B));
seq_printf(m, "InMulticasts: %12llu\t"
"OutPause: %12llu\t"
"512-1023 Octets:%12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x07),
cvm_oct_stats_read_switch(dev, 0x1b, 0x15),
cvm_oct_stats_read_switch(dev, 0x1b, 0x0C));
seq_printf(m, "InPause: %12llu\t"
"Excessive: %12llu\t"
"1024-Max Octets:%12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x16),
cvm_oct_stats_read_switch(dev, 0x1b, 0x11),
cvm_oct_stats_read_switch(dev, 0x1b, 0x0D));
seq_printf(m, "InUndersize: %12llu\t"
"Collisions: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x18),
cvm_oct_stats_read_switch(dev, 0x1b, 0x1E));
seq_printf(m, "InFragments: %12llu\t"
"Deferred: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x19),
cvm_oct_stats_read_switch(dev, 0x1b, 0x05));
seq_printf(m, "InOversize: %12llu\t"
"Single: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x1A),
cvm_oct_stats_read_switch(dev, 0x1b, 0x14));
seq_printf(m, "InJabber: %12llu\t"
"Multiple: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x1B),
cvm_oct_stats_read_switch(dev, 0x1b, 0x17));
seq_printf(m, "In RxErr: %12llu\t"
"OutFCSErr: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x1C),
cvm_oct_stats_read_switch(dev, 0x1b, 0x03));
seq_printf(m, "InFCSErr: %12llu\t"
"Late: %12llu\n",
cvm_oct_stats_read_switch(dev, 0x1b, 0x1D),
cvm_oct_stats_read_switch(dev, 0x1b, 0x1F));
index++;
}
return 0;
}
/** /**
* User is reading /proc/octeon_ethernet_stats * User is reading /proc/octeon_ethernet_stats
* *
@ -215,11 +108,6 @@ static int cvm_oct_stats_show(struct seq_file *m, void *v)
} }
} }
if (cvm_oct_device[0]) {
priv = netdev_priv(cvm_oct_device[0]);
if (priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII)
cvm_oct_stats_switch_show(m, v);
}
return 0; return 0;
} }

View file

@ -147,32 +147,36 @@ static void cvm_oct_rgmii_poll(struct net_device *dev)
cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface), cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
gmxx_rxx_int_reg.u64); gmxx_rxx_int_reg.u64);
} }
if (priv->phydev == NULL) {
link_info = cvmx_helper_link_autoconf(priv->port); link_info = cvmx_helper_link_autoconf(priv->port);
priv->link_info = link_info.u64; priv->link_info = link_info.u64;
}
spin_unlock_irqrestore(&global_register_lock, flags); spin_unlock_irqrestore(&global_register_lock, flags);
/* Tell Linux */ if (priv->phydev == NULL) {
if (link_info.s.link_up) { /* Tell core. */
if (link_info.s.link_up) {
if (!netif_carrier_ok(dev)) if (!netif_carrier_ok(dev))
netif_carrier_on(dev); netif_carrier_on(dev);
if (priv->queue != -1) if (priv->queue != -1)
DEBUGPRINT DEBUGPRINT("%s: %u Mbps %s duplex, "
("%s: %u Mbps %s duplex, port %2d, queue %2d\n", "port %2d, queue %2d\n",
dev->name, link_info.s.speed, dev->name, link_info.s.speed,
(link_info.s.full_duplex) ? "Full" : "Half", (link_info.s.full_duplex) ?
priv->port, priv->queue); "Full" : "Half",
else priv->port, priv->queue);
DEBUGPRINT("%s: %u Mbps %s duplex, port %2d, POW\n", else
dev->name, link_info.s.speed, DEBUGPRINT("%s: %u Mbps %s duplex, "
(link_info.s.full_duplex) ? "Full" : "Half", "port %2d, POW\n",
priv->port); dev->name, link_info.s.speed,
} else { (link_info.s.full_duplex) ?
"Full" : "Half",
if (netif_carrier_ok(dev)) priv->port);
netif_carrier_off(dev); } else {
DEBUGPRINT("%s: Link down\n", dev->name); if (netif_carrier_ok(dev))
netif_carrier_off(dev);
DEBUGPRINT("%s: Link down\n", dev->name);
}
} }
} }

View file

@ -113,7 +113,7 @@ int cvm_oct_sgmii_init(struct net_device *dev)
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
cvm_oct_common_init(dev); cvm_oct_common_init(dev);
dev->netdev_ops->ndo_stop(dev); dev->netdev_ops->ndo_stop(dev);
if (!octeon_is_simulation()) if (!octeon_is_simulation() && priv->phydev == NULL)
priv->poll = cvm_oct_sgmii_poll; priv->poll = cvm_oct_sgmii_poll;
/* FIXME: Need autoneg logic */ /* FIXME: Need autoneg logic */

View file

@ -112,7 +112,7 @@ int cvm_oct_xaui_init(struct net_device *dev)
struct octeon_ethernet *priv = netdev_priv(dev); struct octeon_ethernet *priv = netdev_priv(dev);
cvm_oct_common_init(dev); cvm_oct_common_init(dev);
dev->netdev_ops->ndo_stop(dev); dev->netdev_ops->ndo_stop(dev);
if (!octeon_is_simulation()) if (!octeon_is_simulation() && priv->phydev == NULL)
priv->poll = cvm_oct_xaui_poll; priv->poll = cvm_oct_xaui_poll;
return 0; return 0;

View file

@ -30,7 +30,7 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/mii.h> #include <linux/phy.h>
#include <net/dst.h> #include <net/dst.h>
@ -132,8 +132,6 @@ static struct timer_list cvm_oct_poll_timer;
*/ */
struct net_device *cvm_oct_device[TOTAL_NUMBER_OF_PORTS]; struct net_device *cvm_oct_device[TOTAL_NUMBER_OF_PORTS];
extern struct semaphore mdio_sem;
/** /**
* Periodic timer tick for slow management operations * Periodic timer tick for slow management operations
* *
@ -160,13 +158,8 @@ static void cvm_do_timer(unsigned long arg)
goto out; goto out;
priv = netdev_priv(cvm_oct_device[port]); priv = netdev_priv(cvm_oct_device[port]);
if (priv->poll) { if (priv->poll)
/* skip polling if we don't get the lock */ priv->poll(cvm_oct_device[port]);
if (!down_trylock(&mdio_sem)) {
priv->poll(cvm_oct_device[port]);
up(&mdio_sem);
}
}
queues_per_port = cvmx_pko_get_num_queues(port); queues_per_port = cvmx_pko_get_num_queues(port);
/* Drain any pending packets in the free list */ /* Drain any pending packets in the free list */
@ -524,7 +517,7 @@ int cvm_oct_common_init(struct net_device *dev)
dev->features |= NETIF_F_LLTX; dev->features |= NETIF_F_LLTX;
SET_ETHTOOL_OPS(dev, &cvm_oct_ethtool_ops); SET_ETHTOOL_OPS(dev, &cvm_oct_ethtool_ops);
cvm_oct_mdio_setup_device(dev); cvm_oct_phy_setup_device(dev);
dev->netdev_ops->ndo_set_mac_address(dev, &sa); dev->netdev_ops->ndo_set_mac_address(dev, &sa);
dev->netdev_ops->ndo_change_mtu(dev, dev->mtu); dev->netdev_ops->ndo_change_mtu(dev, dev->mtu);
@ -540,7 +533,10 @@ int cvm_oct_common_init(struct net_device *dev)
void cvm_oct_common_uninit(struct net_device *dev) void cvm_oct_common_uninit(struct net_device *dev)
{ {
/* Currently nothing to do */ struct octeon_ethernet *priv = netdev_priv(dev);
if (priv->phydev)
phy_disconnect(priv->phydev);
} }
static const struct net_device_ops cvm_oct_npi_netdev_ops = { static const struct net_device_ops cvm_oct_npi_netdev_ops = {
@ -627,6 +623,8 @@ static const struct net_device_ops cvm_oct_pow_netdev_ops = {
#endif #endif
}; };
extern void octeon_mdiobus_force_mod_depencency(void);
/** /**
* Module/ driver initialization. Creates the linux network * Module/ driver initialization. Creates the linux network
* devices. * devices.
@ -640,6 +638,7 @@ static int __init cvm_oct_init_module(void)
int fau = FAU_NUM_PACKET_BUFFERS_TO_FREE; int fau = FAU_NUM_PACKET_BUFFERS_TO_FREE;
int qos; int qos;
octeon_mdiobus_force_mod_depencency();
pr_notice("cavium-ethernet %s\n", OCTEON_ETHERNET_VERSION); pr_notice("cavium-ethernet %s\n", OCTEON_ETHERNET_VERSION);
if (OCTEON_IS_MODEL(OCTEON_CN52XX)) if (OCTEON_IS_MODEL(OCTEON_CN52XX))

View file

@ -50,9 +50,9 @@ struct octeon_ethernet {
/* List of outstanding tx buffers per queue */ /* List of outstanding tx buffers per queue */
struct sk_buff_head tx_free_list[16]; struct sk_buff_head tx_free_list[16];
/* Device statistics */ /* Device statistics */
struct net_device_stats stats struct net_device_stats stats;
; /* Generic MII info structure */ struct phy_device *phydev;
struct mii_if_info mii_info; unsigned int last_link;
/* Last negotiated link state */ /* Last negotiated link state */
uint64_t link_info; uint64_t link_info;
/* Called periodically to check link status */ /* Called periodically to check link status */