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: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
234709336b
@ -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
|
||||
|
109
drivers/net/phy/mdio-i2c.c
Normal file
109
drivers/net/phy/mdio-i2c.c
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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");
|
19
drivers/net/phy/mdio-i2c.h
Normal file
19
drivers/net/phy/mdio-i2c.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
|
1462
drivers/net/phy/phylink.c
Normal file
1462
drivers/net/phy/phylink.c
Normal file
File diff suppressed because it is too large
Load Diff
475
drivers/net/phy/sfp-bus.c
Normal file
475
drivers/net/phy/sfp-bus.c
Normal file
@ -0,0 +1,475 @@
|
||||
#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);
|
915
drivers/net/phy/sfp.c
Normal file
915
drivers/net/phy/sfp.c
Normal file
@ -0,0 +1,915 @@
|
||||
#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");
|
28
drivers/net/phy/sfp.h
Normal file
28
drivers/net/phy/sfp.h
Normal file
@ -0,0 +1,28 @@
|
||||
#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.
|
||||
|
148
include/linux/phylink.h
Normal file
148
include/linux/phylink.h
Normal file
@ -0,0 +1,148 @@
|
||||
#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
|
434
include/linux/sfp.h
Normal file
434
include/linux/sfp.h
Normal file
@ -0,0 +1,434 @@
|
||||
#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
|
Loading…
Reference in New Issue
Block a user