Commit 23470933 authored by David S. Miller's avatar David S. Miller

Merge branch 'phylink-and-sfp-support'

Russell King says:

====================
phylink and sfp support

This patch series introduces generic support for SFP sockets found on
various Marvell based platforms.  The idea here is to provide common
SFP socket support which can be re-used by network drivers as
appropriate, rather than each network driver having to re-implement
SFP socket support.

SFP sockets typically use other system resources, eg, I2C buses to read
identifying information, and GPIOs to monitor socket state and control
the socket.  Meanwhile, some network drivers drive multiple ethernet
ports from one instantiation of the driver.

It is not desirable to block the initialisation of a network driver
(thus denying other ports from being operational) if the resources
for the SFP socket are not yet available.  This means that an element
of independence between the SFP support code and the driver is
required.

More than that, SFP modules effectively bring hotplug PHYs to
networking - SFP copper modules normally contain a standard PHY
accessed over the I2C bus, and it is desirable to read their state
so network drivers can be appropriately configured.

To add to the complexity, SFP modules can be connected in at least
two places:

1. Directly to the serdes output of a MAC with no intervening PHY.
   For example:

     mvneta ----> SFP socket

2. To a PHY, for example:

     mvpp2 ---> PHY ---> copper
                 |
                 `-----> SFP socket

This code supports both setups, although it's not fully implemented
with scenario (2).

Moreover, the link presented by the SFP module can be one of the
10Gbase-R family (for SFP+ sockets), SGMII or 1000base-X (for SFP
sockets) depending on the module, and network drivers need to
reconfigure themselves accordingly for the link to come up.

For example, if the MAC is configured for SGMII and a fibre module
is plugged in, the link won't come up until the MAC is reconfigured
for 1000base-X mode.

The SFP code manages the SFP socket - detecting the module, reading
the identifying information, and managing the control and status
signals.  Importantly, it disables the SFP module transmitter when
the MAC is down, so that the laser is turned off (but that is not
a guarantee.)

phylink provides the mechanisms necessary to manage the link modes,
based on the SFP module type, and supports hot-plugging of the PHY
without needing the MAC driver to be brought up and down on
transitions.  phylink also supports the classical static PHY and
fixed-link modes.

I currently (but not included in this series) have code to convert
mvneta to use phylink, and the out of tree mvpp2x driver.  I have
nothing for the mvpp2 driver at present as that driver is only
recently becoming functional on 10G hardware, and is missing a lot
of features that are necessary to make things work correctly.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 21e27f2d 73970055
......@@ -106,6 +106,16 @@ config MDIO_HISI_FEMAC
This module provides a driver for the MDIO busses found in the
Hisilicon SoC that have an Fast Ethernet MAC.
config MDIO_I2C
tristate
depends on I2C
help
Support I2C based PHYs. This provides a MDIO bus bridged
to I2C to allow PHYs connected in I2C mode to be accessed
using the existing infrastructure.
This is library mode.
config MDIO_MOXART
tristate "MOXA ART MDIO interface support"
depends on ARCH_MOXART
......@@ -159,6 +169,16 @@ menuconfig PHYLIB
devices. This option provides infrastructure for
managing PHY devices.
config PHYLINK
tristate
depends on NETDEVICES
select PHYLIB
select SWPHY
help
PHYlink models the link between the PHY and MAC, allowing fixed
configuration links, PHYs, and Serdes links with MAC level
autonegotiation modes.
if PHYLIB
config SWPHY
......@@ -180,6 +200,11 @@ config LED_TRIGGER_PHY
comment "MII PHY device drivers"
config SFP
tristate "SFP cage support"
depends on I2C && PHYLINK
select MDIO_I2C
config AMD_PHY
tristate "AMD PHYs"
---help---
......
......@@ -18,6 +18,7 @@ endif
libphy-$(CONFIG_SWPHY) += swphy.o
libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o
obj-$(CONFIG_PHYLINK) += phylink.o
obj-$(CONFIG_PHYLIB) += libphy.o
obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o
......@@ -30,12 +31,17 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o
obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
obj-$(CONFIG_SFP) += sfp.o
sfp-obj-$(CONFIG_SFP) += sfp-bus.o
obj-y += $(sfp-obj-y) $(sfp-obj-m)
obj-$(CONFIG_AMD_PHY) += amd.o
obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
obj-$(CONFIG_AT803X_PHY) += at803x.o
......
/*
* MDIO I2C bridge
*
* Copyright (C) 2015-2016 Russell King
*
* 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.
*
* Network PHYs can appear on I2C buses when they are part of SFP module.
* This driver exposes these PHYs to the networking PHY code, allowing
* our PHY drivers access to these PHYs, and so allowing configuration
* of their settings.
*/
#include <linux/i2c.h>
#include <linux/phy.h>
#include "mdio-i2c.h"
/*
* I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
* specified to be present in SFP modules. These correspond with PHY
* addresses 16 and 17. Disallow access to these "phy" addresses.
*/
static bool i2c_mii_valid_phy_id(int phy_id)
{
return phy_id != 0x10 && phy_id != 0x11;
}
static unsigned int i2c_mii_phy_addr(int phy_id)
{
return phy_id + 0x40;
}
static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
{
struct i2c_adapter *i2c = bus->priv;
struct i2c_msg msgs[2];
u8 data[2], dev_addr = reg;
int bus_addr, ret;
if (!i2c_mii_valid_phy_id(phy_id))
return 0xffff;
bus_addr = i2c_mii_phy_addr(phy_id);
msgs[0].addr = bus_addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &dev_addr;
msgs[1].addr = bus_addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = sizeof(data);
msgs[1].buf = data;
ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return 0xffff;
return data[0] << 8 | data[1];
}
static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
{
struct i2c_adapter *i2c = bus->priv;
struct i2c_msg msg;
int ret;
u8 data[3];
if (!i2c_mii_valid_phy_id(phy_id))
return 0;
data[0] = reg;
data[1] = val >> 8;
data[2] = val;
msg.addr = i2c_mii_phy_addr(phy_id);
msg.flags = 0;
msg.len = 3;
msg.buf = data;
ret = i2c_transfer(i2c, &msg, 1);
return ret < 0 ? ret : 0;
}
struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
{
struct mii_bus *mii;
if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
return ERR_PTR(-EINVAL);
mii = mdiobus_alloc();
if (!mii)
return ERR_PTR(-ENOMEM);
snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
mii->parent = parent;
mii->read = i2c_mii_read;
mii->write = i2c_mii_write;
mii->priv = i2c;
return mii;
}
EXPORT_SYMBOL_GPL(mdio_i2c_alloc);
MODULE_AUTHOR("Russell King");
MODULE_DESCRIPTION("MDIO I2C bridge library");
MODULE_LICENSE("GPL v2");
/*
* MDIO I2C bridge
*
* Copyright (C) 2015 Russell King
*
* 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.
*/
#ifndef MDIO_I2C_H
#define MDIO_I2C_H
struct device;
struct i2c_adapter;
struct mii_bus;
struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
#endif
......@@ -9,6 +9,186 @@
#include <linux/export.h>
#include <linux/phy.h>
const char *phy_speed_to_str(int speed)
{
switch (speed) {
case SPEED_10:
return "10Mbps";
case SPEED_100:
return "100Mbps";
case SPEED_1000:
return "1Gbps";
case SPEED_2500:
return "2.5Gbps";
case SPEED_5000:
return "5Gbps";
case SPEED_10000:
return "10Gbps";
case SPEED_14000:
return "14Gbps";
case SPEED_20000:
return "20Gbps";
case SPEED_25000:
return "25Gbps";
case SPEED_40000:
return "40Gbps";
case SPEED_50000:
return "50Gbps";
case SPEED_56000:
return "56Gbps";
case SPEED_100000:
return "100Gbps";
case SPEED_UNKNOWN:
return "Unknown";
default:
return "Unsupported (update phy-core.c)";
}
}
EXPORT_SYMBOL_GPL(phy_speed_to_str);
const char *phy_duplex_to_str(unsigned int duplex)
{
if (duplex == DUPLEX_HALF)
return "Half";
if (duplex == DUPLEX_FULL)
return "Full";
if (duplex == DUPLEX_UNKNOWN)
return "Unknown";
return "Unsupported (update phy-core.c)";
}
EXPORT_SYMBOL_GPL(phy_duplex_to_str);
/* A mapping of all SUPPORTED settings to speed/duplex. This table
* must be grouped by speed and sorted in descending match priority
* - iow, descending speed. */
static const struct phy_setting settings[] = {
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
},
{
.speed = SPEED_2500,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_HALF,
.bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_HALF,
.bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_FULL,
.bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_HALF,
.bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT,
},
};
/**
* phy_lookup_setting - lookup a PHY setting
* @speed: speed to match
* @duplex: duplex to match
* @mask: allowed link modes
* @maxbit: bit size of link modes
* @exact: an exact match is required
*
* Search the settings array for a setting that matches the speed and
* duplex, and which is supported.
*
* If @exact is unset, either an exact match or %NULL for no match will
* be returned.
*
* If @exact is set, an exact match, the fastest supported setting at
* or below the specified speed, the slowest supported setting, or if
* they all fail, %NULL will be returned.
*/
const struct phy_setting *
phy_lookup_setting(int speed, int duplex, const unsigned long *mask,
size_t maxbit, bool exact)
{
const struct phy_setting *p, *match = NULL, *last = NULL;
int i;
for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
if (p->bit < maxbit && test_bit(p->bit, mask)) {
last = p;
if (p->speed == speed && p->duplex == duplex) {
/* Exact match for speed and duplex */
match = p;
break;
} else if (!exact) {
if (!match && p->speed <= speed)
/* Candidate */
match = p;
if (p->speed < speed)
break;
}
}
}
if (!match && !exact)
match = last;
return match;
}
EXPORT_SYMBOL_GPL(phy_lookup_setting);
size_t phy_speeds(unsigned int *speeds, size_t size,
unsigned long *mask, size_t maxbit)
{
size_t count;
int i;
for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
if (settings[i].bit < maxbit &&
test_bit(settings[i].bit, mask) &&
(count == 0 || speeds[count - 1] != settings[i].speed))
speeds[count++] = settings[i].speed;
return count;
}
static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
u16 regnum)
{
......
......@@ -38,42 +38,6 @@
#include <asm/irq.h>
static const char *phy_speed_to_str(int speed)
{
switch (speed) {
case SPEED_10:
return "10Mbps";
case SPEED_100:
return "100Mbps";
case SPEED_1000:
return "1Gbps";
case SPEED_2500:
return "2.5Gbps";
case SPEED_5000:
return "5Gbps";
case SPEED_10000:
return "10Gbps";
case SPEED_14000:
return "14Gbps";
case SPEED_20000:
return "20Gbps";
case SPEED_25000:
return "25Gbps";
case SPEED_40000:
return "40Gbps";
case SPEED_50000:
return "50Gbps";
case SPEED_56000:
return "56Gbps";
case SPEED_100000:
return "100Gbps";
case SPEED_UNKNOWN:
return "Unknown";
default:
return "Unsupported (update phy.c)";
}
}
#define PHY_STATE_STR(_state) \
case PHY_##_state: \
return __stringify(_state); \
......@@ -109,7 +73,7 @@ void phy_print_status(struct phy_device *phydev)
netdev_info(phydev->attached_dev,
"Link is Up - %s/%s - flow control %s\n",
phy_speed_to_str(phydev->speed),
DUPLEX_FULL == phydev->duplex ? "Full" : "Half",
phy_duplex_to_str(phydev->duplex),
phydev->pause ? "rx/tx" : "off");
} else {
netdev_info(phydev->attached_dev, "Link is Down\n");
......@@ -193,123 +157,6 @@ int phy_aneg_done(struct phy_device *phydev)
}
EXPORT_SYMBOL(phy_aneg_done);
/* A structure for mapping a particular speed and duplex
* combination to a particular SUPPORTED and ADVERTISED value
*/
struct phy_setting {
int speed;
int duplex;
u32 setting;
};
/* A mapping of all SUPPORTED settings to speed/duplex. This table
* must be grouped by speed and sorted in descending match priority
* - iow, descending speed. */
static const struct phy_setting settings[] = {
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_10000baseKR_Full,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_10000baseKX4_Full,
},
{
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_10000baseT_Full,
},
{
.speed = SPEED_2500,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_2500baseX_Full,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_1000baseKX_Full,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_1000baseT_Full,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_1000baseT_Half,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_100baseT_Full,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_100baseT_Half,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_10baseT_Full,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_10baseT_Half,
},
};
/**
* phy_lookup_setting - lookup a PHY setting
* @speed: speed to match
* @duplex: duplex to match
* @features: allowed link modes
* @exact: an exact match is required
*
* Search the settings array for a setting that matches the speed and
* duplex, and which is supported.
*
* If @exact is unset, either an exact match or %NULL for no match will
* be returned.
*
* If @exact is set, an exact match, the fastest supported setting at
* or below the specified speed, the slowest supported setting, or if
* they all fail, %NULL will be returned.
*/
static const struct phy_setting *
phy_lookup_setting(int speed, int duplex, u32 features, bool exact)
{
const struct phy_setting *p, *match = NULL, *last = NULL;
int i;
for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
if (p->setting & features) {
last = p;
if (p->speed == speed && p->duplex == duplex) {
/* Exact match for speed and duplex */
match = p;
break;
} else if (!exact) {
if (!match && p->speed <= speed)
/* Candidate */
match = p;
if (p->speed < speed)
break;
}
}
}
if (!match && !exact)
match = last;
return match;
}
/**
* phy_find_valid - find a PHY setting that matches the requested parameters
* @speed: desired speed
......@@ -326,7 +173,9 @@ phy_lookup_setting(int speed, int duplex, u32 features, bool exact)
static const struct phy_setting *
phy_find_valid(int speed, int duplex, u32 supported)
{
return phy_lookup_setting(speed, duplex, supported, false);
unsigned long mask = supported;
return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false);
}
/**
......@@ -343,16 +192,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
unsigned int *speeds,
unsigned int size)
{
unsigned int count = 0;
unsigned int idx = 0;
unsigned long supported = phy->supported;
for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++)
/* Assumes settings are grouped by speed */
if ((settings[idx].setting & phy->supported) &&
(count == 0 || speeds[count - 1] != settings[idx].speed))
speeds[count++] = settings[idx].speed;
return count;
return phy_speeds(speeds, size, &supported, BITS_PER_LONG);
}
/**
......@@ -366,7 +208,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
*/
static inline bool phy_check_valid(int speed, int duplex, u32 features)
{
return !!phy_lookup_setting(speed, duplex, features, true);
unsigned long mask = features;
return !!phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, true);
}
/**
......@@ -712,6 +556,7 @@ void phy_start_machine(struct phy_device *phydev)
{
queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ);
}
EXPORT_SYMBOL_GPL(phy_start_machine);
/**
* phy_trigger_machine - trigger the state machine to run
......@@ -1021,9 +866,15 @@ void phy_start(struct phy_device *phydev)
}
EXPORT_SYMBOL(phy_start);
static void phy_adjust_link(struct phy_device *phydev)
static void phy_link_up(struct phy_device *phydev)
{
phydev->adjust_link(phydev->attached_dev);
phydev->phy_link_change(phydev, true, true);
phy_led_trigger_change_speed(phydev);
}
static void phy_link_down(struct phy_device *phydev, bool do_carrier)
{
phydev->phy_link_change(phydev, false, do_carrier);
phy_led_trigger_change_speed(phydev);
}
......@@ -1068,8 +919,7 @@ void phy_state_machine(struct work_struct *work)
/* If the link is down, give up on negotiation for now */
if (!phydev->link) {
phydev->state = PHY_NOLINK;
netif_carrier_off(phydev->attached_dev);
phy_adjust_link(phydev);
phy_link_down(phydev, true);
break;
}
......@@ -1081,9 +931,7 @@ void phy_state_machine(struct work_struct *work)
/* If AN is done, we're running */
if (err > 0) {
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_adjust_link(phydev);
phy_link_up(phydev);
} else if (0 == phydev->link_timeout--)
needs_aneg = true;
break;
......@@ -1108,8 +956,7 @@ void phy_state_machine(struct work_struct *work)
}
}
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_adjust_link(phydev);
phy_link_up(phydev);
}
break;
case PHY_FORCING:
......@@ -1119,13 +966,12 @@ void phy_state_machine(struct work_struct *work)
if (phydev->link) {
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_link_up(phydev);
} else {
if (0 == phydev->link_timeout--)
needs_aneg = true;
phy_link_down(phydev, false);
}
phy_adjust_link(phydev);
break;
case PHY_RUNNING:
/* Only register a CHANGE if we are polling and link changed
......@@ -1157,14 +1003,12 @@ void phy_state_machine(struct work_struct *work)
if (phydev->link) {
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_link_up(phydev);
} else {
phydev->state = PHY_NOLINK;
netif_carrier_off(phydev->attached_dev);
phy_link_down(phydev, true);
}
phy_adjust_link(phydev);
if (phy_interrupt_is_valid(phydev))
err = phy_config_interrupt(phydev,
PHY_INTERRUPT_ENABLED);
......@@ -1172,8 +1016,7 @@ void phy_state_machine(struct work_struct *work)
case PHY_HALTED:
if (phydev->link) {
phydev->link = 0;
netif_carrier_off(phydev->attached_dev);
phy_adjust_link(phydev);
phy_link_down(phydev, true);
do_suspend = true;
}
break;
......@@ -1193,11 +1036,11 @@ void phy_state_machine(struct work_struct *work)
if (phydev->link) {
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_link_up(phydev);
} else {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, false);
}
phy_adjust_link(phydev);
} else {
phydev->state = PHY_AN;
phydev->link_timeout = PHY_AN_TIMEOUT;
......@@ -1209,11 +1052,11 @@ void phy_state_machine(struct work_struct *work)
if (phydev->link) {
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phy_link_up(phydev);
} else {
phydev->state = PHY_NOLINK;
phy_link_down(phydev, false);
}
phy_adjust_link(phydev);
}
break;
}
......
......@@ -688,6 +688,19 @@ struct phy_device *phy_find_first(struct mii_bus *bus)
}
EXPORT_SYMBOL(phy_find_first);
static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier)
{
struct net_device *netdev = phydev->attached_dev;
if (do_carrier) {
if (up)
netif_carrier_on(netdev);
else
netif_carrier_off(netdev);
}
phydev->adjust_link(netdev);
}
/**
* phy_prepare_link - prepares the PHY layer to monitor link status
* @phydev: target phy_device struct
......@@ -951,6 +964,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
goto error;
}
phydev->phy_link_change = phy_link_change;
phydev->attached_dev = dev;
dev->phydev = phydev;
......@@ -1070,6 +1084,7 @@ void phy_detach(struct phy_device *phydev)
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
phy_suspend(phydev);
phydev->phylink = NULL;
phy_led_triggers_unregister(phydev);
......
/*
* phylink models the MAC to optional PHY connection, supporting
* technologies such as SFP cages where the PHY is hot-pluggable.
*
* Copyright (C) 2015 Russell King
*
* 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/ethtool.h>
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/phylink.h>
#include <linux/rtnetlink.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include "sfp.h"
#include "swphy.h"
#define SUPPORTED_INTERFACES \
(SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \
SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane)
#define ADVERTISED_INTERFACES \
(ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
enum {
PHYLINK_DISABLE_STOPPED,
PHYLINK_DISABLE_LINK,
};
struct phylink {
struct net_device *netdev;
const struct phylink_mac_ops *ops;
unsigned long phylink_disable_state; /* bitmask of disables */
struct phy_device *phydev;
phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
u8 link_an_mode; /* MLO_AN_xxx */
u8 link_port; /* The current non-phy ethtool port */
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
/* The link configuration settings */
struct phylink_link_state link_config;
struct gpio_desc *link_gpio;
struct mutex state_mutex;
struct phylink_link_state phy_state;
struct work_struct resolve;
bool mac_link_dropped;
struct sfp_bus *sfp_bus;
};
static inline void linkmode_zero(unsigned long *dst)
{
bitmap_zero(dst, __ETHTOOL_LINK_MODE_MASK_NBITS);
}
static inline void linkmode_copy(unsigned long *dst, const unsigned long *src)
{
bitmap_copy(dst, src, __ETHTOOL_LINK_MODE_MASK_NBITS);
}
static inline void linkmode_and(unsigned long *dst, const unsigned long *a,
const unsigned long *b)
{
bitmap_and(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS);
}
static inline void linkmode_or(unsigned long *dst, const unsigned long *a,
const unsigned long *b)
{
bitmap_or(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS);
}
static inline bool linkmode_empty(const unsigned long *src)
{
return bitmap_empty(src, __ETHTOOL_LINK_MODE_MASK_NBITS);
}
void phylink_set_port_modes(unsigned long *mask)
{
phylink_set(mask, TP);
phylink_set(mask, AUI);
phylink_set(mask, MII);
phylink_set(mask, FIBRE);
phylink_set(mask, BNC);
phylink_set(mask, Backplane);
}
EXPORT_SYMBOL_GPL(phylink_set_port_modes);
static int phylink_is_empty_linkmode(const unsigned long *linkmode)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, };
phylink_set_port_modes(tmp);
phylink_set(tmp, Autoneg);
phylink_set(tmp, Pause);
phylink_set(tmp, Asym_Pause);
bitmap_andnot(tmp, linkmode, tmp, __ETHTOOL_LINK_MODE_MASK_NBITS);
return linkmode_empty(tmp);
}
static const char *phylink_an_mode_str(unsigned int mode)
{
static const char *modestr[] = {
[MLO_AN_PHY] = "phy",
[MLO_AN_FIXED] = "fixed",
[MLO_AN_SGMII] = "SGMII",
[MLO_AN_8023Z] = "802.3z",
};
return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
}
static int phylink_validate(struct phylink *pl, unsigned long *supported,
struct phylink_link_state *state)
{
pl->ops->validate(pl->netdev, supported, state);
return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}
static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np)
{
struct device_node *fixed_node;
const struct phy_setting *s;
struct gpio_desc *desc;
const __be32 *fixed_prop;
u32 speed;
int ret, len;
fixed_node = of_get_child_by_name(np, "fixed-link");
if (fixed_node) {
ret = of_property_read_u32(fixed_node, "speed", &speed);
pl->link_config.speed = speed;
pl->link_config.duplex = DUPLEX_HALF;
if (of_property_read_bool(fixed_node, "full-duplex"))
pl->link_config.duplex = DUPLEX_FULL;
/* We treat the "pause" and "asym-pause" terminology as
* defining the link partner's ability. */
if (of_property_read_bool(fixed_node, "pause"))
pl->link_config.pause |= MLO_PAUSE_SYM;
if (of_property_read_bool(fixed_node, "asym-pause"))
pl->link_config.pause |= MLO_PAUSE_ASYM;
if (ret == 0) {
desc = fwnode_get_named_gpiod(&fixed_node->fwnode,
"link-gpios", 0,
GPIOD_IN, "?");
if (!IS_ERR(desc))
pl->link_gpio = desc;
else if (desc == ERR_PTR(-EPROBE_DEFER))
ret = -EPROBE_DEFER;
}
of_node_put(fixed_node);
if (ret)
return ret;
} else {
fixed_prop = of_get_property(np, "fixed-link", &len);
if (!fixed_prop) {
netdev_err(pl->netdev, "broken fixed-link?\n");
return -EINVAL;
}
if (len == 5 * sizeof(*fixed_prop)) {
pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ?
DUPLEX_FULL : DUPLEX_HALF;
pl->link_config.speed = be32_to_cpu(fixed_prop[2]);
if (be32_to_cpu(fixed_prop[3]))
pl->link_config.pause |= MLO_PAUSE_SYM;
if (be32_to_cpu(fixed_prop[4]))
pl->link_config.pause |= MLO_PAUSE_ASYM;
}
}
if (pl->link_config.speed > SPEED_1000 &&
pl->link_config.duplex != DUPLEX_FULL)
netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n",
pl->link_config.speed);
bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
linkmode_copy(pl->link_config.advertising, pl->supported);
phylink_validate(pl, pl->supported, &pl->link_config);
s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
pl->supported,
__ETHTOOL_LINK_MODE_MASK_NBITS, true);
linkmode_zero(pl->supported);
phylink_set(pl->supported, MII);
if (s) {
__set_bit(s->bit, pl->supported);
} else {
netdev_warn(pl->netdev, "fixed link %s duplex %dMbps not recognised\n",
pl->link_config.duplex == DUPLEX_FULL ? "full" : "half",
pl->link_config.speed);
}
linkmode_and(pl->link_config.advertising, pl->link_config.advertising,
pl->supported);
pl->link_config.link = 1;
pl->link_config.an_complete = 1;
return 0;
}
static int phylink_parse_mode(struct phylink *pl, struct device_node *np)
{
struct device_node *dn;
const char *managed;
dn = of_get_child_by_name(np, "fixed-link");
if (dn || of_find_property(np, "fixed-link", NULL))
pl->link_an_mode = MLO_AN_FIXED;
of_node_put(dn);
if (of_property_read_string(np, "managed", &managed) == 0 &&
strcmp(managed, "in-band-status") == 0) {
if (pl->link_an_mode == MLO_AN_FIXED) {
netdev_err(pl->netdev,
"can't use both fixed-link and in-band-status\n");
return -EINVAL;
}
linkmode_zero(pl->supported);
phylink_set(pl->supported, MII);
phylink_set(pl->supported, Autoneg);
phylink_set(pl->supported, Asym_Pause);
phylink_set(pl->supported, Pause);
pl->link_config.an_enabled = true;
switch (pl->link_config.interface) {
case PHY_INTERFACE_MODE_SGMII:
phylink_set(pl->supported, 10baseT_Half);
phylink_set(pl->supported, 10baseT_Full);
phylink_set(pl->supported, 100baseT_Half);
phylink_set(pl->supported, 100baseT_Full);
phylink_set(pl->supported, 1000baseT_Half);
phylink_set(pl->supported, 1000baseT_Full);
pl->link_an_mode = MLO_AN_SGMII;
break;
case PHY_INTERFACE_MODE_1000BASEX:
phylink_set(pl->supported, 1000baseX_Full);
pl->link_an_mode = MLO_AN_8023Z;
break;
case PHY_INTERFACE_MODE_2500BASEX:
phylink_set(pl->supported, 2500baseX_Full);
pl->link_an_mode = MLO_AN_8023Z;
break;
case PHY_INTERFACE_MODE_10GKR:
phylink_set(pl->supported, 10baseT_Half);
phylink_set(pl->supported, 10baseT_Full);
phylink_set(pl->supported, 100baseT_Half);
phylink_set(pl->supported, 100baseT_Full);
phylink_set(pl->supported, 1000baseT_Half);
phylink_set(pl->supported, 1000baseT_Full);
phylink_set(pl->supported, 1000baseX_Full);
phylink_set(pl->supported, 10000baseKR_Full);
phylink_set(pl->supported, 10000baseCR_Full);
phylink_set(pl->supported, 10000baseSR_Full);
phylink_set(pl->supported, 10000baseLR_Full);
phylink_set(pl->supported, 10000baseLRM_Full);
phylink_set(pl->supported, 10000baseER_Full);
pl->link_an_mode = MLO_AN_SGMII;
break;
default:
netdev_err(pl->netdev,
"incorrect link mode %s for in-band status\n",
phy_modes(pl->link_config.interface));
return -EINVAL;
}
linkmode_copy(pl->link_config.advertising, pl->supported);
if (phylink_validate(pl, pl->supported, &pl->link_config)) {
netdev_err(pl->netdev,
"failed to validate link configuration for in-band status\n");
return -EINVAL;
}
}
return 0;
}
static void phylink_mac_config(struct phylink *pl,
const struct phylink_link_state *state)
{
netdev_dbg(pl->netdev,
"%s: mode=%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n",
__func__, phylink_an_mode_str(pl->link_an_mode),
phy_modes(state->interface),
phy_speed_to_str(state->speed),
phy_duplex_to_str(state->duplex),
__ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
state->pause, state->link, state->an_enabled);
pl->ops->mac_config(pl->netdev, pl->link_an_mode, state);
}
static void phylink_mac_an_restart(struct phylink *pl)
{
if (pl->link_config.an_enabled &&
(pl->link_config.interface == PHY_INTERFACE_MODE_1000BASEX ||
pl->link_config.interface == PHY_INTERFACE_MODE_2500BASEX))
pl->ops->mac_an_restart(pl->netdev);
}
static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state)
{
struct net_device *ndev = pl->netdev;
linkmode_copy(state->advertising, pl->link_config.advertising);
linkmode_zero(state->lp_advertising);
state->interface = pl->link_config.interface;
state->an_enabled = pl->link_config.an_enabled;
state->link = 1;
return pl->ops->mac_link_state(ndev, state);
}
/* The fixed state is... fixed except for the link state,
* which may be determined by a GPIO.
*/
static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
{
*state = pl->link_config;
if (pl->link_gpio)
state->link = !!gpiod_get_value(pl->link_gpio);
}
/* Flow control is resolved according to our and the link partners
* advertisments using the following drawn from the 802.3 specs:
* Local device Link partner
* Pause AsymDir Pause AsymDir Result
* 1 X 1 X TX+RX
* 0 1 1 1 RX
* 1 1 0 1 TX
*/
static void phylink_resolve_flow(struct phylink *pl,
struct phylink_link_state *state)
{
int new_pause = 0;
if (pl->link_config.pause & MLO_PAUSE_AN) {
int pause = 0;
if (phylink_test(pl->link_config.advertising, Pause))
pause |= MLO_PAUSE_SYM;
if (phylink_test(pl->link_config.advertising, Asym_Pause))
pause |= MLO_PAUSE_ASYM;
pause &= state->pause;
if (pause & MLO_PAUSE_SYM)
new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX;
else if (pause & MLO_PAUSE_ASYM)
new_pause = state->pause & MLO_PAUSE_SYM ?
MLO_PAUSE_RX : MLO_PAUSE_TX;
} else {
new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK;
}
state->pause &= ~MLO_PAUSE_TXRX_MASK;
state->pause |= new_pause;
}
static const char *phylink_pause_to_str(int pause)
{
switch (pause & MLO_PAUSE_TXRX_MASK) {
case MLO_PAUSE_TX | MLO_PAUSE_RX:
return "rx/tx";
case MLO_PAUSE_TX:
return "tx";
case MLO_PAUSE_RX:
return "rx";
default:
return "off";
}
}
static void phylink_resolve(struct work_struct *w)
{
struct phylink *pl = container_of(w, struct phylink, resolve);
struct phylink_link_state link_state;
struct net_device *ndev = pl->netdev;
mutex_lock(&pl->state_mutex);
if (pl->phylink_disable_state) {
pl->mac_link_dropped = false;
link_state.link = false;
} else if (pl->mac_link_dropped) {
link_state.link = false;
} else {
switch (pl->link_an_mode) {
case MLO_AN_PHY:
link_state = pl->phy_state;
phylink_resolve_flow(pl, &link_state);
phylink_mac_config(pl, &link_state);
break;
case MLO_AN_FIXED:
phylink_get_fixed_state(pl, &link_state);
phylink_mac_config(pl, &link_state);
break;
case MLO_AN_SGMII:
phylink_get_mac_state(pl, &link_state);
if (pl->phydev) {
bool changed = false;
link_state.link = link_state.link &&
pl->phy_state.link;
if (pl->phy_state.interface !=
link_state.interface) {
link_state.interface = pl->phy_state.interface;
changed = true;
}
/* Propagate the flow control from the PHY
* to the MAC. Also propagate the interface
* if changed.
*/
if (pl->phy_state.link || changed) {
link_state.pause |= pl->phy_state.pause;
phylink_resolve_flow(pl, &link_state);
phylink_mac_config(pl, &link_state);
}
}
break;
case MLO_AN_8023Z:
phylink_get_mac_state(pl, &link_state);
break;
}
}
if (link_state.link != netif_carrier_ok(ndev)) {
if (!link_state.link) {
netif_carrier_off(ndev);
pl->ops->mac_link_down(ndev, pl->link_an_mode);
netdev_info(ndev, "Link is Down\n");
} else {
pl->ops->mac_link_up(ndev, pl->link_an_mode,
pl->phydev);
netif_carrier_on(ndev);
netdev_info(ndev,
"Link is Up - %s/%s - flow control %s\n",
phy_speed_to_str(link_state.speed),
phy_duplex_to_str(link_state.duplex),
phylink_pause_to_str(link_state.pause));
}
}
if (!link_state.link && pl->mac_link_dropped) {
pl->mac_link_dropped = false;
queue_work(system_power_efficient_wq, &pl->resolve);
}
mutex_unlock(&pl->state_mutex);
}
static void phylink_run_resolve(struct phylink *pl)
{
if (!pl->phylink_disable_state)
queue_work(system_power_efficient_wq, &pl->resolve);
}
static const struct sfp_upstream_ops sfp_phylink_ops;
static int phylink_register_sfp(struct phylink *pl, struct device_node *np)
{
struct device_node *sfp_np;
sfp_np = of_parse_phandle(np, "sfp", 0);
if (!sfp_np)
return 0;
pl->sfp_bus = sfp_register_upstream(sfp_np, pl->netdev, pl,
&sfp_phylink_ops);
if (!pl->sfp_bus)
return -ENOMEM;
return 0;
}
struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
phy_interface_t iface, const struct phylink_mac_ops *ops)
{
struct phylink *pl;
int ret;
pl = kzalloc(sizeof(*pl), GFP_KERNEL);
if (!pl)
return ERR_PTR(-ENOMEM);
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
pl->netdev = ndev;
pl->phy_state.interface = iface;
pl->link_interface = iface;
pl->link_port = PORT_MII;
pl->link_config.interface = iface;
pl->link_config.pause = MLO_PAUSE_AN;
pl->link_config.speed = SPEED_UNKNOWN;
pl->link_config.duplex = DUPLEX_UNKNOWN;
pl->ops = ops;
__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
linkmode_copy(pl->link_config.advertising, pl->supported);
phylink_validate(pl, pl->supported, &pl->link_config);
ret = phylink_parse_mode(pl, np);
if (ret < 0) {
kfree(pl);
return ERR_PTR(ret);
}
if (pl->link_an_mode == MLO_AN_FIXED) {
ret = phylink_parse_fixedlink(pl, np);
if (ret < 0) {
kfree(pl);
return ERR_PTR(ret);
}
}
ret = phylink_register_sfp(pl, np);
if (ret < 0) {
kfree(pl);
return ERR_PTR(ret);
}
return pl;
}
EXPORT_SYMBOL_GPL(phylink_create);
void phylink_destroy(struct phylink *pl)
{
if (pl->sfp_bus)
sfp_unregister_upstream(pl->sfp_bus);
cancel_work_sync(&pl->resolve);
kfree(pl);
}
EXPORT_SYMBOL_GPL(phylink_destroy);
void phylink_phy_change(struct phy_device *phydev, bool up, bool do_carrier)
{
struct phylink *pl = phydev->phylink;
mutex_lock(&pl->state_mutex);
pl->phy_state.speed = phydev->speed;
pl->phy_state.duplex = phydev->duplex;
pl->phy_state.pause = MLO_PAUSE_NONE;
if (phydev->pause)
pl->phy_state.pause |= MLO_PAUSE_SYM;
if (phydev->asym_pause)
pl->phy_state.pause |= MLO_PAUSE_ASYM;
pl->phy_state.interface = phydev->interface;
pl->phy_state.link = up;
mutex_unlock(&pl->state_mutex);
phylink_run_resolve(pl);
netdev_dbg(pl->netdev, "phy link %s %s/%s/%s\n", up ? "up" : "down",
phy_modes(phydev->interface),
phy_speed_to_str(phydev->speed),
phy_duplex_to_str(phydev->duplex));
}
static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy)
{
struct phylink_link_state config;
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
u32 advertising;
int ret;
memset(&config, 0, sizeof(config));
ethtool_convert_legacy_u32_to_link_mode(supported, phy->supported);
ethtool_convert_legacy_u32_to_link_mode(config.advertising,
phy->advertising);
config.interface = pl->link_config.interface;
/*
* This is the new way of dealing with flow control for PHYs,
* as described by Timur Tabi in commit 529ed1275263 ("net: phy:
* phy drivers should not set SUPPORTED_[Asym_]Pause") except
* using our validate call to the MAC, we rely upon the MAC
* clearing the bits from both supported and advertising fields.
*/
if (phylink_test(supported, Pause))
phylink_set(config.advertising, Pause);
if (phylink_test(supported, Asym_Pause))
phylink_set(config.advertising, Asym_Pause);
ret = phylink_validate(pl, supported, &config);
if (ret)
return ret;
phy->phylink = pl;
phy->phy_link_change = phylink_phy_change;
netdev_info(pl->netdev,
"PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev),
phy->drv->name);
mutex_lock(&phy->lock);
mutex_lock(&pl->state_mutex);
pl->netdev->phydev = phy;
pl->phydev = phy;
linkmode_copy(pl->supported, supported);
linkmode_copy(pl->link_config.advertising, config.advertising);
/* Restrict the phy advertisment according to the MAC support. */
ethtool_convert_link_mode_to_legacy_u32(&advertising, config.advertising);
phy->advertising = advertising;
mutex_unlock(&pl->state_mutex);
mutex_unlock(&phy->lock);
netdev_dbg(pl->netdev,
"phy: setting supported %*pb advertising 0x%08x\n",
__ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported,
phy->advertising);
phy_start_machine(phy);
if (phy->irq > 0)
phy_start_interrupts(phy);
return 0;
}
int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
{
int ret;
ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface);
if (ret)
return ret;
ret = phylink_bringup_phy(pl, phy);
if (ret)
phy_detach(phy);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_connect_phy);
int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn)
{
struct device_node *phy_node;
struct phy_device *phy_dev;
int ret;
/* Fixed links are handled without needing a PHY */
if (pl->link_an_mode == MLO_AN_FIXED)
return 0;
phy_node = of_parse_phandle(dn, "phy-handle", 0);
if (!phy_node)
phy_node = of_parse_phandle(dn, "phy", 0);
if (!phy_node)
phy_node = of_parse_phandle(dn, "phy-device", 0);
if (!phy_node) {
if (pl->link_an_mode == MLO_AN_PHY) {
netdev_err(pl->netdev, "unable to find PHY node\n");
return -ENODEV;
}
return 0;
}
phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface);
/* We're done with the phy_node handle */
of_node_put(phy_node);
if (!phy_dev)
return -ENODEV;
ret = phylink_bringup_phy(pl, phy_dev);
if (ret)
phy_detach(phy_dev);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
void phylink_disconnect_phy(struct phylink *pl)
{
struct phy_device *phy;
WARN_ON(!lockdep_rtnl_is_held());
phy = pl->phydev;
if (phy) {
mutex_lock(&phy->lock);
mutex_lock(&pl->state_mutex);
pl->netdev->phydev = NULL;
pl->phydev = NULL;
mutex_unlock(&pl->state_mutex);
mutex_unlock(&phy->lock);
flush_work(&pl->resolve);
phy_disconnect(phy);
}
}
EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
void phylink_mac_change(struct phylink *pl, bool up)
{
if (!up)
pl->mac_link_dropped = true;
phylink_run_resolve(pl);
netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down");
}
EXPORT_SYMBOL_GPL(phylink_mac_change);
void phylink_start(struct phylink *pl)
{
WARN_ON(!lockdep_rtnl_is_held());
netdev_info(pl->netdev, "configuring for %s/%s link mode\n",
phylink_an_mode_str(pl->link_an_mode),
phy_modes(pl->link_config.interface));
/* Apply the link configuration to the MAC when starting. This allows
* a fixed-link to start with the correct parameters, and also
* ensures that we set the appropriate advertisment for Serdes links.
*/
phylink_resolve_flow(pl, &pl->link_config);
phylink_mac_config(pl, &pl->link_config);
clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
phylink_run_resolve(pl);
if (pl->sfp_bus)
sfp_upstream_start(pl->sfp_bus);
if (pl->phydev)
phy_start(pl->phydev);
}
EXPORT_SYMBOL_GPL(phylink_start);
void phylink_stop(struct phylink *pl)
{
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
phy_stop(pl->phydev);
if (pl->sfp_bus)
sfp_upstream_stop(pl->sfp_bus);
set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
flush_work(&pl->resolve);
}
EXPORT_SYMBOL_GPL(phylink_stop);
void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol)
{
WARN_ON(!lockdep_rtnl_is_held());
wol->supported = 0;
wol->wolopts = 0;
if (pl->phydev)
phy_ethtool_get_wol(pl->phydev, wol);
}
EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol);
int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol)
{
int ret = -EOPNOTSUPP;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_ethtool_set_wol(pl->phydev, wol);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol);
static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask);
linkmode_zero(mask);
phylink_set_port_modes(mask);
linkmode_and(dst, dst, mask);
linkmode_or(dst, dst, b);
}
static void phylink_get_ksettings(const struct phylink_link_state *state,
struct ethtool_link_ksettings *kset)
{
phylink_merge_link_mode(kset->link_modes.advertising, state->advertising);
linkmode_copy(kset->link_modes.lp_advertising, state->lp_advertising);
kset->base.speed = state->speed;
kset->base.duplex = state->duplex;
kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE :
AUTONEG_DISABLE;
}
int phylink_ethtool_ksettings_get(struct phylink *pl,
struct ethtool_link_ksettings *kset)
{
struct phylink_link_state link_state;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev) {
phy_ethtool_ksettings_get(pl->phydev, kset);
} else {
kset->base.port = pl->link_port;
}
linkmode_copy(kset->link_modes.supported, pl->supported);
switch (pl->link_an_mode) {
case MLO_AN_FIXED:
/* We are using fixed settings. Report these as the
* current link settings - and note that these also
* represent the supported speeds/duplex/pause modes.
*/
phylink_get_fixed_state(pl, &link_state);
phylink_get_ksettings(&link_state, kset);
break;
case MLO_AN_SGMII:
/* If there is a phy attached, then use the reported
* settings from the phy with no modification.
*/
if (pl->phydev)
break;
case MLO_AN_8023Z:
phylink_get_mac_state(pl, &link_state);
/* The MAC is reporting the link results from its own PCS
* layer via in-band status. Report these as the current
* link settings.
*/
phylink_get_ksettings(&link_state, kset);
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
int phylink_ethtool_ksettings_set(struct phylink *pl,
const struct ethtool_link_ksettings *kset)
{
struct ethtool_link_ksettings our_kset;
struct phylink_link_state config;
int ret;
WARN_ON(!lockdep_rtnl_is_held());
if (kset->base.autoneg != AUTONEG_DISABLE &&
kset->base.autoneg != AUTONEG_ENABLE)
return -EINVAL;
config = pl->link_config;
/* Mask out unsupported advertisments */
linkmode_and(config.advertising, kset->link_modes.advertising,
pl->supported);
/* FIXME: should we reject autoneg if phy/mac does not support it? */
if (kset->base.autoneg == AUTONEG_DISABLE) {
const struct phy_setting *s;
/* Autonegotiation disabled, select a suitable speed and
* duplex.
*/
s = phy_lookup_setting(kset->base.speed, kset->base.duplex,
pl->supported,
__ETHTOOL_LINK_MODE_MASK_NBITS, false);
if (!s)
return -EINVAL;
/* If we have a fixed link (as specified by firmware), refuse
* to change link parameters.
*/
if (pl->link_an_mode == MLO_AN_FIXED &&
(s->speed != pl->link_config.speed ||
s->duplex != pl->link_config.duplex))
return -EINVAL;
config.speed = s->speed;
config.duplex = s->duplex;
config.an_enabled = false;
__clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising);
} else {
/* If we have a fixed link, refuse to enable autonegotiation */
if (pl->link_an_mode == MLO_AN_FIXED)
return -EINVAL;
config.speed = SPEED_UNKNOWN;
config.duplex = DUPLEX_UNKNOWN;
config.an_enabled = true;
__set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising);
}
if (phylink_validate(pl, pl->supported, &config))
return -EINVAL;
/* If autonegotiation is enabled, we must have an advertisment */
if (config.an_enabled && phylink_is_empty_linkmode(config.advertising))
return -EINVAL;
our_kset = *kset;
linkmode_copy(our_kset.link_modes.advertising, config.advertising);
our_kset.base.speed = config.speed;
our_kset.base.duplex = config.duplex;
/* If we have a PHY, configure the phy */
if (pl->phydev) {
ret = phy_ethtool_ksettings_set(pl->phydev, &our_kset);
if (ret)
return ret;
}
mutex_lock(&pl->state_mutex);
/* Configure the MAC to match the new settings */
linkmode_copy(pl->link_config.advertising, our_kset.link_modes.advertising);
pl->link_config.speed = our_kset.base.speed;
pl->link_config.duplex = our_kset.base.duplex;
pl->link_config.an_enabled = our_kset.base.autoneg != AUTONEG_DISABLE;
if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) {
phylink_mac_config(pl, &pl->link_config);
phylink_mac_an_restart(pl);
}
mutex_unlock(&pl->state_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set);
int phylink_ethtool_nway_reset(struct phylink *pl)
{
int ret = 0;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_restart_aneg(pl->phydev);
phylink_mac_an_restart(pl);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
void phylink_ethtool_get_pauseparam(struct phylink *pl,
struct ethtool_pauseparam *pause)
{
WARN_ON(!lockdep_rtnl_is_held());
pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
}
EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);
int phylink_ethtool_set_pauseparam(struct phylink *pl,
struct ethtool_pauseparam *pause)
{
struct phylink_link_state *config = &pl->link_config;
WARN_ON(!lockdep_rtnl_is_held());
if (!phylink_test(pl->supported, Pause) &&
!phylink_test(pl->supported, Asym_Pause))
return -EOPNOTSUPP;
if (!phylink_test(pl->supported, Asym_Pause) &&
!pause->autoneg && pause->rx_pause != pause->tx_pause)
return -EINVAL;
config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK);
if (pause->autoneg)
config->pause |= MLO_PAUSE_AN;
if (pause->rx_pause)
config->pause |= MLO_PAUSE_RX;
if (pause->tx_pause)
config->pause |= MLO_PAUSE_TX;
if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) {
switch (pl->link_an_mode) {
case MLO_AN_PHY:
/* Silently mark the carrier down, and then trigger a resolve */
netif_carrier_off(pl->netdev);
phylink_run_resolve(pl);
break;
case MLO_AN_FIXED:
/* Should we allow fixed links to change against the config? */
phylink_resolve_flow(pl, config);
phylink_mac_config(pl, config);
break;
case MLO_AN_SGMII:
case MLO_AN_8023Z:
phylink_mac_config(pl, config);
phylink_mac_an_restart(pl);
break;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
int phylink_ethtool_get_module_info(struct phylink *pl,
struct ethtool_modinfo *modinfo)
{
int ret = -EOPNOTSUPP;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->sfp_bus)
ret = sfp_get_module_info(pl->sfp_bus, modinfo);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info);
int phylink_ethtool_get_module_eeprom(struct phylink *pl,
struct ethtool_eeprom *ee, u8 *buf)
{
int ret = -EOPNOTSUPP;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->sfp_bus)
ret = sfp_get_module_eeprom(pl->sfp_bus, ee, buf);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom);
int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
{
int ret = -EPROTONOSUPPORT;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_init_eee(pl->phydev, clk_stop_enable);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_init_eee);
int phylink_get_eee_err(struct phylink *pl)
{
int ret = 0;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_get_eee_err(pl->phydev);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_get_eee_err);
int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
{
int ret = -EOPNOTSUPP;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_ethtool_get_eee(pl->phydev, eee);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
{
int ret = -EOPNOTSUPP;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev)
ret = phy_ethtool_set_eee(pl->phydev, eee);
return ret;
}
EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
/* This emulates MII registers for a fixed-mode phy operating as per the
* passed in state. "aneg" defines if we report negotiation is possible.
*
* FIXME: should deal with negotiation state too.
*/
static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg,
struct phylink_link_state *state, bool aneg)
{
struct fixed_phy_status fs;
int val;
fs.link = state->link;
fs.speed = state->speed;
fs.duplex = state->duplex;
fs.pause = state->pause & MLO_PAUSE_SYM;
fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
val = swphy_read_reg(reg, &fs);
if (reg == MII_BMSR) {
if (!state->an_complete)
val &= ~BMSR_ANEGCOMPLETE;
if (!aneg)
val &= ~BMSR_ANEGCAPABLE;
}
return val;
}
static int phylink_phy_read(struct phylink *pl, unsigned int phy_id,
unsigned int reg)
{
struct phy_device *phydev = pl->phydev;
int prtad, devad;
if (mdio_phy_id_is_c45(phy_id)) {
prtad = mdio_phy_id_prtad(phy_id);
devad = mdio_phy_id_devad(phy_id);
devad = MII_ADDR_C45 | devad << 16 | reg;
} else if (phydev->is_c45) {
switch (reg) {
case MII_BMCR:
case MII_BMSR:
case MII_PHYSID1:
case MII_PHYSID2:
devad = __ffs(phydev->c45_ids.devices_in_package);
break;
case MII_ADVERTISE:
case MII_LPA:
if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN))
return -EINVAL;
devad = MDIO_MMD_AN;
if (reg == MII_ADVERTISE)
reg = MDIO_AN_ADVERTISE;
else
reg = MDIO_AN_LPA;
break;
default:
return -EINVAL;
}
prtad = phy_id;
devad = MII_ADDR_C45 | devad << 16 | reg;
} else {
prtad = phy_id;
devad = reg;
}
return mdiobus_read(pl->phydev->mdio.bus, prtad, devad);
}
static int phylink_phy_write(struct phylink *pl, unsigned int phy_id,
unsigned int reg, unsigned int val)
{
struct phy_device *phydev = pl->phydev;
int prtad, devad;
if (mdio_phy_id_is_c45(phy_id)) {
prtad = mdio_phy_id_prtad(phy_id);
devad = mdio_phy_id_devad(phy_id);
devad = MII_ADDR_C45 | devad << 16 | reg;
} else if (phydev->is_c45) {
switch (reg) {
case MII_BMCR:
case MII_BMSR:
case MII_PHYSID1:
case MII_PHYSID2:
devad = __ffs(phydev->c45_ids.devices_in_package);
break;
case MII_ADVERTISE:
case MII_LPA:
if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN))
return -EINVAL;
devad = MDIO_MMD_AN;
if (reg == MII_ADVERTISE)
reg = MDIO_AN_ADVERTISE;
else
reg = MDIO_AN_LPA;
break;
default:
return -EINVAL;
}
prtad = phy_id;
devad = MII_ADDR_C45 | devad << 16 | reg;
} else {
prtad = phy_id;
devad = reg;
}
return mdiobus_write(phydev->mdio.bus, prtad, devad, val);
}
static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
unsigned int reg)
{
struct phylink_link_state state;
int val = 0xffff;
switch (pl->link_an_mode) {
case MLO_AN_FIXED:
if (phy_id == 0) {
phylink_get_fixed_state(pl, &state);
val = phylink_mii_emul_read(pl->netdev, reg, &state,
true);
}
break;
case MLO_AN_PHY:
return -EOPNOTSUPP;
case MLO_AN_SGMII:
/* No phy, fall through to 8023z method */
case MLO_AN_8023Z:
if (phy_id == 0) {
val = phylink_get_mac_state(pl, &state);
if (val < 0)
return val;
val = phylink_mii_emul_read(pl->netdev, reg, &state,
true);
}
break;
}
return val & 0xffff;
}
static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
unsigned int reg, unsigned int val)
{
switch (pl->link_an_mode) {
case MLO_AN_FIXED:
break;
case MLO_AN_PHY:
return -EOPNOTSUPP;
case MLO_AN_SGMII:
/* No phy, fall through to 8023z method */
case MLO_AN_8023Z:
break;
}
return 0;
}
int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
{
struct mii_ioctl_data *mii = if_mii(ifr);
int ret;
WARN_ON(!lockdep_rtnl_is_held());
if (pl->phydev) {
/* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */
switch (cmd) {
case SIOCGMIIPHY:
mii->phy_id = pl->phydev->mdio.addr;
case SIOCGMIIREG:
ret = phylink_phy_read(pl, mii->phy_id, mii->reg_num);
if (ret >= 0) {
mii->val_out = ret;
ret = 0;
}
break;
case SIOCSMIIREG:
ret = phylink_phy_write(pl, mii->phy_id, mii->reg_num,
mii->val_in);
break;
default:
ret = phy_mii_ioctl(pl->phydev, ifr, cmd);
break;
}
} else {
switch (cmd) {
case SIOCGMIIPHY:
mii->phy_id = 0;
case SIOCGMIIREG:
ret = phylink_mii_read(pl, mii->phy_id, mii->reg_num);
if (ret >= 0) {
mii->val_out = ret;
ret = 0;
}
break;
case SIOCSMIIREG:
ret = phylink_mii_write(pl, mii->phy_id, mii->reg_num,
mii->val_in);
break;
default:
ret = -EOPNOTSUPP;
break;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
static int phylink_sfp_module_insert(void *upstream,
const struct sfp_eeprom_id *id)
{
struct phylink *pl = upstream;
__ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
struct phylink_link_state config;
phy_interface_t iface;
int mode, ret = 0;
bool changed;
u8 port;
sfp_parse_support(pl->sfp_bus, id, support);
port = sfp_parse_port(pl->sfp_bus, id, support);
iface = sfp_parse_interface(pl->sfp_bus, id);
WARN_ON(!lockdep_rtnl_is_held());
switch (iface) {
case PHY_INTERFACE_MODE_SGMII:
mode = MLO_AN_SGMII;
break;
case PHY_INTERFACE_MODE_1000BASEX:
mode = MLO_AN_8023Z;
break;
default:
return -EINVAL;
}
memset(&config, 0, sizeof(config));
linkmode_copy(config.advertising, support);
config.interface = iface;
config.speed = SPEED_UNKNOWN;
config.duplex = DUPLEX_UNKNOWN;
config.pause = MLO_PAUSE_AN;
config.an_enabled = pl->link_config.an_enabled;
/* Ignore errors if we're expecting a PHY to attach later */
ret = phylink_validate(pl, support, &config);
if (ret) {
netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n",
phylink_an_mode_str(mode), phy_modes(config.interface),
__ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
return ret;
}
netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n",
phylink_an_mode_str(mode), phy_modes(config.interface),
__ETHTOOL_LINK_MODE_MASK_NBITS, support);
if (mode == MLO_AN_8023Z && pl->phydev)
return -EINVAL;
changed = !bitmap_equal(pl->supported, support,
__ETHTOOL_LINK_MODE_MASK_NBITS);
if (changed) {
linkmode_copy(pl->supported, support);
linkmode_copy(pl->link_config.advertising, config.advertising);
}
if (pl->link_an_mode != mode ||
pl->link_config.interface != config.interface) {
pl->link_config.interface = config.interface;
pl->link_an_mode = mode;
changed = true;
netdev_info(pl->netdev, "switched to %s/%s link mode\n",
phylink_an_mode_str(mode),
phy_modes(config.interface));
}
pl->link_port = port;
if (changed && !test_bit(PHYLINK_DISABLE_STOPPED,
&pl->phylink_disable_state))
phylink_mac_config(pl, &pl->link_config);
return ret;
}
static void phylink_sfp_link_down(void *upstream)
{
struct phylink *pl = upstream;
WARN_ON(!lockdep_rtnl_is_held());
set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
flush_work(&pl->resolve);
netif_carrier_off(pl->netdev);
}
static void phylink_sfp_link_up(void *upstream)
{
struct phylink *pl = upstream;
WARN_ON(!lockdep_rtnl_is_held());
clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
phylink_run_resolve(pl);
}
static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
{
return phylink_connect_phy(upstream, phy);
}
static void phylink_sfp_disconnect_phy(void *upstream)
{
phylink_disconnect_phy(upstream);
}
static const struct sfp_upstream_ops sfp_phylink_ops = {
.module_insert = phylink_sfp_module_insert,
.link_up = phylink_sfp_link_up,
.link_down = phylink_sfp_link_down,
.connect_phy = phylink_sfp_connect_phy,
.disconnect_phy = phylink_sfp_disconnect_phy,
};
MODULE_LICENSE("GPL");
#include <linux/export.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/phylink.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include "sfp.h"
struct sfp_bus {
struct kref kref;
struct list_head node;
struct device_node *device_node;
const struct sfp_socket_ops *socket_ops;
struct device *sfp_dev;
struct sfp *sfp;
const struct sfp_upstream_ops *upstream_ops;
void *upstream;
struct net_device *netdev;
struct phy_device *phydev;
bool registered;
bool started;
};
int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
unsigned long *support)
{
int port;
/* port is the physical connector, set this from the connector field. */
switch (id->base.connector) {
case SFP_CONNECTOR_SC:
case SFP_CONNECTOR_FIBERJACK:
case SFP_CONNECTOR_LC:
case SFP_CONNECTOR_MT_RJ:
case SFP_CONNECTOR_MU:
case SFP_CONNECTOR_OPTICAL_PIGTAIL:
if (support)
phylink_set(support, FIBRE);
port = PORT_FIBRE;
break;
case SFP_CONNECTOR_RJ45:
if (support)
phylink_set(support, TP);
port = PORT_TP;
break;
case SFP_CONNECTOR_UNSPEC:
if (id->base.e1000_base_t) {
if (support)
phylink_set(support, TP);
port = PORT_TP;
break;
}
/* fallthrough */
case SFP_CONNECTOR_SG: /* guess */
case SFP_CONNECTOR_MPO_1X12:
case SFP_CONNECTOR_MPO_2X16:
case SFP_CONNECTOR_HSSDC_II:
case SFP_CONNECTOR_COPPER_PIGTAIL:
case SFP_CONNECTOR_NOSEPARATE:
case SFP_CONNECTOR_MXC_2X16:
port = PORT_OTHER;
break;
default:
dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n",
id->base.connector);
port = PORT_OTHER;
break;
}
return port;
}
EXPORT_SYMBOL_GPL(sfp_parse_port);
phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
const struct sfp_eeprom_id *id)
{
phy_interface_t iface;
/* Setting the serdes link mode is guesswork: there's no field in
* the EEPROM which indicates what mode should be used.
*
* If the module wants 64b66b, then it must be >= 10G.
*
* If it's a gigabit-only fiber module, it probably does not have
* a PHY, so switch to 802.3z negotiation mode. Otherwise, switch
* to SGMII mode (which is required to support non-gigabit speeds).
*/
switch (id->base.encoding) {
case SFP_ENCODING_8472_64B66B:
iface = PHY_INTERFACE_MODE_10GKR;
break;
case SFP_ENCODING_8B10B:
if (!id->base.e1000_base_t &&
!id->base.e100_base_lx &&
!id->base.e100_base_fx)
iface = PHY_INTERFACE_MODE_1000BASEX;
else
iface = PHY_INTERFACE_MODE_SGMII;
break;
default:
iface = PHY_INTERFACE_MODE_NA;
dev_err(bus->sfp_dev,
"SFP module encoding does not support 8b10b nor 64b66b\n");
break;
}
return iface;
}
EXPORT_SYMBOL_GPL(sfp_parse_interface);
void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
unsigned long *support)
{
phylink_set(support, Autoneg);
phylink_set(support, Pause);
phylink_set(support, Asym_Pause);
/* Set ethtool support from the compliance fields. */
if (id->base.e10g_base_sr)
phylink_set(support, 10000baseSR_Full);
if (id->base.e10g_base_lr)
phylink_set(support, 10000baseLR_Full);
if (id->base.e10g_base_lrm)
phylink_set(support, 10000baseLRM_Full);
if (id->base.e10g_base_er)
phylink_set(support, 10000baseER_Full);
if (id->base.e1000_base_sx ||
id->base.e1000_base_lx ||
id->base.e1000_base_cx)
phylink_set(support, 1000baseX_Full);
if (id->base.e1000_base_t) {
phylink_set(support, 1000baseT_Half);
phylink_set(support, 1000baseT_Full);
}
switch (id->base.extended_cc) {
case 0x00: /* Unspecified */
break;
case 0x02: /* 100Gbase-SR4 or 25Gbase-SR */
phylink_set(support, 100000baseSR4_Full);
phylink_set(support, 25000baseSR_Full);
break;
case 0x03: /* 100Gbase-LR4 or 25Gbase-LR */
case 0x04: /* 100Gbase-ER4 or 25Gbase-ER */
phylink_set(support, 100000baseLR4_ER4_Full);
break;
case 0x0b: /* 100Gbase-CR4 or 25Gbase-CR CA-L */
case 0x0c: /* 25Gbase-CR CA-S */
case 0x0d: /* 25Gbase-CR CA-N */
phylink_set(support, 100000baseCR4_Full);
phylink_set(support, 25000baseCR_Full);
break;
default:
dev_warn(bus->sfp_dev,
"Unknown/unsupported extended compliance code: 0x%02x\n",
id->base.extended_cc);
break;
}
/* For fibre channel SFP, derive possible BaseX modes */
if (id->base.fc_speed_100 ||
id->base.fc_speed_200 ||
id->base.fc_speed_400) {
if (id->base.br_nominal >= 31)
phylink_set(support, 2500baseX_Full);
if (id->base.br_nominal >= 12)
phylink_set(support, 1000baseX_Full);
}
switch (id->base.connector) {
case SFP_CONNECTOR_SC:
case SFP_CONNECTOR_FIBERJACK:
case SFP_CONNECTOR_LC:
case SFP_CONNECTOR_MT_RJ:
case SFP_CONNECTOR_MU:
case SFP_CONNECTOR_OPTICAL_PIGTAIL:
break;
case SFP_CONNECTOR_UNSPEC:
if (id->base.e1000_base_t)
break;
case SFP_CONNECTOR_SG: /* guess */
case SFP_CONNECTOR_MPO_1X12:
case SFP_CONNECTOR_MPO_2X16:
case SFP_CONNECTOR_HSSDC_II:
case SFP_CONNECTOR_COPPER_PIGTAIL:
case SFP_CONNECTOR_NOSEPARATE:
case SFP_CONNECTOR_MXC_2X16:
default:
/* a guess at the supported link modes */
dev_warn(bus->sfp_dev,
"Guessing link modes, please report...\n");
phylink_set(support, 1000baseT_Half);
phylink_set(support, 1000baseT_Full);
break;
}
}
EXPORT_SYMBOL_GPL(sfp_parse_support);
static LIST_HEAD(sfp_buses);
static DEFINE_MUTEX(sfp_mutex);
static const struct sfp_upstream_ops *sfp_get_upstream_ops(struct sfp_bus *bus)
{
return bus->registered ? bus->upstream_ops : NULL;
}
static struct sfp_bus *sfp_bus_get(struct device_node *np)
{
struct sfp_bus *sfp, *new, *found = NULL;
new = kzalloc(sizeof(*new), GFP_KERNEL);
mutex_lock(&sfp_mutex);
list_for_each_entry(sfp, &sfp_buses, node) {
if (sfp->device_node == np) {
kref_get(&sfp->kref);
found = sfp;
break;
}
}
if (!found && new) {
kref_init(&new->kref);
new->device_node = np;
list_add(&new->node, &sfp_buses);
found = new;
new = NULL;
}
mutex_unlock(&sfp_mutex);
kfree(new);
return found;
}
static void sfp_bus_release(struct kref *kref) __releases(sfp_mutex)
{
struct sfp_bus *bus = container_of(kref, struct sfp_bus, kref);
list_del(&bus->node);
mutex_unlock(&sfp_mutex);
kfree(bus);
}
static void sfp_bus_put(struct sfp_bus *bus)
{
kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex);
}
static int sfp_register_bus(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = bus->upstream_ops;
int ret;
if (ops) {
if (ops->link_down)
ops->link_down(bus->upstream);
if (ops->connect_phy && bus->phydev) {
ret = ops->connect_phy(bus->upstream, bus->phydev);
if (ret)
return ret;
}
}
if (bus->started)
bus->socket_ops->start(bus->sfp);
bus->registered = true;
return 0;
}
static void sfp_unregister_bus(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = bus->upstream_ops;
if (bus->registered) {
if (bus->started)
bus->socket_ops->stop(bus->sfp);
if (bus->phydev && ops && ops->disconnect_phy)
ops->disconnect_phy(bus->upstream);
}
bus->registered = false;
}
int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo)
{
if (!bus->registered)
return -ENOIOCTLCMD;
return bus->socket_ops->module_info(bus->sfp, modinfo);
}
EXPORT_SYMBOL_GPL(sfp_get_module_info);
int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
u8 *data)
{
if (!bus->registered)
return -ENOIOCTLCMD;
return bus->socket_ops->module_eeprom(bus->sfp, ee, data);
}
EXPORT_SYMBOL_GPL(sfp_get_module_eeprom);
void sfp_upstream_start(struct sfp_bus *bus)
{
if (bus->registered)
bus->socket_ops->start(bus->sfp);
bus->started = true;
}
EXPORT_SYMBOL_GPL(sfp_upstream_start);
void sfp_upstream_stop(struct sfp_bus *bus)
{
if (bus->registered)
bus->socket_ops->stop(bus->sfp);
bus->started = false;
}
EXPORT_SYMBOL_GPL(sfp_upstream_stop);
struct sfp_bus *sfp_register_upstream(struct device_node *np,
struct net_device *ndev, void *upstream,
const struct sfp_upstream_ops *ops)
{
struct sfp_bus *bus = sfp_bus_get(np);
int ret = 0;
if (bus) {
rtnl_lock();
bus->upstream_ops = ops;
bus->upstream = upstream;
bus->netdev = ndev;
if (bus->sfp)
ret = sfp_register_bus(bus);
rtnl_unlock();
}
if (ret) {
sfp_bus_put(bus);
bus = NULL;
}
return bus;
}
EXPORT_SYMBOL_GPL(sfp_register_upstream);
void sfp_unregister_upstream(struct sfp_bus *bus)
{
rtnl_lock();
sfp_unregister_bus(bus);
bus->upstream = NULL;
bus->netdev = NULL;
rtnl_unlock();
sfp_bus_put(bus);
}
EXPORT_SYMBOL_GPL(sfp_unregister_upstream);
/* Socket driver entry points */
int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
int ret = 0;
if (ops && ops->connect_phy)
ret = ops->connect_phy(bus->upstream, phydev);
if (ret == 0)
bus->phydev = phydev;
return ret;
}
EXPORT_SYMBOL_GPL(sfp_add_phy);
void sfp_remove_phy(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
if (ops && ops->disconnect_phy)
ops->disconnect_phy(bus->upstream);
bus->phydev = NULL;
}
EXPORT_SYMBOL_GPL(sfp_remove_phy);
void sfp_link_up(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
if (ops && ops->link_up)
ops->link_up(bus->upstream);
}
EXPORT_SYMBOL_GPL(sfp_link_up);
void sfp_link_down(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
if (ops && ops->link_down)
ops->link_down(bus->upstream);
}
EXPORT_SYMBOL_GPL(sfp_link_down);
int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
int ret = 0;
if (ops && ops->module_insert)
ret = ops->module_insert(bus->upstream, id);
return ret;
}
EXPORT_SYMBOL_GPL(sfp_module_insert);
void sfp_module_remove(struct sfp_bus *bus)
{
const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
if (ops && ops->module_remove)
ops->module_remove(bus->upstream);
}
EXPORT_SYMBOL_GPL(sfp_module_remove);
struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
const struct sfp_socket_ops *ops)
{
struct sfp_bus *bus = sfp_bus_get(dev->of_node);
int ret = 0;
if (bus) {
rtnl_lock();
bus->sfp_dev = dev;
bus->sfp = sfp;
bus->socket_ops = ops;
if (bus->netdev)
ret = sfp_register_bus(bus);
rtnl_unlock();
}
if (ret) {
sfp_bus_put(bus);
bus = NULL;
}
return bus;
}
EXPORT_SYMBOL_GPL(sfp_register_socket);
void sfp_unregister_socket(struct sfp_bus *bus)
{
rtnl_lock();
sfp_unregister_bus(bus);
bus->sfp_dev = NULL;
bus->sfp = NULL;
bus->socket_ops = NULL;
rtnl_unlock();
sfp_bus_put(bus);
}
EXPORT_SYMBOL_GPL(sfp_unregister_socket);
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "mdio-i2c.h"
#include "sfp.h"
#include "swphy.h"
enum {
GPIO_MODDEF0,
GPIO_LOS,
GPIO_TX_FAULT,
GPIO_TX_DISABLE,
GPIO_RATE_SELECT,
GPIO_MAX,
SFP_F_PRESENT = BIT(GPIO_MODDEF0),
SFP_F_LOS = BIT(GPIO_LOS),
SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
SFP_E_INSERT = 0,
SFP_E_REMOVE,
SFP_E_DEV_DOWN,
SFP_E_DEV_UP,
SFP_E_TX_FAULT,
SFP_E_TX_CLEAR,
SFP_E_LOS_HIGH,
SFP_E_LOS_LOW,
SFP_E_TIMEOUT,
SFP_MOD_EMPTY = 0,
SFP_MOD_PROBE,
SFP_MOD_PRESENT,
SFP_MOD_ERROR,
SFP_DEV_DOWN = 0,
SFP_DEV_UP,
SFP_S_DOWN = 0,
SFP_S_INIT,
SFP_S_WAIT_LOS,
SFP_S_LINK_UP,
SFP_S_TX_FAULT,
SFP_S_REINIT,
SFP_S_TX_DISABLE,
};
static const char *gpio_of_names[] = {
"moddef0",
"los",
"tx-fault",
"tx-disable",
"rate-select",
};
static const enum gpiod_flags gpio_flags[] = {
GPIOD_IN,
GPIOD_IN,
GPIOD_IN,
GPIOD_ASIS,
GPIOD_ASIS,
};
#define T_INIT_JIFFIES msecs_to_jiffies(300)
#define T_RESET_US 10
#define T_FAULT_RECOVER msecs_to_jiffies(1000)
/* SFP module presence detection is poor: the three MOD DEF signals are
* the same length on the PCB, which means it's possible for MOD DEF 0 to
* connect before the I2C bus on MOD DEF 1/2.
*
* The SFP MSA specifies 300ms as t_init (the time taken for TX_FAULT to
* be deasserted) but makes no mention of the earliest time before we can
* access the I2C EEPROM. However, Avago modules require 300ms.
*/
#define T_PROBE_INIT msecs_to_jiffies(300)
#define T_PROBE_RETRY msecs_to_jiffies(100)
/*
* SFP modules appear to always have their PHY configured for bus address
* 0x56 (which with mdio-i2c, translates to a PHY address of 22).
*/
#define SFP_PHY_ADDR 22
/*
* Give this long for the PHY to reset.
*/
#define T_PHY_RESET_MS 50
static DEFINE_MUTEX(sfp_mutex);
struct sfp {
struct device *dev;
struct i2c_adapter *i2c;
struct mii_bus *i2c_mii;
struct sfp_bus *sfp_bus;
struct phy_device *mod_phy;
unsigned int (*get_state)(struct sfp *);
void (*set_state)(struct sfp *, unsigned int);
int (*read)(struct sfp *, bool, u8, void *, size_t);
struct gpio_desc *gpio[GPIO_MAX];
unsigned int state;
struct delayed_work poll;
struct delayed_work timeout;
struct mutex sm_mutex;
unsigned char sm_mod_state;
unsigned char sm_dev_state;
unsigned short sm_state;
unsigned int sm_retries;
struct sfp_eeprom_id id;
};
static unsigned long poll_jiffies;
static unsigned int sfp_gpio_get_state(struct sfp *sfp)
{
unsigned int i, state, v;
for (i = state = 0; i < GPIO_MAX; i++) {
if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
continue;
v = gpiod_get_value_cansleep(sfp->gpio[i]);
if (v)
state |= BIT(i);
}
return state;
}
static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
{
if (state & SFP_F_PRESENT) {
/* If the module is present, drive the signals */
if (sfp->gpio[GPIO_TX_DISABLE])
gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
state & SFP_F_TX_DISABLE);
if (state & SFP_F_RATE_SELECT)
gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
state & SFP_F_RATE_SELECT);
} else {
/* Otherwise, let them float to the pull-ups */
if (sfp->gpio[GPIO_TX_DISABLE])
gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
if (state & SFP_F_RATE_SELECT)
gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
}
}
static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr,
void *buf, size_t len)
{
struct i2c_msg msgs[2];
int ret;
msgs[0].addr = bus_addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &dev_addr;
msgs[1].addr = bus_addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = buf;
ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
return ret == ARRAY_SIZE(msgs) ? len : 0;
}
static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf,
size_t len)
{
return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len);
}
static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
{
struct mii_bus *i2c_mii;
int ret;
if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
return -EINVAL;
sfp->i2c = i2c;
sfp->read = sfp_i2c_read;
i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
if (IS_ERR(i2c_mii))
return PTR_ERR(i2c_mii);
i2c_mii->name = "SFP I2C Bus";
i2c_mii->phy_mask = ~0;
ret = mdiobus_register(i2c_mii);
if (ret < 0) {
mdiobus_free(i2c_mii);
return ret;
}
sfp->i2c_mii = i2c_mii;
return 0;
}
/* Interface */
static unsigned int sfp_get_state(struct sfp *sfp)
{
return sfp->get_state(sfp);
}
static void sfp_set_state(struct sfp *sfp, unsigned int state)
{
sfp->set_state(sfp, state);
}
static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
{
return sfp->read(sfp, a2, addr, buf, len);
}
static unsigned int sfp_check(void *buf, size_t len)
{
u8 *p, check;
for (p = buf, check = 0; len; p++, len--)
check += *p;
return check;
}
/* Helpers */
static void sfp_module_tx_disable(struct sfp *sfp)
{
dev_dbg(sfp->dev, "tx disable %u -> %u\n",
sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
sfp->state |= SFP_F_TX_DISABLE;
sfp_set_state(sfp, sfp->state);
}
static void sfp_module_tx_enable(struct sfp *sfp)
{
dev_dbg(sfp->dev, "tx disable %u -> %u\n",
sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
sfp->state &= ~SFP_F_TX_DISABLE;
sfp_set_state(sfp, sfp->state);
}
static void sfp_module_tx_fault_reset(struct sfp *sfp)
{
unsigned int state = sfp->state;
if (state & SFP_F_TX_DISABLE)
return;
sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
udelay(T_RESET_US);
sfp_set_state(sfp, state);
}
/* SFP state machine */
static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
{
if (timeout)
mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
timeout);
else
cancel_delayed_work(&sfp->timeout);
}
static void sfp_sm_next(struct sfp *sfp, unsigned int state,
unsigned int timeout)
{
sfp->sm_state = state;
sfp_sm_set_timer(sfp, timeout);
}
static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout)
{
sfp->sm_mod_state = state;
sfp_sm_set_timer(sfp, timeout);
}
static void sfp_sm_phy_detach(struct sfp *sfp)
{
phy_stop(sfp->mod_phy);
sfp_remove_phy(sfp->sfp_bus);
phy_device_remove(sfp->mod_phy);
phy_device_free(sfp->mod_phy);
sfp->mod_phy = NULL;
}
static void sfp_sm_probe_phy(struct sfp *sfp)
{
struct phy_device *phy;
int err;
msleep(T_PHY_RESET_MS);
phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
if (IS_ERR(phy)) {
dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
return;
}
if (!phy) {
dev_info(sfp->dev, "no PHY detected\n");
return;
}
err = sfp_add_phy(sfp->sfp_bus, phy);
if (err) {
phy_device_remove(phy);
phy_device_free(phy);
dev_err(sfp->dev, "sfp_add_phy failed: %d\n", err);
return;
}
sfp->mod_phy = phy;
phy_start(phy);
}
static void sfp_sm_link_up(struct sfp *sfp)
{
sfp_link_up(sfp->sfp_bus);
sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
}
static void sfp_sm_link_down(struct sfp *sfp)
{
sfp_link_down(sfp->sfp_bus);
}
static void sfp_sm_link_check_los(struct sfp *sfp)
{
unsigned int los = sfp->state & SFP_F_LOS;
/* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor
* SFP_OPTIONS_LOS_NORMAL are set? For now, we assume
* the same as SFP_OPTIONS_LOS_NORMAL set.
*/
if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED)
los ^= SFP_F_LOS;
if (los)
sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
else
sfp_sm_link_up(sfp);
}
static void sfp_sm_fault(struct sfp *sfp, bool warn)
{
if (sfp->sm_retries && !--sfp->sm_retries) {
dev_err(sfp->dev, "module persistently indicates fault, disabling\n");
sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
} else {
if (warn)
dev_err(sfp->dev, "module transmit fault indicated\n");
sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER);
}
}
static void sfp_sm_mod_init(struct sfp *sfp)
{
sfp_module_tx_enable(sfp);
/* Wait t_init before indicating that the link is up, provided the
* current state indicates no TX_FAULT. If TX_FAULT clears before
* this time, that's fine too.
*/
sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
sfp->sm_retries = 5;
/* Setting the serdes link mode is guesswork: there's no
* field in the EEPROM which indicates what mode should
* be used.
*
* If it's a gigabit-only fiber module, it probably does
* not have a PHY, so switch to 802.3z negotiation mode.
* Otherwise, switch to SGMII mode (which is required to
* support non-gigabit speeds) and probe for a PHY.
*/
if (sfp->id.base.e1000_base_t ||
sfp->id.base.e100_base_lx ||
sfp->id.base.e100_base_fx)
sfp_sm_probe_phy(sfp);
}
static int sfp_sm_mod_probe(struct sfp *sfp)
{
/* SFP module inserted - read I2C data */
struct sfp_eeprom_id id;
char vendor[17];
char part[17];
char sn[17];
char date[9];
char rev[5];
u8 check;
int err;
err = sfp_read(sfp, false, 0, &id, sizeof(id));
if (err < 0) {
dev_err(sfp->dev, "failed to read EEPROM: %d\n", err);
return -EAGAIN;
}
if (err != sizeof(id)) {
dev_err(sfp->dev, "EEPROM short read: %d\n", err);
return -EAGAIN;
}
/* Validate the checksum over the base structure */
check = sfp_check(&id.base, sizeof(id.base) - 1);
if (check != id.base.cc_base) {
dev_err(sfp->dev,
"EEPROM base structure checksum failure: 0x%02x\n",
check);
print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
16, 1, &id, sizeof(id.base) - 1, true);
return -EINVAL;
}
check = sfp_check(&id.ext, sizeof(id.ext) - 1);
if (check != id.ext.cc_ext) {
dev_err(sfp->dev,
"EEPROM extended structure checksum failure: 0x%02x\n",
check);
memset(&id.ext, 0, sizeof(id.ext));
}
sfp->id = id;
memcpy(vendor, sfp->id.base.vendor_name, 16);
vendor[16] = '\0';
memcpy(part, sfp->id.base.vendor_pn, 16);
part[16] = '\0';
memcpy(rev, sfp->id.base.vendor_rev, 4);
rev[4] = '\0';
memcpy(sn, sfp->id.ext.vendor_sn, 16);
sn[16] = '\0';
memcpy(date, sfp->id.ext.datecode, 8);
date[8] = '\0';
dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
/* We only support SFP modules, not the legacy GBIC modules. */
if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) {
dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n",
sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
return -EINVAL;
}
return sfp_module_insert(sfp->sfp_bus, &sfp->id);
}
static void sfp_sm_mod_remove(struct sfp *sfp)
{
sfp_module_remove(sfp->sfp_bus);
if (sfp->mod_phy)
sfp_sm_phy_detach(sfp);
sfp_module_tx_disable(sfp);
memset(&sfp->id, 0, sizeof(sfp->id));
dev_info(sfp->dev, "module removed\n");
}
static void sfp_sm_event(struct sfp *sfp, unsigned int event)
{
mutex_lock(&sfp->sm_mutex);
dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n",
sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event);
/* This state machine tracks the insert/remove state of
* the module, and handles probing the on-board EEPROM.
*/
switch (sfp->sm_mod_state) {
default:
if (event == SFP_E_INSERT) {
sfp_module_tx_disable(sfp);
sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
}
break;
case SFP_MOD_PROBE:
if (event == SFP_E_REMOVE) {
sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
} else if (event == SFP_E_TIMEOUT) {
int err = sfp_sm_mod_probe(sfp);
if (err == 0)
sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
else if (err == -EAGAIN)
sfp_sm_set_timer(sfp, T_PROBE_RETRY);
else
sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
}
break;
case SFP_MOD_PRESENT:
case SFP_MOD_ERROR:
if (event == SFP_E_REMOVE) {
sfp_sm_mod_remove(sfp);
sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
}
break;
}
/* This state machine tracks the netdev up/down state */
switch (sfp->sm_dev_state) {
default:
if (event == SFP_E_DEV_UP)
sfp->sm_dev_state = SFP_DEV_UP;
break;
case SFP_DEV_UP:
if (event == SFP_E_DEV_DOWN) {
/* If the module has a PHY, avoid raising TX disable
* as this resets the PHY. Otherwise, raise it to
* turn the laser off.
*/
if (!sfp->mod_phy)
sfp_module_tx_disable(sfp);
sfp->sm_dev_state = SFP_DEV_DOWN;
}
break;
}
/* Some events are global */
if (sfp->sm_state != SFP_S_DOWN &&
(sfp->sm_mod_state != SFP_MOD_PRESENT ||
sfp->sm_dev_state != SFP_DEV_UP)) {
if (sfp->sm_state == SFP_S_LINK_UP &&
sfp->sm_dev_state == SFP_DEV_UP)
sfp_sm_link_down(sfp);
if (sfp->mod_phy)
sfp_sm_phy_detach(sfp);
sfp_sm_next(sfp, SFP_S_DOWN, 0);
mutex_unlock(&sfp->sm_mutex);
return;
}
/* The main state machine */
switch (sfp->sm_state) {
case SFP_S_DOWN:
if (sfp->sm_mod_state == SFP_MOD_PRESENT &&
sfp->sm_dev_state == SFP_DEV_UP)
sfp_sm_mod_init(sfp);
break;
case SFP_S_INIT:
if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
sfp_sm_fault(sfp, true);
else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
sfp_sm_link_check_los(sfp);
break;
case SFP_S_WAIT_LOS:
if (event == SFP_E_TX_FAULT)
sfp_sm_fault(sfp, true);
else if (event ==
(sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
SFP_E_LOS_HIGH : SFP_E_LOS_LOW))
sfp_sm_link_up(sfp);
break;
case SFP_S_LINK_UP:
if (event == SFP_E_TX_FAULT) {
sfp_sm_link_down(sfp);
sfp_sm_fault(sfp, true);
} else if (event ==
(sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) {
sfp_sm_link_down(sfp);
sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
}
break;
case SFP_S_TX_FAULT:
if (event == SFP_E_TIMEOUT) {
sfp_module_tx_fault_reset(sfp);
sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES);
}
break;
case SFP_S_REINIT:
if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
sfp_sm_fault(sfp, false);
} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
dev_info(sfp->dev, "module transmit fault recovered\n");
sfp_sm_link_check_los(sfp);
}
break;
case SFP_S_TX_DISABLE:
break;
}
dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n",
sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state);
mutex_unlock(&sfp->sm_mutex);
}
static void sfp_start(struct sfp *sfp)
{
sfp_sm_event(sfp, SFP_E_DEV_UP);
}
static void sfp_stop(struct sfp *sfp)
{
sfp_sm_event(sfp, SFP_E_DEV_DOWN);
}
static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo)
{
/* locking... and check module is present */
if (sfp->id.ext.sff8472_compliance) {
modinfo->type = ETH_MODULE_SFF_8472;
modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
} else {
modinfo->type = ETH_MODULE_SFF_8079;
modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
}
return 0;
}
static int sfp_module_eeprom(struct sfp *sfp, struct ethtool_eeprom *ee,
u8 *data)
{
unsigned int first, last, len;
int ret;
if (ee->len == 0)
return -EINVAL;
first = ee->offset;
last = ee->offset + ee->len;
if (first < ETH_MODULE_SFF_8079_LEN) {
len = min_t(unsigned int, last, ETH_MODULE_SFF_8079_LEN);
len -= first;
ret = sfp->read(sfp, false, first, data, len);
if (ret < 0)
return ret;
first += len;
data += len;
}
if (first >= ETH_MODULE_SFF_8079_LEN &&
first < ETH_MODULE_SFF_8472_LEN) {
len = min_t(unsigned int, last, ETH_MODULE_SFF_8472_LEN);
len -= first;
first -= ETH_MODULE_SFF_8079_LEN;
ret = sfp->read(sfp, true, first, data, len);
if (ret < 0)
return ret;
}
return 0;
}
static const struct sfp_socket_ops sfp_module_ops = {
.start = sfp_start,
.stop = sfp_stop,
.module_info = sfp_module_info,
.module_eeprom = sfp_module_eeprom,
};
static void sfp_timeout(struct work_struct *work)
{
struct sfp *sfp = container_of(work, struct sfp, timeout.work);
rtnl_lock();
sfp_sm_event(sfp, SFP_E_TIMEOUT);
rtnl_unlock();
}
static void sfp_check_state(struct sfp *sfp)
{
unsigned int state, i, changed;
state = sfp_get_state(sfp);
changed = state ^ sfp->state;
changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
for (i = 0; i < GPIO_MAX; i++)
if (changed & BIT(i))
dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
!!(sfp->state & BIT(i)), !!(state & BIT(i)));
state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
sfp->state = state;
rtnl_lock();
if (changed & SFP_F_PRESENT)
sfp_sm_event(sfp, state & SFP_F_PRESENT ?
SFP_E_INSERT : SFP_E_REMOVE);
if (changed & SFP_F_TX_FAULT)
sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
if (changed & SFP_F_LOS)
sfp_sm_event(sfp, state & SFP_F_LOS ?
SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
rtnl_unlock();
}
static irqreturn_t sfp_irq(int irq, void *data)
{
struct sfp *sfp = data;
sfp_check_state(sfp);
return IRQ_HANDLED;
}
static void sfp_poll(struct work_struct *work)
{
struct sfp *sfp = container_of(work, struct sfp, poll.work);
sfp_check_state(sfp);
mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
}
static struct sfp *sfp_alloc(struct device *dev)
{
struct sfp *sfp;
sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
if (!sfp)
return ERR_PTR(-ENOMEM);
sfp->dev = dev;
mutex_init(&sfp->sm_mutex);
INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
return sfp;
}
static void sfp_cleanup(void *data)
{
struct sfp *sfp = data;
cancel_delayed_work_sync(&sfp->poll);
cancel_delayed_work_sync(&sfp->timeout);
if (sfp->i2c_mii) {
mdiobus_unregister(sfp->i2c_mii);
mdiobus_free(sfp->i2c_mii);
}
if (sfp->i2c)
i2c_put_adapter(sfp->i2c);
kfree(sfp);
}
static int sfp_probe(struct platform_device *pdev)
{
struct sfp *sfp;
bool poll = false;
int irq, err, i;
sfp = sfp_alloc(&pdev->dev);
if (IS_ERR(sfp))
return PTR_ERR(sfp);
platform_set_drvdata(pdev, sfp);
err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
if (err < 0)
return err;
if (pdev->dev.of_node) {
struct device_node *node = pdev->dev.of_node;
struct device_node *np;
np = of_parse_phandle(node, "i2c-bus", 0);
if (np) {
struct i2c_adapter *i2c;
i2c = of_find_i2c_adapter_by_node(np);
of_node_put(np);
if (!i2c)
return -EPROBE_DEFER;
err = sfp_i2c_configure(sfp, i2c);
if (err < 0) {
i2c_put_adapter(i2c);
return err;
}
}
for (i = 0; i < GPIO_MAX; i++) {
sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
gpio_of_names[i], gpio_flags[i]);
if (IS_ERR(sfp->gpio[i]))
return PTR_ERR(sfp->gpio[i]);
}
sfp->get_state = sfp_gpio_get_state;
sfp->set_state = sfp_gpio_set_state;
}
sfp->sfp_bus = sfp_register_socket(sfp->dev, sfp, &sfp_module_ops);
if (!sfp->sfp_bus)
return -ENOMEM;
/* Get the initial state, and always signal TX disable,
* since the network interface will not be up.
*/
sfp->state = sfp_get_state(sfp) | SFP_F_TX_DISABLE;
if (sfp->gpio[GPIO_RATE_SELECT] &&
gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
sfp->state |= SFP_F_RATE_SELECT;
sfp_set_state(sfp, sfp->state);
sfp_module_tx_disable(sfp);
rtnl_lock();
if (sfp->state & SFP_F_PRESENT)
sfp_sm_event(sfp, SFP_E_INSERT);
rtnl_unlock();
for (i = 0; i < GPIO_MAX; i++) {
if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
continue;
irq = gpiod_to_irq(sfp->gpio[i]);
if (!irq) {
poll = true;
continue;
}
err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
dev_name(sfp->dev), sfp);
if (err)
poll = true;
}
if (poll)
mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
return 0;
}
static int sfp_remove(struct platform_device *pdev)
{
struct sfp *sfp = platform_get_drvdata(pdev);
sfp_unregister_socket(sfp->sfp_bus);
return 0;
}
static const struct of_device_id sfp_of_match[] = {
{ .compatible = "sff,sfp", },
{ },
};
MODULE_DEVICE_TABLE(of, sfp_of_match);
static struct platform_driver sfp_driver = {
.probe = sfp_probe,
.remove = sfp_remove,
.driver = {
.name = "sfp",
.of_match_table = sfp_of_match,
},
};
static int sfp_init(void)
{
poll_jiffies = msecs_to_jiffies(100);
return platform_driver_register(&sfp_driver);
}
module_init(sfp_init);
static void sfp_exit(void)
{
platform_driver_unregister(&sfp_driver);
}
module_exit(sfp_exit);
MODULE_ALIAS("platform:sfp");
MODULE_AUTHOR("Russell King");
MODULE_LICENSE("GPL v2");
#ifndef SFP_H
#define SFP_H
#include <linux/ethtool.h>
#include <linux/sfp.h>
struct sfp;
struct sfp_socket_ops {
void (*start)(struct sfp *sfp);
void (*stop)(struct sfp *sfp);
int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
u8 *data);
};
int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
void sfp_remove_phy(struct sfp_bus *bus);
void sfp_link_up(struct sfp_bus *bus);
void sfp_link_down(struct sfp_bus *bus);
int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
void sfp_module_remove(struct sfp_bus *bus);
int sfp_link_configure(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
const struct sfp_socket_ops *ops);
void sfp_unregister_socket(struct sfp_bus *bus);
#endif
......@@ -182,6 +182,7 @@ static inline const char *phy_modes(phy_interface_t interface)
#define MII_ADDR_C45 (1<<30)
struct device;
struct phylink;
struct sk_buff;
/*
......@@ -469,11 +470,13 @@ struct phy_device {
struct mutex lock;
struct phylink *phylink;
struct net_device *attached_dev;
u8 mdix;
u8 mdix_ctrl;
void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);
void (*adjust_link)(struct net_device *dev);
};
#define to_phy_device(d) container_of(to_mdio_device(d), \
......@@ -667,6 +670,24 @@ struct phy_fixup {
int (*run)(struct phy_device *phydev);
};
const char *phy_speed_to_str(int speed);
const char *phy_duplex_to_str(unsigned int duplex);
/* A structure for mapping a particular speed and duplex
* combination to a particular SUPPORTED and ADVERTISED value
*/
struct phy_setting {
u32 speed;
u8 duplex;
u8 bit;
};
const struct phy_setting *
phy_lookup_setting(int speed, int duplex, const unsigned long *mask,
size_t maxbit, bool exact);
size_t phy_speeds(unsigned int *speeds, size_t size,
unsigned long *mask, size_t maxbit);
/**
* phy_read_mmd - Convenience function for reading a register
* from an MMD on a given PHY.
......
#ifndef NETDEV_PCS_H
#define NETDEV_PCS_H
#include <linux/phy.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
struct device_node;
struct ethtool_cmd;
struct net_device;
enum {
MLO_PAUSE_NONE,
MLO_PAUSE_ASYM = BIT(0),
MLO_PAUSE_SYM = BIT(1),
MLO_PAUSE_RX = BIT(2),
MLO_PAUSE_TX = BIT(3),
MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX,
MLO_PAUSE_AN = BIT(4),
MLO_AN_PHY = 0, /* Conventional PHY */
MLO_AN_FIXED, /* Fixed-link mode */
MLO_AN_SGMII, /* Cisco SGMII protocol */
MLO_AN_8023Z, /* 1000base-X protocol */
};
static inline bool phylink_autoneg_inband(unsigned int mode)
{
return mode == MLO_AN_SGMII || mode == MLO_AN_8023Z;
}
struct phylink_link_state {
__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
__ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising);
phy_interface_t interface; /* PHY_INTERFACE_xxx */
int speed;
int duplex;
int pause;
unsigned int link:1;
unsigned int an_enabled:1;
unsigned int an_complete:1;
};
struct phylink_mac_ops {
/**
* validate: validate and update the link configuration
* @ndev: net_device structure associated with MAC
* @config: configuration to validate
*
* Update the %config->supported and %config->advertised masks
* clearing bits that can not be supported.
*
* Note: the PHY may be able to transform from one connection
* technology to another, so, eg, don't clear 1000BaseX just
* because the MAC is unable to support it. This is more about
* clearing unsupported speeds and duplex settings.
*
* If the %config->interface mode is %PHY_INTERFACE_MODE_1000BASEX
* or %PHY_INTERFACE_MODE_2500BASEX, select the appropriate mode
* based on %config->advertised and/or %config->speed.
*/
void (*validate)(struct net_device *ndev, unsigned long *supported,
struct phylink_link_state *state);
/* Read the current link state from the hardware */
int (*mac_link_state)(struct net_device *, struct phylink_link_state *);
/* Configure the MAC */
/**
* mac_config: configure the MAC for the selected mode and state
* @ndev: net_device structure for the MAC
* @mode: one of MLO_AN_FIXED, MLO_AN_PHY, MLO_AN_8023Z, MLO_AN_SGMII
* @state: state structure
*
* The action performed depends on the currently selected mode:
*
* %MLO_AN_FIXED, %MLO_AN_PHY:
* set the specified speed, duplex, pause mode, and phy interface
* mode in the provided @state.
* %MLO_AN_8023Z:
* place the link in 1000base-X mode, advertising the parameters
* given in advertising in @state.
* %MLO_AN_SGMII:
* place the link in Cisco SGMII mode - there is no advertisment
* to make as the PHY communicates the speed and duplex to the
* MAC over the in-band control word. Configuration of the pause
* mode is as per MLO_AN_PHY since this is not included.
*/
void (*mac_config)(struct net_device *ndev, unsigned int mode,
const struct phylink_link_state *state);
/**
* mac_an_restart: restart 802.3z BaseX autonegotiation
* @ndev: net_device structure for the MAC
*/
void (*mac_an_restart)(struct net_device *ndev);
void (*mac_link_down)(struct net_device *, unsigned int mode);
void (*mac_link_up)(struct net_device *, unsigned int mode,
struct phy_device *);
};
struct phylink *phylink_create(struct net_device *, struct device_node *,
phy_interface_t iface, const struct phylink_mac_ops *ops);
void phylink_destroy(struct phylink *);
int phylink_connect_phy(struct phylink *, struct phy_device *);
int phylink_of_phy_connect(struct phylink *, struct device_node *);
void phylink_disconnect_phy(struct phylink *);
void phylink_mac_change(struct phylink *, bool up);
void phylink_start(struct phylink *);
void phylink_stop(struct phylink *);
void phylink_ethtool_get_wol(struct phylink *, struct ethtool_wolinfo *);
int phylink_ethtool_set_wol(struct phylink *, struct ethtool_wolinfo *);
int phylink_ethtool_ksettings_get(struct phylink *,
struct ethtool_link_ksettings *);
int phylink_ethtool_ksettings_set(struct phylink *,
const struct ethtool_link_ksettings *);
int phylink_ethtool_nway_reset(struct phylink *);
void phylink_ethtool_get_pauseparam(struct phylink *,
struct ethtool_pauseparam *);
int phylink_ethtool_set_pauseparam(struct phylink *,
struct ethtool_pauseparam *);
int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *);
int phylink_ethtool_get_module_eeprom(struct phylink *,
struct ethtool_eeprom *, u8 *);
int phylink_init_eee(struct phylink *, bool);
int phylink_get_eee_err(struct phylink *);
int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
#define phylink_zero(bm) \
bitmap_zero(bm, __ETHTOOL_LINK_MODE_MASK_NBITS)
#define __phylink_do_bit(op, bm, mode) \
op(ETHTOOL_LINK_MODE_ ## mode ## _BIT, bm)
#define phylink_set(bm, mode) __phylink_do_bit(__set_bit, bm, mode)
#define phylink_clear(bm, mode) __phylink_do_bit(__clear_bit, bm, mode)
#define phylink_test(bm, mode) __phylink_do_bit(test_bit, bm, mode)
void phylink_set_port_modes(unsigned long *bits);
#endif
#ifndef LINUX_SFP_H
#define LINUX_SFP_H
#include <linux/phy.h>
struct __packed sfp_eeprom_base {
u8 phys_id;
u8 phys_ext_id;
u8 connector;
#if defined __BIG_ENDIAN_BITFIELD
u8 e10g_base_er:1;
u8 e10g_base_lrm:1;
u8 e10g_base_lr:1;
u8 e10g_base_sr:1;
u8 if_1x_sx:1;
u8 if_1x_lx:1;
u8 if_1x_copper_active:1;
u8 if_1x_copper_passive:1;
u8 escon_mmf_1310_led:1;
u8 escon_smf_1310_laser:1;
u8 sonet_oc192_short_reach:1;
u8 sonet_reach_bit1:1;
u8 sonet_reach_bit2:1;
u8 sonet_oc48_long_reach:1;
u8 sonet_oc48_intermediate_reach:1;
u8 sonet_oc48_short_reach:1;
u8 unallocated_5_7:1;
u8 sonet_oc12_smf_long_reach:1;
u8 sonet_oc12_smf_intermediate_reach:1;
u8 sonet_oc12_short_reach:1;
u8 unallocated_5_3:1;
u8 sonet_oc3_smf_long_reach:1;
u8 sonet_oc3_smf_intermediate_reach:1;
u8 sonet_oc3_short_reach:1;
u8 e_base_px:1;
u8 e_base_bx10:1;
u8 e100_base_fx:1;
u8 e100_base_lx:1;
u8 e1000_base_t:1;
u8 e1000_base_cx:1;
u8 e1000_base_lx:1;
u8 e1000_base_sx:1;
u8 fc_ll_v:1;
u8 fc_ll_s:1;
u8 fc_ll_i:1;
u8 fc_ll_l:1;
u8 fc_ll_m:1;
u8 fc_tech_sa:1;
u8 fc_tech_lc:1;
u8 fc_tech_electrical_inter_enclosure:1;
u8 fc_tech_electrical_intra_enclosure:1;
u8 fc_tech_sn:1;
u8 fc_tech_sl:1;
u8 fc_tech_ll:1;
u8 sfp_ct_active:1;
u8 sfp_ct_passive:1;
u8 unallocated_8_1:1;
u8 unallocated_8_0:1;
u8 fc_media_tw:1;
u8 fc_media_tp:1;
u8 fc_media_mi:1;
u8 fc_media_tv:1;
u8 fc_media_m6:1;
u8 fc_media_m5:1;
u8 unallocated_9_1:1;
u8 fc_media_sm:1;
u8 fc_speed_1200:1;
u8 fc_speed_800:1;
u8 fc_speed_1600:1;
u8 fc_speed_400:1;
u8 fc_speed_3200:1;
u8 fc_speed_200:1;
u8 unallocated_10_1:1;
u8 fc_speed_100:1;
#elif defined __LITTLE_ENDIAN_BITFIELD
u8 if_1x_copper_passive:1;
u8 if_1x_copper_active:1;
u8 if_1x_lx:1;
u8 if_1x_sx:1;
u8 e10g_base_sr:1;
u8 e10g_base_lr:1;
u8 e10g_base_lrm:1;
u8 e10g_base_er:1;
u8 sonet_oc3_short_reach:1;
u8 sonet_oc3_smf_intermediate_reach:1;
u8 sonet_oc3_smf_long_reach:1;
u8 unallocated_5_3:1;
u8 sonet_oc12_short_reach:1;
u8 sonet_oc12_smf_intermediate_reach:1;
u8 sonet_oc12_smf_long_reach:1;
u8 unallocated_5_7:1;
u8 sonet_oc48_short_reach:1;
u8 sonet_oc48_intermediate_reach:1;
u8 sonet_oc48_long_reach:1;
u8 sonet_reach_bit2:1;
u8 sonet_reach_bit1:1;
u8 sonet_oc192_short_reach:1;
u8 escon_smf_1310_laser:1;
u8 escon_mmf_1310_led:1;
u8 e1000_base_sx:1;
u8 e1000_base_lx:1;
u8 e1000_base_cx:1;
u8 e1000_base_t:1;
u8 e100_base_lx:1;
u8 e100_base_fx:1;
u8 e_base_bx10:1;
u8 e_base_px:1;
u8 fc_tech_electrical_inter_enclosure:1;
u8 fc_tech_lc:1;
u8 fc_tech_sa:1;
u8 fc_ll_m:1;
u8 fc_ll_l:1;
u8 fc_ll_i:1;
u8 fc_ll_s:1;
u8 fc_ll_v:1;
u8 unallocated_8_0:1;
u8 unallocated_8_1:1;
u8 sfp_ct_passive:1;
u8 sfp_ct_active:1;
u8 fc_tech_ll:1;
u8 fc_tech_sl:1;
u8 fc_tech_sn:1;
u8 fc_tech_electrical_intra_enclosure:1;
u8 fc_media_sm:1;
u8 unallocated_9_1:1;
u8 fc_media_m5:1;
u8 fc_media_m6:1;
u8 fc_media_tv:1;
u8 fc_media_mi:1;
u8 fc_media_tp:1;
u8 fc_media_tw:1;
u8 fc_speed_100:1;
u8 unallocated_10_1:1;
u8 fc_speed_200:1;
u8 fc_speed_3200:1;
u8 fc_speed_400:1;
u8 fc_speed_1600:1;
u8 fc_speed_800:1;
u8 fc_speed_1200:1;
#else
#error Unknown Endian
#endif
u8 encoding;
u8 br_nominal;
u8 rate_id;
u8 link_len[6];
char vendor_name[16];
u8 extended_cc;
char vendor_oui[3];
char vendor_pn[16];
char vendor_rev[4];
union {
__be16 optical_wavelength;
u8 cable_spec;
};
u8 reserved62;
u8 cc_base;
};
struct __packed sfp_eeprom_ext {
__be16 options;
u8 br_max;
u8 br_min;
char vendor_sn[16];
char datecode[8];
u8 diagmon;
u8 enhopts;
u8 sff8472_compliance;
u8 cc_ext;
};
struct __packed sfp_eeprom_id {
struct sfp_eeprom_base base;
struct sfp_eeprom_ext ext;
};
/* SFP EEPROM registers */
enum {
SFP_PHYS_ID = 0x00,
SFP_PHYS_EXT_ID = 0x01,
SFP_CONNECTOR = 0x02,
SFP_COMPLIANCE = 0x03,
SFP_ENCODING = 0x0b,
SFP_BR_NOMINAL = 0x0c,
SFP_RATE_ID = 0x0d,
SFP_LINK_LEN_SM_KM = 0x0e,
SFP_LINK_LEN_SM_100M = 0x0f,
SFP_LINK_LEN_50UM_OM2_10M = 0x10,
SFP_LINK_LEN_62_5UM_OM1_10M = 0x11,
SFP_LINK_LEN_COPPER_1M = 0x12,
SFP_LINK_LEN_50UM_OM4_10M = 0x12,
SFP_LINK_LEN_50UM_OM3_10M = 0x13,
SFP_VENDOR_NAME = 0x14,
SFP_VENDOR_OUI = 0x25,
SFP_VENDOR_PN = 0x28,
SFP_VENDOR_REV = 0x38,
SFP_OPTICAL_WAVELENGTH_MSB = 0x3c,
SFP_OPTICAL_WAVELENGTH_LSB = 0x3d,
SFP_CABLE_SPEC = 0x3c,
SFP_CC_BASE = 0x3f,
SFP_OPTIONS = 0x40, /* 2 bytes, MSB, LSB */
SFP_BR_MAX = 0x42,
SFP_BR_MIN = 0x43,
SFP_VENDOR_SN = 0x44,
SFP_DATECODE = 0x54,
SFP_DIAGMON = 0x5c,
SFP_ENHOPTS = 0x5d,
SFP_SFF8472_COMPLIANCE = 0x5e,
SFP_CC_EXT = 0x5f,
SFP_PHYS_ID_SFP = 0x03,
SFP_PHYS_EXT_ID_SFP = 0x04,
SFP_CONNECTOR_UNSPEC = 0x00,
/* codes 01-05 not supportable on SFP, but some modules have single SC */
SFP_CONNECTOR_SC = 0x01,
SFP_CONNECTOR_FIBERJACK = 0x06,
SFP_CONNECTOR_LC = 0x07,
SFP_CONNECTOR_MT_RJ = 0x08,
SFP_CONNECTOR_MU = 0x09,
SFP_CONNECTOR_SG = 0x0a,
SFP_CONNECTOR_OPTICAL_PIGTAIL = 0x0b,
SFP_CONNECTOR_MPO_1X12 = 0x0c,
SFP_CONNECTOR_MPO_2X16 = 0x0d,
SFP_CONNECTOR_HSSDC_II = 0x20,
SFP_CONNECTOR_COPPER_PIGTAIL = 0x21,
SFP_CONNECTOR_RJ45 = 0x22,
SFP_CONNECTOR_NOSEPARATE = 0x23,
SFP_CONNECTOR_MXC_2X16 = 0x24,
SFP_ENCODING_UNSPEC = 0x00,
SFP_ENCODING_8B10B = 0x01,
SFP_ENCODING_4B5B = 0x02,
SFP_ENCODING_NRZ = 0x03,
SFP_ENCODING_8472_MANCHESTER = 0x04,
SFP_ENCODING_8472_SONET = 0x05,
SFP_ENCODING_8472_64B66B = 0x06,
SFP_ENCODING_256B257B = 0x07,
SFP_ENCODING_PAM4 = 0x08,
SFP_OPTIONS_HIGH_POWER_LEVEL = BIT(13),
SFP_OPTIONS_PAGING_A2 = BIT(12),
SFP_OPTIONS_RETIMER = BIT(11),
SFP_OPTIONS_COOLED_XCVR = BIT(10),
SFP_OPTIONS_POWER_DECL = BIT(9),
SFP_OPTIONS_RX_LINEAR_OUT = BIT(8),
SFP_OPTIONS_RX_DECISION_THRESH = BIT(7),
SFP_OPTIONS_TUNABLE_TX = BIT(6),
SFP_OPTIONS_RATE_SELECT = BIT(5),
SFP_OPTIONS_TX_DISABLE = BIT(4),
SFP_OPTIONS_TX_FAULT = BIT(3),
SFP_OPTIONS_LOS_INVERTED = BIT(2),
SFP_OPTIONS_LOS_NORMAL = BIT(1),
SFP_DIAGMON_DDM = BIT(6),
SFP_DIAGMON_INT_CAL = BIT(5),
SFP_DIAGMON_EXT_CAL = BIT(4),
SFP_DIAGMON_RXPWR_AVG = BIT(3),
SFP_DIAGMON_ADDRMODE = BIT(2),
SFP_ENHOPTS_ALARMWARN = BIT(7),
SFP_ENHOPTS_SOFT_TX_DISABLE = BIT(6),
SFP_ENHOPTS_SOFT_TX_FAULT = BIT(5),
SFP_ENHOPTS_SOFT_RX_LOS = BIT(4),
SFP_ENHOPTS_SOFT_RATE_SELECT = BIT(3),
SFP_ENHOPTS_APP_SELECT_SFF8079 = BIT(2),
SFP_ENHOPTS_SOFT_RATE_SFF8431 = BIT(1),
SFP_SFF8472_COMPLIANCE_NONE = 0x00,
SFP_SFF8472_COMPLIANCE_REV9_3 = 0x01,
SFP_SFF8472_COMPLIANCE_REV9_5 = 0x02,
SFP_SFF8472_COMPLIANCE_REV10_2 = 0x03,
SFP_SFF8472_COMPLIANCE_REV10_4 = 0x04,
SFP_SFF8472_COMPLIANCE_REV11_0 = 0x05,
SFP_SFF8472_COMPLIANCE_REV11_3 = 0x06,
SFP_SFF8472_COMPLIANCE_REV11_4 = 0x07,
SFP_SFF8472_COMPLIANCE_REV12_0 = 0x08,
};
/* SFP Diagnostics */
enum {
/* Alarm and warnings stored MSB at lower address then LSB */
SFP_TEMP_HIGH_ALARM = 0x00,
SFP_TEMP_LOW_ALARM = 0x02,
SFP_TEMP_HIGH_WARN = 0x04,
SFP_TEMP_LOW_WARN = 0x06,
SFP_VOLT_HIGH_ALARM = 0x08,
SFP_VOLT_LOW_ALARM = 0x0a,
SFP_VOLT_HIGH_WARN = 0x0c,
SFP_VOLT_LOW_WARN = 0x0e,
SFP_BIAS_HIGH_ALARM = 0x10,
SFP_BIAS_LOW_ALARM = 0x12,
SFP_BIAS_HIGH_WARN = 0x14,
SFP_BIAS_LOW_WARN = 0x16,
SFP_TXPWR_HIGH_ALARM = 0x18,
SFP_TXPWR_LOW_ALARM = 0x1a,
SFP_TXPWR_HIGH_WARN = 0x1c,
SFP_TXPWR_LOW_WARN = 0x1e,
SFP_RXPWR_HIGH_ALARM = 0x20,
SFP_RXPWR_LOW_ALARM = 0x22,
SFP_RXPWR_HIGH_WARN = 0x24,
SFP_RXPWR_LOW_WARN = 0x26,
SFP_LASER_TEMP_HIGH_ALARM = 0x28,
SFP_LASER_TEMP_LOW_ALARM = 0x2a,
SFP_LASER_TEMP_HIGH_WARN = 0x2c,
SFP_LASER_TEMP_LOW_WARN = 0x2e,
SFP_TEC_CUR_HIGH_ALARM = 0x30,
SFP_TEC_CUR_LOW_ALARM = 0x32,
SFP_TEC_CUR_HIGH_WARN = 0x34,
SFP_TEC_CUR_LOW_WARN = 0x36,
SFP_CAL_RXPWR4 = 0x38,
SFP_CAL_RXPWR3 = 0x3c,
SFP_CAL_RXPWR2 = 0x40,
SFP_CAL_RXPWR1 = 0x44,
SFP_CAL_RXPWR0 = 0x48,
SFP_CAL_TXI_SLOPE = 0x4c,
SFP_CAL_TXI_OFFSET = 0x4e,
SFP_CAL_TXPWR_SLOPE = 0x50,
SFP_CAL_TXPWR_OFFSET = 0x52,
SFP_CAL_T_SLOPE = 0x54,
SFP_CAL_T_OFFSET = 0x56,
SFP_CAL_V_SLOPE = 0x58,
SFP_CAL_V_OFFSET = 0x5a,
SFP_CHKSUM = 0x5f,
SFP_TEMP = 0x60,
SFP_VCC = 0x62,
SFP_TX_BIAS = 0x64,
SFP_TX_POWER = 0x66,
SFP_RX_POWER = 0x68,
SFP_LASER_TEMP = 0x6a,
SFP_TEC_CUR = 0x6c,
SFP_STATUS = 0x6e,
SFP_ALARM = 0x70,
SFP_EXT_STATUS = 0x76,
SFP_VSL = 0x78,
SFP_PAGE = 0x7f,
};
struct device_node;
struct ethtool_eeprom;
struct ethtool_modinfo;
struct net_device;
struct sfp_bus;
struct sfp_upstream_ops {
int (*module_insert)(void *, const struct sfp_eeprom_id *id);
void (*module_remove)(void *);
void (*link_down)(void *);
void (*link_up)(void *);
int (*connect_phy)(void *, struct phy_device *);
void (*disconnect_phy)(void *);
};
#if IS_ENABLED(CONFIG_SFP)
int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
unsigned long *support);
phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
const struct sfp_eeprom_id *id);
void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
unsigned long *support);
int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo);
int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
u8 *data);
void sfp_upstream_start(struct sfp_bus *bus);
void sfp_upstream_stop(struct sfp_bus *bus);
struct sfp_bus *sfp_register_upstream(struct device_node *np,
struct net_device *ndev, void *upstream,
const struct sfp_upstream_ops *ops);
void sfp_unregister_upstream(struct sfp_bus *bus);
#else
static inline int sfp_parse_port(struct sfp_bus *bus,
const struct sfp_eeprom_id *id,
unsigned long *support)
{
return PORT_OTHER;
}
static inline phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
const struct sfp_eeprom_id *id)
{
return PHY_INTERFACE_MODE_NA;
}
static inline void sfp_parse_support(struct sfp_bus *bus,
const struct sfp_eeprom_id *id,
unsigned long *support)
{
}
static inline int sfp_get_module_info(struct sfp_bus *bus,
struct ethtool_modinfo *modinfo)
{
return -EOPNOTSUPP;
}
static inline int sfp_get_module_eeprom(struct sfp_bus *bus,
struct ethtool_eeprom *ee, u8 *data)
{
return -EOPNOTSUPP;
}
static inline void sfp_upstream_start(struct sfp_bus *bus)
{
}
static inline void sfp_upstream_stop(struct sfp_bus *bus)
{
}
static inline struct sfp_bus *sfp_register_upstream(struct device_node *np,
struct net_device *ndev, void *upstream,
const struct sfp_upstream_ops *ops)
{
return (struct sfp_bus *)-1;
}
static inline void sfp_unregister_upstream(struct sfp_bus *bus)
{
}
#endif
#endif
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