diff options
author | Russell King <rmk+kernel@armlinux.org.uk> | 2019-11-10 14:07:20 +0000 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2019-11-11 16:17:01 -0800 |
commit | b036a554d0806a711cdd0abb4f5fdbbf0253cc46 (patch) | |
tree | 5207b1740f908e34c79ecb61344994f47af7c151 | |
parent | 6b0da5c9c1a39eeead9c75eef3a0f2f62c375b2f (diff) | |
download | linux-b036a554d0806a711cdd0abb4f5fdbbf0253cc46.tar.bz2 |
net: sfp: split power mode switching from probe
Switch the power mode switching from the probe, so that we don't
repeatedly re-probe the SFP device if there is a problem accessing
the registers at I2C address 0x51.
In splitting this out, we can also fix a bug where we leave the module
in high-power mode when the upstream device is detached but the module
is still inserted.
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/phy/sfp.c | 101 |
1 files changed, 64 insertions, 37 deletions
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index bb4ede83bf53..66946accb044 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -49,6 +49,7 @@ enum { SFP_MOD_EMPTY = 0, SFP_MOD_PROBE, SFP_MOD_HPOWER, + SFP_MOD_WAITPWR, SFP_MOD_PRESENT, SFP_MOD_ERROR, @@ -71,6 +72,7 @@ static const char * const mod_state_strings[] = { [SFP_MOD_EMPTY] = "empty", [SFP_MOD_PROBE] = "probe", [SFP_MOD_HPOWER] = "hpower", + [SFP_MOD_WAITPWR] = "waitpwr", [SFP_MOD_PRESENT] = "present", [SFP_MOD_ERROR] = "error", }; @@ -1423,37 +1425,34 @@ static int sfp_module_parse_power(struct sfp *sfp) return 0; } -static int sfp_sm_mod_hpower(struct sfp *sfp) +static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable) { u8 val; int err; - if (sfp->module_power_mW <= 1000) - return 0; - err = sfp_read(sfp, true, SFP_EXT_STATUS, &val, sizeof(val)); if (err != sizeof(val)) { dev_err(sfp->dev, "Failed to read EEPROM: %d\n", err); - err = -EAGAIN; - goto err; + return -EAGAIN; } - val |= BIT(0); + if (enable) + val |= BIT(0); + else + val &= ~BIT(0); err = sfp_write(sfp, true, SFP_EXT_STATUS, &val, sizeof(val)); if (err != sizeof(val)) { dev_err(sfp->dev, "Failed to write EEPROM: %d\n", err); - err = -EAGAIN; - goto err; + return -EAGAIN; } - dev_info(sfp->dev, "Module switched to %u.%uW power level\n", - sfp->module_power_mW / 1000, - (sfp->module_power_mW / 100) % 10); - return T_HPOWER_LEVEL; + if (enable) + dev_info(sfp->dev, "Module switched to %u.%uW power level\n", + sfp->module_power_mW / 1000, + (sfp->module_power_mW / 100) % 10); -err: - return err; + return 0; } static int sfp_sm_mod_probe(struct sfp *sfp) @@ -1549,7 +1548,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp) if (ret < 0) return ret; - return sfp_sm_mod_hpower(sfp); + return 0; } static void sfp_sm_mod_remove(struct sfp *sfp) @@ -1594,13 +1593,22 @@ static void sfp_sm_device(struct sfp *sfp, unsigned int event) */ static void sfp_sm_module(struct sfp *sfp, unsigned int event) { - /* Handle remove event globally, it resets this state machine. - * Also deal with upstream detachment. - */ - if (event == SFP_E_REMOVE || sfp->sm_dev_state < SFP_DEV_DOWN) { + int err; + + /* Handle remove event globally, it resets this state machine */ + if (event == SFP_E_REMOVE) { if (sfp->sm_mod_state > SFP_MOD_PROBE) sfp_sm_mod_remove(sfp); - if (sfp->sm_mod_state != SFP_MOD_EMPTY) + sfp_sm_mod_next(sfp, SFP_MOD_EMPTY, 0); + return; + } + + /* Handle device detach globally */ + if (sfp->sm_dev_state < SFP_DEV_DOWN) { + if (sfp->module_power_mW > 1000 && + sfp->sm_mod_state > SFP_MOD_HPOWER) + sfp_sm_mod_hpower(sfp, false); + if (sfp->sm_mod_state > SFP_MOD_EMPTY) sfp_sm_mod_next(sfp, SFP_MOD_EMPTY, 0); return; } @@ -1612,26 +1620,45 @@ static void sfp_sm_module(struct sfp *sfp, unsigned int event) break; case SFP_MOD_PROBE: - if (event == SFP_E_TIMEOUT) { - int val = sfp_sm_mod_probe(sfp); - - if (val == 0) - sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0); - else if (val > 0) - sfp_sm_mod_next(sfp, SFP_MOD_HPOWER, val); - else if (val != -EAGAIN) - sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); - else - sfp_sm_set_timer(sfp, T_PROBE_RETRY); + if (event != SFP_E_TIMEOUT) + break; + + err = sfp_sm_mod_probe(sfp); + if (err == -EAGAIN) { + sfp_sm_set_timer(sfp, T_PROBE_RETRY); + break; + } + if (err < 0) { + sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); + break; } - break; + /* If this is a power level 1 module, we are done */ + if (sfp->module_power_mW <= 1000) + goto insert; + + sfp_sm_mod_next(sfp, SFP_MOD_HPOWER, 0); + /* fall through */ case SFP_MOD_HPOWER: - if (event == SFP_E_TIMEOUT) { - sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0); + /* Enable high power mode */ + err = sfp_sm_mod_hpower(sfp, true); + if (err == 0) + sfp_sm_mod_next(sfp, SFP_MOD_WAITPWR, T_HPOWER_LEVEL); + else if (err != -EAGAIN) + sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); + else + sfp_sm_set_timer(sfp, T_PROBE_RETRY); + break; + + case SFP_MOD_WAITPWR: + /* Wait for T_HPOWER_LEVEL to time out */ + if (event != SFP_E_TIMEOUT) break; - } - /* fallthrough */ + + insert: + sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0); + break; + case SFP_MOD_PRESENT: case SFP_MOD_ERROR: break; |