* [PATCH net-next 0/4] HWMON support for SFP modules
@ 2018-07-17 19:48 Andrew Lunn
2018-07-17 19:48 ` [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define Andrew Lunn
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Andrew Lunn @ 2018-07-17 19:48 UTC (permalink / raw)
To: David Miller, Guenter Roeck
Cc: netdev, Florian Fainelli, Russell King, linux-hwmon, Andrew Lunn
This patchset adds HWMON support to SFP modules. The two patches add
some attributes for temperature and power sensors which are currently
missing from the hwmon core. The third patch adds a helper for
filtering out characters in hwmon names which are invalid. The last
patch then extends the core SFP code to export the sensors found in
SFP modules.
This code has been tested with two SFP modules:
module OEM SFP-7000-85 rev 11.0 sn M1512220075 dc 160221
module FINISAR CORP. FTLF8524E2GNL rev A sn PW40MNN dc 160725
The anonymous module uses external calibration, while the FINISAR uses
internal calibration. Thus both code paths have been tested.
Due to the cross subsystem nature of these patches, as discussed with
the RFC, it is hoped Guenter Roeck will ACK the patches, and then Dave
Miller will merge them all via net-next.
Andrew Lunn (4):
hwmon: Add missing HWMON_T_LCRIT_ALARM define
hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm
hwmon: Add helper to tell if a char is invalid in a name
net: phy: sfp: Add HWMON support for module sensors
drivers/hwmon/hwmon.c | 4 +
drivers/net/phy/Kconfig | 1 +
drivers/net/phy/sfp.c | 727 ++++++++++++++++++++++++++++++++++++++++
include/linux/hwmon.h | 32 ++
include/linux/sfp.h | 72 +++-
5 files changed, 835 insertions(+), 1 deletion(-)
--
2.18.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn @ 2018-07-17 19:48 ` Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm Andrew Lunn ` (3 subsequent siblings) 4 siblings, 1 reply; 10+ messages in thread From: Andrew Lunn @ 2018-07-17 19:48 UTC (permalink / raw) To: David Miller, Guenter Roeck Cc: netdev, Florian Fainelli, Russell King, linux-hwmon, Andrew Lunn The enum hwmon_temp_lcrit_alarm exists, but the BIT definition is missing. Signed-off-by: Andrew Lunn <andrew@lunn.ch> --- include/linux/hwmon.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index e5fd2707b6df..1b74ad11a5a4 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -93,6 +93,7 @@ enum hwmon_temp_attributes { #define HWMON_T_MIN_ALARM BIT(hwmon_temp_min_alarm) #define HWMON_T_MAX_ALARM BIT(hwmon_temp_max_alarm) #define HWMON_T_CRIT_ALARM BIT(hwmon_temp_crit_alarm) +#define HWMON_T_LCRIT_ALARM BIT(hwmon_temp_lcrit_alarm) #define HWMON_T_EMERGENCY_ALARM BIT(hwmon_temp_emergency_alarm) #define HWMON_T_FAULT BIT(hwmon_temp_fault) #define HWMON_T_OFFSET BIT(hwmon_temp_offset) -- 2.18.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define 2018-07-17 19:48 ` [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define Andrew Lunn @ 2018-07-17 20:12 ` Guenter Roeck 0 siblings, 0 replies; 10+ messages in thread From: Guenter Roeck @ 2018-07-17 20:12 UTC (permalink / raw) To: Andrew Lunn Cc: David Miller, netdev, Florian Fainelli, Russell King, linux-hwmon On Tue, Jul 17, 2018 at 09:48:10PM +0200, Andrew Lunn wrote: > The enum hwmon_temp_lcrit_alarm exists, but the BIT definition is > missing. > > Signed-off-by: Andrew Lunn <andrew@lunn.ch> Acked-by: Guenter Roeck <linux@roeck-us.net> ... and, yes, I assume the series will be picked up by David. > --- > include/linux/hwmon.h | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h > index e5fd2707b6df..1b74ad11a5a4 100644 > --- a/include/linux/hwmon.h > +++ b/include/linux/hwmon.h > @@ -93,6 +93,7 @@ enum hwmon_temp_attributes { > #define HWMON_T_MIN_ALARM BIT(hwmon_temp_min_alarm) > #define HWMON_T_MAX_ALARM BIT(hwmon_temp_max_alarm) > #define HWMON_T_CRIT_ALARM BIT(hwmon_temp_crit_alarm) > +#define HWMON_T_LCRIT_ALARM BIT(hwmon_temp_lcrit_alarm) > #define HWMON_T_EMERGENCY_ALARM BIT(hwmon_temp_emergency_alarm) > #define HWMON_T_FAULT BIT(hwmon_temp_fault) > #define HWMON_T_OFFSET BIT(hwmon_temp_offset) > -- > 2.18.0 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn 2018-07-17 19:48 ` [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define Andrew Lunn @ 2018-07-17 19:48 ` Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name Andrew Lunn ` (2 subsequent siblings) 4 siblings, 1 reply; 10+ messages in thread From: Andrew Lunn @ 2018-07-17 19:48 UTC (permalink / raw) To: David Miller, Guenter Roeck Cc: netdev, Florian Fainelli, Russell King, linux-hwmon, Andrew Lunn Some sensors support reporting minimal and lower critical power, as well as alarms when these thresholds are reached. Add support for these attributes to the hwmon core. Signed-off-by: Andrew Lunn <andrew@lunn.ch> --- drivers/hwmon/hwmon.c | 4 ++++ include/linux/hwmon.h | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index e88c01961948..33d51281272b 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -394,12 +394,16 @@ static const char * const hwmon_power_attr_templates[] = { [hwmon_power_cap_hyst] = "power%d_cap_hyst", [hwmon_power_cap_max] = "power%d_cap_max", [hwmon_power_cap_min] = "power%d_cap_min", + [hwmon_power_min] = "power%d_min", [hwmon_power_max] = "power%d_max", + [hwmon_power_lcrit] = "power%d_lcrit", [hwmon_power_crit] = "power%d_crit", [hwmon_power_label] = "power%d_label", [hwmon_power_alarm] = "power%d_alarm", [hwmon_power_cap_alarm] = "power%d_cap_alarm", + [hwmon_power_min_alarm] = "power%d_min_alarm", [hwmon_power_max_alarm] = "power%d_max_alarm", + [hwmon_power_lcrit_alarm] = "power%d_lcrit_alarm", [hwmon_power_crit_alarm] = "power%d_crit_alarm", }; diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 1b74ad11a5a4..b217101ca76e 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -188,12 +188,16 @@ enum hwmon_power_attributes { hwmon_power_cap_hyst, hwmon_power_cap_max, hwmon_power_cap_min, + hwmon_power_min, hwmon_power_max, hwmon_power_crit, + hwmon_power_lcrit, hwmon_power_label, hwmon_power_alarm, hwmon_power_cap_alarm, + hwmon_power_min_alarm, hwmon_power_max_alarm, + hwmon_power_lcrit_alarm, hwmon_power_crit_alarm, }; @@ -214,12 +218,16 @@ enum hwmon_power_attributes { #define HWMON_P_CAP_HYST BIT(hwmon_power_cap_hyst) #define HWMON_P_CAP_MAX BIT(hwmon_power_cap_max) #define HWMON_P_CAP_MIN BIT(hwmon_power_cap_min) +#define HWMON_P_MIN BIT(hwmon_power_min) #define HWMON_P_MAX BIT(hwmon_power_max) +#define HWMON_P_LCRIT BIT(hwmon_power_lcrit) #define HWMON_P_CRIT BIT(hwmon_power_crit) #define HWMON_P_LABEL BIT(hwmon_power_label) #define HWMON_P_ALARM BIT(hwmon_power_alarm) #define HWMON_P_CAP_ALARM BIT(hwmon_power_cap_alarm) +#define HWMON_P_MIN_ALARM BIT(hwmon_power_max_alarm) #define HWMON_P_MAX_ALARM BIT(hwmon_power_max_alarm) +#define HWMON_P_LCRIT_ALARM BIT(hwmon_power_lcrit_alarm) #define HWMON_P_CRIT_ALARM BIT(hwmon_power_crit_alarm) enum hwmon_energy_attributes { -- 2.18.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm 2018-07-17 19:48 ` [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm Andrew Lunn @ 2018-07-17 20:12 ` Guenter Roeck 0 siblings, 0 replies; 10+ messages in thread From: Guenter Roeck @ 2018-07-17 20:12 UTC (permalink / raw) To: Andrew Lunn Cc: David Miller, netdev, Florian Fainelli, Russell King, linux-hwmon On Tue, Jul 17, 2018 at 09:48:11PM +0200, Andrew Lunn wrote: > Some sensors support reporting minimal and lower critical power, as > well as alarms when these thresholds are reached. Add support for > these attributes to the hwmon core. > > Signed-off-by: Andrew Lunn <andrew@lunn.ch> Acked-by: Guenter Roeck <linux@roeck-us.net> > --- > drivers/hwmon/hwmon.c | 4 ++++ > include/linux/hwmon.h | 8 ++++++++ > 2 files changed, 12 insertions(+) > > diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c > index e88c01961948..33d51281272b 100644 > --- a/drivers/hwmon/hwmon.c > +++ b/drivers/hwmon/hwmon.c > @@ -394,12 +394,16 @@ static const char * const hwmon_power_attr_templates[] = { > [hwmon_power_cap_hyst] = "power%d_cap_hyst", > [hwmon_power_cap_max] = "power%d_cap_max", > [hwmon_power_cap_min] = "power%d_cap_min", > + [hwmon_power_min] = "power%d_min", > [hwmon_power_max] = "power%d_max", > + [hwmon_power_lcrit] = "power%d_lcrit", > [hwmon_power_crit] = "power%d_crit", > [hwmon_power_label] = "power%d_label", > [hwmon_power_alarm] = "power%d_alarm", > [hwmon_power_cap_alarm] = "power%d_cap_alarm", > + [hwmon_power_min_alarm] = "power%d_min_alarm", > [hwmon_power_max_alarm] = "power%d_max_alarm", > + [hwmon_power_lcrit_alarm] = "power%d_lcrit_alarm", > [hwmon_power_crit_alarm] = "power%d_crit_alarm", > }; > > diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h > index 1b74ad11a5a4..b217101ca76e 100644 > --- a/include/linux/hwmon.h > +++ b/include/linux/hwmon.h > @@ -188,12 +188,16 @@ enum hwmon_power_attributes { > hwmon_power_cap_hyst, > hwmon_power_cap_max, > hwmon_power_cap_min, > + hwmon_power_min, > hwmon_power_max, > hwmon_power_crit, > + hwmon_power_lcrit, > hwmon_power_label, > hwmon_power_alarm, > hwmon_power_cap_alarm, > + hwmon_power_min_alarm, > hwmon_power_max_alarm, > + hwmon_power_lcrit_alarm, > hwmon_power_crit_alarm, > }; > > @@ -214,12 +218,16 @@ enum hwmon_power_attributes { > #define HWMON_P_CAP_HYST BIT(hwmon_power_cap_hyst) > #define HWMON_P_CAP_MAX BIT(hwmon_power_cap_max) > #define HWMON_P_CAP_MIN BIT(hwmon_power_cap_min) > +#define HWMON_P_MIN BIT(hwmon_power_min) > #define HWMON_P_MAX BIT(hwmon_power_max) > +#define HWMON_P_LCRIT BIT(hwmon_power_lcrit) > #define HWMON_P_CRIT BIT(hwmon_power_crit) > #define HWMON_P_LABEL BIT(hwmon_power_label) > #define HWMON_P_ALARM BIT(hwmon_power_alarm) > #define HWMON_P_CAP_ALARM BIT(hwmon_power_cap_alarm) > +#define HWMON_P_MIN_ALARM BIT(hwmon_power_max_alarm) > #define HWMON_P_MAX_ALARM BIT(hwmon_power_max_alarm) > +#define HWMON_P_LCRIT_ALARM BIT(hwmon_power_lcrit_alarm) > #define HWMON_P_CRIT_ALARM BIT(hwmon_power_crit_alarm) > > enum hwmon_energy_attributes { > -- > 2.18.0 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn 2018-07-17 19:48 ` [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define Andrew Lunn 2018-07-17 19:48 ` [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm Andrew Lunn @ 2018-07-17 19:48 ` Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors Andrew Lunn 2018-07-18 1:02 ` [PATCH net-next 0/4] HWMON support for SFP modules David Miller 4 siblings, 1 reply; 10+ messages in thread From: Andrew Lunn @ 2018-07-17 19:48 UTC (permalink / raw) To: David Miller, Guenter Roeck Cc: netdev, Florian Fainelli, Russell King, linux-hwmon, Andrew Lunn HWMON device names are not allowed to contain "-* \t\n". Add a helper which will return true if passed an invalid character. It can be used to massage a string into a hwmon compatible name by replacing invalid characters with '_'. Signed-off-by: Andrew Lunn <andrew@lunn.ch> --- include/linux/hwmon.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index b217101ca76e..9493d4a388db 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -398,4 +398,27 @@ devm_hwmon_device_register_with_info(struct device *dev, void hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev); +/** + * hwmon_is_bad_char - Is the char invalid in a hwmon name + * @ch: the char to be considered + * + * hwmon_is_bad_char() can be used to determine if the given character + * may not be used in a hwmon name. + * + * Returns true if the char is invalid, false otherwise. + */ +static inline bool hwmon_is_bad_char(const char ch) +{ + switch (ch) { + case '-': + case '*': + case ' ': + case '\t': + case '\n': + return true; + default: + return false; + } +} + #endif -- 2.18.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name 2018-07-17 19:48 ` [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name Andrew Lunn @ 2018-07-17 20:12 ` Guenter Roeck 0 siblings, 0 replies; 10+ messages in thread From: Guenter Roeck @ 2018-07-17 20:12 UTC (permalink / raw) To: Andrew Lunn Cc: David Miller, netdev, Florian Fainelli, Russell King, linux-hwmon On Tue, Jul 17, 2018 at 09:48:12PM +0200, Andrew Lunn wrote: > HWMON device names are not allowed to contain "-* \t\n". Add a helper > which will return true if passed an invalid character. It can be used > to massage a string into a hwmon compatible name by replacing invalid > characters with '_'. > > Signed-off-by: Andrew Lunn <andrew@lunn.ch> Acked-by: Guenter Roeck <linux@roeck-us.net> > --- > include/linux/hwmon.h | 23 +++++++++++++++++++++++ > 1 file changed, 23 insertions(+) > > diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h > index b217101ca76e..9493d4a388db 100644 > --- a/include/linux/hwmon.h > +++ b/include/linux/hwmon.h > @@ -398,4 +398,27 @@ devm_hwmon_device_register_with_info(struct device *dev, > void hwmon_device_unregister(struct device *dev); > void devm_hwmon_device_unregister(struct device *dev); > > +/** > + * hwmon_is_bad_char - Is the char invalid in a hwmon name > + * @ch: the char to be considered > + * > + * hwmon_is_bad_char() can be used to determine if the given character > + * may not be used in a hwmon name. > + * > + * Returns true if the char is invalid, false otherwise. > + */ > +static inline bool hwmon_is_bad_char(const char ch) > +{ > + switch (ch) { > + case '-': > + case '*': > + case ' ': > + case '\t': > + case '\n': > + return true; > + default: > + return false; > + } > +} > + > #endif > -- > 2.18.0 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn ` (2 preceding siblings ...) 2018-07-17 19:48 ` [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name Andrew Lunn @ 2018-07-17 19:48 ` Andrew Lunn 2018-07-17 20:13 ` Guenter Roeck 2018-07-18 1:02 ` [PATCH net-next 0/4] HWMON support for SFP modules David Miller 4 siblings, 1 reply; 10+ messages in thread From: Andrew Lunn @ 2018-07-17 19:48 UTC (permalink / raw) To: David Miller, Guenter Roeck Cc: netdev, Florian Fainelli, Russell King, linux-hwmon, Andrew Lunn 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@lunn.ch> --- 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 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors 2018-07-17 19:48 ` [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors Andrew Lunn @ 2018-07-17 20:13 ` Guenter Roeck 0 siblings, 0 replies; 10+ messages in thread From: Guenter Roeck @ 2018-07-17 20:13 UTC (permalink / raw) To: Andrew Lunn Cc: David Miller, netdev, Florian Fainelli, Russell King, linux-hwmon 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@lunn.ch> Acked-by: Guenter Roeck <linux@roeck-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 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 0/4] HWMON support for SFP modules 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn ` (3 preceding siblings ...) 2018-07-17 19:48 ` [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors Andrew Lunn @ 2018-07-18 1:02 ` David Miller 4 siblings, 0 replies; 10+ messages in thread From: David Miller @ 2018-07-18 1:02 UTC (permalink / raw) To: andrew; +Cc: linux, netdev, f.fainelli, rmk+kernel, linux-hwmon From: Andrew Lunn <andrew@lunn.ch> Date: Tue, 17 Jul 2018 21:48:09 +0200 > This patchset adds HWMON support to SFP modules. The two patches add > some attributes for temperature and power sensors which are currently > missing from the hwmon core. The third patch adds a helper for > filtering out characters in hwmon names which are invalid. The last > patch then extends the core SFP code to export the sensors found in > SFP modules. > > This code has been tested with two SFP modules: > > module OEM SFP-7000-85 rev 11.0 sn M1512220075 dc 160221 > module FINISAR CORP. FTLF8524E2GNL rev A sn PW40MNN dc 160725 > > The anonymous module uses external calibration, while the FINISAR uses > internal calibration. Thus both code paths have been tested. > > Due to the cross subsystem nature of these patches, as discussed with > the RFC, it is hoped Guenter Roeck will ACK the patches, and then Dave > Miller will merge them all via net-next. Series applied, thanks Andrew. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2018-07-18 1:38 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2018-07-17 19:48 [PATCH net-next 0/4] HWMON support for SFP modules Andrew Lunn 2018-07-17 19:48 ` [PATCH net-next 1/4] hwmon: Add missing HWMON_T_LCRIT_ALARM define Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 2/4] hwmon: Add support for power min, lcrit, min_alarm and lcrit_alarm Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 3/4] hwmon: Add helper to tell if a char is invalid in a name Andrew Lunn 2018-07-17 20:12 ` Guenter Roeck 2018-07-17 19:48 ` [PATCH net-next 4/4] net: phy: sfp: Add HWMON support for module sensors Andrew Lunn 2018-07-17 20:13 ` Guenter Roeck 2018-07-18 1:02 ` [PATCH net-next 0/4] HWMON support for SFP modules David Miller
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).