diff options
| -rw-r--r-- | drivers/net/phy/Makefile | 3 | ||||
| -rw-r--r-- | drivers/net/phy/phylink.c | 157 | ||||
| -rw-r--r-- | drivers/net/phy/sfp-bus.c | 475 | ||||
| -rw-r--r-- | drivers/net/phy/sfp.h | 28 | ||||
| -rw-r--r-- | include/linux/sfp.h | 434 | 
5 files changed, 1097 insertions, 0 deletions
| diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index c43e5b99fda4..4c16a10f420e 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -38,6 +38,9 @@ obj-$(CONFIG_MDIO_SUN4I)	+= mdio-sun4i.o  obj-$(CONFIG_MDIO_THUNDER)	+= mdio-thunder.o  obj-$(CONFIG_MDIO_XGENE)	+= mdio-xgene.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 diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index af61d7d400af..02082f4a8a95 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -21,6 +21,7 @@  #include <linux/spinlock.h>  #include <linux/workqueue.h> +#include "sfp.h"  #include "swphy.h"  #define SUPPORTED_INTERFACES \ @@ -32,6 +33,7 @@  enum {  	PHYLINK_DISABLE_STOPPED, +	PHYLINK_DISABLE_LINK,  };  struct phylink { @@ -54,6 +56,8 @@ struct phylink {  	struct work_struct resolve;  	bool mac_link_dropped; + +	struct sfp_bus *sfp_bus;  };  static inline void linkmode_zero(unsigned long *dst) @@ -466,6 +470,24 @@ static void phylink_run_resolve(struct phylink *pl)  		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)  { @@ -507,12 +529,21 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,  		}  	} +	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);  } @@ -706,6 +737,8 @@ void phylink_start(struct phylink *pl)  	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);  } @@ -717,6 +750,8 @@ void phylink_stop(struct phylink *pl)  	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); @@ -1166,4 +1201,126 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)  }  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"); diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c new file mode 100644 index 000000000000..5cb5384697ea --- /dev/null +++ b/drivers/net/phy/sfp-bus.c @@ -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); diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h new file mode 100644 index 000000000000..31b0acf337e2 --- /dev/null +++ b/drivers/net/phy/sfp.h @@ -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 diff --git a/include/linux/sfp.h b/include/linux/sfp.h new file mode 100644 index 000000000000..4a906f560817 --- /dev/null +++ b/include/linux/sfp.h @@ -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 | 
