[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20180717201308.GD15476@roeck-us.net>
Date: Tue, 17 Jul 2018 13:13:08 -0700
From: Guenter Roeck <linux@...ck-us.net>
To: Andrew Lunn <andrew@...n.ch>
Cc: David Miller <davem@...emloft.net>,
netdev <netdev@...r.kernel.org>,
Florian Fainelli <f.fainelli@...il.com>,
Russell King <rmk+kernel@....linux.org.uk>,
linux-hwmon@...r.kernel.org
Subject: Re: [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module
sensors
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@...n.ch>
Acked-by: Guenter Roeck <linux@...ck-us.net>
> ---
> 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
>
Powered by blists - more mailing lists