| From f9a5a54b59cb904b37bf7409a43635ab195d0214 Mon Sep 17 00:00:00 2001 | 
 | From: Russell King <rmk+kernel@armlinux.org.uk> | 
 | Date: Tue, 19 Nov 2019 10:13:25 +0000 | 
 | Subject: [PATCH 646/660] net: sfp: add module start/stop upstream | 
 |  notifications | 
 |  | 
 | When dealing with some copper modules, we can't positively know the | 
 | module capabilities are until we have probed the PHY. Without the full | 
 | capabilities, we may end up failing a module that we could otherwise | 
 | drive with a restricted set of capabilities. | 
 |  | 
 | An example of this would be a module with a NBASE-T PHY plugged into | 
 | a host that supports phy interface modes 2500BASE-X and SGMII. The | 
 | PHY supports 10GBASE-R, 5000BASE-X, 2500BASE-X, SGMII interface modes, | 
 | which means a subset of the capabilities are compatible with the host. | 
 |  | 
 | However, reading the module EEPROM leads us to believe that the module | 
 | only supports ethtool link mode 10GBASE-T, which is incompatible with | 
 | the host - and thus results in the module being rejected. | 
 |  | 
 | This patch adds an extra notification which are triggered after the | 
 | SFP module's PHY probe, and a corresponding notification just before | 
 | the PHY is removed. | 
 |  | 
 | Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> | 
 | --- | 
 |  drivers/net/phy/sfp-bus.c | 21 +++++++++++++++++++++ | 
 |  drivers/net/phy/sfp.c     |  8 ++++++++ | 
 |  drivers/net/phy/sfp.h     |  2 ++ | 
 |  include/linux/sfp.h       |  4 ++++ | 
 |  4 files changed, 35 insertions(+) | 
 |  | 
 | --- a/drivers/net/phy/sfp-bus.c | 
 | +++ b/drivers/net/phy/sfp-bus.c | 
 | @@ -717,6 +717,27 @@ void sfp_module_remove(struct sfp_bus *b | 
 |  } | 
 |  EXPORT_SYMBOL_GPL(sfp_module_remove); | 
 |   | 
 | +int sfp_module_start(struct sfp_bus *bus) | 
 | +{ | 
 | +	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); | 
 | +	int ret = 0; | 
 | + | 
 | +	if (ops && ops->module_start) | 
 | +		ret = ops->module_start(bus->upstream); | 
 | + | 
 | +	return ret; | 
 | +} | 
 | +EXPORT_SYMBOL_GPL(sfp_module_start); | 
 | + | 
 | +void sfp_module_stop(struct sfp_bus *bus) | 
 | +{ | 
 | +	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); | 
 | + | 
 | +	if (ops && ops->module_stop) | 
 | +		ops->module_stop(bus->upstream); | 
 | +} | 
 | +EXPORT_SYMBOL_GPL(sfp_module_stop); | 
 | + | 
 |  static void sfp_socket_clear(struct sfp_bus *bus) | 
 |  { | 
 |  	bus->sfp_dev = NULL; | 
 | --- a/drivers/net/phy/sfp.c | 
 | +++ b/drivers/net/phy/sfp.c | 
 | @@ -59,6 +59,7 @@ enum { | 
 |  	SFP_DEV_UP, | 
 |   | 
 |  	SFP_S_DOWN = 0, | 
 | +	SFP_S_FAIL, | 
 |  	SFP_S_WAIT, | 
 |  	SFP_S_INIT, | 
 |  	SFP_S_INIT_TX_FAULT, | 
 | @@ -122,6 +123,7 @@ static const char *event_to_str(unsigned | 
 |   | 
 |  static const char * const sm_state_strings[] = { | 
 |  	[SFP_S_DOWN] = "down", | 
 | +	[SFP_S_FAIL] = "fail", | 
 |  	[SFP_S_WAIT] = "wait", | 
 |  	[SFP_S_INIT] = "init", | 
 |  	[SFP_S_INIT_TX_FAULT] = "init_tx_fault", | 
 | @@ -1926,6 +1928,8 @@ static void sfp_sm_main(struct sfp *sfp, | 
 |  		if (sfp->sm_state == SFP_S_LINK_UP && | 
 |  		    sfp->sm_dev_state == SFP_DEV_UP) | 
 |  			sfp_sm_link_down(sfp); | 
 | +		if (sfp->sm_state > SFP_S_INIT) | 
 | +			sfp_module_stop(sfp->sfp_bus); | 
 |  		if (sfp->mod_phy) | 
 |  			sfp_sm_phy_detach(sfp); | 
 |  		sfp_module_tx_disable(sfp); | 
 | @@ -1993,6 +1997,10 @@ static void sfp_sm_main(struct sfp *sfp, | 
 |  			 * clear.  Probe for the PHY and check the LOS state. | 
 |  			 */ | 
 |  			sfp_sm_probe_for_phy(sfp); | 
 | +			if (sfp_module_start(sfp->sfp_bus)) { | 
 | +				sfp_sm_next(sfp, SFP_S_FAIL, 0); | 
 | +				break; | 
 | +			} | 
 |  			sfp_sm_link_check_los(sfp); | 
 |   | 
 |  			/* Reset the fault retry count */ | 
 | --- a/drivers/net/phy/sfp.h | 
 | +++ b/drivers/net/phy/sfp.h | 
 | @@ -22,6 +22,8 @@ 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_module_start(struct sfp_bus *bus); | 
 | +void sfp_module_stop(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); | 
 | --- a/include/linux/sfp.h | 
 | +++ b/include/linux/sfp.h | 
 | @@ -507,6 +507,8 @@ struct sfp_bus; | 
 |   * @module_insert: called after a module has been detected to determine | 
 |   *   whether the module is supported for the upstream device. | 
 |   * @module_remove: called after the module has been removed. | 
 | + * @module_start: called after the PHY probe step | 
 | + * @module_stop: called before the PHY is removed | 
 |   * @link_down: called when the link is non-operational for whatever | 
 |   *   reason. | 
 |   * @link_up: called when the link is operational. | 
 | @@ -520,6 +522,8 @@ struct sfp_upstream_ops { | 
 |  	void (*detach)(void *priv, struct sfp_bus *bus); | 
 |  	int (*module_insert)(void *priv, const struct sfp_eeprom_id *id); | 
 |  	void (*module_remove)(void *priv); | 
 | +	int (*module_start)(void *priv); | 
 | +	void (*module_stop)(void *priv); | 
 |  	void (*link_down)(void *priv); | 
 |  	void (*link_up)(void *priv); | 
 |  	int (*connect_phy)(void *priv, struct phy_device *); |