On Tue, Jul 17, 2018 at 09:48:13PM +0200, Andrew Lunn wrote: > SFP modules can contain a number of sensors. The EEPROM also contains > recommended alarm and critical values for each sensor, and indications > of if these have been exceeded. Export this information via > HWMON. Currently temperature, VCC, bias current, transmit power, and > possibly receiver power is supported. > > The sensors in the modules can either return calibrate or uncalibrated > values. Uncalibrated values need to be manipulated, using coefficients > provided in the SFP EEPROM. Uncalibrated receive power values require > floating point maths in order to calibrate them. Performing this in > the kernel is hard. So if the SFP module indicates it uses > uncalibrated values, RX power is not made available. > > With this hwmon device, it is possible to view the sensor values using > lm-sensors programs: > > in0: +3.29 V (crit min = +2.90 V, min = +3.00 V) > (max = +3.60 V, crit max = +3.70 V) > temp1: +33.0°C (low = -5.0°C, high = +80.0°C) > (crit low = -10.0°C, crit = +85.0°C) > power1: 1000.00 nW (max = 794.00 uW, min = 50.00 uW) ALARM (LCRIT) > (lcrit = 40.00 uW, crit = 1000.00 uW) > curr1: +0.00 A (crit min = +0.00 A, min = +0.00 A) ALARM (LCRIT, MIN) > (max = +0.01 A, crit max = +0.01 A) > > The scaling sensors performs on the bias current is not particularly > good. The raw values are more useful: > > curr1: > curr1_input: 0.000 > curr1_min: 0.002 > curr1_max: 0.010 > curr1_lcrit: 0.000 > curr1_crit: 0.011 > curr1_min_alarm: 1.000 > curr1_max_alarm: 0.000 > curr1_lcrit_alarm: 1.000 > curr1_crit_alarm: 0.000 > > In order to keep the I2C overhead to a minimum, the constant values, > such as limits and calibration coefficients are read once at module > insertion time. Thus only reading *_input and *_alarm properties > requires i2c read operations. > > Signed-off-by: Andrew Lunn <andrew@xxxxxxx> Acked-by: Guenter Roeck <linux@xxxxxxxxxxxx> > --- > RFC->V1 > Use __be16 when reading sensor > DIV_ROUND_CLOSEST() > Don't use devm_hwmon_* to fix lifetime issues > Replace invalid chars in hwmon name with _ > --- > drivers/net/phy/Kconfig | 1 + > drivers/net/phy/sfp.c | 727 ++++++++++++++++++++++++++++++++++++++++ > include/linux/sfp.h | 72 +++- > 3 files changed, 799 insertions(+), 1 deletion(-) > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig > index 7761536974bf..f31ae4faf4e5 100644 > --- a/drivers/net/phy/Kconfig > +++ b/drivers/net/phy/Kconfig > @@ -215,6 +215,7 @@ config SFP > tristate "SFP cage support" > depends on I2C > select MDIO_I2C > + imply HWMON > > config AMD_PHY > tristate "AMD PHYs" > diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c > index c4c92db86dfa..5661226cf75b 100644 > --- a/drivers/net/phy/sfp.c > +++ b/drivers/net/phy/sfp.c > @@ -1,5 +1,7 @@ > +#include <linux/ctype.h> > #include <linux/delay.h> > #include <linux/gpio/consumer.h> > +#include <linux/hwmon.h> > #include <linux/i2c.h> > #include <linux/interrupt.h> > #include <linux/jiffies.h> > @@ -131,6 +133,12 @@ struct sfp { > unsigned int sm_retries; > > struct sfp_eeprom_id id; > +#if IS_ENABLED(CONFIG_HWMON) > + struct sfp_diag diag; > + struct device *hwmon_dev; > + char *hwmon_name; > +#endif > + > }; > > static bool sff_module_supported(const struct sfp_eeprom_id *id) > @@ -316,6 +324,719 @@ static unsigned int sfp_check(void *buf, size_t len) > return check; > } > > +/* hwmon */ > +#if IS_ENABLED(CONFIG_HWMON) > +static umode_t sfp_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + const struct sfp *sfp = data; > + > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + case hwmon_temp_min_alarm: > + case hwmon_temp_max_alarm: > + case hwmon_temp_lcrit_alarm: > + case hwmon_temp_crit_alarm: > + case hwmon_temp_min: > + case hwmon_temp_max: > + case hwmon_temp_lcrit: > + case hwmon_temp_crit: > + return 0444; > + default: > + return 0; > + } > + case hwmon_in: > + switch (attr) { > + case hwmon_in_input: > + case hwmon_in_min_alarm: > + case hwmon_in_max_alarm: > + case hwmon_in_lcrit_alarm: > + case hwmon_in_crit_alarm: > + case hwmon_in_min: > + case hwmon_in_max: > + case hwmon_in_lcrit: > + case hwmon_in_crit: > + return 0444; > + default: > + return 0; > + } > + case hwmon_curr: > + switch (attr) { > + case hwmon_curr_input: > + case hwmon_curr_min_alarm: > + case hwmon_curr_max_alarm: > + case hwmon_curr_lcrit_alarm: > + case hwmon_curr_crit_alarm: > + case hwmon_curr_min: > + case hwmon_curr_max: > + case hwmon_curr_lcrit: > + case hwmon_curr_crit: > + return 0444; > + default: > + return 0; > + } > + case hwmon_power: > + /* External calibration of receive power requires > + * floating point arithmetic. Doing that in the kernel > + * is not easy, so just skip it. If the module does > + * not require external calibration, we can however > + * show receiver power, since FP is then not needed. > + */ > + if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL && > + channel == 1) > + return 0; > + switch (attr) { > + case hwmon_power_input: > + case hwmon_power_min_alarm: > + case hwmon_power_max_alarm: > + case hwmon_power_lcrit_alarm: > + case hwmon_power_crit_alarm: > + case hwmon_power_min: > + case hwmon_power_max: > + case hwmon_power_lcrit: > + case hwmon_power_crit: > + return 0444; > + default: > + return 0; > + } > + default: > + return 0; > + } > +} > + > +static int sfp_hwmon_read_sensor(struct sfp *sfp, int reg, long *value) > +{ > + __be16 val; > + int err; > + > + err = sfp_read(sfp, true, reg, &val, sizeof(val)); > + if (err < 0) > + return err; > + > + *value = be16_to_cpu(val); > + > + return 0; > +} > + > +static void sfp_hwmon_to_rx_power(long *value) > +{ > + *value = DIV_ROUND_CLOSEST(*value, 100); > +} > + > +static void sfp_hwmon_calibrate(struct sfp *sfp, unsigned int slope, int offset, > + long *value) > +{ > + if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL) > + *value = DIV_ROUND_CLOSEST(*value * slope, 256) + offset; > +} > + > +static void sfp_hwmon_calibrate_temp(struct sfp *sfp, long *value) > +{ > + sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_t_slope), > + be16_to_cpu(sfp->diag.cal_t_offset), value); > + > + if (*value >= 0x8000) > + *value -= 0x10000; > + > + *value = DIV_ROUND_CLOSEST(*value * 1000, 256); > +} > + > +static void sfp_hwmon_calibrate_vcc(struct sfp *sfp, long *value) > +{ > + sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_v_slope), > + be16_to_cpu(sfp->diag.cal_v_offset), value); > + > + *value = DIV_ROUND_CLOSEST(*value, 10); > +} > + > +static void sfp_hwmon_calibrate_bias(struct sfp *sfp, long *value) > +{ > + sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txi_slope), > + be16_to_cpu(sfp->diag.cal_txi_offset), value); > + > + *value = DIV_ROUND_CLOSEST(*value, 500); > +} > + > +static void sfp_hwmon_calibrate_tx_power(struct sfp *sfp, long *value) > +{ > + sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txpwr_slope), > + be16_to_cpu(sfp->diag.cal_txpwr_offset), value); > + > + *value = DIV_ROUND_CLOSEST(*value, 10); > +} > + > +static int sfp_hwmon_read_temp(struct sfp *sfp, int reg, long *value) > +{ > + int err; > + > + err = sfp_hwmon_read_sensor(sfp, reg, value); > + if (err < 0) > + return err; > + > + sfp_hwmon_calibrate_temp(sfp, value); > + > + return 0; > +} > + > +static int sfp_hwmon_read_vcc(struct sfp *sfp, int reg, long *value) > +{ > + int err; > + > + err = sfp_hwmon_read_sensor(sfp, reg, value); > + if (err < 0) > + return err; > + > + sfp_hwmon_calibrate_vcc(sfp, value); > + > + return 0; > +} > + > +static int sfp_hwmon_read_bias(struct sfp *sfp, int reg, long *value) > +{ > + int err; > + > + err = sfp_hwmon_read_sensor(sfp, reg, value); > + if (err < 0) > + return err; > + > + sfp_hwmon_calibrate_bias(sfp, value); > + > + return 0; > +} > + > +static int sfp_hwmon_read_tx_power(struct sfp *sfp, int reg, long *value) > +{ > + int err; > + > + err = sfp_hwmon_read_sensor(sfp, reg, value); > + if (err < 0) > + return err; > + > + sfp_hwmon_calibrate_tx_power(sfp, value); > + > + return 0; > +} > + > +static int sfp_hwmon_read_rx_power(struct sfp *sfp, int reg, long *value) > +{ > + int err; > + > + err = sfp_hwmon_read_sensor(sfp, reg, value); > + if (err < 0) > + return err; > + > + sfp_hwmon_to_rx_power(value); > + > + return 0; > +} > + > +static int sfp_hwmon_temp(struct sfp *sfp, u32 attr, long *value) > +{ > + u8 status; > + int err; > + > + switch (attr) { > + case hwmon_temp_input: > + return sfp_hwmon_read_temp(sfp, SFP_TEMP, value); > + > + case hwmon_temp_lcrit: > + *value = be16_to_cpu(sfp->diag.temp_low_alarm); > + sfp_hwmon_calibrate_temp(sfp, value); > + return 0; > + > + case hwmon_temp_min: > + *value = be16_to_cpu(sfp->diag.temp_low_warn); > + sfp_hwmon_calibrate_temp(sfp, value); > + return 0; > + case hwmon_temp_max: > + *value = be16_to_cpu(sfp->diag.temp_high_warn); > + sfp_hwmon_calibrate_temp(sfp, value); > + return 0; > + > + case hwmon_temp_crit: > + *value = be16_to_cpu(sfp->diag.temp_high_alarm); > + sfp_hwmon_calibrate_temp(sfp, value); > + return 0; > + > + case hwmon_temp_lcrit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TEMP_LOW); > + return 0; > + > + case hwmon_temp_min_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TEMP_LOW); > + return 0; > + > + case hwmon_temp_max_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TEMP_HIGH); > + return 0; > + > + case hwmon_temp_crit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TEMP_HIGH); > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int sfp_hwmon_vcc(struct sfp *sfp, u32 attr, long *value) > +{ > + u8 status; > + int err; > + > + switch (attr) { > + case hwmon_in_input: > + return sfp_hwmon_read_vcc(sfp, SFP_VCC, value); > + > + case hwmon_in_lcrit: > + *value = be16_to_cpu(sfp->diag.volt_low_alarm); > + sfp_hwmon_calibrate_vcc(sfp, value); > + return 0; > + > + case hwmon_in_min: > + *value = be16_to_cpu(sfp->diag.volt_low_warn); > + sfp_hwmon_calibrate_vcc(sfp, value); > + return 0; > + > + case hwmon_in_max: > + *value = be16_to_cpu(sfp->diag.volt_high_warn); > + sfp_hwmon_calibrate_vcc(sfp, value); > + return 0; > + > + case hwmon_in_crit: > + *value = be16_to_cpu(sfp->diag.volt_high_alarm); > + sfp_hwmon_calibrate_vcc(sfp, value); > + return 0; > + > + case hwmon_in_lcrit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_VCC_LOW); > + return 0; > + > + case hwmon_in_min_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_VCC_LOW); > + return 0; > + > + case hwmon_in_max_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_VCC_HIGH); > + return 0; > + > + case hwmon_in_crit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_VCC_HIGH); > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int sfp_hwmon_bias(struct sfp *sfp, u32 attr, long *value) > +{ > + u8 status; > + int err; > + > + switch (attr) { > + case hwmon_curr_input: > + return sfp_hwmon_read_bias(sfp, SFP_TX_BIAS, value); > + > + case hwmon_curr_lcrit: > + *value = be16_to_cpu(sfp->diag.bias_low_alarm); > + sfp_hwmon_calibrate_bias(sfp, value); > + return 0; > + > + case hwmon_curr_min: > + *value = be16_to_cpu(sfp->diag.bias_low_warn); > + sfp_hwmon_calibrate_bias(sfp, value); > + return 0; > + > + case hwmon_curr_max: > + *value = be16_to_cpu(sfp->diag.bias_high_warn); > + sfp_hwmon_calibrate_bias(sfp, value); > + return 0; > + > + case hwmon_curr_crit: > + *value = be16_to_cpu(sfp->diag.bias_high_alarm); > + sfp_hwmon_calibrate_bias(sfp, value); > + return 0; > + > + case hwmon_curr_lcrit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TX_BIAS_LOW); > + return 0; > + > + case hwmon_curr_min_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TX_BIAS_LOW); > + return 0; > + > + case hwmon_curr_max_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TX_BIAS_HIGH); > + return 0; > + > + case hwmon_curr_crit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TX_BIAS_HIGH); > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int sfp_hwmon_tx_power(struct sfp *sfp, u32 attr, long *value) > +{ > + u8 status; > + int err; > + > + switch (attr) { > + case hwmon_power_input: > + return sfp_hwmon_read_tx_power(sfp, SFP_TX_POWER, value); > + > + case hwmon_power_lcrit: > + *value = be16_to_cpu(sfp->diag.txpwr_low_alarm); > + sfp_hwmon_calibrate_tx_power(sfp, value); > + return 0; > + > + case hwmon_power_min: > + *value = be16_to_cpu(sfp->diag.txpwr_low_warn); > + sfp_hwmon_calibrate_tx_power(sfp, value); > + return 0; > + > + case hwmon_power_max: > + *value = be16_to_cpu(sfp->diag.txpwr_high_warn); > + sfp_hwmon_calibrate_tx_power(sfp, value); > + return 0; > + > + case hwmon_power_crit: > + *value = be16_to_cpu(sfp->diag.txpwr_high_alarm); > + sfp_hwmon_calibrate_tx_power(sfp, value); > + return 0; > + > + case hwmon_power_lcrit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TXPWR_LOW); > + return 0; > + > + case hwmon_power_min_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TXPWR_LOW); > + return 0; > + > + case hwmon_power_max_alarm: > + err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN0_TXPWR_HIGH); > + return 0; > + > + case hwmon_power_crit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM0_TXPWR_HIGH); > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int sfp_hwmon_rx_power(struct sfp *sfp, u32 attr, long *value) > +{ > + u8 status; > + int err; > + > + switch (attr) { > + case hwmon_power_input: > + return sfp_hwmon_read_rx_power(sfp, SFP_RX_POWER, value); > + > + case hwmon_power_lcrit: > + *value = be16_to_cpu(sfp->diag.rxpwr_low_alarm); > + sfp_hwmon_to_rx_power(value); > + return 0; > + > + case hwmon_power_min: > + *value = be16_to_cpu(sfp->diag.rxpwr_low_warn); > + sfp_hwmon_to_rx_power(value); > + return 0; > + > + case hwmon_power_max: > + *value = be16_to_cpu(sfp->diag.rxpwr_high_warn); > + sfp_hwmon_to_rx_power(value); > + return 0; > + > + case hwmon_power_crit: > + *value = be16_to_cpu(sfp->diag.rxpwr_high_alarm); > + sfp_hwmon_to_rx_power(value); > + return 0; > + > + case hwmon_power_lcrit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM1_RXPWR_LOW); > + return 0; > + > + case hwmon_power_min_alarm: > + err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN1_RXPWR_LOW); > + return 0; > + > + case hwmon_power_max_alarm: > + err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_WARN1_RXPWR_HIGH); > + return 0; > + > + case hwmon_power_crit_alarm: > + err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status)); > + if (err < 0) > + return err; > + > + *value = !!(status & SFP_ALARM1_RXPWR_HIGH); > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int sfp_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *value) > +{ > + struct sfp *sfp = dev_get_drvdata(dev); > + > + switch (type) { > + case hwmon_temp: > + return sfp_hwmon_temp(sfp, attr, value); > + case hwmon_in: > + return sfp_hwmon_vcc(sfp, attr, value); > + case hwmon_curr: > + return sfp_hwmon_bias(sfp, attr, value); > + case hwmon_power: > + switch (channel) { > + case 0: > + return sfp_hwmon_tx_power(sfp, attr, value); > + case 1: > + return sfp_hwmon_rx_power(sfp, attr, value); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static const struct hwmon_ops sfp_hwmon_ops = { > + .is_visible = sfp_hwmon_is_visible, > + .read = sfp_hwmon_read, > +}; > + > +static u32 sfp_hwmon_chip_config[] = { > + HWMON_C_REGISTER_TZ, > + 0, > +}; > + > +static const struct hwmon_channel_info sfp_hwmon_chip = { > + .type = hwmon_chip, > + .config = sfp_hwmon_chip_config, > +}; > + > +static u32 sfp_hwmon_temp_config[] = { > + HWMON_T_INPUT | > + HWMON_T_MAX | HWMON_T_MIN | > + HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | > + HWMON_T_CRIT | HWMON_T_LCRIT | > + HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM, > + 0, > +}; > + > +static const struct hwmon_channel_info sfp_hwmon_temp_channel_info = { > + .type = hwmon_temp, > + .config = sfp_hwmon_temp_config, > +}; > + > +static u32 sfp_hwmon_vcc_config[] = { > + HWMON_I_INPUT | > + HWMON_I_MAX | HWMON_I_MIN | > + HWMON_I_MAX_ALARM | HWMON_I_MIN_ALARM | > + HWMON_I_CRIT | HWMON_I_LCRIT | > + HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM, > + 0, > +}; > + > +static const struct hwmon_channel_info sfp_hwmon_vcc_channel_info = { > + .type = hwmon_in, > + .config = sfp_hwmon_vcc_config, > +}; > + > +static u32 sfp_hwmon_bias_config[] = { > + HWMON_C_INPUT | > + HWMON_C_MAX | HWMON_C_MIN | > + HWMON_C_MAX_ALARM | HWMON_C_MIN_ALARM | > + HWMON_C_CRIT | HWMON_C_LCRIT | > + HWMON_C_CRIT_ALARM | HWMON_C_LCRIT_ALARM, > + 0, > +}; > + > +static const struct hwmon_channel_info sfp_hwmon_bias_channel_info = { > + .type = hwmon_curr, > + .config = sfp_hwmon_bias_config, > +}; > + > +static u32 sfp_hwmon_power_config[] = { > + /* Transmit power */ > + HWMON_P_INPUT | > + HWMON_P_MAX | HWMON_P_MIN | > + HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM | > + HWMON_P_CRIT | HWMON_P_LCRIT | > + HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM, > + /* Receive power */ > + HWMON_P_INPUT | > + HWMON_P_MAX | HWMON_P_MIN | > + HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM | > + HWMON_P_CRIT | HWMON_P_LCRIT | > + HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM, > + 0, > +}; > + > +static const struct hwmon_channel_info sfp_hwmon_power_channel_info = { > + .type = hwmon_power, > + .config = sfp_hwmon_power_config, > +}; > + > +static const struct hwmon_channel_info *sfp_hwmon_info[] = { > + &sfp_hwmon_chip, > + &sfp_hwmon_vcc_channel_info, > + &sfp_hwmon_temp_channel_info, > + &sfp_hwmon_bias_channel_info, > + &sfp_hwmon_power_channel_info, > + NULL, > +}; > + > +static const struct hwmon_chip_info sfp_hwmon_chip_info = { > + .ops = &sfp_hwmon_ops, > + .info = sfp_hwmon_info, > +}; > + > +static int sfp_hwmon_insert(struct sfp *sfp) > +{ > + int err, i; > + > + if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE) > + return 0; > + > + if (!(sfp->id.ext.diagmon & SFP_DIAGMON_DDM)) > + return 0; > + > + if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE) > + /* This driver in general does not support address > + * change. > + */ > + return 0; > + > + err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag)); > + if (err < 0) > + return err; > + > + sfp->hwmon_name = kstrdup(dev_name(sfp->dev), GFP_KERNEL); > + if (!sfp->hwmon_name) > + return -ENODEV; > + > + for (i = 0; sfp->hwmon_name[i]; i++) > + if (hwmon_is_bad_char(sfp->hwmon_name[i])) > + sfp->hwmon_name[i] = '_'; > + > + sfp->hwmon_dev = hwmon_device_register_with_info(sfp->dev, > + sfp->hwmon_name, sfp, > + &sfp_hwmon_chip_info, > + NULL); > + > + return PTR_ERR_OR_ZERO(sfp->hwmon_dev); > +} > + > +static void sfp_hwmon_remove(struct sfp *sfp) > +{ > + hwmon_device_unregister(sfp->hwmon_dev); > + kfree(sfp->hwmon_name); > +} > +#else > +static int sfp_hwmon_insert(struct sfp *sfp) > +{ > + return 0; > +} > + > +static void sfp_hwmon_remove(struct sfp *sfp) > +{ > +} > +#endif > + > /* Helpers */ > static void sfp_module_tx_disable(struct sfp *sfp) > { > @@ -636,6 +1357,10 @@ static int sfp_sm_mod_probe(struct sfp *sfp) > dev_warn(sfp->dev, > "module address swap to access page 0xA2 is not supported.\n"); > > + ret = sfp_hwmon_insert(sfp); > + if (ret < 0) > + return ret; > + > ret = sfp_module_insert(sfp->sfp_bus, &sfp->id); > if (ret < 0) > return ret; > @@ -647,6 +1372,8 @@ static void sfp_sm_mod_remove(struct sfp *sfp) > { > sfp_module_remove(sfp->sfp_bus); > > + sfp_hwmon_remove(sfp); > + > if (sfp->mod_phy) > sfp_sm_phy_detach(sfp); > > diff --git a/include/linux/sfp.h b/include/linux/sfp.h > index ebce9e24906a..d37518e89db2 100644 > --- a/include/linux/sfp.h > +++ b/include/linux/sfp.h > @@ -231,6 +231,50 @@ struct sfp_eeprom_id { > struct sfp_eeprom_ext ext; > } __packed; > > +struct sfp_diag { > + __be16 temp_high_alarm; > + __be16 temp_low_alarm; > + __be16 temp_high_warn; > + __be16 temp_low_warn; > + __be16 volt_high_alarm; > + __be16 volt_low_alarm; > + __be16 volt_high_warn; > + __be16 volt_low_warn; > + __be16 bias_high_alarm; > + __be16 bias_low_alarm; > + __be16 bias_high_warn; > + __be16 bias_low_warn; > + __be16 txpwr_high_alarm; > + __be16 txpwr_low_alarm; > + __be16 txpwr_high_warn; > + __be16 txpwr_low_warn; > + __be16 rxpwr_high_alarm; > + __be16 rxpwr_low_alarm; > + __be16 rxpwr_high_warn; > + __be16 rxpwr_low_warn; > + __be16 laser_temp_high_alarm; > + __be16 laser_temp_low_alarm; > + __be16 laser_temp_high_warn; > + __be16 laser_temp_low_warn; > + __be16 tec_cur_high_alarm; > + __be16 tec_cur_low_alarm; > + __be16 tec_cur_high_warn; > + __be16 tec_cur_low_warn; > + __be32 cal_rxpwr4; > + __be32 cal_rxpwr3; > + __be32 cal_rxpwr2; > + __be32 cal_rxpwr1; > + __be32 cal_rxpwr0; > + __be16 cal_txi_slope; > + __be16 cal_txi_offset; > + __be16 cal_txpwr_slope; > + __be16 cal_txpwr_offset; > + __be16 cal_t_slope; > + __be16 cal_t_offset; > + __be16 cal_v_slope; > + __be16 cal_v_offset; > +} __packed; > + > /* SFP EEPROM registers */ > enum { > SFP_PHYS_ID = 0x00, > @@ -384,7 +428,33 @@ enum { > SFP_TEC_CUR = 0x6c, > > SFP_STATUS = 0x6e, > - SFP_ALARM = 0x70, > + SFP_ALARM0 = 0x70, > + SFP_ALARM0_TEMP_HIGH = BIT(7), > + SFP_ALARM0_TEMP_LOW = BIT(6), > + SFP_ALARM0_VCC_HIGH = BIT(5), > + SFP_ALARM0_VCC_LOW = BIT(4), > + SFP_ALARM0_TX_BIAS_HIGH = BIT(3), > + SFP_ALARM0_TX_BIAS_LOW = BIT(2), > + SFP_ALARM0_TXPWR_HIGH = BIT(1), > + SFP_ALARM0_TXPWR_LOW = BIT(0), > + > + SFP_ALARM1 = 0x71, > + SFP_ALARM1_RXPWR_HIGH = BIT(7), > + SFP_ALARM1_RXPWR_LOW = BIT(6), > + > + SFP_WARN0 = 0x74, > + SFP_WARN0_TEMP_HIGH = BIT(7), > + SFP_WARN0_TEMP_LOW = BIT(6), > + SFP_WARN0_VCC_HIGH = BIT(5), > + SFP_WARN0_VCC_LOW = BIT(4), > + SFP_WARN0_TX_BIAS_HIGH = BIT(3), > + SFP_WARN0_TX_BIAS_LOW = BIT(2), > + SFP_WARN0_TXPWR_HIGH = BIT(1), > + SFP_WARN0_TXPWR_LOW = BIT(0), > + > + SFP_WARN1 = 0x75, > + SFP_WARN1_RXPWR_HIGH = BIT(7), > + SFP_WARN1_RXPWR_LOW = BIT(6), > > SFP_EXT_STATUS = 0x76, > SFP_VSL = 0x78, > -- > 2.18.0 > -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html