summaryrefslogtreecommitdiff
path: root/drivers/hwmon
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig465
-rw-r--r--drivers/hwmon/Makefile32
-rw-r--r--drivers/hwmon/abituguru.c4
-rw-r--r--drivers/hwmon/abituguru3.c5
-rw-r--r--drivers/hwmon/acpi_power_meter.c871
-rw-r--r--drivers/hwmon/ad7314.c10
-rw-r--r--drivers/hwmon/ad7414.c2
-rw-r--r--drivers/hwmon/ad7418.c7
-rw-r--r--drivers/hwmon/adc128d818.c63
-rw-r--r--drivers/hwmon/adm1021.c505
-rw-r--r--drivers/hwmon/adm1026.c18
-rw-r--r--drivers/hwmon/adm1029.c5
-rw-r--r--drivers/hwmon/adm1031.c4
-rw-r--r--drivers/hwmon/adm1177.c3
-rw-r--r--drivers/hwmon/adm9240.c17
-rw-r--r--drivers/hwmon/ads7828.c7
-rw-r--r--drivers/hwmon/adt7310.c4
-rw-r--r--drivers/hwmon/adt7410.c19
-rw-r--r--drivers/hwmon/adt7411.c61
-rw-r--r--drivers/hwmon/adt7462.c2
-rw-r--r--drivers/hwmon/adt7470.c24
-rw-r--r--drivers/hwmon/adt7475.c253
-rw-r--r--drivers/hwmon/adt7x10.c45
-rw-r--r--drivers/hwmon/aht10.c62
-rw-r--r--drivers/hwmon/amc6821.c1504
-rw-r--r--drivers/hwmon/aquacomputer_d5next.c174
-rw-r--r--drivers/hwmon/asb100.c2
-rw-r--r--drivers/hwmon/aspeed-g6-pwm-tach.c551
-rw-r--r--drivers/hwmon/aspeed-pwm-tacho.c41
-rw-r--r--drivers/hwmon/asus-ec-sensors.c479
-rw-r--r--drivers/hwmon/asus_atk0110.c17
-rw-r--r--drivers/hwmon/asus_rog_ryujin.c579
-rw-r--r--drivers/hwmon/asus_wmi_sensors.c2
-rw-r--r--drivers/hwmon/atxp1.c2
-rw-r--r--drivers/hwmon/axi-fan-control.c92
-rw-r--r--drivers/hwmon/cgbc-hwmon.c307
-rw-r--r--drivers/hwmon/chipcap2.c777
-rw-r--r--drivers/hwmon/coretemp.c298
-rw-r--r--drivers/hwmon/corsair-cpro.c149
-rw-r--r--drivers/hwmon/corsair-psu.c39
-rw-r--r--drivers/hwmon/cros_ec_hwmon.c597
-rw-r--r--drivers/hwmon/da9052-hwmon.c42
-rw-r--r--drivers/hwmon/dell-smm-hwmon.c744
-rw-r--r--drivers/hwmon/dme1737.c8
-rw-r--r--drivers/hwmon/drivetemp.c13
-rw-r--r--drivers/hwmon/ds1621.c5
-rw-r--r--drivers/hwmon/ds620.c3
-rw-r--r--drivers/hwmon/emc1403.c824
-rw-r--r--drivers/hwmon/emc2103.c6
-rw-r--r--drivers/hwmon/emc2305.c230
-rw-r--r--drivers/hwmon/emc6w201.c2
-rw-r--r--drivers/hwmon/f71805f.c4
-rw-r--r--drivers/hwmon/f71882fg.c3
-rw-r--r--drivers/hwmon/f75375s.c46
-rw-r--r--drivers/hwmon/fam15h_power.c15
-rw-r--r--drivers/hwmon/fschmd.c3
-rw-r--r--drivers/hwmon/ftsteutates.c95
-rw-r--r--drivers/hwmon/g760a.c2
-rw-r--r--drivers/hwmon/g762.c47
-rw-r--r--drivers/hwmon/gigabyte_waterforce.c430
-rw-r--r--drivers/hwmon/gl518sm.c2
-rw-r--r--drivers/hwmon/gl520sm.c2
-rw-r--r--drivers/hwmon/gpd-fan.c683
-rw-r--r--drivers/hwmon/gpio-fan.c119
-rw-r--r--drivers/hwmon/gsc-hwmon.c24
-rw-r--r--drivers/hwmon/hih6130.c2
-rw-r--r--drivers/hwmon/hp-wmi-sensors.c149
-rw-r--r--drivers/hwmon/hs3001.c14
-rw-r--r--drivers/hwmon/htu31.c350
-rw-r--r--drivers/hwmon/hwmon-vid.c4
-rw-r--r--drivers/hwmon/hwmon.c260
-rw-r--r--drivers/hwmon/i5500_temp.c11
-rw-r--r--drivers/hwmon/i5k_amb.c18
-rw-r--r--drivers/hwmon/ibmaem.c27
-rw-r--r--drivers/hwmon/ibmpex.c14
-rw-r--r--drivers/hwmon/ibmpowernv.c2
-rw-r--r--drivers/hwmon/iio_hwmon.c56
-rw-r--r--drivers/hwmon/ina209.c3
-rw-r--r--drivers/hwmon/ina238.c640
-rw-r--r--drivers/hwmon/ina2xx.c1000
-rw-r--r--drivers/hwmon/ina3221.c56
-rw-r--r--drivers/hwmon/intel-m10-bmc-hwmon.c15
-rw-r--r--drivers/hwmon/isl28022.c494
-rw-r--r--drivers/hwmon/it87.c127
-rw-r--r--drivers/hwmon/jc42.c72
-rw-r--r--drivers/hwmon/k10temp.c126
-rw-r--r--drivers/hwmon/kbatt.c147
-rw-r--r--drivers/hwmon/kfan.c246
-rw-r--r--drivers/hwmon/lenovo-ec-sensors.c628
-rw-r--r--drivers/hwmon/lineage-pem.c2
-rw-r--r--drivers/hwmon/lm63.c5
-rw-r--r--drivers/hwmon/lm70.c10
-rw-r--r--drivers/hwmon/lm73.c2
-rw-r--r--drivers/hwmon/lm75.c412
-rw-r--r--drivers/hwmon/lm77.c2
-rw-r--r--drivers/hwmon/lm78.c9
-rw-r--r--drivers/hwmon/lm83.c18
-rw-r--r--drivers/hwmon/lm85.c7
-rw-r--r--drivers/hwmon/lm87.c20
-rw-r--r--drivers/hwmon/lm90.c177
-rw-r--r--drivers/hwmon/lm92.c456
-rw-r--r--drivers/hwmon/lm93.c4
-rw-r--r--drivers/hwmon/lm95234.c809
-rw-r--r--drivers/hwmon/lm95241.c20
-rw-r--r--drivers/hwmon/lm95245.c130
-rw-r--r--drivers/hwmon/lochnagar-hwmon.c18
-rw-r--r--drivers/hwmon/ltc2945.c2
-rw-r--r--drivers/hwmon/ltc2947-core.c112
-rw-r--r--drivers/hwmon/ltc2947-i2c.c2
-rw-r--r--drivers/hwmon/ltc2990.c2
-rw-r--r--drivers/hwmon/ltc2991.c430
-rw-r--r--drivers/hwmon/ltc2992.c49
-rw-r--r--drivers/hwmon/ltc4151.c2
-rw-r--r--drivers/hwmon/ltc4215.c2
-rw-r--r--drivers/hwmon/ltc4222.c2
-rw-r--r--drivers/hwmon/ltc4245.c10
-rw-r--r--drivers/hwmon/ltc4260.c2
-rw-r--r--drivers/hwmon/ltc4261.c2
-rw-r--r--drivers/hwmon/ltc4282.c1706
-rw-r--r--drivers/hwmon/macsmc-hwmon.c851
-rw-r--r--drivers/hwmon/max127.c26
-rw-r--r--drivers/hwmon/max16065.c34
-rw-r--r--drivers/hwmon/max1619.c501
-rw-r--r--drivers/hwmon/max1668.c485
-rw-r--r--drivers/hwmon/max197.c4
-rw-r--r--drivers/hwmon/max31730.c2
-rw-r--r--drivers/hwmon/max31760.c3
-rw-r--r--drivers/hwmon/max31790.c59
-rw-r--r--drivers/hwmon/max31827.c457
-rw-r--r--drivers/hwmon/max6620.c45
-rw-r--r--drivers/hwmon/max6621.c3
-rw-r--r--drivers/hwmon/max6639.c803
-rw-r--r--drivers/hwmon/max6642.c314
-rw-r--r--drivers/hwmon/max6650.c8
-rw-r--r--drivers/hwmon/max6697.c843
-rw-r--r--drivers/hwmon/max77705-hwmon.c221
-rw-r--r--drivers/hwmon/mc13783-adc.c4
-rw-r--r--drivers/hwmon/mc33xs2410_hwmon.c178
-rw-r--r--drivers/hwmon/mc34vr500.c2
-rw-r--r--drivers/hwmon/mcp3021.c6
-rw-r--r--drivers/hwmon/mlxreg-fan.c47
-rw-r--r--drivers/hwmon/mr75203.c2
-rw-r--r--drivers/hwmon/nct6683.c17
-rw-r--r--drivers/hwmon/nct6694-hwmon.c949
-rw-r--r--drivers/hwmon/nct6775-core.c90
-rw-r--r--drivers/hwmon/nct6775-i2c.c16
-rw-r--r--drivers/hwmon/nct6775-platform.c39
-rw-r--r--drivers/hwmon/nct7363.c445
-rw-r--r--drivers/hwmon/nct7802.c73
-rw-r--r--drivers/hwmon/nct7904.c65
-rw-r--r--drivers/hwmon/npcm750-pwm-fan.c62
-rw-r--r--drivers/hwmon/ntc_thermistor.c125
-rw-r--r--drivers/hwmon/nzxt-kraken2.c15
-rw-r--r--drivers/hwmon/nzxt-kraken3.c1028
-rw-r--r--drivers/hwmon/nzxt-smart2.c11
-rw-r--r--drivers/hwmon/occ/common.c242
-rw-r--r--drivers/hwmon/occ/p8_i2c.c3
-rw-r--r--drivers/hwmon/occ/p9_sbe.c10
-rw-r--r--drivers/hwmon/oxp-sensors.c493
-rw-r--r--drivers/hwmon/pc87360.c12
-rw-r--r--drivers/hwmon/pc87427.c4
-rw-r--r--drivers/hwmon/pcf8591.c2
-rw-r--r--drivers/hwmon/peci/common.h3
-rw-r--r--drivers/hwmon/peci/cputemp.c116
-rw-r--r--drivers/hwmon/peci/dimmtemp.c96
-rw-r--r--drivers/hwmon/pmbus/Kconfig213
-rw-r--r--drivers/hwmon/pmbus/Makefile19
-rw-r--r--drivers/hwmon/pmbus/acbel-fsg032.c2
-rw-r--r--drivers/hwmon/pmbus/adm1266.c4
-rw-r--r--drivers/hwmon/pmbus/adm1275.c20
-rw-r--r--drivers/hwmon/pmbus/adp1050.c122
-rw-r--r--drivers/hwmon/pmbus/bel-pfe.c2
-rw-r--r--drivers/hwmon/pmbus/bpa-rs600.c2
-rw-r--r--drivers/hwmon/pmbus/crps.c74
-rw-r--r--drivers/hwmon/pmbus/delta-ahe50dc-fan.c2
-rw-r--r--drivers/hwmon/pmbus/dps920ab.c9
-rw-r--r--drivers/hwmon/pmbus/fsp-3y.c2
-rw-r--r--drivers/hwmon/pmbus/ibm-cffps.c2
-rw-r--r--drivers/hwmon/pmbus/ina233.c191
-rw-r--r--drivers/hwmon/pmbus/inspur-ipsps.c4
-rw-r--r--drivers/hwmon/pmbus/ir35221.c4
-rw-r--r--drivers/hwmon/pmbus/ir36021.c5
-rw-r--r--drivers/hwmon/pmbus/ir38064.c12
-rw-r--r--drivers/hwmon/pmbus/irps5401.c4
-rw-r--r--drivers/hwmon/pmbus/isl68137.c234
-rw-r--r--drivers/hwmon/pmbus/lm25066.c16
-rw-r--r--drivers/hwmon/pmbus/lt3074.c122
-rw-r--r--drivers/hwmon/pmbus/lt7182s.c4
-rw-r--r--drivers/hwmon/pmbus/ltc2978.c87
-rw-r--r--drivers/hwmon/pmbus/ltc3815.c4
-rw-r--r--drivers/hwmon/pmbus/ltc4286.c175
-rw-r--r--drivers/hwmon/pmbus/max15301.c99
-rw-r--r--drivers/hwmon/pmbus/max16064.c4
-rw-r--r--drivers/hwmon/pmbus/max16601.c2
-rw-r--r--drivers/hwmon/pmbus/max17616.c73
-rw-r--r--drivers/hwmon/pmbus/max20730.c2
-rw-r--r--drivers/hwmon/pmbus/max20751.c4
-rw-r--r--drivers/hwmon/pmbus/max31785.c196
-rw-r--r--drivers/hwmon/pmbus/max34440.c159
-rw-r--r--drivers/hwmon/pmbus/max8688.c4
-rw-r--r--drivers/hwmon/pmbus/mp2856.c466
-rw-r--r--drivers/hwmon/pmbus/mp2869.c659
-rw-r--r--drivers/hwmon/pmbus/mp2888.c4
-rw-r--r--drivers/hwmon/pmbus/mp2891.c600
-rw-r--r--drivers/hwmon/pmbus/mp2925.c316
-rw-r--r--drivers/hwmon/pmbus/mp29502.c670
-rw-r--r--drivers/hwmon/pmbus/mp2975.c164
-rw-r--r--drivers/hwmon/pmbus/mp2993.c261
-rw-r--r--drivers/hwmon/pmbus/mp5023.c2
-rw-r--r--drivers/hwmon/pmbus/mp5920.c90
-rw-r--r--drivers/hwmon/pmbus/mp5990.c230
-rw-r--r--drivers/hwmon/pmbus/mp9941.c319
-rw-r--r--drivers/hwmon/pmbus/mp9945.c243
-rw-r--r--drivers/hwmon/pmbus/mpq7932.c23
-rw-r--r--drivers/hwmon/pmbus/mpq8785.c165
-rw-r--r--drivers/hwmon/pmbus/pim4328.c2
-rw-r--r--drivers/hwmon/pmbus/pli1209bc.c30
-rw-r--r--drivers/hwmon/pmbus/pm6764tr.c4
-rw-r--r--drivers/hwmon/pmbus/pmbus.c4
-rw-r--r--drivers/hwmon/pmbus/pmbus.h57
-rw-r--r--drivers/hwmon/pmbus/pmbus_core.c597
-rw-r--r--drivers/hwmon/pmbus/pxe1610.c8
-rw-r--r--drivers/hwmon/pmbus/q54sj108a2.c2
-rw-r--r--drivers/hwmon/pmbus/stpddc60.c6
-rw-r--r--drivers/hwmon/pmbus/tda38640.c160
-rw-r--r--drivers/hwmon/pmbus/tps25990.c436
-rw-r--r--drivers/hwmon/pmbus/tps40422.c4
-rw-r--r--drivers/hwmon/pmbus/tps53679.c39
-rw-r--r--drivers/hwmon/pmbus/tps546d24.c4
-rw-r--r--drivers/hwmon/pmbus/ucd9000.c94
-rw-r--r--drivers/hwmon/pmbus/ucd9200.c2
-rw-r--r--drivers/hwmon/pmbus/xdp710.c131
-rw-r--r--drivers/hwmon/pmbus/xdpe12284.c8
-rw-r--r--drivers/hwmon/pmbus/xdpe152c4.c6
-rw-r--r--drivers/hwmon/pmbus/zl6100.c68
-rw-r--r--drivers/hwmon/powerz.c269
-rw-r--r--drivers/hwmon/powr1220.c24
-rw-r--r--drivers/hwmon/pt5161l.c639
-rw-r--r--drivers/hwmon/pwm-fan.c135
-rw-r--r--drivers/hwmon/qnap-mcu-hwmon.c363
-rw-r--r--drivers/hwmon/raspberrypi-hwmon.c30
-rw-r--r--drivers/hwmon/sa67mcu-hwmon.c161
-rw-r--r--drivers/hwmon/sbrmi.c358
-rw-r--r--drivers/hwmon/sbtsi_temp.c66
-rw-r--r--drivers/hwmon/sch5627.c267
-rw-r--r--drivers/hwmon/sch5636.c7
-rw-r--r--drivers/hwmon/sch56xx-common.c175
-rw-r--r--drivers/hwmon/sch56xx-common.h7
-rw-r--r--drivers/hwmon/scmi-hwmon.c9
-rw-r--r--drivers/hwmon/sfctemp.c36
-rw-r--r--drivers/hwmon/sg2042-mcu.c360
-rw-r--r--drivers/hwmon/sht15.c4
-rw-r--r--drivers/hwmon/sht21.c22
-rw-r--r--drivers/hwmon/sht3x.c84
-rw-r--r--drivers/hwmon/sht4x.c203
-rw-r--r--drivers/hwmon/shtc1.c6
-rw-r--r--drivers/hwmon/sis5595.c12
-rw-r--r--drivers/hwmon/sl28cpld-hwmon.c9
-rw-r--r--drivers/hwmon/smsc47m1.c65
-rw-r--r--drivers/hwmon/smsc47m192.c2
-rw-r--r--drivers/hwmon/spd5118.c778
-rw-r--r--drivers/hwmon/stts751.c5
-rw-r--r--drivers/hwmon/surface_fan.c83
-rw-r--r--drivers/hwmon/surface_temp.c235
-rw-r--r--drivers/hwmon/sy7636a-hwmon.c8
-rw-r--r--drivers/hwmon/tc654.c4
-rw-r--r--drivers/hwmon/tc74.c2
-rw-r--r--drivers/hwmon/thmc50.c4
-rw-r--r--drivers/hwmon/tmp102.c31
-rw-r--r--drivers/hwmon/tmp103.c5
-rw-r--r--drivers/hwmon/tmp108.c84
-rw-r--r--drivers/hwmon/tmp401.c29
-rw-r--r--drivers/hwmon/tmp421.c41
-rw-r--r--drivers/hwmon/tmp464.c48
-rw-r--r--drivers/hwmon/tmp513.c129
-rw-r--r--drivers/hwmon/tps23861.c35
-rw-r--r--drivers/hwmon/tsc1641.c748
-rw-r--r--drivers/hwmon/ultra45_env.c6
-rw-r--r--drivers/hwmon/vexpress-hwmon.c2
-rw-r--r--drivers/hwmon/via-cputemp.c3
-rw-r--r--drivers/hwmon/via686a.c4
-rw-r--r--drivers/hwmon/vt1211.c57
-rw-r--r--drivers/hwmon/vt8231.c24
-rw-r--r--drivers/hwmon/w83627ehf.c13
-rw-r--r--drivers/hwmon/w83627hf.c4
-rw-r--r--drivers/hwmon/w83773g.c1
-rw-r--r--drivers/hwmon/w83781d.c14
-rw-r--r--drivers/hwmon/w83791d.c19
-rw-r--r--drivers/hwmon/w83792d.c2
-rw-r--r--drivers/hwmon/w83793.c3
-rw-r--r--drivers/hwmon/w83795.c4
-rw-r--r--drivers/hwmon/w83l785ts.c2
-rw-r--r--drivers/hwmon/w83l786ng.c28
-rw-r--r--drivers/hwmon/xgene-hwmon.c61
294 files changed, 32946 insertions, 9784 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ec38c8892158..157678b821fc 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -40,7 +40,7 @@ comment "Native drivers"
config SENSORS_ABITUGURU
tristate "Abit uGuru (rev 1 & 2)"
- depends on X86 && DMI
+ depends on (X86 && DMI) || COMPILE_TEST && HAS_IOPORT
help
If you say yes here you get support for the sensor part of the first
and second revision of the Abit uGuru chip. The voltage and frequency
@@ -55,7 +55,7 @@ config SENSORS_ABITUGURU
config SENSORS_ABITUGURU3
tristate "Abit uGuru (rev 3)"
- depends on X86 && DMI
+ depends on (X86 && DMI) || COMPILE_TEST && HAS_IOPORT
help
If you say yes here you get support for the sensor part of the
third revision of the Abit uGuru chip. Only reading the sensors
@@ -105,18 +105,6 @@ config SENSORS_AD7418
This driver can also be built as a module. If so, the module
will be called ad7418.
-config SENSORS_ADM1021
- tristate "Analog Devices ADM1021 and compatibles"
- depends on I2C
- depends on SENSORS_LM90=n
- help
- If you say yes here you get support for Analog Devices ADM1021
- and ADM1023 sensor chips and clones: Maxim MAX1617 and MAX1617A,
- Genesys Logic GL523SM, National Semiconductor LM84 and TI THMC10.
-
- This driver can also be built as a module. If so, the module
- will be called adm1021.
-
config SENSORS_ADM1025
tristate "Analog Devices ADM1025 and compatibles"
depends on I2C
@@ -174,6 +162,7 @@ config SENSORS_ADM9240
tristate "Analog Devices ADM9240 and compatibles"
depends on I2C
select HWMON_VID
+ select REGMAP_I2C
help
If you say yes here you get support for Analog Devices ADM9240,
Dallas DS1780, National Semiconductor LM81 sensor chips.
@@ -186,7 +175,7 @@ config SENSORS_ADT7X10
select REGMAP
help
This module contains common code shared by the ADT7310/ADT7320 and
- ADT7410/ADT7420 temperature monitoring chip drivers.
+ ADT7410/ADT7420/ADT7422 temperature monitoring chip drivers.
If built as a module, the module will be called adt7x10.
@@ -202,12 +191,12 @@ config SENSORS_ADT7310
will be called adt7310.
config SENSORS_ADT7410
- tristate "Analog Devices ADT7410/ADT7420"
+ tristate "Analog Devices ADT7410/ADT7420/ADT7422"
depends on I2C
select SENSORS_ADT7X10
help
If you say yes here you get support for the Analog Devices
- ADT7410 and ADT7420 temperature monitoring chips.
+ ADT7410, ADT7420 and ADT7422 temperature monitoring chips.
This driver can also be built as a module. If so, the module
will be called adt7410.
@@ -235,6 +224,7 @@ config SENSORS_ADT7462
config SENSORS_ADT7470
tristate "Analog Devices ADT7470"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for the Analog Devices
ADT7470 temperature monitoring chips.
@@ -255,12 +245,12 @@ config SENSORS_ADT7475
will be called adt7475.
config SENSORS_AHT10
- tristate "Aosong AHT10, AHT20"
+ tristate "Aosong AHT10, AHT20, DHT20"
depends on I2C
select CRC8
help
- If you say yes here, you get support for the Aosong AHT10 and AHT20
- temperature and humidity sensors
+ If you say yes here, you get support for the Aosong AHT10, AHT20 and
+ DHT20 temperature and humidity sensors
This driver can also be built as a module. If so, the module
will be called aht10.
@@ -301,6 +291,16 @@ config SENSORS_ASC7621
This driver can also be built as a module. If so, the module
will be called asc7621.
+config SENSORS_ASUS_ROG_RYUJIN
+ tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver"
+ depends on HID
+ help
+ If you say yes here you get support for the fans and sensors of
+ the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler.
+
+ This driver can also be built as a module. If so, the module
+ will be called asus_rog_ryujin.
+
config SENSORS_AXI_FAN_CONTROL
tristate "Analog Devices FAN Control HDL Core driver"
help
@@ -324,7 +324,7 @@ config SENSORS_K8TEMP
config SENSORS_K10TEMP
tristate "AMD Family 10h+ temperature sensor"
- depends on X86 && PCI && AMD_NB
+ depends on X86 && PCI && AMD_NODE
help
If you say yes here you get support for the temperature
sensor(s) inside your CPU. Supported are later revisions of
@@ -335,6 +335,26 @@ config SENSORS_K10TEMP
This driver can also be built as a module. If so, the module
will be called k10temp.
+config SENSORS_KBATT
+ tristate "KEBA battery controller support"
+ depends on KEBA_CP500
+ help
+ This driver supports the battery monitoring controller found in
+ KEBA system FPGA devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called kbatt.
+
+config SENSORS_KFAN
+ tristate "KEBA fan controller support"
+ depends on KEBA_CP500
+ help
+ This driver supports the fan controller found in KEBA system
+ FPGA devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called kfan.
+
config SENSORS_FAM15H_POWER
tristate "AMD Family 15h processor power"
depends on X86 && PCI && CPU_SUP_AMD
@@ -412,6 +432,17 @@ config SENSORS_ASPEED
This driver can also be built as a module. If so, the module
will be called aspeed_pwm_tacho.
+config SENSORS_ASPEED_G6
+ tristate "ASPEED G6 PWM and Fan tach driver"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on PWM
+ help
+ This driver provides support for ASPEED G6 PWM and Fan Tach
+ controllers.
+
+ This driver can also be built as a module. If so, the module
+ will be called aspeed_g6_pwm_tach.
+
config SENSORS_ATXP1
tristate "Attansic ATXP1 VID controller"
depends on I2C
@@ -452,6 +483,26 @@ config SENSORS_BT1_PVT_ALARMS
the data conversion will be periodically performed and the data will be
saved in the internal driver cache.
+config SENSORS_CGBC
+ tristate "Congatec Board Controller Sensors"
+ depends on MFD_CGBC
+ help
+ Enable sensors support for the Congatec Board Controller. It has
+ temperature, voltage, current and fan sensors.
+
+ This driver can also be built as a module. If so, the module will be
+ called cgbc-hwmon.
+
+config SENSORS_CHIPCAP2
+ tristate "Amphenol ChipCap 2 relative humidity and temperature sensor"
+ depends on I2C
+ help
+ Say yes here to build support for the Amphenol ChipCap 2
+ relative humidity and temperature sensor.
+
+ To compile this driver as a module, choose M here: the module
+ will be called chipcap2.
+
config SENSORS_CORSAIR_CPRO
tristate "Corsair Commander Pro controller"
depends on HID
@@ -475,6 +526,17 @@ config SENSORS_CORSAIR_PSU
This driver can also be built as a module. If so, the module
will be called corsair-psu.
+config SENSORS_CROS_EC
+ tristate "ChromeOS Embedded Controller sensors"
+ depends on MFD_CROS_EC_DEV
+ default MFD_CROS_EC_DEV
+ help
+ If you say yes here you get support for ChromeOS Embedded Controller
+ sensors.
+
+ This driver can also be built as a module. If so, the module
+ will be called cros_ec_hwmon.
+
config SENSORS_DRIVETEMP
tristate "Hard disk drives with temperature sensors"
depends on SCSI && ATA
@@ -512,6 +574,7 @@ config SENSORS_DS1621
config SENSORS_DELL_SMM
tristate "Dell laptop SMM BIOS hwmon driver"
+ depends on ACPI_WMI
depends on X86
imply THERMAL
help
@@ -579,6 +642,7 @@ config SENSORS_SPARX5
config SENSORS_F71805F
tristate "Fintek F71805F/FG, F71806F/FG and F71872F/FG"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get support for hardware monitoring
@@ -590,6 +654,7 @@ config SENSORS_F71805F
config SENSORS_F71882FG
tristate "Fintek F71882FG and compatibles"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get support for hardware monitoring
@@ -635,6 +700,16 @@ config SENSORS_MC13783_ADC
help
Support for the A/D converter on MC13783 and MC13892 PMIC.
+config SENSORS_MC33XS2410
+ tristate "MC33XS2410 HWMON support"
+ depends on PWM_MC33XS2410
+ help
+ If you say yes here you get hardware monitoring support for
+ MC33XS2410.
+
+ This driver can also be built as a module. If so, the module
+ will be called mc33xs2410_hwmon.
+
config SENSORS_FSCHMD
tristate "Fujitsu Siemens Computers sensor chips"
depends on (X86 || COMPILE_TEST) && I2C
@@ -663,6 +738,16 @@ config SENSORS_FTSTEUTATES
This driver can also be built as a module. If so, the module
will be called ftsteutates.
+config SENSORS_GIGABYTE_WATERFORCE
+ tristate "Gigabyte Waterforce X240/X280/X360 AIO CPU coolers"
+ depends on USB_HID
+ help
+ If you say yes here you get support for hardware monitoring for the
+ Gigabyte Waterforce X240/X280/X360 all-in-one CPU liquid coolers.
+
+ This driver can also be built as a module. If so, the module
+ will be called gigabyte_waterforce.
+
config SENSORS_GL518SM
tristate "Genesys Logic GL518SM"
depends on I2C
@@ -684,6 +769,16 @@ config SENSORS_GL520SM
This driver can also be built as a module. If so, the module
will be called gl520sm.
+config SENSORS_GPD
+ tristate "GPD handhelds"
+ depends on X86 && DMI && HAS_IOPORT
+ help
+ If you say yes here you get support for fan readings and
+ control over GPD handheld devices.
+
+ Can also be built as a module. In that case it will be
+ called gpd-fan.
+
config SENSORS_G760A
tristate "GMT G760A"
depends on I2C
@@ -744,6 +839,17 @@ config SENSORS_HS3001
This driver can also be built as a module. If so, the module
will be called hs3001.
+config SENSORS_HTU31
+ tristate "Measurement Specialties HTU31 humidity and temperature sensor"
+ depends on I2C
+ select CRC8
+ help
+ If you say yes here you get support for the HTU31 humidity
+ and temperature sensors.
+
+ This driver can also be built as a module. If so, the module
+ will be called htu31.
+
config SENSORS_IBMAEM
tristate "IBM Active Energy Manager temperature/power sensors and control"
select IPMI_SI
@@ -810,8 +916,20 @@ config SENSORS_CORETEMP
sensor inside your CPU. Most of the family 6 CPUs
are supported. Check Documentation/hwmon/coretemp.rst for details.
+config SENSORS_ISL28022
+ tristate "Renesas ISL28022"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for ISL28022 power monitor.
+ Check Documentation/hwmon/isl28022.rst for details.
+
+ This driver can also be built as a module. If so, the module
+ will be called isl28022.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
+ depends on HAS_IOPORT
depends on !PPC
select HWMON_VID
help
@@ -839,6 +957,16 @@ config SENSORS_JC42
This driver can also be built as a module. If so, the module
will be called jc42.
+config SENSORS_POWERZ
+ tristate "ChargerLAB POWER-Z USB-C tester"
+ depends on USB
+ help
+ If you say yes here you get support for ChargerLAB POWER-Z series of
+ USB-C charging testers.
+
+ This driver can also be built as a module. If so, the module
+ will be called powerz.
+
config SENSORS_POWR1220
tristate "Lattice POWR1220 Power Monitoring"
depends on I2C
@@ -862,6 +990,16 @@ config SENSORS_LAN966X
This driver can also be built as a module. If so, the module
will be called lan966x-hwmon.
+config SENSORS_LENOVO_EC
+ tristate "Sensor reader for Lenovo ThinkStations"
+ depends on X86
+ help
+ If you say yes here you get support for LENOVO
+ EC Sensor data on newer ThinkStation systems
+
+ This driver can also be built as a module. If so, the module
+ will be called lenovo_ec_sensors.
+
config SENSORS_LINEAGE
tristate "Lineage Compact Power Line Power Entry Module"
depends on I2C
@@ -932,6 +1070,18 @@ config SENSORS_LTC2990
This driver can also be built as a module. If so, the module will
be called ltc2990.
+config SENSORS_LTC2991
+ tristate "Analog Devices LTC2991"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for Analog Devices LTC2991
+ Octal I2C Voltage, Current, and Temperature Monitor. The LTC2991
+ supports a combination of voltage, current and temperature monitoring.
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc2991.
+
config SENSORS_LTC2992
tristate "Linear Technology LTC2992"
depends on I2C
@@ -1006,6 +1156,17 @@ config SENSORS_LTC4261
This driver can also be built as a module. If so, the module will
be called ltc4261.
+config SENSORS_LTC4282
+ tristate "Analog Devices LTC4282"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for Analog Devices LTC4282
+ High Current Hot Swap Controller I2C interface.
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc4282.
+
config SENSORS_LTQ_CPUTEMP
bool "Lantiq cpu temperature sensor driver"
depends on SOC_XWAY
@@ -1013,6 +1174,18 @@ config SENSORS_LTQ_CPUTEMP
If you say yes here you get support for the temperature
sensor inside your CPU.
+config SENSORS_MACSMC_HWMON
+ tristate "Apple SMC (Apple Silicon)"
+ depends on MFD_MACSMC && OF
+ help
+ This driver enables hwmon support for current, power, temperature,
+ and voltage sensors, as well as fan speed reporting and control
+ on Apple Silicon devices. Say Y here if you have an Apple Silicon
+ device.
+
+ This driver can also be built as a module. If so, the module will
+ be called macsmc-hwmon.
+
config SENSORS_MAX1111
tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles"
depends on SPI_MASTER
@@ -1060,6 +1233,7 @@ config SENSORS_MAX1619
config SENSORS_MAX1668
tristate "Maxim MAX1668 and compatibles"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for MAX1668, MAX1989 and
MAX1805 chips.
@@ -1146,6 +1320,7 @@ config SENSORS_MAX6621
config SENSORS_MAX6639
tristate "Maxim MAX6639 sensor chip"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for the MAX6639
sensor chips.
@@ -1153,18 +1328,6 @@ config SENSORS_MAX6639
This driver can also be built as a module. If so, the module
will be called max6639.
-config SENSORS_MAX6642
- tristate "Maxim MAX6642 sensor chip"
- depends on I2C
- depends on SENSORS_LM90=n
- help
- If you say yes here you get support for MAX6642 sensor chip.
- MAX6642 is a SMBus-Compatible Remote/Local Temperature Sensor
- with Overtemperature Alarm from Maxim.
-
- This driver can also be built as a module. If so, the module
- will be called max6642.
-
config SENSORS_MAX6650
tristate "Maxim MAX6650 sensor chip"
depends on I2C
@@ -1197,9 +1360,19 @@ config SENSORS_MAX31790
This driver can also be built as a module. If so, the module
will be called max31790.
+config SENSORS_MAX77705
+ tristate "MAX77705 current and voltage sensor"
+ depends on MFD_MAX77705
+ help
+ If you say yes here you get support for MAX77705 sensors connected with I2C.
+
+ This driver can also be built as a module. If so, the module
+ will be called max77705-hwmon.
+
config SENSORS_MC34VR500
tristate "NXP MC34VR500 hardware monitoring driver"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for the temperature and input
voltage sensors of the NXP MC34VR500.
@@ -1321,7 +1494,9 @@ config SENSORS_LM73
config SENSORS_LM75
tristate "National Semiconductor LM75 and compatibles"
depends on I2C
+ depends on I3C || !I3C
select REGMAP_I2C
+ select REGMAP_I3C if I3C
help
If you say yes here you get support for one common type of
temperature sensor chip, with models including:
@@ -1426,7 +1601,7 @@ config SENSORS_LM90
MAX6657, MAX6658, MAX6659, MAX6680, MAX6681, MAX6692, MAX6695,
MAX6696,
ON Semiconductor NCT1008, NCT210, NCT72, NCT214, NCT218,
- Winbond/Nuvoton W83L771W/G/AWG/ASG,
+ Winbond/Nuvoton W83L771W/G/AWG/ASG, NCT7716, NCT7717 and NCT7718,
Philips NE1618, SA56004, GMT G781, Texas Instruments TMP451 and TMP461
sensor chips.
@@ -1436,9 +1611,10 @@ config SENSORS_LM90
config SENSORS_LM92
tristate "National Semiconductor LM92 and compatibles"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for National Semiconductor LM92
- and Maxim MAX6635 sensor chips.
+ and LM76 as well as Maxim MAX6633/6634/6635 sensor chips.
This driver can also be built as a module. If so, the module
will be called lm92.
@@ -1457,6 +1633,7 @@ config SENSORS_LM93
config SENSORS_LM95234
tristate "National Semiconductor LM95234 and compatibles"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for the LM95233 and LM95234
temperature sensor chips.
@@ -1487,6 +1664,7 @@ config SENSORS_LM95245
config SENSORS_PC87360
tristate "National Semiconductor PC87360 family"
+ depends on HAS_IOPORT
depends on !PPC
select HWMON_VID
help
@@ -1501,6 +1679,7 @@ config SENSORS_PC87360
config SENSORS_PC87427
tristate "National Semiconductor PC87427"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get access to the hardware monitoring
@@ -1528,10 +1707,11 @@ config SENSORS_NTC_THERMISTOR
B57891S0103 from EPCOS.
This driver can also be built as a module. If so, the module
- will be called ntc-thermistor.
+ will be called ntc_thermistor.
config SENSORS_NCT6683
tristate "Nuvoton NCT6683D"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get support for the hardware monitoring
@@ -1540,6 +1720,16 @@ config SENSORS_NCT6683
This driver can also be built as a module. If so, the module
will be called nct6683.
+config SENSORS_NCT6694
+ tristate "Nuvoton NCT6694 Hardware Monitor support"
+ depends on MFD_NCT6694
+ help
+ Say Y here to support Nuvoton NCT6694 hardware monitoring
+ functionality.
+
+ This driver can also be built as a module. If so, the module
+ will be called nct6694-hwmon.
+
config SENSORS_NCT6775_CORE
tristate
select REGMAP
@@ -1553,6 +1743,7 @@ config SENSORS_NCT6775_CORE
config SENSORS_NCT6775
tristate "Platform driver for Nuvoton NCT6775F and compatibles"
+ depends on HAS_IOPORT
depends on !PPC
depends on ACPI || ACPI=n
select HWMON_VID
@@ -1584,6 +1775,17 @@ config SENSORS_NCT6775_I2C
This driver can also be built as a module. If so, the module
will be called nct6775-i2c.
+config SENSORS_NCT7363
+ tristate "Nuvoton NCT7363Y"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Nuvoton NCT7363Y
+ hardware monitoring chip.
+
+ This driver can also be built as a module. If so, the module
+ will be called nct7363.
+
config SENSORS_NCT7802
tristate "Nuvoton NCT7802Y"
depends on I2C
@@ -1642,6 +1844,16 @@ config SENSORS_NZXT_KRAKEN2
This driver can also be built as a module. If so, the module
will be called nzxt-kraken2.
+config SENSORS_NZXT_KRAKEN3
+ tristate "NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers"
+ depends on USB_HID
+ help
+ If you say yes here you get support for hardware monitoring for the
+ NZXT Kraken X53/X63/X73, Z53/Z63/Z73 all-in-one CPU liquid coolers.
+
+ This driver can also be built as a module. If so, the module
+ will be called nzxt-kraken3.
+
config SENSORS_NZXT_SMART2
tristate "NZXT RGB & Fan Controller/Smart Device v2"
depends on USB_HID
@@ -1654,17 +1866,6 @@ config SENSORS_NZXT_SMART2
source "drivers/hwmon/occ/Kconfig"
-config SENSORS_OXP
- tristate "OneXPlayer EC fan control"
- depends on ACPI
- depends on X86
- help
- If you say yes here you get support for fan readings and control over
- OneXPlayer handheld devices. Only OneXPlayer mini AMD handheld variant
- boards are supported.
-
- Can also be built as a module. In that case it will be called oxp-sensors.
-
config SENSORS_PCF8591
tristate "Philips PCF8591 ADC/DAC"
depends on I2C
@@ -1682,9 +1883,19 @@ source "drivers/hwmon/peci/Kconfig"
source "drivers/hwmon/pmbus/Kconfig"
+config SENSORS_PT5161L
+ tristate "Astera Labs PT5161L PCIe retimer hardware monitoring"
+ depends on I2C
+ help
+ If you say yes here you get support for temperature monitoring
+ on the Astera Labs PT5161L PCIe retimer.
+
+ This driver can also be built as a module. If so, the module
+ will be called pt5161l.
+
config SENSORS_PWM_FAN
tristate "PWM fan"
- depends on (PWM && OF) || COMPILE_TEST
+ depends on PWM || COMPILE_TEST
depends on THERMAL || THERMAL=n
help
If you say yes here you get support for fans connected to PWM lines.
@@ -1694,6 +1905,18 @@ config SENSORS_PWM_FAN
This driver can also be built as a module. If so, the module
will be called pwm-fan.
+config SENSORS_QNAP_MCU_HWMON
+ tristate "QNAP MCU hardware monitoring"
+ depends on MFD_QNAP_MCU
+ depends on THERMAL || THERMAL=n
+ help
+ Say yes here to enable support for fan and temperature sensor
+ connected to a QNAP MCU, as found in a number of QNAP network
+ attached storage devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called qnap-mcu-hwmon.
+
config SENSORS_RASPBERRYPI_HWMON
tristate "Raspberry Pi voltage monitor"
depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
@@ -1704,6 +1927,16 @@ config SENSORS_RASPBERRYPI_HWMON
This driver can also be built as a module. If so, the module
will be called raspberrypi-hwmon.
+config SENSORS_SA67MCU
+ tristate "Kontron sa67mcu hardware monitoring driver"
+ depends on MFD_SL28CPLD || COMPILE_TEST
+ help
+ If you say yes here you get support for the voltage and temperature
+ monitor of the sa67 board management controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called sa67mcu-hwmon.
+
config SENSORS_SL28CPLD
tristate "Kontron sl28cpld hardware monitoring driver"
depends on MFD_SL28CPLD || COMPILE_TEST
@@ -1724,16 +1957,6 @@ config SENSORS_SBTSI
This driver can also be built as a module. If so, the module will
be called sbtsi_temp.
-config SENSORS_SBRMI
- tristate "Emulated SB-RMI sensor"
- depends on I2C
- help
- If you say yes here you get support for emulated RMI
- sensors on AMD SoCs with APML interface connected to a BMC device.
-
- This driver can also be built as a module. If so, the module will
- be called sbrmi.
-
config SENSORS_SHT15
tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
depends on GPIOLIB || COMPILE_TEST
@@ -1749,8 +1972,8 @@ config SENSORS_SHT21
tristate "Sensiron humidity and temperature sensors. SHT21 and compat."
depends on I2C
help
- If you say yes here you get support for the Sensiron SHT21, SHT25
- humidity and temperature sensors.
+ If you say yes here you get support for the Sensiron SHT20, SHT21,
+ SHT25 humidity and temperature sensors.
This driver can also be built as a module. If so, the module
will be called sht21.
@@ -1789,7 +2012,7 @@ config SENSORS_SHTC1
config SENSORS_SIS5595
tristate "Silicon Integrated Systems Corp. SiS5595"
- depends on PCI
+ depends on PCI && HAS_IOPORT
help
If you say yes here you get support for the integrated sensors in
SiS5595 South Bridges.
@@ -1809,6 +2032,7 @@ config SENSORS_SY7636A
config SENSORS_DME1737
tristate "SMSC DME1737, SCH311x and compatibles"
+ depends on HAS_IOPORT
depends on I2C && !PPC
select HWMON_VID
help
@@ -1865,6 +2089,7 @@ config SENSORS_EMC6W201
config SENSORS_SMSC47M1
tristate "SMSC LPC47M10x and compatibles"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get support for the integrated fan
@@ -1899,6 +2124,7 @@ config SENSORS_SMSC47M192
config SENSORS_SMSC47B397
tristate "SMSC LPC47B397-NC"
+ depends on HAS_IOPORT
depends on !PPC
help
If you say yes here you get support for the SMSC LPC47B397-NC
@@ -1909,9 +2135,11 @@ config SENSORS_SMSC47B397
config SENSORS_SCH56XX_COMMON
tristate
+ select REGMAP
config SENSORS_SCH5627
tristate "SMSC SCH5627"
+ depends on HAS_IOPORT
depends on !PPC && WATCHDOG
select SENSORS_SCH56XX_COMMON
select WATCHDOG_CORE
@@ -1925,6 +2153,7 @@ config SENSORS_SCH5627
config SENSORS_SCH5636
tristate "SMSC SCH5636"
+ depends on HAS_IOPORT
depends on !PPC && WATCHDOG
select SENSORS_SCH56XX_COMMON
select WATCHDOG_CORE
@@ -1961,6 +2190,42 @@ config SENSORS_SFCTEMP
This driver can also be built as a module. If so, the module
will be called sfctemp.
+config SENSORS_SG2042_MCU
+ tristate "Sophgo onboard MCU support"
+ depends on I2C
+ depends on ARCH_SOPHGO || COMPILE_TEST
+ help
+ Support for onboard MCU of Sophgo SG2042 SoCs. This mcu provides
+ power control and some basic information.
+
+ This driver can be built as a module. If so, the module
+ will be called sg2042-mcu.
+
+config SENSORS_SURFACE_FAN
+ tristate "Surface Fan Driver"
+ depends on SURFACE_AGGREGATOR
+ depends on SURFACE_AGGREGATOR_BUS
+ help
+ Driver that provides monitoring of the fan on Surface Pro devices that
+ have a fan, like the Surface Pro 9.
+
+ This makes the fan's current speed accessible through the hwmon
+ system. It does not provide control over the fan, the firmware is
+ responsible for that, this driver merely provides monitoring.
+
+ Select M or Y here, if you want to be able to read the fan's speed.
+
+config SENSORS_SURFACE_TEMP
+ tristate "Microsoft Surface Thermal Sensor Driver"
+ depends on SURFACE_AGGREGATOR
+ depends on SURFACE_AGGREGATOR_BUS
+ help
+ Driver for monitoring thermal sensors connected via the Surface
+ Aggregator Module (embedded controller) on Microsoft Surface devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called surface_temp.
+
config SENSORS_ADC128D818
tristate "Texas Instruments ADC128D818"
depends on I2C
@@ -1995,6 +2260,7 @@ config SENSORS_ADS7871
config SENSORS_AMC6821
tristate "Texas Instruments AMC6821"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for the Texas Instruments
AMC6821 hardware monitoring chips.
@@ -2018,22 +2284,24 @@ config SENSORS_INA2XX
select REGMAP_I2C
help
If you say yes here you get support for INA219, INA220, INA226,
- INA230, and INA231 power monitor chips.
+ INA230, INA231, INA260, and SY24655 power monitor chips.
The INA2xx driver is configured for the default configuration of
the part as described in the datasheet.
- Default value for Rshunt is 10 mOhms.
+ Default value for Rshunt is 10 mOhms except for INA260 which has an
+ internal 2 mOhm shunt resistor.
This driver can also be built as a module. If so, the module
will be called ina2xx.
config SENSORS_INA238
- tristate "Texas Instruments INA238"
+ tristate "Texas Instruments INA238 and compatibles"
depends on I2C
select REGMAP_I2C
help
- If you say yes here you get support for the INA238 power monitor
- chip. This driver supports voltage, current, power and temperature
- measurements as well as alarm configuration.
+ If you say yes here you get support for INA228, INA237, INA238,
+ INA700, INA780, and SQ52206 power monitor chips. This driver supports
+ voltage, current, power, energy, and temperature measurements as well
+ as alarm configuration.
This driver can also be built as a module. If so, the module
will be called ina238.
@@ -2049,6 +2317,37 @@ config SENSORS_INA3221
This driver can also be built as a module. If so, the module
will be called ina3221.
+config SENSORS_SPD5118
+ tristate "SPD5118 Compliant Temperature Sensors"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for SPD5118 (JEDEC JESD300)
+ compliant temperature sensors. Such sensors are found on DDR5 memory
+ modules.
+
+ This driver can also be built as a module. If so, the module
+ will be called spd5118.
+
+config SENSORS_SPD5118_DETECT
+ bool "Enable detect function"
+ depends on SENSORS_SPD5118
+ default (!DMI || !X86)
+ help
+ If enabled, the driver auto-detects if a chip in the SPD address
+ range is compliant to the SPD51888 standard and auto-instantiates
+ if that is the case. If disabled, SPD5118 compliant devices have
+ to be instantiated by other means. On X86 systems with DMI support
+ this will typically be done from DMI DDR detection code in the
+ I2C SMBus subsystem. Devicetree based systems will instantiate
+ attached devices if the DIMMs are listed in the devicetree file.
+
+ Disabling the detect function will speed up boot time and reduce
+ the risk of mis-detecting SPD5118 compliant devices. However, it
+ may result in missed DIMMs under some circumstances.
+
+ If unsure, say Y.
+
config SENSORS_TC74
tristate "Microchip TC74"
depends on I2C
@@ -2094,10 +2393,12 @@ config SENSORS_TMP103
config SENSORS_TMP108
tristate "Texas Instruments TMP108"
depends on I2C
+ depends on I3C || !I3C
select REGMAP_I2C
+ select REGMAP_I3C if I3C
help
If you say yes here you get support for Texas Instruments TMP108
- sensor chips.
+ sensor chips and NXP P3T1085.
This driver can also be built as a module. If so, the module
will be called tmp108.
@@ -2137,6 +2438,7 @@ config SENSORS_TMP464
config SENSORS_TMP513
tristate "Texas Instruments TMP513 and compatibles"
depends on I2C
+ select REGMAP_I2C
help
If you say yes here you get support for Texas Instruments TMP512,
and TMP513 temperature and power supply sensor chips.
@@ -2144,6 +2446,18 @@ config SENSORS_TMP513
This driver can also be built as a module. If so, the module
will be called tmp513.
+config SENSORS_TSC1641
+ tristate "ST Microelectronics TSC1641 Power Monitor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for TSC1641 power monitor chip.
+ The TSC1641 driver is configured for the default configuration of
+ the part except temperature is enabled by default.
+
+ This driver can also be built as a module. If so, the module
+ will be called tsc1641.
+
config SENSORS_VEXPRESS
tristate "Versatile Express"
depends on VEXPRESS_CONFIG
@@ -2163,7 +2477,7 @@ config SENSORS_VIA_CPUTEMP
config SENSORS_VIA686A
tristate "VIA686A"
- depends on PCI
+ depends on PCI && HAS_IOPORT
help
If you say yes here you get support for the integrated sensors in
Via 686A/B South Bridges.
@@ -2173,6 +2487,7 @@ config SENSORS_VIA686A
config SENSORS_VT1211
tristate "VIA VT1211"
+ depends on HAS_IOPORT
depends on !PPC
select HWMON_VID
help
@@ -2184,7 +2499,7 @@ config SENSORS_VT1211
config SENSORS_VT8231
tristate "VIA VT8231"
- depends on PCI
+ depends on PCI && HAS_IOPORT
select HWMON_VID
help
If you say yes here then you get support for the integrated sensors
@@ -2292,6 +2607,7 @@ config SENSORS_W83L786NG
config SENSORS_W83627HF
tristate "Winbond W83627HF, W83627THF, W83637HF, W83687THF, W83697HF"
+ depends on HAS_IOPORT
depends on !PPC
select HWMON_VID
help
@@ -2304,6 +2620,7 @@ config SENSORS_W83627HF
config SENSORS_W83627EHF
tristate "Winbond W83627EHF/EHG/DHG/UHG, W83667HG"
+ depends on HAS_IOPORT
depends on !PPC
select HWMON_VID
help
@@ -2408,11 +2725,13 @@ config SENSORS_ASUS_WMI
config SENSORS_ASUS_EC
tristate "ASUS EC Sensors"
depends on X86
+ depends on ACPI_EC
help
If you say yes here you get support for the ACPI embedded controller
- hardware monitoring interface found in ASUS motherboards. The driver
- currently supports B550/X570 boards, although other ASUS boards might
- provide this monitoring interface as well.
+ hardware monitoring interface found in some ASUS motherboards. This is
+ where such sensors as water flow and temperature, optional fans, and
+ additional temperature sensors (T_Sensor, chipset temperatures)
+ find themselves.
This driver can also be built as a module. If so, the module
will be called asus_ec_sensors.
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 4ac9452b5430..eade8e3b1bde 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -31,7 +31,6 @@ obj-$(CONFIG_SENSORS_AD7414) += ad7414.o
obj-$(CONFIG_SENSORS_AD7418) += ad7418.o
obj-$(CONFIG_SENSORS_ADC128D818) += adc128d818.o
obj-$(CONFIG_SENSORS_ADCXX) += adcxx.o
-obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o
obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o
@@ -55,12 +54,17 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
+obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
+obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
+obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o
+obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
+obj-$(CONFIG_SENSORS_CROS_EC) += cros_ec_hwmon.o
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
@@ -80,13 +84,16 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o
obj-$(CONFIG_SENSORS_G760A) += g760a.o
obj-$(CONFIG_SENSORS_G762) += g762.o
+obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
+obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
obj-$(CONFIG_SENSORS_HS3001) += hs3001.o
+obj-$(CONFIG_SENSORS_HTU31) += htu31.o
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
obj-$(CONFIG_SENSORS_I5500) += i5500_temp.o
obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
@@ -99,11 +106,15 @@ obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA238) += ina238.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o
+obj-$(CONFIG_SENSORS_ISL28022) += isl28022.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_JC42) += jc42.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
+obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
+obj-$(CONFIG_SENSORS_KFAN) += kfan.o
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
+obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
obj-$(CONFIG_SENSORS_LM63) += lm63.o
@@ -127,6 +138,7 @@ obj-$(CONFIG_SENSORS_LTC2947) += ltc2947-core.o
obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o
obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o
obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o
+obj-$(CONFIG_SENSORS_LTC2991) += ltc2991.o
obj-$(CONFIG_SENSORS_LTC2992) += ltc2992.o
obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
@@ -134,7 +146,9 @@ obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
+obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
+obj-$(CONFIG_SENSORS_MACSMC_HWMON) += macsmc-hwmon.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX127) += max127.o
obj-$(CONFIG_SENSORS_MAX16065) += max16065.o
@@ -147,12 +161,13 @@ obj-$(CONFIG_SENSORS_MAX31760) += max31760.o
obj-$(CONFIG_SENSORS_MAX6620) += max6620.o
obj-$(CONFIG_SENSORS_MAX6621) += max6621.o
obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
-obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
obj-$(CONFIG_SENSORS_MAX6697) += max6697.o
obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
obj-$(CONFIG_MAX31827) += max31827.o
+obj-$(CONFIG_SENSORS_MAX77705) += max77705-hwmon.o
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
+obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
obj-$(CONFIG_SENSORS_TC654) += tc654.o
@@ -161,30 +176,37 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
+obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o
obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
nct6775-objs := nct6775-platform.o
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o
+obj-$(CONFIG_SENSORS_NCT7363) += nct7363.o
obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o
obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o
obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o
obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o
+obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o
obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
-obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
+obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
+obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
+obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
+obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o
obj-$(CONFIG_SENSORS_SFCTEMP) += sfctemp.o
+obj-$(CONFIG_SENSORS_SG2042_MCU) += sg2042-mcu.o
obj-$(CONFIG_SENSORS_SL28CPLD) += sl28cpld-hwmon.o
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
obj-$(CONFIG_SENSORS_SHT21) += sht21.o
@@ -197,7 +219,10 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o
+obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o
obj-$(CONFIG_SENSORS_STTS751) += stts751.o
+obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
+obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o
obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o
obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o
obj-$(CONFIG_SENSORS_TC74) += tc74.o
@@ -209,6 +234,7 @@ obj-$(CONFIG_SENSORS_TMP401) += tmp401.o
obj-$(CONFIG_SENSORS_TMP421) += tmp421.o
obj-$(CONFIG_SENSORS_TMP464) += tmp464.o
obj-$(CONFIG_SENSORS_TMP513) += tmp513.o
+obj-$(CONFIG_SENSORS_TSC1641) += tsc1641.o
obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o
obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o
obj-$(CONFIG_SENSORS_VIA686A) += via686a.o
diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c
index a7cae6568155..ba8c68ae4595 100644
--- a/drivers/hwmon/abituguru.c
+++ b/drivers/hwmon/abituguru.c
@@ -1428,7 +1428,7 @@ abituguru_probe_error:
return res;
}
-static int abituguru_remove(struct platform_device *pdev)
+static void abituguru_remove(struct platform_device *pdev)
{
int i;
struct abituguru_data *data = platform_get_drvdata(pdev);
@@ -1439,8 +1439,6 @@ static int abituguru_remove(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++)
device_remove_file(&pdev->dev,
&abituguru_sysfs_attr[i].dev_attr);
-
- return 0;
}
static struct abituguru_data *abituguru_update_device(struct device *dev)
diff --git a/drivers/hwmon/abituguru3.c b/drivers/hwmon/abituguru3.c
index afb21f73032d..b70330dc2198 100644
--- a/drivers/hwmon/abituguru3.c
+++ b/drivers/hwmon/abituguru3.c
@@ -1061,7 +1061,7 @@ abituguru3_probe_error:
return res;
}
-static int abituguru3_remove(struct platform_device *pdev)
+static void abituguru3_remove(struct platform_device *pdev)
{
int i;
struct abituguru3_data *data = platform_get_drvdata(pdev);
@@ -1072,7 +1072,6 @@ static int abituguru3_remove(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++)
device_remove_file(&pdev->dev,
&abituguru3_sysfs_attr[i].dev_attr);
- return 0;
}
static struct abituguru3_data *abituguru3_update_device(struct device *dev)
@@ -1148,7 +1147,7 @@ static int abituguru3_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(abituguru3_pm, abituguru3_suspend, abituguru3_resume);
static struct platform_driver abituguru3_driver = {
- .driver = {
+ .driver = {
.name = ABIT_UGURU3_NAME,
.pm = pm_sleep_ptr(&abituguru3_pm),
},
diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c
index fa28d447f0df..29ccdc2fb7ff 100644
--- a/drivers/hwmon/acpi_power_meter.c
+++ b/drivers/hwmon/acpi_power_meter.c
@@ -31,6 +31,7 @@
#define POWER_METER_CAN_NOTIFY (1 << 3)
#define POWER_METER_IS_BATTERY (1 << 8)
#define UNKNOWN_HYSTERESIS 0xFFFFFFFF
+#define UNKNOWN_POWER 0xFFFFFFFF
#define METER_NOTIFY_CONFIG 0x80
#define METER_NOTIFY_TRIP 0x81
@@ -83,27 +84,17 @@ struct acpi_power_meter_resource {
u64 power;
u64 cap;
u64 avg_interval;
+ bool power_alarm;
int sensors_valid;
unsigned long sensors_last_updated;
- struct sensor_device_attribute sensors[NUM_SENSORS];
- int num_sensors;
+#define POWER_METER_TRIP_AVERAGE_MIN_IDX 0
+#define POWER_METER_TRIP_AVERAGE_MAX_IDX 1
s64 trip[2];
int num_domain_devices;
struct acpi_device **domain_devices;
struct kobject *holders_dir;
};
-struct sensor_template {
- char *label;
- ssize_t (*show)(struct device *dev,
- struct device_attribute *devattr,
- char *buf);
- ssize_t (*set)(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count);
- int index;
-};
-
/* Averaging interval */
static int update_avg_interval(struct acpi_power_meter_resource *resource)
{
@@ -122,62 +113,6 @@ static int update_avg_interval(struct acpi_power_meter_resource *resource)
return 0;
}
-static ssize_t show_avg_interval(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
-
- mutex_lock(&resource->lock);
- update_avg_interval(resource);
- mutex_unlock(&resource->lock);
-
- return sprintf(buf, "%llu\n", resource->avg_interval);
-}
-
-static ssize_t set_avg_interval(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- union acpi_object arg0 = { ACPI_TYPE_INTEGER };
- struct acpi_object_list args = { 1, &arg0 };
- int res;
- unsigned long temp;
- unsigned long long data;
- acpi_status status;
-
- res = kstrtoul(buf, 10, &temp);
- if (res)
- return res;
-
- if (temp > resource->caps.max_avg_interval ||
- temp < resource->caps.min_avg_interval)
- return -EINVAL;
- arg0.integer.value = temp;
-
- mutex_lock(&resource->lock);
- status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
- &args, &data);
- if (ACPI_SUCCESS(status))
- resource->avg_interval = temp;
- mutex_unlock(&resource->lock);
-
- if (ACPI_FAILURE(status)) {
- acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PAI",
- status);
- return -EINVAL;
- }
-
- /* _PAI returns 0 on success, nonzero otherwise */
- if (data)
- return -EINVAL;
-
- return count;
-}
-
/* Cap functions */
static int update_cap(struct acpi_power_meter_resource *resource)
{
@@ -196,61 +131,6 @@ static int update_cap(struct acpi_power_meter_resource *resource)
return 0;
}
-static ssize_t show_cap(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
-
- mutex_lock(&resource->lock);
- update_cap(resource);
- mutex_unlock(&resource->lock);
-
- return sprintf(buf, "%llu\n", resource->cap * 1000);
-}
-
-static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- union acpi_object arg0 = { ACPI_TYPE_INTEGER };
- struct acpi_object_list args = { 1, &arg0 };
- int res;
- unsigned long temp;
- unsigned long long data;
- acpi_status status;
-
- res = kstrtoul(buf, 10, &temp);
- if (res)
- return res;
-
- temp = DIV_ROUND_CLOSEST(temp, 1000);
- if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
- return -EINVAL;
- arg0.integer.value = temp;
-
- mutex_lock(&resource->lock);
- status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
- &args, &data);
- if (ACPI_SUCCESS(status))
- resource->cap = temp;
- mutex_unlock(&resource->lock);
-
- if (ACPI_FAILURE(status)) {
- acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_SHL",
- status);
- return -EINVAL;
- }
-
- /* _SHL returns 0 on success, nonzero otherwise */
- if (data)
- return -EINVAL;
-
- return count;
-}
-
/* Power meter trip points */
static int set_acpi_trip(struct acpi_power_meter_resource *resource)
{
@@ -285,32 +165,6 @@ static int set_acpi_trip(struct acpi_power_meter_resource *resource)
return 0;
}
-static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- int res;
- unsigned long temp;
-
- res = kstrtoul(buf, 10, &temp);
- if (res)
- return res;
-
- temp = DIV_ROUND_CLOSEST(temp, 1000);
-
- mutex_lock(&resource->lock);
- resource->trip[attr->index - 7] = temp;
- res = set_acpi_trip(resource);
- mutex_unlock(&resource->lock);
-
- if (res)
- return res;
-
- return count;
-}
-
/* Power meter */
static int update_meter(struct acpi_power_meter_resource *resource)
{
@@ -337,189 +191,6 @@ static int update_meter(struct acpi_power_meter_resource *resource)
return 0;
}
-static ssize_t show_power(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
-
- mutex_lock(&resource->lock);
- update_meter(resource);
- mutex_unlock(&resource->lock);
-
- return sprintf(buf, "%llu\n", resource->power * 1000);
-}
-
-/* Miscellaneous */
-static ssize_t show_str(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- acpi_string val;
- int ret;
-
- mutex_lock(&resource->lock);
- switch (attr->index) {
- case 0:
- val = resource->model_number;
- break;
- case 1:
- val = resource->serial_number;
- break;
- case 2:
- val = resource->oem_info;
- break;
- default:
- WARN(1, "Implementation error: unexpected attribute index %d\n",
- attr->index);
- val = "";
- break;
- }
- ret = sprintf(buf, "%s\n", val);
- mutex_unlock(&resource->lock);
- return ret;
-}
-
-static ssize_t show_val(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- u64 val = 0;
-
- switch (attr->index) {
- case 0:
- val = resource->caps.min_avg_interval;
- break;
- case 1:
- val = resource->caps.max_avg_interval;
- break;
- case 2:
- val = resource->caps.min_cap * 1000;
- break;
- case 3:
- val = resource->caps.max_cap * 1000;
- break;
- case 4:
- if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
- return sprintf(buf, "unknown\n");
-
- val = resource->caps.hysteresis * 1000;
- break;
- case 5:
- if (resource->caps.flags & POWER_METER_IS_BATTERY)
- val = 1;
- else
- val = 0;
- break;
- case 6:
- if (resource->power > resource->cap)
- val = 1;
- else
- val = 0;
- break;
- case 7:
- case 8:
- if (resource->trip[attr->index - 7] < 0)
- return sprintf(buf, "unknown\n");
-
- val = resource->trip[attr->index - 7] * 1000;
- break;
- default:
- WARN(1, "Implementation error: unexpected attribute index %d\n",
- attr->index);
- break;
- }
-
- return sprintf(buf, "%llu\n", val);
-}
-
-static ssize_t show_accuracy(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct acpi_device *acpi_dev = to_acpi_device(dev);
- struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
- unsigned int acc = resource->caps.accuracy;
-
- return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
-}
-
-static ssize_t show_name(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
-}
-
-#define RO_SENSOR_TEMPLATE(_label, _show, _index) \
- { \
- .label = _label, \
- .show = _show, \
- .index = _index, \
- }
-
-#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index) \
- { \
- .label = _label, \
- .show = _show, \
- .set = _set, \
- .index = _index, \
- }
-
-/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */
-static struct sensor_template meter_attrs[] = {
- RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0),
- RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0),
- RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0),
- RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1),
- RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5),
- RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval,
- set_avg_interval, 0),
- {},
-};
-
-static struct sensor_template misc_cap_attrs[] = {
- RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2),
- RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3),
- RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4),
- RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6),
- {},
-};
-
-static struct sensor_template ro_cap_attrs[] = {
- RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0),
- {},
-};
-
-static struct sensor_template rw_cap_attrs[] = {
- RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0),
- {},
-};
-
-static struct sensor_template trip_attrs[] = {
- RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7),
- RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8),
- {},
-};
-
-static struct sensor_template misc_attrs[] = {
- RO_SENSOR_TEMPLATE("name", show_name, 0),
- RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0),
- RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2),
- RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1),
- {},
-};
-
-#undef RO_SENSOR_TEMPLATE
-#undef RW_SENSOR_TEMPLATE
-
/* Read power domain data */
static void remove_domain_devices(struct acpi_power_meter_resource *resource)
{
@@ -621,108 +292,434 @@ end:
return res;
}
-/* Registration and deregistration */
-static int register_attrs(struct acpi_power_meter_resource *resource,
- struct sensor_template *attrs)
+static int set_trip(struct acpi_power_meter_resource *resource, u16 trip_idx,
+ unsigned long trip)
{
- struct device *dev = &resource->acpi_dev->dev;
- struct sensor_device_attribute *sensors =
- &resource->sensors[resource->num_sensors];
- int res = 0;
+ unsigned long trip_bk;
+ int ret;
- while (attrs->label) {
- sensors->dev_attr.attr.name = attrs->label;
- sensors->dev_attr.attr.mode = 0444;
- sensors->dev_attr.show = attrs->show;
- sensors->index = attrs->index;
+ trip = DIV_ROUND_CLOSEST(trip, 1000);
+ trip_bk = resource->trip[trip_idx];
- if (attrs->set) {
- sensors->dev_attr.attr.mode |= 0200;
- sensors->dev_attr.store = attrs->set;
- }
-
- sysfs_attr_init(&sensors->dev_attr.attr);
- res = device_create_file(dev, &sensors->dev_attr);
- if (res) {
- sensors->dev_attr.attr.name = NULL;
- goto error;
- }
- sensors++;
- resource->num_sensors++;
- attrs++;
+ resource->trip[trip_idx] = trip;
+ ret = set_acpi_trip(resource);
+ if (ret) {
+ dev_err(&resource->acpi_dev->dev, "set %s failed.\n",
+ (trip_idx == POWER_METER_TRIP_AVERAGE_MIN_IDX) ?
+ "power1_average_min" : "power1_average_max");
+ resource->trip[trip_idx] = trip_bk;
}
-error:
- return res;
+ return ret;
}
-static void remove_attrs(struct acpi_power_meter_resource *resource)
+static int set_cap(struct acpi_power_meter_resource *resource,
+ unsigned long cap)
{
- int i;
+ union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+ struct acpi_object_list args = { 1, &arg0 };
+ unsigned long long data;
+ acpi_status status;
- for (i = 0; i < resource->num_sensors; i++) {
- if (!resource->sensors[i].dev_attr.attr.name)
- continue;
- device_remove_file(&resource->acpi_dev->dev,
- &resource->sensors[i].dev_attr);
+ cap = DIV_ROUND_CLOSEST(cap, 1000);
+ if (cap > resource->caps.max_cap || cap < resource->caps.min_cap)
+ return -EINVAL;
+
+ arg0.integer.value = cap;
+ status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
+ &args, &data);
+ if (ACPI_FAILURE(status)) {
+ acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_SHL",
+ status);
+ return -EINVAL;
}
+ resource->cap = cap;
- remove_domain_devices(resource);
+ /* _SHL returns 0 on success, nonzero otherwise */
+ if (data)
+ return -EINVAL;
- resource->num_sensors = 0;
+ return 0;
}
-static int setup_attrs(struct acpi_power_meter_resource *resource)
+static int set_avg_interval(struct acpi_power_meter_resource *resource,
+ unsigned long val)
{
- int res = 0;
+ union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+ struct acpi_object_list args = { 1, &arg0 };
+ unsigned long long data;
+ acpi_status status;
- res = read_domain_devices(resource);
- if (res)
- return res;
+ if (val > resource->caps.max_avg_interval ||
+ val < resource->caps.min_avg_interval)
+ return -EINVAL;
- if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
- res = register_attrs(resource, meter_attrs);
- if (res)
- goto error;
+ arg0.integer.value = val;
+ status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
+ &args, &data);
+ if (ACPI_FAILURE(status)) {
+ acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PAI",
+ status);
+ return -EINVAL;
}
+ resource->avg_interval = val;
- if (resource->caps.flags & POWER_METER_CAN_CAP) {
- if (!can_cap_in_hardware()) {
- dev_warn(&resource->acpi_dev->dev,
- "Ignoring unsafe software power cap!\n");
- goto skip_unsafe_cap;
+ /* _PAI returns 0 on success, nonzero otherwise */
+ if (data)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int get_power_alarm_state(struct acpi_power_meter_resource *resource,
+ long *val)
+{
+ int ret;
+
+ ret = update_meter(resource);
+ if (ret)
+ return ret;
+
+ /* need to update cap if not to support the notification. */
+ if (!(resource->caps.flags & POWER_METER_CAN_NOTIFY)) {
+ ret = update_cap(resource);
+ if (ret)
+ return ret;
+ resource->power_alarm = resource->power > resource->cap;
+ *val = resource->power_alarm;
+ } else {
+ *val = resource->power_alarm || resource->power > resource->cap;
+ resource->power_alarm = resource->power > resource->cap;
+ }
+
+ return 0;
+}
+
+static umode_t power_meter_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct acpi_power_meter_resource *res = data;
+
+ if (type != hwmon_power)
+ return 0;
+
+ switch (attr) {
+ case hwmon_power_average:
+ case hwmon_power_average_interval_min:
+ case hwmon_power_average_interval_max:
+ if (res->caps.flags & POWER_METER_CAN_MEASURE)
+ return 0444;
+ break;
+ case hwmon_power_average_interval:
+ if (res->caps.flags & POWER_METER_CAN_MEASURE)
+ return 0644;
+ break;
+ case hwmon_power_cap_min:
+ case hwmon_power_cap_max:
+ case hwmon_power_alarm:
+ if (res->caps.flags & POWER_METER_CAN_CAP && can_cap_in_hardware())
+ return 0444;
+ break;
+ case hwmon_power_cap:
+ if (res->caps.flags & POWER_METER_CAN_CAP && can_cap_in_hardware()) {
+ if (res->caps.configurable_cap)
+ return 0644;
+ else
+ return 0444;
}
+ break;
+ default:
+ break;
+ }
- if (resource->caps.configurable_cap)
- res = register_attrs(resource, rw_cap_attrs);
- else
- res = register_attrs(resource, ro_cap_attrs);
+ return 0;
+}
- if (res)
- goto error;
+static int power_meter_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+ int ret = 0;
- res = register_attrs(resource, misc_cap_attrs);
- if (res)
- goto error;
+ if (type != hwmon_power)
+ return -EINVAL;
+
+ guard(mutex)(&res->lock);
+
+ switch (attr) {
+ case hwmon_power_average:
+ ret = update_meter(res);
+ if (ret)
+ return ret;
+ if (res->power == UNKNOWN_POWER)
+ return -ENODATA;
+ *val = res->power * 1000;
+ break;
+ case hwmon_power_average_interval_min:
+ *val = res->caps.min_avg_interval;
+ break;
+ case hwmon_power_average_interval_max:
+ *val = res->caps.max_avg_interval;
+ break;
+ case hwmon_power_average_interval:
+ ret = update_avg_interval(res);
+ if (ret)
+ return ret;
+ *val = (res)->avg_interval;
+ break;
+ case hwmon_power_cap_min:
+ *val = res->caps.min_cap * 1000;
+ break;
+ case hwmon_power_cap_max:
+ *val = res->caps.max_cap * 1000;
+ break;
+ case hwmon_power_alarm:
+ ret = get_power_alarm_state(res, val);
+ if (ret)
+ return ret;
+ break;
+ case hwmon_power_cap:
+ ret = update_cap(res);
+ if (ret)
+ return ret;
+ *val = res->cap * 1000;
+ break;
+ default:
+ break;
}
-skip_unsafe_cap:
- if (resource->caps.flags & POWER_METER_CAN_TRIP) {
- res = register_attrs(resource, trip_attrs);
- if (res)
- goto error;
+ return 0;
+}
+
+static int power_meter_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+ int ret;
+
+ if (type != hwmon_power)
+ return -EINVAL;
+
+ guard(mutex)(&res->lock);
+ switch (attr) {
+ case hwmon_power_cap:
+ ret = set_cap(res, val);
+ break;
+ case hwmon_power_average_interval:
+ ret = set_avg_interval(res, val);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
}
- res = register_attrs(resource, misc_attrs);
- if (res)
- goto error;
+ return ret;
+}
- return res;
-error:
- remove_attrs(resource);
- return res;
+static const struct hwmon_channel_info * const power_meter_info[] = {
+ HWMON_CHANNEL_INFO(power, HWMON_P_AVERAGE |
+ HWMON_P_AVERAGE_INTERVAL | HWMON_P_AVERAGE_INTERVAL_MIN |
+ HWMON_P_AVERAGE_INTERVAL_MAX | HWMON_P_CAP | HWMON_P_CAP_MIN |
+ HWMON_P_CAP_MAX | HWMON_P_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops power_meter_ops = {
+ .is_visible = power_meter_is_visible,
+ .read = power_meter_read,
+ .write = power_meter_write,
+};
+
+static const struct hwmon_chip_info power_meter_chip_info = {
+ .ops = &power_meter_ops,
+ .info = power_meter_info,
+};
+
+static ssize_t power1_average_max_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+ unsigned long trip;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &trip);
+ if (ret)
+ return ret;
+
+ mutex_lock(&res->lock);
+ ret = set_trip(res, POWER_METER_TRIP_AVERAGE_MAX_IDX, trip);
+ mutex_unlock(&res->lock);
+
+ return ret == 0 ? count : ret;
+}
+
+static ssize_t power1_average_min_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+ unsigned long trip;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &trip);
+ if (ret)
+ return ret;
+
+ mutex_lock(&res->lock);
+ ret = set_trip(res, POWER_METER_TRIP_AVERAGE_MIN_IDX, trip);
+ mutex_unlock(&res->lock);
+
+ return ret == 0 ? count : ret;
+}
+
+static ssize_t power1_average_min_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ if (res->trip[POWER_METER_TRIP_AVERAGE_MIN_IDX] < 0)
+ return sysfs_emit(buf, "unknown\n");
+
+ return sysfs_emit(buf, "%lld\n",
+ res->trip[POWER_METER_TRIP_AVERAGE_MIN_IDX] * 1000);
+}
+
+static ssize_t power1_average_max_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ if (res->trip[POWER_METER_TRIP_AVERAGE_MAX_IDX] < 0)
+ return sysfs_emit(buf, "unknown\n");
+
+ return sysfs_emit(buf, "%lld\n",
+ res->trip[POWER_METER_TRIP_AVERAGE_MAX_IDX] * 1000);
+}
+
+static ssize_t power1_cap_hyst_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ if (res->caps.hysteresis == UNKNOWN_HYSTERESIS)
+ return sysfs_emit(buf, "unknown\n");
+
+ return sysfs_emit(buf, "%llu\n", res->caps.hysteresis * 1000);
+}
+
+static ssize_t power1_accuracy_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+ unsigned int acc = res->caps.accuracy;
+
+ return sysfs_emit(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
}
+static ssize_t power1_is_battery_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n",
+ res->caps.flags & POWER_METER_IS_BATTERY ? 1 : 0);
+}
+
+static ssize_t power1_model_number_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", res->model_number);
+}
+
+static ssize_t power1_oem_info_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", res->oem_info);
+}
+
+static ssize_t power1_serial_number_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", res->serial_number);
+}
+
+/* depend on POWER_METER_CAN_TRIP */
+static DEVICE_ATTR_RW(power1_average_max);
+static DEVICE_ATTR_RW(power1_average_min);
+
+/* depend on POWER_METER_CAN_CAP */
+static DEVICE_ATTR_RO(power1_cap_hyst);
+
+/* depend on POWER_METER_CAN_MEASURE */
+static DEVICE_ATTR_RO(power1_accuracy);
+static DEVICE_ATTR_RO(power1_is_battery);
+
+static DEVICE_ATTR_RO(power1_model_number);
+static DEVICE_ATTR_RO(power1_oem_info);
+static DEVICE_ATTR_RO(power1_serial_number);
+
+static umode_t power_extra_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct acpi_power_meter_resource *res = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_power1_is_battery.attr ||
+ attr == &dev_attr_power1_accuracy.attr) {
+ if ((res->caps.flags & POWER_METER_CAN_MEASURE) == 0)
+ return 0;
+ }
+
+ if (attr == &dev_attr_power1_cap_hyst.attr) {
+ if ((res->caps.flags & POWER_METER_CAN_CAP) == 0) {
+ return 0;
+ } else if (!can_cap_in_hardware()) {
+ dev_warn(&res->acpi_dev->dev,
+ "Ignoring unsafe software power cap!\n");
+ return 0;
+ }
+ }
+
+ if (attr == &dev_attr_power1_average_max.attr ||
+ attr == &dev_attr_power1_average_min.attr) {
+ if ((res->caps.flags & POWER_METER_CAN_TRIP) == 0)
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static struct attribute *power_extra_attrs[] = {
+ &dev_attr_power1_average_max.attr,
+ &dev_attr_power1_average_min.attr,
+ &dev_attr_power1_cap_hyst.attr,
+ &dev_attr_power1_accuracy.attr,
+ &dev_attr_power1_is_battery.attr,
+ &dev_attr_power1_model_number.attr,
+ &dev_attr_power1_oem_info.attr,
+ &dev_attr_power1_serial_number.attr,
+ NULL
+};
+
+static const struct attribute_group power_extra_group = {
+ .attrs = power_extra_attrs,
+ .is_visible = power_extra_is_visible,
+};
+
+__ATTRIBUTE_GROUPS(power_extra);
+
static void free_capabilities(struct acpi_power_meter_resource *resource)
{
acpi_string *str;
@@ -796,14 +793,13 @@ static int read_capabilities(struct acpi_power_meter_resource *resource)
goto error;
}
- *str = kcalloc(element->string.length + 1, sizeof(u8),
- GFP_KERNEL);
+ *str = kmemdup_nul(element->string.pointer, element->string.length,
+ GFP_KERNEL);
if (!*str) {
res = -ENOMEM;
goto error;
}
- strncpy(*str, element->string.pointer, element->string.length);
str++;
}
@@ -831,24 +827,42 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
case METER_NOTIFY_CONFIG:
mutex_lock(&resource->lock);
free_capabilities(resource);
+ remove_domain_devices(resource);
+ hwmon_device_unregister(resource->hwmon_dev);
res = read_capabilities(resource);
- mutex_unlock(&resource->lock);
if (res)
- break;
-
- remove_attrs(resource);
- setup_attrs(resource);
+ dev_err_once(&device->dev, "read capabilities failed.\n");
+ res = read_domain_devices(resource);
+ if (res && res != -ENODEV)
+ dev_err_once(&device->dev, "read domain devices failed.\n");
+ resource->hwmon_dev =
+ hwmon_device_register_with_info(&device->dev,
+ ACPI_POWER_METER_NAME,
+ resource,
+ &power_meter_chip_info,
+ power_extra_groups);
+ if (IS_ERR(resource->hwmon_dev))
+ dev_err_once(&device->dev, "register hwmon device failed.\n");
+ mutex_unlock(&resource->lock);
break;
case METER_NOTIFY_TRIP:
sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME);
break;
case METER_NOTIFY_CAP:
+ mutex_lock(&resource->lock);
+ res = update_cap(resource);
+ if (res)
+ dev_err_once(&device->dev, "update cap failed when capping value is changed.\n");
+ mutex_unlock(&resource->lock);
sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME);
break;
case METER_NOTIFY_INTERVAL:
sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME);
break;
case METER_NOTIFY_CAPPING:
+ mutex_lock(&resource->lock);
+ resource->power_alarm = true;
+ mutex_unlock(&resource->lock);
sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME);
dev_info(&device->dev, "Capping in progress.\n");
break;
@@ -880,6 +894,22 @@ static int acpi_power_meter_add(struct acpi_device *device)
strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
device->driver_data = resource;
+#if IS_REACHABLE(CONFIG_ACPI_IPMI)
+ /*
+ * On Dell systems several methods of acpi_power_meter access
+ * variables in IPMI region, so wait until IPMI space handler is
+ * installed by acpi_ipmi and also wait until SMI is selected to make
+ * the space handler fully functional.
+ */
+ if (dmi_match(DMI_SYS_VENDOR, "Dell Inc.")) {
+ struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1);
+
+ if (ipi_device && acpi_wait_for_acpi_ipmi())
+ dev_warn(&device->dev, "Waiting for ACPI IPMI timeout");
+ acpi_dev_put(ipi_device);
+ }
+#endif
+
res = read_capabilities(resource);
if (res)
goto exit_free;
@@ -887,11 +917,16 @@ static int acpi_power_meter_add(struct acpi_device *device)
resource->trip[0] = -1;
resource->trip[1] = -1;
- res = setup_attrs(resource);
- if (res)
+ /* _PMD method is optional. */
+ res = read_domain_devices(resource);
+ if (res && res != -ENODEV)
goto exit_free_capability;
- resource->hwmon_dev = hwmon_device_register(&device->dev);
+ resource->hwmon_dev =
+ hwmon_device_register_with_info(&device->dev,
+ ACPI_POWER_METER_NAME, resource,
+ &power_meter_chip_info,
+ power_extra_groups);
if (IS_ERR(resource->hwmon_dev)) {
res = PTR_ERR(resource->hwmon_dev);
goto exit_remove;
@@ -901,7 +936,7 @@ static int acpi_power_meter_add(struct acpi_device *device)
goto exit;
exit_remove:
- remove_attrs(resource);
+ remove_domain_devices(resource);
exit_free_capability:
free_capabilities(resource);
exit_free:
@@ -920,7 +955,7 @@ static void acpi_power_meter_remove(struct acpi_device *device)
resource = acpi_driver_data(device);
hwmon_device_unregister(resource->hwmon_dev);
- remove_attrs(resource);
+ remove_domain_devices(resource);
free_capabilities(resource);
kfree(resource);
diff --git a/drivers/hwmon/ad7314.c b/drivers/hwmon/ad7314.c
index 7802bbf5f958..59424103f634 100644
--- a/drivers/hwmon/ad7314.c
+++ b/drivers/hwmon/ad7314.c
@@ -22,11 +22,13 @@
*/
#define AD7314_TEMP_MASK 0x7FE0
#define AD7314_TEMP_SHIFT 5
+#define AD7314_LEADING_ZEROS_MASK BIT(15)
/*
* ADT7301 and ADT7302 temperature masks
*/
#define ADT7301_TEMP_MASK 0x3FFF
+#define ADT7301_LEADING_ZEROS_MASK (BIT(15) | BIT(14))
enum ad7314_variant {
adt7301,
@@ -65,12 +67,20 @@ static ssize_t ad7314_temperature_show(struct device *dev,
return ret;
switch (spi_get_device_id(chip->spi_dev)->driver_data) {
case ad7314:
+ if (ret & AD7314_LEADING_ZEROS_MASK) {
+ /* Invalid read-out, leading zero part is missing */
+ return -EIO;
+ }
data = (ret & AD7314_TEMP_MASK) >> AD7314_TEMP_SHIFT;
data = sign_extend32(data, 9);
return sprintf(buf, "%d\n", 250 * data);
case adt7301:
case adt7302:
+ if (ret & ADT7301_LEADING_ZEROS_MASK) {
+ /* Invalid read-out, leading zero part is missing */
+ return -EIO;
+ }
/*
* Documented as a 13 bit twos complement register
* with a sign bit - which is a 14 bit 2's complement
diff --git a/drivers/hwmon/ad7414.c b/drivers/hwmon/ad7414.c
index 7f1bef59046f..f0b17e59827f 100644
--- a/drivers/hwmon/ad7414.c
+++ b/drivers/hwmon/ad7414.c
@@ -205,7 +205,7 @@ static int ad7414_probe(struct i2c_client *client)
}
static const struct i2c_device_id ad7414_id[] = {
- { "ad7414", 0 },
+ { "ad7414" },
{}
};
MODULE_DEVICE_TABLE(i2c, ad7414_id);
diff --git a/drivers/hwmon/ad7418.c b/drivers/hwmon/ad7418.c
index 4829f83ff52e..7a132accdf8a 100644
--- a/drivers/hwmon/ad7418.c
+++ b/drivers/hwmon/ad7418.c
@@ -230,8 +230,6 @@ static void ad7418_init_client(struct i2c_client *client)
}
}
-static const struct i2c_device_id ad7418_id[];
-
static int ad7418_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -252,10 +250,7 @@ static int ad7418_probe(struct i2c_client *client)
mutex_init(&data->lock);
data->client = client;
- if (dev->of_node)
- data->type = (uintptr_t)of_device_get_match_data(dev);
- else
- data->type = i2c_match_id(ad7418_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
switch (data->type) {
case ad7416:
diff --git a/drivers/hwmon/adc128d818.c b/drivers/hwmon/adc128d818.c
index 46e3c8c50765..5e805d4ee76a 100644
--- a/drivers/hwmon/adc128d818.c
+++ b/drivers/hwmon/adc128d818.c
@@ -58,7 +58,6 @@ static const u8 num_inputs[] = { 7, 8, 4, 6 };
struct adc128_data {
struct i2c_client *client;
- struct regulator *regulator;
int vref; /* Reference voltage in mV */
struct mutex update_lock;
u8 mode; /* Operation mode */
@@ -176,7 +175,7 @@ static ssize_t adc128_in_store(struct device *dev,
mutex_lock(&data->update_lock);
/* 10 mV LSB on limit registers */
- regval = clamp_val(DIV_ROUND_CLOSEST(val, 10), 0, 255);
+ regval = DIV_ROUND_CLOSEST(clamp_val(val, 0, 2550), 10);
data->in[index][nr] = regval << 4;
reg = index == 1 ? ADC128_REG_IN_MIN(nr) : ADC128_REG_IN_MAX(nr);
i2c_smbus_write_byte_data(data->client, reg, regval);
@@ -214,7 +213,7 @@ static ssize_t adc128_temp_store(struct device *dev,
return err;
mutex_lock(&data->update_lock);
- regval = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127);
+ regval = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
data->temp[index] = regval << 1;
i2c_smbus_write_byte_data(data->client,
index == 1 ? ADC128_REG_TEMP_MAX
@@ -389,7 +388,7 @@ static int adc128_detect(struct i2c_client *client, struct i2c_board_info *info)
return 0;
}
-static int adc128_init_client(struct adc128_data *data)
+static int adc128_init_client(struct adc128_data *data, bool external_vref)
{
struct i2c_client *client = data->client;
int err;
@@ -408,7 +407,7 @@ static int adc128_init_client(struct adc128_data *data)
regval |= data->mode << 1;
/* If external vref is selected, configure the chip to use it */
- if (data->regulator)
+ if (external_vref)
regval |= 0x01;
/* Write advanced configuration register */
@@ -430,9 +429,9 @@ static int adc128_init_client(struct adc128_data *data)
static int adc128_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
- struct regulator *regulator;
struct device *hwmon_dev;
struct adc128_data *data;
+ bool external_vref;
int err, vref;
data = devm_kzalloc(dev, sizeof(struct adc128_data), GFP_KERNEL);
@@ -440,20 +439,15 @@ static int adc128_probe(struct i2c_client *client)
return -ENOMEM;
/* vref is optional. If specified, is used as chip reference voltage */
- regulator = devm_regulator_get_optional(dev, "vref");
- if (!IS_ERR(regulator)) {
- data->regulator = regulator;
- err = regulator_enable(regulator);
- if (err < 0)
- return err;
- vref = regulator_get_voltage(regulator);
- if (vref < 0) {
- err = vref;
- goto error;
- }
- data->vref = DIV_ROUND_CLOSEST(vref, 1000);
- } else {
+ vref = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (vref == -ENODEV) {
+ external_vref = false;
data->vref = 2560; /* 2.56V, in mV */
+ } else if (vref < 0) {
+ return vref;
+ } else {
+ external_vref = true;
+ data->vref = DIV_ROUND_CLOSEST(vref, 1000);
}
/* Operation mode is optional. If unspecified, keep current mode */
@@ -461,13 +455,12 @@ static int adc128_probe(struct i2c_client *client)
if (data->mode > 3) {
dev_err(dev, "invalid operation mode %d\n",
data->mode);
- err = -EINVAL;
- goto error;
+ return -EINVAL;
}
} else {
err = i2c_smbus_read_byte_data(client, ADC128_REG_CONFIG_ADV);
if (err < 0)
- goto error;
+ return err;
data->mode = (err >> 1) & ADC128_REG_MASK;
}
@@ -476,35 +469,18 @@ static int adc128_probe(struct i2c_client *client)
mutex_init(&data->update_lock);
/* Initialize the chip */
- err = adc128_init_client(data);
+ err = adc128_init_client(data, external_vref);
if (err < 0)
- goto error;
+ return err;
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
data, adc128_groups);
- if (IS_ERR(hwmon_dev)) {
- err = PTR_ERR(hwmon_dev);
- goto error;
- }
-
- return 0;
-
-error:
- if (data->regulator)
- regulator_disable(data->regulator);
- return err;
-}
-
-static void adc128_remove(struct i2c_client *client)
-{
- struct adc128_data *data = i2c_get_clientdata(client);
- if (data->regulator)
- regulator_disable(data->regulator);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id adc128_id[] = {
- { "adc128d818", 0 },
+ { "adc128d818" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adc128_id);
@@ -522,7 +498,6 @@ static struct i2c_driver adc128_driver = {
.of_match_table = of_match_ptr(adc128_of_match),
},
.probe = adc128_probe,
- .remove = adc128_remove,
.id_table = adc128_id,
.detect = adc128_detect,
.address_list = normal_i2c,
diff --git a/drivers/hwmon/adm1021.c b/drivers/hwmon/adm1021.c
deleted file mode 100644
index 7c15398ebb37..000000000000
--- a/drivers/hwmon/adm1021.c
+++ /dev/null
@@ -1,505 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * adm1021.c - Part of lm_sensors, Linux kernel modules for hardware
- * monitoring
- * Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
- * Philip Edelbrock <phil@netroedge.com>
- */
-
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
-#include <linux/mutex.h>
-
-
-/* Addresses to scan */
-static const unsigned short normal_i2c[] = {
- 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END };
-
-enum chips {
- adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066 };
-
-/* adm1021 constants specified below */
-
-/* The adm1021 registers */
-/* Read-only */
-/* For nr in 0-1 */
-#define ADM1021_REG_TEMP(nr) (nr)
-#define ADM1021_REG_STATUS 0x02
-/* 0x41 = AD, 0x49 = TI, 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi */
-#define ADM1021_REG_MAN_ID 0xFE
-/* ADM1021 = 0x0X, ADM1023 = 0x3X */
-#define ADM1021_REG_DEV_ID 0xFF
-/* These use different addresses for reading/writing */
-#define ADM1021_REG_CONFIG_R 0x03
-#define ADM1021_REG_CONFIG_W 0x09
-#define ADM1021_REG_CONV_RATE_R 0x04
-#define ADM1021_REG_CONV_RATE_W 0x0A
-/* These are for the ADM1023's additional precision on the remote temp sensor */
-#define ADM1023_REG_REM_TEMP_PREC 0x10
-#define ADM1023_REG_REM_OFFSET 0x11
-#define ADM1023_REG_REM_OFFSET_PREC 0x12
-#define ADM1023_REG_REM_TOS_PREC 0x13
-#define ADM1023_REG_REM_THYST_PREC 0x14
-/* limits */
-/* For nr in 0-1 */
-#define ADM1021_REG_TOS_R(nr) (0x05 + 2 * (nr))
-#define ADM1021_REG_TOS_W(nr) (0x0B + 2 * (nr))
-#define ADM1021_REG_THYST_R(nr) (0x06 + 2 * (nr))
-#define ADM1021_REG_THYST_W(nr) (0x0C + 2 * (nr))
-/* write-only */
-#define ADM1021_REG_ONESHOT 0x0F
-
-/* Initial values */
-
-/*
- * Note: Even though I left the low and high limits named os and hyst,
- * they don't quite work like a thermostat the way the LM75 does. I.e.,
- * a lower temp than THYST actually triggers an alarm instead of
- * clearing it. Weird, ey? --Phil
- */
-
-/* Each client has this additional data */
-struct adm1021_data {
- struct i2c_client *client;
- enum chips type;
-
- const struct attribute_group *groups[3];
-
- struct mutex update_lock;
- bool valid; /* true if following fields are valid */
- char low_power; /* !=0 if device in low power mode */
- unsigned long last_updated; /* In jiffies */
-
- int temp_max[2]; /* Register values */
- int temp_min[2];
- int temp[2];
- u8 alarms;
- /* Special values for ADM1023 only */
- u8 remote_temp_offset;
- u8 remote_temp_offset_prec;
-};
-
-/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */
-static bool read_only;
-
-static struct adm1021_data *adm1021_update_device(struct device *dev)
-{
- struct adm1021_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
-
- mutex_lock(&data->update_lock);
-
- if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
- || !data->valid) {
- int i;
-
- dev_dbg(dev, "Starting adm1021 update\n");
-
- for (i = 0; i < 2; i++) {
- data->temp[i] = 1000 *
- (s8) i2c_smbus_read_byte_data(
- client, ADM1021_REG_TEMP(i));
- data->temp_max[i] = 1000 *
- (s8) i2c_smbus_read_byte_data(
- client, ADM1021_REG_TOS_R(i));
- if (data->type != lm84) {
- data->temp_min[i] = 1000 *
- (s8) i2c_smbus_read_byte_data(client,
- ADM1021_REG_THYST_R(i));
- }
- }
- data->alarms = i2c_smbus_read_byte_data(client,
- ADM1021_REG_STATUS) & 0x7c;
- if (data->type == adm1023) {
- /*
- * The ADM1023 provides 3 extra bits of precision for
- * the remote sensor in extra registers.
- */
- data->temp[1] += 125 * (i2c_smbus_read_byte_data(
- client, ADM1023_REG_REM_TEMP_PREC) >> 5);
- data->temp_max[1] += 125 * (i2c_smbus_read_byte_data(
- client, ADM1023_REG_REM_TOS_PREC) >> 5);
- data->temp_min[1] += 125 * (i2c_smbus_read_byte_data(
- client, ADM1023_REG_REM_THYST_PREC) >> 5);
- data->remote_temp_offset =
- i2c_smbus_read_byte_data(client,
- ADM1023_REG_REM_OFFSET);
- data->remote_temp_offset_prec =
- i2c_smbus_read_byte_data(client,
- ADM1023_REG_REM_OFFSET_PREC);
- }
- data->last_updated = jiffies;
- data->valid = true;
- }
-
- mutex_unlock(&data->update_lock);
-
- return data;
-}
-
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct adm1021_data *data = adm1021_update_device(dev);
-
- return sprintf(buf, "%d\n", data->temp[index]);
-}
-
-static ssize_t temp_max_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct adm1021_data *data = adm1021_update_device(dev);
-
- return sprintf(buf, "%d\n", data->temp_max[index]);
-}
-
-static ssize_t temp_min_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct adm1021_data *data = adm1021_update_device(dev);
-
- return sprintf(buf, "%d\n", data->temp_min[index]);
-}
-
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- int index = to_sensor_dev_attr(attr)->index;
- struct adm1021_data *data = adm1021_update_device(dev);
- return sprintf(buf, "%u\n", (data->alarms >> index) & 1);
-}
-
-static ssize_t alarms_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct adm1021_data *data = adm1021_update_device(dev);
- return sprintf(buf, "%u\n", data->alarms);
-}
-
-static ssize_t temp_max_store(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct adm1021_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long temp;
- int reg_val, err;
-
- err = kstrtol(buf, 10, &temp);
- if (err)
- return err;
- temp /= 1000;
-
- mutex_lock(&data->update_lock);
- reg_val = clamp_val(temp, -128, 127);
- data->temp_max[index] = reg_val * 1000;
- if (!read_only)
- i2c_smbus_write_byte_data(client, ADM1021_REG_TOS_W(index),
- reg_val);
- mutex_unlock(&data->update_lock);
-
- return count;
-}
-
-static ssize_t temp_min_store(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct adm1021_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long temp;
- int reg_val, err;
-
- err = kstrtol(buf, 10, &temp);
- if (err)
- return err;
- temp /= 1000;
-
- mutex_lock(&data->update_lock);
- reg_val = clamp_val(temp, -128, 127);
- data->temp_min[index] = reg_val * 1000;
- if (!read_only)
- i2c_smbus_write_byte_data(client, ADM1021_REG_THYST_W(index),
- reg_val);
- mutex_unlock(&data->update_lock);
-
- return count;
-}
-
-static ssize_t low_power_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct adm1021_data *data = adm1021_update_device(dev);
- return sprintf(buf, "%d\n", data->low_power);
-}
-
-static ssize_t low_power_store(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
-{
- struct adm1021_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- char low_power;
- unsigned long val;
- int err;
-
- err = kstrtoul(buf, 10, &val);
- if (err)
- return err;
- low_power = val != 0;
-
- mutex_lock(&data->update_lock);
- if (low_power != data->low_power) {
- int config = i2c_smbus_read_byte_data(
- client, ADM1021_REG_CONFIG_R);
- data->low_power = low_power;
- i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W,
- (config & 0xBF) | (low_power << 6));
- }
- mutex_unlock(&data->update_lock);
-
- return count;
-}
-
-
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp_max, 0);
-static SENSOR_DEVICE_ATTR_RW(temp1_min, temp_min, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp_max, 1);
-static SENSOR_DEVICE_ATTR_RW(temp2_min, temp_min, 1);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6);
-static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 5);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 4);
-static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, alarm, 3);
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 2);
-
-static DEVICE_ATTR_RO(alarms);
-static DEVICE_ATTR_RW(low_power);
-
-static struct attribute *adm1021_attributes[] = {
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &dev_attr_alarms.attr,
- &dev_attr_low_power.attr,
- NULL
-};
-
-static const struct attribute_group adm1021_group = {
- .attrs = adm1021_attributes,
-};
-
-static struct attribute *adm1021_min_attributes[] = {
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- NULL
-};
-
-static const struct attribute_group adm1021_min_group = {
- .attrs = adm1021_min_attributes,
-};
-
-/* Return 0 if detection is successful, -ENODEV otherwise */
-static int adm1021_detect(struct i2c_client *client,
- struct i2c_board_info *info)
-{
- struct i2c_adapter *adapter = client->adapter;
- const char *type_name;
- int reg, conv_rate, status, config, man_id, dev_id;
-
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
- pr_debug("detect failed, smbus byte data not supported!\n");
- return -ENODEV;
- }
-
- status = i2c_smbus_read_byte_data(client, ADM1021_REG_STATUS);
- conv_rate = i2c_smbus_read_byte_data(client,
- ADM1021_REG_CONV_RATE_R);
- config = i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R);
-
- /* Check unused bits */
- if ((status & 0x03) || (config & 0x3F) || (conv_rate & 0xF8)) {
- pr_debug("detect failed, chip not detected!\n");
- return -ENODEV;
- }
-
- /* Determine the chip type. */
- man_id = i2c_smbus_read_byte_data(client, ADM1021_REG_MAN_ID);
- dev_id = i2c_smbus_read_byte_data(client, ADM1021_REG_DEV_ID);
-
- if (man_id < 0 || dev_id < 0)
- return -ENODEV;
-
- if (man_id == 0x4d && dev_id == 0x01) {
- /*
- * dev_id 0x01 matches MAX6680, MAX6695, MAX6696, and possibly
- * others. Read register which is unsupported on MAX1617 but
- * exists on all those chips and compare with the dev_id
- * register. If it matches, it may be a MAX1617A.
- */
- reg = i2c_smbus_read_byte_data(client,
- ADM1023_REG_REM_TEMP_PREC);
- if (reg != dev_id)
- return -ENODEV;
- type_name = "max1617a";
- } else if (man_id == 0x41) {
- if ((dev_id & 0xF0) == 0x30)
- type_name = "adm1023";
- else if ((dev_id & 0xF0) == 0x00)
- type_name = "adm1021";
- else
- return -ENODEV;
- } else if (man_id == 0x49)
- type_name = "thmc10";
- else if (man_id == 0x23)
- type_name = "gl523sm";
- else if (man_id == 0x54)
- type_name = "mc1066";
- else {
- int lte, rte, lhi, rhi, llo, rlo;
-
- /* extra checks for LM84 and MAX1617 to avoid misdetections */
-
- llo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(0));
- rlo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(1));
-
- /* fail if any of the additional register reads failed */
- if (llo < 0 || rlo < 0)
- return -ENODEV;
-
- lte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(0));
- rte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(1));
- lhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(0));
- rhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(1));
-
- /*
- * Fail for negative temperatures and negative high limits.
- * This check also catches read errors on the tested registers.
- */
- if ((s8)lte < 0 || (s8)rte < 0 || (s8)lhi < 0 || (s8)rhi < 0)
- return -ENODEV;
-
- /* fail if all registers hold the same value */
- if (lte == rte && lte == lhi && lte == rhi && lte == llo
- && lte == rlo)
- return -ENODEV;
-
- /*
- * LM84 Mfr ID is in a different place,
- * and it has more unused bits. Registers at 0xfe and 0xff
- * are undefined and return the most recently read value,
- * here the value of the configuration register.
- */
- if (conv_rate == 0x00
- && man_id == config && dev_id == config
- && (config & 0x7F) == 0x00
- && (status & 0xAB) == 0x00) {
- type_name = "lm84";
- } else {
- if ((config & 0x3f) || (status & 0x03))
- return -ENODEV;
- /* fail if low limits are larger than high limits */
- if ((s8)llo > lhi || (s8)rlo > rhi)
- return -ENODEV;
- type_name = "max1617";
- }
- }
-
- pr_debug("Detected chip %s at adapter %d, address 0x%02x.\n",
- type_name, i2c_adapter_id(adapter), client->addr);
- strscpy(info->type, type_name, I2C_NAME_SIZE);
-
- return 0;
-}
-
-static void adm1021_init_client(struct i2c_client *client)
-{
- /* Enable ADC and disable suspend mode */
- i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W,
- i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R) & 0xBF);
- /* Set Conversion rate to 1/sec (this can be tinkered with) */
- i2c_smbus_write_byte_data(client, ADM1021_REG_CONV_RATE_W, 0x04);
-}
-
-static const struct i2c_device_id adm1021_id[];
-
-static int adm1021_probe(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- struct adm1021_data *data;
- struct device *hwmon_dev;
-
- data = devm_kzalloc(dev, sizeof(struct adm1021_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- data->client = client;
- data->type = i2c_match_id(adm1021_id, client)->driver_data;
- mutex_init(&data->update_lock);
-
- /* Initialize the ADM1021 chip */
- if (data->type != lm84 && !read_only)
- adm1021_init_client(client);
-
- data->groups[0] = &adm1021_group;
- if (data->type != lm84)
- data->groups[1] = &adm1021_min_group;
-
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data, data->groups);
-
- return PTR_ERR_OR_ZERO(hwmon_dev);
-}
-
-static const struct i2c_device_id adm1021_id[] = {
- { "adm1021", adm1021 },
- { "adm1023", adm1023 },
- { "max1617", max1617 },
- { "max1617a", max1617a },
- { "thmc10", thmc10 },
- { "lm84", lm84 },
- { "gl523sm", gl523sm },
- { "mc1066", mc1066 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, adm1021_id);
-
-static struct i2c_driver adm1021_driver = {
- .class = I2C_CLASS_HWMON,
- .driver = {
- .name = "adm1021",
- },
- .probe = adm1021_probe,
- .id_table = adm1021_id,
- .detect = adm1021_detect,
- .address_list = normal_i2c,
-};
-
-module_i2c_driver(adm1021_driver);
-
-MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "
- "Philip Edelbrock <phil@netroedge.com>");
-MODULE_DESCRIPTION("adm1021 driver");
-MODULE_LICENSE("GPL");
-
-module_param(read_only, bool, 0);
-MODULE_PARM_DESC(read_only, "Don't set any values, read only mode");
diff --git a/drivers/hwmon/adm1026.c b/drivers/hwmon/adm1026.c
index 581d8edf70ea..c38c932e5d2a 100644
--- a/drivers/hwmon/adm1026.c
+++ b/drivers/hwmon/adm1026.c
@@ -197,8 +197,16 @@ static int adm1026_scaling[] = { /* .001 Volts */
#define FAN_TO_REG(val, div) ((val) <= 0 ? 0xff : \
clamp_val(1350000 / ((val) * (div)), \
1, 254))
-#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : (val) == 0xff ? 0 : \
- 1350000 / ((val) * (div)))
+
+static int fan_from_reg(int val, int div)
+{
+ if (val == 0)
+ return -1;
+ if (val == 0xff)
+ return 0;
+ return 1350000 / (val * div);
+}
+
#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val) >= 8 ? 3 : (val) >= 4 ? 2 : (val) >= 2 ? 1 : 0)
@@ -656,7 +664,7 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
int nr = sensor_attr->index;
struct adm1026_data *data = adm1026_update_device(dev);
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan[nr],
data->fan_div[nr]));
}
static ssize_t fan_min_show(struct device *dev, struct device_attribute *attr,
@@ -665,7 +673,7 @@ static ssize_t fan_min_show(struct device *dev, struct device_attribute *attr,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
int nr = sensor_attr->index;
struct adm1026_data *data = adm1026_update_device(dev);
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan_min[nr],
data->fan_div[nr]));
}
static ssize_t fan_min_store(struct device *dev,
@@ -1849,7 +1857,7 @@ static int adm1026_probe(struct i2c_client *client)
}
static const struct i2c_device_id adm1026_id[] = {
- { "adm1026", 0 },
+ { "adm1026" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1026_id);
diff --git a/drivers/hwmon/adm1029.c b/drivers/hwmon/adm1029.c
index 9a465f3f71c8..71eea8ae51b9 100644
--- a/drivers/hwmon/adm1029.c
+++ b/drivers/hwmon/adm1029.c
@@ -171,14 +171,17 @@ fan_show(struct device *dev, struct device_attribute *devattr, char *buf)
struct adm1029_data *data = adm1029_update_device(dev);
u16 val;
+ mutex_lock(&data->update_lock);
if (data->fan[attr->index] == 0 ||
(data->fan_div[attr->index] & 0xC0) == 0 ||
data->fan[attr->index] == 255) {
+ mutex_unlock(&data->update_lock);
return sprintf(buf, "0\n");
}
val = 1880 * 120 / DIV_FROM_REG(data->fan_div[attr->index])
/ data->fan[attr->index];
+ mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", val);
}
@@ -379,7 +382,7 @@ static int adm1029_probe(struct i2c_client *client)
}
static const struct i2c_device_id adm1029_id[] = {
- { "adm1029", 0 },
+ { "adm1029" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1029_id);
diff --git a/drivers/hwmon/adm1031.c b/drivers/hwmon/adm1031.c
index 88c7e0d62d08..343118532cdb 100644
--- a/drivers/hwmon/adm1031.c
+++ b/drivers/hwmon/adm1031.c
@@ -1021,8 +1021,6 @@ static void adm1031_init_client(struct i2c_client *client)
data->update_interval = update_intervals[i];
}
-static const struct i2c_device_id adm1031_id[];
-
static int adm1031_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -1035,7 +1033,7 @@ static int adm1031_probe(struct i2c_client *client)
i2c_set_clientdata(client, data);
data->client = client;
- data->chip_type = i2c_match_id(adm1031_id, client)->driver_data;
+ data->chip_type = (uintptr_t)i2c_get_match_data(client);
mutex_init(&data->update_lock);
if (data->chip_type == adm1030)
diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c
index 60a893f27159..8b2c965480e3 100644
--- a/drivers/hwmon/adm1177.c
+++ b/drivers/hwmon/adm1177.c
@@ -238,7 +238,7 @@ static int adm1177_probe(struct i2c_client *client)
}
static const struct i2c_device_id adm1177_id[] = {
- {"adm1177", 0},
+ {"adm1177"},
{}
};
MODULE_DEVICE_TABLE(i2c, adm1177_id);
@@ -250,7 +250,6 @@ static const struct of_device_id adm1177_dt_ids[] = {
MODULE_DEVICE_TABLE(of, adm1177_dt_ids);
static struct i2c_driver adm1177_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "adm1177",
.of_match_table = adm1177_dt_ids,
diff --git a/drivers/hwmon/adm9240.c b/drivers/hwmon/adm9240.c
index 6dfbeb6acf00..86f6044b5bd0 100644
--- a/drivers/hwmon/adm9240.c
+++ b/drivers/hwmon/adm9240.c
@@ -37,7 +37,6 @@
#include <linux/hwmon.h>
#include <linux/hwmon-vid.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/regmap.h>
/* Addresses to scan */
@@ -125,7 +124,6 @@ static inline unsigned int AOUT_FROM_REG(u8 reg)
struct adm9240_data {
struct device *dev;
struct regmap *regmap;
- struct mutex update_lock;
u8 fan_div[2]; /* rw fan1_div, read-only accessor */
u8 vrm; /* -- vrm set on startup, no accessor */
@@ -170,8 +168,6 @@ static int adm9240_fan_min_write(struct adm9240_data *data, int channel, long va
u8 fan_min;
int err;
- mutex_lock(&data->update_lock);
-
if (!val) {
fan_min = 255;
new_div = data->fan_div[channel];
@@ -206,8 +202,6 @@ static int adm9240_fan_min_write(struct adm9240_data *data, int channel, long va
}
err = regmap_write(data->regmap, ADM9240_REG_FAN_MIN(channel), fan_min);
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -501,23 +495,17 @@ static int adm9240_fan_read(struct device *dev, u32 attr, int channel, long *val
switch (attr) {
case hwmon_fan_input:
- mutex_lock(&data->update_lock);
err = regmap_read(data->regmap, ADM9240_REG_FAN(channel), &regval);
- if (err < 0) {
- mutex_unlock(&data->update_lock);
+ if (err < 0)
return err;
- }
if (regval == 255 && data->fan_div[channel] < 3) {
/* adjust fan clock divider on overflow */
err = adm9240_write_fan_div(data, channel,
++data->fan_div[channel]);
- if (err) {
- mutex_unlock(&data->update_lock);
+ if (err)
return err;
- }
}
*val = FAN_FROM_REG(regval, BIT(data->fan_div[channel]));
- mutex_unlock(&data->update_lock);
break;
case hwmon_fan_div:
*val = BIT(data->fan_div[channel]);
@@ -791,7 +779,6 @@ static int adm9240_probe(struct i2c_client *client)
return -ENOMEM;
data->dev = dev;
- mutex_init(&data->update_lock);
data->regmap = devm_regmap_init_i2c(client, &adm9240_regmap_config);
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
diff --git a/drivers/hwmon/ads7828.c b/drivers/hwmon/ads7828.c
index 809e830f52a6..436637264056 100644
--- a/drivers/hwmon/ads7828.c
+++ b/drivers/hwmon/ads7828.c
@@ -99,8 +99,6 @@ static const struct regmap_config ads2830_regmap_config = {
.val_bits = 8,
};
-static const struct i2c_device_id ads7828_device_ids[];
-
static int ads7828_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -138,10 +136,7 @@ static int ads7828_probe(struct i2c_client *client)
}
}
- if (client->dev.of_node)
- chip = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- chip = i2c_match_id(ads7828_device_ids, client)->driver_data;
+ chip = (uintptr_t)i2c_get_match_data(client);
/* Bound Vref with min/max values */
vref_mv = clamp_val(vref_mv, ADS7828_EXT_VREF_MV_MIN,
diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c
index 067865f4887a..6a834a37bc65 100644
--- a/drivers/hwmon/adt7310.c
+++ b/drivers/hwmon/adt7310.c
@@ -10,7 +10,7 @@
#include <linux/init.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "adt7x10.h"
@@ -124,7 +124,7 @@ static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config adt7310_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7310_regmap_is_volatile,
.reg_read = adt7310_reg_read,
.reg_write = adt7310_reg_write,
diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c
index 952506779336..73b196a78f3a 100644
--- a/drivers/hwmon/adt7410.c
+++ b/drivers/hwmon/adt7410.c
@@ -7,6 +7,7 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
@@ -69,7 +70,7 @@ static const struct regmap_config adt7410_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = ADT7X10_ID,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7410_regmap_is_volatile,
.reg_read = adt7410_reg_read,
.reg_write = adt7410_reg_write,
@@ -88,21 +89,29 @@ static int adt7410_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id adt7410_ids[] = {
- { "adt7410", 0 },
- { "adt7420", 0 },
+ { "adt7410" },
+ { "adt7420" },
+ { "adt7422" },
{}
};
MODULE_DEVICE_TABLE(i2c, adt7410_ids);
+static const struct of_device_id adt7410_of_match[] = {
+ { .compatible = "adi,adt7410" },
+ { .compatible = "adi,adt7420" },
+ { .compatible = "adi,adt7422" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adt7410_of_match);
+
static struct i2c_driver adt7410_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "adt7410",
.pm = pm_sleep_ptr(&adt7x10_dev_pm_ops),
+ .of_match_table = adt7410_of_match,
},
.probe = adt7410_i2c_probe,
.id_table = adt7410_ids,
- .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b),
};
module_i2c_driver(adt7410_driver);
diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c
index 45fe4e8aae4e..b9991a69e6c6 100644
--- a/drivers/hwmon/adt7411.c
+++ b/drivers/hwmon/adt7411.c
@@ -11,7 +11,6 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
@@ -99,8 +98,6 @@ static const u8 adt7411_in_alarm_bits[] = {
};
struct adt7411_data {
- struct mutex device_lock; /* for "atomic" device accesses */
- struct mutex update_lock;
unsigned long next_update;
long vref_cached;
struct i2c_client *client;
@@ -110,55 +107,41 @@ struct adt7411_data {
/*
* When reading a register containing (up to 4) lsb, all associated
* msb-registers get locked by the hardware. After _one_ of those msb is read,
- * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb
- * is protected here with a mutex, too.
+ * _all_ are unlocked.
*/
static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg,
- u8 msb_reg, u8 lsb_shift)
+ u8 msb_reg, u8 lsb_shift)
{
- struct adt7411_data *data = i2c_get_clientdata(client);
int val, tmp;
- mutex_lock(&data->device_lock);
-
val = i2c_smbus_read_byte_data(client, lsb_reg);
if (val < 0)
- goto exit_unlock;
+ return val;
tmp = (val >> lsb_shift) & 3;
val = i2c_smbus_read_byte_data(client, msb_reg);
+ if (val < 0)
+ return val;
- if (val >= 0)
- val = (val << 2) | tmp;
-
- exit_unlock:
- mutex_unlock(&data->device_lock);
-
+ val = (val << 2) | tmp;
return val;
}
static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit,
- bool flag)
+ bool flag)
{
- struct adt7411_data *data = i2c_get_clientdata(client);
int ret, val;
- mutex_lock(&data->device_lock);
-
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
- goto exit_unlock;
+ return ret;
if (flag)
val = ret | bit;
else
val = ret & ~bit;
- ret = i2c_smbus_write_byte_data(client, reg, val);
-
- exit_unlock:
- mutex_unlock(&data->device_lock);
- return ret;
+ return i2c_smbus_write_byte_data(client, reg, val);
}
static ssize_t adt7411_show_bit(struct device *dev,
@@ -186,12 +169,11 @@ static ssize_t adt7411_set_bit(struct device *dev,
if (ret || flag > 1)
return -EINVAL;
+ hwmon_lock(dev);
ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag);
-
/* force update */
- mutex_lock(&data->update_lock);
data->next_update = jiffies;
- mutex_unlock(&data->update_lock);
+ hwmon_unlock(dev);
return ret < 0 ? ret : count;
}
@@ -294,10 +276,9 @@ static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel,
int reg, lsb_reg, lsb_shift;
int nr = channel - 1;
- mutex_lock(&data->update_lock);
ret = adt7411_update_vref(dev);
if (ret < 0)
- goto exit_unlock;
+ return ret;
switch (attr) {
case hwmon_in_input:
@@ -307,7 +288,7 @@ static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel,
ADT7411_REG_EXT_TEMP_AIN1_MSB + nr,
lsb_shift);
if (ret < 0)
- goto exit_unlock;
+ return ret;
*val = ret * data->vref_cached / 1024;
ret = 0;
break;
@@ -318,7 +299,7 @@ static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel,
: ADT7411_REG_IN_HIGH(channel);
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
- goto exit_unlock;
+ return ret;
*val = ret * data->vref_cached / 256;
ret = 0;
break;
@@ -329,8 +310,6 @@ static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel,
ret = -EOPNOTSUPP;
break;
}
- exit_unlock:
- mutex_unlock(&data->update_lock);
return ret;
}
@@ -457,10 +436,9 @@ static int adt7411_write_in_chan(struct device *dev, u32 attr, int channel,
struct i2c_client *client = data->client;
int ret, reg;
- mutex_lock(&data->update_lock);
ret = adt7411_update_vref(dev);
if (ret < 0)
- goto exit_unlock;
+ return ret;
val = clamp_val(val, 0, 255 * data->vref_cached / 256);
val = DIV_ROUND_CLOSEST(val * 256, data->vref_cached);
@@ -472,13 +450,10 @@ static int adt7411_write_in_chan(struct device *dev, u32 attr, int channel,
reg = ADT7411_REG_IN_HIGH(channel);
break;
default:
- ret = -EOPNOTSUPP;
- goto exit_unlock;
+ return -EOPNOTSUPP;
}
ret = i2c_smbus_write_byte_data(client, reg, val);
- exit_unlock:
- mutex_unlock(&data->update_lock);
return ret;
}
@@ -679,8 +654,6 @@ static int adt7411_probe(struct i2c_client *client)
i2c_set_clientdata(client, data);
data->client = client;
- mutex_init(&data->device_lock);
- mutex_init(&data->update_lock);
ret = adt7411_init_device(data);
if (ret < 0)
@@ -697,7 +670,7 @@ static int adt7411_probe(struct i2c_client *client)
}
static const struct i2c_device_id adt7411_id[] = {
- { "adt7411", 0 },
+ { "adt7411" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adt7411_id);
diff --git a/drivers/hwmon/adt7462.c b/drivers/hwmon/adt7462.c
index 429566c4245d..174dfee47f7a 100644
--- a/drivers/hwmon/adt7462.c
+++ b/drivers/hwmon/adt7462.c
@@ -1809,7 +1809,7 @@ static int adt7462_probe(struct i2c_client *client)
}
static const struct i2c_device_id adt7462_id[] = {
- { "adt7462", 0 },
+ { "adt7462" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adt7462_id);
diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c
index c4b3a4a18670..dbee6926fa05 100644
--- a/drivers/hwmon/adt7470.c
+++ b/drivers/hwmon/adt7470.c
@@ -728,30 +728,22 @@ static const int adt7470_freq_map[] = {
static int pwm1_freq_get(struct device *dev)
{
struct adt7470_data *data = dev_get_drvdata(dev);
- unsigned int cfg_reg_1, cfg_reg_2;
+ unsigned int regs[2] = {ADT7470_REG_CFG, ADT7470_REG_CFG_2};
+ u8 cfg_reg[2];
int index;
int err;
- mutex_lock(&data->lock);
- err = regmap_read(data->regmap, ADT7470_REG_CFG, &cfg_reg_1);
- if (err < 0)
- goto out;
- err = regmap_read(data->regmap, ADT7470_REG_CFG_2, &cfg_reg_2);
- if (err < 0)
- goto out;
- mutex_unlock(&data->lock);
+ err = regmap_multi_reg_read(data->regmap, regs, cfg_reg, 2);
+ if (err)
+ return err;
- index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT;
- if (!(cfg_reg_1 & ADT7470_CFG_LF))
+ index = (cfg_reg[1] & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT;
+ if (!(cfg_reg[0] & ADT7470_CFG_LF))
index += 8;
if (index >= ARRAY_SIZE(adt7470_freq_map))
index = ARRAY_SIZE(adt7470_freq_map) - 1;
return adt7470_freq_map[index];
-
-out:
- mutex_unlock(&data->lock);
- return err;
}
static int adt7470_pwm_read(struct device *dev, u32 attr, int channel, long *val)
@@ -1304,7 +1296,7 @@ static void adt7470_remove(struct i2c_client *client)
}
static const struct i2c_device_id adt7470_id[] = {
- { "adt7470", 0 },
+ { "adt7470" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adt7470_id);
diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c
index 03acadc3a6cb..8cefa14e1633 100644
--- a/drivers/hwmon/adt7475.c
+++ b/drivers/hwmon/adt7475.c
@@ -21,28 +21,31 @@
#include <linux/of.h>
#include <linux/util_macros.h>
-/* Indexes for the sysfs hooks */
-
-#define INPUT 0
-#define MIN 1
-#define MAX 2
-#define CONTROL 3
-#define OFFSET 3
-#define AUTOMIN 4
-#define THERM 5
-#define HYSTERSIS 6
+#include <dt-bindings/pwm/pwm.h>
+/* Indexes for the sysfs hooks */
+enum adt_sysfs_id {
+ INPUT = 0,
+ MIN = 1,
+ MAX = 2,
+ CONTROL = 3,
+ OFFSET = 3, // Dup
+ AUTOMIN = 4,
+ THERM = 5,
+ HYSTERSIS = 6,
/*
* These are unique identifiers for the sysfs functions - unlike the
* numbers above, these are not also indexes into an array
*/
+ ALARM = 9,
+ FAULT = 10,
+};
-#define ALARM 9
-#define FAULT 10
/* 7475 Common Registers */
#define REG_DEVREV2 0x12 /* ADT7490 only */
+#define REG_IMON 0x1D /* ADT7490 only */
#define REG_VTT 0x1E /* ADT7490 only */
#define REG_EXTEND3 0x1F /* ADT7490 only */
@@ -103,6 +106,9 @@
#define REG_VTT_MIN 0x84 /* ADT7490 only */
#define REG_VTT_MAX 0x86 /* ADT7490 only */
+#define REG_IMON_MIN 0x85 /* ADT7490 only */
+#define REG_IMON_MAX 0x87 /* ADT7490 only */
+
#define VID_VIDSEL 0x80 /* ADT7476 only */
#define CONFIG2_ATTN 0x20
@@ -123,7 +129,7 @@
/* ADT7475 Settings */
-#define ADT7475_VOLTAGE_COUNT 5 /* Not counting Vtt */
+#define ADT7475_VOLTAGE_COUNT 5 /* Not counting Vtt or Imon */
#define ADT7475_TEMP_COUNT 3
#define ADT7475_TACH_COUNT 4
#define ADT7475_PWM_COUNT 3
@@ -204,7 +210,7 @@ struct adt7475_data {
u8 has_fan4:1;
u8 has_vid:1;
u32 alarms;
- u16 voltage[3][6];
+ u16 voltage[3][7];
u16 temp[7][3];
u16 tach[2][4];
u8 pwm[4][3];
@@ -215,7 +221,7 @@ struct adt7475_data {
u8 vid;
u8 vrm;
- const struct attribute_group *groups[9];
+ const struct attribute_group *groups[10];
};
static struct i2c_driver adt7475_driver;
@@ -273,13 +279,14 @@ static inline u16 rpm2tach(unsigned long rpm)
}
/* Scaling factors for voltage inputs, taken from the ADT7490 datasheet */
-static const int adt7473_in_scaling[ADT7475_VOLTAGE_COUNT + 1][2] = {
+static const int adt7473_in_scaling[ADT7475_VOLTAGE_COUNT + 2][2] = {
{ 45, 94 }, /* +2.5V */
{ 175, 525 }, /* Vccp */
{ 68, 71 }, /* Vcc */
{ 93, 47 }, /* +5V */
{ 120, 20 }, /* +12V */
{ 45, 45 }, /* Vtt */
+ { 45, 45 }, /* Imon */
};
static inline int reg2volt(int channel, u16 reg, u8 bypass_attn)
@@ -369,11 +376,16 @@ static ssize_t voltage_store(struct device *dev,
reg = VOLTAGE_MIN_REG(sattr->index);
else
reg = VOLTAGE_MAX_REG(sattr->index);
- } else {
+ } else if (sattr->index == 5) {
if (sattr->nr == MIN)
reg = REG_VTT_MIN;
else
reg = REG_VTT_MAX;
+ } else {
+ if (sattr->nr == MIN)
+ reg = REG_IMON_MIN;
+ else
+ reg = REG_IMON_MAX;
}
i2c_smbus_write_byte_data(client, reg,
@@ -1104,6 +1116,10 @@ static SENSOR_DEVICE_ATTR_2_RO(in5_input, voltage, INPUT, 5);
static SENSOR_DEVICE_ATTR_2_RW(in5_max, voltage, MAX, 5);
static SENSOR_DEVICE_ATTR_2_RW(in5_min, voltage, MIN, 5);
static SENSOR_DEVICE_ATTR_2_RO(in5_alarm, voltage, ALARM, 31);
+static SENSOR_DEVICE_ATTR_2_RO(in6_input, voltage, INPUT, 6);
+static SENSOR_DEVICE_ATTR_2_RW(in6_max, voltage, MAX, 6);
+static SENSOR_DEVICE_ATTR_2_RW(in6_min, voltage, MIN, 6);
+static SENSOR_DEVICE_ATTR_2_RO(in6_alarm, voltage, ALARM, 30);
static SENSOR_DEVICE_ATTR_2_RO(temp1_input, temp, INPUT, 0);
static SENSOR_DEVICE_ATTR_2_RO(temp1_alarm, temp, ALARM, 0);
static SENSOR_DEVICE_ATTR_2_RO(temp1_fault, temp, FAULT, 0);
@@ -1294,6 +1310,14 @@ static struct attribute *in5_attrs[] = {
NULL
};
+static struct attribute *in6_attrs[] = {
+ &sensor_dev_attr_in6_input.dev_attr.attr,
+ &sensor_dev_attr_in6_max.dev_attr.attr,
+ &sensor_dev_attr_in6_min.dev_attr.attr,
+ &sensor_dev_attr_in6_alarm.dev_attr.attr,
+ NULL
+};
+
static struct attribute *vid_attrs[] = {
&dev_attr_cpu0_vid.attr,
&dev_attr_vrm.attr,
@@ -1307,6 +1331,7 @@ static const struct attribute_group in0_attr_group = { .attrs = in0_attrs };
static const struct attribute_group in3_attr_group = { .attrs = in3_attrs };
static const struct attribute_group in4_attr_group = { .attrs = in4_attrs };
static const struct attribute_group in5_attr_group = { .attrs = in5_attrs };
+static const struct attribute_group in6_attr_group = { .attrs = in6_attrs };
static const struct attribute_group vid_attr_group = { .attrs = vid_attrs };
static int adt7475_detect(struct i2c_client *client,
@@ -1389,6 +1414,18 @@ static int adt7475_update_limits(struct i2c_client *client)
data->voltage[MAX][5] = ret << 2;
}
+ if (data->has_voltage & (1 << 6)) {
+ ret = adt7475_read(REG_IMON_MIN);
+ if (ret < 0)
+ return ret;
+ data->voltage[MIN][6] = ret << 2;
+
+ ret = adt7475_read(REG_IMON_MAX);
+ if (ret < 0)
+ return ret;
+ data->voltage[MAX][6] = ret << 2;
+ }
+
for (i = 0; i < ADT7475_TEMP_COUNT; i++) {
/* Adjust values so they match the input precision */
ret = adt7475_read(TEMP_MIN_REG(i));
@@ -1627,6 +1664,143 @@ static int adt7475_set_pwm_polarity(struct i2c_client *client)
return 0;
}
+struct adt7475_pwm_config {
+ int index;
+ int freq;
+ int flags;
+ int duty;
+};
+
+static int _adt7475_pwm_properties_parse_args(u32 args[4], struct adt7475_pwm_config *cfg)
+{
+ int freq_hz;
+ int duty;
+
+ if (args[1] == 0)
+ return -EINVAL;
+
+ freq_hz = 1000000000UL / args[1];
+ if (args[3] >= args[1])
+ duty = 255;
+ else
+ duty = div_u64(255ULL * args[3], args[1]);
+
+ cfg->index = args[0];
+ cfg->freq = find_closest(freq_hz, pwmfreq_table, ARRAY_SIZE(pwmfreq_table));
+ cfg->flags = args[2];
+ cfg->duty = duty;
+
+ return 0;
+}
+
+static int adt7475_pwm_properties_parse_reference_args(struct fwnode_handle *fwnode,
+ struct adt7475_pwm_config *cfg)
+{
+ int ret, i;
+ struct fwnode_reference_args rargs = {};
+ u32 args[4] = {};
+
+ ret = fwnode_property_get_reference_args(fwnode, "pwms", "#pwm-cells", 0, 0, &rargs);
+ if (ret)
+ return ret;
+
+ if (rargs.nargs != 3 && rargs.nargs != 4) {
+ fwnode_handle_put(rargs.fwnode);
+ return -EINVAL;
+ }
+
+ /* Let duty_cycle default to period */
+ args[3] = rargs.args[1];
+
+ for (i = 0; i < rargs.nargs; i++)
+ args[i] = rargs.args[i];
+
+ ret = _adt7475_pwm_properties_parse_args(args, cfg);
+
+ fwnode_handle_put(rargs.fwnode);
+
+ return ret;
+}
+
+static int adt7475_pwm_properties_parse_args(struct fwnode_handle *fwnode,
+ struct adt7475_pwm_config *cfg)
+{
+ int ret;
+ u32 args[4] = {};
+ size_t n_vals = fwnode_property_count_u32(fwnode, "pwms");
+
+ if (n_vals != 3 && n_vals != 4)
+ return -EOVERFLOW;
+
+ ret = fwnode_property_read_u32_array(fwnode, "pwms", args, n_vals);
+ if (ret)
+ return ret;
+
+ /*
+ * If there are no item to define the duty_cycle, default it to the
+ * period.
+ */
+ if (n_vals == 3)
+ args[3] = args[1];
+
+ return _adt7475_pwm_properties_parse_args(args, cfg);
+}
+
+static int adt7475_fan_pwm_config(struct i2c_client *client)
+{
+ struct adt7475_data *data = i2c_get_clientdata(client);
+ struct adt7475_pwm_config cfg = {};
+ int ret;
+
+ device_for_each_child_node_scoped(&client->dev, child) {
+ if (!fwnode_property_present(child, "pwms"))
+ continue;
+
+ if (is_of_node(child))
+ ret = adt7475_pwm_properties_parse_reference_args(child, &cfg);
+ else
+ ret = adt7475_pwm_properties_parse_args(child, &cfg);
+
+ if (cfg.index >= ADT7475_PWM_COUNT)
+ return -EINVAL;
+
+ ret = adt7475_read(PWM_CONFIG_REG(cfg.index));
+ if (ret < 0)
+ return ret;
+ data->pwm[CONTROL][cfg.index] = ret;
+ if (cfg.flags & PWM_POLARITY_INVERTED)
+ data->pwm[CONTROL][cfg.index] |= BIT(4);
+ else
+ data->pwm[CONTROL][cfg.index] &= ~BIT(4);
+
+ /* Force to manual mode so PWM values take effect */
+ data->pwm[CONTROL][cfg.index] &= ~0xE0;
+ data->pwm[CONTROL][cfg.index] |= 0x07 << 5;
+
+ ret = i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(cfg.index),
+ data->pwm[CONTROL][cfg.index]);
+ if (ret)
+ return ret;
+
+ data->pwm[INPUT][cfg.index] = cfg.duty;
+ ret = i2c_smbus_write_byte_data(client, PWM_REG(cfg.index),
+ data->pwm[INPUT][cfg.index]);
+ if (ret)
+ return ret;
+
+ data->range[cfg.index] = adt7475_read(TEMP_TRANGE_REG(cfg.index));
+ data->range[cfg.index] &= ~0xf;
+ data->range[cfg.index] |= cfg.freq;
+
+ ret = i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(cfg.index),
+ data->range[cfg.index]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static int adt7475_probe(struct i2c_client *client)
{
enum chips chip;
@@ -1641,7 +1815,6 @@ static int adt7475_probe(struct i2c_client *client)
struct device *hwmon_dev;
int i, ret = 0, revision, group_num = 0;
u8 config3;
- const struct i2c_device_id *id = i2c_match_id(adt7475_id, client);
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
@@ -1651,10 +1824,7 @@ static int adt7475_probe(struct i2c_client *client)
data->client = client;
i2c_set_clientdata(client, data);
- if (client->dev.of_node)
- chip = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- chip = id->driver_data;
+ chip = (uintptr_t)i2c_get_match_data(client);
/* Initialize device-specific values */
switch (chip) {
@@ -1663,7 +1833,7 @@ static int adt7475_probe(struct i2c_client *client)
revision = adt7475_read(REG_DEVID2) & 0x07;
break;
case adt7490:
- data->has_voltage = 0x3e; /* in1 to in5 */
+ data->has_voltage = 0x7e; /* in1 to in6 */
revision = adt7475_read(REG_DEVID2) & 0x03;
if (revision == 0x03)
revision += adt7475_read(REG_DEVREV2);
@@ -1682,7 +1852,7 @@ static int adt7475_probe(struct i2c_client *client)
if (!(config3 & CONFIG3_SMBALERT))
data->has_pwm2 = 1;
/* Meaning of this bit is inverted for the ADT7473-1 */
- if (id->driver_data == adt7473 && revision >= 1)
+ if (chip == adt7473 && revision >= 1)
data->has_pwm2 = !data->has_pwm2;
data->config4 = adt7475_read(REG_CONFIG4);
@@ -1695,12 +1865,12 @@ static int adt7475_probe(struct i2c_client *client)
* because 2 different pins (TACH4 and +2.5 Vin) can be used for
* this function
*/
- if (id->driver_data == adt7490) {
+ if (chip == adt7490) {
if ((data->config4 & CONFIG4_PINFUNC) == 0x1 &&
!(config3 & CONFIG3_THERM))
data->has_fan4 = 1;
}
- if (id->driver_data == adt7476 || id->driver_data == adt7490) {
+ if (chip == adt7476 || chip == adt7490) {
if (!(config3 & CONFIG3_THERM) ||
(data->config4 & CONFIG4_PINFUNC) == 0x1)
data->has_voltage |= (1 << 0); /* in0 */
@@ -1710,7 +1880,7 @@ static int adt7475_probe(struct i2c_client *client)
* On the ADT7476, the +12V input pin may instead be used as VID5,
* and VID pins may alternatively be used as GPIO
*/
- if (id->driver_data == adt7476) {
+ if (chip == adt7476) {
u8 vid = adt7475_read(REG_VID);
if (!(vid & VID_VIDSEL))
data->has_voltage |= (1 << 4); /* in4 */
@@ -1743,6 +1913,10 @@ static int adt7475_probe(struct i2c_client *client)
if (ret && ret != -EINVAL)
dev_warn(&client->dev, "Error configuring pwm polarity\n");
+ ret = adt7475_fan_pwm_config(client);
+ if (ret)
+ dev_warn(&client->dev, "Error %d configuring fan/pwm\n", ret);
+
/* Start monitoring */
switch (chip) {
case adt7475:
@@ -1775,6 +1949,9 @@ static int adt7475_probe(struct i2c_client *client)
if (data->has_voltage & (1 << 5)) {
data->groups[group_num++] = &in5_attr_group;
}
+ if (data->has_voltage & (1 << 6)) {
+ data->groups[group_num++] = &in6_attr_group;
+ }
if (data->has_vid) {
data->vrm = vid_which_vrm();
data->groups[group_num] = &vid_attr_group;
@@ -1791,7 +1968,7 @@ static int adt7475_probe(struct i2c_client *client)
}
dev_info(&client->dev, "%s device, revision %d\n",
- names[id->driver_data], revision);
+ names[chip], revision);
if ((data->has_voltage & 0x11) || data->has_fan4 || data->has_pwm2)
dev_info(&client->dev, "Optional features:%s%s%s%s%s\n",
(data->has_voltage & (1 << 0)) ? " in0" : "",
@@ -1862,7 +2039,7 @@ static void adt7475_read_pwm(struct i2c_client *client, int index)
data->pwm[CONTROL][index] &= ~0xE0;
data->pwm[CONTROL][index] |= (7 << 5);
- i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
+ i2c_smbus_write_byte_data(client, PWM_REG(index),
data->pwm[INPUT][index]);
i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index),
@@ -1960,6 +2137,24 @@ static int adt7475_update_measure(struct device *dev)
((ext >> 4) & 3);
}
+ if (data->has_voltage & (1 << 6)) {
+ ret = adt7475_read(REG_STATUS4);
+ if (ret < 0)
+ return ret;
+ data->alarms |= ret << 24;
+
+ ret = adt7475_read(REG_EXTEND3);
+ if (ret < 0)
+ return ret;
+ ext = ret;
+
+ ret = adt7475_read(REG_IMON);
+ if (ret < 0)
+ return ret;
+ data->voltage[INPUT][6] = ret << 2 |
+ ((ext >> 6) & 3);
+ }
+
for (i = 0; i < ADT7475_TACH_COUNT; i++) {
if (i == 3 && !data->has_fan4)
continue;
diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c
index 6701920de17f..d003ee3ebf06 100644
--- a/drivers/hwmon/adt7x10.c
+++ b/drivers/hwmon/adt7x10.c
@@ -15,7 +15,6 @@
#include <linux/jiffies.h>
#include <linux/hwmon.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
@@ -55,7 +54,6 @@
/* Each client has this additional data */
struct adt7x10_data {
struct regmap *regmap;
- struct mutex update_lock;
u8 config;
u8 oldconfig;
bool valid; /* true if temperature valid */
@@ -137,17 +135,13 @@ static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val)
unsigned int regval;
int ret;
- mutex_lock(&data->update_lock);
if (index == adt7x10_temperature && !data->valid) {
/* wait for valid temperature */
ret = adt7x10_temp_ready(data->regmap);
- if (ret) {
- mutex_unlock(&data->update_lock);
+ if (ret)
return ret;
- }
data->valid = true;
}
- mutex_unlock(&data->update_lock);
ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &regval);
if (ret)
@@ -159,32 +153,21 @@ static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val)
static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp)
{
- int ret;
-
- mutex_lock(&data->update_lock);
- ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[index],
- ADT7X10_TEMP_TO_REG(temp));
- mutex_unlock(&data->update_lock);
- return ret;
+ return regmap_write(data->regmap, ADT7X10_REG_TEMP[index],
+ ADT7X10_TEMP_TO_REG(temp));
}
static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val)
{
- int hyst, temp, ret;
-
- mutex_lock(&data->update_lock);
- ret = regmap_read(data->regmap, ADT7X10_T_HYST, &hyst);
- if (ret) {
- mutex_unlock(&data->update_lock);
- return ret;
- }
+ unsigned int regs[2] = {ADT7X10_T_HYST, ADT7X10_REG_TEMP[index]};
+ int hyst, ret;
+ u16 regdata[2];
- ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &temp);
- mutex_unlock(&data->update_lock);
+ ret = regmap_multi_reg_read(data->regmap, regs, regdata, 2);
if (ret)
return ret;
- hyst = (hyst & ADT7X10_T_HYST_MASK) * 1000;
+ hyst = (regdata[0] & ADT7X10_T_HYST_MASK) * 1000;
/*
* hysteresis is stored as a 4 bit offset in the device, convert it
@@ -194,7 +177,7 @@ static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val)
if (index == adt7x10_t_alarm_low)
hyst = -hyst;
- *val = ADT7X10_REG_TO_TEMP(data, temp) - hyst;
+ *val = ADT7X10_REG_TO_TEMP(data, regdata[1]) - hyst;
return 0;
}
@@ -203,22 +186,17 @@ static int adt7x10_hyst_write(struct adt7x10_data *data, long hyst)
unsigned int regval;
int limit, ret;
- mutex_lock(&data->update_lock);
-
/* convert absolute hysteresis value to a 4 bit delta value */
ret = regmap_read(data->regmap, ADT7X10_T_ALARM_HIGH, &regval);
if (ret < 0)
- goto abort;
+ return ret;
limit = ADT7X10_REG_TO_TEMP(data, regval);
hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX);
regval = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), 0,
ADT7X10_T_HYST_MASK);
- ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval);
-abort:
- mutex_unlock(&data->update_lock);
- return ret;
+ return regmap_write(data->regmap, ADT7X10_T_HYST, regval);
}
static int adt7x10_alarm_read(struct adt7x10_data *data, int index, long *val)
@@ -350,7 +328,6 @@ int adt7x10_probe(struct device *dev, const char *name, int irq,
data->regmap = regmap;
dev_set_drvdata(dev, data);
- mutex_init(&data->update_lock);
/* configure as specified */
ret = regmap_read(regmap, ADT7X10_CONFIG, &config);
diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c
index f136bf3ff40a..007befdba977 100644
--- a/drivers/hwmon/aht10.c
+++ b/drivers/hwmon/aht10.c
@@ -37,6 +37,8 @@
#define AHT10_CMD_MEAS 0b10101100
#define AHT10_CMD_RST 0b10111010
+#define DHT20_CMD_INIT 0x71
+
/*
* Flags in the answer byte/command
*/
@@ -48,11 +50,12 @@
#define AHT10_MAX_POLL_INTERVAL_LEN 30
-enum aht10_variant { aht10, aht20 };
+enum aht10_variant { aht10, aht20, dht20};
static const struct i2c_device_id aht10_id[] = {
{ "aht10", aht10 },
{ "aht20", aht20 },
+ { "dht20", dht20 },
{ },
};
MODULE_DEVICE_TABLE(i2c, aht10_id);
@@ -60,8 +63,6 @@ MODULE_DEVICE_TABLE(i2c, aht10_id);
/**
* struct aht10_data - All the data required to operate an AHT10/AHT20 chip
* @client: the i2c client associated with the AHT10/AHT20
- * @lock: a mutex that is used to prevent parallel access to the
- * i2c client
* @min_poll_interval: the minimum poll interval
* While the poll rate limit is not 100% necessary,
* the datasheet recommends that a measurement
@@ -77,37 +78,34 @@ MODULE_DEVICE_TABLE(i2c, aht10_id);
* AHT10/AHT20
* @crc8: crc8 support flag
* @meas_size: measurements data size
+ * @init_cmd: Initialization command
*/
struct aht10_data {
struct i2c_client *client;
- /*
- * Prevent simultaneous access to the i2c
- * client and previous_poll_time
- */
- struct mutex lock;
ktime_t min_poll_interval;
ktime_t previous_poll_time;
int temperature;
int humidity;
bool crc8;
unsigned int meas_size;
+ u8 init_cmd;
};
-/**
+/*
* aht10_init() - Initialize an AHT10/AHT20 chip
* @data: the data associated with this AHT10/AHT20 chip
* Return: 0 if successful, 1 if not
*/
static int aht10_init(struct aht10_data *data)
{
- const u8 cmd_init[] = {AHT10_CMD_INIT, AHT10_CAL_ENABLED | AHT10_MODE_CYC,
+ const u8 cmd_init[] = {data->init_cmd, AHT10_CAL_ENABLED | AHT10_MODE_CYC,
0x00};
int res;
u8 status;
struct i2c_client *client = data->client;
- res = i2c_master_send(client, cmd_init, 3);
+ res = i2c_master_send(client, cmd_init, sizeof(cmd_init));
if (res < 0)
return res;
@@ -124,7 +122,7 @@ static int aht10_init(struct aht10_data *data)
return 0;
}
-/**
+/*
* aht10_polltime_expired() - check if the minimum poll interval has
* expired
* @data: the data containing the time to compare
@@ -140,7 +138,7 @@ static int aht10_polltime_expired(struct aht10_data *data)
DECLARE_CRC8_TABLE(crc8_table);
-/**
+/*
* crc8_check() - check crc of the sensor's measurements
* @raw_data: data frame received from sensor(including crc as the last byte)
* @count: size of the data frame
@@ -155,7 +153,7 @@ static int crc8_check(u8 *raw_data, int count)
return crc8(crc8_table, raw_data, count, CRC8_INIT_VALUE);
}
-/**
+/*
* aht10_read_values() - read and parse the raw data from the AHT10/AHT20
* @data: the struct aht10_data to use for the lock
* Return: 0 if successful, 1 if not
@@ -168,32 +166,24 @@ static int aht10_read_values(struct aht10_data *data)
u8 raw_data[AHT20_MEAS_SIZE];
struct i2c_client *client = data->client;
- mutex_lock(&data->lock);
- if (!aht10_polltime_expired(data)) {
- mutex_unlock(&data->lock);
+ if (!aht10_polltime_expired(data))
return 0;
- }
res = i2c_master_send(client, cmd_meas, sizeof(cmd_meas));
- if (res < 0) {
- mutex_unlock(&data->lock);
+ if (res < 0)
return res;
- }
usleep_range(AHT10_MEAS_DELAY, AHT10_MEAS_DELAY + AHT10_DELAY_EXTRA);
res = i2c_master_recv(client, raw_data, data->meas_size);
if (res != data->meas_size) {
- mutex_unlock(&data->lock);
if (res >= 0)
return -ENODATA;
return res;
}
- if (data->crc8 && crc8_check(raw_data, data->meas_size)) {
- mutex_unlock(&data->lock);
+ if (data->crc8 && crc8_check(raw_data, data->meas_size))
return -EIO;
- }
hum = ((u32)raw_data[1] << 12u) |
((u32)raw_data[2] << 4u) |
@@ -210,11 +200,10 @@ static int aht10_read_values(struct aht10_data *data)
data->humidity = hum;
data->previous_poll_time = ktime_get_boottime();
- mutex_unlock(&data->lock);
return 0;
}
-/**
+/*
* aht10_interval_write() - store the given minimum poll interval.
* Return: 0 on success, -EINVAL if a value lower than the
* AHT10_MIN_POLL_INTERVAL is given
@@ -226,7 +215,7 @@ static ssize_t aht10_interval_write(struct aht10_data *data,
return 0;
}
-/**
+/*
* aht10_interval_read() - read the minimum poll interval
* in milliseconds
*/
@@ -237,7 +226,7 @@ static ssize_t aht10_interval_read(struct aht10_data *data,
return 0;
}
-/**
+/*
* aht10_temperature1_read() - read the temperature in millidegrees
*/
static int aht10_temperature1_read(struct aht10_data *data, long *val)
@@ -252,7 +241,7 @@ static int aht10_temperature1_read(struct aht10_data *data, long *val)
return 0;
}
-/**
+/*
* aht10_humidity1_read() - read the relative humidity in millipercent
*/
static int aht10_humidity1_read(struct aht10_data *data, long *val)
@@ -331,8 +320,7 @@ static const struct hwmon_chip_info aht10_chip_info = {
static int aht10_probe(struct i2c_client *client)
{
- const struct i2c_device_id *id = i2c_match_id(aht10_id, client);
- enum aht10_variant variant = id->driver_data;
+ enum aht10_variant variant = (uintptr_t)i2c_get_match_data(client);
struct device *device = &client->dev;
struct device *hwmon_dev;
struct aht10_data *data;
@@ -353,14 +341,20 @@ static int aht10_probe(struct i2c_client *client)
data->meas_size = AHT20_MEAS_SIZE;
data->crc8 = true;
crc8_populate_msb(crc8_table, AHT20_CRC8_POLY);
+ data->init_cmd = AHT10_CMD_INIT;
+ break;
+ case dht20:
+ data->meas_size = AHT20_MEAS_SIZE;
+ data->crc8 = true;
+ crc8_populate_msb(crc8_table, AHT20_CRC8_POLY);
+ data->init_cmd = DHT20_CMD_INIT;
break;
default:
data->meas_size = AHT10_MEAS_SIZE;
+ data->init_cmd = AHT10_CMD_INIT;
break;
}
- mutex_init(&data->lock);
-
res = aht10_init(data);
if (res < 0)
return res;
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
index 2a7a4b6b0094..d5f864b360b0 100644
--- a/drivers/hwmon/amc6821.c
+++ b/drivers/hwmon/amc6821.c
@@ -6,18 +6,29 @@
*
* Based on max6650.c:
* Copyright (C) 2007 Hans J. Koch <hjk@hansjkoch.de>
+ *
+ * Conversion to regmap and with_info API:
+ * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
*/
-#include <linux/kernel.h> /* Needed for KERN_INFO */
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include <dt-bindings/pwm/pwm.h>
/*
* Addresses to scan.
@@ -30,525 +41,600 @@ static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e,
* Insmod parameters
*/
-static int pwminv; /*Inverted PWM output. */
+static int pwminv = -1; /*Inverted PWM output. */
module_param(pwminv, int, 0444);
static int init = 1; /*Power-on initialization.*/
module_param(init, int, 0444);
-enum chips { amc6821 };
-
-#define AMC6821_REG_DEV_ID 0x3D
-#define AMC6821_REG_COMP_ID 0x3E
-#define AMC6821_REG_CONF1 0x00
-#define AMC6821_REG_CONF2 0x01
-#define AMC6821_REG_CONF3 0x3F
-#define AMC6821_REG_CONF4 0x04
-#define AMC6821_REG_STAT1 0x02
-#define AMC6821_REG_STAT2 0x03
-#define AMC6821_REG_TDATA_LOW 0x08
-#define AMC6821_REG_TDATA_HI 0x09
-#define AMC6821_REG_LTEMP_HI 0x0A
-#define AMC6821_REG_RTEMP_HI 0x0B
-#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
-#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
-#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
-#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
-#define AMC6821_REG_LTEMP_CRIT 0x1B
-#define AMC6821_REG_RTEMP_CRIT 0x1D
-#define AMC6821_REG_PSV_TEMP 0x1C
-#define AMC6821_REG_DCY 0x22
-#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
-#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
-#define AMC6821_REG_DCY_LOW_TEMP 0x21
-
-#define AMC6821_REG_TACH_LLIMITL 0x10
-#define AMC6821_REG_TACH_LLIMITH 0x11
-#define AMC6821_REG_TACH_HLIMITL 0x12
-#define AMC6821_REG_TACH_HLIMITH 0x13
-
-#define AMC6821_CONF1_START 0x01
-#define AMC6821_CONF1_FAN_INT_EN 0x02
-#define AMC6821_CONF1_FANIE 0x04
-#define AMC6821_CONF1_PWMINV 0x08
-#define AMC6821_CONF1_FAN_FAULT_EN 0x10
-#define AMC6821_CONF1_FDRC0 0x20
-#define AMC6821_CONF1_FDRC1 0x40
-#define AMC6821_CONF1_THERMOVIE 0x80
-
-#define AMC6821_CONF2_PWM_EN 0x01
-#define AMC6821_CONF2_TACH_MODE 0x02
-#define AMC6821_CONF2_TACH_EN 0x04
-#define AMC6821_CONF2_RTFIE 0x08
-#define AMC6821_CONF2_LTOIE 0x10
-#define AMC6821_CONF2_RTOIE 0x20
-#define AMC6821_CONF2_PSVIE 0x40
-#define AMC6821_CONF2_RST 0x80
-
-#define AMC6821_CONF3_THERM_FAN_EN 0x80
-#define AMC6821_CONF3_REV_MASK 0x0F
-
-#define AMC6821_CONF4_OVREN 0x10
-#define AMC6821_CONF4_TACH_FAST 0x20
-#define AMC6821_CONF4_PSPR 0x40
-#define AMC6821_CONF4_MODE 0x80
-
-#define AMC6821_STAT1_RPM_ALARM 0x01
-#define AMC6821_STAT1_FANS 0x02
-#define AMC6821_STAT1_RTH 0x04
-#define AMC6821_STAT1_RTL 0x08
-#define AMC6821_STAT1_R_THERM 0x10
-#define AMC6821_STAT1_RTF 0x20
-#define AMC6821_STAT1_LTH 0x40
-#define AMC6821_STAT1_LTL 0x80
-
-#define AMC6821_STAT2_RTC 0x08
-#define AMC6821_STAT2_LTC 0x10
-#define AMC6821_STAT2_LPSV 0x20
-#define AMC6821_STAT2_L_THERM 0x40
-#define AMC6821_STAT2_THERM_IN 0x80
-
-enum {IDX_TEMP1_INPUT = 0, IDX_TEMP1_MIN, IDX_TEMP1_MAX,
- IDX_TEMP1_CRIT, IDX_TEMP2_INPUT, IDX_TEMP2_MIN,
- IDX_TEMP2_MAX, IDX_TEMP2_CRIT,
- TEMP_IDX_LEN, };
-
-static const u8 temp_reg[] = {AMC6821_REG_LTEMP_HI,
- AMC6821_REG_LTEMP_LIMIT_MIN,
- AMC6821_REG_LTEMP_LIMIT_MAX,
- AMC6821_REG_LTEMP_CRIT,
- AMC6821_REG_RTEMP_HI,
- AMC6821_REG_RTEMP_LIMIT_MIN,
- AMC6821_REG_RTEMP_LIMIT_MAX,
- AMC6821_REG_RTEMP_CRIT, };
-
-enum {IDX_FAN1_INPUT = 0, IDX_FAN1_MIN, IDX_FAN1_MAX,
- FAN1_IDX_LEN, };
-
-static const u8 fan_reg_low[] = {AMC6821_REG_TDATA_LOW,
- AMC6821_REG_TACH_LLIMITL,
- AMC6821_REG_TACH_HLIMITL, };
-
-
-static const u8 fan_reg_hi[] = {AMC6821_REG_TDATA_HI,
- AMC6821_REG_TACH_LLIMITH,
- AMC6821_REG_TACH_HLIMITH, };
+#define AMC6821_REG_DEV_ID 0x3D
+#define AMC6821_REG_COMP_ID 0x3E
+#define AMC6821_REG_CONF1 0x00
+#define AMC6821_REG_CONF2 0x01
+#define AMC6821_REG_CONF3 0x3F
+#define AMC6821_REG_CONF4 0x04
+#define AMC6821_REG_STAT1 0x02
+#define AMC6821_REG_STAT2 0x03
+#define AMC6821_REG_TEMP_LO 0x06
+#define AMC6821_REG_TDATA_LOW 0x08
+#define AMC6821_REG_TDATA_HI 0x09
+#define AMC6821_REG_LTEMP_HI 0x0A
+#define AMC6821_REG_RTEMP_HI 0x0B
+#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15
+#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14
+#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19
+#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18
+#define AMC6821_REG_LTEMP_CRIT 0x1B
+#define AMC6821_REG_RTEMP_CRIT 0x1D
+#define AMC6821_REG_PSV_TEMP 0x1C
+#define AMC6821_REG_DCY 0x22
+#define AMC6821_REG_LTEMP_FAN_CTRL 0x24
+#define AMC6821_REG_RTEMP_FAN_CTRL 0x25
+#define AMC6821_REG_DCY_LOW_TEMP 0x21
+
+#define AMC6821_REG_TACH_LLIMITL 0x10
+#define AMC6821_REG_TACH_HLIMITL 0x12
+#define AMC6821_REG_TACH_SETTINGL 0x1e
+
+#define AMC6821_CONF1_START BIT(0)
+#define AMC6821_CONF1_FAN_INT_EN BIT(1)
+#define AMC6821_CONF1_FANIE BIT(2)
+#define AMC6821_CONF1_PWMINV BIT(3)
+#define AMC6821_CONF1_FAN_FAULT_EN BIT(4)
+#define AMC6821_CONF1_FDRC0 BIT(5)
+#define AMC6821_CONF1_FDRC1 BIT(6)
+#define AMC6821_CONF1_THERMOVIE BIT(7)
+
+#define AMC6821_CONF2_PWM_EN BIT(0)
+#define AMC6821_CONF2_TACH_MODE BIT(1)
+#define AMC6821_CONF2_TACH_EN BIT(2)
+#define AMC6821_CONF2_RTFIE BIT(3)
+#define AMC6821_CONF2_LTOIE BIT(4)
+#define AMC6821_CONF2_RTOIE BIT(5)
+#define AMC6821_CONF2_PSVIE BIT(6)
+#define AMC6821_CONF2_RST BIT(7)
+
+#define AMC6821_CONF3_THERM_FAN_EN BIT(7)
+#define AMC6821_CONF3_REV_MASK GENMASK(3, 0)
+
+#define AMC6821_CONF4_OVREN BIT(4)
+#define AMC6821_CONF4_TACH_FAST BIT(5)
+#define AMC6821_CONF4_PSPR BIT(6)
+#define AMC6821_CONF4_MODE BIT(7)
+
+#define AMC6821_STAT1_RPM_ALARM BIT(0)
+#define AMC6821_STAT1_FANS BIT(1)
+#define AMC6821_STAT1_RTH BIT(2)
+#define AMC6821_STAT1_RTL BIT(3)
+#define AMC6821_STAT1_R_THERM BIT(4)
+#define AMC6821_STAT1_RTF BIT(5)
+#define AMC6821_STAT1_LTH BIT(6)
+#define AMC6821_STAT1_LTL BIT(7)
+
+#define AMC6821_STAT2_RTC BIT(3)
+#define AMC6821_STAT2_LTC BIT(4)
+#define AMC6821_STAT2_LPSV BIT(5)
+#define AMC6821_STAT2_L_THERM BIT(6)
+#define AMC6821_STAT2_THERM_IN BIT(7)
+
+#define AMC6821_TEMP_SLOPE_MASK GENMASK(2, 0)
+#define AMC6821_TEMP_LIMIT_MASK GENMASK(7, 3)
/*
* Client data (each client gets its own)
*/
struct amc6821_data {
- struct i2c_client *client;
+ struct regmap *regmap;
struct mutex update_lock;
- bool valid; /* false until following fields are valid */
- unsigned long last_updated; /* in jiffies */
+ unsigned long fan_state;
+ unsigned long fan_max_state;
+ unsigned int *fan_cooling_levels;
+ enum pwm_polarity pwm_polarity;
+};
- /* register values */
- int temp[TEMP_IDX_LEN];
+/*
+ * Return 0 on success or negative error code.
+ *
+ * temps returns set of three temperatures, in °C:
+ * temps[0]: Passive cooling temperature, applies to both channels
+ * temps[1]: Low temperature, start slope calculations
+ * temps[2]: High temperature
+ *
+ * Channel 0: local, channel 1: remote.
+ */
+static int amc6821_get_auto_point_temps(struct regmap *regmap, int channel, u8 *temps)
+{
+ u32 regs[] = {
+ AMC6821_REG_DCY_LOW_TEMP,
+ AMC6821_REG_PSV_TEMP,
+ channel ? AMC6821_REG_RTEMP_FAN_CTRL : AMC6821_REG_LTEMP_FAN_CTRL
+ };
+ u8 regvals[3];
+ int slope;
+ int err;
- u16 fan[FAN1_IDX_LEN];
- u8 fan1_div;
+ err = regmap_multi_reg_read(regmap, regs, regvals, 3);
+ if (err)
+ return err;
+ temps[0] = regvals[1];
+ temps[1] = FIELD_GET(AMC6821_TEMP_LIMIT_MASK, regvals[2]) * 4;
- u8 pwm1;
- u8 temp1_auto_point_temp[3];
- u8 temp2_auto_point_temp[3];
- u8 pwm1_auto_point_pwm[3];
- u8 pwm1_enable;
- u8 pwm1_auto_channels_temp;
+ /* slope is 32 >> <slope bits> in °C */
+ slope = 32 >> FIELD_GET(AMC6821_TEMP_SLOPE_MASK, regvals[2]);
+ if (slope)
+ temps[2] = temps[1] + DIV_ROUND_CLOSEST(255 - regvals[0], slope);
+ else
+ temps[2] = 255;
- u8 stat1;
- u8 stat2;
-};
+ return 0;
+}
-static struct amc6821_data *amc6821_update_device(struct device *dev)
+static int amc6821_temp_read_values(struct regmap *regmap, u32 attr, int channel, long *val)
{
- struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int timeout = HZ;
- u8 reg;
- int i;
+ int reg, err;
+ u32 regval;
- mutex_lock(&data->update_lock);
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = channel ? AMC6821_REG_RTEMP_HI : AMC6821_REG_LTEMP_HI;
+ break;
+ case hwmon_temp_min:
+ reg = channel ? AMC6821_REG_RTEMP_LIMIT_MIN : AMC6821_REG_LTEMP_LIMIT_MIN;
+ break;
+ case hwmon_temp_max:
+ reg = channel ? AMC6821_REG_RTEMP_LIMIT_MAX : AMC6821_REG_LTEMP_LIMIT_MAX;
+ break;
+ case hwmon_temp_crit:
+ reg = channel ? AMC6821_REG_RTEMP_CRIT : AMC6821_REG_LTEMP_CRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ err = regmap_read(regmap, reg, &regval);
+ if (err)
+ return err;
+ *val = sign_extend32(regval, 7) * 1000;
+ return 0;
+}
- if (time_after(jiffies, data->last_updated + timeout) ||
- !data->valid) {
-
- for (i = 0; i < TEMP_IDX_LEN; i++)
- data->temp[i] = (int8_t)i2c_smbus_read_byte_data(
- client, temp_reg[i]);
-
- data->stat1 = i2c_smbus_read_byte_data(client,
- AMC6821_REG_STAT1);
- data->stat2 = i2c_smbus_read_byte_data(client,
- AMC6821_REG_STAT2);
-
- data->pwm1 = i2c_smbus_read_byte_data(client,
- AMC6821_REG_DCY);
- for (i = 0; i < FAN1_IDX_LEN; i++) {
- data->fan[i] = i2c_smbus_read_byte_data(
- client,
- fan_reg_low[i]);
- data->fan[i] += i2c_smbus_read_byte_data(
- client,
- fan_reg_hi[i]) << 8;
- }
- data->fan1_div = i2c_smbus_read_byte_data(client,
- AMC6821_REG_CONF4);
- data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 : 2;
-
- data->pwm1_auto_point_pwm[0] = 0;
- data->pwm1_auto_point_pwm[2] = 255;
- data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client,
- AMC6821_REG_DCY_LOW_TEMP);
-
- data->temp1_auto_point_temp[0] =
- i2c_smbus_read_byte_data(client,
- AMC6821_REG_PSV_TEMP);
- data->temp2_auto_point_temp[0] =
- data->temp1_auto_point_temp[0];
- reg = i2c_smbus_read_byte_data(client,
- AMC6821_REG_LTEMP_FAN_CTRL);
- data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1;
- reg &= 0x07;
- reg = 0x20 >> reg;
- if (reg > 0)
- data->temp1_auto_point_temp[2] =
- data->temp1_auto_point_temp[1] +
- (data->pwm1_auto_point_pwm[2] -
- data->pwm1_auto_point_pwm[1]) / reg;
- else
- data->temp1_auto_point_temp[2] = 255;
-
- reg = i2c_smbus_read_byte_data(client,
- AMC6821_REG_RTEMP_FAN_CTRL);
- data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1;
- reg &= 0x07;
- reg = 0x20 >> reg;
- if (reg > 0)
- data->temp2_auto_point_temp[2] =
- data->temp2_auto_point_temp[1] +
- (data->pwm1_auto_point_pwm[2] -
- data->pwm1_auto_point_pwm[1]) / reg;
- else
- data->temp2_auto_point_temp[2] = 255;
-
- reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
- reg = (reg >> 5) & 0x3;
- switch (reg) {
- case 0: /*open loop: software sets pwm1*/
- data->pwm1_auto_channels_temp = 0;
- data->pwm1_enable = 1;
+static int amc6821_read_alarms(struct regmap *regmap, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ int reg, mask, err;
+ u32 regval;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_min_alarm:
+ reg = AMC6821_REG_STAT1;
+ mask = channel ? AMC6821_STAT1_RTL : AMC6821_STAT1_LTL;
break;
- case 2: /*closed loop: remote T (temp2)*/
- data->pwm1_auto_channels_temp = 2;
- data->pwm1_enable = 2;
+ case hwmon_temp_max_alarm:
+ reg = AMC6821_REG_STAT1;
+ mask = channel ? AMC6821_STAT1_RTH : AMC6821_STAT1_LTH;
break;
- case 3: /*closed loop: local and remote T (temp2)*/
- data->pwm1_auto_channels_temp = 3;
- data->pwm1_enable = 3;
+ case hwmon_temp_crit_alarm:
+ reg = AMC6821_REG_STAT2;
+ mask = channel ? AMC6821_STAT2_RTC : AMC6821_STAT2_LTC;
break;
- case 1: /*
- * semi-open loop: software sets rpm, chip controls
- * pwm1, currently not implemented
- */
- data->pwm1_auto_channels_temp = 0;
- data->pwm1_enable = 0;
+ case hwmon_temp_fault:
+ reg = AMC6821_REG_STAT1;
+ mask = AMC6821_STAT1_RTF;
break;
+ default:
+ return -EOPNOTSUPP;
}
-
- data->last_updated = jiffies;
- data->valid = true;
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_fault:
+ reg = AMC6821_REG_STAT1;
+ mask = AMC6821_STAT1_FANS;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
}
- mutex_unlock(&data->update_lock);
- return data;
+ err = regmap_read(regmap, reg, &regval);
+ if (err)
+ return err;
+ *val = !!(regval & mask);
+ return 0;
}
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
+static int amc6821_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
- struct amc6821_data *data = amc6821_update_device(dev);
- int ix = to_sensor_dev_attr(devattr)->index;
+ struct amc6821_data *data = dev_get_drvdata(dev);
- return sprintf(buf, "%d\n", data->temp[ix] * 1000);
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return amc6821_temp_read_values(data->regmap, attr, channel, val);
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_fault:
+ return amc6821_read_alarms(data->regmap, hwmon_temp, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t temp_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static int amc6821_temp_write(struct device *dev, u32 attr, int channel, long val)
{
struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int ix = to_sensor_dev_attr(attr)->index;
- long val;
+ int reg;
- int ret = kstrtol(buf, 10, &val);
- if (ret)
- return ret;
- val = clamp_val(val / 1000, -128, 127);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
- mutex_lock(&data->update_lock);
- data->temp[ix] = val;
- if (i2c_smbus_write_byte_data(client, temp_reg[ix], data->temp[ix])) {
- dev_err(&client->dev, "Register write error, aborting.\n");
- count = -EIO;
+ switch (attr) {
+ case hwmon_temp_min:
+ reg = channel ? AMC6821_REG_RTEMP_LIMIT_MIN : AMC6821_REG_LTEMP_LIMIT_MIN;
+ break;
+ case hwmon_temp_max:
+ reg = channel ? AMC6821_REG_RTEMP_LIMIT_MAX : AMC6821_REG_LTEMP_LIMIT_MAX;
+ break;
+ case hwmon_temp_crit:
+ reg = channel ? AMC6821_REG_RTEMP_CRIT : AMC6821_REG_LTEMP_CRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
}
- mutex_unlock(&data->update_lock);
- return count;
+ return regmap_write(data->regmap, reg, val);
}
-static ssize_t temp_alarm_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
+static int amc6821_pwm_read(struct device *dev, u32 attr, long *val)
{
- struct amc6821_data *data = amc6821_update_device(dev);
- int ix = to_sensor_dev_attr(devattr)->index;
- u8 flag;
+ struct amc6821_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
- switch (ix) {
- case IDX_TEMP1_MIN:
- flag = data->stat1 & AMC6821_STAT1_LTL;
- break;
- case IDX_TEMP1_MAX:
- flag = data->stat1 & AMC6821_STAT1_LTH;
+ switch (attr) {
+ case hwmon_pwm_enable:
+ err = regmap_read(regmap, AMC6821_REG_CONF1, &regval);
+ if (err)
+ return err;
+ switch (regval & (AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1)) {
+ case 0:
+ *val = 1; /* manual */
+ break;
+ case AMC6821_CONF1_FDRC0:
+ *val = 4; /* target rpm (fan1_target) controlled */
+ break;
+ case AMC6821_CONF1_FDRC1:
+ *val = 2; /* remote temp controlled */
+ break;
+ default:
+ *val = 3; /* max(local, remote) temp controlled */
+ break;
+ }
+ return 0;
+ case hwmon_pwm_mode:
+ err = regmap_read(regmap, AMC6821_REG_CONF2, &regval);
+ if (err)
+ return err;
+ *val = !!(regval & AMC6821_CONF2_TACH_MODE);
+ return 0;
+ case hwmon_pwm_auto_channels_temp:
+ err = regmap_read(regmap, AMC6821_REG_CONF1, &regval);
+ if (err)
+ return err;
+ switch (regval & (AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1)) {
+ case 0:
+ case AMC6821_CONF1_FDRC0:
+ *val = 0; /* manual or target rpm controlled */
+ break;
+ case AMC6821_CONF1_FDRC1:
+ *val = 2; /* remote temp controlled */
+ break;
+ default:
+ *val = 3; /* max(local, remote) temp controlled */
+ break;
+ }
+ return 0;
+ case hwmon_pwm_input:
+ err = regmap_read(regmap, AMC6821_REG_DCY, &regval);
+ if (err)
+ return err;
+ *val = regval;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amc6821_pwm_write(struct device *dev, u32 attr, long val)
+{
+ struct amc6821_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 mode;
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ switch (val) {
+ case 1:
+ mode = 0;
+ break;
+ case 2:
+ mode = AMC6821_CONF1_FDRC1;
+ break;
+ case 3:
+ mode = AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1;
+ break;
+ case 4:
+ mode = AMC6821_CONF1_FDRC0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return regmap_update_bits(regmap, AMC6821_REG_CONF1,
+ AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1,
+ mode);
+ case hwmon_pwm_mode:
+ if (val < 0 || val > 1)
+ return -EINVAL;
+ return regmap_update_bits(regmap, AMC6821_REG_CONF2,
+ AMC6821_CONF2_TACH_MODE,
+ val ? AMC6821_CONF2_TACH_MODE : 0);
break;
- case IDX_TEMP1_CRIT:
- flag = data->stat2 & AMC6821_STAT2_LTC;
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ return regmap_write(regmap, AMC6821_REG_DCY, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amc6821_fan_read_rpm(struct regmap *regmap, u32 attr, long *val)
+{
+ int reg, err;
+ u8 regs[2];
+ u32 regval;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ reg = AMC6821_REG_TDATA_LOW;
break;
- case IDX_TEMP2_MIN:
- flag = data->stat1 & AMC6821_STAT1_RTL;
+ case hwmon_fan_min:
+ reg = AMC6821_REG_TACH_LLIMITL;
break;
- case IDX_TEMP2_MAX:
- flag = data->stat1 & AMC6821_STAT1_RTH;
+ case hwmon_fan_max:
+ reg = AMC6821_REG_TACH_HLIMITL;
break;
- case IDX_TEMP2_CRIT:
- flag = data->stat2 & AMC6821_STAT2_RTC;
+ case hwmon_fan_target:
+ reg = AMC6821_REG_TACH_SETTINGL;
break;
default:
- dev_dbg(dev, "Unknown attr->index (%d).\n", ix);
- return -EINVAL;
+ return -EOPNOTSUPP;
}
- if (flag)
- return sprintf(buf, "1");
- else
- return sprintf(buf, "0");
-}
-static ssize_t temp2_fault_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- if (data->stat1 & AMC6821_STAT1_RTF)
- return sprintf(buf, "1");
- else
- return sprintf(buf, "0");
-}
+ err = regmap_bulk_read(regmap, reg, regs, 2);
+ if (err)
+ return err;
-static ssize_t pwm1_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- return sprintf(buf, "%d\n", data->pwm1);
+ regval = (regs[1] << 8) | regs[0];
+ *val = regval ? 6000000 / regval : 0;
+
+ return 0;
}
-static ssize_t pwm1_store(struct device *dev,
- struct device_attribute *devattr, const char *buf,
- size_t count)
+static int amc6821_fan_read(struct device *dev, u32 attr, long *val)
{
struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int ret = kstrtol(buf, 10, &val);
- if (ret)
- return ret;
-
- mutex_lock(&data->update_lock);
- data->pwm1 = clamp_val(val , 0, 255);
- i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1);
- mutex_unlock(&data->update_lock);
- return count;
-}
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
-static ssize_t pwm1_enable_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- return sprintf(buf, "%d\n", data->pwm1_enable);
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_min:
+ case hwmon_fan_max:
+ case hwmon_fan_target:
+ return amc6821_fan_read_rpm(regmap, attr, val);
+ case hwmon_fan_fault:
+ return amc6821_read_alarms(regmap, hwmon_fan, attr, 0, val);
+ case hwmon_fan_pulses:
+ err = regmap_read(regmap, AMC6821_REG_CONF4, &regval);
+ if (err)
+ return err;
+ *val = (regval & AMC6821_CONF4_PSPR) ? 4 : 2;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t pwm1_enable_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static int amc6821_fan_write(struct device *dev, u32 attr, long val)
{
struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int config = kstrtol(buf, 10, &val);
- if (config)
- return config;
-
- mutex_lock(&data->update_lock);
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
- count = config;
- goto unlock;
+ struct regmap *regmap = data->regmap;
+ u8 regs[2];
+ int reg;
+
+ if (attr == hwmon_fan_pulses) {
+ if (val != 2 && val != 4)
+ return -EINVAL;
+ return regmap_update_bits(regmap, AMC6821_REG_CONF4,
+ AMC6821_CONF4_PSPR,
+ val == 4 ? AMC6821_CONF4_PSPR : 0);
}
- switch (val) {
- case 1:
- config &= ~AMC6821_CONF1_FDRC0;
- config &= ~AMC6821_CONF1_FDRC1;
+ if (val < 0)
+ return -EINVAL;
+
+ switch (attr) {
+ case hwmon_fan_min:
+ if (!val) /* no unlimited minimum speed */
+ return -EINVAL;
+ reg = AMC6821_REG_TACH_LLIMITL;
break;
- case 2:
- config &= ~AMC6821_CONF1_FDRC0;
- config |= AMC6821_CONF1_FDRC1;
+ case hwmon_fan_max:
+ reg = AMC6821_REG_TACH_HLIMITL;
break;
- case 3:
- config |= AMC6821_CONF1_FDRC0;
- config |= AMC6821_CONF1_FDRC1;
+ case hwmon_fan_target:
+ if (!val) /* no unlimited target speed */
+ return -EINVAL;
+ reg = AMC6821_REG_TACH_SETTINGL;
break;
default:
- count = -EINVAL;
- goto unlock;
- }
- if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
- count = -EIO;
+ return -EOPNOTSUPP;
}
-unlock:
- mutex_unlock(&data->update_lock);
- return count;
-}
-static ssize_t pwm1_auto_channels_temp_show(struct device *dev,
- struct device_attribute *devattr,
- char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp);
+ val = val ? 6000000 / clamp_val(val, 1, 6000000) : 0;
+ val = clamp_val(val, 0, 0xffff);
+
+ regs[0] = val & 0xff;
+ regs[1] = val >> 8;
+
+ return regmap_bulk_write(data->regmap, reg, regs, 2);
}
static ssize_t temp_auto_point_temp_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
+ struct amc6821_data *data = dev_get_drvdata(dev);
int ix = to_sensor_dev_attr_2(devattr)->index;
int nr = to_sensor_dev_attr_2(devattr)->nr;
- struct amc6821_data *data = amc6821_update_device(dev);
- switch (nr) {
- case 1:
- return sprintf(buf, "%d\n",
- data->temp1_auto_point_temp[ix] * 1000);
- case 2:
- return sprintf(buf, "%d\n",
- data->temp2_auto_point_temp[ix] * 1000);
- default:
- dev_dbg(dev, "Unknown attr->nr (%d).\n", nr);
- return -EINVAL;
- }
+ u8 temps[3];
+ int err;
+
+ mutex_lock(&data->update_lock);
+ err = amc6821_get_auto_point_temps(data->regmap, nr, temps);
+ mutex_unlock(&data->update_lock);
+ if (err)
+ return err;
+
+ return sysfs_emit(buf, "%d\n", temps[ix] * 1000);
}
static ssize_t pwm1_auto_point_pwm_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
+ struct amc6821_data *data = dev_get_drvdata(dev);
int ix = to_sensor_dev_attr(devattr)->index;
- struct amc6821_data *data = amc6821_update_device(dev);
- return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[ix]);
+ u32 val;
+ int err;
+
+ switch (ix) {
+ case 0:
+ val = 0;
+ break;
+ case 1:
+ err = regmap_read(data->regmap, AMC6821_REG_DCY_LOW_TEMP, &val);
+ if (err)
+ return err;
+ break;
+ default:
+ val = 255;
+ break;
+ }
+ return sysfs_emit(buf, "%d\n", val);
}
-static inline ssize_t set_slope_register(struct i2c_client *client,
- u8 reg,
- u8 dpwm,
- u8 *ptemp)
+/*
+ * Set TEMP[0-4] (low temperature) and SLP[0-2] (slope) of local or remote
+ * TEMP-FAN control register.
+ *
+ * Return 0 on success or negative error code.
+ *
+ * Channel 0: local, channel 1: remote
+ */
+static inline int set_slope_register(struct regmap *regmap, int channel, u8 *temps)
{
- int dt;
- u8 tmp;
+ u8 regval = FIELD_PREP(AMC6821_TEMP_LIMIT_MASK, temps[1] / 4);
+ u8 tmp, dpwm;
+ int err, dt;
+ u32 pwm;
- dt = ptemp[2]-ptemp[1];
+ err = regmap_read(regmap, AMC6821_REG_DCY_LOW_TEMP, &pwm);
+ if (err)
+ return err;
+
+ dpwm = 255 - pwm;
+
+ dt = temps[2] - temps[1];
for (tmp = 4; tmp > 0; tmp--) {
- if (dt * (0x20 >> tmp) >= dpwm)
+ if (dt * (32 >> tmp) >= dpwm)
break;
}
- tmp |= (ptemp[1] & 0x7C) << 1;
- if (i2c_smbus_write_byte_data(client,
- reg, tmp)) {
- dev_err(&client->dev, "Register write error, aborting.\n");
- return -EIO;
- }
- return 0;
+ regval |= FIELD_PREP(AMC6821_TEMP_SLOPE_MASK, tmp);
+
+ return regmap_write(regmap,
+ channel ? AMC6821_REG_RTEMP_FAN_CTRL : AMC6821_REG_LTEMP_FAN_CTRL,
+ regval);
}
static ssize_t temp_auto_point_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- struct amc6821_data *data = amc6821_update_device(dev);
- struct i2c_client *client = data->client;
+ struct amc6821_data *data = dev_get_drvdata(dev);
int ix = to_sensor_dev_attr_2(attr)->index;
int nr = to_sensor_dev_attr_2(attr)->nr;
- u8 *ptemp;
- u8 reg;
- int dpwm;
+ struct regmap *regmap = data->regmap;
+ u8 temps[3], otemps[3];
long val;
- int ret = kstrtol(buf, 10, &val);
+ int ret;
+
+ ret = kstrtol(buf, 10, &val);
if (ret)
return ret;
- switch (nr) {
- case 1:
- ptemp = data->temp1_auto_point_temp;
- reg = AMC6821_REG_LTEMP_FAN_CTRL;
- break;
- case 2:
- ptemp = data->temp2_auto_point_temp;
- reg = AMC6821_REG_RTEMP_FAN_CTRL;
- break;
- default:
- dev_dbg(dev, "Unknown attr->nr (%d).\n", nr);
- return -EINVAL;
- }
-
mutex_lock(&data->update_lock);
- data->valid = false;
+
+ ret = amc6821_get_auto_point_temps(data->regmap, nr, temps);
+ if (ret)
+ goto unlock;
switch (ix) {
case 0:
- ptemp[0] = clamp_val(val / 1000, 0,
- data->temp1_auto_point_temp[1]);
- ptemp[0] = clamp_val(ptemp[0], 0,
- data->temp2_auto_point_temp[1]);
- ptemp[0] = clamp_val(ptemp[0], 0, 63);
- if (i2c_smbus_write_byte_data(
- client,
- AMC6821_REG_PSV_TEMP,
- ptemp[0])) {
- dev_err(&client->dev,
- "Register write error, aborting.\n");
- count = -EIO;
- }
- goto EXIT;
+ /*
+ * Passive cooling temperature. Range limit against low limit
+ * of both channels.
+ */
+ ret = amc6821_get_auto_point_temps(data->regmap, 1 - nr, otemps);
+ if (ret)
+ goto unlock;
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 63000), 1000);
+ val = clamp_val(val, 0, min(temps[1], otemps[1]));
+ ret = regmap_write(regmap, AMC6821_REG_PSV_TEMP, val);
+ break;
case 1:
- ptemp[1] = clamp_val(val / 1000, (ptemp[0] & 0x7C) + 4, 124);
- ptemp[1] &= 0x7C;
- ptemp[2] = clamp_val(ptemp[2], ptemp[1] + 1, 255);
+ /*
+ * Low limit; must be between passive and high limit,
+ * and not exceed 124. Step size is 4 degrees C.
+ */
+ val = clamp_val(val, DIV_ROUND_UP(temps[0], 4) * 4000, 124000);
+ temps[1] = DIV_ROUND_CLOSEST(val, 4000) * 4;
+ val = temps[1] / 4;
+ /* Auto-adjust high limit if necessary */
+ temps[2] = clamp_val(temps[2], temps[1] + 1, 255);
+ ret = set_slope_register(regmap, nr, temps);
break;
case 2:
- ptemp[2] = clamp_val(val / 1000, ptemp[1]+1, 255);
+ /* high limit, must be higher than low limit */
+ val = clamp_val(val, (temps[1] + 1) * 1000, 255000);
+ temps[2] = DIV_ROUND_CLOSEST(val, 1000);
+ ret = set_slope_register(regmap, nr, temps);
break;
default:
- dev_dbg(dev, "Unknown attr->index (%d).\n", ix);
- count = -EINVAL;
- goto EXIT;
+ ret = -EINVAL;
+ break;
}
- dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];
- if (set_slope_register(client, reg, dpwm, ptemp))
- count = -EIO;
-
-EXIT:
+unlock:
mutex_unlock(&data->update_lock);
- return count;
+ return ret ? : count;
}
static ssize_t pwm1_auto_point_pwm_store(struct device *dev,
@@ -556,204 +642,52 @@ static ssize_t pwm1_auto_point_pwm_store(struct device *dev,
const char *buf, size_t count)
{
struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int dpwm;
- long val;
- int ret = kstrtol(buf, 10, &val);
- if (ret)
- return ret;
-
- mutex_lock(&data->update_lock);
- data->pwm1_auto_point_pwm[1] = clamp_val(val, 0, 254);
- if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP,
- data->pwm1_auto_point_pwm[1])) {
- dev_err(&client->dev, "Register write error, aborting.\n");
- count = -EIO;
- goto EXIT;
- }
- dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1];
- if (set_slope_register(client, AMC6821_REG_LTEMP_FAN_CTRL, dpwm,
- data->temp1_auto_point_temp)) {
- count = -EIO;
- goto EXIT;
- }
- if (set_slope_register(client, AMC6821_REG_RTEMP_FAN_CTRL, dpwm,
- data->temp2_auto_point_temp)) {
- count = -EIO;
- goto EXIT;
- }
-
-EXIT:
- data->valid = false;
- mutex_unlock(&data->update_lock);
- return count;
-}
-
-static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- int ix = to_sensor_dev_attr(devattr)->index;
- if (0 == data->fan[ix])
- return sprintf(buf, "0");
- return sprintf(buf, "%d\n", (int)(6000000 / data->fan[ix]));
-}
-
-static ssize_t fan1_fault_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- if (data->stat1 & AMC6821_STAT1_FANS)
- return sprintf(buf, "1");
- else
- return sprintf(buf, "0");
-}
+ struct regmap *regmap = data->regmap;
+ int i, ret;
+ u8 val;
-static ssize_t fan_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int ix = to_sensor_dev_attr(attr)->index;
- int ret = kstrtol(buf, 10, &val);
+ ret = kstrtou8(buf, 10, &val);
if (ret)
return ret;
- val = 1 > val ? 0xFFFF : 6000000/val;
mutex_lock(&data->update_lock);
- data->fan[ix] = (u16) clamp_val(val, 1, 0xFFFF);
- if (i2c_smbus_write_byte_data(client, fan_reg_low[ix],
- data->fan[ix] & 0xFF)) {
- dev_err(&client->dev, "Register write error, aborting.\n");
- count = -EIO;
- goto EXIT;
- }
- if (i2c_smbus_write_byte_data(client,
- fan_reg_hi[ix], data->fan[ix] >> 8)) {
- dev_err(&client->dev, "Register write error, aborting.\n");
- count = -EIO;
- }
-EXIT:
- mutex_unlock(&data->update_lock);
- return count;
-}
-
-static ssize_t fan1_div_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct amc6821_data *data = amc6821_update_device(dev);
- return sprintf(buf, "%d\n", data->fan1_div);
-}
+ ret = regmap_write(regmap, AMC6821_REG_DCY_LOW_TEMP, val);
+ if (ret)
+ goto unlock;
-static ssize_t fan1_div_store(struct device *dev,
- struct device_attribute *attr, const char *buf,
- size_t count)
-{
- struct amc6821_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int config = kstrtol(buf, 10, &val);
- if (config)
- return config;
+ for (i = 0; i < 2; i++) {
+ u8 temps[3];
- mutex_lock(&data->update_lock);
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
- count = config;
- goto EXIT;
- }
- switch (val) {
- case 2:
- config &= ~AMC6821_CONF4_PSPR;
- data->fan1_div = 2;
- break;
- case 4:
- config |= AMC6821_CONF4_PSPR;
- data->fan1_div = 4;
- break;
- default:
- count = -EINVAL;
- goto EXIT;
- }
- if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
- count = -EIO;
+ ret = amc6821_get_auto_point_temps(regmap, i, temps);
+ if (ret)
+ break;
+ ret = set_slope_register(regmap, i, temps);
+ if (ret)
+ break;
}
-EXIT:
+unlock:
mutex_unlock(&data->update_lock);
- return count;
+ return ret ? : count;
}
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, IDX_TEMP1_INPUT);
-static SENSOR_DEVICE_ATTR_RW(temp1_min, temp, IDX_TEMP1_MIN);
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, IDX_TEMP1_MAX);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit, temp, IDX_TEMP1_CRIT);
-static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, temp_alarm, IDX_TEMP1_MIN);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, temp_alarm, IDX_TEMP1_MAX);
-static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, temp_alarm, IDX_TEMP1_CRIT);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, IDX_TEMP2_INPUT);
-static SENSOR_DEVICE_ATTR_RW(temp2_min, temp, IDX_TEMP2_MIN);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, IDX_TEMP2_MAX);
-static SENSOR_DEVICE_ATTR_RW(temp2_crit, temp, IDX_TEMP2_CRIT);
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, temp2_fault, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, temp_alarm, IDX_TEMP2_MIN);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, temp_alarm, IDX_TEMP2_MAX);
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, temp_alarm, IDX_TEMP2_CRIT);
-static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, IDX_FAN1_INPUT);
-static SENSOR_DEVICE_ATTR_RW(fan1_min, fan, IDX_FAN1_MIN);
-static SENSOR_DEVICE_ATTR_RW(fan1_max, fan, IDX_FAN1_MAX);
-static SENSOR_DEVICE_ATTR_RO(fan1_fault, fan1_fault, 0);
-static SENSOR_DEVICE_ATTR_RW(fan1_div, fan1_div, 0);
-
-static SENSOR_DEVICE_ATTR_RW(pwm1, pwm1, 0);
-static SENSOR_DEVICE_ATTR_RW(pwm1_enable, pwm1_enable, 0);
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point1_pwm, pwm1_auto_point_pwm, 0);
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm1_auto_point_pwm, 1);
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point3_pwm, pwm1_auto_point_pwm, 2);
-static SENSOR_DEVICE_ATTR_RO(pwm1_auto_channels_temp, pwm1_auto_channels_temp,
- 0);
static SENSOR_DEVICE_ATTR_2_RO(temp1_auto_point1_temp, temp_auto_point_temp,
- 1, 0);
+ 0, 0);
static SENSOR_DEVICE_ATTR_2_RW(temp1_auto_point2_temp, temp_auto_point_temp,
- 1, 1);
+ 0, 1);
static SENSOR_DEVICE_ATTR_2_RW(temp1_auto_point3_temp, temp_auto_point_temp,
- 1, 2);
+ 0, 2);
static SENSOR_DEVICE_ATTR_2_RW(temp2_auto_point1_temp, temp_auto_point_temp,
- 2, 0);
+ 1, 0);
static SENSOR_DEVICE_ATTR_2_RW(temp2_auto_point2_temp, temp_auto_point_temp,
- 2, 1);
+ 1, 1);
static SENSOR_DEVICE_ATTR_2_RW(temp2_auto_point3_temp, temp_auto_point_temp,
- 2, 2);
+ 1, 2);
static struct attribute *amc6821_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_fan1_input.dev_attr.attr,
- &sensor_dev_attr_fan1_min.dev_attr.attr,
- &sensor_dev_attr_fan1_max.dev_attr.attr,
- &sensor_dev_attr_fan1_fault.dev_attr.attr,
- &sensor_dev_attr_fan1_div.dev_attr.attr,
- &sensor_dev_attr_pwm1.dev_attr.attr,
- &sensor_dev_attr_pwm1_enable.dev_attr.attr,
- &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
@@ -765,13 +699,177 @@ static struct attribute *amc6821_attrs[] = {
&sensor_dev_attr_temp2_auto_point3_temp.dev_attr.attr,
NULL
};
-
ATTRIBUTE_GROUPS(amc6821);
+static int amc6821_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return amc6821_temp_read(dev, attr, channel, val);
+ case hwmon_fan:
+ return amc6821_fan_read(dev, attr, val);
+ case hwmon_pwm:
+ return amc6821_pwm_read(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amc6821_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return amc6821_temp_write(dev, attr, channel, val);
+ case hwmon_fan:
+ return amc6821_fan_write(dev, attr, val);
+ case hwmon_pwm:
+ return amc6821_pwm_write(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t amc6821_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_fault:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return 0644;
+ default:
+ return 0;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_fault:
+ return 0444;
+ case hwmon_fan_pulses:
+ case hwmon_fan_min:
+ case hwmon_fan_max:
+ case hwmon_fan_target:
+ return 0644;
+ default:
+ return 0;
+ }
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_mode:
+ case hwmon_pwm_enable:
+ case hwmon_pwm_input:
+ return 0644;
+ case hwmon_pwm_auto_channels_temp:
+ return 0444;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info * const amc6821_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_FAULT),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+ HWMON_F_TARGET | HWMON_F_PULSES | HWMON_F_FAULT),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_MODE |
+ HWMON_PWM_AUTO_CHANNELS_TEMP),
+ NULL
+};
+
+static const struct hwmon_ops amc6821_hwmon_ops = {
+ .is_visible = amc6821_is_visible,
+ .read = amc6821_read,
+ .write = amc6821_write,
+};
+
+static const struct hwmon_chip_info amc6821_chip_info = {
+ .ops = &amc6821_hwmon_ops,
+ .info = amc6821_info,
+};
+
+static int amc6821_set_sw_dcy(struct amc6821_data *data, u8 duty_cycle)
+{
+ int ret;
+
+ ret = regmap_write(data->regmap, AMC6821_REG_DCY, duty_cycle);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, AMC6821_REG_CONF1,
+ AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1, 0);
+}
+
+static int amc6821_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct amc6821_data *data = cdev->devdata;
+
+ if (!data)
+ return -EINVAL;
+
+ *state = data->fan_max_state;
+
+ return 0;
+}
+
+static int amc6821_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct amc6821_data *data = cdev->devdata;
+
+ if (!data)
+ return -EINVAL;
+
+ *state = data->fan_state;
+
+ return 0;
+}
+
+static int amc6821_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct amc6821_data *data = cdev->devdata;
+ int ret;
+
+ if (!data || state > data->fan_max_state)
+ return -EINVAL;
+
+ ret = amc6821_set_sw_dcy(data, data->fan_cooling_levels[state]);
+ if (ret)
+ return ret;
+
+ data->fan_state = state;
+
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops amc6821_cooling_ops = {
+ .get_max_state = amc6821_get_max_state,
+ .get_cur_state = amc6821_get_cur_state,
+ .set_cur_state = amc6821_set_cur_state,
+};
+
/* Return 0 if detection is successful, -ENODEV otherwise */
-static int amc6821_detect(
- struct i2c_client *client,
- struct i2c_board_info *info)
+static int amc6821_detect(struct i2c_client *client, struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int address = client->addr;
@@ -814,130 +912,200 @@ static int amc6821_detect(
return 0;
}
-static int amc6821_init_client(struct i2c_client *client)
+static enum pwm_polarity amc6821_pwm_polarity(struct i2c_client *client,
+ struct device_node *fan_np)
{
- int config;
- int err = -EIO;
+ enum pwm_polarity polarity = PWM_POLARITY_NORMAL;
+ struct of_phandle_args args;
- if (init) {
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4);
-
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
- return err;
- }
+ /*
+ * For backward compatibility, the pwminv module parameter takes
+ * always the precedence over any other device description
+ */
+ if (pwminv == 0)
+ return PWM_POLARITY_NORMAL;
+ if (pwminv > 0)
+ return PWM_POLARITY_INVERSED;
+
+ if (of_parse_phandle_with_args(fan_np, "pwms", "#pwm-cells", 0, &args))
+ goto out;
+ of_node_put(args.np);
+
+ if (args.args_count != 2)
+ goto out;
+
+ if (args.args[1] & PWM_POLARITY_INVERTED)
+ polarity = PWM_POLARITY_INVERSED;
+out:
+ return polarity;
+}
- config |= AMC6821_CONF4_MODE;
+static int amc6821_of_fan_read_data(struct i2c_client *client,
+ struct amc6821_data *data,
+ struct device_node *fan_np)
+{
+ int num;
- if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4,
- config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
- return err;
- }
+ data->pwm_polarity = amc6821_pwm_polarity(client, fan_np);
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3);
+ num = of_property_count_u32_elems(fan_np, "cooling-levels");
+ if (num <= 0)
+ return 0;
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
- return err;
- }
+ data->fan_max_state = num - 1;
- dev_info(&client->dev, "Revision %d\n", config & 0x0f);
+ data->fan_cooling_levels = devm_kcalloc(&client->dev, num,
+ sizeof(u32),
+ GFP_KERNEL);
- config &= ~AMC6821_CONF3_THERM_FAN_EN;
+ if (!data->fan_cooling_levels)
+ return -ENOMEM;
- if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3,
- config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
- return err;
- }
+ return of_property_read_u32_array(fan_np, "cooling-levels",
+ data->fan_cooling_levels, num);
+}
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2);
+static int amc6821_init_client(struct i2c_client *client, struct amc6821_data *data)
+{
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
+ if (init) {
+ err = regmap_set_bits(regmap, AMC6821_REG_CONF4, AMC6821_CONF4_MODE);
+ if (err)
return err;
- }
-
- config &= ~AMC6821_CONF2_RTFIE;
- config &= ~AMC6821_CONF2_LTOIE;
- config &= ~AMC6821_CONF2_RTOIE;
- if (i2c_smbus_write_byte_data(client,
- AMC6821_REG_CONF2, config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
+ err = regmap_clear_bits(regmap, AMC6821_REG_CONF3, AMC6821_CONF3_THERM_FAN_EN);
+ if (err)
+ return err;
+ err = regmap_clear_bits(regmap, AMC6821_REG_CONF2,
+ AMC6821_CONF2_RTFIE |
+ AMC6821_CONF2_LTOIE |
+ AMC6821_CONF2_RTOIE);
+ if (err)
return err;
- }
- config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1);
+ regval = AMC6821_CONF1_START;
+ if (data->pwm_polarity == PWM_POLARITY_INVERSED)
+ regval |= AMC6821_CONF1_PWMINV;
- if (config < 0) {
- dev_err(&client->dev,
- "Error reading configuration register, aborting.\n");
+ err = regmap_update_bits(regmap, AMC6821_REG_CONF1,
+ AMC6821_CONF1_THERMOVIE | AMC6821_CONF1_FANIE |
+ AMC6821_CONF1_START | AMC6821_CONF1_PWMINV,
+ regval);
+ if (err)
return err;
- }
- config &= ~AMC6821_CONF1_THERMOVIE;
- config &= ~AMC6821_CONF1_FANIE;
- config |= AMC6821_CONF1_START;
- if (pwminv)
- config |= AMC6821_CONF1_PWMINV;
- else
- config &= ~AMC6821_CONF1_PWMINV;
-
- if (i2c_smbus_write_byte_data(
- client, AMC6821_REG_CONF1, config)) {
- dev_err(&client->dev,
- "Configuration register write error, aborting.\n");
- return err;
+ /* Software DCY-control mode with fan enabled when cooling-levels present */
+ if (data->fan_cooling_levels) {
+ err = amc6821_set_sw_dcy(data,
+ data->fan_cooling_levels[data->fan_max_state]);
+ if (err)
+ return err;
}
}
return 0;
}
+static bool amc6821_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AMC6821_REG_STAT1:
+ case AMC6821_REG_STAT2:
+ case AMC6821_REG_TEMP_LO:
+ case AMC6821_REG_TDATA_LOW:
+ case AMC6821_REG_LTEMP_HI:
+ case AMC6821_REG_RTEMP_HI:
+ case AMC6821_REG_TDATA_HI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config amc6821_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = amc6821_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
static int amc6821_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct amc6821_data *data;
struct device *hwmon_dev;
+ struct regmap *regmap;
+ struct device_node *fan_np __free(device_node) = NULL;
int err;
data = devm_kzalloc(dev, sizeof(struct amc6821_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->client = client;
- mutex_init(&data->update_lock);
+ regmap = devm_regmap_init_i2c(client, &amc6821_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize regmap\n");
+ data->regmap = regmap;
+
+ fan_np = of_get_child_by_name(dev->of_node, "fan");
+ if (fan_np) {
+ err = amc6821_of_fan_read_data(client, data, fan_np);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to read fan device tree data\n");
+ }
- /*
- * Initialize the amc6821 chip
- */
- err = amc6821_init_client(client);
+ err = amc6821_init_client(client, data);
if (err)
return err;
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data,
- amc6821_groups);
- return PTR_ERR_OR_ZERO(hwmon_dev);
+ if (of_device_is_compatible(dev->of_node, "tsd,mule")) {
+ err = devm_of_platform_populate(dev);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to create sub-devices\n");
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &amc6821_chip_info,
+ amc6821_groups);
+ if (IS_ERR(hwmon_dev))
+ return dev_err_probe(dev, PTR_ERR(hwmon_dev),
+ "Failed to initialize hwmon\n");
+
+ if (IS_ENABLED(CONFIG_THERMAL) && fan_np && data->fan_cooling_levels)
+ return PTR_ERR_OR_ZERO(devm_thermal_of_cooling_device_register(dev,
+ fan_np, client->name, data, &amc6821_cooling_ops));
+
+ return 0;
}
static const struct i2c_device_id amc6821_id[] = {
- { "amc6821", amc6821 },
+ { "amc6821" },
{ }
};
MODULE_DEVICE_TABLE(i2c, amc6821_id);
+static const struct of_device_id __maybe_unused amc6821_of_match[] = {
+ {
+ .compatible = "ti,amc6821",
+ },
+ {
+ .compatible = "tsd,mule",
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, amc6821_of_match);
+
static struct i2c_driver amc6821_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "amc6821",
+ .of_match_table = of_match_ptr(amc6821_of_match),
},
.probe = amc6821_probe,
.id_table = amc6821_id,
diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c
index 023807859be7..1ca70e726298 100644
--- a/drivers/hwmon/aquacomputer_d5next.c
+++ b/drivers/hwmon/aquacomputer_d5next.c
@@ -1,11 +1,12 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* hwmon driver for Aquacomputer devices (D5 Next, Farbwerk, Farbwerk 360, Octo,
- * Quadro, High Flow Next, Aquaero, Aquastream Ultimate, Leakshield)
+ * Quadro, High Flow Next, Aquaero, Aquastream Ultimate, Leakshield,
+ * High Flow USB/MPS Flow family)
*
* Aquacomputer devices send HID reports (with ID 0x01) every second to report
* sensor values, except for devices that communicate through the
- * legacy way (currently, Poweradjust 3).
+ * legacy way (currently, Poweradjust 3 and High Flow USB/MPS Flow family).
*
* Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
* Copyright 2022 Jack Doan <me@jackdoan.com>
@@ -19,9 +20,8 @@
#include <linux/jiffies.h>
#include <linux/ktime.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/seq_file.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#define USB_VENDOR_ID_AQUACOMPUTER 0x0c70
#define USB_PRODUCT_ID_AQUAERO 0xf001
@@ -35,11 +35,12 @@
#define USB_PRODUCT_ID_AQUASTREAMXT 0xf0b6
#define USB_PRODUCT_ID_AQUASTREAMULT 0xf00b
#define USB_PRODUCT_ID_POWERADJUST3 0xf0bd
+#define USB_PRODUCT_ID_HIGHFLOW 0xf003
enum kinds {
d5next, farbwerk, farbwerk360, octo, quadro,
highflownext, aquaero, poweradjust3, aquastreamult,
- aquastreamxt, leakshield
+ aquastreamxt, leakshield, highflow
};
static const char *const aqc_device_names[] = {
@@ -53,7 +54,8 @@ static const char *const aqc_device_names[] = {
[aquastreamxt] = "aquastreamxt",
[aquaero] = "aquaero",
[aquastreamult] = "aquastreamultimate",
- [poweradjust3] = "poweradjust3"
+ [poweradjust3] = "poweradjust3",
+ [highflow] = "highflow" /* Covers MPS Flow devices */
};
#define DRIVER_NAME "aquacomputer_d5next"
@@ -90,6 +92,8 @@ static u8 aquaero_secondary_ctrl_report[] = {
#define POWERADJUST3_STATUS_REPORT_ID 0x03
+#define HIGHFLOW_STATUS_REPORT_ID 0x02
+
/* Data types for reading and writing control reports */
#define AQC_8 0
#define AQC_BE16 1
@@ -197,16 +201,19 @@ static u16 aquastreamult_sensor_fan_offsets[] = { AQUASTREAMULT_FAN_OFFSET };
#define OCTO_NUM_FANS 8
#define OCTO_NUM_SENSORS 4
#define OCTO_NUM_VIRTUAL_SENSORS 16
+#define OCTO_NUM_FLOW_SENSORS 1
#define OCTO_CTRL_REPORT_SIZE 0x65F
/* Sensor report offsets for the Octo */
#define OCTO_POWER_CYCLES 0x18
#define OCTO_SENSOR_START 0x3D
#define OCTO_VIRTUAL_SENSORS_START 0x45
+#define OCTO_FLOW_SENSOR_OFFSET 0x7B
static u16 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 };
/* Control report offsets for the Octo */
#define OCTO_TEMP_CTRL_OFFSET 0xA
+#define OCTO_FLOW_PULSES_CTRL_OFFSET 0x6
/* Fan speed offsets (0-100%) */
static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0x259, 0x2AE };
@@ -282,6 +289,17 @@ static u16 aquastreamxt_sensor_fan_offsets[] = { 0x13, 0x1b };
/* Sensor report offsets for the Poweradjust 3 */
#define POWERADJUST3_SENSOR_START 0x03
+/* Specs of the High Flow USB */
+#define HIGHFLOW_NUM_SENSORS 2
+#define HIGHFLOW_NUM_FLOW_SENSORS 1
+#define HIGHFLOW_SENSOR_REPORT_SIZE 0x76
+
+/* Sensor report offsets for the High Flow USB */
+#define HIGHFLOW_FIRMWARE_VERSION 0x3
+#define HIGHFLOW_SERIAL_START 0x9
+#define HIGHFLOW_FLOW_SENSOR_OFFSET 0x23
+#define HIGHFLOW_SENSOR_START 0x2b
+
/* Labels for D5 Next */
static const char *const label_d5next_temp[] = {
"Coolant temp"
@@ -347,18 +365,6 @@ static const char *const label_aquaero_calc_temp_sensors[] = {
"Calc. virtual sensor 4"
};
-/* Labels for Octo and Quadro (except speed) */
-static const char *const label_fan_speed[] = {
- "Fan 1 speed",
- "Fan 2 speed",
- "Fan 3 speed",
- "Fan 4 speed",
- "Fan 5 speed",
- "Fan 6 speed",
- "Fan 7 speed",
- "Fan 8 speed"
-};
-
static const char *const label_fan_power[] = {
"Fan 1 power",
"Fan 2 power",
@@ -392,6 +398,19 @@ static const char *const label_fan_current[] = {
"Fan 8 current"
};
+/* Labels for Octo fan speeds */
+static const char *const label_octo_speeds[] = {
+ "Fan 1 speed",
+ "Fan 2 speed",
+ "Fan 3 speed",
+ "Fan 4 speed",
+ "Fan 5 speed",
+ "Fan 6 speed",
+ "Fan 7 speed",
+ "Fan 8 speed",
+ "Flow speed [dL/h]",
+};
+
/* Labels for Quadro fan speeds */
static const char *const label_quadro_speeds[] = {
"Fan 1 speed",
@@ -486,6 +505,16 @@ static const char *const label_poweradjust3_temp_sensors[] = {
"External sensor"
};
+/* Labels for Highflow */
+static const char *const label_highflow_temp[] = {
+ "External temp",
+ "Internal temp"
+};
+
+static const char *const label_highflow_speeds[] = {
+ "Flow speed [dL/h]"
+};
+
struct aqc_fan_structure_offsets {
u8 voltage;
u8 curr;
@@ -521,7 +550,6 @@ struct aqc_data {
struct hid_device *hdev;
struct device *hwmon_dev;
struct dentry *debugfs;
- struct mutex mutex; /* Used for locking access when reading and writing PWM values */
enum kinds kind;
const char *name;
@@ -567,7 +595,7 @@ struct aqc_data {
/* Sensor values */
s32 temp_input[20]; /* Max 4 physical and 16 virtual or 8 physical and 12 virtual */
- s32 speed_input[8];
+ s32 speed_input[9];
u32 speed_input_min[1];
u32 speed_input_target[1];
u32 speed_input_max[1];
@@ -632,7 +660,6 @@ static void aqc_delay_ctrl_report(struct aqc_data *priv)
}
}
-/* Expects the mutex to be locked */
static int aqc_get_ctrl_data(struct aqc_data *priv)
{
int ret;
@@ -650,7 +677,6 @@ static int aqc_get_ctrl_data(struct aqc_data *priv)
return ret;
}
-/* Expects the mutex to be locked */
static int aqc_send_ctrl_data(struct aqc_data *priv)
{
int ret;
@@ -691,11 +717,9 @@ static int aqc_get_ctrl_val(struct aqc_data *priv, int offset, long *val, int ty
{
int ret;
- mutex_lock(&priv->mutex);
-
ret = aqc_get_ctrl_data(priv);
if (ret < 0)
- goto unlock_and_return;
+ return ret;
switch (type) {
case AQC_BE16:
@@ -707,9 +731,6 @@ static int aqc_get_ctrl_val(struct aqc_data *priv, int offset, long *val, int ty
default:
ret = -EINVAL;
}
-
-unlock_and_return:
- mutex_unlock(&priv->mutex);
return ret;
}
@@ -717,11 +738,9 @@ static int aqc_set_ctrl_vals(struct aqc_data *priv, int *offsets, long *vals, in
{
int ret, i;
- mutex_lock(&priv->mutex);
-
ret = aqc_get_ctrl_data(priv);
if (ret < 0)
- goto unlock_and_return;
+ return ret;
for (i = 0; i < len; i++) {
switch (types[i]) {
@@ -732,18 +751,11 @@ static int aqc_set_ctrl_vals(struct aqc_data *priv, int *offsets, long *vals, in
priv->buffer[offsets[i]] = (u8)vals[i];
break;
default:
- ret = -EINVAL;
+ return -EINVAL;
}
}
- if (ret < 0)
- goto unlock_and_return;
-
- ret = aqc_send_ctrl_data(priv);
-
-unlock_and_return:
- mutex_unlock(&priv->mutex);
- return ret;
+ return aqc_send_ctrl_data(priv);
}
static int aqc_set_ctrl_val(struct aqc_data *priv, int offset, long val, int type)
@@ -818,7 +830,9 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
return 0444;
break;
case aquaero:
+ case octo:
case quadro:
+ case highflow:
/* Special case to support flow sensors */
if (channel < priv->num_fans + priv->num_flow_sensors)
return 0444;
@@ -830,9 +844,16 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
}
break;
case hwmon_fan_pulses:
- /* Special case for Quadro flow sensor */
- if (priv->kind == quadro && channel == priv->num_fans)
- return 0644;
+ /* Special case for Quadro/Octo flow sensor */
+ if (channel == priv->num_fans) {
+ switch (priv->kind) {
+ case quadro:
+ case octo:
+ return 0644;
+ default:
+ break;
+ }
+ }
break;
case hwmon_fan_min:
case hwmon_fan_max:
@@ -914,19 +935,20 @@ static int aqc_legacy_read(struct aqc_data *priv)
{
int ret, i, sensor_value;
- mutex_lock(&priv->mutex);
-
memset(priv->buffer, 0x00, priv->buffer_size);
ret = hid_hw_raw_request(priv->hdev, priv->status_report_id, priv->buffer,
priv->buffer_size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0)
- goto unlock_and_return;
+ return ret;
/* Temperature sensor readings */
for (i = 0; i < priv->num_temp_sensors; i++) {
sensor_value = get_unaligned_le16(priv->buffer + priv->temp_sensor_start_offset +
i * AQC_SENSOR_SIZE);
- priv->temp_input[i] = sensor_value * 10;
+ if (sensor_value == AQC_SENSOR_NA)
+ priv->temp_input[i] = -ENODATA;
+ else
+ priv->temp_input[i] = sensor_value * 10;
}
/* Special-case sensor readings */
@@ -962,15 +984,23 @@ static int aqc_legacy_read(struct aqc_data *priv)
sensor_value = get_unaligned_le16(priv->buffer + AQUASTREAMXT_FAN_VOLTAGE_OFFSET);
priv->voltage_input[1] = DIV_ROUND_CLOSEST(sensor_value * 1000, 63);
break;
+ case highflow:
+ /* Info provided with every report */
+ priv->serial_number[0] = get_unaligned_le16(priv->buffer +
+ priv->serial_number_start_offset);
+ priv->firmware_version =
+ get_unaligned_le16(priv->buffer + priv->firmware_version_offset);
+
+ /* Read flow speed */
+ priv->speed_input[0] = get_unaligned_le16(priv->buffer +
+ priv->flow_sensors_start_offset);
+ break;
default:
break;
}
priv->updated = jiffies;
-
-unlock_and_return:
- mutex_unlock(&priv->mutex);
- return ret;
+ return 0;
}
static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
@@ -1248,7 +1278,8 @@ static const struct hwmon_channel_info * const aqc_info[] = {
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_PULSES,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,
- HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_PULSES),
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_LABEL,
HWMON_P_INPUT | HWMON_P_LABEL,
@@ -1435,8 +1466,6 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
return 0;
}
-#ifdef CONFIG_DEBUG_FS
-
static int serial_number_show(struct seq_file *seqf, void *unused)
{
struct aqc_data *priv = seqf->private;
@@ -1486,14 +1515,6 @@ static void aqc_debugfs_init(struct aqc_data *priv)
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
}
-#else
-
-static void aqc_debugfs_init(struct aqc_data *priv)
-{
-}
-
-#endif
-
static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct aqc_data *priv;
@@ -1627,16 +1648,20 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->temp_sensor_start_offset = OCTO_SENSOR_START;
priv->num_virtual_temp_sensors = OCTO_NUM_VIRTUAL_SENSORS;
priv->virtual_temp_sensor_start_offset = OCTO_VIRTUAL_SENSORS_START;
+ priv->num_flow_sensors = OCTO_NUM_FLOW_SENSORS;
+ priv->flow_sensors_start_offset = OCTO_FLOW_SENSOR_OFFSET;
+
priv->temp_ctrl_offset = OCTO_TEMP_CTRL_OFFSET;
priv->buffer_size = OCTO_CTRL_REPORT_SIZE;
priv->ctrl_report_delay = CTRL_REPORT_DELAY;
+ priv->flow_pulses_ctrl_offset = OCTO_FLOW_PULSES_CTRL_OFFSET;
priv->power_cycle_count_offset = OCTO_POWER_CYCLES;
priv->temp_label = label_temp_sensors;
priv->virtual_temp_label = label_virtual_temp_sensors;
- priv->speed_label = label_fan_speed;
+ priv->speed_label = label_octo_speeds;
priv->power_label = label_fan_power;
priv->voltage_label = label_fan_voltage;
priv->current_label = label_fan_current;
@@ -1747,6 +1772,20 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->temp_label = label_poweradjust3_temp_sensors;
break;
+ case USB_PRODUCT_ID_HIGHFLOW:
+ priv->kind = highflow;
+
+ priv->num_fans = 0;
+
+ priv->num_temp_sensors = HIGHFLOW_NUM_SENSORS;
+ priv->temp_sensor_start_offset = HIGHFLOW_SENSOR_START;
+ priv->num_flow_sensors = HIGHFLOW_NUM_FLOW_SENSORS;
+ priv->flow_sensors_start_offset = HIGHFLOW_FLOW_SENSOR_OFFSET;
+ priv->buffer_size = HIGHFLOW_SENSOR_REPORT_SIZE;
+
+ priv->temp_label = label_highflow_temp;
+ priv->speed_label = label_highflow_speeds;
+ break;
default:
break;
}
@@ -1772,6 +1811,12 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->status_report_id = AQUASTREAMXT_STATUS_REPORT_ID;
break;
+ case highflow:
+ priv->serial_number_start_offset = HIGHFLOW_SERIAL_START;
+ priv->firmware_version_offset = HIGHFLOW_FIRMWARE_VERSION;
+
+ priv->status_report_id = HIGHFLOW_STATUS_REPORT_ID;
+ break;
default:
priv->serial_number_start_offset = AQC_SERIAL_START;
priv->firmware_version_offset = AQC_FIRMWARE_VERSION;
@@ -1802,8 +1847,6 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto fail_and_close;
}
- mutex_init(&priv->mutex);
-
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv,
&aqc_chip_info, NULL);
@@ -1846,6 +1889,7 @@ static const struct hid_device_id aqc_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_AQUASTREAMXT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_AQUASTREAMULT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_POWERADJUST3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_HIGHFLOW) },
{ }
};
diff --git a/drivers/hwmon/asb100.c b/drivers/hwmon/asb100.c
index 974521e9b6b4..14e7737866c2 100644
--- a/drivers/hwmon/asb100.c
+++ b/drivers/hwmon/asb100.c
@@ -213,7 +213,7 @@ static struct asb100_data *asb100_update_device(struct device *dev);
static void asb100_init_client(struct i2c_client *client);
static const struct i2c_device_id asb100_id[] = {
- { "asb100", 0 },
+ { "asb100" },
{ }
};
MODULE_DEVICE_TABLE(i2c, asb100_id);
diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c
new file mode 100644
index 000000000000..44e1ecba205d
--- /dev/null
+++ b/drivers/hwmon/aspeed-g6-pwm-tach.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Aspeed Technology Inc.
+ *
+ * PWM/TACH controller driver for Aspeed ast2600 SoCs.
+ * This drivers doesn't support earlier version of the IP.
+ *
+ * The hardware operates in time quantities of length
+ * Q := (DIV_L + 1) << DIV_H / input-clk
+ * The length of a PWM period is (DUTY_CYCLE_PERIOD + 1) * Q.
+ * The maximal value for DUTY_CYCLE_PERIOD is used here to provide
+ * a fine grained selection for the duty cycle.
+ *
+ * This driver uses DUTY_CYCLE_RISING_POINT = 0, so from the start of a
+ * period the output is active until DUTY_CYCLE_FALLING_POINT * Q. Note
+ * that if DUTY_CYCLE_RISING_POINT = DUTY_CYCLE_FALLING_POINT the output is
+ * always active.
+ *
+ * Register usage:
+ * PIN_ENABLE: When it is unset the pwm controller will emit inactive level to the external.
+ * Use to determine whether the PWM channel is enabled or disabled
+ * CLK_ENABLE: When it is unset the pwm controller will assert the duty counter reset and
+ * emit inactive level to the PIN_ENABLE mux after that the driver can still change the pwm period
+ * and duty and the value will apply when CLK_ENABLE be set again.
+ * Use to determine whether duty_cycle bigger than 0.
+ * PWM_ASPEED_CTRL_INVERSE: When it is toggled the output value will inverse immediately.
+ * PWM_ASPEED_DUTY_CYCLE_FALLING_POINT/PWM_ASPEED_DUTY_CYCLE_RISING_POINT: When these two
+ * values are equal it means the duty cycle = 100%.
+ *
+ * The glitch may generate at:
+ * - Enabled changing when the duty_cycle bigger than 0% and less than 100%.
+ * - Polarity changing when the duty_cycle bigger than 0% and less than 100%.
+ *
+ * Limitations:
+ * - When changing both duty cycle and period, we cannot prevent in
+ * software that the output might produce a period with mixed
+ * settings.
+ * - Disabling the PWM doesn't complete the current period.
+ *
+ * Improvements:
+ * - When only changing one of duty cycle or period, our pwm controller will not
+ * generate the glitch, the configure will change at next cycle of pwm.
+ * This improvement can disable/enable through PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/sysfs.h>
+
+/* The channel number of Aspeed pwm controller */
+#define PWM_ASPEED_NR_PWMS 16
+/* PWM Control Register */
+#define PWM_ASPEED_CTRL(ch) ((ch) * 0x10 + 0x00)
+#define PWM_ASPEED_CTRL_LOAD_SEL_RISING_AS_WDT BIT(19)
+#define PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE BIT(18)
+#define PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE BIT(17)
+#define PWM_ASPEED_CTRL_CLK_ENABLE BIT(16)
+#define PWM_ASPEED_CTRL_LEVEL_OUTPUT BIT(15)
+#define PWM_ASPEED_CTRL_INVERSE BIT(14)
+#define PWM_ASPEED_CTRL_OPEN_DRAIN_ENABLE BIT(13)
+#define PWM_ASPEED_CTRL_PIN_ENABLE BIT(12)
+#define PWM_ASPEED_CTRL_CLK_DIV_H GENMASK(11, 8)
+#define PWM_ASPEED_CTRL_CLK_DIV_L GENMASK(7, 0)
+
+/* PWM Duty Cycle Register */
+#define PWM_ASPEED_DUTY_CYCLE(ch) ((ch) * 0x10 + 0x04)
+#define PWM_ASPEED_DUTY_CYCLE_PERIOD GENMASK(31, 24)
+#define PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT GENMASK(23, 16)
+#define PWM_ASPEED_DUTY_CYCLE_FALLING_POINT GENMASK(15, 8)
+#define PWM_ASPEED_DUTY_CYCLE_RISING_POINT GENMASK(7, 0)
+
+/* PWM fixed value */
+#define PWM_ASPEED_FIXED_PERIOD FIELD_MAX(PWM_ASPEED_DUTY_CYCLE_PERIOD)
+
+/* The channel number of Aspeed tach controller */
+#define TACH_ASPEED_NR_TACHS 16
+/* TACH Control Register */
+#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08)
+#define TACH_ASPEED_IER BIT(31)
+#define TACH_ASPEED_INVERS_LIMIT BIT(30)
+#define TACH_ASPEED_LOOPBACK BIT(29)
+#define TACH_ASPEED_ENABLE BIT(28)
+#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26)
+#define TACH_ASPEED_DEBOUNCE_BIT 26
+#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24)
+#define TACH_ASPEED_IO_EDGE_BIT 24
+#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20)
+#define TACH_ASPEED_CLK_DIV_BIT 20
+#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0)
+/* [27:26] */
+#define DEBOUNCE_3_CLK 0x00
+#define DEBOUNCE_2_CLK 0x01
+#define DEBOUNCE_1_CLK 0x02
+#define DEBOUNCE_0_CLK 0x03
+/* [25:24] */
+#define F2F_EDGES 0x00
+#define R2R_EDGES 0x01
+#define BOTH_EDGES 0x02
+/* [23:20] */
+/* divisor = 4 to the nth power, n = register value */
+#define DEFAULT_TACH_DIV 1024
+#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1)
+
+/* TACH Status Register */
+#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C)
+
+/*PWM_TACH_STS */
+#define TACH_ASPEED_ISR BIT(31)
+#define TACH_ASPEED_PWM_OUT BIT(25)
+#define TACH_ASPEED_PWM_OEN BIT(24)
+#define TACH_ASPEED_DEB_INPUT BIT(23)
+#define TACH_ASPEED_RAW_INPUT BIT(22)
+#define TACH_ASPEED_VALUE_UPDATE BIT(21)
+#define TACH_ASPEED_FULL_MEASUREMENT BIT(20)
+#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0)
+/**********************************************************
+ * Software setting
+ *********************************************************/
+#define DEFAULT_FAN_PULSE_PR 2
+
+struct aspeed_pwm_tach_data {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *reset;
+ unsigned long clk_rate;
+ bool tach_present[TACH_ASPEED_NR_TACHS];
+ u32 tach_divisor;
+};
+
+static inline struct aspeed_pwm_tach_data *
+aspeed_pwm_chip_to_data(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
+ u32 hwpwm = pwm->hwpwm;
+ bool polarity, pin_en, clk_en;
+ u32 duty_pt, val;
+ u64 div_h, div_l, duty_cycle_period, dividend;
+
+ val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
+ polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val);
+ pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val);
+ clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val);
+ div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val);
+ div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val);
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val);
+ duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val);
+ /*
+ * This multiplication doesn't overflow, the upper bound is
+ * 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000
+ */
+ dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1)
+ << div_h;
+ state->period = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
+
+ if (clk_en && duty_pt) {
+ dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt
+ << div_h;
+ state->duty_cycle = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
+ } else {
+ state->duty_cycle = clk_en ? state->period : 0;
+ }
+ state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+ state->enabled = pin_en;
+ return 0;
+}
+
+static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
+ u32 hwpwm = pwm->hwpwm, duty_pt, val;
+ u64 div_h, div_l, divisor, expect_period;
+ bool clk_en;
+
+ expect_period = div64_u64(ULLONG_MAX, (u64)priv->clk_rate);
+ expect_period = min(expect_period, state->period);
+ dev_dbg(pwmchip_parent(chip), "expect period: %lldns, duty_cycle: %lldns",
+ expect_period, state->duty_cycle);
+ /*
+ * Pick the smallest value for div_h so that div_l can be the biggest
+ * which results in a finer resolution near the target period value.
+ */
+ divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) *
+ (FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1);
+ div_h = order_base_2(DIV64_U64_ROUND_UP(priv->clk_rate * expect_period, divisor));
+ if (div_h > 0xf)
+ div_h = 0xf;
+
+ divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h;
+ div_l = div64_u64(priv->clk_rate * expect_period, divisor);
+
+ if (div_l == 0)
+ return -ERANGE;
+
+ div_l -= 1;
+
+ if (div_l > 255)
+ div_l = 255;
+
+ dev_dbg(pwmchip_parent(chip), "clk source: %ld div_h %lld, div_l : %lld\n",
+ priv->clk_rate, div_h, div_l);
+ /* duty_pt = duty_cycle * (PERIOD + 1) / period */
+ duty_pt = div64_u64(state->duty_cycle * priv->clk_rate,
+ (u64)NSEC_PER_SEC * (div_l + 1) << div_h);
+ dev_dbg(pwmchip_parent(chip), "duty_cycle = %lld, duty_pt = %d\n",
+ state->duty_cycle, duty_pt);
+
+ /*
+ * Fixed DUTY_CYCLE_PERIOD to its max value to get a
+ * fine-grained resolution for duty_cycle at the expense of a
+ * coarser period resolution.
+ */
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ val &= ~PWM_ASPEED_DUTY_CYCLE_PERIOD;
+ val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_PERIOD,
+ PWM_ASPEED_FIXED_PERIOD);
+ writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+
+ if (duty_pt == 0) {
+ /* emit inactive level and assert the duty counter reset */
+ clk_en = 0;
+ } else {
+ clk_en = 1;
+ if (duty_pt >= (PWM_ASPEED_FIXED_PERIOD + 1))
+ duty_pt = 0;
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ val &= ~(PWM_ASPEED_DUTY_CYCLE_RISING_POINT |
+ PWM_ASPEED_DUTY_CYCLE_FALLING_POINT);
+ val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, duty_pt);
+ writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ }
+
+ val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
+ val &= ~(PWM_ASPEED_CTRL_CLK_DIV_H | PWM_ASPEED_CTRL_CLK_DIV_L |
+ PWM_ASPEED_CTRL_PIN_ENABLE | PWM_ASPEED_CTRL_CLK_ENABLE |
+ PWM_ASPEED_CTRL_INVERSE);
+ val |= FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_H, div_h) |
+ FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_L, div_l) |
+ FIELD_PREP(PWM_ASPEED_CTRL_PIN_ENABLE, state->enabled) |
+ FIELD_PREP(PWM_ASPEED_CTRL_CLK_ENABLE, clk_en) |
+ FIELD_PREP(PWM_ASPEED_CTRL_INVERSE, state->polarity);
+ writel(val, priv->base + PWM_ASPEED_CTRL(hwpwm));
+
+ return 0;
+}
+
+static const struct pwm_ops aspeed_pwm_ops = {
+ .apply = aspeed_pwm_apply,
+ .get_state = aspeed_pwm_get_state,
+};
+
+static void aspeed_tach_ch_enable(struct aspeed_pwm_tach_data *priv, u8 tach_ch,
+ bool enable)
+{
+ if (enable)
+ writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) |
+ TACH_ASPEED_ENABLE,
+ priv->base + TACH_ASPEED_CTRL(tach_ch));
+ else
+ writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) &
+ ~TACH_ASPEED_ENABLE,
+ priv->base + TACH_ASPEED_CTRL(tach_ch));
+}
+
+static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, u32 tach_val)
+{
+ u64 rpm;
+ u32 tach_div;
+
+ tach_div = tach_val * priv->tach_divisor * DEFAULT_FAN_PULSE_PR;
+
+ dev_dbg(priv->dev, "clk %ld, tach_val %d , tach_div %d\n",
+ priv->clk_rate, tach_val, tach_div);
+
+ rpm = (u64)priv->clk_rate * 60;
+ do_div(rpm, tach_div);
+
+ return (int)rpm;
+}
+
+static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tach_data *priv,
+ u8 fan_tach_ch)
+{
+ u32 val;
+
+ val = readl(priv->base + TACH_ASPEED_STS(fan_tach_ch));
+
+ if (!(val & TACH_ASPEED_FULL_MEASUREMENT))
+ return 0;
+ val = FIELD_GET(TACH_ASPEED_VALUE_MASK, val);
+ return aspeed_tach_val_to_rpm(priv, val);
+}
+
+static int aspeed_tach_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
+ u32 reg_val;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ *val = aspeed_get_fan_tach_ch_rpm(priv, channel);
+ break;
+ case hwmon_fan_div:
+ reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
+ reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val);
+ *val = BIT(reg_val << 1);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int aspeed_tach_hwmon_write(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
+ u32 reg_val;
+
+ switch (attr) {
+ case hwmon_fan_div:
+ if (!is_power_of_2(val) || (ilog2(val) % 2) ||
+ DIV_TO_REG(val) > 0xb)
+ return -EINVAL;
+ priv->tach_divisor = val;
+ reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
+ reg_val &= ~TACH_ASPEED_CLK_DIV_T_MASK;
+ reg_val |= FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
+ DIV_TO_REG(priv->tach_divisor));
+ writel(reg_val, priv->base + TACH_ASPEED_CTRL(channel));
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t aspeed_tach_dev_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct aspeed_pwm_tach_data *priv = drvdata;
+
+ if (!priv->tach_present[channel])
+ return 0;
+ switch (attr) {
+ case hwmon_fan_input:
+ return 0444;
+ case hwmon_fan_div:
+ return 0644;
+ }
+ return 0;
+}
+
+static const struct hwmon_ops aspeed_tach_ops = {
+ .is_visible = aspeed_tach_dev_is_visible,
+ .read = aspeed_tach_hwmon_read,
+ .write = aspeed_tach_hwmon_write,
+};
+
+static const struct hwmon_channel_info *aspeed_tach_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV),
+ NULL
+};
+
+static const struct hwmon_chip_info aspeed_tach_chip_info = {
+ .ops = &aspeed_tach_ops,
+ .info = aspeed_tach_info,
+};
+
+static void aspeed_present_fan_tach(struct aspeed_pwm_tach_data *priv, u8 *tach_ch, int count)
+{
+ u8 ch, index;
+ u32 val;
+
+ for (index = 0; index < count; index++) {
+ ch = tach_ch[index];
+ priv->tach_present[ch] = true;
+ priv->tach_divisor = DEFAULT_TACH_DIV;
+
+ val = readl(priv->base + TACH_ASPEED_CTRL(ch));
+ val &= ~(TACH_ASPEED_INVERS_LIMIT | TACH_ASPEED_DEBOUNCE_MASK |
+ TACH_ASPEED_IO_EDGE_MASK | TACH_ASPEED_CLK_DIV_T_MASK |
+ TACH_ASPEED_THRESHOLD_MASK);
+ val |= (DEBOUNCE_3_CLK << TACH_ASPEED_DEBOUNCE_BIT) |
+ F2F_EDGES |
+ FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
+ DIV_TO_REG(priv->tach_divisor));
+ writel(val, priv->base + TACH_ASPEED_CTRL(ch));
+
+ aspeed_tach_ch_enable(priv, ch, true);
+ }
+}
+
+static int aspeed_create_fan_monitor(struct device *dev,
+ struct device_node *child,
+ struct aspeed_pwm_tach_data *priv)
+{
+ int ret, count;
+ u8 *tach_ch;
+
+ count = of_property_count_u8_elems(child, "tach-ch");
+ if (count < 1)
+ return -EINVAL;
+ tach_ch = devm_kcalloc(dev, count, sizeof(*tach_ch), GFP_KERNEL);
+ if (!tach_ch)
+ return -ENOMEM;
+ ret = of_property_read_u8_array(child, "tach-ch", tach_ch, count);
+ if (ret)
+ return ret;
+
+ aspeed_present_fan_tach(priv, tach_ch, count);
+
+ return 0;
+}
+
+static void aspeed_pwm_tach_reset_assert(void *data)
+{
+ struct reset_control *rst = data;
+
+ reset_control_assert(rst);
+}
+
+static int aspeed_pwm_tach_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev, *hwmon;
+ int ret;
+ struct aspeed_pwm_tach_data *priv;
+ struct pwm_chip *chip;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = dev;
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "Couldn't get clock\n");
+ priv->clk_rate = clk_get_rate(priv->clk);
+ priv->reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->reset))
+ return dev_err_probe(dev, PTR_ERR(priv->reset),
+ "Couldn't get reset control\n");
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Couldn't deassert reset control\n");
+ ret = devm_add_action_or_reset(dev, aspeed_pwm_tach_reset_assert,
+ priv->reset);
+ if (ret)
+ return ret;
+
+ chip = devm_pwmchip_alloc(dev, PWM_ASPEED_NR_PWMS, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ pwmchip_set_drvdata(chip, priv);
+ chip->ops = &aspeed_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ for_each_child_of_node_scoped(dev->of_node, child) {
+ ret = aspeed_create_fan_monitor(dev, child, priv);
+ if (ret) {
+ dev_warn(dev, "Failed to create fan %d", ret);
+ return 0;
+ }
+ }
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "aspeed_tach", priv,
+ &aspeed_tach_chip_info, NULL);
+ ret = PTR_ERR_OR_ZERO(hwmon);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register hwmon device\n");
+
+ of_platform_populate(dev->of_node, NULL, NULL, dev);
+
+ return 0;
+}
+
+static void aspeed_pwm_tach_remove(struct platform_device *pdev)
+{
+ struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev);
+
+ reset_control_assert(priv->reset);
+}
+
+static const struct of_device_id aspeed_pwm_tach_match[] = {
+ {
+ .compatible = "aspeed,ast2600-pwm-tach",
+ },
+ {
+ .compatible = "aspeed,ast2700-pwm-tach",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match);
+
+static struct platform_driver aspeed_pwm_tach_driver = {
+ .probe = aspeed_pwm_tach_probe,
+ .remove = aspeed_pwm_tach_remove,
+ .driver = {
+ .name = "aspeed-g6-pwm-tach",
+ .of_match_table = aspeed_pwm_tach_match,
+ },
+};
+
+module_platform_driver(aspeed_pwm_tach_driver);
+
+MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
+MODULE_DESCRIPTION("Aspeed ast2600 PWM and Fan Tach device driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c
index 997df4b40509..aa159bf158a3 100644
--- a/drivers/hwmon/aspeed-pwm-tacho.c
+++ b/drivers/hwmon/aspeed-pwm-tacho.c
@@ -166,6 +166,8 @@
#define MAX_CDEV_NAME_LEN 16
+#define MAX_ASPEED_FAN_TACH_CHANNELS 16
+
struct aspeed_cooling_device {
char name[16];
struct aspeed_pwm_tacho_data *priv;
@@ -181,7 +183,7 @@ struct aspeed_pwm_tacho_data {
struct reset_control *rst;
unsigned long clk_freq;
bool pwm_present[8];
- bool fan_tach_present[16];
+ bool fan_tach_present[MAX_ASPEED_FAN_TACH_CHANNELS];
u8 type_pwm_clock_unit[3];
u8 type_pwm_clock_division_h[3];
u8 type_pwm_clock_division_l[3];
@@ -190,9 +192,11 @@ struct aspeed_pwm_tacho_data {
u16 type_fan_tach_unit[3];
u8 pwm_port_type[8];
u8 pwm_port_fan_ctrl[8];
- u8 fan_tach_ch_source[16];
+ u8 fan_tach_ch_source[MAX_ASPEED_FAN_TACH_CHANNELS];
struct aspeed_cooling_device *cdev[8];
const struct attribute_group *groups[3];
+ /* protects access to shared ASPEED_PTCR_RESULT */
+ struct mutex tach_lock;
};
enum type { TYPEM, TYPEN, TYPEO };
@@ -527,6 +531,8 @@ static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tacho_data *priv,
u8 fan_tach_ch_source, type, mode, both;
int ret;
+ mutex_lock(&priv->tach_lock);
+
regmap_write(priv->regmap, ASPEED_PTCR_TRIGGER, 0);
regmap_write(priv->regmap, ASPEED_PTCR_TRIGGER, 0x1 << fan_tach_ch);
@@ -544,6 +550,8 @@ static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tacho_data *priv,
ASPEED_RPM_STATUS_SLEEP_USEC,
usec);
+ mutex_unlock(&priv->tach_lock);
+
/* return -ETIMEDOUT if we didn't get an answer. */
if (ret)
return ret;
@@ -737,20 +745,27 @@ static void aspeed_create_pwm_port(struct aspeed_pwm_tacho_data *priv,
aspeed_set_pwm_port_fan_ctrl(priv, pwm_port, INIT_FAN_CTRL);
}
-static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv,
- u8 *fan_tach_ch,
- int count,
- u8 pwm_source)
+static int aspeed_create_fan_tach_channel(struct device *dev,
+ struct aspeed_pwm_tacho_data *priv,
+ u8 *fan_tach_ch,
+ int count,
+ u8 pwm_source)
{
u8 val, index;
for (val = 0; val < count; val++) {
index = fan_tach_ch[val];
+ if (index >= MAX_ASPEED_FAN_TACH_CHANNELS) {
+ dev_err(dev, "Invalid Fan Tach input channel %u\n.", index);
+ return -EINVAL;
+ }
aspeed_set_fan_tach_ch_enable(priv->regmap, index, true);
priv->fan_tach_present[index] = true;
priv->fan_tach_ch_source[index] = pwm_source;
aspeed_set_fan_tach_ch_source(priv->regmap, index, pwm_source);
}
+
+ return 0;
}
static int
@@ -874,7 +889,10 @@ static int aspeed_create_fan(struct device *dev,
fan_tach_ch, count);
if (ret)
return ret;
- aspeed_create_fan_tach_channel(priv, fan_tach_ch, count, pwm_port);
+
+ ret = aspeed_create_fan_tach_channel(dev, priv, fan_tach_ch, count, pwm_port);
+ if (ret)
+ return ret;
return 0;
}
@@ -889,7 +907,7 @@ static void aspeed_pwm_tacho_remove(void *data)
static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np, *child;
+ struct device_node *np;
struct aspeed_pwm_tacho_data *priv;
void __iomem *regs;
struct device *hwmon;
@@ -903,6 +921,7 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
+ mutex_init(&priv->tach_lock);
priv->regmap = devm_regmap_init(dev, NULL, (__force void *)regs,
&aspeed_pwm_tacho_regmap_config);
if (IS_ERR(priv->regmap))
@@ -932,12 +951,10 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
aspeed_create_type(priv);
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
ret = aspeed_create_fan(dev, child, priv);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
}
priv->groups[0] = &pwm_dev_group;
diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c
index 51f9c2db403e..61b18b88ee8f 100644
--- a/drivers/hwmon/asus-ec-sensors.c
+++ b/drivers/hwmon/asus-ec-sensors.c
@@ -34,7 +34,7 @@
#include <linux/sort.h>
#include <linux/units.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
static char *mutex_path_override;
@@ -49,15 +49,19 @@ static char *mutex_path_override;
*/
#define ASUS_EC_MAX_BANK 3
-#define ACPI_LOCK_DELAY_MS 500
+#define ACPI_LOCK_DELAY_MS 800
/* ACPI mutex for locking access to the EC for the firmware */
#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX"
#define ASUS_HW_ACCESS_MUTEX_RMTW_ASMX "\\RMTW.ASMX"
+#define ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0 "\\_SB.PC00.LPCB.SIO1.MUT0"
+
#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0 "\\_SB_.PCI0.SBRG.SIO1.MUT0"
+#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0 "\\_SB_.PCI0.LPCB.SIO1.MUT0"
+
#define MAX_IDENTICAL_BOARD_VARIATIONS 3
/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */
@@ -109,16 +113,28 @@ enum ec_sensors {
ec_sensor_temp_t_sensor,
/* VRM temperature [℃] */
ec_sensor_temp_vrm,
+ /* VRM east (right) temperature [℃] */
+ ec_sensor_temp_vrme,
+ /* VRM west (left) temperature [℃] */
+ ec_sensor_temp_vrmw,
/* CPU Core voltage [mV] */
ec_sensor_in_cpu_core,
/* CPU_Opt fan [RPM] */
ec_sensor_fan_cpu_opt,
/* VRM heat sink fan [RPM] */
ec_sensor_fan_vrm_hs,
+ /* VRM east (right) heat sink fan [RPM] */
+ ec_sensor_fan_vrme_hs,
+ /* VRM west (left) heat sink fan [RPM] */
+ ec_sensor_fan_vrmw_hs,
/* Chipset fan [RPM] */
ec_sensor_fan_chipset,
/* Water flow sensor reading [RPM] */
ec_sensor_fan_water_flow,
+ /* USB4 fan [RPM] */
+ ec_sensor_fan_usb4,
+ /* M.2 fan [RPM] */
+ ec_sensor_fan_m2,
/* CPU current [A] */
ec_sensor_curr_cpu,
/* "Water_In" temperature sensor reading [℃] */
@@ -145,11 +161,17 @@ enum ec_sensors {
#define SENSOR_TEMP_MB BIT(ec_sensor_temp_mb)
#define SENSOR_TEMP_T_SENSOR BIT(ec_sensor_temp_t_sensor)
#define SENSOR_TEMP_VRM BIT(ec_sensor_temp_vrm)
+#define SENSOR_TEMP_VRME BIT(ec_sensor_temp_vrme)
+#define SENSOR_TEMP_VRMW BIT(ec_sensor_temp_vrmw)
#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core)
#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt)
#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs)
+#define SENSOR_FAN_VRME_HS BIT(ec_sensor_fan_vrme_hs)
+#define SENSOR_FAN_VRMW_HS BIT(ec_sensor_fan_vrmw_hs)
#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset)
#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow)
+#define SENSOR_FAN_USB4 BIT(ec_sensor_fan_usb4)
+#define SENSOR_FAN_M2 BIT(ec_sensor_fan_m2)
#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu)
#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in)
#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out)
@@ -165,11 +187,21 @@ enum board_family {
family_amd_400_series,
family_amd_500_series,
family_amd_600_series,
+ family_amd_800_series,
+ family_amd_trx_50,
+ family_amd_wrx_90,
+ family_intel_200_series,
family_intel_300_series,
- family_intel_600_series
+ family_intel_400_series,
+ family_intel_600_series,
+ family_intel_700_series
};
-/* All the known sensors for ASUS EC controllers */
+/*
+ * All the known sensors for ASUS EC controllers. These arrays have to be sorted
+ * by the full ((bank << 8) + index) register index (see asus_ec_block_read() as
+ * to why).
+ */
static const struct ec_sensor_info sensors_family_amd_400[] = {
[ec_sensor_temp_chipset] =
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
@@ -183,10 +215,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = {
EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
[ec_sensor_in_cpu_core] =
EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2),
- [ec_sensor_fan_cpu_opt] =
- EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
[ec_sensor_fan_vrm_hs] =
EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
[ec_sensor_fan_chipset] =
/* no chipset fans in this generation */
EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00),
@@ -194,10 +226,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = {
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4),
[ec_sensor_curr_cpu] =
EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4),
- [ec_sensor_temp_water_in] =
- EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d),
[ec_sensor_temp_water_out] =
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b),
+ [ec_sensor_temp_water_in] =
+ EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d),
};
static const struct ec_sensor_info sensors_family_amd_500[] = {
@@ -239,17 +271,76 @@ static const struct ec_sensor_info sensors_family_amd_500[] = {
static const struct ec_sensor_info sensors_family_amd_600[] = {
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x30),
- [ec_sensor_temp_cpu_package] = EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
+ [ec_sensor_temp_cpu_package] =
+ EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
[ec_sensor_temp_mb] =
EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x32),
[ec_sensor_temp_vrm] =
EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x36),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
[ec_sensor_temp_water_in] =
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
[ec_sensor_temp_water_out] =
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
};
+static const struct ec_sensor_info sensors_family_amd_800[] = {
+ [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x30),
+ [ec_sensor_temp_cpu_package] =
+ EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
+ [ec_sensor_temp_mb] =
+ EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x32),
+ [ec_sensor_temp_vrm] =
+ EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x36),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
+};
+
+static const struct ec_sensor_info sensors_family_amd_trx_50[] = {
+ [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x30),
+ [ec_sensor_temp_cpu_package] =
+ EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
+ [ec_sensor_temp_vrme] = EC_SENSOR("VRM_E", hwmon_temp, 1, 0x00, 0x33),
+ [ec_sensor_temp_vrmw] = EC_SENSOR("VRM_W", hwmon_temp, 1, 0x00, 0x34),
+ [ec_sensor_fan_cpu_opt] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
+ [ec_sensor_fan_vrmw_hs] = EC_SENSOR("VRM_E HS", hwmon_fan, 2, 0x00, 0xb4),
+ [ec_sensor_fan_vrme_hs] = EC_SENSOR("VRM_W HS", hwmon_fan, 2, 0x00, 0xbc),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04),
+};
+
+static const struct ec_sensor_info sensors_family_amd_wrx_90[] = {
+ [ec_sensor_temp_cpu_package] =
+ EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
+ [ec_sensor_fan_vrmw_hs] =
+ EC_SENSOR("VRMW HS", hwmon_fan, 2, 0x00, 0xb4),
+ [ec_sensor_fan_usb4] = EC_SENSOR("USB4", hwmon_fan, 2, 0x00, 0xb6),
+ [ec_sensor_fan_vrme_hs] =
+ EC_SENSOR("VRME HS", hwmon_fan, 2, 0x00, 0xbc),
+ [ec_sensor_fan_m2] = EC_SENSOR("M.2", hwmon_fan, 2, 0x00, 0xbe),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04),
+};
+
+static const struct ec_sensor_info sensors_family_intel_200[] = {
+ [ec_sensor_temp_chipset] =
+ EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
+ [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
+ [ec_sensor_temp_mb] =
+ EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
+};
+
static const struct ec_sensor_info sensors_family_intel_300[] = {
[ec_sensor_temp_chipset] =
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
@@ -270,10 +361,42 @@ static const struct ec_sensor_info sensors_family_intel_300[] = {
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
};
+static const struct ec_sensor_info sensors_family_intel_400[] = {
+ [ec_sensor_temp_chipset] =
+ EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
+ [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
+ [ec_sensor_temp_mb] =
+ EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
+ [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
+ [ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
+};
+
static const struct ec_sensor_info sensors_family_intel_600[] = {
[ec_sensor_temp_t_sensor] =
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
+ [ec_sensor_fan_water_flow] =
+ EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe),
+ [ec_sensor_temp_water_in] =
+ EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
+ [ec_sensor_temp_water_out] =
+ EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
+ [ec_sensor_temp_water_block_in] =
+ EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02),
+};
+
+static const struct ec_sensor_info sensors_family_intel_700[] = {
+ [ec_sensor_temp_t_sensor] =
+ EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x09),
+ [ec_sensor_temp_t_sensor_2] =
+ EC_SENSOR("T_Sensor 2", hwmon_temp, 1, 0x01, 0x05),
+ [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
+ [ec_sensor_fan_cpu_opt] =
+ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
};
/* Shortcuts for common combinations */
@@ -296,6 +419,77 @@ struct ec_board_info {
enum board_family family;
};
+static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
+ SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
+ SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_amd_500_series,
+};
+
+static const struct ec_board_info board_info_crosshair_viii_hero = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
+ SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
+ SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
+ SENSOR_IN_CPU_CORE,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_amd_500_series,
+};
+
+static const struct ec_board_info board_info_crosshair_viii_impact = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
+ SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
+ SENSOR_IN_CPU_CORE,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_amd_500_series,
+};
+
+static const struct ec_board_info board_info_crosshair_x670e_gene = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_crosshair_x670e_hero = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
+ SENSOR_SET_TEMP_WATER,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_maximus_vi_hero = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
+ SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_intel_300_series,
+};
+
+static const struct ec_board_info board_info_maximus_xi_hero = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
+ SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_intel_300_series,
+};
+
+static const struct ec_board_info board_info_maximus_z690_formula = {
+ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
+ SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
+ .family = family_intel_600_series,
+};
+
static const struct ec_board_info board_info_prime_x470_pro = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
@@ -312,12 +506,19 @@ static const struct ec_board_info board_info_prime_x570_pro = {
.family = family_amd_500_series,
};
-static const struct ec_board_info board_info_pro_art_x570_creator_wifi = {
- .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
- SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
- SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
- .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
- .family = family_amd_500_series,
+static const struct ec_board_info board_info_prime_x670e_pro_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
+ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_prime_z270_a = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0,
+ .family = family_intel_200_series,
};
static const struct ec_board_info board_info_pro_art_b550_creator = {
@@ -328,57 +529,52 @@ static const struct ec_board_info board_info_pro_art_b550_creator = {
.family = family_amd_500_series,
};
-static const struct ec_board_info board_info_pro_ws_x570_ace = {
+static const struct ec_board_info board_info_pro_art_x570_creator_wifi = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
- SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET |
+ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
-static const struct ec_board_info board_info_crosshair_x670e_hero = {
+static const struct ec_board_info board_info_pro_art_x670E_creator_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
- SENSOR_SET_TEMP_WATER,
+ SENSOR_TEMP_T_SENSOR,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
-static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
- .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
- SENSOR_TEMP_T_SENSOR |
- SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
- SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
- SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
- .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
- .family = family_amd_500_series,
+static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
+ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_800_series,
};
-static const struct ec_board_info board_info_crosshair_viii_hero = {
- .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
- SENSOR_TEMP_T_SENSOR |
- SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
- SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
- SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
- SENSOR_IN_CPU_CORE,
- .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
- .family = family_amd_500_series,
+static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = {
+ /* Board also has a nct6798 */
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME |
+ SENSOR_TEMP_VRMW | SENSOR_FAN_CPU_OPT | SENSOR_FAN_VRME_HS |
+ SENSOR_FAN_VRMW_HS | SENSOR_TEMP_T_SENSOR,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
+ .family = family_amd_trx_50,
};
-static const struct ec_board_info board_info_maximus_xi_hero = {
- .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
- SENSOR_TEMP_T_SENSOR |
- SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
- SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
- .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
- .family = family_intel_300_series,
+static const struct ec_board_info board_info_pro_ws_wrx90e_sage_se = {
+ /* Board also has a nct6798 with 7 more fans and temperatures */
+ .sensors = SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR |
+ SENSOR_FAN_CPU_OPT | SENSOR_FAN_USB4 | SENSOR_FAN_M2 |
+ SENSOR_FAN_VRME_HS | SENSOR_FAN_VRMW_HS,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
+ .family = family_amd_wrx_90,
};
-static const struct ec_board_info board_info_crosshair_viii_impact = {
- .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
- SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
- SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
- SENSOR_IN_CPU_CORE,
+static const struct ec_board_info board_info_pro_ws_x570_ace = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
+ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET |
+ SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
@@ -400,9 +596,31 @@ static const struct ec_board_info board_info_strix_b550_i_gaming = {
.family = family_amd_500_series,
};
-static const struct ec_board_info board_info_strix_x570_e_gaming = {
+static const struct ec_board_info board_info_strix_b650e_i_gaming = {
+ .sensors = SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR |
+ SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_IN_CPU_CORE,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_800_series,
+};
+
+static const struct ec_board_info board_info_strix_x470_i_gaming = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
+ SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_amd_400_series,
+};
+
+static const struct ec_board_info board_info_strix_x570_e_gaming = {
+ .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
+ SENSOR_TEMP_T_SENSOR |
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
@@ -433,6 +651,50 @@ static const struct ec_board_info board_info_strix_x570_i_gaming = {
.family = family_amd_500_series,
};
+static const struct ec_board_info board_info_strix_x670e_e_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
+static const struct ec_board_info board_info_strix_x870_f_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_800_series,
+};
+
+static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_800_series,
+};
+
+static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
+ SENSOR_FAN_CPU_OPT,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_800_series,
+};
+
+static const struct ec_board_info board_info_strix_x870e_h_gaming_wifi7 = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR |
+ SENSOR_FAN_CPU_OPT,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
+ .family = family_amd_800_series,
+};
+
static const struct ec_board_info board_info_strix_z390_f_gaming = {
.sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR |
@@ -441,12 +703,53 @@ static const struct ec_board_info board_info_strix_z390_f_gaming = {
.family = family_intel_300_series,
};
+static const struct ec_board_info board_info_strix_z490_f_gaming = {
+ .sensors = SENSOR_TEMP_CHIPSET |
+ SENSOR_TEMP_CPU |
+ SENSOR_TEMP_MB |
+ SENSOR_TEMP_T_SENSOR |
+ SENSOR_TEMP_VRM |
+ SENSOR_FAN_CPU_OPT |
+ SENSOR_FAN_VRM_HS,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
+ .family = family_intel_400_series,
+};
+
static const struct ec_board_info board_info_strix_z690_a_gaming_wifi_d4 = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
.family = family_intel_600_series,
};
+static const struct ec_board_info board_info_strix_z690_e_gaming_wifi = {
+ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
+ .family = family_intel_600_series,
+};
+
+static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = {
+ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
+ SENSOR_FAN_CPU_OPT,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
+ .family = family_intel_700_series,
+};
+
+static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = {
+ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 |
+ SENSOR_TEMP_VRM,
+ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
+ .family = family_intel_700_series,
+};
+
+static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
+ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
+ SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
+ SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
+ SENSOR_FAN_CPU_OPT,
+ .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
+ .family = family_amd_600_series,
+};
+
static const struct ec_board_info board_info_zenith_ii_extreme = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
@@ -470,14 +773,28 @@ static const struct ec_board_info board_info_zenith_ii_extreme = {
}
static const struct dmi_system_id dmi_table[] = {
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("MAXIMUS VI HERO",
+ &board_info_maximus_vi_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO",
&board_info_prime_x470_pro),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO",
&board_info_prime_x570_pro),
- DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI",
- &board_info_pro_art_x570_creator_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X670E-PRO WIFI",
+ &board_info_prime_x670e_pro_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z270-A",
+ &board_info_prime_z270_a),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR",
&board_info_pro_art_b550_creator),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI",
+ &board_info_pro_art_x570_creator_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X670E-CREATOR WIFI",
+ &board_info_pro_art_x670E_creator_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI",
+ &board_info_pro_art_x870E_creator_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI",
+ &board_info_pro_ws_trx50_sage_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS WRX90E-SAGE SE",
+ &board_info_pro_ws_wrx90e_sage_se),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE",
&board_info_pro_ws_x570_ace),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO",
@@ -488,18 +805,28 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_crosshair_viii_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO (WI-FI)",
&board_info_crosshair_viii_hero),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
+ &board_info_crosshair_viii_impact),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE",
+ &board_info_crosshair_x670e_gene),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
&board_info_crosshair_x670e_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO",
&board_info_maximus_xi_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)",
&board_info_maximus_xi_hero),
- DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
- &board_info_crosshair_viii_impact),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA",
+ &board_info_maximus_z690_formula),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
&board_info_strix_b550_e_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING",
&board_info_strix_b550_i_gaming),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B650E-I GAMING WIFI",
+ &board_info_strix_b650e_i_gaming),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI",
+ &board_info_strix_b850_i_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING",
+ &board_info_strix_x470_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING",
&board_info_strix_x570_e_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING WIFI II",
@@ -508,14 +835,38 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_strix_x570_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING",
&board_info_strix_x570_i_gaming),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-E GAMING WIFI",
+ &board_info_strix_x670e_e_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI",
+ &board_info_strix_x670e_i_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-F GAMING WIFI",
+ &board_info_strix_x870_f_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI",
+ &board_info_strix_x870_i_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI",
+ &board_info_strix_x870e_e_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7",
+ &board_info_strix_x870e_h_gaming_wifi7),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING",
&board_info_strix_z390_f_gaming),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING",
+ &board_info_strix_z490_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4",
&board_info_strix_z690_a_gaming_wifi_d4),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-E GAMING WIFI",
+ &board_info_strix_z690_e_gaming_wifi),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II",
+ &board_info_strix_z790_e_gaming_wifi_ii),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI",
+ &board_info_strix_z790_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME",
&board_info_zenith_ii_extreme),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME ALPHA",
&board_info_zenith_ii_extreme),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS",
+ &board_info_tuf_gaming_x670e_plus),
+ DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS WIFI",
+ &board_info_tuf_gaming_x670e_plus),
{},
};
@@ -888,6 +1239,10 @@ static int asus_ec_hwmon_read_string(struct device *dev,
{
struct ec_sensors_data *state = dev_get_drvdata(dev);
int sensor_index = find_ec_sensor_index(state, type, channel);
+
+ if (sensor_index < 0)
+ return sensor_index;
+
*str = get_sensor_info(state, sensor_index)->label;
return 0;
@@ -976,12 +1331,30 @@ static int asus_ec_probe(struct platform_device *pdev)
case family_amd_600_series:
ec_data->sensors_info = sensors_family_amd_600;
break;
+ case family_amd_800_series:
+ ec_data->sensors_info = sensors_family_amd_800;
+ break;
+ case family_amd_trx_50:
+ ec_data->sensors_info = sensors_family_amd_trx_50;
+ break;
+ case family_amd_wrx_90:
+ ec_data->sensors_info = sensors_family_amd_wrx_90;
+ break;
+ case family_intel_200_series:
+ ec_data->sensors_info = sensors_family_intel_200;
+ break;
case family_intel_300_series:
ec_data->sensors_info = sensors_family_intel_300;
break;
+ case family_intel_400_series:
+ ec_data->sensors_info = sensors_family_intel_400;
+ break;
case family_intel_600_series:
ec_data->sensors_info = sensors_family_intel_600;
break;
+ case family_intel_700_series:
+ ec_data->sensors_info = sensors_family_intel_700;
+ break;
default:
dev_err(dev, "Unknown board family: %d",
ec_data->board_info->family);
diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c
index d778a2aaefec..c80350e499e9 100644
--- a/drivers/hwmon/asus_atk0110.c
+++ b/drivers/hwmon/asus_atk0110.c
@@ -17,6 +17,7 @@
#include <linux/jiffies.h>
#include <linux/err.h>
#include <linux/acpi.h>
+#include <linux/string_choices.h>
#define ATK_HID "ATK0110"
@@ -441,7 +442,7 @@ static void atk_print_sensor(struct atk_data *data, union acpi_object *obj)
flags->integer.value,
name->string.pointer,
limit1->integer.value, limit2->integer.value,
- enable->integer.value ? "enabled" : "disabled");
+ str_enabled_disabled(enable->integer.value));
#endif
}
@@ -783,7 +784,6 @@ static const struct file_operations atk_debugfs_ggrp_fops = {
.read = atk_debugfs_ggrp_read,
.open = atk_debugfs_ggrp_open,
.release = atk_debugfs_ggrp_release,
- .llseek = no_llseek,
};
static void atk_debugfs_init(struct atk_data *data)
@@ -1075,8 +1075,7 @@ static int atk_ec_enabled(struct atk_data *data)
err = -EIO;
} else {
err = (buf->value != 0);
- dev_dbg(dev, "EC is %sabled\n",
- err ? "en" : "dis");
+ dev_dbg(dev, "EC is %s\n", str_enabled_disabled(err));
}
ACPI_FREE(obj);
@@ -1097,18 +1096,15 @@ static int atk_ec_ctl(struct atk_data *data, int enable)
obj = atk_sitm(data, &sitm);
if (IS_ERR(obj)) {
- dev_err(dev, "Failed to %sable the EC\n",
- enable ? "en" : "dis");
+ dev_err(dev, "Failed to %s the EC\n", str_enable_disable(enable));
return PTR_ERR(obj);
}
ec_ret = (struct atk_acpi_ret_buffer *)obj->buffer.pointer;
if (ec_ret->flags == 0) {
- dev_err(dev, "Failed to %sable the EC\n",
- enable ? "en" : "dis");
+ dev_err(dev, "Failed to %s the EC\n", str_enable_disable(enable));
err = -EIO;
} else {
- dev_info(dev, "EC %sabled\n",
- enable ? "en" : "dis");
+ dev_info(dev, "EC %s\n", str_enabled_disabled(enable));
}
ACPI_FREE(obj);
@@ -1389,4 +1385,5 @@ static void __exit atk0110_exit(void)
module_init(atk0110_init);
module_exit(atk0110_exit);
+MODULE_DESCRIPTION("ASUS ATK0110 driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c
new file mode 100644
index 000000000000..10a1f5aca988
--- /dev/null
+++ b/drivers/hwmon/asus_rog_ryujin.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for Asus ROG Ryujin II 360 AIO cooler.
+ *
+ * Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/unaligned.h>
+
+#define DRIVER_NAME "asus_rog_ryujin"
+
+#define USB_VENDOR_ID_ASUS_ROG 0x0b05
+#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
+
+#define STATUS_VALIDITY 1500 /* ms */
+#define MAX_REPORT_LENGTH 65
+
+/* Cooler status report offsets */
+#define RYUJIN_TEMP_SENSOR_1 3
+#define RYUJIN_TEMP_SENSOR_2 4
+#define RYUJIN_PUMP_SPEED 5
+#define RYUJIN_INTERNAL_FAN_SPEED 7
+
+/* Cooler duty report offsets */
+#define RYUJIN_PUMP_DUTY 4
+#define RYUJIN_INTERNAL_FAN_DUTY 5
+
+/* Controller status (speeds) report offsets */
+#define RYUJIN_CONTROLLER_SPEED_1 5
+#define RYUJIN_CONTROLLER_SPEED_2 7
+#define RYUJIN_CONTROLLER_SPEED_3 9
+#define RYUJIN_CONTROLLER_SPEED_4 3
+
+/* Controller duty report offsets */
+#define RYUJIN_CONTROLLER_DUTY 4
+
+/* Control commands and their inner offsets */
+#define RYUJIN_CMD_PREFIX 0xEC
+
+static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 };
+static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A };
+static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 };
+static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 };
+
+#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3
+#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4
+static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };
+
+#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4
+static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };
+
+/* Command lengths */
+#define GET_CMD_LENGTH 2 /* Same length for all get commands */
+#define SET_CMD_LENGTH 5 /* Same length for all set commands */
+
+/* Command response headers */
+#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE 0x19
+#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A
+#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20
+#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21
+
+static const char *const rog_ryujin_temp_label[] = {
+ "Coolant temp"
+};
+
+static const char *const rog_ryujin_speed_label[] = {
+ "Pump speed",
+ "Internal fan speed",
+ "Controller fan 1 speed",
+ "Controller fan 2 speed",
+ "Controller fan 3 speed",
+ "Controller fan 4 speed",
+};
+
+struct rog_ryujin_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ /* For reinitializing the completions below */
+ spinlock_t status_report_request_lock;
+ struct completion cooler_status_received;
+ struct completion controller_status_received;
+ struct completion cooler_duty_received;
+ struct completion controller_duty_received;
+ struct completion cooler_duty_set;
+ struct completion controller_duty_set;
+
+ /* Sensor data */
+ s32 temp_input[1];
+ u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */
+ u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */
+
+ u8 *buffer;
+ unsigned long updated; /* jiffies */
+};
+
+static int rog_ryujin_percent_to_pwm(u16 val)
+{
+ return DIV_ROUND_CLOSEST(val * 255, 100);
+}
+
+static int rog_ryujin_pwm_to_percent(long val)
+{
+ return DIV_ROUND_CLOSEST(val * 100, 255);
+}
+
+static umode_t rog_ryujin_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_label:
+ case hwmon_fan_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Writes the command to the device with the rest of the report filled with zeroes */
+static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length)
+{
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ return hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+}
+
+static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,
+ struct completion *status_completion)
+{
+ int ret;
+
+ /*
+ * Disable raw event parsing for a moment to safely reinitialize the
+ * completion. Reinit is done because hidraw could have triggered
+ * the raw event parsing and marked the passed in completion as done.
+ */
+ spin_lock_bh(&priv->status_report_request_lock);
+ reinit_completion(status_completion);
+ spin_unlock_bh(&priv->status_report_request_lock);
+
+ /* Send command for getting data */
+ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(status_completion,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
+{
+ int ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ return 0;
+ }
+
+ /* Retrieve cooler status */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,
+ &priv->cooler_status_received);
+ if (ret < 0)
+ return ret;
+
+ /* Retrieve controller status (speeds) */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
+ &priv->controller_status_received);
+ if (ret < 0)
+ return ret;
+
+ /* Retrieve cooler duty */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
+ &priv->cooler_duty_received);
+ if (ret < 0)
+ return ret;
+
+ /* Retrieve controller duty */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
+ &priv->controller_duty_received);
+ if (ret < 0)
+ return ret;
+
+ priv->updated = jiffies;
+ return 0;
+}
+
+static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct rog_ryujin_data *priv = dev_get_drvdata(dev);
+ int ret = rog_ryujin_get_status(priv);
+
+ if (ret < 0)
+ return ret;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->speed_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ *val = priv->duty_input[channel];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = rog_ryujin_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = rog_ryujin_speed_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)
+{
+ u8 set_cmd[SET_CMD_LENGTH];
+ int ret;
+
+ if (channel < 2) {
+ /*
+ * Retrieve cooler duty since both pump and internal fan are set
+ * together, then write back with one of them modified.
+ */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
+ &priv->cooler_duty_received);
+ if (ret < 0)
+ return ret;
+
+ memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
+
+ /* Cooler duties are set as 0-100% */
+ val = rog_ryujin_pwm_to_percent(val);
+
+ if (channel == 0) {
+ /* Cooler pump duty */
+ set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val;
+ set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] =
+ rog_ryujin_pwm_to_percent(priv->duty_input[1]);
+ } else if (channel == 1) {
+ /* Cooler internal fan duty */
+ set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] =
+ rog_ryujin_pwm_to_percent(priv->duty_input[0]);
+ set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
+ }
+
+ return rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
+ } else {
+ /*
+ * Controller fan duty (channel == 2). No need to retrieve current
+ * duty, so just send the command.
+ */
+ memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH);
+ set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val;
+
+ ret =
+ rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH,
+ &priv->controller_duty_set);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Lock onto this value until next refresh cycle */
+ priv->duty_input[channel] = val;
+
+ return 0;
+}
+
+static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct rog_ryujin_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ ret = rog_ryujin_write_fixed_duty(priv, channel, val);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops rog_ryujin_hwmon_ops = {
+ .is_visible = rog_ryujin_is_visible,
+ .read = rog_ryujin_read,
+ .read_string = rog_ryujin_read_string,
+ .write = rog_ryujin_write
+};
+
+static const struct hwmon_channel_info *rog_ryujin_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info rog_ryujin_chip_info = {
+ .ops = &rog_ryujin_hwmon_ops,
+ .info = rog_ryujin_info,
+};
+
+static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
+ int size)
+{
+ struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
+
+ if (data[0] != RYUJIN_CMD_PREFIX)
+ return 0;
+
+ if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
+ /* Received coolant temp and speeds of pump and internal fan */
+ priv->temp_input[0] =
+ data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;
+ priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);
+ priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);
+
+ if (!completion_done(&priv->cooler_status_received))
+ complete_all(&priv->cooler_status_received);
+ } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {
+ /* Received speeds of four fans attached to the controller */
+ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);
+ priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);
+ priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3);
+ priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4);
+
+ if (!completion_done(&priv->controller_status_received))
+ complete_all(&priv->controller_status_received);
+ } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {
+ /* Received report for pump and internal fan duties (in %) */
+ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {
+ /*
+ * We received a report with zeroes for duty in both places.
+ * The device returns this as a confirmation that setting values
+ * is successful. If we initiated a write, mark it as complete.
+ */
+ if (!completion_done(&priv->cooler_duty_set))
+ complete_all(&priv->cooler_duty_set);
+ else if (!completion_done(&priv->cooler_duty_received))
+ /*
+ * We didn't initiate a write, but received both zeroes.
+ * This means that either both duties are actually zero,
+ * or that we received a success report caused by userspace.
+ * We're expecting a report, so parse it.
+ */
+ goto read_cooler_duty;
+ return 0;
+ }
+read_cooler_duty:
+ priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);
+ priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]);
+
+ if (!completion_done(&priv->cooler_duty_received))
+ complete_all(&priv->cooler_duty_received);
+ } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {
+ /* Received report for controller duty for fans (in PWM) */
+ if (data[RYUJIN_CONTROLLER_DUTY] == 0) {
+ /*
+ * We received a report with a zero for duty. The device returns this as
+ * a confirmation that setting the controller duty value was successful.
+ * If we initiated a write, mark it as complete.
+ */
+ if (!completion_done(&priv->controller_duty_set))
+ complete_all(&priv->controller_duty_set);
+ else if (!completion_done(&priv->controller_duty_received))
+ /*
+ * We didn't initiate a write, but received a zero for duty.
+ * This means that either the duty is actually zero, or that
+ * we received a success report caused by userspace.
+ * We're expecting a report, so parse it.
+ */
+ goto read_controller_duty;
+ return 0;
+ }
+read_controller_duty:
+ priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];
+
+ if (!completion_done(&priv->controller_duty_received))
+ complete_all(&priv->controller_duty_received);
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct rog_ryujin_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for rog_ryujin_read() without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /* Enable hidraw so existing user-space tools can continue to work */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ spin_lock_init(&priv->status_report_request_lock);
+ init_completion(&priv->cooler_status_received);
+ init_completion(&priv->controller_status_received);
+ init_completion(&priv->cooler_duty_received);
+ init_completion(&priv->controller_duty_received);
+ init_completion(&priv->cooler_duty_set);
+ init_completion(&priv->controller_duty_set);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin",
+ priv, &rog_ryujin_chip_info, NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void rog_ryujin_remove(struct hid_device *hdev)
+{
+ struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
+
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id rog_ryujin_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, rog_ryujin_table);
+
+static struct hid_driver rog_ryujin_driver = {
+ .name = "rog_ryujin",
+ .id_table = rog_ryujin_table,
+ .probe = rog_ryujin_probe,
+ .remove = rog_ryujin_remove,
+ .raw_event = rog_ryujin_raw_event,
+};
+
+static int __init rog_ryujin_init(void)
+{
+ return hid_register_driver(&rog_ryujin_driver);
+}
+
+static void __exit rog_ryujin_exit(void)
+{
+ hid_unregister_driver(&rog_ryujin_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(rog_ryujin_init);
+module_exit(rog_ryujin_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");
diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c
index 6e8a908171f0..c2dd7ff882f2 100644
--- a/drivers/hwmon/asus_wmi_sensors.c
+++ b/drivers/hwmon/asus_wmi_sensors.c
@@ -300,7 +300,7 @@ static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s)
goto out_free_obj;
}
- strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1);
+ strscpy(s->name, name_obj.string.pointer, sizeof(s->name));
data_type_obj = obj->package.elements[1];
if (data_type_obj.type != ACPI_TYPE_INTEGER) {
diff --git a/drivers/hwmon/atxp1.c b/drivers/hwmon/atxp1.c
index d1de020abec6..1c7e9a98b757 100644
--- a/drivers/hwmon/atxp1.c
+++ b/drivers/hwmon/atxp1.c
@@ -278,7 +278,7 @@ static int atxp1_probe(struct i2c_client *client)
};
static const struct i2c_device_id atxp1_id[] = {
- { "atxp1", 0 },
+ { "atxp1" },
{ }
};
MODULE_DEVICE_TABLE(i2c, atxp1_id);
diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c
index 5fd136baf1cd..b7bb325c3ad9 100644
--- a/drivers/hwmon/axi-fan-control.c
+++ b/drivers/hwmon/axi-fan-control.c
@@ -4,17 +4,18 @@
*
* Copyright 2019 Analog Devices Inc.
*/
+#include <linux/adi-axi-common.h>
#include <linux/bits.h>
#include <linux/clk.h>
-#include <linux/fpga/adi-axi-common.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
/* register map */
#define ADI_REG_RSTN 0x0080
@@ -83,7 +84,7 @@ static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute
temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230;
- return sprintf(buf, "%u\n", temp);
+ return sysfs_emit(buf, "%u\n", temp);
}
static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da,
@@ -368,12 +369,12 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
}
static int axi_fan_control_init(struct axi_fan_control_data *ctl,
- const struct device_node *np)
+ const struct device *dev)
{
int ret;
/* get fan pulses per revolution */
- ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr);
+ ret = device_property_read_u32(dev, "pulses-per-revolution", &ctl->ppr);
if (ret)
return ret;
@@ -443,25 +444,16 @@ static struct attribute *axi_fan_control_attrs[] = {
};
ATTRIBUTE_GROUPS(axi_fan_control);
-static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
-
-static const struct of_device_id axi_fan_control_of_match[] = {
- { .compatible = "adi,axi-fan-control-1.00.a",
- .data = (void *)&version_1_0_0},
- {},
-};
-MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
-
static int axi_fan_control_probe(struct platform_device *pdev)
{
struct axi_fan_control_data *ctl;
struct clk *clk;
- const struct of_device_id *id;
+ const unsigned int *id;
const char *name = "axi_fan_control";
u32 version;
int ret;
- id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node);
+ id = device_get_match_data(&pdev->dev);
if (!id)
return -EINVAL;
@@ -474,10 +466,9 @@ static int axi_fan_control_probe(struct platform_device *pdev)
return PTR_ERR(ctl->base);
clk = devm_clk_get_enabled(&pdev->dev, NULL);
- if (IS_ERR(clk)) {
- dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk));
- return PTR_ERR(clk);
- }
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "clk_get failed\n");
ctl->clk_rate = clk_get_rate(clk);
if (!ctl->clk_rate)
@@ -485,16 +476,29 @@ static int axi_fan_control_probe(struct platform_device *pdev)
version = axi_ioread(ADI_AXI_REG_VERSION, ctl);
if (ADI_AXI_PCORE_VER_MAJOR(version) !=
- ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) {
- dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
- ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_MAJOR(version),
- ADI_AXI_PCORE_VER_MINOR(version),
- ADI_AXI_PCORE_VER_PATCH(version));
- return -ENODEV;
- }
+ ADI_AXI_PCORE_VER_MAJOR((*id)))
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+ ADI_AXI_PCORE_VER_MAJOR(*id),
+ ADI_AXI_PCORE_VER_MINOR(*id),
+ ADI_AXI_PCORE_VER_PATCH(*id),
+ ADI_AXI_PCORE_VER_MAJOR(version),
+ ADI_AXI_PCORE_VER_MINOR(version),
+ ADI_AXI_PCORE_VER_PATCH(version));
+
+ ret = axi_fan_control_init(ctl, &pdev->dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to initialize device\n");
+
+ ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev,
+ name,
+ ctl,
+ &axi_chip_info,
+ axi_fan_control_groups);
+
+ if (IS_ERR(ctl->hdev))
+ return PTR_ERR(ctl->hdev);
ctl->irq = platform_get_irq(pdev, 0);
if (ctl->irq < 0)
@@ -504,25 +508,21 @@ static int axi_fan_control_probe(struct platform_device *pdev)
axi_fan_control_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
pdev->driver_override, ctl);
- if (ret) {
- dev_err(&pdev->dev, "failed to request an irq, %d", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to request an irq\n");
- ret = axi_fan_control_init(ctl, pdev->dev.of_node);
- if (ret) {
- dev_err(&pdev->dev, "Failed to initialize device\n");
- return ret;
- }
+ return 0;
+}
- ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev,
- name,
- ctl,
- &axi_chip_info,
- axi_fan_control_groups);
+static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
- return PTR_ERR_OR_ZERO(ctl->hdev);
-}
+static const struct of_device_id axi_fan_control_of_match[] = {
+ { .compatible = "adi,axi-fan-control-1.00.a",
+ .data = (void *)&version_1_0_0},
+ {},
+};
+MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
static struct platform_driver axi_fan_control_driver = {
.driver = {
diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c
new file mode 100644
index 000000000000..3aff4e092132
--- /dev/null
+++ b/drivers/hwmon/cgbc-hwmon.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * cgbc-hwmon - Congatec Board Controller hardware monitoring driver
+ *
+ * Copyright (C) 2024 Thomas Richard <thomas.richard@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/mfd/cgbc.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define CGBC_HWMON_CMD_SENSOR 0x77
+#define CGBC_HWMON_CMD_SENSOR_DATA_SIZE 0x05
+
+#define CGBC_HWMON_TYPE_MASK GENMASK(6, 5)
+#define CGBC_HWMON_ID_MASK GENMASK(4, 0)
+#define CGBC_HWMON_ACTIVE_BIT BIT(7)
+
+struct cgbc_hwmon_sensor {
+ enum hwmon_sensor_types type;
+ bool active;
+ unsigned int index;
+ unsigned int channel;
+ const char *label;
+};
+
+struct cgbc_hwmon_data {
+ struct cgbc_device_data *cgbc;
+ unsigned int nb_sensors;
+ struct cgbc_hwmon_sensor *sensors;
+};
+
+enum cgbc_sensor_types {
+ CGBC_HWMON_TYPE_TEMP = 1,
+ CGBC_HWMON_TYPE_IN,
+ CGBC_HWMON_TYPE_FAN
+};
+
+static const char * const cgbc_hwmon_labels_temp[] = {
+ "CPU Temperature",
+ "Box Temperature",
+ "Ambient Temperature",
+ "Board Temperature",
+ "Carrier Temperature",
+ "Chipset Temperature",
+ "Video Temperature",
+ "Other Temperature",
+ "TOPDIM Temperature",
+ "BOTTOMDIM Temperature",
+};
+
+static const struct {
+ enum hwmon_sensor_types type;
+ const char *label;
+} cgbc_hwmon_labels_in[] = {
+ { hwmon_in, "CPU Voltage" },
+ { hwmon_in, "DC Runtime Voltage" },
+ { hwmon_in, "DC Standby Voltage" },
+ { hwmon_in, "CMOS Battery Voltage" },
+ { hwmon_in, "Battery Voltage" },
+ { hwmon_in, "AC Voltage" },
+ { hwmon_in, "Other Voltage" },
+ { hwmon_in, "5V Voltage" },
+ { hwmon_in, "5V Standby Voltage" },
+ { hwmon_in, "3V3 Voltage" },
+ { hwmon_in, "3V3 Standby Voltage" },
+ { hwmon_in, "VCore A Voltage" },
+ { hwmon_in, "VCore B Voltage" },
+ { hwmon_in, "12V Voltage" },
+ { hwmon_curr, "DC Current" },
+ { hwmon_curr, "5V Current" },
+ { hwmon_curr, "12V Current" },
+};
+
+#define CGBC_HWMON_NB_IN_SENSORS 14
+
+static const char * const cgbc_hwmon_labels_fan[] = {
+ "CPU Fan",
+ "Box Fan",
+ "Ambient Fan",
+ "Chipset Fan",
+ "Video Fan",
+ "Other Fan",
+};
+
+static int cgbc_hwmon_cmd(struct cgbc_device_data *cgbc, u8 index, u8 *data)
+{
+ u8 cmd[2] = {CGBC_HWMON_CMD_SENSOR, index};
+
+ return cgbc_command(cgbc, cmd, sizeof(cmd), data, CGBC_HWMON_CMD_SENSOR_DATA_SIZE, NULL);
+}
+
+static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data *hwmon)
+{
+ struct cgbc_device_data *cgbc = hwmon->cgbc;
+ struct cgbc_hwmon_sensor *sensor = hwmon->sensors;
+ u8 data[CGBC_HWMON_CMD_SENSOR_DATA_SIZE], nb_sensors, i;
+ int ret;
+
+ ret = cgbc_hwmon_cmd(cgbc, 0, &data[0]);
+ if (ret)
+ return ret;
+
+ nb_sensors = data[0];
+
+ hwmon->sensors = devm_kzalloc(dev, sizeof(*hwmon->sensors) * nb_sensors, GFP_KERNEL);
+ if (!hwmon->sensors)
+ return -ENOMEM;
+
+ sensor = hwmon->sensors;
+
+ for (i = 0; i < nb_sensors; i++) {
+ enum cgbc_sensor_types type;
+ unsigned int channel;
+
+ /*
+ * No need to request data for the first sensor.
+ * We got data for the first sensor when we ask the number of sensors to the Board
+ * Controller.
+ */
+ if (i) {
+ ret = cgbc_hwmon_cmd(cgbc, i, &data[0]);
+ if (ret)
+ return ret;
+ }
+
+ type = FIELD_GET(CGBC_HWMON_TYPE_MASK, data[1]);
+ channel = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]) - 1;
+
+ if (type == CGBC_HWMON_TYPE_TEMP && channel < ARRAY_SIZE(cgbc_hwmon_labels_temp)) {
+ sensor->type = hwmon_temp;
+ sensor->label = cgbc_hwmon_labels_temp[channel];
+ } else if (type == CGBC_HWMON_TYPE_IN &&
+ channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) {
+ /*
+ * The Board Controller doesn't differentiate current and voltage sensors.
+ * Get the sensor type from cgbc_hwmon_labels_in[channel].type instead.
+ */
+ sensor->type = cgbc_hwmon_labels_in[channel].type;
+ sensor->label = cgbc_hwmon_labels_in[channel].label;
+ } else if (type == CGBC_HWMON_TYPE_FAN &&
+ channel < ARRAY_SIZE(cgbc_hwmon_labels_fan)) {
+ sensor->type = hwmon_fan;
+ sensor->label = cgbc_hwmon_labels_fan[channel];
+ } else {
+ dev_warn(dev, "Board Controller returned an unknown sensor (type=%d, channel=%d), ignore it",
+ type, channel);
+ continue;
+ }
+
+ sensor->active = FIELD_GET(CGBC_HWMON_ACTIVE_BIT, data[1]);
+ sensor->channel = channel;
+ sensor->index = i;
+ sensor++;
+ hwmon->nb_sensors++;
+ }
+
+ return 0;
+}
+
+static struct cgbc_hwmon_sensor *cgbc_hwmon_find_sensor(struct cgbc_hwmon_data *hwmon,
+ enum hwmon_sensor_types type, int channel)
+{
+ struct cgbc_hwmon_sensor *sensor = NULL;
+ int i;
+
+ /*
+ * The Board Controller doesn't differentiate current and voltage sensors.
+ * The channel value (from the Board Controller point of view) shall be computed for current
+ * sensors.
+ */
+ if (type == hwmon_curr)
+ channel += CGBC_HWMON_NB_IN_SENSORS;
+
+ for (i = 0; i < hwmon->nb_sensors; i++) {
+ if (hwmon->sensors[i].type == type && hwmon->sensors[i].channel == channel) {
+ sensor = &hwmon->sensors[i];
+ break;
+ }
+ }
+
+ return sensor;
+}
+
+static int cgbc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct cgbc_hwmon_data *hwmon = dev_get_drvdata(dev);
+ struct cgbc_hwmon_sensor *sensor = cgbc_hwmon_find_sensor(hwmon, type, channel);
+ struct cgbc_device_data *cgbc = hwmon->cgbc;
+ u8 data[CGBC_HWMON_CMD_SENSOR_DATA_SIZE];
+ int ret;
+
+ ret = cgbc_hwmon_cmd(cgbc, sensor->index, &data[0]);
+ if (ret)
+ return ret;
+
+ *val = (data[3] << 8) | data[2];
+
+ /*
+ * For the Board Controller 1lsb = 0.1 degree centigrade.
+ * Other units are as expected.
+ */
+ if (sensor->type == hwmon_temp)
+ *val *= 100;
+
+ return 0;
+}
+
+static umode_t cgbc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ struct cgbc_hwmon_data *data = (struct cgbc_hwmon_data *)_data;
+ struct cgbc_hwmon_sensor *sensor;
+
+ sensor = cgbc_hwmon_find_sensor(data, type, channel);
+ if (!sensor)
+ return 0;
+
+ return sensor->active ? 0444 : 0;
+}
+
+static int cgbc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ struct cgbc_hwmon_data *hwmon = dev_get_drvdata(dev);
+ struct cgbc_hwmon_sensor *sensor = cgbc_hwmon_find_sensor(hwmon, type, channel);
+
+ *str = sensor->label;
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const cgbc_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops cgbc_hwmon_ops = {
+ .is_visible = cgbc_hwmon_is_visible,
+ .read = cgbc_hwmon_read,
+ .read_string = cgbc_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info cgbc_chip_info = {
+ .ops = &cgbc_hwmon_ops,
+ .info = cgbc_hwmon_info,
+};
+
+static int cgbc_hwmon_probe(struct platform_device *pdev)
+{
+ struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct cgbc_hwmon_data *data;
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->cgbc = cgbc;
+
+ ret = cgbc_hwmon_probe_sensors(dev, data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to probe sensors");
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "cgbc_hwmon", data, &cgbc_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver cgbc_hwmon_driver = {
+ .driver = {
+ .name = "cgbc-hwmon",
+ },
+ .probe = cgbc_hwmon_probe,
+};
+
+module_platform_driver(cgbc_hwmon_driver);
+
+MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
+MODULE_DESCRIPTION("Congatec Board Controller Hardware Monitoring Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c
new file mode 100644
index 000000000000..645b8c2e704e
--- /dev/null
+++ b/drivers/hwmon/chipcap2.c
@@ -0,0 +1,777 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor
+ *
+ * Part numbers supported:
+ * CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
+ *
+ * Author: Javier Carrasco <javier.carrasco.cruz@gmail.com>
+ *
+ * Datasheet and application notes:
+ * https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#define CC2_START_CM 0xA0
+#define CC2_START_NOM 0x80
+#define CC2_R_ALARM_H_ON 0x18
+#define CC2_R_ALARM_H_OFF 0x19
+#define CC2_R_ALARM_L_ON 0x1A
+#define CC2_R_ALARM_L_OFF 0x1B
+#define CC2_RW_OFFSET 0x40
+#define CC2_W_ALARM_H_ON (CC2_R_ALARM_H_ON + CC2_RW_OFFSET)
+#define CC2_W_ALARM_H_OFF (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET)
+#define CC2_W_ALARM_L_ON (CC2_R_ALARM_L_ON + CC2_RW_OFFSET)
+#define CC2_W_ALARM_L_OFF (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET)
+
+#define CC2_STATUS_FIELD GENMASK(7, 6)
+#define CC2_STATUS_VALID_DATA 0x00
+#define CC2_STATUS_STALE_DATA 0x01
+#define CC2_STATUS_CMD_MODE 0x02
+
+#define CC2_RESPONSE_FIELD GENMASK(1, 0)
+#define CC2_RESPONSE_BUSY 0x00
+#define CC2_RESPONSE_ACK 0x01
+#define CC2_RESPONSE_NACK 0x02
+
+#define CC2_ERR_CORR_EEPROM BIT(2)
+#define CC2_ERR_UNCORR_EEPROM BIT(3)
+#define CC2_ERR_RAM_PARITY BIT(4)
+#define CC2_ERR_CONFIG_LOAD BIT(5)
+
+#define CC2_EEPROM_SIZE 10
+#define CC2_EEPROM_DATA_LEN 3
+#define CC2_MEASUREMENT_DATA_LEN 4
+
+#define CC2_RH_DATA_FIELD GENMASK(13, 0)
+
+/* ensure clean off -> on transitions */
+#define CC2_POWER_CYCLE_MS 80
+
+#define CC2_STARTUP_TO_DATA_MS 55
+#define CC2_RESP_START_CM_US 100
+#define CC2_RESP_EEPROM_R_US 100
+#define CC2_RESP_EEPROM_W_MS 12
+#define CC2_STARTUP_TIME_US 1250
+
+#define CC2_RH_MAX (100 * 1000U)
+
+#define CC2_CM_RETRIES 5
+
+struct cc2_rh_alarm_info {
+ bool low_alarm;
+ bool high_alarm;
+ bool low_alarm_visible;
+ bool high_alarm_visible;
+};
+
+struct cc2_data {
+ struct cc2_rh_alarm_info rh_alarm;
+ struct completion complete;
+ struct device *hwmon;
+ struct i2c_client *client;
+ struct regulator *regulator;
+ const char *name;
+ int irq_ready;
+ int irq_low;
+ int irq_high;
+ bool process_irqs;
+};
+
+enum cc2_chan_addr {
+ CC2_CHAN_TEMP = 0,
+ CC2_CHAN_HUMIDITY,
+};
+
+/* %RH as a per cent mille from a register value */
+static long cc2_rh_convert(u16 data)
+{
+ unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX;
+
+ return tmp / ((1 << 14) - 1);
+}
+
+/* convert %RH to a register value */
+static u16 cc2_rh_to_reg(long data)
+{
+ return data * ((1 << 14) - 1) / CC2_RH_MAX;
+}
+
+/* temperature in milli degrees celsius from a register value */
+static long cc2_temp_convert(u16 data)
+{
+ unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1);
+
+ return tmp - 40 * 1000U;
+}
+
+static int cc2_enable(struct cc2_data *data)
+{
+ int ret;
+
+ /* exclusive regulator, check in case a disable failed */
+ if (regulator_is_enabled(data->regulator))
+ return 0;
+
+ /* clear any pending completion */
+ try_wait_for_completion(&data->complete);
+
+ ret = regulator_enable(data->regulator);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125);
+
+ data->process_irqs = true;
+
+ return 0;
+}
+
+static void cc2_disable(struct cc2_data *data)
+{
+ int err;
+
+ /* ignore alarms triggered by voltage toggling when powering up */
+ data->process_irqs = false;
+
+ /* exclusive regulator, check in case an enable failed */
+ if (regulator_is_enabled(data->regulator)) {
+ err = regulator_disable(data->regulator);
+ if (err)
+ dev_dbg(&data->client->dev, "Failed to disable device");
+ }
+}
+
+static int cc2_cmd_response_diagnostic(struct device *dev, u8 status)
+{
+ int resp;
+
+ if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) {
+ dev_dbg(dev, "Command sent out of command window\n");
+ return -ETIMEDOUT;
+ }
+
+ resp = FIELD_GET(CC2_RESPONSE_FIELD, status);
+ switch (resp) {
+ case CC2_RESPONSE_ACK:
+ return 0;
+ case CC2_RESPONSE_BUSY:
+ return -EBUSY;
+ case CC2_RESPONSE_NACK:
+ if (resp & CC2_ERR_CORR_EEPROM)
+ dev_dbg(dev, "Command failed: corrected EEPROM\n");
+ if (resp & CC2_ERR_UNCORR_EEPROM)
+ dev_dbg(dev, "Command failed: uncorrected EEPROM\n");
+ if (resp & CC2_ERR_RAM_PARITY)
+ dev_dbg(dev, "Command failed: RAM parity\n");
+ if (resp & CC2_ERR_RAM_PARITY)
+ dev_dbg(dev, "Command failed: configuration error\n");
+ return -ENODATA;
+ default:
+ dev_dbg(dev, "Unknown command reply\n");
+ return -EINVAL;
+ }
+}
+
+static int cc2_read_command_status(struct i2c_client *client)
+{
+ u8 status;
+ int ret;
+
+ ret = i2c_master_recv(client, &status, 1);
+ if (ret != 1) {
+ ret = ret < 0 ? ret : -EIO;
+ return ret;
+ }
+
+ return cc2_cmd_response_diagnostic(&client->dev, status);
+}
+
+/*
+ * The command mode is only accessible after sending the START_CM command in the
+ * first 10 ms after power-up. Only in case the command window is missed,
+ * CC2_CM_RETRIES retries are attempted before giving up and returning an error.
+ */
+static int cc2_command_mode_start(struct cc2_data *data)
+{
+ unsigned long timeout;
+ int i, ret;
+
+ for (i = 0; i < CC2_CM_RETRIES; i++) {
+ ret = cc2_enable(data);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0);
+ if (ret < 0)
+ return ret;
+
+ if (data->irq_ready > 0) {
+ timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US);
+ ret = wait_for_completion_timeout(&data->complete,
+ timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+ } else {
+ usleep_range(CC2_RESP_START_CM_US,
+ 2 * CC2_RESP_START_CM_US);
+ }
+ ret = cc2_read_command_status(data->client);
+ if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES)
+ break;
+
+ /* command window missed, prepare for a retry */
+ cc2_disable(data);
+ msleep(CC2_POWER_CYCLE_MS);
+ }
+
+ return ret;
+}
+
+/* Sending a Start_NOM command finishes the command mode immediately with no
+ * reply and the device enters normal operation mode
+ */
+static int cc2_command_mode_finish(struct cc2_data *data)
+{
+ int ret;
+
+ ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val)
+{
+ unsigned long timeout;
+ int ret;
+
+ ret = cc2_command_mode_start(data);
+ if (ret < 0)
+ goto disable;
+
+ cpu_to_be16s(&val);
+ ret = i2c_smbus_write_word_data(data->client, reg, val);
+ if (ret < 0)
+ goto disable;
+
+ if (data->irq_ready > 0) {
+ timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto disable;
+ }
+ } else {
+ msleep(CC2_RESP_EEPROM_W_MS);
+ }
+
+ ret = cc2_read_command_status(data->client);
+
+disable:
+ cc2_disable(data);
+
+ return ret;
+}
+
+static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val)
+{
+ u8 buf[CC2_EEPROM_DATA_LEN];
+ unsigned long timeout;
+ int ret;
+
+ ret = cc2_command_mode_start(data);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_word_data(data->client, reg, 0);
+ if (ret < 0)
+ return ret;
+
+ if (data->irq_ready > 0) {
+ timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+
+ } else {
+ usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10);
+ }
+ ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN);
+ if (ret != CC2_EEPROM_DATA_LEN)
+ return ret < 0 ? ret : -EIO;
+
+ *val = be16_to_cpup((__be16 *)&buf[1]);
+
+ return cc2_read_command_status(data->client);
+}
+
+static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val)
+{
+ u16 reg_val;
+ int ret;
+
+ ret = cc2_read_reg(data, reg, &reg_val);
+ if (!ret)
+ *val = cc2_rh_convert(reg_val);
+
+ cc2_disable(data);
+
+ return ret;
+}
+
+static int cc2_data_fetch(struct i2c_client *client,
+ enum hwmon_sensor_types type, long *val)
+{
+ u8 data[CC2_MEASUREMENT_DATA_LEN];
+ u8 status;
+ int ret;
+
+ ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN);
+ if (ret != CC2_MEASUREMENT_DATA_LEN) {
+ ret = ret < 0 ? ret : -EIO;
+ return ret;
+ }
+ status = FIELD_GET(CC2_STATUS_FIELD, data[0]);
+ if (status == CC2_STATUS_STALE_DATA)
+ return -EBUSY;
+
+ if (status != CC2_STATUS_VALID_DATA)
+ return -EIO;
+
+ switch (type) {
+ case hwmon_humidity:
+ *val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0]));
+ break;
+ case hwmon_temp:
+ *val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2]));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cc2_read_measurement(struct cc2_data *data,
+ enum hwmon_sensor_types type, long *val)
+{
+ unsigned long timeout;
+ int ret;
+
+ if (data->irq_ready > 0) {
+ timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+
+ } else {
+ msleep(CC2_STARTUP_TO_DATA_MS);
+ }
+
+ ret = cc2_data_fetch(data->client, type, val);
+
+ return ret;
+}
+
+/*
+ * A measurement requires enabling the device, waiting for the automatic
+ * measurement to finish, reading the measurement data and disabling the device
+ * again.
+ */
+static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type,
+ long *val)
+{
+ int ret;
+
+ ret = cc2_enable(data);
+ if (ret)
+ return ret;
+
+ ret = cc2_read_measurement(data, type, val);
+
+ cc2_disable(data);
+
+ return ret;
+}
+
+/*
+ * In order to check alarm status, the corresponding ALARM_OFF (hysteresis)
+ * register must be read and a new measurement must be carried out to trigger
+ * the alarm signals. Given that the device carries out a measurement after
+ * exiting the command mode, there is no need to force two power-up sequences.
+ * Instead, a NOM command is sent and the device is disabled after the
+ * measurement is read.
+ */
+static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg,
+ long *hyst, long *measurement)
+{
+ u16 reg_val;
+ int ret;
+
+ ret = cc2_read_reg(data, reg, &reg_val);
+ if (ret)
+ goto disable;
+
+ *hyst = cc2_rh_convert(reg_val);
+
+ ret = cc2_command_mode_finish(data);
+ if (ret)
+ goto disable;
+
+ ret = cc2_read_measurement(data, hwmon_humidity, measurement);
+
+disable:
+ cc2_disable(data);
+
+ return ret;
+}
+
+static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct cc2_data *cc2 = data;
+
+ switch (type) {
+ case hwmon_humidity:
+ switch (attr) {
+ case hwmon_humidity_input:
+ return 0444;
+ case hwmon_humidity_min_alarm:
+ return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
+ case hwmon_humidity_max_alarm:
+ return cc2->rh_alarm.high_alarm_visible ? 0444 : 0;
+ case hwmon_humidity_min:
+ case hwmon_humidity_min_hyst:
+ return cc2->rh_alarm.low_alarm_visible ? 0644 : 0;
+ case hwmon_humidity_max:
+ case hwmon_humidity_max_hyst:
+ return cc2->rh_alarm.high_alarm_visible ? 0644 : 0;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static irqreturn_t cc2_ready_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs)
+ complete(&cc2->complete);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cc2_low_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs) {
+ hwmon_notify_event(cc2->hwmon, hwmon_humidity,
+ hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
+ cc2->rh_alarm.low_alarm = true;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cc2_high_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs) {
+ hwmon_notify_event(cc2->hwmon, hwmon_humidity,
+ hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
+ cc2->rh_alarm.high_alarm = true;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val)
+{
+ long measurement, min_hyst;
+ int ret;
+
+ ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst,
+ &measurement);
+ if (ret < 0)
+ return ret;
+
+ if (data->rh_alarm.low_alarm) {
+ *val = (measurement < min_hyst) ? 1 : 0;
+ data->rh_alarm.low_alarm = *val;
+ } else {
+ *val = 0;
+ }
+
+ return 0;
+}
+
+static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
+{
+ long measurement, max_hyst;
+ int ret;
+
+ ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst,
+ &measurement);
+ if (ret < 0)
+ return ret;
+
+ if (data->rh_alarm.high_alarm) {
+ *val = (measurement > max_hyst) ? 1 : 0;
+ data->rh_alarm.high_alarm = *val;
+ } else {
+ *val = 0;
+ }
+
+ return 0;
+}
+
+static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct cc2_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return cc2_measurement(data, type, val);
+ case hwmon_humidity:
+ switch (attr) {
+ case hwmon_humidity_input:
+ return cc2_measurement(data, type, val);
+ case hwmon_humidity_min:
+ return cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val);
+ case hwmon_humidity_min_hyst:
+ return cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val);
+ case hwmon_humidity_max:
+ return cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val);
+ case hwmon_humidity_max_hyst:
+ return cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val);
+ case hwmon_humidity_min_alarm:
+ return cc2_humidity_min_alarm_status(data, val);
+ case hwmon_humidity_max_alarm:
+ return cc2_humidity_max_alarm_status(data, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct cc2_data *data = dev_get_drvdata(dev);
+ u16 arg;
+ u8 cmd;
+
+ if (type != hwmon_humidity)
+ return -EOPNOTSUPP;
+
+ if (val < 0 || val > CC2_RH_MAX)
+ return -EINVAL;
+
+ switch (attr) {
+ case hwmon_humidity_min:
+ cmd = CC2_W_ALARM_L_ON;
+ arg = cc2_rh_to_reg(val);
+ return cc2_write_reg(data, cmd, arg);
+ case hwmon_humidity_min_hyst:
+ cmd = CC2_W_ALARM_L_OFF;
+ arg = cc2_rh_to_reg(val);
+ return cc2_write_reg(data, cmd, arg);
+ case hwmon_humidity_max:
+ cmd = CC2_W_ALARM_H_ON;
+ arg = cc2_rh_to_reg(val);
+ return cc2_write_reg(data, cmd, arg);
+ case hwmon_humidity_max_hyst:
+ cmd = CC2_W_ALARM_H_OFF;
+ arg = cc2_rh_to_reg(val);
+ return cc2_write_reg(data, cmd, arg);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev)
+{
+ int ret = 0;
+
+ data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready");
+ if (data->irq_ready > 0) {
+ init_completion(&data->complete);
+ ret = devm_request_threaded_irq(dev, data->irq_ready, NULL,
+ cc2_ready_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ }
+
+ return ret;
+}
+
+static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
+{
+ int ret = 0;
+
+ data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low");
+ if (data->irq_low > 0) {
+ ret = devm_request_threaded_irq(dev, data->irq_low, NULL,
+ cc2_low_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ data->rh_alarm.low_alarm_visible = true;
+ }
+
+ data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high");
+ if (data->irq_high > 0) {
+ ret = devm_request_threaded_irq(dev, data->irq_high, NULL,
+ cc2_high_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ data->rh_alarm.high_alarm_visible = true;
+ }
+
+ return ret;
+}
+
+static const struct hwmon_channel_info *cc2_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
+ HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
+ HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops cc2_hwmon_ops = {
+ .is_visible = cc2_is_visible,
+ .read = cc2_read,
+ .write = cc2_write,
+};
+
+static const struct hwmon_chip_info cc2_chip_info = {
+ .ops = &cc2_hwmon_ops,
+ .info = cc2_info,
+};
+
+static int cc2_probe(struct i2c_client *client)
+{
+ struct cc2_data *data;
+ struct device *dev = &client->dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, data);
+
+ data->client = client;
+
+ data->regulator = devm_regulator_get_exclusive(dev, "vdd");
+ if (IS_ERR(data->regulator))
+ return dev_err_probe(dev, PTR_ERR(data->regulator),
+ "Failed to get regulator\n");
+
+ ret = cc2_request_ready_irq(data, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request ready irq\n");
+
+ ret = cc2_request_alarm_irqs(data, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request alarm irqs\n");
+
+ data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &cc2_chip_info,
+ NULL);
+ if (IS_ERR(data->hwmon))
+ return dev_err_probe(dev, PTR_ERR(data->hwmon),
+ "Failed to register hwmon device\n");
+
+ return 0;
+}
+
+static void cc2_remove(struct i2c_client *client)
+{
+ struct cc2_data *data = i2c_get_clientdata(client);
+
+ cc2_disable(data);
+}
+
+static const struct i2c_device_id cc2_id[] = {
+ { "cc2d23" },
+ { "cc2d23s" },
+ { "cc2d25" },
+ { "cc2d25s" },
+ { "cc2d33" },
+ { "cc2d33s" },
+ { "cc2d35" },
+ { "cc2d35s" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cc2_id);
+
+static const struct of_device_id cc2_of_match[] = {
+ { .compatible = "amphenol,cc2d23" },
+ { .compatible = "amphenol,cc2d23s" },
+ { .compatible = "amphenol,cc2d25" },
+ { .compatible = "amphenol,cc2d25s" },
+ { .compatible = "amphenol,cc2d33" },
+ { .compatible = "amphenol,cc2d33s" },
+ { .compatible = "amphenol,cc2d35" },
+ { .compatible = "amphenol,cc2d35s" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, cc2_of_match);
+
+static struct i2c_driver cc2_driver = {
+ .driver = {
+ .name = "cc2d23",
+ .of_match_table = cc2_of_match,
+ },
+ .probe = cc2_probe,
+ .remove = cc2_remove,
+ .id_table = cc2_id,
+};
+module_i2c_driver(cc2_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gamil.com>");
+MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c
index eba94f68585a..ad79db5a183e 100644
--- a/drivers/hwmon/coretemp.c
+++ b/drivers/hwmon/coretemp.c
@@ -39,13 +39,18 @@ static int force_tjmax;
module_param_named(tjmax, force_tjmax, int, 0444);
MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
-#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */
-#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */
-#define NUM_REAL_CORES 128 /* Number of Real cores per cpu */
-#define CORETEMP_NAME_LENGTH 19 /* String Length of attrs */
-#define MAX_CORE_ATTRS 4 /* Maximum no of basic attrs */
-#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1)
-#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO)
+#define NUM_REAL_CORES 512 /* Number of Real cores per cpu */
+#define CORETEMP_NAME_LENGTH 28 /* String Length of attrs */
+
+enum coretemp_attr_index {
+ ATTR_LABEL,
+ ATTR_CRIT_ALARM,
+ ATTR_TEMP,
+ ATTR_TJMAX,
+ ATTR_TTARGET,
+ MAX_CORE_ATTRS = ATTR_TJMAX + 1, /* Maximum no of basic attrs */
+ TOTAL_ATTRS = ATTR_TTARGET + 1 /* Maximum no of possible attrs */
+};
#ifdef CONFIG_SMP
#define for_each_sibling(i, cpu) \
@@ -65,19 +70,17 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
* @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS,
* from where the temperature values should be read.
* @attr_size: Total number of pre-core attrs displayed in the sysfs.
- * @is_pkg_data: If this is 1, the temp_data holds pkgtemp data.
- * Otherwise, temp_data holds coretemp data.
*/
struct temp_data {
int temp;
int tjmax;
unsigned long last_updated;
unsigned int cpu;
+ int index;
u32 cpu_core_id;
u32 status_reg;
int attr_size;
- bool is_pkg_data;
- struct sensor_device_attribute sd_attrs[TOTAL_ATTRS];
+ struct device_attribute sd_attrs[TOTAL_ATTRS];
char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH];
struct attribute *attrs[TOTAL_ATTRS + 1];
struct attribute_group attr_group;
@@ -88,10 +91,11 @@ struct temp_data {
struct platform_data {
struct device *hwmon_dev;
u16 pkg_id;
- u16 cpu_map[NUM_REAL_CORES];
+ int nr_cores;
struct ida ida;
struct cpumask cpumask;
- struct temp_data *core_data[MAX_CORE_DATA];
+ struct temp_data *pkg_data;
+ struct temp_data **core_data;
struct device_attribute name_attr;
};
@@ -118,31 +122,36 @@ static const struct tjmax tjmax_table[] = {
};
struct tjmax_model {
- u8 model;
- u8 mask;
+ u32 vfm;
+ u8 stepping_mask;
int tjmax;
};
#define ANY 0xff
static const struct tjmax_model tjmax_model_table[] = {
- { 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
- { 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others
- * Note: Also matches 230 and 330,
- * which are covered by tjmax_table
- */
- { 0x26, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
- * Note: TjMax for E6xxT is 110C, but CPU type
- * is undetectable by software
- */
- { 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */
- { 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
- { 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
- * Also matches S12x0 (stepping 9), covered by
- * PCI table
- */
+ { INTEL_ATOM_BONNELL, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
+ { INTEL_ATOM_BONNELL, ANY, 90000 }, /* Z5xx, N2xx, possibly others
+ * Note: Also matches 230 and 330,
+ * which are covered by tjmax_table
+ */
+ { INTEL_ATOM_BONNELL_MID, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
+ * Note: TjMax for E6xxT is 110C, but CPU type
+ * is undetectable by software
+ */
+ { INTEL_ATOM_SALTWELL_MID, ANY, 90000 }, /* Atom Medfield (Z2460) */
+ { INTEL_ATOM_SALTWELL_TABLET, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
+ { INTEL_ATOM_SALTWELL, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
+ * Also matches S12x0 (stepping 9), covered by
+ * PCI table
+ */
};
+static bool is_pkg_temp_data(struct temp_data *tdata)
+{
+ return tdata->index < 0;
+}
+
static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
{
/* The 100C is default for both mobile and non mobile CPUs */
@@ -171,6 +180,11 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
}
pci_dev_put(host_bridge);
+ /*
+ * This is literally looking for "CPU XXX" in the model string.
+ * Not checking it against the model as well. Just purely a
+ * string search.
+ */
for (i = 0; i < ARRAY_SIZE(tjmax_table); i++) {
if (strstr(c->x86_model_id, tjmax_table[i].id))
return tjmax_table[i].tjmax;
@@ -178,17 +192,18 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
for (i = 0; i < ARRAY_SIZE(tjmax_model_table); i++) {
const struct tjmax_model *tm = &tjmax_model_table[i];
- if (c->x86_model == tm->model &&
- (tm->mask == ANY || c->x86_stepping == tm->mask))
+ if (c->x86_vfm == tm->vfm &&
+ (tm->stepping_mask == ANY ||
+ tm->stepping_mask == c->x86_stepping))
return tm->tjmax;
}
/* Early chips have no MSR for TjMax */
- if (c->x86_model == 0xf && c->x86_stepping < 4)
+ if (c->x86_vfm == INTEL_CORE2_MEROM && c->x86_stepping < 4)
usemsr_ee = 0;
- if (c->x86_model > 0xe && usemsr_ee) {
+ if (c->x86_vfm > INTEL_CORE_YONAH && usemsr_ee) {
u8 platform_id;
/*
@@ -202,7 +217,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
"Unable to access MSR 0x17, assuming desktop"
" CPU\n");
usemsr_ee = 0;
- } else if (c->x86_model < 0x17 && !(eax & 0x10000000)) {
+ } else if (c->x86_vfm < INTEL_CORE2_PENRYN &&
+ !(eax & 0x10000000)) {
/*
* Trust bit 28 up to Penryn, I could not find any
* documentation on that; if you happen to know
@@ -217,7 +233,7 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
* Mobile Penryn CPU seems to be platform ID 7 or 5
* (guesswork)
*/
- if (c->x86_model == 0x17 &&
+ if (c->x86_vfm == INTEL_CORE2_PENRYN &&
(platform_id == 5 || platform_id == 7)) {
/*
* If MSR EE bit is set, set it to 90 degrees C,
@@ -249,18 +265,6 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
return tjmax;
}
-static bool cpu_has_tjmax(struct cpuinfo_x86 *c)
-{
- u8 model = c->x86_model;
-
- return model > 0xe &&
- model != 0x1c &&
- model != 0x26 &&
- model != 0x27 &&
- model != 0x35 &&
- model != 0x36;
-}
-
static int get_tjmax(struct temp_data *tdata, struct device *dev)
{
struct cpuinfo_x86 *c = &cpu_data(tdata->cpu);
@@ -278,8 +282,7 @@ static int get_tjmax(struct temp_data *tdata, struct device *dev)
*/
err = rdmsr_safe_on_cpu(tdata->cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err) {
- if (cpu_has_tjmax(c))
- dev_warn(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
+ dev_warn_once(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
} else {
val = (eax >> 16) & 0xff;
if (val)
@@ -332,11 +335,10 @@ static struct platform_device **zone_devices;
static ssize_t show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_LABEL]);
- if (tdata->is_pkg_data)
+ if (is_pkg_temp_data(tdata))
return sprintf(buf, "Package id %u\n", pdata->pkg_id);
return sprintf(buf, "Core %u\n", tdata->cpu_core_id);
@@ -346,9 +348,8 @@ static ssize_t show_crit_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data,
+ sd_attrs[ATTR_CRIT_ALARM]);
mutex_lock(&tdata->update_lock);
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
@@ -360,9 +361,7 @@ static ssize_t show_crit_alarm(struct device *dev,
static ssize_t show_tjmax(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TJMAX]);
int tjmax;
mutex_lock(&tdata->update_lock);
@@ -375,9 +374,7 @@ static ssize_t show_tjmax(struct device *dev,
static ssize_t show_ttarget(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TTARGET]);
int ttarget;
mutex_lock(&tdata->update_lock);
@@ -393,9 +390,7 @@ static ssize_t show_temp(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TEMP]);
int tjmax;
mutex_lock(&tdata->update_lock);
@@ -410,7 +405,7 @@ static ssize_t show_temp(struct device *dev,
* Return it instead of reporting an error which doesn't
* really help at all.
*/
- tdata->temp = tjmax - ((eax >> 16) & 0x7f) * 1000;
+ tdata->temp = tjmax - ((eax >> 16) & 0xff) * 1000;
tdata->last_updated = jiffies;
}
@@ -418,8 +413,7 @@ static ssize_t show_temp(struct device *dev,
return sprintf(buf, "%d\n", tdata->temp);
}
-static int create_core_attrs(struct temp_data *tdata, struct device *dev,
- int attr_no)
+static int create_core_attrs(struct temp_data *tdata, struct device *dev)
{
int i;
static ssize_t (*const rd_ptr[TOTAL_ATTRS]) (struct device *dev,
@@ -431,14 +425,20 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev,
};
for (i = 0; i < tdata->attr_size; i++) {
+ /*
+ * We map the attr number to core id of the CPU
+ * The attr number is always core id + 2
+ * The Pkgtemp will always show up as temp1_*, if available
+ */
+ int attr_no = is_pkg_temp_data(tdata) ? 1 : tdata->cpu_core_id + 2;
+
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH,
"temp%d_%s", attr_no, suffixes[i]);
- sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr);
- tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i];
- tdata->sd_attrs[i].dev_attr.attr.mode = 0444;
- tdata->sd_attrs[i].dev_attr.show = rd_ptr[i];
- tdata->sd_attrs[i].index = attr_no;
- tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr;
+ sysfs_attr_init(&tdata->sd_attrs[i].attr);
+ tdata->sd_attrs[i].attr.name = tdata->attr_name[i];
+ tdata->sd_attrs[i].attr.mode = 0444;
+ tdata->sd_attrs[i].show = rd_ptr[i];
+ tdata->attrs[i] = &tdata->sd_attrs[i].attr;
}
tdata->attr_group.attrs = tdata->attrs;
return sysfs_create_group(&dev->kobj, &tdata->attr_group);
@@ -454,7 +454,7 @@ static int chk_ucode_version(unsigned int cpu)
* Readings might stop update when processor visited too deep sleep,
* fixed for stepping D0 (6EC).
*/
- if (c->x86_model == 0xe && c->x86_stepping < 0xc && c->microcode < 0x39) {
+ if (c->x86_vfm == INTEL_CORE_YONAH && c->x86_stepping < 0xc && c->microcode < 0x39) {
pr_err("Errata AE18 not fixed, update BIOS or microcode of the CPU!\n");
return -ENODEV;
}
@@ -470,17 +470,44 @@ static struct platform_device *coretemp_get_pdev(unsigned int cpu)
return NULL;
}
-static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
+static struct temp_data *
+init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag)
{
struct temp_data *tdata;
+ if (!pdata->core_data) {
+ /*
+ * TODO:
+ * The information of actual possible cores in a package is broken for now.
+ * Will replace hardcoded NUM_REAL_CORES with actual per package core count
+ * when this information becomes available.
+ */
+ pdata->nr_cores = NUM_REAL_CORES;
+ pdata->core_data = kcalloc(pdata->nr_cores, sizeof(struct temp_data *),
+ GFP_KERNEL);
+ if (!pdata->core_data)
+ return NULL;
+ }
+
tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL);
if (!tdata)
return NULL;
+ if (pkg_flag) {
+ pdata->pkg_data = tdata;
+ /* Use tdata->index as indicator of package temp data */
+ tdata->index = -1;
+ } else {
+ tdata->index = ida_alloc_max(&pdata->ida, pdata->nr_cores - 1, GFP_KERNEL);
+ if (tdata->index < 0) {
+ kfree(tdata);
+ return NULL;
+ }
+ pdata->core_data[tdata->index] = tdata;
+ }
+
tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS :
MSR_IA32_THERM_STATUS;
- tdata->is_pkg_data = pkg_flag;
tdata->cpu = cpu;
tdata->cpu_core_id = topology_core_id(cpu);
tdata->attr_size = MAX_CORE_ATTRS;
@@ -488,6 +515,36 @@ static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
return tdata;
}
+static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata)
+{
+ if (is_pkg_temp_data(tdata)) {
+ pdata->pkg_data = NULL;
+ kfree(pdata->core_data);
+ pdata->core_data = NULL;
+ pdata->nr_cores = 0;
+ } else {
+ pdata->core_data[tdata->index] = NULL;
+ ida_free(&pdata->ida, tdata->index);
+ }
+ kfree(tdata);
+}
+
+static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu)
+{
+ int i;
+
+ /* cpu < 0 means get pkg temp_data */
+ if (cpu < 0)
+ return pdata->pkg_data;
+
+ for (i = 0; i < pdata->nr_cores; i++) {
+ if (pdata->core_data[i] &&
+ pdata->core_data[i]->cpu_core_id == topology_core_id(cpu))
+ return pdata->core_data[i];
+ }
+ return NULL;
+}
+
static int create_core_data(struct platform_device *pdev, unsigned int cpu,
int pkg_flag)
{
@@ -495,42 +552,19 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
struct platform_data *pdata = platform_get_drvdata(pdev);
struct cpuinfo_x86 *c = &cpu_data(cpu);
u32 eax, edx;
- int err, index, attr_no;
+ int err;
if (!housekeeping_cpu(cpu, HK_TYPE_MISC))
return 0;
- /*
- * Find attr number for sysfs:
- * We map the attr number to core id of the CPU
- * The attr number is always core id + 2
- * The Pkgtemp will always show up as temp1_*, if available
- */
- if (pkg_flag) {
- attr_no = PKG_SYSFS_ATTR_NO;
- } else {
- index = ida_alloc(&pdata->ida, GFP_KERNEL);
- if (index < 0)
- return index;
- pdata->cpu_map[index] = topology_core_id(cpu);
- attr_no = index + BASE_SYSFS_ATTR_NO;
- }
-
- if (attr_no > MAX_CORE_DATA - 1) {
- err = -ERANGE;
- goto ida_free;
- }
-
- tdata = init_temp_data(cpu, pkg_flag);
- if (!tdata) {
- err = -ENOMEM;
- goto ida_free;
- }
+ tdata = init_temp_data(pdata, cpu, pkg_flag);
+ if (!tdata)
+ return -ENOMEM;
/* Test if we can access the status register */
err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx);
if (err)
- goto exit_free;
+ goto err;
/* Make sure tdata->tjmax is a valid indicator for dynamic/static tjmax */
get_tjmax(tdata, &pdev->dev);
@@ -540,24 +574,19 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
* MSR_IA32_TEMPERATURE_TARGET register. Atoms don't have the register
* at all.
*/
- if (c->x86_model > 0xe && c->x86_model != 0x1c)
+ if (c->x86_vfm > INTEL_CORE_YONAH && c->x86_vfm != INTEL_ATOM_BONNELL)
if (get_ttarget(tdata, &pdev->dev) >= 0)
tdata->attr_size++;
- pdata->core_data[attr_no] = tdata;
-
/* Create sysfs interfaces */
- err = create_core_attrs(tdata, pdata->hwmon_dev, attr_no);
+ err = create_core_attrs(tdata, pdata->hwmon_dev);
if (err)
- goto exit_free;
+ goto err;
return 0;
-exit_free:
- pdata->core_data[attr_no] = NULL;
- kfree(tdata);
-ida_free:
- if (!pkg_flag)
- ida_free(&pdata->ida, index);
+
+err:
+ destroy_temp_data(pdata, tdata);
return err;
}
@@ -568,10 +597,8 @@ coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag)
dev_err(&pdev->dev, "Adding Core %u failed\n", cpu);
}
-static void coretemp_remove_core(struct platform_data *pdata, int indx)
+static void coretemp_remove_core(struct platform_data *pdata, struct temp_data *tdata)
{
- struct temp_data *tdata = pdata->core_data[indx];
-
/* if we errored on add then this is already gone */
if (!tdata)
return;
@@ -579,11 +606,7 @@ static void coretemp_remove_core(struct platform_data *pdata, int indx)
/* Remove the sysfs attributes */
sysfs_remove_group(&pdata->hwmon_dev->kobj, &tdata->attr_group);
- kfree(pdata->core_data[indx]);
- pdata->core_data[indx] = NULL;
-
- if (indx >= BASE_SYSFS_ATTR_NO)
- ida_free(&pdata->ida, indx - BASE_SYSFS_ATTR_NO);
+ destroy_temp_data(pdata, tdata);
}
static int coretemp_device_add(int zoneid)
@@ -696,7 +719,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
struct platform_device *pdev = coretemp_get_pdev(cpu);
struct platform_data *pd;
struct temp_data *tdata;
- int i, indx = -1, target;
+ int target;
/* No need to tear down any interfaces for suspend */
if (cpuhp_tasks_frozen)
@@ -707,18 +730,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
if (!pd->hwmon_dev)
return 0;
- for (i = 0; i < NUM_REAL_CORES; i++) {
- if (pd->cpu_map[i] == topology_core_id(cpu)) {
- indx = i + BASE_SYSFS_ATTR_NO;
- break;
- }
- }
-
- /* Too many cores and this core is not populated, just return */
- if (indx < 0)
- return 0;
-
- tdata = pd->core_data[indx];
+ tdata = get_temp_data(pd, cpu);
cpumask_clear_cpu(cpu, &pd->cpumask);
@@ -729,7 +741,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
*/
target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu));
if (target >= nr_cpu_ids) {
- coretemp_remove_core(pd, indx);
+ coretemp_remove_core(pd, tdata);
} else if (tdata && tdata->cpu == cpu) {
mutex_lock(&tdata->update_lock);
tdata->cpu = target;
@@ -739,10 +751,10 @@ static int coretemp_cpu_offline(unsigned int cpu)
/*
* If all cores in this pkg are offline, remove the interface.
*/
- tdata = pd->core_data[PKG_SYSFS_ATTR_NO];
+ tdata = get_temp_data(pd, -1);
if (cpumask_empty(&pd->cpumask)) {
if (tdata)
- coretemp_remove_core(pd, PKG_SYSFS_ATTR_NO);
+ coretemp_remove_core(pd, tdata);
hwmon_device_unregister(pd->hwmon_dev);
pd->hwmon_dev = NULL;
return 0;
@@ -775,12 +787,14 @@ static int __init coretemp_init(void)
/*
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
* sensors. We check this bit only, all the early CPUs
- * without thermal sensors will be filtered out.
+ * without thermal sensors will be filtered out. This
+ * includes all the Family 5 and Family 15 (Pentium 4)
+ * models, since they never set the CPUID bit.
*/
if (!x86_match_cpu(coretemp_ids))
return -ENODEV;
- max_zones = topology_max_packages() * topology_max_die_per_package();
+ max_zones = topology_max_packages() * topology_max_dies_per_package();
zone_devices = kcalloc(max_zones, sizeof(struct platform_device *),
GFP_KERNEL);
if (!zone_devices)
diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c
index 463ab4296ede..b6e508e43fa1 100644
--- a/drivers/hwmon/corsair-cpro.c
+++ b/drivers/hwmon/corsair-cpro.c
@@ -10,12 +10,15 @@
#include <linux/bitops.h>
#include <linux/completion.h>
+#include <linux/debugfs.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#define USB_VENDOR_ID_CORSAIR 0x1b1c
@@ -27,6 +30,8 @@
#define LABEL_LENGTH 11
#define REQ_TIMEOUT 300
+#define CTL_GET_FW_VER 0x02 /* returns the firmware version in bytes 1-3 */
+#define CTL_GET_BL_VER 0x06 /* returns the bootloader version in bytes 1-2 */
#define CTL_GET_TMP_CNCT 0x10 /*
* returns in bytes 1-4 for each temp sensor:
* 0 not connected
@@ -35,7 +40,7 @@
#define CTL_GET_TMP 0x11 /*
* send: byte 1 is channel, rest zero
* rcv: returns temp for channel in centi-degree celsius
- * in bytes 1 and 2
+ * in bytes 1 and 2 as a two's complement value
* returns 0x11 in byte 0 if no sensor is connected
*/
#define CTL_GET_VOLT 0x12 /*
@@ -77,13 +82,20 @@
struct ccp_device {
struct hid_device *hdev;
struct device *hwmon_dev;
+ struct dentry *debugfs;
+ /* For reinitializing the completion below */
+ spinlock_t wait_input_report_lock;
struct completion wait_input_report;
struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */
+ u8 *cmd_buffer;
u8 *buffer;
- int target[6];
+ int buffer_recv_size; /* number of received bytes in buffer */
+ int target[NUM_FANS];
DECLARE_BITMAP(temp_cnct, NUM_TEMP_SENSORS);
DECLARE_BITMAP(fan_cnct, NUM_FANS);
- char fan_label[6][LABEL_LENGTH];
+ char fan_label[NUM_FANS][LABEL_LENGTH];
+ u8 firmware_ver[3];
+ u8 bootloader_ver[2];
};
/* converts response error in buffer to errno */
@@ -111,15 +123,23 @@ static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2,
unsigned long t;
int ret;
- memset(ccp->buffer, 0x00, OUT_BUFFER_SIZE);
- ccp->buffer[0] = command;
- ccp->buffer[1] = byte1;
- ccp->buffer[2] = byte2;
- ccp->buffer[3] = byte3;
-
+ memset(ccp->cmd_buffer, 0x00, OUT_BUFFER_SIZE);
+ ccp->cmd_buffer[0] = command;
+ ccp->cmd_buffer[1] = byte1;
+ ccp->cmd_buffer[2] = byte2;
+ ccp->cmd_buffer[3] = byte3;
+
+ /*
+ * Disable raw event parsing for a moment to safely reinitialize the
+ * completion. Reinit is done because hidraw could have triggered
+ * the raw event parsing and marked the ccp->wait_input_report
+ * completion as done.
+ */
+ spin_lock_bh(&ccp->wait_input_report_lock);
reinit_completion(&ccp->wait_input_report);
+ spin_unlock_bh(&ccp->wait_input_report_lock);
- ret = hid_hw_output_report(ccp->hdev, ccp->buffer, OUT_BUFFER_SIZE);
+ ret = hid_hw_output_report(ccp->hdev, ccp->cmd_buffer, OUT_BUFFER_SIZE);
if (ret < 0)
return ret;
@@ -127,6 +147,9 @@ static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2,
if (!t)
return -ETIMEDOUT;
+ if (ccp->buffer_recv_size != IN_BUFFER_SIZE)
+ return -EPROTO;
+
return ccp_get_errno(ccp);
}
@@ -135,11 +158,13 @@ static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8
struct ccp_device *ccp = hid_get_drvdata(hdev);
/* only copy buffer when requested */
- if (completion_done(&ccp->wait_input_report))
- return 0;
-
- memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
- complete(&ccp->wait_input_report);
+ spin_lock(&ccp->wait_input_report_lock);
+ if (!completion_done(&ccp->wait_input_report)) {
+ memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
+ ccp->buffer_recv_size = size;
+ complete_all(&ccp->wait_input_report);
+ }
+ spin_unlock(&ccp->wait_input_report_lock);
return 0;
}
@@ -233,7 +258,7 @@ static int ccp_read(struct device *dev, enum hwmon_sensor_types type,
ret = get_data(ccp, CTL_GET_TMP, channel, true);
if (ret < 0)
return ret;
- *val = ret * 10;
+ *val = (s16)ret * 10;
return 0;
default:
break;
@@ -483,6 +508,83 @@ static int get_temp_cnct(struct ccp_device *ccp)
return 0;
}
+/* read firmware version */
+static int get_fw_version(struct ccp_device *ccp)
+{
+ int ret;
+
+ ret = send_usb_cmd(ccp, CTL_GET_FW_VER, 0, 0, 0);
+ if (ret) {
+ hid_notice(ccp->hdev, "Failed to read firmware version.\n");
+ return ret;
+ }
+ ccp->firmware_ver[0] = ccp->buffer[1];
+ ccp->firmware_ver[1] = ccp->buffer[2];
+ ccp->firmware_ver[2] = ccp->buffer[3];
+
+ return 0;
+}
+
+/* read bootloader version */
+static int get_bl_version(struct ccp_device *ccp)
+{
+ int ret;
+
+ ret = send_usb_cmd(ccp, CTL_GET_BL_VER, 0, 0, 0);
+ if (ret) {
+ hid_notice(ccp->hdev, "Failed to read bootloader version.\n");
+ return ret;
+ }
+ ccp->bootloader_ver[0] = ccp->buffer[1];
+ ccp->bootloader_ver[1] = ccp->buffer[2];
+
+ return 0;
+}
+
+static int firmware_show(struct seq_file *seqf, void *unused)
+{
+ struct ccp_device *ccp = seqf->private;
+
+ seq_printf(seqf, "%d.%d.%d\n",
+ ccp->firmware_ver[0],
+ ccp->firmware_ver[1],
+ ccp->firmware_ver[2]);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware);
+
+static int bootloader_show(struct seq_file *seqf, void *unused)
+{
+ struct ccp_device *ccp = seqf->private;
+
+ seq_printf(seqf, "%d.%d\n",
+ ccp->bootloader_ver[0],
+ ccp->bootloader_ver[1]);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(bootloader);
+
+static void ccp_debugfs_init(struct ccp_device *ccp)
+{
+ char name[32];
+ int ret;
+
+ scnprintf(name, sizeof(name), "corsaircpro-%s", dev_name(&ccp->hdev->dev));
+ ccp->debugfs = debugfs_create_dir(name, NULL);
+
+ ret = get_fw_version(ccp);
+ if (!ret)
+ debugfs_create_file("firmware_version", 0444,
+ ccp->debugfs, ccp, &firmware_fops);
+
+ ret = get_bl_version(ccp);
+ if (!ret)
+ debugfs_create_file("bootloader_version", 0444,
+ ccp->debugfs, ccp, &bootloader_fops);
+}
+
static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct ccp_device *ccp;
@@ -492,7 +594,11 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (!ccp)
return -ENOMEM;
- ccp->buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);
+ ccp->cmd_buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);
+ if (!ccp->cmd_buffer)
+ return -ENOMEM;
+
+ ccp->buffer = devm_kmalloc(&hdev->dev, IN_BUFFER_SIZE, GFP_KERNEL);
if (!ccp->buffer)
return -ENOMEM;
@@ -510,7 +616,9 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
ccp->hdev = hdev;
hid_set_drvdata(hdev, ccp);
+
mutex_init(&ccp->mutex);
+ spin_lock_init(&ccp->wait_input_report_lock);
init_completion(&ccp->wait_input_report);
hid_device_io_start(hdev);
@@ -523,8 +631,11 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = get_fan_cnct(ccp);
if (ret)
goto out_hw_close;
+
+ ccp_debugfs_init(ccp);
+
ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
- ccp, &ccp_chip_info, 0);
+ ccp, &ccp_chip_info, NULL);
if (IS_ERR(ccp->hwmon_dev)) {
ret = PTR_ERR(ccp->hwmon_dev);
goto out_hw_close;
@@ -543,6 +654,7 @@ static void ccp_remove(struct hid_device *hdev)
{
struct ccp_device *ccp = hid_get_drvdata(hdev);
+ debugfs_remove_recursive(ccp->debugfs);
hwmon_device_unregister(ccp->hwmon_dev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -563,6 +675,7 @@ static struct hid_driver ccp_driver = {
};
MODULE_DEVICE_TABLE(hid, ccp_devices);
+MODULE_DESCRIPTION("Corsair Commander Pro controller driver");
MODULE_LICENSE("GPL");
static int __init ccp_init(void)
diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c
index 904890598c11..dddbd2463f8d 100644
--- a/drivers/hwmon/corsair-psu.c
+++ b/drivers/hwmon/corsair-psu.c
@@ -9,11 +9,9 @@
#include <linux/errno.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -124,7 +122,6 @@ struct corsairpsu_data {
struct device *hwmon_dev;
struct dentry *debugfs;
struct completion wait_completion;
- struct mutex lock; /* for locking access to cmd_buffer */
u8 *cmd_buffer;
char vendor[REPLY_SIZE];
char product[REPLY_SIZE];
@@ -220,7 +217,6 @@ static int corsairpsu_request(struct corsairpsu_data *priv, u8 cmd, u8 rail, voi
{
int ret;
- mutex_lock(&priv->lock);
switch (cmd) {
case PSU_CMD_RAIL_VOLTS_HCRIT:
case PSU_CMD_RAIL_VOLTS_LCRIT:
@@ -230,17 +226,13 @@ static int corsairpsu_request(struct corsairpsu_data *priv, u8 cmd, u8 rail, voi
case PSU_CMD_RAIL_WATTS:
ret = corsairpsu_usb_cmd(priv, 2, PSU_CMD_SELECT_RAIL, rail, NULL);
if (ret < 0)
- goto cmd_fail;
+ return ret;
break;
default:
break;
}
- ret = corsairpsu_usb_cmd(priv, 3, cmd, 0, data);
-
-cmd_fail:
- mutex_unlock(&priv->lock);
- return ret;
+ return corsairpsu_usb_cmd(priv, 3, cmd, 0, data);
}
static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, long *val)
@@ -797,7 +789,6 @@ static int corsairpsu_probe(struct hid_device *hdev, const struct hid_device_id
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
- mutex_init(&priv->lock);
init_completion(&priv->wait_completion);
hid_device_io_start(hdev);
@@ -875,15 +866,17 @@ static const struct hid_device_id corsairpsu_idtable[] = {
{ HID_USB_DEVICE(0x1b1c, 0x1c04) }, /* Corsair HX650i */
{ HID_USB_DEVICE(0x1b1c, 0x1c05) }, /* Corsair HX750i */
{ HID_USB_DEVICE(0x1b1c, 0x1c06) }, /* Corsair HX850i */
- { HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i Series 2022 */
- { HID_USB_DEVICE(0x1b1c, 0x1c08) }, /* Corsair HX1200i */
+ { HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i Legacy */
+ { HID_USB_DEVICE(0x1b1c, 0x1c08) }, /* Corsair HX1200i Legacy */
{ HID_USB_DEVICE(0x1b1c, 0x1c09) }, /* Corsair RM550i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0a) }, /* Corsair RM650i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0b) }, /* Corsair RM750i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */
{ HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */
- { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Series 2022 and 2023 */
+ { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */
+ { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */
+ { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */
{ },
};
MODULE_DEVICE_TABLE(hid, corsairpsu_idtable);
@@ -899,7 +892,23 @@ static struct hid_driver corsairpsu_driver = {
.reset_resume = corsairpsu_resume,
#endif
};
-module_hid_driver(corsairpsu_driver);
+
+static int __init corsair_init(void)
+{
+ return hid_register_driver(&corsairpsu_driver);
+}
+
+static void __exit corsair_exit(void)
+{
+ hid_unregister_driver(&corsairpsu_driver);
+}
+
+/*
+ * With module_init() the driver would load before the HID bus when
+ * built-in, so use late_initcall() instead.
+ */
+late_initcall(corsair_init);
+module_exit(corsair_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wilken Gottwalt <wilken.gottwalt@posteo.net>");
diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c
new file mode 100644
index 000000000000..48331703f2f5
--- /dev/null
+++ b/drivers/hwmon/cros_ec_hwmon.c
@@ -0,0 +1,597 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ChromeOS EC driver for hwmon
+ *
+ * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
+ */
+
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define DRV_NAME "cros-ec-hwmon"
+
+#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION 0
+#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION 1
+#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION 2
+
+struct cros_ec_hwmon_priv {
+ struct cros_ec_device *cros_ec;
+ const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
+ u8 usable_fans;
+ bool fan_control_supported;
+ u8 manual_fans; /* bits to indicate whether the fan is set to manual */
+ u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES];
+};
+
+struct cros_ec_hwmon_cooling_priv {
+ struct cros_ec_hwmon_priv *hwmon_priv;
+ u8 index;
+};
+
+static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
+{
+ int ret;
+ __le16 __speed;
+
+ ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_FAN + index * 2, 2, &__speed);
+ if (ret < 0)
+ return ret;
+
+ *speed = le16_to_cpu(__speed);
+ return 0;
+}
+
+static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value)
+{
+ struct ec_params_pwm_get_fan_duty req = {
+ .fan_idx = index,
+ };
+ struct ec_response_pwm_get_fan_duty resp;
+ int ret;
+
+ ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION,
+ EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ *pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100);
+ return 0;
+}
+
+static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index,
+ u8 *control_method)
+{
+ struct ec_params_auto_fan_ctrl_v2 req = {
+ .cmd = EC_AUTO_FAN_CONTROL_CMD_GET,
+ .fan_idx = index,
+ };
+ struct ec_response_auto_fan_control resp;
+ int ret;
+
+ ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
+ EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ *control_method = resp.is_auto ? 2 : 1;
+ return 0;
+}
+
+static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
+{
+ unsigned int offset;
+ int ret;
+
+ if (index < EC_TEMP_SENSOR_ENTRIES)
+ offset = EC_MEMMAP_TEMP_SENSOR + index;
+ else
+ offset = EC_MEMMAP_TEMP_SENSOR_B + index - EC_TEMP_SENSOR_ENTRIES;
+
+ ret = cros_ec_cmd_readmem(cros_ec, offset, 1, temp);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static bool cros_ec_hwmon_is_error_fan(u16 speed)
+{
+ return speed == EC_FAN_SPEED_NOT_PRESENT || speed == EC_FAN_SPEED_STALLED;
+}
+
+static bool cros_ec_hwmon_is_error_temp(u8 temp)
+{
+ return temp == EC_TEMP_SENSOR_NOT_PRESENT ||
+ temp == EC_TEMP_SENSOR_ERROR ||
+ temp == EC_TEMP_SENSOR_NOT_POWERED ||
+ temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
+}
+
+static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
+{
+ return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
+}
+
+static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
+ int ret = -EOPNOTSUPP;
+ u8 control_method;
+ u8 pwm_value;
+ u16 speed;
+ u8 temp;
+
+ if (type == hwmon_fan) {
+ if (attr == hwmon_fan_input) {
+ ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
+ if (ret == 0) {
+ if (cros_ec_hwmon_is_error_fan(speed))
+ ret = -ENODATA;
+ else
+ *val = speed;
+ }
+ } else if (attr == hwmon_fan_fault) {
+ ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
+ if (ret == 0)
+ *val = cros_ec_hwmon_is_error_fan(speed);
+ }
+ } else if (type == hwmon_pwm) {
+ if (attr == hwmon_pwm_enable) {
+ ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel,
+ &control_method);
+ if (ret == 0)
+ *val = control_method;
+ } else if (attr == hwmon_pwm_input) {
+ ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value);
+ if (ret == 0)
+ *val = pwm_value;
+ }
+ } else if (type == hwmon_temp) {
+ if (attr == hwmon_temp_input) {
+ ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
+ if (ret == 0) {
+ if (cros_ec_hwmon_is_error_temp(temp))
+ ret = -ENODATA;
+ else
+ *val = cros_ec_hwmon_temp_to_millicelsius(temp);
+ }
+ } else if (attr == hwmon_temp_fault) {
+ ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
+ if (ret == 0)
+ *val = cros_ec_hwmon_is_error_temp(temp);
+ }
+ }
+
+ return ret;
+}
+
+static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
+
+ if (type == hwmon_temp && attr == hwmon_temp_label) {
+ *str = priv->temp_sensor_names[channel];
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val)
+{
+ struct ec_params_pwm_set_fan_duty_v1 req = {
+ .fan_idx = index,
+ .percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255),
+ };
+ int ret;
+
+ ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION,
+ EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val)
+{
+ u8 control_method;
+ int ret;
+
+ ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method);
+ if (ret)
+ return ret;
+ if (control_method != 1)
+ return -EOPNOTSUPP;
+
+ return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val);
+}
+
+static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val)
+{
+ struct ec_params_auto_fan_ctrl_v2 req = {
+ .fan_idx = index,
+ .cmd = EC_AUTO_FAN_CONTROL_CMD_SET,
+ };
+ int ret;
+
+ /* No CrOS EC supports no fan speed control */
+ if (val == 0)
+ return -EOPNOTSUPP;
+
+ req.set_auto = (val != 1) ? true : false;
+ ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
+ EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
+
+ if (type == hwmon_pwm) {
+ switch (attr) {
+ case hwmon_pwm_input:
+ return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val);
+ case hwmon_pwm_enable:
+ return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct cros_ec_hwmon_priv *priv = data;
+
+ if (type == hwmon_fan) {
+ if (priv->usable_fans & BIT(channel))
+ return 0444;
+ } else if (type == hwmon_pwm) {
+ if (priv->fan_control_supported && priv->usable_fans & BIT(channel))
+ return 0644;
+ } else if (type == hwmon_temp) {
+ if (priv->temp_sensor_names[channel])
+ return 0444;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_FAULT,
+ HWMON_F_INPUT | HWMON_F_FAULT,
+ HWMON_F_INPUT | HWMON_F_FAULT,
+ HWMON_F_INPUT | HWMON_F_FAULT),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL),
+ NULL
+};
+
+static int cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *val)
+{
+ *val = 255;
+ return 0;
+}
+
+static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *val)
+{
+ const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
+ u8 read_val;
+ int ret;
+
+ ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val);
+ if (ret)
+ return ret;
+
+ *val = read_val;
+ return 0;
+}
+
+static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long val)
+{
+ const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
+
+ return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val);
+}
+
+static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops = {
+ .get_max_state = cros_ec_hwmon_cooling_get_max_state,
+ .get_cur_state = cros_ec_hwmon_cooling_get_cur_state,
+ .set_cur_state = cros_ec_hwmon_cooling_set_cur_state,
+};
+
+static const struct hwmon_ops cros_ec_hwmon_ops = {
+ .read = cros_ec_hwmon_read,
+ .read_string = cros_ec_hwmon_read_string,
+ .write = cros_ec_hwmon_write,
+ .is_visible = cros_ec_hwmon_is_visible,
+};
+
+static const struct hwmon_chip_info cros_ec_hwmon_chip_info = {
+ .ops = &cros_ec_hwmon_ops,
+ .info = cros_ec_hwmon_info,
+};
+
+static void cros_ec_hwmon_probe_temp_sensors(struct device *dev, struct cros_ec_hwmon_priv *priv,
+ u8 thermal_version)
+{
+ struct ec_params_temp_sensor_get_info req = {};
+ struct ec_response_temp_sensor_get_info resp;
+ size_t candidates, i, sensor_name_size;
+ int ret;
+ u8 temp;
+
+ if (thermal_version < 2)
+ candidates = EC_TEMP_SENSOR_ENTRIES;
+ else
+ candidates = ARRAY_SIZE(priv->temp_sensor_names);
+
+ for (i = 0; i < candidates; i++) {
+ if (cros_ec_hwmon_read_temp(priv->cros_ec, i, &temp) < 0)
+ continue;
+
+ if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
+ continue;
+
+ req.id = i;
+ ret = cros_ec_cmd(priv->cros_ec, 0, EC_CMD_TEMP_SENSOR_GET_INFO,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (ret < 0)
+ continue;
+
+ sensor_name_size = strnlen(resp.sensor_name, sizeof(resp.sensor_name));
+ priv->temp_sensor_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
+ (int)sensor_name_size,
+ resp.sensor_name);
+ }
+}
+
+static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
+{
+ u16 speed;
+ size_t i;
+ int ret;
+
+ for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
+ ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, i, &speed);
+ if (ret == 0 && speed != EC_FAN_SPEED_NOT_PRESENT)
+ priv->usable_fans |= BIT(i);
+ }
+}
+
+static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec,
+ u16 cmd, u8 version)
+{
+ int ret;
+
+ ret = cros_ec_get_cmd_versions(cros_ec, cmd);
+ return ret >= 0 && (ret & EC_VER_MASK(version));
+}
+
+static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec)
+{
+ return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY,
+ CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) &&
+ is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY,
+ CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) &&
+ is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL,
+ CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION);
+}
+
+static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev,
+ struct cros_ec_hwmon_priv *priv)
+{
+ struct cros_ec_hwmon_cooling_priv *cpriv;
+ struct thermal_cooling_device *cdev;
+ const char *type;
+ size_t i;
+
+ if (!IS_ENABLED(CONFIG_THERMAL))
+ return;
+
+ if (!priv->fan_control_supported)
+ return;
+
+ for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
+ if (!(priv->usable_fans & BIT(i)))
+ continue;
+
+ cpriv = devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL);
+ if (!cpriv)
+ continue;
+
+ type = devm_kasprintf(dev, GFP_KERNEL, "%s-fan%zu", dev_name(dev), i);
+ if (!type) {
+ dev_warn(dev, "no memory to compose cooling device type for fan %zu\n", i);
+ continue;
+ }
+
+ cpriv->hwmon_priv = priv;
+ cpriv->index = i;
+ cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv,
+ &cros_ec_thermal_cooling_ops);
+ if (IS_ERR(cdev)) {
+ dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i,
+ cdev);
+ continue;
+ }
+ }
+}
+
+static int cros_ec_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
+ struct cros_ec_device *cros_ec = ec_dev->ec_dev;
+ struct cros_ec_hwmon_priv *priv;
+ struct device *hwmon_dev;
+ u8 thermal_version;
+ int ret;
+
+ ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_THERMAL_VERSION, 1, &thermal_version);
+ if (ret < 0)
+ return ret;
+
+ /* Covers both fan and temp sensors */
+ if (thermal_version == 0)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->cros_ec = cros_ec;
+
+ cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
+ cros_ec_hwmon_probe_fans(priv);
+ priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
+ cros_ec_hwmon_register_fan_cooling_devices(dev, priv);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
+ &cros_ec_hwmon_chip_info, NULL);
+ platform_set_drvdata(pdev, priv);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
+ u8 control_method;
+ size_t i;
+ int ret;
+
+ if (!priv->fan_control_supported)
+ return 0;
+
+ /* EC sets fan control to auto after suspended, store settings before suspending. */
+ for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
+ if (!(priv->usable_fans & BIT(i)))
+ continue;
+
+ ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method);
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i,
+ ret);
+ continue;
+ }
+
+ if (control_method != 1) {
+ priv->manual_fans &= ~BIT(i);
+ continue;
+ } else {
+ priv->manual_fans |= BIT(i);
+ }
+
+ ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]);
+ /*
+ * If storing the value failed, invalidate the stored mode value by setting it
+ * to auto control. EC will automatically switch to auto mode for that fan after
+ * suspended.
+ */
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i,
+ ERR_PTR(ret));
+ priv->manual_fans &= ~BIT(i);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+static int cros_ec_hwmon_resume(struct platform_device *pdev)
+{
+ const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
+ size_t i;
+ int ret;
+
+ if (!priv->fan_control_supported)
+ return 0;
+
+ /* EC sets fan control to auto after suspend, restore to settings before suspend. */
+ for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
+ if (!(priv->manual_fans & BIT(i)))
+ continue;
+
+ /*
+ * Setting fan PWM value to EC will change the mode to manual for that fan in EC as
+ * well, so we do not need to issue a separate fan mode to manual call.
+ */
+ ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i,
+ ERR_PTR(ret));
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id cros_ec_hwmon_id[] = {
+ { DRV_NAME, 0 },
+ {}
+};
+
+static struct platform_driver cros_ec_hwmon_driver = {
+ .driver.name = DRV_NAME,
+ .probe = cros_ec_hwmon_probe,
+ .suspend = pm_ptr(cros_ec_hwmon_suspend),
+ .resume = pm_ptr(cros_ec_hwmon_resume),
+ .id_table = cros_ec_hwmon_id,
+};
+module_platform_driver(cros_ec_hwmon_driver);
+
+MODULE_DEVICE_TABLE(platform, cros_ec_hwmon_id);
+MODULE_DESCRIPTION("ChromeOS EC Hardware Monitoring Driver");
+MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/da9052-hwmon.c b/drivers/hwmon/da9052-hwmon.c
index ed6c5df94fdf..588e96790850 100644
--- a/drivers/hwmon/da9052-hwmon.c
+++ b/drivers/hwmon/da9052-hwmon.c
@@ -26,7 +26,6 @@ struct da9052_hwmon {
struct mutex hwmon_lock;
bool tsi_as_adc;
int tsiref_mv;
- struct regulator *tsiref;
struct completion tsidone;
};
@@ -397,7 +396,7 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct da9052_hwmon *hwmon;
struct device *hwmon_dev;
- int err;
+ int err, tsiref_uv;
hwmon = devm_kzalloc(dev, sizeof(struct da9052_hwmon), GFP_KERNEL);
if (!hwmon)
@@ -414,32 +413,20 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
device_property_read_bool(pdev->dev.parent, "dlg,tsi-as-adc");
if (hwmon->tsi_as_adc) {
- hwmon->tsiref = devm_regulator_get(pdev->dev.parent, "tsiref");
- if (IS_ERR(hwmon->tsiref)) {
- err = PTR_ERR(hwmon->tsiref);
- dev_err(&pdev->dev, "failed to get tsiref: %d", err);
- return err;
- }
-
- err = regulator_enable(hwmon->tsiref);
- if (err)
- return err;
-
- hwmon->tsiref_mv = regulator_get_voltage(hwmon->tsiref);
- if (hwmon->tsiref_mv < 0) {
- err = hwmon->tsiref_mv;
- goto exit_regulator;
- }
+ tsiref_uv = devm_regulator_get_enable_read_voltage(dev->parent,
+ "tsiref");
+ if (tsiref_uv < 0)
+ return dev_err_probe(dev, tsiref_uv,
+ "failed to get tsiref voltage\n");
/* convert from microvolt (DT) to millivolt (hwmon) */
- hwmon->tsiref_mv /= 1000;
+ hwmon->tsiref_mv = tsiref_uv / 1000;
/* TSIREF limits from datasheet */
if (hwmon->tsiref_mv < 1800 || hwmon->tsiref_mv > 2600) {
dev_err(hwmon->da9052->dev, "invalid TSIREF voltage: %d",
hwmon->tsiref_mv);
- err = -ENXIO;
- goto exit_regulator;
+ return -ENXIO;
}
/* disable touchscreen features */
@@ -456,7 +443,7 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
if (err) {
dev_err(&pdev->dev, "Failed to register TSIRDY IRQ: %d",
err);
- goto exit_regulator;
+ return err;
}
}
@@ -472,23 +459,16 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
exit_irq:
if (hwmon->tsi_as_adc)
da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon);
-exit_regulator:
- if (hwmon->tsiref)
- regulator_disable(hwmon->tsiref);
return err;
}
-static int da9052_hwmon_remove(struct platform_device *pdev)
+static void da9052_hwmon_remove(struct platform_device *pdev)
{
struct da9052_hwmon *hwmon = platform_get_drvdata(pdev);
- if (hwmon->tsi_as_adc) {
+ if (hwmon->tsi_as_adc)
da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon);
- regulator_disable(hwmon->tsiref);
- }
-
- return 0;
}
static struct platform_driver da9052_hwmon_driver = {
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 44aaf9b9191d..a34753fc2973 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/acpi.h>
#include <linux/capability.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
@@ -23,6 +24,7 @@
#include <linux/init.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
@@ -34,8 +36,10 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
+#include <linux/wmi.h>
#include <linux/i8k.h>
+#include <linux/unaligned.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
@@ -66,8 +70,25 @@
#define I8K_POWER_AC 0x05
#define I8K_POWER_BATTERY 0x01
+#define DELL_SMM_WMI_GUID "F1DDEE52-063C-4784-A11E-8A06684B9B01"
+#define DELL_SMM_LEGACY_EXECUTE 0x1
+
#define DELL_SMM_NO_TEMP 10
-#define DELL_SMM_NO_FANS 3
+#define DELL_SMM_NO_FANS 4
+
+struct smm_regs {
+ unsigned int eax;
+ unsigned int ebx;
+ unsigned int ecx;
+ unsigned int edx;
+ unsigned int esi;
+ unsigned int edi;
+};
+
+struct dell_smm_ops {
+ struct device *smm_dev;
+ int (*smm_call)(struct device *smm_dev, struct smm_regs *regs);
+};
struct dell_smm_data {
struct mutex i8k_mutex; /* lock for sensors writes */
@@ -76,14 +97,11 @@ struct dell_smm_data {
uint i8k_fan_mult;
uint i8k_pwm_mult;
uint i8k_fan_max;
- bool disallow_fan_type_call;
- bool disallow_fan_support;
- unsigned int manual_fan;
- unsigned int auto_fan;
int temp_type[DELL_SMM_NO_TEMP];
bool fan[DELL_SMM_NO_FANS];
int fan_type[DELL_SMM_NO_FANS];
int *fan_nominal_speed[DELL_SMM_NO_FANS];
+ const struct dell_smm_ops *ops;
};
struct dell_smm_cooling_data {
@@ -91,7 +109,7 @@ struct dell_smm_cooling_data {
struct dell_smm_data *data;
};
-MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_AUTHOR("Massimo Dal Zotto <dz@debian.org>");
MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
MODULE_LICENSE("GPL");
@@ -123,14 +141,9 @@ static uint fan_max;
module_param(fan_max, uint, 0);
MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
-struct smm_regs {
- unsigned int eax;
- unsigned int ebx;
- unsigned int ecx;
- unsigned int edx;
- unsigned int esi;
- unsigned int edi;
-};
+static bool disallow_fan_type_call, disallow_fan_support;
+
+static unsigned int manual_fan, auto_fan;
static const char * const temp_labels[] = {
"CPU",
@@ -171,12 +184,8 @@ static inline const char __init *i8k_get_dmi_data(int field)
*/
static int i8k_smm_func(void *par)
{
- ktime_t calltime = ktime_get();
struct smm_regs *regs = par;
- int eax = regs->eax;
- int ebx = regs->ebx;
unsigned char carry;
- long long duration;
/* SMM requires CPU 0 */
if (smp_processor_id() != 0)
@@ -193,14 +202,7 @@ static int i8k_smm_func(void *par)
"+S" (regs->esi),
"+D" (regs->edi));
- duration = ktime_us_delta(ktime_get(), calltime);
- pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x carry: %d (took %7lld usecs)\n",
- eax, ebx, regs->eax & 0xffff, carry, duration);
-
- if (duration > DELL_SMM_MAX_DURATION)
- pr_warn_once("SMM call took %lld usecs!\n", duration);
-
- if (carry || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+ if (carry)
return -EINVAL;
return 0;
@@ -209,7 +211,7 @@ static int i8k_smm_func(void *par)
/*
* Call the System Management Mode BIOS.
*/
-static int i8k_smm(struct smm_regs *regs)
+static int i8k_smm_call(struct device *dummy, struct smm_regs *regs)
{
int ret;
@@ -220,6 +222,134 @@ static int i8k_smm(struct smm_regs *regs)
return ret;
}
+static const struct dell_smm_ops i8k_smm_ops = {
+ .smm_call = i8k_smm_call,
+};
+
+/*
+ * Call the System Management Mode BIOS over WMI.
+ */
+static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
+{
+ __le32 value;
+ u32 reg_size;
+
+ if (length <= sizeof(reg_size))
+ return -ENODATA;
+
+ reg_size = get_unaligned_le32(buffer);
+ if (!reg_size || reg_size > sizeof(value))
+ return -ENOMSG;
+
+ if (length < sizeof(reg_size) + reg_size)
+ return -ENODATA;
+
+ memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0);
+ *reg = le32_to_cpu(value);
+
+ return reg_size + sizeof(reg_size);
+}
+
+static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
+{
+ unsigned int *registers[] = {
+ &regs->eax,
+ &regs->ebx,
+ &regs->ecx,
+ &regs->edx
+ };
+ u32 offset = 0;
+ ssize_t ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ if (offset >= length)
+ return -ENODATA;
+
+ ret = wmi_parse_register(buffer + offset, length - offset, registers[i]);
+ if (ret < 0)
+ return ret;
+
+ offset += ret;
+ }
+
+ if (offset != length)
+ return -ENOMSG;
+
+ return 0;
+}
+
+static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
+{
+ struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ u32 wmi_payload[] = {
+ sizeof(regs->eax),
+ regs->eax,
+ sizeof(regs->ebx),
+ regs->ebx,
+ sizeof(regs->ecx),
+ regs->ecx,
+ sizeof(regs->edx),
+ regs->edx
+ };
+ const struct acpi_buffer in = {
+ .length = sizeof(wmi_payload),
+ .pointer = &wmi_payload,
+ };
+ union acpi_object *obj;
+ acpi_status status;
+ int ret;
+
+ status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = out.pointer;
+ if (!obj)
+ return -ENODATA;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ ret = -ENOMSG;
+
+ goto err_free;
+ }
+
+ ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
+
+err_free:
+ kfree(obj);
+
+ return ret;
+}
+
+static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)
+{
+ unsigned int eax = regs->eax;
+ unsigned int ebx = regs->ebx;
+ long long duration;
+ ktime_t calltime;
+ int ret;
+
+ calltime = ktime_get();
+ ret = ops->smm_call(ops->smm_dev, regs);
+ duration = ktime_us_delta(ktime_get(), calltime);
+
+ pr_debug("SMM(0x%.4x 0x%.4x) = 0x%.4x status: %d (took %7lld usecs)\n",
+ eax, ebx, regs->eax & 0xffff, ret, duration);
+
+ if (duration > DELL_SMM_MAX_DURATION)
+ pr_warn_once("SMM call took %lld usecs!\n", duration);
+
+ if (ret < 0)
+ return ret;
+
+ if ((regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+ return -EINVAL;
+
+ return 0;
+}
+
/*
* Read the fan status.
*/
@@ -230,10 +360,10 @@ static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
@@ -246,10 +376,10 @@ static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
+ return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
}
/*
@@ -262,10 +392,10 @@ static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support || data->disallow_fan_type_call)
+ if (disallow_fan_support || disallow_fan_type_call)
return -EINVAL;
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
@@ -280,17 +410,17 @@ static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
/*
* Read the fan nominal rpm for specific fan speed.
*/
-static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
+static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_NOM_SPEED,
.ebx = fan | (speed << 8),
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : (regs.eax & 0xffff);
+ return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff);
}
/*
@@ -300,11 +430,11 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl
{
struct smm_regs regs = { };
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- regs.eax = enable ? data->auto_fan : data->manual_fan;
- return i8k_smm(&regs);
+ regs.eax = enable ? auto_fan : manual_fan;
+ return dell_smm_call(data->ops, &regs);
}
/*
@@ -314,41 +444,40 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
regs.ebx = fan | (speed << 8);
- return i8k_smm(&regs);
+ return dell_smm_call(data->ops, &regs);
}
-static int __init i8k_get_temp_type(u8 sensor)
+static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP_TYPE,
.ebx = sensor,
};
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
* Read the cpu temperature.
*/
-static int _i8k_get_temp(u8 sensor)
+static int _i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP,
.ebx = sensor,
};
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
-static int i8k_get_temp(u8 sensor)
+static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
- int temp = _i8k_get_temp(sensor);
+ int temp = _i8k_get_temp(data, sensor);
/*
* Sometimes the temperature sensor returns 0x99, which is out of range.
@@ -359,7 +488,7 @@ static int i8k_get_temp(u8 sensor)
*/
if (temp == 0x99) {
msleep(100);
- temp = _i8k_get_temp(sensor);
+ temp = _i8k_get_temp(data, sensor);
}
/*
* Return -ENODATA for all invalid temperatures.
@@ -375,12 +504,12 @@ static int i8k_get_temp(u8 sensor)
return temp;
}
-static int __init i8k_get_dell_signature(int req_fn)
+static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
{
struct smm_regs regs = { .eax = req_fn, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(ops, &regs);
if (rc < 0)
return rc;
@@ -392,12 +521,12 @@ static int __init i8k_get_dell_signature(int req_fn)
/*
* Read the Fn key status.
*/
-static int i8k_get_fn_status(void)
+static int i8k_get_fn_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@@ -416,12 +545,12 @@ static int i8k_get_fn_status(void)
/*
* Read the power status.
*/
-static int i8k_get_power_status(void)
+static int i8k_get_power_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@@ -464,15 +593,15 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return 0;
case I8K_FN_STATUS:
- val = i8k_get_fn_status();
+ val = i8k_get_fn_status(data);
break;
case I8K_POWER_STATUS:
- val = i8k_get_power_status();
+ val = i8k_get_power_status(data);
break;
case I8K_GET_TEMP:
- val = i8k_get_temp(0);
+ val = i8k_get_temp(data, 0);
break;
case I8K_GET_SPEED:
@@ -508,6 +637,8 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (copy_from_user(&speed, argp + 1, sizeof(int)))
return -EFAULT;
+ speed = clamp_val(speed, 0, data->i8k_fan_max);
+
mutex_lock(&data->i8k_mutex);
err = i8k_set_fan(data, val, speed);
if (err < 0)
@@ -539,14 +670,14 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
int fn_key, cpu_temp, ac_power;
int left_fan, right_fan, left_speed, right_speed;
- cpu_temp = i8k_get_temp(0); /* 11100 µs */
+ cpu_temp = i8k_get_temp(data, 0); /* 11100 µs */
left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */
right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */
left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */
right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */
- fn_key = i8k_get_fn_status(); /* 750 µs */
+ fn_key = i8k_get_fn_status(data); /* 750 µs */
if (power_status)
- ac_power = i8k_get_power_status(); /* 14700 µs */
+ ac_power = i8k_get_power_status(data); /* 14700 µs */
else
ac_power = -1;
@@ -597,6 +728,11 @@ static void __init i8k_init_procfs(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
+ strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+ sizeof(data->bios_version));
+ strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+ sizeof(data->bios_machineid));
+
/* Only register exit function if creation was successful */
if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data))
devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
@@ -628,6 +764,13 @@ static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned l
if (ret < 0)
return ret;
+ /*
+ * A fan state bigger than i8k_fan_max might indicate that
+ * the fan is currently in automatic mode.
+ */
+ if (ret > cdata->data->i8k_fan_max)
+ return -ENODATA;
+
*state = ret;
return 0;
@@ -665,7 +808,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
switch (attr) {
case hwmon_temp_input:
/* _i8k_get_temp() is fine since we do not care about the actual value */
- if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0)
+ if (data->temp_type[channel] >= 0 || _i8k_get_temp(data, channel) >= 0)
return 0444;
break;
@@ -679,7 +822,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_fan:
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
break;
switch (attr) {
@@ -689,7 +832,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_fan_label:
- if (data->fan[channel] && !data->disallow_fan_type_call)
+ if (data->fan[channel] && !disallow_fan_type_call)
return 0444;
break;
@@ -705,7 +848,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_pwm:
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
break;
switch (attr) {
@@ -715,7 +858,14 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_pwm_enable:
- if (data->auto_fan)
+ if (auto_fan) {
+ /*
+ * The setting affects all fans, so only create a
+ * single attribute for the first fan channel.
+ */
+ if (channel != 0)
+ return 0;
+
/*
* There is no command for retrieve the current status
* from BIOS, and userspace/firmware itself can change
@@ -723,6 +873,10 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
* Thus we can only provide write-only access for now.
*/
return 0200;
+ }
+
+ if (data->fan[channel] && data->i8k_fan_max < I8K_FAN_AUTO)
+ return 0644;
break;
default:
@@ -747,7 +901,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
- ret = i8k_get_temp(channel);
+ ret = i8k_get_temp(data, channel);
if (ret < 0)
return ret;
@@ -792,15 +946,29 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
}
break;
case hwmon_pwm:
+ ret = i8k_get_fan_status(data, channel);
+ if (ret < 0)
+ return ret;
+
switch (attr) {
case hwmon_pwm_input:
- ret = i8k_get_fan_status(data, channel);
- if (ret < 0)
- return ret;
+ /*
+ * A fan state bigger than i8k_fan_max might indicate that
+ * the fan is currently in automatic mode.
+ */
+ if (ret > data->i8k_fan_max)
+ return -ENODATA;
*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
return 0;
+ case hwmon_pwm_enable:
+ if (ret == I8K_FAN_AUTO)
+ *val = 2;
+ else
+ *val = 1;
+
+ return 0;
default:
break;
}
@@ -886,16 +1054,32 @@ static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32
return 0;
case hwmon_pwm_enable:
- if (!val)
- return -EINVAL;
-
- if (val == 1)
+ switch (val) {
+ case 1:
enable = false;
- else
+ break;
+ case 2:
enable = true;
+ break;
+ default:
+ return -EINVAL;
+ }
mutex_lock(&data->i8k_mutex);
- err = i8k_enable_fan_auto_mode(data, enable);
+ if (auto_fan) {
+ err = i8k_enable_fan_auto_mode(data, enable);
+ } else {
+ /*
+ * When putting the fan into manual control mode we have to ensure
+ * that the device does not overheat until the userspace fan control
+ * software takes over. Because of this we set the fan speed to
+ * i8k_fan_max when disabling automatic fan control.
+ */
+ if (enable)
+ err = i8k_set_fan(data, channel, I8K_FAN_AUTO);
+ else
+ err = i8k_set_fan(data, channel, data->i8k_fan_max);
+ }
mutex_unlock(&data->i8k_mutex);
if (err < 0)
@@ -940,12 +1124,15 @@ static const struct hwmon_channel_info * const dell_smm_info[] = {
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
HWMON_F_TARGET,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
+ HWMON_F_TARGET,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
HWMON_F_TARGET
),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
- HWMON_PWM_INPUT,
- HWMON_PWM_INPUT
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE
),
NULL
};
@@ -955,7 +1142,7 @@ static const struct hwmon_chip_info dell_smm_chip_info = {
.info = dell_smm_info,
};
-static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
+static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct thermal_cooling_device *cdev;
@@ -986,7 +1173,7 @@ static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
return ret;
}
-static int __init dell_smm_init_hwmon(struct device *dev)
+static int dell_smm_init_hwmon(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct device *dell_smm_hwmon_dev;
@@ -994,7 +1181,7 @@ static int __init dell_smm_init_hwmon(struct device *dev)
u8 i;
for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
- data->temp_type[i] = i8k_get_temp_type(i);
+ data->temp_type[i] = i8k_get_temp_type(data, i);
if (data->temp_type[i] < 0)
continue;
@@ -1052,41 +1239,25 @@ static int __init dell_smm_init_hwmon(struct device *dev)
return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
}
-struct i8k_config_data {
- uint fan_mult;
- uint fan_max;
-};
+static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
+{
+ struct dell_smm_data *data;
-enum i8k_configs {
- DELL_LATITUDE_D520,
- DELL_PRECISION_490,
- DELL_STUDIO,
- DELL_XPS,
-};
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
-/*
- * Only use for machines which need some special configuration
- * in order to work correctly (e.g. if autoconfig fails on this machines).
- */
+ mutex_init(&data->i8k_mutex);
+ dev_set_drvdata(dev, data);
-static const struct i8k_config_data i8k_config_data[] __initconst = {
- [DELL_LATITUDE_D520] = {
- .fan_mult = 1,
- .fan_max = I8K_FAN_TURBO,
- },
- [DELL_PRECISION_490] = {
- .fan_mult = 1,
- .fan_max = I8K_FAN_TURBO,
- },
- [DELL_STUDIO] = {
- .fan_mult = 1,
- .fan_max = I8K_FAN_HIGH,
- },
- [DELL_XPS] = {
- .fan_mult = 1,
- .fan_max = I8K_FAN_HIGH,
- },
-};
+ data->ops = ops;
+ /* All options must not be 0 */
+ data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
+ data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
+ data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
+
+ return 0;
+}
static const struct dmi_system_id i8k_dmi_table[] __initconst = {
{
@@ -1097,6 +1268,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
},
},
{
+ .ident = "Dell G5 5505",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G5 5505"),
+ },
+ },
+ {
.ident = "Dell Inspiron",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
@@ -1118,14 +1296,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
},
},
{
- .ident = "Dell Latitude D520",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
- },
- .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
- },
- {
.ident = "Dell Latitude 2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1147,13 +1317,25 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
},
},
{
- .ident = "Dell Precision 490",
+ .ident = "Dell OptiPlex 7060",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7060"),
+ },
+ },
+ {
+ .ident = "Dell OptiPlex 7050",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"),
+ },
+ },
+ {
+ .ident = "Dell OptiPlex 7040",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME,
- "Precision WorkStation 490"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7040"),
},
- .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
},
{
.ident = "Dell Precision",
@@ -1175,7 +1357,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
},
- .driver_data = (void *)&i8k_config_data[DELL_STUDIO],
},
{
.ident = "Dell XPS M140",
@@ -1183,7 +1364,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
},
- .driver_data = (void *)&i8k_config_data[DELL_XPS],
},
{
.ident = "Dell XPS",
@@ -1198,6 +1378,64 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
/*
+ * Only use for machines which need some special configuration
+ * in order to work correctly (e.g. if autoconfig fails on this machines).
+ */
+struct i8k_config_data {
+ uint fan_mult;
+ uint fan_max;
+};
+
+enum i8k_configs {
+ DELL_LATITUDE_D520,
+ DELL_STUDIO,
+ DELL_XPS,
+};
+
+static const struct i8k_config_data i8k_config_data[] __initconst = {
+ [DELL_LATITUDE_D520] = {
+ .fan_mult = 1,
+ .fan_max = I8K_FAN_TURBO,
+ },
+ [DELL_STUDIO] = {
+ .fan_mult = 1,
+ .fan_max = I8K_FAN_HIGH,
+ },
+ [DELL_XPS] = {
+ .fan_mult = 1,
+ .fan_max = I8K_FAN_HIGH,
+ },
+};
+
+static const struct dmi_system_id i8k_config_dmi_table[] __initconst = {
+ {
+ .ident = "Dell Latitude D520",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
+ },
+ .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
+ },
+ {
+ .ident = "Dell Studio",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
+ },
+ .driver_data = (void *)&i8k_config_data[DELL_STUDIO],
+ },
+ {
+ .ident = "Dell XPS M140",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
+ },
+ .driver_data = (void *)&i8k_config_data[DELL_XPS],
+ },
+ { }
+};
+
+/*
* On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
* randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
* of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
@@ -1279,10 +1517,15 @@ struct i8k_fan_control_data {
};
enum i8k_fan_controls {
+ I8K_FAN_30A3_31A3,
I8K_FAN_34A3_35A3,
};
static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
+ [I8K_FAN_30A3_31A3] = {
+ .manual_fan = 0x30a3,
+ .auto_fan = 0x31a3,
+ },
[I8K_FAN_34A3_35A3] = {
.manual_fan = 0x34a3,
.auto_fan = 0x35a3,
@@ -1291,6 +1534,15 @@ static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
{
+ .ident = "Dell G5 5505",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G5 5505"),
+
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+ },
+ {
.ident = "Dell Latitude 5480",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1299,6 +1551,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
{
+ .ident = "Dell Latitude 7320",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 7320"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
+ },
+ {
.ident = "Dell Latitude E6440",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1331,6 +1591,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
{
+ .ident = "Dell Precision 7540",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7540"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+ },
+ {
.ident = "Dell XPS 13 7390",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1338,119 +1606,199 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
+ {
+ .ident = "Dell XPS 13 9370",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 9370"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
+ },
+ {
+ .ident = "Dell Optiplex 7000",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7000"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+ },
+ {
+ .ident = "Dell XPS 9315",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 9315"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
+ },
+ {
+ .ident = "Dell G15 5511",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
+ },
{ }
};
+/*
+ * Legacy SMM backend driver.
+ */
static int __init dell_smm_probe(struct platform_device *pdev)
{
- struct dell_smm_data *data;
- const struct dmi_system_id *id, *fan_control;
int ret;
- data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
- if (!data)
+ ret = dell_smm_init_data(&pdev->dev, &i8k_smm_ops);
+ if (ret < 0)
+ return ret;
+
+ ret = dell_smm_init_hwmon(&pdev->dev);
+ if (ret)
+ return ret;
+
+ i8k_init_procfs(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver dell_smm_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+static struct platform_device *dell_smm_device;
+
+/*
+ * WMI SMM backend driver.
+ */
+static int dell_smm_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct dell_smm_ops *ops;
+ int ret;
+
+ ops = devm_kzalloc(&wdev->dev, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
return -ENOMEM;
- mutex_init(&data->i8k_mutex);
- platform_set_drvdata(pdev, data);
+ ops->smm_call = wmi_smm_call;
+ ops->smm_dev = &wdev->dev;
+
+ if (dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG1) &&
+ dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG2))
+ return -ENODEV;
+
+ ret = dell_smm_init_data(&wdev->dev, ops);
+ if (ret < 0)
+ return ret;
+
+ return dell_smm_init_hwmon(&wdev->dev);
+}
+
+static const struct wmi_device_id dell_smm_wmi_id_table[] = {
+ { DELL_SMM_WMI_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, dell_smm_wmi_id_table);
+
+static struct wmi_driver dell_smm_wmi_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = dell_smm_wmi_id_table,
+ .probe = dell_smm_wmi_probe,
+ .no_singleton = true,
+};
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static void __init dell_smm_init_dmi(void)
+{
+ struct i8k_fan_control_data *control;
+ struct i8k_config_data *config;
+ const struct dmi_system_id *id;
if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
if (!force) {
- dev_notice(&pdev->dev, "Disabling fan support due to BIOS bugs\n");
- data->disallow_fan_support = true;
+ pr_notice("Disabling fan support due to BIOS bugs\n");
+ disallow_fan_support = true;
} else {
- dev_warn(&pdev->dev, "Enabling fan support despite BIOS bugs\n");
+ pr_warn("Enabling fan support despite BIOS bugs\n");
}
}
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
if (!force) {
- dev_notice(&pdev->dev, "Disabling fan type call due to BIOS bugs\n");
- data->disallow_fan_type_call = true;
+ pr_notice("Disabling fan type call due to BIOS bugs\n");
+ disallow_fan_type_call = true;
} else {
- dev_warn(&pdev->dev, "Enabling fan type call despite BIOS bugs\n");
+ pr_warn("Enabling fan type call despite BIOS bugs\n");
}
}
- strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
- sizeof(data->bios_version));
- strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
- sizeof(data->bios_machineid));
-
/*
- * Set fan multiplier and maximal fan speed from dmi config
- * Values specified in module parameters override values from dmi
+ * Set fan multiplier and maximal fan speed from DMI config.
+ * Values specified in module parameters override values from DMI.
*/
- id = dmi_first_match(i8k_dmi_table);
+ id = dmi_first_match(i8k_config_dmi_table);
if (id && id->driver_data) {
- const struct i8k_config_data *conf = id->driver_data;
-
- if (!fan_mult && conf->fan_mult)
- fan_mult = conf->fan_mult;
+ config = id->driver_data;
+ if (!fan_mult && config->fan_mult)
+ fan_mult = config->fan_mult;
- if (!fan_max && conf->fan_max)
- fan_max = conf->fan_max;
+ if (!fan_max && config->fan_max)
+ fan_max = config->fan_max;
}
- /* All options must not be 0 */
- data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
- data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
- data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
-
- fan_control = dmi_first_match(i8k_whitelist_fan_control);
- if (fan_control && fan_control->driver_data) {
- const struct i8k_fan_control_data *control = fan_control->driver_data;
+ id = dmi_first_match(i8k_whitelist_fan_control);
+ if (id && id->driver_data) {
+ control = id->driver_data;
+ manual_fan = control->manual_fan;
+ auto_fan = control->auto_fan;
- data->manual_fan = control->manual_fan;
- data->auto_fan = control->auto_fan;
- dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
+ pr_info("Enabling support for setting automatic/manual fan control\n");
}
-
- ret = dell_smm_init_hwmon(&pdev->dev);
- if (ret)
- return ret;
-
- i8k_init_procfs(&pdev->dev);
-
- return 0;
}
-static struct platform_driver dell_smm_driver = {
- .driver = {
- .name = KBUILD_MODNAME,
- },
-};
-
-static struct platform_device *dell_smm_device;
-
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_init(void)
+static int __init dell_smm_legacy_check(void)
{
- /*
- * Get DMI information
- */
if (!dmi_check_system(i8k_dmi_table)) {
if (!ignore_dmi && !force)
return -ENODEV;
- pr_info("not running on a supported Dell system.\n");
+ pr_info("Probing for legacy SMM handler on unsupported machine\n");
pr_info("vendor=%s, model=%s, version=%s\n",
i8k_get_dmi_data(DMI_SYS_VENDOR),
i8k_get_dmi_data(DMI_PRODUCT_NAME),
i8k_get_dmi_data(DMI_BIOS_VERSION));
}
- /*
- * Get SMM Dell signature
- */
- if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
- i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+ if (dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG1) &&
+ dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG2)) {
if (!force)
return -ENODEV;
- pr_err("Unable to get Dell SMM signature\n");
+ pr_warn("Forcing legacy SMM calls on a possibly incompatible machine\n");
+ }
+
+ return 0;
+}
+
+static int __init i8k_init(void)
+{
+ int ret;
+
+ dell_smm_init_dmi();
+
+ ret = dell_smm_legacy_check();
+ if (ret < 0) {
+ /*
+ * On modern machines, SMM communication happens over WMI, meaning
+ * the SMM handler might not react to legacy SMM calls.
+ */
+ return wmi_driver_register(&dell_smm_wmi_driver);
}
dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
@@ -1461,8 +1809,12 @@ static int __init i8k_init(void)
static void __exit i8k_exit(void)
{
- platform_device_unregister(dell_smm_device);
- platform_driver_unregister(&dell_smm_driver);
+ if (dell_smm_device) {
+ platform_device_unregister(dell_smm_device);
+ platform_driver_unregister(&dell_smm_driver);
+ } else {
+ wmi_driver_unregister(&dell_smm_wmi_driver);
+ }
}
module_init(i8k_init);
diff --git a/drivers/hwmon/dme1737.c b/drivers/hwmon/dme1737.c
index cdbf3dff9172..3d4057309950 100644
--- a/drivers/hwmon/dme1737.c
+++ b/drivers/hwmon/dme1737.c
@@ -2461,8 +2461,6 @@ static int dme1737_i2c_detect(struct i2c_client *client,
return 0;
}
-static const struct i2c_device_id dme1737_id[];
-
static int dme1737_i2c_probe(struct i2c_client *client)
{
struct dme1737_data *data;
@@ -2474,7 +2472,7 @@ static int dme1737_i2c_probe(struct i2c_client *client)
return -ENOMEM;
i2c_set_clientdata(client, data);
- data->type = i2c_match_id(dme1737_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
data->client = client;
data->name = client->name;
mutex_init(&data->update_lock);
@@ -2710,14 +2708,12 @@ exit_remove_files:
return err;
}
-static int dme1737_isa_remove(struct platform_device *pdev)
+static void dme1737_isa_remove(struct platform_device *pdev)
{
struct dme1737_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
dme1737_remove_files(&pdev->dev);
-
- return 0;
}
static struct platform_driver dme1737_isa_driver = {
diff --git a/drivers/hwmon/drivetemp.c b/drivers/hwmon/drivetemp.c
index 6bdd21aa005a..9c5b021aab86 100644
--- a/drivers/hwmon/drivetemp.c
+++ b/drivers/hwmon/drivetemp.c
@@ -102,7 +102,6 @@
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_driver.h>
@@ -110,7 +109,6 @@
struct drivetemp_data {
struct list_head list; /* list of instantiated devices */
- struct mutex lock; /* protect data buffer accesses */
struct scsi_device *sdev; /* SCSI device */
struct device *dev; /* instantiating device */
struct device *hwdev; /* hardware monitoring device */
@@ -165,6 +163,7 @@ static int drivetemp_scsi_command(struct drivetemp_data *st,
{
u8 scsi_cmd[MAX_COMMAND_SIZE];
enum req_op op;
+ int err;
memset(scsi_cmd, 0, sizeof(scsi_cmd));
scsi_cmd[0] = ATA_16;
@@ -192,8 +191,11 @@ static int drivetemp_scsi_command(struct drivetemp_data *st,
scsi_cmd[12] = lba_high;
scsi_cmd[14] = ata_command;
- return scsi_execute_cmd(st->sdev, scsi_cmd, op, st->smartdata,
- ATA_SECT_SIZE, HZ, 5, NULL);
+ err = scsi_execute_cmd(st->sdev, scsi_cmd, op, st->smartdata,
+ ATA_SECT_SIZE, 10 * HZ, 5, NULL);
+ if (err > 0)
+ err = -EIO;
+ return err;
}
static int drivetemp_ata_command(struct drivetemp_data *st, u8 feature,
@@ -458,9 +460,7 @@ static int drivetemp_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_temp_input:
case hwmon_temp_lowest:
case hwmon_temp_highest:
- mutex_lock(&st->lock);
err = st->get_temp(st, attr, val);
- mutex_unlock(&st->lock);
break;
case hwmon_temp_lcrit:
*val = st->temp_lcrit;
@@ -562,7 +562,6 @@ static int drivetemp_add(struct device *dev)
st->sdev = sdev;
st->dev = dev;
- mutex_init(&st->lock);
if (drivetemp_identify(st)) {
err = -ENODEV;
diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c
index 21b635046521..42ec34cb8a5f 100644
--- a/drivers/hwmon/ds1621.c
+++ b/drivers/hwmon/ds1621.c
@@ -342,8 +342,6 @@ static const struct attribute_group ds1621_group = {
};
__ATTRIBUTE_GROUPS(ds1621);
-static const struct i2c_device_id ds1621_id[];
-
static int ds1621_probe(struct i2c_client *client)
{
struct ds1621_data *data;
@@ -356,7 +354,7 @@ static int ds1621_probe(struct i2c_client *client)
mutex_init(&data->update_lock);
- data->kind = i2c_match_id(ds1621_id, client)->driver_data;
+ data->kind = (uintptr_t)i2c_get_match_data(client);
data->client = client;
/* Initialize the DS1621 chip */
@@ -380,7 +378,6 @@ MODULE_DEVICE_TABLE(i2c, ds1621_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds1621_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ds1621",
},
diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c
index 2b09536630cb..ce397042d90b 100644
--- a/drivers/hwmon/ds620.c
+++ b/drivers/hwmon/ds620.c
@@ -233,7 +233,7 @@ static int ds620_probe(struct i2c_client *client)
}
static const struct i2c_device_id ds620_id[] = {
- {"ds620", 0},
+ {"ds620"},
{}
};
@@ -241,7 +241,6 @@ MODULE_DEVICE_TABLE(i2c, ds620_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds620_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ds620",
},
diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c
index bb7c859e799d..ccce948a4306 100644
--- a/drivers/hwmon/emc1403.c
+++ b/drivers/hwmon/emc1403.c
@@ -17,70 +17,35 @@
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/sysfs.h>
-#include <linux/mutex.h>
#include <linux/regmap.h>
+#include <linux/util_macros.h>
#define THERMAL_PID_REG 0xfd
#define THERMAL_SMSC_ID_REG 0xfe
#define THERMAL_REVISION_REG 0xff
-enum emc1403_chip { emc1402, emc1403, emc1404 };
+enum emc1403_chip { emc1402, emc1403, emc1404, emc1428 };
struct thermal_data {
+ enum emc1403_chip chip;
struct regmap *regmap;
- struct mutex mutex;
- const struct attribute_group *groups[4];
};
-static ssize_t temp_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static ssize_t power_state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
struct thermal_data *data = dev_get_drvdata(dev);
unsigned int val;
int retval;
- retval = regmap_read(data->regmap, sda->index, &val);
+ retval = regmap_read(data->regmap, 0x03, &val);
if (retval < 0)
return retval;
- return sprintf(buf, "%d000\n", val);
+ return sprintf(buf, "%d\n", !!(val & BIT(6)));
}
-static ssize_t bit_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static ssize_t power_state_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
{
- struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr);
- struct thermal_data *data = dev_get_drvdata(dev);
- unsigned int val;
- int retval;
-
- retval = regmap_read(data->regmap, sda->nr, &val);
- if (retval < 0)
- return retval;
- return sprintf(buf, "%d\n", !!(val & sda->index));
-}
-
-static ssize_t temp_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
- struct thermal_data *data = dev_get_drvdata(dev);
- unsigned long val;
- int retval;
-
- if (kstrtoul(buf, 10, &val))
- return -EINVAL;
- retval = regmap_write(data->regmap, sda->index,
- DIV_ROUND_CLOSEST(val, 1000));
- if (retval < 0)
- return retval;
- return count;
-}
-
-static ssize_t bit_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr);
struct thermal_data *data = dev_get_drvdata(dev);
unsigned long val;
int retval;
@@ -88,233 +53,20 @@ static ssize_t bit_store(struct device *dev, struct device_attribute *attr,
if (kstrtoul(buf, 10, &val))
return -EINVAL;
- retval = regmap_update_bits(data->regmap, sda->nr, sda->index,
- val ? sda->index : 0);
+ retval = regmap_update_bits(data->regmap, 0x03, BIT(6),
+ val ? BIT(6) : 0);
if (retval < 0)
return retval;
return count;
}
-static ssize_t show_hyst_common(struct device *dev,
- struct device_attribute *attr, char *buf,
- bool is_min)
-{
- struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
- struct thermal_data *data = dev_get_drvdata(dev);
- struct regmap *regmap = data->regmap;
- unsigned int limit;
- unsigned int hyst;
- int retval;
-
- retval = regmap_read(regmap, sda->index, &limit);
- if (retval < 0)
- return retval;
-
- retval = regmap_read(regmap, 0x21, &hyst);
- if (retval < 0)
- return retval;
-
- return sprintf(buf, "%d000\n", is_min ? limit + hyst : limit - hyst);
-}
-
-static ssize_t hyst_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- return show_hyst_common(dev, attr, buf, false);
-}
-
-static ssize_t min_hyst_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return show_hyst_common(dev, attr, buf, true);
-}
-
-static ssize_t hyst_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
- struct thermal_data *data = dev_get_drvdata(dev);
- struct regmap *regmap = data->regmap;
- unsigned int limit;
- int retval;
- int hyst;
- unsigned long val;
-
- if (kstrtoul(buf, 10, &val))
- return -EINVAL;
-
- mutex_lock(&data->mutex);
- retval = regmap_read(regmap, sda->index, &limit);
- if (retval < 0)
- goto fail;
-
- hyst = limit * 1000 - val;
- hyst = clamp_val(DIV_ROUND_CLOSEST(hyst, 1000), 0, 255);
- retval = regmap_write(regmap, 0x21, hyst);
- if (retval == 0)
- retval = count;
-fail:
- mutex_unlock(&data->mutex);
- return retval;
-}
-
-/*
- * Sensors. We pass the actual i2c register to the methods.
- */
-
-static SENSOR_DEVICE_ATTR_RW(temp1_min, temp, 0x06);
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, 0x05);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit, temp, 0x20);
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0x00);
-static SENSOR_DEVICE_ATTR_2_RO(temp1_min_alarm, bit, 0x36, 0x01);
-static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, bit, 0x35, 0x01);
-static SENSOR_DEVICE_ATTR_2_RO(temp1_crit_alarm, bit, 0x37, 0x01);
-static SENSOR_DEVICE_ATTR_RO(temp1_min_hyst, min_hyst, 0x06);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_hyst, hyst, 0x05);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, hyst, 0x20);
-
-static SENSOR_DEVICE_ATTR_RW(temp2_min, temp, 0x08);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, 0x07);
-static SENSOR_DEVICE_ATTR_RW(temp2_crit, temp, 0x19);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 0x01);
-static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, bit, 0x1b, 0x02);
-static SENSOR_DEVICE_ATTR_2_RO(temp2_min_alarm, bit, 0x36, 0x02);
-static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, bit, 0x35, 0x02);
-static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, bit, 0x37, 0x02);
-static SENSOR_DEVICE_ATTR_RO(temp2_min_hyst, min_hyst, 0x08);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_hyst, hyst, 0x07);
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, hyst, 0x19);
-
-static SENSOR_DEVICE_ATTR_RW(temp3_min, temp, 0x16);
-static SENSOR_DEVICE_ATTR_RW(temp3_max, temp, 0x15);
-static SENSOR_DEVICE_ATTR_RW(temp3_crit, temp, 0x1A);
-static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 0x23);
-static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, bit, 0x1b, 0x04);
-static SENSOR_DEVICE_ATTR_2_RO(temp3_min_alarm, bit, 0x36, 0x04);
-static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, bit, 0x35, 0x04);
-static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, bit, 0x37, 0x04);
-static SENSOR_DEVICE_ATTR_RO(temp3_min_hyst, min_hyst, 0x16);
-static SENSOR_DEVICE_ATTR_RO(temp3_max_hyst, hyst, 0x15);
-static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, hyst, 0x1A);
-
-static SENSOR_DEVICE_ATTR_RW(temp4_min, temp, 0x2D);
-static SENSOR_DEVICE_ATTR_RW(temp4_max, temp, 0x2C);
-static SENSOR_DEVICE_ATTR_RW(temp4_crit, temp, 0x30);
-static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 0x2A);
-static SENSOR_DEVICE_ATTR_2_RO(temp4_fault, bit, 0x1b, 0x08);
-static SENSOR_DEVICE_ATTR_2_RO(temp4_min_alarm, bit, 0x36, 0x08);
-static SENSOR_DEVICE_ATTR_2_RO(temp4_max_alarm, bit, 0x35, 0x08);
-static SENSOR_DEVICE_ATTR_2_RO(temp4_crit_alarm, bit, 0x37, 0x08);
-static SENSOR_DEVICE_ATTR_RO(temp4_min_hyst, min_hyst, 0x2D);
-static SENSOR_DEVICE_ATTR_RO(temp4_max_hyst, hyst, 0x2C);
-static SENSOR_DEVICE_ATTR_RO(temp4_crit_hyst, hyst, 0x30);
-
-static SENSOR_DEVICE_ATTR_2_RW(power_state, bit, 0x03, 0x40);
-
-static struct attribute *emc1402_attrs[] = {
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_min_hyst.dev_attr.attr,
- &sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
-
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp2_min_hyst.dev_attr.attr,
- &sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
-
- &sensor_dev_attr_power_state.dev_attr.attr,
- NULL
-};
-
-static const struct attribute_group emc1402_group = {
- .attrs = emc1402_attrs,
-};
+static DEVICE_ATTR_RW(power_state);
static struct attribute *emc1403_attrs[] = {
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
-
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
-
- &sensor_dev_attr_temp3_min.dev_attr.attr,
- &sensor_dev_attr_temp3_max.dev_attr.attr,
- &sensor_dev_attr_temp3_crit.dev_attr.attr,
- &sensor_dev_attr_temp3_input.dev_attr.attr,
- &sensor_dev_attr_temp3_fault.dev_attr.attr,
- &sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_min_hyst.dev_attr.attr,
- &sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+ &dev_attr_power_state.attr,
NULL
};
-
-static const struct attribute_group emc1403_group = {
- .attrs = emc1403_attrs,
-};
-
-static struct attribute *emc1404_attrs[] = {
- &sensor_dev_attr_temp4_min.dev_attr.attr,
- &sensor_dev_attr_temp4_max.dev_attr.attr,
- &sensor_dev_attr_temp4_crit.dev_attr.attr,
- &sensor_dev_attr_temp4_input.dev_attr.attr,
- &sensor_dev_attr_temp4_fault.dev_attr.attr,
- &sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_min_hyst.dev_attr.attr,
- &sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp4_crit_hyst.dev_attr.attr,
- NULL
-};
-
-static const struct attribute_group emc1404_group = {
- .attrs = emc1404_attrs,
-};
-
-/*
- * EMC14x2 uses a different register and different bits to report alarm and
- * fault status. For simplicity, provide a separate attribute group for this
- * chip series.
- * Since we can not re-use the same attribute names, create a separate attribute
- * array.
- */
-static struct sensor_device_attribute_2 emc1402_alarms[] = {
- SENSOR_ATTR_2_RO(temp1_min_alarm, bit, 0x02, 0x20),
- SENSOR_ATTR_2_RO(temp1_max_alarm, bit, 0x02, 0x40),
- SENSOR_ATTR_2_RO(temp1_crit_alarm, bit, 0x02, 0x01),
-
- SENSOR_ATTR_2_RO(temp2_fault, bit, 0x02, 0x04),
- SENSOR_ATTR_2_RO(temp2_min_alarm, bit, 0x02, 0x08),
- SENSOR_ATTR_2_RO(temp2_max_alarm, bit, 0x02, 0x10),
- SENSOR_ATTR_2_RO(temp2_crit_alarm, bit, 0x02, 0x02),
-};
-
-static struct attribute *emc1402_alarm_attrs[] = {
- &emc1402_alarms[0].dev_attr.attr,
- &emc1402_alarms[1].dev_attr.attr,
- &emc1402_alarms[2].dev_attr.attr,
- &emc1402_alarms[3].dev_attr.attr,
- &emc1402_alarms[4].dev_attr.attr,
- &emc1402_alarms[5].dev_attr.attr,
- &emc1402_alarms[6].dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group emc1402_alarm_group = {
- .attrs = emc1402_alarm_attrs,
-};
+ATTRIBUTE_GROUPS(emc1403);
static int emc1403_detect(struct i2c_client *client,
struct i2c_board_info *info)
@@ -346,6 +98,15 @@ static int emc1403_detect(struct i2c_client *client,
case 0x27:
strscpy(info->type, "emc1424", I2C_NAME_SIZE);
break;
+ case 0x29:
+ strscpy(info->type, "emc1428", I2C_NAME_SIZE);
+ break;
+ case 0x59:
+ strscpy(info->type, "emc1438", I2C_NAME_SIZE);
+ break;
+ case 0x60:
+ strscpy(info->type, "emc1442", I2C_NAME_SIZE);
+ break;
default:
return -ENODEV;
}
@@ -373,6 +134,14 @@ static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg)
case 0x35: /* high limit status */
case 0x36: /* low limit status */
case 0x37: /* therm limit status */
+ case 0x41: /* external diode 4 high byte */
+ case 0x42: /* external diode 4 low byte */
+ case 0x43: /* external diode 5 high byte */
+ case 0x44: /* external diode 5 low byte */
+ case 0x45: /* external diode 6 high byte */
+ case 0x46: /* external diode 6 low byte */
+ case 0x47: /* external diode 7 high byte */
+ case 0x48: /* external diode 7 low byte */
return true;
default:
return false;
@@ -382,55 +151,490 @@ static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config emc1403_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = emc1403_regmap_is_volatile,
};
-static const struct i2c_device_id emc1403_idtable[];
+enum emc1403_reg_map {temp_min, temp_max, temp_crit, temp_input};
-static int emc1403_probe(struct i2c_client *client)
+static u8 ema1403_temp_map[] = {
+ [hwmon_temp_min] = temp_min,
+ [hwmon_temp_max] = temp_max,
+ [hwmon_temp_crit] = temp_crit,
+ [hwmon_temp_input] = temp_input,
+};
+
+static u8 emc1403_temp_regs[][4] = {
+ [0] = {
+ [temp_min] = 0x06,
+ [temp_max] = 0x05,
+ [temp_crit] = 0x20,
+ [temp_input] = 0x00,
+ },
+ [1] = {
+ [temp_min] = 0x08,
+ [temp_max] = 0x07,
+ [temp_crit] = 0x19,
+ [temp_input] = 0x01,
+ },
+ [2] = {
+ [temp_min] = 0x16,
+ [temp_max] = 0x15,
+ [temp_crit] = 0x1a,
+ [temp_input] = 0x23,
+ },
+ [3] = {
+ [temp_min] = 0x2d,
+ [temp_max] = 0x2c,
+ [temp_crit] = 0x30,
+ [temp_input] = 0x2a,
+ },
+ [4] = {
+ [temp_min] = 0x51,
+ [temp_max] = 0x50,
+ [temp_crit] = 0x64,
+ [temp_input] = 0x41,
+ },
+ [5] = {
+ [temp_min] = 0x55,
+ [temp_max] = 0x54,
+ [temp_crit] = 0x65,
+ [temp_input] = 0x43
+ },
+ [6] = {
+ [temp_min] = 0x59,
+ [temp_max] = 0x58,
+ [temp_crit] = 0x66,
+ [temp_input] = 0x45,
+ },
+ [7] = {
+ [temp_min] = 0x5d,
+ [temp_max] = 0x5c,
+ [temp_crit] = 0x67,
+ [temp_input] = 0x47,
+ },
+};
+
+static s8 emc1403_temp_regs_low[][4] = {
+ [0] = {
+ [temp_min] = -1,
+ [temp_max] = -1,
+ [temp_crit] = -1,
+ [temp_input] = 0x29,
+ },
+ [1] = {
+ [temp_min] = 0x14,
+ [temp_max] = 0x13,
+ [temp_crit] = -1,
+ [temp_input] = 0x10,
+ },
+ [2] = {
+ [temp_min] = 0x18,
+ [temp_max] = 0x17,
+ [temp_crit] = -1,
+ [temp_input] = 0x24,
+ },
+ [3] = {
+ [temp_min] = 0x2f,
+ [temp_max] = 0x2e,
+ [temp_crit] = -1,
+ [temp_input] = 0x2b,
+ },
+ [4] = {
+ [temp_min] = 0x53,
+ [temp_max] = 0x52,
+ [temp_crit] = -1,
+ [temp_input] = 0x42,
+ },
+ [5] = {
+ [temp_min] = 0x57,
+ [temp_max] = 0x56,
+ [temp_crit] = -1,
+ [temp_input] = 0x44,
+ },
+ [6] = {
+ [temp_min] = 0x5b,
+ [temp_max] = 0x5a,
+ [temp_crit] = -1,
+ [temp_input] = 0x46,
+ },
+ [7] = {
+ [temp_min] = 0x5f,
+ [temp_max] = 0x5e,
+ [temp_crit] = -1,
+ [temp_input] = 0x48,
+ },
+};
+
+static int emc1403_get_temp(struct thermal_data *data, int channel,
+ enum emc1403_reg_map map, long *val)
{
- struct thermal_data *data;
- struct device *hwmon_dev;
- const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client);
+ unsigned int regvalh;
+ unsigned int regvall = 0;
+ int ret;
+ s8 reg;
+
+ ret = regmap_read(data->regmap, emc1403_temp_regs[channel][map], &regvalh);
+ if (ret < 0)
+ return ret;
+
+ reg = emc1403_temp_regs_low[channel][map];
+ if (reg >= 0) {
+ ret = regmap_read(data->regmap, reg, &regvall);
+ if (ret < 0)
+ return ret;
+ }
- data = devm_kzalloc(&client->dev, sizeof(struct thermal_data),
- GFP_KERNEL);
- if (data == NULL)
- return -ENOMEM;
+ if (data->chip == emc1428)
+ *val = sign_extend32((regvalh << 3) | (regvall >> 5), 10) * 125;
+ else
+ *val = ((regvalh << 3) | (regvall >> 5)) * 125;
- data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config);
- if (IS_ERR(data->regmap))
- return PTR_ERR(data->regmap);
+ return 0;
+}
- mutex_init(&data->mutex);
-
- switch (id->driver_data) {
- case emc1404:
- data->groups[2] = &emc1404_group;
- fallthrough;
- case emc1403:
- data->groups[1] = &emc1403_group;
- fallthrough;
- case emc1402:
- data->groups[0] = &emc1402_group;
+static int emc1403_get_hyst(struct thermal_data *data, int channel,
+ enum emc1403_reg_map map, long *val)
+{
+ int hyst, ret;
+ long limit;
+
+ ret = emc1403_get_temp(data, channel, map, &limit);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(data->regmap, 0x21, &hyst);
+ if (ret < 0)
+ return ret;
+ if (map == temp_min)
+ *val = limit + hyst * 1000;
+ else
+ *val = limit - hyst * 1000;
+ return 0;
+}
+
+static int emc1403_temp_read(struct thermal_data *data, u32 attr, int channel, long *val)
+{
+ unsigned int regval;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ case hwmon_temp_input:
+ ret = emc1403_get_temp(data, channel, ema1403_temp_map[attr], val);
+ break;
+ case hwmon_temp_min_hyst:
+ ret = emc1403_get_hyst(data, channel, temp_min, val);
+ break;
+ case hwmon_temp_max_hyst:
+ ret = emc1403_get_hyst(data, channel, temp_max, val);
+ break;
+ case hwmon_temp_crit_hyst:
+ ret = emc1403_get_hyst(data, channel, temp_crit, val);
+ break;
+ case hwmon_temp_min_alarm:
+ if (data->chip == emc1402) {
+ ret = regmap_read(data->regmap, 0x02, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(5 - 2 * channel));
+ } else {
+ ret = regmap_read(data->regmap, 0x36, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(channel));
+ }
+ break;
+ case hwmon_temp_max_alarm:
+ if (data->chip == emc1402) {
+ ret = regmap_read(data->regmap, 0x02, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(6 - 2 * channel));
+ } else {
+ ret = regmap_read(data->regmap, 0x35, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(channel));
+ }
+ break;
+ case hwmon_temp_crit_alarm:
+ if (data->chip == emc1402) {
+ ret = regmap_read(data->regmap, 0x02, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(channel));
+ } else {
+ ret = regmap_read(data->regmap, 0x37, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(channel));
+ }
+ break;
+ case hwmon_temp_fault:
+ ret = regmap_read(data->regmap, 0x1b, &regval);
+ if (ret < 0)
+ break;
+ *val = !!(regval & BIT(channel));
+ break;
+ default:
+ return -EOPNOTSUPP;
}
+ return ret;
+}
- if (id->driver_data == emc1402)
- data->groups[1] = &emc1402_alarm_group;
+static int emc1403_get_convrate(struct thermal_data *data, long *val)
+{
+ unsigned int convrate;
+ int ret;
- hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
- client->name, data,
- data->groups);
- if (IS_ERR(hwmon_dev))
- return PTR_ERR(hwmon_dev);
+ ret = regmap_read(data->regmap, 0x04, &convrate);
+ if (ret < 0)
+ return ret;
+ if (convrate > 10)
+ convrate = 4;
- dev_info(&client->dev, "%s Thermal chip found\n", id->name);
+ *val = 16000 >> convrate;
return 0;
}
-static const unsigned short emc1403_address_list[] = {
- 0x18, 0x1c, 0x29, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END
+static int emc1403_chip_read(struct thermal_data *data, u32 attr, long *val)
+{
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return emc1403_get_convrate(data, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int emc1403_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct thermal_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return emc1403_temp_read(data, attr, channel, val);
+ case hwmon_chip:
+ return emc1403_chip_read(data, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int emc1403_set_hyst(struct thermal_data *data, long val)
+{
+ int hyst, ret;
+ long limit;
+
+ if (data->chip == emc1428)
+ val = clamp_val(val, -128000, 127000);
+ else
+ val = clamp_val(val, 0, 255000);
+
+ ret = emc1403_get_temp(data, 0, temp_crit, &limit);
+ if (ret < 0)
+ return ret;
+
+ hyst = limit - val;
+ if (data->chip == emc1428)
+ hyst = clamp_val(DIV_ROUND_CLOSEST(hyst, 1000), 0, 127);
+ else
+ hyst = clamp_val(DIV_ROUND_CLOSEST(hyst, 1000), 0, 255);
+ return regmap_write(data->regmap, 0x21, hyst);
+}
+
+static int emc1403_set_temp(struct thermal_data *data, int channel,
+ enum emc1403_reg_map map, long val)
+{
+ unsigned int regval;
+ int ret;
+ u8 regh;
+ s8 regl;
+
+ regh = emc1403_temp_regs[channel][map];
+ regl = emc1403_temp_regs_low[channel][map];
+
+ if (regl >= 0) {
+ if (data->chip == emc1428)
+ val = clamp_val(val, -128000, 127875);
+ else
+ val = clamp_val(val, 0, 255875);
+ regval = DIV_ROUND_CLOSEST(val, 125);
+ ret = regmap_write(data->regmap, regh, (regval >> 3) & 0xff);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(data->regmap, regl, (regval & 0x07) << 5);
+ } else {
+ if (data->chip == emc1428)
+ val = clamp_val(val, -128000, 127000);
+ else
+ val = clamp_val(val, 0, 255000);
+ regval = DIV_ROUND_CLOSEST(val, 1000);
+ ret = regmap_write(data->regmap, regh, regval);
+ }
+ return ret;
+}
+
+static int emc1403_temp_write(struct thermal_data *data, u32 attr, int channel, long val)
+{
+ switch (attr) {
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return emc1403_set_temp(data, channel, ema1403_temp_map[attr], val);
+ case hwmon_temp_crit_hyst:
+ return emc1403_set_hyst(data, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* Lookup table for temperature conversion times in msec */
+static const u16 ina3221_conv_time[] = {
+ 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62, 31, 16
+};
+
+static int emc1403_set_convrate(struct thermal_data *data, unsigned int interval)
+{
+ int convrate;
+
+ convrate = find_closest_descending(interval, ina3221_conv_time,
+ ARRAY_SIZE(ina3221_conv_time));
+ return regmap_write(data->regmap, 0x04, convrate);
+}
+
+static int emc1403_chip_write(struct thermal_data *data, u32 attr, long val)
+{
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return emc1403_set_convrate(data, clamp_val(val, 0, 100000));
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int emc1403_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct thermal_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return emc1403_temp_write(data, attr, channel, val);
+ case hwmon_chip:
+ return emc1403_chip_write(data, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t emc1403_temp_is_visible(const void *_data, u32 attr, int channel)
+{
+ const struct thermal_data *data = _data;
+
+ if (data->chip == emc1402 && channel > 1)
+ return 0;
+ if (data->chip == emc1403 && channel > 2)
+ return 0;
+ if (data->chip != emc1428 && channel > 3)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_fault:
+ case hwmon_temp_min_hyst:
+ case hwmon_temp_max_hyst:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return 0644;
+ case hwmon_temp_crit_hyst:
+ if (channel == 0)
+ return 0644;
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static umode_t emc1403_chip_is_visible(const void *_data, u32 attr)
+{
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t emc1403_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ return emc1403_temp_is_visible(data, attr, channel);
+ case hwmon_chip:
+ return emc1403_chip_is_visible(data, attr);
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info * const emc1403_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT
+ ),
+ NULL
+};
+
+static const struct hwmon_ops emc1403_hwmon_ops = {
+ .is_visible = emc1403_is_visible,
+ .read = emc1403_read,
+ .write = emc1403_write,
+};
+
+static const struct hwmon_chip_info emc1403_chip_info = {
+ .ops = &emc1403_hwmon_ops,
+ .info = emc1403_info,
};
/* Last digit of chip name indicates number of channels */
@@ -444,10 +648,40 @@ static const struct i2c_device_id emc1403_idtable[] = {
{ "emc1422", emc1402 },
{ "emc1423", emc1403 },
{ "emc1424", emc1404 },
+ { "emc1428", emc1428 },
+ { "emc1438", emc1428 },
+ { "emc1442", emc1402 },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc1403_idtable);
+static int emc1403_probe(struct i2c_client *client)
+{
+ struct thermal_data *data;
+ struct device *hwmon_dev;
+ const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client);
+
+ data = devm_kzalloc(&client->dev, sizeof(struct thermal_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->chip = id->driver_data;
+ data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
+ client->name, data,
+ &emc1403_chip_info,
+ emc1403_groups);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const unsigned short emc1403_address_list[] = {
+ 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END
+};
+
static struct i2c_driver sensor_emc1403 = {
.class = I2C_CLASS_HWMON,
.driver = {
diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c
index b59472bbe5bf..9b8e925af030 100644
--- a/drivers/hwmon/emc2103.c
+++ b/drivers/hwmon/emc2103.c
@@ -277,8 +277,10 @@ fan1_input_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2103_data *data = emc2103_update_device(dev);
int rpm = 0;
+ mutex_lock(&data->update_lock);
if (data->fan_tach != 0)
rpm = (FAN_RPM_FACTOR * data->fan_multiplier) / data->fan_tach;
+ mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", rpm);
}
@@ -363,10 +365,12 @@ fan1_target_show(struct device *dev, struct device_attribute *da, char *buf)
struct emc2103_data *data = emc2103_update_device(dev);
int rpm = 0;
+ mutex_lock(&data->update_lock);
/* high byte of 0xff indicates disabled so return 0 */
if ((data->fan_target != 0) && ((data->fan_target & 0x1fe0) != 0x1fe0))
rpm = (FAN_RPM_FACTOR * data->fan_multiplier)
/ data->fan_target;
+ mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", rpm);
}
@@ -620,7 +624,7 @@ emc2103_probe(struct i2c_client *client)
}
static const struct i2c_device_id emc2103_ids[] = {
- { "emc2103", 0, },
+ { "emc2103" },
{ /* LIST END */ }
};
MODULE_DEVICE_TABLE(i2c, emc2103_ids);
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 29f0e4945f19..ceae96c07ac4 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -11,9 +11,9 @@
#include <linux/module.h>
#include <linux/platform_data/emc2305.h>
#include <linux/thermal.h>
-
-static const unsigned short
-emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END };
+#include <linux/pwm.h>
+#include <linux/of_device.h>
+#include <linux/util_macros.h>
#define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
#define EMC2305_REG_VENDOR 0xfe
@@ -26,6 +26,12 @@ emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_EN
#define EMC2305_TACH_REGS_UNUSE_BITS 3
#define EMC2305_TACH_CNT_MULTIPLIER 0x02
#define EMC2305_TACH_RANGE_MIN 480
+#define EMC2305_DEFAULT_OUTPUT 0x0
+#define EMC2305_DEFAULT_POLARITY 0x0
+#define EMC2305_REG_POLARITY 0x2a
+#define EMC2305_REG_DRIVE_PWM_OUT 0x2b
+#define EMC2305_OPEN_DRAIN 0x0
+#define EMC2305_PUSH_PULL 0x1
#define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max))
@@ -42,6 +48,9 @@ emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_EN
#define EMC2305_REG_FAN_MIN_DRIVE(n) (0x38 + 0x10 * (n))
#define EMC2305_REG_FAN_TACH(n) (0x3e + 0x10 * (n))
+/* Supported base PWM frequencies */
+static const unsigned int base_freq_table[] = { 2441, 4882, 19530, 26000 };
+
enum emc230x_product_id {
EMC2305 = 0x34,
EMC2303 = 0x35,
@@ -50,10 +59,10 @@ enum emc230x_product_id {
};
static const struct i2c_device_id emc2305_ids[] = {
- { "emc2305", 0 },
- { "emc2303", 0 },
- { "emc2302", 0 },
- { "emc2301", 0 },
+ { "emc2305" },
+ { "emc2303" },
+ { "emc2302" },
+ { "emc2301" },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc2305_ids);
@@ -92,8 +101,11 @@ struct emc2305_cdev_data {
* @hwmon_dev: hwmon device
* @max_state: maximum cooling state of the cooling device
* @pwm_num: number of PWM channels
+ * @pwm_output_mask: PWM output mask
+ * @pwm_polarity_mask: PWM polarity mask
* @pwm_separate: separate PWM settings for every channel
* @pwm_min: array of minimum PWM per channel
+ * @pwm_freq: array of PWM frequency per channel
* @cdev_data: array of cooling devices data
*/
struct emc2305_data {
@@ -101,8 +113,11 @@ struct emc2305_data {
struct device *hwmon_dev;
u8 max_state;
u8 pwm_num;
+ u8 pwm_output_mask;
+ u8 pwm_polarity_mask;
bool pwm_separate;
u8 pwm_min[EMC2305_PWM_MAX];
+ u16 pwm_freq[EMC2305_PWM_MAX];
struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
};
@@ -115,8 +130,6 @@ static char *emc2305_fan_name[] = {
"emc2305_fan5",
};
-static void emc2305_unset_tz(struct device *dev);
-
static int emc2305_get_max_channel(const struct emc2305_data *data)
{
return data->pwm_num;
@@ -286,7 +299,7 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel)
return 0;
}
-static int emc2305_set_single_tz(struct device *dev, int idx)
+static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_node, int idx)
{
struct emc2305_data *data = dev_get_drvdata(dev);
long pwm;
@@ -296,13 +309,20 @@ static int emc2305_set_single_tz(struct device *dev, int idx)
pwm = data->pwm_min[cdev_idx];
data->cdev_data[cdev_idx].cdev =
- thermal_cooling_device_register(emc2305_fan_name[idx], data,
- &emc2305_cooling_ops);
+ devm_thermal_of_cooling_device_register(dev, fan_node,
+ emc2305_fan_name[idx], data,
+ &emc2305_cooling_ops);
if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
return PTR_ERR(data->cdev_data[cdev_idx].cdev);
}
+
+ if (data->cdev_data[cdev_idx].cur_state > 0)
+ /* Update pwm when temperature is above trips */
+ pwm = EMC2305_PWM_STATE2DUTY(data->cdev_data[cdev_idx].cur_state,
+ data->max_state, EMC2305_FAN_MAX);
+
/* Set minimal PWM speed. */
if (data->pwm_separate) {
ret = emc2305_set_pwm(dev, pwm, cdev_idx);
@@ -316,10 +336,10 @@ static int emc2305_set_single_tz(struct device *dev, int idx)
}
}
data->cdev_data[cdev_idx].cur_state =
- EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
+ EMC2305_PWM_DUTY2STATE(pwm, data->max_state,
EMC2305_FAN_MAX);
data->cdev_data[cdev_idx].last_hwmon_state =
- EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
+ EMC2305_PWM_DUTY2STATE(pwm, data->max_state,
EMC2305_FAN_MAX);
return 0;
}
@@ -330,29 +350,14 @@ static int emc2305_set_tz(struct device *dev)
int i, ret;
if (!data->pwm_separate)
- return emc2305_set_single_tz(dev, 0);
+ return emc2305_set_single_tz(dev, dev->of_node, 0);
for (i = 0; i < data->pwm_num; i++) {
- ret = emc2305_set_single_tz(dev, i + 1);
+ ret = emc2305_set_single_tz(dev, dev->of_node, i + 1);
if (ret)
- goto thermal_cooling_device_register_fail;
+ return ret;
}
return 0;
-
-thermal_cooling_device_register_fail:
- emc2305_unset_tz(dev);
- return ret;
-}
-
-static void emc2305_unset_tz(struct device *dev)
-{
- struct emc2305_data *data = dev_get_drvdata(dev);
- int i;
-
- /* Unregister cooling device. */
- for (i = 0; i < EMC2305_PWM_MAX; i++)
- if (data->cdev_data[i].cdev)
- thermal_cooling_device_unregister(data->cdev_data[i].cdev);
}
static umode_t
@@ -530,15 +535,83 @@ static int emc2305_identify(struct device *dev)
return 0;
}
+static int emc2305_of_parse_pwm_child(struct device *dev,
+ struct device_node *child,
+ struct emc2305_data *data)
+{ u32 ch;
+ int ret;
+ struct of_phandle_args args;
+
+ ret = of_property_read_u32(child, "reg", &ch);
+ if (ret) {
+ dev_err(dev, "missing reg property of %pOFn\n", child);
+ return ret;
+ }
+
+ ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells", 0, &args);
+
+ if (ret)
+ return ret;
+
+ if (args.args_count > 0) {
+ data->pwm_freq[ch] = find_closest(args.args[0], base_freq_table,
+ ARRAY_SIZE(base_freq_table));
+ } else {
+ data->pwm_freq[ch] = base_freq_table[3];
+ }
+
+ if (args.args_count > 1) {
+ if (args.args[1] == PWM_POLARITY_NORMAL || args.args[1] == PWM_POLARITY_INVERSED)
+ data->pwm_polarity_mask |= args.args[1] << ch;
+ else
+ dev_err(dev, "Wrong PWM polarity config provided: %d\n", args.args[0]);
+ } else {
+ data->pwm_polarity_mask |= PWM_POLARITY_NORMAL << ch;
+ }
+
+ if (args.args_count > 2) {
+ if (args.args[2] == EMC2305_PUSH_PULL || args.args[2] <= EMC2305_OPEN_DRAIN)
+ data->pwm_output_mask |= args.args[2] << ch;
+ else
+ dev_err(dev, "Wrong PWM output config provided: %d\n", args.args[1]);
+ } else {
+ data->pwm_output_mask |= EMC2305_OPEN_DRAIN << ch;
+ }
+
+ return 0;
+}
+
+static int emc2305_probe_childs_from_dt(struct device *dev)
+{
+ struct emc2305_data *data = dev_get_drvdata(dev);
+ struct device_node *child;
+ int ret, count = 0;
+
+ data->pwm_output_mask = 0x0;
+ data->pwm_polarity_mask = 0x0;
+
+ for_each_child_of_node(dev->of_node, child) {
+ if (of_property_present(child, "reg")) {
+ ret = emc2305_of_parse_pwm_child(dev, child, data);
+ if (ret)
+ continue;
+ count++;
+ }
+ }
+ return count;
+}
+
static int emc2305_probe(struct i2c_client *client)
{
struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
+ struct device_node *child;
struct emc2305_data *data;
struct emc2305_platform_data *pdata;
int vendor;
int ret;
int i;
+ int pwm_childs;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
@@ -558,22 +631,40 @@ static int emc2305_probe(struct i2c_client *client)
if (ret)
return ret;
+ pwm_childs = emc2305_probe_childs_from_dt(dev);
+
pdata = dev_get_platdata(&client->dev);
- if (pdata) {
- if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE)
- return -EINVAL;
- data->max_state = pdata->max_state;
- /*
- * Validate a number of active PWM channels. Note that
- * configured number can be less than the actual maximum
- * supported by the device.
- */
- if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX)
- return -EINVAL;
- data->pwm_num = pdata->pwm_num;
- data->pwm_separate = pdata->pwm_separate;
- for (i = 0; i < EMC2305_PWM_MAX; i++)
- data->pwm_min[i] = pdata->pwm_min[i];
+
+ if (!pwm_childs) {
+ if (pdata) {
+ if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE)
+ return -EINVAL;
+ data->max_state = pdata->max_state;
+ /*
+ * Validate a number of active PWM channels. Note that
+ * configured number can be less than the actual maximum
+ * supported by the device.
+ */
+ if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX)
+ return -EINVAL;
+ data->pwm_num = pdata->pwm_num;
+ data->pwm_output_mask = pdata->pwm_output_mask;
+ data->pwm_polarity_mask = pdata->pwm_polarity_mask;
+ data->pwm_separate = pdata->pwm_separate;
+ for (i = 0; i < EMC2305_PWM_MAX; i++) {
+ data->pwm_min[i] = pdata->pwm_min[i];
+ data->pwm_freq[i] = pdata->pwm_freq[i];
+ }
+ } else {
+ data->max_state = EMC2305_FAN_MAX_STATE;
+ data->pwm_separate = false;
+ data->pwm_output_mask = EMC2305_DEFAULT_OUTPUT;
+ data->pwm_polarity_mask = EMC2305_DEFAULT_POLARITY;
+ for (i = 0; i < EMC2305_PWM_MAX; i++) {
+ data->pwm_min[i] = EMC2305_FAN_MIN;
+ data->pwm_freq[i] = base_freq_table[3];
+ }
+ }
} else {
data->max_state = EMC2305_FAN_MAX_STATE;
data->pwm_separate = false;
@@ -587,11 +678,34 @@ static int emc2305_probe(struct i2c_client *client)
return PTR_ERR(data->hwmon_dev);
if (IS_REACHABLE(CONFIG_THERMAL)) {
- ret = emc2305_set_tz(dev);
- if (ret != 0)
- return ret;
+ /* Parse and check for the available PWM child nodes */
+ if (pwm_childs > 0) {
+ i = 0;
+ for_each_child_of_node(dev->of_node, child) {
+ ret = emc2305_set_single_tz(dev, child, i);
+ if (ret != 0) {
+ of_node_put(child);
+ return ret;
+ }
+ i++;
+ }
+ } else {
+ ret = emc2305_set_tz(dev);
+ if (ret != 0)
+ return ret;
+ }
}
+ ret = i2c_smbus_write_byte_data(client, EMC2305_REG_DRIVE_PWM_OUT,
+ data->pwm_output_mask);
+ if (ret < 0)
+ dev_err(dev, "Failed to configure pwm output, using default\n");
+
+ ret = i2c_smbus_write_byte_data(client, EMC2305_REG_POLARITY,
+ data->pwm_polarity_mask);
+ if (ret < 0)
+ dev_err(dev, "Failed to configure pwm polarity, using default\n");
+
for (i = 0; i < data->pwm_num; i++) {
ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_MIN_DRIVE(i),
data->pwm_min[i]);
@@ -602,23 +716,19 @@ static int emc2305_probe(struct i2c_client *client)
return 0;
}
-static void emc2305_remove(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
-
- if (IS_REACHABLE(CONFIG_THERMAL))
- emc2305_unset_tz(dev);
-}
+static const struct of_device_id of_emc2305_match_table[] = {
+ { .compatible = "microchip,emc2305", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_emc2305_match_table);
static struct i2c_driver emc2305_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "emc2305",
+ .of_match_table = of_emc2305_match_table,
},
.probe = emc2305_probe,
- .remove = emc2305_remove,
.id_table = emc2305_ids,
- .address_list = emc2305_normal_i2c,
};
module_i2c_driver(emc2305_driver);
diff --git a/drivers/hwmon/emc6w201.c b/drivers/hwmon/emc6w201.c
index 9a4f868bf1e6..1100c6e5daa7 100644
--- a/drivers/hwmon/emc6w201.c
+++ b/drivers/hwmon/emc6w201.c
@@ -464,7 +464,7 @@ static int emc6w201_probe(struct i2c_client *client)
}
static const struct i2c_device_id emc6w201_id[] = {
- { "emc6w201", 0 },
+ { "emc6w201" },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc6w201_id);
diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c
index 7f20edb0677c..820f894d9ffd 100644
--- a/drivers/hwmon/f71805f.c
+++ b/drivers/hwmon/f71805f.c
@@ -1480,7 +1480,7 @@ exit_remove_files:
return err;
}
-static int f71805f_remove(struct platform_device *pdev)
+static void f71805f_remove(struct platform_device *pdev)
{
struct f71805f_data *data = platform_get_drvdata(pdev);
int i;
@@ -1490,8 +1490,6 @@ static int f71805f_remove(struct platform_device *pdev)
for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
-
- return 0;
}
static struct platform_driver f71805f_driver = {
diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c
index 27207ec6f7fe..df83f9866fbc 100644
--- a/drivers/hwmon/f71882fg.c
+++ b/drivers/hwmon/f71882fg.c
@@ -2223,7 +2223,7 @@ static int f71882fg_create_fan_sysfs_files(
return err;
}
-static int f71882fg_remove(struct platform_device *pdev)
+static void f71882fg_remove(struct platform_device *pdev)
{
struct f71882fg_data *data = platform_get_drvdata(pdev);
int nr_fans = f71882fg_nr_fans[data->type];
@@ -2333,7 +2333,6 @@ static int f71882fg_remove(struct platform_device *pdev)
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
}
}
- return 0;
}
static int f71882fg_probe(struct platform_device *pdev)
diff --git a/drivers/hwmon/f75375s.c b/drivers/hwmon/f75375s.c
index 8c572bb64f5d..7e867f132420 100644
--- a/drivers/hwmon/f75375s.c
+++ b/drivers/hwmon/f75375s.c
@@ -111,31 +111,6 @@ struct f75375_data {
s8 temp_max_hyst[2];
};
-static int f75375_detect(struct i2c_client *client,
- struct i2c_board_info *info);
-static int f75375_probe(struct i2c_client *client);
-static void f75375_remove(struct i2c_client *client);
-
-static const struct i2c_device_id f75375_id[] = {
- { "f75373", f75373 },
- { "f75375", f75375 },
- { "f75387", f75387 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, f75375_id);
-
-static struct i2c_driver f75375_driver = {
- .class = I2C_CLASS_HWMON,
- .driver = {
- .name = "f75375",
- },
- .probe = f75375_probe,
- .remove = f75375_remove,
- .id_table = f75375_id,
- .detect = f75375_detect,
- .address_list = normal_i2c,
-};
-
static inline int f75375_read8(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
@@ -830,7 +805,7 @@ static int f75375_probe(struct i2c_client *client)
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
- data->kind = i2c_match_id(f75375_id, client)->driver_data;
+ data->kind = (uintptr_t)i2c_get_match_data(client);
err = sysfs_create_group(&client->dev.kobj, &f75375_group);
if (err)
@@ -901,6 +876,25 @@ static int f75375_detect(struct i2c_client *client,
return 0;
}
+static const struct i2c_device_id f75375_id[] = {
+ { "f75373", f75373 },
+ { "f75375", f75375 },
+ { "f75387", f75387 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, f75375_id);
+
+static struct i2c_driver f75375_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "f75375",
+ },
+ .probe = f75375_probe,
+ .remove = f75375_remove,
+ .id_table = f75375_id,
+ .detect = f75375_detect,
+ .address_list = normal_i2c,
+};
module_i2c_driver(f75375_driver);
MODULE_AUTHOR("Riku Voipio");
diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c
index 521534d5c1e5..8ecebea53651 100644
--- a/drivers/hwmon/fam15h_power.c
+++ b/drivers/hwmon/fam15h_power.c
@@ -17,6 +17,7 @@
#include <linux/cpumask.h>
#include <linux/time.h>
#include <linux/sched.h>
+#include <linux/topology.h>
#include <asm/processor.h>
#include <asm/msr.h>
@@ -134,18 +135,16 @@ static DEVICE_ATTR_RO(power1_crit);
static void do_read_registers_on_cu(void *_data)
{
struct fam15h_power_data *data = _data;
- int cpu, cu;
-
- cpu = smp_processor_id();
+ int cu;
/*
* With the new x86 topology modelling, cpu core id actually
* is compute unit id.
*/
- cu = cpu_data(cpu).cpu_core_id;
+ cu = topology_core_id(smp_processor_id());
- rdmsrl_safe(MSR_F15H_CU_PWR_ACCUMULATOR, &data->cu_acc_power[cu]);
- rdmsrl_safe(MSR_F15H_PTSC, &data->cpu_sw_pwr_ptsc[cu]);
+ rdmsrq_safe(MSR_F15H_CU_PWR_ACCUMULATOR, &data->cu_acc_power[cu]);
+ rdmsrq_safe(MSR_F15H_PTSC, &data->cpu_sw_pwr_ptsc[cu]);
data->cu_on[cu] = 1;
}
@@ -210,7 +209,7 @@ static ssize_t power1_average_show(struct device *dev,
* With the new x86 topology modelling, x86_max_cores is the
* compute unit number.
*/
- cu_num = boot_cpu_data.x86_max_cores;
+ cu_num = topology_num_cores_per_package();
ret = read_registers(data);
if (ret)
@@ -425,7 +424,7 @@ static int fam15h_power_init_data(struct pci_dev *f4,
*/
data->cpu_pwr_sample_ratio = cpuid_ecx(0x80000007);
- if (rdmsrl_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &tmp)) {
+ if (rdmsrq_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &tmp)) {
pr_err("Failed to read max compute unit power accumulator MSR\n");
return -ENODEV;
}
diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c
index b30512a705a7..a303959879ef 100644
--- a/drivers/hwmon/fschmd.c
+++ b/drivers/hwmon/fschmd.c
@@ -948,7 +948,6 @@ static long watchdog_ioctl(struct file *filp, unsigned int cmd,
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
- .llseek = no_llseek,
.open = watchdog_open,
.release = watchdog_release,
.write = watchdog_write,
@@ -1087,7 +1086,7 @@ static int fschmd_probe(struct i2c_client *client)
"Heracles", "Heimdall", "Hades", "Syleus" };
static const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 };
int i, err;
- enum chips kind = i2c_match_id(fschmd_id, client)->driver_data;
+ enum chips kind = (uintptr_t)i2c_get_match_data(client);
data = kzalloc(sizeof(struct fschmd_data), GFP_KERNEL);
if (!data)
diff --git a/drivers/hwmon/ftsteutates.c b/drivers/hwmon/ftsteutates.c
index b74a2665e733..08dcc6a7fb62 100644
--- a/drivers/hwmon/ftsteutates.c
+++ b/drivers/hwmon/ftsteutates.c
@@ -12,7 +12,6 @@
#include <linux/jiffies.h>
#include <linux/math.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/watchdog.h>
@@ -50,7 +49,7 @@
static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
static const struct i2c_device_id fts_id[] = {
- { "ftsteutates", 0 },
+ { "ftsteutates" },
{ }
};
MODULE_DEVICE_TABLE(i2c, fts_id);
@@ -62,10 +61,6 @@ enum WATCHDOG_RESOLUTION {
struct fts_data {
struct i2c_client *client;
- /* update sensor data lock */
- struct mutex update_lock;
- /* read/write register lock */
- struct mutex access_lock;
unsigned long last_updated; /* in jiffies */
struct watchdog_device wdd;
enum WATCHDOG_RESOLUTION resolution;
@@ -98,21 +93,15 @@ static int fts_read_byte(struct i2c_client *client, unsigned short reg)
{
int ret;
unsigned char page = reg >> 8;
- struct fts_data *data = dev_get_drvdata(&client->dev);
-
- mutex_lock(&data->access_lock);
dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page);
if (ret < 0)
- goto error;
+ return ret;
reg &= 0xFF;
ret = i2c_smbus_read_byte_data(client, reg);
dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret);
-
-error:
- mutex_unlock(&data->access_lock);
return ret;
}
@@ -121,22 +110,16 @@ static int fts_write_byte(struct i2c_client *client, unsigned short reg,
{
int ret;
unsigned char page = reg >> 8;
- struct fts_data *data = dev_get_drvdata(&client->dev);
-
- mutex_lock(&data->access_lock);
dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page);
if (ret < 0)
- goto error;
+ return ret;
reg &= 0xFF;
dev_dbg(&client->dev,
"write - reg: 0x%.02x: val: 0x%.02x\n", reg, value);
ret = i2c_smbus_write_byte_data(client, reg, value);
-
-error:
- mutex_unlock(&data->access_lock);
return ret;
}
@@ -145,44 +128,40 @@ error:
/*****************************************************************************/
static int fts_update_device(struct fts_data *data)
{
- int i;
- int err = 0;
+ int i, err;
- mutex_lock(&data->update_lock);
if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid)
- goto exit;
+ return 0;
err = fts_read_byte(data->client, FTS_DEVICE_STATUS_REG);
if (err < 0)
- goto exit;
+ return err;
data->valid = !!(err & 0x02); /* Data not ready yet */
- if (unlikely(!data->valid)) {
- err = -EAGAIN;
- goto exit;
- }
+ if (unlikely(!data->valid))
+ return -EAGAIN;
err = fts_read_byte(data->client, FTS_FAN_PRESENT_REG);
if (err < 0)
- goto exit;
+ return err;
data->fan_present = err;
err = fts_read_byte(data->client, FTS_FAN_EVENT_REG);
if (err < 0)
- goto exit;
+ return err;
data->fan_alarm = err;
for (i = 0; i < FTS_NO_FAN_SENSORS; i++) {
if (data->fan_present & BIT(i)) {
err = fts_read_byte(data->client, FTS_REG_FAN_INPUT(i));
if (err < 0)
- goto exit;
+ return err;
data->fan_input[i] = err;
err = fts_read_byte(data->client,
FTS_REG_FAN_SOURCE(i));
if (err < 0)
- goto exit;
+ return err;
data->fan_source[i] = err;
} else {
data->fan_input[i] = 0;
@@ -192,27 +171,24 @@ static int fts_update_device(struct fts_data *data)
err = fts_read_byte(data->client, FTS_SENSOR_EVENT_REG);
if (err < 0)
- goto exit;
+ return err;
data->temp_alarm = err;
for (i = 0; i < FTS_NO_TEMP_SENSORS; i++) {
err = fts_read_byte(data->client, FTS_REG_TEMP_INPUT(i));
if (err < 0)
- goto exit;
+ return err;
data->temp_input[i] = err;
}
for (i = 0; i < FTS_NO_VOLT_SENSORS; i++) {
err = fts_read_byte(data->client, FTS_REG_VOLT(i));
if (err < 0)
- goto exit;
+ return err;
data->volt[i] = err;
}
data->last_updated = jiffies;
- err = 0;
-exit:
- mutex_unlock(&data->update_lock);
- return err;
+ return 0;
}
/*****************************************************************************/
@@ -423,13 +399,16 @@ static int fts_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
break;
case hwmon_pwm:
switch (attr) {
- case hwmon_pwm_auto_channels_temp:
- if (data->fan_source[channel] == FTS_FAN_SOURCE_INVALID)
+ case hwmon_pwm_auto_channels_temp: {
+ u8 fan_source = data->fan_source[channel];
+
+ if (fan_source == FTS_FAN_SOURCE_INVALID || fan_source >= BITS_PER_LONG)
*val = 0;
else
- *val = BIT(data->fan_source[channel]);
+ *val = BIT(fan_source);
return 0;
+ }
default:
break;
}
@@ -467,18 +446,14 @@ static int fts_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
if (val)
return -EINVAL;
- mutex_lock(&data->update_lock);
ret = fts_read_byte(data->client, FTS_REG_TEMP_CONTROL(channel));
- if (ret >= 0)
- ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(channel),
- ret | 0x1);
- if (ret >= 0)
- data->valid = false;
-
- mutex_unlock(&data->update_lock);
if (ret < 0)
return ret;
-
+ ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(channel),
+ ret | 0x1);
+ if (ret < 0)
+ return ret;
+ data->valid = false;
return 0;
default:
break;
@@ -490,18 +465,14 @@ static int fts_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
if (val)
return -EINVAL;
- mutex_lock(&data->update_lock);
ret = fts_read_byte(data->client, FTS_REG_FAN_CONTROL(channel));
- if (ret >= 0)
- ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(channel),
- ret | 0x1);
- if (ret >= 0)
- data->valid = false;
-
- mutex_unlock(&data->update_lock);
if (ret < 0)
return ret;
-
+ ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(channel),
+ ret | 0x1);
+ if (ret < 0)
+ return ret;
+ data->valid = false;
return 0;
default:
break;
@@ -645,8 +616,6 @@ static int fts_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- mutex_init(&data->update_lock);
- mutex_init(&data->access_lock);
data->client = client;
dev_set_drvdata(&client->dev, data);
diff --git a/drivers/hwmon/g760a.c b/drivers/hwmon/g760a.c
index b5edee00267b..39ae8f826417 100644
--- a/drivers/hwmon/g760a.c
+++ b/drivers/hwmon/g760a.c
@@ -197,7 +197,7 @@ static int g760a_probe(struct i2c_client *client)
}
static const struct i2c_device_id g760a_id[] = {
- { "g760a", 0 },
+ { "g760a" },
{ }
};
MODULE_DEVICE_TABLE(i2c, g760a_id);
diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c
index fad69ef56c75..4fa3aa1271da 100644
--- a/drivers/hwmon/g762.c
+++ b/drivers/hwmon/g762.c
@@ -44,8 +44,9 @@
#define DRVNAME "g762"
static const struct i2c_device_id g762_id[] = {
- { "g762", 0 },
- { "g763", 0 },
+ { "g761" },
+ { "g762" },
+ { "g763" },
{ }
};
MODULE_DEVICE_TABLE(i2c, g762_id);
@@ -69,6 +70,7 @@ enum g762_regs {
#define G762_REG_FAN_CMD1_PWM_POLARITY 0x02 /* PWM polarity */
#define G762_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */
+#define G761_REG_FAN_CMD2_FAN_CLOCK 0x20 /* choose internal clock*/
#define G762_REG_FAN_CMD2_GEAR_MODE_1 0x08 /* fan gear mode */
#define G762_REG_FAN_CMD2_GEAR_MODE_0 0x04
#define G762_REG_FAN_CMD2_FAN_STARTV_1 0x02 /* fan startup voltage */
@@ -115,6 +117,7 @@ enum g762_regs {
struct g762_data {
struct i2c_client *client;
+ bool internal_clock;
struct clk *clk;
/* update mutex */
@@ -566,6 +569,7 @@ static int do_set_fan_startv(struct device *dev, unsigned long val)
#ifdef CONFIG_OF
static const struct of_device_id g762_dt_match[] = {
+ { .compatible = "gmt,g761" },
{ .compatible = "gmt,g762" },
{ .compatible = "gmt,g763" },
{ },
@@ -597,6 +601,21 @@ static int g762_of_clock_enable(struct i2c_client *client)
if (!client->dev.of_node)
return 0;
+ data = i2c_get_clientdata(client);
+
+ /*
+ * Skip CLK detection and handling if we use internal clock.
+ * This is only valid for g761.
+ */
+ data->internal_clock = of_device_is_compatible(client->dev.of_node,
+ "gmt,g761") &&
+ !of_property_present(client->dev.of_node,
+ "clocks");
+ if (data->internal_clock) {
+ do_set_clk_freq(&client->dev, 32768);
+ return 0;
+ }
+
clk = of_clk_get(client->dev.of_node, 0);
if (IS_ERR(clk)) {
dev_err(&client->dev, "failed to get clock\n");
@@ -616,7 +635,6 @@ static int g762_of_clock_enable(struct i2c_client *client)
goto clk_unprep;
}
- data = i2c_get_clientdata(client);
data->clk = clk;
ret = devm_add_action(&client->dev, g762_of_clock_disable, data);
@@ -1025,16 +1043,26 @@ ATTRIBUTE_GROUPS(g762);
static inline int g762_fan_init(struct device *dev)
{
struct g762_data *data = g762_update_client(dev);
+ int ret;
if (IS_ERR(data))
return PTR_ERR(data);
+ /* internal_clock can only be set with compatible g761 */
+ if (data->internal_clock)
+ data->fan_cmd2 |= G761_REG_FAN_CMD2_FAN_CLOCK;
+
data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_FAIL;
data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_OOC;
data->valid = false;
- return i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1,
- data->fan_cmd1);
+ ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1,
+ data->fan_cmd1);
+ if (ret)
+ return ret;
+
+ return i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD2,
+ data->fan_cmd2);
}
static int g762_probe(struct i2c_client *client)
@@ -1056,15 +1084,16 @@ static int g762_probe(struct i2c_client *client)
data->client = client;
mutex_init(&data->update_lock);
- /* Enable fan failure detection and fan out of control protection */
- ret = g762_fan_init(dev);
+ /* Get configuration via DT ... */
+ ret = g762_of_clock_enable(client);
if (ret)
return ret;
- /* Get configuration via DT ... */
- ret = g762_of_clock_enable(client);
+ /* Enable fan failure detection and fan out of control protection */
+ ret = g762_fan_init(dev);
if (ret)
return ret;
+
ret = g762_of_prop_import(client);
if (ret)
return ret;
diff --git a/drivers/hwmon/gigabyte_waterforce.c b/drivers/hwmon/gigabyte_waterforce.c
new file mode 100644
index 000000000000..27487e215bdd
--- /dev/null
+++ b/drivers/hwmon/gigabyte_waterforce.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for Gigabyte AORUS Waterforce AIO CPU coolers: X240, X280 and X360.
+ *
+ * Copyright 2023 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/unaligned.h>
+
+#define DRIVER_NAME "gigabyte_waterforce"
+
+#define USB_VENDOR_ID_GIGABYTE 0x1044
+#define USB_PRODUCT_ID_WATERFORCE 0x7a4d /* Gigabyte AORUS WATERFORCE X240, X280 and X360 */
+
+#define STATUS_VALIDITY (2 * 1000) /* ms */
+#define MAX_REPORT_LENGTH 6144
+
+#define WATERFORCE_TEMP_SENSOR 0xD
+#define WATERFORCE_FAN_SPEED 0x02
+#define WATERFORCE_PUMP_SPEED 0x05
+#define WATERFORCE_FAN_DUTY 0x08
+#define WATERFORCE_PUMP_DUTY 0x09
+
+/* Control commands, inner offsets and lengths */
+static const u8 get_status_cmd[] = { 0x99, 0xDA };
+
+#define FIRMWARE_VER_START_OFFSET_1 2
+#define FIRMWARE_VER_START_OFFSET_2 3
+static const u8 get_firmware_ver_cmd[] = { 0x99, 0xD6 };
+
+/* Command lengths */
+#define GET_STATUS_CMD_LENGTH 2
+#define GET_FIRMWARE_VER_CMD_LENGTH 2
+
+static const char *const waterforce_temp_label[] = {
+ "Coolant temp"
+};
+
+static const char *const waterforce_speed_label[] = {
+ "Fan speed",
+ "Pump speed"
+};
+
+struct waterforce_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ struct dentry *debugfs;
+ /* For locking access to buffer */
+ struct mutex buffer_lock;
+ /* For queueing multiple readers */
+ struct mutex status_report_request_mutex;
+ /* For reinitializing the completion below */
+ spinlock_t status_report_request_lock;
+ struct completion status_report_received;
+ struct completion fw_version_processed;
+
+ /* Sensor data */
+ s32 temp_input[1];
+ u16 speed_input[2]; /* Fan and pump speed in RPM */
+ u8 duty_input[2]; /* Fan and pump duty in 0-100% */
+
+ u8 *buffer;
+ int firmware_version;
+ unsigned long updated; /* jiffies */
+};
+
+static umode_t waterforce_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_label:
+ case hwmon_fan_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Writes the command to the device with the rest of the report filled with zeroes */
+static int waterforce_write_expanded(struct waterforce_data *priv, const u8 *cmd, int cmd_length)
+{
+ int ret;
+
+ mutex_lock(&priv->buffer_lock);
+
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+
+ mutex_unlock(&priv->buffer_lock);
+ return ret;
+}
+
+static int waterforce_get_status(struct waterforce_data *priv)
+{
+ int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ goto unlock_and_return;
+ }
+
+ /*
+ * Disable raw event parsing for a moment to safely reinitialize the
+ * completion. Reinit is done because hidraw could have triggered
+ * the raw event parsing and marked the priv->status_report_received
+ * completion as done.
+ */
+ spin_lock_bh(&priv->status_report_request_lock);
+ reinit_completion(&priv->status_report_received);
+ spin_unlock_bh(&priv->status_report_request_lock);
+
+ /* Send command for getting status */
+ ret = waterforce_write_expanded(priv, get_status_cmd, GET_STATUS_CMD_LENGTH);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_received,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+
+unlock_and_return:
+ mutex_unlock(&priv->status_report_request_mutex);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int waterforce_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct waterforce_data *priv = dev_get_drvdata(dev);
+ int ret = waterforce_get_status(priv);
+
+ if (ret < 0)
+ return ret;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->speed_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ *val = DIV_ROUND_CLOSEST(priv->duty_input[channel] * 255, 100);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int waterforce_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = waterforce_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = waterforce_speed_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int waterforce_get_fw_ver(struct hid_device *hdev)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = waterforce_write_expanded(priv, get_firmware_ver_cmd, GET_FIRMWARE_VER_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct hwmon_ops waterforce_hwmon_ops = {
+ .is_visible = waterforce_is_visible,
+ .read = waterforce_read,
+ .read_string = waterforce_read_string
+};
+
+static const struct hwmon_channel_info *waterforce_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info waterforce_chip_info = {
+ .ops = &waterforce_hwmon_ops,
+ .info = waterforce_info,
+};
+
+static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
+ int size)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+
+ if (data[0] == get_firmware_ver_cmd[0] && data[1] == get_firmware_ver_cmd[1]) {
+ /* Received a firmware version report */
+ priv->firmware_version =
+ data[FIRMWARE_VER_START_OFFSET_1] * 10 + data[FIRMWARE_VER_START_OFFSET_2];
+
+ if (!completion_done(&priv->fw_version_processed))
+ complete_all(&priv->fw_version_processed);
+ return 0;
+ }
+
+ if (data[0] != get_status_cmd[0] || data[1] != get_status_cmd[1])
+ return 0;
+
+ priv->temp_input[0] = data[WATERFORCE_TEMP_SENSOR] * 1000;
+ priv->speed_input[0] = get_unaligned_le16(data + WATERFORCE_FAN_SPEED);
+ priv->speed_input[1] = get_unaligned_le16(data + WATERFORCE_PUMP_SPEED);
+ priv->duty_input[0] = data[WATERFORCE_FAN_DUTY];
+ priv->duty_input[1] = data[WATERFORCE_PUMP_DUTY];
+
+ spin_lock(&priv->status_report_request_lock);
+ if (!completion_done(&priv->status_report_received))
+ complete_all(&priv->status_report_received);
+ spin_unlock(&priv->status_report_request_lock);
+
+ priv->updated = jiffies;
+
+ return 0;
+}
+
+static int firmware_version_show(struct seq_file *seqf, void *unused)
+{
+ struct waterforce_data *priv = seqf->private;
+
+ seq_printf(seqf, "%u\n", priv->firmware_version);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware_version);
+
+static void waterforce_debugfs_init(struct waterforce_data *priv)
+{
+ char name[64];
+
+ if (!priv->firmware_version)
+ return; /* There's nothing to show in debugfs */
+
+ scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
+
+ priv->debugfs = debugfs_create_dir(name, NULL);
+ debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
+}
+
+static int waterforce_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct waterforce_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for waterforce_read() without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Enable hidraw so existing user-space tools can continue to work.
+ */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ mutex_init(&priv->status_report_request_mutex);
+ mutex_init(&priv->buffer_lock);
+ spin_lock_init(&priv->status_report_request_lock);
+ init_completion(&priv->status_report_received);
+ init_completion(&priv->fw_version_processed);
+
+ hid_device_io_start(hdev);
+ ret = waterforce_get_fw_ver(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "fw version request failed with %d\n", ret);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "waterforce",
+ priv, &waterforce_chip_info, NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ waterforce_debugfs_init(priv);
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void waterforce_remove(struct hid_device *hdev)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+
+ debugfs_remove_recursive(priv->debugfs);
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id waterforce_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_GIGABYTE, USB_PRODUCT_ID_WATERFORCE) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, waterforce_table);
+
+static struct hid_driver waterforce_driver = {
+ .name = "waterforce",
+ .id_table = waterforce_table,
+ .probe = waterforce_probe,
+ .remove = waterforce_remove,
+ .raw_event = waterforce_raw_event,
+};
+
+static int __init waterforce_init(void)
+{
+ return hid_register_driver(&waterforce_driver);
+}
+
+static void __exit waterforce_exit(void)
+{
+ hid_unregister_driver(&waterforce_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(waterforce_init);
+module_exit(waterforce_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for Gigabyte AORUS Waterforce AIO coolers");
diff --git a/drivers/hwmon/gl518sm.c b/drivers/hwmon/gl518sm.c
index 03db6158b13a..9c68bc013950 100644
--- a/drivers/hwmon/gl518sm.c
+++ b/drivers/hwmon/gl518sm.c
@@ -642,7 +642,7 @@ static int gl518_probe(struct i2c_client *client)
}
static const struct i2c_device_id gl518_id[] = {
- { "gl518sm", 0 },
+ { "gl518sm" },
{ }
};
MODULE_DEVICE_TABLE(i2c, gl518_id);
diff --git a/drivers/hwmon/gl520sm.c b/drivers/hwmon/gl520sm.c
index 8bbc6a4f2928..972f4f8caa2b 100644
--- a/drivers/hwmon/gl520sm.c
+++ b/drivers/hwmon/gl520sm.c
@@ -885,7 +885,7 @@ static int gl520_probe(struct i2c_client *client)
}
static const struct i2c_device_id gl520_id[] = {
- { "gl520sm", 0 },
+ { "gl520sm" },
{ }
};
MODULE_DEVICE_TABLE(i2c, gl520_id);
diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c
new file mode 100644
index 000000000000..237f496c4862
--- /dev/null
+++ b/drivers/hwmon/gpd-fan.c
@@ -0,0 +1,683 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/* Platform driver for GPD devices that expose fan control via hwmon sysfs.
+ *
+ * Fan control is provided via pwm interface in the range [0-255].
+ * Each model has a different range in the EC, the written value is scaled to
+ * accommodate for that.
+ *
+ * Based on this repo:
+ * https://github.com/Cryolitia/gpd-fan-driver
+ *
+ * Copyright (c) 2024 Cryolitia PukNgae
+ */
+
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "gpdfan"
+#define GPD_PWM_CTR_OFFSET 0x1841
+
+static char *gpd_fan_board = "";
+module_param(gpd_fan_board, charp, 0444);
+
+enum gpd_board {
+ win_mini,
+ win4_6800u,
+ win_max_2,
+ duo,
+};
+
+enum FAN_PWM_ENABLE {
+ DISABLE = 0,
+ MANUAL = 1,
+ AUTOMATIC = 2,
+};
+
+static struct {
+ enum FAN_PWM_ENABLE pwm_enable;
+ u8 pwm_value;
+
+ const struct gpd_fan_drvdata *drvdata;
+} gpd_driver_priv;
+
+struct gpd_fan_drvdata {
+ const char *board_name; // Board name for module param comparison
+ const enum gpd_board board;
+
+ const u8 addr_port;
+ const u8 data_port;
+ const u16 manual_control_enable;
+ const u16 rpm_read;
+ const u16 pwm_write;
+ const u16 pwm_max;
+};
+
+static struct gpd_fan_drvdata gpd_win_mini_drvdata = {
+ .board_name = "win_mini",
+ .board = win_mini,
+
+ .addr_port = 0x4E,
+ .data_port = 0x4F,
+ .manual_control_enable = 0x047A,
+ .rpm_read = 0x0478,
+ .pwm_write = 0x047A,
+ .pwm_max = 244,
+};
+
+static struct gpd_fan_drvdata gpd_duo_drvdata = {
+ .board_name = "duo",
+ .board = duo,
+
+ .addr_port = 0x4E,
+ .data_port = 0x4F,
+ .manual_control_enable = 0x047A,
+ .rpm_read = 0x0478,
+ .pwm_write = 0x047A,
+ .pwm_max = 244,
+};
+
+static struct gpd_fan_drvdata gpd_win4_drvdata = {
+ .board_name = "win4",
+ .board = win4_6800u,
+
+ .addr_port = 0x2E,
+ .data_port = 0x2F,
+ .manual_control_enable = 0xC311,
+ .rpm_read = 0xC880,
+ .pwm_write = 0xC311,
+ .pwm_max = 127,
+};
+
+static struct gpd_fan_drvdata gpd_wm2_drvdata = {
+ .board_name = "wm2",
+ .board = win_max_2,
+
+ .addr_port = 0x4E,
+ .data_port = 0x4F,
+ .manual_control_enable = 0x0275,
+ .rpm_read = 0x0218,
+ .pwm_write = 0x1809,
+ .pwm_max = 184,
+};
+
+static const struct dmi_system_id dmi_table[] = {
+ {
+ // GPD Win Mini
+ // GPD Win Mini with AMD Ryzen 8840U
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01")
+ },
+ .driver_data = &gpd_win_mini_drvdata,
+ },
+ {
+ // GPD Win Mini
+ // GPD Win Mini with AMD Ryzen HX370
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02")
+ },
+ .driver_data = &gpd_win_mini_drvdata,
+ },
+ {
+ // GPD Win Mini
+ // GPD Win Mini with AMD Ryzen HX370
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02-L")
+ },
+ .driver_data = &gpd_win_mini_drvdata,
+ },
+ {
+ // GPD Win 4 with AMD Ryzen 6800U
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
+ DMI_MATCH(DMI_BOARD_VERSION, "Default string"),
+ },
+ .driver_data = &gpd_win4_drvdata,
+ },
+ {
+ // GPD Win 4 with Ryzen 7840U
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
+ DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"),
+ },
+ // Since 7840U, win4 uses the same drvdata as wm2
+ .driver_data = &gpd_wm2_drvdata,
+ },
+ {
+ // GPD Win 4 with Ryzen 7840U (another)
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
+ DMI_MATCH(DMI_BOARD_VERSION, "Ver.1.0"),
+ },
+ .driver_data = &gpd_wm2_drvdata,
+ },
+ {
+ // GPD Win Max 2 with Ryzen 6800U
+ // GPD Win Max 2 2023 with Ryzen 7840U
+ // GPD Win Max 2 2024 with Ryzen 8840U
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
+ },
+ .driver_data = &gpd_wm2_drvdata,
+ },
+ {
+ // GPD Win Max 2 with AMD Ryzen HX370
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"),
+ },
+ .driver_data = &gpd_wm2_drvdata,
+ },
+ {
+ // GPD Duo
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01"),
+ },
+ .driver_data = &gpd_duo_drvdata,
+ },
+ {
+ // GPD Duo (another)
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01-L"),
+ },
+ .driver_data = &gpd_duo_drvdata,
+ },
+ {
+ // GPD Pocket 4
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04"),
+ },
+ .driver_data = &gpd_win_mini_drvdata,
+ },
+ {
+ // GPD Pocket 4 (another)
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04-L"),
+ },
+ .driver_data = &gpd_win_mini_drvdata,
+ },
+ {}
+};
+
+static const struct gpd_fan_drvdata *gpd_module_drvdata[] = {
+ &gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL
+};
+
+// Helper functions to handle EC read/write
+static void gpd_ecram_read(u16 offset, u8 *val)
+{
+ u16 addr_port = gpd_driver_priv.drvdata->addr_port;
+ u16 data_port = gpd_driver_priv.drvdata->data_port;
+
+ outb(0x2E, addr_port);
+ outb(0x11, data_port);
+ outb(0x2F, addr_port);
+ outb((u8)((offset >> 8) & 0xFF), data_port);
+
+ outb(0x2E, addr_port);
+ outb(0x10, data_port);
+ outb(0x2F, addr_port);
+ outb((u8)(offset & 0xFF), data_port);
+
+ outb(0x2E, addr_port);
+ outb(0x12, data_port);
+ outb(0x2F, addr_port);
+ *val = inb(data_port);
+}
+
+static void gpd_ecram_write(u16 offset, u8 value)
+{
+ u16 addr_port = gpd_driver_priv.drvdata->addr_port;
+ u16 data_port = gpd_driver_priv.drvdata->data_port;
+
+ outb(0x2E, addr_port);
+ outb(0x11, data_port);
+ outb(0x2F, addr_port);
+ outb((u8)((offset >> 8) & 0xFF), data_port);
+
+ outb(0x2E, addr_port);
+ outb(0x10, data_port);
+ outb(0x2F, addr_port);
+ outb((u8)(offset & 0xFF), data_port);
+
+ outb(0x2E, addr_port);
+ outb(0x12, data_port);
+ outb(0x2F, addr_port);
+ outb(value, data_port);
+}
+
+static int gpd_generic_read_rpm(void)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+ u8 high, low;
+
+ gpd_ecram_read(drvdata->rpm_read, &high);
+ gpd_ecram_read(drvdata->rpm_read + 1, &low);
+
+ return (u16)high << 8 | low;
+}
+
+static int gpd_wm2_read_rpm(void)
+{
+ for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET;
+ pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) {
+ u8 PWMCTR;
+
+ gpd_ecram_read(pwm_ctr_offset, &PWMCTR);
+
+ if (PWMCTR != 0xB8)
+ gpd_ecram_write(pwm_ctr_offset, 0xB8);
+ }
+
+ return gpd_generic_read_rpm();
+}
+
+// Read value for fan1_input
+static int gpd_read_rpm(void)
+{
+ switch (gpd_driver_priv.drvdata->board) {
+ case win4_6800u:
+ case win_mini:
+ case duo:
+ return gpd_generic_read_rpm();
+ case win_max_2:
+ return gpd_wm2_read_rpm();
+ }
+
+ return 0;
+}
+
+static int gpd_wm2_read_pwm(void)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+ u8 var;
+
+ gpd_ecram_read(drvdata->pwm_write, &var);
+
+ // Match gpd_generic_write_pwm(u8) below
+ return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1));
+}
+
+// Read value for pwm1
+static int gpd_read_pwm(void)
+{
+ switch (gpd_driver_priv.drvdata->board) {
+ case win_mini:
+ case duo:
+ case win4_6800u:
+ switch (gpd_driver_priv.pwm_enable) {
+ case DISABLE:
+ return 255;
+ case MANUAL:
+ return gpd_driver_priv.pwm_value;
+ case AUTOMATIC:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case win_max_2:
+ return gpd_wm2_read_pwm();
+ }
+ return 0;
+}
+
+// PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it.
+static inline u8 gpd_cast_pwm_range(u8 val)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+
+ return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1;
+}
+
+static void gpd_generic_write_pwm(u8 val)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+ u8 pwm_reg;
+
+ pwm_reg = gpd_cast_pwm_range(val);
+ gpd_ecram_write(drvdata->pwm_write, pwm_reg);
+}
+
+static void gpd_duo_write_pwm(u8 val)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+ u8 pwm_reg;
+
+ pwm_reg = gpd_cast_pwm_range(val);
+ gpd_ecram_write(drvdata->pwm_write, pwm_reg);
+ gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg);
+}
+
+// Write value for pwm1
+static int gpd_write_pwm(u8 val)
+{
+ if (gpd_driver_priv.pwm_enable != MANUAL)
+ return -EPERM;
+
+ switch (gpd_driver_priv.drvdata->board) {
+ case duo:
+ gpd_duo_write_pwm(val);
+ break;
+ case win_mini:
+ case win4_6800u:
+ case win_max_2:
+ gpd_generic_write_pwm(val);
+ break;
+ }
+
+ return 0;
+}
+
+static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
+{
+ switch (pwm_enable) {
+ case DISABLE:
+ gpd_generic_write_pwm(255);
+ break;
+ case MANUAL:
+ gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
+ break;
+ case AUTOMATIC:
+ gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
+ break;
+ }
+}
+
+static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
+{
+ switch (pwm_enable) {
+ case DISABLE:
+ gpd_duo_write_pwm(255);
+ break;
+ case MANUAL:
+ gpd_duo_write_pwm(gpd_driver_priv.pwm_value);
+ break;
+ case AUTOMATIC:
+ gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
+ break;
+ }
+}
+
+static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable)
+{
+ const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
+
+ switch (enable) {
+ case DISABLE:
+ gpd_generic_write_pwm(255);
+ gpd_ecram_write(drvdata->manual_control_enable, 1);
+ break;
+ case MANUAL:
+ gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
+ gpd_ecram_write(drvdata->manual_control_enable, 1);
+ break;
+ case AUTOMATIC:
+ gpd_ecram_write(drvdata->manual_control_enable, 0);
+ break;
+ }
+}
+
+// Write value for pwm1_enable
+static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable)
+{
+ if (enable == MANUAL)
+ // Set pwm_value to max firstly when switching to manual mode, in
+ // consideration of device safety.
+ gpd_driver_priv.pwm_value = 255;
+
+ switch (gpd_driver_priv.drvdata->board) {
+ case win_mini:
+ case win4_6800u:
+ gpd_win_mini_set_pwm_enable(enable);
+ break;
+ case duo:
+ gpd_duo_set_pwm_enable(enable);
+ break;
+ case win_max_2:
+ gpd_wm2_set_pwm_enable(enable);
+ break;
+ }
+}
+
+static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr,
+ __always_unused int channel)
+{
+ if (type == hwmon_fan && attr == hwmon_fan_input) {
+ return 0444;
+ } else if (type == hwmon_pwm) {
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int gpd_fan_hwmon_read(__always_unused struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ __always_unused int channel, long *val)
+{
+ int ret;
+
+ if (type == hwmon_fan) {
+ if (attr == hwmon_fan_input) {
+ ret = gpd_read_rpm();
+
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+ }
+ } else if (type == hwmon_pwm) {
+ switch (attr) {
+ case hwmon_pwm_enable:
+ *val = gpd_driver_priv.pwm_enable;
+ return 0;
+ case hwmon_pwm_input:
+ ret = gpd_read_pwm();
+
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int gpd_fan_hwmon_write(__always_unused struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ __always_unused int channel, long val)
+{
+ if (type == hwmon_pwm) {
+ switch (attr) {
+ case hwmon_pwm_enable:
+ if (!in_range(val, 0, 3))
+ return -EINVAL;
+
+ gpd_driver_priv.pwm_enable = val;
+
+ gpd_set_pwm_enable(gpd_driver_priv.pwm_enable);
+ return 0;
+ case hwmon_pwm_input:
+ if (!in_range(val, 0, 256))
+ return -EINVAL;
+
+ gpd_driver_priv.pwm_value = val;
+
+ return gpd_write_pwm(val);
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops gpd_fan_ops = {
+ .is_visible = gpd_fan_hwmon_is_visible,
+ .read = gpd_fan_hwmon_read,
+ .write = gpd_fan_hwmon_write,
+};
+
+static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL
+};
+
+static struct hwmon_chip_info gpd_fan_chip_info = {
+ .ops = &gpd_fan_ops,
+ .info = gpd_fan_hwmon_channel_info
+};
+
+static void gpd_win4_init_ec(void)
+{
+ u8 chip_id, chip_ver;
+
+ gpd_ecram_read(0x2000, &chip_id);
+
+ if (chip_id == 0x55) {
+ gpd_ecram_read(0x1060, &chip_ver);
+ gpd_ecram_write(0x1060, chip_ver | 0x80);
+ }
+}
+
+static void gpd_init_ec(void)
+{
+ // The buggy firmware won't initialize EC properly on boot.
+ // Before its initialization, reading RPM will always return 0,
+ // and writing PWM will have no effect.
+ // Initialize it manually on driver load.
+ if (gpd_driver_priv.drvdata->board == win4_6800u)
+ gpd_win4_init_ec();
+}
+
+static int gpd_fan_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct resource *region;
+ const struct resource *res;
+ const struct device *hwdev;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to get platform resource\n");
+
+ region = devm_request_region(dev, res->start,
+ resource_size(res), DRIVER_NAME);
+ if (!region)
+ return dev_err_probe(dev, -EBUSY,
+ "Failed to request region\n");
+
+ hwdev = devm_hwmon_device_register_with_info(dev,
+ DRIVER_NAME,
+ NULL,
+ &gpd_fan_chip_info,
+ NULL);
+ if (IS_ERR(hwdev))
+ return dev_err_probe(dev, PTR_ERR(hwdev),
+ "Failed to register hwmon device\n");
+
+ gpd_init_ec();
+
+ return 0;
+}
+
+static void gpd_fan_remove(__always_unused struct platform_device *pdev)
+{
+ gpd_driver_priv.pwm_enable = AUTOMATIC;
+ gpd_set_pwm_enable(AUTOMATIC);
+}
+
+static struct platform_driver gpd_fan_driver = {
+ .probe = gpd_fan_probe,
+ .remove = gpd_fan_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+static struct platform_device *gpd_fan_platform_device;
+
+static int __init gpd_fan_init(void)
+{
+ const struct gpd_fan_drvdata *match = NULL;
+
+ for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) {
+ if (strcmp(gpd_fan_board, (*p)->board_name) == 0) {
+ match = *p;
+ break;
+ }
+ }
+
+ if (!match) {
+ const struct dmi_system_id *dmi_match =
+ dmi_first_match(dmi_table);
+ if (dmi_match)
+ match = dmi_match->driver_data;
+ }
+
+ if (!match)
+ return -ENODEV;
+
+ gpd_driver_priv.pwm_enable = AUTOMATIC;
+ gpd_driver_priv.pwm_value = 255;
+ gpd_driver_priv.drvdata = match;
+
+ struct resource gpd_fan_resources[] = {
+ {
+ .start = match->addr_port,
+ .end = match->data_port,
+ .flags = IORESOURCE_IO,
+ },
+ };
+
+ gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver,
+ gpd_fan_probe,
+ gpd_fan_resources,
+ 1, NULL, 0);
+
+ if (IS_ERR(gpd_fan_platform_device)) {
+ pr_warn("Failed to create platform device\n");
+ return PTR_ERR(gpd_fan_platform_device);
+ }
+
+ return 0;
+}
+
+static void __exit gpd_fan_exit(void)
+{
+ platform_device_unregister(gpd_fan_platform_device);
+ platform_driver_unregister(&gpd_fan_driver);
+}
+
+MODULE_DEVICE_TABLE(dmi, dmi_table);
+
+module_init(gpd_fan_init);
+module_exit(gpd_fan_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cryolitia PukNgae <cryolitia@uniontech.com>");
+MODULE_DESCRIPTION("GPD Devices fan control driver");
diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c
index d92c536be9af..516c34bb61c9 100644
--- a/drivers/hwmon/gpio-fan.c
+++ b/drivers/hwmon/gpio-fan.c
@@ -20,6 +20,9 @@
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_platform.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
#include <linux/thermal.h>
struct gpio_fan_speed {
@@ -42,6 +45,7 @@ struct gpio_fan_data {
bool pwm_enable;
struct gpio_desc *alarm_gpio;
struct work_struct alarm_work;
+ struct regulator *supply;
};
/*
@@ -125,13 +129,32 @@ static int __get_fan_ctrl(struct gpio_fan_data *fan_data)
}
/* Must be called with fan_data->lock held, except during initialization. */
-static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
+static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
{
if (fan_data->speed_index == speed_index)
- return;
+ return 0;
+
+ if (fan_data->speed_index == 0 && speed_index > 0) {
+ int ret;
+
+ ret = pm_runtime_resume_and_get(fan_data->dev);
+ if (ret < 0)
+ return ret;
+ }
__set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val);
+
+ if (fan_data->speed_index > 0 && speed_index == 0) {
+ int ret;
+
+ ret = pm_runtime_put_sync(fan_data->dev);
+ if (ret < 0)
+ return ret;
+ }
+
fan_data->speed_index = speed_index;
+
+ return 0;
}
static int get_fan_speed_index(struct gpio_fan_data *fan_data)
@@ -176,7 +199,7 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr,
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
unsigned long pwm;
int speed_index;
- int ret = count;
+ int ret;
if (kstrtoul(buf, 10, &pwm) || pwm > 255)
return -EINVAL;
@@ -189,12 +212,12 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr,
}
speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255);
- set_fan_speed(fan_data, speed_index);
+ ret = set_fan_speed(fan_data, speed_index);
exit_unlock:
mutex_unlock(&fan_data->lock);
- return ret;
+ return ret ? ret : count;
}
static ssize_t pwm1_enable_show(struct device *dev,
@@ -211,6 +234,7 @@ static ssize_t pwm1_enable_store(struct device *dev,
{
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
unsigned long val;
+ int ret = 0;
if (kstrtoul(buf, 10, &val) || val > 1)
return -EINVAL;
@@ -224,11 +248,11 @@ static ssize_t pwm1_enable_store(struct device *dev,
/* Disable manual control mode: set fan at full speed. */
if (val == 0)
- set_fan_speed(fan_data, fan_data->num_speed - 1);
+ ret = set_fan_speed(fan_data, fan_data->num_speed - 1);
mutex_unlock(&fan_data->lock);
- return count;
+ return ret ? ret : count;
}
static ssize_t pwm1_mode_show(struct device *dev,
@@ -279,7 +303,7 @@ static ssize_t set_rpm(struct device *dev, struct device_attribute *attr,
goto exit_unlock;
}
- set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm));
+ ret = set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm));
exit_unlock:
mutex_unlock(&fan_data->lock);
@@ -386,6 +410,7 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct gpio_fan_data *fan_data = cdev->devdata;
+ int ret;
if (!fan_data)
return -EINVAL;
@@ -393,8 +418,13 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev,
if (state >= fan_data->num_speed)
return -EINVAL;
- set_fan_speed(fan_data, state);
- return 0;
+ mutex_lock(&fan_data->lock);
+
+ ret = set_fan_speed(fan_data, state);
+
+ mutex_unlock(&fan_data->lock);
+
+ return ret;
}
static const struct thermal_cooling_device_ops gpio_fan_cool_ops = {
@@ -489,7 +519,13 @@ MODULE_DEVICE_TABLE(of, of_gpio_fan_match);
static void gpio_fan_stop(void *data)
{
+ struct gpio_fan_data *fan_data = data;
+
+ mutex_lock(&fan_data->lock);
set_fan_speed(data, 0);
+ mutex_unlock(&fan_data->lock);
+
+ pm_runtime_disable(fan_data->dev);
}
static int gpio_fan_probe(struct platform_device *pdev)
@@ -512,6 +548,11 @@ static int gpio_fan_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, fan_data);
mutex_init(&fan_data->lock);
+ fan_data->supply = devm_regulator_get(dev, "fan");
+ if (IS_ERR(fan_data->supply))
+ return dev_err_probe(dev, PTR_ERR(fan_data->supply),
+ "Failed to get fan-supply");
+
/* Configure control GPIOs if available. */
if (fan_data->gpios && fan_data->num_gpios > 0) {
if (!fan_data->speed || fan_data->num_speed <= 1)
@@ -539,6 +580,17 @@ static int gpio_fan_probe(struct platform_device *pdev)
return err;
}
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ /* If current GPIO state is active, mark RPM as active as well */
+ if (fan_data->speed_index > 0) {
+ int ret;
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret)
+ return ret;
+ }
+
/* Optional cooling device register for Device tree platforms */
fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np,
"gpio-fan", fan_data, &gpio_fan_cool_ops);
@@ -556,36 +608,69 @@ static void gpio_fan_shutdown(struct platform_device *pdev)
set_fan_speed(fan_data, 0);
}
+static int gpio_fan_runtime_suspend(struct device *dev)
+{
+ struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (fan_data->supply)
+ ret = regulator_disable(fan_data->supply);
+
+ return ret;
+}
+
+static int gpio_fan_runtime_resume(struct device *dev)
+{
+ struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (fan_data->supply)
+ ret = regulator_enable(fan_data->supply);
+
+ return ret;
+}
+
static int gpio_fan_suspend(struct device *dev)
{
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
+ int ret = 0;
if (fan_data->gpios) {
fan_data->resume_speed = fan_data->speed_index;
- set_fan_speed(fan_data, 0);
+ mutex_lock(&fan_data->lock);
+ ret = set_fan_speed(fan_data, 0);
+ mutex_unlock(&fan_data->lock);
}
- return 0;
+ return ret;
}
static int gpio_fan_resume(struct device *dev)
{
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
+ int ret = 0;
- if (fan_data->gpios)
- set_fan_speed(fan_data, fan_data->resume_speed);
+ if (fan_data->gpios) {
+ mutex_lock(&fan_data->lock);
+ ret = set_fan_speed(fan_data, fan_data->resume_speed);
+ mutex_unlock(&fan_data->lock);
+ }
- return 0;
+ return ret;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume);
+static const struct dev_pm_ops gpio_fan_pm = {
+ RUNTIME_PM_OPS(gpio_fan_runtime_suspend,
+ gpio_fan_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(gpio_fan_suspend, gpio_fan_resume)
+};
static struct platform_driver gpio_fan_driver = {
.probe = gpio_fan_probe,
.shutdown = gpio_fan_shutdown,
.driver = {
.name = "gpio-fan",
- .pm = pm_sleep_ptr(&gpio_fan_pm),
+ .pm = pm_ptr(&gpio_fan_pm),
.of_match_table = of_gpio_fan_match,
},
};
diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c
index 1501ceb551e7..105b9f9dbec3 100644
--- a/drivers/hwmon/gsc-hwmon.c
+++ b/drivers/hwmon/gsc-hwmon.c
@@ -39,7 +39,7 @@ struct gsc_hwmon_data {
struct hwmon_chip_info chip;
};
-static struct regmap_bus gsc_hwmon_regmap_bus = {
+static const struct regmap_bus gsc_hwmon_regmap_bus = {
.reg_read = gsc_read,
.reg_write = gsc_write,
};
@@ -47,7 +47,6 @@ static struct regmap_bus gsc_hwmon_regmap_bus = {
static const struct regmap_config gsc_hwmon_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_NONE,
};
static ssize_t pwm_auto_point_temp_show(struct device *dev,
@@ -65,7 +64,7 @@ static ssize_t pwm_auto_point_temp_show(struct device *dev,
return ret;
ret = regs[0] | regs[1] << 8;
- return sprintf(buf, "%d\n", ret * 10);
+ return sprintf(buf, "%d\n", ret * 100);
}
static ssize_t pwm_auto_point_temp_store(struct device *dev,
@@ -100,7 +99,7 @@ static ssize_t pwm_auto_point_pwm_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%d\n", 255 * (50 + (attr->index * 10)));
+ return sprintf(buf, "%d\n", 255 * (50 + (attr->index * 10)) / 100);
}
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0);
@@ -231,15 +230,8 @@ gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
return 0;
}
-static umode_t
-gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
- int ch)
-{
- return 0444;
-}
-
static const struct hwmon_ops gsc_hwmon_ops = {
- .is_visible = gsc_hwmon_is_visible,
+ .visible = 0444,
.read = gsc_hwmon_read,
.read_string = gsc_hwmon_read_string,
};
@@ -249,7 +241,6 @@ gsc_hwmon_get_devtree_pdata(struct device *dev)
{
struct gsc_hwmon_platform_data *pdata;
struct gsc_hwmon_channel *ch;
- struct fwnode_handle *child;
struct device_node *fan;
int nchannels;
@@ -276,25 +267,21 @@ gsc_hwmon_get_devtree_pdata(struct device *dev)
ch = pdata->channels;
/* allocate structures for channels and count instances of each type */
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
if (fwnode_property_read_string(child, "label", &ch->name)) {
dev_err(dev, "channel without label\n");
- fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
dev_err(dev, "channel without reg\n");
- fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
if (fwnode_property_read_u32(child, "gw,mode", &ch->mode)) {
dev_err(dev, "channel without mode\n");
- fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
if (ch->mode > mode_max) {
dev_err(dev, "invalid channel mode\n");
- fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
@@ -405,6 +392,7 @@ static const struct of_device_id gsc_hwmon_of_match[] = {
{ .compatible = "gw,gsc-adc", },
{}
};
+MODULE_DEVICE_TABLE(of, gsc_hwmon_of_match);
static struct platform_driver gsc_hwmon_driver = {
.driver = {
diff --git a/drivers/hwmon/hih6130.c b/drivers/hwmon/hih6130.c
index a9726b5370fb..85af8299150a 100644
--- a/drivers/hwmon/hih6130.c
+++ b/drivers/hwmon/hih6130.c
@@ -233,7 +233,7 @@ static int hih6130_probe(struct i2c_client *client)
/* Device ID table */
static const struct i2c_device_id hih6130_id[] = {
- { "hih6130", 0 },
+ { "hih6130" },
{ }
};
MODULE_DEVICE_TABLE(i2c, hih6130_id);
diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c
index 17ae62f88bbf..03c684ba83bd 100644
--- a/drivers/hwmon/hp-wmi-sensors.c
+++ b/drivers/hwmon/hp-wmi-sensors.c
@@ -17,6 +17,8 @@
* Available: https://github.com/linuxhw/ACPI
* [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
* 2017. [Online]. Available: https://github.com/pali/bmfdec
+ * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online].
+ * Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
*/
#include <linux/acpi.h>
@@ -24,6 +26,7 @@
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
+#include <linux/nls.h>
#include <linux/units.h>
#include <linux/wmi.h>
@@ -395,6 +398,50 @@ struct hp_wmi_sensors {
struct mutex lock; /* Lock polling WMI and driver state changes. */
};
+static bool is_raw_wmi_string(const u8 *pointer, u32 length)
+{
+ const u16 *ptr;
+ u16 len;
+
+ /* WMI strings are length-prefixed UTF-16 [5]. */
+ if (length <= sizeof(*ptr))
+ return false;
+
+ length -= sizeof(*ptr);
+ ptr = (const u16 *)pointer;
+ len = *ptr;
+
+ return len <= length && !(len & 1);
+}
+
+static char *convert_raw_wmi_string(const u8 *buf)
+{
+ const wchar_t *src;
+ unsigned int cps;
+ unsigned int len;
+ char *dst;
+ int i;
+
+ src = (const wchar_t *)buf;
+
+ /* Count UTF-16 code points. Exclude trailing null padding. */
+ cps = *src / sizeof(*src);
+ while (cps && !src[cps])
+ cps--;
+
+ /* Each code point becomes up to 3 UTF-8 characters. */
+ len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1);
+
+ dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL);
+ if (!dst)
+ return NULL;
+
+ i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
+ dst[i] = '\0';
+
+ return dst;
+}
+
/* hp_wmi_strdup - devm_kstrdup, but length-limited */
static char *hp_wmi_strdup(struct device *dev, const char *src)
{
@@ -412,6 +459,23 @@ static char *hp_wmi_strdup(struct device *dev, const char *src)
return dst;
}
+/* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */
+static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
+{
+ char *src;
+ char *dst;
+
+ src = convert_raw_wmi_string(buf);
+ if (!src)
+ return NULL;
+
+ dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */
+
+ kfree(src);
+
+ return dst;
+}
+
/*
* hp_wmi_get_wobj - poll WMI for a WMI object instance
* @guid: WMI object GUID
@@ -462,8 +526,14 @@ static int check_wobj(const union acpi_object *wobj,
for (prop = 0; prop <= last_prop; prop++) {
type = elements[prop].type;
valid_type = property_map[prop];
- if (type != valid_type)
+ if (type != valid_type) {
+ if (type == ACPI_TYPE_BUFFER &&
+ valid_type == ACPI_TYPE_STRING &&
+ is_raw_wmi_string(elements[prop].buffer.pointer,
+ elements[prop].buffer.length))
+ continue;
return -EINVAL;
+ }
}
return 0;
@@ -480,7 +550,9 @@ static int extract_acpi_value(struct device *dev,
break;
case ACPI_TYPE_STRING:
- *out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
+ *out_string = element->type == ACPI_TYPE_BUFFER ?
+ hp_wmi_wstrdup(dev, element->buffer.pointer) :
+ hp_wmi_strdup(dev, strim(element->string.pointer));
if (!*out_string)
return -ENOMEM;
break;
@@ -861,7 +933,9 @@ update_numeric_sensor_from_wobj(struct device *dev,
{
const union acpi_object *elements;
const union acpi_object *element;
- const char *string;
+ const char *new_string;
+ char *trimmed;
+ char *string;
bool is_new;
int offset;
u8 size;
@@ -885,11 +959,21 @@ update_numeric_sensor_from_wobj(struct device *dev,
offset = is_new ? size - 1 : -2;
element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
- string = strim(element->string.pointer);
-
- if (strcmp(string, nsensor->current_state)) {
- devm_kfree(dev, nsensor->current_state);
- nsensor->current_state = hp_wmi_strdup(dev, string);
+ string = element->type == ACPI_TYPE_BUFFER ?
+ convert_raw_wmi_string(element->buffer.pointer) :
+ element->string.pointer;
+
+ if (string) {
+ trimmed = strim(string);
+ if (strcmp(trimmed, nsensor->current_state)) {
+ new_string = hp_wmi_strdup(dev, trimmed);
+ if (new_string) {
+ devm_kfree(dev, nsensor->current_state);
+ nsensor->current_state = new_string;
+ }
+ }
+ if (element->type == ACPI_TYPE_BUFFER)
+ kfree(string);
}
/* Old variant: -2 (not -1) because it lacks the Size property. */
@@ -996,11 +1080,15 @@ static int check_event_wobj(const union acpi_object *wobj)
HP_WMI_EVENT_PROPERTY_STATUS);
}
-static int populate_event_from_wobj(struct hp_wmi_event *event,
+static int populate_event_from_wobj(struct device *dev,
+ struct hp_wmi_event *event,
union acpi_object *wobj)
{
int prop = HP_WMI_EVENT_PROPERTY_NAME;
union acpi_object *element;
+ acpi_object_type type;
+ char *string;
+ u32 value;
int err;
err = check_event_wobj(wobj);
@@ -1009,20 +1097,24 @@ static int populate_event_from_wobj(struct hp_wmi_event *event,
element = wobj->package.elements;
- /* Extracted strings are NOT device-managed copies. */
-
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
+ type = hp_wmi_event_property_map[prop];
+
+ err = extract_acpi_value(dev, element, type, &value, &string);
+ if (err)
+ return err;
+
switch (prop) {
case HP_WMI_EVENT_PROPERTY_NAME:
- event->name = strim(element->string.pointer);
+ event->name = string;
break;
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
- event->description = strim(element->string.pointer);
+ event->description = string;
break;
case HP_WMI_EVENT_PROPERTY_CATEGORY:
- event->category = element->integer.value;
+ event->category = value;
break;
default:
@@ -1105,7 +1197,7 @@ static int hp_wmi_update_info(struct hp_wmi_sensors *state,
if (time_after(jiffies, info->last_updated + HZ)) {
mutex_lock(&state->lock);
- wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, instance);
+ wobj = wmidev_block_query(state->wdev, instance);
if (!wobj) {
ret = -EIO;
goto out_unlock;
@@ -1505,15 +1597,13 @@ static void hp_wmi_devm_notify_remove(void *ignored)
}
/* hp_wmi_notify - WMI event notification handler */
-static void hp_wmi_notify(u32 value, void *context)
+static void hp_wmi_notify(union acpi_object *wobj, void *context)
{
struct hp_wmi_info *temp_info[HP_WMI_MAX_INSTANCES] = {};
- struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct hp_wmi_sensors *state = context;
struct device *dev = &state->wdev->dev;
+ struct hp_wmi_event event = {};
struct hp_wmi_info *fan_info;
- struct hp_wmi_event event;
- union acpi_object *wobj;
acpi_status err;
int event_type;
u8 count;
@@ -1538,18 +1628,15 @@ static void hp_wmi_notify(u32 value, void *context)
* HPBIOS_BIOSEvent instance.
*/
- mutex_lock(&state->lock);
-
- err = wmi_get_event_data(value, &out);
- if (ACPI_FAILURE(err))
- goto out_unlock;
+ if (!wobj)
+ return;
- wobj = out.pointer;
+ mutex_lock(&state->lock);
- err = populate_event_from_wobj(&event, wobj);
+ err = populate_event_from_wobj(dev, &event, wobj);
if (err) {
dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
- goto out_free_wobj;
+ goto out_free;
}
event_type = classify_event(event.name, event.category);
@@ -1574,10 +1661,10 @@ static void hp_wmi_notify(u32 value, void *context)
break;
}
-out_free_wobj:
- kfree(wobj);
+out_free:
+ devm_kfree(dev, event.name);
+ devm_kfree(dev, event.description);
-out_unlock:
mutex_unlock(&state->lock);
}
@@ -1658,7 +1745,7 @@ static int init_numeric_sensors(struct hp_wmi_sensors *state,
return -ENOMEM;
for (i = 0, info = info_arr; i < icount; i++, info++) {
- wobj = hp_wmi_get_wobj(HP_WMI_NUMERIC_SENSOR_GUID, i);
+ wobj = wmidev_block_query(state->wdev, i);
if (!wobj)
return -EIO;
diff --git a/drivers/hwmon/hs3001.c b/drivers/hwmon/hs3001.c
index ac574e46d069..50c6c15f8b18 100644
--- a/drivers/hwmon/hs3001.c
+++ b/drivers/hwmon/hs3001.c
@@ -42,7 +42,6 @@
struct hs3001_data {
struct i2c_client *client;
- struct mutex i2c_lock; /* lock for sending i2c commands */
u32 wait_time; /* in us */
int temperature; /* in milli degree */
u32 humidity; /* in milli % */
@@ -62,7 +61,7 @@ static u32 hs3001_extract_humidity(u16 raw)
{
u32 hum = (raw & HS3001_MASK_HUMIDITY_0X3FFF) * HS3001_FIXPOINT_ARITH * 100;
- return hum /= (1 << 14) - 1;
+ return hum / (1 << 14) - 1;
}
static int hs3001_data_fetch_command(struct i2c_client *client,
@@ -112,12 +111,9 @@ static int hs3001_read(struct device *dev, enum hwmon_sensor_types type,
struct i2c_client *client = data->client;
int ret;
- mutex_lock(&data->i2c_lock);
ret = i2c_master_send(client, NULL, 0);
- if (ret < 0) {
- mutex_unlock(&data->i2c_lock);
+ if (ret < 0)
return ret;
- }
/*
* Sensor needs some time to process measurement depending on
@@ -126,8 +122,6 @@ static int hs3001_read(struct device *dev, enum hwmon_sensor_types type,
fsleep(data->wait_time);
ret = hs3001_data_fetch_command(client, data);
- mutex_unlock(&data->i2c_lock);
-
if (ret < 0)
return ret;
@@ -175,7 +169,7 @@ static const struct hwmon_chip_info hs3001_chip_info = {
/* device ID table */
static const struct i2c_device_id hs3001_ids[] = {
- { "hs3001", 0 },
+ { "hs3001" },
{ },
};
@@ -211,8 +205,6 @@ static int hs3001_probe(struct i2c_client *client)
data->wait_time = (HS3001_WAKEUP_TIME + HS3001_14BIT_RESOLUTION +
HS3001_14BIT_RESOLUTION);
- mutex_init(&data->i2c_lock);
-
hwmon_dev = devm_hwmon_device_register_with_info(dev,
client->name,
data,
diff --git a/drivers/hwmon/htu31.c b/drivers/hwmon/htu31.c
new file mode 100644
index 000000000000..7521a371aa6c
--- /dev/null
+++ b/drivers/hwmon/htu31.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The driver for Measurement Specialties HTU31 Temperature and Humidity sensor.
+ *
+ * Copyright (C) 2025
+ * Author: Andrei Lalaev <andrey.lalaev@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#define HTU31_READ_TEMP_HUM_CMD 0x00
+#define HTU31_READ_SERIAL_CMD 0x0a
+#define HTU31_CONVERSION_CMD 0x5e
+#define HTU31_HEATER_OFF_CMD 0x02
+#define HTU31_HEATER_ON_CMD 0x04
+
+#define HTU31_TEMP_HUM_LEN 6
+
+/* Conversion time for the highest resolution */
+#define HTU31_HUMIDITY_CONV_TIME 10000 /* us */
+#define HTU31_TEMPERATURE_CONV_TIME 15000 /* us */
+
+#define HTU31_SERIAL_NUMBER_LEN 3
+#define HTU31_SERIAL_NUMBER_CRC_LEN 1
+#define HTU31_SERIAL_NUMBER_CRC_OFFSET 3
+
+#define HTU31_CRC8_INIT_VAL 0
+#define HTU31_CRC8_POLYNOMIAL 0x31
+DECLARE_CRC8_TABLE(htu31_crc8_table);
+
+/**
+ * struct htu31_data - all the data required to operate a HTU31 chip
+ * @client: the i2c client associated with the HTU31
+ * @lock: a mutex to prevent parallel access to the data
+ * @wait_time: the time needed by sensor to convert values
+ * @temperature: the latest temperature value in millidegrees
+ * @humidity: the latest relative humidity value in millipercent
+ * @serial_number: the serial number of the sensor
+ * @heater_enable: the internal state of the heater
+ */
+struct htu31_data {
+ struct i2c_client *client;
+ struct mutex lock; /* Used to protect against parallel data updates */
+ long wait_time;
+ long temperature;
+ long humidity;
+ u8 serial_number[HTU31_SERIAL_NUMBER_LEN];
+ bool heater_enable;
+};
+
+static long htu31_temp_to_millicelsius(u16 val)
+{
+ return -40000 + DIV_ROUND_CLOSEST_ULL(165000ULL * val, 65535);
+}
+
+static long htu31_relative_humidity(u16 val)
+{
+ return DIV_ROUND_CLOSEST_ULL(100000ULL * val, 65535);
+}
+
+static int htu31_data_fetch_command(struct htu31_data *data)
+{
+ struct i2c_client *client = data->client;
+ u8 conversion_on = HTU31_CONVERSION_CMD;
+ u8 read_data_cmd = HTU31_READ_TEMP_HUM_CMD;
+ u8 t_h_buf[HTU31_TEMP_HUM_LEN] = {};
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &read_data_cmd,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(t_h_buf),
+ .buf = t_h_buf,
+ },
+ };
+ int ret;
+ u8 crc;
+
+ guard(mutex)(&data->lock);
+
+ ret = i2c_master_send(client, &conversion_on, 1);
+ if (ret != 1) {
+ ret = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev,
+ "Conversion command is failed. Error code: %d\n", ret);
+ return ret;
+ }
+
+ fsleep(data->wait_time);
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret != ARRAY_SIZE(msgs)) {
+ ret = ret < 0 ? ret : -EIO;
+ dev_err(&client->dev,
+ "T&H command is failed. Error code: %d\n", ret);
+ return ret;
+ }
+
+ crc = crc8(htu31_crc8_table, &t_h_buf[0], 2, HTU31_CRC8_INIT_VAL);
+ if (crc != t_h_buf[2]) {
+ dev_err(&client->dev, "Temperature CRC mismatch\n");
+ return -EIO;
+ }
+
+ crc = crc8(htu31_crc8_table, &t_h_buf[3], 2, HTU31_CRC8_INIT_VAL);
+ if (crc != t_h_buf[5]) {
+ dev_err(&client->dev, "Humidity CRC mismatch\n");
+ return -EIO;
+ }
+
+ data->temperature = htu31_temp_to_millicelsius(be16_to_cpup((__be16 *)&t_h_buf[0]));
+ data->humidity = htu31_relative_humidity(be16_to_cpup((__be16 *)&t_h_buf[3]));
+
+ return 0;
+}
+
+static umode_t htu31_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ case hwmon_humidity:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static int htu31_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct htu31_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = htu31_data_fetch_command(data);
+ if (ret < 0)
+ return ret;
+
+ switch (type) {
+ case hwmon_temp:
+ if (attr != hwmon_temp_input)
+ return -EINVAL;
+
+ *val = data->temperature;
+ break;
+ case hwmon_humidity:
+ if (attr != hwmon_humidity_input)
+ return -EINVAL;
+
+ *val = data->humidity;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int htu31_read_serial_number(struct htu31_data *data)
+{
+ struct i2c_client *client = data->client;
+ u8 read_sn_cmd = HTU31_READ_SERIAL_CMD;
+ u8 sn_buf[HTU31_SERIAL_NUMBER_LEN + HTU31_SERIAL_NUMBER_CRC_LEN];
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &read_sn_cmd,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(sn_buf),
+ .buf = sn_buf,
+ },
+ };
+ int ret;
+ u8 crc;
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return ret;
+
+ crc = crc8(htu31_crc8_table, sn_buf, HTU31_SERIAL_NUMBER_LEN, HTU31_CRC8_INIT_VAL);
+ if (crc != sn_buf[HTU31_SERIAL_NUMBER_CRC_OFFSET]) {
+ dev_err(&client->dev, "Serial number CRC mismatch\n");
+ return -EIO;
+ }
+
+ memcpy(data->serial_number, sn_buf, HTU31_SERIAL_NUMBER_LEN);
+
+ return 0;
+}
+
+static ssize_t heater_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct htu31_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", data->heater_enable);
+}
+
+static ssize_t heater_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct htu31_data *data = dev_get_drvdata(dev);
+ u8 heater_cmd;
+ bool status;
+ int ret;
+
+ ret = kstrtobool(buf, &status);
+ if (ret)
+ return ret;
+
+ heater_cmd = status ? HTU31_HEATER_ON_CMD : HTU31_HEATER_OFF_CMD;
+
+ guard(mutex)(&data->lock);
+
+ ret = i2c_master_send(data->client, &heater_cmd, 1);
+ if (ret < 0)
+ return ret;
+
+ data->heater_enable = status;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(heater_enable);
+
+static int serial_number_show(struct seq_file *seq_file,
+ void *unused)
+{
+ struct htu31_data *data = seq_file->private;
+
+ seq_printf(seq_file, "%X%X%X\n", data->serial_number[0],
+ data->serial_number[1], data->serial_number[2]);
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(serial_number);
+
+static struct attribute *htu31_attrs[] = {
+ &dev_attr_heater_enable.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(htu31);
+
+static const struct hwmon_channel_info * const htu31_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops htu31_hwmon_ops = {
+ .is_visible = htu31_is_visible,
+ .read = htu31_read,
+};
+
+static const struct hwmon_chip_info htu31_chip_info = {
+ .info = htu31_info,
+ .ops = &htu31_hwmon_ops,
+};
+
+static int htu31_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct htu31_data *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ data->wait_time = HTU31_TEMPERATURE_CONV_TIME + HTU31_HUMIDITY_CONV_TIME;
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ crc8_populate_msb(htu31_crc8_table, HTU31_CRC8_POLYNOMIAL);
+
+ ret = htu31_read_serial_number(data);
+ if (ret) {
+ dev_err(dev, "Failed to read serial number\n");
+ return ret;
+ }
+
+ debugfs_create_file("serial_number",
+ 0444,
+ client->debugfs,
+ data,
+ &serial_number_fops);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev,
+ client->name,
+ data,
+ &htu31_chip_info,
+ htu31_groups);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id htu31_id[] = {
+ { "htu31" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, htu31_id);
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id htu31_of_match[] = {
+ { .compatible = "meas,htu31" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, htu31_of_match);
+#endif
+
+static struct i2c_driver htu31_driver = {
+ .driver = {
+ .name = "htu31",
+ .of_match_table = of_match_ptr(htu31_of_match),
+ },
+ .probe = htu31_probe,
+ .id_table = htu31_id,
+};
+module_i2c_driver(htu31_driver);
+
+MODULE_AUTHOR("Andrei Lalaev <andrey.lalaev@gmail.com>");
+MODULE_DESCRIPTION("HTU31 Temperature and Humidity sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/hwmon-vid.c b/drivers/hwmon/hwmon-vid.c
index 6d1175a51832..2df4956296ed 100644
--- a/drivers/hwmon/hwmon-vid.c
+++ b/drivers/hwmon/hwmon-vid.c
@@ -15,6 +15,10 @@
#include <linux/kernel.h>
#include <linux/hwmon-vid.h>
+#ifdef CONFIG_X86
+#include <asm/msr.h>
+#endif
+
/*
* Common code for decoding VID pins.
*
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index c7dd3f5b2bd5..0b4bdcd33c7b 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -14,10 +14,12 @@
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/hwmon.h>
+#include <linux/i2c.h>
#include <linux/idr.h>
#include <linux/kstrtox.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/property.h>
#include <linux/slab.h>
@@ -35,6 +37,7 @@ struct hwmon_device {
const char *label;
struct device dev;
const struct hwmon_chip_info *chip;
+ struct mutex lock;
struct list_head tzdata;
struct attribute_group group;
const struct attribute_group **groups;
@@ -136,7 +139,7 @@ static void hwmon_dev_release(struct device *dev)
kfree(hwdev);
}
-static struct class hwmon_class = {
+static const struct class hwmon_class = {
.name = "hwmon",
.dev_groups = hwmon_dev_attr_groups,
.dev_release = hwmon_dev_release,
@@ -144,13 +147,19 @@ static struct class hwmon_class = {
static DEFINE_IDA(hwmon_ida);
+static umode_t hwmon_is_visible(const struct hwmon_ops *ops,
+ const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (ops->visible)
+ return ops->visible;
+
+ return ops->is_visible(drvdata, type, attr, channel);
+}
+
/* Thermal zone handling */
-/*
- * The complex conditional is necessary to avoid a cyclic dependency
- * between hwmon and thermal_sys modules.
- */
-#ifdef CONFIG_THERMAL_OF
static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct hwmon_thermal_data *tdata = thermal_zone_device_priv(tz);
@@ -158,6 +167,8 @@ static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
int ret;
long t;
+ guard(mutex)(&hwdev->lock);
+
ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
tdata->index, &t);
if (ret < 0)
@@ -186,6 +197,8 @@ static int hwmon_thermal_set_trips(struct thermal_zone_device *tz, int low, int
if (!info[i])
return 0;
+ guard(mutex)(&hwdev->lock);
+
if (info[i]->config[tdata->index] & HWMON_T_MIN) {
err = chip->ops->write(tdata->dev, hwmon_temp,
hwmon_temp_min, tdata->index, low);
@@ -256,6 +269,9 @@ static int hwmon_thermal_register_sensors(struct device *dev)
void *drvdata = dev_get_drvdata(dev);
int i;
+ if (!IS_ENABLED(CONFIG_THERMAL_OF))
+ return 0;
+
for (i = 1; info[i]; i++) {
int j;
@@ -266,8 +282,8 @@ static int hwmon_thermal_register_sensors(struct device *dev)
int err;
if (!(info[i]->config[j] & HWMON_T_INPUT) ||
- !chip->ops->is_visible(drvdata, hwmon_temp,
- hwmon_temp_input, j))
+ !hwmon_is_visible(chip->ops, drvdata, hwmon_temp,
+ hwmon_temp_input, j))
continue;
err = hwmon_thermal_add_sensor(dev, j);
@@ -284,6 +300,9 @@ static void hwmon_thermal_notify(struct device *dev, int index)
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tzdata;
+ if (!IS_ENABLED(CONFIG_THERMAL_OF))
+ return;
+
list_for_each_entry(tzdata, &hwdev->tzdata, node) {
if (tzdata->index == index) {
thermal_zone_device_update(tzdata->tzd,
@@ -292,22 +311,116 @@ static void hwmon_thermal_notify(struct device *dev, int index)
}
}
-#else
-static int hwmon_thermal_register_sensors(struct device *dev)
+static int hwmon_attr_base(enum hwmon_sensor_types type)
{
- return 0;
+ if (type == hwmon_in || type == hwmon_intrusion)
+ return 0;
+ return 1;
}
-static void hwmon_thermal_notify(struct device *dev, int index) { }
+#if IS_REACHABLE(CONFIG_I2C)
-#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
+/*
+ * PEC support
+ *
+ * The 'pec' attribute is attached to I2C client devices. It is only provided
+ * if the i2c controller supports PEC.
+ *
+ * The mutex ensures that PEC configuration between i2c device and the hardware
+ * is consistent. Use a single mutex because attribute writes are supposed to be
+ * rare, and maintaining a separate mutex for each hardware monitoring device
+ * would add substantial complexity to the driver for little if any gain.
+ *
+ * The hardware monitoring device is identified as child of the i2c client
+ * device. This assumes that only a single hardware monitoring device is
+ * attached to an i2c client device.
+ */
-static int hwmon_attr_base(enum hwmon_sensor_types type)
+static int hwmon_match_device(struct device *dev, const void *data)
{
- if (type == hwmon_in || type == hwmon_intrusion)
+ return dev->class == &hwmon_class;
+}
+
+static ssize_t pec_show(struct device *dev, struct device_attribute *dummy,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC));
+}
+
+static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct hwmon_device *hwdev;
+ struct device *hdev;
+ bool val;
+ int err;
+
+ err = kstrtobool(buf, &val);
+ if (err < 0)
+ return err;
+
+ hdev = device_find_child(dev, NULL, hwmon_match_device);
+ if (!hdev)
+ return -ENODEV;
+
+ /*
+ * If there is no write function, we assume that chip specific
+ * handling is not required.
+ */
+ hwdev = to_hwmon_device(hdev);
+ guard(mutex)(&hwdev->lock);
+ if (hwdev->chip->ops->write) {
+ err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
+ if (err && err != -EOPNOTSUPP)
+ goto put;
+ }
+
+ if (!val)
+ client->flags &= ~I2C_CLIENT_PEC;
+ else
+ client->flags |= I2C_CLIENT_PEC;
+
+ err = count;
+put:
+ put_device(hdev);
+
+ return err;
+}
+
+static DEVICE_ATTR_RW(pec);
+
+static void hwmon_remove_pec(void *dev)
+{
+ device_remove_file(dev, &dev_attr_pec);
+}
+
+static int hwmon_pec_register(struct device *hdev)
+{
+ struct i2c_client *client = i2c_verify_client(hdev->parent);
+ int err;
+
+ if (!client)
+ return -EINVAL;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC))
return 0;
- return 1;
+
+ err = device_create_file(&client->dev, &dev_attr_pec);
+ if (err)
+ return err;
+
+ return devm_add_action_or_reset(hdev, hwmon_remove_pec, &client->dev);
+}
+
+#else /* CONFIG_I2C */
+static int hwmon_pec_register(struct device *hdev)
+{
+ return -EINVAL;
}
+#endif /* CONFIG_I2C */
/* sysfs attribute management */
@@ -315,18 +428,25 @@ static ssize_t hwmon_attr_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+ struct hwmon_device *hwdev = to_hwmon_device(dev);
+ s64 val64;
long val;
int ret;
+ guard(mutex)(&hwdev->lock);
+
ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index,
- &val);
+ (hattr->type == hwmon_energy64) ? (long *)&val64 : &val);
if (ret < 0)
return ret;
+ if (hattr->type != hwmon_energy64)
+ val64 = val;
+
trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type),
- hattr->name, val);
+ hattr->name, val64);
- return sprintf(buf, "%ld\n", val);
+ return sprintf(buf, "%lld\n", val64);
}
static ssize_t hwmon_attr_show_string(struct device *dev,
@@ -334,10 +454,13 @@ static ssize_t hwmon_attr_show_string(struct device *dev,
char *buf)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+ struct hwmon_device *hwdev = to_hwmon_device(dev);
enum hwmon_sensor_types type = hattr->type;
const char *s;
int ret;
+ guard(mutex)(&hwdev->lock);
+
ret = hattr->ops->read_string(dev, hattr->type, hattr->attr,
hattr->index, &s);
if (ret < 0)
@@ -354,6 +477,7 @@ static ssize_t hwmon_attr_store(struct device *dev,
const char *buf, size_t count)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+ struct hwmon_device *hwdev = to_hwmon_device(dev);
long val;
int ret;
@@ -361,13 +485,15 @@ static ssize_t hwmon_attr_store(struct device *dev,
if (ret < 0)
return ret;
+ guard(mutex)(&hwdev->lock);
+
ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index,
val);
if (ret < 0)
return ret;
trace_hwmon_attr_store(hattr->index + hwmon_attr_base(hattr->type),
- hattr->name, val);
+ hattr->name, (s64)val);
return count;
}
@@ -397,11 +523,7 @@ static struct attribute *hwmon_genattr(const void *drvdata,
const char *name;
bool is_string = is_string_attr(type, attr);
- /* The attribute is invisible if there is no template string */
- if (!template)
- return ERR_PTR(-ENOENT);
-
- mode = ops->is_visible(drvdata, type, attr, index);
+ mode = hwmon_is_visible(ops, drvdata, type, attr, index);
if (!mode)
return ERR_PTR(-ENOENT);
@@ -510,6 +632,7 @@ static const char * const hwmon_in_attr_templates[] = {
[hwmon_in_rated_min] = "in%d_rated_min",
[hwmon_in_rated_max] = "in%d_rated_max",
[hwmon_in_beep] = "in%d_beep",
+ [hwmon_in_fault] = "in%d_fault",
};
static const char * const hwmon_curr_attr_templates[] = {
@@ -538,8 +661,8 @@ static const char * const hwmon_power_attr_templates[] = {
[hwmon_power_enable] = "power%d_enable",
[hwmon_power_average] = "power%d_average",
[hwmon_power_average_interval] = "power%d_average_interval",
- [hwmon_power_average_interval_max] = "power%d_interval_max",
- [hwmon_power_average_interval_min] = "power%d_interval_min",
+ [hwmon_power_average_interval_max] = "power%d_average_interval_max",
+ [hwmon_power_average_interval_min] = "power%d_average_interval_min",
[hwmon_power_average_highest] = "power%d_average_highest",
[hwmon_power_average_lowest] = "power%d_average_lowest",
[hwmon_power_average_max] = "power%d_average_max",
@@ -586,6 +709,8 @@ static const char * const hwmon_humidity_attr_templates[] = {
[hwmon_humidity_fault] = "humidity%d_fault",
[hwmon_humidity_rated_min] = "humidity%d_rated_min",
[hwmon_humidity_rated_max] = "humidity%d_rated_max",
+ [hwmon_humidity_min_alarm] = "humidity%d_min_alarm",
+ [hwmon_humidity_max_alarm] = "humidity%d_max_alarm",
};
static const char * const hwmon_fan_attr_templates[] = {
@@ -624,6 +749,7 @@ static const char * const *__templates[] = {
[hwmon_curr] = hwmon_curr_attr_templates,
[hwmon_power] = hwmon_power_attr_templates,
[hwmon_energy] = hwmon_energy_attr_templates,
+ [hwmon_energy64] = hwmon_energy_attr_templates,
[hwmon_humidity] = hwmon_humidity_attr_templates,
[hwmon_fan] = hwmon_fan_attr_templates,
[hwmon_pwm] = hwmon_pwm_attr_templates,
@@ -637,6 +763,7 @@ static const int __templates_size[] = {
[hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates),
[hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates),
[hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates),
+ [hwmon_energy64] = ARRAY_SIZE(hwmon_energy_attr_templates),
[hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates),
[hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates),
[hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates),
@@ -675,6 +802,22 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
}
EXPORT_SYMBOL_GPL(hwmon_notify_event);
+void hwmon_lock(struct device *dev)
+{
+ struct hwmon_device *hwdev = to_hwmon_device(dev);
+
+ mutex_lock(&hwdev->lock);
+}
+EXPORT_SYMBOL_GPL(hwmon_lock);
+
+void hwmon_unlock(struct device *dev)
+{
+ struct hwmon_device *hwdev = to_hwmon_device(dev);
+
+ mutex_unlock(&hwdev->lock);
+}
+EXPORT_SYMBOL_GPL(hwmon_unlock);
+
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
{
int i, n;
@@ -709,8 +852,8 @@ static int hwmon_genattrs(const void *drvdata,
attr = __ffs(attr_mask);
attr_mask &= ~BIT(attr);
- if (attr >= template_size)
- return -EINVAL;
+ if (attr >= template_size || !templates[attr])
+ continue; /* attribute is invisible */
a = hwmon_genattr(drvdata, info->type, attr, i,
templates[attr], ops);
if (IS_ERR(a)) {
@@ -835,6 +978,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
tdev = tdev->parent;
hdev->of_node = tdev ? tdev->of_node : NULL;
hwdev->chip = chip;
+ mutex_init(&hwdev->lock);
dev_set_drvdata(hdev, drvdata);
dev_set_name(hdev, HWMON_ID_FORMAT, id);
err = device_register(hdev);
@@ -846,16 +990,26 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
INIT_LIST_HEAD(&hwdev->tzdata);
if (hdev->of_node && chip && chip->ops->read &&
- chip->info[0]->type == hwmon_chip &&
- (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
- err = hwmon_thermal_register_sensors(hdev);
- if (err) {
- device_unregister(hdev);
- /*
- * Don't worry about hwdev; hwmon_dev_release(), called
- * from device_unregister(), will free it.
- */
- goto ida_remove;
+ chip->info[0]->type == hwmon_chip) {
+ u32 config = chip->info[0]->config[0];
+
+ if (config & HWMON_C_REGISTER_TZ) {
+ err = hwmon_thermal_register_sensors(hdev);
+ if (err) {
+ device_unregister(hdev);
+ /*
+ * Don't worry about hwdev; hwmon_dev_release(),
+ * called from device_unregister(), will free it.
+ */
+ goto ida_remove;
+ }
+ }
+ if (config & HWMON_C_PEC) {
+ err = hwmon_pec_register(hdev);
+ if (err) {
+ device_unregister(hdev);
+ goto ida_remove;
+ }
}
}
@@ -915,7 +1069,7 @@ hwmon_device_register_with_info(struct device *dev, const char *name,
if (!dev || !name || !chip)
return ERR_PTR(-EINVAL);
- if (!chip->ops || !chip->ops->is_visible || !chip->info)
+ if (!chip->ops || !(chip->ops->visible || chip->ops->is_visible) || !chip->info)
return ERR_PTR(-EINVAL);
return __hwmon_device_register(dev, name, drvdata, chip, extra_groups);
@@ -945,7 +1099,7 @@ hwmon_device_register_for_thermal(struct device *dev, const char *name,
return __hwmon_device_register(dev, name, drvdata, NULL, NULL);
}
-EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, HWMON_THERMAL);
+EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, "HWMON_THERMAL");
/**
* hwmon_device_register - register w/ hwmon
@@ -1050,6 +1204,12 @@ devm_hwmon_device_register_with_info(struct device *dev, const char *name,
if (!dev)
return ERR_PTR(-EINVAL);
+ if (!name) {
+ name = devm_hwmon_sanitize_name(dev, dev_name(dev));
+ if (IS_ERR(name))
+ return ERR_CAST(name);
+ }
+
ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
@@ -1070,24 +1230,6 @@ error:
}
EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_info);
-static int devm_hwmon_match(struct device *dev, void *res, void *data)
-{
- struct device **hwdev = res;
-
- return *hwdev == data;
-}
-
-/**
- * devm_hwmon_device_unregister - removes a previously registered hwmon device
- *
- * @dev: the parent device of the device to unregister
- */
-void devm_hwmon_device_unregister(struct device *dev)
-{
- WARN_ON(devres_release(dev, devm_hwmon_release, devm_hwmon_match, dev));
-}
-EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister);
-
static char *__hwmon_sanitize_name(struct device *dev, const char *old_name)
{
char *name, *p;
diff --git a/drivers/hwmon/i5500_temp.c b/drivers/hwmon/i5500_temp.c
index 7b00b38c7f7b..bf006cb272b1 100644
--- a/drivers/hwmon/i5500_temp.c
+++ b/drivers/hwmon/i5500_temp.c
@@ -8,13 +8,10 @@
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/hwmon.h>
#include <linux/err.h>
-#include <linux/mutex.h>
/* Register definitions from datasheet */
#define REG_TSTHRCATA 0xE2
@@ -29,12 +26,6 @@
#define REG_CTCTRL 0xF7
#define REG_TSTIMER 0xF8
-static umode_t i5500_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
- int channel)
-{
- return 0444;
-}
-
static int i5500_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
{
@@ -84,7 +75,7 @@ static int i5500_read(struct device *dev, enum hwmon_sensor_types type, u32 attr
}
static const struct hwmon_ops i5500_ops = {
- .is_visible = i5500_is_visible,
+ .visible = 0444,
.read = i5500_read,
};
diff --git a/drivers/hwmon/i5k_amb.c b/drivers/hwmon/i5k_amb.c
index 783fa936e4d1..b22e0423e324 100644
--- a/drivers/hwmon/i5k_amb.c
+++ b/drivers/hwmon/i5k_amb.c
@@ -101,14 +101,7 @@ struct i5k_amb_data {
unsigned int num_attrs;
};
-static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- return sprintf(buf, "%s\n", DRVNAME);
-}
-
-
-static DEVICE_ATTR_RO(name);
+static DEVICE_STRING_ATTR_RO(name, 0444, DRVNAME);
static struct platform_device *amb_pdev;
@@ -373,7 +366,7 @@ static int i5k_amb_hwmon_init(struct platform_device *pdev)
}
}
- res = device_create_file(&pdev->dev, &dev_attr_name);
+ res = device_create_file(&pdev->dev, &dev_attr_name.attr);
if (res)
goto exit_remove;
@@ -386,7 +379,7 @@ static int i5k_amb_hwmon_init(struct platform_device *pdev)
return res;
exit_remove:
- device_remove_file(&pdev->dev, &dev_attr_name);
+ device_remove_file(&pdev->dev, &dev_attr_name.attr);
for (i = 0; i < data->num_attrs; i++)
device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);
kfree(data->attrs);
@@ -555,20 +548,19 @@ err:
return res;
}
-static int i5k_amb_remove(struct platform_device *pdev)
+static void i5k_amb_remove(struct platform_device *pdev)
{
int i;
struct i5k_amb_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
- device_remove_file(&pdev->dev, &dev_attr_name);
+ device_remove_file(&pdev->dev, &dev_attr_name.attr);
for (i = 0; i < data->num_attrs; i++)
device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);
kfree(data->attrs);
iounmap(data->amb_mmio);
release_mem_region(data->amb_base, data->amb_len);
kfree(data);
- return 0;
}
static struct platform_driver i5k_amb_driver = {
diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c
index 157e232aace0..daed437d34a4 100644
--- a/drivers/hwmon/ibmaem.c
+++ b/drivers/hwmon/ibmaem.c
@@ -349,7 +349,7 @@ static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
static int aem_read_sensor(struct aem_data *data, u8 elt, u8 reg,
void *buf, size_t size)
{
- int rs_size, res;
+ int rs_size;
struct aem_read_sensor_req rs_req;
/* Use preallocated rx buffer */
struct aem_read_sensor_resp *rs_resp = data->rs_resp;
@@ -383,17 +383,12 @@ static int aem_read_sensor(struct aem_data *data, u8 elt, u8 reg,
aem_send_message(ipmi);
- res = wait_for_completion_timeout(&ipmi->read_complete, IPMI_TIMEOUT);
- if (!res) {
- res = -ETIMEDOUT;
- goto out;
- }
+ if (!wait_for_completion_timeout(&ipmi->read_complete, IPMI_TIMEOUT))
+ return -ETIMEDOUT;
if (ipmi->rx_result || ipmi->rx_msg_len != rs_size ||
- memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id))) {
- res = -ENOENT;
- goto out;
- }
+ memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id)))
+ return -ENOENT;
switch (size) {
case 1: {
@@ -417,10 +412,8 @@ static int aem_read_sensor(struct aem_data *data, u8 elt, u8 reg,
break;
}
}
- res = 0;
-out:
- return res;
+ return 0;
}
/* Update AEM energy registers */
@@ -491,7 +484,6 @@ static void aem_delete(struct aem_data *data)
/* Retrieve version and module handle for an AEM1 instance */
static int aem_find_aem1_count(struct aem_ipmi_data *data)
{
- int res;
struct aem_find_firmware_req ff_req;
struct aem_find_firmware_resp ff_resp;
@@ -508,8 +500,7 @@ static int aem_find_aem1_count(struct aem_ipmi_data *data)
aem_send_message(data);
- res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT);
- if (!res)
+ if (!wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT))
return -ETIMEDOUT;
if (data->rx_result || data->rx_msg_len != sizeof(ff_resp) ||
@@ -632,7 +623,6 @@ static int aem_find_aem2(struct aem_ipmi_data *data,
struct aem_find_instance_resp *fi_resp,
int instance_num)
{
- int res;
struct aem_find_instance_req fi_req;
fi_req.id = system_x_id;
@@ -648,8 +638,7 @@ static int aem_find_aem2(struct aem_ipmi_data *data,
aem_send_message(data);
- res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT);
- if (!res)
+ if (!wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT))
return -ETIMEDOUT;
if (data->rx_result || data->rx_msg_len != sizeof(*fi_resp) ||
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
index db066b368918..228c5f6c6f38 100644
--- a/drivers/hwmon/ibmpex.c
+++ b/drivers/hwmon/ibmpex.c
@@ -256,12 +256,7 @@ static struct ibmpex_bmc_data *get_bmc_data(int iface)
return NULL;
}
-static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- return sprintf(buf, "%s\n", DRVNAME);
-}
-static SENSOR_DEVICE_ATTR_RO(name, name, 0);
+static DEVICE_STRING_ATTR_RO(name, 0444, DRVNAME);
static ssize_t ibmpex_show_sensor(struct device *dev,
struct device_attribute *devattr,
@@ -415,8 +410,7 @@ static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
if (err)
goto exit_remove;
- err = device_create_file(data->bmc_device,
- &sensor_dev_attr_name.dev_attr);
+ err = device_create_file(data->bmc_device, &dev_attr_name.attr);
if (err)
goto exit_remove;
@@ -425,7 +419,7 @@ static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
exit_remove:
device_remove_file(data->bmc_device,
&sensor_dev_attr_reset_high_low.dev_attr);
- device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ device_remove_file(data->bmc_device, &dev_attr_name.attr);
for (i = 0; i < data->num_sensors; i++)
for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
if (!data->sensors[i].attr[j].dev_attr.attr.name)
@@ -516,7 +510,7 @@ static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
device_remove_file(data->bmc_device,
&sensor_dev_attr_reset_high_low.dev_attr);
- device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ device_remove_file(data->bmc_device, &dev_attr_name.attr);
for (i = 0; i < data->num_sensors; i++)
for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
if (!data->sensors[i].attr[j].dev_attr.attr.name)
diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c
index 594254d6a72d..70ca833259ab 100644
--- a/drivers/hwmon/ibmpowernv.c
+++ b/drivers/hwmon/ibmpowernv.c
@@ -234,7 +234,7 @@ static int get_sensor_index_attr(const char *name, u32 *index, char *attr)
if (copy_len >= sizeof(buf))
return -EINVAL;
- strncpy(buf, hash_pos + 1, copy_len);
+ memcpy(buf, hash_pos + 1, copy_len);
err = kstrtou32(buf, 10, index);
if (err)
diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
index 4c8a80847891..e376d4cde5ad 100644
--- a/drivers/hwmon/iio_hwmon.c
+++ b/drivers/hwmon/iio_hwmon.c
@@ -33,6 +33,17 @@ struct iio_hwmon_state {
struct attribute **attrs;
};
+static ssize_t iio_hwmon_read_label(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+ struct iio_hwmon_state *state = dev_get_drvdata(dev);
+ struct iio_channel *chan = &state->channels[sattr->index];
+
+ return iio_read_channel_label(chan, buf);
+}
+
/*
* Assumes that IIO and hwmon operate in the same base units.
* This is supposed to be true, but needs verification for
@@ -49,16 +60,17 @@ static ssize_t iio_hwmon_read_val(struct device *dev,
struct iio_channel *chan = &state->channels[sattr->index];
enum iio_chan_type type;
- ret = iio_read_channel_processed(chan, &result);
- if (ret < 0)
- return ret;
-
ret = iio_get_channel_type(chan, &type);
if (ret < 0)
return ret;
if (type == IIO_POWER)
- result *= 1000; /* mili-Watts to micro-Watts conversion */
+ /* mili-Watts to micro-Watts conversion */
+ ret = iio_read_channel_processed_scale(chan, &result, 1000);
+ else
+ ret = iio_read_channel_processed(chan, &result);
+ if (ret < 0)
+ return ret;
return sprintf(buf, "%d\n", result);
}
@@ -68,12 +80,13 @@ static int iio_hwmon_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct iio_hwmon_state *st;
struct sensor_device_attribute *a;
- int ret, i;
+ int ret, i, attr = 0;
int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1, power_i = 1;
enum iio_chan_type type;
struct iio_channel *channels;
struct device *hwmon_dev;
char *sname;
+ void *buf;
channels = devm_iio_channel_get_all(dev);
if (IS_ERR(channels)) {
@@ -85,17 +98,18 @@ static int iio_hwmon_probe(struct platform_device *pdev)
}
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
- if (st == NULL)
+ buf = (void *)devm_get_free_pages(dev, GFP_KERNEL, 0);
+ if (!st || !buf)
return -ENOMEM;
st->channels = channels;
- /* count how many attributes we have */
+ /* count how many channels we have */
while (st->channels[st->num_channels].indio_dev)
st->num_channels++;
st->attrs = devm_kcalloc(dev,
- st->num_channels + 1, sizeof(*st->attrs),
+ 2 * st->num_channels + 1, sizeof(*st->attrs),
GFP_KERNEL);
if (st->attrs == NULL)
return -ENOMEM;
@@ -147,9 +161,31 @@ static int iio_hwmon_probe(struct platform_device *pdev)
a->dev_attr.show = iio_hwmon_read_val;
a->dev_attr.attr.mode = 0444;
a->index = i;
- st->attrs[i] = &a->dev_attr.attr;
+ st->attrs[attr++] = &a->dev_attr.attr;
+
+ /* Let's see if we have a label... */
+ if (iio_read_channel_label(&st->channels[i], buf) < 0)
+ continue;
+
+ a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
+ if (a == NULL)
+ return -ENOMEM;
+
+ sysfs_attr_init(&a->dev_attr.attr);
+ a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s%d_label",
+ prefix, n);
+ if (!a->dev_attr.attr.name)
+ return -ENOMEM;
+
+ a->dev_attr.show = iio_hwmon_read_label;
+ a->dev_attr.attr.mode = 0444;
+ a->index = i;
+ st->attrs[attr++] = &a->dev_attr.attr;
}
+ devm_free_pages(dev, (unsigned long)buf);
+
st->attr_group.attrs = st->attrs;
st->groups[0] = &st->attr_group;
diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
index c558143e5285..bd7b3380d847 100644
--- a/drivers/hwmon/ina209.c
+++ b/drivers/hwmon/ina209.c
@@ -576,7 +576,7 @@ static void ina209_remove(struct i2c_client *client)
}
static const struct i2c_device_id ina209_id[] = {
- { "ina209", 0 },
+ { "ina209" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ina209_id);
@@ -589,7 +589,6 @@ MODULE_DEVICE_TABLE(of, ina209_of_match);
/* This is the driver that will be inserted */
static struct i2c_driver ina209_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ina209",
.of_match_table = of_match_ptr(ina209_of_match),
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index f519c22d3907..ff67b03189f7 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -6,6 +6,7 @@
* Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com>
*/
+#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
@@ -15,17 +16,18 @@
#include <linux/of.h>
#include <linux/regmap.h>
-#include <linux/platform_data/ina2xx.h>
-
/* INA238 register definitions */
#define INA238_CONFIG 0x0
#define INA238_ADC_CONFIG 0x1
#define INA238_SHUNT_CALIBRATION 0x2
+#define SQ52206_SHUNT_TEMPCO 0x3
#define INA238_SHUNT_VOLTAGE 0x4
#define INA238_BUS_VOLTAGE 0x5
#define INA238_DIE_TEMP 0x6
#define INA238_CURRENT 0x7
#define INA238_POWER 0x8
+#define SQ52206_ENERGY 0x9
+#define SQ52206_CHARGE 0xa
#define INA238_DIAG_ALERT 0xb
#define INA238_SHUNT_OVER_VOLTAGE 0xc
#define INA238_SHUNT_UNDER_VOLTAGE 0xd
@@ -33,9 +35,12 @@
#define INA238_BUS_UNDER_VOLTAGE 0xf
#define INA238_TEMP_LIMIT 0x10
#define INA238_POWER_LIMIT 0x11
-#define INA238_DEVICE_ID 0x3f
+#define SQ52206_POWER_PEAK 0x20
+#define INA238_DEVICE_ID 0x3f /* not available on INA237 */
#define INA238_CONFIG_ADCRANGE BIT(4)
+#define SQ52206_CONFIG_ADCRANGE_HIGH BIT(4)
+#define SQ52206_CONFIG_ADCRANGE_LOW BIT(3)
#define INA238_DIAG_ALERT_TMPOL BIT(7)
#define INA238_DIAG_ALERT_SHNTOL BIT(6)
@@ -44,16 +49,18 @@
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
-#define INA238_REGISTERS 0x11
+#define INA238_REGISTERS 0x20
-#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */
+#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
/* Default configuration of device on reset. */
#define INA238_CONFIG_DEFAULT 0
+#define SQ52206_CONFIG_DEFAULT 0x0005
/* 16 sample averaging, 1052us conversion time, continuous mode */
#define INA238_ADC_CONFIG_DEFAULT 0xfb6a
/* Configure alerts to be based on averaged value (SLOWALERT) */
#define INA238_DIAG_ALERT_DEFAULT 0x2000
+#define INA238_DIAG_ALERT_APOL BIT(12)
/*
* This driver uses a fixed calibration value in order to scale current/power
* based on a fixed shunt resistor value. This allows for conversion within the
@@ -61,53 +68,121 @@
* relative to the shunt resistor value within the driver. This is similar to
* how the ina2xx driver handles current/power scaling.
*
- * The end result of this is that increasing shunt values (from a fixed 20 mOhm
- * shunt) increase the effective current/power accuracy whilst limiting the
- * range and decreasing shunt values decrease the effective accuracy but
- * increase the range.
- *
- * The value of the Current register is calculated given the following:
- * Current (A) = (shunt voltage register * 5) * calibration / 81920
- *
- * The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4).
- * With the maximum current value of 0x7fff and a fixed shunt value results in
- * a calibration value of 16384 (0x4000).
- *
- * 0x7fff = (0x7fff * 5) * calibration / 81920
- * calibration = 0x4000
- *
- * Equivalent calibration is applied for the Power register (maximum value for
- * bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can
- * occur is ~16776192 uW (register value 0x147a8):
- *
- * This scaling means the resulting values for Current and Power registers need
- * to be scaled by the difference between the fixed shunt resistor and the
- * actual shunt resistor:
+ * To achieve the best possible dynamic range, the value of the shunt voltage
+ * register should match the value of the current register. With that, the shunt
+ * voltage of 0x7fff = 32,767 uV = 163,785 uV matches the maximum current,
+ * and no accuracy is lost. Experiments with a real chip show that this is
+ * achieved by setting the SHUNT_CAL register to a value of 0x1000 = 4,096.
+ * Per datasheet,
+ * SHUNT_CAL = 819.2 x 10^6 x CURRENT_LSB x Rshunt
+ * = 819,200,000 x CURRENT_LSB x Rshunt
+ * With SHUNT_CAL set to 4,096, we get
+ * CURRENT_LSB = 4,096 / (819,200,000 x Rshunt)
+ * Assuming an Rshunt value of 5 mOhm, we get
+ * CURRENT_LSB = 4,096 / (819,200,000 x 0.005) = 1mA
+ * and thus a dynamic range of 1mA ... 32,767mA, which is sufficient for most
+ * applications. The actual dynamic range is of course determined by the actual
+ * shunt resistor value.
*
- * shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb)
- *
- * Current (mA) = register value * 20000 / rshunt / 4 * gain
- * Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain
+ * Power and energy values are scaled accordingly.
*/
-#define INA238_CALIBRATION_VALUE 16384
-#define INA238_FIXED_SHUNT 20000
+#define INA238_CALIBRATION_VALUE 4096
+#define INA238_FIXED_SHUNT 5000
+
+#define INA238_SHUNT_VOLTAGE_LSB 5000 /* 5 uV/lsb, in nV */
+#define INA238_BUS_VOLTAGE_LSB 3125000 /* 3.125 mV/lsb, in nV */
+#define SQ52206_BUS_VOLTAGE_LSB 3750000 /* 3.75 mV/lsb, in nV */
-#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */
-#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */
-#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */
+#define NUNIT_PER_MUNIT 1000000 /* n[AV] -> m[AV] */
-static struct regmap_config ina238_regmap_config = {
+static const struct regmap_config ina238_regmap_config = {
.max_register = INA238_REGISTERS,
.reg_bits = 8,
.val_bits = 16,
};
+enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
+
+struct ina238_config {
+ bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */
+ bool has_power_highest; /* chip detection power peak */
+ bool has_energy; /* chip detection energy */
+ u8 temp_resolution; /* temperature register resolution in bit */
+ u16 config_default; /* Power-on default state */
+ u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
+ u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
+ int current_lsb; /* current LSB, in uA */
+};
+
struct ina238_data {
+ const struct ina238_config *config;
struct i2c_client *client;
- struct mutex config_lock;
struct regmap *regmap;
u32 rshunt;
int gain;
+ u32 voltage_lsb[2]; /* shunt, bus voltage LSB, in nV */
+ int current_lsb; /* current LSB, in uA */
+ int power_lsb; /* power LSB, in uW */
+ int energy_lsb; /* energy LSB, in uJ */
+};
+
+static const struct ina238_config ina238_config[] = {
+ [ina228] = {
+ .has_20bit_voltage_current = true,
+ .has_energy = true,
+ .has_power_highest = false,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_resolution = 16,
+ },
+ [ina237] = {
+ .has_20bit_voltage_current = false,
+ .has_energy = false,
+ .has_power_highest = false,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_resolution = 12,
+ },
+ [ina238] = {
+ .has_20bit_voltage_current = false,
+ .has_energy = false,
+ .has_power_highest = false,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_resolution = 12,
+ },
+ [ina700] = {
+ .has_20bit_voltage_current = false,
+ .has_energy = true,
+ .has_power_highest = false,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_resolution = 12,
+ .current_lsb = 480,
+ },
+ [ina780] = {
+ .has_20bit_voltage_current = false,
+ .has_energy = true,
+ .has_power_highest = false,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_resolution = 12,
+ .current_lsb = 2400,
+ },
+ [sq52206] = {
+ .has_20bit_voltage_current = false,
+ .has_energy = true,
+ .has_power_highest = true,
+ .power_calculate_factor = 24,
+ .config_default = SQ52206_CONFIG_DEFAULT,
+ .bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
+ .temp_resolution = 16,
+ },
};
static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
@@ -126,20 +201,80 @@ static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
return 0;
}
+static int ina238_read_reg40(const struct i2c_client *client, u8 reg, u64 *val)
+{
+ u8 data[5];
+ u32 low;
+ int err;
+
+ /* 40-bit register read */
+ err = i2c_smbus_read_i2c_block_data(client, reg, 5, data);
+ if (err < 0)
+ return err;
+ if (err != 5)
+ return -EIO;
+ low = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4];
+ *val = ((long long)data[0] << 32) | low;
+
+ return 0;
+}
+
+static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *val)
+{
+ u32 regval;
+ int err;
+
+ err = ina238_read_reg24(client, reg, &regval);
+ if (err)
+ return err;
+
+ /* bits 3-0 Reserved, always zero */
+ regval >>= 4;
+
+ *val = sign_extend32(regval, 19);
+
+ return 0;
+}
+
+static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
+{
+ int reg = channel ? INA238_BUS_VOLTAGE : INA238_CURRENT;
+ u32 lsb = data->voltage_lsb[channel];
+ u32 factor = NUNIT_PER_MUNIT;
+ int err, regval;
+
+ if (data->config->has_20bit_voltage_current) {
+ err = ina238_read_field_s20(data->client, reg, &regval);
+ if (err)
+ return err;
+ /* Adjust accuracy: LSB in units of 500 pV */
+ lsb /= 8;
+ factor *= 2;
+ } else {
+ err = regmap_read(data->regmap, reg, &regval);
+ if (err)
+ return err;
+ regval = (s16)regval;
+ }
+
+ *val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, factor);
+ return 0;
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
- int reg, mask;
+ int reg, mask = 0;
int regval;
int err;
+ if (attr == hwmon_in_input)
+ return ina228_read_voltage(data, channel, val);
+
switch (channel) {
case 0:
switch (attr) {
- case hwmon_in_input:
- reg = INA238_SHUNT_VOLTAGE;
- break;
case hwmon_in_max:
reg = INA238_SHUNT_OVER_VOLTAGE;
break;
@@ -160,9 +295,6 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
break;
case 1:
switch (attr) {
- case hwmon_in_input:
- reg = INA238_BUS_VOLTAGE;
- break;
case hwmon_in_max:
reg = INA238_BUS_OVER_VOLTAGE;
break;
@@ -189,100 +321,126 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
if (err < 0)
return err;
- switch (attr) {
- case hwmon_in_input:
- case hwmon_in_max:
- case hwmon_in_min:
- /* signed register, value in mV */
- regval = (s16)regval;
- if (channel == 0)
- /* gain of 1 -> LSB / 4 */
- *val = (regval * INA238_SHUNT_VOLTAGE_LSB) /
- (1000 * (4 - data->gain + 1));
- else
- *val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000;
- break;
- case hwmon_in_max_alarm:
- case hwmon_in_min_alarm:
+ if (mask)
*val = !!(regval & mask);
- break;
- }
+ else
+ *val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->voltage_lsb[channel],
+ NUNIT_PER_MUNIT);
return 0;
}
-static int ina238_write_in(struct device *dev, u32 attr, int channel,
- long val)
+static int ina238_write_in(struct device *dev, u32 attr, int channel, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ static const int low_limits[2] = {-164, 0};
+ static const int high_limits[2] = {164, 150000};
+ static const u8 low_regs[2] = {INA238_SHUNT_UNDER_VOLTAGE, INA238_BUS_UNDER_VOLTAGE};
+ static const u8 high_regs[2] = {INA238_SHUNT_OVER_VOLTAGE, INA238_BUS_OVER_VOLTAGE};
int regval;
- if (attr != hwmon_in_max && attr != hwmon_in_min)
- return -EOPNOTSUPP;
-
- /* convert decimal to register value */
- switch (channel) {
- case 0:
- /* signed value, clamp to max range +/-163 mV */
- regval = clamp_val(val, -163, 163);
- regval = (regval * 1000 * (4 - data->gain + 1)) /
- INA238_SHUNT_VOLTAGE_LSB;
- regval = clamp_val(regval, S16_MIN, S16_MAX);
+ /* Initial clamp to avoid overflows */
+ val = clamp_val(val, low_limits[channel], high_limits[channel]);
+ val = DIV_S64_ROUND_CLOSEST((s64)val * NUNIT_PER_MUNIT, data->voltage_lsb[channel]);
+ /* Final clamp to register limits */
+ regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
- switch (attr) {
- case hwmon_in_max:
- return regmap_write(data->regmap,
- INA238_SHUNT_OVER_VOLTAGE, regval);
- case hwmon_in_min:
- return regmap_write(data->regmap,
- INA238_SHUNT_UNDER_VOLTAGE, regval);
- default:
- return -EOPNOTSUPP;
- }
- case 1:
- /* signed value, positive values only. Clamp to max 102.396 V */
- regval = clamp_val(val, 0, 102396);
- regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB;
- regval = clamp_val(regval, 0, S16_MAX);
-
- switch (attr) {
- case hwmon_in_max:
- return regmap_write(data->regmap,
- INA238_BUS_OVER_VOLTAGE, regval);
- case hwmon_in_min:
- return regmap_write(data->regmap,
- INA238_BUS_UNDER_VOLTAGE, regval);
- default:
- return -EOPNOTSUPP;
- }
+ switch (attr) {
+ case hwmon_in_min:
+ return regmap_write(data->regmap, low_regs[channel], regval);
+ case hwmon_in_max:
+ return regmap_write(data->regmap, high_regs[channel], regval);
default:
return -EOPNOTSUPP;
}
}
-static int ina238_read_current(struct device *dev, u32 attr, long *val)
+static int __ina238_read_curr(struct ina238_data *data, long *val)
+{
+ u32 lsb = data->current_lsb;
+ int err, regval;
+
+ if (data->config->has_20bit_voltage_current) {
+ err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
+ if (err)
+ return err;
+ lsb /= 16; /* Adjust accuracy */
+ } else {
+ err = regmap_read(data->regmap, INA238_CURRENT, &regval);
+ if (err)
+ return err;
+ regval = (s16)regval;
+ }
+
+ *val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, 1000);
+ return 0;
+}
+
+static int ina238_read_curr(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
+ int reg, mask = 0;
int regval;
int err;
- switch (attr) {
- case hwmon_curr_input:
- err = regmap_read(data->regmap, INA238_CURRENT, &regval);
- if (err < 0)
- return err;
+ if (attr == hwmon_curr_input)
+ return __ina238_read_curr(data, val);
- /* Signed register, fixed 1mA current lsb. result in mA */
- *val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain,
- data->rshunt * 4);
+ switch (attr) {
+ case hwmon_curr_min:
+ reg = INA238_SHUNT_UNDER_VOLTAGE;
+ break;
+ case hwmon_curr_min_alarm:
+ reg = INA238_DIAG_ALERT;
+ mask = INA238_DIAG_ALERT_SHNTUL;
+ break;
+ case hwmon_curr_max:
+ reg = INA238_SHUNT_OVER_VOLTAGE;
+ break;
+ case hwmon_curr_max_alarm:
+ reg = INA238_DIAG_ALERT;
+ mask = INA238_DIAG_ALERT_SHNTOL;
break;
default:
return -EOPNOTSUPP;
}
+ err = regmap_read(data->regmap, reg, &regval);
+ if (err < 0)
+ return err;
+
+ if (mask)
+ *val = !!(regval & mask);
+ else
+ *val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->current_lsb, 1000);
+
return 0;
}
+static int ina238_write_curr(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ int regval;
+
+ /* Set baseline range to avoid over/underflows */
+ val = clamp_val(val, -1000000, 1000000);
+ /* Scale */
+ val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb);
+ /* Clamp to register size */
+ regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
+
+ switch (attr) {
+ case hwmon_curr_min:
+ return regmap_write(data->regmap, INA238_SHUNT_UNDER_VOLTAGE,
+ regval);
+ case hwmon_curr_max:
+ return regmap_write(data->regmap, INA238_SHUNT_OVER_VOLTAGE,
+ regval);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int ina238_read_power(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
@@ -296,9 +454,16 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
if (err)
return err;
- /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
- power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT *
- data->gain, 20 * data->rshunt);
+ power = (long long)regval * data->power_lsb;
+ /* Clamp value to maximum value of long */
+ *val = clamp_val(power, 0, LONG_MAX);
+ break;
+ case hwmon_power_input_highest:
+ err = ina238_read_reg24(data->client, SQ52206_POWER_PEAK, &regval);
+ if (err)
+ return err;
+
+ power = (long long)regval * data->power_lsb;
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@@ -311,8 +476,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
* Truncated 24-bit compare register, lower 8-bits are
* truncated. Same conversion to/from uW as POWER register.
*/
- power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT *
- data->gain, 20 * data->rshunt);
+ power = ((long long)regval << 8) * data->power_lsb;
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@@ -330,25 +494,26 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
return 0;
}
-static int ina238_write_power(struct device *dev, u32 attr, long val)
+static int ina238_write_power_max(struct device *dev, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
- long regval;
-
- if (attr != hwmon_power_max)
- return -EOPNOTSUPP;
/*
* Unsigned postive values. Compared against the 24-bit power register,
* lower 8-bits are truncated. Same conversion to/from uW as POWER
* register.
+ * The first clamp_val() is to establish a baseline to avoid overflows.
*/
- regval = clamp_val(val, 0, LONG_MAX);
- regval = div_u64(val * 20ULL * data->rshunt,
- 1000ULL * INA238_FIXED_SHUNT * data->gain);
- regval = clamp_val(regval >> 8, 0, U16_MAX);
+ val = clamp_val(val, 0, LONG_MAX / 2);
+ val = DIV_ROUND_CLOSEST(val, data->power_lsb);
+ val = clamp_val(val >> 8, 0, U16_MAX);
- return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
+ return regmap_write(data->regmap, INA238_POWER_LIMIT, val);
+}
+
+static int ina238_temp_from_reg(s16 regval, u8 resolution)
+{
+ return ((regval >> (16 - resolution)) * 1000) >> (resolution - 9);
}
static int ina238_read_temp(struct device *dev, u32 attr, long *val)
@@ -362,17 +527,14 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
err = regmap_read(data->regmap, INA238_DIE_TEMP, &regval);
if (err)
return err;
-
- /* Signed, bits 15-4 of register, result in mC */
- *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
+ *val = ina238_temp_from_reg(regval, data->config->temp_resolution);
break;
case hwmon_temp_max:
err = regmap_read(data->regmap, INA238_TEMP_LIMIT, &regval);
if (err)
return err;
-
- /* Signed, bits 15-4 of register, result in mC */
- *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
+ /* Signed, result in mC */
+ *val = ina238_temp_from_reg(regval, data->config->temp_resolution);
break;
case hwmon_temp_max_alarm:
err = regmap_read(data->regmap, INA238_DIAG_ALERT, &regval);
@@ -388,19 +550,37 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
return 0;
}
-static int ina238_write_temp(struct device *dev, u32 attr, long val)
+static u16 ina238_temp_to_reg(long val, u8 resolution)
+{
+ int fraction = 1000 - DIV_ROUND_CLOSEST(1000, BIT(resolution - 9));
+
+ val = clamp_val(val, -255000 - fraction, 255000 + fraction);
+
+ return (DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution)) & 0xffff;
+}
+
+static int ina238_write_temp_max(struct device *dev, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
- if (attr != hwmon_temp_max)
- return -EOPNOTSUPP;
+ regval = ina238_temp_to_reg(val, data->config->temp_resolution);
+ return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
+}
- /* Signed, bits 15-4 of register */
- regval = (val / INA238_DIE_TEMP_LSB) << 4;
- regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0;
+static int ina238_read_energy(struct device *dev, s64 *energy)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ u64 regval;
+ int ret;
- return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
+ ret = ina238_read_reg40(data->client, SQ52206_ENERGY, &regval);
+ if (ret)
+ return ret;
+
+ /* result in uJ */
+ *energy = regval * data->energy_lsb;
+ return 0;
}
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
@@ -410,9 +590,11 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
- return ina238_read_current(dev, attr, val);
+ return ina238_read_curr(dev, attr, val);
case hwmon_power:
return ina238_read_power(dev, attr, val);
+ case hwmon_energy64:
+ return ina238_read_energy(dev, (s64 *)val);
case hwmon_temp:
return ina238_read_temp(dev, attr, val);
default:
@@ -422,36 +604,30 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
}
static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long val)
+ u32 attr, int channel, long val)
{
- struct ina238_data *data = dev_get_drvdata(dev);
- int err;
-
- mutex_lock(&data->config_lock);
-
switch (type) {
case hwmon_in:
- err = ina238_write_in(dev, attr, channel, val);
- break;
+ return ina238_write_in(dev, attr, channel, val);
+ case hwmon_curr:
+ return ina238_write_curr(dev, attr, val);
case hwmon_power:
- err = ina238_write_power(dev, attr, val);
- break;
+ return ina238_write_power_max(dev, val);
case hwmon_temp:
- err = ina238_write_temp(dev, attr, val);
- break;
+ return ina238_write_temp_max(dev, val);
default:
- err = -EOPNOTSUPP;
- break;
+ return -EOPNOTSUPP;
}
-
- mutex_unlock(&data->config_lock);
- return err;
}
static umode_t ina238_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
+ const struct ina238_data *data = drvdata;
+ bool has_power_highest = data->config->has_power_highest;
+ bool has_energy = data->config->has_energy;
+
switch (type) {
case hwmon_in:
switch (attr) {
@@ -468,7 +644,12 @@ static umode_t ina238_is_visible(const void *drvdata,
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
return 0444;
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ return 0644;
default:
return 0;
}
@@ -479,9 +660,18 @@ static umode_t ina238_is_visible(const void *drvdata,
return 0444;
case hwmon_power_max:
return 0644;
+ case hwmon_power_input_highest:
+ if (has_power_highest)
+ return 0444;
+ return 0;
default:
return 0;
}
+ case hwmon_energy64:
+ /* hwmon_energy_input */
+ if (has_energy)
+ return 0444;
+ return 0;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
@@ -509,10 +699,14 @@ static const struct hwmon_channel_info * const ina238_info[] = {
INA238_HWMON_IN_CONFIG),
HWMON_CHANNEL_INFO(curr,
/* 0: current through shunt */
- HWMON_C_INPUT),
+ HWMON_C_INPUT | HWMON_C_MIN | HWMON_C_MIN_ALARM |
+ HWMON_C_MAX | HWMON_C_MAX_ALARM),
HWMON_CHANNEL_INFO(power,
/* 0: power */
- HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM),
+ HWMON_P_INPUT | HWMON_P_MAX |
+ HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST),
+ HWMON_CHANNEL_INFO(energy64,
+ HWMON_E_INPUT),
HWMON_CHANNEL_INFO(temp,
/* 0: die temperature */
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
@@ -532,19 +726,22 @@ static const struct hwmon_chip_info ina238_chip_info = {
static int ina238_probe(struct i2c_client *client)
{
- struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct ina238_data *data;
+ enum ina238_ids chip;
int config;
int ret;
+ chip = (uintptr_t)i2c_get_match_data(client);
+
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
- mutex_init(&data->config_lock);
+ /* set the device type */
+ data->config = &ina238_config[chip];
data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config);
if (IS_ERR(data->regmap)) {
@@ -552,27 +749,48 @@ static int ina238_probe(struct i2c_client *client)
return PTR_ERR(data->regmap);
}
- /* load shunt value */
- data->rshunt = INA238_RSHUNT_DEFAULT;
- if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
- data->rshunt = pdata->shunt_uohms;
- if (data->rshunt == 0) {
- dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
- return -EINVAL;
- }
+ /* Setup CONFIG register */
+ config = data->config->config_default;
+ if (data->config->current_lsb) {
+ data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB;
+ data->current_lsb = data->config->current_lsb;
+ } else {
+ /* load shunt value */
+ if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0)
+ data->rshunt = INA238_RSHUNT_DEFAULT;
+ if (data->rshunt == 0) {
+ dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
+ return -EINVAL;
+ }
+
+ /* load shunt gain value */
+ if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
+ data->gain = 4; /* Default of ADCRANGE = 0 */
+ if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
+ dev_err(dev, "invalid shunt gain value %u\n", data->gain);
+ return -EINVAL;
+ }
- /* load shunt gain value */
- if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
- data->gain = 4; /* Default of ADCRANGE = 0 */
- if (data->gain != 1 && data->gain != 4) {
- dev_err(dev, "invalid shunt gain value %u\n", data->gain);
- return -EINVAL;
+ /* Setup SHUNT_CALIBRATION register with fixed value */
+ ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
+ INA238_CALIBRATION_VALUE);
+ if (ret < 0) {
+ dev_err(dev, "error configuring the device: %d\n", ret);
+ return -ENODEV;
+ }
+ if (chip == sq52206) {
+ if (data->gain == 1) /* ADCRANGE = 10/11 is /1 */
+ config |= SQ52206_CONFIG_ADCRANGE_HIGH;
+ else if (data->gain == 2) /* ADCRANGE = 01 is /2 */
+ config |= SQ52206_CONFIG_ADCRANGE_LOW;
+ } else if (data->gain == 1) { /* ADCRANGE = 1 is /1 */
+ config |= INA238_CONFIG_ADCRANGE;
+ }
+ data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4;
+ data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain,
+ data->rshunt);
}
- /* Setup CONFIG register */
- config = INA238_CONFIG_DEFAULT;
- if (data->gain == 1)
- config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */
ret = regmap_write(data->regmap, INA238_CONFIG, config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
@@ -587,48 +805,78 @@ static int ina238_probe(struct i2c_client *client)
return -ENODEV;
}
- /* Setup SHUNT_CALIBRATION register with fixed value */
- ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
- INA238_CALIBRATION_VALUE);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return -ENODEV;
- }
-
/* Setup alert/alarm configuration */
- ret = regmap_write(data->regmap, INA238_DIAG_ALERT,
- INA238_DIAG_ALERT_DEFAULT);
+ config = INA238_DIAG_ALERT_DEFAULT;
+ if (device_property_read_bool(dev, "ti,alert-polarity-active-high"))
+ config |= INA238_DIAG_ALERT_APOL;
+
+ ret = regmap_write(data->regmap, INA238_DIAG_ALERT, config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
}
+ data->voltage_lsb[1] = data->config->bus_voltage_lsb;
+
+ data->power_lsb = DIV_ROUND_CLOSEST(data->current_lsb *
+ data->config->power_calculate_factor,
+ 100);
+
+ data->energy_lsb = data->power_lsb * 16;
+
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
- &ina238_chip_info,
- NULL);
+ &ina238_chip_info, NULL);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
- dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
- client->name, data->rshunt, data->gain);
+ if (data->rshunt)
+ dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
+ client->name, data->rshunt, data->gain);
return 0;
}
static const struct i2c_device_id ina238_id[] = {
- { "ina238", 0 },
+ { "ina228", ina228 },
+ { "ina237", ina237 },
+ { "ina238", ina238 },
+ { "ina700", ina700 },
+ { "ina780", ina780 },
+ { "sq52206", sq52206 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ina238_id);
static const struct of_device_id __maybe_unused ina238_of_match[] = {
- { .compatible = "ti,ina238" },
- { },
+ {
+ .compatible = "ti,ina228",
+ .data = (void *)ina228
+ },
+ {
+ .compatible = "ti,ina237",
+ .data = (void *)ina237
+ },
+ {
+ .compatible = "ti,ina238",
+ .data = (void *)ina238
+ },
+ {
+ .compatible = "ti,ina700",
+ .data = (void *)ina700
+ },
+ {
+ .compatible = "ti,ina780",
+ .data = (void *)ina780
+ },
+ {
+ .compatible = "silergy,sq52206",
+ .data = (void *)sq52206
+ },
+ { }
};
MODULE_DEVICE_TABLE(of, ina238_of_match);
static struct i2c_driver ina238_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ina238",
.of_match_table = of_match_ptr(ina238_of_match),
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index d8415d1f21fc..69ac0468dee4 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -22,21 +22,21 @@
* Thanks to Jan Volkering
*/
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/err.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/jiffies.h>
-#include <linux/of.h>
-#include <linux/delay.h>
+#include <linux/sysfs.h>
#include <linux/util_macros.h>
-#include <linux/regmap.h>
-
-#include <linux/platform_data/ina2xx.h>
/* common register definitions */
#define INA2XX_CONFIG 0x00
@@ -51,59 +51,98 @@
#define INA226_ALERT_LIMIT 0x07
#define INA226_DIE_ID 0xFF
-/* register count */
-#define INA219_REGISTERS 6
-#define INA226_REGISTERS 8
-
-#define INA2XX_MAX_REGISTERS 8
+/* SY24655 register definitions */
+#define SY24655_EIN 0x0A
+#define SY24655_ACCUM_CONFIG 0x0D
+#define INA2XX_MAX_REGISTERS 0x0D
/* settings - depend on use case */
#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */
#define INA226_CONFIG_DEFAULT 0x4527 /* averages=16 */
+#define INA260_CONFIG_DEFAULT 0x6527 /* averages=16 */
+#define SY24655_CONFIG_DEFAULT 0x4527 /* averages=16 */
+
+/* (only for sy24655) */
+#define SY24655_ACCUM_CONFIG_DEFAULT 0x044C /* continuous mode, clear after read*/
/* worst case is 68.10 ms (~14.6Hz, ina219) */
#define INA2XX_CONVERSION_RATE 15
#define INA2XX_MAX_DELAY 69 /* worst case delay in ms */
#define INA2XX_RSHUNT_DEFAULT 10000
+#define INA260_RSHUNT 2000
/* bit mask for reading the averaging setting in the configuration register */
-#define INA226_AVG_RD_MASK 0x0E00
+#define INA226_AVG_RD_MASK GENMASK(11, 9)
-#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9)
-#define INA226_SHIFT_AVG(val) ((val) << 9)
+#define INA226_READ_AVG(reg) FIELD_GET(INA226_AVG_RD_MASK, reg)
+
+#define INA226_ALERT_LATCH_ENABLE BIT(0)
+#define INA226_ALERT_POLARITY BIT(1)
/* bit number of alert functions in Mask/Enable Register */
-#define INA226_SHUNT_OVER_VOLTAGE_BIT 15
-#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14
-#define INA226_BUS_OVER_VOLTAGE_BIT 13
-#define INA226_BUS_UNDER_VOLTAGE_BIT 12
-#define INA226_POWER_OVER_LIMIT_BIT 11
+#define INA226_SHUNT_OVER_VOLTAGE_MASK BIT(15)
+#define INA226_SHUNT_UNDER_VOLTAGE_MASK BIT(14)
+#define INA226_BUS_OVER_VOLTAGE_MASK BIT(13)
+#define INA226_BUS_UNDER_VOLTAGE_MASK BIT(12)
+#define INA226_POWER_OVER_LIMIT_MASK BIT(11)
/* bit mask for alert config bits of Mask/Enable Register */
-#define INA226_ALERT_CONFIG_MASK 0xFC00
+#define INA226_ALERT_CONFIG_MASK GENMASK(15, 10)
#define INA226_ALERT_FUNCTION_FLAG BIT(4)
-/* common attrs, ina226 attrs and NULL */
-#define INA2XX_MAX_ATTRIBUTE_GROUPS 3
-
/*
* Both bus voltage and shunt voltage conversion times for ina226 are set
* to 0b0100 on POR, which translates to 2200 microseconds in total.
*/
#define INA226_TOTAL_CONV_TIME_DEFAULT 2200
-static struct regmap_config ina2xx_regmap_config = {
+static bool ina2xx_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case INA2XX_CONFIG:
+ case INA2XX_CALIBRATION:
+ case INA226_MASK_ENABLE:
+ case INA226_ALERT_LIMIT:
+ case SY24655_ACCUM_CONFIG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ina2xx_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case INA2XX_SHUNT_VOLTAGE:
+ case INA2XX_BUS_VOLTAGE:
+ case INA2XX_POWER:
+ case INA2XX_CURRENT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ina2xx_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
+ .use_single_write = true,
+ .use_single_read = true,
+ .max_register = INA2XX_MAX_REGISTERS,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = ina2xx_volatile_reg,
+ .writeable_reg = ina2xx_writeable_reg,
};
-enum ina2xx_ids { ina219, ina226 };
+enum ina2xx_ids { ina219, ina226, ina260, sy24655 };
struct ina2xx_config {
u16 config_default;
+ bool has_alerts; /* chip supports alerts and limits */
+ bool has_ishunt; /* chip has internal shunt resistor */
+ bool has_power_average; /* chip has internal shunt resistor */
int calibration_value;
- int registers;
int shunt_div;
int bus_voltage_shift;
int bus_voltage_lsb; /* uV */
@@ -112,34 +151,58 @@ struct ina2xx_config {
struct ina2xx_data {
const struct ina2xx_config *config;
+ enum ina2xx_ids chip;
long rshunt;
long current_lsb_uA;
long power_lsb_uW;
- struct mutex config_lock;
struct regmap *regmap;
-
- const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS];
+ struct i2c_client *client;
};
static const struct ina2xx_config ina2xx_config[] = {
[ina219] = {
.config_default = INA219_CONFIG_DEFAULT,
.calibration_value = 4096,
- .registers = INA219_REGISTERS,
.shunt_div = 100,
.bus_voltage_shift = 3,
.bus_voltage_lsb = 4000,
.power_lsb_factor = 20,
+ .has_alerts = false,
+ .has_ishunt = false,
+ .has_power_average = false,
},
[ina226] = {
.config_default = INA226_CONFIG_DEFAULT,
.calibration_value = 2048,
- .registers = INA226_REGISTERS,
.shunt_div = 400,
.bus_voltage_shift = 0,
.bus_voltage_lsb = 1250,
.power_lsb_factor = 25,
+ .has_alerts = true,
+ .has_ishunt = false,
+ .has_power_average = false,
+ },
+ [ina260] = {
+ .config_default = INA260_CONFIG_DEFAULT,
+ .shunt_div = 400,
+ .bus_voltage_shift = 0,
+ .bus_voltage_lsb = 1250,
+ .power_lsb_factor = 8,
+ .has_alerts = true,
+ .has_ishunt = true,
+ .has_power_average = false,
+ },
+ [sy24655] = {
+ .config_default = SY24655_CONFIG_DEFAULT,
+ .calibration_value = 4096,
+ .shunt_div = 400,
+ .bus_voltage_shift = 0,
+ .bus_voltage_lsb = 1250,
+ .power_lsb_factor = 25,
+ .has_alerts = true,
+ .has_ishunt = false,
+ .has_power_average = true,
},
};
@@ -166,58 +229,85 @@ static int ina226_reg_to_interval(u16 config)
* Return the new, shifted AVG field value of CONFIG register,
* to use with regmap_update_bits
*/
-static u16 ina226_interval_to_reg(int interval)
+static u16 ina226_interval_to_reg(long interval)
{
int avg, avg_bits;
+ /*
+ * The maximum supported interval is 1,024 * (2 * 8.244ms) ~= 16.8s.
+ * Clamp to 32 seconds before calculations to avoid overflows.
+ */
+ interval = clamp_val(interval, 0, 32000);
+
avg = DIV_ROUND_CLOSEST(interval * 1000,
INA226_TOTAL_CONV_TIME_DEFAULT);
avg_bits = find_closest(avg, ina226_avg_tab,
ARRAY_SIZE(ina226_avg_tab));
- return INA226_SHIFT_AVG(avg_bits);
+ return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits);
}
-/*
- * Calibration register is set to the best value, which eliminates
- * truncation errors on calculating current register in hardware.
- * According to datasheet (eq. 3) the best values are 2048 for
- * ina226 and 4096 for ina219. They are hardcoded as calibration_value.
- */
-static int ina2xx_calibrate(struct ina2xx_data *data)
+static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
+ unsigned int regval)
{
- return regmap_write(data->regmap, INA2XX_CALIBRATION,
- data->config->calibration_value);
-}
+ int val;
-/*
- * Initialize the configuration and calibration registers.
- */
-static int ina2xx_init(struct ina2xx_data *data)
-{
- int ret = regmap_write(data->regmap, INA2XX_CONFIG,
- data->config->config_default);
- if (ret < 0)
- return ret;
+ switch (reg) {
+ case INA2XX_SHUNT_VOLTAGE:
+ /* signed register */
+ val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div);
+ break;
+ case INA2XX_BUS_VOLTAGE:
+ val = (regval >> data->config->bus_voltage_shift) *
+ data->config->bus_voltage_lsb;
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ break;
+ case INA2XX_POWER:
+ val = regval * data->power_lsb_uW;
+ break;
+ case INA2XX_CURRENT:
+ /* signed register, result in mA */
+ val = (s16)regval * data->current_lsb_uA;
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ break;
+ case INA2XX_CALIBRATION:
+ val = regval;
+ break;
+ default:
+ /* programmer goofed */
+ WARN_ON_ONCE(1);
+ val = 0;
+ break;
+ }
- return ina2xx_calibrate(data);
+ return val;
}
-static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
+/*
+ * Read and convert register value from chip. If the register value is 0,
+ * check if the chip has been power cycled or reset. If so, re-initialize it.
+ */
+static int ina2xx_read_init(struct device *dev, int reg, long *val)
{
struct ina2xx_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
int ret, retry;
- dev_dbg(dev, "Starting register %d read\n", reg);
+ if (data->config->has_ishunt) {
+ /* No calibration needed */
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret < 0)
+ return ret;
+ *val = ina2xx_get_value(data, reg, regval);
+ return 0;
+ }
for (retry = 5; retry; retry--) {
-
- ret = regmap_read(data->regmap, reg, regval);
+ ret = regmap_read(regmap, reg, &regval);
if (ret < 0)
return ret;
- dev_dbg(dev, "read %d, val = 0x%04x\n", reg, *regval);
-
/*
* If the current value in the calibration register is 0, the
* power and current registers will also remain at 0. In case
@@ -226,20 +316,19 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
* We do that extra read of the calibration register if there
* is some hint of a chip reset.
*/
- if (*regval == 0) {
+ if (regval == 0) {
unsigned int cal;
- ret = regmap_read(data->regmap, INA2XX_CALIBRATION,
- &cal);
+ ret = regmap_read_bypassed(regmap, INA2XX_CALIBRATION, &cal);
if (ret < 0)
return ret;
if (cal == 0) {
dev_warn(dev, "chip not calibrated, reinitializing\n");
- ret = ina2xx_init(data);
- if (ret < 0)
- return ret;
+ regcache_mark_dirty(regmap);
+ regcache_sync(regmap);
+
/*
* Let's make sure the power and current
* registers have been updated before trying
@@ -249,6 +338,7 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
continue;
}
}
+ *val = ina2xx_get_value(data, reg, regval);
return 0;
}
@@ -261,393 +351,599 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
return -ENODEV;
}
-static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
- unsigned int regval)
+/*
+ * Turns alert limit values into register values.
+ * Opposite of the formula in ina2xx_get_value().
+ */
+static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val)
{
- int val;
-
switch (reg) {
case INA2XX_SHUNT_VOLTAGE:
- /* signed register */
- val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div);
- break;
+ val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div);
+ val *= data->config->shunt_div;
+ return clamp_val(val, 0, SHRT_MAX);
case INA2XX_BUS_VOLTAGE:
- val = (regval >> data->config->bus_voltage_shift)
- * data->config->bus_voltage_lsb;
- val = DIV_ROUND_CLOSEST(val, 1000);
- break;
+ val = clamp_val(val, 0, 200000);
+ val = (val * 1000) << data->config->bus_voltage_shift;
+ val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
+ return clamp_val(val, 0, USHRT_MAX);
case INA2XX_POWER:
- val = regval * data->power_lsb_uW;
- break;
+ val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW);
+ val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
+ return clamp_val(val, 0, USHRT_MAX);
case INA2XX_CURRENT:
+ val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000);
/* signed register, result in mA */
- val = (s16)regval * data->current_lsb_uA;
- val = DIV_ROUND_CLOSEST(val, 1000);
- break;
- case INA2XX_CALIBRATION:
- val = regval;
- break;
+ val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA);
+ return clamp_val(val, SHRT_MIN, SHRT_MAX);
default:
/* programmer goofed */
WARN_ON_ONCE(1);
- val = 0;
- break;
+ return 0;
}
+}
- return val;
+static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, long *val)
+{
+ struct regmap *regmap = data->regmap;
+ int regval;
+ int ret;
+
+ ret = regmap_read(regmap, INA226_MASK_ENABLE, &regval);
+ if (ret)
+ return ret;
+
+ if (regval & mask) {
+ ret = regmap_read(regmap, INA226_ALERT_LIMIT, &regval);
+ if (ret)
+ return ret;
+ *val = ina2xx_get_value(data, reg, regval);
+ } else {
+ *val = 0;
+ }
+ return 0;
}
-static ssize_t ina2xx_value_show(struct device *dev,
- struct device_attribute *da, char *buf)
+static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, long val)
+{
+ struct regmap *regmap = data->regmap;
+ int ret;
+
+ if (val < 0)
+ return -EINVAL;
+
+ /*
+ * Clear all alerts first to avoid accidentally triggering ALERT pin
+ * due to register write sequence. Then, only enable the alert
+ * if the value is non-zero.
+ */
+ ret = regmap_update_bits(regmap, INA226_MASK_ENABLE,
+ INA226_ALERT_CONFIG_MASK, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(regmap, INA226_ALERT_LIMIT,
+ ina226_alert_to_reg(data, reg, val));
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ return regmap_update_bits(regmap, INA226_MASK_ENABLE,
+ INA226_ALERT_CONFIG_MASK, mask);
+ return 0;
+}
+
+static int ina2xx_chip_read(struct device *dev, u32 attr, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ina2xx_data *data = dev_get_drvdata(dev);
+ u32 regval;
+ int ret;
+
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ ret = regmap_read(data->regmap, INA2XX_CONFIG, &regval);
+ if (ret)
+ return ret;
+
+ *val = ina226_reg_to_interval(regval);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val)
+{
unsigned int regval;
+ int ret;
- int err = ina2xx_read_reg(dev, attr->index, &regval);
+ ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, &regval);
+ if (ret)
+ return ret;
- if (err < 0)
- return err;
+ *val = (regval & mask) && (regval & INA226_ALERT_FUNCTION_FLAG);
- return sysfs_emit(buf, "%d\n", ina2xx_get_value(data, attr->index, regval));
+ return 0;
}
-static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval)
+static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val)
{
- int reg;
+ int voltage_reg = channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE;
+ u32 under_voltage_mask = channel ? INA226_BUS_UNDER_VOLTAGE_MASK
+ : INA226_SHUNT_UNDER_VOLTAGE_MASK;
+ u32 over_voltage_mask = channel ? INA226_BUS_OVER_VOLTAGE_MASK
+ : INA226_SHUNT_OVER_VOLTAGE_MASK;
+ struct ina2xx_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+ int ret;
- switch (bit) {
- case INA226_SHUNT_OVER_VOLTAGE_BIT:
- case INA226_SHUNT_UNDER_VOLTAGE_BIT:
- reg = INA2XX_SHUNT_VOLTAGE;
- break;
- case INA226_BUS_OVER_VOLTAGE_BIT:
- case INA226_BUS_UNDER_VOLTAGE_BIT:
- reg = INA2XX_BUS_VOLTAGE;
- break;
- case INA226_POWER_OVER_LIMIT_BIT:
- reg = INA2XX_POWER;
+ switch (attr) {
+ case hwmon_in_input:
+ ret = regmap_read(regmap, voltage_reg, &regval);
+ if (ret)
+ return ret;
+ *val = ina2xx_get_value(data, voltage_reg, regval);
break;
+ case hwmon_in_lcrit:
+ return ina226_alert_limit_read(data, under_voltage_mask,
+ voltage_reg, val);
+ case hwmon_in_crit:
+ return ina226_alert_limit_read(data, over_voltage_mask,
+ voltage_reg, val);
+ case hwmon_in_lcrit_alarm:
+ return ina226_alert_read(regmap, under_voltage_mask, val);
+ case hwmon_in_crit_alarm:
+ return ina226_alert_read(regmap, over_voltage_mask, val);
default:
- /* programmer goofed */
- WARN_ON_ONCE(1);
- return 0;
+ return -EOPNOTSUPP;
}
-
- return ina2xx_get_value(data, reg, regval);
+ return 0;
}
/*
- * Turns alert limit values into register values.
- * Opposite of the formula in ina2xx_get_value().
+ * Configuring the READ_EIN (bit 10) of the ACCUM_CONFIG register to 1
+ * can clear accumulator and sample_count after reading the EIN register.
+ * This way, the average power between the last read and the current
+ * read can be obtained. By combining with accurate time data from
+ * outside, the energy consumption during that period can be calculated.
*/
-static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val)
+static int sy24655_average_power_read(struct ina2xx_data *data, u8 reg, long *val)
{
- switch (bit) {
- case INA226_SHUNT_OVER_VOLTAGE_BIT:
- case INA226_SHUNT_UNDER_VOLTAGE_BIT:
- val *= data->config->shunt_div;
- return clamp_val(val, SHRT_MIN, SHRT_MAX);
- case INA226_BUS_OVER_VOLTAGE_BIT:
- case INA226_BUS_UNDER_VOLTAGE_BIT:
- val = (val * 1000) << data->config->bus_voltage_shift;
- val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
- return clamp_val(val, 0, SHRT_MAX);
- case INA226_POWER_OVER_LIMIT_BIT:
- val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
- return clamp_val(val, 0, USHRT_MAX);
- default:
- /* programmer goofed */
- WARN_ON_ONCE(1);
+ u8 template[6];
+ int ret;
+ long accumulator_24, sample_count;
+
+ /* 48-bit register read */
+ ret = i2c_smbus_read_i2c_block_data(data->client, reg, 6, template);
+ if (ret < 0)
+ return ret;
+ if (ret != 6)
+ return -EIO;
+ accumulator_24 = ((template[3] << 16) |
+ (template[4] << 8) |
+ template[5]);
+ sample_count = ((template[0] << 16) |
+ (template[1] << 8) |
+ template[2]);
+ if (sample_count <= 0) {
+ *val = 0;
return 0;
}
+
+ *val = DIV_ROUND_CLOSEST(accumulator_24, sample_count) * data->power_lsb_uW;
+
+ return 0;
}
-static ssize_t ina226_alert_show(struct device *dev,
- struct device_attribute *da, char *buf)
+static int ina2xx_power_read(struct device *dev, u32 attr, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ina2xx_data *data = dev_get_drvdata(dev);
- int regval;
- int val = 0;
- int ret;
-
- mutex_lock(&data->config_lock);
- ret = regmap_read(data->regmap, INA226_MASK_ENABLE, &regval);
- if (ret)
- goto abort;
- if (regval & BIT(attr->index)) {
- ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, &regval);
- if (ret)
- goto abort;
- val = ina226_reg_to_alert(data, attr->index, regval);
+ switch (attr) {
+ case hwmon_power_input:
+ return ina2xx_read_init(dev, INA2XX_POWER, val);
+ case hwmon_power_average:
+ return sy24655_average_power_read(data, SY24655_EIN, val);
+ case hwmon_power_crit:
+ return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK,
+ INA2XX_POWER, val);
+ case hwmon_power_crit_alarm:
+ return ina226_alert_read(data->regmap, INA226_POWER_OVER_LIMIT_MASK, val);
+ default:
+ return -EOPNOTSUPP;
}
-
- ret = sysfs_emit(buf, "%d\n", val);
-abort:
- mutex_unlock(&data->config_lock);
- return ret;
}
-static ssize_t ina226_alert_store(struct device *dev,
- struct device_attribute *da,
- const char *buf, size_t count)
+static int ina2xx_curr_read(struct device *dev, u32 attr, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ina2xx_data *data = dev_get_drvdata(dev);
- unsigned long val;
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
int ret;
- ret = kstrtoul(buf, 10, &val);
- if (ret < 0)
- return ret;
-
/*
- * Clear all alerts first to avoid accidentally triggering ALERT pin
- * due to register write sequence. Then, only enable the alert
- * if the value is non-zero.
+ * While the chips supported by this driver do not directly support
+ * current limits, they do support setting shunt voltage limits.
+ * The shunt voltage divided by the shunt resistor value is the current.
+ * On top of that, calibration values are set such that in the shunt
+ * voltage register and the current register report the same values.
+ * That means we can report and configure current limits based on shunt
+ * voltage limits.
*/
- mutex_lock(&data->config_lock);
- ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
- INA226_ALERT_CONFIG_MASK, 0);
- if (ret < 0)
- goto abort;
+ switch (attr) {
+ case hwmon_curr_input:
+ /*
+ * Since the shunt voltage and the current register report the
+ * same values when the chip is calibrated, we can calculate
+ * the current directly from the shunt voltage without relying
+ * on chip calibration.
+ */
+ ret = regmap_read(regmap, INA2XX_SHUNT_VOLTAGE, &regval);
+ if (ret)
+ return ret;
+ *val = ina2xx_get_value(data, INA2XX_CURRENT, regval);
+ return 0;
+ case hwmon_curr_lcrit:
+ return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK,
+ INA2XX_CURRENT, val);
+ case hwmon_curr_crit:
+ return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK,
+ INA2XX_CURRENT, val);
+ case hwmon_curr_lcrit_alarm:
+ return ina226_alert_read(regmap, INA226_SHUNT_UNDER_VOLTAGE_MASK, val);
+ case hwmon_curr_crit_alarm:
+ return ina226_alert_read(regmap, INA226_SHUNT_OVER_VOLTAGE_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- ret = regmap_write(data->regmap, INA226_ALERT_LIMIT,
- ina226_alert_to_reg(data, attr->index, val));
- if (ret < 0)
- goto abort;
+static int ina2xx_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_chip:
+ return ina2xx_chip_read(dev, attr, val);
+ case hwmon_in:
+ return ina2xx_in_read(dev, attr, channel, val);
+ case hwmon_power:
+ return ina2xx_power_read(dev, attr, val);
+ case hwmon_curr:
+ return ina2xx_curr_read(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- if (val != 0) {
- ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
- INA226_ALERT_CONFIG_MASK,
- BIT(attr->index));
- if (ret < 0)
- goto abort;
+static int ina2xx_chip_write(struct device *dev, u32 attr, long val)
+{
+ struct ina2xx_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return regmap_update_bits(data->regmap, INA2XX_CONFIG,
+ INA226_AVG_RD_MASK,
+ ina226_interval_to_reg(val));
+ default:
+ return -EOPNOTSUPP;
}
+}
- ret = count;
-abort:
- mutex_unlock(&data->config_lock);
- return ret;
+static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val)
+{
+ struct ina2xx_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_in_lcrit:
+ return ina226_alert_limit_write(data,
+ channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK,
+ channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE,
+ val);
+ case hwmon_in_crit:
+ return ina226_alert_limit_write(data,
+ channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK,
+ channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t ina226_alarm_show(struct device *dev,
- struct device_attribute *da, char *buf)
+static int ina2xx_power_write(struct device *dev, u32 attr, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ina2xx_data *data = dev_get_drvdata(dev);
- int regval;
- int alarm = 0;
- int ret;
- ret = regmap_read(data->regmap, INA226_MASK_ENABLE, &regval);
- if (ret)
- return ret;
+ switch (attr) {
+ case hwmon_power_crit:
+ return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK,
+ INA2XX_POWER, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int ina2xx_curr_write(struct device *dev, u32 attr, long val)
+{
+ struct ina2xx_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_curr_lcrit:
+ return ina226_alert_limit_write(data, INA226_SHUNT_UNDER_VOLTAGE_MASK,
+ INA2XX_CURRENT, val);
+ case hwmon_curr_crit:
+ return ina226_alert_limit_write(data, INA226_SHUNT_OVER_VOLTAGE_MASK,
+ INA2XX_CURRENT, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
- alarm = (regval & BIT(attr->index)) &&
- (regval & INA226_ALERT_FUNCTION_FLAG);
- return sysfs_emit(buf, "%d\n", alarm);
+static int ina2xx_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_chip:
+ return ina2xx_chip_write(dev, attr, val);
+ case hwmon_in:
+ return ina2xx_in_write(dev, attr, channel, val);
+ case hwmon_power:
+ return ina2xx_power_write(dev, attr, val);
+ case hwmon_curr:
+ return ina2xx_curr_write(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct ina2xx_data *data = _data;
+ bool has_alerts = data->config->has_alerts;
+ bool has_power_average = data->config->has_power_average;
+ enum ina2xx_ids chip = data->chip;
+
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ case hwmon_in_lcrit:
+ case hwmon_in_crit:
+ if (has_alerts)
+ return 0644;
+ break;
+ case hwmon_in_lcrit_alarm:
+ case hwmon_in_crit_alarm:
+ if (has_alerts)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ return 0444;
+ case hwmon_curr_lcrit:
+ case hwmon_curr_crit:
+ if (has_alerts)
+ return 0644;
+ break;
+ case hwmon_curr_lcrit_alarm:
+ case hwmon_curr_crit_alarm:
+ if (has_alerts)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_input:
+ return 0444;
+ case hwmon_power_crit:
+ if (has_alerts)
+ return 0644;
+ break;
+ case hwmon_power_crit_alarm:
+ if (has_alerts)
+ return 0444;
+ break;
+ case hwmon_power_average:
+ if (has_power_average)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ if (chip == ina226 || chip == ina260)
+ return 0644;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
}
+static const struct hwmon_channel_info * const ina2xx_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_CRIT_ALARM |
+ HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM,
+ HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_CRIT_ALARM |
+ HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM
+ ),
+ HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM |
+ HWMON_C_LCRIT | HWMON_C_LCRIT_ALARM),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_CRIT | HWMON_P_CRIT_ALARM |
+ HWMON_P_AVERAGE),
+ NULL
+};
+
+static const struct hwmon_ops ina2xx_hwmon_ops = {
+ .is_visible = ina2xx_is_visible,
+ .read = ina2xx_read,
+ .write = ina2xx_write,
+};
+
+static const struct hwmon_chip_info ina2xx_chip_info = {
+ .ops = &ina2xx_hwmon_ops,
+ .info = ina2xx_info,
+};
+
+/* shunt resistance */
+
/*
* In order to keep calibration register value fixed, the product
* of current_lsb and shunt_resistor should also be fixed and equal
* to shunt_voltage_lsb = 1 / shunt_div multiplied by 10^9 in order
* to keep the scale.
*/
-static int ina2xx_set_shunt(struct ina2xx_data *data, long val)
+static int ina2xx_set_shunt(struct ina2xx_data *data, unsigned long val)
{
unsigned int dividend = DIV_ROUND_CLOSEST(1000000000,
data->config->shunt_div);
- if (val <= 0 || val > dividend)
+ if (!val || val > dividend)
return -EINVAL;
- mutex_lock(&data->config_lock);
data->rshunt = val;
data->current_lsb_uA = DIV_ROUND_CLOSEST(dividend, val);
data->power_lsb_uW = data->config->power_lsb_factor *
data->current_lsb_uA;
- mutex_unlock(&data->config_lock);
return 0;
}
-static ssize_t ina2xx_shunt_show(struct device *dev,
- struct device_attribute *da, char *buf)
+static ssize_t shunt_resistor_show(struct device *dev,
+ struct device_attribute *da, char *buf)
{
struct ina2xx_data *data = dev_get_drvdata(dev);
return sysfs_emit(buf, "%li\n", data->rshunt);
}
-static ssize_t ina2xx_shunt_store(struct device *dev,
- struct device_attribute *da,
- const char *buf, size_t count)
+static ssize_t shunt_resistor_store(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
{
+ struct ina2xx_data *data = dev_get_drvdata(dev);
unsigned long val;
int status;
- struct ina2xx_data *data = dev_get_drvdata(dev);
status = kstrtoul(buf, 10, &val);
if (status < 0)
return status;
+ hwmon_lock(dev);
status = ina2xx_set_shunt(data, val);
+ hwmon_unlock(dev);
if (status < 0)
return status;
return count;
}
-static ssize_t ina226_interval_store(struct device *dev,
- struct device_attribute *da,
- const char *buf, size_t count)
-{
- struct ina2xx_data *data = dev_get_drvdata(dev);
- unsigned long val;
- int status;
-
- status = kstrtoul(buf, 10, &val);
- if (status < 0)
- return status;
-
- if (val > INT_MAX || val == 0)
- return -EINVAL;
-
- status = regmap_update_bits(data->regmap, INA2XX_CONFIG,
- INA226_AVG_RD_MASK,
- ina226_interval_to_reg(val));
- if (status < 0)
- return status;
+static DEVICE_ATTR_RW(shunt_resistor);
- return count;
-}
+/* pointers to created device attributes */
+static struct attribute *ina2xx_attrs[] = {
+ &dev_attr_shunt_resistor.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ina2xx);
-static ssize_t ina226_interval_show(struct device *dev,
- struct device_attribute *da, char *buf)
+/*
+ * Initialize chip
+ */
+static int ina2xx_init(struct device *dev, struct ina2xx_data *data)
{
- struct ina2xx_data *data = dev_get_drvdata(dev);
- int status;
- unsigned int regval;
-
- status = regmap_read(data->regmap, INA2XX_CONFIG, &regval);
- if (status)
- return status;
-
- return sysfs_emit(buf, "%d\n", ina226_reg_to_interval(regval));
-}
-
-/* shunt voltage */
-static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE);
-/* shunt voltage over/under voltage alert setting and alarm */
-static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert,
- INA226_SHUNT_OVER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert,
- INA226_SHUNT_UNDER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm,
- INA226_SHUNT_OVER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm,
- INA226_SHUNT_UNDER_VOLTAGE_BIT);
-
-/* bus voltage */
-static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE);
-/* bus voltage over/under voltage alert setting and alarm */
-static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert,
- INA226_BUS_OVER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert,
- INA226_BUS_UNDER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm,
- INA226_BUS_OVER_VOLTAGE_BIT);
-static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm,
- INA226_BUS_UNDER_VOLTAGE_BIT);
-
-/* calculated current */
-static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);
-
-/* calculated power */
-static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER);
-/* over-limit power alert setting and alarm */
-static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert,
- INA226_POWER_OVER_LIMIT_BIT);
-static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm,
- INA226_POWER_OVER_LIMIT_BIT);
+ struct regmap *regmap = data->regmap;
+ u32 shunt;
+ int ret;
-/* shunt resistance */
-static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION);
+ if (data->config->has_ishunt)
+ shunt = INA260_RSHUNT;
+ else if (device_property_read_u32(dev, "shunt-resistor", &shunt) < 0)
+ shunt = INA2XX_RSHUNT_DEFAULT;
-/* update interval (ina226 only) */
-static SENSOR_DEVICE_ATTR_RW(update_interval, ina226_interval, 0);
+ ret = ina2xx_set_shunt(data, shunt);
+ if (ret < 0)
+ return ret;
-/* pointers to created device attributes */
-static struct attribute *ina2xx_attrs[] = {
- &sensor_dev_attr_in0_input.dev_attr.attr,
- &sensor_dev_attr_in1_input.dev_attr.attr,
- &sensor_dev_attr_curr1_input.dev_attr.attr,
- &sensor_dev_attr_power1_input.dev_attr.attr,
- &sensor_dev_attr_shunt_resistor.dev_attr.attr,
- NULL,
-};
+ ret = regmap_write(regmap, INA2XX_CONFIG, data->config->config_default);
+ if (ret < 0)
+ return ret;
-static const struct attribute_group ina2xx_group = {
- .attrs = ina2xx_attrs,
-};
+ if (data->config->has_alerts) {
+ bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high");
-static struct attribute *ina226_attrs[] = {
- &sensor_dev_attr_in0_crit.dev_attr.attr,
- &sensor_dev_attr_in0_lcrit.dev_attr.attr,
- &sensor_dev_attr_in0_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr,
- &sensor_dev_attr_in1_crit.dev_attr.attr,
- &sensor_dev_attr_in1_lcrit.dev_attr.attr,
- &sensor_dev_attr_in1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr,
- &sensor_dev_attr_power1_crit.dev_attr.attr,
- &sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_update_interval.dev_attr.attr,
- NULL,
-};
+ regmap_update_bits(regmap, INA226_MASK_ENABLE,
+ INA226_ALERT_LATCH_ENABLE | INA226_ALERT_POLARITY,
+ INA226_ALERT_LATCH_ENABLE |
+ FIELD_PREP(INA226_ALERT_POLARITY, active_high));
+ }
+ if (data->config->has_power_average) {
+ if (data->chip == sy24655) {
+ /*
+ * Initialize the power accumulation method to continuous
+ * mode and clear the EIN register after each read of the
+ * EIN register
+ */
+ ret = regmap_write(regmap, SY24655_ACCUM_CONFIG,
+ SY24655_ACCUM_CONFIG_DEFAULT);
+ if (ret < 0)
+ return ret;
+ }
+ }
-static const struct attribute_group ina226_group = {
- .attrs = ina226_attrs,
-};
+ if (data->config->has_ishunt)
+ return 0;
-static const struct i2c_device_id ina2xx_id[];
+ /*
+ * Calibration register is set to the best value, which eliminates
+ * truncation errors on calculating current register in hardware.
+ * According to datasheet (eq. 3) the best values are 2048 for
+ * ina226 and 4096 for ina219. They are hardcoded as calibration_value.
+ */
+ return regmap_write(regmap, INA2XX_CALIBRATION,
+ data->config->calibration_value);
+}
static int ina2xx_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ina2xx_data *data;
struct device *hwmon_dev;
- u32 val;
- int ret, group = 0;
enum ina2xx_ids chip;
+ int ret;
- if (client->dev.of_node)
- chip = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- chip = i2c_match_id(ina2xx_id, client)->driver_data;
+ chip = (uintptr_t)i2c_get_match_data(client);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/* set the device type */
+ data->client = client;
data->config = &ina2xx_config[chip];
- mutex_init(&data->config_lock);
-
- if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) {
- struct ina2xx_platform_data *pdata = dev_get_platdata(dev);
-
- if (pdata)
- val = pdata->shunt_uohms;
- else
- val = INA2XX_RSHUNT_DEFAULT;
- }
-
- ina2xx_set_shunt(data, val);
-
- ina2xx_regmap_config.max_register = data->config->registers;
+ data->chip = chip;
data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config);
if (IS_ERR(data->regmap)) {
@@ -655,22 +951,22 @@ static int ina2xx_probe(struct i2c_client *client)
return PTR_ERR(data->regmap);
}
- ret = devm_regulator_get_enable(dev, "vs");
- if (ret)
+ /*
+ * Regulator core returns -ENODEV if the 'vs' is not available.
+ * Hence the check for -ENODEV return code is necessary.
+ */
+ ret = devm_regulator_get_enable_optional(dev, "vs");
+ if (ret < 0 && ret != -ENODEV)
return dev_err_probe(dev, ret, "failed to enable vs regulator\n");
- ret = ina2xx_init(data);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return -ENODEV;
- }
-
- data->groups[group++] = &ina2xx_group;
- if (chip == ina226)
- data->groups[group++] = &ina226_group;
+ ret = ina2xx_init(dev, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to configure device\n");
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data, data->groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &ina2xx_chip_info,
+ data->config->has_ishunt ?
+ NULL : ina2xx_groups);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
@@ -686,12 +982,18 @@ static const struct i2c_device_id ina2xx_id[] = {
{ "ina226", ina226 },
{ "ina230", ina226 },
{ "ina231", ina226 },
+ { "ina260", ina260 },
+ { "sy24655", sy24655 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ina2xx_id);
static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
{
+ .compatible = "silergy,sy24655",
+ .data = (void *)sy24655
+ },
+ {
.compatible = "ti,ina219",
.data = (void *)ina219
},
@@ -711,7 +1013,11 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
.compatible = "ti,ina231",
.data = (void *)ina226
},
- { },
+ {
+ .compatible = "ti,ina260",
+ .data = (void *)ina260
+ },
+ { }
};
MODULE_DEVICE_TABLE(of, ina2xx_of_match);
diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c
index 5ab944056ec0..5ecc68dcf169 100644
--- a/drivers/hwmon/ina3221.c
+++ b/drivers/hwmon/ina3221.c
@@ -6,11 +6,11 @@
* Andrew F. Davis <afd@ti.com>
*/
+#include <linux/debugfs.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -99,11 +99,13 @@ enum ina3221_channels {
* @label: label of channel input source
* @shunt_resistor: shunt resistor value of channel input source
* @disconnected: connection status of channel input source
+ * @summation_disable: channel summation status of input source
*/
struct ina3221_input {
const char *label;
int shunt_resistor;
bool disconnected;
+ bool summation_disable;
};
/**
@@ -112,9 +114,9 @@ struct ina3221_input {
* @regmap: Register map of the device
* @fields: Register fields of the device
* @inputs: Array of channel input source specific structures
- * @lock: mutex lock to serialize sysfs attribute accesses
* @reg_config: Register value of INA3221_CONFIG
* @summation_shunt_resistor: equivalent shunt resistor value for summation
+ * @summation_channel_control: Value written to SCC field in INA3221_MASK_ENABLE
* @single_shot: running in single-shot operating mode
*/
struct ina3221_data {
@@ -122,9 +124,9 @@ struct ina3221_data {
struct regmap *regmap;
struct regmap_field *fields[F_MAX_FIELDS];
struct ina3221_input inputs[INA3221_NUM_CHANNELS];
- struct mutex lock;
u32 reg_config;
int summation_shunt_resistor;
+ u32 summation_channel_control;
bool single_shot;
};
@@ -154,7 +156,8 @@ static inline int ina3221_summation_shunt_resistor(struct ina3221_data *ina)
int i, shunt_resistor = 0;
for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
- if (input[i].disconnected || !input[i].shunt_resistor)
+ if (input[i].disconnected || !input[i].shunt_resistor ||
+ input[i].summation_disable)
continue;
if (!shunt_resistor) {
/* Found the reference shunt resistor value */
@@ -524,11 +527,8 @@ fail:
static int ina3221_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
- struct ina3221_data *ina = dev_get_drvdata(dev);
int ret;
- mutex_lock(&ina->lock);
-
switch (type) {
case hwmon_chip:
ret = ina3221_read_chip(dev, attr, val);
@@ -544,20 +544,14 @@ static int ina3221_read(struct device *dev, enum hwmon_sensor_types type,
ret = -EOPNOTSUPP;
break;
}
-
- mutex_unlock(&ina->lock);
-
return ret;
}
static int ina3221_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
- struct ina3221_data *ina = dev_get_drvdata(dev);
int ret;
- mutex_lock(&ina->lock);
-
switch (type) {
case hwmon_chip:
ret = ina3221_write_chip(dev, attr, val);
@@ -573,9 +567,6 @@ static int ina3221_write(struct device *dev, enum hwmon_sensor_types type,
ret = -EOPNOTSUPP;
break;
}
-
- mutex_unlock(&ina->lock);
-
return ret;
}
@@ -754,7 +745,7 @@ static const struct regmap_config ina3221_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_table = &ina3221_volatile_table,
};
@@ -786,6 +777,9 @@ static int ina3221_probe_child_from_dt(struct device *dev,
/* Save the connected input label if available */
of_property_read_string(child, "label", &input->label);
+ /* summation channel control */
+ input->summation_disable = of_property_read_bool(child, "ti,summation-disable");
+
/* Overwrite default shunt resistor value optionally */
if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val)) {
if (val < 1 || val > INT_MAX) {
@@ -802,7 +796,6 @@ static int ina3221_probe_child_from_dt(struct device *dev,
static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina)
{
const struct device_node *np = dev->of_node;
- struct device_node *child;
int ret;
/* Compatible with non-DT platforms */
@@ -811,12 +804,10 @@ static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina)
ina->single_shot = of_property_read_bool(np, "ti,single-shot");
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
ret = ina3221_probe_child_from_dt(dev, child, ina);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
}
return 0;
@@ -827,6 +818,7 @@ static int ina3221_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct ina3221_data *ina;
struct device *hwmon_dev;
+ char name[32];
int i, ret;
ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL);
@@ -873,9 +865,12 @@ static int ina3221_probe(struct i2c_client *client)
/* Initialize summation_shunt_resistor for summation channel control */
ina->summation_shunt_resistor = ina3221_summation_shunt_resistor(ina);
+ for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
+ if (!ina->inputs[i].summation_disable)
+ ina->summation_channel_control |= BIT(14 - i);
+ }
ina->pm_dev = dev;
- mutex_init(&ina->lock);
dev_set_drvdata(dev, ina);
/* Enable PM runtime -- status is suspended by default */
@@ -900,6 +895,12 @@ static int ina3221_probe(struct i2c_client *client)
goto fail;
}
+ for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
+ scnprintf(name, sizeof(name), "in%d_summation_disable", i);
+ debugfs_create_bool(name, 0400, client->debugfs,
+ &ina->inputs[i].summation_disable);
+ }
+
return 0;
fail:
@@ -908,7 +909,6 @@ fail:
/* pm_runtime_put_noidle() will decrease the PM refcount until 0 */
for (i = 0; i < INA3221_NUM_CHANNELS; i++)
pm_runtime_put_noidle(ina->pm_dev);
- mutex_destroy(&ina->lock);
return ret;
}
@@ -924,8 +924,6 @@ static void ina3221_remove(struct i2c_client *client)
/* pm_runtime_put_noidle() will decrease the PM refcount until 0 */
for (i = 0; i < INA3221_NUM_CHANNELS; i++)
pm_runtime_put_noidle(ina->pm_dev);
-
- mutex_destroy(&ina->lock);
}
static int ina3221_suspend(struct device *dev)
@@ -978,13 +976,13 @@ static int ina3221_resume(struct device *dev)
/* Initialize summation channel control */
if (ina->summation_shunt_resistor) {
/*
- * Take all three channels into summation by default
+ * Sum only channels that are not disabled for summation.
* Shunt measurements of disconnected channels should
* be 0, so it does not matter for summation.
*/
ret = regmap_update_bits(ina->regmap, INA3221_MASK_ENABLE,
INA3221_MASK_ENABLE_SCC_MASK,
- INA3221_MASK_ENABLE_SCC_MASK);
+ ina->summation_channel_control);
if (ret) {
dev_err(dev, "Unable to control summation channel\n");
return ret;
@@ -1004,7 +1002,7 @@ static const struct of_device_id ina3221_of_match_table[] = {
MODULE_DEVICE_TABLE(of, ina3221_of_match_table);
static const struct i2c_device_id ina3221_ids[] = {
- { "ina3221", 0 },
+ { "ina3221" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, ina3221_ids);
diff --git a/drivers/hwmon/intel-m10-bmc-hwmon.c b/drivers/hwmon/intel-m10-bmc-hwmon.c
index 6500ca548f9c..aa01a4bedc21 100644
--- a/drivers/hwmon/intel-m10-bmc-hwmon.c
+++ b/drivers/hwmon/intel-m10-bmc-hwmon.c
@@ -358,7 +358,7 @@ static const struct m10bmc_sdata n6000bmc_temp_tbl[] = {
{ 0x4f0, 0x4f4, 0x4f8, 0x52c, 0x0, 500, "Board Top Near FPGA Temperature" },
{ 0x4fc, 0x500, 0x504, 0x52c, 0x0, 500, "Board Bottom Near CVL Temperature" },
{ 0x508, 0x50c, 0x510, 0x52c, 0x0, 500, "Board Top East Near VRs Temperature" },
- { 0x514, 0x518, 0x51c, 0x52c, 0x0, 500, "Columbiaville Die Temperature" },
+ { 0x514, 0x518, 0x51c, 0x52c, 0x0, 500, "CVL Die Temperature" },
{ 0x520, 0x524, 0x528, 0x52c, 0x0, 500, "Board Rear Side Temperature" },
{ 0x530, 0x534, 0x538, 0x52c, 0x0, 500, "Board Front Side Temperature" },
{ 0x53c, 0x540, 0x544, 0x0, 0x0, 500, "QSFP1 Case Temperature" },
@@ -429,7 +429,7 @@ static const struct m10bmc_sdata n6000bmc_curr_tbl[] = {
};
static const struct m10bmc_sdata n6000bmc_power_tbl[] = {
- { 0x724, 0x0, 0x0, 0x0, 0x0, 1, "Board Power" },
+ { 0x724, 0x0, 0x0, 0x0, 0x0, 1000, "Board Power" },
};
static const struct hwmon_channel_info * const n6000bmc_hinfo[] = {
@@ -565,13 +565,6 @@ static const struct m10bmc_hwmon_board_data n6000bmc_hwmon_bdata = {
.hinfo = n6000bmc_hinfo,
};
-static umode_t
-m10bmc_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
- u32 attr, int channel)
-{
- return 0444;
-}
-
static const struct m10bmc_sdata *
find_sensor_data(struct m10bmc_hwmon *hw, enum hwmon_sensor_types type,
int channel)
@@ -729,7 +722,7 @@ static int m10bmc_hwmon_read_string(struct device *dev,
}
static const struct hwmon_ops m10bmc_hwmon_ops = {
- .is_visible = m10bmc_hwmon_is_visible,
+ .visible = 0444,
.read = m10bmc_hwmon_read,
.read_string = m10bmc_hwmon_read_string,
};
@@ -794,4 +787,4 @@ MODULE_DEVICE_TABLE(platform, intel_m10bmc_hwmon_ids);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel MAX 10 BMC hardware monitor");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(INTEL_M10_BMC_CORE);
+MODULE_IMPORT_NS("INTEL_M10_BMC_CORE");
diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c
new file mode 100644
index 000000000000..c2e559dde63f
--- /dev/null
+++ b/drivers/hwmon/isl28022.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * isl28022.c - driver for Renesas ISL28022 power monitor chip monitoring
+ *
+ * Copyright (c) 2023 Carsten Spieß <mail@carsten-spiess.de>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+/* ISL28022 registers */
+#define ISL28022_REG_CONFIG 0x00
+#define ISL28022_REG_SHUNT 0x01
+#define ISL28022_REG_BUS 0x02
+#define ISL28022_REG_POWER 0x03
+#define ISL28022_REG_CURRENT 0x04
+#define ISL28022_REG_CALIB 0x05
+#define ISL28022_REG_SHUNT_THR 0x06
+#define ISL28022_REG_BUS_THR 0x07
+#define ISL28022_REG_INT 0x08
+#define ISL28022_REG_AUX 0x09
+#define ISL28022_REG_MAX ISL28022_REG_AUX
+
+/* ISL28022 config flags */
+/* mode flags */
+#define ISL28022_MODE_SHIFT 0
+#define ISL28022_MODE_MASK 0x0007
+
+#define ISL28022_MODE_PWR_DOWN 0x0
+#define ISL28022_MODE_TRG_S 0x1
+#define ISL28022_MODE_TRG_B 0x2
+#define ISL28022_MODE_TRG_SB 0x3
+#define ISL28022_MODE_ADC_OFF 0x4
+#define ISL28022_MODE_CONT_S 0x5
+#define ISL28022_MODE_CONT_B 0x6
+#define ISL28022_MODE_CONT_SB 0x7
+
+/* shunt ADC settings */
+#define ISL28022_SADC_SHIFT 3
+#define ISL28022_SADC_MASK 0x0078
+
+#define ISL28022_BADC_SHIFT 7
+#define ISL28022_BADC_MASK 0x0780
+
+#define ISL28022_ADC_12 0x0 /* 12 bit ADC */
+#define ISL28022_ADC_13 0x1 /* 13 bit ADC */
+#define ISL28022_ADC_14 0x2 /* 14 bit ADC */
+#define ISL28022_ADC_15 0x3 /* 15 bit ADC */
+#define ISL28022_ADC_15_1 0x8 /* 15 bit ADC, 1 sample */
+#define ISL28022_ADC_15_2 0x9 /* 15 bit ADC, 2 samples */
+#define ISL28022_ADC_15_4 0xA /* 15 bit ADC, 4 samples */
+#define ISL28022_ADC_15_8 0xB /* 15 bit ADC, 8 samples */
+#define ISL28022_ADC_15_16 0xC /* 15 bit ADC, 16 samples */
+#define ISL28022_ADC_15_32 0xD /* 15 bit ADC, 32 samples */
+#define ISL28022_ADC_15_64 0xE /* 15 bit ADC, 64 samples */
+#define ISL28022_ADC_15_128 0xF /* 15 bit ADC, 128 samples */
+
+/* shunt voltage range */
+#define ISL28022_PG_SHIFT 11
+#define ISL28022_PG_MASK 0x1800
+
+#define ISL28022_PG_40 0x0 /* +/-40 mV */
+#define ISL28022_PG_80 0x1 /* +/-80 mV */
+#define ISL28022_PG_160 0x2 /* +/-160 mV */
+#define ISL28022_PG_320 0x3 /* +/-3200 mV */
+
+/* bus voltage range */
+#define ISL28022_BRNG_SHIFT 13
+#define ISL28022_BRNG_MASK 0x6000
+
+#define ISL28022_BRNG_16 0x0 /* 16 V */
+#define ISL28022_BRNG_32 0x1 /* 32 V */
+#define ISL28022_BRNG_60 0x3 /* 60 V */
+
+/* reset */
+#define ISL28022_RESET 0x8000
+
+struct isl28022_data {
+ struct regmap *regmap;
+ u32 shunt;
+ u32 gain;
+ u32 average;
+};
+
+static int isl28022_read_in(struct device *dev, u32 attr, int channel, long *val)
+{
+ struct isl28022_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+ u16 sign_bit;
+
+ switch (channel) {
+ case 0:
+ switch (attr) {
+ case hwmon_in_input:
+ err = regmap_read(data->regmap,
+ ISL28022_REG_BUS, &regval);
+ if (err < 0)
+ return err;
+ /* driver supports only 60V mode (BRNG 11) */
+ *val = (long)(((u16)regval) & 0xFFFC);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case 1:
+ switch (attr) {
+ case hwmon_in_input:
+ err = regmap_read(data->regmap,
+ ISL28022_REG_SHUNT, &regval);
+ if (err < 0)
+ return err;
+ switch (data->gain) {
+ case 8:
+ sign_bit = (regval >> 15) & 0x01;
+ *val = (long)((((u16)regval) & 0x7FFF) -
+ (sign_bit * 32768)) / 100;
+ break;
+ case 4:
+ sign_bit = (regval >> 14) & 0x01;
+ *val = (long)((((u16)regval) & 0x3FFF) -
+ (sign_bit * 16384)) / 100;
+ break;
+ case 2:
+ sign_bit = (regval >> 13) & 0x01;
+ *val = (long)((((u16)regval) & 0x1FFF) -
+ (sign_bit * 8192)) / 100;
+ break;
+ case 1:
+ sign_bit = (regval >> 12) & 0x01;
+ *val = (long)((((u16)regval) & 0x0FFF) -
+ (sign_bit * 4096)) / 100;
+ break;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int isl28022_read_current(struct device *dev, u32 attr, long *val)
+{
+ struct isl28022_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+ u16 sign_bit;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ err = regmap_read(data->regmap,
+ ISL28022_REG_CURRENT, &regval);
+ if (err < 0)
+ return err;
+ sign_bit = (regval >> 15) & 0x01;
+ *val = (((long)(((u16)regval) & 0x7FFF) - (sign_bit * 32768)) *
+ 1250L * (long)data->gain) / (long)data->shunt;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int isl28022_read_power(struct device *dev, u32 attr, long *val)
+{
+ struct isl28022_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ switch (attr) {
+ case hwmon_power_input:
+ err = regmap_read(data->regmap,
+ ISL28022_REG_POWER, &regval);
+ if (err < 0)
+ return err;
+ *val = ((51200000L * ((long)data->gain)) /
+ (long)data->shunt) * (long)regval;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int isl28022_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ return isl28022_read_in(dev, attr, channel, val);
+ case hwmon_curr:
+ return isl28022_read_current(dev, attr, val);
+ case hwmon_power:
+ return isl28022_read_power(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static umode_t isl28022_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct hwmon_channel_info *isl28022_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT, /* channel 0: bus voltage (mV) */
+ HWMON_I_INPUT), /* channel 1: shunt voltage (mV) */
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT), /* channel 1: current (mA) */
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT), /* channel 1: power (µW) */
+ NULL
+};
+
+static const struct hwmon_ops isl28022_hwmon_ops = {
+ .is_visible = isl28022_is_visible,
+ .read = isl28022_read,
+};
+
+static const struct hwmon_chip_info isl28022_chip_info = {
+ .ops = &isl28022_hwmon_ops,
+ .info = isl28022_info,
+};
+
+static bool isl28022_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ISL28022_REG_CONFIG:
+ case ISL28022_REG_CALIB:
+ case ISL28022_REG_SHUNT_THR:
+ case ISL28022_REG_BUS_THR:
+ case ISL28022_REG_INT:
+ case ISL28022_REG_AUX:
+ return true;
+ }
+
+ return false;
+}
+
+static bool isl28022_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ISL28022_REG_CONFIG:
+ case ISL28022_REG_SHUNT:
+ case ISL28022_REG_BUS:
+ case ISL28022_REG_POWER:
+ case ISL28022_REG_CURRENT:
+ case ISL28022_REG_INT:
+ case ISL28022_REG_AUX:
+ return true;
+ }
+ return true;
+}
+
+static const struct regmap_config isl28022_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = ISL28022_REG_MAX,
+ .writeable_reg = isl28022_is_writeable_reg,
+ .volatile_reg = isl28022_is_volatile_reg,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_MAPLE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int shunt_voltage_show(struct seq_file *seqf, void *unused)
+{
+ struct isl28022_data *data = seqf->private;
+ unsigned int regval;
+ int err;
+
+ err = regmap_read(data->regmap,
+ ISL28022_REG_SHUNT, &regval);
+ if (err)
+ return err;
+
+ /* print shunt voltage in micro volt */
+ seq_printf(seqf, "%d\n", regval * 10);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(shunt_voltage);
+
+/*
+ * read property values and make consistency checks.
+ *
+ * following values for shunt range and resistor are allowed:
+ * 40 mV -> gain 1, shunt min. 800 micro ohms
+ * 80 mV -> gain 2, shunt min. 1600 micro ohms
+ * 160 mV -> gain 4, shunt min. 3200 micro ohms
+ * 320 mV -> gain 8, shunt min. 6400 micro ohms
+ */
+static int isl28022_read_properties(struct device *dev, struct isl28022_data *data)
+{
+ u32 val;
+ int err;
+
+ err = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val);
+ if (err == -EINVAL)
+ val = 10000;
+ else if (err < 0)
+ return err;
+ data->shunt = val;
+
+ err = device_property_read_u32(dev, "renesas,shunt-range-microvolt", &val);
+ if (err == -EINVAL)
+ val = 320000;
+ else if (err < 0)
+ return err;
+
+ switch (val) {
+ case 40000:
+ data->gain = 1;
+ if (data->shunt < 800)
+ goto shunt_invalid;
+ break;
+ case 80000:
+ data->gain = 2;
+ if (data->shunt < 1600)
+ goto shunt_invalid;
+ break;
+ case 160000:
+ data->gain = 4;
+ if (data->shunt < 3200)
+ goto shunt_invalid;
+ break;
+ case 320000:
+ data->gain = 8;
+ if (data->shunt < 6400)
+ goto shunt_invalid;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "renesas,shunt-range-microvolt invalid value %d\n",
+ val);
+ }
+
+ err = device_property_read_u32(dev, "renesas,average-samples", &val);
+ if (err == -EINVAL)
+ val = 1;
+ else if (err < 0)
+ return err;
+ if (val > 128 || hweight32(val) != 1)
+ return dev_err_probe(dev, -EINVAL,
+ "renesas,average-samples invalid value %d\n",
+ val);
+
+ data->average = val;
+
+ return 0;
+
+shunt_invalid:
+ return dev_err_probe(dev, -EINVAL,
+ "renesas,shunt-resistor-microvolt invalid value %d\n",
+ data->shunt);
+}
+
+/*
+ * write configuration and calibration registers
+ *
+ * The driver supports only shunt and bus continuous ADC mode at 15bit resolution
+ * with averaging from 1 to 128 samples (pow of 2) on both channels.
+ * Shunt voltage gain 1,2,4 or 8 is allowed.
+ * The bus voltage range is 60V fixed.
+ */
+static int isl28022_config(struct isl28022_data *data)
+{
+ int err;
+ u16 config;
+ u16 calib;
+
+ config = (ISL28022_MODE_CONT_SB << ISL28022_MODE_SHIFT) |
+ (ISL28022_BRNG_60 << ISL28022_BRNG_SHIFT) |
+ (__ffs(data->gain) << ISL28022_PG_SHIFT) |
+ ((ISL28022_ADC_15_1 + __ffs(data->average)) << ISL28022_SADC_SHIFT) |
+ ((ISL28022_ADC_15_1 + __ffs(data->average)) << ISL28022_BADC_SHIFT);
+
+ calib = data->shunt ? 0x8000 / data->gain : 0;
+
+ err = regmap_write(data->regmap, ISL28022_REG_CONFIG, config);
+ if (err < 0)
+ return err;
+
+ return regmap_write(data->regmap, ISL28022_REG_CALIB, calib);
+}
+
+static int isl28022_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct isl28022_data *data;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA))
+ return -ENODEV;
+
+ data = devm_kzalloc(dev, sizeof(struct isl28022_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = isl28022_read_properties(dev, data);
+ if (err)
+ return err;
+
+ data->regmap = devm_regmap_init_i2c(client, &isl28022_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ err = isl28022_config(data);
+ if (err)
+ return err;
+
+ debugfs_create_file("shunt_voltage", 0444, client->debugfs, data, &shunt_voltage_fops);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &isl28022_chip_info, NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id isl28022_ids[] = {
+ { "isl28022" },
+ { /* LIST END */ }
+};
+MODULE_DEVICE_TABLE(i2c, isl28022_ids);
+
+static const struct of_device_id __maybe_unused isl28022_of_match[] = {
+ { .compatible = "renesas,isl28022"},
+ { /* LIST END */ }
+};
+MODULE_DEVICE_TABLE(of, isl28022_of_match);
+
+static struct i2c_driver isl28022_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "isl28022",
+ },
+ .probe = isl28022_probe,
+ .id_table = isl28022_ids,
+};
+module_i2c_driver(isl28022_driver);
+
+MODULE_AUTHOR("Carsten Spieß <mail@carsten-spiess.de>");
+MODULE_DESCRIPTION("ISL28022 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index fbe86cec6055..e233aafa8856 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -117,7 +117,7 @@ static inline void superio_select(int ioreg, int ldn)
outb(ldn, ioreg + 1);
}
-static inline int superio_enter(int ioreg)
+static inline int superio_enter(int ioreg, bool noentry)
{
/*
* Try to reserve ioreg and ioreg + 1 for exclusive access.
@@ -125,7 +125,8 @@ static inline int superio_enter(int ioreg)
if (!request_muxed_region(ioreg, 2, DRVNAME))
return -EBUSY;
- __superio_enter(ioreg);
+ if (!noentry)
+ __superio_enter(ioreg);
return 0;
}
@@ -320,7 +321,7 @@ struct it87_devices {
* second SIO address. Never exit configuration mode on these
* chips to avoid the problem.
*/
-#define FEAT_CONF_NOEXIT BIT(19) /* Chip should not exit conf mode */
+#define FEAT_NOCONF BIT(19) /* Chip conf mode enabled on startup */
#define FEAT_FOUR_FANS BIT(20) /* Supports four fans */
#define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */
#define FEAT_FOUR_TEMP BIT(22)
@@ -452,7 +453,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT8790E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
- | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_CONF_NOEXIT,
+ | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
.peci_mask = 0x07,
},
[it8792] = {
@@ -461,7 +462,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
- | FEAT_CONF_NOEXIT,
+ | FEAT_NOCONF,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -507,7 +508,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
- | FEAT_CONF_NOEXIT,
+ | FEAT_NOCONF,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -544,7 +545,7 @@ static const struct it87_devices it87_devices[] = {
#define has_four_temp(data) ((data)->features & FEAT_FOUR_TEMP)
#define has_six_temp(data) ((data)->features & FEAT_SIX_TEMP)
#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V)
-#define has_conf_noexit(data) ((data)->features & FEAT_CONF_NOEXIT)
+#define has_noconf(data) ((data)->features & FEAT_NOCONF)
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
FEAT_10_9MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
@@ -742,13 +743,13 @@ static int smbus_disable(struct it87_data *data)
int err;
if (data->smbus_bitmap) {
- err = superio_enter(data->sioaddr);
+ err = superio_enter(data->sioaddr, has_noconf(data));
if (err)
return err;
superio_select(data->sioaddr, PME);
superio_outb(data->sioaddr, IT87_SPECIAL_CFG_REG,
data->ec_special_config & ~data->smbus_bitmap);
- superio_exit(data->sioaddr, has_conf_noexit(data));
+ superio_exit(data->sioaddr, has_noconf(data));
}
return 0;
}
@@ -758,14 +759,14 @@ static int smbus_enable(struct it87_data *data)
int err;
if (data->smbus_bitmap) {
- err = superio_enter(data->sioaddr);
+ err = superio_enter(data->sioaddr, has_noconf(data));
if (err)
return err;
superio_select(data->sioaddr, PME);
superio_outb(data->sioaddr, IT87_SPECIAL_CFG_REG,
data->ec_special_config);
- superio_exit(data->sioaddr, has_conf_noexit(data));
+ superio_exit(data->sioaddr, has_noconf(data));
}
return 0;
}
@@ -2666,6 +2667,27 @@ static const struct attribute_group it87_group_auto_pwm = {
.is_visible = it87_auto_pwm_is_visible,
};
+/*
+ * Original explanation:
+ * On various Gigabyte AM4 boards (AB350, AX370), the second Super-IO chip
+ * (IT8792E) needs to be in configuration mode before accessing the first
+ * due to a bug in IT8792E which otherwise results in LPC bus access errors.
+ * This needs to be done before accessing the first Super-IO chip since
+ * the second chip may have been accessed prior to loading this driver.
+ *
+ * The problem is also reported to affect IT8795E, which is used on X299 boards
+ * and has the same chip ID as IT8792E (0x8733). It also appears to affect
+ * systems with IT8790E, which is used on some Z97X-Gaming boards as well as
+ * Z87X-OC.
+ *
+ * From other information supplied:
+ * ChipIDs 0x8733, 0x8695 (early ID for IT87952E) and 0x8790 are initialized
+ * and left in configuration mode, and entering and/or exiting configuration
+ * mode is what causes the crash.
+ *
+ * The recommendation is to look up the chipID before doing any mode swap
+ * and then act accordingly.
+ */
/* SuperIO detection - will change isa_address if a chip is found */
static int __init it87_find(int sioaddr, unsigned short *address,
struct it87_sio_data *sio_data, int chip_cnt)
@@ -2673,16 +2695,25 @@ static int __init it87_find(int sioaddr, unsigned short *address,
int err;
u16 chip_type;
const struct it87_devices *config = NULL;
+ bool enabled = false;
- err = superio_enter(sioaddr);
+ /* First step, lock memory but don't enter configuration mode */
+ err = superio_enter(sioaddr, true);
if (err)
return err;
err = -ENODEV;
chip_type = superio_inw(sioaddr, DEVID);
- /* check first for a valid chip before forcing chip id */
- if (chip_type == 0xffff)
- goto exit;
+ /* Check for a valid chip before forcing chip id */
+ if (chip_type == 0xffff) {
+ /* Enter configuration mode */
+ __superio_enter(sioaddr);
+ enabled = true;
+ /* and then try again */
+ chip_type = superio_inw(sioaddr, DEVID);
+ if (chip_type == 0xffff)
+ goto exit;
+ }
if (force_id_cnt == 1) {
/* If only one value given use for all chips */
@@ -2766,6 +2797,18 @@ static int __init it87_find(int sioaddr, unsigned short *address,
config = &it87_devices[sio_data->type];
+ /*
+ * If previously we didn't enter configuration mode and it isn't a
+ * chip we know is initialised in configuration mode, then enter
+ * configuration mode.
+ *
+ * I don't know if any such chips can exist but be defensive.
+ */
+ if (!enabled && !has_noconf(config)) {
+ __superio_enter(sioaddr);
+ enabled = true;
+ }
+
superio_select(sioaddr, PME);
if (!(superio_inb(sioaddr, IT87_ACT_REG) & 0x01)) {
pr_info("Device (chip %s ioreg 0x%x) not activated, skipping\n",
@@ -3143,7 +3186,7 @@ static int __init it87_find(int sioaddr, unsigned short *address,
}
exit:
- superio_exit(sioaddr, config ? has_conf_noexit(config) : false);
+ superio_exit(sioaddr, !enabled);
return err;
}
@@ -3520,7 +3563,7 @@ static void it87_resume_sio(struct platform_device *pdev)
if (!data->need_in7_reroute)
return;
- err = superio_enter(data->sioaddr);
+ err = superio_enter(data->sioaddr, has_noconf(data));
if (err) {
dev_warn(&pdev->dev,
"Unable to enter Super I/O to reroute in7 (%d)",
@@ -3540,7 +3583,7 @@ static void it87_resume_sio(struct platform_device *pdev)
reg2c);
}
- superio_exit(data->sioaddr, has_conf_noexit(data));
+ superio_exit(data->sioaddr, has_noconf(data));
}
static int it87_resume(struct device *dev)
@@ -3641,27 +3684,6 @@ static int it87_dmi_cb(const struct dmi_system_id *dmi_entry)
}
/*
- * On various Gigabyte AM4 boards (AB350, AX370), the second Super-IO chip
- * (IT8792E) needs to be in configuration mode before accessing the first
- * due to a bug in IT8792E which otherwise results in LPC bus access errors.
- * This needs to be done before accessing the first Super-IO chip since
- * the second chip may have been accessed prior to loading this driver.
- *
- * The problem is also reported to affect IT8795E, which is used on X299 boards
- * and has the same chip ID as IT8792E (0x8733). It also appears to affect
- * systems with IT8790E, which is used on some Z97X-Gaming boards as well as
- * Z87X-OC.
- * DMI entries for those systems will be added as they become available and
- * as the problem is confirmed to affect those boards.
- */
-static int it87_sio_force(const struct dmi_system_id *dmi_entry)
-{
- __superio_enter(REG_4E);
-
- return it87_dmi_cb(dmi_entry);
-};
-
-/*
* On the Shuttle SN68PT, FAN_CTL2 is apparently not
* connected to a fan, but to something else. One user
* has reported instant system power-off when changing
@@ -3683,34 +3705,7 @@ static struct it87_dmi_data nvidia_fn68pt = {
.driver_data = data, \
}
-#define IT87_DMI_MATCH_GBT(name, cb, data) \
- IT87_DMI_MATCH_VND("Gigabyte Technology Co., Ltd.", name, cb, data)
-
static const struct dmi_system_id it87_dmi_table[] __initconst = {
- IT87_DMI_MATCH_GBT("AB350", it87_sio_force, NULL),
- /* ? + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("AX370", it87_sio_force, NULL),
- /* ? + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("Z97X-Gaming G1", it87_sio_force, NULL),
- /* ? + IT8790E */
- IT87_DMI_MATCH_GBT("TRX40 AORUS XTREME", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("Z390 AORUS ULTRA-CF", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("B550 AORUS PRO AC", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("X570 AORUS MASTER", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("X570 AORUS PRO", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("X570 AORUS PRO WIFI", it87_sio_force, NULL),
- /* IT8688E + IT8792E/IT8795E */
- IT87_DMI_MATCH_GBT("X570S AERO G", it87_sio_force, NULL),
- /* IT8689E + IT87952E */
- IT87_DMI_MATCH_GBT("Z690 AORUS PRO DDR4", it87_sio_force, NULL),
- /* IT8689E + IT87952E */
- IT87_DMI_MATCH_GBT("Z690 AORUS PRO", it87_sio_force, NULL),
- /* IT8689E + IT87952E */
IT87_DMI_MATCH_VND("nVIDIA", "FN68PT", it87_dmi_cb, &nvidia_fn68pt),
{ }
diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c
index f958e830b23c..6549dc543781 100644
--- a/drivers/hwmon/jc42.c
+++ b/drivers/hwmon/jc42.c
@@ -11,6 +11,7 @@
#include <linux/bitops.h>
#include <linux/bitfield.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
@@ -18,8 +19,6 @@
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/of.h>
#include <linux/regmap.h>
/* Addresses to scan */
@@ -79,20 +78,9 @@ static const unsigned short normal_i2c[] = {
#define AT30TS00_DEVID 0x8201
#define AT30TS00_DEVID_MASK 0xffff
-#define AT30TSE004_DEVID 0x2200
-#define AT30TSE004_DEVID_MASK 0xffff
-
-/* Giantec */
-#define GT30TS00_DEVID 0x2200
-#define GT30TS00_DEVID_MASK 0xff00
-
#define GT34TS02_DEVID 0x3300
#define GT34TS02_DEVID_MASK 0xff00
-/* IDT */
-#define TSE2004_DEVID 0x2200
-#define TSE2004_DEVID_MASK 0xff00
-
#define TS3000_DEVID 0x2900 /* Also matches TSE2002 */
#define TS3000_DEVID_MASK 0xff00
@@ -116,9 +104,6 @@ static const unsigned short normal_i2c[] = {
#define MCP98243_DEVID 0x2100
#define MCP98243_DEVID_MASK 0xfffc
-#define MCP98244_DEVID 0x2200
-#define MCP98244_DEVID_MASK 0xfffc
-
#define MCP9843_DEVID 0x0000 /* Also matches mcp9805 */
#define MCP9843_DEVID_MASK 0xfffe
@@ -136,12 +121,6 @@ static const unsigned short normal_i2c[] = {
#define CAT34TS02C_DEVID 0x0a00
#define CAT34TS02C_DEVID_MASK 0xfff0
-#define CAT34TS04_DEVID 0x2200
-#define CAT34TS04_DEVID_MASK 0xfff0
-
-#define N34TS04_DEVID 0x2230
-#define N34TS04_DEVID_MASK 0xfff0
-
/* ST Microelectronics */
#define STTS424_DEVID 0x0101
#define STTS424_DEVID_MASK 0xffff
@@ -152,15 +131,12 @@ static const unsigned short normal_i2c[] = {
#define STTS2002_DEVID 0x0300
#define STTS2002_DEVID_MASK 0xffff
-#define STTS2004_DEVID 0x2201
-#define STTS2004_DEVID_MASK 0xffff
-
#define STTS3000_DEVID 0x0200
#define STTS3000_DEVID_MASK 0xffff
-/* Seiko Instruments */
-#define S34TS04A_DEVID 0x2221
-#define S34TS04A_DEVID_MASK 0xffff
+/* TSE2004 compliant sensors */
+#define TSE2004_DEVID 0x2200
+#define TSE2004_DEVID_MASK 0xff00
static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 };
@@ -173,8 +149,8 @@ struct jc42_chips {
static struct jc42_chips jc42_chips[] = {
{ ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK },
{ ATMEL_MANID, AT30TS00_DEVID, AT30TS00_DEVID_MASK },
- { ATMEL_MANID2, AT30TSE004_DEVID, AT30TSE004_DEVID_MASK },
- { GT_MANID, GT30TS00_DEVID, GT30TS00_DEVID_MASK },
+ { ATMEL_MANID2, TSE2004_DEVID, TSE2004_DEVID_MASK },
+ { GT_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ GT_MANID2, GT34TS02_DEVID, GT34TS02_DEVID_MASK },
{ IDT_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ IDT_MANID, TS3000_DEVID, TS3000_DEVID_MASK },
@@ -184,25 +160,24 @@ static struct jc42_chips jc42_chips[] = {
{ MCP_MANID, MCP9808_DEVID, MCP9808_DEVID_MASK },
{ MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK },
{ MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK },
- { MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK },
+ { MCP_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ MCP_MANID, MCP9843_DEVID, MCP9843_DEVID_MASK },
{ NXP_MANID, SE97_DEVID, SE97_DEVID_MASK },
{ ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK },
{ ONS_MANID, CAT34TS02C_DEVID, CAT34TS02C_DEVID_MASK },
- { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK },
- { ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK },
+ { ONS_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
+ { ONS_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ NXP_MANID, SE98_DEVID, SE98_DEVID_MASK },
- { SI_MANID, S34TS04A_DEVID, S34TS04A_DEVID_MASK },
+ { SI_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK },
{ STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK },
{ STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK },
- { STM_MANID, STTS2004_DEVID, STTS2004_DEVID_MASK },
+ { STM_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ STM_MANID, STTS3000_DEVID, STTS3000_DEVID_MASK },
};
/* Each client has this additional data */
struct jc42_data {
- struct mutex update_lock; /* protect register access */
struct regmap *regmap;
bool extended; /* true if extended range supported */
bool valid;
@@ -239,8 +214,6 @@ static int jc42_read(struct device *dev, enum hwmon_sensor_types type,
unsigned int regval;
int ret, temp, hyst;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_temp_input:
ret = regmap_read(data->regmap, JC42_REG_TEMP, &regval);
@@ -318,8 +291,6 @@ static int jc42_read(struct device *dev, enum hwmon_sensor_types type,
break;
}
- mutex_unlock(&data->update_lock);
-
return ret;
}
@@ -331,8 +302,6 @@ static int jc42_write(struct device *dev, enum hwmon_sensor_types type,
int diff, hyst;
int ret;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_temp_min:
ret = regmap_write(data->regmap, JC42_REG_TEMP_LOWER,
@@ -379,8 +348,6 @@ static int jc42_write(struct device *dev, enum hwmon_sensor_types type,
break;
}
- mutex_unlock(&data->update_lock);
-
return ret;
}
@@ -436,7 +403,11 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
if (cap < 0 || config < 0 || manid < 0 || devid < 0)
return -ENODEV;
- if ((cap & 0xff00) || (config & 0xf800))
+ if ((cap & 0xff00) || (config & 0xf820))
+ return -ENODEV;
+
+ if ((devid & TSE2004_DEVID_MASK) == TSE2004_DEVID &&
+ (cap & 0x0062) != 0x0062)
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(jc42_chips); i++) {
@@ -497,7 +468,7 @@ static const struct regmap_config jc42_regmap_config = {
.writeable_reg = jc42_writable_reg,
.readable_reg = jc42_readable_reg,
.volatile_reg = jc42_volatile_reg,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static int jc42_probe(struct i2c_client *client)
@@ -517,7 +488,6 @@ static int jc42_probe(struct i2c_client *client)
return PTR_ERR(data->regmap);
i2c_set_clientdata(client, data);
- mutex_init(&data->update_lock);
ret = regmap_read(data->regmap, JC42_REG_CAP, &cap);
if (ret)
@@ -609,25 +579,23 @@ static const struct dev_pm_ops jc42_dev_pm_ops = {
#endif /* CONFIG_PM */
static const struct i2c_device_id jc42_id[] = {
- { "jc42", 0 },
+ { "jc42" },
{ }
};
MODULE_DEVICE_TABLE(i2c, jc42_id);
-#ifdef CONFIG_OF
static const struct of_device_id jc42_of_ids[] = {
{ .compatible = "jedec,jc-42.4-temp", },
{ }
};
MODULE_DEVICE_TABLE(of, jc42_of_ids);
-#endif
static struct i2c_driver jc42_driver = {
- .class = I2C_CLASS_SPD | I2C_CLASS_HWMON,
+ .class = I2C_CLASS_HWMON,
.driver = {
.name = "jc42",
.pm = JC42_DEV_PM_OPS,
- .of_match_table = of_match_ptr(jc42_of_ids),
+ .of_match_table = jc42_of_ids,
},
.probe = jc42_probe,
.remove = jc42_remove,
diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c
index bae0becfa24b..a5d8f45b7881 100644
--- a/drivers/hwmon/k10temp.c
+++ b/drivers/hwmon/k10temp.c
@@ -20,7 +20,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
-#include <asm/amd_nb.h>
+#include <asm/amd/node.h>
#include <asm/processor.h>
MODULE_DESCRIPTION("AMD Family 10h+ CPU core temperature monitor");
@@ -31,9 +31,6 @@ static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "force loading on processors with erratum 319");
-/* Provide lock for writing to NB_SMU_IND_ADDR */
-static DEFINE_MUTEX(nb_smu_ind_mutex);
-
#ifndef PCI_DEVICE_ID_AMD_15H_M70H_NB_F3
#define PCI_DEVICE_ID_AMD_15H_M70H_NB_F3 0x15b3
#endif
@@ -84,6 +81,19 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
*/
#define AMD_I3255_STR "3255"
+/*
+ * PCI Device IDs for AMD's Family 17h-based SOCs.
+ * Defining locally as IDs are not shared.
+ */
+#define PCI_DEVICE_ID_AMD_17H_M90H_DF_F3 0x1663
+
+/*
+ * PCI Device IDs for AMD's Family 1Ah-based SOCs.
+ * Defining locally as IDs are not shared.
+ */
+#define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb
+#define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b
+
struct k10temp_data {
struct pci_dev *pdev;
void (*read_htcreg)(struct pci_dev *pdev, u32 *regval);
@@ -101,7 +111,6 @@ struct k10temp_data {
#define TCCD_BIT(x) ((x) + 2)
#define HAVE_TEMP(d, channel) ((d)->show_temp & BIT(channel))
-#define HAVE_TDIE(d) HAVE_TEMP(d, TDIE_BIT)
struct tctl_offset {
u8 model;
@@ -131,12 +140,10 @@ static void read_tempreg_pci(struct pci_dev *pdev, u32 *regval)
static void amd_nb_index_read(struct pci_dev *pdev, unsigned int devfn,
unsigned int base, int offset, u32 *val)
{
- mutex_lock(&nb_smu_ind_mutex);
pci_bus_write_config_dword(pdev->bus, devfn,
base, offset);
pci_bus_read_config_dword(pdev->bus, devfn,
base + 4, val);
- mutex_unlock(&nb_smu_ind_mutex);
}
static void read_htcreg_nb_f15(struct pci_dev *pdev, u32 *regval)
@@ -151,10 +158,23 @@ static void read_tempreg_nb_f15(struct pci_dev *pdev, u32 *regval)
F15H_M60H_REPORTED_TEMP_CTRL_OFFSET, regval);
}
+static u16 amd_pci_dev_to_node_id(struct pci_dev *pdev)
+{
+ return PCI_SLOT(pdev->devfn) - AMD_NODE0_PCI_SLOT;
+}
+
static void read_tempreg_nb_zen(struct pci_dev *pdev, u32 *regval)
{
- amd_smn_read(amd_pci_dev_to_node_id(pdev),
- ZEN_REPORTED_TEMP_CTRL_BASE, regval);
+ if (amd_smn_read(amd_pci_dev_to_node_id(pdev),
+ ZEN_REPORTED_TEMP_CTRL_BASE, regval))
+ *regval = 0;
+}
+
+static int read_ccd_temp_reg(struct k10temp_data *data, int ccd, u32 *regval)
+{
+ u16 node_id = amd_pci_dev_to_node_id(data->pdev);
+
+ return amd_smn_read(node_id, ZEN_CCD_TEMP(data->ccd_offset, ccd), regval);
}
static long get_raw_temp(struct k10temp_data *data)
@@ -205,6 +225,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
long *val)
{
struct k10temp_data *data = dev_get_drvdata(dev);
+ int ret = -EOPNOTSUPP;
u32 regval;
switch (attr) {
@@ -221,13 +242,15 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
*val = 0;
break;
case 2 ... 13: /* Tccd{1-12} */
- amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
- ZEN_CCD_TEMP(data->ccd_offset, channel - 2),
- &regval);
+ ret = read_ccd_temp_reg(data, channel - 2, &regval);
+
+ if (ret)
+ return ret;
+
*val = (regval & ZEN_CCD_TEMP_MASK) * 125 - 49000;
break;
default:
- return -EOPNOTSUPP;
+ return ret;
}
break;
case hwmon_temp_max:
@@ -243,7 +266,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
- ((regval >> 24) & 0xf)) * 500 + 52000;
break;
default:
- return -EOPNOTSUPP;
+ return ret;
}
return 0;
}
@@ -259,11 +282,11 @@ static int k10temp_read(struct device *dev, enum hwmon_sensor_types type,
}
}
-static umode_t k10temp_is_visible(const void *_data,
+static umode_t k10temp_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
- const struct k10temp_data *data = _data;
+ const struct k10temp_data *data = drvdata;
struct pci_dev *pdev = data->pdev;
u32 reg;
@@ -374,15 +397,25 @@ static const struct hwmon_chip_info k10temp_chip_info = {
.info = k10temp_info,
};
-static void k10temp_get_ccd_support(struct pci_dev *pdev,
- struct k10temp_data *data, int limit)
+static void k10temp_get_ccd_support(struct k10temp_data *data, int limit)
{
u32 regval;
int i;
for (i = 0; i < limit; i++) {
- amd_smn_read(amd_pci_dev_to_node_id(pdev),
- ZEN_CCD_TEMP(data->ccd_offset, i), &regval);
+ /*
+ * Ignore inaccessible CCDs.
+ *
+ * Some systems will return a register value of 0, and the TEMP_VALID
+ * bit check below will naturally fail.
+ *
+ * Other systems will return a PCI_ERROR_RESPONSE (0xFFFFFFFF) for
+ * the register value. And this will incorrectly pass the TEMP_VALID
+ * bit check.
+ */
+ if (read_ccd_temp_reg(data, i, &regval))
+ continue;
+
if (regval & ZEN_CCD_TEMP_VALID)
data->show_temp |= BIT(TCCD_BIT(i));
}
@@ -418,70 +451,73 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
data->disp_negative = true;
}
- if (boot_cpu_data.x86 == 0x15 &&
+ data->is_zen = cpu_feature_enabled(X86_FEATURE_ZEN);
+ if (data->is_zen) {
+ data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
+ data->read_tempreg = read_tempreg_nb_zen;
+ } else if (boot_cpu_data.x86 == 0x15 &&
((boot_cpu_data.x86_model & 0xf0) == 0x60 ||
(boot_cpu_data.x86_model & 0xf0) == 0x70)) {
data->read_htcreg = read_htcreg_nb_f15;
data->read_tempreg = read_tempreg_nb_f15;
- } else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
- data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
- data->read_tempreg = read_tempreg_nb_zen;
- data->is_zen = true;
+ } else {
+ data->read_htcreg = read_htcreg_pci;
+ data->read_tempreg = read_tempreg_pci;
+ }
+ if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
switch (boot_cpu_data.x86_model) {
case 0x1: /* Zen */
case 0x8: /* Zen+ */
case 0x11: /* Zen APU */
case 0x18: /* Zen+ APU */
data->ccd_offset = 0x154;
- k10temp_get_ccd_support(pdev, data, 4);
+ k10temp_get_ccd_support(data, 4);
break;
case 0x31: /* Zen2 Threadripper */
+ case 0x47: /* Cyan Skillfish */
case 0x60: /* Renoir */
case 0x68: /* Lucienne */
case 0x71: /* Zen2 */
data->ccd_offset = 0x154;
- k10temp_get_ccd_support(pdev, data, 8);
+ k10temp_get_ccd_support(data, 8);
break;
case 0xa0 ... 0xaf:
data->ccd_offset = 0x300;
- k10temp_get_ccd_support(pdev, data, 8);
+ k10temp_get_ccd_support(data, 8);
break;
}
} else if (boot_cpu_data.x86 == 0x19) {
- data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
- data->read_tempreg = read_tempreg_nb_zen;
- data->is_zen = true;
-
switch (boot_cpu_data.x86_model) {
case 0x0 ... 0x1: /* Zen3 SP3/TR */
+ case 0x8: /* Zen3 TR Chagall */
case 0x21: /* Zen3 Ryzen Desktop */
case 0x50 ... 0x5f: /* Green Sardine */
data->ccd_offset = 0x154;
- k10temp_get_ccd_support(pdev, data, 8);
+ k10temp_get_ccd_support(data, 8);
break;
case 0x40 ... 0x4f: /* Yellow Carp */
data->ccd_offset = 0x300;
- k10temp_get_ccd_support(pdev, data, 8);
+ k10temp_get_ccd_support(data, 8);
break;
case 0x60 ... 0x6f:
case 0x70 ... 0x7f:
data->ccd_offset = 0x308;
- k10temp_get_ccd_support(pdev, data, 8);
+ k10temp_get_ccd_support(data, 8);
break;
case 0x10 ... 0x1f:
case 0xa0 ... 0xaf:
data->ccd_offset = 0x300;
- k10temp_get_ccd_support(pdev, data, 12);
+ k10temp_get_ccd_support(data, 12);
break;
}
} else if (boot_cpu_data.x86 == 0x1a) {
- data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
- data->read_tempreg = read_tempreg_nb_zen;
- data->is_zen = true;
- } else {
- data->read_htcreg = read_htcreg_pci;
- data->read_tempreg = read_tempreg_pci;
+ switch (boot_cpu_data.x86_model) {
+ case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */
+ data->ccd_offset = 0x308;
+ k10temp_get_ccd_support(data, 8);
+ break;
+ }
}
for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) {
@@ -515,8 +551,10 @@ static const struct pci_device_id k10temp_id_table[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M10H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M30H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M40H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M90H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_MA0H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) },
@@ -527,6 +565,10 @@ static const struct pci_device_id k10temp_id_table[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M78H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M00H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M20H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3) },
+ { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) },
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{}
};
diff --git a/drivers/hwmon/kbatt.c b/drivers/hwmon/kbatt.c
new file mode 100644
index 000000000000..501b8f4ded33
--- /dev/null
+++ b/drivers/hwmon/kbatt.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 KEBA Industrial Automation GmbH
+ *
+ * Driver for KEBA battery monitoring controller FPGA IP core
+ */
+
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/misc/keba.h>
+#include <linux/mutex.h>
+
+#define KBATT "kbatt"
+
+#define KBATT_CONTROL_REG 0x4
+#define KBATT_CONTROL_BAT_TEST 0x01
+
+#define KBATT_STATUS_REG 0x8
+#define KBATT_STATUS_BAT_OK 0x01
+
+#define KBATT_MAX_UPD_INTERVAL (10 * HZ)
+#define KBATT_SETTLE_TIME_US (100 * USEC_PER_MSEC)
+
+struct kbatt {
+ /* update lock */
+ struct mutex lock;
+ void __iomem *base;
+
+ unsigned long next_update; /* in jiffies */
+ bool alarm;
+};
+
+static bool kbatt_alarm(struct kbatt *kbatt)
+{
+ mutex_lock(&kbatt->lock);
+
+ if (!kbatt->next_update || time_after(jiffies, kbatt->next_update)) {
+ /* switch load on */
+ iowrite8(KBATT_CONTROL_BAT_TEST,
+ kbatt->base + KBATT_CONTROL_REG);
+
+ /* wait some time to let things settle */
+ fsleep(KBATT_SETTLE_TIME_US);
+
+ /* check battery state */
+ if (ioread8(kbatt->base + KBATT_STATUS_REG) &
+ KBATT_STATUS_BAT_OK)
+ kbatt->alarm = false;
+ else
+ kbatt->alarm = true;
+
+ /* switch load off */
+ iowrite8(0, kbatt->base + KBATT_CONTROL_REG);
+
+ kbatt->next_update = jiffies + KBATT_MAX_UPD_INTERVAL;
+ }
+
+ mutex_unlock(&kbatt->lock);
+
+ return kbatt->alarm;
+}
+
+static int kbatt_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct kbatt *kbatt = dev_get_drvdata(dev);
+
+ *val = kbatt_alarm(kbatt) ? 1 : 0;
+
+ return 0;
+}
+
+static umode_t kbatt_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (channel == 0 && attr == hwmon_in_min_alarm)
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *kbatt_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ /* 0: input minimum alarm channel */
+ HWMON_I_MIN_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops kbatt_hwmon_ops = {
+ .is_visible = kbatt_is_visible,
+ .read = kbatt_read,
+};
+
+static const struct hwmon_chip_info kbatt_chip_info = {
+ .ops = &kbatt_hwmon_ops,
+ .info = kbatt_info,
+};
+
+static int kbatt_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct keba_batt_auxdev *kbatt_auxdev =
+ container_of(auxdev, struct keba_batt_auxdev, auxdev);
+ struct device *dev = &auxdev->dev;
+ struct device *hwmon_dev;
+ struct kbatt *kbatt;
+ int retval;
+
+ kbatt = devm_kzalloc(dev, sizeof(*kbatt), GFP_KERNEL);
+ if (!kbatt)
+ return -ENOMEM;
+
+ retval = devm_mutex_init(dev, &kbatt->lock);
+ if (retval)
+ return retval;
+
+ kbatt->base = devm_ioremap_resource(dev, &kbatt_auxdev->io);
+ if (IS_ERR(kbatt->base))
+ return PTR_ERR(kbatt->base);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, KBATT, kbatt,
+ &kbatt_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct auxiliary_device_id kbatt_devtype_aux[] = {
+ { .name = "keba.batt" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, kbatt_devtype_aux);
+
+static struct auxiliary_driver kbatt_driver_aux = {
+ .name = KBATT,
+ .id_table = kbatt_devtype_aux,
+ .probe = kbatt_probe,
+};
+module_auxiliary_driver(kbatt_driver_aux);
+
+MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
+MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
+MODULE_DESCRIPTION("KEBA battery monitoring controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/kfan.c b/drivers/hwmon/kfan.c
new file mode 100644
index 000000000000..f353acb66749
--- /dev/null
+++ b/drivers/hwmon/kfan.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 KEBA Industrial Automation GmbH
+ *
+ * Driver for KEBA fan controller FPGA IP core
+ *
+ */
+
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/misc/keba.h>
+
+#define KFAN "kfan"
+
+#define KFAN_CONTROL_REG 0x04
+
+#define KFAN_STATUS_REG 0x08
+#define KFAN_STATUS_PRESENT 0x01
+#define KFAN_STATUS_REGULABLE 0x02
+#define KFAN_STATUS_TACHO 0x04
+#define KFAN_STATUS_BLOCKED 0x08
+
+#define KFAN_TACHO_REG 0x0c
+
+#define KFAN_DEFAULT_DIV 2
+
+struct kfan {
+ void __iomem *base;
+ bool tacho;
+ bool regulable;
+
+ /* hwmon API configuration */
+ u32 fan_channel_config[2];
+ struct hwmon_channel_info fan_info;
+ u32 pwm_channel_config[2];
+ struct hwmon_channel_info pwm_info;
+ const struct hwmon_channel_info *info[3];
+ struct hwmon_chip_info chip;
+};
+
+static bool kfan_get_fault(struct kfan *kfan)
+{
+ u8 status = ioread8(kfan->base + KFAN_STATUS_REG);
+
+ if (!(status & KFAN_STATUS_PRESENT))
+ return true;
+
+ if (!kfan->tacho && (status & KFAN_STATUS_BLOCKED))
+ return true;
+
+ return false;
+}
+
+static unsigned int kfan_count_to_rpm(u16 count)
+{
+ if (count == 0 || count == 0xffff)
+ return 0;
+
+ return 5000000UL / (KFAN_DEFAULT_DIV * count);
+}
+
+static unsigned int kfan_get_rpm(struct kfan *kfan)
+{
+ unsigned int rpm;
+ u16 count;
+
+ count = ioread16(kfan->base + KFAN_TACHO_REG);
+ rpm = kfan_count_to_rpm(count);
+
+ return rpm;
+}
+
+static unsigned int kfan_get_pwm(struct kfan *kfan)
+{
+ return ioread8(kfan->base + KFAN_CONTROL_REG);
+}
+
+static int kfan_set_pwm(struct kfan *kfan, long val)
+{
+ if (val < 0 || val > 0xff)
+ return -EINVAL;
+
+ /* if none-regulable, then only 0 or 0xff can be written */
+ if (!kfan->regulable && val > 0)
+ val = 0xff;
+
+ iowrite8(val, kfan->base + KFAN_CONTROL_REG);
+
+ return 0;
+}
+
+static int kfan_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct kfan *kfan = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return kfan_set_pwm(kfan, val);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int kfan_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct kfan *kfan = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_fault:
+ *val = kfan_get_fault(kfan);
+ return 0;
+ case hwmon_fan_input:
+ *val = kfan_get_rpm(kfan);
+ return 0;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ *val = kfan_get_pwm(kfan);
+ return 0;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t kfan_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ return 0444;
+ case hwmon_fan_fault:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops kfan_hwmon_ops = {
+ .is_visible = kfan_is_visible,
+ .read = kfan_read,
+ .write = kfan_write,
+};
+
+static int kfan_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct keba_fan_auxdev *kfan_auxdev =
+ container_of(auxdev, struct keba_fan_auxdev, auxdev);
+ struct device *dev = &auxdev->dev;
+ struct device *hwmon_dev;
+ struct kfan *kfan;
+ u8 status;
+
+ kfan = devm_kzalloc(dev, sizeof(*kfan), GFP_KERNEL);
+ if (!kfan)
+ return -ENOMEM;
+
+ kfan->base = devm_ioremap_resource(dev, &kfan_auxdev->io);
+ if (IS_ERR(kfan->base))
+ return PTR_ERR(kfan->base);
+
+ status = ioread8(kfan->base + KFAN_STATUS_REG);
+ if (status & KFAN_STATUS_REGULABLE)
+ kfan->regulable = true;
+ if (status & KFAN_STATUS_TACHO)
+ kfan->tacho = true;
+
+ /* fan */
+ kfan->fan_channel_config[0] = HWMON_F_FAULT;
+ if (kfan->tacho)
+ kfan->fan_channel_config[0] |= HWMON_F_INPUT;
+ kfan->fan_info.type = hwmon_fan;
+ kfan->fan_info.config = kfan->fan_channel_config;
+ kfan->info[0] = &kfan->fan_info;
+
+ /* PWM */
+ kfan->pwm_channel_config[0] = HWMON_PWM_INPUT;
+ kfan->pwm_info.type = hwmon_pwm;
+ kfan->pwm_info.config = kfan->pwm_channel_config;
+ kfan->info[1] = &kfan->pwm_info;
+
+ kfan->chip.ops = &kfan_hwmon_ops;
+ kfan->chip.info = kfan->info;
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, KFAN, kfan,
+ &kfan->chip, NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct auxiliary_device_id kfan_devtype_aux[] = {
+ { .name = "keba.fan" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, kfan_devtype_aux);
+
+static struct auxiliary_driver kfan_driver_aux = {
+ .name = KFAN,
+ .id_table = kfan_devtype_aux,
+ .probe = kfan_probe,
+};
+module_auxiliary_driver(kfan_driver_aux);
+
+MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
+MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
+MODULE_DESCRIPTION("KEBA fan controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/lenovo-ec-sensors.c b/drivers/hwmon/lenovo-ec-sensors.c
new file mode 100644
index 000000000000..8681bbf6665b
--- /dev/null
+++ b/drivers/hwmon/lenovo-ec-sensors.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for Lenovo ThinkStation based workstations
+ * via the embedded controller registers
+ *
+ * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com>
+ *
+ * EC provides:
+ * - CPU temperature
+ * - DIMM temperature
+ * - Chassis zone temperatures
+ * - CPU fan RPM
+ * - DIMM fan RPM
+ * - Chassis fans RPM
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define MCHP_SING_IDX 0x0000
+#define MCHP_EMI0_APPLICATION_ID 0x090C
+#define MCHP_EMI0_EC_ADDRESS 0x0902
+#define MCHP_EMI0_EC_DATA_BYTE0 0x0904
+#define MCHP_EMI0_EC_DATA_BYTE1 0x0905
+#define MCHP_EMI0_EC_DATA_BYTE2 0x0906
+#define MCHP_EMI0_EC_DATA_BYTE3 0x0907
+#define IO_REGION_START 0x0900
+#define IO_REGION_LENGTH 0xD
+
+static inline u8
+get_ec_reg(unsigned char page, unsigned char index)
+{
+ u8 onebyte;
+ unsigned short m_index;
+ unsigned short phy_index = page * 256 + index;
+
+ outb_p(0x01, MCHP_EMI0_APPLICATION_ID);
+
+ m_index = phy_index & GENMASK(14, 2);
+ outw_p(m_index, MCHP_EMI0_EC_ADDRESS);
+
+ onebyte = inb_p(MCHP_EMI0_EC_DATA_BYTE0 + (phy_index & GENMASK(1, 0)));
+
+ outb_p(0x01, MCHP_EMI0_APPLICATION_ID); /* write 0x01 again to clean */
+ return onebyte;
+}
+
+enum systems {
+ LENOVO_PX,
+ LENOVO_P7,
+ LENOVO_P5,
+ LENOVO_P8,
+};
+
+static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31, 32};
+
+static const char * const lenovo_px_ec_temp_label[] = {
+ "CPU1",
+ "CPU2",
+ "R_DIMM1",
+ "L_DIMM1",
+ "R_DIMM2",
+ "L_DIMM2",
+ "PCH",
+ "M2_R",
+ "M2_Z1R",
+ "M2_Z2R",
+ "PCI_Z1",
+ "PCI_Z2",
+ "PCI_Z3",
+ "PCI_Z4",
+ "AMB",
+ "PSU1",
+ "PSU2",
+};
+
+static int p8_temp_map[] = {0, 1, 2, 8, 9, 13, 14, 15, 16, 17, 19, 20, 33};
+
+static const char * const lenovo_p8_ec_temp_label[] = {
+ "CPU1",
+ "CPU_DIMM_BANK1",
+ "CPU_DIMM_BANK2",
+ "M2_Z2R",
+ "M2_Z3R",
+ "DIMM_RIGHT",
+ "DIMM_LEFT",
+ "PCI_Z1",
+ "PCI_Z2",
+ "PCI_Z3",
+ "AMB",
+ "REAR_VR",
+ "PSU",
+};
+
+static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31};
+
+static const char * const lenovo_gen_ec_temp_label[] = {
+ "CPU1",
+ "R_DIMM",
+ "L_DIMM",
+ "PCH",
+ "M2_R",
+ "M2_Z1R",
+ "M2_Z2R",
+ "PCI_Z1",
+ "PCI_Z2",
+ "PCI_Z3",
+ "PCI_Z4",
+ "AMB",
+ "PSU",
+};
+
+static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+static const char * const px_ec_fan_label[] = {
+ "CPU1_Fan",
+ "CPU2_Fan",
+ "Front_Fan1-1",
+ "Front_Fan1-2",
+ "Front_Fan2",
+ "Front_Fan3",
+ "MEM_Fan1",
+ "MEM_Fan2",
+ "Rear_Fan1",
+ "Rear_Fan2",
+ "Flex_Bay_Fan1",
+ "Flex_Bay_Fan2",
+ "Flex_Bay_Fan2",
+ "PSU_HDD_Fan",
+ "PSU1_Fan",
+ "PSU2_Fan",
+};
+
+static int p7_fan_map[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14};
+
+static const char * const p7_ec_fan_label[] = {
+ "CPU1_Fan",
+ "HP_CPU_Fan1",
+ "HP_CPU_Fan2",
+ "PCIE1_4_Fan",
+ "PCIE5_7_Fan",
+ "MEM_Fan1",
+ "MEM_Fan2",
+ "Rear_Fan1",
+ "BCB_Fan",
+ "Flex_Bay_Fan",
+ "PSU_Fan",
+};
+
+static int p5_fan_map[] = {0, 5, 6, 7, 8, 10, 11, 14};
+
+static const char * const p5_ec_fan_label[] = {
+ "CPU_Fan",
+ "HDD_Fan",
+ "Duct_Fan1",
+ "MEM_Fan",
+ "Rear_Fan",
+ "Front_Fan",
+ "Flex_Bay_Fan",
+ "PSU_Fan",
+};
+
+static int p8_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14};
+
+static const char * const p8_ec_fan_label[] = {
+ "CPU1_Fan",
+ "CPU2_Fan",
+ "HP_CPU_Fan1",
+ "HP_CPU_Fan2",
+ "PCIE1_4_Fan",
+ "PCIE5_7_Fan",
+ "DIMM1_Fan1",
+ "DIMM1_Fan2",
+ "DIMM2_Fan1",
+ "DIMM2_Fan2",
+ "Rear_Fan",
+ "HDD_Bay_Fan",
+ "Flex_Bay_Fan",
+ "PSU_Fan",
+};
+
+struct ec_sensors_data {
+ struct mutex mec_mutex; /* lock for sensor data access */
+ const char *const *fan_labels;
+ const char *const *temp_labels;
+ const int *fan_map;
+ const int *temp_map;
+};
+
+static int
+lenovo_ec_do_read_temp(struct ec_sensors_data *data, u32 attr, int channel, long *val)
+{
+ u8 lsb;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ mutex_lock(&data->mec_mutex);
+ lsb = get_ec_reg(2, 0x81 + channel);
+ mutex_unlock(&data->mec_mutex);
+ if (lsb <= 0x40)
+ return -ENODATA;
+ *val = (lsb - 0x40) * 1000;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int
+lenovo_ec_do_read_fan(struct ec_sensors_data *data, u32 attr, int channel, long *val)
+{
+ u8 lsb, msb;
+
+ channel *= 2;
+ switch (attr) {
+ case hwmon_fan_input:
+ mutex_lock(&data->mec_mutex);
+ lsb = get_ec_reg(4, 0x20 + channel);
+ msb = get_ec_reg(4, 0x21 + channel);
+ mutex_unlock(&data->mec_mutex);
+ *val = (msb << 8) + lsb;
+ return 0;
+ case hwmon_fan_max:
+ mutex_lock(&data->mec_mutex);
+ lsb = get_ec_reg(4, 0x40 + channel);
+ msb = get_ec_reg(4, 0x41 + channel);
+ mutex_unlock(&data->mec_mutex);
+ *val = (msb << 8) + lsb;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int
+lenovo_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct ec_sensors_data *state = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ *str = state->temp_labels[channel];
+ return 0;
+ case hwmon_fan:
+ *str = state->fan_labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int
+lenovo_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct ec_sensors_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return lenovo_ec_do_read_temp(data, attr, data->temp_map[channel], val);
+ case hwmon_fan:
+ return lenovo_ec_do_read_fan(data, attr, data->fan_map[channel], val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t
+lenovo_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ if (attr == hwmon_temp_input || attr == hwmon_temp_label)
+ return 0444;
+ return 0;
+ case hwmon_fan:
+ if (attr == hwmon_fan_input || attr == hwmon_fan_max || attr == hwmon_fan_label)
+ return 0444;
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_ops lenovo_ec_hwmon_ops = {
+ .is_visible = lenovo_ec_hwmon_is_visible,
+ .read = lenovo_ec_hwmon_read,
+ .read_string = lenovo_ec_hwmon_read_string,
+};
+
+static struct hwmon_chip_info lenovo_ec_chip_info = {
+ .ops = &lenovo_ec_hwmon_ops,
+};
+
+static const struct dmi_system_id thinkstation_dmi_table[] = {
+ {
+ .ident = "LENOVO_PX",
+ .driver_data = (void *)(long)LENOVO_PX,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30EU"),
+ },
+ },
+ {
+ .ident = "LENOVO_PX",
+ .driver_data = (void *)(long)LENOVO_PX,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30EV"),
+ },
+ },
+ {
+ .ident = "LENOVO_P7",
+ .driver_data = (void *)(long)LENOVO_P7,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30F2"),
+ },
+ },
+ {
+ .ident = "LENOVO_P7",
+ .driver_data = (void *)(long)LENOVO_P7,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30F3"),
+ },
+ },
+ {
+ .ident = "LENOVO_P5",
+ .driver_data = (void *)(long)LENOVO_P5,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30G9"),
+ },
+ },
+ {
+ .ident = "LENOVO_P5",
+ .driver_data = (void *)(long)LENOVO_P5,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30GA"),
+ },
+ },
+ {
+ .ident = "LENOVO_P8",
+ .driver_data = (void *)(long)LENOVO_P8,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30HH"),
+ },
+ },
+ {
+ .ident = "LENOVO_P8",
+ .driver_data = (void *)(long)LENOVO_P8,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "30HJ"),
+ },
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(dmi, thinkstation_dmi_table);
+
+static int lenovo_ec_probe(struct platform_device *pdev)
+{
+ struct device *hwdev;
+ struct ec_sensors_data *ec_data;
+ const struct hwmon_chip_info *chip_info;
+ struct device *dev = &pdev->dev;
+ const struct dmi_system_id *dmi_id;
+ int app_id;
+
+ ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), GFP_KERNEL);
+ if (!ec_data)
+ return -ENOMEM;
+
+ if (!request_region(IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) {
+ pr_err(":request fail\n");
+ return -EIO;
+ }
+
+ dev_set_drvdata(dev, ec_data);
+
+ chip_info = &lenovo_ec_chip_info;
+
+ mutex_init(&ec_data->mec_mutex);
+
+ mutex_lock(&ec_data->mec_mutex);
+ app_id = inb_p(MCHP_EMI0_APPLICATION_ID);
+ if (app_id) /* check EMI Application ID Value */
+ outb_p(app_id, MCHP_EMI0_APPLICATION_ID); /* set EMI Application ID to 0 */
+ outw_p(MCHP_SING_IDX, MCHP_EMI0_EC_ADDRESS);
+ mutex_unlock(&ec_data->mec_mutex);
+
+ if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') &&
+ (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') &&
+ (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') &&
+ (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) {
+ release_region(IO_REGION_START, IO_REGION_LENGTH);
+ return -ENODEV;
+ }
+
+ dmi_id = dmi_first_match(thinkstation_dmi_table);
+
+ switch ((long)dmi_id->driver_data) {
+ case 0:
+ ec_data->fan_labels = px_ec_fan_label;
+ ec_data->temp_labels = lenovo_px_ec_temp_label;
+ ec_data->fan_map = px_fan_map;
+ ec_data->temp_map = px_temp_map;
+ lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_px;
+ break;
+ case 1:
+ ec_data->fan_labels = p7_ec_fan_label;
+ ec_data->temp_labels = lenovo_gen_ec_temp_label;
+ ec_data->fan_map = p7_fan_map;
+ ec_data->temp_map = gen_temp_map;
+ lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p7;
+ break;
+ case 2:
+ ec_data->fan_labels = p5_ec_fan_label;
+ ec_data->temp_labels = lenovo_gen_ec_temp_label;
+ ec_data->fan_map = p5_fan_map;
+ ec_data->temp_map = gen_temp_map;
+ lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p5;
+ break;
+ case 3:
+ ec_data->fan_labels = p8_ec_fan_label;
+ ec_data->temp_labels = lenovo_p8_ec_temp_label;
+ ec_data->fan_map = p8_fan_map;
+ ec_data->temp_map = p8_temp_map;
+ lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8;
+ break;
+ default:
+ release_region(IO_REGION_START, IO_REGION_LENGTH);
+ return -ENODEV;
+ }
+
+ hwdev = devm_hwmon_device_register_with_info(dev, "lenovo_ec",
+ ec_data,
+ chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwdev);
+}
+
+static struct platform_driver lenovo_ec_sensors_platform_driver = {
+ .driver = {
+ .name = "lenovo-ec-sensors",
+ },
+ .probe = lenovo_ec_probe,
+};
+
+static struct platform_device *lenovo_ec_sensors_platform_device;
+
+static int __init lenovo_ec_init(void)
+{
+ if (!dmi_check_system(thinkstation_dmi_table))
+ return -ENODEV;
+
+ lenovo_ec_sensors_platform_device =
+ platform_create_bundle(&lenovo_ec_sensors_platform_driver,
+ lenovo_ec_probe, NULL, 0, NULL, 0);
+
+ if (IS_ERR(lenovo_ec_sensors_platform_device)) {
+ release_region(IO_REGION_START, IO_REGION_LENGTH);
+ return PTR_ERR(lenovo_ec_sensors_platform_device);
+ }
+
+ return 0;
+}
+module_init(lenovo_ec_init);
+
+static void __exit lenovo_ec_exit(void)
+{
+ release_region(IO_REGION_START, IO_REGION_LENGTH);
+ platform_device_unregister(lenovo_ec_sensors_platform_device);
+ platform_driver_unregister(&lenovo_ec_sensors_platform_driver);
+}
+module_exit(lenovo_ec_exit);
+
+MODULE_AUTHOR("David Ober <dober@lenovo.com>");
+MODULE_DESCRIPTION("HWMON driver for sensors accessible via EC in LENOVO motherboards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/lineage-pem.c b/drivers/hwmon/lineage-pem.c
index df69c380cde7..64a335a64a2e 100644
--- a/drivers/hwmon/lineage-pem.c
+++ b/drivers/hwmon/lineage-pem.c
@@ -502,7 +502,7 @@ static int pem_probe(struct i2c_client *client)
}
static const struct i2c_device_id pem_id[] = {
- {"lineage_pem", 0},
+ {"lineage_pem"},
{}
};
MODULE_DEVICE_TABLE(i2c, pem_id);
diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c
index 0878a044dd8e..035176a98ce9 100644
--- a/drivers/hwmon/lm63.c
+++ b/drivers/hwmon/lm63.c
@@ -1104,10 +1104,7 @@ static int lm63_probe(struct i2c_client *client)
mutex_init(&data->update_lock);
/* Set the device type */
- if (client->dev.of_node)
- data->kind = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- data->kind = i2c_match_id(lm63_id, client)->driver_data;
+ data->kind = (uintptr_t)i2c_get_match_data(client);
if (data->kind == lm64)
data->temp2_offset = 16000;
diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c
index c20a749fc7f2..0d5a250cb672 100644
--- a/drivers/hwmon/lm70.c
+++ b/drivers/hwmon/lm70.c
@@ -6,9 +6,9 @@
* Copyright (C) 2006 Kaiwan N Billimoria <kaiwan@designergraphix.com>
*
* The LM70 communicates with a host processor via an SPI/Microwire Bus
- * interface. The complete datasheet is available at National's website
+ * interface. The complete datasheet is available at TI's website
* here:
- * http://www.national.com/pf/LM/LM70.html
+ * https://www.ti.com/product/LM70
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -169,11 +169,7 @@ static int lm70_probe(struct spi_device *spi)
struct lm70 *p_lm70;
int chip;
- if (dev_fwnode(&spi->dev))
- chip = (int)(uintptr_t)device_get_match_data(&spi->dev);
- else
- chip = spi_get_device_id(spi)->driver_data;
-
+ chip = (kernel_ulong_t)spi_get_device_match_data(spi);
/* signaling is SPI_MODE_0 */
if ((spi->mode & SPI_MODE_X_MASK) != SPI_MODE_0)
diff --git a/drivers/hwmon/lm73.c b/drivers/hwmon/lm73.c
index 637d35c5ae23..581b01572e1b 100644
--- a/drivers/hwmon/lm73.c
+++ b/drivers/hwmon/lm73.c
@@ -220,7 +220,7 @@ lm73_probe(struct i2c_client *client)
}
static const struct i2c_device_id lm73_ids[] = {
- { "lm73", 0 },
+ { "lm73" },
{ /* LIST END */ }
};
MODULE_DEVICE_TABLE(i2c, lm73_ids);
diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c
index 5b2ea05c951e..eda93a8c23c9 100644
--- a/drivers/hwmon/lm75.c
+++ b/drivers/hwmon/lm75.c
@@ -7,11 +7,12 @@
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
+#include <linux/i3c/device.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/regmap.h>
@@ -25,6 +26,7 @@
enum lm75_type { /* keep sorted in alphabetical order */
adt75,
+ as6200,
at30ts74,
ds1775,
ds75,
@@ -37,6 +39,8 @@ enum lm75_type { /* keep sorted in alphabetical order */
max6626,
max31725,
mcp980x,
+ p3t1750,
+ p3t1755,
pct2075,
stds75,
stlm75,
@@ -55,6 +59,7 @@ enum lm75_type { /* keep sorted in alphabetical order */
/**
* struct lm75_params - lm75 configuration parameters.
+ * @config_reg_16bits: Configure register size is 2 bytes.
* @set_mask: Bits to set in configuration register when configuring
* the chip.
* @clr_mask: Bits to clear in configuration register when configuring
@@ -75,17 +80,20 @@ enum lm75_type { /* keep sorted in alphabetical order */
* @sample_times: All the possible sample times to be set. Mandatory if
* num_sample_times is larger than 1. If set, number of
* entries must match num_sample_times.
+ * @alarm: Alarm bit is supported.
*/
struct lm75_params {
- u8 set_mask;
- u8 clr_mask;
+ bool config_reg_16bits;
+ u16 set_mask;
+ u16 clr_mask;
u8 default_resolution;
u8 resolution_limits;
const u8 *resolutions;
unsigned int default_sample_time;
u8 num_sample_times;
const unsigned int *sample_times;
+ bool alarm;
};
/* Addresses scanned */
@@ -99,17 +107,15 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
#define LM75_REG_MAX 0x03
#define PCT2075_REG_IDLE 0x04
-/* Each client has this additional data */
struct lm75_data {
- struct i2c_client *client;
struct regmap *regmap;
- struct regulator *vs;
- u8 orig_conf;
- u8 current_conf;
+ u16 orig_conf;
u8 resolution; /* In bits, 9 to 16 */
unsigned int sample_time; /* In ms */
enum lm75_type kind;
const struct lm75_params *params;
+ u8 reg_buf[1];
+ u8 val_buf[3];
};
/*-----------------------------------------------------------------------*/
@@ -128,6 +134,15 @@ static const struct lm75_params device_params[] = {
.default_resolution = 12,
.default_sample_time = MSEC_PER_SEC / 10,
},
+ [as6200] = {
+ .config_reg_16bits = true,
+ .set_mask = 0x94C0, /* 8 sample/s, 4 CF, positive polarity */
+ .default_resolution = 12,
+ .default_sample_time = 125,
+ .num_sample_times = 4,
+ .sample_times = (unsigned int []){ 125, 250, 1000, 4000 },
+ .alarm = true,
+ },
[at30ts74] = {
.set_mask = 3 << 5, /* 12-bit mode*/
.default_resolution = 12,
@@ -208,6 +223,20 @@ static const struct lm75_params device_params[] = {
.default_resolution = 9,
.default_sample_time = MSEC_PER_SEC / 18,
},
+ [p3t1750] = {
+ .clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
+ .default_resolution = 12,
+ .default_sample_time = 55,
+ .num_sample_times = 4,
+ .sample_times = (unsigned int []){ 28, 55, 110, 220 },
+ },
+ [p3t1755] = {
+ .clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
+ .default_resolution = 12,
+ .default_sample_time = 55,
+ .num_sample_times = 4,
+ .sample_times = (unsigned int []){ 28, 55, 110, 220 },
+ },
[pct2075] = {
.default_resolution = 11,
.default_sample_time = MSEC_PER_SEC / 10,
@@ -255,12 +284,14 @@ static const struct lm75_params device_params[] = {
.resolutions = (u8 []) {9, 10, 11, 12 },
},
[tmp112] = {
- .set_mask = 3 << 5, /* 8 samples / second */
- .clr_mask = 1 << 7, /* no one-shot mode*/
+ .config_reg_16bits = true,
+ .set_mask = 0x60C0, /* 12-bit mode, 8 samples / second */
+ .clr_mask = 1 << 15, /* no one-shot mode*/
.default_resolution = 12,
.default_sample_time = 125,
.num_sample_times = 4,
.sample_times = (unsigned int []){ 125, 250, 1000, 4000 },
+ .alarm = true,
},
[tmp175] = {
.set_mask = 3 << 5, /* 12-bit mode */
@@ -317,25 +348,19 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
}
-static int lm75_write_config(struct lm75_data *data, u8 set_mask,
- u8 clr_mask)
+static inline int lm75_write_config(struct lm75_data *data, u16 set_mask,
+ u16 clr_mask)
{
- u8 value;
-
- clr_mask |= LM75_SHUTDOWN;
- value = data->current_conf & ~clr_mask;
- value |= set_mask;
+ return regmap_update_bits(data->regmap, LM75_REG_CONF,
+ clr_mask | LM75_SHUTDOWN, set_mask);
+}
- if (data->current_conf != value) {
- s32 err;
+static irqreturn_t lm75_alarm_handler(int irq, void *private)
+{
+ struct device *hwmon_dev = private;
- err = i2c_smbus_write_byte_data(data->client, LM75_REG_CONF,
- value);
- if (err)
- return err;
- data->current_conf = value;
- }
- return 0;
+ hwmon_notify_event(hwmon_dev, hwmon_temp, hwmon_temp_alarm, 0);
+ return IRQ_HANDLED;
}
static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
@@ -366,6 +391,9 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_temp_max_hyst:
reg = LM75_REG_HYST;
break;
+ case hwmon_temp_alarm:
+ reg = LM75_REG_CONF;
+ break;
default:
return -EINVAL;
}
@@ -373,7 +401,18 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
if (err < 0)
return err;
- *val = lm75_reg_to_mc(regval, data->resolution);
+ if (attr == hwmon_temp_alarm) {
+ switch (data->kind) {
+ case as6200:
+ case tmp112:
+ *val = (regval >> 13) & 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ *val = lm75_reg_to_mc(regval, data->resolution);
+ }
break;
default:
return -EINVAL;
@@ -417,7 +456,6 @@ static int lm75_write_temp(struct device *dev, u32 attr, long temp)
static int lm75_update_interval(struct device *dev, long val)
{
struct lm75_data *data = dev_get_drvdata(dev);
- unsigned int reg;
u8 index;
s32 err;
@@ -436,19 +474,15 @@ static int lm75_update_interval(struct device *dev, long val)
data->resolution = data->params->resolutions[index];
break;
case tmp112:
- err = regmap_read(data->regmap, LM75_REG_CONF, &reg);
- if (err < 0)
- return err;
- reg &= ~0x00c0;
- reg |= (3 - index) << 6;
- err = regmap_write(data->regmap, LM75_REG_CONF, reg);
+ case as6200:
+ err = regmap_update_bits(data->regmap, LM75_REG_CONF,
+ 0xc000, (3 - index) << 14);
if (err < 0)
return err;
data->sample_time = data->params->sample_times[index];
break;
case pct2075:
- err = i2c_smbus_write_byte_data(data->client, PCT2075_REG_IDLE,
- index + 1);
+ err = regmap_write(data->regmap, PCT2075_REG_IDLE, index + 1);
if (err)
return err;
data->sample_time = data->params->sample_times[index];
@@ -503,6 +537,10 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type,
case hwmon_temp_max:
case hwmon_temp_max_hyst:
return 0644;
+ case hwmon_temp_alarm:
+ if (config_data->params->alarm)
+ return 0444;
+ break;
}
break;
default:
@@ -515,7 +553,8 @@ static const struct hwmon_channel_info * const lm75_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
- HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_ALARM),
NULL
};
@@ -540,6 +579,115 @@ static bool lm75_is_volatile_reg(struct device *dev, unsigned int reg)
return reg == LM75_REG_TEMP || reg == LM75_REG_CONF;
}
+static int lm75_i2c_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct i2c_client *client = context;
+ struct lm75_data *data = i2c_get_clientdata(client);
+ int ret;
+
+ if (reg == LM75_REG_CONF) {
+ if (!data->params->config_reg_16bits)
+ ret = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
+ else
+ ret = i2c_smbus_read_word_data(client, LM75_REG_CONF);
+ } else {
+ ret = i2c_smbus_read_word_swapped(client, reg);
+ }
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return 0;
+}
+
+static int lm75_i2c_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct i2c_client *client = context;
+ struct lm75_data *data = i2c_get_clientdata(client);
+
+ if (reg == PCT2075_REG_IDLE ||
+ (reg == LM75_REG_CONF && !data->params->config_reg_16bits))
+ return i2c_smbus_write_byte_data(client, reg, val);
+ else if (reg == LM75_REG_CONF)
+ return i2c_smbus_write_word_data(client, reg, val);
+ return i2c_smbus_write_word_swapped(client, reg, val);
+}
+
+static const struct regmap_bus lm75_i2c_regmap_bus = {
+ .reg_read = lm75_i2c_reg_read,
+ .reg_write = lm75_i2c_reg_write,
+};
+
+static int lm75_i3c_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct i3c_device *i3cdev = context;
+ struct lm75_data *data = i3cdev_get_drvdata(i3cdev);
+ struct i3c_xfer xfers[] = {
+ {
+ .rnw = false,
+ .len = 1,
+ .data.out = data->reg_buf,
+ },
+ {
+ .rnw = true,
+ .len = 2,
+ .data.in = data->val_buf,
+ },
+ };
+ int ret;
+
+ data->reg_buf[0] = reg;
+
+ if (reg == LM75_REG_CONF && !data->params->config_reg_16bits)
+ xfers[1].len--;
+
+ ret = i3c_device_do_xfers(i3cdev, xfers, 2, I3C_SDR);
+ if (ret < 0)
+ return ret;
+
+ if (reg == LM75_REG_CONF && !data->params->config_reg_16bits)
+ *val = data->val_buf[0];
+ else if (reg == LM75_REG_CONF)
+ *val = data->val_buf[0] | (data->val_buf[1] << 8);
+ else
+ *val = data->val_buf[1] | (data->val_buf[0] << 8);
+
+ return 0;
+}
+
+static int lm75_i3c_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct i3c_device *i3cdev = context;
+ struct lm75_data *data = i3cdev_get_drvdata(i3cdev);
+ struct i3c_xfer xfers[] = {
+ {
+ .rnw = false,
+ .len = 3,
+ .data.out = data->val_buf,
+ },
+ };
+
+ data->val_buf[0] = reg;
+
+ if (reg == PCT2075_REG_IDLE ||
+ (reg == LM75_REG_CONF && !data->params->config_reg_16bits)) {
+ xfers[0].len--;
+ data->val_buf[1] = val & 0xff;
+ } else if (reg == LM75_REG_CONF) {
+ data->val_buf[1] = val & 0xff;
+ data->val_buf[2] = (val >> 8) & 0xff;
+ } else {
+ data->val_buf[1] = (val >> 8) & 0xff;
+ data->val_buf[2] = val & 0xff;
+ }
+
+ return i3c_device_do_xfers(i3cdev, xfers, 1, I3C_SDR);
+}
+
+static const struct regmap_bus lm75_i3c_regmap_bus = {
+ .reg_read = lm75_i3c_reg_read,
+ .reg_write = lm75_i3c_reg_write,
+};
+
static const struct regmap_config lm75_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
@@ -552,54 +700,33 @@ static const struct regmap_config lm75_regmap_config = {
.use_single_write = true,
};
-static void lm75_disable_regulator(void *data)
-{
- struct lm75_data *lm75 = data;
-
- regulator_disable(lm75->vs);
-}
-
static void lm75_remove(void *data)
{
struct lm75_data *lm75 = data;
- struct i2c_client *client = lm75->client;
- i2c_smbus_write_byte_data(client, LM75_REG_CONF, lm75->orig_conf);
+ regmap_write(lm75->regmap, LM75_REG_CONF, lm75->orig_conf);
}
-static const struct i2c_device_id lm75_ids[];
-
-static int lm75_probe(struct i2c_client *client)
+static int lm75_generic_probe(struct device *dev, const char *name,
+ enum lm75_type kind, int irq, struct regmap *regmap)
{
- struct device *dev = &client->dev;
struct device *hwmon_dev;
struct lm75_data *data;
int status, err;
- enum lm75_type kind;
-
- if (client->dev.of_node)
- kind = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- kind = i2c_match_id(lm75_ids, client)->driver_data;
-
- if (!i2c_check_functionality(client->adapter,
- I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
- return -EIO;
data = devm_kzalloc(dev, sizeof(struct lm75_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->client = client;
- data->kind = kind;
+ /* needed by custom regmap callbacks */
+ dev_set_drvdata(dev, data);
- data->vs = devm_regulator_get(dev, "vs");
- if (IS_ERR(data->vs))
- return PTR_ERR(data->vs);
+ data->kind = kind;
+ data->regmap = regmap;
- data->regmap = devm_regmap_init_i2c(client, &lm75_regmap_config);
- if (IS_ERR(data->regmap))
- return PTR_ERR(data->regmap);
+ err = devm_regulator_get_enable(dev, "vs");
+ if (err)
+ return err;
/* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
* Then tweak to be more precise when appropriate.
@@ -611,25 +738,11 @@ static int lm75_probe(struct i2c_client *client)
data->sample_time = data->params->default_sample_time;
data->resolution = data->params->default_resolution;
- /* Enable the power */
- err = regulator_enable(data->vs);
- if (err) {
- dev_err(dev, "failed to enable regulator: %d\n", err);
- return err;
- }
-
- err = devm_add_action_or_reset(dev, lm75_disable_regulator, data);
+ /* Cache original configuration */
+ err = regmap_read(data->regmap, LM75_REG_CONF, &status);
if (err)
return err;
-
- /* Cache original configuration */
- status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
- if (status < 0) {
- dev_dbg(dev, "Can't read config? %d\n", status);
- return status;
- }
data->orig_conf = status;
- data->current_conf = status;
err = lm75_write_config(data, data->params->set_mask,
data->params->clr_mask);
@@ -640,19 +753,53 @@ static int lm75_probe(struct i2c_client *client)
if (err)
return err;
- hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
- data, &lm75_chip_info,
- NULL);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, name, data,
+ &lm75_chip_info, NULL);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
- dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
+ if (irq) {
+ if (data->params->alarm) {
+ err = devm_request_threaded_irq(dev,
+ irq,
+ NULL,
+ &lm75_alarm_handler,
+ IRQF_ONESHOT,
+ name,
+ hwmon_dev);
+ if (err)
+ return err;
+ } else {
+ /* alarm is only supported for chips with alarm bit */
+ dev_err(dev, "alarm interrupt is not supported\n");
+ }
+ }
+
+ dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), name);
return 0;
}
-static const struct i2c_device_id lm75_ids[] = {
+static int lm75_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ regmap = devm_regmap_init(dev, &lm75_i2c_regmap_bus, client, &lm75_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return lm75_generic_probe(dev, client->name, (uintptr_t)i2c_get_match_data(client),
+ client->irq, regmap);
+}
+
+static const struct i2c_device_id lm75_i2c_ids[] = {
{ "adt75", adt75, },
+ { "as6200", as6200, },
{ "at30ts74", at30ts74, },
{ "ds1775", ds1775, },
{ "ds75", ds75, },
@@ -666,6 +813,8 @@ static const struct i2c_device_id lm75_ids[] = {
{ "max31725", max31725, },
{ "max31726", max31725, },
{ "mcp980x", mcp980x, },
+ { "p3t1750", p3t1750, },
+ { "p3t1755", p3t1755, },
{ "pct2075", pct2075, },
{ "stds75", stds75, },
{ "stlm75", stlm75, },
@@ -682,7 +831,38 @@ static const struct i2c_device_id lm75_ids[] = {
{ "tmp1075", tmp1075, },
{ /* LIST END */ }
};
-MODULE_DEVICE_TABLE(i2c, lm75_ids);
+MODULE_DEVICE_TABLE(i2c, lm75_i2c_ids);
+
+struct lm75_i3c_device {
+ enum lm75_type type;
+ const char *name;
+};
+
+static const struct lm75_i3c_device lm75_i3c_p3t1755 = {
+ .name = "p3t1755",
+ .type = p3t1755,
+};
+
+static const struct i3c_device_id lm75_i3c_ids[] = {
+ I3C_DEVICE(0x011b, 0x152a, &lm75_i3c_p3t1755),
+ { /* LIST END */ }
+};
+MODULE_DEVICE_TABLE(i3c, lm75_i3c_ids);
+
+static int lm75_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ const struct lm75_i3c_device *id_data;
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init(dev, &lm75_i3c_regmap_bus, i3cdev, &lm75_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ id_data = i3c_device_match_id(i3cdev, lm75_i3c_ids)->data;
+
+ return lm75_generic_probe(dev, id_data->name, id_data->type, 0, regmap);
+}
static const struct of_device_id __maybe_unused lm75_of_match[] = {
{
@@ -690,6 +870,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
.data = (void *)adt75
},
{
+ .compatible = "ams,as6200",
+ .data = (void *)as6200
+ },
+ {
.compatible = "atmel,at30ts74",
.data = (void *)at30ts74
},
@@ -742,6 +926,14 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
.data = (void *)mcp980x
},
{
+ .compatible = "nxp,p3t1750",
+ .data = (void *)p3t1750
+ },
+ {
+ .compatible = "nxp,p3t1755",
+ .data = (void *)p3t1755
+ },
+ {
.compatible = "nxp,pct2075",
.data = (void *)pct2075
},
@@ -900,32 +1092,16 @@ static int lm75_detect(struct i2c_client *new_client,
#ifdef CONFIG_PM
static int lm75_suspend(struct device *dev)
{
- int status;
- struct i2c_client *client = to_i2c_client(dev);
+ struct lm75_data *data = dev_get_drvdata(dev);
- status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
- if (status < 0) {
- dev_dbg(&client->dev, "Can't read config? %d\n", status);
- return status;
- }
- status = status | LM75_SHUTDOWN;
- i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
- return 0;
+ return regmap_update_bits(data->regmap, LM75_REG_CONF, LM75_SHUTDOWN, LM75_SHUTDOWN);
}
static int lm75_resume(struct device *dev)
{
- int status;
- struct i2c_client *client = to_i2c_client(dev);
+ struct lm75_data *data = dev_get_drvdata(dev);
- status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
- if (status < 0) {
- dev_dbg(&client->dev, "Can't read config? %d\n", status);
- return status;
- }
- status = status & ~LM75_SHUTDOWN;
- i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
- return 0;
+ return regmap_update_bits(data->regmap, LM75_REG_CONF, LM75_SHUTDOWN, 0);
}
static const struct dev_pm_ops lm75_dev_pm_ops = {
@@ -937,20 +1113,28 @@ static const struct dev_pm_ops lm75_dev_pm_ops = {
#define LM75_DEV_PM_OPS NULL
#endif /* CONFIG_PM */
-static struct i2c_driver lm75_driver = {
+static struct i2c_driver lm75_i2c_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "lm75",
.of_match_table = of_match_ptr(lm75_of_match),
.pm = LM75_DEV_PM_OPS,
},
- .probe = lm75_probe,
- .id_table = lm75_ids,
+ .probe = lm75_i2c_probe,
+ .id_table = lm75_i2c_ids,
.detect = lm75_detect,
.address_list = normal_i2c,
};
-module_i2c_driver(lm75_driver);
+static struct i3c_driver lm75_i3c_driver = {
+ .driver = {
+ .name = "lm75_i3c",
+ },
+ .probe = lm75_i3c_probe,
+ .id_table = lm75_i3c_ids,
+};
+
+module_i3c_i2c_driver(lm75_i3c_driver, &lm75_i2c_driver)
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_DESCRIPTION("LM75 driver");
diff --git a/drivers/hwmon/lm77.c b/drivers/hwmon/lm77.c
index 8b9862519178..80f7a6a3f9a2 100644
--- a/drivers/hwmon/lm77.c
+++ b/drivers/hwmon/lm77.c
@@ -337,7 +337,7 @@ static int lm77_probe(struct i2c_client *client)
}
static const struct i2c_device_id lm77_id[] = {
- { "lm77", 0 },
+ { "lm77" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm77_id);
diff --git a/drivers/hwmon/lm78.c b/drivers/hwmon/lm78.c
index b739c354311b..9378a47bf5af 100644
--- a/drivers/hwmon/lm78.c
+++ b/drivers/hwmon/lm78.c
@@ -627,8 +627,6 @@ static int lm78_i2c_detect(struct i2c_client *client,
return -ENODEV;
}
-static const struct i2c_device_id lm78_i2c_id[];
-
static int lm78_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -640,7 +638,7 @@ static int lm78_i2c_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- data->type = i2c_match_id(lm78_i2c_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
/* Initialize the LM78 chip */
lm78_init_device(data);
@@ -845,17 +843,18 @@ static int __init lm78_isa_found(unsigned short address)
}
}
-#define REALLY_SLOW_IO
/*
* We need the timeouts for at least some LM78-like
* chips. But only if we read 'undefined' registers.
+ * There used to be a "#define REALLY_SLOW_IO" to enforce that, but
+ * this has been without any effect since more than a decade, so it
+ * has been dropped.
*/
val = inb_p(address + 1);
if (inb_p(address + 2) != val
|| inb_p(address + 3) != val
|| inb_p(address + 7) != val)
goto release;
-#undef REALLY_SLOW_IO
/*
* We should be able to change the 7 LSB of the address port. The
diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c
index 5befedca6abb..f800fe2ef18b 100644
--- a/drivers/hwmon/lm83.c
+++ b/drivers/hwmon/lm83.c
@@ -165,7 +165,7 @@ static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config lm83_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = lm83_regmap_is_volatile,
.reg_read = lm83_regmap_reg_read,
.reg_write = lm83_regmap_reg_write,
@@ -417,13 +417,6 @@ static int lm83_detect(struct i2c_client *client,
return 0;
}
-static const struct i2c_device_id lm83_id[] = {
- { "lm83", lm83 },
- { "lm82", lm82 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, lm83_id);
-
static int lm83_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -438,7 +431,7 @@ static int lm83_probe(struct i2c_client *client)
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
- data->type = i2c_match_id(lm83_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data, &lm83_chip_info, NULL);
@@ -449,6 +442,13 @@ static int lm83_probe(struct i2c_client *client)
* Driver data (common to all clients)
*/
+static const struct i2c_device_id lm83_id[] = {
+ { "lm83", lm83 },
+ { "lm82", lm82 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm83_id);
+
static struct i2c_driver lm83_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c
index 68c210002357..1c244ed75122 100644
--- a/drivers/hwmon/lm85.c
+++ b/drivers/hwmon/lm85.c
@@ -1544,8 +1544,6 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info)
return 0;
}
-static const struct i2c_device_id lm85_id[];
-
static int lm85_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -1558,10 +1556,7 @@ static int lm85_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- if (client->dev.of_node)
- data->type = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- data->type = i2c_match_id(lm85_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
mutex_init(&data->update_lock);
/* Fill in the chip specific driver values */
diff --git a/drivers/hwmon/lm87.c b/drivers/hwmon/lm87.c
index 2195a735d28e..37bf2d1d3d09 100644
--- a/drivers/hwmon/lm87.c
+++ b/drivers/hwmon/lm87.c
@@ -116,8 +116,14 @@ static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C };
(((val) < 0 ? (val) - 500 : \
(val) + 500) / 1000))
-#define FAN_FROM_REG(reg, div) ((reg) == 255 || (reg) == 0 ? 0 : \
- (1350000 + (reg)*(div) / 2) / ((reg) * (div)))
+static int fan_from_reg(int reg, int div)
+{
+ if (reg == 255 || reg == 0)
+ return 0;
+
+ return (1350000 + reg * div / 2) / (reg * div);
+}
+
#define FAN_TO_REG(val, div) ((val) * (div) * 255 <= 1350000 ? 255 : \
(1350000 + (val)*(div) / 2) / ((val) * (div)))
@@ -465,7 +471,7 @@ static ssize_t fan_input_show(struct device *dev,
struct lm87_data *data = lm87_update_device(dev);
int nr = to_sensor_dev_attr(attr)->index;
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan[nr],
FAN_DIV_FROM_REG(data->fan_div[nr])));
}
@@ -475,7 +481,7 @@ static ssize_t fan_min_show(struct device *dev, struct device_attribute *attr,
struct lm87_data *data = lm87_update_device(dev);
int nr = to_sensor_dev_attr(attr)->index;
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan_min[nr],
FAN_DIV_FROM_REG(data->fan_div[nr])));
}
@@ -534,7 +540,7 @@ static ssize_t fan_div_store(struct device *dev,
return err;
mutex_lock(&data->update_lock);
- min = FAN_FROM_REG(data->fan_min[nr],
+ min = fan_from_reg(data->fan_min[nr],
FAN_DIV_FROM_REG(data->fan_div[nr]));
switch (val) {
@@ -975,8 +981,8 @@ static int lm87_probe(struct i2c_client *client)
*/
static const struct i2c_device_id lm87_id[] = {
- { "lm87", 0 },
- { "adm1024", 0 },
+ { "lm87" },
+ { "adm1024" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm87_id);
diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c
index e0d7454a301c..3c10a5066b53 100644
--- a/drivers/hwmon/lm90.c
+++ b/drivers/hwmon/lm90.c
@@ -90,6 +90,9 @@
* This driver also supports NE1618 from Philips. It is similar to NE1617
* but supports 11 bit external temperature values.
*
+ * This driver also supports NCT7716, NCT7717 and NCT7718 from Nuvoton.
+ * The NCT7716 is similar to NCT7717 but has one more address support.
+ *
* Since the LM90 was the first chipset supported by this driver, most
* comments will refer to this chipset, but are actually general and
* concern all supported chipsets, unless mentioned otherwise.
@@ -105,7 +108,6 @@
#include <linux/hwmon.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
@@ -119,13 +121,15 @@
* Address is fully defined internally and cannot be changed except for
* MAX6659, MAX6680 and MAX6681.
* LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, ADT7461A, MAX6649,
- * MAX6657, MAX6658, NCT1008 and W83L771 have address 0x4c.
+ * MAX6657, MAX6658, NCT1008, NCT7718 and W83L771 have address 0x4c.
* ADM1032-2, ADT7461-2, ADT7461A-2, LM89-1, LM99-1, MAX6646, and NCT1008D
* have address 0x4d.
* MAX6647 has address 0x4e.
* MAX6659 can have address 0x4c, 0x4d or 0x4e.
* MAX6654, MAX6680, and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29,
* 0x2a, 0x2b, 0x4c, 0x4d or 0x4e.
+ * NCT7716 can have address 0x48 or 0x49.
+ * NCT7717 has address 0x48.
* SA56004 can have address 0x48 through 0x4F.
*/
@@ -136,7 +140,7 @@ static const unsigned short normal_i2c[] = {
enum chips { adm1023, adm1032, adt7461, adt7461a, adt7481,
g781, lm84, lm90, lm99,
max1617, max6642, max6646, max6648, max6654, max6657, max6659, max6680, max6696,
- nct210, nct72, ne1618, sa56004, tmp451, tmp461, w83l771,
+ nct210, nct72, nct7716, nct7717, nct7718, ne1618, sa56004, tmp451, tmp461, w83l771,
};
/*
@@ -191,6 +195,9 @@ enum chips { adm1023, adm1032, adt7461, adt7461a, adt7481,
#define ADT7481_REG_MAN_ID 0x3e
#define ADT7481_REG_CHIP_ID 0x3d
+/* NCT7716/7717/7718 registers */
+#define NCT7716_REG_CHIP_ID 0xFD
+
/* Device features */
#define LM90_HAVE_EXTENDED_TEMP BIT(0) /* extended temperature support */
#define LM90_HAVE_OFFSET BIT(1) /* temperature offset register */
@@ -275,6 +282,9 @@ static const struct i2c_device_id lm90_id[] = {
{ "nct214", nct72 },
{ "nct218", nct72 },
{ "nct72", nct72 },
+ { "nct7716", nct7716 },
+ { "nct7717", nct7717 },
+ { "nct7718", nct7718 },
{ "ne1618", ne1618 },
{ "w83l771", w83l771 },
{ "sa56004", sa56004 },
@@ -383,6 +393,18 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = {
.data = (void *)nct72
},
{
+ .compatible = "nuvoton,nct7716",
+ .data = (void *)nct7716
+ },
+ {
+ .compatible = "nuvoton,nct7717",
+ .data = (void *)nct7717
+ },
+ {
+ .compatible = "nuvoton,nct7718",
+ .data = (void *)nct7718
+ },
+ {
.compatible = "winbond,w83l771",
.data = (void *)w83l771
},
@@ -601,6 +623,26 @@ static const struct lm90_params lm90_params[] = {
.resolution = 11,
.max_convrate = 7,
},
+ [nct7716] = {
+ .flags = LM90_HAVE_ALARMS | LM90_HAVE_CONVRATE,
+ .alert_alarms = 0x40,
+ .resolution = 8,
+ .max_convrate = 8,
+ },
+ [nct7717] = {
+ .flags = LM90_HAVE_ALARMS | LM90_HAVE_CONVRATE,
+ .alert_alarms = 0x40,
+ .resolution = 8,
+ .max_convrate = 8,
+ },
+ [nct7718] = {
+ .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT | LM90_HAVE_CRIT
+ | LM90_HAVE_ALARMS | LM90_HAVE_LOW | LM90_HAVE_CONVRATE
+ | LM90_HAVE_REMOTE_EXT,
+ .alert_alarms = 0x7c,
+ .resolution = 11,
+ .max_convrate = 8,
+ },
[ne1618] = {
.flags = LM90_PAUSE_FOR_CONFIG | LM90_HAVE_BROKEN_ALERT
| LM90_HAVE_LOW | LM90_HAVE_CONVRATE | LM90_HAVE_REMOTE_EXT,
@@ -692,7 +734,6 @@ struct lm90_data {
struct hwmon_channel_info temp_info;
const struct hwmon_channel_info *info[3];
struct hwmon_chip_info chip;
- struct mutex update_lock;
struct delayed_work alert_work;
struct work_struct report_work;
bool valid; /* true if register values are valid */
@@ -1183,16 +1224,16 @@ static int lm90_update_alarms(struct lm90_data *data, bool force)
{
int err;
- mutex_lock(&data->update_lock);
+ hwmon_lock(data->hwmon_dev);
err = lm90_update_alarms_locked(data, force);
- mutex_unlock(&data->update_lock);
+ hwmon_unlock(data->hwmon_dev);
return err;
}
static void lm90_alert_work(struct work_struct *__work)
{
- struct delayed_work *delayed_work = container_of(__work, struct delayed_work, work);
+ struct delayed_work *delayed_work = to_delayed_work(__work);
struct lm90_data *data = container_of(delayed_work, struct lm90_data, alert_work);
/* Nothing to do if alerts are enabled */
@@ -1270,42 +1311,6 @@ static int lm90_update_device(struct device *dev)
return 0;
}
-/* pec used for devices with PEC support */
-static ssize_t pec_show(struct device *dev, struct device_attribute *dummy,
- char *buf)
-{
- struct i2c_client *client = to_i2c_client(dev);
-
- return sprintf(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC));
-}
-
-static ssize_t pec_store(struct device *dev, struct device_attribute *dummy,
- const char *buf, size_t count)
-{
- struct i2c_client *client = to_i2c_client(dev);
- long val;
- int err;
-
- err = kstrtol(buf, 10, &val);
- if (err < 0)
- return err;
-
- switch (val) {
- case 0:
- client->flags &= ~I2C_CLIENT_PEC;
- break;
- case 1:
- client->flags |= I2C_CLIENT_PEC;
- break;
- default:
- return -EINVAL;
- }
-
- return count;
-}
-
-static DEVICE_ATTR_RW(pec);
-
static int lm90_temp_get_resolution(struct lm90_data *data, int index)
{
switch (index) {
@@ -1512,9 +1517,7 @@ static int lm90_temp_read(struct device *dev, u32 attr, int channel, long *val)
int err;
u16 bit;
- mutex_lock(&data->update_lock);
err = lm90_update_device(dev);
- mutex_unlock(&data->update_lock);
if (err)
return err;
@@ -1583,11 +1586,9 @@ static int lm90_temp_write(struct device *dev, u32 attr, int channel, long val)
struct lm90_data *data = dev_get_drvdata(dev);
int err;
- mutex_lock(&data->update_lock);
-
err = lm90_update_device(dev);
if (err)
- goto error;
+ return err;
switch (attr) {
case hwmon_temp_min:
@@ -1617,9 +1618,6 @@ static int lm90_temp_write(struct device *dev, u32 attr, int channel, long val)
err = -EOPNOTSUPP;
break;
}
-error:
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -1655,9 +1653,7 @@ static int lm90_chip_read(struct device *dev, u32 attr, int channel, long *val)
struct lm90_data *data = dev_get_drvdata(dev);
int err;
- mutex_lock(&data->update_lock);
err = lm90_update_device(dev);
- mutex_unlock(&data->update_lock);
if (err)
return err;
@@ -1703,11 +1699,9 @@ static int lm90_chip_write(struct device *dev, u32 attr, int channel, long val)
struct i2c_client *client = data->client;
int err;
- mutex_lock(&data->update_lock);
-
err = lm90_update_device(dev);
if (err)
- goto error;
+ return err;
switch (attr) {
case hwmon_chip_update_interval:
@@ -1721,9 +1715,6 @@ static int lm90_chip_write(struct device *dev, u32 attr, int channel, long val)
err = -EOPNOTSUPP;
break;
}
-error:
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -2336,6 +2327,38 @@ static const char *lm90_detect_nuvoton(struct i2c_client *client, int chip_id,
return name;
}
+static const char *lm90_detect_nuvoton_50(struct i2c_client *client, int chip_id,
+ int config1, int convrate)
+{
+ int chip_id2 = i2c_smbus_read_byte_data(client, NCT7716_REG_CHIP_ID);
+ int config2 = i2c_smbus_read_byte_data(client, LM90_REG_CONFIG2);
+ int address = client->addr;
+ const char *name = NULL;
+
+ if (chip_id2 < 0 || config2 < 0)
+ return NULL;
+
+ if (chip_id2 != 0x50 || convrate > 0x08)
+ return NULL;
+
+ switch (chip_id) {
+ case 0x90:
+ if (address == 0x48 && !(config1 & 0x3e) && !(config2 & 0xfe))
+ name = "nct7717";
+ break;
+ case 0x91:
+ if ((address == 0x48 || address == 0x49) && !(config1 & 0x3e) &&
+ !(config2 & 0xfe))
+ name = "nct7716";
+ else if (address == 0x4c && !(config1 & 0x38) && !(config2 & 0xf8))
+ name = "nct7718";
+ break;
+ default:
+ break;
+ }
+ return name;
+}
+
static const char *lm90_detect_nxp(struct i2c_client *client, bool common_address,
int chip_id, int config1, int convrate)
{
@@ -2520,6 +2543,9 @@ static int lm90_detect(struct i2c_client *client, struct i2c_board_info *info)
name = lm90_detect_maxim(client, common_address, chip_id,
config1, convrate);
break;
+ case 0x50:
+ name = lm90_detect_nuvoton_50(client, chip_id, config1, convrate);
+ break;
case 0x54: /* ON MC1066, Microchip TC1068, TCM1617 (originally TelCom) */
if (common_address && !(config1 & 0x3f) && !(convrate & 0xf8))
name = "mc1066";
@@ -2659,11 +2685,6 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id)
return IRQ_NONE;
}
-static void lm90_remove_pec(void *dev)
-{
- device_remove_file(dev, &dev_attr_pec);
-}
-
static int lm90_probe_channel_from_dt(struct i2c_client *client,
struct device_node *child,
struct lm90_data *data)
@@ -2715,19 +2736,16 @@ static int lm90_parse_dt_channel_info(struct i2c_client *client,
struct lm90_data *data)
{
int err;
- struct device_node *child;
struct device *dev = &client->dev;
const struct device_node *np = dev->of_node;
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
if (strcmp(child->name, "channel"))
continue;
err = lm90_probe_channel_from_dt(client, child, data);
- if (err) {
- of_node_put(child);
+ if (err)
return err;
- }
}
return 0;
@@ -2759,15 +2777,11 @@ static int lm90_probe(struct i2c_client *client)
data->client = client;
i2c_set_clientdata(client, data);
- mutex_init(&data->update_lock);
INIT_DELAYED_WORK(&data->alert_work, lm90_alert_work);
INIT_WORK(&data->report_work, lm90_report_alarms);
/* Set the device type */
- if (client->dev.of_node)
- data->kind = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- data->kind = i2c_match_id(lm90_id, client)->driver_data;
+ data->kind = (uintptr_t)i2c_get_match_data(client);
/*
* Different devices have different alarm bits triggering the
@@ -2802,6 +2816,8 @@ static int lm90_probe(struct i2c_client *client)
data->chip_config[0] |= HWMON_C_UPDATE_INTERVAL;
if (data->flags & LM90_HAVE_FAULTQUEUE)
data->chip_config[0] |= HWMON_C_TEMP_SAMPLES;
+ if (data->flags & (LM90_HAVE_PEC | LM90_HAVE_PARTIAL_PEC))
+ data->chip_config[0] |= HWMON_C_PEC;
data->info[1] = &data->temp_info;
info = &data->temp_info;
@@ -2878,19 +2894,6 @@ static int lm90_probe(struct i2c_client *client)
return err;
}
- /*
- * The 'pec' attribute is attached to the i2c device and thus created
- * separately.
- */
- if (data->flags & (LM90_HAVE_PEC | LM90_HAVE_PARTIAL_PEC)) {
- err = device_create_file(dev, &dev_attr_pec);
- if (err)
- return err;
- err = devm_add_action_or_reset(dev, lm90_remove_pec, dev);
- if (err)
- return err;
- }
-
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data, &data->chip,
NULL);
diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c
index 46579a3e1715..91a6b7525bb6 100644
--- a/drivers/hwmon/lm92.c
+++ b/drivers/hwmon/lm92.c
@@ -27,15 +27,13 @@
* with the LM92.
*/
-#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/jiffies.h>
/*
* The LM92 and MAX6635 have 2 two-state pins for address selection,
@@ -43,8 +41,6 @@
*/
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b,
I2C_CLIENT_END };
-enum chips { lm92, max6635 };
-
/* The LM92 registers */
#define LM92_REG_CONFIG 0x01 /* 8-bit, RW */
#define LM92_REG_TEMP 0x00 /* 16-bit, RO */
@@ -66,10 +62,10 @@ static inline int TEMP_FROM_REG(s16 reg)
return reg / 8 * 625 / 10;
}
-static inline s16 TEMP_TO_REG(long val)
+static inline s16 TEMP_TO_REG(long val, int resolution)
{
val = clamp_val(val, -60000, 160000);
- return val * 10 / 625 * 8;
+ return DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution);
}
/* Alarm flags are stored in the 3 LSB of the temperature register */
@@ -78,239 +74,330 @@ static inline u8 ALARMS_FROM_REG(s16 reg)
return reg & 0x0007;
}
-enum temp_index {
- t_input,
- t_crit,
- t_min,
- t_max,
- t_hyst,
- t_num_regs
-};
-
-static const u8 regs[t_num_regs] = {
- [t_input] = LM92_REG_TEMP,
- [t_crit] = LM92_REG_TEMP_CRIT,
- [t_min] = LM92_REG_TEMP_LOW,
- [t_max] = LM92_REG_TEMP_HIGH,
- [t_hyst] = LM92_REG_TEMP_HYST,
-};
-
/* Client data (each client gets its own) */
struct lm92_data {
- struct i2c_client *client;
- struct mutex update_lock;
- bool valid; /* false until following fields are valid */
- unsigned long last_updated; /* in jiffies */
-
- /* registers values */
- s16 temp[t_num_regs]; /* index with enum temp_index */
+ struct regmap *regmap;
+ int resolution;
};
-/*
- * Sysfs attributes and callback functions
- */
-
-static struct lm92_data *lm92_update_device(struct device *dev)
+static int lm92_temp_read(struct lm92_data *data, u32 attr, int channel, long *val)
{
- struct lm92_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int i;
-
- mutex_lock(&data->update_lock);
-
- if (time_after(jiffies, data->last_updated + HZ) ||
- !data->valid) {
- dev_dbg(&client->dev, "Updating lm92 data\n");
- for (i = 0; i < t_num_regs; i++) {
- data->temp[i] =
- i2c_smbus_read_word_swapped(client, regs[i]);
- }
- data->last_updated = jiffies;
- data->valid = true;
+ int reg = -1, hyst_reg = -1, alarm_bit = 0;
+ struct regmap *regmap = data->regmap;
+ u32 temp;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = LM92_REG_TEMP;
+ break;
+ case hwmon_temp_min:
+ reg = LM92_REG_TEMP_LOW;
+ break;
+ case hwmon_temp_max:
+ reg = LM92_REG_TEMP_HIGH;
+ break;
+ case hwmon_temp_crit:
+ reg = LM92_REG_TEMP_CRIT;
+ break;
+ case hwmon_temp_min_hyst:
+ hyst_reg = LM92_REG_TEMP_LOW;
+ break;
+ case hwmon_temp_max_hyst:
+ hyst_reg = LM92_REG_TEMP_HIGH;
+ break;
+ case hwmon_temp_crit_hyst:
+ hyst_reg = LM92_REG_TEMP_CRIT;
+ break;
+ case hwmon_temp_min_alarm:
+ alarm_bit = 0;
+ break;
+ case hwmon_temp_max_alarm:
+ alarm_bit = 1;
+ break;
+ case hwmon_temp_crit_alarm:
+ alarm_bit = 2;
+ break;
+ default:
+ return -EOPNOTSUPP;
}
-
- mutex_unlock(&data->update_lock);
-
- return data;
+ if (reg >= 0) {
+ ret = regmap_read(regmap, reg, &temp);
+ if (ret < 0)
+ return ret;
+ *val = TEMP_FROM_REG(temp);
+ } else if (hyst_reg >= 0) {
+ u32 regs[2] = { hyst_reg, LM92_REG_TEMP_HYST };
+ u16 regvals[2];
+
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
+ if (ret)
+ return ret;
+ if (attr == hwmon_temp_min_hyst)
+ *val = TEMP_FROM_REG(regvals[0]) + TEMP_FROM_REG(regvals[1]);
+ else
+ *val = TEMP_FROM_REG(regvals[0]) - TEMP_FROM_REG(regvals[1]);
+ } else {
+ ret = regmap_read(regmap, LM92_REG_TEMP, &temp);
+ if (ret)
+ return ret;
+ *val = !!(temp & BIT(alarm_bit));
+ }
+ return 0;
}
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
+static int lm92_chip_read(struct lm92_data *data, u32 attr, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct lm92_data *data = lm92_update_device(dev);
-
- return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]));
+ u32 temp;
+ int ret;
+
+ switch (attr) {
+ case hwmon_chip_alarms:
+ ret = regmap_read(data->regmap, LM92_REG_TEMP, &temp);
+ if (ret)
+ return ret;
+ *val = ALARMS_FROM_REG(temp);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t temp_store(struct device *dev,
- struct device_attribute *devattr, const char *buf,
- size_t count)
+static int lm92_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm92_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int nr = attr->index;
- long val;
- int err;
-
- err = kstrtol(buf, 10, &val);
- if (err)
- return err;
-
- mutex_lock(&data->update_lock);
- data->temp[nr] = TEMP_TO_REG(val);
- i2c_smbus_write_word_swapped(client, regs[nr], data->temp[nr]);
- mutex_unlock(&data->update_lock);
- return count;
-}
-static ssize_t temp_hyst_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct lm92_data *data = lm92_update_device(dev);
-
- return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])
- - TEMP_FROM_REG(data->temp[t_hyst]));
+ switch (type) {
+ case hwmon_chip:
+ return lm92_chip_read(data, attr, val);
+ case hwmon_temp:
+ return lm92_temp_read(data, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t temp1_min_hyst_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static int lm92_temp_write(struct lm92_data *data, u32 attr, long val)
{
- struct lm92_data *data = lm92_update_device(dev);
-
- return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[t_min])
- + TEMP_FROM_REG(data->temp[t_hyst]));
+ struct regmap *regmap = data->regmap;
+ int reg, err;
+ u32 temp;
+
+ switch (attr) {
+ case hwmon_temp_min:
+ reg = LM92_REG_TEMP_LOW;
+ break;
+ case hwmon_temp_max:
+ reg = LM92_REG_TEMP_HIGH;
+ break;
+ case hwmon_temp_crit:
+ reg = LM92_REG_TEMP_CRIT;
+ break;
+ case hwmon_temp_crit_hyst:
+ val = clamp_val(val, -120000, 220000);
+ err = regmap_read(regmap, LM92_REG_TEMP_CRIT, &temp);
+ if (err)
+ return err;
+ val = TEMP_TO_REG(TEMP_FROM_REG(temp) - val, data->resolution);
+ return regmap_write(regmap, LM92_REG_TEMP_HYST, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return regmap_write(regmap, reg, TEMP_TO_REG(val, data->resolution));
}
-static ssize_t temp_hyst_store(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
+static int lm92_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm92_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int err;
- err = kstrtol(buf, 10, &val);
- if (err)
- return err;
-
- val = clamp_val(val, -120000, 220000);
- mutex_lock(&data->update_lock);
- data->temp[t_hyst] =
- TEMP_TO_REG(TEMP_FROM_REG(data->temp[attr->index]) - val);
- i2c_smbus_write_word_swapped(client, LM92_REG_TEMP_HYST,
- data->temp[t_hyst]);
- mutex_unlock(&data->update_lock);
- return count;
+ switch (type) {
+ case hwmon_temp:
+ return lm92_temp_write(data, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t alarms_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static umode_t lm92_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
- struct lm92_data *data = lm92_update_device(dev);
-
- return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->temp[t_input]));
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_alarms:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ case hwmon_temp_crit_hyst:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_min_hyst:
+ case hwmon_temp_max_hyst:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
}
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- int bitnr = to_sensor_dev_attr(attr)->index;
- struct lm92_data *data = lm92_update_device(dev);
- return sprintf(buf, "%d\n", (data->temp[t_input] >> bitnr) & 1);
-}
+static const struct hwmon_channel_info * const lm92_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT |
+ HWMON_T_MIN | HWMON_T_MIN_HYST |
+ HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_CRIT | HWMON_T_CRIT_HYST |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_CRIT_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops lm92_hwmon_ops = {
+ .is_visible = lm92_is_visible,
+ .read = lm92_read,
+ .write = lm92_write,
+};
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit, temp, t_crit);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, temp_hyst, t_crit);
-static SENSOR_DEVICE_ATTR_RW(temp1_min, temp, t_min);
-static DEVICE_ATTR_RO(temp1_min_hyst);
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, t_max);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_hyst, temp_hyst, t_max);
-static DEVICE_ATTR_RO(alarms);
-static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 2);
-static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 0);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 1);
+static const struct hwmon_chip_info lm92_chip_info = {
+ .ops = &lm92_hwmon_ops,
+ .info = lm92_info,
+};
/*
* Detection and registration
*/
-static void lm92_init_client(struct i2c_client *client)
+static int lm92_init_client(struct regmap *regmap)
{
- u8 config;
-
- /* Start the conversions if needed */
- config = i2c_smbus_read_byte_data(client, LM92_REG_CONFIG);
- if (config & 0x01)
- i2c_smbus_write_byte_data(client, LM92_REG_CONFIG,
- config & 0xFE);
+ return regmap_clear_bits(regmap, LM92_REG_CONFIG, 0x01);
}
-static struct attribute *lm92_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &dev_attr_temp1_min_hyst.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
- &dev_attr_alarms.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(lm92);
-
/* Return 0 if detection is successful, -ENODEV otherwise */
static int lm92_detect(struct i2c_client *new_client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = new_client->adapter;
- u8 config;
- u16 man_id;
+ u8 config_addr = LM92_REG_CONFIG;
+ u8 man_id_addr = LM92_REG_MAN_ID;
+ int i, regval;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
- config = i2c_smbus_read_byte_data(new_client, LM92_REG_CONFIG);
- man_id = i2c_smbus_read_word_data(new_client, LM92_REG_MAN_ID);
-
- if ((config & 0xe0) == 0x00 && man_id == 0x0180)
- pr_info("lm92: Found National Semiconductor LM92 chip\n");
- else
- return -ENODEV;
+ /*
+ * Register values repeat with multiples of 8.
+ * Read twice to improve detection accuracy.
+ */
+ for (i = 0; i < 2; i++) {
+ regval = i2c_smbus_read_word_data(new_client, man_id_addr);
+ if (regval != 0x0180)
+ return -ENODEV;
+ regval = i2c_smbus_read_byte_data(new_client, config_addr);
+ if (regval < 0 || (regval & 0xe0))
+ return -ENODEV;
+ config_addr += 8;
+ man_id_addr += 8;
+ }
strscpy(info->type, "lm92", I2C_NAME_SIZE);
return 0;
}
-static int lm92_probe(struct i2c_client *new_client)
+/* regmap */
+
+static int lm92_reg_read(void *context, unsigned int reg, unsigned int *val)
{
+ int ret;
+
+ if (reg == LM92_REG_CONFIG)
+ ret = i2c_smbus_read_byte_data(context, reg);
+ else
+ ret = i2c_smbus_read_word_swapped(context, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+
+static int lm92_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ if (reg == LM92_REG_CONFIG)
+ return i2c_smbus_write_byte_data(context, LM92_REG_CONFIG, val);
+
+ return i2c_smbus_write_word_swapped(context, reg, val);
+}
+
+static bool lm92_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+ return reg == LM92_REG_TEMP;
+}
+
+static bool lm92_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+ return reg >= LM92_REG_CONFIG;
+}
+
+static const struct regmap_config lm92_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = LM92_REG_TEMP_HIGH,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = lm92_regmap_is_volatile,
+ .writeable_reg = lm92_regmap_is_writeable,
+};
+
+static const struct regmap_bus lm92_regmap_bus = {
+ .reg_write = lm92_reg_write,
+ .reg_read = lm92_reg_read,
+};
+
+static int lm92_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
struct device *hwmon_dev;
struct lm92_data *data;
+ struct regmap *regmap;
+ int err;
+
+ regmap = devm_regmap_init(dev, &lm92_regmap_bus, client,
+ &lm92_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
- data = devm_kzalloc(&new_client->dev, sizeof(struct lm92_data),
- GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(struct lm92_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->client = new_client;
- mutex_init(&data->update_lock);
+ data->regmap = regmap;
+ data->resolution = (unsigned long)i2c_get_match_data(client);
/* Initialize the chipset */
- lm92_init_client(new_client);
+ err = lm92_init_client(regmap);
+ if (err)
+ return err;
- hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev,
- new_client->name,
- data, lm92_groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ &lm92_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
@@ -318,9 +405,10 @@ static int lm92_probe(struct i2c_client *new_client)
* Module and driver stuff
*/
+/* .driver_data is limit register resolution */
static const struct i2c_device_id lm92_id[] = {
- { "lm92", lm92 },
- { "max6635", max6635 },
+ { "lm92", 13 },
+ { "max6635", 9 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm92_id);
diff --git a/drivers/hwmon/lm93.c b/drivers/hwmon/lm93.c
index 75bca805720e..be4853fad80f 100644
--- a/drivers/hwmon/lm93.c
+++ b/drivers/hwmon/lm93.c
@@ -2624,8 +2624,8 @@ static int lm93_probe(struct i2c_client *client)
}
static const struct i2c_device_id lm93_id[] = {
- { "lm93", 0 },
- { "lm94", 0 },
+ { "lm93" },
+ { "lm94" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm93_id);
diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c
index 67b9d7636ee4..387b3ba81dbf 100644
--- a/drivers/hwmon/lm95234.c
+++ b/drivers/hwmon/lm95234.c
@@ -8,16 +8,14 @@
* Copyright (C) 2008, 2010 Davide Rizzo <elpa.rizzo@gmail.com>
*/
-#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
#include <linux/init.h>
+#include <linux/module.h>
#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/sysfs.h>
+#include <linux/regmap.h>
+#include <linux/util_macros.h>
#define DRVNAME "lm95234"
@@ -32,6 +30,8 @@ static const unsigned short normal_i2c[] = {
#define LM95234_REG_STATUS 0x02
#define LM95234_REG_CONFIG 0x03
#define LM95234_REG_CONVRATE 0x04
+#define LM95234_REG_ENABLE 0x05
+#define LM95234_REG_FILTER 0x06
#define LM95234_REG_STS_FAULT 0x07
#define LM95234_REG_STS_TCRIT1 0x08
#define LM95234_REG_STS_TCRIT2 0x09
@@ -52,540 +52,366 @@ static const unsigned short normal_i2c[] = {
/* Client data (each client gets its own) */
struct lm95234_data {
- struct i2c_client *client;
- const struct attribute_group *groups[3];
- struct mutex update_lock;
- unsigned long last_updated, interval; /* in jiffies */
- bool valid; /* false until following fields are valid */
- /* registers values */
- int temp[5]; /* temperature (signed) */
- u32 status; /* fault/alarm status */
- u8 tcrit1[5]; /* critical temperature limit */
- u8 tcrit2[2]; /* high temperature limit */
- s8 toffset[4]; /* remote temperature offset */
- u8 thyst; /* common hysteresis */
-
- u8 sensor_type; /* temperature sensor type */
+ struct regmap *regmap;
+ enum chips type;
};
-static int lm95234_read_temp(struct i2c_client *client, int index, int *t)
+static int lm95234_read_temp(struct regmap *regmap, int index, long *t)
{
- int val;
- u16 temp = 0;
+ unsigned int regs[2];
+ int temp = 0, ret;
+ u8 regvals[2];
if (index) {
- val = i2c_smbus_read_byte_data(client,
- LM95234_REG_UTEMPH(index - 1));
- if (val < 0)
- return val;
- temp = val << 8;
- val = i2c_smbus_read_byte_data(client,
- LM95234_REG_UTEMPL(index - 1));
- if (val < 0)
- return val;
- temp |= val;
- *t = temp;
+ regs[0] = LM95234_REG_UTEMPH(index - 1);
+ regs[1] = LM95234_REG_UTEMPL(index - 1);
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
+ if (ret)
+ return ret;
+ temp = (regvals[0] << 8) | regvals[1];
}
/*
* Read signed temperature if unsigned temperature is 0,
* or if this is the local sensor.
*/
if (!temp) {
- val = i2c_smbus_read_byte_data(client,
- LM95234_REG_TEMPH(index));
- if (val < 0)
- return val;
- temp = val << 8;
- val = i2c_smbus_read_byte_data(client,
- LM95234_REG_TEMPL(index));
- if (val < 0)
- return val;
- temp |= val;
- *t = (s16)temp;
- }
- return 0;
-}
-
-static u16 update_intervals[] = { 143, 364, 1000, 2500 };
-
-/* Fill value cache. Must be called with update lock held. */
-
-static int lm95234_fill_cache(struct lm95234_data *data,
- struct i2c_client *client)
-{
- int i, ret;
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE);
- if (ret < 0)
- return ret;
-
- data->interval = msecs_to_jiffies(update_intervals[ret & 0x03]);
-
- for (i = 0; i < ARRAY_SIZE(data->tcrit1); i++) {
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT1(i));
- if (ret < 0)
- return ret;
- data->tcrit1[i] = ret;
- }
- for (i = 0; i < ARRAY_SIZE(data->tcrit2); i++) {
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT2(i));
- if (ret < 0)
+ regs[0] = LM95234_REG_TEMPH(index);
+ regs[1] = LM95234_REG_TEMPL(index);
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
+ if (ret)
return ret;
- data->tcrit2[i] = ret;
+ temp = (regvals[0] << 8) | regvals[1];
+ temp = sign_extend32(temp, 15);
}
- for (i = 0; i < ARRAY_SIZE(data->toffset); i++) {
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_OFFSET(i));
- if (ret < 0)
- return ret;
- data->toffset[i] = ret;
- }
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT_HYST);
- if (ret < 0)
- return ret;
- data->thyst = ret;
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL);
- if (ret < 0)
- return ret;
- data->sensor_type = ret;
-
+ *t = DIV_ROUND_CLOSEST(temp * 125, 32);
return 0;
}
-static int lm95234_update_device(struct lm95234_data *data)
+static int lm95234_hyst_get(struct regmap *regmap, int reg, long *val)
{
- struct i2c_client *client = data->client;
+ unsigned int regs[2] = {reg, LM95234_REG_TCRIT_HYST};
+ u8 regvals[2];
int ret;
- mutex_lock(&data->update_lock);
-
- if (time_after(jiffies, data->last_updated + data->interval) ||
- !data->valid) {
- int i;
-
- if (!data->valid) {
- ret = lm95234_fill_cache(data, client);
- if (ret < 0)
- goto abort;
- }
-
- data->valid = false;
- for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
- ret = lm95234_read_temp(client, i, &data->temp[i]);
- if (ret < 0)
- goto abort;
- }
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_FAULT);
- if (ret < 0)
- goto abort;
- data->status = ret;
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT1);
- if (ret < 0)
- goto abort;
- data->status |= ret << 8;
-
- ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT2);
- if (ret < 0)
- goto abort;
- data->status |= ret << 16;
-
- data->last_updated = jiffies;
- data->valid = true;
- }
- ret = 0;
-abort:
- mutex_unlock(&data->update_lock);
-
- return ret;
-}
-
-static ssize_t temp_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (ret)
return ret;
-
- return sprintf(buf, "%d\n",
- DIV_ROUND_CLOSEST(data->temp[index] * 125, 32));
-}
-
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct lm95234_data *data = dev_get_drvdata(dev);
- u32 mask = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- return sprintf(buf, "%u", !!(data->status & mask));
-}
-
-static ssize_t type_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct lm95234_data *data = dev_get_drvdata(dev);
- u8 mask = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- return sprintf(buf, data->sensor_type & mask ? "1\n" : "2\n");
+ *val = (regvals[0] - regvals[1]) * 1000;
+ return 0;
}
-static ssize_t type_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static ssize_t lm95234_hyst_set(struct lm95234_data *data, long val)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- unsigned long val;
- u8 mask = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
+ u32 tcrit;
+ int ret;
+ ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(0), &tcrit);
if (ret)
return ret;
- ret = kstrtoul(buf, 10, &val);
- if (ret < 0)
- return ret;
-
- if (val != 1 && val != 2)
- return -EINVAL;
-
- mutex_lock(&data->update_lock);
- if (val == 1)
- data->sensor_type |= mask;
- else
- data->sensor_type &= ~mask;
- data->valid = false;
- i2c_smbus_write_byte_data(data->client, LM95234_REG_REM_MODEL,
- data->sensor_type);
- mutex_unlock(&data->update_lock);
-
- return count;
-}
-
-static ssize_t tcrit2_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
+ val = DIV_ROUND_CLOSEST(clamp_val(val, -255000, 255000), 1000);
+ val = clamp_val((int)tcrit - val, 0, 31);
- return sprintf(buf, "%u", data->tcrit2[index] * 1000);
+ return regmap_write(data->regmap, LM95234_REG_TCRIT_HYST, val);
}
-static ssize_t tcrit2_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static int lm95234_crit_reg(int channel)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- long val;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- ret = kstrtol(buf, 10, &val);
- if (ret < 0)
- return ret;
-
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, index ? 255 : 127);
-
- mutex_lock(&data->update_lock);
- data->tcrit2[index] = val;
- i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT2(index), val);
- mutex_unlock(&data->update_lock);
-
- return count;
+ if (channel == 1 || channel == 2)
+ return LM95234_REG_TCRIT2(channel - 1);
+ return LM95234_REG_TCRIT1(channel);
}
-static ssize_t tcrit2_hyst_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static int lm95234_temp_write(struct device *dev, u32 attr, int channel, long val)
{
struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- /* Result can be negative, so be careful with unsigned operands */
- return sprintf(buf, "%d",
- ((int)data->tcrit2[index] - (int)data->thyst) * 1000);
+ struct regmap *regmap = data->regmap;
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ if (val && val != 1)
+ return -EINVAL;
+ return regmap_update_bits(regmap, LM95234_REG_ENABLE,
+ BIT(channel), val ? BIT(channel) : 0);
+ case hwmon_temp_type:
+ if (val != 1 && val != 2)
+ return -EINVAL;
+ return regmap_update_bits(regmap, LM95234_REG_REM_MODEL,
+ BIT(channel),
+ val == 1 ? BIT(channel) : 0);
+ case hwmon_temp_offset:
+ val = DIV_ROUND_CLOSEST(clamp_val(val, -64000, 63500), 500);
+ return regmap_write(regmap, LM95234_REG_OFFSET(channel - 1), val);
+ case hwmon_temp_max:
+ val = clamp_val(val, 0, channel == 1 ? 127000 : 255000);
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ return regmap_write(regmap, lm95234_crit_reg(channel), val);
+ case hwmon_temp_max_hyst:
+ return lm95234_hyst_set(data, val);
+ case hwmon_temp_crit:
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 255000), 1000);
+ return regmap_write(regmap, LM95234_REG_TCRIT1(channel), val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t tcrit1_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static int lm95234_alarm_reg(int channel)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
-
- return sprintf(buf, "%u", data->tcrit1[index] * 1000);
+ if (channel == 1 || channel == 2)
+ return LM95234_REG_STS_TCRIT2;
+ return LM95234_REG_STS_TCRIT1;
}
-static ssize_t tcrit1_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static int lm95234_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
- long val;
-
- if (ret)
- return ret;
-
- ret = kstrtol(buf, 10, &val);
- if (ret < 0)
- return ret;
-
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255);
-
- mutex_lock(&data->update_lock);
- data->tcrit1[index] = val;
- i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT1(index), val);
- mutex_unlock(&data->update_lock);
+ struct regmap *regmap = data->regmap;
+ u32 regval, mask;
+ int ret;
- return count;
+ switch (attr) {
+ case hwmon_temp_enable:
+ ret = regmap_read(regmap, LM95234_REG_ENABLE, &regval);
+ if (ret)
+ return ret;
+ *val = !!(regval & BIT(channel));
+ break;
+ case hwmon_temp_input:
+ return lm95234_read_temp(regmap, channel, val);
+ case hwmon_temp_max_alarm:
+ ret = regmap_read(regmap, lm95234_alarm_reg(channel), &regval);
+ if (ret)
+ return ret;
+ *val = !!(regval & BIT(channel));
+ break;
+ case hwmon_temp_crit_alarm:
+ ret = regmap_read(regmap, LM95234_REG_STS_TCRIT1, &regval);
+ if (ret)
+ return ret;
+ *val = !!(regval & BIT(channel));
+ break;
+ case hwmon_temp_crit_hyst:
+ return lm95234_hyst_get(regmap, LM95234_REG_TCRIT1(channel), val);
+ case hwmon_temp_type:
+ ret = regmap_read(regmap, LM95234_REG_REM_MODEL, &regval);
+ if (ret)
+ return ret;
+ *val = (regval & BIT(channel)) ? 1 : 2;
+ break;
+ case hwmon_temp_offset:
+ ret = regmap_read(regmap, LM95234_REG_OFFSET(channel - 1), &regval);
+ if (ret)
+ return ret;
+ *val = sign_extend32(regval, 7) * 500;
+ break;
+ case hwmon_temp_fault:
+ ret = regmap_read(regmap, LM95234_REG_STS_FAULT, &regval);
+ if (ret)
+ return ret;
+ mask = (BIT(0) | BIT(1)) << ((channel - 1) << 1);
+ *val = !!(regval & mask);
+ break;
+ case hwmon_temp_max:
+ ret = regmap_read(regmap, lm95234_crit_reg(channel), &regval);
+ if (ret)
+ return ret;
+ *val = regval * 1000;
+ break;
+ case hwmon_temp_max_hyst:
+ return lm95234_hyst_get(regmap, lm95234_crit_reg(channel), val);
+ case hwmon_temp_crit:
+ ret = regmap_read(regmap, LM95234_REG_TCRIT1(channel), &regval);
+ if (ret)
+ return ret;
+ *val = regval * 1000;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t tcrit1_hyst_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- /* Result can be negative, so be careful with unsigned operands */
- return sprintf(buf, "%d",
- ((int)data->tcrit1[index] - (int)data->thyst) * 1000);
-}
+static u16 update_intervals[] = { 143, 364, 1000, 2500 };
-static ssize_t tcrit1_hyst_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static int lm95234_chip_write(struct device *dev, u32 attr, long val)
{
struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
- long val;
-
- if (ret)
- return ret;
-
- ret = kstrtol(buf, 10, &val);
- if (ret < 0)
- return ret;
- val = DIV_ROUND_CLOSEST(val, 1000);
- val = clamp_val((int)data->tcrit1[index] - val, 0, 31);
-
- mutex_lock(&data->update_lock);
- data->thyst = val;
- i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT_HYST, val);
- mutex_unlock(&data->update_lock);
-
- return count;
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ val = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals));
+ return regmap_write(data->regmap, LM95234_REG_CONVRATE, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t offset_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static int lm95234_chip_read(struct device *dev, u32 attr, long *val)
{
struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
+ u32 convrate;
+ int ret;
- if (ret)
- return ret;
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ ret = regmap_read(data->regmap, LM95234_REG_CONVRATE, &convrate);
+ if (ret)
+ return ret;
- return sprintf(buf, "%d", data->toffset[index] * 500);
+ *val = update_intervals[convrate & 0x03];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
-static ssize_t offset_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static int lm95234_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int index = to_sensor_dev_attr(attr)->index;
- int ret = lm95234_update_device(data);
- long val;
-
- if (ret)
- return ret;
-
- ret = kstrtol(buf, 10, &val);
- if (ret < 0)
- return ret;
-
- /* Accuracy is 1/2 degrees C */
- val = clamp_val(DIV_ROUND_CLOSEST(val, 500), -128, 127);
-
- mutex_lock(&data->update_lock);
- data->toffset[index] = val;
- i2c_smbus_write_byte_data(data->client, LM95234_REG_OFFSET(index), val);
- mutex_unlock(&data->update_lock);
-
- return count;
+ switch (type) {
+ case hwmon_chip:
+ return lm95234_chip_write(dev, attr, val);
+ case hwmon_temp:
+ return lm95234_temp_write(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t update_interval_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static int lm95234_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int ret = lm95234_update_device(data);
-
- if (ret)
- return ret;
-
- return sprintf(buf, "%lu\n",
- DIV_ROUND_CLOSEST(data->interval * 1000, HZ));
+ switch (type) {
+ case hwmon_chip:
+ return lm95234_chip_read(dev, attr, val);
+ case hwmon_temp:
+ return lm95234_temp_read(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t update_interval_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static umode_t lm95234_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
- struct lm95234_data *data = dev_get_drvdata(dev);
- int ret = lm95234_update_device(data);
- unsigned long val;
- u8 regval;
+ const struct lm95234_data *data = _data;
- if (ret)
- return ret;
-
- ret = kstrtoul(buf, 10, &val);
- if (ret < 0)
- return ret;
+ if (data->type == lm95233 && channel > 2)
+ return 0;
- for (regval = 0; regval < 3; regval++) {
- if (val <= update_intervals[regval])
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_crit_hyst:
+ return (channel && channel < 3) ? 0444 : 0;
+ case hwmon_temp_type:
+ case hwmon_temp_offset:
+ return channel ? 0644 : 0;
+ case hwmon_temp_fault:
+ return channel ? 0444 : 0;
+ case hwmon_temp_max:
+ case hwmon_temp_enable:
+ return 0644;
+ case hwmon_temp_max_hyst:
+ return channel ? 0444 : 0644;
+ case hwmon_temp_crit:
+ return (channel && channel < 3) ? 0644 : 0;
+ default:
break;
+ }
+ break;
+ default:
+ break;
}
-
- mutex_lock(&data->update_lock);
- data->interval = msecs_to_jiffies(update_intervals[regval]);
- i2c_smbus_write_byte_data(data->client, LM95234_REG_CONVRATE, regval);
- mutex_unlock(&data->update_lock);
-
- return count;
+ return 0;
}
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
-static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
-static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
-static SENSOR_DEVICE_ATTR_RO(temp5_input, temp, 4);
-
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, BIT(0) | BIT(1));
-static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, BIT(2) | BIT(3));
-static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, BIT(4) | BIT(5));
-static SENSOR_DEVICE_ATTR_RO(temp5_fault, alarm, BIT(6) | BIT(7));
-
-static SENSOR_DEVICE_ATTR_RW(temp2_type, type, BIT(1));
-static SENSOR_DEVICE_ATTR_RW(temp3_type, type, BIT(2));
-static SENSOR_DEVICE_ATTR_RW(temp4_type, type, BIT(3));
-static SENSOR_DEVICE_ATTR_RW(temp5_type, type, BIT(4));
-
-static SENSOR_DEVICE_ATTR_RW(temp1_max, tcrit1, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, tcrit2, 0);
-static SENSOR_DEVICE_ATTR_RW(temp3_max, tcrit2, 1);
-static SENSOR_DEVICE_ATTR_RW(temp4_max, tcrit1, 3);
-static SENSOR_DEVICE_ATTR_RW(temp5_max, tcrit1, 4);
-
-static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, tcrit1_hyst, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_hyst, tcrit2_hyst, 0);
-static SENSOR_DEVICE_ATTR_RO(temp3_max_hyst, tcrit2_hyst, 1);
-static SENSOR_DEVICE_ATTR_RO(temp4_max_hyst, tcrit1_hyst, 3);
-static SENSOR_DEVICE_ATTR_RO(temp5_max_hyst, tcrit1_hyst, 4);
-
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, BIT(0 + 8));
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, BIT(1 + 16));
-static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, BIT(2 + 16));
-static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, BIT(3 + 8));
-static SENSOR_DEVICE_ATTR_RO(temp5_max_alarm, alarm, BIT(4 + 8));
-
-static SENSOR_DEVICE_ATTR_RW(temp2_crit, tcrit1, 1);
-static SENSOR_DEVICE_ATTR_RW(temp3_crit, tcrit1, 2);
-
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, tcrit1_hyst, 1);
-static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, tcrit1_hyst, 2);
-
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, BIT(1 + 8));
-static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, BIT(2 + 8));
-
-static SENSOR_DEVICE_ATTR_RW(temp2_offset, offset, 0);
-static SENSOR_DEVICE_ATTR_RW(temp3_offset, offset, 1);
-static SENSOR_DEVICE_ATTR_RW(temp4_offset, offset, 2);
-static SENSOR_DEVICE_ATTR_RW(temp5_offset, offset, 3);
-
-static DEVICE_ATTR_RW(update_interval);
-
-static struct attribute *lm95234_common_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp3_input.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp3_fault.dev_attr.attr,
- &sensor_dev_attr_temp2_type.dev_attr.attr,
- &sensor_dev_attr_temp3_type.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp3_max.dev_attr.attr,
- &sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp3_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
- &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_offset.dev_attr.attr,
- &sensor_dev_attr_temp3_offset.dev_attr.attr,
- &dev_attr_update_interval.attr,
+static const struct hwmon_channel_info * const lm95234_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_MAX_ALARM | HWMON_T_ENABLE,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE |
+ HWMON_T_CRIT | HWMON_T_CRIT_HYST |
+ HWMON_T_CRIT_ALARM | HWMON_T_OFFSET | HWMON_T_ENABLE,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE |
+ HWMON_T_CRIT | HWMON_T_CRIT_HYST |
+ HWMON_T_CRIT_ALARM | HWMON_T_OFFSET | HWMON_T_ENABLE,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE |
+ HWMON_T_OFFSET | HWMON_T_ENABLE,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE |
+ HWMON_T_OFFSET | HWMON_T_ENABLE),
NULL
};
-static const struct attribute_group lm95234_common_group = {
- .attrs = lm95234_common_attrs,
+static const struct hwmon_ops lm95234_hwmon_ops = {
+ .is_visible = lm95234_is_visible,
+ .read = lm95234_read,
+ .write = lm95234_write,
};
-static struct attribute *lm95234_attrs[] = {
- &sensor_dev_attr_temp4_input.dev_attr.attr,
- &sensor_dev_attr_temp5_input.dev_attr.attr,
- &sensor_dev_attr_temp4_fault.dev_attr.attr,
- &sensor_dev_attr_temp5_fault.dev_attr.attr,
- &sensor_dev_attr_temp4_type.dev_attr.attr,
- &sensor_dev_attr_temp5_type.dev_attr.attr,
- &sensor_dev_attr_temp4_max.dev_attr.attr,
- &sensor_dev_attr_temp5_max.dev_attr.attr,
- &sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp5_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp5_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_offset.dev_attr.attr,
- &sensor_dev_attr_temp5_offset.dev_attr.attr,
- NULL
+static const struct hwmon_chip_info lm95234_chip_info = {
+ .ops = &lm95234_hwmon_ops,
+ .info = lm95234_info,
};
-static const struct attribute_group lm95234_group = {
- .attrs = lm95234_attrs,
+static bool lm95234_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LM95234_REG_TEMPH(0) ... LM95234_REG_TEMPH(4):
+ case LM95234_REG_TEMPL(0) ... LM95234_REG_TEMPL(4):
+ case LM95234_REG_UTEMPH(0) ... LM95234_REG_UTEMPH(3):
+ case LM95234_REG_UTEMPL(0) ... LM95234_REG_UTEMPL(3):
+ case LM95234_REG_STS_FAULT:
+ case LM95234_REG_STS_TCRIT1:
+ case LM95234_REG_STS_TCRIT2:
+ case LM95234_REG_REM_MODEL_STS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool lm95234_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LM95234_REG_CONFIG ... LM95234_REG_FILTER:
+ case LM95234_REG_REM_MODEL ... LM95234_REG_OFFSET(3):
+ case LM95234_REG_TCRIT1(0) ... LM95234_REG_TCRIT1(4):
+ case LM95234_REG_TCRIT2(0) ... LM95234_REG_TCRIT2(1):
+ case LM95234_REG_TCRIT_HYST:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config lm95234_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .writeable_reg = lm95234_writeable_reg,
+ .volatile_reg = lm95234_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
};
static int lm95234_detect(struct i2c_client *client,
@@ -648,62 +474,59 @@ static int lm95234_detect(struct i2c_client *client,
return 0;
}
-static int lm95234_init_client(struct i2c_client *client)
+static int lm95234_init_client(struct device *dev, struct regmap *regmap)
{
- int val, model;
+ u32 val, model;
+ int ret;
/* start conversion if necessary */
- val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG);
- if (val < 0)
- return val;
- if (val & 0x40)
- i2c_smbus_write_byte_data(client, LM95234_REG_CONFIG,
- val & ~0x40);
+ ret = regmap_clear_bits(regmap, LM95234_REG_CONFIG, 0x40);
+ if (ret)
+ return ret;
/* If diode type status reports an error, try to fix it */
- val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS);
- if (val < 0)
- return val;
- model = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL);
- if (model < 0)
- return model;
+ ret = regmap_read(regmap, LM95234_REG_REM_MODEL_STS, &val);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(regmap, LM95234_REG_REM_MODEL, &model);
+ if (ret < 0)
+ return ret;
if (model & val) {
- dev_notice(&client->dev,
+ dev_notice(dev,
"Fixing remote diode type misconfiguration (0x%x)\n",
val);
- i2c_smbus_write_byte_data(client, LM95234_REG_REM_MODEL,
- model & ~val);
+ ret = regmap_write(regmap, LM95234_REG_REM_MODEL, model & ~val);
}
- return 0;
+ return ret;
}
-static const struct i2c_device_id lm95234_id[];
-
static int lm95234_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct lm95234_data *data;
struct device *hwmon_dev;
+ struct regmap *regmap;
int err;
data = devm_kzalloc(dev, sizeof(struct lm95234_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->client = client;
- mutex_init(&data->update_lock);
+ data->type = (uintptr_t)i2c_get_match_data(client);
+
+ regmap = devm_regmap_init_i2c(client, &lm95234_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ data->regmap = regmap;
/* Initialize the LM95234 chip */
- err = lm95234_init_client(client);
+ err = lm95234_init_client(dev, regmap);
if (err < 0)
return err;
- data->groups[0] = &lm95234_common_group;
- if (i2c_match_id(lm95234_id, client)->driver_data == lm95234)
- data->groups[1] = &lm95234_group;
-
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data, data->groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &lm95234_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
diff --git a/drivers/hwmon/lm95241.c b/drivers/hwmon/lm95241.c
index 475551f5024b..456381b0938e 100644
--- a/drivers/hwmon/lm95241.c
+++ b/drivers/hwmon/lm95241.c
@@ -15,7 +15,6 @@
#include <linux/jiffies.h>
#include <linux/hwmon.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/slab.h>
#define DEVNAME "lm95241"
@@ -75,7 +74,6 @@ static const u8 lm95241_reg_address[] = {
/* Client data (each client gets its own) */
struct lm95241_data {
struct i2c_client *client;
- struct mutex update_lock;
unsigned long last_updated; /* in jiffies */
unsigned long interval; /* in milli-seconds */
bool valid; /* false until following fields are valid */
@@ -102,8 +100,6 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
struct lm95241_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
- mutex_lock(&data->update_lock);
-
if (time_after(jiffies, data->last_updated
+ msecs_to_jiffies(data->interval)) ||
!data->valid) {
@@ -120,9 +116,6 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
data->last_updated = jiffies;
data->valid = true;
}
-
- mutex_unlock(&data->update_lock);
-
return data;
}
@@ -204,8 +197,6 @@ static int lm95241_write_chip(struct device *dev, u32 attr, int channel,
u8 config;
int ret;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_chip_update_interval:
config = data->config & ~CFG_CRMASK;
@@ -231,7 +222,6 @@ static int lm95241_write_chip(struct device *dev, u32 attr, int channel,
ret = -EOPNOTSUPP;
break;
}
- mutex_unlock(&data->update_lock);
return ret;
}
@@ -242,8 +232,6 @@ static int lm95241_write_temp(struct device *dev, u32 attr, int channel,
struct i2c_client *client = data->client;
int ret;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_temp_min:
if (channel == 1) {
@@ -313,9 +301,6 @@ static int lm95241_write_temp(struct device *dev, u32 attr, int channel,
ret = -EOPNOTSUPP;
break;
}
-
- mutex_unlock(&data->update_lock);
-
return ret;
}
@@ -443,7 +428,6 @@ static int lm95241_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->update_lock);
/* Initialize the LM95241 chip */
lm95241_init_client(client, data);
@@ -457,8 +441,8 @@ static int lm95241_probe(struct i2c_client *client)
/* Driver data (common to all clients) */
static const struct i2c_device_id lm95241_id[] = {
- { "lm95231", 0 },
- { "lm95241", 0 },
+ { "lm95231" },
+ { "lm95241" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm95241_id);
diff --git a/drivers/hwmon/lm95245.c b/drivers/hwmon/lm95245.c
index 17ff54bd4015..9ed300c6b5f7 100644
--- a/drivers/hwmon/lm95245.c
+++ b/drivers/hwmon/lm95245.c
@@ -13,7 +13,6 @@
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -86,7 +85,6 @@ static const unsigned short normal_i2c[] = {
/* Client data (each client gets its own) */
struct lm95245_data {
struct regmap *regmap;
- struct mutex update_lock;
int interval; /* in msecs */
};
@@ -161,18 +159,18 @@ static int lm95245_read_temp(struct device *dev, u32 attr, int channel,
{
struct lm95245_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
- int ret, regl, regh, regvall, regvalh;
+ unsigned int regs[2];
+ unsigned int regval;
+ u8 regvals[2];
+ int ret;
switch (attr) {
case hwmon_temp_input:
- regl = channel ? LM95245_REG_R_REMOTE_TEMPL_S :
- LM95245_REG_R_LOCAL_TEMPL_S;
- regh = channel ? LM95245_REG_R_REMOTE_TEMPH_S :
- LM95245_REG_R_LOCAL_TEMPH_S;
- ret = regmap_read(regmap, regl, &regvall);
- if (ret < 0)
- return ret;
- ret = regmap_read(regmap, regh, &regvalh);
+ regs[0] = channel ? LM95245_REG_R_REMOTE_TEMPL_S :
+ LM95245_REG_R_LOCAL_TEMPL_S;
+ regs[1] = channel ? LM95245_REG_R_REMOTE_TEMPH_S :
+ LM95245_REG_R_LOCAL_TEMPH_S;
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (ret < 0)
return ret;
/*
@@ -181,92 +179,77 @@ static int lm95245_read_temp(struct device *dev, u32 attr, int channel,
* Use signed calculation for remote if signed bit is set
* or if reported temperature is below signed limit.
*/
- if (!channel || (regvalh & 0x80) || regvalh < 0x7f) {
- *val = temp_from_reg_signed(regvalh, regvall);
+ if (!channel || (regvals[1] & 0x80) || regvals[1] < 0x7f) {
+ *val = temp_from_reg_signed(regvals[1], regvals[0]);
return 0;
}
- ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPL_U,
- &regvall);
- if (ret < 0)
- return ret;
- ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U,
- &regvalh);
- if (ret < 0)
+ ret = regmap_bulk_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U, regvals, 2);
+ if (ret)
return ret;
- *val = temp_from_reg_unsigned(regvalh, regvall);
+ *val = temp_from_reg_unsigned(regvals[0], regvals[1]);
return 0;
case hwmon_temp_max:
ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT,
- &regvalh);
+ &regval);
if (ret < 0)
return ret;
- *val = regvalh * 1000;
+ *val = regval * 1000;
return 0;
case hwmon_temp_crit:
- regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
- LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
- ret = regmap_read(regmap, regh, &regvalh);
+ regs[0] = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
+ LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
+ ret = regmap_read(regmap, regs[0], &regval);
if (ret < 0)
return ret;
- *val = regvalh * 1000;
+ *val = regval * 1000;
return 0;
case hwmon_temp_max_hyst:
- ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT,
- &regvalh);
+ regs[0] = LM95245_REG_RW_REMOTE_OS_LIMIT;
+ regs[1] = LM95245_REG_RW_COMMON_HYSTERESIS;
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (ret < 0)
return ret;
- ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
- &regvall);
- if (ret < 0)
- return ret;
- *val = (regvalh - regvall) * 1000;
+ *val = (regvals[0] - regvals[1]) * 1000;
return 0;
case hwmon_temp_crit_hyst:
- regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
- LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
- ret = regmap_read(regmap, regh, &regvalh);
- if (ret < 0)
- return ret;
- ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
- &regvall);
+ regs[0] = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT :
+ LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT;
+ regs[1] = LM95245_REG_RW_COMMON_HYSTERESIS;
+
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (ret < 0)
return ret;
- *val = (regvalh - regvall) * 1000;
+ *val = (regvals[0] - regvals[1]) * 1000;
return 0;
case hwmon_temp_type:
- ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, &regvalh);
+ ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, &regval);
if (ret < 0)
return ret;
- *val = (regvalh & CFG2_REMOTE_TT) ? 1 : 2;
+ *val = (regval & CFG2_REMOTE_TT) ? 1 : 2;
return 0;
case hwmon_temp_offset:
- ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFL,
- &regvall);
- if (ret < 0)
- return ret;
- ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFH,
- &regvalh);
+ ret = regmap_bulk_read(regmap, LM95245_REG_RW_REMOTE_OFFH, regvals, 2);
if (ret < 0)
return ret;
- *val = temp_from_reg_signed(regvalh, regvall);
+ *val = temp_from_reg_signed(regvals[0], regvals[1]);
return 0;
case hwmon_temp_max_alarm:
- ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regvalh);
+ ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regval);
if (ret < 0)
return ret;
- *val = !!(regvalh & STATUS1_ROS);
+ *val = !!(regval & STATUS1_ROS);
return 0;
case hwmon_temp_crit_alarm:
- ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regvalh);
+ ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regval);
if (ret < 0)
return ret;
- *val = !!(regvalh & (channel ? STATUS1_RTCRIT : STATUS1_LOC));
+ *val = !!(regval & (channel ? STATUS1_RTCRIT : STATUS1_LOC));
return 0;
case hwmon_temp_fault:
- ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regvalh);
+ ret = regmap_read(regmap, LM95245_REG_R_STATUS1, &regval);
if (ret < 0)
return ret;
- *val = !!(regvalh & STATUS1_DIODE_FAULT);
+ *val = !!(regval & STATUS1_DIODE_FAULT);
return 0;
default:
return -EOPNOTSUPP;
@@ -279,6 +262,7 @@ static int lm95245_write_temp(struct device *dev, u32 attr, int channel,
struct lm95245_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
unsigned int regval;
+ u8 regvals[2];
int ret, reg;
switch (attr) {
@@ -293,34 +277,24 @@ static int lm95245_write_temp(struct device *dev, u32 attr, int channel,
ret = regmap_write(regmap, reg, val);
return ret;
case hwmon_temp_crit_hyst:
- mutex_lock(&data->update_lock);
ret = regmap_read(regmap, LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT,
&regval);
- if (ret < 0) {
- mutex_unlock(&data->update_lock);
+ if (ret < 0)
return ret;
- }
/* Clamp to reasonable range to prevent overflow */
val = clamp_val(val, -1000000, 1000000);
val = regval - val / 1000;
val = clamp_val(val, 0, 31);
ret = regmap_write(regmap, LM95245_REG_RW_COMMON_HYSTERESIS,
val);
- mutex_unlock(&data->update_lock);
return ret;
case hwmon_temp_offset:
val = clamp_val(val, -128000, 127875);
val = val * 256 / 1000;
- mutex_lock(&data->update_lock);
- ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFL,
- val & 0xe0);
- if (ret < 0) {
- mutex_unlock(&data->update_lock);
- return ret;
- }
- ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFH,
- (val >> 8) & 0xff);
- mutex_unlock(&data->update_lock);
+ regvals[0] = val >> 8;
+ regvals[1] = val & 0xe0;
+
+ ret = regmap_bulk_write(regmap, LM95245_REG_RW_REMOTE_OFFH, regvals, 2);
return ret;
case hwmon_temp_type:
if (val != 1 && val != 2)
@@ -352,14 +326,10 @@ static int lm95245_write_chip(struct device *dev, u32 attr, int channel,
long val)
{
struct lm95245_data *data = dev_get_drvdata(dev);
- int ret;
switch (attr) {
case hwmon_chip_update_interval:
- mutex_lock(&data->update_lock);
- ret = lm95245_set_conversion_rate(data, val);
- mutex_unlock(&data->update_lock);
- return ret;
+ return lm95245_set_conversion_rate(data, val);
default:
return -EOPNOTSUPP;
}
@@ -562,8 +532,6 @@ static int lm95245_probe(struct i2c_client *client)
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
- mutex_init(&data->update_lock);
-
/* Initialize the LM95245 chip */
ret = lm95245_init_client(data);
if (ret < 0)
@@ -578,8 +546,8 @@ static int lm95245_probe(struct i2c_client *client)
/* Driver data (common to all clients) */
static const struct i2c_device_id lm95245_id[] = {
- { "lm95235", 0 },
- { "lm95245", 0 },
+ { "lm95235" },
+ { "lm95245" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm95245_id);
diff --git a/drivers/hwmon/lochnagar-hwmon.c b/drivers/hwmon/lochnagar-hwmon.c
index 5202dddfd61e..c1ba72f6132e 100644
--- a/drivers/hwmon/lochnagar-hwmon.c
+++ b/drivers/hwmon/lochnagar-hwmon.c
@@ -10,7 +10,6 @@
#include <linux/delay.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/math64.h>
#include <linux/mfd/lochnagar.h>
#include <linux/mfd/lochnagar2_regs.h>
@@ -42,9 +41,6 @@ struct lochnagar_hwmon {
struct regmap *regmap;
long power_nsamples[ARRAY_SIZE(lochnagar_chan_names)];
-
- /* Lock to ensure only a single sensor is read at a time */
- struct mutex sensor_lock;
};
enum lochnagar_measure_mode {
@@ -178,26 +174,20 @@ static int read_sensor(struct device *dev, int chan,
u32 data;
int ret;
- mutex_lock(&priv->sensor_lock);
-
ret = do_measurement(regmap, chan, mode, nsamples);
if (ret < 0) {
dev_err(dev, "Failed to perform measurement: %d\n", ret);
- goto error;
+ return ret;
}
ret = request_data(regmap, chan, &data);
if (ret < 0) {
dev_err(dev, "Failed to read measurement: %d\n", ret);
- goto error;
+ return ret;
}
*val = float_to_long(data, precision);
-
-error:
- mutex_unlock(&priv->sensor_lock);
-
- return ret;
+ return 0;
}
static int read_power(struct device *dev, int chan, long *val)
@@ -378,8 +368,6 @@ static int lochnagar_hwmon_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
- mutex_init(&priv->sensor_lock);
-
priv->regmap = dev_get_regmap(dev->parent, NULL);
if (!priv->regmap) {
dev_err(dev, "No register map found\n");
diff --git a/drivers/hwmon/ltc2945.c b/drivers/hwmon/ltc2945.c
index 45b87a863cae..3e0e0e0687bd 100644
--- a/drivers/hwmon/ltc2945.c
+++ b/drivers/hwmon/ltc2945.c
@@ -508,7 +508,7 @@ static int ltc2945_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc2945_id[] = {
- {"ltc2945", 0},
+ {"ltc2945"},
{ }
};
diff --git a/drivers/hwmon/ltc2947-core.c b/drivers/hwmon/ltc2947-core.c
index d2ff6e700770..ad7120d1e469 100644
--- a/drivers/hwmon/ltc2947-core.c
+++ b/drivers/hwmon/ltc2947-core.c
@@ -9,9 +9,10 @@
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include "ltc2947.h"
@@ -119,12 +120,6 @@
struct ltc2947_data {
struct regmap *map;
struct device *dev;
- /*
- * The mutex is needed because the device has 2 memory pages. When
- * reading/writing the correct page needs to be set so that, the
- * complete sequence select_page->read/write needs to be protected.
- */
- struct mutex lock;
u32 lsb_energy;
bool gpio_out;
};
@@ -180,13 +175,9 @@ static int ltc2947_val_read(struct ltc2947_data *st, const u8 reg,
int ret;
u64 __val = 0;
- mutex_lock(&st->lock);
-
ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
- if (ret) {
- mutex_unlock(&st->lock);
+ if (ret)
return ret;
- }
dev_dbg(st->dev, "Read val, reg:%02X, p:%d sz:%zu\n", reg, page,
size);
@@ -206,8 +197,6 @@ static int ltc2947_val_read(struct ltc2947_data *st, const u8 reg,
break;
}
- mutex_unlock(&st->lock);
-
if (ret)
return ret;
@@ -241,13 +230,10 @@ static int ltc2947_val_write(struct ltc2947_data *st, const u8 reg,
{
int ret;
- mutex_lock(&st->lock);
/* set device on correct page */
ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, page);
- if (ret) {
- mutex_unlock(&st->lock);
+ if (ret)
return ret;
- }
dev_dbg(st->dev, "Write val, r:%02X, p:%d, sz:%zu, val:%016llX\n",
reg, page, size, val);
@@ -264,8 +250,6 @@ static int ltc2947_val_write(struct ltc2947_data *st, const u8 reg,
break;
}
- mutex_unlock(&st->lock);
-
return ret;
}
@@ -294,11 +278,9 @@ static int ltc2947_alarm_read(struct ltc2947_data *st, const u8 reg,
memset(alarms, 0, sizeof(alarms));
- mutex_lock(&st->lock);
-
ret = regmap_write(st->map, LTC2947_REG_PAGE_CTRL, LTC2947_PAGE0);
if (ret)
- goto unlock;
+ return ret;
dev_dbg(st->dev, "Read alarm, reg:%02X, mask:%02X\n", reg, mask);
/*
@@ -309,31 +291,11 @@ static int ltc2947_alarm_read(struct ltc2947_data *st, const u8 reg,
ret = regmap_bulk_read(st->map, LTC2947_REG_STATUS, alarms,
sizeof(alarms));
if (ret)
- goto unlock;
+ return ret;
/* get the alarm */
*val = !!(alarms[offset] & mask);
-unlock:
- mutex_unlock(&st->lock);
- return ret;
-}
-
-static ssize_t ltc2947_show_value(struct device *dev,
- struct device_attribute *da, char *buf)
-{
- struct ltc2947_data *st = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
- int ret;
- s64 val = 0;
-
- ret = ltc2947_val_read(st, attr->index, LTC2947_PAGE0, 6, &val);
- if (ret)
- return ret;
-
- /* value in microJoule. st->lsb_energy was multiplied by 10E9 */
- val = div_s64(val * st->lsb_energy, 1000);
-
- return sprintf(buf, "%lld\n", val);
+ return 0;
}
static int ltc2947_read_temp(struct device *dev, const u32 attr, long *val,
@@ -587,6 +549,23 @@ static int ltc2947_read_in(struct device *dev, const u32 attr, long *val,
return 0;
}
+static int ltc2947_read_energy(struct device *dev, s64 *val, const int channel)
+{
+ int reg = channel ? LTC2947_REG_ENERGY2 : LTC2947_REG_ENERGY1;
+ struct ltc2947_data *st = dev_get_drvdata(dev);
+ s64 __val = 0;
+ int ret;
+
+ ret = ltc2947_val_read(st, reg, LTC2947_PAGE0, 6, &__val);
+ if (ret)
+ return ret;
+
+ /* value in microJoule. st->lsb_energy was multiplied by 10E9 */
+ *val = DIV_S64_ROUND_CLOSEST(__val * st->lsb_energy, 1000);
+
+ return 0;
+}
+
static int ltc2947_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@@ -599,6 +578,8 @@ static int ltc2947_read(struct device *dev, enum hwmon_sensor_types type,
return ltc2947_read_power(dev, attr, val);
case hwmon_temp:
return ltc2947_read_temp(dev, attr, val, channel);
+ case hwmon_energy64:
+ return ltc2947_read_energy(dev, (s64 *)val, channel);
default:
return -ENOTSUPP;
}
@@ -896,6 +877,8 @@ static umode_t ltc2947_is_visible(const void *data,
return ltc2947_power_is_visible(attr);
case hwmon_temp:
return ltc2947_temp_is_visible(attr);
+ case hwmon_energy64:
+ return 0444;
default:
return 0;
}
@@ -928,6 +911,9 @@ static const struct hwmon_channel_info * const ltc2947_info[] = {
HWMON_T_LABEL,
HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM | HWMON_T_MAX |
HWMON_T_MIN | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(energy64,
+ HWMON_E_INPUT,
+ HWMON_E_INPUT),
NULL
};
@@ -943,19 +929,6 @@ static const struct hwmon_chip_info ltc2947_chip_info = {
.info = ltc2947_info,
};
-/* energy attributes are 6bytes wide so we need u64 */
-static SENSOR_DEVICE_ATTR(energy1_input, 0444, ltc2947_show_value, NULL,
- LTC2947_REG_ENERGY1);
-static SENSOR_DEVICE_ATTR(energy2_input, 0444, ltc2947_show_value, NULL,
- LTC2947_REG_ENERGY2);
-
-static struct attribute *ltc2947_attrs[] = {
- &sensor_dev_attr_energy1_input.dev_attr.attr,
- &sensor_dev_attr_energy2_input.dev_attr.attr,
- NULL,
-};
-ATTRIBUTE_GROUPS(ltc2947);
-
static int ltc2947_setup(struct ltc2947_data *st)
{
int ret;
@@ -1034,9 +1007,8 @@ static int ltc2947_setup(struct ltc2947_data *st)
/* 19.89E-6 * 10E9 */
st->lsb_energy = 19890;
}
- ret = of_property_read_u32_array(st->dev->of_node,
- "adi,accumulator-ctl-pol", accum,
- ARRAY_SIZE(accum));
+ ret = device_property_read_u32_array(st->dev, "adi,accumulator-ctl-pol",
+ accum, ARRAY_SIZE(accum));
if (!ret) {
u32 accum_reg = LTC2947_ACCUM_POL_1(accum[0]) |
LTC2947_ACCUM_POL_2(accum[1]);
@@ -1045,9 +1017,9 @@ static int ltc2947_setup(struct ltc2947_data *st)
if (ret)
return ret;
}
- ret = of_property_read_u32(st->dev->of_node,
- "adi,accumulation-deadband-microamp",
- &deadband);
+ ret = device_property_read_u32(st->dev,
+ "adi,accumulation-deadband-microamp",
+ &deadband);
if (!ret) {
/* the LSB is the same as the current, so 3mA */
ret = regmap_write(st->map, LTC2947_REG_ACCUM_DEADBAND,
@@ -1056,7 +1028,7 @@ static int ltc2947_setup(struct ltc2947_data *st)
return ret;
}
/* check gpio cfg */
- ret = of_property_read_u32(st->dev->of_node, "adi,gpio-out-pol", &pol);
+ ret = device_property_read_u32(st->dev, "adi,gpio-out-pol", &pol);
if (!ret) {
/* setup GPIO as output */
u32 gpio_ctl = LTC2947_GPIO_EN(1) | LTC2947_GPIO_FAN_EN(1) |
@@ -1067,8 +1039,8 @@ static int ltc2947_setup(struct ltc2947_data *st)
if (ret)
return ret;
}
- ret = of_property_read_u32_array(st->dev->of_node, "adi,gpio-in-accum",
- accum, ARRAY_SIZE(accum));
+ ret = device_property_read_u32_array(st->dev, "adi,gpio-in-accum",
+ accum, ARRAY_SIZE(accum));
if (!ret) {
/*
* Setup the accum options. The gpioctl is already defined as
@@ -1107,15 +1079,13 @@ int ltc2947_core_probe(struct regmap *map, const char *name)
st->map = map;
st->dev = dev;
dev_set_drvdata(dev, st);
- mutex_init(&st->lock);
ret = ltc2947_setup(st);
if (ret)
return ret;
hwmon = devm_hwmon_device_register_with_info(dev, name, st,
- &ltc2947_chip_info,
- ltc2947_groups);
+ &ltc2947_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon);
}
EXPORT_SYMBOL_GPL(ltc2947_core_probe);
diff --git a/drivers/hwmon/ltc2947-i2c.c b/drivers/hwmon/ltc2947-i2c.c
index 33f574bf2ce7..176d710706dd 100644
--- a/drivers/hwmon/ltc2947-i2c.c
+++ b/drivers/hwmon/ltc2947-i2c.c
@@ -27,7 +27,7 @@ static int ltc2947_probe(struct i2c_client *i2c)
}
static const struct i2c_device_id ltc2947_id[] = {
- {"ltc2947", 0},
+ {"ltc2947"},
{}
};
MODULE_DEVICE_TABLE(i2c, ltc2947_id);
diff --git a/drivers/hwmon/ltc2990.c b/drivers/hwmon/ltc2990.c
index 1ad362c0fd2c..f1c1933c52cf 100644
--- a/drivers/hwmon/ltc2990.c
+++ b/drivers/hwmon/ltc2990.c
@@ -259,7 +259,7 @@ static int ltc2990_i2c_probe(struct i2c_client *i2c)
}
static const struct i2c_device_id ltc2990_i2c_id[] = {
- { "ltc2990", 0 },
+ { "ltc2990" },
{}
};
MODULE_DEVICE_TABLE(i2c, ltc2990_i2c_id);
diff --git a/drivers/hwmon/ltc2991.c b/drivers/hwmon/ltc2991.c
new file mode 100644
index 000000000000..6d5d4cb846da
--- /dev/null
+++ b/drivers/hwmon/ltc2991.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Analog Devices, Inc.
+ * Author: Antoniu Miclaus <antoniu.miclaus@analog.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define LTC2991_STATUS_LOW 0x00
+#define LTC2991_CH_EN_TRIGGER 0x01
+#define LTC2991_V1_V4_CTRL 0x06
+#define LTC2991_V5_V8_CTRL 0x07
+#define LTC2991_PWM_TH_LSB_T_INT 0x08
+#define LTC2991_PWM_TH_MSB 0x09
+#define LTC2991_CHANNEL_V_MSB(x) (0x0A + ((x) * 2))
+#define LTC2991_CHANNEL_T_MSB(x) (0x0A + ((x) * 4))
+#define LTC2991_CHANNEL_C_MSB(x) (0x0C + ((x) * 4))
+#define LTC2991_T_INT_MSB 0x1A
+#define LTC2991_VCC_MSB 0x1C
+
+#define LTC2991_V7_V8_EN BIT(7)
+#define LTC2991_V5_V6_EN BIT(6)
+#define LTC2991_V3_V4_EN BIT(5)
+#define LTC2991_V1_V2_EN BIT(4)
+#define LTC2991_T_INT_VCC_EN BIT(3)
+
+#define LTC2991_V3_V4_FILT_EN BIT(7)
+#define LTC2991_V3_V4_TEMP_EN BIT(5)
+#define LTC2991_V3_V4_DIFF_EN BIT(4)
+#define LTC2991_V1_V2_FILT_EN BIT(3)
+#define LTC2991_V1_V2_TEMP_EN BIT(1)
+#define LTC2991_V1_V2_DIFF_EN BIT(0)
+
+#define LTC2991_V7_V8_FILT_EN BIT(7)
+#define LTC2991_V7_V8_TEMP_EN BIT(5)
+#define LTC2991_V7_V8_DIFF_EN BIT(4)
+#define LTC2991_V5_V6_FILT_EN BIT(3)
+#define LTC2991_V5_V6_TEMP_EN BIT(1)
+#define LTC2991_V5_V6_DIFF_EN BIT(0)
+
+#define LTC2991_REPEAT_ACQ_EN BIT(4)
+#define LTC2991_T_INT_FILT_EN BIT(3)
+
+#define LTC2991_MAX_CHANNEL 4
+#define LTC2991_T_INT_CH_NR 4
+#define LTC2991_VCC_CH_NR 0
+
+struct ltc2991_state {
+ struct regmap *regmap;
+ u32 r_sense_uohm[LTC2991_MAX_CHANNEL];
+ bool temp_en[LTC2991_MAX_CHANNEL];
+};
+
+static int ltc2991_read_reg(struct ltc2991_state *st, u8 addr, u8 reg_len,
+ int *val)
+{
+ __be16 regvals;
+ int ret;
+
+ if (reg_len < 2)
+ return regmap_read(st->regmap, addr, val);
+
+ ret = regmap_bulk_read(st->regmap, addr, &regvals, reg_len);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(regvals);
+
+ return 0;
+}
+
+static int ltc2991_get_voltage(struct ltc2991_state *st, u32 reg, long *val)
+{
+ int reg_val, ret, offset = 0;
+
+ ret = ltc2991_read_reg(st, reg, 2, &reg_val);
+ if (ret)
+ return ret;
+
+ if (reg == LTC2991_VCC_MSB)
+ /* Vcc 2.5V offset */
+ offset = 2500;
+
+ /* Vx, 305.18uV/LSB */
+ *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 14) * 30518,
+ 1000 * 100) + offset;
+
+ return 0;
+}
+
+static int ltc2991_read_in(struct device *dev, u32 attr, int channel, long *val)
+{
+ struct ltc2991_state *st = dev_get_drvdata(dev);
+ u32 reg;
+
+ switch (attr) {
+ case hwmon_in_input:
+ if (channel == LTC2991_VCC_CH_NR)
+ reg = LTC2991_VCC_MSB;
+ else
+ reg = LTC2991_CHANNEL_V_MSB(channel - 1);
+
+ return ltc2991_get_voltage(st, reg, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc2991_get_curr(struct ltc2991_state *st, u32 reg, int channel,
+ long *val)
+{
+ int reg_val, ret;
+
+ ret = ltc2991_read_reg(st, reg, 2, &reg_val);
+ if (ret)
+ return ret;
+
+ /* Vx-Vy, 19.075uV/LSB */
+ *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 14) * 19075,
+ (s32)st->r_sense_uohm[channel]);
+
+ return 0;
+}
+
+static int ltc2991_read_curr(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct ltc2991_state *st = dev_get_drvdata(dev);
+ u32 reg;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ reg = LTC2991_CHANNEL_C_MSB(channel);
+ return ltc2991_get_curr(st, reg, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc2991_get_temp(struct ltc2991_state *st, u32 reg, int channel,
+ long *val)
+{
+ int reg_val, ret;
+
+ ret = ltc2991_read_reg(st, reg, 2, &reg_val);
+ if (ret)
+ return ret;
+
+ /* Temp LSB = 0.0625 Degrees */
+ *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 12) * 1000, 16);
+
+ return 0;
+}
+
+static int ltc2991_read_temp(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct ltc2991_state *st = dev_get_drvdata(dev);
+ u32 reg;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ if (channel == LTC2991_T_INT_CH_NR)
+ reg = LTC2991_T_INT_MSB;
+ else
+ reg = LTC2991_CHANNEL_T_MSB(channel);
+
+ return ltc2991_get_temp(st, reg, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc2991_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc2991_read_in(dev, attr, channel, val);
+ case hwmon_curr:
+ return ltc2991_read_curr(dev, attr, channel, val);
+ case hwmon_temp:
+ return ltc2991_read_temp(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t ltc2991_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct ltc2991_state *st = data;
+
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ if (channel == LTC2991_VCC_CH_NR)
+ return 0444;
+ if (st->temp_en[(channel - 1) / 2])
+ break;
+ if (channel % 2)
+ return 0444;
+ if (!st->r_sense_uohm[(channel - 1) / 2])
+ return 0444;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ if (st->r_sense_uohm[channel])
+ return 0444;
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ if (channel == LTC2991_T_INT_CH_NR ||
+ st->temp_en[channel])
+ return 0444;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops ltc2991_hwmon_ops = {
+ .is_visible = ltc2991_is_visible,
+ .read = ltc2991_read,
+};
+
+static const struct hwmon_channel_info *ltc2991_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT
+ ),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT
+ ),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT
+ ),
+ NULL
+};
+
+static const struct hwmon_chip_info ltc2991_chip_info = {
+ .ops = &ltc2991_hwmon_ops,
+ .info = ltc2991_info,
+};
+
+static const struct regmap_config ltc2991_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x1D,
+};
+
+static int ltc2991_init(struct ltc2991_state *st, struct device *dev)
+{
+ int ret;
+ u32 val, addr;
+ u8 v5_v8_reg_data = 0, v1_v4_reg_data = 0;
+
+ ret = devm_regulator_get_enable(dev, "vcc");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable regulator\n");
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = fwnode_property_read_u32(child, "reg", &addr);
+ if (ret < 0)
+ return ret;
+
+ if (addr > 3)
+ return -EINVAL;
+
+ ret = fwnode_property_read_u32(child,
+ "shunt-resistor-micro-ohms",
+ &val);
+ if (!ret) {
+ if (!val)
+ return dev_err_probe(dev, -EINVAL,
+ "shunt resistor value cannot be zero\n");
+
+ st->r_sense_uohm[addr] = val;
+
+ switch (addr) {
+ case 0:
+ v1_v4_reg_data |= LTC2991_V1_V2_DIFF_EN;
+ break;
+ case 1:
+ v1_v4_reg_data |= LTC2991_V3_V4_DIFF_EN;
+ break;
+ case 2:
+ v5_v8_reg_data |= LTC2991_V5_V6_DIFF_EN;
+ break;
+ case 3:
+ v5_v8_reg_data |= LTC2991_V7_V8_DIFF_EN;
+ break;
+ default:
+ break;
+ }
+ }
+
+ ret = fwnode_property_read_bool(child,
+ "adi,temperature-enable");
+ if (ret) {
+ st->temp_en[addr] = ret;
+
+ switch (addr) {
+ case 0:
+ v1_v4_reg_data |= LTC2991_V1_V2_TEMP_EN;
+ break;
+ case 1:
+ v1_v4_reg_data |= LTC2991_V3_V4_TEMP_EN;
+ break;
+ case 2:
+ v5_v8_reg_data |= LTC2991_V5_V6_TEMP_EN;
+ break;
+ case 3:
+ v5_v8_reg_data |= LTC2991_V7_V8_TEMP_EN;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ ret = regmap_write(st->regmap, LTC2991_V5_V8_CTRL, v5_v8_reg_data);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error: Failed to set V5-V8 CTRL reg.\n");
+
+ ret = regmap_write(st->regmap, LTC2991_V1_V4_CTRL, v1_v4_reg_data);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error: Failed to set V1-V4 CTRL reg.\n");
+
+ ret = regmap_write(st->regmap, LTC2991_PWM_TH_LSB_T_INT,
+ LTC2991_REPEAT_ACQ_EN);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error: Failed to set continuous mode.\n");
+
+ /* Enable all channels and trigger conversions */
+ return regmap_write(st->regmap, LTC2991_CH_EN_TRIGGER,
+ LTC2991_V7_V8_EN | LTC2991_V5_V6_EN |
+ LTC2991_V3_V4_EN | LTC2991_V1_V2_EN |
+ LTC2991_T_INT_VCC_EN);
+}
+
+static int ltc2991_i2c_probe(struct i2c_client *client)
+{
+ int ret;
+ struct device *hwmon_dev;
+ struct ltc2991_state *st;
+
+ st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->regmap = devm_regmap_init_i2c(client, &ltc2991_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = ltc2991_init(st, &client->dev);
+ if (ret)
+ return ret;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
+ client->name, st,
+ &ltc2991_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id ltc2991_of_match[] = {
+ { .compatible = "adi,ltc2991" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc2991_of_match);
+
+static const struct i2c_device_id ltc2991_i2c_id[] = {
+ { "ltc2991" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ltc2991_i2c_id);
+
+static struct i2c_driver ltc2991_i2c_driver = {
+ .driver = {
+ .name = "ltc2991",
+ .of_match_table = ltc2991_of_match,
+ },
+ .probe = ltc2991_i2c_probe,
+ .id_table = ltc2991_i2c_id,
+};
+
+module_i2c_driver(ltc2991_i2c_driver);
+
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2991 HWMON Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
index 589bcd07ce7f..1fcd320d6161 100644
--- a/drivers/hwmon/ltc2992.c
+++ b/drivers/hwmon/ltc2992.c
@@ -256,33 +256,38 @@ static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask
return 0;
}
-static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+static int ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_ctrl;
- int reg;
+ int reg, ret;
mutex_lock(&st->gpio_mutex);
reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1);
if (reg < 0) {
mutex_unlock(&st->gpio_mutex);
- return;
+ return reg;
}
gpio_ctrl = reg;
assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value);
- ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl);
+ ret = ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1,
+ gpio_ctrl);
mutex_unlock(&st->gpio_mutex);
+
+ return ret;
}
-static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
- unsigned long *bits)
+static int ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_ctrl_io = 0;
unsigned long gpio_ctrl = 0;
unsigned int gpio_nr;
+ int ret;
for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) {
if (gpio_nr < 3)
@@ -293,9 +298,14 @@ static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mas
}
mutex_lock(&st->gpio_mutex);
- ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
- ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
+ ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
+ if (ret)
+ goto out;
+
+ ret = ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
+out:
mutex_unlock(&st->gpio_mutex);
+ return ret;
}
static int ltc2992_config_gpio(struct ltc2992_state *st)
@@ -854,29 +864,26 @@ static const struct regmap_config ltc2992_regmap_config = {
static int ltc2992_parse_dt(struct ltc2992_state *st)
{
- struct fwnode_handle *fwnode;
- struct fwnode_handle *child;
u32 addr;
u32 val;
int ret;
- fwnode = dev_fwnode(&st->client->dev);
-
- fwnode_for_each_available_child_node(fwnode, child) {
+ device_for_each_child_node_scoped(&st->client->dev, child) {
ret = fwnode_property_read_u32(child, "reg", &addr);
- if (ret < 0) {
- fwnode_handle_put(child);
+ if (ret < 0)
return ret;
- }
- if (addr > 1) {
- fwnode_handle_put(child);
+ if (addr > 1)
return -EINVAL;
- }
ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val);
- if (!ret)
+ if (!ret) {
+ if (!val)
+ return dev_err_probe(&st->client->dev, -EINVAL,
+ "shunt resistor value cannot be zero\n");
+
st->r_sense_uohm[addr] = val;
+ }
}
return 0;
@@ -918,7 +925,7 @@ static const struct of_device_id ltc2992_of_match[] = {
MODULE_DEVICE_TABLE(of, ltc2992_of_match);
static const struct i2c_device_id ltc2992_i2c_id[] = {
- {"ltc2992", 0},
+ {"ltc2992"},
{}
};
MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id);
diff --git a/drivers/hwmon/ltc4151.c b/drivers/hwmon/ltc4151.c
index f42ac3c9475e..fa66eda78efe 100644
--- a/drivers/hwmon/ltc4151.c
+++ b/drivers/hwmon/ltc4151.c
@@ -188,7 +188,7 @@ static int ltc4151_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4151_id[] = {
- { "ltc4151", 0 },
+ { "ltc4151" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc4151_id);
diff --git a/drivers/hwmon/ltc4215.c b/drivers/hwmon/ltc4215.c
index 66fd28f713ab..cce452711cec 100644
--- a/drivers/hwmon/ltc4215.c
+++ b/drivers/hwmon/ltc4215.c
@@ -245,7 +245,7 @@ static int ltc4215_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4215_id[] = {
- { "ltc4215", 0 },
+ { "ltc4215" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc4215_id);
diff --git a/drivers/hwmon/ltc4222.c b/drivers/hwmon/ltc4222.c
index 9098ef521739..f7eb007fd766 100644
--- a/drivers/hwmon/ltc4222.c
+++ b/drivers/hwmon/ltc4222.c
@@ -200,7 +200,7 @@ static int ltc4222_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4222_id[] = {
- {"ltc4222", 0},
+ {"ltc4222"},
{ }
};
diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c
index b90184a3e0ec..e8131a48bda7 100644
--- a/drivers/hwmon/ltc4245.c
+++ b/drivers/hwmon/ltc4245.c
@@ -18,7 +18,6 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/platform_data/ltc4245.h>
@@ -51,7 +50,6 @@ enum ltc4245_cmd {
struct ltc4245_data {
struct i2c_client *client;
- struct mutex update_lock;
bool valid;
unsigned long last_updated; /* in jiffies */
@@ -132,10 +130,7 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev)
s32 val;
int i;
- mutex_lock(&data->update_lock);
-
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
-
/* Read control registers -- 0x00 to 0x07 */
for (i = 0; i < ARRAY_SIZE(data->cregs); i++) {
val = i2c_smbus_read_byte_data(client, i);
@@ -161,8 +156,6 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev)
data->valid = true;
}
- mutex_unlock(&data->update_lock);
-
return data;
}
@@ -454,7 +447,6 @@ static int ltc4245_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->update_lock);
data->use_extra_gpios = ltc4245_use_extra_gpios(client);
/* Initialize the LTC4245 chip */
@@ -469,7 +461,7 @@ static int ltc4245_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4245_id[] = {
- { "ltc4245", 0 },
+ { "ltc4245" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc4245_id);
diff --git a/drivers/hwmon/ltc4260.c b/drivers/hwmon/ltc4260.c
index 52f7a809b27b..9750dc9aa336 100644
--- a/drivers/hwmon/ltc4260.c
+++ b/drivers/hwmon/ltc4260.c
@@ -163,7 +163,7 @@ static int ltc4260_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4260_id[] = {
- {"ltc4260", 0},
+ {"ltc4260"},
{ }
};
diff --git a/drivers/hwmon/ltc4261.c b/drivers/hwmon/ltc4261.c
index 509e68176c7a..2cd218a6a3be 100644
--- a/drivers/hwmon/ltc4261.c
+++ b/drivers/hwmon/ltc4261.c
@@ -222,7 +222,7 @@ static int ltc4261_probe(struct i2c_client *client)
}
static const struct i2c_device_id ltc4261_id[] = {
- {"ltc4261", 0},
+ {"ltc4261"},
{}
};
diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c
new file mode 100644
index 000000000000..b9cad89f2cd9
--- /dev/null
+++ b/drivers/hwmon/ltc4282.c
@@ -0,0 +1,1706 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C
+ *
+ * Copyright 2023 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+#define LTC4282_CTRL_LSB 0x00
+ #define LTC4282_CTRL_OV_RETRY_MASK BIT(0)
+ #define LTC4282_CTRL_UV_RETRY_MASK BIT(1)
+ #define LTC4282_CTRL_OC_RETRY_MASK BIT(2)
+ #define LTC4282_CTRL_ON_ACTIVE_LOW_MASK BIT(5)
+ #define LTC4282_CTRL_ON_DELAY_MASK BIT(6)
+#define LTC4282_CTRL_MSB 0x01
+ #define LTC4282_CTRL_VIN_MODE_MASK GENMASK(1, 0)
+ #define LTC4282_CTRL_OV_MODE_MASK GENMASK(3, 2)
+ #define LTC4282_CTRL_UV_MODE_MASK GENMASK(5, 4)
+#define LTC4282_FAULT_LOG 0x04
+ #define LTC4282_OV_FAULT_MASK BIT(0)
+ #define LTC4282_UV_FAULT_MASK BIT(1)
+ #define LTC4282_VDD_FAULT_MASK \
+ (LTC4282_OV_FAULT_MASK | LTC4282_UV_FAULT_MASK)
+ #define LTC4282_OC_FAULT_MASK BIT(2)
+ #define LTC4282_POWER_BAD_FAULT_MASK BIT(3)
+ #define LTC4282_FET_SHORT_FAULT_MASK BIT(5)
+ #define LTC4282_FET_BAD_FAULT_MASK BIT(6)
+ #define LTC4282_FET_FAILURE_FAULT_MASK \
+ (LTC4282_FET_SHORT_FAULT_MASK | LTC4282_FET_BAD_FAULT_MASK)
+#define LTC4282_ADC_ALERT_LOG 0x05
+ #define LTC4282_GPIO_ALARM_L_MASK BIT(0)
+ #define LTC4282_GPIO_ALARM_H_MASK BIT(1)
+ #define LTC4282_VSOURCE_ALARM_L_MASK BIT(2)
+ #define LTC4282_VSOURCE_ALARM_H_MASK BIT(3)
+ #define LTC4282_VSENSE_ALARM_L_MASK BIT(4)
+ #define LTC4282_VSENSE_ALARM_H_MASK BIT(5)
+ #define LTC4282_POWER_ALARM_L_MASK BIT(6)
+ #define LTC4282_POWER_ALARM_H_MASK BIT(7)
+#define LTC4282_FET_BAD_FAULT_TIMEOUT 0x06
+ #define LTC4282_FET_BAD_MAX_TIMEOUT 255
+#define LTC4282_GPIO_CONFIG 0x07
+ #define LTC4282_GPIO_2_FET_STRESS_MASK BIT(1)
+ #define LTC4282_GPIO_1_CONFIG_MASK GENMASK(5, 4)
+#define LTC4282_VGPIO_MIN 0x08
+#define LTC4282_VGPIO_MAX 0x09
+#define LTC4282_VSOURCE_MIN 0x0a
+#define LTC4282_VSOURCE_MAX 0x0b
+#define LTC4282_VSENSE_MIN 0x0c
+#define LTC4282_VSENSE_MAX 0x0d
+#define LTC4282_POWER_MIN 0x0e
+#define LTC4282_POWER_MAX 0x0f
+#define LTC4282_CLK_DIV 0x10
+ #define LTC4282_CLK_DIV_MASK GENMASK(4, 0)
+ #define LTC4282_CLKOUT_MASK GENMASK(6, 5)
+#define LTC4282_ILIM_ADJUST 0x11
+ #define LTC4282_GPIO_MODE_MASK BIT(1)
+ #define LTC4282_VDD_MONITOR_MASK BIT(2)
+ #define LTC4282_FOLDBACK_MODE_MASK GENMASK(4, 3)
+ #define LTC4282_ILIM_ADJUST_MASK GENMASK(7, 5)
+#define LTC4282_ENERGY 0x12
+#define LTC4282_TIME_COUNTER 0x18
+#define LTC4282_ALERT_CTRL 0x1c
+ #define LTC4282_ALERT_OUT_MASK BIT(6)
+#define LTC4282_ADC_CTRL 0x1d
+ #define LTC4282_FAULT_LOG_EN_MASK BIT(2)
+ #define LTC4282_METER_HALT_MASK BIT(5)
+ #define LTC4282_METER_RESET_MASK BIT(6)
+ #define LTC4282_RESET_MASK BIT(7)
+#define LTC4282_STATUS_LSB 0x1e
+ #define LTC4282_OV_STATUS_MASK BIT(0)
+ #define LTC4282_UV_STATUS_MASK BIT(1)
+ #define LTC4282_VDD_STATUS_MASK \
+ (LTC4282_OV_STATUS_MASK | LTC4282_UV_STATUS_MASK)
+ #define LTC4282_OC_STATUS_MASK BIT(2)
+ #define LTC4282_POWER_GOOD_MASK BIT(3)
+ #define LTC4282_FET_FAILURE_MASK GENMASK(6, 5)
+#define LTC4282_STATUS_MSB 0x1f
+#define LTC4282_RESERVED_1 0x32
+#define LTC4282_RESERVED_2 0x33
+#define LTC4282_VGPIO 0x34
+#define LTC4282_VGPIO_LOWEST 0x36
+#define LTC4282_VGPIO_HIGHEST 0x38
+#define LTC4282_VSOURCE 0x3a
+#define LTC4282_VSOURCE_LOWEST 0x3c
+#define LTC4282_VSOURCE_HIGHEST 0x3e
+#define LTC4282_VSENSE 0x40
+#define LTC4282_VSENSE_LOWEST 0x42
+#define LTC4282_VSENSE_HIGHEST 0x44
+#define LTC4282_POWER 0x46
+#define LTC4282_POWER_LOWEST 0x48
+#define LTC4282_POWER_HIGHEST 0x4a
+#define LTC4282_RESERVED_3 0x50
+
+#define LTC4282_CLKIN_MIN (250 * KILO)
+#define LTC4282_CLKIN_MAX (15500 * KILO)
+#define LTC4282_CLKIN_RANGE (LTC4282_CLKIN_MAX - LTC4282_CLKIN_MIN + 1)
+#define LTC4282_CLKOUT_SYSTEM (250 * KILO)
+#define LTC4282_CLKOUT_CNV 15
+
+enum {
+ LTC4282_CHAN_VSOURCE,
+ LTC4282_CHAN_VDD,
+ LTC4282_CHAN_VGPIO,
+};
+
+struct ltc4282_cache {
+ u32 in_max_raw;
+ u32 in_min_raw;
+ long in_highest;
+ long in_lowest;
+ bool en;
+};
+
+struct ltc4282_state {
+ struct regmap *map;
+ struct clk_hw clk_hw;
+ /*
+ * Used to cache values for VDD/VSOURCE depending which will be used
+ * when hwmon is not enabled for that channel. Needed because they share
+ * the same registers.
+ */
+ struct ltc4282_cache in0_1_cache[LTC4282_CHAN_VGPIO];
+ u32 vsense_max;
+ long power_max;
+ u32 rsense;
+ u16 vdd;
+ u16 vfs_out;
+ bool energy_en;
+};
+
+enum {
+ LTC4282_CLKOUT_NONE,
+ LTC4282_CLKOUT_INT,
+ LTC4282_CLKOUT_TICK,
+};
+
+static int ltc4282_set_rate(struct clk_hw *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ struct ltc4282_state *st = container_of(hw, struct ltc4282_state,
+ clk_hw);
+ u32 val = LTC4282_CLKOUT_INT;
+
+ if (rate == LTC4282_CLKOUT_CNV)
+ val = LTC4282_CLKOUT_TICK;
+
+ return regmap_update_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK,
+ FIELD_PREP(LTC4282_CLKOUT_MASK, val));
+}
+
+/*
+ * Note the 15HZ conversion rate assumes 12bit ADC which is what we are
+ * supporting for now.
+ */
+static const unsigned int ltc4282_out_rates[] = {
+ LTC4282_CLKOUT_CNV, LTC4282_CLKOUT_SYSTEM
+};
+
+static int ltc4282_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ int idx = find_closest(req->rate, ltc4282_out_rates,
+ ARRAY_SIZE(ltc4282_out_rates));
+
+ req->rate = ltc4282_out_rates[idx];
+
+ return 0;
+}
+
+static unsigned long ltc4282_recalc_rate(struct clk_hw *hw,
+ unsigned long parent)
+{
+ struct ltc4282_state *st = container_of(hw, struct ltc4282_state,
+ clk_hw);
+ u32 clkdiv;
+ int ret;
+
+ ret = regmap_read(st->map, LTC4282_CLK_DIV, &clkdiv);
+ if (ret)
+ return 0;
+
+ clkdiv = FIELD_GET(LTC4282_CLKOUT_MASK, clkdiv);
+ if (!clkdiv)
+ return 0;
+ if (clkdiv == LTC4282_CLKOUT_INT)
+ return LTC4282_CLKOUT_SYSTEM;
+
+ return LTC4282_CLKOUT_CNV;
+}
+
+static void ltc4282_disable(struct clk_hw *clk_hw)
+{
+ struct ltc4282_state *st = container_of(clk_hw, struct ltc4282_state,
+ clk_hw);
+
+ regmap_clear_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK);
+}
+
+static int ltc4282_read_voltage_word(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long *val)
+{
+ __be16 in;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &in, sizeof(in));
+ if (ret)
+ return ret;
+
+ /*
+ * This is also used to calculate current in which case fs comes in
+ * 10 * uV. Hence the ULL usage.
+ */
+ *val = DIV_ROUND_CLOSEST_ULL(be16_to_cpu(in) * (u64)fs, U16_MAX);
+ return 0;
+}
+
+static int ltc4282_read_voltage_byte_cached(const struct ltc4282_state *st,
+ u32 reg, u32 fs, long *val,
+ u32 *cached_raw)
+{
+ int ret;
+ u32 in;
+
+ if (cached_raw) {
+ in = *cached_raw;
+ } else {
+ ret = regmap_read(st->map, reg, &in);
+ if (ret)
+ return ret;
+ }
+
+ *val = DIV_ROUND_CLOSEST(in * fs, U8_MAX);
+ return 0;
+}
+
+static int ltc4282_read_voltage_byte(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long *val)
+{
+ return ltc4282_read_voltage_byte_cached(st, reg, fs, val, NULL);
+}
+
+static int __ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask,
+ long *val)
+{
+ u32 alarm;
+ int ret;
+
+ ret = regmap_read(st->map, reg, &alarm);
+ if (ret)
+ return ret;
+
+ *val = !!(alarm & mask);
+
+ /* if not status/fault logs, clear the alarm after reading it */
+ if (reg != LTC4282_STATUS_LSB && reg != LTC4282_FAULT_LOG)
+ return regmap_clear_bits(st->map, reg, mask);
+
+ return 0;
+}
+
+static int ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask,
+ long *val)
+{
+ return __ltc4282_read_alarm(st, reg, mask, val);
+}
+
+static int ltc4282_vdd_source_read_in(struct ltc4282_state *st, u32 channel,
+ long *val)
+{
+ if (!st->in0_1_cache[channel].en)
+ return -ENODATA;
+
+ return ltc4282_read_voltage_word(st, LTC4282_VSOURCE, st->vfs_out, val);
+}
+
+static int ltc4282_vdd_source_read_hist(struct ltc4282_state *st, u32 reg,
+ u32 channel, long *cached, long *val)
+{
+ int ret;
+
+ if (!st->in0_1_cache[channel].en) {
+ *val = *cached;
+ return 0;
+ }
+
+ ret = ltc4282_read_voltage_word(st, reg, st->vfs_out, val);
+ if (ret)
+ return ret;
+
+ *cached = *val;
+ return 0;
+}
+
+static int ltc4282_vdd_source_read_lim(struct ltc4282_state *st, u32 reg,
+ u32 channel, u32 *cached, long *val)
+{
+ if (!st->in0_1_cache[channel].en)
+ return ltc4282_read_voltage_byte_cached(st, reg, st->vfs_out,
+ val, cached);
+
+ return ltc4282_read_voltage_byte(st, reg, st->vfs_out, val);
+}
+
+static int ltc4282_vdd_source_read_alm(struct ltc4282_state *st, u32 mask,
+ u32 channel, long *val)
+{
+ if (!st->in0_1_cache[channel].en) {
+ /*
+ * Do this otherwise alarms can get confused because we clear
+ * them after reading them. So, if someone mistakenly reads
+ * VSOURCE right before VDD (or the other way around), we might
+ * get no alarm just because it was cleared when reading VSOURCE
+ * and had no time for a new conversion and thus having the
+ * alarm again.
+ */
+ *val = 0;
+ return 0;
+ }
+
+ return __ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, mask, val);
+}
+
+static int ltc4282_read_in(struct ltc4282_state *st, u32 attr, long *val,
+ u32 channel)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st, LTC4282_VGPIO,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_in(st, channel, val);
+ case hwmon_in_highest:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st,
+ LTC4282_VGPIO_HIGHEST,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_HIGHEST,
+ channel,
+ &st->in0_1_cache[channel].in_highest, val);
+ case hwmon_in_lowest:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st, LTC4282_VGPIO_LOWEST,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_LOWEST,
+ channel,
+ &st->in0_1_cache[channel].in_lowest, val);
+ case hwmon_in_max_alarm:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_GPIO_ALARM_H_MASK,
+ val);
+
+ return ltc4282_vdd_source_read_alm(st,
+ LTC4282_VSOURCE_ALARM_H_MASK,
+ channel, val);
+ case hwmon_in_min_alarm:
+ if (channel == LTC4282_CHAN_VGPIO)
+ ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_GPIO_ALARM_L_MASK, val);
+
+ return ltc4282_vdd_source_read_alm(st,
+ LTC4282_VSOURCE_ALARM_L_MASK,
+ channel, val);
+ case hwmon_in_crit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_OV_STATUS_MASK, val);
+ case hwmon_in_lcrit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_UV_STATUS_MASK, val);
+ case hwmon_in_max:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MAX,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MAX,
+ channel,
+ &st->in0_1_cache[channel].in_max_raw, val);
+ case hwmon_in_min:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MIN,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MIN,
+ channel,
+ &st->in0_1_cache[channel].in_min_raw, val);
+ case hwmon_in_enable:
+ *val = st->in0_1_cache[channel].en;
+ return 0;
+ case hwmon_in_fault:
+ /*
+ * We report failure if we detect either a fer_bad or a
+ * fet_short in the status register.
+ */
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_FET_FAILURE_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read_current_word(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ long in;
+ int ret;
+
+ /*
+ * We pass in full scale in 10 * micro (note that 40 is already
+ * millivolt) so we have better approximations to calculate current.
+ */
+ ret = ltc4282_read_voltage_word(st, reg, DECA * 40 * MILLI, &in);
+ if (ret)
+ return ret;
+
+ *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_current_byte(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ long in;
+ int ret;
+
+ ret = ltc4282_read_voltage_byte(st, reg, DECA * 40 * MILLI, &in);
+ if (ret)
+ return ret;
+
+ *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_curr(struct ltc4282_state *st, const u32 attr,
+ long *val)
+{
+ switch (attr) {
+ case hwmon_curr_input:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE, val);
+ case hwmon_curr_highest:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE_HIGHEST,
+ val);
+ case hwmon_curr_lowest:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE_LOWEST,
+ val);
+ case hwmon_curr_max:
+ return ltc4282_read_current_byte(st, LTC4282_VSENSE_MAX, val);
+ case hwmon_curr_min:
+ return ltc4282_read_current_byte(st, LTC4282_VSENSE_MIN, val);
+ case hwmon_curr_max_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_VSENSE_ALARM_H_MASK, val);
+ case hwmon_curr_min_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_VSENSE_ALARM_L_MASK, val);
+ case hwmon_curr_crit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_OC_STATUS_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read_power_word(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ u64 temp = DECA * 40ULL * st->vfs_out * BIT(16), temp_2;
+ __be16 raw;
+ u16 power;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &raw, sizeof(raw));
+ if (ret)
+ return ret;
+
+ power = be16_to_cpu(raw);
+ /*
+ * Power is given by:
+ * P = CODE(16b) * 0.040 * Vfs(out) * 2^16 / ((2^16 - 1)^2 * Rsense)
+ */
+ if (check_mul_overflow(power * temp, MICRO, &temp_2)) {
+ temp = DIV_ROUND_CLOSEST_ULL(power * temp, U16_MAX);
+ *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO,
+ U16_MAX * (u64)st->rsense);
+ return 0;
+ }
+
+ *val = DIV64_U64_ROUND_CLOSEST(temp_2,
+ st->rsense * int_pow(U16_MAX, 2));
+
+ return 0;
+}
+
+static int ltc4282_read_power_byte(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ u32 power;
+ u64 temp;
+ int ret;
+
+ ret = regmap_read(st->map, reg, &power);
+ if (ret)
+ return ret;
+
+ temp = power * 40 * DECA * st->vfs_out * BIT_ULL(8);
+ *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO,
+ int_pow(U8_MAX, 2) * st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_energy(const struct ltc4282_state *st, s64 *val)
+{
+ u64 temp, energy;
+ __be64 raw;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, LTC4282_ENERGY, &raw, 6);
+ if (ret)
+ return ret;
+
+ energy = be64_to_cpu(raw) >> 16;
+ /*
+ * The formula for energy is given by:
+ * E = CODE(48b) * 0.040 * Vfs(out) * Tconv * 256 /
+ * ((2^16 - 1)^2 * Rsense)
+ *
+ * Since we only support 12bit ADC, Tconv = 0.065535s. Passing Vfs(out)
+ * and 0.040 to mV and Tconv to us, we can simplify the formula to:
+ * E = CODE(48b) * 40 * Vfs(out) * 256 / (U16_MAX * Rsense)
+ *
+ * As Rsense can have tenths of micro-ohm resolution, we need to
+ * multiply by DECA to get microujoule.
+ */
+ if (check_mul_overflow(DECA * st->vfs_out * 40 * BIT(8), energy, &temp)) {
+ temp = DIV_ROUND_CLOSEST(DECA * st->vfs_out * 40 * BIT(8), U16_MAX);
+ *val = DIV_ROUND_CLOSEST_ULL(temp * energy, st->rsense);
+ return 0;
+ }
+
+ *val = DIV64_U64_ROUND_CLOSEST(temp, U16_MAX * (u64)st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_power(struct ltc4282_state *st, const u32 attr,
+ long *val)
+{
+ switch (attr) {
+ case hwmon_power_input:
+ return ltc4282_read_power_word(st, LTC4282_POWER, val);
+ case hwmon_power_input_highest:
+ return ltc4282_read_power_word(st, LTC4282_POWER_HIGHEST, val);
+ case hwmon_power_input_lowest:
+ return ltc4282_read_power_word(st, LTC4282_POWER_LOWEST, val);
+ case hwmon_power_max_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_POWER_ALARM_H_MASK, val);
+ case hwmon_power_min_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_POWER_ALARM_L_MASK, val);
+ case hwmon_power_max:
+ return ltc4282_read_power_byte(st, LTC4282_POWER_MAX, val);
+ case hwmon_power_min:
+ return ltc4282_read_power_byte(st, LTC4282_POWER_MIN, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct ltc4282_state *st = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_in:
+ return ltc4282_read_in(st, attr, val, channel);
+ case hwmon_curr:
+ return ltc4282_read_curr(st, attr, val);
+ case hwmon_power:
+ return ltc4282_read_power(st, attr, val);
+ case hwmon_energy:
+ *val = st->energy_en;
+ return 0;
+ case hwmon_energy64:
+ if (st->energy_en)
+ return ltc4282_read_energy(st, (s64 *)val);
+ return -ENODATA;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_write_power_byte(const struct ltc4282_state *st, u32 reg,
+ long val)
+{
+ u32 power;
+ u64 temp;
+
+ if (val > st->power_max)
+ val = st->power_max;
+
+ temp = val * int_pow(U8_MAX, 2) * st->rsense;
+ power = DIV64_U64_ROUND_CLOSEST(temp,
+ MICRO * DECA * 256ULL * st->vfs_out * 40);
+
+ return regmap_write(st->map, reg, power);
+}
+
+static int ltc4282_write_power_word(const struct ltc4282_state *st, u32 reg,
+ long val)
+{
+ u64 temp = int_pow(U16_MAX, 2) * st->rsense, temp_2;
+ __be16 __raw;
+ u16 code;
+
+ if (check_mul_overflow(temp, val, &temp_2)) {
+ temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MICRO);
+ code = DIV64_U64_ROUND_CLOSEST(temp * val,
+ 40ULL * BIT(16) * st->vfs_out);
+ } else {
+ temp = DECA * MICRO * 40ULL * BIT(16) * st->vfs_out;
+ code = DIV64_U64_ROUND_CLOSEST(temp_2, temp);
+ }
+
+ __raw = cpu_to_be16(code);
+ return regmap_bulk_write(st->map, reg, &__raw, sizeof(__raw));
+}
+
+static int __ltc4282_in_write_history(const struct ltc4282_state *st, u32 reg,
+ long lowest, long highest, u32 fs)
+{
+ __be16 __raw;
+ u16 tmp;
+ int ret;
+
+ tmp = DIV_ROUND_CLOSEST(U16_MAX * lowest, fs);
+
+ __raw = cpu_to_be16(tmp);
+
+ ret = regmap_bulk_write(st->map, reg, &__raw, 2);
+ if (ret)
+ return ret;
+
+ tmp = DIV_ROUND_CLOSEST(U16_MAX * highest, fs);
+
+ __raw = cpu_to_be16(tmp);
+
+ return regmap_bulk_write(st->map, reg + 2, &__raw, 2);
+}
+
+static int ltc4282_in_write_history(struct ltc4282_state *st, u32 reg,
+ long lowest, long highest, u32 fs)
+{
+ return __ltc4282_in_write_history(st, reg, lowest, highest, fs);
+}
+
+static int ltc4282_power_reset_hist(struct ltc4282_state *st)
+{
+ int ret;
+
+ ret = ltc4282_write_power_word(st, LTC4282_POWER_LOWEST,
+ st->power_max);
+ if (ret)
+ return ret;
+
+ ret = ltc4282_write_power_word(st, LTC4282_POWER_HIGHEST, 0);
+ if (ret)
+ return ret;
+
+ /* now, let's also clear possible power_bad fault logs */
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_POWER_BAD_FAULT_MASK);
+}
+
+static int ltc4282_write_power(struct ltc4282_state *st, u32 attr,
+ long val)
+{
+ switch (attr) {
+ case hwmon_power_max:
+ return ltc4282_write_power_byte(st, LTC4282_POWER_MAX, val);
+ case hwmon_power_min:
+ return ltc4282_write_power_byte(st, LTC4282_POWER_MIN, val);
+ case hwmon_power_reset_history:
+ return ltc4282_power_reset_hist(st);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_write_voltage_byte_cached(const struct ltc4282_state *st,
+ u32 reg, u32 fs, long val,
+ u32 *cache_raw)
+{
+ u32 in;
+
+ val = clamp_val(val, 0, fs);
+ in = DIV_ROUND_CLOSEST(val * U8_MAX, fs);
+
+ if (cache_raw) {
+ *cache_raw = in;
+ return 0;
+ }
+
+ return regmap_write(st->map, reg, in);
+}
+
+static int ltc4282_write_voltage_byte(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long val)
+{
+ return ltc4282_write_voltage_byte_cached(st, reg, fs, val, NULL);
+}
+
+static int ltc4282_cache_history(struct ltc4282_state *st, u32 channel)
+{
+ long val;
+ int ret;
+
+ ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_LOWEST, st->vfs_out,
+ &val);
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].in_lowest = val;
+
+ ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_HIGHEST,
+ st->vfs_out, &val);
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].in_highest = val;
+
+ ret = regmap_read(st->map, LTC4282_VSOURCE_MIN,
+ &st->in0_1_cache[channel].in_min_raw);
+ if (ret)
+ return ret;
+
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[channel].in_max_raw);
+}
+
+static int ltc4282_cache_sync(struct ltc4282_state *st, u32 channel)
+{
+ int ret;
+
+ ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->in0_1_cache[channel].in_lowest,
+ st->in0_1_cache[channel].in_highest,
+ st->vfs_out);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->map, LTC4282_VSOURCE_MIN,
+ st->in0_1_cache[channel].in_min_raw);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->map, LTC4282_VSOURCE_MAX,
+ st->in0_1_cache[channel].in_max_raw);
+}
+
+static int ltc4282_vdd_source_write_lim(struct ltc4282_state *st, u32 reg,
+ int channel, u32 *cache, long val)
+{
+ int ret;
+
+ if (st->in0_1_cache[channel].en)
+ ret = ltc4282_write_voltage_byte(st, reg, st->vfs_out, val);
+ else
+ ret = ltc4282_write_voltage_byte_cached(st, reg, st->vfs_out,
+ val, cache);
+
+ return ret;
+}
+
+static int ltc4282_vdd_source_reset_hist(struct ltc4282_state *st, int channel)
+{
+ long lowest = st->vfs_out;
+ int ret;
+
+ if (channel == LTC4282_CHAN_VDD)
+ lowest = st->vdd;
+
+ if (st->in0_1_cache[channel].en) {
+ ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ lowest, 0, st->vfs_out);
+ if (ret)
+ return ret;
+ }
+
+ st->in0_1_cache[channel].in_lowest = lowest;
+ st->in0_1_cache[channel].in_highest = 0;
+
+ /*
+ * We are also clearing possible fault logs in reset_history. Clearing
+ * the logs might be important when the auto retry bits are not enabled
+ * as the chip only enables the output again after having these logs
+ * cleared. As some of these logs are related to limits, it makes sense
+ * to clear them in here. For VDD, we need to clear under/over voltage
+ * events. For VSOURCE, fet_short and fet_bad...
+ */
+ if (channel == LTC4282_CHAN_VSOURCE)
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_FET_FAILURE_FAULT_MASK);
+
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_VDD_FAULT_MASK);
+}
+
+/*
+ * We need to mux between VSOURCE and VDD which means they are mutually
+ * exclusive. Moreover, we can't really disable both VDD and VSOURCE as the ADC
+ * is continuously running (we cannot independently halt it without also
+ * stopping VGPIO). Hence, the logic is that disabling or enabling VDD will
+ * automatically have the reverse effect on VSOURCE and vice-versa.
+ */
+static int ltc4282_vdd_source_enable(struct ltc4282_state *st, int channel,
+ long val)
+{
+ int ret, other_chan = ~channel & 0x1;
+ u8 __val = val;
+
+ if (st->in0_1_cache[channel].en == !!val)
+ return 0;
+
+ /* clearing the bit makes the ADC to monitor VDD */
+ if (channel == LTC4282_CHAN_VDD)
+ __val = !__val;
+
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_VDD_MONITOR_MASK,
+ FIELD_PREP(LTC4282_VDD_MONITOR_MASK, !!__val));
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].en = !!val;
+ st->in0_1_cache[other_chan].en = !val;
+
+ if (st->in0_1_cache[channel].en) {
+ /*
+ * Then, we are disabling @other_chan. Let's save it's current
+ * history.
+ */
+ ret = ltc4282_cache_history(st, other_chan);
+ if (ret)
+ return ret;
+
+ return ltc4282_cache_sync(st, channel);
+ }
+ /*
+ * Then, we are enabling @other_chan. We need to do the opposite from
+ * above.
+ */
+ ret = ltc4282_cache_history(st, channel);
+ if (ret)
+ return ret;
+
+ return ltc4282_cache_sync(st, other_chan);
+}
+
+static int ltc4282_write_in(struct ltc4282_state *st, u32 attr, long val,
+ int channel)
+{
+ switch (attr) {
+ case hwmon_in_max:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MAX,
+ 1280, val);
+
+ return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MAX,
+ channel,
+ &st->in0_1_cache[channel].in_max_raw, val);
+ case hwmon_in_min:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MIN,
+ 1280, val);
+
+ return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MIN,
+ channel,
+ &st->in0_1_cache[channel].in_min_raw, val);
+ case hwmon_in_reset_history:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_in_write_history(st,
+ LTC4282_VGPIO_LOWEST,
+ 1280, 0, 1280);
+
+ return ltc4282_vdd_source_reset_hist(st, channel);
+ case hwmon_in_enable:
+ return ltc4282_vdd_source_enable(st, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_curr_reset_hist(struct ltc4282_state *st)
+{
+ int ret;
+
+ ret = __ltc4282_in_write_history(st, LTC4282_VSENSE_LOWEST,
+ st->vsense_max, 0, 40 * MILLI);
+ if (ret)
+ return ret;
+
+ /* now, let's also clear possible overcurrent fault logs */
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_OC_FAULT_MASK);
+}
+
+static int ltc4282_write_curr(struct ltc4282_state *st, u32 attr,
+ long val)
+{
+ /* need to pass it in millivolt */
+ u32 in = DIV_ROUND_CLOSEST_ULL((u64)val * st->rsense, DECA * MICRO);
+
+ switch (attr) {
+ case hwmon_curr_max:
+ return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40,
+ in);
+ case hwmon_curr_min:
+ return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MIN, 40,
+ in);
+ case hwmon_curr_reset_history:
+ return ltc4282_curr_reset_hist(st);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_energy_enable_set(struct ltc4282_state *st, long val)
+{
+ int ret;
+
+ /* setting the bit halts the meter */
+ ret = regmap_update_bits(st->map, LTC4282_ADC_CTRL,
+ LTC4282_METER_HALT_MASK,
+ FIELD_PREP(LTC4282_METER_HALT_MASK, !val));
+ if (ret)
+ return ret;
+
+ st->energy_en = !!val;
+
+ return 0;
+}
+
+static int ltc4282_write(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct ltc4282_state *st = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_power:
+ return ltc4282_write_power(st, attr, val);
+ case hwmon_in:
+ return ltc4282_write_in(st, attr, val, channel);
+ case hwmon_curr:
+ return ltc4282_write_curr(st, attr, val);
+ case hwmon_energy:
+ return ltc4282_energy_enable_set(st, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t ltc4282_in_is_visible(const struct ltc4282_state *st, u32 attr)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_highest:
+ case hwmon_in_lowest:
+ case hwmon_in_max_alarm:
+ case hwmon_in_min_alarm:
+ case hwmon_in_label:
+ case hwmon_in_lcrit_alarm:
+ case hwmon_in_crit_alarm:
+ case hwmon_in_fault:
+ return 0444;
+ case hwmon_in_max:
+ case hwmon_in_min:
+ case hwmon_in_enable:
+ case hwmon_in_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_curr_is_visible(u32 attr)
+{
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_highest:
+ case hwmon_curr_lowest:
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
+ case hwmon_curr_crit_alarm:
+ case hwmon_curr_label:
+ return 0444;
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ case hwmon_curr_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_power_is_visible(u32 attr)
+{
+ switch (attr) {
+ case hwmon_power_input:
+ case hwmon_power_input_highest:
+ case hwmon_power_input_lowest:
+ case hwmon_power_label:
+ case hwmon_power_max_alarm:
+ case hwmon_power_min_alarm:
+ return 0444;
+ case hwmon_power_max:
+ case hwmon_power_min:
+ case hwmon_power_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc4282_in_is_visible(data, attr);
+ case hwmon_curr:
+ return ltc4282_curr_is_visible(attr);
+ case hwmon_power:
+ return ltc4282_power_is_visible(attr);
+ case hwmon_energy:
+ /* hwmon_energy_enable */
+ return 0644;
+ case hwmon_energy64:
+ /* hwmon_energy_input */
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static const char * const ltc4282_in_strs[] = {
+ "VSOURCE", "VDD", "VGPIO"
+};
+
+static int ltc4282_read_labels(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_in:
+ *str = ltc4282_in_strs[channel];
+ return 0;
+ case hwmon_curr:
+ *str = "ISENSE";
+ return 0;
+ case hwmon_power:
+ *str = "Power";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct clk_ops ltc4282_ops = {
+ .recalc_rate = ltc4282_recalc_rate,
+ .determine_rate = ltc4282_determine_rate,
+ .set_rate = ltc4282_set_rate,
+ .disable = ltc4282_disable,
+};
+
+static int ltc428_clk_provider_setup(struct ltc4282_state *st,
+ struct device *dev)
+{
+ struct clk_init_data init;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk",
+ fwnode_get_name(dev_fwnode(dev)));
+ if (!init.name)
+ return -ENOMEM;
+
+ init.ops = &ltc4282_ops;
+ init.flags = CLK_GET_RATE_NOCACHE;
+ st->clk_hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &st->clk_hw);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+ &st->clk_hw);
+}
+
+static int ltc428_clks_setup(struct ltc4282_state *st, struct device *dev)
+{
+ unsigned long rate;
+ struct clk *clkin;
+ u32 val;
+ int ret;
+
+ ret = ltc428_clk_provider_setup(st, dev);
+ if (ret)
+ return ret;
+
+ clkin = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clkin))
+ return dev_err_probe(dev, PTR_ERR(clkin),
+ "Failed to get clkin");
+ if (!clkin)
+ return 0;
+
+ rate = clk_get_rate(clkin);
+ if (!in_range(rate, LTC4282_CLKIN_MIN, LTC4282_CLKIN_RANGE))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid clkin range(%lu) [%lu %lu]\n",
+ rate, LTC4282_CLKIN_MIN,
+ LTC4282_CLKIN_MAX);
+
+ /*
+ * Clocks faster than 250KHZ should be reduced to 250KHZ. The clock
+ * frequency is divided by twice the value in the register.
+ */
+ val = rate / (2 * LTC4282_CLKIN_MIN);
+
+ return regmap_update_bits(st->map, LTC4282_CLK_DIV,
+ LTC4282_CLK_DIV_MASK,
+ FIELD_PREP(LTC4282_CLK_DIV_MASK, val));
+}
+
+static const int ltc4282_curr_lim_uv[] = {
+ 12500, 15625, 18750, 21875, 25000, 28125, 31250, 34375
+};
+
+static int ltc4282_get_defaults(struct ltc4282_state *st, u32 *vin_mode)
+{
+ u32 reg_val, ilm_adjust;
+ int ret;
+
+ ret = regmap_read(st->map, LTC4282_ADC_CTRL, &reg_val);
+ if (ret)
+ return ret;
+
+ st->energy_en = !FIELD_GET(LTC4282_METER_HALT_MASK, reg_val);
+
+ ret = regmap_read(st->map, LTC4282_CTRL_MSB, &reg_val);
+ if (ret)
+ return ret;
+
+ *vin_mode = FIELD_GET(LTC4282_CTRL_VIN_MODE_MASK, reg_val);
+
+ ret = regmap_read(st->map, LTC4282_ILIM_ADJUST, &reg_val);
+ if (ret)
+ return ret;
+
+ ilm_adjust = FIELD_GET(LTC4282_ILIM_ADJUST_MASK, reg_val);
+ st->vsense_max = ltc4282_curr_lim_uv[ilm_adjust];
+
+ st->in0_1_cache[LTC4282_CHAN_VSOURCE].en = FIELD_GET(LTC4282_VDD_MONITOR_MASK,
+ ilm_adjust);
+ if (!st->in0_1_cache[LTC4282_CHAN_VSOURCE].en) {
+ st->in0_1_cache[LTC4282_CHAN_VDD].en = true;
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_max_raw);
+ }
+
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[LTC4282_CHAN_VDD].in_max_raw);
+}
+
+/*
+ * Set max limits for ISENSE and Power as that depends on the max voltage on
+ * rsense that is defined in ILIM_ADJUST. This is specially important for power
+ * because for some rsense and vfsout values, if we allow the default raw 255
+ * value, that would overflow long in 32bit archs when reading back the max
+ * power limit.
+ *
+ * Also set meaningful historic values for VDD and VSOURCE
+ * (0 would not mean much).
+ */
+static int ltc4282_set_max_limits(struct ltc4282_state *st)
+{
+ int ret;
+
+ ret = ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40 * MILLI,
+ st->vsense_max);
+ if (ret)
+ return ret;
+
+ /* Power is given by ISENSE * Vout. */
+ st->power_max = DIV_ROUND_CLOSEST(st->vsense_max * DECA * MILLI, st->rsense) * st->vfs_out;
+ ret = ltc4282_write_power_byte(st, LTC4282_POWER_MAX, st->power_max);
+ if (ret)
+ return ret;
+
+ if (st->in0_1_cache[LTC4282_CHAN_VDD].en) {
+ st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_lowest = st->vfs_out;
+ return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->vdd, 0, st->vfs_out);
+ }
+
+ st->in0_1_cache[LTC4282_CHAN_VDD].in_lowest = st->vdd;
+ return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->vfs_out, 0, st->vfs_out);
+}
+
+static const char * const ltc4282_gpio1_modes[] = {
+ "power_bad", "power_good"
+};
+
+static const char * const ltc4282_gpio2_modes[] = {
+ "adc_input", "stress_fet"
+};
+
+static int ltc4282_gpio_setup(struct ltc4282_state *st, struct device *dev)
+{
+ const char *func = NULL;
+ int ret;
+
+ ret = device_property_read_string(dev, "adi,gpio1-mode", &func);
+ if (!ret) {
+ ret = match_string(ltc4282_gpio1_modes,
+ ARRAY_SIZE(ltc4282_gpio1_modes), func);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Invalid func(%s) for gpio1\n",
+ func);
+
+ ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG,
+ LTC4282_GPIO_1_CONFIG_MASK,
+ FIELD_PREP(LTC4282_GPIO_1_CONFIG_MASK, ret));
+ if (ret)
+ return ret;
+ }
+
+ ret = device_property_read_string(dev, "adi,gpio2-mode", &func);
+ if (!ret) {
+ ret = match_string(ltc4282_gpio2_modes,
+ ARRAY_SIZE(ltc4282_gpio2_modes), func);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Invalid func(%s) for gpio2\n",
+ func);
+ if (!ret) {
+ /* setting the bit to 1 so the ADC to monitors GPIO2 */
+ ret = regmap_set_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_GPIO_MODE_MASK);
+ } else {
+ ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG,
+ LTC4282_GPIO_2_FET_STRESS_MASK,
+ FIELD_PREP(LTC4282_GPIO_2_FET_STRESS_MASK, 1));
+ }
+
+ if (ret)
+ return ret;
+ }
+
+ if (!device_property_read_bool(dev, "adi,gpio3-monitor-enable"))
+ return 0;
+
+ if (func && !strcmp(func, "adc_input"))
+ return dev_err_probe(dev, -EINVAL,
+ "Cannot have both gpio2 and gpio3 muxed into the ADC");
+
+ return regmap_clear_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_GPIO_MODE_MASK);
+}
+
+static const char * const ltc4282_dividers[] = {
+ "external", "vdd_5_percent", "vdd_10_percent", "vdd_15_percent"
+};
+
+/* This maps the Vout full scale for the given Vin mode */
+static const u16 ltc4282_vfs_milli[] = { 5540, 8320, 16640, 33280 };
+
+static const u16 ltc4282_vdd_milli[] = { 3300, 5000, 12000, 24000 };
+
+enum {
+ LTC4282_VIN_3_3V,
+ LTC4282_VIN_5V,
+ LTC4282_VIN_12V,
+ LTC4282_VIN_24V,
+};
+
+static int ltc4282_setup(struct ltc4282_state *st, struct device *dev)
+{
+ const char *divider;
+ u32 val, vin_mode;
+ int ret;
+
+ /* The part has an eeprom so let's get the needed defaults from it */
+ ret = ltc4282_get_defaults(st, &vin_mode);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
+ &st->rsense);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read adi,rsense-nano-ohms\n");
+ if (st->rsense < CENTI)
+ return dev_err_probe(dev, -EINVAL,
+ "adi,rsense-nano-ohms too small (< %lu)\n",
+ CENTI);
+
+ /*
+ * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
+ * means we need nano in the bindings. However, to make things easier to
+ * handle (with respect to overflows) we divide it by 100 as we don't
+ * really need the last two digits.
+ */
+ st->rsense /= CENTI;
+
+ val = vin_mode;
+ ret = device_property_read_u32(dev, "adi,vin-mode-microvolt", &val);
+ if (!ret) {
+ switch (val) {
+ case 3300000:
+ val = LTC4282_VIN_3_3V;
+ break;
+ case 5000000:
+ val = LTC4282_VIN_5V;
+ break;
+ case 12000000:
+ val = LTC4282_VIN_12V;
+ break;
+ case 24000000:
+ val = LTC4282_VIN_24V;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%u) for vin-mode-microvolt\n",
+ val);
+ }
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_VIN_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_VIN_MODE_MASK, val));
+ if (ret)
+ return ret;
+
+ /* Foldback mode should also be set to the input voltage */
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_FOLDBACK_MODE_MASK,
+ FIELD_PREP(LTC4282_FOLDBACK_MODE_MASK, val));
+ if (ret)
+ return ret;
+ }
+
+ st->vfs_out = ltc4282_vfs_milli[val];
+ st->vdd = ltc4282_vdd_milli[val];
+
+ ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
+ &st->vsense_max);
+ if (!ret) {
+ int reg_val;
+
+ switch (val) {
+ case 12500:
+ reg_val = 0;
+ break;
+ case 15625:
+ reg_val = 1;
+ break;
+ case 18750:
+ reg_val = 2;
+ break;
+ case 21875:
+ reg_val = 3;
+ break;
+ case 25000:
+ reg_val = 4;
+ break;
+ case 28125:
+ reg_val = 5;
+ break;
+ case 31250:
+ reg_val = 6;
+ break;
+ case 34375:
+ reg_val = 7;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%u) for adi,current-limit-microvolt\n",
+ st->vsense_max);
+ }
+
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_ILIM_ADJUST_MASK,
+ FIELD_PREP(LTC4282_ILIM_ADJUST_MASK, reg_val));
+ if (ret)
+ return ret;
+ }
+
+ ret = ltc4282_set_max_limits(st);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_string(dev, "adi,overvoltage-dividers",
+ &divider);
+ if (!ret) {
+ int div = match_string(ltc4282_dividers,
+ ARRAY_SIZE(ltc4282_dividers), divider);
+ if (div < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%s) for adi,overvoltage-divider\n",
+ divider);
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_OV_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_OV_MODE_MASK, div));
+ }
+
+ ret = device_property_read_string(dev, "adi,undervoltage-dividers",
+ &divider);
+ if (!ret) {
+ int div = match_string(ltc4282_dividers,
+ ARRAY_SIZE(ltc4282_dividers), divider);
+ if (div < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%s) for adi,undervoltage-divider\n",
+ divider);
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_UV_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_UV_MODE_MASK, div));
+ }
+
+ if (device_property_read_bool(dev, "adi,overcurrent-retry")) {
+ ret = regmap_set_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_OC_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
+ ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_OV_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
+ ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_UV_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,fault-log-enable")) {
+ ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_FAULT_LOG_EN_MASK);
+ if (ret)
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "adi,fet-bad-timeout-ms", &val);
+ if (!ret) {
+ if (val > LTC4282_FET_BAD_MAX_TIMEOUT)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid value(%u) for adi,fet-bad-timeout-ms",
+ val);
+
+ ret = regmap_write(st->map, LTC4282_FET_BAD_FAULT_TIMEOUT, val);
+ if (ret)
+ return ret;
+ }
+
+ return ltc4282_gpio_setup(st, dev);
+}
+
+static bool ltc4282_readable_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2)
+ return false;
+
+ return true;
+}
+
+static bool ltc4282_writable_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == LTC4282_STATUS_LSB || reg == LTC4282_STATUS_MSB)
+ return false;
+ if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2)
+ return false;
+
+ return true;
+}
+
+static const struct regmap_config ltc4282_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = LTC4282_RESERVED_3,
+ .readable_reg = ltc4282_readable_reg,
+ .writeable_reg = ltc4282_writable_reg,
+};
+
+static const struct hwmon_channel_info * const ltc4282_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_MAX_ALARM | HWMON_I_ENABLE |
+ HWMON_I_RESET_HISTORY | HWMON_I_FAULT |
+ HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_MAX_ALARM | HWMON_I_LCRIT_ALARM |
+ HWMON_I_CRIT_ALARM | HWMON_I_ENABLE |
+ HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
+ HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
+ HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
+ HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
+ HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
+ HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
+ HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
+ HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
+ HWMON_CHANNEL_INFO(energy,
+ HWMON_E_ENABLE),
+ HWMON_CHANNEL_INFO(energy64,
+ HWMON_E_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops ltc4282_hwmon_ops = {
+ .read = ltc4282_read,
+ .write = ltc4282_write,
+ .is_visible = ltc4282_is_visible,
+ .read_string = ltc4282_read_labels,
+};
+
+static const struct hwmon_chip_info ltc4282_chip_info = {
+ .ops = &ltc4282_hwmon_ops,
+ .info = ltc4282_info,
+};
+
+static int ltc4282_show_fault_log(void *arg, u64 *val, u32 mask)
+{
+ struct ltc4282_state *st = arg;
+ long alarm;
+ int ret;
+
+ ret = ltc4282_read_alarm(st, LTC4282_FAULT_LOG, mask, &alarm);
+ if (ret)
+ return ret;
+
+ *val = alarm;
+
+ return 0;
+}
+
+static int ltc4282_show_curr1_crit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_OC_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_curr1_crit_fault_log,
+ ltc4282_show_curr1_crit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_in1_lcrit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_UV_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_lcrit_fault_log,
+ ltc4282_show_in1_lcrit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_in1_crit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_OV_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_crit_fault_log,
+ ltc4282_show_in1_crit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_fet_bad_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_FET_BAD_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_bad_fault_log,
+ ltc4282_show_fet_bad_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_fet_short_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_FET_SHORT_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_short_fault_log,
+ ltc4282_show_fet_short_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_power1_bad_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_POWER_BAD_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_power1_bad_fault_log,
+ ltc4282_show_power1_bad_fault_log, NULL, "%llu\n");
+
+static void ltc4282_debugfs_init(struct ltc4282_state *st, struct i2c_client *i2c)
+{
+ debugfs_create_file_unsafe("power1_bad_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_power1_bad_fault_log);
+ debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_fet_short_fault_log);
+ debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_fet_bad_fault_log);
+ debugfs_create_file_unsafe("in1_crit_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_in1_crit_fault_log);
+ debugfs_create_file_unsafe("in1_lcrit_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_in1_lcrit_fault_log);
+ debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
+ &ltc4282_curr1_crit_fault_log);
+}
+
+static int ltc4282_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev, *hwmon;
+ struct ltc4282_state *st;
+ int ret;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->map = devm_regmap_init_i2c(i2c, &ltc4282_regmap_config);
+ if (IS_ERR(st->map))
+ return dev_err_probe(dev, PTR_ERR(st->map),
+ "failed regmap init\n");
+
+ /* Soft reset */
+ ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_RESET_MASK);
+ if (ret)
+ return ret;
+
+ /* Yes, it's big but it is as specified in the datasheet */
+ msleep(3200);
+
+ ret = ltc428_clks_setup(st, dev);
+ if (ret)
+ return ret;
+
+ ret = ltc4282_setup(st, dev);
+ if (ret)
+ return ret;
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "ltc4282", st,
+ &ltc4282_chip_info, NULL);
+ if (IS_ERR(hwmon))
+ return PTR_ERR(hwmon);
+
+ ltc4282_debugfs_init(st, i2c);
+
+ return 0;
+}
+
+static const struct of_device_id ltc4282_of_match[] = {
+ { .compatible = "adi,ltc4282" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ltc4282_of_match);
+
+static struct i2c_driver ltc4282_driver = {
+ .driver = {
+ .name = "ltc4282",
+ .of_match_table = ltc4282_of_match,
+ },
+ .probe = ltc4282_probe,
+};
+module_i2c_driver(ltc4282_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("LTC4282 I2C High Current Hot Swap Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
new file mode 100644
index 000000000000..1c0bbec7e8eb
--- /dev/null
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -0,0 +1,851 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC hwmon driver for Apple Silicon platforms
+ *
+ * The System Management Controller on Apple Silicon devices is responsible for
+ * measuring data from sensors across the SoC and machine. These include power,
+ * temperature, voltage and current sensors. Some "sensors" actually expose
+ * derived values. An example of this is the key PHPC, which is an estimate
+ * of the heat energy being dissipated by the SoC.
+ *
+ * While each SoC only has one SMC variant, each platform exposes a different
+ * set of sensors. For example, M1 MacBooks expose battery telemetry sensors
+ * which are not present on the M1 Mac mini. For this reason, the available
+ * sensors for a given platform are described in the device tree in a child
+ * node of the SMC device. We must walk this list of available sensors and
+ * populate the required hwmon data structures at runtime.
+ *
+ * Originally based on a concept by Jean-Francois Bortolotti <jeff@borto.fr>
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define MAX_LABEL_LENGTH 32
+
+/* Temperature, voltage, current, power, fan(s) */
+#define NUM_SENSOR_TYPES 5
+
+#define FLT_EXP_BIAS 127
+#define FLT_EXP_MASK GENMASK(30, 23)
+#define FLT_MANT_BIAS 23
+#define FLT_MANT_MASK GENMASK(22, 0)
+#define FLT_SIGN_MASK BIT(31)
+
+static bool fan_control;
+module_param_unsafe(fan_control, bool, 0644);
+MODULE_PARM_DESC(fan_control,
+ "Override the SMC to set your own fan speeds on supported machines");
+
+struct macsmc_hwmon_sensor {
+ struct apple_smc_key_info info;
+ smc_key macsmc_key;
+ char label[MAX_LABEL_LENGTH];
+ u32 attrs;
+};
+
+struct macsmc_hwmon_fan {
+ struct macsmc_hwmon_sensor now;
+ struct macsmc_hwmon_sensor min;
+ struct macsmc_hwmon_sensor max;
+ struct macsmc_hwmon_sensor set;
+ struct macsmc_hwmon_sensor mode;
+ char label[MAX_LABEL_LENGTH];
+ u32 attrs;
+ bool manual;
+};
+
+struct macsmc_hwmon_sensors {
+ struct hwmon_channel_info channel_info;
+ struct macsmc_hwmon_sensor *sensors;
+ u32 count;
+};
+
+struct macsmc_hwmon_fans {
+ struct hwmon_channel_info channel_info;
+ struct macsmc_hwmon_fan *fans;
+ u32 count;
+};
+
+struct macsmc_hwmon {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct device *hwmon_dev;
+ struct hwmon_chip_info chip_info;
+ /* Chip + sensor types + NULL */
+ const struct hwmon_channel_info *channel_infos[1 + NUM_SENSOR_TYPES + 1];
+ struct macsmc_hwmon_sensors temp;
+ struct macsmc_hwmon_sensors volt;
+ struct macsmc_hwmon_sensors curr;
+ struct macsmc_hwmon_sensors power;
+ struct macsmc_hwmon_fans fan;
+};
+
+static int macsmc_hwmon_read_label(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ *str = hwmon->temp.sensors[channel].label;
+ break;
+ case hwmon_in:
+ *str = hwmon->volt.sensors[channel].label;
+ break;
+ case hwmon_curr:
+ *str = hwmon->curr.sensors[channel].label;
+ break;
+ case hwmon_power:
+ *str = hwmon->power.sensors[channel].label;
+ break;
+ case hwmon_fan:
+ *str = hwmon->fan.fans[channel].label;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/*
+ * A number of sensors report data in a 48.16 fixed-point decimal format that is
+ * not used by any other function of the SMC.
+ */
+static int macsmc_hwmon_read_ioft_scaled(struct apple_smc *smc, smc_key key,
+ u64 *p, int scale)
+{
+ u64 val;
+ int ret;
+
+ ret = apple_smc_read_u64(smc, key, &val);
+ if (ret < 0)
+ return ret;
+
+ *p = mult_frac(val, scale, 65536);
+
+ return 0;
+}
+
+/*
+ * Many sensors report their data as IEEE-754 floats. No other SMC function uses
+ * them.
+ */
+static int macsmc_hwmon_read_f32_scaled(struct apple_smc *smc, smc_key key,
+ int *p, int scale)
+{
+ u32 fval;
+ u64 val;
+ int ret, exp;
+
+ ret = apple_smc_read_u32(smc, key, &fval);
+ if (ret < 0)
+ return ret;
+
+ val = ((u64)((fval & FLT_MANT_MASK) | BIT(23)));
+ exp = ((fval >> 23) & 0xff) - FLT_EXP_BIAS - FLT_MANT_BIAS;
+
+ /* We never have negatively scaled SMC floats */
+ val *= scale;
+
+ if (exp > 63)
+ val = U64_MAX;
+ else if (exp < -63)
+ val = 0;
+ else if (exp < 0)
+ val >>= -exp;
+ else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */
+ val = U64_MAX;
+ else
+ val <<= exp;
+
+ if (fval & FLT_SIGN_MASK) {
+ if (val > (-(s64)INT_MIN))
+ *p = INT_MIN;
+ else
+ *p = -val;
+ } else {
+ if (val > INT_MAX)
+ *p = INT_MAX;
+ else
+ *p = val;
+ }
+
+ return 0;
+}
+
+/*
+ * The SMC has keys of multiple types, denoted by a FourCC of the same format
+ * as the key ID. We don't know what data type a key encodes until we poke at it.
+ */
+static int macsmc_hwmon_read_key(struct apple_smc *smc,
+ struct macsmc_hwmon_sensor *sensor, int scale,
+ long *val)
+{
+ int ret;
+
+ switch (sensor->info.type_code) {
+ /* 32-bit IEEE 754 float */
+ case __SMC_KEY('f', 'l', 't', ' '): {
+ u32 flt_ = 0;
+
+ ret = macsmc_hwmon_read_f32_scaled(smc, sensor->macsmc_key,
+ &flt_, scale);
+ if (ret)
+ return ret;
+
+ *val = flt_;
+ break;
+ }
+ /* 48.16 fixed point decimal */
+ case __SMC_KEY('i', 'o', 'f', 't'): {
+ u64 ioft = 0;
+
+ ret = macsmc_hwmon_read_ioft_scaled(smc, sensor->macsmc_key,
+ &ioft, scale);
+ if (ret)
+ return ret;
+
+ *val = (long)ioft;
+ break;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int macsmc_hwmon_write_f32(struct apple_smc *smc, smc_key key, int value)
+{
+ u64 val;
+ u32 fval = 0;
+ int exp = 0, neg;
+
+ val = abs(value);
+ neg = val != value;
+
+ if (val) {
+ int msb = __fls(val) - exp;
+
+ if (msb > 23) {
+ val >>= msb - FLT_MANT_BIAS;
+ exp -= msb - FLT_MANT_BIAS;
+ } else if (msb < 23) {
+ val <<= FLT_MANT_BIAS - msb;
+ exp += msb;
+ }
+
+ fval = FIELD_PREP(FLT_SIGN_MASK, neg) |
+ FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) |
+ FIELD_PREP(FLT_MANT_MASK, val);
+ }
+
+ return apple_smc_write_u32(smc, key, fval);
+}
+
+static int macsmc_hwmon_write_key(struct apple_smc *smc,
+ struct macsmc_hwmon_sensor *sensor, long val)
+{
+ switch (sensor->info.type_code) {
+ /* 32-bit IEEE 754 float */
+ case __SMC_KEY('f', 'l', 't', ' '):
+ return macsmc_hwmon_write_f32(smc, sensor->macsmc_key, val);
+ /* unsigned 8-bit integer */
+ case __SMC_KEY('u', 'i', '8', ' '):
+ return apple_smc_write_u8(smc, sensor->macsmc_key, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan,
+ long *val)
+{
+ switch (attr) {
+ case hwmon_fan_input:
+ return macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->fan.fans[chan].now, 1, val);
+ case hwmon_fan_min:
+ return macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->fan.fans[chan].min, 1, val);
+ case hwmon_fan_max:
+ return macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->fan.fans[chan].max, 1, val);
+ case hwmon_fan_target:
+ return macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->fan.fans[chan].set, 1, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+ long min, max;
+ int ret;
+
+ if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0)
+ return -EOPNOTSUPP;
+
+ /*
+ * The SMC does no sanity checks on requested fan speeds, so we need to.
+ */
+ ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min,
+ 1, &min);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max,
+ 1, &max);
+ if (ret)
+ return ret;
+
+ if (val >= min && val <= max) {
+ if (!hwmon->fan.fans[channel].manual) {
+ /* Write 1 to mode key for manual control */
+ ret = macsmc_hwmon_write_key(hwmon->smc,
+ &hwmon->fan.fans[channel].mode, 1);
+ if (ret < 0)
+ return ret;
+
+ hwmon->fan.fans[channel].manual = true;
+ }
+ return macsmc_hwmon_write_key(hwmon->smc,
+ &hwmon->fan.fans[channel].set, val);
+ } else if (!val) {
+ if (hwmon->fan.fans[channel].manual) {
+ ret = macsmc_hwmon_write_key(hwmon->smc,
+ &hwmon->fan.fans[channel].mode, 0);
+ if (ret < 0)
+ return ret;
+
+ hwmon->fan.fans[channel].manual = false;
+ }
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+ int ret = 0;
+
+ switch (type) {
+ case hwmon_temp:
+ ret = macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->temp.sensors[channel], 1000, val);
+ break;
+ case hwmon_in:
+ ret = macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->volt.sensors[channel], 1000, val);
+ break;
+ case hwmon_curr:
+ ret = macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->curr.sensors[channel], 1000, val);
+ break;
+ case hwmon_power:
+ /* SMC returns power in Watts with acceptable precision to scale to uW */
+ ret = macsmc_hwmon_read_key(hwmon->smc,
+ &hwmon->power.sensors[channel],
+ 1000000, val);
+ break;
+ case hwmon_fan:
+ ret = macsmc_hwmon_read_fan(hwmon, attr, channel, val);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_fan:
+ return macsmc_hwmon_write_fan(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t macsmc_hwmon_fan_is_visible(const struct macsmc_hwmon_fan *fan,
+ u32 attr)
+{
+ if (fan->attrs & BIT(attr)) {
+ if (attr == hwmon_fan_target && fan_control && fan->mode.macsmc_key)
+ return 0644;
+
+ return 0444;
+ }
+
+ return 0;
+}
+
+static umode_t macsmc_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct macsmc_hwmon *hwmon = data;
+ struct macsmc_hwmon_sensor *sensor;
+
+ switch (type) {
+ case hwmon_in:
+ sensor = &hwmon->volt.sensors[channel];
+ break;
+ case hwmon_curr:
+ sensor = &hwmon->curr.sensors[channel];
+ break;
+ case hwmon_power:
+ sensor = &hwmon->power.sensors[channel];
+ break;
+ case hwmon_temp:
+ sensor = &hwmon->temp.sensors[channel];
+ break;
+ case hwmon_fan:
+ return macsmc_hwmon_fan_is_visible(&hwmon->fan.fans[channel], attr);
+ default:
+ return 0;
+ }
+
+ /* Sensors only register ro attributes */
+ if (sensor->attrs & BIT(attr))
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_ops macsmc_hwmon_ops = {
+ .is_visible = macsmc_hwmon_is_visible,
+ .read = macsmc_hwmon_read,
+ .read_string = macsmc_hwmon_read_label,
+ .write = macsmc_hwmon_write,
+};
+
+/*
+ * Get the key metadata, including key data type, from the SMC.
+ */
+static int macsmc_hwmon_parse_key(struct device *dev, struct apple_smc *smc,
+ struct macsmc_hwmon_sensor *sensor,
+ const char *key)
+{
+ int ret;
+
+ ret = apple_smc_get_key_info(smc, _SMC_KEY(key), &sensor->info);
+ if (ret) {
+ dev_dbg(dev, "Failed to retrieve key info for %s\n", key);
+ return ret;
+ }
+
+ sensor->macsmc_key = _SMC_KEY(key);
+
+ return 0;
+}
+
+/*
+ * A sensor is a single key-value pair as made available by the SMC.
+ * The devicetree gives us the SMC key ID and a friendly name where the
+ * purpose of the sensor is known.
+ */
+static int macsmc_hwmon_create_sensor(struct device *dev, struct apple_smc *smc,
+ struct device_node *sensor_node,
+ struct macsmc_hwmon_sensor *sensor)
+{
+ const char *key, *label;
+ int ret;
+
+ ret = of_property_read_string(sensor_node, "apple,key-id", &key);
+ if (ret) {
+ dev_dbg(dev, "Could not find apple,key-id in sensor node\n");
+ return ret;
+ }
+
+ ret = macsmc_hwmon_parse_key(dev, smc, sensor, key);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_string(sensor_node, "label", &label);
+ if (ret)
+ dev_dbg(dev, "No label found for sensor %s\n", key);
+ else
+ strscpy_pad(sensor->label, label, sizeof(sensor->label));
+
+ return 0;
+}
+
+/*
+ * Fan data is exposed by the SMC as multiple sensors.
+ *
+ * The devicetree schema reuses apple,key-id for the actual fan speed sensor.
+ * Min, max and target keys do not need labels, so we can reuse label
+ * for naming the entire fan.
+ */
+static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
+ struct device_node *fan_node,
+ struct macsmc_hwmon_fan *fan)
+{
+ const char *label, *now, *min, *max, *set, *mode;
+ int ret;
+
+ ret = of_property_read_string(fan_node, "apple,key-id", &now);
+ if (ret) {
+ dev_err(dev, "apple,key-id not found in fan node!\n");
+ return ret;
+ }
+
+ ret = macsmc_hwmon_parse_key(dev, smc, &fan->now, now);
+ if (ret)
+ return ret;
+
+ fan->attrs = HWMON_F_INPUT;
+
+ ret = of_property_read_string(fan_node, "label", &label);
+ if (ret) {
+ dev_dbg(dev, "No label found for fan %s\n", now);
+ } else {
+ strscpy_pad(fan->label, label, sizeof(fan->label));
+ fan->attrs |= HWMON_F_LABEL;
+ }
+
+ /* The following keys are not required to simply monitor fan speed */
+ if (!of_property_read_string(fan_node, "apple,fan-minimum", &min)) {
+ ret = macsmc_hwmon_parse_key(dev, smc, &fan->min, min);
+ if (ret)
+ return ret;
+
+ fan->attrs |= HWMON_F_MIN;
+ }
+
+ if (!of_property_read_string(fan_node, "apple,fan-maximum", &max)) {
+ ret = macsmc_hwmon_parse_key(dev, smc, &fan->max, max);
+ if (ret)
+ return ret;
+
+ fan->attrs |= HWMON_F_MAX;
+ }
+
+ if (!of_property_read_string(fan_node, "apple,fan-target", &set)) {
+ ret = macsmc_hwmon_parse_key(dev, smc, &fan->set, set);
+ if (ret)
+ return ret;
+
+ fan->attrs |= HWMON_F_TARGET;
+ }
+
+ if (!of_property_read_string(fan_node, "apple,fan-mode", &mode)) {
+ ret = macsmc_hwmon_parse_key(dev, smc, &fan->mode, mode);
+ if (ret)
+ return ret;
+ }
+
+ /* Initialise fan control mode to automatic */
+ fan->manual = false;
+
+ return 0;
+}
+
+static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
+ struct device_node *hwmon_node)
+{
+ struct device_node *key_node __maybe_unused;
+ struct macsmc_hwmon_sensor *sensor;
+ u32 n_current = 0, n_fan = 0, n_power = 0, n_temperature = 0, n_voltage = 0;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") {
+ n_current++;
+ }
+
+ if (n_current) {
+ hwmon->curr.sensors = devm_kcalloc(hwmon->dev, n_current,
+ sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
+ if (!hwmon->curr.sensors)
+ return -ENOMEM;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") {
+ sensor = &hwmon->curr.sensors[hwmon->curr.count];
+ if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
+ sensor->attrs = HWMON_C_INPUT;
+
+ if (*sensor->label)
+ sensor->attrs |= HWMON_C_LABEL;
+
+ hwmon->curr.count++;
+ }
+ }
+ }
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") {
+ n_fan++;
+ }
+
+ if (n_fan) {
+ hwmon->fan.fans = devm_kcalloc(hwmon->dev, n_fan,
+ sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
+ if (!hwmon->fan.fans)
+ return -ENOMEM;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") {
+ if (!macsmc_hwmon_create_fan(hwmon->dev, hwmon->smc, key_node,
+ &hwmon->fan.fans[hwmon->fan.count]))
+ hwmon->fan.count++;
+ }
+ }
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") {
+ n_power++;
+ }
+
+ if (n_power) {
+ hwmon->power.sensors = devm_kcalloc(hwmon->dev, n_power,
+ sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
+ if (!hwmon->power.sensors)
+ return -ENOMEM;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") {
+ sensor = &hwmon->power.sensors[hwmon->power.count];
+ if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
+ sensor->attrs = HWMON_P_INPUT;
+
+ if (*sensor->label)
+ sensor->attrs |= HWMON_P_LABEL;
+
+ hwmon->power.count++;
+ }
+ }
+ }
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") {
+ n_temperature++;
+ }
+
+ if (n_temperature) {
+ hwmon->temp.sensors = devm_kcalloc(hwmon->dev, n_temperature,
+ sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
+ if (!hwmon->temp.sensors)
+ return -ENOMEM;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") {
+ sensor = &hwmon->temp.sensors[hwmon->temp.count];
+ if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
+ sensor->attrs = HWMON_T_INPUT;
+
+ if (*sensor->label)
+ sensor->attrs |= HWMON_T_LABEL;
+
+ hwmon->temp.count++;
+ }
+ }
+ }
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "voltage-") {
+ n_voltage++;
+ }
+
+ if (n_voltage) {
+ hwmon->volt.sensors = devm_kcalloc(hwmon->dev, n_voltage,
+ sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
+ if (!hwmon->volt.sensors)
+ return -ENOMEM;
+
+ for_each_child_of_node_with_prefix(hwmon_node, key_node, "volt-") {
+ sensor = &hwmon->temp.sensors[hwmon->temp.count];
+ if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
+ sensor->attrs = HWMON_I_INPUT;
+
+ if (*sensor->label)
+ sensor->attrs |= HWMON_I_LABEL;
+
+ hwmon->volt.count++;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Create NULL-terminated config arrays */
+static void macsmc_hwmon_populate_configs(u32 *configs, const struct macsmc_hwmon_sensors *sensors)
+{
+ int idx;
+
+ for (idx = 0; idx < sensors->count; idx++)
+ configs[idx] = sensors->sensors[idx].attrs;
+}
+
+static void macsmc_hwmon_populate_fan_configs(u32 *configs, const struct macsmc_hwmon_fans *fans)
+{
+ int idx;
+
+ for (idx = 0; idx < fans->count; idx++)
+ configs[idx] = fans->fans[idx].attrs;
+}
+
+static const struct hwmon_channel_info *const macsmc_chip_channel_info =
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ);
+
+static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon)
+{
+ struct hwmon_channel_info *channel_info;
+ int i = 0;
+
+ /* chip */
+ hwmon->channel_infos[i++] = macsmc_chip_channel_info;
+
+ if (hwmon->curr.count) {
+ channel_info = &hwmon->curr.channel_info;
+ channel_info->type = hwmon_curr;
+ channel_info->config = devm_kcalloc(hwmon->dev, hwmon->curr.count + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!channel_info->config)
+ return -ENOMEM;
+
+ macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->curr);
+ hwmon->channel_infos[i++] = channel_info;
+ }
+
+ if (hwmon->fan.count) {
+ channel_info = &hwmon->fan.channel_info;
+ channel_info->type = hwmon_fan;
+ channel_info->config = devm_kcalloc(hwmon->dev, hwmon->fan.count + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!channel_info->config)
+ return -ENOMEM;
+
+ macsmc_hwmon_populate_fan_configs((u32 *)channel_info->config, &hwmon->fan);
+ hwmon->channel_infos[i++] = channel_info;
+ }
+
+ if (hwmon->power.count) {
+ channel_info = &hwmon->power.channel_info;
+ channel_info->type = hwmon_power;
+ channel_info->config = devm_kcalloc(hwmon->dev, hwmon->power.count + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!channel_info->config)
+ return -ENOMEM;
+
+ macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->power);
+ hwmon->channel_infos[i++] = channel_info;
+ }
+
+ if (hwmon->temp.count) {
+ channel_info = &hwmon->temp.channel_info;
+ channel_info->type = hwmon_temp;
+ channel_info->config = devm_kcalloc(hwmon->dev, hwmon->temp.count + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!channel_info->config)
+ return -ENOMEM;
+
+ macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->temp);
+ hwmon->channel_infos[i++] = channel_info;
+ }
+
+ if (hwmon->volt.count) {
+ channel_info = &hwmon->volt.channel_info;
+ channel_info->type = hwmon_in;
+ channel_info->config = devm_kcalloc(hwmon->dev, hwmon->volt.count + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!channel_info->config)
+ return -ENOMEM;
+
+ macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->volt);
+ hwmon->channel_infos[i++] = channel_info;
+ }
+
+ return 0;
+}
+
+static int macsmc_hwmon_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_hwmon *hwmon;
+ int ret;
+
+ /*
+ * The MFD driver will try to probe us unconditionally. Some devices
+ * with the SMC do not have hwmon capabilities. Only probe if we have
+ * a hwmon node.
+ */
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon),
+ GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ hwmon->dev = &pdev->dev;
+ hwmon->smc = smc;
+
+ ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node);
+ if (ret) {
+ dev_err(hwmon->dev, "Could not parse sensors\n");
+ return ret;
+ }
+
+ if (!hwmon->curr.count && !hwmon->fan.count &&
+ !hwmon->power.count && !hwmon->temp.count &&
+ !hwmon->volt.count) {
+ dev_err(hwmon->dev,
+ "No valid sensors found of any supported type\n");
+ return -ENODEV;
+ }
+
+ ret = macsmc_hwmon_create_infos(hwmon);
+ if (ret)
+ return ret;
+
+ hwmon->chip_info.ops = &macsmc_hwmon_ops;
+ hwmon->chip_info.info =
+ (const struct hwmon_channel_info *const *)&hwmon->channel_infos;
+
+ hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "macsmc_hwmon", hwmon,
+ &hwmon->chip_info, NULL);
+ if (IS_ERR(hwmon->hwmon_dev))
+ return dev_err_probe(hwmon->dev, PTR_ERR(hwmon->hwmon_dev),
+ "Probing SMC hwmon device failed\n");
+
+ dev_dbg(hwmon->dev, "Registered SMC hwmon device. Sensors:\n");
+ dev_dbg(hwmon->dev,
+ "Current: %d, Fans: %d, Power: %d, Temperature: %d, Voltage: %d",
+ hwmon->curr.count, hwmon->fan.count,
+ hwmon->power.count, hwmon->temp.count,
+ hwmon->volt.count);
+
+ return 0;
+}
+
+static const struct of_device_id macsmc_hwmon_of_table[] = {
+ { .compatible = "apple,smc-hwmon" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table);
+
+static struct platform_driver macsmc_hwmon_driver = {
+ .probe = macsmc_hwmon_probe,
+ .driver = {
+ .name = "macsmc-hwmon",
+ .of_match_table = macsmc_hwmon_of_table,
+ },
+};
+module_platform_driver(macsmc_hwmon_driver);
+
+MODULE_DESCRIPTION("Apple Silicon SMC hwmon driver");
+MODULE_AUTHOR("James Calligeros <jcalligeros99@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c
index ee5ead06d612..5102d86d2619 100644
--- a/drivers/hwmon/max127.c
+++ b/drivers/hwmon/max127.c
@@ -45,7 +45,6 @@
#define MAX127_SIGN_BIT BIT(11)
struct max127_data {
- struct mutex lock;
struct i2c_client *client;
u8 ctrl_byte[MAX127_NUM_CHANNELS];
};
@@ -121,21 +120,16 @@ static int max127_read_input(struct max127_data *data, int channel, long *val)
struct i2c_client *client = data->client;
u8 ctrl_byte = data->ctrl_byte[channel];
- mutex_lock(&data->lock);
-
status = max127_select_channel(client, ctrl_byte);
if (status)
- goto exit;
+ return status;
status = max127_read_channel(client, &raw);
if (status)
- goto exit;
+ return status;
*val = max127_process_raw(ctrl_byte, raw);
-
-exit:
- mutex_unlock(&data->lock);
- return status;
+ return 0;
}
static int max127_read_min(struct max127_data *data, int channel, long *val)
@@ -170,8 +164,6 @@ static int max127_write_min(struct max127_data *data, int channel, long val)
{
u8 ctrl;
- mutex_lock(&data->lock);
-
ctrl = data->ctrl_byte[channel];
if (val <= -MAX127_FULL_RANGE) {
ctrl |= (MAX127_CTRL_RNG | MAX127_CTRL_BIP);
@@ -182,23 +174,15 @@ static int max127_write_min(struct max127_data *data, int channel, long val)
ctrl &= ~MAX127_CTRL_BIP;
}
data->ctrl_byte[channel] = ctrl;
-
- mutex_unlock(&data->lock);
-
return 0;
}
static int max127_write_max(struct max127_data *data, int channel, long val)
{
- mutex_lock(&data->lock);
-
if (val >= MAX127_FULL_RANGE)
data->ctrl_byte[channel] |= MAX127_CTRL_RNG;
else
data->ctrl_byte[channel] &= ~MAX127_CTRL_RNG;
-
- mutex_unlock(&data->lock);
-
return 0;
}
@@ -315,7 +299,6 @@ static int max127_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->lock);
for (i = 0; i < ARRAY_SIZE(data->ctrl_byte); i++)
data->ctrl_byte[i] = (MAX127_CTRL_START |
MAX127_SET_CHANNEL(i));
@@ -329,13 +312,12 @@ static int max127_probe(struct i2c_client *client)
}
static const struct i2c_device_id max127_id[] = {
- { "max127", 0 },
+ { "max127" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max127_id);
static struct i2c_driver max127_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max127",
},
diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c
index aa38c45adc09..4c9e7892a73c 100644
--- a/drivers/hwmon/max16065.c
+++ b/drivers/hwmon/max16065.c
@@ -79,7 +79,7 @@ static const bool max16065_have_current[] = {
};
struct max16065_data {
- enum chips type;
+ enum chips chip;
struct i2c_client *client;
const struct attribute_group *groups[4];
struct mutex update_lock;
@@ -114,9 +114,10 @@ static inline int LIMIT_TO_MV(int limit, int range)
return limit * range / 256;
}
-static inline int MV_TO_LIMIT(int mv, int range)
+static inline int MV_TO_LIMIT(unsigned long mv, int range)
{
- return clamp_val(DIV_ROUND_CLOSEST(mv * 256, range), 0, 255);
+ mv = clamp_val(mv, 0, ULONG_MAX / 256);
+ return DIV_ROUND_CLOSEST(clamp_val(mv * 256, 0, range * 255), range);
}
static inline int ADC_TO_CURR(int adc, int gain)
@@ -161,10 +162,17 @@ static struct max16065_data *max16065_update_device(struct device *dev)
MAX16065_CURR_SENSE);
}
- for (i = 0; i < DIV_ROUND_UP(data->num_adc, 8); i++)
+ for (i = 0; i < 2; i++)
data->fault[i]
= i2c_smbus_read_byte_data(client, MAX16065_FAULT(i));
+ /*
+ * MAX16067 and MAX16068 have separate undervoltage and
+ * overvoltage alarm bits. Squash them together.
+ */
+ if (data->chip == max16067 || data->chip == max16068)
+ data->fault[0] |= data->fault[1];
+
data->last_updated = jiffies;
data->valid = true;
}
@@ -208,12 +216,13 @@ static ssize_t max16065_current_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct max16065_data *data = max16065_update_device(dev);
+ int curr_sense = data->curr_sense;
- if (unlikely(data->curr_sense < 0))
- return data->curr_sense;
+ if (unlikely(curr_sense < 0))
+ return curr_sense;
return sysfs_emit(buf, "%d\n",
- ADC_TO_CURR(data->curr_sense, data->curr_gain));
+ ADC_TO_CURR(curr_sense, data->curr_gain));
}
static ssize_t max16065_limit_store(struct device *dev,
@@ -493,8 +502,6 @@ static const struct attribute_group max16065_max_group = {
.is_visible = max16065_secondary_is_visible,
};
-static const struct i2c_device_id max16065_id[];
-
static int max16065_probe(struct i2c_client *client)
{
struct i2c_adapter *adapter = client->adapter;
@@ -505,7 +512,7 @@ static int max16065_probe(struct i2c_client *client)
bool have_secondary; /* true if chip has secondary limits */
bool secondary_is_max = false; /* secondary limits reflect max */
int groups = 0;
- const struct i2c_device_id *id = i2c_match_id(max16065_id, client);
+ enum chips chip = (uintptr_t)i2c_get_match_data(client);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_READ_WORD_DATA))
@@ -515,12 +522,13 @@ static int max16065_probe(struct i2c_client *client)
if (unlikely(!data))
return -ENOMEM;
+ data->chip = chip;
data->client = client;
mutex_init(&data->update_lock);
- data->num_adc = max16065_num_adc[id->driver_data];
- data->have_current = max16065_have_current[id->driver_data];
- have_secondary = max16065_have_secondary[id->driver_data];
+ data->num_adc = max16065_num_adc[chip];
+ data->have_current = max16065_have_current[chip];
+ have_secondary = max16065_have_secondary[chip];
if (have_secondary) {
val = i2c_smbus_read_byte_data(client, MAX16065_SW_ENABLE);
diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c
index 500dc926a7a7..9b6d03cff4df 100644
--- a/drivers/hwmon/max1619.c
+++ b/drivers/hwmon/max1619.c
@@ -12,280 +12,361 @@
* http://pdfserv.maxim-ic.com/en/ds/MAX1619.pdf
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/util_macros.h>
static const unsigned short normal_i2c[] = {
0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END };
-/*
- * The MAX1619 registers
- */
+#define MAX1619_REG_LOCAL_TEMP 0x00
+#define MAX1619_REG_REMOTE_TEMP 0x01
+#define MAX1619_REG_STATUS 0x02
+#define MAX1619_REG_CONFIG 0x03
+#define MAX1619_REG_CONVRATE 0x04
+#define MAX1619_REG_REMOTE_HIGH 0x07
+#define MAX1619_REG_REMOTE_LOW 0x08
+#define MAX1619_REG_REMOTE_CRIT 0x10
+#define MAX1619_REG_REMOTE_CRIT_HYST 0x11
+#define MAX1619_REG_MAN_ID 0xFE
+#define MAX1619_REG_CHIP_ID 0xFF
+
+static int get_alarms(struct regmap *regmap)
+{
+ static u32 regs[2] = { MAX1619_REG_STATUS, MAX1619_REG_CONFIG };
+ u8 regdata[2];
+ int ret;
-#define MAX1619_REG_R_MAN_ID 0xFE
-#define MAX1619_REG_R_CHIP_ID 0xFF
-#define MAX1619_REG_R_CONFIG 0x03
-#define MAX1619_REG_W_CONFIG 0x09
-#define MAX1619_REG_R_CONVRATE 0x04
-#define MAX1619_REG_W_CONVRATE 0x0A
-#define MAX1619_REG_R_STATUS 0x02
-#define MAX1619_REG_R_LOCAL_TEMP 0x00
-#define MAX1619_REG_R_REMOTE_TEMP 0x01
-#define MAX1619_REG_R_REMOTE_HIGH 0x07
-#define MAX1619_REG_W_REMOTE_HIGH 0x0D
-#define MAX1619_REG_R_REMOTE_LOW 0x08
-#define MAX1619_REG_W_REMOTE_LOW 0x0E
-#define MAX1619_REG_R_REMOTE_CRIT 0x10
-#define MAX1619_REG_W_REMOTE_CRIT 0x12
-#define MAX1619_REG_R_TCRIT_HYST 0x11
-#define MAX1619_REG_W_TCRIT_HYST 0x13
+ ret = regmap_multi_reg_read(regmap, regs, regdata, 2);
+ if (ret)
+ return ret;
-/*
- * Conversions
- */
+ /* OVERT status bit may be reversed */
+ if (!(regdata[1] & 0x20))
+ regdata[0] ^= 0x02;
-static int temp_from_reg(int val)
-{
- return (val & 0x80 ? val-0x100 : val) * 1000;
+ return regdata[0] & 0x1e;
}
-static int temp_to_reg(int val)
+static int max1619_temp_read(struct regmap *regmap, u32 attr, int channel, long *val)
{
- return (val < 0 ? val+0x100*1000 : val) / 1000;
+ int reg = -1, alarm_bit = 0;
+ u32 temp;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = channel ? MAX1619_REG_REMOTE_TEMP : MAX1619_REG_LOCAL_TEMP;
+ break;
+ case hwmon_temp_min:
+ reg = MAX1619_REG_REMOTE_LOW;
+ break;
+ case hwmon_temp_max:
+ reg = MAX1619_REG_REMOTE_HIGH;
+ break;
+ case hwmon_temp_crit:
+ reg = MAX1619_REG_REMOTE_CRIT;
+ break;
+ case hwmon_temp_crit_hyst:
+ reg = MAX1619_REG_REMOTE_CRIT_HYST;
+ break;
+ case hwmon_temp_min_alarm:
+ alarm_bit = 3;
+ break;
+ case hwmon_temp_max_alarm:
+ alarm_bit = 4;
+ break;
+ case hwmon_temp_crit_alarm:
+ alarm_bit = 1;
+ break;
+ case hwmon_temp_fault:
+ alarm_bit = 2;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ if (reg >= 0) {
+ ret = regmap_read(regmap, reg, &temp);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(temp, 7) * 1000;
+ } else {
+ ret = get_alarms(regmap);
+ if (ret < 0)
+ return ret;
+ *val = !!(ret & BIT(alarm_bit));
+ }
+ return 0;
}
-enum temp_index {
- t_input1 = 0,
- t_input2,
- t_low2,
- t_high2,
- t_crit2,
- t_hyst2,
- t_num_regs
-};
-
-/*
- * Client data (each client gets its own)
- */
-
-struct max1619_data {
- struct i2c_client *client;
- struct mutex update_lock;
- bool valid; /* false until following fields are valid */
- unsigned long last_updated; /* in jiffies */
-
- /* registers values */
- u8 temp[t_num_regs]; /* index with enum temp_index */
- u8 alarms;
-};
+static u16 update_intervals[] = { 16000, 8000, 4000, 2000, 1000, 500, 250, 125 };
-static const u8 regs_read[t_num_regs] = {
- [t_input1] = MAX1619_REG_R_LOCAL_TEMP,
- [t_input2] = MAX1619_REG_R_REMOTE_TEMP,
- [t_low2] = MAX1619_REG_R_REMOTE_LOW,
- [t_high2] = MAX1619_REG_R_REMOTE_HIGH,
- [t_crit2] = MAX1619_REG_R_REMOTE_CRIT,
- [t_hyst2] = MAX1619_REG_R_TCRIT_HYST,
-};
-
-static const u8 regs_write[t_num_regs] = {
- [t_low2] = MAX1619_REG_W_REMOTE_LOW,
- [t_high2] = MAX1619_REG_W_REMOTE_HIGH,
- [t_crit2] = MAX1619_REG_W_REMOTE_CRIT,
- [t_hyst2] = MAX1619_REG_W_TCRIT_HYST,
-};
-
-static struct max1619_data *max1619_update_device(struct device *dev)
+static int max1619_chip_read(struct regmap *regmap, u32 attr, long *val)
{
- struct max1619_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- int config, i;
-
- mutex_lock(&data->update_lock);
-
- if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) {
- dev_dbg(&client->dev, "Updating max1619 data.\n");
- for (i = 0; i < t_num_regs; i++)
- data->temp[i] = i2c_smbus_read_byte_data(client,
- regs_read[i]);
- data->alarms = i2c_smbus_read_byte_data(client,
- MAX1619_REG_R_STATUS);
- /* If OVERT polarity is low, reverse alarm bit */
- config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG);
- if (!(config & 0x20))
- data->alarms ^= 0x02;
-
- data->last_updated = jiffies;
- data->valid = true;
+ int alarms, ret;
+ u32 regval;
+
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ ret = regmap_read(regmap, MAX1619_REG_CONVRATE, &regval);
+ if (ret < 0)
+ return ret;
+ *val = update_intervals[regval & 7];
+ break;
+ case hwmon_chip_alarms:
+ alarms = get_alarms(regmap);
+ if (alarms < 0)
+ return alarms;
+ *val = alarms;
+ break;
+ default:
+ return -EOPNOTSUPP;
}
-
- mutex_unlock(&data->update_lock);
-
- return data;
+ return 0;
}
-/*
- * Sysfs stuff
- */
-
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
+static int max1619_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct max1619_data *data = max1619_update_device(dev);
+ struct regmap *regmap = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_chip:
+ return max1619_chip_read(regmap, attr, val);
+ case hwmon_temp:
+ return max1619_temp_read(regmap, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
+static int max1619_chip_write(struct regmap *regmap, u32 attr, long val)
+{
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ val = find_closest_descending(val, update_intervals, ARRAY_SIZE(update_intervals));
+ return regmap_write(regmap, MAX1619_REG_CONVRATE, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t temp_store(struct device *dev,
- struct device_attribute *devattr, const char *buf,
- size_t count)
+static int max1619_temp_write(struct regmap *regmap,
+ u32 attr, int channel, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct max1619_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long val;
- int err = kstrtol(buf, 10, &val);
- if (err)
- return err;
-
- mutex_lock(&data->update_lock);
- data->temp[attr->index] = temp_to_reg(val);
- i2c_smbus_write_byte_data(client, regs_write[attr->index],
- data->temp[attr->index]);
- mutex_unlock(&data->update_lock);
- return count;
+ int reg;
+
+ switch (attr) {
+ case hwmon_temp_min:
+ reg = MAX1619_REG_REMOTE_LOW;
+ break;
+ case hwmon_temp_max:
+ reg = MAX1619_REG_REMOTE_HIGH;
+ break;
+ case hwmon_temp_crit:
+ reg = MAX1619_REG_REMOTE_CRIT;
+ break;
+ case hwmon_temp_crit_hyst:
+ reg = MAX1619_REG_REMOTE_CRIT_HYST;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
+ return regmap_write(regmap, reg, val);
}
-static ssize_t alarms_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static int max1619_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
{
- struct max1619_data *data = max1619_update_device(dev);
- return sprintf(buf, "%d\n", data->alarms);
+ struct regmap *regmap = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_chip:
+ return max1619_chip_write(regmap, attr, val);
+ case hwmon_temp:
+ return max1619_temp_write(regmap, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static umode_t max1619_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
- int bitnr = to_sensor_dev_attr(attr)->index;
- struct max1619_data *data = max1619_update_device(dev);
- return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return 0644;
+ case hwmon_chip_alarms:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ case hwmon_temp_crit_hyst:
+ return 0644;
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_fault:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
}
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input1);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, t_input2);
-static SENSOR_DEVICE_ATTR_RW(temp2_min, temp, t_low2);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, t_high2);
-static SENSOR_DEVICE_ATTR_RW(temp2_crit, temp, t_crit2);
-static SENSOR_DEVICE_ATTR_RW(temp2_crit_hyst, temp, t_hyst2);
-
-static DEVICE_ATTR_RO(alarms);
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 1);
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 2);
-static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, alarm, 3);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 4);
-
-static struct attribute *max1619_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,
-
- &dev_attr_alarms.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+static const struct hwmon_channel_info * const max1619_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS | HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_CRIT | HWMON_T_CRIT_HYST |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_CRIT_ALARM | HWMON_T_FAULT),
NULL
};
-ATTRIBUTE_GROUPS(max1619);
+
+static const struct hwmon_ops max1619_hwmon_ops = {
+ .is_visible = max1619_is_visible,
+ .read = max1619_read,
+ .write = max1619_write,
+};
+
+static const struct hwmon_chip_info max1619_chip_info = {
+ .ops = &max1619_hwmon_ops,
+ .info = max1619_info,
+};
/* Return 0 if detection is successful, -ENODEV otherwise */
static int max1619_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
- u8 reg_config, reg_convrate, reg_status, man_id, chip_id;
+ int regval;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
- /* detection */
- reg_config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG);
- reg_convrate = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONVRATE);
- reg_status = i2c_smbus_read_byte_data(client, MAX1619_REG_R_STATUS);
- if ((reg_config & 0x03) != 0x00
- || reg_convrate > 0x07 || (reg_status & 0x61) != 0x00) {
- dev_dbg(&adapter->dev, "MAX1619 detection failed at 0x%02x\n",
- client->addr);
+ regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CONFIG);
+ if (regval < 0 || (regval & 0x03))
+ return -ENODEV;
+ regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CONVRATE);
+ if (regval < 0 || regval > 0x07)
+ return -ENODEV;
+ regval = i2c_smbus_read_byte_data(client, MAX1619_REG_STATUS);
+ if (regval < 0 || (regval & 0x61))
return -ENODEV;
- }
- /* identification */
- man_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_MAN_ID);
- chip_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CHIP_ID);
- if (man_id != 0x4D || chip_id != 0x04) {
- dev_info(&adapter->dev,
- "Unsupported chip (man_id=0x%02X, chip_id=0x%02X).\n",
- man_id, chip_id);
+ regval = i2c_smbus_read_byte_data(client, MAX1619_REG_MAN_ID);
+ if (regval != 0x4d)
+ return -ENODEV;
+ regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CHIP_ID);
+ if (regval != 0x04)
return -ENODEV;
- }
strscpy(info->type, "max1619", I2C_NAME_SIZE);
return 0;
}
-static void max1619_init_client(struct i2c_client *client)
+static int max1619_init_chip(struct regmap *regmap)
{
- u8 config;
-
- /*
- * Start the conversions.
- */
- i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONVRATE,
- 5); /* 2 Hz */
- config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG);
- if (config & 0x40)
- i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONFIG,
- config & 0xBF); /* run */
+ int ret;
+
+ ret = regmap_write(regmap, MAX1619_REG_CONVRATE, 5); /* 2 Hz */
+ if (ret)
+ return ret;
+
+ /* Start conversions */
+ return regmap_clear_bits(regmap, MAX1619_REG_CONFIG, 0x40);
}
-static int max1619_probe(struct i2c_client *new_client)
+/* regmap */
+
+static int max1619_reg_read(void *context, unsigned int reg, unsigned int *val)
{
- struct max1619_data *data;
- struct device *hwmon_dev;
+ int ret;
- data = devm_kzalloc(&new_client->dev, sizeof(struct max1619_data),
- GFP_KERNEL);
- if (!data)
- return -ENOMEM;
+ ret = i2c_smbus_read_byte_data(context, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+
+static int max1619_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ int offset = reg < MAX1619_REG_REMOTE_CRIT ? 6 : 2;
+
+ return i2c_smbus_write_byte_data(context, reg + offset, val);
+}
+
+static bool max1619_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+ return reg <= MAX1619_REG_STATUS;
+}
+
+static bool max1619_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+ return reg > MAX1619_REG_STATUS && reg <= MAX1619_REG_REMOTE_CRIT_HYST;
+}
+
+static const struct regmap_config max1619_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX1619_REG_REMOTE_CRIT_HYST,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = max1619_regmap_is_volatile,
+ .writeable_reg = max1619_regmap_is_writeable,
+};
+
+static const struct regmap_bus max1619_regmap_bus = {
+ .reg_write = max1619_reg_write,
+ .reg_read = max1619_reg_read,
+};
+
+static int max1619_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct regmap *regmap;
+ int ret;
- data->client = new_client;
- mutex_init(&data->update_lock);
+ regmap = devm_regmap_init(dev, &max1619_regmap_bus, client,
+ &max1619_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
- /* Initialize the MAX1619 chip */
- max1619_init_client(new_client);
+ ret = max1619_init_chip(regmap);
+ if (ret)
+ return ret;
- hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev,
- new_client->name,
- data,
- max1619_groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ regmap, &max1619_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id max1619_id[] = {
- { "max1619", 0 },
+ { "max1619" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max1619_id);
diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c
index c4a02edefbee..a8197a86f559 100644
--- a/drivers/hwmon/max1668.c
+++ b/drivers/hwmon/max1668.c
@@ -6,15 +6,15 @@
* some credit to Christoph Scheurer, but largely a rewrite
*/
-#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
-#include <linux/mutex.h>
/* Addresses to scan */
static const unsigned short max1668_addr_list[] = {
@@ -30,14 +30,10 @@ static const unsigned short max1668_addr_list[] = {
/* limits */
-/* write high limits */
-#define MAX1668_REG_LIMH_WR(nr) (0x13 + 2 * (nr))
-/* write low limits */
-#define MAX1668_REG_LIML_WR(nr) (0x14 + 2 * (nr))
-/* read high limits */
-#define MAX1668_REG_LIMH_RD(nr) (0x08 + 2 * (nr))
+/* high limits */
+#define MAX1668_REG_LIMH(nr) (0x08 + 2 * (nr))
/* read low limits */
-#define MAX1668_REG_LIML_RD(nr) (0x09 + 2 * (nr))
+#define MAX1668_REG_LIML(nr) (0x09 + 2 * (nr))
/* manufacturer and device ID Constants */
#define MAN_ID_MAXIM 0x4d
@@ -50,309 +46,146 @@ static bool read_only;
module_param(read_only, bool, 0);
MODULE_PARM_DESC(read_only, "Don't set any values, read only mode");
-enum chips { max1668, max1805, max1989 };
-
struct max1668_data {
- struct i2c_client *client;
- const struct attribute_group *groups[3];
- enum chips type;
-
- struct mutex update_lock;
- bool valid; /* true if following fields are valid */
- unsigned long last_updated; /* In jiffies */
-
- /* 1x local and 4x remote */
- s8 temp_max[5];
- s8 temp_min[5];
- s8 temp[5];
- u16 alarms;
+ struct regmap *regmap;
+ int channels;
};
-static struct max1668_data *max1668_update_device(struct device *dev)
+static int max1668_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
{
struct max1668_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- struct max1668_data *ret = data;
- s32 val;
- int i;
-
- mutex_lock(&data->update_lock);
-
- if (data->valid && !time_after(jiffies,
- data->last_updated + HZ + HZ / 2))
- goto abort;
-
- for (i = 0; i < 5; i++) {
- val = i2c_smbus_read_byte_data(client, MAX1668_REG_TEMP(i));
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp[i] = (s8) val;
-
- val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIMH_RD(i));
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp_max[i] = (s8) val;
-
- val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIML_RD(i));
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp_min[i] = (s8) val;
- }
-
- val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT1);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->alarms = val << 8;
+ struct regmap *regmap = data->regmap;
+ u32 regs[2] = { MAX1668_REG_STAT1, MAX1668_REG_TEMP(channel) };
+ u8 regvals[2];
+ u32 regval;
+ int ret;
- val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT2);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
+ switch (attr) {
+ case hwmon_temp_input:
+ ret = regmap_read(regmap, MAX1668_REG_TEMP(channel), &regval);
+ if (ret)
+ return ret;
+ *val = sign_extend32(regval, 7) * 1000;
+ break;
+ case hwmon_temp_min:
+ ret = regmap_read(regmap, MAX1668_REG_LIML(channel), &regval);
+ if (ret)
+ return ret;
+ *val = sign_extend32(regval, 7) * 1000;
+ break;
+ case hwmon_temp_max:
+ ret = regmap_read(regmap, MAX1668_REG_LIMH(channel), &regval);
+ if (ret)
+ return ret;
+ *val = sign_extend32(regval, 7) * 1000;
+ break;
+ case hwmon_temp_min_alarm:
+ ret = regmap_read(regmap,
+ channel ? MAX1668_REG_STAT2 : MAX1668_REG_STAT1,
+ &regval);
+ if (ret)
+ return ret;
+ if (channel)
+ *val = !!(regval & BIT(9 - channel * 2));
+ else
+ *val = !!(regval & BIT(5));
+ break;
+ case hwmon_temp_max_alarm:
+ ret = regmap_read(regmap,
+ channel ? MAX1668_REG_STAT2 : MAX1668_REG_STAT1,
+ &regval);
+ if (ret)
+ return ret;
+ if (channel)
+ *val = !!(regval & BIT(8 - channel * 2));
+ else
+ *val = !!(regval & BIT(6));
+ break;
+ case hwmon_temp_fault:
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
+ if (ret)
+ return ret;
+ *val = !!((regvals[0] & BIT(4)) && regvals[1] == 127);
+ break;
+ default:
+ return -EOPNOTSUPP;
}
- data->alarms |= val;
-
- data->last_updated = jiffies;
- data->valid = true;
-abort:
- mutex_unlock(&data->update_lock);
-
- return ret;
-}
-
-static ssize_t show_temp(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max1668_data *data = max1668_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- return sprintf(buf, "%d\n", data->temp[index] * 1000);
-}
-
-static ssize_t show_temp_max(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max1668_data *data = max1668_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- return sprintf(buf, "%d\n", data->temp_max[index] * 1000);
-}
-
-static ssize_t show_temp_min(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max1668_data *data = max1668_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- return sprintf(buf, "%d\n", data->temp_min[index] * 1000);
-}
-
-static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- int index = to_sensor_dev_attr(attr)->index;
- struct max1668_data *data = max1668_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1);
-}
-
-static ssize_t show_fault(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max1668_data *data = max1668_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- return sprintf(buf, "%u\n",
- (data->alarms & (1 << 12)) && data->temp[index] == 127);
+ return 0;
}
-static ssize_t set_temp_max(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
+static int max1668_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
{
- int index = to_sensor_dev_attr(devattr)->index;
struct max1668_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long temp;
- int ret;
-
- ret = kstrtol(buf, 10, &temp);
- if (ret < 0)
- return ret;
+ struct regmap *regmap = data->regmap;
- mutex_lock(&data->update_lock);
- data->temp_max[index] = clamp_val(temp/1000, -128, 127);
- ret = i2c_smbus_write_byte_data(client,
- MAX1668_REG_LIMH_WR(index),
- data->temp_max[index]);
- if (ret < 0)
- count = ret;
- mutex_unlock(&data->update_lock);
+ val = clamp_val(val / 1000, -128, 127);
- return count;
+ switch (attr) {
+ case hwmon_temp_min:
+ return regmap_write(regmap, MAX1668_REG_LIML(channel), val);
+ case hwmon_temp_max:
+ return regmap_write(regmap, MAX1668_REG_LIMH(channel), val);
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t set_temp_min(struct device *dev,
- struct device_attribute *devattr,
- const char *buf, size_t count)
+static umode_t max1668_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max1668_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- long temp;
- int ret;
-
- ret = kstrtol(buf, 10, &temp);
- if (ret < 0)
- return ret;
-
- mutex_lock(&data->update_lock);
- data->temp_min[index] = clamp_val(temp/1000, -128, 127);
- ret = i2c_smbus_write_byte_data(client,
- MAX1668_REG_LIML_WR(index),
- data->temp_min[index]);
- if (ret < 0)
- count = ret;
- mutex_unlock(&data->update_lock);
-
- return count;
+ const struct max1668_data *data = _data;
+
+ if (channel >= data->channels)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ return read_only ? 0444 : 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ case hwmon_temp_fault:
+ if (channel)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ return 0;
}
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max,
- set_temp_max, 0);
-static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min,
- set_temp_min, 0);
-static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max,
- set_temp_max, 1);
-static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min,
- set_temp_min, 1);
-static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max,
- set_temp_max, 2);
-static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min,
- set_temp_min, 2);
-static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max,
- set_temp_max, 3);
-static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min,
- set_temp_min, 3);
-static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4);
-static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max,
- set_temp_max, 4);
-static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min,
- set_temp_min, 4);
-
-static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 14);
-static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 13);
-static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 7);
-static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 6);
-static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_alarm, NULL, 5);
-static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4);
-static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_alarm, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, show_alarm, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 0);
-
-static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_fault, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_fault, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_fault, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_fault, NULL, 4);
-
-/* Attributes common to MAX1668, MAX1989 and MAX1805 */
-static struct attribute *max1668_attribute_common[] = {
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp3_max.dev_attr.attr,
- &sensor_dev_attr_temp3_min.dev_attr.attr,
- &sensor_dev_attr_temp3_input.dev_attr.attr,
-
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
-
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp3_fault.dev_attr.attr,
+static const struct hwmon_channel_info * const max1668_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_FAULT,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
+ HWMON_T_FAULT),
NULL
};
-/* Attributes not present on MAX1805 */
-static struct attribute *max1668_attribute_unique[] = {
- &sensor_dev_attr_temp4_max.dev_attr.attr,
- &sensor_dev_attr_temp4_min.dev_attr.attr,
- &sensor_dev_attr_temp4_input.dev_attr.attr,
- &sensor_dev_attr_temp5_max.dev_attr.attr,
- &sensor_dev_attr_temp5_min.dev_attr.attr,
- &sensor_dev_attr_temp5_input.dev_attr.attr,
-
- &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp5_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp5_min_alarm.dev_attr.attr,
-
- &sensor_dev_attr_temp4_fault.dev_attr.attr,
- &sensor_dev_attr_temp5_fault.dev_attr.attr,
- NULL
+static const struct hwmon_ops max1668_hwmon_ops = {
+ .is_visible = max1668_is_visible,
+ .read = max1668_read,
+ .write = max1668_write,
};
-static umode_t max1668_attribute_mode(struct kobject *kobj,
- struct attribute *attr, int index)
-{
- umode_t ret = S_IRUGO;
- if (read_only)
- return ret;
- if (attr == &sensor_dev_attr_temp1_max.dev_attr.attr ||
- attr == &sensor_dev_attr_temp2_max.dev_attr.attr ||
- attr == &sensor_dev_attr_temp3_max.dev_attr.attr ||
- attr == &sensor_dev_attr_temp4_max.dev_attr.attr ||
- attr == &sensor_dev_attr_temp5_max.dev_attr.attr ||
- attr == &sensor_dev_attr_temp1_min.dev_attr.attr ||
- attr == &sensor_dev_attr_temp2_min.dev_attr.attr ||
- attr == &sensor_dev_attr_temp3_min.dev_attr.attr ||
- attr == &sensor_dev_attr_temp4_min.dev_attr.attr ||
- attr == &sensor_dev_attr_temp5_min.dev_attr.attr)
- ret |= S_IWUSR;
- return ret;
-}
-
-static const struct attribute_group max1668_group_common = {
- .attrs = max1668_attribute_common,
- .is_visible = max1668_attribute_mode
-};
-
-static const struct attribute_group max1668_group_unique = {
- .attrs = max1668_attribute_unique,
- .is_visible = max1668_attribute_mode
+static const struct hwmon_chip_info max1668_chip_info = {
+ .ops = &max1668_hwmon_ops,
+ .info = max1668_info,
};
/* Return 0 if detection is successful, -ENODEV otherwise */
@@ -391,7 +224,47 @@ static int max1668_detect(struct i2c_client *client,
return 0;
}
-static const struct i2c_device_id max1668_id[];
+/* regmap */
+
+static int max1668_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(context, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+
+static int max1668_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ return i2c_smbus_write_byte_data(context, reg + 11, val);
+}
+
+static bool max1668_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+ return reg <= MAX1668_REG_STAT2;
+}
+
+static bool max1668_regmap_is_writeable(struct device *dev, unsigned int reg)
+{
+ return reg > MAX1668_REG_STAT2 && reg <= MAX1668_REG_LIML(4);
+}
+
+static const struct regmap_config max1668_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = max1668_regmap_is_volatile,
+ .writeable_reg = max1668_regmap_is_writeable,
+};
+
+static const struct regmap_bus max1668_regmap_bus = {
+ .reg_write = max1668_reg_write,
+ .reg_read = max1668_reg_read,
+};
static int max1668_probe(struct i2c_client *client)
{
@@ -407,24 +280,22 @@ static int max1668_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- data->client = client;
- data->type = i2c_match_id(max1668_id, client)->driver_data;
- mutex_init(&data->update_lock);
+ data->regmap = devm_regmap_init(dev, &max1668_regmap_bus, client,
+ &max1668_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
- /* sysfs hooks */
- data->groups[0] = &max1668_group_common;
- if (data->type == max1668 || data->type == max1989)
- data->groups[1] = &max1668_group_unique;
+ data->channels = (uintptr_t)i2c_get_match_data(client);
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data, data->groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ &max1668_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id max1668_id[] = {
- { "max1668", max1668 },
- { "max1805", max1805 },
- { "max1989", max1989 },
+ { "max1668", 5 },
+ { "max1805", 3 },
+ { "max1989", 5 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max1668_id);
diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c
index 56add579e32f..f0048ff37607 100644
--- a/drivers/hwmon/max197.c
+++ b/drivers/hwmon/max197.c
@@ -312,14 +312,12 @@ error:
return ret;
}
-static int max197_remove(struct platform_device *pdev)
+static void max197_remove(struct platform_device *pdev)
{
struct max197_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&pdev->dev.kobj, &max197_sysfs_group);
-
- return 0;
}
static const struct platform_device_id max197_device_ids[] = {
diff --git a/drivers/hwmon/max31730.c b/drivers/hwmon/max31730.c
index 7d237db6e57c..2f4b419b6c9e 100644
--- a/drivers/hwmon/max31730.c
+++ b/drivers/hwmon/max31730.c
@@ -345,7 +345,7 @@ max31730_probe(struct i2c_client *client)
}
static const struct i2c_device_id max31730_ids[] = {
- { "max31730", 0, },
+ { "max31730" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max31730_ids);
diff --git a/drivers/hwmon/max31760.c b/drivers/hwmon/max31760.c
index 79945eb466ae..127e31ca3c87 100644
--- a/drivers/hwmon/max31760.c
+++ b/drivers/hwmon/max31760.c
@@ -60,7 +60,7 @@ static const struct regmap_config regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x5B,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = max31760_volatile_reg,
};
@@ -578,7 +578,6 @@ static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend,
max31760_resume);
static struct i2c_driver max31760_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max31760",
.of_match_table = max31760_of_match,
diff --git a/drivers/hwmon/max31790.c b/drivers/hwmon/max31790.c
index 0cd44c1e998a..4f6171a17d9f 100644
--- a/drivers/hwmon/max31790.c
+++ b/drivers/hwmon/max31790.c
@@ -49,12 +49,14 @@
#define NR_CHANNEL 6
+#define PWM_INPUT_SCALE 255
+#define MAX31790_REG_PWMOUT_SCALE 511
+
/*
* Client data (each client gets its own)
*/
struct max31790_data {
struct i2c_client *client;
- struct mutex update_lock;
bool valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
@@ -71,30 +73,27 @@ static struct max31790_data *max31790_update_device(struct device *dev)
{
struct max31790_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
- struct max31790_data *ret = data;
- int i;
- int rv;
-
- mutex_lock(&data->update_lock);
+ int i, rv;
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+ data->valid = false;
rv = i2c_smbus_read_byte_data(client,
MAX31790_REG_FAN_FAULT_STATUS1);
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->fault_status |= rv & 0x3F;
rv = i2c_smbus_read_byte_data(client,
MAX31790_REG_FAN_FAULT_STATUS2);
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->fault_status |= (rv & 0x3F) << 6;
for (i = 0; i < NR_CHANNEL; i++) {
rv = i2c_smbus_read_word_swapped(client,
MAX31790_REG_TACH_COUNT(i));
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->tach[i] = rv;
if (data->fan_config[i]
@@ -103,19 +102,19 @@ static struct max31790_data *max31790_update_device(struct device *dev)
MAX31790_REG_TACH_COUNT(NR_CHANNEL
+ i));
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->tach[NR_CHANNEL + i] = rv;
} else {
rv = i2c_smbus_read_word_swapped(client,
MAX31790_REG_PWM_DUTY_CYCLE(i));
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->pwm[i] = rv;
rv = i2c_smbus_read_word_swapped(client,
MAX31790_REG_TARGET_COUNT(i));
if (rv < 0)
- goto abort;
+ return ERR_PTR(rv);
data->target_count[i] = rv;
}
}
@@ -123,16 +122,7 @@ static struct max31790_data *max31790_update_device(struct device *dev)
data->last_updated = jiffies;
data->valid = true;
}
- goto done;
-
-abort:
- data->valid = false;
- ret = ERR_PTR(rv);
-
-done:
- mutex_unlock(&data->update_lock);
-
- return ret;
+ return data;
}
static const u8 tach_period[8] = { 1, 2, 4, 8, 16, 32, 32, 32 };
@@ -186,7 +176,6 @@ static int max31790_read_fan(struct device *dev, u32 attr, int channel,
*val = rpm;
return 0;
case hwmon_fan_fault:
- mutex_lock(&data->update_lock);
*val = !!(data->fault_status & (1 << channel));
data->fault_status &= ~(1 << channel);
/*
@@ -197,10 +186,9 @@ static int max31790_read_fan(struct device *dev, u32 attr, int channel,
if (*val) {
int reg = MAX31790_REG_TARGET_COUNT(channel % NR_CHANNEL);
- i2c_smbus_write_byte_data(data->client, reg,
- data->target_count[channel % NR_CHANNEL] >> 8);
+ return i2c_smbus_write_byte_data(data->client, reg,
+ data->target_count[channel % NR_CHANNEL] >> 8);
}
- mutex_unlock(&data->update_lock);
return 0;
case hwmon_fan_enable:
*val = !!(data->fan_config[channel] & MAX31790_FAN_CFG_TACH_INPUT_EN);
@@ -220,8 +208,6 @@ static int max31790_write_fan(struct device *dev, u32 attr, int channel,
u8 bits, fan_config;
int sr;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_fan_target:
val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX);
@@ -267,9 +253,6 @@ static int max31790_write_fan(struct device *dev, u32 attr, int channel,
err = -EOPNOTSUPP;
break;
}
-
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -335,18 +318,19 @@ static int max31790_write_pwm(struct device *dev, u32 attr, int channel,
u8 fan_config;
int err = 0;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_pwm_input:
if (val < 0 || val > 255) {
err = -EINVAL;
break;
}
+
+ val = DIV_ROUND_CLOSEST(val * MAX31790_REG_PWMOUT_SCALE,
+ PWM_INPUT_SCALE);
data->valid = false;
err = i2c_smbus_write_word_swapped(client,
MAX31790_REG_PWMOUT(channel),
- val << 8);
+ val << 7);
break;
case hwmon_pwm_enable:
fan_config = data->fan_config[channel];
@@ -383,9 +367,6 @@ static int max31790_write_pwm(struct device *dev, u32 attr, int channel,
err = -EOPNOTSUPP;
break;
}
-
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -519,7 +500,6 @@ static int max31790_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->update_lock);
/*
* Initialize the max31790 chip
@@ -537,13 +517,12 @@ static int max31790_probe(struct i2c_client *client)
}
static const struct i2c_device_id max31790_id[] = {
- { "max31790", 0 },
+ { "max31790" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max31790_id);
static struct i2c_driver max31790_driver = {
- .class = I2C_CLASS_HWMON,
.probe = max31790_probe,
.driver = {
.name = "max31790",
diff --git a/drivers/hwmon/max31827.c b/drivers/hwmon/max31827.c
index 602f4e4f81ff..9b2e56c040df 100644
--- a/drivers/hwmon/max31827.c
+++ b/drivers/hwmon/max31827.c
@@ -10,8 +10,9 @@
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
-#include <linux/mutex.h>
+#include <linux/of_device.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#define MAX31827_T_REG 0x0
#define MAX31827_CONFIGURATION_REG 0x2
@@ -22,30 +23,85 @@
#define MAX31827_CONFIGURATION_1SHOT_MASK BIT(0)
#define MAX31827_CONFIGURATION_CNV_RATE_MASK GENMASK(3, 1)
+#define MAX31827_CONFIGURATION_PEC_EN_MASK BIT(4)
+#define MAX31827_CONFIGURATION_TIMEOUT_MASK BIT(5)
+#define MAX31827_CONFIGURATION_RESOLUTION_MASK GENMASK(7, 6)
+#define MAX31827_CONFIGURATION_ALRM_POL_MASK BIT(8)
+#define MAX31827_CONFIGURATION_COMP_INT_MASK BIT(9)
+#define MAX31827_CONFIGURATION_FLT_Q_MASK GENMASK(11, 10)
#define MAX31827_CONFIGURATION_U_TEMP_STAT_MASK BIT(14)
#define MAX31827_CONFIGURATION_O_TEMP_STAT_MASK BIT(15)
-#define MAX31827_12_BIT_CNV_TIME 141
+#define MAX31827_ALRM_POL_LOW 0x0
+#define MAX31827_ALRM_POL_HIGH 0x1
+#define MAX31827_FLT_Q_1 0x0
+#define MAX31827_FLT_Q_4 0x2
-#define MAX31827_CNV_1_DIV_64_HZ 0x1
-#define MAX31827_CNV_1_DIV_32_HZ 0x2
-#define MAX31827_CNV_1_DIV_16_HZ 0x3
-#define MAX31827_CNV_1_DIV_4_HZ 0x4
-#define MAX31827_CNV_1_HZ 0x5
-#define MAX31827_CNV_4_HZ 0x6
-#define MAX31827_CNV_8_HZ 0x7
+#define MAX31827_8_BIT_CNV_TIME 9
+#define MAX31827_9_BIT_CNV_TIME 18
+#define MAX31827_10_BIT_CNV_TIME 35
+#define MAX31827_12_BIT_CNV_TIME 140
#define MAX31827_16_BIT_TO_M_DGR(x) (sign_extend32(x, 15) * 1000 / 16)
#define MAX31827_M_DGR_TO_16_BIT(x) (((x) << 4) / 1000)
#define MAX31827_DEVICE_ENABLE(x) ((x) ? 0xA : 0x0)
+/*
+ * The enum passed in the .data pointer of struct of_device_id must
+ * start with a value != 0 since that is a requirement for using
+ * device_get_match_data().
+ */
+enum chips { max31827 = 1, max31828, max31829 };
+
+enum max31827_cnv {
+ MAX31827_CNV_1_DIV_64_HZ = 1,
+ MAX31827_CNV_1_DIV_32_HZ,
+ MAX31827_CNV_1_DIV_16_HZ,
+ MAX31827_CNV_1_DIV_4_HZ,
+ MAX31827_CNV_1_HZ,
+ MAX31827_CNV_4_HZ,
+ MAX31827_CNV_8_HZ,
+};
+
+static const u16 max31827_conversions[] = {
+ [MAX31827_CNV_1_DIV_64_HZ] = 64000,
+ [MAX31827_CNV_1_DIV_32_HZ] = 32000,
+ [MAX31827_CNV_1_DIV_16_HZ] = 16000,
+ [MAX31827_CNV_1_DIV_4_HZ] = 4000,
+ [MAX31827_CNV_1_HZ] = 1000,
+ [MAX31827_CNV_4_HZ] = 250,
+ [MAX31827_CNV_8_HZ] = 125,
+};
+
+enum max31827_resolution {
+ MAX31827_RES_8_BIT = 0,
+ MAX31827_RES_9_BIT,
+ MAX31827_RES_10_BIT,
+ MAX31827_RES_12_BIT,
+};
+
+static const u16 max31827_resolutions[] = {
+ [MAX31827_RES_8_BIT] = 1000,
+ [MAX31827_RES_9_BIT] = 500,
+ [MAX31827_RES_10_BIT] = 250,
+ [MAX31827_RES_12_BIT] = 62,
+};
+
+static const u16 max31827_conv_times[] = {
+ [MAX31827_RES_8_BIT] = MAX31827_8_BIT_CNV_TIME,
+ [MAX31827_RES_9_BIT] = MAX31827_9_BIT_CNV_TIME,
+ [MAX31827_RES_10_BIT] = MAX31827_10_BIT_CNV_TIME,
+ [MAX31827_RES_12_BIT] = MAX31827_12_BIT_CNV_TIME,
+};
+
struct max31827_state {
/*
* Prevent simultaneous access to the i2c client.
*/
- struct mutex lock;
struct regmap *regmap;
bool enable;
+ unsigned int resolution;
+ unsigned int update_interval;
};
static const struct regmap_config max31827_regmap = {
@@ -54,49 +110,54 @@ static const struct regmap_config max31827_regmap = {
.max_register = 0xA,
};
-static int write_alarm_val(struct max31827_state *st, unsigned int reg,
- long val)
+static int shutdown_write(struct max31827_state *st, unsigned int reg,
+ unsigned int mask, unsigned int val)
{
unsigned int cfg;
- unsigned int tmp;
+ unsigned int cnv_rate;
int ret;
- val = MAX31827_M_DGR_TO_16_BIT(val);
-
/*
- * Before the Temperature Threshold Alarm and Alarm Hysteresis Threshold
- * register values are changed over I2C, the part must be in shutdown
- * mode.
- *
- * Mutex is used to ensure, that some other process doesn't change the
- * configuration register.
+ * Before the Temperature Threshold Alarm, Alarm Hysteresis Threshold
+ * and Resolution bits from Configuration register are changed over I2C,
+ * the part must be in shutdown mode.
*/
- mutex_lock(&st->lock);
-
if (!st->enable) {
- ret = regmap_write(st->regmap, reg, val);
- goto unlock;
+ if (!mask)
+ return regmap_write(st->regmap, reg, val);
+ return regmap_update_bits(st->regmap, reg, mask, val);
}
ret = regmap_read(st->regmap, MAX31827_CONFIGURATION_REG, &cfg);
if (ret)
- goto unlock;
+ return ret;
- tmp = cfg & ~(MAX31827_CONFIGURATION_1SHOT_MASK |
+ cnv_rate = MAX31827_CONFIGURATION_CNV_RATE_MASK & cfg;
+ cfg = cfg & ~(MAX31827_CONFIGURATION_1SHOT_MASK |
MAX31827_CONFIGURATION_CNV_RATE_MASK);
- ret = regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, tmp);
+ ret = regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, cfg);
if (ret)
- goto unlock;
+ return ret;
+
+ if (!mask)
+ ret = regmap_write(st->regmap, reg, val);
+ else
+ ret = regmap_update_bits(st->regmap, reg, mask, val);
- ret = regmap_write(st->regmap, reg, val);
if (ret)
- goto unlock;
+ return ret;
- ret = regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, cfg);
+ return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_CNV_RATE_MASK,
+ cnv_rate);
+}
-unlock:
- mutex_unlock(&st->lock);
- return ret;
+static int write_alarm_val(struct max31827_state *st, unsigned int reg,
+ long val)
+{
+ val = MAX31827_M_DGR_TO_16_BIT(val);
+
+ return shutdown_write(st, reg, 0, val);
}
static umode_t max31827_is_visible(const void *state,
@@ -149,29 +210,26 @@ static int max31827_read(struct device *dev, enum hwmon_sensor_types type,
break;
case hwmon_temp_input:
- mutex_lock(&st->lock);
-
if (!st->enable) {
- /*
- * This operation requires mutex protection,
- * because the chip configuration should not
- * be changed during the conversion process.
- */
-
ret = regmap_update_bits(st->regmap,
MAX31827_CONFIGURATION_REG,
MAX31827_CONFIGURATION_1SHOT_MASK,
1);
- if (ret) {
- mutex_unlock(&st->lock);
+ if (ret)
return ret;
- }
-
- msleep(MAX31827_12_BIT_CNV_TIME);
+ msleep(max31827_conv_times[st->resolution]);
}
- ret = regmap_read(st->regmap, MAX31827_T_REG, &uval);
- mutex_unlock(&st->lock);
+ /*
+ * For 12-bit resolution the conversion time is 140 ms,
+ * thus an additional 15 ms is needed to complete the
+ * conversion: 125 ms + 15 ms = 140 ms
+ */
+ if (max31827_resolutions[st->resolution] == 12 &&
+ st->update_interval == 125)
+ usleep_range(15000, 20000);
+
+ ret = regmap_read(st->regmap, MAX31827_T_REG, &uval);
if (ret)
break;
@@ -243,32 +301,7 @@ static int max31827_read(struct device *dev, enum hwmon_sensor_types type,
uval = FIELD_GET(MAX31827_CONFIGURATION_CNV_RATE_MASK,
uval);
- switch (uval) {
- case MAX31827_CNV_1_DIV_64_HZ:
- *val = 64000;
- break;
- case MAX31827_CNV_1_DIV_32_HZ:
- *val = 32000;
- break;
- case MAX31827_CNV_1_DIV_16_HZ:
- *val = 16000;
- break;
- case MAX31827_CNV_1_DIV_4_HZ:
- *val = 4000;
- break;
- case MAX31827_CNV_1_HZ:
- *val = 1000;
- break;
- case MAX31827_CNV_4_HZ:
- *val = 250;
- break;
- case MAX31827_CNV_8_HZ:
- *val = 125;
- break;
- default:
- *val = 0;
- break;
- }
+ *val = max31827_conversions[uval];
}
break;
@@ -284,6 +317,7 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct max31827_state *st = dev_get_drvdata(dev);
+ int res = 1;
int ret;
switch (type) {
@@ -293,7 +327,6 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
if (val >> 1)
return -EINVAL;
- mutex_lock(&st->lock);
/**
* The chip should not be enabled while a conversion is
* performed. Neither should the chip be enabled when
@@ -302,15 +335,11 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
st->enable = val;
- ret = regmap_update_bits(st->regmap,
- MAX31827_CONFIGURATION_REG,
- MAX31827_CONFIGURATION_1SHOT_MASK |
- MAX31827_CONFIGURATION_CNV_RATE_MASK,
- MAX31827_DEVICE_ENABLE(val));
-
- mutex_unlock(&st->lock);
-
- return ret;
+ return regmap_update_bits(st->regmap,
+ MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_1SHOT_MASK |
+ MAX31827_CONFIGURATION_CNV_RATE_MASK,
+ MAX31827_DEVICE_ENABLE(val));
case hwmon_temp_max:
return write_alarm_val(st, MAX31827_TH_REG, val);
@@ -329,61 +358,205 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
}
case hwmon_chip:
- if (attr == hwmon_chip_update_interval) {
+ switch (attr) {
+ case hwmon_chip_update_interval:
if (!st->enable)
return -EINVAL;
- switch (val) {
- case 125:
- val = MAX31827_CNV_8_HZ;
- break;
- case 250:
- val = MAX31827_CNV_4_HZ;
- break;
- case 1000:
- val = MAX31827_CNV_1_HZ;
- break;
- case 4000:
- val = MAX31827_CNV_1_DIV_4_HZ;
- break;
- case 16000:
- val = MAX31827_CNV_1_DIV_16_HZ;
- break;
- case 32000:
- val = MAX31827_CNV_1_DIV_32_HZ;
- break;
- case 64000:
- val = MAX31827_CNV_1_DIV_64_HZ;
- break;
- default:
- return -EINVAL;
- }
+ /*
+ * Convert the desired conversion rate into register
+ * bits. res is already initialized with 1.
+ *
+ * This was inspired by lm73 driver.
+ */
+ while (res < ARRAY_SIZE(max31827_conversions) &&
+ val < max31827_conversions[res])
+ res++;
- val = FIELD_PREP(MAX31827_CONFIGURATION_CNV_RATE_MASK,
- val);
+ if (res == ARRAY_SIZE(max31827_conversions))
+ res = ARRAY_SIZE(max31827_conversions) - 1;
- return regmap_update_bits(st->regmap,
- MAX31827_CONFIGURATION_REG,
- MAX31827_CONFIGURATION_CNV_RATE_MASK,
- val);
- }
- break;
+ res = FIELD_PREP(MAX31827_CONFIGURATION_CNV_RATE_MASK,
+ res);
+
+ ret = regmap_update_bits(st->regmap,
+ MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_CNV_RATE_MASK,
+ res);
+ if (ret)
+ return ret;
+
+ st->update_interval = val;
+ return 0;
+ case hwmon_chip_pec:
+ return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_PEC_EN_MASK,
+ val ? MAX31827_CONFIGURATION_PEC_EN_MASK : 0);
+ default:
+ return -EOPNOTSUPP;
+ }
default:
return -EOPNOTSUPP;
}
+}
+
+static ssize_t temp1_resolution_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct max31827_state *st = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, MAX31827_CONFIGURATION_REG, &val);
+ if (ret)
+ return ret;
- return -EOPNOTSUPP;
+ val = FIELD_GET(MAX31827_CONFIGURATION_RESOLUTION_MASK, val);
+
+ return sysfs_emit(buf, "%u\n", max31827_resolutions[val]);
}
-static int max31827_init_client(struct max31827_state *st)
+static ssize_t temp1_resolution_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
{
+ struct max31827_state *st = dev_get_drvdata(dev);
+ unsigned int idx = 0;
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ /*
+ * Convert the desired resolution into register
+ * bits. idx is already initialized with 0.
+ *
+ * This was inspired by lm73 driver.
+ */
+ while (idx < ARRAY_SIZE(max31827_resolutions) &&
+ val < max31827_resolutions[idx])
+ idx++;
+
+ if (idx == ARRAY_SIZE(max31827_resolutions))
+ idx = ARRAY_SIZE(max31827_resolutions) - 1;
+
+ st->resolution = idx;
+
+ ret = shutdown_write(st, MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_RESOLUTION_MASK,
+ FIELD_PREP(MAX31827_CONFIGURATION_RESOLUTION_MASK,
+ idx));
+
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RW(temp1_resolution);
+
+static struct attribute *max31827_attrs[] = {
+ &dev_attr_temp1_resolution.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(max31827);
+
+static const struct i2c_device_id max31827_i2c_ids[] = {
+ { "max31827", max31827 },
+ { "max31828", max31828 },
+ { "max31829", max31829 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids);
+
+static int max31827_init_client(struct max31827_state *st,
+ struct device *dev)
+{
+ struct fwnode_handle *fwnode;
+ unsigned int res = 0;
+ u32 data, lsb_idx;
+ enum chips type;
+ bool prop;
+ int ret;
+
+ fwnode = dev_fwnode(dev);
+
st->enable = true;
+ res |= MAX31827_DEVICE_ENABLE(1);
- return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG,
- MAX31827_CONFIGURATION_1SHOT_MASK |
- MAX31827_CONFIGURATION_CNV_RATE_MASK,
- MAX31827_DEVICE_ENABLE(1));
+ res |= MAX31827_CONFIGURATION_RESOLUTION_MASK;
+
+ prop = fwnode_property_read_bool(fwnode, "adi,comp-int");
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_COMP_INT_MASK, prop);
+
+ prop = fwnode_property_read_bool(fwnode, "adi,timeout-enable");
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_TIMEOUT_MASK, !prop);
+
+ type = (enum chips)(uintptr_t)device_get_match_data(dev);
+
+ if (fwnode_property_present(fwnode, "adi,alarm-pol")) {
+ ret = fwnode_property_read_u32(fwnode, "adi,alarm-pol", &data);
+ if (ret)
+ return ret;
+
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK, !!data);
+ } else {
+ /*
+ * Set default value.
+ */
+ switch (type) {
+ case max31827:
+ case max31828:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK,
+ MAX31827_ALRM_POL_LOW);
+ break;
+ case max31829:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK,
+ MAX31827_ALRM_POL_HIGH);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (fwnode_property_present(fwnode, "adi,fault-q")) {
+ ret = fwnode_property_read_u32(fwnode, "adi,fault-q", &data);
+ if (ret)
+ return ret;
+
+ /*
+ * Convert the desired fault queue into register bits.
+ */
+ if (data != 0)
+ lsb_idx = __ffs(data);
+
+ if (hweight32(data) != 1 || lsb_idx > 4) {
+ dev_err(dev, "Invalid data in adi,fault-q\n");
+ return -EINVAL;
+ }
+
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK, lsb_idx);
+ } else {
+ /*
+ * Set default value.
+ */
+ switch (type) {
+ case max31827:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK,
+ MAX31827_FLT_Q_1);
+ break;
+ case max31828:
+ case max31829:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK,
+ MAX31827_FLT_Q_4);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, res);
}
static const struct hwmon_channel_info *max31827_info[] = {
@@ -391,7 +564,7 @@ static const struct hwmon_channel_info *max31827_info[] = {
HWMON_T_MIN_HYST | HWMON_T_MIN_ALARM |
HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_MAX_ALARM),
- HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL | HWMON_C_PEC),
NULL,
};
@@ -420,38 +593,44 @@ static int max31827_probe(struct i2c_client *client)
if (!st)
return -ENOMEM;
- mutex_init(&st->lock);
-
st->regmap = devm_regmap_init_i2c(client, &max31827_regmap);
if (IS_ERR(st->regmap))
return dev_err_probe(dev, PTR_ERR(st->regmap),
"Failed to allocate regmap.\n");
- err = max31827_init_client(st);
+ err = devm_regulator_get_enable(dev, "vref");
+ if (err)
+ return dev_err_probe(dev, err, "failed to enable regulator\n");
+
+ err = max31827_init_client(st, dev);
if (err)
return err;
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, st,
&max31827_chip_info,
- NULL);
+ max31827_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
-static const struct i2c_device_id max31827_i2c_ids[] = {
- { "max31827", 0 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids);
-
static const struct of_device_id max31827_of_match[] = {
- { .compatible = "adi,max31827" },
+ {
+ .compatible = "adi,max31827",
+ .data = (void *)max31827
+ },
+ {
+ .compatible = "adi,max31828",
+ .data = (void *)max31828
+ },
+ {
+ .compatible = "adi,max31829",
+ .data = (void *)max31829
+ },
{ }
};
MODULE_DEVICE_TABLE(of, max31827_of_match);
static struct i2c_driver max31827_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max31827",
.of_match_table = max31827_of_match,
diff --git a/drivers/hwmon/max6620.c b/drivers/hwmon/max6620.c
index 5d12fb9c9786..4316dcdd03fc 100644
--- a/drivers/hwmon/max6620.c
+++ b/drivers/hwmon/max6620.c
@@ -130,7 +130,6 @@ static const u8 target_reg[] = {
struct max6620_data {
struct i2c_client *client;
- struct mutex update_lock;
bool valid; /* false until following fields are valid */
unsigned long last_updated; /* in jiffies */
@@ -161,39 +160,36 @@ static int max6620_update_device(struct device *dev)
{
struct max6620_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
- int i;
- int ret = 0;
-
- mutex_lock(&data->update_lock);
+ int i, ret;
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
for (i = 0; i < 4; i++) {
ret = i2c_smbus_read_byte_data(client, config_reg[i]);
if (ret < 0)
- goto error;
+ return ret;
data->fancfg[i] = ret;
ret = i2c_smbus_read_byte_data(client, dyn_reg[i]);
if (ret < 0)
- goto error;
+ return ret;
data->fandyn[i] = ret;
ret = i2c_smbus_read_byte_data(client, tach_reg[i]);
if (ret < 0)
- goto error;
+ return ret;
data->tach[i] = (ret << 3) & 0x7f8;
ret = i2c_smbus_read_byte_data(client, tach_reg[i] + 1);
if (ret < 0)
- goto error;
+ return ret;
data->tach[i] |= (ret >> 5) & 0x7;
ret = i2c_smbus_read_byte_data(client, target_reg[i]);
if (ret < 0)
- goto error;
+ return ret;
data->target[i] = (ret << 3) & 0x7f8;
ret = i2c_smbus_read_byte_data(client, target_reg[i] + 1);
if (ret < 0)
- goto error;
+ return ret;
data->target[i] |= (ret >> 5) & 0x7;
}
@@ -204,16 +200,13 @@ static int max6620_update_device(struct device *dev)
*/
ret = i2c_smbus_read_byte_data(client, MAX6620_REG_FAULT);
if (ret < 0)
- goto error;
+ return ret;
data->fault |= (ret >> 4) & (ret & 0x0F);
data->last_updated = jiffies;
data->valid = true;
}
-
-error:
- mutex_unlock(&data->update_lock);
- return ret;
+ return 0;
}
static umode_t
@@ -261,7 +254,6 @@ max6620_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
case hwmon_fan:
switch (attr) {
case hwmon_fan_alarm:
- mutex_lock(&data->update_lock);
*val = !!(data->fault & BIT(channel));
/* Setting TACH count to re-enable fan fault detection */
@@ -270,21 +262,15 @@ max6620_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
val2 = (data->target[channel] << 5) & 0xe0;
ret = i2c_smbus_write_byte_data(client,
target_reg[channel], val1);
- if (ret < 0) {
- mutex_unlock(&data->update_lock);
+ if (ret < 0)
return ret;
- }
ret = i2c_smbus_write_byte_data(client,
target_reg[channel] + 1, val2);
- if (ret < 0) {
- mutex_unlock(&data->update_lock);
+ if (ret < 0)
return ret;
- }
data->fault &= ~BIT(channel);
}
- mutex_unlock(&data->update_lock);
-
break;
case hwmon_fan_div:
*val = max6620_fan_div_from_reg(data->fandyn[channel]);
@@ -334,7 +320,6 @@ max6620_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
return ret;
data = dev_get_drvdata(dev);
client = data->client;
- mutex_lock(&data->update_lock);
switch (type) {
case hwmon_fan:
@@ -360,8 +345,7 @@ max6620_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
div = 5;
break;
default:
- ret = -EINVAL;
- goto error;
+ return -EINVAL;
}
data->fandyn[channel] &= 0x1F;
data->fandyn[channel] |= div << 5;
@@ -396,8 +380,6 @@ max6620_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
break;
}
-error:
- mutex_unlock(&data->update_lock);
return ret;
}
@@ -478,7 +460,6 @@ static int max6620_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->update_lock);
err = max6620_init_client(data);
if (err)
@@ -493,7 +474,7 @@ static int max6620_probe(struct i2c_client *client)
}
static const struct i2c_device_id max6620_id[] = {
- { "max6620", 0 },
+ { "max6620" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max6620_id);
diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c
index af7e62685898..a7066f3a0bb4 100644
--- a/drivers/hwmon/max6621.c
+++ b/drivers/hwmon/max6621.c
@@ -537,7 +537,7 @@ static int max6621_probe(struct i2c_client *client)
}
static const struct i2c_device_id max6621_id[] = {
- { MAX6621_DRV_NAME, 0 },
+ { MAX6621_DRV_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, max6621_id);
@@ -549,7 +549,6 @@ static const struct of_device_id __maybe_unused max6621_of_match[] = {
MODULE_DEVICE_TABLE(of, max6621_of_match);
static struct i2c_driver max6621_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = MAX6621_DRV_NAME,
.of_match_table = of_match_ptr(max6621_of_match),
diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c
index aa7f21ab2395..99140a2ca995 100644
--- a/drivers/hwmon/max6639.c
+++ b/drivers/hwmon/max6639.c
@@ -16,10 +16,9 @@
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/platform_data/max6639.h>
+#include <linux/regmap.h>
+#include <linux/util_macros.h>
/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
@@ -54,11 +53,17 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END };
#define MAX6639_GCONFIG_PWM_FREQ_HI 0x08
#define MAX6639_FAN_CONFIG1_PWM 0x80
-
+#define MAX6639_FAN_CONFIG3_FREQ_MASK 0x03
#define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED 0x40
+#define MAX6639_NUM_CHANNELS 2
+
static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 };
+/* Supported PWM frequency */
+static const unsigned int freq_table[] = { 20, 33, 50, 100, 5000, 8333, 12500,
+ 25000 };
+
#define FAN_FROM_REG(val, rpm_range) ((val) == 0 || (val) == 255 ? \
0 : (rpm_ranges[rpm_range] * 30) / (val))
#define TEMP_LIMIT_TO_REG(val) clamp_val((val) / 1000, 0, 255)
@@ -67,323 +72,435 @@ static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 };
* Client data (each client gets its own)
*/
struct max6639_data {
- struct i2c_client *client;
- struct mutex update_lock;
- bool valid; /* true if following fields are valid */
- unsigned long last_updated; /* In jiffies */
-
- /* Register values sampled regularly */
- u16 temp[2]; /* Temperature, in 1/8 C, 0..255 C */
- bool temp_fault[2]; /* Detected temperature diode failure */
- u8 fan[2]; /* Register value: TACH count for fans >=30 */
- u8 status; /* Detected channel alarms and fan failures */
-
- /* Register values only written to */
- u8 pwm[2]; /* Register value: Duty cycle 0..120 */
- u8 temp_therm[2]; /* THERM Temperature, 0..255 C (->_max) */
- u8 temp_alert[2]; /* ALERT Temperature, 0..255 C (->_crit) */
- u8 temp_ot[2]; /* OT Temperature, 0..255 C (->_emergency) */
+ struct regmap *regmap;
/* Register values initialized only once */
- u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */
- u8 rpm_range; /* Index in above rpm_ranges table */
+ u8 ppr[MAX6639_NUM_CHANNELS]; /* Pulses per rotation 0..3 for 1..4 ppr */
+ u8 rpm_range[MAX6639_NUM_CHANNELS]; /* Index in above rpm_ranges table */
+ u32 target_rpm[MAX6639_NUM_CHANNELS];
/* Optional regulator for FAN supply */
struct regulator *reg;
};
-static struct max6639_data *max6639_update_device(struct device *dev)
+static int max6639_temp_read_input(struct device *dev, int channel, long *temp)
{
+ u32 regs[2] = { MAX6639_REG_TEMP_EXT(channel), MAX6639_REG_TEMP(channel) };
struct max6639_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- struct max6639_data *ret = data;
- int i;
- int status_reg;
+ u8 regvals[2];
+ int res;
- mutex_lock(&data->update_lock);
+ res = regmap_multi_reg_read(data->regmap, regs, regvals, 2);
+ if (res < 0)
+ return res;
- if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) {
- int res;
+ *temp = ((regvals[0] >> 5) | (regvals[1] << 3)) * 125;
- dev_dbg(&client->dev, "Starting max6639 update\n");
+ return 0;
+}
- status_reg = i2c_smbus_read_byte_data(client,
- MAX6639_REG_STATUS);
- if (status_reg < 0) {
- ret = ERR_PTR(status_reg);
- goto abort;
- }
+static int max6639_temp_read_fault(struct device *dev, int channel, long *fault)
+{
+ struct max6639_data *data = dev_get_drvdata(dev);
+ unsigned int val;
+ int res;
- data->status = status_reg;
-
- for (i = 0; i < 2; i++) {
- res = i2c_smbus_read_byte_data(client,
- MAX6639_REG_FAN_CNT(i));
- if (res < 0) {
- ret = ERR_PTR(res);
- goto abort;
- }
- data->fan[i] = res;
-
- res = i2c_smbus_read_byte_data(client,
- MAX6639_REG_TEMP_EXT(i));
- if (res < 0) {
- ret = ERR_PTR(res);
- goto abort;
- }
- data->temp[i] = res >> 5;
- data->temp_fault[i] = res & 0x01;
-
- res = i2c_smbus_read_byte_data(client,
- MAX6639_REG_TEMP(i));
- if (res < 0) {
- ret = ERR_PTR(res);
- goto abort;
- }
- data->temp[i] |= res << 3;
- }
+ res = regmap_read(data->regmap, MAX6639_REG_TEMP_EXT(channel), &val);
+ if (res < 0)
+ return res;
- data->last_updated = jiffies;
- data->valid = true;
- }
-abort:
- mutex_unlock(&data->update_lock);
+ *fault = val & 1;
- return ret;
+ return 0;
}
-static ssize_t temp_input_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_temp_read_max(struct device *dev, int channel, long *max)
{
- long temp;
- struct max6639_data *data = max6639_update_device(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
+ struct max6639_data *data = dev_get_drvdata(dev);
+ unsigned int val;
+ int res;
- if (IS_ERR(data))
- return PTR_ERR(data);
+ res = regmap_read(data->regmap, MAX6639_REG_THERM_LIMIT(channel), &val);
+ if (res < 0)
+ return res;
+
+ *max = (long)val * 1000;
- temp = data->temp[attr->index] * 125;
- return sprintf(buf, "%ld\n", temp);
+ return 0;
}
-static ssize_t temp_fault_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_temp_read_crit(struct device *dev, int channel, long *crit)
{
- struct max6639_data *data = max6639_update_device(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
+ struct max6639_data *data = dev_get_drvdata(dev);
+ unsigned int val;
+ int res;
- if (IS_ERR(data))
- return PTR_ERR(data);
+ res = regmap_read(data->regmap, MAX6639_REG_ALERT_LIMIT(channel), &val);
+ if (res < 0)
+ return res;
+
+ *crit = (long)val * 1000;
- return sprintf(buf, "%d\n", data->temp_fault[attr->index]);
+ return 0;
}
-static ssize_t temp_max_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_temp_read_emergency(struct device *dev, int channel, long *emerg)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
struct max6639_data *data = dev_get_drvdata(dev);
+ unsigned int val;
+ int res;
- return sprintf(buf, "%d\n", (data->temp_therm[attr->index] * 1000));
+ res = regmap_read(data->regmap, MAX6639_REG_OT_LIMIT(channel), &val);
+ if (res < 0)
+ return res;
+
+ *emerg = (long)val * 1000;
+
+ return 0;
}
-static ssize_t temp_max_store(struct device *dev,
- struct device_attribute *dev_attr,
- const char *buf, size_t count)
+static int max6639_get_status(struct device *dev, unsigned int *status)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
struct max6639_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- unsigned long val;
+ unsigned int val;
int res;
- res = kstrtoul(buf, 10, &val);
- if (res)
+ res = regmap_read(data->regmap, MAX6639_REG_STATUS, &val);
+ if (res < 0)
return res;
- mutex_lock(&data->update_lock);
- data->temp_therm[attr->index] = TEMP_LIMIT_TO_REG(val);
- i2c_smbus_write_byte_data(client,
- MAX6639_REG_THERM_LIMIT(attr->index),
- data->temp_therm[attr->index]);
- mutex_unlock(&data->update_lock);
- return count;
+ *status = val;
+
+ return 0;
}
-static ssize_t temp_crit_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_temp_set_max(struct max6639_data *data, int channel, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
- struct max6639_data *data = dev_get_drvdata(dev);
+ int res;
- return sprintf(buf, "%d\n", (data->temp_alert[attr->index] * 1000));
+ res = regmap_write(data->regmap, MAX6639_REG_THERM_LIMIT(channel),
+ TEMP_LIMIT_TO_REG(val));
+ return res;
}
-static ssize_t temp_crit_store(struct device *dev,
- struct device_attribute *dev_attr,
- const char *buf, size_t count)
+static int max6639_temp_set_crit(struct max6639_data *data, int channel, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
- struct max6639_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- unsigned long val;
int res;
- res = kstrtoul(buf, 10, &val);
- if (res)
- return res;
+ res = regmap_write(data->regmap, MAX6639_REG_ALERT_LIMIT(channel), TEMP_LIMIT_TO_REG(val));
- mutex_lock(&data->update_lock);
- data->temp_alert[attr->index] = TEMP_LIMIT_TO_REG(val);
- i2c_smbus_write_byte_data(client,
- MAX6639_REG_ALERT_LIMIT(attr->index),
- data->temp_alert[attr->index]);
- mutex_unlock(&data->update_lock);
- return count;
+ return res;
}
-static ssize_t temp_emergency_show(struct device *dev,
- struct device_attribute *dev_attr,
- char *buf)
+static int max6639_temp_set_emergency(struct max6639_data *data, int channel, long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
- struct max6639_data *data = dev_get_drvdata(dev);
+ int res;
- return sprintf(buf, "%d\n", (data->temp_ot[attr->index] * 1000));
+ res = regmap_write(data->regmap, MAX6639_REG_OT_LIMIT(channel), TEMP_LIMIT_TO_REG(val));
+
+ return res;
}
-static ssize_t temp_emergency_store(struct device *dev,
- struct device_attribute *dev_attr,
- const char *buf, size_t count)
+static int max6639_read_fan(struct device *dev, u32 attr, int channel,
+ long *fan_val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
struct max6639_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- unsigned long val;
+ unsigned int val;
int res;
- res = kstrtoul(buf, 10, &val);
- if (res)
- return res;
+ switch (attr) {
+ case hwmon_fan_input:
+ res = regmap_read(data->regmap, MAX6639_REG_FAN_CNT(channel), &val);
+ if (res < 0)
+ return res;
+ *fan_val = FAN_FROM_REG(val, data->rpm_range[channel]);
+ return 0;
+ case hwmon_fan_fault:
+ res = max6639_get_status(dev, &val);
+ if (res < 0)
+ return res;
+ *fan_val = !!(val & BIT(1 - channel));
+ return 0;
+ case hwmon_fan_pulses:
+ *fan_val = data->ppr[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- mutex_lock(&data->update_lock);
- data->temp_ot[attr->index] = TEMP_LIMIT_TO_REG(val);
- i2c_smbus_write_byte_data(client,
- MAX6639_REG_OT_LIMIT(attr->index),
- data->temp_ot[attr->index]);
- mutex_unlock(&data->update_lock);
- return count;
+static int max6639_set_ppr(struct max6639_data *data, int channel, u8 ppr)
+{
+ /* Decrement the PPR value and shift left by 6 to match the register format */
+ return regmap_write(data->regmap, MAX6639_REG_FAN_PPR(channel), ppr-- << 6);
}
-static ssize_t pwm_show(struct device *dev, struct device_attribute *dev_attr,
- char *buf)
+static int max6639_write_fan(struct device *dev, u32 attr, int channel,
+ long val)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
struct max6639_data *data = dev_get_drvdata(dev);
+ int err;
- return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120);
+ switch (attr) {
+ case hwmon_fan_pulses:
+ if (val <= 0 || val > 4)
+ return -EINVAL;
+
+ /* Set Fan pulse per revolution */
+ err = max6639_set_ppr(data, channel, val);
+ if (err < 0)
+ return err;
+ data->ppr[channel] = val;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t pwm_store(struct device *dev,
- struct device_attribute *dev_attr, const char *buf,
- size_t count)
+static umode_t max6639_fan_is_visible(const void *_data, u32 attr, int channel)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_fault:
+ return 0444;
+ case hwmon_fan_pulses:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int max6639_read_pwm(struct device *dev, u32 attr, int channel,
+ long *pwm_val)
+{
+ u32 regs[2] = { MAX6639_REG_FAN_CONFIG3(channel), MAX6639_REG_GCONFIG };
struct max6639_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- unsigned long val;
+ unsigned int val;
+ u8 regvals[2];
int res;
+ u8 i;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ res = regmap_read(data->regmap, MAX6639_REG_TARGTDUTY(channel), &val);
+ if (res < 0)
+ return res;
+ *pwm_val = val * 255 / 120;
+ return 0;
+ case hwmon_pwm_freq:
+ res = regmap_multi_reg_read(data->regmap, regs, regvals, 2);
+ if (res < 0)
+ return res;
+ i = regvals[0] & MAX6639_FAN_CONFIG3_FREQ_MASK;
+ if (regvals[1] & MAX6639_GCONFIG_PWM_FREQ_HI)
+ i |= 0x4;
+ *pwm_val = freq_table[i];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- res = kstrtoul(buf, 10, &val);
- if (res)
- return res;
+static int max6639_write_pwm(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct max6639_data *data = dev_get_drvdata(dev);
+ int err;
+ u8 i;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ return regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(channel),
+ val * 120 / 255);
+ case hwmon_pwm_freq:
+ val = clamp_val(val, 0, 25000);
+
+ i = find_closest(val, freq_table, ARRAY_SIZE(freq_table));
+
+ err = regmap_update_bits(data->regmap, MAX6639_REG_FAN_CONFIG3(channel),
+ MAX6639_FAN_CONFIG3_FREQ_MASK, i);
+ if (err < 0)
+ return err;
+
+ if (i >> 2)
+ err = regmap_set_bits(data->regmap, MAX6639_REG_GCONFIG,
+ MAX6639_GCONFIG_PWM_FREQ_HI);
+ else
+ err = regmap_clear_bits(data->regmap, MAX6639_REG_GCONFIG,
+ MAX6639_GCONFIG_PWM_FREQ_HI);
+
+ return err;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- val = clamp_val(val, 0, 255);
+static umode_t max6639_pwm_is_visible(const void *_data, u32 attr, int channel)
+{
+ switch (attr) {
+ case hwmon_pwm_input:
+ case hwmon_pwm_freq:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int max6639_read_temp(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ unsigned int status;
+ int res;
- mutex_lock(&data->update_lock);
- data->pwm[attr->index] = (u8)(val * 120 / 255);
- i2c_smbus_write_byte_data(client,
- MAX6639_REG_TARGTDUTY(attr->index),
- data->pwm[attr->index]);
- mutex_unlock(&data->update_lock);
- return count;
+ switch (attr) {
+ case hwmon_temp_input:
+ res = max6639_temp_read_input(dev, channel, val);
+ return res;
+ case hwmon_temp_fault:
+ res = max6639_temp_read_fault(dev, channel, val);
+ return res;
+ case hwmon_temp_max:
+ res = max6639_temp_read_max(dev, channel, val);
+ return res;
+ case hwmon_temp_crit:
+ res = max6639_temp_read_crit(dev, channel, val);
+ return res;
+ case hwmon_temp_emergency:
+ res = max6639_temp_read_emergency(dev, channel, val);
+ return res;
+ case hwmon_temp_max_alarm:
+ res = max6639_get_status(dev, &status);
+ if (res < 0)
+ return res;
+ *val = !!(status & BIT(3 - channel));
+ return 0;
+ case hwmon_temp_crit_alarm:
+ res = max6639_get_status(dev, &status);
+ if (res < 0)
+ return res;
+ *val = !!(status & BIT(7 - channel));
+ return 0;
+ case hwmon_temp_emergency_alarm:
+ res = max6639_get_status(dev, &status);
+ if (res < 0)
+ return res;
+ *val = !!(status & BIT(5 - channel));
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
}
-static ssize_t fan_input_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_write_temp(struct device *dev, u32 attr, int channel,
+ long val)
{
- struct max6639_data *data = max6639_update_device(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
+ struct max6639_data *data = dev_get_drvdata(dev);
- if (IS_ERR(data))
- return PTR_ERR(data);
+ switch (attr) {
+ case hwmon_temp_max:
+ return max6639_temp_set_max(data, channel, val);
+ case hwmon_temp_crit:
+ return max6639_temp_set_crit(data, channel, val);
+ case hwmon_temp_emergency:
+ return max6639_temp_set_emergency(data, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index],
- data->rpm_range));
+static umode_t max6639_temp_is_visible(const void *_data, u32 attr, int channel)
+{
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_fault:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_emergency_alarm:
+ return 0444;
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ case hwmon_temp_emergency:
+ return 0644;
+ default:
+ return 0;
+ }
}
-static ssize_t alarm_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
+static int max6639_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
{
- struct max6639_data *data = max6639_update_device(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
+ switch (type) {
+ case hwmon_fan:
+ return max6639_read_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return max6639_read_pwm(dev, attr, channel, val);
+ case hwmon_temp:
+ return max6639_read_temp(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- if (IS_ERR(data))
- return PTR_ERR(data);
+static int max6639_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_fan:
+ return max6639_write_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return max6639_write_pwm(dev, attr, channel, val);
+ case hwmon_temp:
+ return max6639_write_temp(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
- return sprintf(buf, "%d\n", !!(data->status & (1 << attr->index)));
+static umode_t max6639_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ return max6639_fan_is_visible(data, attr, channel);
+ case hwmon_pwm:
+ return max6639_pwm_is_visible(data, attr, channel);
+ case hwmon_temp:
+ return max6639_temp_is_visible(data, attr, channel);
+ default:
+ return 0;
+ }
}
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp_input, 1);
-static SENSOR_DEVICE_ATTR_RO(temp1_fault, temp_fault, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, temp_fault, 1);
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp_max, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp_max, 1);
-static SENSOR_DEVICE_ATTR_RW(temp1_crit, temp_crit, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_crit, temp_crit, 1);
-static SENSOR_DEVICE_ATTR_RW(temp1_emergency, temp_emergency, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_emergency, temp_emergency, 1);
-static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0);
-static SENSOR_DEVICE_ATTR_RW(pwm2, pwm, 1);
-static SENSOR_DEVICE_ATTR_RO(fan1_input, fan_input, 0);
-static SENSOR_DEVICE_ATTR_RO(fan2_input, fan_input, 1);
-static SENSOR_DEVICE_ATTR_RO(fan1_fault, alarm, 1);
-static SENSOR_DEVICE_ATTR_RO(fan2_fault, alarm, 0);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 3);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 2);
-static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 7);
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 6);
-static SENSOR_DEVICE_ATTR_RO(temp1_emergency_alarm, alarm, 5);
-static SENSOR_DEVICE_ATTR_RO(temp2_emergency_alarm, alarm, 4);
-
-
-static struct attribute *max6639_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp1_fault.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_emergency.dev_attr.attr,
- &sensor_dev_attr_temp2_emergency.dev_attr.attr,
- &sensor_dev_attr_pwm1.dev_attr.attr,
- &sensor_dev_attr_pwm2.dev_attr.attr,
- &sensor_dev_attr_fan1_input.dev_attr.attr,
- &sensor_dev_attr_fan2_input.dev_attr.attr,
- &sensor_dev_attr_fan1_fault.dev_attr.attr,
- &sensor_dev_attr_fan2_fault.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_emergency_alarm.dev_attr.attr,
+static const struct hwmon_channel_info * const max6639_info[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_PULSES,
+ HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_PULSES),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_FREQ,
+ HWMON_PWM_INPUT | HWMON_PWM_FREQ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_EMERGENCY |
+ HWMON_T_EMERGENCY_ALARM,
+ HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_EMERGENCY |
+ HWMON_T_EMERGENCY_ALARM),
NULL
};
-ATTRIBUTE_GROUPS(max6639);
+
+static const struct hwmon_ops max6639_hwmon_ops = {
+ .is_visible = max6639_is_visible,
+ .read = max6639_read,
+ .write = max6639_write,
+};
+
+static const struct hwmon_chip_info max6639_chip_info = {
+ .ops = &max6639_hwmon_ops,
+ .info = max6639_info,
+};
/*
* returns respective index in rpm_ranges table
@@ -401,101 +518,136 @@ static int rpm_range_to_reg(int range)
return 1; /* default: 4000 RPM */
}
+static int max6639_probe_child_from_dt(struct i2c_client *client,
+ struct device_node *child,
+ struct max6639_data *data)
+
+{
+ struct device *dev = &client->dev;
+ u32 i;
+ int err, val;
+
+ err = of_property_read_u32(child, "reg", &i);
+ if (err) {
+ dev_err(dev, "missing reg property of %pOFn\n", child);
+ return err;
+ }
+
+ if (i > 1) {
+ dev_err(dev, "Invalid fan index reg %d\n", i);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32(child, "pulses-per-revolution", &val);
+ if (!err) {
+ if (val < 1 || val > 5) {
+ dev_err(dev, "invalid pulses-per-revolution %d of %pOFn\n", val, child);
+ return -EINVAL;
+ }
+ data->ppr[i] = val;
+ }
+
+ err = of_property_read_u32(child, "max-rpm", &val);
+ if (!err)
+ data->rpm_range[i] = rpm_range_to_reg(val);
+
+ err = of_property_read_u32(child, "target-rpm", &val);
+ if (!err)
+ data->target_rpm[i] = val;
+
+ return 0;
+}
+
static int max6639_init_client(struct i2c_client *client,
struct max6639_data *data)
{
- struct max6639_platform_data *max6639_info =
- dev_get_platdata(&client->dev);
- int i;
- int rpm_range = 1; /* default: 4000 RPM */
- int err;
+ struct device *dev = &client->dev;
+ const struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int i, err;
+ u8 target_duty;
/* Reset chip to default values, see below for GCONFIG setup */
- err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
- MAX6639_GCONFIG_POR);
+ err = regmap_write(data->regmap, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_POR);
if (err)
- goto exit;
+ return err;
/* Fans pulse per revolution is 2 by default */
- if (max6639_info && max6639_info->ppr > 0 &&
- max6639_info->ppr < 5)
- data->ppr = max6639_info->ppr;
- else
- data->ppr = 2;
- data->ppr -= 1;
+ data->ppr[0] = 2;
+ data->ppr[1] = 2;
+
+ /* default: 4000 RPM */
+ data->rpm_range[0] = 1;
+ data->rpm_range[1] = 1;
+ data->target_rpm[0] = 4000;
+ data->target_rpm[1] = 4000;
+
+ for_each_child_of_node(np, child) {
+ if (strcmp(child->name, "fan"))
+ continue;
- if (max6639_info)
- rpm_range = rpm_range_to_reg(max6639_info->rpm_range);
- data->rpm_range = rpm_range;
+ err = max6639_probe_child_from_dt(client, child, data);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < MAX6639_NUM_CHANNELS; i++) {
+ err = regmap_set_bits(data->regmap, MAX6639_REG_OUTPUT_MASK, BIT(1 - i));
+ if (err)
+ return err;
/* Set Fan pulse per revolution */
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_FAN_PPR(i),
- data->ppr << 6);
+ err = max6639_set_ppr(data, i, data->ppr[i]);
if (err)
- goto exit;
+ return err;
/* Fans config PWM, RPM */
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_FAN_CONFIG1(i),
- MAX6639_FAN_CONFIG1_PWM | rpm_range);
+ err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG1(i),
+ MAX6639_FAN_CONFIG1_PWM | data->rpm_range[i]);
if (err)
- goto exit;
+ return err;
/* Fans PWM polarity high by default */
- if (max6639_info && max6639_info->pwm_polarity == 0)
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_FAN_CONFIG2a(i), 0x00);
- else
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_FAN_CONFIG2a(i), 0x02);
+ err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG2a(i), 0x00);
if (err)
- goto exit;
+ return err;
/*
* /THERM full speed enable,
* PWM frequency 25kHz, see also GCONFIG below
*/
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_FAN_CONFIG3(i),
- MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03);
+ err = regmap_write(data->regmap, MAX6639_REG_FAN_CONFIG3(i),
+ MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03);
if (err)
- goto exit;
+ return err;
/* Max. temp. 80C/90C/100C */
- data->temp_therm[i] = 80;
- data->temp_alert[i] = 90;
- data->temp_ot[i] = 100;
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_THERM_LIMIT(i),
- data->temp_therm[i]);
+ err = regmap_write(data->regmap, MAX6639_REG_THERM_LIMIT(i), 80);
if (err)
- goto exit;
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_ALERT_LIMIT(i),
- data->temp_alert[i]);
+ return err;
+ err = regmap_write(data->regmap, MAX6639_REG_ALERT_LIMIT(i), 90);
if (err)
- goto exit;
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]);
+ return err;
+ err = regmap_write(data->regmap, MAX6639_REG_OT_LIMIT(i), 100);
if (err)
- goto exit;
+ return err;
- /* PWM 120/120 (i.e. 100%) */
- data->pwm[i] = 120;
- err = i2c_smbus_write_byte_data(client,
- MAX6639_REG_TARGTDUTY(i), data->pwm[i]);
+ /* Set PWM based on target RPM if specified */
+ if (data->target_rpm[i] > rpm_ranges[data->rpm_range[i]])
+ data->target_rpm[i] = rpm_ranges[data->rpm_range[i]];
+
+ target_duty = 120 * data->target_rpm[i] / rpm_ranges[data->rpm_range[i]];
+ err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), target_duty);
if (err)
- goto exit;
+ return err;
}
/* Start monitoring */
- err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG,
- MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL |
- MAX6639_GCONFIG_PWM_FREQ_HI);
-exit:
- return err;
+ return regmap_write(data->regmap, MAX6639_REG_GCONFIG,
+ MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL |
+ MAX6639_GCONFIG_PWM_FREQ_HI);
+
}
/* Return 0 if detection is successful, -ENODEV otherwise */
@@ -524,6 +676,32 @@ static void max6639_regulator_disable(void *data)
regulator_disable(data);
}
+static bool max6639_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX6639_REG_TEMP(0):
+ case MAX6639_REG_TEMP_EXT(0):
+ case MAX6639_REG_TEMP(1):
+ case MAX6639_REG_TEMP_EXT(1):
+ case MAX6639_REG_STATUS:
+ case MAX6639_REG_FAN_CNT(0):
+ case MAX6639_REG_FAN_CNT(1):
+ case MAX6639_REG_TARGTDUTY(0):
+ case MAX6639_REG_TARGTDUTY(1):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config max6639_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX6639_REG_DEVREV,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = max6639_regmap_is_volatile,
+};
+
static int max6639_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -535,7 +713,11 @@ static int max6639_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- data->client = client;
+ data->regmap = devm_regmap_init_i2c(client, &max6639_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev,
+ PTR_ERR(data->regmap),
+ "regmap initialization failed\n");
data->reg = devm_regulator_get_optional(dev, "fan");
if (IS_ERR(data->reg)) {
@@ -558,38 +740,31 @@ static int max6639_probe(struct i2c_client *client)
}
}
- mutex_init(&data->update_lock);
-
/* Initialize the max6639 chip */
err = max6639_init_client(client, data);
if (err < 0)
return err;
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data,
- max6639_groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &max6639_chip_info,
+ NULL);
+
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static int max6639_suspend(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
struct max6639_data *data = dev_get_drvdata(dev);
- int ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
-
- if (ret < 0)
- return ret;
if (data->reg)
regulator_disable(data->reg);
- return i2c_smbus_write_byte_data(client,
- MAX6639_REG_GCONFIG, ret | MAX6639_GCONFIG_STANDBY);
+ return regmap_write_bits(data->regmap, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_STANDBY,
+ MAX6639_GCONFIG_STANDBY);
}
static int max6639_resume(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
struct max6639_data *data = dev_get_drvdata(dev);
int ret;
@@ -601,16 +776,12 @@ static int max6639_resume(struct device *dev)
}
}
- ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
- if (ret < 0)
- return ret;
-
- return i2c_smbus_write_byte_data(client,
- MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY);
+ return regmap_write_bits(data->regmap, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_STANDBY,
+ ~MAX6639_GCONFIG_STANDBY);
}
static const struct i2c_device_id max6639_id[] = {
- {"max6639", 0},
+ {"max6639"},
{ }
};
diff --git a/drivers/hwmon/max6642.c b/drivers/hwmon/max6642.c
deleted file mode 100644
index 8b2e4d6101a2..000000000000
--- a/drivers/hwmon/max6642.c
+++ /dev/null
@@ -1,314 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Driver for +/-1 degree C, SMBus-Compatible Remote/Local Temperature Sensor
- * with Overtemperature Alarm
- *
- * Copyright (C) 2011 AppearTV AS
- *
- * Derived from:
- *
- * Based on the max1619 driver.
- * Copyright (C) 2003-2004 Oleksij Rempel <bug-track@fisher-privat.net>
- * Jean Delvare <jdelvare@suse.de>
- *
- * The MAX6642 is a sensor chip made by Maxim.
- * It reports up to two temperatures (its own plus up to
- * one external one). Complete datasheet can be
- * obtained from Maxim's website at:
- * http://datasheets.maxim-ic.com/en/ds/MAX6642.pdf
- */
-
-
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/err.h>
-#include <linux/mutex.h>
-#include <linux/sysfs.h>
-
-static const unsigned short normal_i2c[] = {
- 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
-
-/*
- * The MAX6642 registers
- */
-
-#define MAX6642_REG_R_MAN_ID 0xFE
-#define MAX6642_REG_R_CONFIG 0x03
-#define MAX6642_REG_W_CONFIG 0x09
-#define MAX6642_REG_R_STATUS 0x02
-#define MAX6642_REG_R_LOCAL_TEMP 0x00
-#define MAX6642_REG_R_LOCAL_TEMPL 0x11
-#define MAX6642_REG_R_LOCAL_HIGH 0x05
-#define MAX6642_REG_W_LOCAL_HIGH 0x0B
-#define MAX6642_REG_R_REMOTE_TEMP 0x01
-#define MAX6642_REG_R_REMOTE_TEMPL 0x10
-#define MAX6642_REG_R_REMOTE_HIGH 0x07
-#define MAX6642_REG_W_REMOTE_HIGH 0x0D
-
-/*
- * Conversions
- */
-
-static int temp_from_reg10(int val)
-{
- return val * 250;
-}
-
-static int temp_from_reg(int val)
-{
- return val * 1000;
-}
-
-static int temp_to_reg(int val)
-{
- return val / 1000;
-}
-
-/*
- * Client data (each client gets its own)
- */
-
-struct max6642_data {
- struct i2c_client *client;
- struct mutex update_lock;
- bool valid; /* zero until following fields are valid */
- unsigned long last_updated; /* in jiffies */
-
- /* registers values */
- u16 temp_input[2]; /* local/remote */
- u16 temp_high[2]; /* local/remote */
- u8 alarms;
-};
-
-/*
- * Real code
- */
-
-static void max6642_init_client(struct max6642_data *data,
- struct i2c_client *client)
-{
- u8 config;
-
- /*
- * Start the conversions.
- */
- config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG);
- if (config & 0x40)
- i2c_smbus_write_byte_data(client, MAX6642_REG_W_CONFIG,
- config & 0xBF); /* run */
-
- data->temp_high[0] = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_LOCAL_HIGH);
- data->temp_high[1] = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_REMOTE_HIGH);
-}
-
-/* Return 0 if detection is successful, -ENODEV otherwise */
-static int max6642_detect(struct i2c_client *client,
- struct i2c_board_info *info)
-{
- struct i2c_adapter *adapter = client->adapter;
- u8 reg_config, reg_status, man_id;
-
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
- return -ENODEV;
-
- /* identification */
- man_id = i2c_smbus_read_byte_data(client, MAX6642_REG_R_MAN_ID);
- if (man_id != 0x4D)
- return -ENODEV;
-
- /* sanity check */
- if (i2c_smbus_read_byte_data(client, 0x04) != 0x4D
- || i2c_smbus_read_byte_data(client, 0x06) != 0x4D
- || i2c_smbus_read_byte_data(client, 0xff) != 0x4D)
- return -ENODEV;
-
- /*
- * We read the config and status register, the 4 lower bits in the
- * config register should be zero and bit 5, 3, 1 and 0 should be
- * zero in the status register.
- */
- reg_config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG);
- if ((reg_config & 0x0f) != 0x00)
- return -ENODEV;
-
- /* in between, another round of sanity checks */
- if (i2c_smbus_read_byte_data(client, 0x04) != reg_config
- || i2c_smbus_read_byte_data(client, 0x06) != reg_config
- || i2c_smbus_read_byte_data(client, 0xff) != reg_config)
- return -ENODEV;
-
- reg_status = i2c_smbus_read_byte_data(client, MAX6642_REG_R_STATUS);
- if ((reg_status & 0x2b) != 0x00)
- return -ENODEV;
-
- strscpy(info->type, "max6642", I2C_NAME_SIZE);
-
- return 0;
-}
-
-static struct max6642_data *max6642_update_device(struct device *dev)
-{
- struct max6642_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- u16 val, tmp;
-
- mutex_lock(&data->update_lock);
-
- if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
- dev_dbg(dev, "Updating max6642 data.\n");
- val = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_LOCAL_TEMPL);
- tmp = (val >> 6) & 3;
- val = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_LOCAL_TEMP);
- val = (val << 2) | tmp;
- data->temp_input[0] = val;
- val = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_REMOTE_TEMPL);
- tmp = (val >> 6) & 3;
- val = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_REMOTE_TEMP);
- val = (val << 2) | tmp;
- data->temp_input[1] = val;
- data->alarms = i2c_smbus_read_byte_data(client,
- MAX6642_REG_R_STATUS);
-
- data->last_updated = jiffies;
- data->valid = true;
- }
-
- mutex_unlock(&data->update_lock);
-
- return data;
-}
-
-/*
- * Sysfs stuff
- */
-
-static ssize_t temp_max10_show(struct device *dev,
- struct device_attribute *dev_attr, char *buf)
-{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
- struct max6642_data *data = max6642_update_device(dev);
-
- return sprintf(buf, "%d\n",
- temp_from_reg10(data->temp_input[attr->index]));
-}
-
-static ssize_t temp_max_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
- struct max6642_data *data = max6642_update_device(dev);
-
- return sprintf(buf, "%d\n", temp_from_reg(data->temp_high[attr2->nr]));
-}
-
-static ssize_t temp_max_store(struct device *dev,
- struct device_attribute *attr, const char *buf,
- size_t count)
-{
- struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
- struct max6642_data *data = dev_get_drvdata(dev);
- unsigned long val;
- int err;
-
- err = kstrtoul(buf, 10, &val);
- if (err < 0)
- return err;
-
- mutex_lock(&data->update_lock);
- data->temp_high[attr2->nr] = clamp_val(temp_to_reg(val), 0, 255);
- i2c_smbus_write_byte_data(data->client, attr2->index,
- data->temp_high[attr2->nr]);
- mutex_unlock(&data->update_lock);
- return count;
-}
-
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- int bitnr = to_sensor_dev_attr(attr)->index;
- struct max6642_data *data = max6642_update_device(dev);
- return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
-}
-
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_max10, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp_max10, 1);
-static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp_max, 0,
- MAX6642_REG_W_LOCAL_HIGH);
-static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp_max, 1,
- MAX6642_REG_W_REMOTE_HIGH);
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 2);
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 4);
-
-static struct attribute *max6642_attrs[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
-
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(max6642);
-
-static int max6642_probe(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- struct max6642_data *data;
- struct device *hwmon_dev;
-
- data = devm_kzalloc(dev, sizeof(struct max6642_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- data->client = client;
- mutex_init(&data->update_lock);
-
- /* Initialize the MAX6642 chip */
- max6642_init_client(data, client);
-
- hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
- client->name, data,
- max6642_groups);
- return PTR_ERR_OR_ZERO(hwmon_dev);
-}
-
-/*
- * Driver data (common to all clients)
- */
-
-static const struct i2c_device_id max6642_id[] = {
- { "max6642", 0 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, max6642_id);
-
-static struct i2c_driver max6642_driver = {
- .class = I2C_CLASS_HWMON,
- .driver = {
- .name = "max6642",
- },
- .probe = max6642_probe,
- .id_table = max6642_id,
- .detect = max6642_detect,
- .address_list = normal_i2c,
-};
-
-module_i2c_driver(max6642_driver);
-
-MODULE_AUTHOR("Per Dalen <per.dalen@appeartv.com>");
-MODULE_DESCRIPTION("MAX6642 sensor driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c
index cc8428a3045d..9649c6611d5f 100644
--- a/drivers/hwmon/max6650.c
+++ b/drivers/hwmon/max6650.c
@@ -26,7 +26,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/thermal.h>
/*
@@ -763,8 +763,6 @@ static int max6650_probe(struct i2c_client *client)
{
struct thermal_cooling_device *cooling_dev;
struct device *dev = &client->dev;
- const struct of_device_id *of_id =
- of_match_device(of_match_ptr(max6650_dt_match), dev);
struct max6650_data *data;
struct device *hwmon_dev;
int err;
@@ -776,8 +774,8 @@ static int max6650_probe(struct i2c_client *client)
data->client = client;
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
- data->nr_fans = of_id ? (int)(uintptr_t)of_id->data :
- i2c_match_id(max6650_id, client)->driver_data;
+
+ data->nr_fans = (uintptr_t)i2c_get_match_data(client);
/*
* Initialize the max6650 chip
diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c
index 7d10dd434f2e..dd906cf491ca 100644
--- a/drivers/hwmon/max6697.c
+++ b/drivers/hwmon/max6697.c
@@ -6,18 +6,16 @@
* Copyright (c) 2011 David George <david.george@ska.ac.za>
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/jiffies.h>
-#include <linux/i2c.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/err.h>
-#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
#include <linux/of.h>
-
-#include <linux/platform_data/max6697.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694,
max6697, max6698, max6699 };
@@ -33,21 +31,36 @@ static const u8 MAX6697_REG_MAX[] = {
static const u8 MAX6697_REG_CRIT[] = {
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 };
+#define MAX6697_REG_MIN 0x30
/*
- * Map device tree / platform data register bit map to chip bit map.
+ * Map device tree / internal register bit map to chip bit map.
* Applies to alert register and over-temperature register.
*/
+
+#define MAX6697_EXTERNAL_MASK_DT GENMASK(7, 1)
+#define MAX6697_LOCAL_MASK_DT BIT(0)
+#define MAX6697_EXTERNAL_MASK_CHIP GENMASK(6, 0)
+#define MAX6697_LOCAL_MASK_CHIP BIT(7)
+
+/* alert - local channel is in bit 6 */
#define MAX6697_ALERT_MAP_BITS(reg) ((((reg) & 0x7e) >> 1) | \
(((reg) & 0x01) << 6) | ((reg) & 0x80))
-#define MAX6697_OVERT_MAP_BITS(reg) (((reg) >> 1) | (((reg) & 0x01) << 7))
-#define MAX6697_REG_STAT(n) (0x44 + (n))
+/* over-temperature - local channel is in bit 7 */
+#define MAX6697_OVERT_MAP_BITS(reg) \
+ (FIELD_PREP(MAX6697_EXTERNAL_MASK_CHIP, FIELD_GET(MAX6697_EXTERNAL_MASK_DT, reg)) | \
+ FIELD_PREP(MAX6697_LOCAL_MASK_CHIP, FIELD_GET(MAX6697_LOCAL_MASK_DT, reg)))
+
+#define MAX6697_REG_STAT_ALARM 0x44
+#define MAX6697_REG_STAT_CRIT 0x45
+#define MAX6697_REG_STAT_FAULT 0x46
+#define MAX6697_REG_STAT_MIN_ALARM 0x47
#define MAX6697_REG_CONFIG 0x41
-#define MAX6581_CONF_EXTENDED (1 << 1)
-#define MAX6693_CONF_BETA (1 << 2)
-#define MAX6697_CONF_RESISTANCE (1 << 3)
-#define MAX6697_CONF_TIMEOUT (1 << 5)
+#define MAX6581_CONF_EXTENDED BIT(1)
+#define MAX6693_CONF_BETA BIT(2)
+#define MAX6697_CONF_RESISTANCE BIT(3)
+#define MAX6697_CONF_TIMEOUT BIT(5)
#define MAX6697_REG_ALERT_MASK 0x42
#define MAX6697_REG_OVERT_MASK 0x43
@@ -67,24 +80,16 @@ struct max6697_chip_data {
u32 have_crit;
u32 have_fault;
u8 valid_conf;
- const u8 *alarm_map;
};
struct max6697_data {
- struct i2c_client *client;
+ struct regmap *regmap;
enum chips type;
const struct max6697_chip_data *chip;
- int update_interval; /* in milli-seconds */
int temp_offset; /* in degrees C */
- struct mutex update_lock;
- unsigned long last_updated; /* In jiffies */
- bool valid; /* true if following fields are valid */
-
- /* 1x local and up to 7x remote */
- u8 temp[8][4]; /* [nr][0]=temp [1]=ext [2]=max [3]=crit */
#define MAX6697_TEMP_INPUT 0
#define MAX6697_TEMP_EXT 1
#define MAX6697_TEMP_MAX 2
@@ -92,11 +97,6 @@ struct max6697_data {
u32 alarms;
};
-/* Diode fault status bits on MAX6581 are right shifted by one bit */
-static const u8 max6581_alarm_map[] = {
- 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15,
- 16, 17, 18, 19, 20, 21, 22, 23 };
-
static const struct max6697_chip_data max6697_chip_data[] = {
[max6581] = {
.channels = 8,
@@ -104,7 +104,6 @@ static const struct max6697_chip_data max6697_chip_data[] = {
.have_ext = 0x7f,
.have_fault = 0xfe,
.valid_conf = MAX6581_CONF_EXTENDED | MAX6697_CONF_TIMEOUT,
- .alarm_map = max6581_alarm_map,
},
[max6602] = {
.channels = 5,
@@ -173,549 +172,394 @@ static const struct max6697_chip_data max6697_chip_data[] = {
},
};
-static inline int max6581_offset_to_millic(int val)
+static int max6697_alarm_channel_map(int channel)
{
- return sign_extend32(val, 7) * 250;
+ switch (channel) {
+ case 0:
+ return 6;
+ case 7:
+ return 7;
+ default:
+ return channel - 1;
+ }
}
-static struct max6697_data *max6697_update_device(struct device *dev)
+static int max6697_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
{
+ unsigned int offset_regs[2] = { MAX6581_REG_OFFSET_SELECT, MAX6581_REG_OFFSET };
+ unsigned int temp_regs[2] = { MAX6697_REG_TEMP[channel],
+ MAX6697_REG_TEMP_EXT[channel] };
struct max6697_data *data = dev_get_drvdata(dev);
- struct i2c_client *client = data->client;
- struct max6697_data *ret = data;
- int val;
- int i;
- u32 alarms;
-
- mutex_lock(&data->update_lock);
-
- if (data->valid &&
- !time_after(jiffies, data->last_updated
- + msecs_to_jiffies(data->update_interval)))
- goto abort;
-
- for (i = 0; i < data->chip->channels; i++) {
- if (data->chip->have_ext & (1 << i)) {
- val = i2c_smbus_read_byte_data(client,
- MAX6697_REG_TEMP_EXT[i]);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp[i][MAX6697_TEMP_EXT] = val;
- }
+ struct regmap *regmap = data->regmap;
+ u8 regdata[2] = { };
+ u32 regval;
+ int ret;
- val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP[i]);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp[i][MAX6697_TEMP_INPUT] = val;
+ switch (attr) {
+ case hwmon_temp_input:
+ ret = regmap_multi_reg_read(regmap, temp_regs, regdata,
+ data->chip->have_ext & BIT(channel) ? 2 : 1);
+ if (ret)
+ return ret;
+ *val = (((regdata[0] - data->temp_offset) << 3) | (regdata[1] >> 5)) * 125;
+ break;
+ case hwmon_temp_max:
+ ret = regmap_read(regmap, MAX6697_REG_MAX[channel], &regval);
+ if (ret)
+ return ret;
+ *val = ((int)regval - data->temp_offset) * 1000;
+ break;
+ case hwmon_temp_crit:
+ ret = regmap_read(regmap, MAX6697_REG_CRIT[channel], &regval);
+ if (ret)
+ return ret;
+ *val = ((int)regval - data->temp_offset) * 1000;
+ break;
+ case hwmon_temp_min:
+ ret = regmap_read(regmap, MAX6697_REG_MIN, &regval);
+ if (ret)
+ return ret;
+ *val = ((int)regval - data->temp_offset) * 1000;
+ break;
+ case hwmon_temp_offset:
+ ret = regmap_multi_reg_read(regmap, offset_regs, regdata, 2);
+ if (ret)
+ return ret;
- val = i2c_smbus_read_byte_data(client, MAX6697_REG_MAX[i]);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp[i][MAX6697_TEMP_MAX] = val;
-
- if (data->chip->have_crit & (1 << i)) {
- val = i2c_smbus_read_byte_data(client,
- MAX6697_REG_CRIT[i]);
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- data->temp[i][MAX6697_TEMP_CRIT] = val;
- }
- }
+ if (!(regdata[0] & BIT(channel - 1)))
+ regdata[1] = 0;
- alarms = 0;
- for (i = 0; i < 3; i++) {
- val = i2c_smbus_read_byte_data(client, MAX6697_REG_STAT(i));
- if (unlikely(val < 0)) {
- ret = ERR_PTR(val);
- goto abort;
- }
- alarms = (alarms << 8) | val;
+ *val = sign_extend32(regdata[1], 7) * 250;
+ break;
+ case hwmon_temp_fault:
+ ret = regmap_read(regmap, MAX6697_REG_STAT_FAULT, &regval);
+ if (ret)
+ return ret;
+ if (data->type == max6581)
+ *val = !!(regval & BIT(channel - 1));
+ else
+ *val = !!(regval & BIT(channel));
+ break;
+ case hwmon_temp_crit_alarm:
+ ret = regmap_read(regmap, MAX6697_REG_STAT_CRIT, &regval);
+ if (ret)
+ return ret;
+ /*
+ * In the MAX6581 datasheet revision 0 to 3, the local channel
+ * overtemperature status is reported in bit 6 of register 0x45,
+ * and the overtemperature status for remote channel 7 is
+ * reported in bit 7. In Revision 4 and later, the local channel
+ * overtemperature status is reported in bit 7, and the remote
+ * channel 7 overtemperature status is reported in bit 6. A real
+ * chip was found to match the functionality documented in
+ * Revision 4 and later.
+ */
+ *val = !!(regval & BIT(channel ? channel - 1 : 7));
+ break;
+ case hwmon_temp_max_alarm:
+ ret = regmap_read(regmap, MAX6697_REG_STAT_ALARM, &regval);
+ if (ret)
+ return ret;
+ *val = !!(regval & BIT(max6697_alarm_channel_map(channel)));
+ break;
+ case hwmon_temp_min_alarm:
+ ret = regmap_read(regmap, MAX6697_REG_STAT_MIN_ALARM, &regval);
+ if (ret)
+ return ret;
+ *val = !!(regval & BIT(max6697_alarm_channel_map(channel)));
+ break;
+ default:
+ return -EOPNOTSUPP;
}
- data->alarms = alarms;
- data->last_updated = jiffies;
- data->valid = true;
-abort:
- mutex_unlock(&data->update_lock);
-
- return ret;
-}
-
-static ssize_t temp_input_show(struct device *dev,
- struct device_attribute *devattr, char *buf)
-{
- int index = to_sensor_dev_attr(devattr)->index;
- struct max6697_data *data = max6697_update_device(dev);
- int temp;
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- temp = (data->temp[index][MAX6697_TEMP_INPUT] - data->temp_offset) << 3;
- temp |= data->temp[index][MAX6697_TEMP_EXT] >> 5;
-
- return sprintf(buf, "%d\n", temp * 125);
-}
-
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
- char *buf)
-{
- int nr = to_sensor_dev_attr_2(devattr)->nr;
- int index = to_sensor_dev_attr_2(devattr)->index;
- struct max6697_data *data = max6697_update_device(dev);
- int temp;
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- temp = data->temp[nr][index];
- temp -= data->temp_offset;
-
- return sprintf(buf, "%d\n", temp * 1000);
-}
-
-static ssize_t alarm_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- int index = to_sensor_dev_attr(attr)->index;
- struct max6697_data *data = max6697_update_device(dev);
-
- if (IS_ERR(data))
- return PTR_ERR(data);
-
- if (data->chip->alarm_map)
- index = data->chip->alarm_map[index];
-
- return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1);
+ return 0;
}
-static ssize_t temp_store(struct device *dev,
- struct device_attribute *devattr, const char *buf,
- size_t count)
+static int max6697_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
{
- int nr = to_sensor_dev_attr_2(devattr)->nr;
- int index = to_sensor_dev_attr_2(devattr)->index;
struct max6697_data *data = dev_get_drvdata(dev);
- long temp;
+ struct regmap *regmap = data->regmap;
int ret;
- ret = kstrtol(buf, 10, &temp);
- if (ret < 0)
- return ret;
-
- mutex_lock(&data->update_lock);
- temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset;
- temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127);
- data->temp[nr][index] = temp;
- ret = i2c_smbus_write_byte_data(data->client,
- index == 2 ? MAX6697_REG_MAX[nr]
- : MAX6697_REG_CRIT[nr],
- temp);
- mutex_unlock(&data->update_lock);
-
- return ret < 0 ? ret : count;
-}
-
-static ssize_t offset_store(struct device *dev, struct device_attribute *devattr, const char *buf,
- size_t count)
-{
- int val, ret, index, select;
- struct max6697_data *data;
- bool channel_enabled;
- long temp;
-
- index = to_sensor_dev_attr(devattr)->index;
- data = dev_get_drvdata(dev);
- ret = kstrtol(buf, 10, &temp);
- if (ret < 0)
+ switch (attr) {
+ case hwmon_temp_max:
+ val = clamp_val(val, -1000000, 1000000); /* prevent underflow */
+ val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset;
+ val = clamp_val(val, 0, data->type == max6581 ? 255 : 127);
+ return regmap_write(regmap, MAX6697_REG_MAX[channel], val);
+ case hwmon_temp_crit:
+ val = clamp_val(val, -1000000, 1000000); /* prevent underflow */
+ val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset;
+ val = clamp_val(val, 0, data->type == max6581 ? 255 : 127);
+ return regmap_write(regmap, MAX6697_REG_CRIT[channel], val);
+ case hwmon_temp_min:
+ val = clamp_val(val, -1000000, 1000000); /* prevent underflow */
+ val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset;
+ val = clamp_val(val, 0, 255);
+ return regmap_write(regmap, MAX6697_REG_MIN, val);
+ case hwmon_temp_offset:
+ val = clamp_val(val, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX);
+ val = DIV_ROUND_CLOSEST(val, 250);
+ if (!val) { /* disable this (and only this) channel */
+ ret = regmap_clear_bits(regmap, MAX6581_REG_OFFSET_SELECT,
+ BIT(channel - 1));
+ } else {
+ /* enable channel and update offset */
+ ret = regmap_set_bits(regmap, MAX6581_REG_OFFSET_SELECT,
+ BIT(channel - 1));
+ if (ret)
+ return ret;
+ ret = regmap_write(regmap, MAX6581_REG_OFFSET, val);
+ }
return ret;
-
- mutex_lock(&data->update_lock);
- select = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET_SELECT);
- if (select < 0) {
- ret = select;
- goto abort;
+ default:
+ return -EOPNOTSUPP;
}
- channel_enabled = (select & (1 << (index - 1)));
- temp = clamp_val(temp, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX);
- val = DIV_ROUND_CLOSEST(temp, 250);
- /* disable the offset for channel if the new offset is 0 */
- if (val == 0) {
- if (channel_enabled)
- ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT,
- select & ~(1 << (index - 1)));
- ret = ret < 0 ? ret : count;
- goto abort;
- }
- if (!channel_enabled) {
- ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT,
- select | (1 << (index - 1)));
- if (ret < 0)
- goto abort;
- }
- ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET, val);
- ret = ret < 0 ? ret : count;
-
-abort:
- mutex_unlock(&data->update_lock);
- return ret;
-}
-
-static ssize_t offset_show(struct device *dev, struct device_attribute *devattr, char *buf)
-{
- struct max6697_data *data;
- int select, ret, index;
-
- index = to_sensor_dev_attr(devattr)->index;
- data = dev_get_drvdata(dev);
- mutex_lock(&data->update_lock);
- select = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET_SELECT);
- if (select < 0)
- ret = select;
- else if (select & (1 << (index - 1)))
- ret = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET);
- else
- ret = 0;
- mutex_unlock(&data->update_lock);
- return ret < 0 ? ret : sprintf(buf, "%d\n", max6581_offset_to_millic(ret));
}
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0);
-static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp, 0, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp1_crit, temp, 0, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp_input, 1);
-static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp, 1, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp2_crit, temp, 1, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp3_input, temp_input, 2);
-static SENSOR_DEVICE_ATTR_2_RW(temp3_max, temp, 2, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp3_crit, temp, 2, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp4_input, temp_input, 3);
-static SENSOR_DEVICE_ATTR_2_RW(temp4_max, temp, 3, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp4_crit, temp, 3, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp5_input, temp_input, 4);
-static SENSOR_DEVICE_ATTR_2_RW(temp5_max, temp, 4, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp5_crit, temp, 4, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp6_input, temp_input, 5);
-static SENSOR_DEVICE_ATTR_2_RW(temp6_max, temp, 5, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp6_crit, temp, 5, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp7_input, temp_input, 6);
-static SENSOR_DEVICE_ATTR_2_RW(temp7_max, temp, 6, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp7_crit, temp, 6, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp8_input, temp_input, 7);
-static SENSOR_DEVICE_ATTR_2_RW(temp8_max, temp, 7, MAX6697_TEMP_MAX);
-static SENSOR_DEVICE_ATTR_2_RW(temp8_crit, temp, 7, MAX6697_TEMP_CRIT);
-
-static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 22);
-static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 16);
-static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, 17);
-static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, 18);
-static SENSOR_DEVICE_ATTR_RO(temp5_max_alarm, alarm, 19);
-static SENSOR_DEVICE_ATTR_RO(temp6_max_alarm, alarm, 20);
-static SENSOR_DEVICE_ATTR_RO(temp7_max_alarm, alarm, 21);
-static SENSOR_DEVICE_ATTR_RO(temp8_max_alarm, alarm, 23);
-
-static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 14);
-static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 8);
-static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, 9);
-static SENSOR_DEVICE_ATTR_RO(temp4_crit_alarm, alarm, 10);
-static SENSOR_DEVICE_ATTR_RO(temp5_crit_alarm, alarm, 11);
-static SENSOR_DEVICE_ATTR_RO(temp6_crit_alarm, alarm, 12);
-static SENSOR_DEVICE_ATTR_RO(temp7_crit_alarm, alarm, 13);
-static SENSOR_DEVICE_ATTR_RO(temp8_crit_alarm, alarm, 15);
-
-static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 1);
-static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, 2);
-static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, 3);
-static SENSOR_DEVICE_ATTR_RO(temp5_fault, alarm, 4);
-static SENSOR_DEVICE_ATTR_RO(temp6_fault, alarm, 5);
-static SENSOR_DEVICE_ATTR_RO(temp7_fault, alarm, 6);
-static SENSOR_DEVICE_ATTR_RO(temp8_fault, alarm, 7);
-
-/* There is no offset for local temperature so starting from temp2 */
-static SENSOR_DEVICE_ATTR_RW(temp2_offset, offset, 1);
-static SENSOR_DEVICE_ATTR_RW(temp3_offset, offset, 2);
-static SENSOR_DEVICE_ATTR_RW(temp4_offset, offset, 3);
-static SENSOR_DEVICE_ATTR_RW(temp5_offset, offset, 4);
-static SENSOR_DEVICE_ATTR_RW(temp6_offset, offset, 5);
-static SENSOR_DEVICE_ATTR_RW(temp7_offset, offset, 6);
-static SENSOR_DEVICE_ATTR_RW(temp8_offset, offset, 7);
-
-static DEVICE_ATTR(dummy, 0, NULL, NULL);
-
-static umode_t max6697_is_visible(struct kobject *kobj, struct attribute *attr,
- int index)
+static umode_t max6697_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
- struct device *dev = kobj_to_dev(kobj);
- struct max6697_data *data = dev_get_drvdata(dev);
+ const struct max6697_data *data = _data;
const struct max6697_chip_data *chip = data->chip;
- int channel = index / 7; /* channel number */
- int nr = index % 7; /* attribute index within channel */
if (channel >= chip->channels)
return 0;
- if ((nr == 3 || nr == 4) && !(chip->have_crit & (1 << channel)))
- return 0;
- if (nr == 5 && !(chip->have_fault & (1 << channel)))
- return 0;
- /* offset reg is only supported on max6581 remote channels */
- if (nr == 6)
- if (data->type != max6581 || channel == 0)
- return 0;
-
- return attr->mode;
+ switch (attr) {
+ case hwmon_temp_max:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ case hwmon_temp_min:
+ if (data->type == max6581)
+ return channel ? 0444 : 0644;
+ break;
+ case hwmon_temp_min_alarm:
+ if (data->type == max6581)
+ return 0444;
+ break;
+ case hwmon_temp_crit:
+ if (chip->have_crit & BIT(channel))
+ return 0644;
+ break;
+ case hwmon_temp_crit_alarm:
+ if (chip->have_crit & BIT(channel))
+ return 0444;
+ break;
+ case hwmon_temp_fault:
+ if (chip->have_fault & BIT(channel))
+ return 0444;
+ break;
+ case hwmon_temp_offset:
+ if (data->type == max6581 && channel)
+ return 0644;
+ break;
+ default:
+ break;
+ }
+ return 0;
}
-/*
- * max6697_is_visible uses the index into the following array to determine
- * if attributes should be created or not. Any change in order or content
- * must be matched in max6697_is_visible.
- */
-static struct attribute *max6697_attributes[] = {
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_crit.dev_attr.attr,
- &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
- &dev_attr_dummy.attr,
- &dev_attr_dummy.attr,
-
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_crit.dev_attr.attr,
- &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_fault.dev_attr.attr,
- &sensor_dev_attr_temp2_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp3_input.dev_attr.attr,
- &sensor_dev_attr_temp3_max.dev_attr.attr,
- &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_crit.dev_attr.attr,
- &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_fault.dev_attr.attr,
- &sensor_dev_attr_temp3_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp4_input.dev_attr.attr,
- &sensor_dev_attr_temp4_max.dev_attr.attr,
- &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_crit.dev_attr.attr,
- &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_fault.dev_attr.attr,
- &sensor_dev_attr_temp4_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp5_input.dev_attr.attr,
- &sensor_dev_attr_temp5_max.dev_attr.attr,
- &sensor_dev_attr_temp5_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp5_crit.dev_attr.attr,
- &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp5_fault.dev_attr.attr,
- &sensor_dev_attr_temp5_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp6_input.dev_attr.attr,
- &sensor_dev_attr_temp6_max.dev_attr.attr,
- &sensor_dev_attr_temp6_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp6_crit.dev_attr.attr,
- &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp6_fault.dev_attr.attr,
- &sensor_dev_attr_temp6_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp7_input.dev_attr.attr,
- &sensor_dev_attr_temp7_max.dev_attr.attr,
- &sensor_dev_attr_temp7_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp7_crit.dev_attr.attr,
- &sensor_dev_attr_temp7_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp7_fault.dev_attr.attr,
- &sensor_dev_attr_temp7_offset.dev_attr.attr,
-
- &sensor_dev_attr_temp8_input.dev_attr.attr,
- &sensor_dev_attr_temp8_max.dev_attr.attr,
- &sensor_dev_attr_temp8_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp8_crit.dev_attr.attr,
- &sensor_dev_attr_temp8_crit_alarm.dev_attr.attr,
- &sensor_dev_attr_temp8_fault.dev_attr.attr,
- &sensor_dev_attr_temp8_offset.dev_attr.attr,
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static const struct hwmon_channel_info * const max6697_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
+ HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_FAULT | HWMON_T_OFFSET),
NULL
};
-static const struct attribute_group max6697_group = {
- .attrs = max6697_attributes, .is_visible = max6697_is_visible,
+static const struct hwmon_ops max6697_hwmon_ops = {
+ .is_visible = max6697_is_visible,
+ .read = max6697_read,
+ .write = max6697_write,
};
-__ATTRIBUTE_GROUPS(max6697);
-static void max6697_get_config_of(struct device_node *node,
- struct max6697_platform_data *pdata)
-{
- int len;
- const __be32 *prop;
-
- pdata->smbus_timeout_disable =
- of_property_read_bool(node, "smbus-timeout-disable");
- pdata->extended_range_enable =
- of_property_read_bool(node, "extended-range-enable");
- pdata->beta_compensation =
- of_property_read_bool(node, "beta-compensation-enable");
-
- prop = of_get_property(node, "alert-mask", &len);
- if (prop && len == sizeof(u32))
- pdata->alert_mask = be32_to_cpu(prop[0]);
- prop = of_get_property(node, "over-temperature-mask", &len);
- if (prop && len == sizeof(u32))
- pdata->over_temperature_mask = be32_to_cpu(prop[0]);
- prop = of_get_property(node, "resistance-cancellation", &len);
- if (prop) {
- if (len == sizeof(u32))
- pdata->resistance_cancellation = be32_to_cpu(prop[0]);
- else
- pdata->resistance_cancellation = 0xfe;
- }
- prop = of_get_property(node, "transistor-ideality", &len);
- if (prop && len == 2 * sizeof(u32)) {
- pdata->ideality_mask = be32_to_cpu(prop[0]);
- pdata->ideality_value = be32_to_cpu(prop[1]);
- }
-}
+static const struct hwmon_chip_info max6697_chip_info = {
+ .ops = &max6697_hwmon_ops,
+ .info = max6697_info,
+};
-static int max6697_init_chip(struct max6697_data *data,
- struct i2c_client *client)
+static int max6697_config_of(struct device_node *node, struct max6697_data *data)
{
- struct max6697_platform_data *pdata = dev_get_platdata(&client->dev);
- struct max6697_platform_data p;
const struct max6697_chip_data *chip = data->chip;
- int factor = chip->channels;
- int ret, reg;
-
- /*
- * Don't touch configuration if neither platform data nor OF
- * configuration was specified. If that is the case, use the
- * current chip configuration.
- */
- if (!pdata && !client->dev.of_node) {
- reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG);
- if (reg < 0)
- return reg;
- if (data->type == max6581) {
- if (reg & MAX6581_CONF_EXTENDED)
- data->temp_offset = 64;
- reg = i2c_smbus_read_byte_data(client,
- MAX6581_REG_RESISTANCE);
- if (reg < 0)
- return reg;
- factor += hweight8(reg);
- } else {
- if (reg & MAX6697_CONF_RESISTANCE)
- factor++;
- }
- goto done;
- }
-
- if (client->dev.of_node) {
- memset(&p, 0, sizeof(p));
- max6697_get_config_of(client->dev.of_node, &p);
- pdata = &p;
- }
+ struct regmap *regmap = data->regmap;
+ int ret, confreg;
+ u32 vals[2];
- reg = 0;
- if (pdata->smbus_timeout_disable &&
+ confreg = 0;
+ if (of_property_read_bool(node, "smbus-timeout-disable") &&
(chip->valid_conf & MAX6697_CONF_TIMEOUT)) {
- reg |= MAX6697_CONF_TIMEOUT;
+ confreg |= MAX6697_CONF_TIMEOUT;
}
- if (pdata->extended_range_enable &&
+ if (of_property_read_bool(node, "extended-range-enable") &&
(chip->valid_conf & MAX6581_CONF_EXTENDED)) {
- reg |= MAX6581_CONF_EXTENDED;
+ confreg |= MAX6581_CONF_EXTENDED;
data->temp_offset = 64;
}
- if (pdata->resistance_cancellation &&
- (chip->valid_conf & MAX6697_CONF_RESISTANCE)) {
- reg |= MAX6697_CONF_RESISTANCE;
- factor++;
- }
- if (pdata->beta_compensation &&
+ if (of_property_read_bool(node, "beta-compensation-enable") &&
(chip->valid_conf & MAX6693_CONF_BETA)) {
- reg |= MAX6693_CONF_BETA;
+ confreg |= MAX6693_CONF_BETA;
}
- ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, reg);
- if (ret < 0)
+ if (of_property_read_u32(node, "alert-mask", vals))
+ vals[0] = 0;
+ ret = regmap_write(regmap, MAX6697_REG_ALERT_MASK,
+ MAX6697_ALERT_MAP_BITS(vals[0]));
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK,
- MAX6697_ALERT_MAP_BITS(pdata->alert_mask));
- if (ret < 0)
+ if (of_property_read_u32(node, "over-temperature-mask", vals))
+ vals[0] = 0;
+ ret = regmap_write(regmap, MAX6697_REG_OVERT_MASK,
+ MAX6697_OVERT_MAP_BITS(vals[0]));
+ if (ret)
return ret;
- ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK,
- MAX6697_OVERT_MAP_BITS(pdata->over_temperature_mask));
- if (ret < 0)
- return ret;
+ if (data->type != max6581) {
+ if (of_property_read_bool(node, "resistance-cancellation") &&
+ chip->valid_conf & MAX6697_CONF_RESISTANCE) {
+ confreg |= MAX6697_CONF_RESISTANCE;
+ }
+ } else {
+ if (of_property_read_u32(node, "resistance-cancellation", &vals[0])) {
+ if (of_property_read_bool(node, "resistance-cancellation"))
+ vals[0] = 0xfe;
+ else
+ vals[0] = 0;
+ }
- if (data->type == max6581) {
- factor += hweight8(pdata->resistance_cancellation >> 1);
- ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE,
- pdata->resistance_cancellation >> 1);
+ vals[0] &= 0xfe;
+ ret = regmap_write(regmap, MAX6581_REG_RESISTANCE, vals[0] >> 1);
if (ret < 0)
return ret;
- ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY,
- pdata->ideality_value);
+
+ if (of_property_read_u32_array(node, "transistor-ideality", vals, 2)) {
+ vals[0] = 0;
+ vals[1] = 0;
+ }
+
+ ret = regmap_write(regmap, MAX6581_REG_IDEALITY, vals[1]);
if (ret < 0)
return ret;
- ret = i2c_smbus_write_byte_data(client,
- MAX6581_REG_IDEALITY_SELECT,
- pdata->ideality_mask >> 1);
+ ret = regmap_write(regmap, MAX6581_REG_IDEALITY_SELECT,
+ (vals[0] & 0xfe) >> 1);
if (ret < 0)
return ret;
}
-done:
- data->update_interval = factor * MAX6697_CONV_TIME;
- return 0;
+ return regmap_write(regmap, MAX6697_REG_CONFIG, confreg);
}
-static const struct i2c_device_id max6697_id[];
+static int max6697_init_chip(struct device_node *np, struct max6697_data *data)
+{
+ unsigned int reg;
+ int ret;
+
+ /*
+ * Don't touch configuration if there is no devicetree configuration.
+ * If that is the case, use the current chip configuration.
+ */
+ if (!np) {
+ struct regmap *regmap = data->regmap;
+
+ ret = regmap_read(regmap, MAX6697_REG_CONFIG, &reg);
+ if (ret < 0)
+ return ret;
+ if (data->type == max6581) {
+ if (reg & MAX6581_CONF_EXTENDED)
+ data->temp_offset = 64;
+ ret = regmap_read(regmap, MAX6581_REG_RESISTANCE, &reg);
+ }
+ } else {
+ ret = max6697_config_of(np, data);
+ }
+
+ return ret;
+}
+
+static bool max6697_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x00 ... 0x09: /* temperature high bytes */
+ case 0x44 ... 0x47: /* status */
+ case 0x51 ... 0x58: /* temperature low bytes */
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool max6697_writeable_reg(struct device *dev, unsigned int reg)
+{
+ return reg != 0x0a && reg != 0x0f && !max6697_volatile_reg(dev, reg);
+}
+
+static const struct regmap_config max6697_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x58,
+ .writeable_reg = max6697_writeable_reg,
+ .volatile_reg = max6697_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
static int max6697_probe(struct i2c_client *client)
{
- struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct max6697_data *data;
struct device *hwmon_dev;
+ struct regmap *regmap;
int err;
- if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
- return -ENODEV;
+ regmap = devm_regmap_init_i2c(client, &max6697_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
data = devm_kzalloc(dev, sizeof(struct max6697_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- if (client->dev.of_node)
- data->type = (uintptr_t)of_device_get_match_data(&client->dev);
- else
- data->type = i2c_match_id(max6697_id, client)->driver_data;
+ data->regmap = regmap;
+ data->type = (uintptr_t)i2c_get_match_data(client);
data->chip = &max6697_chip_data[data->type];
- data->client = client;
- mutex_init(&data->update_lock);
- err = max6697_init_chip(data, client);
+ err = max6697_init_chip(client->dev.of_node, data);
if (err)
return err;
- hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- data,
- max6697_groups);
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ &max6697_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
@@ -780,7 +624,6 @@ static const struct of_device_id __maybe_unused max6697_of_match[] = {
MODULE_DEVICE_TABLE(of, max6697_of_match);
static struct i2c_driver max6697_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max6697",
.of_match_table = of_match_ptr(max6697_of_match),
diff --git a/drivers/hwmon/max77705-hwmon.c b/drivers/hwmon/max77705-hwmon.c
new file mode 100644
index 000000000000..990023e6474e
--- /dev/null
+++ b/drivers/hwmon/max77705-hwmon.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MAX77705 voltage and current hwmon driver.
+ *
+ * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct channel_desc {
+ u8 reg;
+ u8 avg_reg;
+ const char *const label;
+ // register resolution. nano Volts for voltage, nano Amperes for current
+ u32 resolution;
+};
+
+static const struct channel_desc current_channel_desc[] = {
+ {
+ .reg = IIN_REG,
+ .label = "IIN_REG",
+ .resolution = 125000
+ },
+ {
+ .reg = ISYS_REG,
+ .avg_reg = AVGISYS_REG,
+ .label = "ISYS_REG",
+ .resolution = 312500
+ }
+};
+
+static const struct channel_desc voltage_channel_desc[] = {
+ {
+ .reg = VBYP_REG,
+ .label = "VBYP_REG",
+ .resolution = 427246
+ },
+ {
+ .reg = VSYS_REG,
+ .label = "VSYS_REG",
+ .resolution = 156250
+ }
+};
+
+static int max77705_read_and_convert(struct regmap *regmap, u8 reg, u32 res,
+ bool is_signed, long *val)
+{
+ int ret;
+ u32 regval;
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret < 0)
+ return ret;
+
+ if (is_signed)
+ *val = mult_frac((long)sign_extend32(regval, 15), res, 1000000);
+ else
+ *val = mult_frac((long)regval, res, 1000000);
+
+ return 0;
+}
+
+static umode_t max77705_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_label:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_in_label:
+ return 0444;
+ case hwmon_curr_average:
+ if (current_channel_desc[channel].avg_reg)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int max77705_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **buf)
+{
+ switch (type) {
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_in_label:
+ *buf = current_channel_desc[channel].label;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_label:
+ *buf = voltage_channel_desc[channel].label;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int max77705_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct regmap *regmap = dev_get_drvdata(dev);
+ u8 reg;
+ u32 res;
+
+ switch (type) {
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ reg = current_channel_desc[channel].reg;
+ res = current_channel_desc[channel].resolution;
+
+ return max77705_read_and_convert(regmap, reg, res, true, val);
+ case hwmon_curr_average:
+ reg = current_channel_desc[channel].avg_reg;
+ res = current_channel_desc[channel].resolution;
+
+ return max77705_read_and_convert(regmap, reg, res, true, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ reg = voltage_channel_desc[channel].reg;
+ res = voltage_channel_desc[channel].resolution;
+
+ return max77705_read_and_convert(regmap, reg, res, false, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops max77705_hwmon_ops = {
+ .is_visible = max77705_is_visible,
+ .read = max77705_read,
+ .read_string = max77705_read_string,
+};
+
+static const struct hwmon_channel_info *max77705_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL
+ ),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_AVERAGE | HWMON_C_LABEL
+ ),
+ NULL
+};
+
+static const struct hwmon_chip_info max77705_chip_info = {
+ .ops = &max77705_hwmon_ops,
+ .info = max77705_info,
+};
+
+static int max77705_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *hwmon_dev;
+ struct regmap *regmap;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "max77705", regmap,
+ &max77705_chip_info, NULL);
+ if (IS_ERR(hwmon_dev))
+ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev),
+ "Unable to register hwmon device\n");
+
+ return 0;
+};
+
+static struct platform_driver max77705_hwmon_driver = {
+ .driver = {
+ .name = "max77705-hwmon",
+ },
+ .probe = max77705_hwmon_probe,
+};
+
+module_platform_driver(max77705_hwmon_driver);
+
+MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
+MODULE_DESCRIPTION("MAX77705 monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/mc13783-adc.c b/drivers/hwmon/mc13783-adc.c
index ff147e5e1b8c..66304d48d33a 100644
--- a/drivers/hwmon/mc13783-adc.c
+++ b/drivers/hwmon/mc13783-adc.c
@@ -285,7 +285,7 @@ out_err_create_16chans:
return ret;
}
-static int mc13783_adc_remove(struct platform_device *pdev)
+static void mc13783_adc_remove(struct platform_device *pdev)
{
struct mc13783_adc_priv *priv = platform_get_drvdata(pdev);
kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data;
@@ -299,8 +299,6 @@ static int mc13783_adc_remove(struct platform_device *pdev)
sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_16chans);
sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_base);
-
- return 0;
}
static const struct platform_device_id mc13783_adc_idtable[] = {
diff --git a/drivers/hwmon/mc33xs2410_hwmon.c b/drivers/hwmon/mc33xs2410_hwmon.c
new file mode 100644
index 000000000000..23eb90e33709
--- /dev/null
+++ b/drivers/hwmon/mc33xs2410_hwmon.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Liebherr-Electronics and Drives GmbH
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/hwmon.h>
+#include <linux/mc33xs2410.h>
+#include <linux/module.h>
+
+/* ctrl registers */
+
+#define MC33XS2410_TEMP_WT 0x29
+#define MC33XS2410_TEMP_WT_MASK GENMASK(7, 0)
+
+/* diag registers */
+
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_OUT_STA(chan) (0x02 + (chan) - 1)
+#define MC33XS2410_OUT_STA_OTW BIT(8)
+
+#define MC33XS2410_TS_TEMP_DIE 0x26
+#define MC33XS2410_TS_TEMP_MASK GENMASK(9, 0)
+
+/* chan in { 1 ... 4 } */
+#define MC33XS2410_TS_TEMP(chan) (0x2f + (chan) - 1)
+
+static const struct hwmon_channel_info * const mc33xs2410_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
+ HWMON_T_ALARM,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
+ HWMON_T_ALARM,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
+ HWMON_T_ALARM,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
+ HWMON_T_ALARM),
+ NULL,
+};
+
+static umode_t mc33xs2410_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_alarm:
+ case hwmon_temp_label:
+ return 0444;
+ case hwmon_temp_max:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int mc33xs2410_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct spi_device *spi = dev_get_drvdata(dev);
+ u16 reg_val;
+ int ret;
+ u8 reg;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = (channel == 0) ? MC33XS2410_TS_TEMP_DIE :
+ MC33XS2410_TS_TEMP(channel);
+ ret = mc33xs2410_read_reg_diag(spi, reg, &reg_val);
+ if (ret < 0)
+ return ret;
+
+ /* LSB is 0.25 degree celsius */
+ *val = FIELD_GET(MC33XS2410_TS_TEMP_MASK, reg_val) * 250 - 40000;
+ return 0;
+ case hwmon_temp_alarm:
+ ret = mc33xs2410_read_reg_diag(spi, MC33XS2410_OUT_STA(channel),
+ &reg_val);
+ if (ret < 0)
+ return ret;
+
+ *val = FIELD_GET(MC33XS2410_OUT_STA_OTW, reg_val);
+ return 0;
+ case hwmon_temp_max:
+ ret = mc33xs2410_read_reg_ctrl(spi, MC33XS2410_TEMP_WT, &reg_val);
+ if (ret < 0)
+ return ret;
+
+ /* LSB is 1 degree celsius */
+ *val = FIELD_GET(MC33XS2410_TEMP_WT_MASK, reg_val) * 1000 - 40000;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int mc33xs2410_hwmon_write(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct spi_device *spi = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_temp_max:
+ val = clamp_val(val, -40000, 215000);
+
+ /* LSB is 1 degree celsius */
+ val = (val / 1000) + 40;
+ return mc33xs2410_modify_reg(spi, MC33XS2410_TEMP_WT,
+ MC33XS2410_TEMP_WT_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const char *const mc33xs2410_temp_label[] = {
+ "Central die temperature",
+ "Channel 1 temperature",
+ "Channel 2 temperature",
+ "Channel 3 temperature",
+ "Channel 4 temperature",
+};
+
+static int mc33xs2410_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ *str = mc33xs2410_temp_label[channel];
+
+ return 0;
+}
+
+static const struct hwmon_ops mc33xs2410_hwmon_hwmon_ops = {
+ .is_visible = mc33xs2410_hwmon_is_visible,
+ .read = mc33xs2410_hwmon_read,
+ .read_string = mc33xs2410_read_string,
+ .write = mc33xs2410_hwmon_write,
+};
+
+static const struct hwmon_chip_info mc33xs2410_hwmon_chip_info = {
+ .ops = &mc33xs2410_hwmon_hwmon_ops,
+ .info = mc33xs2410_hwmon_info,
+};
+
+static int mc33xs2410_hwmon_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct spi_device *spi = container_of(dev->parent, struct spi_device, dev);
+ struct device *hwmon;
+
+ hwmon = devm_hwmon_device_register_with_info(dev, NULL, spi,
+ &mc33xs2410_hwmon_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+
+static const struct auxiliary_device_id mc33xs2410_hwmon_ids[] = {
+ {
+ .name = "pwm_mc33xs2410.hwmon",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, mc33xs2410_hwmon_ids);
+
+static struct auxiliary_driver mc33xs2410_hwmon_driver = {
+ .probe = mc33xs2410_hwmon_probe,
+ .id_table = mc33xs2410_hwmon_ids,
+};
+module_auxiliary_driver(mc33xs2410_hwmon_driver);
+
+MODULE_DESCRIPTION("NXP MC33XS2410 hwmon driver");
+MODULE_AUTHOR("Dimitri Fedrau <dimitri.fedrau@liebherr.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/mc34vr500.c b/drivers/hwmon/mc34vr500.c
index 0c8fd3fce83e..84458e4533d8 100644
--- a/drivers/hwmon/mc34vr500.c
+++ b/drivers/hwmon/mc34vr500.c
@@ -235,7 +235,7 @@ static int mc34vr500_probe(struct i2c_client *client)
}
static const struct i2c_device_id mc34vr500_id[] = {
- { "mc34vr500", 0 },
+ { "mc34vr500" },
{ },
};
MODULE_DEVICE_TABLE(i2c, mc34vr500_id);
diff --git a/drivers/hwmon/mcp3021.c b/drivers/hwmon/mcp3021.c
index 9814eaf24564..bcddf6804d3a 100644
--- a/drivers/hwmon/mcp3021.c
+++ b/drivers/hwmon/mcp3021.c
@@ -116,13 +116,12 @@ static const struct hwmon_chip_info mcp3021_chip_info = {
.info = mcp3021_info,
};
-static const struct i2c_device_id mcp3021_id[];
-
static int mcp3021_probe(struct i2c_client *client)
{
struct mcp3021_data *data = NULL;
struct device_node *np = client->dev.of_node;
struct device *hwmon_dev;
+ enum chips type;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
@@ -149,7 +148,8 @@ static int mcp3021_probe(struct i2c_client *client)
data->vdd = MCP3021_VDD_REF_DEFAULT;
}
- switch (i2c_match_id(mcp3021_id, client)->driver_data) {
+ type = (uintptr_t)i2c_get_match_data(client);
+ switch (type) {
case mcp3021:
data->sar_shift = MCP3021_SAR_SHIFT;
data->sar_mask = MCP3021_SAR_MASK;
diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c
index a5f89aab3fb4..137a90dd2075 100644
--- a/drivers/hwmon/mlxreg-fan.c
+++ b/drivers/hwmon/mlxreg-fan.c
@@ -63,12 +63,14 @@ struct mlxreg_fan;
* @reg: register offset;
* @mask: fault mask;
* @prsnt: present register offset;
+ * @shift: tacho presence bit shift;
*/
struct mlxreg_fan_tacho {
bool connected;
u32 reg;
u32 mask;
u32 prsnt;
+ u32 shift;
};
/*
@@ -113,8 +115,8 @@ struct mlxreg_fan {
int divider;
};
-static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
- unsigned long state);
+static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state, bool thermal);
static int
mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
@@ -143,8 +145,10 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
/*
* Map channel to presence bit - drawer can be equipped with
* one or few FANs, while presence is indicated per drawer.
+ * Shift channel value if necessary to align with register value.
*/
- if (BIT(channel / fan->tachos_per_drwr) & regval) {
+ if (BIT(rol32(channel, tacho->shift) / fan->tachos_per_drwr) &
+ regval) {
/* FAN is not connected - return zero for FAN speed. */
*val = 0;
return 0;
@@ -224,8 +228,9 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
* last thermal state.
*/
if (pwm->last_hwmon_state >= pwm->last_thermal_state)
- return mlxreg_fan_set_cur_state(pwm->cdev,
- pwm->last_hwmon_state);
+ return _mlxreg_fan_set_cur_state(pwm->cdev,
+ pwm->last_hwmon_state,
+ false);
return 0;
}
return regmap_write(fan->regmap, pwm->reg, val);
@@ -357,9 +362,8 @@ static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev,
return 0;
}
-static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
- unsigned long state)
-
+static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state, bool thermal)
{
struct mlxreg_fan_pwm *pwm = cdev->devdata;
struct mlxreg_fan *fan = pwm->fan;
@@ -369,7 +373,8 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
return -EINVAL;
/* Save thermal state. */
- pwm->last_thermal_state = state;
+ if (thermal)
+ pwm->last_thermal_state = state;
state = max_t(unsigned long, state, pwm->last_hwmon_state);
err = regmap_write(fan->regmap, pwm->reg,
@@ -381,6 +386,13 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
return 0;
}
+static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+
+{
+ return _mlxreg_fan_set_cur_state(cdev, state, true);
+}
+
static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
.get_max_state = mlxreg_fan_get_max_state,
.get_cur_state = mlxreg_fan_get_cur_state,
@@ -400,7 +412,7 @@ static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan,
return err;
}
- return !!(regval & data->bit);
+ return data->slot ? (data->slot <= regval ? 1 : 0) : !!(regval & data->bit);
}
static int mlxreg_pwm_connect_verify(struct mlxreg_fan *fan,
@@ -537,7 +549,15 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan,
return err;
}
- drwr_avail = hweight32(regval);
+ /*
+ * The number of drawers could be specified in registers by counters for newer
+ * systems, or by bitmasks for older systems. In case the data is provided by
+ * counter, it is indicated through 'version' field.
+ */
+ if (pdata->version)
+ drwr_avail = regval;
+ else
+ drwr_avail = hweight32(regval);
if (!tacho_avail || !drwr_avail || tacho_avail < drwr_avail) {
dev_err(fan->dev, "Configuration is invalid: drawers num %d tachos num %d\n",
drwr_avail, tacho_avail);
@@ -561,15 +581,14 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan)
if (!pwm->connected)
continue;
pwm->fan = fan;
+ /* Set minimal PWM speed. */
+ pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY);
pwm->cdev = devm_thermal_of_cooling_device_register(dev, NULL, mlxreg_fan_name[i],
pwm, &mlxreg_fan_cooling_ops);
if (IS_ERR(pwm->cdev)) {
dev_err(dev, "Failed to register cooling device\n");
return PTR_ERR(pwm->cdev);
}
-
- /* Set minimal PWM speed. */
- pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY);
}
return 0;
diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c
index 50a8b9c3f94d..32c1e42e1278 100644
--- a/drivers/hwmon/mr75203.c
+++ b/drivers/hwmon/mr75203.c
@@ -14,7 +14,6 @@
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
-#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
@@ -925,4 +924,5 @@ static struct platform_driver moortec_pvt_driver = {
};
module_platform_driver(moortec_pvt_driver);
+MODULE_DESCRIPTION("Moortec Semiconductor MR75203 PVT Controller driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
index f673f7d07941..6cda35388b24 100644
--- a/drivers/hwmon/nct6683.c
+++ b/drivers/hwmon/nct6683.c
@@ -174,8 +174,13 @@ superio_exit(int ioreg)
#define NCT6683_CUSTOMER_ID_MITAC 0xa0e
#define NCT6683_CUSTOMER_ID_MSI 0x201
#define NCT6683_CUSTOMER_ID_MSI2 0x200
+#define NCT6683_CUSTOMER_ID_MSI3 0x207
+#define NCT6683_CUSTOMER_ID_MSI4 0x20d
+#define NCT6683_CUSTOMER_ID_AMD 0x162b
#define NCT6683_CUSTOMER_ID_ASROCK 0xe2c
#define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b
+#define NCT6683_CUSTOMER_ID_ASROCK3 0x1631
+#define NCT6683_CUSTOMER_ID_ASROCK4 0x163e
#define NCT6683_REG_BUILD_YEAR 0x604
#define NCT6683_REG_BUILD_MONTH 0x605
@@ -1223,13 +1228,25 @@ static int nct6683_probe(struct platform_device *pdev)
break;
case NCT6683_CUSTOMER_ID_MSI2:
break;
+ case NCT6683_CUSTOMER_ID_MSI3:
+ break;
+ case NCT6683_CUSTOMER_ID_MSI4:
+ break;
+ case NCT6683_CUSTOMER_ID_AMD:
+ break;
case NCT6683_CUSTOMER_ID_ASROCK:
break;
case NCT6683_CUSTOMER_ID_ASROCK2:
break;
+ case NCT6683_CUSTOMER_ID_ASROCK3:
+ break;
+ case NCT6683_CUSTOMER_ID_ASROCK4:
+ break;
default:
if (!force)
return -ENODEV;
+ dev_warn(dev, "Enabling support for unknown customer ID 0x%04x\n", data->customer_id);
+ break;
}
nct6683_init_device(data);
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
new file mode 100644
index 000000000000..6dcf22ca5018
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 report channel
+ * This defines the module type used for communication with the NCT6694
+ * report channel over the USB interface.
+ */
+#define NCT6694_RPT_MOD 0xFF
+
+/* Report channel */
+/*
+ * The report channel is used to report the status of the hardware monitor
+ * devices, such as voltage, temperature, fan speed, and PWM.
+ */
+#define NCT6694_VIN_IDX(x) (0x00 + (x))
+#define NCT6694_TIN_IDX(x) \
+ ({ typeof(x) (_x) = (x); \
+ ((_x) < 10) ? (0x10 + ((_x) * 2)) : \
+ (0x30 + (((_x) - 10) * 2)); })
+#define NCT6694_FIN_IDX(x) (0x50 + ((x) * 2))
+#define NCT6694_PWM_IDX(x) (0x70 + (x))
+#define NCT6694_VIN_STS(x) (0x68 + (x))
+#define NCT6694_TIN_STS(x) (0x6A + (x))
+#define NCT6694_FIN_STS(x) (0x6E + (x))
+
+/*
+ * USB command module type for NCT6694 HWMON controller.
+ * This defines the module type used for communication with the NCT6694
+ * HWMON controller over the USB interface.
+ */
+#define NCT6694_HWMON_MOD 0x00
+
+/* Command 00h - Hardware Monitor Control */
+#define NCT6694_HWMON_CONTROL 0x00
+#define NCT6694_HWMON_CONTROL_SEL 0x00
+
+/* Command 02h - Alarm Control */
+#define NCT6694_HWMON_ALARM 0x02
+#define NCT6694_HWMON_ALARM_SEL 0x00
+
+/*
+ * USB command module type for NCT6694 PWM controller.
+ * This defines the module type used for communication with the NCT6694
+ * PWM controller over the USB interface.
+ */
+#define NCT6694_PWM_MOD 0x01
+
+/* PWM Command - Manual Control */
+#define NCT6694_PWM_CONTROL 0x01
+#define NCT6694_PWM_CONTROL_SEL 0x00
+
+#define NCT6694_FREQ_FROM_REG(reg) ((reg) * 25000 / 255)
+#define NCT6694_FREQ_TO_REG(val) \
+ (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
+
+#define NCT6694_LSB_REG_MASK GENMASK(7, 5)
+#define NCT6694_TIN_HYST_MASK GENMASK(7, 5)
+
+enum nct6694_hwmon_temp_mode {
+ NCT6694_HWMON_TWOTIME_IRQ = 0,
+ NCT6694_HWMON_ONETIME_IRQ,
+ NCT6694_HWMON_REALTIME_IRQ,
+ NCT6694_HWMON_COMPARE_IRQ,
+};
+
+struct __packed nct6694_hwmon_control {
+ u8 vin_en[2];
+ u8 tin_en[2];
+ u8 fin_en[2];
+ u8 pwm_en[2];
+ u8 reserved1[40];
+ u8 pwm_freq[10];
+ u8 reserved2[6];
+};
+
+struct __packed nct6694_hwmon_alarm {
+ u8 smi_ctrl;
+ u8 reserved1[15];
+ struct {
+ u8 hl;
+ u8 ll;
+ } vin_limit[16];
+ struct {
+ u8 hyst;
+ s8 hl;
+ } tin_cfg[32];
+ __be16 fin_ll[10];
+ u8 reserved2[4];
+};
+
+struct __packed nct6694_pwm_control {
+ u8 mal_en[2];
+ u8 mal_val[10];
+ u8 reserved[12];
+};
+
+union __packed nct6694_hwmon_rpt {
+ u8 vin;
+ struct {
+ u8 msb;
+ u8 lsb;
+ } tin;
+ __be16 fin;
+ u8 pwm;
+ u8 status;
+};
+
+union __packed nct6694_hwmon_msg {
+ struct nct6694_hwmon_alarm hwmon_alarm;
+ struct nct6694_pwm_control pwm_ctrl;
+};
+
+struct nct6694_hwmon_data {
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ struct nct6694_hwmon_control hwmon_en;
+ union nct6694_hwmon_rpt *rpt;
+ union nct6694_hwmon_msg *msg;
+};
+
+static inline long in_from_reg(u8 reg)
+{
+ return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 16);
+}
+
+static inline long temp_from_reg(s8 reg)
+{
+ return reg * 1000;
+}
+
+static inline s8 temp_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 1000);
+}
+
+#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE | \
+ HWMON_I_MAX | HWMON_I_MIN | \
+ HWMON_I_ALARM)
+#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \
+ HWMON_T_MAX | HWMON_T_MAX_HYST | \
+ HWMON_T_MAX_ALARM)
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \
+ HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \
+ HWMON_PWM_FREQ)
+static const struct hwmon_channel_info *nct6694_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ NCT6694_HWMON_IN_CONFIG, /* VIN0 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN1 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN2 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN3 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN5 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN6 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN7 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN14 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN15 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN16 */
+ NCT6694_HWMON_IN_CONFIG, /* VBAT */
+ NCT6694_HWMON_IN_CONFIG, /* VSB */
+ NCT6694_HWMON_IN_CONFIG, /* AVSB */
+ NCT6694_HWMON_IN_CONFIG, /* VCC */
+ NCT6694_HWMON_IN_CONFIG, /* VHIF */
+ NCT6694_HWMON_IN_CONFIG), /* VTT */
+
+ HWMON_CHANNEL_INFO(temp,
+ NCT6694_HWMON_TEMP_CONFIG, /* THR1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR14 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR15 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR16 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */
+ NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */
+
+ HWMON_CHANNEL_INFO(fan,
+ NCT6694_HWMON_FAN_CONFIG, /* FIN0 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN1 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN2 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN3 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN4 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN5 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN6 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN7 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN8 */
+ NCT6694_HWMON_FAN_CONFIG), /* FIN9 */
+
+ HWMON_CHANNEL_INFO(pwm,
+ NCT6694_HWMON_PWM_CONFIG, /* PWM0 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM1 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM2 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM3 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM4 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM5 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM6 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM7 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM8 */
+ NCT6694_HWMON_PWM_CONFIG), /* PWM9 */
+ NULL
+};
+
+static int nct6694_in_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char vin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ vin_en = data->hwmon_en.vin_en[(channel / 8)];
+ *val = !!(vin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_in_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->vin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->vin);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->rpt->vin);
+
+ return 0;
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl);
+
+ return 0;
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll);
+
+ return 0;
+ case hwmon_in_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_en, temp_hyst;
+ signed char temp_max;
+ int ret, temp_raw;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ temp_en = data->hwmon_en.tin_en[channel / 8];
+ *val = !!(temp_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_temp_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->tin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->tin);
+ if (ret)
+ return ret;
+
+ temp_raw = data->rpt->tin.msb << 3;
+ temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb);
+
+ /* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */
+ *val = sign_extend32(temp_raw, 10) * 125;
+
+ return 0;
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl);
+
+ return 0;
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst);
+ *val = temp_from_reg(temp_max - temp_hyst);
+
+ return 0;
+ case hwmon_temp_max_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char fanin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ fanin_en = data->hwmon_en.fin_en[channel / 8];
+ *val = !!(fanin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_fan_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->fin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->fin);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->rpt->fin);
+
+ return 0;
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]);
+
+ return 0;
+ case hwmon_fan_min_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char pwm_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ pwm_en = data->hwmon_en.pwm_en[channel / 8];
+ *val = !!(pwm_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_pwm_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_PWM_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->pwm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->pwm);
+ if (ret)
+ return ret;
+
+ *val = data->rpt->pwm;
+
+ return 0;
+ case hwmon_pwm_freq:
+ *val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]);
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ if (val == 0)
+ data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_hyst;
+ signed char temp_max;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ if (val == 0)
+ data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, -127000, 127000);
+ data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+
+ val = clamp_val(val, -127000, 127000);
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = temp_max - temp_to_reg(val);
+ temp_hyst = clamp_val(temp_hyst, 0, 7);
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst =
+ (data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) |
+ FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ if (val == 0)
+ data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 1, 65535);
+ data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ if (val == 0)
+ data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_PWM_MOD,
+ .cmd = NCT6694_PWM_CONTROL,
+ .sel = NCT6694_PWM_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->pwm_ctrl))
+ };
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ if (ret)
+ return ret;
+
+ data->msg->pwm_ctrl.mal_val[channel] = val;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ case hwmon_pwm_freq:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ /* in mV */
+ return nct6694_in_read(dev, attr, channel, val);
+ case hwmon_temp:
+ /* in mC */
+ return nct6694_temp_read(dev, attr, channel, val);
+ case hwmon_fan:
+ /* in RPM */
+ return nct6694_fan_read(dev, attr, channel, val);
+ case hwmon_pwm:
+ /* in value 0~255 */
+ return nct6694_pwm_read(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_in:
+ return nct6694_in_write(dev, attr, channel, val);
+ case hwmon_temp:
+ return nct6694_temp_write(dev, attr, channel, val);
+ case hwmon_fan:
+ return nct6694_fan_write(dev, attr, channel, val);
+ case hwmon_pwm:
+ return nct6694_pwm_write(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct6694_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_enable:
+ case hwmon_in_max:
+ case hwmon_in_min:
+ return 0644;
+ case hwmon_in_alarm:
+ case hwmon_in_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_enable:
+ case hwmon_temp_max:
+ case hwmon_temp_max_hyst:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_enable:
+ case hwmon_fan_min:
+ return 0644;
+ case hwmon_fan_input:
+ case hwmon_fan_min_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_freq:
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops nct6694_hwmon_ops = {
+ .is_visible = nct6694_is_visible,
+ .read = nct6694_read,
+ .write = nct6694_write,
+};
+
+static const struct hwmon_chip_info nct6694_chip_info = {
+ .ops = &nct6694_hwmon_ops,
+ .info = nct6694_info,
+};
+
+static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
+{
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+ int ret;
+
+ /*
+ * Record each Hardware Monitor Channel enable status
+ * and PWM frequency register
+ */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ if (ret)
+ return ret;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+
+ /* Select hwmon device alarm mode */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+}
+
+static int nct6694_hwmon_probe(struct platform_device *pdev)
+{
+ struct nct6694_hwmon_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt),
+ GFP_KERNEL);
+ if (!data->rpt)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ ret = devm_mutex_init(&pdev->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = nct6694_hwmon_init(data);
+ if (ret)
+ return ret;
+
+ /* Register hwmon device to HWMON framework */
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "nct6694", data,
+ &nct6694_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+ .driver = {
+ .name = "nct6694-hwmon",
+ },
+ .probe = nct6694_hwmon_probe,
+};
+
+module_platform_driver(nct6694_hwmon_driver);
+
+MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-hwmon");
diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c
index 02a71244fc3b..79bc67ffb998 100644
--- a/drivers/hwmon/nct6775-core.c
+++ b/drivers/hwmon/nct6775-core.c
@@ -42,6 +42,9 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#undef DEFAULT_SYMBOL_NAMESPACE
+#define DEFAULT_SYMBOL_NAMESPACE "HWMON_NCT6775"
+
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
@@ -56,26 +59,23 @@
#include "lm75.h"
#include "nct6775.h"
-#undef DEFAULT_SYMBOL_NAMESPACE
-#define DEFAULT_SYMBOL_NAMESPACE HWMON_NCT6775
-
#define USE_ALTERNATE
/* used to set data->name = nct6775_device_names[data->sio_kind] */
static const char * const nct6775_device_names[] = {
- "nct6106",
- "nct6116",
- "nct6775",
- "nct6776",
- "nct6779",
- "nct6791",
- "nct6792",
- "nct6793",
- "nct6795",
- "nct6796",
- "nct6797",
- "nct6798",
- "nct6799",
+ [nct6106] = "nct6106",
+ [nct6116] = "nct6116",
+ [nct6775] = "nct6775",
+ [nct6776] = "nct6776",
+ [nct6779] = "nct6779",
+ [nct6791] = "nct6791",
+ [nct6792] = "nct6792",
+ [nct6793] = "nct6793",
+ [nct6795] = "nct6795",
+ [nct6796] = "nct6796",
+ [nct6797] = "nct6797",
+ [nct6798] = "nct6798",
+ [nct6799] = "nct6799",
};
/* Common and NCT6775 specific data */
@@ -273,8 +273,8 @@ static const s8 NCT6776_BEEP_BITS[NUM_BEEP_BITS] = {
static const u16 NCT6776_REG_TOLERANCE_H[] = {
0x10c, 0x20c, 0x30c, 0x80c, 0x90c, 0xa0c, 0xb0c };
-static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0, 0, 0, 0 };
-static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0, 0, 0, 0 };
+static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0, 0, 0, 0, 0 };
+static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0, 0, 0, 0, 0 };
static const u16 NCT6776_REG_FAN_MIN[] = {
0x63a, 0x63c, 0x63e, 0x640, 0x642, 0x64a, 0x64c };
@@ -767,9 +767,9 @@ static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 };
static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 };
static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 };
-static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 };
-static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 };
-static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c };
+static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3, 0, 0 };
+static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04, 0, 0 };
+static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c, 0xd8, 0xd9 };
static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 };
static const u16 NCT6106_REG_TEMP_SOURCE[] = {
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 };
@@ -1614,17 +1614,21 @@ struct nct6775_data *nct6775_update_device(struct device *dev)
data->fan_div[i]);
if (data->has_fan_min & BIT(i)) {
- err = nct6775_read_value(data, data->REG_FAN_MIN[i], &reg);
+ u16 tmp;
+
+ err = nct6775_read_value(data, data->REG_FAN_MIN[i], &tmp);
if (err)
goto out;
- data->fan_min[i] = reg;
+ data->fan_min[i] = tmp;
}
if (data->REG_FAN_PULSES[i]) {
- err = nct6775_read_value(data, data->REG_FAN_PULSES[i], &reg);
+ u16 tmp;
+
+ err = nct6775_read_value(data, data->REG_FAN_PULSES[i], &tmp);
if (err)
goto out;
- data->fan_pulses[i] = (reg >> data->FAN_PULSE_SHIFT[i]) & 0x03;
+ data->fan_pulses[i] = (tmp >> data->FAN_PULSE_SHIFT[i]) & 0x03;
}
err = nct6775_select_fan_div(dev, data, i, reg);
@@ -1910,6 +1914,10 @@ static umode_t nct6775_in_is_visible(struct kobject *kobj,
struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev);
int in = index / 5; /* voltage index */
+ int nr = index % 5; /* attribute index */
+
+ if (nr == 1 && data->ALARM_BITS[in] == -1)
+ return 0;
if (!(data->have_in & BIT(in)))
return 0;
@@ -2254,7 +2262,7 @@ store_temp_offset(struct device *dev, struct device_attribute *attr,
if (err < 0)
return err;
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
mutex_lock(&data->update_lock);
data->temp_offset[nr] = val;
@@ -2545,6 +2553,13 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf,
int err;
u16 reg;
+ /*
+ * The fan control mode should be set to manual if the user wants to adjust
+ * the fan speed. Otherwise, it will fail to set.
+ */
+ if (index == 0 && data->pwm_enable[nr] > manual)
+ return -EBUSY;
+
err = kstrtoul(buf, 10, &val);
if (err < 0)
return err;
@@ -2863,8 +2878,7 @@ store_target_temp(struct device *dev, struct device_attribute *attr,
if (err < 0)
return err;
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0,
- data->target_temp_mask);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, data->target_temp_mask * 1000), 1000);
mutex_lock(&data->update_lock);
data->target_temp[nr] = val;
@@ -2944,7 +2958,7 @@ store_temp_tolerance(struct device *dev, struct device_attribute *attr,
return err;
/* Limit tolerance as needed */
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, data->tolerance_mask * 1000), 1000);
mutex_lock(&data->update_lock);
data->temp_tolerance[index][nr] = val;
@@ -3070,7 +3084,7 @@ store_weight_temp(struct device *dev, struct device_attribute *attr,
if (err < 0)
return err;
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 255000), 1000);
mutex_lock(&data->update_lock);
data->weight_temp[index][nr] = val;
@@ -3497,6 +3511,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit;
const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL;
int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp;
+ int num_reg_temp_config;
struct device *hwmon_dev;
struct sensor_template_group tsi_temp_tg;
@@ -3579,6 +3594,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6106_REG_TEMP_OVER;
reg_temp_hyst = NCT6106_REG_TEMP_HYST;
reg_temp_config = NCT6106_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6106_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6106_REG_TEMP_CRIT;
reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L;
@@ -3587,7 +3603,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
break;
case nct6116:
data->in_num = 9;
- data->pwm_num = 3;
+ data->pwm_num = 5;
data->auto_pwm_num = 4;
data->temp_fixed_num = 3;
data->num_temp_alarms = 3;
@@ -3654,6 +3670,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6106_REG_TEMP_OVER;
reg_temp_hyst = NCT6106_REG_TEMP_HYST;
reg_temp_config = NCT6106_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6106_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6106_REG_TEMP_CRIT;
reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L;
@@ -3731,6 +3748,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6775_REG_TEMP_OVER;
reg_temp_hyst = NCT6775_REG_TEMP_HYST;
reg_temp_config = NCT6775_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6775_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6775_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6775_REG_TEMP_CRIT;
@@ -3806,6 +3824,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6775_REG_TEMP_OVER;
reg_temp_hyst = NCT6775_REG_TEMP_HYST;
reg_temp_config = NCT6776_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6776_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6776_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6776_REG_TEMP_CRIT;
@@ -3885,6 +3904,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6779_REG_TEMP_OVER;
reg_temp_hyst = NCT6779_REG_TEMP_HYST;
reg_temp_config = NCT6779_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6779_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6779_REG_TEMP_CRIT;
@@ -4019,6 +4039,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6779_REG_TEMP_OVER;
reg_temp_hyst = NCT6779_REG_TEMP_HYST;
reg_temp_config = NCT6779_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6779_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6779_REG_TEMP_CRIT;
@@ -4108,6 +4129,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_over = NCT6798_REG_TEMP_OVER;
reg_temp_hyst = NCT6798_REG_TEMP_HYST;
reg_temp_config = NCT6779_REG_TEMP_CONFIG;
+ num_reg_temp_config = ARRAY_SIZE(NCT6779_REG_TEMP_CONFIG);
reg_temp_alternate = NCT6798_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6798_REG_TEMP_CRIT;
@@ -4189,7 +4211,8 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
= reg_temp_crit[src - 1];
if (reg_temp_crit_l && reg_temp_crit_l[i])
data->reg_temp[4][src - 1] = reg_temp_crit_l[i];
- data->reg_temp_config[src - 1] = reg_temp_config[i];
+ if (i < num_reg_temp_config)
+ data->reg_temp_config[src - 1] = reg_temp_config[i];
data->temp_src[src - 1] = src;
continue;
}
@@ -4202,7 +4225,8 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
data->reg_temp[0][s] = reg_temp[i];
data->reg_temp[1][s] = reg_temp_over[i];
data->reg_temp[2][s] = reg_temp_hyst[i];
- data->reg_temp_config[s] = reg_temp_config[i];
+ if (i < num_reg_temp_config)
+ data->reg_temp_config[s] = reg_temp_config[i];
if (reg_temp_crit_h && reg_temp_crit_h[i])
data->reg_temp[3][s] = reg_temp_crit_h[i];
else if (reg_temp_crit[src - 1])
diff --git a/drivers/hwmon/nct6775-i2c.c b/drivers/hwmon/nct6775-i2c.c
index 87a4fc78c571..ba71d776a291 100644
--- a/drivers/hwmon/nct6775-i2c.c
+++ b/drivers/hwmon/nct6775-i2c.c
@@ -21,7 +21,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/regmap.h>
#include "nct6775.h"
@@ -155,23 +155,13 @@ static const struct regmap_config nct6775_i2c_regmap_config = {
static int nct6775_i2c_probe(struct i2c_client *client)
{
struct nct6775_data *data;
- const struct of_device_id *of_id;
- const struct i2c_device_id *i2c_id;
struct device *dev = &client->dev;
- of_id = of_match_device(nct6775_i2c_of_match, dev);
- i2c_id = i2c_match_id(nct6775_i2c_id, client);
-
- if (of_id && (unsigned long)of_id->data != i2c_id->driver_data)
- dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n",
- of_id->name, i2c_id->name);
-
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->kind = i2c_id->driver_data;
-
+ data->kind = (enum kinds)(uintptr_t)i2c_get_match_data(client);
data->read_only = true;
data->driver_data = client;
data->driver_init = nct6775_i2c_probe_init;
@@ -194,4 +184,4 @@ module_i2c_driver(nct6775_i2c_driver);
MODULE_AUTHOR("Zev Weiss <zev@bewilderbeest.net>");
MODULE_DESCRIPTION("I2C driver for NCT6775F and compatible chips");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(HWMON_NCT6775);
+MODULE_IMPORT_NS("HWMON_NCT6775");
diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c
index 81bf03dad6bb..c3a719aef1ac 100644
--- a/drivers/hwmon/nct6775-platform.c
+++ b/drivers/hwmon/nct6775-platform.c
@@ -23,19 +23,19 @@
enum sensor_access { access_direct, access_asuswmi };
static const char * const nct6775_sio_names[] __initconst = {
- "NCT6106D",
- "NCT6116D",
- "NCT6775F",
- "NCT6776D/F",
- "NCT6779D",
- "NCT6791D",
- "NCT6792D",
- "NCT6793D",
- "NCT6795D",
- "NCT6796D",
- "NCT6797D",
- "NCT6798D",
- "NCT6796D-S/NCT6799D-R",
+ [nct6106] = "NCT6106D",
+ [nct6116] = "NCT6116D",
+ [nct6775] = "NCT6775F",
+ [nct6776] = "NCT6776D/F",
+ [nct6779] = "NCT6779D",
+ [nct6791] = "NCT6791D",
+ [nct6792] = "NCT6792D",
+ [nct6793] = "NCT6793D",
+ [nct6795] = "NCT6795D",
+ [nct6796] = "NCT6796D",
+ [nct6797] = "NCT6797D",
+ [nct6798] = "NCT6798D",
+ [nct6799] = "NCT6796D-S/NCT6799D-R",
};
static unsigned short force_id;
@@ -167,7 +167,8 @@ static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val)
static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val)
{
- u32 ret, tmp = 0;
+ u32 tmp = 0;
+ int ret;
ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank,
reg, 0, &tmp);
@@ -1269,6 +1270,7 @@ static const char * const asus_msi_boards[] = {
"EX-B760M-V5 D4",
"EX-H510M-V3",
"EX-H610M-V3 D4",
+ "G15CF",
"PRIME A620M-A",
"PRIME B560-PLUS",
"PRIME B560-PLUS AC-HES",
@@ -1349,6 +1351,8 @@ static const char * const asus_msi_boards[] = {
"Pro H610M-CT D4",
"Pro H610T D4",
"Pro Q670M-C",
+ "Pro WS 600M-CL",
+ "Pro WS 665-ACE",
"Pro WS W680-ACE",
"Pro WS W680-ACE IPMI",
"Pro WS W790-ACE",
@@ -1399,6 +1403,7 @@ static const char * const asus_msi_boards[] = {
"ROG STRIX X670E-E GAMING WIFI",
"ROG STRIX X670E-F GAMING WIFI",
"ROG STRIX X670E-I GAMING WIFI",
+ "ROG STRIX X870E-H GAMING WIFI7",
"ROG STRIX Z590-A GAMING WIFI",
"ROG STRIX Z590-A GAMING WIFI II",
"ROG STRIX Z590-E GAMING WIFI",
@@ -1465,10 +1470,8 @@ static const char * const asus_msi_boards[] = {
static int nct6775_asuswmi_device_match(struct device *dev, void *data)
{
struct acpi_device *adev = to_acpi_device(dev);
- const char *uid = acpi_device_uid(adev);
- const char *hid = acpi_device_hid(adev);
- if (hid && !strcmp(hid, ASUSWMI_DEVICE_HID) && uid && !strcmp(uid, data)) {
+ if (acpi_dev_hid_uid_match(adev, ASUSWMI_DEVICE_HID, data)) {
asus_acpi_dev = adev;
return 1;
}
@@ -1621,7 +1624,7 @@ static void __exit sensors_nct6775_platform_exit(void)
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
MODULE_DESCRIPTION("Platform driver for NCT6775F and compatible chips");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(HWMON_NCT6775);
+MODULE_IMPORT_NS("HWMON_NCT6775");
module_init(sensors_nct6775_platform_init);
module_exit(sensors_nct6775_platform_exit);
diff --git a/drivers/hwmon/nct7363.c b/drivers/hwmon/nct7363.c
new file mode 100644
index 000000000000..71cef794835d
--- /dev/null
+++ b/drivers/hwmon/nct7363.c
@@ -0,0 +1,445 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Nuvoton Technology corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define NCT7363_REG_FUNC_CFG_BASE(x) (0x20 + (x))
+#define NCT7363_REG_LSRS(x) (0x34 + ((x) / 8))
+#define NCT7363_REG_PWMEN_BASE(x) (0x38 + (x))
+#define NCT7363_REG_FANINEN_BASE(x) (0x41 + (x))
+#define NCT7363_REG_FANINX_HVAL(x) (0x48 + ((x) * 2))
+#define NCT7363_REG_FANINX_LVAL(x) (0x49 + ((x) * 2))
+#define NCT7363_REG_FANINX_HL(x) (0x6C + ((x) * 2))
+#define NCT7363_REG_FANINX_LL(x) (0x6D + ((x) * 2))
+#define NCT7363_REG_FSCPXDUTY(x) (0x90 + ((x) * 2))
+#define NCT7363_REG_FSCPXDIV(x) (0x91 + ((x) * 2))
+
+#define PWM_SEL(x) (BIT(0) << ((x) * 2))
+#define FANIN_SEL(_x) ({typeof(_x) (x) = (_x); \
+ BIT(1) << (((x) < 8) ? \
+ (((x) + 8) * 2) : \
+ (((x) % 8) * 2)); })
+#define ALARM_SEL(x, y) ((x) & (BIT((y) % 8)))
+#define VALUE_TO_REG(x, y) (((x) >> ((y) * 8)) & 0xFF)
+
+#define NCT7363_FANINX_LVAL_MASK GENMASK(4, 0)
+#define NCT7363_FANIN_MASK GENMASK(12, 0)
+
+#define NCT7363_PWM_COUNT 16
+
+static inline unsigned int fan_from_reg(u16 val)
+{
+ if (val == NCT7363_FANIN_MASK || val == 0)
+ return 0;
+
+ return (1350000UL / val);
+}
+
+static const struct of_device_id nct7363_of_match[] = {
+ { .compatible = "nuvoton,nct7363", },
+ { .compatible = "nuvoton,nct7362", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, nct7363_of_match);
+
+struct nct7363_data {
+ struct regmap *regmap;
+
+ u16 fanin_mask;
+ u16 pwm_mask;
+};
+
+static int nct7363_read_fan(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct7363_data *data = dev_get_drvdata(dev);
+ unsigned int reg;
+ u8 regval[2];
+ int ret;
+ u16 cnt;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ /*
+ * High-byte register should be read first to latch
+ * synchronous low-byte value
+ */
+ ret = regmap_bulk_read(data->regmap,
+ NCT7363_REG_FANINX_HVAL(channel),
+ &regval, 2);
+ if (ret)
+ return ret;
+
+ cnt = (regval[0] << 5) | (regval[1] & NCT7363_FANINX_LVAL_MASK);
+ *val = fan_from_reg(cnt);
+ return 0;
+ case hwmon_fan_min:
+ ret = regmap_bulk_read(data->regmap,
+ NCT7363_REG_FANINX_HL(channel),
+ &regval, 2);
+ if (ret)
+ return ret;
+
+ cnt = (regval[0] << 5) | (regval[1] & NCT7363_FANINX_LVAL_MASK);
+ *val = fan_from_reg(cnt);
+ return 0;
+ case hwmon_fan_alarm:
+ ret = regmap_read(data->regmap,
+ NCT7363_REG_LSRS(channel), &reg);
+ if (ret)
+ return ret;
+
+ *val = (long)ALARM_SEL(reg, channel) > 0 ? 1 : 0;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct7363_write_fan(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct7363_data *data = dev_get_drvdata(dev);
+ u8 regval[2];
+ int ret;
+
+ if (val <= 0)
+ return -EINVAL;
+
+ switch (attr) {
+ case hwmon_fan_min:
+ val = clamp_val(DIV_ROUND_CLOSEST(1350000, val),
+ 1, NCT7363_FANIN_MASK);
+ regval[0] = val >> 5;
+ regval[1] = val & NCT7363_FANINX_LVAL_MASK;
+
+ ret = regmap_bulk_write(data->regmap,
+ NCT7363_REG_FANINX_HL(channel),
+ regval, 2);
+ return ret;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct7363_fan_is_visible(const void *_data, u32 attr, int channel)
+{
+ const struct nct7363_data *data = _data;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_alarm:
+ if (data->fanin_mask & BIT(channel))
+ return 0444;
+ break;
+ case hwmon_fan_min:
+ if (data->fanin_mask & BIT(channel))
+ return 0644;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int nct7363_read_pwm(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct7363_data *data = dev_get_drvdata(dev);
+ unsigned int regval;
+ int ret;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ ret = regmap_read(data->regmap,
+ NCT7363_REG_FSCPXDUTY(channel), &regval);
+ if (ret)
+ return ret;
+
+ *val = (long)regval;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct7363_write_pwm(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct7363_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ ret = regmap_write(data->regmap,
+ NCT7363_REG_FSCPXDUTY(channel), val);
+
+ return ret;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct7363_pwm_is_visible(const void *_data, u32 attr, int channel)
+{
+ const struct nct7363_data *data = _data;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (data->pwm_mask & BIT(channel))
+ return 0644;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int nct7363_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_fan:
+ return nct7363_read_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return nct7363_read_pwm(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct7363_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_fan:
+ return nct7363_write_fan(dev, attr, channel, val);
+ case hwmon_pwm:
+ return nct7363_write_pwm(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct7363_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ return nct7363_fan_is_visible(data, attr, channel);
+ case hwmon_pwm:
+ return nct7363_pwm_is_visible(data, attr, channel);
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info *nct7363_info[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM,
+ HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ALARM),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops nct7363_hwmon_ops = {
+ .is_visible = nct7363_is_visible,
+ .read = nct7363_read,
+ .write = nct7363_write,
+};
+
+static const struct hwmon_chip_info nct7363_chip_info = {
+ .ops = &nct7363_hwmon_ops,
+ .info = nct7363_info,
+};
+
+static int nct7363_init_chip(struct nct7363_data *data)
+{
+ u32 func_config = 0;
+ int i, ret;
+
+ /* Pin Function Configuration */
+ for (i = 0; i < NCT7363_PWM_COUNT; i++) {
+ if (data->pwm_mask & BIT(i))
+ func_config |= PWM_SEL(i);
+ if (data->fanin_mask & BIT(i))
+ func_config |= FANIN_SEL(i);
+ }
+
+ for (i = 0; i < 4; i++) {
+ ret = regmap_write(data->regmap, NCT7363_REG_FUNC_CFG_BASE(i),
+ VALUE_TO_REG(func_config, i));
+ if (ret < 0)
+ return ret;
+ }
+
+ /* PWM and FANIN Monitoring Enable */
+ for (i = 0; i < 2; i++) {
+ ret = regmap_write(data->regmap, NCT7363_REG_PWMEN_BASE(i),
+ VALUE_TO_REG(data->pwm_mask, i));
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, NCT7363_REG_FANINEN_BASE(i),
+ VALUE_TO_REG(data->fanin_mask, i));
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int nct7363_present_pwm_fanin(struct device *dev,
+ struct device_node *child,
+ struct nct7363_data *data)
+{
+ u8 fanin_ch[NCT7363_PWM_COUNT];
+ struct of_phandle_args args;
+ int ret, fanin_cnt;
+ u8 ch, index;
+
+ ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ if (args.args[0] >= NCT7363_PWM_COUNT)
+ return -EINVAL;
+ data->pwm_mask |= BIT(args.args[0]);
+
+ fanin_cnt = of_property_count_u8_elems(child, "tach-ch");
+ if (fanin_cnt < 1 || fanin_cnt > NCT7363_PWM_COUNT)
+ return -EINVAL;
+
+ ret = of_property_read_u8_array(child, "tach-ch", fanin_ch, fanin_cnt);
+ if (ret)
+ return ret;
+
+ for (ch = 0; ch < fanin_cnt; ch++) {
+ index = fanin_ch[ch];
+ if (index >= NCT7363_PWM_COUNT)
+ return -EINVAL;
+ data->fanin_mask |= BIT(index);
+ }
+
+ return 0;
+}
+
+static bool nct7363_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case NCT7363_REG_LSRS(0) ... NCT7363_REG_LSRS(15):
+ case NCT7363_REG_FANINX_HVAL(0) ... NCT7363_REG_FANINX_LVAL(15):
+ case NCT7363_REG_FANINX_HL(0) ... NCT7363_REG_FANINX_LL(15):
+ case NCT7363_REG_FSCPXDUTY(0) ... NCT7363_REG_FSCPXDIV(15):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config nct7363_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .use_single_read = true,
+ .use_single_write = true,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = nct7363_regmap_is_volatile,
+};
+
+static int nct7363_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device_node *child;
+ struct nct7363_data *data;
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = devm_regmap_init_i2c(client, &nct7363_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ for_each_child_of_node(dev->of_node, child) {
+ ret = nct7363_present_pwm_fanin(dev, child, data);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+ }
+
+ /* Initialize the chip */
+ ret = nct7363_init_chip(data);
+ if (ret)
+ return ret;
+
+ hwmon_dev =
+ devm_hwmon_device_register_with_info(dev, client->name, data,
+ &nct7363_chip_info, NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct i2c_driver nct7363_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "nct7363",
+ .of_match_table = nct7363_of_match,
+ },
+ .probe = nct7363_probe,
+};
+
+module_i2c_driver(nct7363_driver);
+
+MODULE_AUTHOR("CW Ho <cwho@nuvoton.com>");
+MODULE_AUTHOR("Ban Feng <kcfeng0@nuvoton.com>");
+MODULE_DESCRIPTION("NCT7363 Hardware Monitoring Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c
index 024cff151c36..8c9351da12c6 100644
--- a/drivers/hwmon/nct7802.c
+++ b/drivers/hwmon/nct7802.c
@@ -229,41 +229,34 @@ abort:
static int nct7802_read_fan(struct nct7802_data *data, u8 reg_fan)
{
- unsigned int f1, f2;
+ unsigned int regs[2] = {reg_fan, REG_FANCOUNT_LOW};
+ u8 f[2];
int ret;
- mutex_lock(&data->access_lock);
- ret = regmap_read(data->regmap, reg_fan, &f1);
- if (ret < 0)
- goto abort;
- ret = regmap_read(data->regmap, REG_FANCOUNT_LOW, &f2);
- if (ret < 0)
- goto abort;
- ret = (f1 << 5) | (f2 >> 3);
+ ret = regmap_multi_reg_read(data->regmap, regs, f, 2);
+ if (ret)
+ return ret;
+ ret = (f[0] << 5) | (f[1] >> 3);
/* convert fan count to rpm */
if (ret == 0x1fff) /* maximum value, assume fan is stopped */
ret = 0;
else if (ret)
ret = DIV_ROUND_CLOSEST(1350000U, ret);
-abort:
- mutex_unlock(&data->access_lock);
return ret;
}
static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low,
u8 reg_fan_high)
{
- unsigned int f1, f2;
+ unsigned int regs[2] = {reg_fan_low, reg_fan_high};
+ u8 f[2];
int ret;
- mutex_lock(&data->access_lock);
- ret = regmap_read(data->regmap, reg_fan_low, &f1);
- if (ret < 0)
- goto abort;
- ret = regmap_read(data->regmap, reg_fan_high, &f2);
+ ret = regmap_multi_reg_read(data->regmap, regs, f, 2);
if (ret < 0)
- goto abort;
- ret = f1 | ((f2 & 0xf8) << 5);
+ return ret;
+
+ ret = f[0] | ((f[1] & 0xf8) << 5);
/* convert fan count to rpm */
if (ret == 0x1fff) /* maximum value, assume no limit */
ret = 0;
@@ -271,8 +264,6 @@ static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low,
ret = DIV_ROUND_CLOSEST(1350000U, ret);
else
ret = 1350000U;
-abort:
- mutex_unlock(&data->access_lock);
return ret;
}
@@ -302,33 +293,26 @@ static u8 nct7802_vmul[] = { 4, 2, 2, 2, 2 };
static int nct7802_read_voltage(struct nct7802_data *data, int nr, int index)
{
- unsigned int v1, v2;
+ u8 v[2];
int ret;
- mutex_lock(&data->access_lock);
if (index == 0) { /* voltage */
- ret = regmap_read(data->regmap, REG_VOLTAGE[nr], &v1);
- if (ret < 0)
- goto abort;
- ret = regmap_read(data->regmap, REG_VOLTAGE_LOW, &v2);
+ unsigned int regs[2] = {REG_VOLTAGE[nr], REG_VOLTAGE_LOW};
+
+ ret = regmap_multi_reg_read(data->regmap, regs, v, 2);
if (ret < 0)
- goto abort;
- ret = ((v1 << 2) | (v2 >> 6)) * nct7802_vmul[nr];
+ return ret;
+ ret = ((v[0] << 2) | (v[1] >> 6)) * nct7802_vmul[nr];
} else { /* limit */
int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr];
+ unsigned int regs[2] = {REG_VOLTAGE_LIMIT_LSB[index - 1][nr],
+ REG_VOLTAGE_LIMIT_MSB[nr]};
- ret = regmap_read(data->regmap,
- REG_VOLTAGE_LIMIT_LSB[index - 1][nr], &v1);
- if (ret < 0)
- goto abort;
- ret = regmap_read(data->regmap, REG_VOLTAGE_LIMIT_MSB[nr],
- &v2);
+ ret = regmap_multi_reg_read(data->regmap, regs, v, 2);
if (ret < 0)
- goto abort;
- ret = (v1 | ((v2 << shift) & 0x300)) * nct7802_vmul[nr];
+ return ret;
+ ret = (v[0] | ((v[1] << shift) & 0x300)) * nct7802_vmul[nr];
}
-abort:
- mutex_unlock(&data->access_lock);
return ret;
}
@@ -1051,7 +1035,7 @@ static bool nct7802_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config nct7802_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = nct7802_regmap_is_volatile,
};
@@ -1145,17 +1129,14 @@ static int nct7802_configure_channels(struct device *dev,
{
/* Enable local temperature sensor by default */
u8 mode_mask = MODE_LTD_EN, mode_val = MODE_LTD_EN;
- struct device_node *node;
int err;
if (dev->of_node) {
- for_each_child_of_node(dev->of_node, node) {
+ for_each_child_of_node_scoped(dev->of_node, node) {
err = nct7802_get_channel_config(dev, node, &mode_mask,
&mode_val);
- if (err) {
- of_node_put(node);
+ if (err)
return err;
- }
}
}
@@ -1212,7 +1193,7 @@ static const unsigned short nct7802_address_list[] = {
};
static const struct i2c_device_id nct7802_idtable[] = {
- { "nct7802", 0 },
+ { "nct7802" },
{ }
};
MODULE_DEVICE_TABLE(i2c, nct7802_idtable);
diff --git a/drivers/hwmon/nct7904.c b/drivers/hwmon/nct7904.c
index 8f867d4570e1..2fa091720c79 100644
--- a/drivers/hwmon/nct7904.c
+++ b/drivers/hwmon/nct7904.c
@@ -21,7 +21,6 @@
#include <linux/device.h>
#include <linux/init.h>
#include <linux/i2c.h>
-#include <linux/mutex.h>
#include <linux/hwmon.h>
#include <linux/watchdog.h>
@@ -128,7 +127,6 @@ static const unsigned short normal_i2c[] = {
struct nct7904_data {
struct i2c_client *client;
struct watchdog_device wdt;
- struct mutex bank_lock;
int bank_sel;
u32 fanin_mask;
u32 vsen_mask;
@@ -142,24 +140,19 @@ struct nct7904_data {
};
/* Access functions */
-static int nct7904_bank_lock(struct nct7904_data *data, unsigned int bank)
+static int nct7904_bank_select(struct nct7904_data *data, unsigned int bank)
{
int ret;
- mutex_lock(&data->bank_lock);
if (data->bank_sel == bank)
return 0;
ret = i2c_smbus_write_byte_data(data->client, BANK_SEL_REG, bank);
- if (ret == 0)
- data->bank_sel = bank;
- else
+ if (ret < 0) {
data->bank_sel = -1;
- return ret;
-}
-
-static inline void nct7904_bank_release(struct nct7904_data *data)
-{
- mutex_unlock(&data->bank_lock);
+ return ret;
+ }
+ data->bank_sel = bank;
+ return 0;
}
/* Read 1-byte register. Returns unsigned reg or -ERRNO on error. */
@@ -169,12 +162,10 @@ static int nct7904_read_reg(struct nct7904_data *data,
struct i2c_client *client = data->client;
int ret;
- ret = nct7904_bank_lock(data, bank);
- if (ret == 0)
- ret = i2c_smbus_read_byte_data(client, reg);
-
- nct7904_bank_release(data);
- return ret;
+ ret = nct7904_bank_select(data, bank);
+ if (ret < 0)
+ return ret;
+ return i2c_smbus_read_byte_data(client, reg);
}
/*
@@ -187,19 +178,16 @@ static int nct7904_read_reg16(struct nct7904_data *data,
struct i2c_client *client = data->client;
int ret, hi;
- ret = nct7904_bank_lock(data, bank);
- if (ret == 0) {
- ret = i2c_smbus_read_byte_data(client, reg);
- if (ret >= 0) {
- hi = ret;
- ret = i2c_smbus_read_byte_data(client, reg + 1);
- if (ret >= 0)
- ret |= hi << 8;
- }
- }
-
- nct7904_bank_release(data);
- return ret;
+ ret = nct7904_bank_select(data, bank);
+ if (ret < 0)
+ return ret;
+ hi = i2c_smbus_read_byte_data(client, reg);
+ if (hi < 0)
+ return hi;
+ ret = i2c_smbus_read_byte_data(client, reg + 1);
+ if (ret < 0)
+ return ret;
+ return ret | (hi << 8);
}
/* Write 1-byte register. Returns 0 or -ERRNO on error. */
@@ -209,12 +197,10 @@ static int nct7904_write_reg(struct nct7904_data *data,
struct i2c_client *client = data->client;
int ret;
- ret = nct7904_bank_lock(data, bank);
- if (ret == 0)
- ret = i2c_smbus_write_byte_data(client, reg, val);
-
- nct7904_bank_release(data);
- return ret;
+ ret = nct7904_bank_select(data, bank);
+ if (ret < 0)
+ return ret;
+ return i2c_smbus_write_byte_data(client, reg, val);
}
static int nct7904_read_fan(struct device *dev, u32 attr, int channel,
@@ -1023,7 +1009,6 @@ static int nct7904_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->bank_lock);
data->bank_sel = -1;
/* Setup sensor groups. */
@@ -1161,7 +1146,7 @@ static int nct7904_probe(struct i2c_client *client)
}
static const struct i2c_device_id nct7904_id[] = {
- {"nct7904", 0},
+ {"nct7904"},
{}
};
MODULE_DEVICE_TABLE(i2c, nct7904_id);
diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c
index 10ed3f4335d4..c8f5e695fb6d 100644
--- a/drivers/hwmon/npcm750-pwm-fan.c
+++ b/drivers/hwmon/npcm750-pwm-fan.c
@@ -4,7 +4,6 @@
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -46,9 +45,9 @@
#define NPCM7XX_PWM_CTRL_CH3_EN_BIT BIT(16)
/* Define the maximum PWM channel number */
-#define NPCM7XX_PWM_MAX_CHN_NUM 8
+#define NPCM7XX_PWM_MAX_CHN_NUM 12
#define NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE 4
-#define NPCM7XX_PWM_MAX_MODULES 2
+#define NPCM7XX_PWM_MAX_MODULES 3
/* Define the Counter Register, value = 100 for match 100% */
#define NPCM7XX_PWM_COUNTER_DEFAULT_NUM 255
@@ -171,6 +170,10 @@
#define FAN_PREPARE_TO_GET_FIRST_CAPTURE 0x01
#define FAN_ENOUGH_SAMPLE 0x02
+struct npcm_hwmon_info {
+ u32 pwm_max_channel;
+};
+
struct npcm7xx_fan_dev {
u8 fan_st_flg;
u8 fan_pls_per_rev;
@@ -191,11 +194,9 @@ struct npcm7xx_cooling_device {
struct npcm7xx_pwm_fan_data {
void __iomem *pwm_base;
void __iomem *fan_base;
- unsigned long pwm_clk_freq;
- unsigned long fan_clk_freq;
+ int pwm_modules;
struct clk *pwm_clk;
struct clk *fan_clk;
- struct mutex pwm_lock[NPCM7XX_PWM_MAX_MODULES];
spinlock_t fan_lock[NPCM7XX_FAN_MAX_MODULE];
int fan_irq[NPCM7XX_FAN_MAX_MODULE];
bool pwm_present[NPCM7XX_PWM_MAX_CHN_NUM];
@@ -204,6 +205,7 @@ struct npcm7xx_pwm_fan_data {
struct timer_list fan_timer;
struct npcm7xx_fan_dev fan_dev[NPCM7XX_FAN_MAX_CHN_NUM];
struct npcm7xx_cooling_device *cdev[NPCM7XX_PWM_MAX_CHN_NUM];
+ const struct npcm_hwmon_info *info;
u8 fan_select;
};
@@ -217,7 +219,6 @@ static int npcm7xx_pwm_config_set(struct npcm7xx_pwm_fan_data *data,
/*
* Config PWM Comparator register for setting duty cycle
*/
- mutex_lock(&data->pwm_lock[module]);
/* write new CMR value */
iowrite32(val, NPCM7XX_PWM_REG_CMRx(data->pwm_base, module, pwm_ch));
@@ -241,7 +242,6 @@ static int npcm7xx_pwm_config_set(struct npcm7xx_pwm_fan_data *data,
env_bit = NPCM7XX_PWM_CTRL_CH3_INV_BIT;
break;
default:
- mutex_unlock(&data->pwm_lock[module]);
return -ENODEV;
}
@@ -256,8 +256,6 @@ static int npcm7xx_pwm_config_set(struct npcm7xx_pwm_fan_data *data,
}
iowrite32(tmp_buf, NPCM7XX_PWM_REG_CR(data->pwm_base, module));
- mutex_unlock(&data->pwm_lock[module]);
-
return 0;
}
@@ -327,7 +325,7 @@ static void npcm7xx_fan_polling(struct timer_list *t)
struct npcm7xx_pwm_fan_data *data;
int i;
- data = from_timer(data, t, fan_timer);
+ data = timer_container_of(data, t, fan_timer);
/*
* Polling two module per one round,
@@ -542,7 +540,7 @@ static umode_t npcm7xx_pwm_is_visible(const void *_data, u32 attr, int channel)
{
const struct npcm7xx_pwm_fan_data *data = _data;
- if (!data->pwm_present[channel])
+ if (!data->pwm_present[channel] || channel >= data->info->pwm_max_channel)
return 0;
switch (attr) {
@@ -638,6 +636,10 @@ static const struct hwmon_channel_info * const npcm7xx_info[] = {
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
HWMON_PWM_INPUT),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT,
@@ -670,15 +672,24 @@ static const struct hwmon_chip_info npcm7xx_chip_info = {
.info = npcm7xx_info,
};
+static const struct npcm_hwmon_info npxm7xx_hwmon_info = {
+ .pwm_max_channel = 8,
+};
+
+static const struct npcm_hwmon_info npxm8xx_hwmon_info = {
+ .pwm_max_channel = 12,
+};
+
static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data)
{
int m, ch;
u32 prescale_val, output_freq;
+ unsigned long pwm_clk_freq;
- data->pwm_clk_freq = clk_get_rate(data->pwm_clk);
+ pwm_clk_freq = clk_get_rate(data->pwm_clk);
/* Adjust NPCM7xx PWMs output frequency to ~25Khz */
- output_freq = data->pwm_clk_freq / PWN_CNT_DEFAULT;
+ output_freq = pwm_clk_freq / PWN_CNT_DEFAULT;
prescale_val = DIV_ROUND_CLOSEST(output_freq, PWM_OUTPUT_FREQ_25KHZ);
/* If prescale_val = 0, then the prescale output clock is stopped */
@@ -693,7 +704,7 @@ static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data)
/* Setting PWM Prescale Register value register to both modules */
prescale_val |= (prescale_val << NPCM7XX_PWM_PRESCALE_SHIFT_CH01);
- for (m = 0; m < NPCM7XX_PWM_MAX_MODULES ; m++) {
+ for (m = 0; m < data->pwm_modules; m++) {
iowrite32(prescale_val, NPCM7XX_PWM_REG_PR(data->pwm_base, m));
iowrite32(NPCM7XX_PWM_PRESCALE2_DEFAULT,
NPCM7XX_PWM_REG_CSR(data->pwm_base, m));
@@ -875,6 +886,8 @@ static int npcm7xx_en_pwm_fan(struct device *dev,
data->pwm_present[pwm_port] = true;
ret = npcm7xx_pwm_config_set(data, pwm_port,
NPCM7XX_PWM_CMR_DEFAULT_NUM);
+ if (ret)
+ return ret;
ret = of_property_count_u8_elems(child, "cooling-levels");
if (ret > 0) {
@@ -908,13 +921,13 @@ static int npcm7xx_en_pwm_fan(struct device *dev,
static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np, *child;
+ struct device_node *np;
struct npcm7xx_pwm_fan_data *data;
struct resource *res;
struct device *hwmon;
char name[20];
- int ret, cnt;
u32 output_freq;
+ int ret;
u32 i;
np = dev->of_node;
@@ -923,6 +936,12 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
+ data->info = device_get_match_data(dev);
+ if (!data->info)
+ return -EINVAL;
+
+ data->pwm_modules = data->info->pwm_max_channel / NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE;
+
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
if (!res) {
dev_err(dev, "pwm resource not found\n");
@@ -960,9 +979,6 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
output_freq = npcm7xx_pwm_init(data);
npcm7xx_fan_init(data);
- for (cnt = 0; cnt < NPCM7XX_PWM_MAX_MODULES ; cnt++)
- mutex_init(&data->pwm_lock[cnt]);
-
for (i = 0; i < NPCM7XX_FAN_MAX_MODULE; i++) {
spin_lock_init(&data->fan_lock[i]);
@@ -979,11 +995,10 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
}
}
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
ret = npcm7xx_en_pwm_fan(dev, child, data);
if (ret) {
dev_err(dev, "enable pwm and fan failed\n");
- of_node_put(child);
return ret;
}
}
@@ -1015,7 +1030,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
}
static const struct of_device_id of_pwm_fan_match_table[] = {
- { .compatible = "nuvoton,npcm750-pwm-fan", },
+ { .compatible = "nuvoton,npcm750-pwm-fan", .data = &npxm7xx_hwmon_info},
+ { .compatible = "nuvoton,npcm845-pwm-fan", .data = &npxm8xx_hwmon_info},
{},
};
MODULE_DEVICE_TABLE(of, of_pwm_fan_match_table);
diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c
index ef75b63f5894..d6b48178343d 100644
--- a/drivers/hwmon/ntc_thermistor.c
+++ b/drivers/hwmon/ntc_thermistor.c
@@ -24,6 +24,7 @@ enum ntc_thermistor_type {
TYPE_NCPXXWF104,
TYPE_NCPXXWL333,
TYPE_NCPXXXH103,
+ TYPE_NCPXXWM474,
};
struct ntc_compensation {
@@ -46,6 +47,7 @@ enum {
NTC_NCP18WB473,
NTC_NCP21WB473,
NTC_SSG1404001221,
+ NTC_NCP18WM474,
NTC_LAST,
};
@@ -60,8 +62,10 @@ static const struct platform_device_id ntc_thermistor_id[] = {
[NTC_NCP18WB473] = { "ncp18wb473", TYPE_NCPXXWB473 },
[NTC_NCP21WB473] = { "ncp21wb473", TYPE_NCPXXWB473 },
[NTC_SSG1404001221] = { "ssg1404_001221", TYPE_NCPXXWB473 },
+ [NTC_NCP18WM474] = { "ncp18wm474", TYPE_NCPXXWM474 },
[NTC_LAST] = { },
};
+MODULE_DEVICE_TABLE(platform, ntc_thermistor_id);
/*
* A compensation table should be sorted by the values of .ohm
@@ -180,40 +184,77 @@ static const struct ntc_compensation ncpXXwf104[] = {
};
static const struct ntc_compensation ncpXXxh103[] = {
- { .temp_c = -40, .ohm = 247565 },
- { .temp_c = -35, .ohm = 181742 },
- { .temp_c = -30, .ohm = 135128 },
- { .temp_c = -25, .ohm = 101678 },
- { .temp_c = -20, .ohm = 77373 },
- { .temp_c = -15, .ohm = 59504 },
- { .temp_c = -10, .ohm = 46222 },
- { .temp_c = -5, .ohm = 36244 },
- { .temp_c = 0, .ohm = 28674 },
- { .temp_c = 5, .ohm = 22878 },
- { .temp_c = 10, .ohm = 18399 },
- { .temp_c = 15, .ohm = 14910 },
- { .temp_c = 20, .ohm = 12169 },
+ { .temp_c = -40, .ohm = 195652 },
+ { .temp_c = -35, .ohm = 148171 },
+ { .temp_c = -30, .ohm = 113347 },
+ { .temp_c = -25, .ohm = 87559 },
+ { .temp_c = -20, .ohm = 68237 },
+ { .temp_c = -15, .ohm = 53650 },
+ { .temp_c = -10, .ohm = 42506 },
+ { .temp_c = -5, .ohm = 33892 },
+ { .temp_c = 0, .ohm = 27219 },
+ { .temp_c = 5, .ohm = 22021 },
+ { .temp_c = 10, .ohm = 17926 },
+ { .temp_c = 15, .ohm = 14674 },
+ { .temp_c = 20, .ohm = 12081 },
{ .temp_c = 25, .ohm = 10000 },
- { .temp_c = 30, .ohm = 8271 },
- { .temp_c = 35, .ohm = 6883 },
- { .temp_c = 40, .ohm = 5762 },
- { .temp_c = 45, .ohm = 4851 },
- { .temp_c = 50, .ohm = 4105 },
- { .temp_c = 55, .ohm = 3492 },
- { .temp_c = 60, .ohm = 2985 },
- { .temp_c = 65, .ohm = 2563 },
- { .temp_c = 70, .ohm = 2211 },
- { .temp_c = 75, .ohm = 1915 },
- { .temp_c = 80, .ohm = 1666 },
- { .temp_c = 85, .ohm = 1454 },
- { .temp_c = 90, .ohm = 1275 },
- { .temp_c = 95, .ohm = 1121 },
- { .temp_c = 100, .ohm = 990 },
- { .temp_c = 105, .ohm = 876 },
- { .temp_c = 110, .ohm = 779 },
- { .temp_c = 115, .ohm = 694 },
- { .temp_c = 120, .ohm = 620 },
- { .temp_c = 125, .ohm = 556 },
+ { .temp_c = 30, .ohm = 8315 },
+ { .temp_c = 35, .ohm = 6948 },
+ { .temp_c = 40, .ohm = 5834 },
+ { .temp_c = 45, .ohm = 4917 },
+ { .temp_c = 50, .ohm = 4161 },
+ { .temp_c = 55, .ohm = 3535 },
+ { .temp_c = 60, .ohm = 3014 },
+ { .temp_c = 65, .ohm = 2586 },
+ { .temp_c = 70, .ohm = 2228 },
+ { .temp_c = 75, .ohm = 1925 },
+ { .temp_c = 80, .ohm = 1669 },
+ { .temp_c = 85, .ohm = 1452 },
+ { .temp_c = 90, .ohm = 1268 },
+ { .temp_c = 95, .ohm = 1110 },
+ { .temp_c = 100, .ohm = 974 },
+ { .temp_c = 105, .ohm = 858 },
+ { .temp_c = 110, .ohm = 758 },
+ { .temp_c = 115, .ohm = 672 },
+ { .temp_c = 120, .ohm = 596 },
+ { .temp_c = 125, .ohm = 531 },
+};
+
+static const struct ntc_compensation ncpXXwm474[] = {
+ { .temp_c = -40, .ohm = 10900000 },
+ { .temp_c = -35, .ohm = 9600000 },
+ { .temp_c = -30, .ohm = 8300000 },
+ { .temp_c = -25, .ohm = 7000000 },
+ { .temp_c = -20, .ohm = 5980000 },
+ { .temp_c = -15, .ohm = 4960000 },
+ { .temp_c = -10, .ohm = 3940000 },
+ { .temp_c = -5, .ohm = 2920000 },
+ { .temp_c = 0, .ohm = 1900000 },
+ { .temp_c = 5, .ohm = 1614000 },
+ { .temp_c = 10, .ohm = 1328000 },
+ { .temp_c = 15, .ohm = 1042000 },
+ { .temp_c = 20, .ohm = 756000 },
+ { .temp_c = 25, .ohm = 470000 },
+ { .temp_c = 30, .ohm = 404000 },
+ { .temp_c = 35, .ohm = 338000 },
+ { .temp_c = 40, .ohm = 272000 },
+ { .temp_c = 45, .ohm = 206000 },
+ { .temp_c = 50, .ohm = 140000 },
+ { .temp_c = 55, .ohm = 122000 },
+ { .temp_c = 60, .ohm = 104000 },
+ { .temp_c = 65, .ohm = 86000 },
+ { .temp_c = 70, .ohm = 68000 },
+ { .temp_c = 75, .ohm = 50000 },
+ { .temp_c = 80, .ohm = 44200 },
+ { .temp_c = 85, .ohm = 38400 },
+ { .temp_c = 90, .ohm = 32600 },
+ { .temp_c = 95, .ohm = 26800 },
+ { .temp_c = 100, .ohm = 21000 },
+ { .temp_c = 105, .ohm = 18600 },
+ { .temp_c = 110, .ohm = 16200 },
+ { .temp_c = 115, .ohm = 13800 },
+ { .temp_c = 120, .ohm = 11400 },
+ { .temp_c = 125, .ohm = 9000 },
};
/*
@@ -318,6 +359,7 @@ static const struct ntc_type ntc_type[] = {
NTC_TYPE(TYPE_NCPXXWF104, ncpXXwf104),
NTC_TYPE(TYPE_NCPXXWL333, ncpXXwl333),
NTC_TYPE(TYPE_NCPXXXH103, ncpXXxh103),
+ NTC_TYPE(TYPE_NCPXXWM474, ncpXXwm474),
};
/*
@@ -386,12 +428,9 @@ static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv)
puo = data->pullup_ohm;
pdo = data->pulldown_ohm;
- if (uv == 0)
- return (data->connect == NTC_CONNECTED_POSITIVE) ?
- INT_MAX : 0;
- if (uv >= puv)
- return (data->connect == NTC_CONNECTED_POSITIVE) ?
- 0 : INT_MAX;
+ /* faulty adc value */
+ if (uv == 0 || uv >= puv)
+ return -ENODATA;
if (data->connect == NTC_CONNECTED_POSITIVE && puo == 0)
n = div_u64(pdo * (puv - uv), uv);
@@ -403,8 +442,10 @@ static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv)
else
n = div64_u64_safe(pdo * puo * uv, pdo * (puv - uv) - puo * uv);
- if (n > INT_MAX)
- n = INT_MAX;
+ /* sensor out of bounds */
+ if (n > data->comp[0].ohm || n < data->comp[data->n_comp - 1].ohm)
+ return -ENODATA;
+
return n;
}
@@ -675,6 +716,8 @@ static const struct of_device_id ntc_match[] = {
.data = &ntc_thermistor_id[NTC_NCP21WB473] },
{ .compatible = "samsung,1404-001221",
.data = &ntc_thermistor_id[NTC_SSG1404001221] },
+ { .compatible = "murata,ncp18wm474",
+ .data = &ntc_thermistor_id[NTC_NCP18WM474] },
/* Usage of vendor name "ntc" is deprecated */
{ .compatible = "ntc,ncp03wb473",
diff --git a/drivers/hwmon/nzxt-kraken2.c b/drivers/hwmon/nzxt-kraken2.c
index 428c77b5fce5..034698232758 100644
--- a/drivers/hwmon/nzxt-kraken2.c
+++ b/drivers/hwmon/nzxt-kraken2.c
@@ -9,7 +9,7 @@
* Copyright 2019-2021 Jonas Malaco <jonas@protocubo.io>
*/
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
@@ -35,13 +35,6 @@ struct kraken2_priv_data {
unsigned long updated; /* jiffies */
};
-static umode_t kraken2_is_visible(const void *data,
- enum hwmon_sensor_types type,
- u32 attr, int channel)
-{
- return 0444;
-}
-
static int kraken2_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@@ -81,7 +74,7 @@ static int kraken2_read_string(struct device *dev, enum hwmon_sensor_types type,
}
static const struct hwmon_ops kraken2_hwmon_ops = {
- .is_visible = kraken2_is_visible,
+ .visible = 0444,
.read = kraken2_read,
.read_string = kraken2_read_string,
};
@@ -161,13 +154,13 @@ static int kraken2_probe(struct hid_device *hdev,
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret) {
hid_err(hdev, "hid hw start failed with %d\n", ret);
- goto fail_and_stop;
+ return ret;
}
ret = hid_hw_open(hdev);
if (ret) {
hid_err(hdev, "hid hw open failed with %d\n", ret);
- goto fail_and_close;
+ goto fail_and_stop;
}
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "kraken2",
diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c
new file mode 100644
index 000000000000..d00409bcab93
--- /dev/null
+++ b/drivers/hwmon/nzxt-kraken3.c
@@ -0,0 +1,1028 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers.
+ * X53 and Z53 in code refer to all models in their respective series (shortened for brevity).
+ * 2023 models use the Z53 code paths.
+ *
+ * Copyright 2021 Jonas Malaco <jonas@protocubo.io>
+ * Copyright 2022 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/unaligned.h>
+
+#define USB_VENDOR_ID_NZXT 0x1e71
+#define USB_PRODUCT_ID_X53 0x2007
+#define USB_PRODUCT_ID_X53_SECOND 0x2014
+#define USB_PRODUCT_ID_Z53 0x3008
+#define USB_PRODUCT_ID_KRAKEN2023 0x300E
+#define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C
+
+enum kinds { X53, Z53, KRAKEN2023 } __packed;
+enum pwm_enable { off, manual, curve } __packed;
+
+#define DRIVER_NAME "nzxt_kraken3"
+#define STATUS_REPORT_ID 0x75
+#define FIRMWARE_REPORT_ID 0x11
+#define STATUS_VALIDITY 2000 /* In ms, equivalent to period of four status reports */
+#define CUSTOM_CURVE_POINTS 40 /* For temps from 20C to 59C (critical temp) */
+#define PUMP_DUTY_MIN 20 /* In percent */
+
+/* Sensor report offsets for Kraken X53 and Z53 */
+#define TEMP_SENSOR_START_OFFSET 15
+#define TEMP_SENSOR_END_OFFSET 16
+#define PUMP_SPEED_OFFSET 17
+#define PUMP_DUTY_OFFSET 19
+
+/* Firmware version report offset for Kraken X53 and Z53 */
+#define FIRMWARE_VERSION_OFFSET 17
+
+/* Sensor report offsets for Kraken Z53 */
+#define Z53_FAN_SPEED_OFFSET 23
+#define Z53_FAN_DUTY_OFFSET 25
+
+/* Report offsets for control commands for Kraken X53 and Z53 */
+#define SET_DUTY_ID_OFFSET 1
+
+/* Control commands and their lengths for Kraken X53 and Z53 */
+
+/* Last byte sets the report interval at 0.5s */
+static const u8 set_interval_cmd[] = { 0x70, 0x02, 0x01, 0xB8, 1 };
+static const u8 finish_init_cmd[] = { 0x70, 0x01 };
+static const u8 __maybe_unused get_fw_version_cmd[] = { 0x10, 0x01 };
+static const u8 set_pump_duty_cmd_header[] = { 0x72, 0x00, 0x00, 0x00 };
+static const u8 z53_get_status_cmd[] = { 0x74, 0x01 };
+
+#define SET_INTERVAL_CMD_LENGTH 5
+#define FINISH_INIT_CMD_LENGTH 2
+#define GET_FW_VERSION_CMD_LENGTH 2
+#define MAX_REPORT_LENGTH 64
+#define MIN_REPORT_LENGTH 20
+#define SET_CURVE_DUTY_CMD_HEADER_LENGTH 4
+/* 4 byte header and 40 duty offsets */
+#define SET_CURVE_DUTY_CMD_LENGTH (4 + 40)
+#define Z53_GET_STATUS_CMD_LENGTH 2
+
+static const char *const kraken3_temp_label[] = {
+ "Coolant temp",
+};
+
+static const char *const kraken3_fan_label[] = {
+ "Pump speed",
+ "Fan speed"
+};
+
+struct kraken3_channel_info {
+ enum pwm_enable mode;
+
+ /* Both values are PWM */
+ u16 reported_duty;
+ u16 fixed_duty; /* Manually set fixed duty */
+
+ u8 pwm_points[CUSTOM_CURVE_POINTS];
+};
+
+struct kraken3_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ struct dentry *debugfs;
+ struct mutex buffer_lock; /* For locking access to buffer */
+ struct mutex z53_status_request_lock;
+ struct completion fw_version_processed;
+ /*
+ * For X53 devices, tracks whether an initial (one) sensor report was received to
+ * make fancontrol not bail outright. For Z53 devices, whether a status report
+ * was processed after requesting one.
+ */
+ struct completion status_report_processed;
+ /* For locking the above completion */
+ spinlock_t status_completion_lock;
+
+ u8 *buffer;
+ struct kraken3_channel_info channel_info[2]; /* Pump and fan */
+ bool is_device_faulty;
+
+ /* Sensor values */
+ s32 temp_input[1];
+ u16 fan_input[2];
+
+ enum kinds kind;
+ u8 firmware_version[3];
+
+ unsigned long updated; /* jiffies */
+};
+
+static umode_t kraken3_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct kraken3_data *priv = data;
+
+ switch (type) {
+ case hwmon_temp:
+ if (channel < 1)
+ return 0444;
+ break;
+ case hwmon_fan:
+ switch (priv->kind) {
+ case X53:
+ /* Just the pump */
+ if (channel < 1)
+ return 0444;
+ break;
+ case Z53:
+ case KRAKEN2023:
+ /* Pump and fan */
+ if (channel < 2)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_input:
+ switch (priv->kind) {
+ case X53:
+ /* Just the pump */
+ if (channel < 1)
+ return 0644;
+ break;
+ case Z53:
+ case KRAKEN2023:
+ /* Pump and fan */
+ if (channel < 2)
+ return 0644;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Writes the command to the device with the rest of the report (up to 64 bytes) filled
+ * with zeroes.
+ */
+static int kraken3_write_expanded(struct kraken3_data *priv, const u8 *cmd, int cmd_length)
+{
+ int ret;
+
+ mutex_lock(&priv->buffer_lock);
+
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+
+ mutex_unlock(&priv->buffer_lock);
+ return ret;
+}
+
+static int kraken3_percent_to_pwm(long val)
+{
+ return DIV_ROUND_CLOSEST(val * 255, 100);
+}
+
+static int kraken3_pwm_to_percent(long val, int channel)
+{
+ int percent_value;
+
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ percent_value = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ /* Bring up pump duty to min value if needed */
+ if (channel == 0 && percent_value < PUMP_DUTY_MIN)
+ percent_value = PUMP_DUTY_MIN;
+
+ return percent_value;
+}
+
+static int kraken3_read_x53(struct kraken3_data *priv)
+{
+ int ret;
+
+ if (completion_done(&priv->status_report_processed))
+ /*
+ * We're here because data is stale. This means that sensor reports haven't
+ * been received for some time in kraken3_raw_event(). On X-series sensor data
+ * can't be manually requested, so return an error.
+ */
+ return -ENODATA;
+
+ /*
+ * Data needs to be read, but a sensor report wasn't yet received. It's usually
+ * fancontrol that requests data this early and it exits if it reads an error code.
+ * So, wait for the first report to be parsed (but up to STATUS_VALIDITY).
+ * This does not concern the Z series devices, because they send a sensor report
+ * only when requested.
+ */
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ /* The first sensor report was parsed on time and reading can continue */
+ return 0;
+}
+
+/* Covers Z53 and KRAKEN2023 device kinds */
+static int kraken3_read_z53(struct kraken3_data *priv)
+{
+ int ret = mutex_lock_interruptible(&priv->z53_status_request_lock);
+
+ if (ret < 0)
+ return ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ goto unlock_and_return;
+ }
+
+ /*
+ * Disable interrupts for a moment to safely reinit the completion,
+ * as hidraw calls could have allowed one or more readers to complete.
+ */
+ spin_lock_bh(&priv->status_completion_lock);
+ reinit_completion(&priv->status_report_processed);
+ spin_unlock_bh(&priv->status_completion_lock);
+
+ /* Send command for getting status */
+ ret = kraken3_write_expanded(priv, z53_get_status_cmd, Z53_GET_STATUS_CMD_LENGTH);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ /* Wait for completion from kraken3_raw_event() */
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+
+unlock_and_return:
+ mutex_unlock(&priv->z53_status_request_lock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int kraken3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ if (priv->kind == X53)
+ ret = kraken3_read_x53(priv);
+ else
+ ret = kraken3_read_z53(priv);
+
+ if (ret < 0)
+ return ret;
+
+ if (priv->is_device_faulty)
+ return -ENODATA;
+ }
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->fan_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ *val = priv->channel_info[channel].mode;
+ break;
+ case hwmon_pwm_input:
+ *val = priv->channel_info[channel].reported_duty;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int kraken3_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = kraken3_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = kraken3_fan_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/* Writes custom curve to device */
+static int kraken3_write_curve(struct kraken3_data *priv, u8 *curve_array, int channel)
+{
+ u8 fixed_duty_cmd[SET_CURVE_DUTY_CMD_LENGTH];
+ int ret;
+
+ /* Copy command header */
+ memcpy(fixed_duty_cmd, set_pump_duty_cmd_header, SET_CURVE_DUTY_CMD_HEADER_LENGTH);
+
+ /* Set the correct ID for writing pump/fan duty (0x01 or 0x02, respectively) */
+ fixed_duty_cmd[SET_DUTY_ID_OFFSET] = channel + 1;
+
+ if (priv->kind == KRAKEN2023) {
+ /* These require 1s in the next one or two slots after SET_DUTY_ID_OFFSET */
+ fixed_duty_cmd[SET_DUTY_ID_OFFSET + 1] = 1;
+ if (channel == 1) /* Fan */
+ fixed_duty_cmd[SET_DUTY_ID_OFFSET + 2] = 1;
+ }
+
+ /* Copy curve to command */
+ memcpy(fixed_duty_cmd + SET_CURVE_DUTY_CMD_HEADER_LENGTH, curve_array, CUSTOM_CURVE_POINTS);
+
+ ret = kraken3_write_expanded(priv, fixed_duty_cmd, SET_CURVE_DUTY_CMD_LENGTH);
+ return ret;
+}
+
+static int kraken3_write_fixed_duty(struct kraken3_data *priv, long val, int channel)
+{
+ u8 fixed_curve_points[CUSTOM_CURVE_POINTS];
+ int ret, percent_val, i;
+
+ percent_val = kraken3_pwm_to_percent(val, channel);
+ if (percent_val < 0)
+ return percent_val;
+
+ /*
+ * The devices can only control the duty through a curve.
+ * Since we're setting a fixed duty here, fill the whole curve
+ * (ranging from 20C to 59C) with the same duty, except for
+ * the last point, the critical temperature, where it's maxed
+ * out for safety.
+ */
+
+ /* Fill the custom curve with the fixed value we're setting */
+ for (i = 0; i < CUSTOM_CURVE_POINTS - 1; i++)
+ fixed_curve_points[i] = percent_val;
+
+ /* Force duty to 100% at critical temp */
+ fixed_curve_points[CUSTOM_CURVE_POINTS - 1] = 100;
+
+ /* Write the fixed duty curve to the device */
+ ret = kraken3_write_curve(priv, fixed_curve_points, channel);
+ return ret;
+}
+
+static int kraken3_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ /* Remember the last set fixed duty for channel */
+ priv->channel_info[channel].fixed_duty = val;
+
+ if (priv->channel_info[channel].mode == manual) {
+ ret = kraken3_write_fixed_duty(priv, val, channel);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Lock onto this value and report it until next interrupt status
+ * report is received, so userspace tools can continue to work.
+ */
+ priv->channel_info[channel].reported_duty = val;
+ }
+ break;
+ case hwmon_pwm_enable:
+ if (val < 0 || val > 2)
+ return -EINVAL;
+
+ switch (val) {
+ case 0:
+ /* Set channel to 100%, direct duty value */
+ ret = kraken3_write_fixed_duty(priv, 255, channel);
+ if (ret < 0)
+ return ret;
+
+ /* We don't control anything anymore */
+ priv->channel_info[channel].mode = off;
+ break;
+ case 1:
+ /* Apply the last known direct duty value */
+ ret =
+ kraken3_write_fixed_duty(priv,
+ priv->channel_info[channel].fixed_duty,
+ channel);
+ if (ret < 0)
+ return ret;
+
+ priv->channel_info[channel].mode = manual;
+ break;
+ case 2:
+ /* Apply the curve and note as enabled */
+ ret =
+ kraken3_write_curve(priv,
+ priv->channel_info[channel].pwm_points,
+ channel);
+ if (ret < 0)
+ return ret;
+
+ priv->channel_info[channel].mode = curve;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static ssize_t kraken3_fan_curve_pwm_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ long val;
+ int ret;
+
+ if (kstrtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ val = kraken3_pwm_to_percent(val, dev_attr->nr);
+ if (val < 0)
+ return val;
+
+ priv->channel_info[dev_attr->nr].pwm_points[dev_attr->index] = val;
+
+ if (priv->channel_info[dev_attr->nr].mode == curve) {
+ /* Apply the curve */
+ ret =
+ kraken3_write_curve(priv,
+ priv->channel_info[dev_attr->nr].pwm_points, dev_attr->nr);
+ if (ret < 0)
+ return ret;
+ }
+
+ return count;
+}
+
+static umode_t kraken3_curve_props_are_visible(struct kobject *kobj, struct attribute *attr,
+ int index)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+
+ /* X53 does not have a fan */
+ if (index >= CUSTOM_CURVE_POINTS && priv->kind == X53)
+ return 0;
+
+ return attr->mode;
+}
+
+/* Custom pump curve from 20C to 59C (critical temp) */
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point1_pwm, kraken3_fan_curve_pwm, 0, 0);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point2_pwm, kraken3_fan_curve_pwm, 0, 1);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point3_pwm, kraken3_fan_curve_pwm, 0, 2);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point4_pwm, kraken3_fan_curve_pwm, 0, 3);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point5_pwm, kraken3_fan_curve_pwm, 0, 4);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point6_pwm, kraken3_fan_curve_pwm, 0, 5);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point7_pwm, kraken3_fan_curve_pwm, 0, 6);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point8_pwm, kraken3_fan_curve_pwm, 0, 7);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point9_pwm, kraken3_fan_curve_pwm, 0, 8);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point10_pwm, kraken3_fan_curve_pwm, 0, 9);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point11_pwm, kraken3_fan_curve_pwm, 0, 10);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point12_pwm, kraken3_fan_curve_pwm, 0, 11);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point13_pwm, kraken3_fan_curve_pwm, 0, 12);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point14_pwm, kraken3_fan_curve_pwm, 0, 13);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point15_pwm, kraken3_fan_curve_pwm, 0, 14);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point16_pwm, kraken3_fan_curve_pwm, 0, 15);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point17_pwm, kraken3_fan_curve_pwm, 0, 16);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point18_pwm, kraken3_fan_curve_pwm, 0, 17);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point19_pwm, kraken3_fan_curve_pwm, 0, 18);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point20_pwm, kraken3_fan_curve_pwm, 0, 19);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point21_pwm, kraken3_fan_curve_pwm, 0, 20);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point22_pwm, kraken3_fan_curve_pwm, 0, 21);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point23_pwm, kraken3_fan_curve_pwm, 0, 22);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point24_pwm, kraken3_fan_curve_pwm, 0, 23);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point25_pwm, kraken3_fan_curve_pwm, 0, 24);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point26_pwm, kraken3_fan_curve_pwm, 0, 25);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point27_pwm, kraken3_fan_curve_pwm, 0, 26);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point28_pwm, kraken3_fan_curve_pwm, 0, 27);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point29_pwm, kraken3_fan_curve_pwm, 0, 28);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point30_pwm, kraken3_fan_curve_pwm, 0, 29);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point31_pwm, kraken3_fan_curve_pwm, 0, 30);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point32_pwm, kraken3_fan_curve_pwm, 0, 31);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point33_pwm, kraken3_fan_curve_pwm, 0, 32);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point34_pwm, kraken3_fan_curve_pwm, 0, 33);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point35_pwm, kraken3_fan_curve_pwm, 0, 34);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point36_pwm, kraken3_fan_curve_pwm, 0, 35);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point37_pwm, kraken3_fan_curve_pwm, 0, 36);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point38_pwm, kraken3_fan_curve_pwm, 0, 37);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point39_pwm, kraken3_fan_curve_pwm, 0, 38);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point40_pwm, kraken3_fan_curve_pwm, 0, 39);
+
+/* Custom fan curve from 20C to 59C (critical temp) */
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point1_pwm, kraken3_fan_curve_pwm, 1, 0);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point2_pwm, kraken3_fan_curve_pwm, 1, 1);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point3_pwm, kraken3_fan_curve_pwm, 1, 2);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point4_pwm, kraken3_fan_curve_pwm, 1, 3);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point5_pwm, kraken3_fan_curve_pwm, 1, 4);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point6_pwm, kraken3_fan_curve_pwm, 1, 5);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point7_pwm, kraken3_fan_curve_pwm, 1, 6);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point8_pwm, kraken3_fan_curve_pwm, 1, 7);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point9_pwm, kraken3_fan_curve_pwm, 1, 8);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point10_pwm, kraken3_fan_curve_pwm, 1, 9);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point11_pwm, kraken3_fan_curve_pwm, 1, 10);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point12_pwm, kraken3_fan_curve_pwm, 1, 11);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point13_pwm, kraken3_fan_curve_pwm, 1, 12);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point14_pwm, kraken3_fan_curve_pwm, 1, 13);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point15_pwm, kraken3_fan_curve_pwm, 1, 14);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point16_pwm, kraken3_fan_curve_pwm, 1, 15);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point17_pwm, kraken3_fan_curve_pwm, 1, 16);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point18_pwm, kraken3_fan_curve_pwm, 1, 17);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point19_pwm, kraken3_fan_curve_pwm, 1, 18);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point20_pwm, kraken3_fan_curve_pwm, 1, 19);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point21_pwm, kraken3_fan_curve_pwm, 1, 20);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point22_pwm, kraken3_fan_curve_pwm, 1, 21);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point23_pwm, kraken3_fan_curve_pwm, 1, 22);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point24_pwm, kraken3_fan_curve_pwm, 1, 23);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point25_pwm, kraken3_fan_curve_pwm, 1, 24);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point26_pwm, kraken3_fan_curve_pwm, 1, 25);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point27_pwm, kraken3_fan_curve_pwm, 1, 26);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point28_pwm, kraken3_fan_curve_pwm, 1, 27);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point29_pwm, kraken3_fan_curve_pwm, 1, 28);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point30_pwm, kraken3_fan_curve_pwm, 1, 29);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point31_pwm, kraken3_fan_curve_pwm, 1, 30);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point32_pwm, kraken3_fan_curve_pwm, 1, 31);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point33_pwm, kraken3_fan_curve_pwm, 1, 32);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point34_pwm, kraken3_fan_curve_pwm, 1, 33);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point35_pwm, kraken3_fan_curve_pwm, 1, 34);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point36_pwm, kraken3_fan_curve_pwm, 1, 35);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point37_pwm, kraken3_fan_curve_pwm, 1, 36);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point38_pwm, kraken3_fan_curve_pwm, 1, 37);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point39_pwm, kraken3_fan_curve_pwm, 1, 38);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point40_pwm, kraken3_fan_curve_pwm, 1, 39);
+
+static struct attribute *kraken3_curve_attrs[] = {
+ /* Pump control curve */
+ &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point8_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point9_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point10_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point11_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point12_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point13_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point14_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point15_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point16_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point17_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point18_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point19_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point20_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point21_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point22_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point23_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point24_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point25_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point26_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point27_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point28_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point29_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point30_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point31_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point32_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point33_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point34_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point35_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point36_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point37_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point38_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point39_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point40_pwm.dev_attr.attr,
+ /* Fan control curve (Z53 only) */
+ &sensor_dev_attr_temp2_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point8_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point9_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point10_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point11_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point12_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point13_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point14_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point15_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point16_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point17_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point18_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point19_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point20_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point21_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point22_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point23_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point24_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point25_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point26_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point27_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point28_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point29_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point30_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point31_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point32_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point33_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point34_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point35_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point36_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point37_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point38_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point39_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point40_pwm.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group kraken3_curves_group = {
+ .attrs = kraken3_curve_attrs,
+ .is_visible = kraken3_curve_props_are_visible
+};
+
+static const struct attribute_group *kraken3_groups[] = {
+ &kraken3_curves_group,
+ NULL
+};
+
+static const struct hwmon_ops kraken3_hwmon_ops = {
+ .is_visible = kraken3_is_visible,
+ .read = kraken3_read,
+ .read_string = kraken3_read_string,
+ .write = kraken3_write
+};
+
+static const struct hwmon_channel_info *kraken3_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL
+};
+
+static const struct hwmon_chip_info kraken3_chip_info = {
+ .ops = &kraken3_hwmon_ops,
+ .info = kraken3_info,
+};
+
+static int kraken3_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int i;
+
+ if (size < MIN_REPORT_LENGTH)
+ return 0;
+
+ if (report->id == FIRMWARE_REPORT_ID) {
+ /* Read firmware version */
+ for (i = 0; i < 3; i++)
+ priv->firmware_version[i] = data[FIRMWARE_VERSION_OFFSET + i];
+
+ if (!completion_done(&priv->fw_version_processed))
+ complete_all(&priv->fw_version_processed);
+
+ return 0;
+ }
+
+ if (report->id != STATUS_REPORT_ID)
+ return 0;
+
+ if (data[TEMP_SENSOR_START_OFFSET] == 0xff && data[TEMP_SENSOR_END_OFFSET] == 0xff) {
+ hid_err_once(hdev,
+ "firmware or device is possibly damaged (is SATA power connected?), not parsing reports\n");
+
+ /*
+ * Mark first X-series device report as received,
+ * as well as all for Z-series, if faulty.
+ */
+ spin_lock(&priv->status_completion_lock);
+ if (priv->kind != X53 || !completion_done(&priv->status_report_processed)) {
+ priv->is_device_faulty = true;
+ complete_all(&priv->status_report_processed);
+ }
+ spin_unlock(&priv->status_completion_lock);
+
+ return 0;
+ }
+
+ /* Received normal data */
+ priv->is_device_faulty = false;
+
+ /* Temperature and fan sensor readings */
+ priv->temp_input[0] =
+ data[TEMP_SENSOR_START_OFFSET] * 1000 + data[TEMP_SENSOR_END_OFFSET] * 100;
+
+ priv->fan_input[0] = get_unaligned_le16(data + PUMP_SPEED_OFFSET);
+ priv->channel_info[0].reported_duty = kraken3_percent_to_pwm(data[PUMP_DUTY_OFFSET]);
+
+ spin_lock(&priv->status_completion_lock);
+ if (priv->kind == X53 && !completion_done(&priv->status_report_processed)) {
+ /* Mark first X-series device report as received */
+ complete_all(&priv->status_report_processed);
+ } else if (priv->kind == Z53 || priv->kind == KRAKEN2023) {
+ /* Additional readings for Z53 and KRAKEN2023 */
+ priv->fan_input[1] = get_unaligned_le16(data + Z53_FAN_SPEED_OFFSET);
+ priv->channel_info[1].reported_duty =
+ kraken3_percent_to_pwm(data[Z53_FAN_DUTY_OFFSET]);
+
+ if (!completion_done(&priv->status_report_processed))
+ complete_all(&priv->status_report_processed);
+ }
+ spin_unlock(&priv->status_completion_lock);
+
+ priv->updated = jiffies;
+
+ return 0;
+}
+
+static int kraken3_init_device(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ /* Set the polling interval */
+ ret = kraken3_write_expanded(priv, set_interval_cmd, SET_INTERVAL_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ /* Finalize the init process */
+ ret = kraken3_write_expanded(priv, finish_init_cmd, FINISH_INIT_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int kraken3_get_fw_ver(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = kraken3_write_expanded(priv, get_fw_version_cmd, GET_FW_VERSION_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int __maybe_unused kraken3_reset_resume(struct hid_device *hdev)
+{
+ int ret;
+
+ ret = kraken3_init_device(hdev);
+ if (ret)
+ hid_err(hdev, "req init (reset_resume) failed with %d\n", ret);
+
+ return ret;
+}
+
+static int firmware_version_show(struct seq_file *seqf, void *unused)
+{
+ struct kraken3_data *priv = seqf->private;
+
+ seq_printf(seqf, "%u.%u.%u\n", priv->firmware_version[0], priv->firmware_version[1],
+ priv->firmware_version[2]);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware_version);
+
+static void kraken3_debugfs_init(struct kraken3_data *priv, const char *device_name)
+{
+ char name[64];
+
+ if (!priv->firmware_version[0])
+ return; /* Nothing to display in debugfs */
+
+ scnprintf(name, sizeof(name), "%s_%s-%s", DRIVER_NAME, device_name,
+ dev_name(&priv->hdev->dev));
+
+ priv->debugfs = debugfs_create_dir(name, NULL);
+ debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
+}
+
+static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct kraken3_data *priv;
+ const char *device_name;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize ->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for kraken3_read without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /* Enable hidraw so existing user-space tools can continue to work */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ switch (hdev->product) {
+ case USB_PRODUCT_ID_X53:
+ case USB_PRODUCT_ID_X53_SECOND:
+ priv->kind = X53;
+ device_name = "x53";
+ break;
+ case USB_PRODUCT_ID_Z53:
+ priv->kind = Z53;
+ device_name = "z53";
+ break;
+ case USB_PRODUCT_ID_KRAKEN2023:
+ priv->kind = KRAKEN2023;
+ device_name = "kraken2023";
+ break;
+ case USB_PRODUCT_ID_KRAKEN2023_ELITE:
+ priv->kind = KRAKEN2023;
+ device_name = "kraken2023elite";
+ break;
+ default:
+ ret = -ENODEV;
+ goto fail_and_close;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ mutex_init(&priv->buffer_lock);
+ mutex_init(&priv->z53_status_request_lock);
+ init_completion(&priv->fw_version_processed);
+ init_completion(&priv->status_report_processed);
+ spin_lock_init(&priv->status_completion_lock);
+
+ hid_device_io_start(hdev);
+ ret = kraken3_init_device(hdev);
+ if (ret < 0) {
+ hid_err(hdev, "device init failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ ret = kraken3_get_fw_ver(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "fw version request failed with %d\n", ret);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, device_name, priv,
+ &kraken3_chip_info, kraken3_groups);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ kraken3_debugfs_init(priv, device_name);
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void kraken3_remove(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+
+ debugfs_remove_recursive(priv->debugfs);
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id kraken3_table[] = {
+ /* NZXT Kraken X53/X63/X73 have two possible product IDs */
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53_SECOND) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, kraken3_table);
+
+static struct hid_driver kraken3_driver = {
+ .name = DRIVER_NAME,
+ .id_table = kraken3_table,
+ .probe = kraken3_probe,
+ .remove = kraken3_remove,
+ .raw_event = kraken3_raw_event,
+#ifdef CONFIG_PM
+ .reset_resume = kraken3_reset_resume,
+#endif
+};
+
+static int __init kraken3_init(void)
+{
+ return hid_register_driver(&kraken3_driver);
+}
+
+static void __exit kraken3_exit(void)
+{
+ hid_unregister_driver(&kraken3_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(kraken3_init);
+module_exit(kraken3_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers");
diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c
index 7aa586eb74be..58ef9fa0184b 100644
--- a/drivers/hwmon/nzxt-smart2.c
+++ b/drivers/hwmon/nzxt-smart2.c
@@ -14,7 +14,7 @@
#include <linux/wait.h>
#include <asm/byteorder.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
/*
* The device has only 3 fan channels/connectors. But all HID reports have
@@ -721,11 +721,6 @@ static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev)
return init_device(drvdata, drvdata->update_interval);
}
-static void mutex_fini(void *lock)
-{
- mutex_destroy(lock);
-}
-
static int nzxt_smart2_hid_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -741,8 +736,7 @@ static int nzxt_smart2_hid_probe(struct hid_device *hdev,
init_waitqueue_head(&drvdata->wq);
- mutex_init(&drvdata->mutex);
- ret = devm_add_action_or_reset(&hdev->dev, mutex_fini, &drvdata->mutex);
+ ret = devm_mutex_init(&hdev->dev, &drvdata->mutex);
if (ret)
return ret;
@@ -799,6 +793,7 @@ static const struct hid_device_id nzxt_smart2_hid_id_table[] = {
{ HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */
{ HID_USB_DEVICE(0x1e71, 0x2011) }, /* NZXT RGB & Fan Controller (6 RGB) */
{ HID_USB_DEVICE(0x1e71, 0x2019) }, /* NZXT RGB & Fan Controller (6 RGB) */
+ { HID_USB_DEVICE(0x1e71, 0x2020) }, /* NZXT RGB & Fan Controller (6 RGB) */
{},
};
diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index dd690f700d49..b3694a4209b9 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -12,7 +12,7 @@
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/sysfs.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "common.h"
@@ -459,12 +459,10 @@ static ssize_t occ_show_power_1(struct device *dev,
return sysfs_emit(buf, "%llu\n", val);
}
-static u64 occ_get_powr_avg(u64 *accum, u32 *samples)
+static u64 occ_get_powr_avg(u64 accum, u32 samples)
{
- u64 divisor = get_unaligned_be32(samples);
-
- return (divisor == 0) ? 0 :
- div64_u64(get_unaligned_be64(accum) * 1000000ULL, divisor);
+ return (samples == 0) ? 0 :
+ mul_u64_u32_div(accum, 1000000UL, samples);
}
static ssize_t occ_show_power_2(struct device *dev,
@@ -489,8 +487,8 @@ static ssize_t occ_show_power_2(struct device *dev,
get_unaligned_be32(&power->sensor_id),
power->function_id, power->apss_channel);
case 1:
- val = occ_get_powr_avg(&power->accumulator,
- &power->update_tag);
+ val = occ_get_powr_avg(get_unaligned_be64(&power->accumulator),
+ get_unaligned_be32(&power->update_tag));
break;
case 2:
val = (u64)get_unaligned_be32(&power->update_tag) *
@@ -527,8 +525,8 @@ static ssize_t occ_show_power_a0(struct device *dev,
return sysfs_emit(buf, "%u_system\n",
get_unaligned_be32(&power->sensor_id));
case 1:
- val = occ_get_powr_avg(&power->system.accumulator,
- &power->system.update_tag);
+ val = occ_get_powr_avg(get_unaligned_be64(&power->system.accumulator),
+ get_unaligned_be32(&power->system.update_tag));
break;
case 2:
val = (u64)get_unaligned_be32(&power->system.update_tag) *
@@ -541,8 +539,8 @@ static ssize_t occ_show_power_a0(struct device *dev,
return sysfs_emit(buf, "%u_proc\n",
get_unaligned_be32(&power->sensor_id));
case 5:
- val = occ_get_powr_avg(&power->proc.accumulator,
- &power->proc.update_tag);
+ val = occ_get_powr_avg(get_unaligned_be64(&power->proc.accumulator),
+ get_unaligned_be32(&power->proc.update_tag));
break;
case 6:
val = (u64)get_unaligned_be32(&power->proc.update_tag) *
@@ -555,8 +553,8 @@ static ssize_t occ_show_power_a0(struct device *dev,
return sysfs_emit(buf, "%u_vdd\n",
get_unaligned_be32(&power->sensor_id));
case 9:
- val = occ_get_powr_avg(&power->vdd.accumulator,
- &power->vdd.update_tag);
+ val = occ_get_powr_avg(get_unaligned_be64(&power->vdd.accumulator),
+ get_unaligned_be32(&power->vdd.update_tag));
break;
case 10:
val = (u64)get_unaligned_be32(&power->vdd.update_tag) *
@@ -569,8 +567,8 @@ static ssize_t occ_show_power_a0(struct device *dev,
return sysfs_emit(buf, "%u_vdn\n",
get_unaligned_be32(&power->sensor_id));
case 13:
- val = occ_get_powr_avg(&power->vdn.accumulator,
- &power->vdn.update_tag);
+ val = occ_get_powr_avg(get_unaligned_be64(&power->vdn.accumulator),
+ get_unaligned_be32(&power->vdn.update_tag));
break;
case 14:
val = (u64)get_unaligned_be32(&power->vdn.update_tag) *
@@ -747,29 +745,30 @@ static ssize_t occ_show_extended(struct device *dev,
}
/*
- * Some helper macros to make it easier to define an occ_attribute. Since these
- * are dynamically allocated, we shouldn't use the existing kernel macros which
+ * A helper to make it easier to define an occ_attribute. Since these
+ * are dynamically allocated, we cannot use the existing kernel macros which
* stringify the name argument.
*/
-#define ATTR_OCC(_name, _mode, _show, _store) { \
- .attr = { \
- .name = _name, \
- .mode = VERIFY_OCTAL_PERMISSIONS(_mode), \
- }, \
- .show = _show, \
- .store = _store, \
-}
-
-#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) { \
- .dev_attr = ATTR_OCC(_name, _mode, _show, _store), \
- .index = _index, \
- .nr = _nr, \
+static void occ_init_attribute(struct occ_attribute *attr, int mode,
+ ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf),
+ ssize_t (*store)(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count),
+ int nr, int index, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(attr->name, sizeof(attr->name), fmt, args);
+ va_end(args);
+
+ attr->sensor.dev_attr.attr.name = attr->name;
+ attr->sensor.dev_attr.attr.mode = mode;
+ attr->sensor.dev_attr.show = show;
+ attr->sensor.dev_attr.store = store;
+ attr->sensor.index = index;
+ attr->sensor.nr = nr;
}
-#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \
- ((struct sensor_device_attribute_2) \
- SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index))
-
/*
* Allocate and instatiate sensor_device_attribute_2s. It's most efficient to
* use our own instead of the built-in hwmon attribute types.
@@ -855,14 +854,15 @@ static int occ_setup_sensor_attrs(struct occ *occ)
sensors->extended.num_sensors = 0;
}
- occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs,
+ occ->attrs = devm_kcalloc(dev, num_attrs, sizeof(*occ->attrs),
GFP_KERNEL);
if (!occ->attrs)
return -ENOMEM;
/* null-terminated list */
- occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) *
- num_attrs + 1, GFP_KERNEL);
+ occ->group.attrs = devm_kcalloc(dev, num_attrs + 1,
+ sizeof(*occ->group.attrs),
+ GFP_KERNEL);
if (!occ->group.attrs)
return -ENOMEM;
@@ -872,43 +872,33 @@ static int occ_setup_sensor_attrs(struct occ *occ)
s = i + 1;
temp = ((struct temp_sensor_2 *)sensors->temp.data) + i;
- snprintf(attr->name, sizeof(attr->name), "temp%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
- 0, i);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 0, i, "temp%d_label", s);
attr++;
if (sensors->temp.version == 2 &&
temp->fru_type == OCC_FRU_TYPE_VRM) {
- snprintf(attr->name, sizeof(attr->name),
- "temp%d_alarm", s);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 1, i, "temp%d_alarm", s);
} else {
- snprintf(attr->name, sizeof(attr->name),
- "temp%d_input", s);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 1, i, "temp%d_input", s);
}
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
- 1, i);
attr++;
if (sensors->temp.version > 1) {
- snprintf(attr->name, sizeof(attr->name),
- "temp%d_fru_type", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_temp, NULL, 2, i);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 2, i, "temp%d_fru_type", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "temp%d_fault", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_temp, NULL, 3, i);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 3, i, "temp%d_fault", s);
attr++;
if (sensors->temp.version == 0x10) {
- snprintf(attr->name, sizeof(attr->name),
- "temp%d_max", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_temp, NULL,
- 4, i);
+ occ_init_attribute(attr, 0444, show_temp, NULL,
+ 4, i, "temp%d_max", s);
attr++;
}
}
@@ -917,14 +907,12 @@ static int occ_setup_sensor_attrs(struct occ *occ)
for (i = 0; i < sensors->freq.num_sensors; ++i) {
s = i + 1;
- snprintf(attr->name, sizeof(attr->name), "freq%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
- 0, i);
+ occ_init_attribute(attr, 0444, show_freq, NULL,
+ 0, i, "freq%d_label", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "freq%d_input", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
- 1, i);
+ occ_init_attribute(attr, 0444, show_freq, NULL,
+ 1, i, "freq%d_input", s);
attr++;
}
@@ -940,32 +928,24 @@ static int occ_setup_sensor_attrs(struct occ *occ)
s = (i * 4) + 1;
for (j = 0; j < 4; ++j) {
- snprintf(attr->name, sizeof(attr->name),
- "power%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL,
- nr++, i);
+ occ_init_attribute(attr, 0444, show_power,
+ NULL, nr++, i,
+ "power%d_label", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_average", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL,
- nr++, i);
+ occ_init_attribute(attr, 0444, show_power,
+ NULL, nr++, i,
+ "power%d_average", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_average_interval", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL,
- nr++, i);
+ occ_init_attribute(attr, 0444, show_power,
+ NULL, nr++, i,
+ "power%d_average_interval", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_input", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL,
- nr++, i);
+ occ_init_attribute(attr, 0444, show_power,
+ NULL, nr++, i,
+ "power%d_input", s);
attr++;
s++;
@@ -977,28 +957,20 @@ static int occ_setup_sensor_attrs(struct occ *occ)
for (i = 0; i < sensors->power.num_sensors; ++i) {
s = i + 1;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL, 0, i);
+ occ_init_attribute(attr, 0444, show_power, NULL,
+ 0, i, "power%d_label", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_average", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL, 1, i);
+ occ_init_attribute(attr, 0444, show_power, NULL,
+ 1, i, "power%d_average", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_average_interval", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL, 2, i);
+ occ_init_attribute(attr, 0444, show_power, NULL,
+ 2, i, "power%d_average_interval", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_input", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_power, NULL, 3, i);
+ occ_init_attribute(attr, 0444, show_power, NULL,
+ 3, i, "power%d_input", s);
attr++;
}
@@ -1006,56 +978,43 @@ static int occ_setup_sensor_attrs(struct occ *occ)
}
if (sensors->caps.num_sensors >= 1) {
- snprintf(attr->name, sizeof(attr->name), "power%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 0, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 0, 0, "power%d_label", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "power%d_cap", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 1, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 1, 0, "power%d_cap", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "power%d_input", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 2, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 2, 0, "power%d_input", s);
attr++;
- snprintf(attr->name, sizeof(attr->name),
- "power%d_cap_not_redundant", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 3, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 3, 0, "power%d_cap_not_redundant", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "power%d_cap_max", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 4, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 4, 0, "power%d_cap_max", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "power%d_cap_min", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
- 5, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 5, 0, "power%d_cap_min", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "power%d_cap_user",
- s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps,
- occ_store_caps_user, 6, 0);
+ occ_init_attribute(attr, 0644, show_caps, occ_store_caps_user,
+ 6, 0, "power%d_cap_user", s);
attr++;
if (sensors->caps.version > 1) {
- snprintf(attr->name, sizeof(attr->name),
- "power%d_cap_user_source", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_caps, NULL, 7, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 7, 0, "power%d_cap_user_source", s);
attr++;
if (sensors->caps.version > 2) {
- snprintf(attr->name, sizeof(attr->name),
- "power%d_cap_min_soft", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- show_caps, NULL,
- 8, 0);
+ occ_init_attribute(attr, 0444, show_caps, NULL,
+ 8, 0,
+ "power%d_cap_min_soft", s);
attr++;
}
}
@@ -1064,19 +1023,16 @@ static int occ_setup_sensor_attrs(struct occ *occ)
for (i = 0; i < sensors->extended.num_sensors; ++i) {
s = i + 1;
- snprintf(attr->name, sizeof(attr->name), "extn%d_label", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- occ_show_extended, NULL, 0, i);
+ occ_init_attribute(attr, 0444, occ_show_extended, NULL,
+ 0, i, "extn%d_label", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- occ_show_extended, NULL, 1, i);
+ occ_init_attribute(attr, 0444, occ_show_extended, NULL,
+ 1, i, "extn%d_flags", s);
attr++;
- snprintf(attr->name, sizeof(attr->name), "extn%d_input", s);
- attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
- occ_show_extended, NULL, 2, i);
+ occ_init_attribute(attr, 0444, occ_show_extended, NULL,
+ 2, i, "extn%d_input", s);
attr++;
}
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 06095975f5c8..5817a099c622 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -8,7 +8,7 @@
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/sched.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "common.h"
@@ -241,7 +241,6 @@ static const struct of_device_id p8_i2c_occ_of_match[] = {
MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
static struct i2c_driver p8_i2c_occ_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "occ-hwmon",
.of_match_table = p8_i2c_occ_of_match,
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index 96521363b696..1e3749dfa598 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -30,7 +30,7 @@ struct p9_sbe_occ {
#define to_p9_sbe_occ(x) container_of((x), struct p9_sbe_occ, occ)
static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *battr, char *buf, loff_t pos,
+ const struct bin_attribute *battr, char *buf, loff_t pos,
size_t count)
{
ssize_t rc = 0;
@@ -48,7 +48,7 @@ static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
return rc;
}
-static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
+static const BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ *ctx, const void *resp,
size_t resp_len)
@@ -167,7 +167,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
return rc;
}
-static int p9_sbe_occ_remove(struct platform_device *pdev)
+static void p9_sbe_occ_remove(struct platform_device *pdev)
{
struct occ *occ = platform_get_drvdata(pdev);
struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
@@ -178,8 +178,6 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
occ_shutdown(occ);
kvfree(ctx->ffdc);
-
- return 0;
}
static const struct of_device_id p9_sbe_occ_of_match[] = {
@@ -194,7 +192,7 @@ static struct platform_driver p9_sbe_occ_driver = {
.name = "occ-hwmon",
.of_match_table = p9_sbe_occ_of_match,
},
- .probe = p9_sbe_occ_probe,
+ .probe = p9_sbe_occ_probe,
.remove = p9_sbe_occ_remove,
};
diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c
deleted file mode 100644
index ea9602063eab..000000000000
--- a/drivers/hwmon/oxp-sensors.c
+++ /dev/null
@@ -1,493 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Platform driver for OneXPlayer, AOK ZOE, and Aya Neo Handhelds that expose
- * fan reading and control via hwmon sysfs.
- *
- * Old OXP boards have the same DMI strings and they are told apart by
- * the boot cpu vendor (Intel/AMD). Currently only AMD boards are
- * supported but the code is made to be simple to add other handheld
- * boards in the future.
- * Fan control is provided via pwm interface in the range [0-255].
- * Old AMD boards use [0-100] as range in the EC, the written value is
- * scaled to accommodate for that. Newer boards like the mini PRO and
- * AOK ZOE are not scaled but have the same EC layout.
- *
- * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
- */
-
-#include <linux/acpi.h>
-#include <linux/dmi.h>
-#include <linux/hwmon.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/processor.h>
-
-/* Handle ACPI lock mechanism */
-static u32 oxp_mutex;
-
-#define ACPI_LOCK_DELAY_MS 500
-
-static bool lock_global_acpi_lock(void)
-{
- return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
-}
-
-static bool unlock_global_acpi_lock(void)
-{
- return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
-}
-
-enum oxp_board {
- aok_zoe_a1 = 1,
- aya_neo_2,
- aya_neo_air,
- aya_neo_air_pro,
- aya_neo_geek,
- oxp_mini_amd,
- oxp_mini_amd_a07,
- oxp_mini_amd_pro,
-};
-
-static enum oxp_board board;
-
-/* Fan reading and PWM */
-#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */
-#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */
-#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */
-
-/* Turbo button takeover function
- * Older boards have different values and EC registers
- * for the same function
- */
-#define OXP_OLD_TURBO_SWITCH_REG 0x1E
-#define OXP_OLD_TURBO_TAKE_VAL 0x01
-#define OXP_OLD_TURBO_RETURN_VAL 0x00
-
-#define OXP_TURBO_SWITCH_REG 0xF1
-#define OXP_TURBO_TAKE_VAL 0x40
-#define OXP_TURBO_RETURN_VAL 0x00
-
-static const struct dmi_system_id dmi_table[] = {
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
- },
- .driver_data = (void *)aok_zoe_a1,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
- },
- .driver_data = (void *)aok_zoe_a1,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
- },
- .driver_data = (void *)aya_neo_2,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
- },
- .driver_data = (void *)aya_neo_air,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
- },
- .driver_data = (void *)aya_neo_air_pro,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"),
- },
- .driver_data = (void *)aya_neo_geek,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
- },
- .driver_data = (void *)oxp_mini_amd,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
- },
- .driver_data = (void *)oxp_mini_amd_a07,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
- },
- .driver_data = (void *)oxp_mini_amd_pro,
- },
- {},
-};
-
-/* Helper functions to handle EC read/write */
-static int read_from_ec(u8 reg, int size, long *val)
-{
- int i;
- int ret;
- u8 buffer;
-
- if (!lock_global_acpi_lock())
- return -EBUSY;
-
- *val = 0;
- for (i = 0; i < size; i++) {
- ret = ec_read(reg + i, &buffer);
- if (ret)
- return ret;
- *val <<= i * 8;
- *val += buffer;
- }
-
- if (!unlock_global_acpi_lock())
- return -EBUSY;
-
- return 0;
-}
-
-static int write_to_ec(u8 reg, u8 value)
-{
- int ret;
-
- if (!lock_global_acpi_lock())
- return -EBUSY;
-
- ret = ec_write(reg, value);
-
- if (!unlock_global_acpi_lock())
- return -EBUSY;
-
- return ret;
-}
-
-/* Turbo button toggle functions */
-static int tt_toggle_enable(void)
-{
- u8 reg;
- u8 val;
-
- switch (board) {
- case oxp_mini_amd_a07:
- reg = OXP_OLD_TURBO_SWITCH_REG;
- val = OXP_OLD_TURBO_TAKE_VAL;
- break;
- case oxp_mini_amd_pro:
- case aok_zoe_a1:
- reg = OXP_TURBO_SWITCH_REG;
- val = OXP_TURBO_TAKE_VAL;
- break;
- default:
- return -EINVAL;
- }
- return write_to_ec(reg, val);
-}
-
-static int tt_toggle_disable(void)
-{
- u8 reg;
- u8 val;
-
- switch (board) {
- case oxp_mini_amd_a07:
- reg = OXP_OLD_TURBO_SWITCH_REG;
- val = OXP_OLD_TURBO_RETURN_VAL;
- break;
- case oxp_mini_amd_pro:
- case aok_zoe_a1:
- reg = OXP_TURBO_SWITCH_REG;
- val = OXP_TURBO_RETURN_VAL;
- break;
- default:
- return -EINVAL;
- }
- return write_to_ec(reg, val);
-}
-
-/* Callbacks for turbo toggle attribute */
-static umode_t tt_toggle_is_visible(struct kobject *kobj,
- struct attribute *attr, int n)
-{
- switch (board) {
- case aok_zoe_a1:
- case oxp_mini_amd_a07:
- case oxp_mini_amd_pro:
- return attr->mode;
- default:
- break;
- }
- return 0;
-}
-
-static ssize_t tt_toggle_store(struct device *dev,
- struct device_attribute *attr, const char *buf,
- size_t count)
-{
- int rval;
- bool value;
-
- rval = kstrtobool(buf, &value);
- if (rval)
- return rval;
-
- if (value) {
- rval = tt_toggle_enable();
- } else {
- rval = tt_toggle_disable();
- }
- if (rval)
- return rval;
-
- return count;
-}
-
-static ssize_t tt_toggle_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- int retval;
- u8 reg;
- long val;
-
- switch (board) {
- case oxp_mini_amd_a07:
- reg = OXP_OLD_TURBO_SWITCH_REG;
- break;
- case oxp_mini_amd_pro:
- case aok_zoe_a1:
- reg = OXP_TURBO_SWITCH_REG;
- break;
- default:
- return -EINVAL;
- }
-
- retval = read_from_ec(reg, 1, &val);
- if (retval)
- return retval;
-
- return sysfs_emit(buf, "%d\n", !!val);
-}
-
-static DEVICE_ATTR_RW(tt_toggle);
-
-/* PWM enable/disable functions */
-static int oxp_pwm_enable(void)
-{
- return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01);
-}
-
-static int oxp_pwm_disable(void)
-{
- return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00);
-}
-
-/* Callbacks for hwmon interface */
-static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
- enum hwmon_sensor_types type, u32 attr, int channel)
-{
- switch (type) {
- case hwmon_fan:
- return 0444;
- case hwmon_pwm:
- return 0644;
- default:
- return 0;
- }
-}
-
-static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long *val)
-{
- int ret;
-
- switch (type) {
- case hwmon_fan:
- switch (attr) {
- case hwmon_fan_input:
- return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
- default:
- break;
- }
- break;
- case hwmon_pwm:
- switch (attr) {
- case hwmon_pwm_input:
- ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
- if (ret)
- return ret;
- switch (board) {
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_pro:
- case aya_neo_geek:
- case oxp_mini_amd:
- case oxp_mini_amd_a07:
- *val = (*val * 255) / 100;
- break;
- case oxp_mini_amd_pro:
- case aok_zoe_a1:
- default:
- break;
- }
- return 0;
- case hwmon_pwm_enable:
- return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
- default:
- break;
- }
- break;
- default:
- break;
- }
- return -EOPNOTSUPP;
-}
-
-static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long val)
-{
- switch (type) {
- case hwmon_pwm:
- switch (attr) {
- case hwmon_pwm_enable:
- if (val == 1)
- return oxp_pwm_enable();
- else if (val == 0)
- return oxp_pwm_disable();
- return -EINVAL;
- case hwmon_pwm_input:
- if (val < 0 || val > 255)
- return -EINVAL;
- switch (board) {
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_pro:
- case aya_neo_geek:
- case oxp_mini_amd:
- case oxp_mini_amd_a07:
- val = (val * 100) / 255;
- break;
- case aok_zoe_a1:
- case oxp_mini_amd_pro:
- default:
- break;
- }
- return write_to_ec(OXP_SENSOR_PWM_REG, val);
- default:
- break;
- }
- break;
- default:
- break;
- }
- return -EOPNOTSUPP;
-}
-
-/* Known sensors in the OXP EC controllers */
-static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
- HWMON_CHANNEL_INFO(fan,
- HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm,
- HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
- NULL,
-};
-
-static struct attribute *oxp_ec_attrs[] = {
- &dev_attr_tt_toggle.attr,
- NULL
-};
-
-static struct attribute_group oxp_ec_attribute_group = {
- .is_visible = tt_toggle_is_visible,
- .attrs = oxp_ec_attrs,
-};
-
-static const struct attribute_group *oxp_ec_groups[] = {
- &oxp_ec_attribute_group,
- NULL
-};
-
-static const struct hwmon_ops oxp_ec_hwmon_ops = {
- .is_visible = oxp_ec_hwmon_is_visible,
- .read = oxp_platform_read,
- .write = oxp_platform_write,
-};
-
-static const struct hwmon_chip_info oxp_ec_chip_info = {
- .ops = &oxp_ec_hwmon_ops,
- .info = oxp_platform_sensors,
-};
-
-/* Initialization logic */
-static int oxp_platform_probe(struct platform_device *pdev)
-{
- struct device *dev = &pdev->dev;
- struct device *hwdev;
-
- hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
- &oxp_ec_chip_info, NULL);
-
- return PTR_ERR_OR_ZERO(hwdev);
-}
-
-static struct platform_driver oxp_platform_driver = {
- .driver = {
- .name = "oxp-platform",
- .dev_groups = oxp_ec_groups,
- },
- .probe = oxp_platform_probe,
-};
-
-static struct platform_device *oxp_platform_device;
-
-static int __init oxp_platform_init(void)
-{
- const struct dmi_system_id *dmi_entry;
-
- /*
- * Have to check for AMD processor here because DMI strings are the
- * same between Intel and AMD boards, the only way to tell them apart
- * is the CPU.
- * Intel boards seem to have different EC registers and values to
- * read/write.
- */
- dmi_entry = dmi_first_match(dmi_table);
- if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
- return -ENODEV;
-
- board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
-
- oxp_platform_device =
- platform_create_bundle(&oxp_platform_driver,
- oxp_platform_probe, NULL, 0, NULL, 0);
-
- return PTR_ERR_OR_ZERO(oxp_platform_device);
-}
-
-static void __exit oxp_platform_exit(void)
-{
- platform_device_unregister(oxp_platform_device);
- platform_driver_unregister(&oxp_platform_driver);
-}
-
-MODULE_DEVICE_TABLE(dmi, dmi_table);
-
-module_init(oxp_platform_init);
-module_exit(oxp_platform_exit);
-
-MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
-MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
-MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c
index a4adc8bd531f..0f8aa6b42164 100644
--- a/drivers/hwmon/pc87360.c
+++ b/drivers/hwmon/pc87360.c
@@ -323,7 +323,11 @@ static struct pc87360_data *pc87360_update_device(struct device *dev)
}
/* Voltages */
- for (i = 0; i < data->innr; i++) {
+ /*
+ * The min() below does not have any practical meaning and is
+ * only needed to silence a warning observed with gcc 12+.
+ */
+ for (i = 0; i < min(data->innr, ARRAY_SIZE(data->in)); i++) {
data->in_status[i] = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS);
/* Clear bits */
@@ -1311,7 +1315,7 @@ static void pc87360_init_device(struct platform_device *pdev,
(reg & 0xC0) | 0x11);
}
- nr = data->innr < 11 ? data->innr : 11;
+ nr = min(data->innr, 11);
for (i = 0; i < nr; i++) {
reg = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS);
@@ -1586,14 +1590,12 @@ error:
return err;
}
-static int pc87360_remove(struct platform_device *pdev)
+static void pc87360_remove(struct platform_device *pdev)
{
struct pc87360_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
pc87360_remove_files(&pdev->dev);
-
- return 0;
}
/*
diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c
index eaab83d879fb..571402a89368 100644
--- a/drivers/hwmon/pc87427.c
+++ b/drivers/hwmon/pc87427.c
@@ -1115,14 +1115,12 @@ exit_remove_files:
return err;
}
-static int pc87427_remove(struct platform_device *pdev)
+static void pc87427_remove(struct platform_device *pdev)
{
struct pc87427_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
pc87427_remove_files(&pdev->dev);
-
- return 0;
}
diff --git a/drivers/hwmon/pcf8591.c b/drivers/hwmon/pcf8591.c
index 66c76b28c9e0..167d2fe4d543 100644
--- a/drivers/hwmon/pcf8591.c
+++ b/drivers/hwmon/pcf8591.c
@@ -285,7 +285,7 @@ static int pcf8591_read_channel(struct device *dev, int channel)
}
static const struct i2c_device_id pcf8591_id[] = {
- { "pcf8591", 0 },
+ { "pcf8591" },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8591_id);
diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h
index 734506b0eca2..92a7ee1925bc 100644
--- a/drivers/hwmon/peci/common.h
+++ b/drivers/hwmon/peci/common.h
@@ -1,7 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2021 Intel Corporation */
-#include <linux/mutex.h>
#include <linux/types.h>
#ifndef __PECI_HWMON_COMMON_H
@@ -13,12 +12,10 @@
* struct peci_sensor_state - PECI state information
* @valid: flag to indicate the sensor value is valid
* @last_updated: time of the last update in jiffies
- * @lock: mutex to protect sensor access
*/
struct peci_sensor_state {
bool valid;
unsigned long last_updated;
- struct mutex lock; /* protect sensor access */
};
/**
diff --git a/drivers/hwmon/peci/cputemp.c b/drivers/hwmon/peci/cputemp.c
index e5b65a382772..b2fc936851e1 100644
--- a/drivers/hwmon/peci/cputemp.c
+++ b/drivers/hwmon/peci/cputemp.c
@@ -116,11 +116,9 @@ static int get_temp_target(struct peci_cputemp *priv, enum peci_temp_target_type
{
int ret;
- mutex_lock(&priv->temp.target.state.lock);
-
ret = update_temp_target(priv);
if (ret)
- goto unlock;
+ return ret;
switch (type) {
case tcontrol_type:
@@ -139,9 +137,6 @@ static int get_temp_target(struct peci_cputemp *priv, enum peci_temp_target_type
ret = -EOPNOTSUPP;
break;
}
-unlock:
- mutex_unlock(&priv->temp.target.state.lock);
-
return ret;
}
@@ -177,26 +172,23 @@ static s32 dts_eight_dot_eight_to_millidegree(u16 val)
static int get_die_temp(struct peci_cputemp *priv, long *val)
{
- int ret = 0;
long tjmax;
u16 temp;
+ int ret;
- mutex_lock(&priv->temp.die.state.lock);
if (!peci_sensor_need_update(&priv->temp.die.state))
goto skip_update;
ret = peci_temp_read(priv->peci_dev, &temp);
if (ret)
- goto err_unlock;
+ return ret;
- if (!dts_valid(temp)) {
- ret = -EIO;
- goto err_unlock;
- }
+ if (!dts_valid(temp))
+ return -EIO;
ret = get_temp_target(priv, tjmax_type, &tjmax);
if (ret)
- goto err_unlock;
+ return ret;
priv->temp.die.value = (s32)tjmax + dts_ten_dot_six_to_millidegree(temp);
@@ -204,35 +196,30 @@ static int get_die_temp(struct peci_cputemp *priv, long *val)
skip_update:
*val = priv->temp.die.value;
-err_unlock:
- mutex_unlock(&priv->temp.die.state.lock);
- return ret;
+ return 0;
}
static int get_dts(struct peci_cputemp *priv, long *val)
{
- int ret = 0;
u16 thermal_margin;
long tcontrol;
u32 pcs;
+ int ret;
- mutex_lock(&priv->temp.dts.state.lock);
if (!peci_sensor_need_update(&priv->temp.dts.state))
goto skip_update;
ret = peci_pcs_read(priv->peci_dev, PECI_PCS_THERMAL_MARGIN, 0, &pcs);
if (ret)
- goto err_unlock;
+ return ret;
thermal_margin = FIELD_GET(DTS_MARGIN_MASK, pcs);
- if (!dts_valid(thermal_margin)) {
- ret = -EIO;
- goto err_unlock;
- }
+ if (!dts_valid(thermal_margin))
+ return -EIO;
ret = get_temp_target(priv, tcontrol_type, &tcontrol);
if (ret)
- goto err_unlock;
+ return ret;
/* Note that the tcontrol should be available before calling it */
priv->temp.dts.value =
@@ -242,35 +229,30 @@ static int get_dts(struct peci_cputemp *priv, long *val)
skip_update:
*val = priv->temp.dts.value;
-err_unlock:
- mutex_unlock(&priv->temp.dts.state.lock);
- return ret;
+ return 0;
}
static int get_core_temp(struct peci_cputemp *priv, int core_index, long *val)
{
- int ret = 0;
u16 core_dts_margin;
long tjmax;
u32 pcs;
+ int ret;
- mutex_lock(&priv->temp.core[core_index].state.lock);
if (!peci_sensor_need_update(&priv->temp.core[core_index].state))
goto skip_update;
ret = peci_pcs_read(priv->peci_dev, PECI_PCS_MODULE_TEMP, core_index, &pcs);
if (ret)
- goto err_unlock;
+ return ret;
core_dts_margin = FIELD_GET(PCS_MODULE_TEMP_MASK, pcs);
- if (!dts_valid(core_dts_margin)) {
- ret = -EIO;
- goto err_unlock;
- }
+ if (!dts_valid(core_dts_margin))
+ return -EIO;
ret = get_temp_target(priv, tjmax_type, &tjmax);
if (ret)
- goto err_unlock;
+ return ret;
/* Note that the tjmax should be available before calling it */
priv->temp.core[core_index].value =
@@ -280,9 +262,7 @@ static int get_core_temp(struct peci_cputemp *priv, int core_index, long *val)
skip_update:
*val = priv->temp.core[core_index].value;
-err_unlock:
- mutex_unlock(&priv->temp.core[core_index].state.lock);
- return ret;
+ return 0;
}
static int cputemp_read_string(struct device *dev, enum hwmon_sensor_types type,
@@ -360,9 +340,11 @@ static int init_core_mask(struct peci_cputemp *priv)
int ret;
/* Get the RESOLVED_CORES register value */
- switch (peci_dev->info.model) {
- case INTEL_FAM6_ICELAKE_X:
- case INTEL_FAM6_ICELAKE_D:
+ switch (peci_dev->info.x86_vfm) {
+ case INTEL_ICELAKE_X:
+ case INTEL_ICELAKE_D:
+ case INTEL_SAPPHIRERAPIDS_X:
+ case INTEL_EMERALDRAPIDS_X:
ret = peci_ep_pci_local_read(peci_dev, 0, reg->bus, reg->dev,
reg->func, reg->offset + 4, &data);
if (ret)
@@ -429,18 +411,6 @@ static void check_resolved_cores(struct peci_cputemp *priv)
bitmap_zero(priv->core_mask, CORE_NUMS_MAX);
}
-static void sensor_init(struct peci_cputemp *priv)
-{
- int i;
-
- mutex_init(&priv->temp.target.state.lock);
- mutex_init(&priv->temp.die.state.lock);
- mutex_init(&priv->temp.dts.state.lock);
-
- for_each_set_bit(i, priv->core_mask, CORE_NUMS_MAX)
- mutex_init(&priv->temp.core[i].state.lock);
-}
-
static const struct hwmon_ops peci_cputemp_ops = {
.is_visible = cputemp_is_visible,
.read_string = cputemp_read_string,
@@ -505,8 +475,6 @@ static int peci_cputemp_probe(struct auxiliary_device *adev,
check_resolved_cores(priv);
- sensor_init(priv);
-
hwmon_dev = devm_hwmon_device_register_with_info(priv->dev, priv->name,
priv, &peci_cputemp_chip_info, NULL);
@@ -531,6 +499,20 @@ static struct resolved_cores_reg resolved_cores_reg_icx = {
.offset = 0xd0,
};
+static struct resolved_cores_reg resolved_cores_reg_spr = {
+ .bus = 31,
+ .dev = 30,
+ .func = 6,
+ .offset = 0x80,
+};
+
+static struct resolved_cores_reg resolved_cores_reg_emr = {
+ .bus = 31,
+ .dev = 30,
+ .func = 6,
+ .offset = 0x80,
+};
+
static const struct cpu_info cpu_hsx = {
.reg = &resolved_cores_reg_hsx,
.min_peci_revision = 0x33,
@@ -549,6 +531,18 @@ static const struct cpu_info cpu_icx = {
.thermal_margin_to_millidegree = &dts_ten_dot_six_to_millidegree,
};
+static const struct cpu_info cpu_spr = {
+ .reg = &resolved_cores_reg_spr,
+ .min_peci_revision = 0x40,
+ .thermal_margin_to_millidegree = &dts_ten_dot_six_to_millidegree,
+};
+
+static const struct cpu_info cpu_emr = {
+ .reg = &resolved_cores_reg_emr,
+ .min_peci_revision = 0x40,
+ .thermal_margin_to_millidegree = &dts_ten_dot_six_to_millidegree,
+};
+
static const struct auxiliary_device_id peci_cputemp_ids[] = {
{
.name = "peci_cpu.cputemp.hsx",
@@ -574,6 +568,14 @@ static const struct auxiliary_device_id peci_cputemp_ids[] = {
.name = "peci_cpu.cputemp.icxd",
.driver_data = (kernel_ulong_t)&cpu_icx,
},
+ {
+ .name = "peci_cpu.cputemp.spr",
+ .driver_data = (kernel_ulong_t)&cpu_spr,
+ },
+ {
+ .name = "peci_cpu.cputemp.emr",
+ .driver_data = (kernel_ulong_t)&cpu_emr,
+ },
{ }
};
MODULE_DEVICE_TABLE(auxiliary, peci_cputemp_ids);
@@ -589,4 +591,4 @@ MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
MODULE_DESCRIPTION("PECI cputemp driver");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PECI_CPU);
+MODULE_IMPORT_NS("PECI_CPU");
diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c
index ce89da3937a0..bd3e8715dfec 100644
--- a/drivers/hwmon/peci/dimmtemp.c
+++ b/drivers/hwmon/peci/dimmtemp.c
@@ -30,6 +30,10 @@
#define DIMM_IDX_MAX_ON_ICX 2
#define CHAN_RANK_MAX_ON_ICXD 4
#define DIMM_IDX_MAX_ON_ICXD 2
+#define CHAN_RANK_MAX_ON_SPR 8
+#define DIMM_IDX_MAX_ON_SPR 2
+#define CHAN_RANK_MAX_ON_EMR 8
+#define DIMM_IDX_MAX_ON_EMR 2
#define CHAN_RANK_MAX CHAN_RANK_MAX_ON_HSX
#define DIMM_IDX_MAX DIMM_IDX_MAX_ON_HSX
@@ -45,7 +49,7 @@
#define GET_TEMP_MAX(x) (((x) & DIMM_TEMP_MAX) >> 8)
#define GET_TEMP_CRIT(x) (((x) & DIMM_TEMP_CRIT) >> 16)
-#define NO_DIMM_RETRY_COUNT_MAX 5
+#define NO_DIMM_RETRY_COUNT_MAX 120
struct peci_dimmtemp;
@@ -92,16 +96,15 @@ static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no, long *val)
{
int dimm_order = dimm_no % priv->gen_info->dimm_idx_max;
int chan_rank = dimm_no / priv->gen_info->dimm_idx_max;
- int ret = 0;
u32 data;
+ int ret;
- mutex_lock(&priv->dimm[dimm_no].temp.state.lock);
if (!peci_sensor_need_update(&priv->dimm[dimm_no].temp.state))
goto skip_update;
ret = peci_pcs_read(priv->peci_dev, PECI_PCS_DDR_DIMM_TEMP, chan_rank, &data);
if (ret)
- goto unlock;
+ return ret;
priv->dimm[dimm_no].temp.value = __dimm_temp(data, dimm_order) * MILLIDEGREE_PER_DEGREE;
@@ -109,9 +112,7 @@ static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no, long *val)
skip_update:
*val = priv->dimm[dimm_no].temp.value;
-unlock:
- mutex_unlock(&priv->dimm[dimm_no].temp.state.lock);
- return ret;
+ return 0;
}
static int update_thresholds(struct peci_dimmtemp *priv, int dimm_no)
@@ -125,8 +126,6 @@ static int update_thresholds(struct peci_dimmtemp *priv, int dimm_no)
return 0;
ret = priv->gen_info->read_thresholds(priv, dimm_order, chan_rank, &data);
- if (ret == -ENODATA) /* Use default or previous value */
- return 0;
if (ret)
return ret;
@@ -143,10 +142,9 @@ static int get_dimm_thresholds(struct peci_dimmtemp *priv, enum peci_dimm_thresh
{
int ret;
- mutex_lock(&priv->dimm[dimm_no].thresholds.state.lock);
ret = update_thresholds(priv, dimm_no);
if (ret)
- goto unlock;
+ return ret;
switch (type) {
case temp_max_type:
@@ -159,9 +157,6 @@ static int get_dimm_thresholds(struct peci_dimmtemp *priv, enum peci_dimm_thresh
ret = -EOPNOTSUPP;
break;
}
-unlock:
- mutex_unlock(&priv->dimm[dimm_no].thresholds.state.lock);
-
return ret;
}
@@ -347,8 +342,6 @@ static int create_dimm_temp_info(struct peci_dimmtemp *priv)
ret = create_dimm_temp_label(priv, i);
if (ret)
return ret;
- mutex_init(&priv->dimm[i].thresholds.state.lock);
- mutex_init(&priv->dimm[i].temp.state.lock);
}
dev = devm_hwmon_device_register_with_info(priv->dev, priv->name, priv,
@@ -507,11 +500,11 @@ read_thresholds_icx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u
ret = peci_ep_pci_local_read(priv->peci_dev, 0, 13, 0, 2, 0xd4, &reg_val);
if (ret || !(reg_val & BIT(31)))
- return -ENODATA; /* Use default or previous value */
+ return -ENODATA;
ret = peci_ep_pci_local_read(priv->peci_dev, 0, 13, 0, 2, 0xd0, &reg_val);
if (ret)
- return -ENODATA; /* Use default or previous value */
+ return -ENODATA;
/*
* Device 26, Offset 224e0: IMC 0 channel 0 -> rank 0
@@ -534,6 +527,49 @@ read_thresholds_icx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u
return 0;
}
+static int
+read_thresholds_spr(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data)
+{
+ u32 reg_val;
+ u64 offset;
+ int ret;
+ u8 dev;
+
+ ret = peci_ep_pci_local_read(priv->peci_dev, 0, 30, 0, 2, 0xd4, &reg_val);
+ if (ret || !(reg_val & BIT(31)))
+ return -ENODATA;
+
+ ret = peci_ep_pci_local_read(priv->peci_dev, 0, 30, 0, 2, 0xd0, &reg_val);
+ if (ret)
+ return -ENODATA;
+
+ /*
+ * Device 26, Offset 219a8: IMC 0 channel 0 -> rank 0
+ * Device 26, Offset 299a8: IMC 0 channel 1 -> rank 1
+ * Device 27, Offset 219a8: IMC 1 channel 0 -> rank 2
+ * Device 27, Offset 299a8: IMC 1 channel 1 -> rank 3
+ * Device 28, Offset 219a8: IMC 2 channel 0 -> rank 4
+ * Device 28, Offset 299a8: IMC 2 channel 1 -> rank 5
+ * Device 29, Offset 219a8: IMC 3 channel 0 -> rank 6
+ * Device 29, Offset 299a8: IMC 3 channel 1 -> rank 7
+ */
+ dev = 26 + chan_rank / 2;
+ offset = 0x219a8 + dimm_order * 4 + (chan_rank % 2) * 0x8000;
+
+ ret = peci_mmio_read(priv->peci_dev, 0, GET_CPU_SEG(reg_val), GET_CPU_BUS(reg_val),
+ dev, 0, offset, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int read_thresholds_emr(struct peci_dimmtemp *priv, int dimm_order,
+ int chan_rank, u32 *data)
+{
+ return read_thresholds_spr(priv, dimm_order, chan_rank, data);
+}
+
static const struct dimm_info dimm_hsx = {
.chan_rank_max = CHAN_RANK_MAX_ON_HSX,
.dimm_idx_max = DIMM_IDX_MAX_ON_HSX,
@@ -576,6 +612,20 @@ static const struct dimm_info dimm_icxd = {
.read_thresholds = &read_thresholds_icx,
};
+static const struct dimm_info dimm_spr = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_SPR,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_SPR,
+ .min_peci_revision = 0x40,
+ .read_thresholds = &read_thresholds_spr,
+};
+
+static const struct dimm_info dimm_emr = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_EMR,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_EMR,
+ .min_peci_revision = 0x40,
+ .read_thresholds = &read_thresholds_emr,
+};
+
static const struct auxiliary_device_id peci_dimmtemp_ids[] = {
{
.name = "peci_cpu.dimmtemp.hsx",
@@ -601,6 +651,14 @@ static const struct auxiliary_device_id peci_dimmtemp_ids[] = {
.name = "peci_cpu.dimmtemp.icxd",
.driver_data = (kernel_ulong_t)&dimm_icxd,
},
+ {
+ .name = "peci_cpu.dimmtemp.spr",
+ .driver_data = (kernel_ulong_t)&dimm_spr,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.emr",
+ .driver_data = (kernel_ulong_t)&dimm_emr,
+ },
{ }
};
MODULE_DEVICE_TABLE(auxiliary, peci_dimmtemp_ids);
@@ -616,4 +674,4 @@ MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
MODULE_DESCRIPTION("PECI dimmtemp driver");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PECI_CPU);
+MODULE_IMPORT_NS("PECI_CPU");
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index b4e93bd5835e..f3fb94cebf1a 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -51,12 +51,32 @@ config SENSORS_ADM1275
tristate "Analog Devices ADM1275 and compatibles"
help
If you say yes here you get hardware monitoring support for Analog
- Devices ADM1075, ADM1272, ADM1275, ADM1276, ADM1278, ADM1293,
- and ADM1294 Hot-Swap Controller and Digital Power Monitors.
+ Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281,
+ ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and
+ Digital Power Monitors.
This driver can also be built as a module. If so, the module will
be called adm1275.
+config SENSORS_ADP1050
+ tristate "Analog Devices ADP1050 digital controller for Power Supplies"
+ help
+ If you say yes here you get hardware monitoring support for Analog
+ Devices ADP1050 digital controller for isolated power supply with
+ PMBus interface.
+
+ This driver can also be built as a module. If so, the module will
+ be called adp1050.
+
+config SENSORS_ADP1050_REGULATOR
+ bool "Regulator support for ADP1050 and compatibles"
+ depends on SENSORS_ADP1050 && REGULATOR
+ help
+ If you say yes here you get regulator support for Analog Devices
+ LTP8800-1A, LTP8800-4A, and LTP8800-2. LTP8800 is a family of DC/DC
+ µModule regulators that can provide microprocessor power from 54V
+ power distribution architecture.
+
config SENSORS_BEL_PFE
tristate "Bel PFE Compatible Power Supplies"
help
@@ -75,6 +95,15 @@ config SENSORS_BPA_RS600
This driver can also be built as a module. If so, the module will
be called bpa-rs600.
+config SENSORS_CRPS
+ tristate "Intel Common Redundant Power Supply"
+ help
+ If you say yes here you get hardware monitoring support for the Intel
+ Common Redundant Power Supply.
+
+ This driver can also be built as a module. If so, the module will
+ be called crps.
+
config SENSORS_DELTA_AHE50DC_FAN
tristate "Delta AHE-50DC fan control module"
help
@@ -114,6 +143,15 @@ config SENSORS_DPS920AB
This driver can also be built as a module. If so, the module will
be called dps920ab.
+config SENSORS_INA233
+ tristate "Texas Instruments INA233 and compatibles"
+ help
+ If you say yes here you get hardware monitoring support for Texas
+ Instruments INA233.
+
+ This driver can also be built as a module. If so, the module will
+ be called ina233.
+
config SENSORS_INSPUR_IPSPS
tristate "INSPUR Power System Power Supply"
help
@@ -190,6 +228,24 @@ config SENSORS_LM25066_REGULATOR
If you say yes here you get regulator support for National
Semiconductor LM25066, LM5064, and LM5066.
+config SENSORS_LT3074
+ tristate "Analog Devices LT3074"
+ help
+ If you say yes here you get hardware monitoring support for Analog
+ Devices LT3074.
+
+ This driver can also be built as a module. If so, the module will
+ be called lt3074.
+
+config SENSORS_LT3074_REGULATOR
+ tristate "Regulator support for LT3074"
+ depends on SENSORS_LT3074 && REGULATOR
+ help
+ If you say yes here you get regulator support for Analog Devices
+ LT3074. The LT3074 is a low voltage, ultralow noise, high PSRR,
+ dropout linear regulator. The device supplies up to 3A with a
+ typical dropout voltage of 45mV.
+
config SENSORS_LT7182S
tristate "Analog Devices LT7182S"
help
@@ -214,9 +270,9 @@ config SENSORS_LTC2978_REGULATOR
depends on SENSORS_LTC2978 && REGULATOR
help
If you say yes here you get regulator support for Linear Technology
- LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880,
- LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686,
- and LTM4700.
+ LT7170, LT7171, LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889,
+ LTC7841, LTC7880, LTM4644, LTM4673, LTM4675, LTM4676, LTM4677,
+ LTM4678, LTM4680, LTM4686, and LTM4700.
config SENSORS_LTC3815
tristate "Linear Technologies LTC3815"
@@ -227,11 +283,21 @@ config SENSORS_LTC3815
This driver can also be built as a module. If so, the module will
be called ltc3815.
+config SENSORS_LTC4286
+ bool "Analog Devices LTC4286"
+ help
+ LTC4286 is an integrated solution for hot swap applications that
+ allows a board to be safely inserted and removed from a live
+ backplane.
+ This chip could be used to monitor voltage, current, ...etc.
+ If you say yes here you get hardware monitoring support for Analog
+ Devices LTC4286.
+
config SENSORS_MAX15301
tristate "Maxim MAX15301"
help
If you say yes here you get hardware monitoring support for Maxim
- MAX15301, as well as for Flex BMR461.
+ MAX15301, MAX15303, as well as for Flex BMR461.
This driver can also be built as a module. If so, the module will
be called max15301.
@@ -254,6 +320,15 @@ config SENSORS_MAX16601
This driver can also be built as a module. If so, the module will
be called max16601.
+config SENSORS_MAX17616
+ tristate "Analog Devices MAX17616/MAX17616A"
+ help
+ If you say yes here you get hardware monitoring support for Analog
+ Devices MAX17616/MAX17616A.
+
+ This driver can also be built as a module. If so, the module will
+ be called max17616.
+
config SENSORS_MAX20730
tristate "Maxim MAX20710, MAX20730, MAX20734, MAX20743"
help
@@ -286,6 +361,7 @@ config SENSORS_MAX34440
help
If you say yes here you get hardware monitoring support for Maxim
MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461.
+ Other compatible are ADPM12160, and ADPM12200.
This driver can also be built as a module. If so, the module will
be called max34440.
@@ -299,6 +375,24 @@ config SENSORS_MAX8688
This driver can also be built as a module. If so, the module will
be called max8688.
+config SENSORS_MP2856
+ tristate "MPS MP2856"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2856 MP2857 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2856.
+
+config SENSORS_MP2869
+ tristate "MPS MP2869"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2869 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2869.
+
config SENSORS_MP2888
tristate "MPS MP2888"
help
@@ -308,6 +402,33 @@ config SENSORS_MP2888
This driver can also be built as a module. If so, the module will
be called mp2888.
+config SENSORS_MP2891
+ tristate "MPS MP2891"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2891 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2891.
+
+config SENSORS_MP2925
+ tristate "MPS MP2925"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2925 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2925.
+
+config SENSORS_MP29502
+ tristate "MPS MP29502"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP29502 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp29502.
+
config SENSORS_MP2975
tristate "MPS MP2975"
help
@@ -317,6 +438,15 @@ config SENSORS_MP2975
This driver can also be built as a module. If so, the module will
be called mp2975.
+config SENSORS_MP2993
+ tristate "MPS MP2993"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2993 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2993.
+
config SENSORS_MP2975_REGULATOR
depends on SENSORS_MP2975 && REGULATOR
bool "Regulator support for MPS MP2975"
@@ -333,6 +463,42 @@ config SENSORS_MP5023
This driver can also be built as a module. If so, the module will
be called mp5023.
+config SENSORS_MP5920
+ tristate "MPS MP5920"
+ help
+ If you say yes here you get hardware monitoring support for Monolithic
+ MP5920.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp5920.
+
+config SENSORS_MP5990
+ tristate "MPS MP5990"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP5990.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp5990.
+
+config SENSORS_MP9941
+ tristate "MPS MP9941"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP9941.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp9941.
+
+config SENSORS_MP9945
+ tristate "MPS MP9945"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP9945.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp9945.
+
config SENSORS_MPQ7932_REGULATOR
bool "Regulator support for MPQ7932"
depends on SENSORS_MPQ7932 && REGULATOR
@@ -349,6 +515,15 @@ config SENSORS_MPQ7932
This driver can also be built as a module. If so, the module will
be called mpq7932.
+config SENSORS_MPQ8785
+ tristate "MPS MPQ8785"
+ help
+ If you say yes here you get hardware monitoring functionality support
+ for power management IC MPS MPQ8785.
+
+ This driver can also be built as a module. If so, the module will
+ be called mpq8785.
+
config SENSORS_PIM4328
tristate "Flex PIM4328 and compatibles"
help
@@ -427,6 +602,23 @@ config SENSORS_TDA38640_REGULATOR
If you say yes here you get regulator support for Infineon
TDA38640 as regulator.
+config SENSORS_TPS25990
+ tristate "TI TPS25990"
+ help
+ If you say yes here you get hardware monitoring support for TI
+ TPS25990.
+
+ This driver can also be built as a module. If so, the module will
+ be called tps25990.
+
+config SENSORS_TPS25990_REGULATOR
+ bool "Regulator support for TPS25990 and compatibles"
+ depends on SENSORS_TPS25990 && REGULATOR
+ default SENSORS_TPS25990
+ help
+ If you say yes here you get regulator support for Texas Instruments
+ TPS25990.
+
config SENSORS_TPS40422
tristate "TI TPS40422"
help
@@ -474,6 +666,15 @@ config SENSORS_UCD9200
This driver can also be built as a module. If so, the module will
be called ucd9200.
+config SENSORS_XDP710
+ tristate "Infineon XDP710 family"
+ help
+ If you say yes here you get hardware monitoring support for Infineon
+ XDP710.
+
+ This driver can also be built as a module. If so, the module will
+ be called xdp710.
+
config SENSORS_XDPE152
tristate "Infineon XDPE152 family"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 84ee960a6c2d..349a89b6d92e 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -8,12 +8,14 @@ obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
obj-$(CONFIG_SENSORS_ACBEL_FSG032) += acbel-fsg032.o
obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
+obj-$(CONFIG_SENSORS_ADP1050) += adp1050.o
obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o
obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o
obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o
+obj-$(CONFIG_SENSORS_INA233) += ina233.o
obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
obj-$(CONFIG_SENSORS_IR36021) += ir36021.o
@@ -21,33 +23,50 @@ obj-$(CONFIG_SENSORS_IR38064) += ir38064.o
obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o
obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
+obj-$(CONFIG_SENSORS_LT3074) += lt3074.o
obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
+obj-$(CONFIG_SENSORS_LTC4286) += ltc4286.o
obj-$(CONFIG_SENSORS_MAX15301) += max15301.o
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
+obj-$(CONFIG_SENSORS_MAX17616) += max17616.o
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
+obj-$(CONFIG_SENSORS_MP2856) += mp2856.o
+obj-$(CONFIG_SENSORS_MP2869) += mp2869.o
obj-$(CONFIG_SENSORS_MP2888) += mp2888.o
+obj-$(CONFIG_SENSORS_MP2891) += mp2891.o
+obj-$(CONFIG_SENSORS_MP2925) += mp2925.o
+obj-$(CONFIG_SENSORS_MP29502) += mp29502.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
+obj-$(CONFIG_SENSORS_MP2993) += mp2993.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
+obj-$(CONFIG_SENSORS_MP5920) += mp5920.o
+obj-$(CONFIG_SENSORS_MP5990) += mp5990.o
+obj-$(CONFIG_SENSORS_MP9941) += mp9941.o
+obj-$(CONFIG_SENSORS_MP9945) += mp9945.o
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
+obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o
obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o
obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o
+obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
+obj-$(CONFIG_SENSORS_XDP710) += xdp710.o
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o
+obj-$(CONFIG_SENSORS_CRPS) += crps.o
diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c
index e0c55fd8f3a6..9f07fb4abaff 100644
--- a/drivers/hwmon/pmbus/acbel-fsg032.c
+++ b/drivers/hwmon/pmbus/acbel-fsg032.c
@@ -120,4 +120,4 @@ module_i2c_driver(acbel_fsg032_driver);
MODULE_AUTHOR("Lakshmi Yadlapati");
MODULE_DESCRIPTION("PMBus driver for AcBel Power System power supplies");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index ed0a7b9fae4b..d90f8f80be8e 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -490,7 +490,7 @@ static const struct of_device_id adm1266_of_match[] = {
MODULE_DEVICE_TABLE(of, adm1266_of_match);
static const struct i2c_device_id adm1266_id[] = {
- { "adm1266", 0 },
+ { "adm1266" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1266_id);
@@ -509,4 +509,4 @@ module_i2c_driver(adm1266_driver);
MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>");
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1266");
MODULE_LICENSE("GPL v2");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
index e2c61d6fa521..bc2a6a07dc3e 100644
--- a/drivers/hwmon/pmbus/adm1275.c
+++ b/drivers/hwmon/pmbus/adm1275.c
@@ -18,7 +18,8 @@
#include <linux/log2.h>
#include "pmbus.h"
-enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 };
+enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
+ adm1293, adm1294, sq24905c };
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
@@ -479,11 +480,14 @@ static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg)
static const struct i2c_device_id adm1275_id[] = {
{ "adm1075", adm1075 },
{ "adm1272", adm1272 },
+ { "adm1273", adm1273 },
{ "adm1275", adm1275 },
{ "adm1276", adm1276 },
{ "adm1278", adm1278 },
+ { "adm1281", adm1281 },
{ "adm1293", adm1293 },
{ "adm1294", adm1294 },
+ { "mc09c", sq24905c },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1275_id);
@@ -530,7 +534,8 @@ static int adm1275_probe(struct i2c_client *client)
dev_err(&client->dev, "Failed to read Manufacturer ID\n");
return ret;
}
- if (ret != 3 || strncmp(block_buffer, "ADI", 3)) {
+ if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) &&
+ (ret != 2 || strncmp(block_buffer, "SY", 2))) {
dev_err(&client->dev, "Unsupported Manufacturer ID\n");
return -ENODEV;
}
@@ -554,8 +559,10 @@ static int adm1275_probe(struct i2c_client *client)
"Device mismatch: Configured %s, detected %s\n",
client->name, mid->name);
- if (mid->driver_data == adm1272 || mid->driver_data == adm1278 ||
- mid->driver_data == adm1293 || mid->driver_data == adm1294)
+ if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
+ mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
+ mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
+ mid->driver_data == sq24905c)
config_read_fn = i2c_smbus_read_word_data;
else
config_read_fn = i2c_smbus_read_byte_data;
@@ -628,6 +635,7 @@ static int adm1275_probe(struct i2c_client *client)
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
break;
case adm1272:
+ case adm1273:
data->have_vout = true;
data->have_pin_max = true;
data->have_temp_max = true;
@@ -703,6 +711,8 @@ static int adm1275_probe(struct i2c_client *client)
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
break;
case adm1278:
+ case adm1281:
+ case sq24905c:
data->have_vout = true;
data->have_pin_max = true;
data->have_temp_max = true;
@@ -863,4 +873,4 @@ module_i2c_driver(adm1275_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/adp1050.c b/drivers/hwmon/pmbus/adp1050.c
new file mode 100644
index 000000000000..a73774f8da2d
--- /dev/null
+++ b/drivers/hwmon/pmbus/adp1050.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Analog Devices ADP1050
+ *
+ * Copyright (C) 2024 Analog Devices, Inc.
+ */
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+#if IS_ENABLED(CONFIG_SENSORS_ADP1050_REGULATOR)
+static const struct regulator_desc adp1050_reg_desc[] = {
+ PMBUS_REGULATOR_ONE("vout"),
+};
+#endif /* CONFIG_SENSORS_ADP1050_REGULATOR */
+
+static struct pmbus_driver_info adp1050_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_IIN | PMBUS_HAVE_TEMP
+ | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static struct pmbus_driver_info adp1051_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT
+ | PMBUS_HAVE_TEMP
+ | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
+ | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static struct pmbus_driver_info adp1055_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT
+ | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3
+ | PMBUS_HAVE_POUT
+ | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
+ | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static struct pmbus_driver_info ltp8800_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT
+ | PMBUS_HAVE_TEMP
+ | PMBUS_HAVE_POUT
+ | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_STATUS_TEMP,
+#if IS_ENABLED(CONFIG_SENSORS_ADP1050_REGULATOR)
+ .num_regulators = 1,
+ .reg_desc = adp1050_reg_desc,
+#endif
+};
+
+static int adp1050_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+
+ info = (struct pmbus_driver_info *)i2c_get_match_data(client);
+ if (!info)
+ return -ENODEV;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id adp1050_id[] = {
+ { .name = "adp1050", .driver_data = (kernel_ulong_t)&adp1050_info },
+ { .name = "adp1051", .driver_data = (kernel_ulong_t)&adp1051_info },
+ { .name = "adp1055", .driver_data = (kernel_ulong_t)&adp1055_info },
+ { .name = "ltp8800", .driver_data = (kernel_ulong_t)&ltp8800_info },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, adp1050_id);
+
+static const struct of_device_id adp1050_of_match[] = {
+ { .compatible = "adi,adp1050", .data = &adp1050_info },
+ { .compatible = "adi,adp1051", .data = &adp1051_info },
+ { .compatible = "adi,adp1055", .data = &adp1055_info },
+ { .compatible = "adi,ltp8800", .data = &ltp8800_info },
+ {}
+};
+MODULE_DEVICE_TABLE(of, adp1050_of_match);
+
+static struct i2c_driver adp1050_driver = {
+ .driver = {
+ .name = "adp1050",
+ .of_match_table = adp1050_of_match,
+ },
+ .probe = adp1050_probe,
+ .id_table = adp1050_id,
+};
+module_i2c_driver(adp1050_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADP1050 HWMON PMBus Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c
index 7c5f4b10a7c1..ddf9d9a2958c 100644
--- a/drivers/hwmon/pmbus/bel-pfe.c
+++ b/drivers/hwmon/pmbus/bel-pfe.c
@@ -129,4 +129,4 @@ module_i2c_driver(pfe_pmbus_driver);
MODULE_AUTHOR("Tao Ren <rentao.bupt@gmail.com>");
MODULE_DESCRIPTION("PMBus driver for BEL PFE Family Power Supplies");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c
index 0dce26c35556..6c3875ba37a0 100644
--- a/drivers/hwmon/pmbus/bpa-rs600.c
+++ b/drivers/hwmon/pmbus/bpa-rs600.c
@@ -205,4 +205,4 @@ module_i2c_driver(bpa_rs600_driver);
MODULE_AUTHOR("Chris Packham");
MODULE_DESCRIPTION("PMBus driver for BluTek BPA-RS600");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/crps.c b/drivers/hwmon/pmbus/crps.c
new file mode 100644
index 000000000000..164b33fed312
--- /dev/null
+++ b/drivers/hwmon/pmbus/crps.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2024 IBM Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/pmbus.h>
+
+#include "pmbus.h"
+
+static const struct i2c_device_id crps_id[] = {
+ { "intel_crps185" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, crps_id);
+
+static struct pmbus_driver_info crps_info = {
+ .pages = 1,
+ /* PSU uses default linear data format. */
+ .func[0] = PMBUS_HAVE_PIN | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_IIN |
+ PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
+ PMBUS_HAVE_STATUS_TEMP |
+ PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12,
+};
+
+static int crps_probe(struct i2c_client *client)
+{
+ int rc;
+ struct device *dev = &client->dev;
+ char buf[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
+
+ rc = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
+ if (rc < 0)
+ return dev_err_probe(dev, rc, "Failed to read PMBUS_MFR_MODEL\n");
+
+ if (rc != 7 || strncmp(buf, "03NK260", 7)) {
+ buf[rc] = '\0';
+ return dev_err_probe(dev, -ENODEV, "Model '%s' not supported\n", buf);
+ }
+
+ rc = pmbus_do_probe(client, &crps_info);
+ if (rc)
+ return dev_err_probe(dev, rc, "Failed to probe\n");
+
+ return 0;
+}
+
+static const struct of_device_id crps_of_match[] = {
+ {
+ .compatible = "intel,crps185",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, crps_of_match);
+
+static struct i2c_driver crps_driver = {
+ .driver = {
+ .name = "crps",
+ .of_match_table = crps_of_match,
+ },
+ .probe = crps_probe,
+ .id_table = crps_id,
+};
+
+module_i2c_driver(crps_driver);
+
+MODULE_AUTHOR("Ninad Palsule");
+MODULE_DESCRIPTION("PMBus driver for Intel Common Redundant power supplies");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/delta-ahe50dc-fan.c b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c
index 4dd3b6686d6a..3850eaea75da 100644
--- a/drivers/hwmon/pmbus/delta-ahe50dc-fan.c
+++ b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c
@@ -127,4 +127,4 @@ module_i2c_driver(ahe50dc_fan_driver);
MODULE_AUTHOR("Zev Weiss <zev@bewilderbeest.net>");
MODULE_DESCRIPTION("Driver for Delta AHE-50DC power shelf fan control module");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/dps920ab.c b/drivers/hwmon/pmbus/dps920ab.c
index 04e0d598a6e5..325111a955e6 100644
--- a/drivers/hwmon/pmbus/dps920ab.c
+++ b/drivers/hwmon/pmbus/dps920ab.c
@@ -190,12 +190,19 @@ static const struct of_device_id __maybe_unused dps920ab_of_match[] = {
MODULE_DEVICE_TABLE(of, dps920ab_of_match);
+static const struct i2c_device_id dps920ab_device_id[] = {
+ { "dps920ab" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, dps920ab_device_id);
+
static struct i2c_driver dps920ab_driver = {
.driver = {
.name = "dps920ab",
.of_match_table = of_match_ptr(dps920ab_of_match),
},
.probe = dps920ab_probe,
+ .id_table = dps920ab_device_id,
};
module_i2c_driver(dps920ab_driver);
@@ -203,4 +210,4 @@ module_i2c_driver(dps920ab_driver);
MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
MODULE_DESCRIPTION("PMBus driver for Delta DPS920AB PSU");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/fsp-3y.c b/drivers/hwmon/pmbus/fsp-3y.c
index 72a7c261ef06..a4dc09e2ef75 100644
--- a/drivers/hwmon/pmbus/fsp-3y.c
+++ b/drivers/hwmon/pmbus/fsp-3y.c
@@ -291,4 +291,4 @@ module_i2c_driver(fsp3y_driver);
MODULE_AUTHOR("Václav Kubernát");
MODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c
index 1ba4c5e95820..d05ef7a968a9 100644
--- a/drivers/hwmon/pmbus/ibm-cffps.c
+++ b/drivers/hwmon/pmbus/ibm-cffps.c
@@ -614,4 +614,4 @@ module_i2c_driver(ibm_cffps_driver);
MODULE_AUTHOR("Eddie James");
MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ina233.c b/drivers/hwmon/pmbus/ina233.c
new file mode 100644
index 000000000000..dde1e1678394
--- /dev/null
+++ b/drivers/hwmon/pmbus/ina233.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for ina233
+ *
+ * Copyright (c) 2025 Leo Yang
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "pmbus.h"
+
+#define MFR_READ_VSHUNT 0xd1
+#define MFR_CALIBRATION 0xd4
+
+#define INA233_MAX_CURRENT_DEFAULT 32768000 /* uA */
+#define INA233_RSHUNT_DEFAULT 2000 /* uOhm */
+
+#define MAX_M_VAL 32767
+
+static void calculate_coef(int *m, int *R, u32 current_lsb, int power_coef)
+{
+ u64 scaled_m;
+ int scale_factor = 0;
+ int scale_coef = 1;
+
+ /*
+ * 1000000 from Current_LSB A->uA .
+ * scale_coef is for scaling up to minimize rounding errors,
+ * If there is no decimal information, no need to scale.
+ */
+ if (1000000 % current_lsb) {
+ /* Scaling to keep integer precision */
+ scale_factor = -3;
+ scale_coef = 1000;
+ }
+
+ /*
+ * Unit Conversion (Current_LSB A->uA) and use scaling(scale_factor)
+ * to keep integer precision.
+ * Formulae referenced from spec.
+ */
+ scaled_m = div64_u64(1000000 * scale_coef, (u64)current_lsb * power_coef);
+
+ /* Maximize while keeping it bounded.*/
+ while (scaled_m > MAX_M_VAL) {
+ scaled_m = div_u64(scaled_m, 10);
+ scale_factor++;
+ }
+ /* Scale up only if fractional part exists. */
+ while (scaled_m * 10 < MAX_M_VAL && scale_coef != 1) {
+ scaled_m *= 10;
+ scale_factor--;
+ }
+
+ *m = scaled_m;
+ *R = scale_factor;
+}
+
+static int ina233_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIRT_READ_VMON:
+ ret = pmbus_read_word_data(client, 0, 0xff, MFR_READ_VSHUNT);
+
+ /* Adjust returned value to match VIN coefficients */
+ /* VIN: 1.25 mV VSHUNT: 2.5 uV LSB */
+ ret = DIV_ROUND_CLOSEST(ret * 25, 12500);
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+ return ret;
+}
+
+static int ina233_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ int ret, m, R;
+ u32 rshunt;
+ u32 max_current;
+ u32 current_lsb;
+ u16 calibration;
+ struct pmbus_driver_info *info;
+
+ info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->pages = 1;
+ info->format[PSC_VOLTAGE_IN] = direct;
+ info->format[PSC_VOLTAGE_OUT] = direct;
+ info->format[PSC_CURRENT_OUT] = direct;
+ info->format[PSC_POWER] = direct;
+ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+ | PMBUS_HAVE_POUT
+ | PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON;
+ info->m[PSC_VOLTAGE_IN] = 8;
+ info->R[PSC_VOLTAGE_IN] = 2;
+ info->m[PSC_VOLTAGE_OUT] = 8;
+ info->R[PSC_VOLTAGE_OUT] = 2;
+ info->read_word_data = ina233_read_word_data;
+
+ /* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed. */
+ /* read rshunt value (uOhm) */
+ ret = device_property_read_u32(dev, "shunt-resistor", &rshunt);
+ if (ret) {
+ if (ret != -EINVAL)
+ return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n");
+ rshunt = INA233_RSHUNT_DEFAULT;
+ }
+ if (!rshunt)
+ return dev_err_probe(dev, -EINVAL,
+ "Shunt resistor cannot be zero.\n");
+
+ /* read Maximum expected current value (uA) */
+ ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current);
+ if (ret) {
+ if (ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "Maximum expected current property read fail.\n");
+ max_current = INA233_MAX_CURRENT_DEFAULT;
+ }
+ if (max_current < 32768)
+ return dev_err_probe(dev, -EINVAL,
+ "Maximum expected current cannot less then 32768.\n");
+
+ /* Calculate Current_LSB according to the spec formula */
+ current_lsb = max_current / 32768;
+
+ /* calculate current coefficient */
+ calculate_coef(&m, &R, current_lsb, 1);
+ info->m[PSC_CURRENT_OUT] = m;
+ info->R[PSC_CURRENT_OUT] = R;
+
+ /* calculate power coefficient */
+ calculate_coef(&m, &R, current_lsb, 25);
+ info->m[PSC_POWER] = m;
+ info->R[PSC_POWER] = R;
+
+ /* write MFR_CALIBRATION register, Apply formula from spec with unit scaling. */
+ calibration = div64_u64(5120000000ULL, (u64)rshunt * current_lsb);
+ if (calibration > 0x7FFF)
+ return dev_err_probe(dev, -EINVAL,
+ "Product of Current_LSB %u and shunt resistor %u too small, MFR_CALIBRATION reg exceeds 0x7FFF.\n",
+ current_lsb, rshunt);
+ ret = i2c_smbus_write_word_data(client, MFR_CALIBRATION, calibration);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Unable to write calibration.\n");
+
+ dev_dbg(dev, "power monitor %s (Rshunt = %u uOhm, Current_LSB = %u uA/bit)\n",
+ client->name, rshunt, current_lsb);
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id ina233_id[] = {
+ {"ina233", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ina233_id);
+
+static const struct of_device_id __maybe_unused ina233_of_match[] = {
+ { .compatible = "ti,ina233" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ina233_of_match);
+
+static struct i2c_driver ina233_driver = {
+ .driver = {
+ .name = "ina233",
+ .of_match_table = of_match_ptr(ina233_of_match),
+ },
+ .probe = ina233_probe,
+ .id_table = ina233_id,
+};
+
+module_i2c_driver(ina233_driver);
+
+MODULE_AUTHOR("Leo Yang <leo.yang.sy0@gmail.com>");
+MODULE_DESCRIPTION("PMBus driver for INA233 and compatible chips");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/inspur-ipsps.c b/drivers/hwmon/pmbus/inspur-ipsps.c
index dfeae68b5e52..074e0f164ee1 100644
--- a/drivers/hwmon/pmbus/inspur-ipsps.c
+++ b/drivers/hwmon/pmbus/inspur-ipsps.c
@@ -197,7 +197,7 @@ static int ipsps_probe(struct i2c_client *client)
}
static const struct i2c_device_id ipsps_id[] = {
- { "ipsps1", 0 },
+ { "ipsps1" },
{}
};
MODULE_DEVICE_TABLE(i2c, ipsps_id);
@@ -224,4 +224,4 @@ module_i2c_driver(ipsps_driver);
MODULE_AUTHOR("John Wang");
MODULE_DESCRIPTION("PMBus driver for Inspur Power System power supplies");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c
index e3ee5c1bd967..46d8f334d49a 100644
--- a/drivers/hwmon/pmbus/ir35221.c
+++ b/drivers/hwmon/pmbus/ir35221.c
@@ -126,7 +126,7 @@ static int ir35221_probe(struct i2c_client *client)
}
static const struct i2c_device_id ir35221_id[] = {
- {"ir35221", 0},
+ {"ir35221"},
{}
};
@@ -145,4 +145,4 @@ module_i2c_driver(ir35221_driver);
MODULE_AUTHOR("Samuel Mendoza-Jonas <sam@mendozajonas.com");
MODULE_DESCRIPTION("PMBus driver for IR35221");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c
index 382ba6b6031a..34ce15fc708b 100644
--- a/drivers/hwmon/pmbus/ir36021.c
+++ b/drivers/hwmon/pmbus/ir36021.c
@@ -51,7 +51,7 @@ static int ir36021_probe(struct i2c_client *client)
}
static const struct i2c_device_id ir36021_id[] = {
- { "ir36021", 0 },
+ { "ir36021" },
{},
};
MODULE_DEVICE_TABLE(i2c, ir36021_id);
@@ -63,7 +63,6 @@ static const struct of_device_id __maybe_unused ir36021_of_id[] = {
MODULE_DEVICE_TABLE(of, ir36021_of_id);
static struct i2c_driver ir36021_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ir36021",
.of_match_table = of_match_ptr(ir36021_of_id),
@@ -77,4 +76,4 @@ module_i2c_driver(ir36021_driver);
MODULE_AUTHOR("Chris Packham <chris.packham@alliedtelesis.co.nz>");
MODULE_DESCRIPTION("PMBus driver for Infineon IR36021");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c
index 04185be3fdb6..7b4188e8bf48 100644
--- a/drivers/hwmon/pmbus/ir38064.c
+++ b/drivers/hwmon/pmbus/ir38064.c
@@ -22,7 +22,7 @@
#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR)
static const struct regulator_desc ir38064_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE("vout"),
};
#endif /* CONFIG_SENSORS_IR38064_REGULATOR */
@@ -53,10 +53,10 @@ static int ir38064_probe(struct i2c_client *client)
}
static const struct i2c_device_id ir38064_id[] = {
- {"ir38060", 0},
- {"ir38064", 0},
- {"ir38164", 0},
- {"ir38263", 0},
+ {"ir38060"},
+ {"ir38064"},
+ {"ir38164"},
+ {"ir38263"},
{}
};
@@ -87,4 +87,4 @@ module_i2c_driver(ir38064_driver);
MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and compatible chips");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/irps5401.c b/drivers/hwmon/pmbus/irps5401.c
index 146d32a35a7c..43674c64841d 100644
--- a/drivers/hwmon/pmbus/irps5401.c
+++ b/drivers/hwmon/pmbus/irps5401.c
@@ -44,7 +44,7 @@ static int irps5401_probe(struct i2c_client *client)
}
static const struct i2c_device_id irps5401_id[] = {
- {"irps5401", 0},
+ {"irps5401"},
{}
};
@@ -63,4 +63,4 @@ module_i2c_driver(irps5401_driver);
MODULE_AUTHOR("Robert Hancock");
MODULE_DESCRIPTION("PMBus driver for Infineon IRPS5401");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c
index 7e53fb1d5ea3..97b61836f53a 100644
--- a/drivers/hwmon/pmbus/isl68137.c
+++ b/drivers/hwmon/pmbus/isl68137.c
@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/string.h>
#include <linux/sysfs.h>
@@ -20,6 +21,7 @@
#define ISL68137_VOUT_AVS 0x30
#define RAA_DMPVR2_READ_VMON 0xc8
+#define MAX_CHANNELS 4
enum chips {
isl68137,
@@ -59,8 +61,12 @@ enum chips {
raa228004,
raa228006,
raa228228,
+ raa228244,
+ raa228246,
raa229001,
raa229004,
+ raa229141,
+ raa229621,
};
enum variants {
@@ -68,10 +74,22 @@ enum variants {
raa_dmpvr2_1rail,
raa_dmpvr2_2rail,
raa_dmpvr2_2rail_nontc,
+ raa_dmpvr2_2rail_pmbus,
raa_dmpvr2_3rail,
raa_dmpvr2_hv,
};
+struct isl68137_channel {
+ u32 vout_voltage_divider[2];
+};
+
+struct isl68137_data {
+ struct pmbus_driver_info info;
+ struct isl68137_channel channel[MAX_CHANNELS];
+};
+
+#define to_isl68137_data(x) container_of(x, struct isl68137_data, info)
+
static const struct i2c_device_id raa_dmpvr_id[];
static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client,
@@ -163,13 +181,32 @@ static const struct attribute_group *isl68137_attribute_groups[] = {
static int raa_dmpvr2_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ const struct isl68137_data *data = to_isl68137_data(info);
int ret;
+ u64 temp;
switch (reg) {
case PMBUS_VIRT_READ_VMON:
ret = pmbus_read_word_data(client, page, phase,
RAA_DMPVR2_READ_VMON);
break;
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_VOUT:
+ /*
+ * In cases where a voltage divider is attached to the target
+ * rail between Vout and the Vsense pin, both Vout and Pout
+ * should be scaled by the voltage divider scaling factor.
+ * I.e. Vout = Vsense * Rtotal / Rout
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret > 0) {
+ temp = DIV_U64_ROUND_CLOSEST((u64)ret *
+ data->channel[page].vout_voltage_divider[1],
+ data->channel[page].vout_voltage_divider[0]);
+ ret = clamp_val(temp, 0, 0xffff);
+ }
+ break;
default:
ret = -ENODATA;
break;
@@ -178,6 +215,40 @@ static int raa_dmpvr2_read_word_data(struct i2c_client *client, int page,
return ret;
}
+static int raa_dmpvr2_write_word_data(struct i2c_client *client, int page,
+ int reg, u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ const struct isl68137_data *data = to_isl68137_data(info);
+ int ret;
+ u64 temp;
+
+ switch (reg) {
+ case PMBUS_VOUT_MAX:
+ case PMBUS_VOUT_MARGIN_HIGH:
+ case PMBUS_VOUT_MARGIN_LOW:
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ case PMBUS_VOUT_COMMAND:
+ /*
+ * In cases where a voltage divider is attached to the target
+ * rail between Vout and the Vsense pin, Vout related PMBus
+ * commands should be scaled based on the expected voltage
+ * at the Vsense pin.
+ * I.e. Vsense = Vout * Rout / Rtotal
+ */
+ temp = DIV_U64_ROUND_CLOSEST((u64)word *
+ data->channel[page].vout_voltage_divider[0],
+ data->channel[page].vout_voltage_divider[1]);
+ ret = clamp_val(temp, 0, 0xffff);
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+ return ret;
+}
+
static struct pmbus_driver_info raa_dmpvr_info = {
.pages = 3,
.format[PSC_VOLTAGE_IN] = direct,
@@ -220,14 +291,89 @@ static struct pmbus_driver_info raa_dmpvr_info = {
| PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT,
};
+static int isl68137_probe_child_from_dt(struct device *dev,
+ struct device_node *child,
+ struct isl68137_data *data)
+{
+ u32 channel, rout, rtotal;
+ int err;
+
+ err = of_property_read_u32(child, "reg", &channel);
+ if (err) {
+ dev_err(dev, "missing reg property of %pOFn\n", child);
+ return err;
+ }
+ if (channel >= data->info.pages) {
+ dev_err(dev, "invalid reg %d of %pOFn\n", channel, child);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32_array(child, "vout-voltage-divider",
+ data->channel[channel].vout_voltage_divider,
+ ARRAY_SIZE(data->channel[channel].vout_voltage_divider));
+ if (err && err != -EINVAL) {
+ dev_err(dev,
+ "malformed vout-voltage-divider value for channel %d\n",
+ channel);
+ return err;
+ }
+
+ rout = data->channel[channel].vout_voltage_divider[0];
+ rtotal = data->channel[channel].vout_voltage_divider[1];
+ if (rout == 0) {
+ dev_err(dev,
+ "Voltage divider output resistance must be greater than 0\n");
+ return -EINVAL;
+ }
+ if (rtotal < rout) {
+ dev_err(dev,
+ "Voltage divider total resistance is less than output resistance\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int isl68137_probe_from_dt(struct device *dev,
+ struct isl68137_data *data)
+{
+ const struct device_node *np = dev->of_node;
+ int err;
+
+ for_each_child_of_node_scoped(np, child) {
+ if (strcmp(child->name, "channel"))
+ continue;
+
+ err = isl68137_probe_child_from_dt(dev, child, data);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static int isl68137_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
struct pmbus_driver_info *info;
+ struct isl68137_data *data;
+ int i, err;
- info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
- if (!info)
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
return -ENOMEM;
- memcpy(info, &raa_dmpvr_info, sizeof(*info));
+
+ /*
+ * Initialize all voltage dividers to Rout=1 and Rtotal=1 to simplify
+ * logic in PMBus word read/write functions
+ */
+ for (i = 0; i < MAX_CHANNELS; i++)
+ memset(data->channel[i].vout_voltage_divider,
+ 1,
+ sizeof(data->channel[i].vout_voltage_divider));
+
+ memcpy(&data->info, &raa_dmpvr_info, sizeof(data->info));
+ info = &data->info;
switch (i2c_match_id(raa_dmpvr_id, client)->driver_data) {
case raa_dmpvr1_2rail:
@@ -237,11 +383,14 @@ static int isl68137_probe(struct i2c_client *client)
info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
| PMBUS_HAVE_POUT;
+ info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
info->groups = isl68137_attribute_groups;
break;
case raa_dmpvr2_1rail:
info->pages = 1;
info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
break;
case raa_dmpvr2_2rail_nontc:
info->func[0] &= ~PMBUS_HAVE_TEMP3;
@@ -250,9 +399,22 @@ static int isl68137_probe(struct i2c_client *client)
case raa_dmpvr2_2rail:
info->pages = 2;
info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
+ break;
+ case raa_dmpvr2_2rail_pmbus:
+ info->format[PSC_VOLTAGE_IN] = linear,
+ info->format[PSC_VOLTAGE_OUT] = linear,
+ info->format[PSC_CURRENT_IN] = linear;
+ info->format[PSC_CURRENT_OUT] = linear;
+ info->format[PSC_POWER] = linear;
+ info->format[PSC_TEMPERATURE] = linear;
+ info->pages = 2;
+ info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
break;
case raa_dmpvr2_3rail:
info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
break;
case raa_dmpvr2_hv:
info->pages = 1;
@@ -263,11 +425,16 @@ static int isl68137_probe(struct i2c_client *client)
info->m[PSC_POWER] = 2;
info->R[PSC_POWER] = -1;
info->read_word_data = raa_dmpvr2_read_word_data;
+ info->write_word_data = raa_dmpvr2_write_word_data;
break;
default:
return -ENODEV;
}
+ err = isl68137_probe_from_dt(dev, data);
+ if (err)
+ return err;
+
return pmbus_do_probe(client, info);
}
@@ -311,18 +478,73 @@ static const struct i2c_device_id raa_dmpvr_id[] = {
{"raa228004", raa_dmpvr2_hv},
{"raa228006", raa_dmpvr2_hv},
{"raa228228", raa_dmpvr2_2rail_nontc},
+ {"raa228244", raa_dmpvr2_2rail_nontc},
+ {"raa228246", raa_dmpvr2_2rail_nontc},
{"raa229001", raa_dmpvr2_2rail},
{"raa229004", raa_dmpvr2_2rail},
+ {"raa229141", raa_dmpvr2_2rail_pmbus},
+ {"raa229621", raa_dmpvr2_2rail},
{}
};
MODULE_DEVICE_TABLE(i2c, raa_dmpvr_id);
+static const struct of_device_id isl68137_of_match[] = {
+ { .compatible = "isil,isl68137", .data = (void *)raa_dmpvr1_2rail },
+ { .compatible = "renesas,isl68220", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl68221", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl68222", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl68223", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl68224", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl68225", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl68226", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl68227", .data = (void *)raa_dmpvr2_1rail },
+ { .compatible = "renesas,isl68229", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl68233", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl68239", .data = (void *)raa_dmpvr2_3rail },
+
+ { .compatible = "renesas,isl69222", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69223", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl69224", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69225", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69227", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl69228", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl69234", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69236", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69239", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl69242", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69243", .data = (void *)raa_dmpvr2_1rail },
+ { .compatible = "renesas,isl69247", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69248", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69254", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69255", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69256", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69259", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "isil,isl69260", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,isl69268", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "isil,isl69269", .data = (void *)raa_dmpvr2_3rail },
+ { .compatible = "renesas,isl69298", .data = (void *)raa_dmpvr2_2rail },
+
+ { .compatible = "renesas,raa228000", .data = (void *)raa_dmpvr2_hv },
+ { .compatible = "renesas,raa228004", .data = (void *)raa_dmpvr2_hv },
+ { .compatible = "renesas,raa228006", .data = (void *)raa_dmpvr2_hv },
+ { .compatible = "renesas,raa228228", .data = (void *)raa_dmpvr2_2rail_nontc },
+ { .compatible = "renesas,raa228244", .data = (void *)raa_dmpvr2_2rail_nontc },
+ { .compatible = "renesas,raa228246", .data = (void *)raa_dmpvr2_2rail_nontc },
+ { .compatible = "renesas,raa229001", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,raa229004", .data = (void *)raa_dmpvr2_2rail },
+ { .compatible = "renesas,raa229621", .data = (void *)raa_dmpvr2_2rail },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, isl68137_of_match);
+
/* This is the driver that will be inserted */
static struct i2c_driver isl68137_driver = {
.driver = {
- .name = "isl68137",
- },
+ .name = "isl68137",
+ .of_match_table = isl68137_of_match,
+ },
.probe = isl68137_probe,
.id_table = raa_dmpvr_id,
};
@@ -332,4 +554,4 @@ module_i2c_driver(isl68137_driver);
MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>");
MODULE_DESCRIPTION("PMBus driver for Renesas digital multiphase voltage regulators");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c
index 929fa6d34efd..dd7275a67a0a 100644
--- a/drivers/hwmon/pmbus/lm25066.c
+++ b/drivers/hwmon/pmbus/lm25066.c
@@ -14,7 +14,7 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/log2.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include "pmbus.h"
enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i };
@@ -437,7 +437,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR)
static const struct regulator_desc lm25066_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE_NODE("vout"),
};
#endif
@@ -468,8 +468,6 @@ static int lm25066_probe(struct i2c_client *client)
struct lm25066_data *data;
struct pmbus_driver_info *info;
const struct __coeff *coeff;
- const struct of_device_id *of_id;
- const struct i2c_device_id *i2c_id;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA))
@@ -484,14 +482,8 @@ static int lm25066_probe(struct i2c_client *client)
if (config < 0)
return config;
- i2c_id = i2c_match_id(lm25066_id, client);
+ data->id = (enum chips)(unsigned long)i2c_get_match_data(client);
- of_id = of_match_device(lm25066_of_match, &client->dev);
- if (of_id && (unsigned long)of_id->data != i2c_id->driver_data)
- dev_notice(&client->dev, "Device mismatch: %s in device tree, %s detected\n",
- of_id->name, i2c_id->name);
-
- data->id = i2c_id->driver_data;
info = &data->info;
info->pages = 1;
@@ -577,4 +569,4 @@ module_i2c_driver(lm25066_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for LM25066 and compatible chips");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/lt3074.c b/drivers/hwmon/pmbus/lt3074.c
new file mode 100644
index 000000000000..3704dbe7b54a
--- /dev/null
+++ b/drivers/hwmon/pmbus/lt3074.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Analog Devices LT3074
+ *
+ * Copyright (C) 2025 Analog Devices, Inc.
+ */
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+#define LT3074_MFR_READ_VBIAS 0xc6
+#define LT3074_MFR_BIAS_OV_WARN_LIMIT 0xc7
+#define LT3074_MFR_BIAS_UV_WARN_LIMIT 0xc8
+#define LT3074_MFR_SPECIAL_ID 0xe7
+
+#define LT3074_SPECIAL_ID_VALUE 0x1c1d
+
+static const struct regulator_desc __maybe_unused lt3074_reg_desc[] = {
+ PMBUS_REGULATOR_ONE("regulator"),
+};
+
+static int lt3074_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ switch (reg) {
+ case PMBUS_VIRT_READ_VMON:
+ return pmbus_read_word_data(client, page, phase,
+ LT3074_MFR_READ_VBIAS);
+ case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
+ return pmbus_read_word_data(client, page, phase,
+ LT3074_MFR_BIAS_UV_WARN_LIMIT);
+ case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
+ return pmbus_read_word_data(client, page, phase,
+ LT3074_MFR_BIAS_OV_WARN_LIMIT);
+ default:
+ return -ENODATA;
+ }
+}
+
+static int lt3074_write_word_data(struct i2c_client *client, int page,
+ int reg, u16 word)
+{
+ switch (reg) {
+ case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
+ return pmbus_write_word_data(client, 0,
+ LT3074_MFR_BIAS_UV_WARN_LIMIT,
+ word);
+ case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
+ return pmbus_write_word_data(client, 0,
+ LT3074_MFR_BIAS_OV_WARN_LIMIT,
+ word);
+ default:
+ return -ENODATA;
+ }
+}
+
+static struct pmbus_driver_info lt3074_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_VMON |
+ PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .read_word_data = lt3074_read_word_data,
+ .write_word_data = lt3074_write_word_data,
+#if IS_ENABLED(CONFIG_SENSORS_LT3074_REGULATOR)
+ .num_regulators = 1,
+ .reg_desc = lt3074_reg_desc,
+#endif
+};
+
+static int lt3074_probe(struct i2c_client *client)
+{
+ int ret;
+ struct device *dev = &client->dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ return -ENODEV;
+
+ ret = i2c_smbus_read_word_data(client, LT3074_MFR_SPECIAL_ID);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read ID\n");
+
+ if (ret != LT3074_SPECIAL_ID_VALUE)
+ return dev_err_probe(dev, -ENODEV, "ID mismatch\n");
+
+ return pmbus_do_probe(client, &lt3074_info);
+}
+
+static const struct i2c_device_id lt3074_id[] = {
+ { "lt3074", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lt3074_id);
+
+static const struct of_device_id __maybe_unused lt3074_of_match[] = {
+ { .compatible = "adi,lt3074" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lt3074_of_match);
+
+static struct i2c_driver lt3074_driver = {
+ .driver = {
+ .name = "lt3074",
+ .of_match_table = of_match_ptr(lt3074_of_match),
+ },
+ .probe = lt3074_probe,
+ .id_table = lt3074_id,
+};
+module_i2c_driver(lt3074_driver);
+
+MODULE_AUTHOR("Cedric Encarnacion <cedricjustine.encarnacion@analog.com>");
+MODULE_DESCRIPTION("PMBus driver for Analog Devices LT3074");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/lt7182s.c b/drivers/hwmon/pmbus/lt7182s.c
index 28afc5f15ae8..9d6d50f39bd6 100644
--- a/drivers/hwmon/pmbus/lt7182s.c
+++ b/drivers/hwmon/pmbus/lt7182s.c
@@ -168,7 +168,7 @@ static int lt7182s_probe(struct i2c_client *client)
}
static const struct i2c_device_id lt7182s_id[] = {
- { "lt7182s", 0 },
+ { "lt7182s" },
{}
};
MODULE_DEVICE_TABLE(i2c, lt7182s_id);
@@ -192,4 +192,4 @@ module_i2c_driver(lt7182s_driver);
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
MODULE_DESCRIPTION("PMBus driver for Analog Devices LT7182S");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c
index 73a86f4d6472..8f5be520a15d 100644
--- a/drivers/hwmon/pmbus/ltc2978.c
+++ b/drivers/hwmon/pmbus/ltc2978.c
@@ -23,10 +23,11 @@ enum chips {
/* Managers */
ltc2972, ltc2974, ltc2975, ltc2977, ltc2978, ltc2979, ltc2980,
/* Controllers */
- ltc3880, ltc3882, ltc3883, ltc3884, ltc3886, ltc3887, ltc3889, ltc7132, ltc7880,
+ lt7170, lt7171, ltc3880, ltc3882, ltc3883, ltc3884, ltc3886, ltc3887,
+ ltc3889, ltc7132, ltc7841, ltc7880,
/* Modules */
- ltm2987, ltm4664, ltm4675, ltm4676, ltm4677, ltm4678, ltm4680, ltm4686,
- ltm4700,
+ ltm2987, ltm4664, ltm4673, ltm4675, ltm4676, ltm4677, ltm4678, ltm4680,
+ ltm4686, ltm4700,
};
/* Common for all chips */
@@ -50,7 +51,7 @@ enum chips {
#define LTC3880_MFR_CLEAR_PEAKS 0xe3
#define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4
-/* LTC3883, LTC3884, LTC3886, LTC3889, LTC7132, LTC7880 */
+/* LTC3883, LTC3884, LTC3886, LTC3889, LTC7132, LTC7841 and LTC7880 only */
#define LTC3883_MFR_IIN_PEAK 0xe1
/* LTC2975 only */
@@ -61,6 +62,7 @@ enum chips {
#define LTC2978_ID_MASK 0xfff0
+#define LT7170_ID 0x1C10
#define LTC2972_ID 0x0310
#define LTC2974_ID 0x0210
#define LTC2975_ID 0x0220
@@ -80,10 +82,13 @@ enum chips {
#define LTC3887_ID 0x4700
#define LTC3889_ID 0x4900
#define LTC7132_ID 0x4CE0
+#define LTC7841_ID 0x40D0
#define LTC7880_ID 0x49E0
#define LTM2987_ID_A 0x8010 /* A/B for two die IDs */
#define LTM2987_ID_B 0x8020
#define LTM4664_ID 0x4120
+#define LTM4673_ID_REV1 0x0230
+#define LTM4673_ID 0x4480
#define LTM4675_ID 0x47a0
#define LTM4676_ID_REV1 0x4400
#define LTM4676_ID_REV2 0x4480
@@ -533,6 +538,8 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page,
}
static const struct i2c_device_id ltc2978_id[] = {
+ {"lt7170", lt7170},
+ {"lt7171", lt7171},
{"ltc2972", ltc2972},
{"ltc2974", ltc2974},
{"ltc2975", ltc2975},
@@ -548,9 +555,11 @@ static const struct i2c_device_id ltc2978_id[] = {
{"ltc3887", ltc3887},
{"ltc3889", ltc3889},
{"ltc7132", ltc7132},
+ {"ltc7841", ltc7841},
{"ltc7880", ltc7880},
{"ltm2987", ltm2987},
{"ltm4664", ltm4664},
+ {"ltm4673", ltm4673},
{"ltm4675", ltm4675},
{"ltm4676", ltm4676},
{"ltm4677", ltm4677},
@@ -609,7 +618,7 @@ static int ltc2978_get_id(struct i2c_client *client)
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
if (ret < 0)
return ret;
- if (ret < 3 || strncmp(buf, "LTC", 3))
+ if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3)))
return -ENODEV;
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
@@ -624,6 +633,25 @@ static int ltc2978_get_id(struct i2c_client *client)
chip_id &= LTC2978_ID_MASK;
+ if (chip_id == LT7170_ID) {
+ u8 buf[I2C_SMBUS_BLOCK_MAX];
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
+ sizeof(buf), buf);
+ if (ret < 0)
+ return ret;
+
+ if (!strncmp(buf + 1, "LT7170", 6) ||
+ !strncmp(buf + 1, "LT7170-1", 8))
+ return lt7170;
+ if (!strncmp(buf + 1, "LT7171", 6) ||
+ !strncmp(buf + 1, "LT7171-1", 8))
+ return lt7171;
+
+ return -ENODEV;
+ }
+
if (chip_id == LTC2972_ID)
return ltc2972;
else if (chip_id == LTC2974_ID)
@@ -654,12 +682,16 @@ static int ltc2978_get_id(struct i2c_client *client)
return ltc3889;
else if (chip_id == LTC7132_ID)
return ltc7132;
+ else if (chip_id == LTC7841_ID)
+ return ltc7841;
else if (chip_id == LTC7880_ID)
return ltc7880;
else if (chip_id == LTM2987_ID_A || chip_id == LTM2987_ID_B)
return ltm2987;
else if (chip_id == LTM4664_ID)
return ltm4664;
+ else if (chip_id == LTM4673_ID || chip_id == LTM4673_ID_REV1)
+ return ltm4673;
else if (chip_id == LTM4675_ID)
return ltm4675;
else if (chip_id == LTM4676_ID_REV1 || chip_id == LTM4676_ID_REV2 ||
@@ -731,6 +763,20 @@ static int ltc2978_probe(struct i2c_client *client)
data->temp2_max = 0x7c00;
switch (data->id) {
+ case lt7170:
+ case lt7171:
+ data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING;
+ info->read_word_data = ltc3883_read_word_data;
+ info->pages = LTC3883_NUM_PAGES;
+ info->format[PSC_VOLTAGE_IN] = ieee754;
+ info->format[PSC_VOLTAGE_OUT] = ieee754;
+ info->format[PSC_CURRENT_OUT] = ieee754;
+ info->format[PSC_TEMPERATURE] = ieee754;
+ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+ | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+ break;
case ltc2972:
info->read_word_data = ltc2975_read_word_data;
info->pages = LTC2972_NUM_PAGES;
@@ -854,6 +900,31 @@ static int ltc2978_probe(struct i2c_client *client)
| PMBUS_HAVE_POUT
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
break;
+ case ltc7841:
+ data->features |= FEAT_CLEAR_PEAKS;
+ info->read_word_data = ltc3883_read_word_data;
+ info->pages = LTC3883_NUM_PAGES;
+ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
+ | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_IOUT
+ | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+ break;
+ case ltm4673:
+ data->features |= FEAT_NEEDS_POLLING;
+ info->read_word_data = ltc2975_read_word_data;
+ info->pages = LTC2974_NUM_PAGES;
+ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
+ | PMBUS_HAVE_TEMP2;
+ for (i = 0; i < info->pages; i++) {
+ info->func[i] |= PMBUS_HAVE_IIN
+ | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+ | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+ | PMBUS_HAVE_PIN
+ | PMBUS_HAVE_POUT
+ | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+ }
+ break;
default:
return -ENODEV;
}
@@ -892,6 +963,8 @@ static int ltc2978_probe(struct i2c_client *client)
#ifdef CONFIG_OF
static const struct of_device_id ltc2978_of_match[] = {
+ { .compatible = "lltc,lt7170" },
+ { .compatible = "lltc,lt7171" },
{ .compatible = "lltc,ltc2972" },
{ .compatible = "lltc,ltc2974" },
{ .compatible = "lltc,ltc2975" },
@@ -907,9 +980,11 @@ static const struct of_device_id ltc2978_of_match[] = {
{ .compatible = "lltc,ltc3887" },
{ .compatible = "lltc,ltc3889" },
{ .compatible = "lltc,ltc7132" },
+ { .compatible = "lltc,ltc7841" },
{ .compatible = "lltc,ltc7880" },
{ .compatible = "lltc,ltm2987" },
{ .compatible = "lltc,ltm4664" },
+ { .compatible = "lltc,ltm4673" },
{ .compatible = "lltc,ltm4675" },
{ .compatible = "lltc,ltm4676" },
{ .compatible = "lltc,ltm4677" },
@@ -936,4 +1011,4 @@ module_i2c_driver(ltc2978_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for LTC2978 and compatible chips");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ltc3815.c b/drivers/hwmon/pmbus/ltc3815.c
index f2023b17aa8d..824c16a75e2c 100644
--- a/drivers/hwmon/pmbus/ltc3815.c
+++ b/drivers/hwmon/pmbus/ltc3815.c
@@ -143,7 +143,7 @@ static int ltc3815_write_word_data(struct i2c_client *client, int page,
}
static const struct i2c_device_id ltc3815_id[] = {
- {"ltc3815", 0},
+ {"ltc3815"},
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc3815_id);
@@ -208,4 +208,4 @@ module_i2c_driver(ltc3815_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for LTC3815");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c
new file mode 100644
index 000000000000..aabd0bcdfeee
--- /dev/null
+++ b/drivers/hwmon/pmbus/ltc4286.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include "pmbus.h"
+
+/* LTC4286 register */
+#define LTC4286_MFR_CONFIG1 0xF2
+
+/* LTC4286 configuration */
+#define VRANGE_SELECT_BIT BIT(1)
+
+#define LTC4286_MFR_ID_SIZE 3
+
+/*
+ * Initialize the MBR as default settings which is referred to LTC4286 datasheet
+ * (March 22, 2022 version) table 3 page 16
+ */
+static struct pmbus_driver_info ltc4286_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 32,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_VOLTAGE_OUT] = 32,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 1,
+ .m[PSC_CURRENT_OUT] = 1024,
+ .b[PSC_CURRENT_OUT] = 0,
+ /*
+ * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit.
+ * However, the rsense value that user input is micro ohm.
+ * Thus, the MBR setting which involves rsense should be shifted by 6 digits.
+ */
+ .R[PSC_CURRENT_OUT] = 3 - 6,
+ .m[PSC_POWER] = 1,
+ .b[PSC_POWER] = 0,
+ /*
+ * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit.
+ * However, the rsense value that user input is micro ohm.
+ * Thus, the MBR setting which involves rsense should be shifted by 6 digits.
+ */
+ .R[PSC_POWER] = 4 - 6,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 273,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static const struct i2c_device_id ltc4286_id[] = {
+ { "ltc4286", },
+ { "ltc4287", },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ltc4286_id);
+
+static int ltc4286_probe(struct i2c_client *client)
+{
+ int ret;
+ const struct i2c_device_id *mid;
+ u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
+ struct pmbus_driver_info *info;
+ u32 rsense;
+ int vrange_nval, vrange_oval;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer);
+ if (ret < 0) {
+ return dev_err_probe(&client->dev, ret,
+ "Failed to read manufacturer id\n");
+ }
+
+ /*
+ * Refer to ltc4286 datasheet page 20
+ * the manufacturer id is LTC
+ */
+ if (ret != LTC4286_MFR_ID_SIZE ||
+ strncmp(block_buffer, "LTC", LTC4286_MFR_ID_SIZE)) {
+ return dev_err_probe(&client->dev, -ENODEV,
+ "Manufacturer id mismatch\n");
+ }
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
+ if (ret < 0) {
+ return dev_err_probe(&client->dev, ret,
+ "Failed to read manufacturer model\n");
+ }
+
+ for (mid = ltc4286_id; mid->name[0]; mid++) {
+ if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
+ break;
+ }
+ if (!mid->name[0])
+ return dev_err_probe(&client->dev, -ENODEV,
+ "Unsupported device\n");
+
+ if (of_property_read_u32(client->dev.of_node,
+ "shunt-resistor-micro-ohms", &rsense))
+ rsense = 300; /* 0.3 mOhm if not set via DT */
+
+ if (rsense == 0)
+ return -EINVAL;
+
+ /* Check for the latter MBR value won't overflow */
+ if (rsense > (INT_MAX / 1024))
+ return -EINVAL;
+
+ info = devm_kmemdup(&client->dev, &ltc4286_info, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ /* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */
+ vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1);
+ if (vrange_oval < 0)
+ return dev_err_probe(&client->dev, vrange_oval,
+ "Failed to read manufacturer configuration one\n");
+ vrange_nval = vrange_oval;
+
+ if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) {
+ vrange_nval &=
+ ~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */
+
+ info->m[PSC_VOLTAGE_IN] = 128;
+ info->m[PSC_VOLTAGE_OUT] = 128;
+ info->m[PSC_POWER] = 4 * rsense;
+ } else {
+ vrange_nval |=
+ VRANGE_SELECT_BIT; /* VRANGE_SELECT = 1, 102.4 volts */
+
+ info->m[PSC_POWER] = rsense;
+ }
+ if (vrange_nval != vrange_oval) {
+ /* Set MFR1 CONFIG register bit 1 VRANGE_SELECT */
+ ret = i2c_smbus_write_word_data(client, LTC4286_MFR_CONFIG1,
+ vrange_nval);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to set vrange\n");
+ }
+
+ info->m[PSC_CURRENT_OUT] = 1024 * rsense;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id ltc4286_of_match[] = {
+ { .compatible = "lltc,ltc4286" },
+ { .compatible = "lltc,ltc4287" },
+ {}
+};
+
+static struct i2c_driver ltc4286_driver = {
+ .driver = {
+ .name = "ltc4286",
+ .of_match_table = ltc4286_of_match,
+ },
+ .probe = ltc4286_probe,
+ .id_table = ltc4286_id,
+};
+
+module_i2c_driver(ltc4286_driver);
+
+MODULE_AUTHOR("Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>");
+MODULE_DESCRIPTION("PMBUS driver for LTC4286 and compatibles");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/pmbus/max15301.c b/drivers/hwmon/pmbus/max15301.c
index 2cfaa62aedd6..d5810b88ea8d 100644
--- a/drivers/hwmon/pmbus/max15301.c
+++ b/drivers/hwmon/pmbus/max15301.c
@@ -23,16 +23,15 @@
#include "pmbus.h"
static const struct i2c_device_id max15301_id[] = {
- {"bmr461", 0},
- {"max15301", 0},
+ { "bmr461" },
+ { "max15301" },
+ { "max15303" },
{}
};
MODULE_DEVICE_TABLE(i2c, max15301_id);
struct max15301_data {
int id;
- ktime_t access; /* Chip access time */
- int delay; /* Delay between chip accesses in us */
struct pmbus_driver_info info;
};
@@ -55,89 +54,6 @@ static struct max15301_data max15301_data = {
}
};
-/* This chip needs a delay between accesses */
-static inline void max15301_wait(const struct max15301_data *data)
-{
- if (data->delay) {
- s64 delta = ktime_us_delta(ktime_get(), data->access);
-
- if (delta < data->delay)
- udelay(data->delay - delta);
- }
-}
-
-static int max15301_read_word_data(struct i2c_client *client, int page,
- int phase, int reg)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct max15301_data *data = to_max15301_data(info);
- int ret;
-
- if (page > 0)
- return -ENXIO;
-
- if (reg >= PMBUS_VIRT_BASE)
- return -ENXIO;
-
- max15301_wait(data);
- ret = pmbus_read_word_data(client, page, phase, reg);
- data->access = ktime_get();
-
- return ret;
-}
-
-static int max15301_read_byte_data(struct i2c_client *client, int page, int reg)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct max15301_data *data = to_max15301_data(info);
- int ret;
-
- if (page > 0)
- return -ENXIO;
-
- max15301_wait(data);
- ret = pmbus_read_byte_data(client, page, reg);
- data->access = ktime_get();
-
- return ret;
-}
-
-static int max15301_write_word_data(struct i2c_client *client, int page, int reg,
- u16 word)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct max15301_data *data = to_max15301_data(info);
- int ret;
-
- if (page > 0)
- return -ENXIO;
-
- if (reg >= PMBUS_VIRT_BASE)
- return -ENXIO;
-
- max15301_wait(data);
- ret = pmbus_write_word_data(client, page, reg, word);
- data->access = ktime_get();
-
- return ret;
-}
-
-static int max15301_write_byte(struct i2c_client *client, int page, u8 value)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct max15301_data *data = to_max15301_data(info);
- int ret;
-
- if (page > 0)
- return -ENXIO;
-
- max15301_wait(data);
- ret = pmbus_write_byte(client, page, value);
- data->access = ktime_get();
-
- return ret;
-}
-
static int max15301_probe(struct i2c_client *client)
{
int status;
@@ -164,12 +80,7 @@ static int max15301_probe(struct i2c_client *client)
return -ENODEV;
}
- max15301_data.delay = delay;
-
- info->read_byte_data = max15301_read_byte_data;
- info->read_word_data = max15301_read_word_data;
- info->write_byte = max15301_write_byte;
- info->write_word_data = max15301_write_word_data;
+ info->access_delay = delay;
return pmbus_do_probe(client, info);
}
@@ -187,4 +98,4 @@ module_i2c_driver(max15301_driver);
MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX15301");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c
index a573a0ab9e48..eb84915c2a83 100644
--- a/drivers/hwmon/pmbus/max16064.c
+++ b/drivers/hwmon/pmbus/max16064.c
@@ -91,7 +91,7 @@ static int max16064_probe(struct i2c_client *client)
}
static const struct i2c_device_id max16064_id[] = {
- {"max16064", 0},
+ {"max16064"},
{}
};
@@ -111,4 +111,4 @@ module_i2c_driver(max16064_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c
index 3ab219504600..d696e506aafb 100644
--- a/drivers/hwmon/pmbus/max16601.c
+++ b/drivers/hwmon/pmbus/max16601.c
@@ -366,4 +366,4 @@ module_i2c_driver(max16601_driver);
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601");
MODULE_LICENSE("GPL v2");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max17616.c b/drivers/hwmon/pmbus/max17616.c
new file mode 100644
index 000000000000..1d4a0ddb95bb
--- /dev/null
+++ b/drivers/hwmon/pmbus/max17616.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Analog Devices MAX17616/MAX17616A
+ *
+ * Copyright (C) 2025 Analog Devices, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+static struct pmbus_driver_info max17616_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .m[PSC_VOLTAGE_IN] = 512,
+ .b[PSC_VOLTAGE_IN] = -18,
+ .R[PSC_VOLTAGE_IN] = -1,
+
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .m[PSC_VOLTAGE_OUT] = 512,
+ .b[PSC_VOLTAGE_OUT] = -18,
+ .R[PSC_VOLTAGE_OUT] = -1,
+
+ .format[PSC_CURRENT_OUT] = direct,
+ .m[PSC_CURRENT_OUT] = 5845,
+ .b[PSC_CURRENT_OUT] = 80,
+ .R[PSC_CURRENT_OUT] = -1,
+
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_TEMPERATURE] = 71,
+ .b[PSC_TEMPERATURE] = 19653,
+ .R[PSC_TEMPERATURE] = -1,
+
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_HAVE_STATUS_TEMP,
+};
+
+static int max17616_probe(struct i2c_client *client)
+{
+ return pmbus_do_probe(client, &max17616_info);
+}
+
+static const struct i2c_device_id max17616_id[] = {
+ { "max17616" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17616_id);
+
+static const struct of_device_id max17616_of_match[] = {
+ { .compatible = "adi,max17616" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max17616_of_match);
+
+static struct i2c_driver max17616_driver = {
+ .driver = {
+ .name = "max17616",
+ .of_match_table = max17616_of_match,
+ },
+ .probe = max17616_probe,
+ .id_table = max17616_id,
+};
+module_i2c_driver(max17616_driver);
+
+MODULE_AUTHOR("Kim Seer Paller <kimseer.paller@analog.com>");
+MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX17616/MAX17616A");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c
index d56ec24764fd..95869d198ecf 100644
--- a/drivers/hwmon/pmbus/max20730.c
+++ b/drivers/hwmon/pmbus/max20730.c
@@ -787,4 +787,4 @@ module_i2c_driver(max20730_driver);
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX20710 / MAX20730 / MAX20734 / MAX20743");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c
index 6ebd71cd081b..ac8c43122133 100644
--- a/drivers/hwmon/pmbus/max20751.c
+++ b/drivers/hwmon/pmbus/max20751.c
@@ -32,7 +32,7 @@ static int max20751_probe(struct i2c_client *client)
}
static const struct i2c_device_id max20751_id[] = {
- {"max20751", 0},
+ {"max20751"},
{}
};
@@ -51,4 +51,4 @@ module_i2c_driver(max20751_driver);
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX20751");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c
index f9aa576495a5..1f94d38a1637 100644
--- a/drivers/hwmon/pmbus/max31785.c
+++ b/drivers/hwmon/pmbus/max31785.c
@@ -3,6 +3,7 @@
* Copyright (C) 2017 IBM Corp.
*/
+#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@@ -23,19 +24,119 @@ enum max31785_regs {
#define MAX31785_NR_PAGES 23
#define MAX31785_NR_FAN_PAGES 6
+#define MAX31785_WAIT_DELAY_US 250
-static int max31785_read_byte_data(struct i2c_client *client, int page,
- int reg)
+struct max31785_data {
+ ktime_t access; /* Chip access time */
+ struct pmbus_driver_info info;
+};
+
+#define to_max31785_data(x) container_of(x, struct max31785_data, info)
+
+/*
+ * MAX31785 Driver Workaround
+ *
+ * The MAX31785 fan controller occasionally exhibits communication issues.
+ * These issues are not indicated by the device itself, except for occasional
+ * NACK responses during master transactions. No error bits are set in STATUS_BYTE.
+ *
+ * To address this, we introduce a delay of 250us between consecutive accesses
+ * to the fan controller. This delay helps mitigate communication problems by
+ * allowing sufficient time between accesses.
+ */
+static inline void max31785_wait(const struct max31785_data *data)
{
- if (page < MAX31785_NR_PAGES)
- return -ENODATA;
+ s64 delta = ktime_us_delta(ktime_get(), data->access);
+
+ if (delta < MAX31785_WAIT_DELAY_US)
+ usleep_range(MAX31785_WAIT_DELAY_US - delta,
+ MAX31785_WAIT_DELAY_US);
+}
+
+static int max31785_i2c_write_byte_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int command, u16 data)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = i2c_smbus_write_byte_data(client, command, data);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int max31785_i2c_read_word_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int command)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = i2c_smbus_read_word_data(client, command);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int _max31785_read_byte_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int page, int command)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = pmbus_read_byte_data(client, page, command);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int _max31785_write_byte_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int page, int command, u16 data)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = pmbus_write_byte_data(client, page, command, data);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int _max31785_read_word_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int page, int phase, int command)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = pmbus_read_word_data(client, page, phase, command);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int _max31785_write_word_data(struct i2c_client *client,
+ struct max31785_data *driver_data,
+ int page, int command, u16 data)
+{
+ int rc;
+
+ max31785_wait(driver_data);
+ rc = pmbus_write_word_data(client, page, command, data);
+ driver_data->access = ktime_get();
+ return rc;
+}
+
+static int max31785_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct max31785_data *driver_data = to_max31785_data(info);
switch (reg) {
case PMBUS_VOUT_MODE:
return -ENOTSUPP;
case PMBUS_FAN_CONFIG_12:
- return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES,
- reg);
+ return _max31785_read_byte_data(client, driver_data,
+ page - MAX31785_NR_PAGES,
+ reg);
}
return -ENODATA;
@@ -102,16 +203,19 @@ static int max31785_get_pwm(struct i2c_client *client, int page)
return rv;
}
-static int max31785_get_pwm_mode(struct i2c_client *client, int page)
+static int max31785_get_pwm_mode(struct i2c_client *client,
+ struct max31785_data *driver_data, int page)
{
int config;
int command;
- config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);
+ config = _max31785_read_byte_data(client, driver_data, page,
+ PMBUS_FAN_CONFIG_12);
if (config < 0)
return config;
- command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1);
+ command = _max31785_read_word_data(client, driver_data, page, 0xff,
+ PMBUS_FAN_COMMAND_1);
if (command < 0)
return command;
@@ -129,6 +233,8 @@ static int max31785_get_pwm_mode(struct i2c_client *client, int page)
static int max31785_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct max31785_data *driver_data = to_max31785_data(info);
u32 val;
int rv;
@@ -157,7 +263,7 @@ static int max31785_read_word_data(struct i2c_client *client, int page,
rv = max31785_get_pwm(client, page);
break;
case PMBUS_VIRT_PWM_ENABLE_1:
- rv = max31785_get_pwm_mode(client, page);
+ rv = max31785_get_pwm_mode(client, driver_data, page);
break;
default:
rv = -ENODATA;
@@ -188,8 +294,36 @@ static inline u32 max31785_scale_pwm(u32 sensor_val)
return (sensor_val * 100) / 255;
}
-static int max31785_pwm_enable(struct i2c_client *client, int page,
- u16 word)
+static int max31785_update_fan(struct i2c_client *client,
+ struct max31785_data *driver_data, int page,
+ u8 config, u8 mask, u16 command)
+{
+ int from, rv;
+ u8 to;
+
+ from = _max31785_read_byte_data(client, driver_data, page,
+ PMBUS_FAN_CONFIG_12);
+ if (from < 0)
+ return from;
+
+ to = (from & ~mask) | (config & mask);
+
+ if (to != from) {
+ rv = _max31785_write_byte_data(client, driver_data, page,
+ PMBUS_FAN_CONFIG_12, to);
+ if (rv < 0)
+ return rv;
+ }
+
+ rv = _max31785_write_word_data(client, driver_data, page,
+ PMBUS_FAN_COMMAND_1, command);
+
+ return rv;
+}
+
+static int max31785_pwm_enable(struct i2c_client *client,
+ struct max31785_data *driver_data, int page,
+ u16 word)
{
int config = 0;
int rate;
@@ -217,18 +351,23 @@ static int max31785_pwm_enable(struct i2c_client *client, int page,
return -EINVAL;
}
- return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate);
+ return max31785_update_fan(client, driver_data, page, config,
+ PB_FAN_1_RPM, rate);
}
static int max31785_write_word_data(struct i2c_client *client, int page,
int reg, u16 word)
{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct max31785_data *driver_data = to_max31785_data(info);
+
switch (reg) {
case PMBUS_VIRT_PWM_1:
- return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM,
- max31785_scale_pwm(word));
+ return max31785_update_fan(client, driver_data, page, 0,
+ PB_FAN_1_RPM,
+ max31785_scale_pwm(word));
case PMBUS_VIRT_PWM_ENABLE_1:
- return max31785_pwm_enable(client, page, word);
+ return max31785_pwm_enable(client, driver_data, page, word);
default:
break;
}
@@ -303,13 +442,16 @@ static int max31785_configure_dual_tach(struct i2c_client *client,
{
int ret;
int i;
+ struct max31785_data *driver_data = to_max31785_data(info);
for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) {
- ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ ret = max31785_i2c_write_byte_data(client, driver_data,
+ PMBUS_PAGE, i);
if (ret < 0)
return ret;
- ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG);
+ ret = max31785_i2c_read_word_data(client, driver_data,
+ MFR_FAN_CONFIG);
if (ret < 0)
return ret;
@@ -329,6 +471,7 @@ static int max31785_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct pmbus_driver_info *info;
+ struct max31785_data *driver_data;
bool dual_tach = false;
int ret;
@@ -337,13 +480,16 @@ static int max31785_probe(struct i2c_client *client)
I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
- info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);
- if (!info)
+ driver_data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL);
+ if (!driver_data)
return -ENOMEM;
+ info = &driver_data->info;
+ driver_data->access = ktime_get();
*info = max31785_info;
- ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255);
+ ret = max31785_i2c_write_byte_data(client, driver_data,
+ PMBUS_PAGE, 255);
if (ret < 0)
return ret;
@@ -372,9 +518,9 @@ static int max31785_probe(struct i2c_client *client)
}
static const struct i2c_device_id max31785_id[] = {
- { "max31785", 0 },
- { "max31785a", 0 },
- { "max31785b", 0 },
+ { "max31785" },
+ { "max31785a" },
+ { "max31785b" },
{ },
};
@@ -403,4 +549,4 @@ module_i2c_driver(max31785_driver);
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("PMBus driver for the Maxim MAX31785");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c
index fe7f6b1b0985..8ea4e68d4e9d 100644
--- a/drivers/hwmon/pmbus/max34440.c
+++ b/drivers/hwmon/pmbus/max34440.c
@@ -12,9 +12,27 @@
#include <linux/init.h>
#include <linux/err.h>
#include <linux/i2c.h>
+#include <linux/delay.h>
#include "pmbus.h"
-enum chips { max34440, max34441, max34446, max34451, max34460, max34461 };
+enum chips {
+ adpm12160,
+ adpm12200,
+ max34440,
+ max34441,
+ max34446,
+ max34451,
+ max34460,
+ max34461,
+};
+
+/*
+ * Firmware is sometimes not ready if we try and read the
+ * data from the page immediately after setting. Maxim
+ * recommends 50us delay due to the chip failing to clock
+ * stretch long enough here.
+ */
+#define MAX34440_PAGE_CHANGE_DELAY 50
#define MAX34440_MFR_VOUT_PEAK 0xd4
#define MAX34440_MFR_IOUT_PEAK 0xd5
@@ -34,16 +52,21 @@ enum chips { max34440, max34441, max34446, max34451, max34460, max34461 };
/*
* The whole max344* family have IOUT_OC_WARN_LIMIT and IOUT_OC_FAULT_LIMIT
* swapped from the standard pmbus spec addresses.
+ * For max34451, version MAX34451ETNA6+ and later has this issue fixed.
*/
#define MAX34440_IOUT_OC_WARN_LIMIT 0x46
#define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A
+#define MAX34451ETNA6_MFR_REV 0x0012
+
#define MAX34451_MFR_CHANNEL_CONFIG 0xe4
#define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f
struct max34440_data {
int id;
struct pmbus_driver_info info;
+ u8 iout_oc_warn_limit;
+ u8 iout_oc_fault_limit;
};
#define to_max34440_data(x) container_of(x, struct max34440_data, info)
@@ -60,11 +83,11 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
switch (reg) {
case PMBUS_IOUT_OC_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase,
- MAX34440_IOUT_OC_FAULT_LIMIT);
+ data->iout_oc_fault_limit);
break;
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_read_word_data(client, page, phase,
- MAX34440_IOUT_OC_WARN_LIMIT);
+ data->iout_oc_warn_limit);
break;
case PMBUS_VIRT_READ_VOUT_MIN:
ret = pmbus_read_word_data(client, page, phase,
@@ -75,7 +98,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
MAX34440_MFR_VOUT_PEAK);
break;
case PMBUS_VIRT_READ_IOUT_AVG:
- if (data->id != max34446 && data->id != max34451)
+ if (data->id != max34446 && data->id != max34451 &&
+ data->id != adpm12160 && data->id != adpm12200)
return -ENXIO;
ret = pmbus_read_word_data(client, page, phase,
MAX34446_MFR_IOUT_AVG);
@@ -133,11 +157,11 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
switch (reg) {
case PMBUS_IOUT_OC_FAULT_LIMIT:
- ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_FAULT_LIMIT,
+ ret = pmbus_write_word_data(client, page, data->iout_oc_fault_limit,
word);
break;
case PMBUS_IOUT_OC_WARN_LIMIT:
- ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_WARN_LIMIT,
+ ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit,
word);
break;
case PMBUS_VIRT_RESET_POUT_HISTORY:
@@ -159,7 +183,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
case PMBUS_VIRT_RESET_IOUT_HISTORY:
ret = pmbus_write_word_data(client, page,
MAX34440_MFR_IOUT_PEAK, 0);
- if (!ret && (data->id == max34446 || data->id == max34451))
+ if (!ret && (data->id == max34446 || data->id == max34451 ||
+ data->id == adpm12160 || data->id == adpm12200))
ret = pmbus_write_word_data(client, page,
MAX34446_MFR_IOUT_AVG, 0);
@@ -235,9 +260,29 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
*/
int page, rv;
+ bool max34451_na6 = false;
+
+ rv = i2c_smbus_read_word_data(client, PMBUS_MFR_REVISION);
+ if (rv < 0)
+ return rv;
+
+ if (rv >= MAX34451ETNA6_MFR_REV) {
+ max34451_na6 = true;
+ data->info.format[PSC_VOLTAGE_IN] = direct;
+ data->info.format[PSC_CURRENT_IN] = direct;
+ data->info.m[PSC_VOLTAGE_IN] = 1;
+ data->info.b[PSC_VOLTAGE_IN] = 0;
+ data->info.R[PSC_VOLTAGE_IN] = 3;
+ data->info.m[PSC_CURRENT_IN] = 1;
+ data->info.b[PSC_CURRENT_IN] = 0;
+ data->info.R[PSC_CURRENT_IN] = 2;
+ data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT;
+ data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT;
+ }
for (page = 0; page < 16; page++) {
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ fsleep(MAX34440_PAGE_CHANGE_DELAY);
if (rv < 0)
return rv;
@@ -251,16 +296,30 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
case 0x20:
data->info.func[page] = PMBUS_HAVE_VOUT |
PMBUS_HAVE_STATUS_VOUT;
+
+ if (max34451_na6)
+ data->info.func[page] |= PMBUS_HAVE_VIN |
+ PMBUS_HAVE_STATUS_INPUT;
break;
case 0x21:
data->info.func[page] = PMBUS_HAVE_VOUT;
+
+ if (max34451_na6)
+ data->info.func[page] |= PMBUS_HAVE_VIN;
break;
case 0x22:
data->info.func[page] = PMBUS_HAVE_IOUT |
PMBUS_HAVE_STATUS_IOUT;
+
+ if (max34451_na6)
+ data->info.func[page] |= PMBUS_HAVE_IIN |
+ PMBUS_HAVE_STATUS_INPUT;
break;
case 0x23:
data->info.func[page] = PMBUS_HAVE_IOUT;
+
+ if (max34451_na6)
+ data->info.func[page] |= PMBUS_HAVE_IIN;
break;
default:
break;
@@ -271,6 +330,77 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
}
static struct pmbus_driver_info max34440_info[] = {
+ [adpm12160] = {
+ .pages = 19,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 125,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .m[PSC_VOLTAGE_OUT] = 125,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 0,
+ .m[PSC_CURRENT_IN] = 250,
+ .b[PSC_CURRENT_IN] = 0,
+ .R[PSC_CURRENT_IN] = -1,
+ .m[PSC_CURRENT_OUT] = 250,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = -1,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 2,
+ /* absent func below [18] are not for monitoring */
+ .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT,
+ .func[4] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[5] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[6] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[7] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[8] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[9] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[10] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
+ .read_word_data = max34440_read_word_data,
+ .write_word_data = max34440_write_word_data,
+ },
+ [adpm12200] = {
+ .pages = 19,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 125,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .m[PSC_VOLTAGE_OUT] = 125,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 0,
+ .m[PSC_CURRENT_IN] = 250,
+ .b[PSC_CURRENT_IN] = 0,
+ .R[PSC_CURRENT_IN] = -1,
+ .m[PSC_CURRENT_OUT] = 250,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = -1,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 2,
+ /* absent func below [18] are not for monitoring */
+ .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT,
+ .func[4] = PMBUS_HAVE_STATUS_IOUT,
+ .func[5] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[6] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[7] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[8] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
+ .func[9] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[10] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[14] = PMBUS_HAVE_IOUT,
+ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
+ .read_word_data = max34440_read_word_data,
+ .write_word_data = max34440_write_word_data,
+ },
[max34440] = {
.pages = 14,
.format[PSC_VOLTAGE_IN] = direct,
@@ -312,6 +442,7 @@ static struct pmbus_driver_info max34440_info[] = {
.read_byte_data = max34440_read_byte_data,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
[max34441] = {
.pages = 12,
@@ -355,6 +486,7 @@ static struct pmbus_driver_info max34440_info[] = {
.read_byte_data = max34440_read_byte_data,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
[max34446] = {
.pages = 7,
@@ -392,6 +524,7 @@ static struct pmbus_driver_info max34440_info[] = {
.read_byte_data = max34440_read_byte_data,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
[max34451] = {
.pages = 21,
@@ -415,6 +548,7 @@ static struct pmbus_driver_info max34440_info[] = {
.func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
[max34460] = {
.pages = 18,
@@ -445,6 +579,7 @@ static struct pmbus_driver_info max34440_info[] = {
.func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
[max34461] = {
.pages = 23,
@@ -480,6 +615,7 @@ static struct pmbus_driver_info max34440_info[] = {
.func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
.read_word_data = max34440_read_word_data,
.write_word_data = max34440_write_word_data,
+ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
},
};
@@ -494,17 +630,24 @@ static int max34440_probe(struct i2c_client *client)
return -ENOMEM;
data->id = i2c_match_id(max34440_id, client)->driver_data;
data->info = max34440_info[data->id];
+ data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT;
+ data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT;
if (data->id == max34451) {
rv = max34451_set_supported_funcs(client, data);
if (rv)
return rv;
+ } else if (data->id == adpm12160 || data->id == adpm12200) {
+ data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT;
+ data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT;
}
return pmbus_do_probe(client, &data->info);
}
static const struct i2c_device_id max34440_id[] = {
+ {"adpm12160", adpm12160},
+ {"adpm12200", adpm12200},
{"max34440", max34440},
{"max34441", max34441},
{"max34446", max34446},
@@ -529,4 +672,4 @@ module_i2c_driver(max34440_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c
index ae8573fdf5ba..b3a2a7492bbf 100644
--- a/drivers/hwmon/pmbus/max8688.c
+++ b/drivers/hwmon/pmbus/max8688.c
@@ -171,7 +171,7 @@ static int max8688_probe(struct i2c_client *client)
}
static const struct i2c_device_id max8688_id[] = {
- {"max8688", 0},
+ {"max8688"},
{ }
};
@@ -191,4 +191,4 @@ module_i2c_driver(max8688_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2856.c b/drivers/hwmon/pmbus/mp2856.c
new file mode 100644
index 000000000000..e83c70a3583f
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2856.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS2856/2857
+ * Monolithic Power Systems VR Controllers
+ *
+ * Copyright (C) 2023 Quanta Computer lnc.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include "pmbus.h"
+
+/* Vendor specific registers. */
+#define MP2856_MFR_VR_MULTI_CONFIG_R1 0x0d
+#define MP2856_MFR_VR_MULTI_CONFIG_R2 0x1d
+
+#define MP2856_MUL1_BOOT_SR_R2 0x10
+#define MP2856_VR_ACTIVE BIT(15)
+
+#define MP2856_MFR_VR_CONFIG2 0x5e
+#define MP2856_VOUT_MODE BIT(11)
+
+#define MP2856_MFR_VR_CONFIG1 0x68
+#define MP2856_DRMOS_KCS GENMASK(13, 12)
+
+#define MP2856_MFR_READ_CS1_2_R1 0x82
+#define MP2856_MFR_READ_CS3_4_R1 0x83
+#define MP2856_MFR_READ_CS5_6_R1 0x84
+#define MP2856_MFR_READ_CS7_8_R1 0x85
+#define MP2856_MFR_READ_CS9_10_R1 0x86
+#define MP2856_MFR_READ_CS11_12_R1 0x87
+
+#define MP2856_MFR_READ_CS1_2_R2 0x85
+#define MP2856_MFR_READ_CS3_4_R2 0x86
+#define MP2856_MFR_READ_CS5_6_R2 0x87
+
+#define MP2856_MAX_PHASE_RAIL1 8
+#define MP2856_MAX_PHASE_RAIL2 4
+
+#define MP2857_MAX_PHASE_RAIL1 12
+#define MP2857_MAX_PHASE_RAIL2 4
+
+#define MP2856_PAGE_NUM 2
+
+enum chips { mp2856, mp2857 };
+
+static const int mp2856_max_phases[][MP2856_PAGE_NUM] = {
+ [mp2856] = { MP2856_MAX_PHASE_RAIL1, MP2856_MAX_PHASE_RAIL2 },
+ [mp2857] = { MP2857_MAX_PHASE_RAIL1, MP2857_MAX_PHASE_RAIL2 },
+};
+
+static const struct i2c_device_id mp2856_id[] = {
+ {"mp2856", mp2856},
+ {"mp2857", mp2857},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, mp2856_id);
+
+struct mp2856_data {
+ struct pmbus_driver_info info;
+ int vout_format[MP2856_PAGE_NUM];
+ int curr_sense_gain[MP2856_PAGE_NUM];
+ int max_phases[MP2856_PAGE_NUM];
+};
+
+#define to_mp2856_data(x) container_of(x, struct mp2856_data, info)
+
+#define MAX_LIN_MANTISSA (1023 * 1000)
+#define MIN_LIN_MANTISSA (511 * 1000)
+
+static u16 val2linear11(s64 val)
+{
+ s16 exponent = 0, mantissa;
+ bool negative = false;
+
+ if (val == 0)
+ return 0;
+
+ if (val < 0) {
+ negative = true;
+ val = -val;
+ }
+
+ /* Reduce large mantissa until it fits into 10 bit */
+ while (val >= MAX_LIN_MANTISSA && exponent < 15) {
+ exponent++;
+ val >>= 1;
+ }
+ /* Increase small mantissa to improve precision */
+ while (val < MIN_LIN_MANTISSA && exponent > -15) {
+ exponent--;
+ val <<= 1;
+ }
+
+ /* Convert mantissa from milli-units to units */
+ mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff);
+
+ /* restore sign */
+ if (negative)
+ mantissa = -mantissa;
+
+ /* Convert to 5 bit exponent, 11 bit mantissa */
+ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
+}
+
+static int
+mp2856_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg,
+ u16 mask)
+{
+ int ret = pmbus_read_word_data(client, page, phase, reg);
+
+ return (ret > 0) ? ret & mask : ret;
+}
+
+static int
+mp2856_read_vout(struct i2c_client *client, struct mp2856_data *data, int page,
+ int phase, u8 reg)
+{
+ int ret;
+
+ ret = mp2856_read_word_helper(client, page, phase, reg,
+ GENMASK(9, 0));
+ if (ret < 0)
+ return ret;
+
+ /* convert vout result to direct format */
+ ret = (data->vout_format[page] == vid) ?
+ ((ret + 49) * 5) : ((ret * 1000) >> 8);
+
+ return ret;
+}
+
+static int
+mp2856_read_phase(struct i2c_client *client, struct mp2856_data *data,
+ int page, int phase, u8 reg)
+{
+ int ret;
+ int val;
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (!((phase + 1) % MP2856_PAGE_NUM))
+ ret >>= 8;
+ ret &= 0xff;
+
+ /*
+ * Output value is calculated as: (READ_CSx * 12.5mV - 1.23V) / (Kcs * Rcs)
+ */
+ val = (ret * 125) - 12300;
+
+ return val2linear11(val);
+}
+
+static int
+mp2856_read_phases(struct i2c_client *client, struct mp2856_data *data,
+ int page, int phase)
+{
+ int ret;
+
+ if (page == 0) {
+ switch (phase) {
+ case 0 ... 1:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R1);
+ break;
+ case 2 ... 3:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS3_4_R1);
+ break;
+ case 4 ... 5:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS5_6_R1);
+ break;
+ case 6 ... 7:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS7_8_R1);
+ break;
+ default:
+ return -ENODATA;
+ }
+ } else {
+ switch (phase) {
+ case 0 ... 1:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R2);
+ break;
+ case 2 ... 3:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R2);
+ break;
+ default:
+ return -ENODATA;
+ }
+ }
+ return ret;
+}
+
+static int
+mp2856_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2856_data *data = to_mp2856_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = mp2856_read_vout(client, data, page, phase, reg);
+ break;
+ case PMBUS_READ_IOUT:
+ if (phase != 0xff)
+ ret = mp2856_read_phases(client, data, page, phase);
+ else
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+static int
+mp2856_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /* Enforce VOUT direct format. */
+ return PB_VOUT_MODE_DIRECT;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int
+mp2856_identify_multiphase(struct i2c_client *client, u8 reg, u8 max_phase,
+ u16 mask)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= mask;
+ return (ret >= max_phase) ? max_phase : ret;
+}
+
+static int
+mp2856_identify_multiphase_rail1(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int ret, i;
+
+ ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R1,
+ MP2856_MAX_PHASE_RAIL1, GENMASK(3, 0));
+ if (ret < 0)
+ return ret;
+
+ data->info.phases[0] = (ret > data->max_phases[0]) ?
+ data->max_phases[0] : ret;
+
+ for (i = 0 ; i < data->info.phases[0]; i++)
+ data->info.pfunc[i] |= PMBUS_HAVE_IOUT;
+
+ return 0;
+}
+
+static int
+mp2856_identify_multiphase_rail2(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int ret, i;
+
+ ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R2,
+ MP2856_MAX_PHASE_RAIL2, GENMASK(2, 0));
+ if (ret < 0)
+ return ret;
+
+ data->info.phases[1] = (ret > data->max_phases[1]) ?
+ data->max_phases[1] : ret;
+
+ for (i = 0 ; i < data->info.phases[0]; i++)
+ data->info.pfunc[i] |= PMBUS_HAVE_IOUT;
+
+ return 0;
+}
+
+static int
+mp2856_current_sense_gain_get(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int i, ret;
+
+ /*
+ * Obtain DrMOS current sense gain of power stage from the register
+ * MP2856_MFR_VR_CONFIG1, bits 13-12. The value is selected as below:
+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other
+ * values are invalid.
+ */
+ for (i = 0 ; i < data->info.pages; i++) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ if (ret < 0)
+ return ret;
+ ret = i2c_smbus_read_word_data(client,
+ MP2856_MFR_VR_CONFIG1);
+ if (ret < 0)
+ return ret;
+
+ switch ((ret & MP2856_DRMOS_KCS) >> 12) {
+ case 0:
+ data->curr_sense_gain[i] = 50;
+ break;
+ case 1:
+ data->curr_sense_gain[i] = 85;
+ break;
+ case 2:
+ data->curr_sense_gain[i] = 97;
+ break;
+ default:
+ data->curr_sense_gain[i] = 100;
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+mp2856_identify_vout_format(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int i, ret;
+
+ for (i = 0; i < data->info.pages; i++) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MP2856_MFR_VR_CONFIG2);
+ if (ret < 0)
+ return ret;
+
+ data->vout_format[i] = (ret & MP2856_VOUT_MODE) ? linear : vid;
+ }
+ return 0;
+}
+
+static bool
+mp2856_is_rail2_active(struct i2c_client *client)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return true;
+
+ ret = i2c_smbus_read_word_data(client, MP2856_MUL1_BOOT_SR_R2);
+ if (ret < 0)
+ return true;
+
+ return (ret & MP2856_VR_ACTIVE) ? true : false;
+}
+
+static struct pmbus_driver_info mp2856_info = {
+ .pages = MP2856_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_TEMPERATURE] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP,
+ .read_byte_data = mp2856_read_byte_data,
+ .read_word_data = mp2856_read_word_data,
+};
+
+static int mp2856_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp2856_data *data;
+ enum chips chip_id;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp2856_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ chip_id = (kernel_ulong_t)i2c_get_match_data(client);
+
+ memcpy(data->max_phases, mp2856_max_phases[chip_id],
+ sizeof(data->max_phases));
+
+ memcpy(&data->info, &mp2856_info, sizeof(*info));
+ info = &data->info;
+
+ /* Identify multiphase configuration. */
+ ret = mp2856_identify_multiphase_rail1(client, data);
+ if (ret < 0)
+ return ret;
+
+ if (mp2856_is_rail2_active(client)) {
+ ret = mp2856_identify_multiphase_rail2(client, data);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* rail2 is not active */
+ info->pages = 1;
+ }
+
+ /* Obtain current sense gain of power stage. */
+ ret = mp2856_current_sense_gain_get(client, data);
+ if (ret)
+ return ret;
+
+ /* Identify vout format. */
+ ret = mp2856_identify_vout_format(client, data);
+ if (ret)
+ return ret;
+
+ /* set the device to page 0 */
+ i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id __maybe_unused mp2856_of_match[] = {
+ {.compatible = "mps,mp2856", .data = (void *)mp2856},
+ {.compatible = "mps,mp2857", .data = (void *)mp2857},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2856_of_match);
+
+static struct i2c_driver mp2856_driver = {
+ .driver = {
+ .name = "mp2856",
+ .of_match_table = mp2856_of_match,
+ },
+ .probe = mp2856_probe,
+ .id_table = mp2856_id,
+};
+
+module_i2c_driver(mp2856_driver);
+
+MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2856/MP2857 device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2869.c b/drivers/hwmon/pmbus/mp2869.c
new file mode 100644
index 000000000000..cc69a1e91dfe
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2869.c
@@ -0,0 +1,659 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2869)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+/*
+ * Vender specific registers, the register MFR_SVI3_IOUT_PRT(0x67),
+ * READ_PIN_EST(0x94)and READ_IIN_EST(0x95) redefine the standard
+ * PMBUS register. The MFR_VOUT_LOOP_CTRL(0x29) is used to identify
+ * the vout scale and the MFR_SVI3_IOUT_PRT(0x67) is used to identify
+ * the iout scale. The READ_PIN_EST(0x94) is used to read input power
+ * per rail. The MP2891 does not have standard READ_IIN register(0x89),
+ * the iin telemetry can be obtained through the vendor redefined
+ * register READ_IIN_EST(0x95).
+ */
+#define MFR_SVI3_IOUT_PRT 0x67
+#define MFR_READ_PIN_EST 0x94
+#define MFR_READ_IIN_EST 0x95
+#define MFR_TSNS_FLT_SET 0xBB
+
+#define MP2869_VIN_OV_FAULT_GAIN 4
+#define MP2869_READ_VOUT_DIV 1024
+#define MP2869_READ_IOUT_DIV 32
+#define MP2869_OVUV_LIMIT_SCALE 10
+#define MP2869_OVUV_DELTA_SCALE 50
+#define MP2869_TEMP_LIMIT_OFFSET 40
+#define MP2869_IOUT_LIMIT_UINT 8
+#define MP2869_POUT_OP_GAIN 2
+
+#define MP2869_PAGE_NUM 2
+
+#define MP2869_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+#define MP2869_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
+ PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
+ PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+struct mp2869_data {
+ struct pmbus_driver_info info;
+ bool mfr_thwn_flt_en;
+ int vout_scale[MP2869_PAGE_NUM];
+ int iout_scale[MP2869_PAGE_NUM];
+};
+
+static const int mp2869_vout_sacle[8] = {6400, 5120, 2560, 2048, 1024,
+ 4, 2, 1};
+static const int mp2869_iout_sacle[8] = {32, 1, 2, 4, 8, 16, 32, 64};
+
+#define to_mp2869_data(x) container_of(x, struct mp2869_data, info)
+
+static u16 mp2869_reg2data_linear11(u16 word)
+{
+ s16 exponent;
+ s32 mantissa;
+ s64 val;
+
+ exponent = ((s16)word) >> 11;
+ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
+ val = mantissa;
+
+ if (exponent >= 0)
+ val <<= exponent;
+ else
+ val >>= -exponent;
+
+ return val;
+}
+
+static int
+mp2869_identify_thwn_flt(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
+ if (ret < 0)
+ return ret;
+
+ data->mfr_thwn_flt_en = FIELD_GET(GENMASK(13, 13), ret);
+
+ return 0;
+}
+
+static int
+mp2869_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The output voltage is equal to the READ_VOUT(0x8B) register value multiply
+ * by vout_scale.
+ * Obtain vout scale from the register PMBUS_VOUT_SCALE_LOOP, bits 12-10
+ * PMBUS_VOUT_SCALE_LOOP[12:10]:
+ * 000b - 6.25mV/LSB, 001b - 5mV/LSB, 010b - 2.5mV/LSB, 011b - 2mV/LSB
+ * 100b - 1mV/Lsb, 101b - (1/256)mV/LSB, 110b - (1/512)mV/LSB,
+ * 111b - (1/1024)mV/LSB
+ */
+ data->vout_scale[page] = mp2869_vout_sacle[FIELD_GET(GENMASK(12, 10), ret)];
+
+ return 0;
+}
+
+static int
+mp2869_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The output current is equal to the READ_IOUT(0x8C) register value
+ * multiply by iout_scale.
+ * Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0].
+ * The value is selected as below:
+ * 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB,
+ * 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB
+ * 110b - 1A/LSB, 111b - 2A/LSB
+ */
+ data->iout_scale[page] = mp2869_iout_sacle[FIELD_GET(GENMASK(2, 0), ret)];
+
+ return 0;
+}
+
+static int mp2869_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /*
+ * The calculation of vout in this driver is based on direct format.
+ * As a result, the format of vout is enforced to direct.
+ */
+ ret = PB_VOUT_MODE_DIRECT;
+ break;
+ case PMBUS_STATUS_BYTE:
+ /*
+ * If the tsns digital fault is enabled, the TEMPERATURE flag
+ * of PMBUS_STATUS_BYTE should come from STATUS_MFR_SPECIFIC
+ * register bit1.
+ */
+ if (!data->mfr_thwn_flt_en)
+ return -ENODATA;
+
+ ret = pmbus_read_byte_data(client, page, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & ~GENMASK(2, 2)) |
+ FIELD_PREP(GENMASK(2, 2),
+ FIELD_GET(GENMASK(1, 1),
+ pmbus_read_byte_data(client, page,
+ PMBUS_STATUS_MFR_SPECIFIC)));
+ break;
+ case PMBUS_STATUS_TEMPERATURE:
+ /*
+ * If the tsns digital fault is enabled, the OT Fault and OT Warning
+ * flag of PMBUS_STATUS_TEMPERATURE should come from STATUS_MFR_SPECIFIC
+ * register bit1.
+ */
+ if (!data->mfr_thwn_flt_en)
+ return -ENODATA;
+
+ ret = pmbus_read_byte_data(client, page, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & ~GENMASK(7, 6)) |
+ FIELD_PREP(GENMASK(6, 6),
+ FIELD_GET(GENMASK(1, 1),
+ pmbus_read_byte_data(client, page,
+ PMBUS_STATUS_MFR_SPECIFIC))) |
+ FIELD_PREP(GENMASK(7, 7),
+ FIELD_GET(GENMASK(1, 1),
+ pmbus_read_byte_data(client, page,
+ PMBUS_STATUS_MFR_SPECIFIC)));
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2869_read_word_data(struct i2c_client *client, int page, int phase,
+ int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_STATUS_WORD:
+ /*
+ * If the tsns digital fault is enabled, the OT Fault flag
+ * of PMBUS_STATUS_WORD should come from STATUS_MFR_SPECIFIC
+ * register bit1.
+ */
+ if (!data->mfr_thwn_flt_en)
+ return -ENODATA;
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & ~GENMASK(2, 2)) |
+ FIELD_PREP(GENMASK(2, 2),
+ FIELD_GET(GENMASK(1, 1),
+ pmbus_read_byte_data(client, page,
+ PMBUS_STATUS_MFR_SPECIFIC)));
+ break;
+ case PMBUS_READ_VIN:
+ /*
+ * The MP2869 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
+ * 31.25mV/LSB. And the vin scale is set to 31.25mV/Lsb(using r/m/b scale)
+ * in MP2869 pmbus_driver_info struct, so the word data bit0-bit10 can be
+ * returned to pmbus core directly.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(10, 0), ret);
+ break;
+ case PMBUS_READ_IIN:
+ /*
+ * The MP2869 redefine the standard 0x95 register as iin telemetry
+ * per rail.
+ */
+ ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case PMBUS_READ_PIN:
+ /*
+ * The MP2869 redefine the standard 0x94 register as pin telemetry
+ * per rail. The MP2869 MFR_READ_PIN_EST register is linear11 format,
+ * but the pin scale is set to 1W/Lsb(using r/m/b scale). As a result,
+ * the pin read from MP2869 should be converted to W, then return
+ * the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
+ if (ret < 0)
+ return ret;
+
+ ret = mp2869_reg2data_linear11(ret);
+ break;
+ case PMBUS_READ_VOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page],
+ MP2869_READ_VOUT_DIV);
+ break;
+ case PMBUS_READ_IOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page],
+ MP2869_READ_IOUT_DIV);
+ break;
+ case PMBUS_READ_POUT:
+ /*
+ * The MP2869 PMBUS_READ_POUT register is linear11 format, but the pout
+ * scale is set to 1W/Lsb(using r/m/b scale). As a result, the pout read
+ * from MP2869 should be converted to W, then return the result to pmbus
+ * core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = mp2869_reg2data_linear11(ret);
+ break;
+ case PMBUS_READ_TEMPERATURE_1:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(10, 0), ret);
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(12, 9), ret))
+ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE +
+ (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
+ else
+ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(12, 9), ret))
+ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE -
+ (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
+ else
+ ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
+ * is 1°C/LSB and they have 40°C offset.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) - MP2869_TEMP_LIMIT_OFFSET;
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) * MP2869_VIN_OV_FAULT_GAIN;
+ break;
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(9, 0), ret);
+ break;
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] *
+ MP2869_IOUT_LIMIT_UINT, MP2869_READ_IOUT_DIV);
+ break;
+ case PMBUS_POUT_OP_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) * MP2869_POUT_OP_GAIN;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2869_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2869_data *data = to_mp2869_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ /*
+ * The MP2869 PMBUS_VOUT_UV_FAULT_LIMIT[8:0] is the limit value,
+ * and bit9-bit15 should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(12, 9), ret))
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(8, 0)) |
+ FIELD_PREP(GENMASK(8, 0),
+ DIV_ROUND_CLOSEST(word +
+ (FIELD_GET(GENMASK(12, 9),
+ ret) + 1) *
+ MP2869_OVUV_DELTA_SCALE,
+ MP2869_OVUV_LIMIT_SCALE)));
+ else
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(8, 0)) |
+ FIELD_PREP(GENMASK(8, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2869_OVUV_LIMIT_SCALE)));
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ /*
+ * The MP2869 PMBUS_VOUT_OV_FAULT_LIMIT[8:0] is the limit value,
+ * and bit9-bit15 should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(12, 9), ret))
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(8, 0)) |
+ FIELD_PREP(GENMASK(8, 0),
+ DIV_ROUND_CLOSEST(word -
+ (FIELD_GET(GENMASK(12, 9),
+ ret) + 1) *
+ MP2869_OVUV_DELTA_SCALE,
+ MP2869_OVUV_LIMIT_SCALE)));
+ else
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(8, 0)) |
+ FIELD_PREP(GENMASK(8, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2869_OVUV_LIMIT_SCALE)));
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * If the tsns digital fault is enabled, the PMBUS_OT_FAULT_LIMIT and
+ * PMBUS_OT_WARN_LIMIT can not be written.
+ */
+ if (data->mfr_thwn_flt_en)
+ return -EINVAL;
+
+ /*
+ * The MP2869 scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
+ * have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15
+ * should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ word + MP2869_TEMP_LIMIT_OFFSET));
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /*
+ * The MP2869 PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
+ * should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb,
+ * but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data
+ * should divide by MP2869_VIN_OV_FAULT_GAIN(4)
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2869_VIN_OV_FAULT_GAIN)));
+ break;
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VIN_UV_LIMIT[9:0] is the limit value, and bit10-bit15 should
+ * not be changed. The scale of PMBUS_VIN_UV_LIMIT is 31.25mV/Lsb, and the
+ * vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data can
+ * be written directly.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(9, 0)) |
+ FIELD_PREP(GENMASK(9, 0),
+ word));
+ break;
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word * MP2869_READ_IOUT_DIV,
+ MP2869_IOUT_LIMIT_UINT *
+ data->iout_scale[page]));
+ break;
+ case PMBUS_POUT_OP_WARN_LIMIT:
+ /*
+ * The POUT_OP_WARN_LIMIT[11:0] is the limit value, and bit12-bit15 should
+ * not be changed. The scale of POUT_OP_WARN_LIMIT is 2W/Lsb.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(11, 0)) |
+ FIELD_PREP(GENMASK(11, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2869_POUT_OP_GAIN)));
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2869_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ /* Identify whether tsns digital fault is enable */
+ ret = mp2869_identify_thwn_flt(client, info, 1);
+ if (ret < 0)
+ return 0;
+
+ /* Identify vout scale for rail1. */
+ ret = mp2869_identify_vout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify vout scale for rail2. */
+ ret = mp2869_identify_vout_scale(client, info, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Identify iout scale for rail 1. */
+ ret = mp2869_identify_iout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify iout scale for rail 2. */
+ return mp2869_identify_iout_scale(client, info, 1);
+}
+
+static const struct pmbus_driver_info mp2869_info = {
+ .pages = MP2869_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+
+ .m[PSC_VOLTAGE_IN] = 32,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .b[PSC_VOLTAGE_IN] = 0,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .m[PSC_CURRENT_OUT] = 1,
+ .R[PSC_CURRENT_OUT] = 0,
+ .b[PSC_CURRENT_OUT] = 0,
+
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 0,
+ .b[PSC_TEMPERATURE] = 0,
+
+ .m[PSC_POWER] = 1,
+ .R[PSC_POWER] = 0,
+ .b[PSC_POWER] = 0,
+
+ .func[0] = MP2869_RAIL1_FUNC,
+ .func[1] = MP2869_RAIL2_FUNC,
+ .read_word_data = mp2869_read_word_data,
+ .write_word_data = mp2869_write_word_data,
+ .read_byte_data = mp2869_read_byte_data,
+ .identify = mp2869_identify,
+};
+
+static int mp2869_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp2869_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp2869_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp2869_info, sizeof(*info));
+ info = &data->info;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id mp2869_id[] = {
+ {"mp2869", 0},
+ {"mp29608", 1},
+ {"mp29612", 2},
+ {"mp29816", 3},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp2869_id);
+
+static const struct of_device_id __maybe_unused mp2869_of_match[] = {
+ {.compatible = "mps,mp2869", .data = (void *)0},
+ {.compatible = "mps,mp29608", .data = (void *)1},
+ {.compatible = "mps,mp29612", .data = (void *)2},
+ {.compatible = "mps,mp29816", .data = (void *)3},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2869_of_match);
+
+static struct i2c_driver mp2869_driver = {
+ .driver = {
+ .name = "mp2869",
+ .of_match_table = mp2869_of_match,
+ },
+ .probe = mp2869_probe,
+ .id_table = mp2869_id,
+};
+
+module_i2c_driver(mp2869_driver);
+
+MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2869");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2888.c b/drivers/hwmon/pmbus/mp2888.c
index 50662ed8e3d5..772a623ca7d0 100644
--- a/drivers/hwmon/pmbus/mp2888.c
+++ b/drivers/hwmon/pmbus/mp2888.c
@@ -378,7 +378,7 @@ static int mp2888_probe(struct i2c_client *client)
}
static const struct i2c_device_id mp2888_id[] = {
- {"mp2888", 0},
+ {"mp2888"},
{}
};
@@ -404,4 +404,4 @@ module_i2c_driver(mp2888_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>");
MODULE_DESCRIPTION("PMBus driver for MPS MP2888 device");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2891.c b/drivers/hwmon/pmbus/mp2891.c
new file mode 100644
index 000000000000..f8f4c91ec23c
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2891.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2891)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+/*
+ * Vender specific registers, the register MFR_SVI3_IOUT_PRT(0x65),
+ * MFR_VOUT_LOOP_CTRL(0xBD), READ_PIN_EST(0x94)and READ_IIN_EST(0x95)
+ * redefine the standard PMBUS register. The MFR_SVI3_IOUT_PRT(0x65)
+ * is used to identify the iout scale and the MFR_VOUT_LOOP_CTRL(0xBD)
+ * is used to identify the vout scale. The READ_PIN_EST(0x94) is used
+ * to read input power per rail. The MP2891 does not have standard
+ * READ_IIN register(0x89), the iin telemetry can be obtained through
+ * the vendor redefined register READ_IIN_EST(0x95).
+ */
+#define MFR_VOUT_LOOP_CTRL 0xBD
+#define READ_PIN_EST 0x94
+#define READ_IIN_EST 0x95
+#define MFR_SVI3_IOUT_PRT 0x65
+
+#define MP2891_TEMP_LIMIT_OFFSET 40
+#define MP2891_PIN_LIMIT_UINT 2
+#define MP2891_IOUT_LIMIT_UINT 8
+#define MP2891_IOUT_SCALE_DIV 32
+#define MP2891_VOUT_SCALE_DIV 100
+#define MP2891_OVUV_DELTA_SCALE 50
+#define MP2891_OV_LIMIT_SCALE 20
+#define MP2891_UV_LIMIT_SCALE 5
+
+#define MP2891_PAGE_NUM 2
+
+#define MP2891_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \
+ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_INPUT | \
+ PMBUS_HAVE_STATUS_TEMP)
+
+#define MP2891_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_INPUT | \
+ PMBUS_HAVE_STATUS_TEMP)
+
+struct mp2891_data {
+ struct pmbus_driver_info info;
+ int vout_scale[MP2891_PAGE_NUM];
+ int iout_scale[MP2891_PAGE_NUM];
+};
+
+#define to_mp2891_data(x) container_of(x, struct mp2891_data, info)
+
+/* Converts a LINEAR11 value to DIRECT format */
+static u16 mp2891_reg2data_linear11(u16 word)
+{
+ s16 exponent;
+ s32 mantissa;
+ s64 val;
+
+ exponent = ((s16)word) >> 11;
+ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
+ val = mantissa;
+
+ if (exponent >= 0)
+ val <<= exponent;
+ else
+ val >>= -exponent;
+
+ return val;
+}
+
+static int
+mp2891_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2891_data *data = to_mp2891_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VOUT_LOOP_CTRL);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The output voltage is equal to the READ_VOUT(0x8B) register value multiplied
+ * by vout_scale.
+ * Obtain vout scale from the register MFR_VOUT_LOOP_CTRL, bits 15-14,bit 13.
+ * If MFR_VOUT_LOOP_CTRL[13] = 1, the vout scale is below:
+ * 2.5mV/LSB
+ * If MFR_VOUT_LOOP_CTRL[13] = 0, the vout scale is decided by
+ * MFR_VOUT_LOOP_CTRL[15:14]:
+ * 00b - 6.25mV/LSB, 01b - 5mV/LSB, 10b - 2mV/LSB, 11b - 1mV
+ */
+ if (ret & GENMASK(13, 13)) {
+ data->vout_scale[page] = 250;
+ } else {
+ ret = FIELD_GET(GENMASK(15, 14), ret);
+ if (ret == 0)
+ data->vout_scale[page] = 625;
+ else if (ret == 1)
+ data->vout_scale[page] = 500;
+ else if (ret == 2)
+ data->vout_scale[page] = 200;
+ else
+ data->vout_scale[page] = 100;
+ }
+
+ return 0;
+}
+
+static int
+mp2891_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2891_data *data = to_mp2891_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The output current is equal to the READ_IOUT(0x8C) register value
+ * multiplied by iout_scale.
+ * Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0].
+ * The value is selected as below:
+ * 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB,
+ * 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB
+ * 110b - 1A/LSB, 111b - 2A/LSB
+ */
+ switch (ret & GENMASK(2, 0)) {
+ case 0:
+ case 6:
+ data->iout_scale[page] = 32;
+ break;
+ case 1:
+ data->iout_scale[page] = 1;
+ break;
+ case 2:
+ data->iout_scale[page] = 2;
+ break;
+ case 3:
+ data->iout_scale[page] = 4;
+ break;
+ case 4:
+ data->iout_scale[page] = 8;
+ break;
+ case 5:
+ data->iout_scale[page] = 16;
+ break;
+ default:
+ data->iout_scale[page] = 64;
+ break;
+ }
+
+ return 0;
+}
+
+static int mp2891_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ /* Identify vout scale for rail 1. */
+ ret = mp2891_identify_vout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify vout scale for rail 2. */
+ ret = mp2891_identify_vout_scale(client, info, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Identify iout scale for rail 1. */
+ ret = mp2891_identify_iout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify iout scale for rail 2. */
+ return mp2891_identify_iout_scale(client, info, 1);
+}
+
+static int mp2891_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /*
+ * The MP2891 does not follow standard PMBus protocol completely, the
+ * PMBUS_VOUT_MODE(0x20) in MP2891 is reserved and 0x00 is always
+ * returned when the register is read. But the calculation of vout in
+ * this driver is based on direct format. As a result, the format of
+ * vout is enforced to direct.
+ */
+ ret = PB_VOUT_MODE_DIRECT;
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2891_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2891_data *data = to_mp2891_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VIN:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ret & GENMASK(9, 0);
+ break;
+ case PMBUS_READ_IIN:
+ /*
+ * The MP2891 does not have standard PMBUS_READ_IIN register(0x89),
+ * the iin telemetry can be obtained through the vender redefined
+ * register READ_IIN_EST(0x95). The MP2891 PMBUS_READ_IIN register
+ * is linear11 format, But the pout scale is set to 1A/Lsb(using
+ * r/m/b scale). As a result, the iin read from MP2891 should be
+ * calculated to A, then return the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, READ_IIN_EST);
+ if (ret < 0)
+ return ret;
+
+ ret = mp2891_reg2data_linear11(ret);
+ break;
+ case PMBUS_READ_PIN:
+ /*
+ * The MP2891 has standard PMBUS_READ_PIN register(0x97), but this
+ * is not used to read the input power per rail. The input power
+ * per rail is read through the vender redefined register
+ * READ_PIN_EST(0x94). The MP2891 PMBUS_READ_PIN register is linear11
+ * format, But the pout scale is set to 1W/Lsb(using r/m/b scale).
+ * As a result, the pin read from MP2891 should be calculated to W,
+ * then return the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, READ_PIN_EST);
+ if (ret < 0)
+ return ret;
+
+ ret = mp2891_reg2data_linear11(ret);
+ break;
+ case PMBUS_READ_POUT:
+ /*
+ * The MP2891 PMBUS_READ_POUT register is linear11 format, and the
+ * exponent is not a constant value. But the pout scale is set to
+ * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP2891
+ * should be calculated to W, then return the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = mp2891_reg2data_linear11(ret);
+ break;
+ case PMBUS_READ_VOUT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST(ret * data->vout_scale[page], MP2891_VOUT_SCALE_DIV);
+ break;
+ case PMBUS_READ_IOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page],
+ MP2891_IOUT_SCALE_DIV);
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The scale of MP2891 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
+ * is 1°C/LSB and they have 40°C offset.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) - MP2891_TEMP_LIMIT_OFFSET;
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /*
+ * The MP2891 PMBUS_VIN_OV_FAULT_LIMIT scale is 125mV/Lsb.
+ * but the vin scale is set to 31.25mV/Lsb(using r/m/b scale).
+ * As a result, the limit value should be multiplied by 4.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) * 4;
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(11, 8), ret))
+ ret = FIELD_GET(GENMASK(7, 0), ret) * MP2891_UV_LIMIT_SCALE -
+ (FIELD_GET(GENMASK(11, 8), ret) + 1) * MP2891_OVUV_DELTA_SCALE;
+ else
+ ret = FIELD_GET(GENMASK(7, 0), ret) * MP2891_UV_LIMIT_SCALE;
+
+ ret = ret < 0 ? 0 : ret;
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(11, 8), ret))
+ ret = FIELD_GET(GENMASK(7, 0), ret) * MP2891_OV_LIMIT_SCALE +
+ (FIELD_GET(GENMASK(11, 8), ret) + 1) * MP2891_OVUV_DELTA_SCALE;
+ else
+ ret = FIELD_GET(GENMASK(7, 0), ret) * MP2891_OV_LIMIT_SCALE;
+ break;
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] *
+ MP2891_IOUT_LIMIT_UINT, MP2891_IOUT_SCALE_DIV);
+ break;
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ /*
+ * The scale of PMBUS_IIN_OC_WARN_LIMIT is 0.5A/Lsb, but the iin scale
+ * is set to 1A/Lsb(using r/m/b scale), so the word data should be
+ * divided by 2.
+ */
+ ret = pmbus_read_word_data(client, 0, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)), 2);
+ break;
+ case PMBUS_PIN_OP_WARN_LIMIT:
+ /*
+ * The scale of PMBUS_PIN_OP_WARN_LIMIT is 2W/Lsb, but the pin scale
+ * is set to 1W/Lsb(using r/m/b scale), so the word data should be
+ * multiplied by 2.
+ */
+ ret = pmbus_read_word_data(client, 0, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(9, 0)) * MP2891_PIN_LIMIT_UINT;
+ break;
+ case PMBUS_READ_TEMPERATURE_1:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2891_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2891_data *data = to_mp2891_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word * MP2891_VOUT_SCALE_DIV,
+ data->vout_scale[page]));
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VOUT_UV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
+ * should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(11, 8), ret))
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word +
+ (FIELD_GET(GENMASK(11, 8), ret) + 1) *
+ MP2891_OVUV_DELTA_SCALE,
+ MP2891_UV_LIMIT_SCALE)));
+ else
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2891_UV_LIMIT_SCALE)));
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VOUT_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
+ * should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(11, 8), ret))
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word -
+ (FIELD_GET(GENMASK(11, 8), ret) + 1) *
+ MP2891_OVUV_DELTA_SCALE,
+ MP2891_OV_LIMIT_SCALE)));
+ else
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP2891_OV_LIMIT_SCALE)));
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
+ * should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb,
+ * but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data
+ * should be divided by 4.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word, 4)));
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The scale of MP2891 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
+ * have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15
+ * should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0), word + MP2891_TEMP_LIMIT_OFFSET));
+ break;
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word * MP2891_IOUT_SCALE_DIV,
+ MP2891_IOUT_LIMIT_UINT *
+ data->iout_scale[page]));
+ break;
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ /*
+ * The scale of PMBUS_IIN_OC_WARN_LIMIT is 0.5A/Lsb, but the iin scale
+ * is set to 1A/Lsb(using r/m/b scale), so the word data should be
+ * multiplied by 2.
+ */
+ ret = pmbus_write_word_data(client, page, reg, word * 2);
+ break;
+ case PMBUS_PIN_OP_WARN_LIMIT:
+ /*
+ * The scale of PMBUS_PIN_OP_WARN_LIMIT is 2W/Lsb, but the pin scale
+ * is set to 1W/Lsb(using r/m/b scale), so the word data should be
+ * divided by 2.
+ */
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word, MP2891_PIN_LIMIT_UINT));
+ break;
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct pmbus_driver_info mp2891_info = {
+ .pages = MP2891_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+
+ /* set vin scale 31.25mV/Lsb */
+ .m[PSC_VOLTAGE_IN] = 32,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .b[PSC_VOLTAGE_IN] = 0,
+
+ /* set temp scale 1000m°C/Lsb */
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 0,
+ .b[PSC_TEMPERATURE] = 0,
+
+ .m[PSC_CURRENT_IN] = 1,
+ .R[PSC_CURRENT_IN] = 0,
+ .b[PSC_CURRENT_IN] = 0,
+
+ .m[PSC_CURRENT_OUT] = 1,
+ .R[PSC_CURRENT_OUT] = 0,
+ .b[PSC_CURRENT_OUT] = 0,
+
+ .m[PSC_POWER] = 1,
+ .R[PSC_POWER] = 0,
+ .b[PSC_POWER] = 0,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .func[0] = MP2891_RAIL1_FUNC,
+ .func[1] = MP2891_RAIL2_FUNC,
+ .read_word_data = mp2891_read_word_data,
+ .write_word_data = mp2891_write_word_data,
+ .read_byte_data = mp2891_read_byte_data,
+ .identify = mp2891_identify,
+};
+
+static int mp2891_probe(struct i2c_client *client)
+{
+ struct mp2891_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp2891_info, sizeof(mp2891_info));
+
+ return pmbus_do_probe(client, &data->info);
+}
+
+static const struct i2c_device_id mp2891_id[] = {
+ { "mp2891" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mp2891_id);
+
+static const struct of_device_id __maybe_unused mp2891_of_match[] = {
+ {.compatible = "mps,mp2891"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2891_of_match);
+
+static struct i2c_driver mp2891_driver = {
+ .driver = {
+ .name = "mp2891",
+ .of_match_table = mp2891_of_match,
+ },
+ .probe = mp2891_probe,
+ .id_table = mp2891_id,
+};
+
+module_i2c_driver(mp2891_driver);
+
+MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2891");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2925.c b/drivers/hwmon/pmbus/mp2925.c
new file mode 100644
index 000000000000..6bebd6023021
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2925.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2925)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+/*
+ * Vender specific register MFR_VR_MULTI_CONFIG(0x08).
+ * This register is used to obtain vid scale.
+ */
+#define MFR_VR_MULTI_CONFIG 0x08
+
+#define MP2925_VOUT_DIV 512
+#define MP2925_VOUT_OVUV_UINT 195
+#define MP2925_VOUT_OVUV_DIV 100
+
+#define MP2925_PAGE_NUM 2
+
+#define MP2925_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
+ PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+#define MP2925_RAIL2_FUNC (PMBUS_HAVE_PIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+struct mp2925_data {
+ struct pmbus_driver_info info;
+ int vout_scale[MP2925_PAGE_NUM];
+};
+
+#define to_mp2925_data(x) container_of(x, struct mp2925_data, info)
+
+static u16 mp2925_linear_exp_transfer(u16 word, u16 expect_exponent)
+{
+ s16 exponent, mantissa, target_exponent;
+
+ exponent = ((s16)word) >> 11;
+ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
+ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11;
+
+ if (exponent > target_exponent)
+ mantissa = mantissa << (exponent - target_exponent);
+ else
+ mantissa = mantissa >> (target_exponent - exponent);
+
+ return (mantissa & 0x7ff) | ((expect_exponent << 11) & 0xf800);
+}
+
+static int mp2925_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /*
+ * The MP2925 does not follow standard PMBus protocol completely,
+ * and the calculation of vout in this driver is based on direct
+ * format. As a result, the format of vout is enforced to direct.
+ */
+ ret = PB_VOUT_MODE_DIRECT;
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2925_read_word_data(struct i2c_client *client, int page, int phase,
+ int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp2925_data *data = to_mp2925_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page],
+ MP2925_VOUT_DIV);
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * MP2925_VOUT_OVUV_UINT,
+ MP2925_VOUT_OVUV_DIV);
+ break;
+ case PMBUS_STATUS_WORD:
+ case PMBUS_READ_VIN:
+ case PMBUS_READ_IOUT:
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_PIN:
+ case PMBUS_READ_IIN:
+ case PMBUS_READ_TEMPERATURE_1:
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2925_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VIN_OV_FAULT_LIMIT, PMBUS_VIN_OV_WARN_LIMIT,
+ * PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT
+ * of MP2925 is linear11 format, and the exponent is a
+ * constant value(5'b11100), so the exponent of word
+ * parameter should be converted to 5'b11100(0x1C).
+ */
+ ret = pmbus_write_word_data(client, page, reg,
+ mp2925_linear_exp_transfer(word, 0x1C));
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ /*
+ * The bit0-bit11 is the limit value, and bit12-bit15
+ * should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(11, 0)) |
+ FIELD_PREP(GENMASK(11, 0),
+ DIV_ROUND_CLOSEST(word * MP2925_VOUT_OVUV_DIV,
+ MP2925_VOUT_OVUV_UINT)));
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT of
+ * MP2925 is linear11 format, and the exponent is a
+ * constant value(5'b00000), so the exponent of word
+ * parameter should be converted to 5'b00000.
+ */
+ ret = pmbus_write_word_data(client, page, reg,
+ mp2925_linear_exp_transfer(word, 0x00));
+ break;
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ /*
+ * The PMBUS_IOUT_OC_FAULT_LIMIT and PMBUS_IOUT_OC_WARN_LIMIT
+ * of MP2925 is linear11 format, and the exponent can not be
+ * changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ mp2925_linear_exp_transfer(word,
+ FIELD_GET(GENMASK(15, 11),
+ ret)));
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+mp2925_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp2925_data *data = to_mp2925_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(5, 5), ret)) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE,
+ page == 0 ? 3 : 4);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(5, 5), ret))
+ data->vout_scale[page] = 2560;
+ else
+ data->vout_scale[page] = 5120;
+ } else if (FIELD_GET(GENMASK(4, 4), ret)) {
+ data->vout_scale[page] = 1;
+ } else {
+ data->vout_scale[page] = 512;
+ }
+
+ return 0;
+}
+
+static int mp2925_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ ret = mp2925_identify_vout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ return mp2925_identify_vout_scale(client, info, 1);
+}
+
+static const struct pmbus_driver_info mp2925_info = {
+ .pages = MP2925_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .func[0] = MP2925_RAIL1_FUNC,
+ .func[1] = MP2925_RAIL2_FUNC,
+ .read_word_data = mp2925_read_word_data,
+ .read_byte_data = mp2925_read_byte_data,
+ .write_word_data = mp2925_write_word_data,
+ .identify = mp2925_identify,
+};
+
+static int mp2925_probe(struct i2c_client *client)
+{
+ struct mp2925_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp2925_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp2925_info, sizeof(mp2925_info));
+
+ return pmbus_do_probe(client, &data->info);
+}
+
+static const struct i2c_device_id mp2925_id[] = {
+ {"mp2925"},
+ {"mp2929"},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp2925_id);
+
+static const struct of_device_id __maybe_unused mp2925_of_match[] = {
+ {.compatible = "mps,mp2925"},
+ {.compatible = "mps,mp2929"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2925_of_match);
+
+static struct i2c_driver mp2925_driver = {
+ .driver = {
+ .name = "mp2925",
+ .of_match_table = mp2925_of_match,
+ },
+ .probe = mp2925_probe,
+ .id_table = mp2925_id,
+};
+
+module_i2c_driver(mp2925_driver);
+
+MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2925");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp29502.c b/drivers/hwmon/pmbus/mp29502.c
new file mode 100644
index 000000000000..7241373f1557
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp29502.c
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP29502)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define MFR_VOUT_SCALE_LOOP 0x29
+#define MFR_SVI3_IOUT_PRT 0x67
+#define MFR_READ_PIN_EST 0x94
+#define MFR_READ_IIN_EST 0x95
+#define MFR_VOUT_PROT1 0x3D
+#define MFR_VOUT_PROT2 0x51
+#define MFR_SLOPE_CNT_SET 0xA8
+#define MFR_TSNS_FLT_SET 0xBB
+
+#define MP29502_VIN_OV_GAIN 4
+#define MP29502_TEMP_LIMIT_OFFSET 40
+#define MP29502_READ_VOUT_DIV 1024
+#define MP29502_READ_IOUT_DIV 32
+#define MP29502_IOUT_LIMIT_UINT 8
+#define MP29502_OVUV_LIMIT_SCALE 10
+#define MP28502_VOUT_OV_GAIN 512
+#define MP28502_VOUT_OV_SCALE 40
+#define MP29502_VOUT_UV_OFFSET 36
+#define MP29502_PIN_GAIN 2
+#define MP29502_IIN_DIV 2
+
+#define MP29502_PAGE_NUM 1
+
+#define MP29502_RAIL_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+struct mp29502_data {
+ struct pmbus_driver_info info;
+ int vout_scale;
+ int vout_bottom_div;
+ int vout_top_div;
+ int ovp_div;
+ int iout_scale;
+};
+
+#define to_mp29502_data(x) container_of(x, struct mp29502_data, info)
+
+static u16 mp29502_reg2data_linear11(u16 word)
+{
+ s16 exponent;
+ s32 mantissa;
+ s64 val;
+
+ exponent = ((s16)word) >> 11;
+ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
+ val = mantissa;
+
+ if (exponent >= 0)
+ val <<= exponent;
+ else
+ val >>= -exponent;
+
+ return val;
+}
+
+static int
+mp29502_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP);
+ if (ret < 0)
+ return ret;
+
+ switch (FIELD_GET(GENMASK(12, 10), ret)) {
+ case 0:
+ data->vout_scale = 6400;
+ break;
+ case 1:
+ data->vout_scale = 5120;
+ break;
+ case 2:
+ data->vout_scale = 2560;
+ break;
+ case 3:
+ data->vout_scale = 2048;
+ break;
+ case 4:
+ data->vout_scale = 1024;
+ break;
+ case 5:
+ data->vout_scale = 4;
+ break;
+ case 6:
+ data->vout_scale = 2;
+ break;
+ case 7:
+ data->vout_scale = 1;
+ break;
+ default:
+ data->vout_scale = 1;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+mp29502_identify_vout_divider(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT1);
+ if (ret < 0)
+ return ret;
+
+ data->vout_bottom_div = FIELD_GET(GENMASK(11, 0), ret);
+
+ ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT2);
+ if (ret < 0)
+ return ret;
+
+ data->vout_top_div = FIELD_GET(GENMASK(14, 0), ret);
+
+ return 0;
+}
+
+static int
+mp29502_identify_ovp_divider(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_SLOPE_CNT_SET);
+ if (ret < 0)
+ return ret;
+
+ data->ovp_div = FIELD_GET(GENMASK(9, 0), ret);
+
+ return 0;
+}
+
+static int
+mp29502_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
+ int page)
+{
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & GENMASK(2, 0)) {
+ case 0:
+ case 6:
+ data->iout_scale = 32;
+ break;
+ case 1:
+ data->iout_scale = 1;
+ break;
+ case 2:
+ data->iout_scale = 2;
+ break;
+ case 3:
+ data->iout_scale = 4;
+ break;
+ case 4:
+ data->iout_scale = 8;
+ break;
+ case 5:
+ data->iout_scale = 16;
+ break;
+ default:
+ data->iout_scale = 64;
+ break;
+ }
+
+ return 0;
+}
+
+static int mp29502_read_vout_ov_limit(struct i2c_client *client, struct mp29502_data *data)
+{
+ int ret;
+ int ov_value;
+
+ /*
+ * This is because the vout ov fault limit value comes from
+ * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
+ * value comes from page0 reg. So the page should be set to
+ * 0 after the reading of vout ov limit.
+ */
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
+ if (ret < 0)
+ return ret;
+
+ ov_value = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(12, 7), ret) *
+ MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE,
+ data->ovp_div);
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ return ov_value;
+}
+
+static int mp29502_write_vout_ov_limit(struct i2c_client *client, u16 word,
+ struct mp29502_data *data)
+{
+ int ret;
+
+ /*
+ * This is because the vout ov fault limit value comes from
+ * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
+ * value comes from page0 reg. So the page should be set to
+ * 0 after the writing of vout ov limit.
+ */
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_word_data(client, MFR_TSNS_FLT_SET,
+ (ret & ~GENMASK(12, 7)) |
+ FIELD_PREP(GENMASK(12, 7),
+ DIV_ROUND_CLOSEST(word * data->ovp_div,
+ MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE)));
+
+ return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+}
+
+static int mp29502_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ ret = PB_VOUT_MODE_DIRECT;
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp29502_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_STATUS_WORD:
+ ret = -ENODATA;
+ break;
+ case PMBUS_READ_VIN:
+ /*
+ * The MP29502 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
+ * 125mV/LSB. And the vin scale is set to 125mV/Lsb(using r/m/b scale)
+ * in MP29502 pmbus_driver_info struct, so the word data bit0-bit10 can
+ * be returned to pmbus core directly.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(10, 0), ret);
+ break;
+ case PMBUS_READ_VOUT:
+ /*
+ * The MP29502 PMBUS_READ_VOUT[11:0] is the vout value, and vout
+ * value is calculated based on vout scale and vout divider.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) *
+ data->vout_scale *
+ (data->vout_bottom_div +
+ 4 * data->vout_top_div),
+ MP29502_READ_VOUT_DIV *
+ data->vout_bottom_div);
+ break;
+ case PMBUS_READ_IIN:
+ /*
+ * The MP29502 MFR_READ_IIN_EST register is linear11 format, and the
+ * exponent is not a constant value. But the iin scale is set to
+ * 1A/Lsb(using r/m/b scale). As a result, the iin read from MP29502
+ * should be calculated to A, then return the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret),
+ MP29502_IIN_DIV);
+ break;
+ case PMBUS_READ_PIN:
+ /*
+ * The MP29502 MFR_READ_PIN_EST register is linear11 format, and the
+ * exponent is not a constant value. But the pin scale is set to
+ * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
+ * should be calculated to W, then return the result to pmbus core.
+ */
+ ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
+ if (ret < 0)
+ return ret;
+
+ ret = mp29502_reg2data_linear11(ret) * MP29502_PIN_GAIN;
+ break;
+ case PMBUS_READ_POUT:
+ /*
+ * The MP29502 PMBUS_READ_POUT register is linear11 format, and the
+ * exponent is not a constant value. But the pout scale is set to
+ * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
+ * should be calculated to W, then return the result to pmbus core.
+ * And the pout is calculated based on vout divider.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret) *
+ (data->vout_bottom_div +
+ 4 * data->vout_top_div),
+ data->vout_bottom_div);
+ break;
+ case PMBUS_READ_IOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale,
+ MP29502_READ_IOUT_DIV);
+ break;
+ case PMBUS_READ_TEMPERATURE_1:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(10, 0), ret);
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /*
+ * The MP29502 PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but
+ * the vin scale is set to 125mV/Lsb(using r/m/b scale),
+ * so the word data should multiply by 4.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(7, 0), ret) * MP29502_VIN_OV_GAIN;
+ break;
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /*
+ * The MP29502 PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT
+ * scale is 125mV/Lsb, but the vin scale is set to 125mV/Lsb(using
+ * r/m/b scale), so the word data bit0-bit9 can be returned to pmbus
+ * core directly.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(GENMASK(9, 0), ret);
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ /*
+ * The MP29502 vout ov fault limit value comes from
+ * page1 MFR_TSNS_FLT_SET[12:7].
+ */
+ ret = mp29502_read_vout_ov_limit(client, data);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((FIELD_GET(GENMASK(8, 0), ret) *
+ MP29502_OVUV_LIMIT_SCALE -
+ MP29502_VOUT_UV_OFFSET) *
+ (data->vout_bottom_div +
+ 4 * data->vout_top_div),
+ data->vout_bottom_div);
+ break;
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) *
+ data->iout_scale *
+ MP29502_IOUT_LIMIT_UINT,
+ MP29502_READ_IOUT_DIV);
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The scale of MP29502 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
+ * is 1°C/LSB and they have 40°C offset.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & GENMASK(7, 0)) - MP29502_TEMP_LIMIT_OFFSET;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp29502_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp29502_data *data = to_mp29502_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (reg) {
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value,
+ * and bit8-bit15 should not be changed. The scale of
+ * PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but the vin
+ * scale is set to 125mV/Lsb(using r/m/b scale), so
+ * the word data should divide by 4.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ DIV_ROUND_CLOSEST(word,
+ MP29502_VIN_OV_GAIN)));
+ break;
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /*
+ * The PMBUS_VIN_UV_WARN_LIMIT[9:0] and PMBUS_VIN_UV_FAULT_LIMIT[9:0]
+ * are the limit value, and bit10-bit15 should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(9, 0)) |
+ FIELD_PREP(GENMASK(9, 0),
+ word));
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ ret = mp29502_write_vout_ov_limit(client, word, data);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(8, 0)) |
+ FIELD_PREP(GENMASK(8, 0),
+ DIV_ROUND_CLOSEST(word *
+ data->vout_bottom_div +
+ MP29502_VOUT_UV_OFFSET *
+ (data->vout_bottom_div +
+ 4 * data->vout_top_div),
+ MP29502_OVUV_LIMIT_SCALE *
+ (data->vout_bottom_div +
+ 4 * data->vout_top_div))));
+ break;
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word *
+ MP29502_READ_IOUT_DIV,
+ MP29502_IOUT_LIMIT_UINT *
+ data->iout_scale));
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The PMBUS_OT_FAULT_LIMIT[7:0] and PMBUS_OT_WARN_LIMIT[7:0]
+ * are the limit value, and bit8-bit15 should not be changed.
+ */
+ ret = pmbus_read_word_data(client, page, 0xff, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = pmbus_write_word_data(client, page, reg,
+ (ret & ~GENMASK(7, 0)) |
+ FIELD_PREP(GENMASK(7, 0),
+ word + MP29502_TEMP_LIMIT_OFFSET));
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp29502_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ /* Identify vout scale */
+ ret = mp29502_identify_vout_scale(client, info, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Identify vout divider. */
+ ret = mp29502_identify_vout_divider(client, info, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Identify ovp divider. */
+ ret = mp29502_identify_ovp_divider(client, info, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Identify iout scale */
+ return mp29502_identify_iout_scale(client, info, 0);
+}
+
+static const struct pmbus_driver_info mp29502_info = {
+ .pages = MP29502_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_POWER] = direct,
+
+ .m[PSC_VOLTAGE_IN] = 8,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .b[PSC_VOLTAGE_IN] = 0,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 0,
+ .b[PSC_TEMPERATURE] = 0,
+
+ .m[PSC_CURRENT_IN] = 1,
+ .R[PSC_CURRENT_IN] = 0,
+ .b[PSC_CURRENT_IN] = 0,
+
+ .m[PSC_CURRENT_OUT] = 1,
+ .R[PSC_CURRENT_OUT] = 0,
+ .b[PSC_CURRENT_OUT] = 0,
+
+ .m[PSC_POWER] = 1,
+ .R[PSC_POWER] = 0,
+ .b[PSC_POWER] = 0,
+
+ .func[0] = MP29502_RAIL_FUNC,
+ .read_word_data = mp29502_read_word_data,
+ .read_byte_data = mp29502_read_byte_data,
+ .write_word_data = mp29502_write_word_data,
+ .identify = mp29502_identify,
+};
+
+static int mp29502_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp29502_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp29502_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp29502_info, sizeof(*info));
+ info = &data->info;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id mp29502_id[] = {
+ {"mp29502", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp29502_id);
+
+static const struct of_device_id __maybe_unused mp29502_of_match[] = {
+ {.compatible = "mps,mp29502"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp29502_of_match);
+
+static struct i2c_driver mp29502_driver = {
+ .driver = {
+ .name = "mp29502",
+ .of_match_table = mp29502_of_match,
+ },
+ .probe = mp29502_probe,
+ .id_table = mp29502_id,
+};
+
+module_i2c_driver(mp29502_driver);
+
+MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net");
+MODULE_DESCRIPTION("PMBus driver for MPS MP29502");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c
index 26ba50633100..c31982d85196 100644
--- a/drivers/hwmon/pmbus/mp2975.c
+++ b/drivers/hwmon/pmbus/mp2975.c
@@ -5,12 +5,14 @@
* Copyright (C) 2020 Nvidia Technologies Ltd.
*/
+#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+
#include "pmbus.h"
/* Vendor specific registers. */
@@ -97,6 +99,11 @@ static const int mp2975_max_phases[][MP2975_PAGE_NUM] = {
[mp2971] = { MP2971_MAX_PHASE_RAIL1, MP2971_MAX_PHASE_RAIL2 },
};
+struct mp2975_driver_info {
+ const struct pmbus_driver_info *info;
+ enum chips chip_id;
+};
+
struct mp2975_data {
struct pmbus_driver_info info;
enum chips chip_id;
@@ -110,15 +117,6 @@ struct mp2975_data {
int curr_sense_gain[MP2975_PAGE_NUM];
};
-static const struct i2c_device_id mp2975_id[] = {
- {"mp2971", mp2971},
- {"mp2973", mp2973},
- {"mp2975", mp2975},
- {}
-};
-
-MODULE_DEVICE_TABLE(i2c, mp2975_id);
-
static const struct regulator_desc __maybe_unused mp2975_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR("vout", 1),
@@ -126,6 +124,21 @@ static const struct regulator_desc __maybe_unused mp2975_reg_desc[] = {
#define to_mp2975_data(x) container_of(x, struct mp2975_data, info)
+static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /*
+ * Report direct format as configured by MFR_DC_LOOP_CTRL.
+ * Unlike on MP2971/MP2973 the reported VOUT_MODE isn't automatically
+ * internally updated, but always reads as PB_VOUT_MODE_VID.
+ */
+ return PB_VOUT_MODE_DIRECT;
+ default:
+ return -ENODATA;
+ }
+}
+
static int
mp2975_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg,
u16 mask)
@@ -297,6 +310,11 @@ static int mp2973_read_word_data(struct i2c_client *client, int page,
int ret;
switch (reg) {
+ case PMBUS_STATUS_WORD:
+ /* MP2973 & MP2971 return PGOOD instead of PB_STATUS_POWER_GOOD_N. */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ ret ^= PB_STATUS_POWER_GOOD_N;
+ break;
case PMBUS_OT_FAULT_LIMIT:
ret = mp2975_read_word_helper(client, page, phase, reg,
GENMASK(7, 0));
@@ -372,6 +390,80 @@ static int mp2973_read_word_data(struct i2c_client *client, int page,
return ret;
}
+static int mp2973_write_word_data(struct i2c_client *client, int page,
+ int reg, u16 word)
+{
+ u8 target, mask;
+ long ret;
+
+ if (reg != PMBUS_SMBALERT_MASK)
+ return -ENODATA;
+
+ /*
+ * Vendor-specific SMBALERT_MASK register with 16 maskable bits.
+ */
+ ret = pmbus_read_word_data(client, 0, 0, PMBUS_SMBALERT_MASK);
+ if (ret < 0)
+ return ret;
+
+ target = word & 0xff;
+ mask = word >> 8;
+
+/*
+ * Set/Clear 'bit' in 'ret' based on condition followed by define for each bit in SMBALERT_MASK.
+ * Also bit 2 & 15 are reserved.
+ */
+
+#define MP2973_TEMP_OT 0
+#define MP2973_VIN_UVLO 1
+#define MP2973_VIN_OVP 3
+#define MP2973_MTP_FAULT 4
+#define MP2973_OTHER_COMM 5
+#define MP2973_MTP_BLK_TRIG 6
+#define MP2973_PACKET_ERROR 7
+#define MP2973_INVALID_DATA 8
+#define MP2973_INVALID_COMMAND 9
+#define MP2973_IOUT_OC_LV 10
+#define MP2973_IOUT_OC 11
+#define MP2973_VOUT_MAX_MIN_WARNING 12
+#define MP2973_VOLTAGE_UV 13
+#define MP2973_VOLTAGE_OV 14
+
+ switch (target) {
+ case PMBUS_STATUS_CML:
+ __assign_bit(MP2973_INVALID_DATA, &ret, !(mask & PB_CML_FAULT_INVALID_DATA));
+ __assign_bit(MP2973_INVALID_COMMAND, &ret, !(mask & PB_CML_FAULT_INVALID_COMMAND));
+ __assign_bit(MP2973_OTHER_COMM, &ret, !(mask & PB_CML_FAULT_OTHER_COMM));
+ __assign_bit(MP2973_PACKET_ERROR, &ret, !(mask & PB_CML_FAULT_PACKET_ERROR));
+ break;
+ case PMBUS_STATUS_VOUT:
+ __assign_bit(MP2973_VOLTAGE_UV, &ret, !(mask & PB_VOLTAGE_UV_FAULT));
+ __assign_bit(MP2973_VOLTAGE_OV, &ret, !(mask & PB_VOLTAGE_OV_FAULT));
+ break;
+ case PMBUS_STATUS_IOUT:
+ __assign_bit(MP2973_IOUT_OC, &ret, !(mask & PB_IOUT_OC_FAULT));
+ __assign_bit(MP2973_IOUT_OC_LV, &ret, !(mask & PB_IOUT_OC_LV_FAULT));
+ break;
+ case PMBUS_STATUS_TEMPERATURE:
+ __assign_bit(MP2973_TEMP_OT, &ret, !(mask & PB_TEMP_OT_FAULT));
+ break;
+ /*
+ * Map remaining bits to MFR specific to let the PMBUS core mask
+ * those bits by default.
+ */
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ __assign_bit(MP2973_VIN_UVLO, &ret, !(mask & BIT(1)));
+ __assign_bit(MP2973_VIN_OVP, &ret, !(mask & BIT(3)));
+ __assign_bit(MP2973_MTP_FAULT, &ret, !(mask & BIT(4)));
+ __assign_bit(MP2973_MTP_BLK_TRIG, &ret, !(mask & BIT(6)));
+ break;
+ default:
+ return 0;
+ }
+
+ return pmbus_write_word_data(client, 0, PMBUS_SMBALERT_MASK, ret);
+}
+
static int mp2975_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
@@ -380,11 +472,6 @@ static int mp2975_read_word_data(struct i2c_client *client, int page,
int ret;
switch (reg) {
- case PMBUS_STATUS_WORD:
- /* MP2973 & MP2971 return PGOOD instead of PB_STATUS_POWER_GOOD_N. */
- ret = pmbus_read_word_data(client, page, phase, reg);
- ret ^= PB_STATUS_POWER_GOOD_N;
- break;
case PMBUS_OT_FAULT_LIMIT:
ret = mp2975_read_word_helper(client, page, phase, reg,
GENMASK(7, 0));
@@ -852,7 +939,7 @@ mp2975_vout_per_rail_config_get(struct i2c_client *client,
return 0;
}
-static struct pmbus_driver_info mp2975_info = {
+static const struct pmbus_driver_info mp2975_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = direct,
@@ -869,6 +956,7 @@ static struct pmbus_driver_info mp2975_info = {
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_PHASE_VIRTUAL,
+ .read_byte_data = mp2975_read_byte_data,
.read_word_data = mp2975_read_word_data,
#if IS_ENABLED(CONFIG_SENSORS_MP2975_REGULATOR)
.num_regulators = 1,
@@ -876,7 +964,7 @@ static struct pmbus_driver_info mp2975_info = {
#endif
};
-static struct pmbus_driver_info mp2973_info = {
+static const struct pmbus_driver_info mp2973_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = direct,
@@ -891,35 +979,41 @@ static struct pmbus_driver_info mp2973_info = {
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
.read_word_data = mp2973_read_word_data,
+ .write_word_data = mp2973_write_word_data,
#if IS_ENABLED(CONFIG_SENSORS_MP2975_REGULATOR)
.num_regulators = 1,
.reg_desc = mp2975_reg_desc,
#endif
};
+static const struct mp2975_driver_info mp2975_ddinfo[] = {
+ [mp2975] = { .info = &mp2975_info, .chip_id = mp2975 },
+ [mp2973] = { .info = &mp2973_info, .chip_id = mp2973 },
+ [mp2971] = { .info = &mp2973_info, .chip_id = mp2971 },
+};
+
static int mp2975_probe(struct i2c_client *client)
{
+ const struct mp2975_driver_info *ddinfo;
struct pmbus_driver_info *info;
struct mp2975_data *data;
int ret;
+ ddinfo = i2c_get_match_data(client);
+ if (!ddinfo)
+ return -ENODEV;
+
data = devm_kzalloc(&client->dev, sizeof(struct mp2975_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
- if (client->dev.of_node)
- data->chip_id = (enum chips)(unsigned long)of_device_get_match_data(&client->dev);
- else
- data->chip_id = i2c_match_id(mp2975_id, client)->driver_data;
+ data->chip_id = ddinfo->chip_id;
memcpy(data->max_phases, mp2975_max_phases[data->chip_id],
sizeof(data->max_phases));
- if (data->chip_id == mp2975)
- memcpy(&data->info, &mp2975_info, sizeof(*info));
- else
- memcpy(&data->info, &mp2973_info, sizeof(*info));
+ memcpy(&data->info, ddinfo->info, sizeof(data->info));
info = &data->info;
@@ -977,18 +1071,26 @@ static int mp2975_probe(struct i2c_client *client)
return pmbus_do_probe(client, info);
}
-static const struct of_device_id __maybe_unused mp2975_of_match[] = {
- {.compatible = "mps,mp2971", .data = (void *)mp2971},
- {.compatible = "mps,mp2973", .data = (void *)mp2973},
- {.compatible = "mps,mp2975", .data = (void *)mp2975},
+static const struct of_device_id mp2975_of_match[] = {
+ {.compatible = "mps,mp2971", .data = &mp2975_ddinfo[mp2971]},
+ {.compatible = "mps,mp2973", .data = &mp2975_ddinfo[mp2973]},
+ {.compatible = "mps,mp2975", .data = &mp2975_ddinfo[mp2975]},
{}
};
MODULE_DEVICE_TABLE(of, mp2975_of_match);
+static const struct i2c_device_id mp2975_id[] = {
+ {"mp2971", (kernel_ulong_t)&mp2975_ddinfo[mp2971]},
+ {"mp2973", (kernel_ulong_t)&mp2975_ddinfo[mp2973]},
+ {"mp2975", (kernel_ulong_t)&mp2975_ddinfo[mp2975]},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp2975_id);
+
static struct i2c_driver mp2975_driver = {
.driver = {
.name = "mp2975",
- .of_match_table = of_match_ptr(mp2975_of_match),
+ .of_match_table = mp2975_of_match,
},
.probe = mp2975_probe,
.id_table = mp2975_id,
@@ -999,4 +1101,4 @@ module_i2c_driver(mp2975_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>");
MODULE_DESCRIPTION("PMBus driver for MPS MP2975 device");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp2993.c b/drivers/hwmon/pmbus/mp2993.c
new file mode 100644
index 000000000000..81c84fc8ed47
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2993.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2993)
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define MP2993_VOUT_OVUV_UINT 125
+#define MP2993_VOUT_OVUV_DIV 64
+#define MP2993_VIN_LIMIT_UINT 1
+#define MP2993_VIN_LIMIT_DIV 8
+#define MP2993_READ_VIN_UINT 1
+#define MP2993_READ_VIN_DIV 32
+
+#define MP2993_PAGE_NUM 2
+
+#define MP2993_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+#define MP2993_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
+ PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+/* Converts a linear11 data exponent to a specified value */
+static u16 mp2993_linear11_exponent_transfer(u16 word, u16 expect_exponent)
+{
+ s16 exponent, mantissa, target_exponent;
+
+ exponent = ((s16)word) >> 11;
+ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
+ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11;
+
+ if (exponent > target_exponent)
+ mantissa = mantissa << (exponent - target_exponent);
+ else
+ mantissa = mantissa >> (target_exponent - exponent);
+
+ return (mantissa & 0x7ff) | ((expect_exponent << 11) & 0xf800);
+}
+
+static int
+mp2993_set_vout_format(struct i2c_client *client, int page, int format)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_write_byte_data(client, PMBUS_VOUT_MODE, format);
+}
+
+static int mp2993_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ /* Set vout to direct format for rail1. */
+ ret = mp2993_set_vout_format(client, 0, PB_VOUT_MODE_DIRECT);
+ if (ret < 0)
+ return ret;
+
+ /* Set vout to direct format for rail2. */
+ return mp2993_set_vout_format(client, 1, PB_VOUT_MODE_DIRECT);
+}
+
+static int mp2993_read_word_data(struct i2c_client *client, int page, int phase,
+ int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST(ret * MP2993_VOUT_OVUV_UINT, MP2993_VOUT_OVUV_DIV);
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The MP2993 ot fault limit value and ot warn limit value
+ * per rail are always the same, so only PMBUS_OT_FAULT_LIMIT
+ * and PMBUS_OT_WARN_LIMIT register in page 0 are defined to
+ * indicates the limit value.
+ */
+ ret = pmbus_read_word_data(client, 0, phase, reg);
+ break;
+ case PMBUS_READ_VIN:
+ /* The MP2993 vin scale is (1/32V)/Lsb */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * MP2993_READ_VIN_UINT,
+ MP2993_READ_VIN_DIV);
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /* The MP2993 vin limit scale is (1/8V)/Lsb */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * MP2993_VIN_LIMIT_UINT,
+ MP2993_VIN_LIMIT_DIV);
+ break;
+ case PMBUS_READ_IOUT:
+ case PMBUS_READ_IIN:
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_READ_VOUT:
+ case PMBUS_READ_PIN:
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_TEMPERATURE_1:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp2993_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = DIV_ROUND_CLOSEST(word * MP2993_VOUT_OVUV_DIV, MP2993_VOUT_OVUV_UINT);
+ ret = pmbus_write_word_data(client, 0, reg, ret);
+ break;
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ /*
+ * The MP2993 ot fault limit value and ot warn limit value
+ * per rail are always the same, so only PMBUS_OT_FAULT_LIMIT
+ * and PMBUS_OT_WARN_LIMIT register in page 0 are defined to
+ * config the ot limit value.
+ */
+ ret = pmbus_write_word_data(client, 0, reg, word);
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ /* The MP2993 vin limit scale is (1/8V)/Lsb */
+ ret = pmbus_write_word_data(client, 0, reg,
+ DIV_ROUND_CLOSEST(word * MP2993_VIN_LIMIT_DIV,
+ MP2993_VIN_LIMIT_UINT));
+ break;
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ /*
+ * The PMBUS_IIN_OC_WARN_LIMIT of MP2993 is linear11 format,
+ * and the exponent is a constant value(5'b00000), so the
+ * exponent of word parameter should be converted to 5'b00000.
+ */
+ ret = pmbus_write_word_data(client, page, reg,
+ mp2993_linear11_exponent_transfer(word, 0x00));
+ break;
+ //
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ /*
+ * The PMBUS_IOUT_OC_FAULT_LIMIT and PMBUS_IOUT_OC_WARN_LIMIT
+ * of MP2993 can be regarded as linear11 format, and the
+ * exponent is a 5'b00001 or 5'b00000. To ensure a larger
+ * range of limit value, so the exponent of word parameter
+ * should be converted to 5'b00001.
+ */
+ ret = pmbus_write_word_data(client, page, reg,
+ mp2993_linear11_exponent_transfer(word, 0x01));
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct pmbus_driver_info mp2993_info = {
+ .pages = MP2993_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_POWER] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .m[PSC_VOLTAGE_IN] = 1,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .b[PSC_VOLTAGE_IN] = 0,
+
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 0,
+ .b[PSC_TEMPERATURE] = 0,
+
+ .func[0] = MP2993_RAIL1_FUNC,
+ .func[1] = MP2993_RAIL2_FUNC,
+ .read_word_data = mp2993_read_word_data,
+ .write_word_data = mp2993_write_word_data,
+ .identify = mp2993_identify,
+};
+
+static int mp2993_probe(struct i2c_client *client)
+{
+ return pmbus_do_probe(client, &mp2993_info);
+}
+
+static const struct i2c_device_id mp2993_id[] = {
+ { "mp2993" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mp2993_id);
+
+static const struct of_device_id __maybe_unused mp2993_of_match[] = {
+ {.compatible = "mps,mp2993"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2993_of_match);
+
+static struct i2c_driver mp2993_driver = {
+ .driver = {
+ .name = "mp2993",
+ .of_match_table = mp2993_of_match,
+ },
+ .probe = mp2993_probe,
+ .id_table = mp2993_id,
+};
+
+module_i2c_driver(mp2993_driver);
+
+MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2993");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp5023.c b/drivers/hwmon/pmbus/mp5023.c
index 21acb7fd9a1a..c466d67e9a8f 100644
--- a/drivers/hwmon/pmbus/mp5023.c
+++ b/drivers/hwmon/pmbus/mp5023.c
@@ -64,4 +64,4 @@ module_i2c_driver(mp5023_driver);
MODULE_AUTHOR("Howard Chiu <howard.chiu@quantatw.com>");
MODULE_DESCRIPTION("PMBus driver for MPS MP5023 HSC");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp5920.c b/drivers/hwmon/pmbus/mp5920.c
new file mode 100644
index 000000000000..319ae2721bcf
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp5920.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MP5920 and compatible chips.
+ */
+
+#include <linux/i2c.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+static struct pmbus_driver_info mp5920_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 2266,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = -1,
+ .m[PSC_VOLTAGE_OUT] = 2266,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = -1,
+ .m[PSC_CURRENT_OUT] = 546,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = -2,
+ .m[PSC_POWER] = 5840,
+ .b[PSC_POWER] = 0,
+ .R[PSC_POWER] = -3,
+ .m[PSC_TEMPERATURE] = 1067,
+ .b[PSC_TEMPERATURE] = 20500,
+ .R[PSC_TEMPERATURE] = -2,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_TEMP,
+};
+
+static int mp5920_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ int ret;
+ u8 buf[I2C_SMBUS_BLOCK_MAX];
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA))
+ return -ENODEV;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read PMBUS_MFR_MODEL\n");
+
+ if (ret != 6 || strncmp(buf, "MP5920", 6)) {
+ return dev_err_probe(dev, -ENODEV, "Model '%.*s' not supported\n",
+ min_t(int, ret, sizeof(buf)), buf);
+ }
+
+ return pmbus_do_probe(client, &mp5920_info);
+}
+
+static const struct of_device_id mp5920_of_match[] = {
+ { .compatible = "mps,mp5920" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, mp5920_of_match);
+
+static const struct i2c_device_id mp5920_id[] = {
+ { "mp5920" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, mp5920_id);
+
+static struct i2c_driver mp5920_driver = {
+ .driver = {
+ .name = "mp5920",
+ .of_match_table = mp5920_of_match,
+ },
+ .probe = mp5920_probe,
+ .id_table = mp5920_id,
+};
+
+module_i2c_driver(mp5920_driver);
+
+MODULE_AUTHOR("Tony Ao <tony_ao@wiwynn.com>");
+MODULE_AUTHOR("Alex Vdovydchenko <xzeol@yahoo.com>");
+MODULE_DESCRIPTION("PMBus driver for MP5920 HSC");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp5990.c b/drivers/hwmon/pmbus/mp5990.c
new file mode 100644
index 000000000000..9a4ee79712cf
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp5990.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MP5990 Hot-Swap Controller
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+enum chips { mp5990, mp5998 };
+
+#define MP5990_EFUSE_CFG (0xC4)
+#define MP5990_VOUT_FORMAT BIT(9)
+
+struct mp5990_data {
+ struct pmbus_driver_info info;
+ u8 vout_mode;
+ u8 vout_linear_exponent;
+};
+
+#define to_mp5990_data(x) container_of(x, struct mp5990_data, info)
+
+static int mp5990_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp5990_data *data = to_mp5990_data(info);
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ if (data->vout_mode == linear) {
+ /*
+ * The VOUT format used by the chip is linear11,
+ * not linear16. Report that VOUT is in linear mode
+ * and return exponent value extracted while probing
+ * the chip.
+ */
+ return data->vout_linear_exponent;
+ }
+
+ /*
+ * The datasheet does not support the VOUT command,
+ * but the device responds with a default value of 0x17.
+ * In the standard, 0x17 represents linear mode.
+ * Therefore, we should report that VOUT is in direct
+ * format when the chip is configured for it.
+ */
+ return PB_VOUT_MODE_DIRECT;
+
+ default:
+ return -ENODATA;
+ }
+}
+
+static int mp5990_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp5990_data *data = to_mp5990_data(info);
+ int ret;
+ s32 mantissa;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ /*
+ * Because the VOUT format used by the chip is linear11 and not
+ * linear16, we disregard bits[15:11]. The exponent is reported
+ * as part of the VOUT_MODE command.
+ */
+ if (data->vout_mode == linear) {
+ mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5;
+ ret = mantissa;
+ }
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+static struct pmbus_driver_info mp5990_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 32,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .m[PSC_VOLTAGE_OUT] = 32,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 0,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_POWER] = 1,
+ .b[PSC_POWER] = 0,
+ .R[PSC_POWER] = 0,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .read_byte_data = mp5990_read_byte_data,
+ .read_word_data = mp5990_read_word_data,
+};
+
+static struct pmbus_driver_info mp5998_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 64,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .m[PSC_VOLTAGE_OUT] = 64,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 0,
+ .m[PSC_CURRENT_IN] = 16,
+ .b[PSC_CURRENT_IN] = 0,
+ .R[PSC_CURRENT_IN] = 0,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_POWER] = 2,
+ .b[PSC_POWER] = 0,
+ .R[PSC_POWER] = 0,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .read_byte_data = mp5990_read_byte_data,
+ .read_word_data = mp5990_read_word_data,
+};
+
+static const struct i2c_device_id mp5990_id[] = {
+ {"mp5990", mp5990},
+ {"mp5998", mp5998},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mp5990_id);
+
+static int mp5990_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp5990_data *data;
+ enum chips chip;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (client->dev.of_node)
+ chip = (uintptr_t)of_device_get_match_data(&client->dev);
+ else
+ chip = i2c_match_id(mp5990_id, client)->driver_data;
+
+ if (chip == mp5990)
+ memcpy(&data->info, &mp5990_info, sizeof(*info));
+ else
+ memcpy(&data->info, &mp5998_info, sizeof(*info));
+ info = &data->info;
+
+ /* Read Vout Config */
+ ret = i2c_smbus_read_word_data(client, MP5990_EFUSE_CFG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get vout mode.");
+ return ret;
+ }
+
+ /*
+ * EFUSE_CFG (0xC4) bit9=1 is linear mode, bit=0 is direct mode.
+ */
+ if (ret & MP5990_VOUT_FORMAT) {
+ data->vout_mode = linear;
+ data->info.format[PSC_VOLTAGE_IN] = linear;
+ data->info.format[PSC_VOLTAGE_OUT] = linear;
+ data->info.format[PSC_CURRENT_OUT] = linear;
+ data->info.format[PSC_POWER] = linear;
+ if (chip == mp5998)
+ data->info.format[PSC_CURRENT_IN] = linear;
+
+ ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get vout exponent.");
+ return ret;
+ }
+ data->vout_linear_exponent = (u8)((ret >> 11) & 0x1f);
+ } else {
+ data->vout_mode = direct;
+ }
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id mp5990_of_match[] = {
+ { .compatible = "mps,mp5990", .data = (void *)mp5990 },
+ { .compatible = "mps,mp5998", .data = (void *)mp5998 },
+ {}
+};
+
+static struct i2c_driver mp5990_driver = {
+ .driver = {
+ .name = "mp5990",
+ .of_match_table = mp5990_of_match,
+ },
+ .probe = mp5990_probe,
+ .id_table = mp5990_id,
+};
+module_i2c_driver(mp5990_driver);
+
+MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for MP5990 HSC");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp9941.c b/drivers/hwmon/pmbus/mp9941.c
new file mode 100644
index 000000000000..42ca6748777a
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp9941.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP9941)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+/*
+ * Vender specific registers. The MFR_ICC_MAX(0x02) is used to
+ * config the iin scale. The MFR_RESO_SET(0xC7) is used to
+ * config the vout format. The MFR_VR_MULTI_CONFIG_R1(0x0D) is
+ * used to identify the vout vid step.
+ */
+#define MFR_ICC_MAX 0x02
+#define MFR_RESO_SET 0xC7
+#define MFR_VR_MULTI_CONFIG_R1 0x0D
+
+#define MP9941_VIN_LIMIT_UINT 1
+#define MP9941_VIN_LIMIT_DIV 8
+#define MP9941_READ_VIN_UINT 1
+#define MP9941_READ_VIN_DIV 32
+
+#define MP9941_PAGE_NUM 1
+
+#define MP9941_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
+ PMBUS_HAVE_IIN | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+struct mp9941_data {
+ struct pmbus_driver_info info;
+ int vid_resolution;
+};
+
+#define to_mp9941_data(x) container_of(x, struct mp9941_data, info)
+
+static int mp9941_set_vout_format(struct i2c_client *client)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_RESO_SET);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * page = 0, MFR_RESO_SET[7:6] defines the vout format
+ * 2'b11 set the vout format as direct
+ */
+ ret = (ret & ~GENMASK(7, 6)) | FIELD_PREP(GENMASK(7, 6), 3);
+
+ return i2c_smbus_write_word_data(client, MFR_RESO_SET, ret);
+}
+
+static int
+mp9941_identify_vid_resolution(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ struct mp9941_data *data = to_mp9941_data(info);
+ int ret;
+
+ /*
+ * page = 2, MFR_VR_MULTI_CONFIG_R1[4:4] defines rail1 vid step value
+ * 1'b0 represents the vid step value is 10mV
+ * 1'b1 represents the vid step value is 5mV
+ */
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1);
+ if (ret < 0)
+ return ret;
+
+ if (FIELD_GET(GENMASK(4, 4), ret))
+ data->vid_resolution = 5;
+ else
+ data->vid_resolution = 10;
+
+ return 0;
+}
+
+static int mp9941_identify_iin_scale(struct i2c_client *client)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_RESO_SET);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & ~GENMASK(3, 2)) | FIELD_PREP(GENMASK(3, 2), 0);
+
+ ret = i2c_smbus_write_word_data(client, MFR_RESO_SET, ret);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * page = 2, MFR_ICC_MAX[15:13] defines the iin scale
+ * 3'b000 set the iout scale as 0.5A/Lsb
+ */
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_ICC_MAX);
+ if (ret < 0)
+ return ret;
+
+ ret = (ret & ~GENMASK(15, 13)) | FIELD_PREP(GENMASK(15, 13), 0);
+
+ return i2c_smbus_write_word_data(client, MFR_ICC_MAX, ret);
+}
+
+static int mp9941_identify(struct i2c_client *client, struct pmbus_driver_info *info)
+{
+ int ret;
+
+ ret = mp9941_identify_iin_scale(client);
+ if (ret < 0)
+ return ret;
+
+ ret = mp9941_identify_vid_resolution(client, info);
+ if (ret < 0)
+ return ret;
+
+ return mp9941_set_vout_format(client);
+}
+
+static int mp9941_read_word_data(struct i2c_client *client, int page, int phase,
+ int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp9941_data *data = to_mp9941_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VIN:
+ /* The MP9941 vin scale is (1/32V)/Lsb */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * MP9941_READ_VIN_UINT,
+ MP9941_READ_VIN_DIV);
+ break;
+ case PMBUS_READ_IIN:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ret & GENMASK(10, 0);
+ break;
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /* The MP9941 vin ov limit scale is (1/8V)/Lsb */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * MP9941_VIN_LIMIT_UINT,
+ MP9941_VIN_LIMIT_DIV);
+ break;
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ret & GENMASK(7, 0);
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ case PMBUS_MFR_VOUT_MIN:
+ case PMBUS_MFR_VOUT_MAX:
+ /*
+ * The vout scale is set to 1mV/Lsb(using r/m/b scale).
+ * But the vout uv limit and vout max/min scale is 1VID/Lsb,
+ * so the vout uv limit and vout max/min value should be
+ * multiplied by vid resolution.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ret * data->vid_resolution;
+ break;
+ case PMBUS_READ_IOUT:
+ case PMBUS_READ_POUT:
+ case PMBUS_READ_TEMPERATURE_1:
+ case PMBUS_READ_VOUT:
+ case PMBUS_READ_PIN:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp9941_write_word_data(struct i2c_client *client, int page, int reg,
+ u16 word)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp9941_data *data = to_mp9941_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ /* The MP9941 vin ov limit scale is (1/8V)/Lsb */
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word * MP9941_VIN_LIMIT_DIV,
+ MP9941_VIN_LIMIT_UINT));
+ break;
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ case PMBUS_MFR_VOUT_MIN:
+ case PMBUS_MFR_VOUT_MAX:
+ ret = pmbus_write_word_data(client, page, reg,
+ DIV_ROUND_CLOSEST(word, data->vid_resolution));
+ break;
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct pmbus_driver_info mp9941_info = {
+ .pages = MP9941_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_IN] = direct,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .format[PSC_TEMPERATURE] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+
+ .m[PSC_TEMPERATURE] = 1,
+ .R[PSC_TEMPERATURE] = 0,
+ .b[PSC_TEMPERATURE] = 0,
+
+ .m[PSC_VOLTAGE_IN] = 1,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .b[PSC_VOLTAGE_IN] = 0,
+
+ .m[PSC_CURRENT_IN] = 2,
+ .R[PSC_CURRENT_IN] = 0,
+ .b[PSC_CURRENT_IN] = 0,
+
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+
+ .func[0] = MP9941_RAIL1_FUNC,
+ .read_word_data = mp9941_read_word_data,
+ .write_word_data = mp9941_write_word_data,
+ .identify = mp9941_identify,
+};
+
+static int mp9941_probe(struct i2c_client *client)
+{
+ struct mp9941_data *data;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp9941_info, sizeof(mp9941_info));
+
+ return pmbus_do_probe(client, &data->info);
+}
+
+static const struct i2c_device_id mp9941_id[] = {
+ { "mp9941" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mp9941_id);
+
+static const struct of_device_id __maybe_unused mp9941_of_match[] = {
+ {.compatible = "mps,mp9941"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp9941_of_match);
+
+static struct i2c_driver mp9941_driver = {
+ .driver = {
+ .name = "mp9941",
+ .of_match_table = mp9941_of_match,
+ },
+ .probe = mp9941_probe,
+ .id_table = mp9941_id,
+};
+
+module_i2c_driver(mp9941_driver);
+
+MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP9941");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mp9945.c b/drivers/hwmon/pmbus/mp9945.c
new file mode 100644
index 000000000000..34822e0de812
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp9945.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Single-phase Digital VR Controllers(MP9945)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define MFR_VR_MULTI_CONFIG_R1 0x08
+#define MFR_SVID_CFG_R1 0xBD
+
+/* VOUT_MODE register values */
+#define VOUT_MODE_LINEAR16 0x17
+#define VOUT_MODE_VID 0x21
+#define VOUT_MODE_DIRECT 0x40
+
+#define MP9945_PAGE_NUM 1
+
+#define MP9945_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | \
+ PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \
+ PMBUS_HAVE_TEMP | \
+ PMBUS_HAVE_STATUS_VOUT | \
+ PMBUS_HAVE_STATUS_IOUT | \
+ PMBUS_HAVE_STATUS_TEMP | \
+ PMBUS_HAVE_STATUS_INPUT)
+
+enum mp9945_vout_mode {
+ MP9945_VOUT_MODE_VID,
+ MP9945_VOUT_MODE_DIRECT,
+ MP9945_VOUT_MODE_LINEAR16,
+};
+
+struct mp9945_data {
+ struct pmbus_driver_info info;
+ enum mp9945_vout_mode vout_mode;
+ int vid_resolution;
+ int vid_offset;
+};
+
+#define to_mp9945_data(x) container_of(x, struct mp9945_data, info)
+
+static int mp9945_read_vout(struct i2c_client *client, struct mp9945_data *data)
+{
+ int ret;
+
+ ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
+ if (ret < 0)
+ return ret;
+
+ ret &= GENMASK(11, 0);
+
+ switch (data->vout_mode) {
+ case MP9945_VOUT_MODE_VID:
+ if (ret > 0)
+ ret = (ret + data->vid_offset) * data->vid_resolution;
+ break;
+ case MP9945_VOUT_MODE_DIRECT:
+ break;
+ case MP9945_VOUT_MODE_LINEAR16:
+ /* LSB: 1000 * 2^-9 (mV) */
+ ret = DIV_ROUND_CLOSEST(ret * 125, 64);
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+static int mp9945_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /*
+ * Override VOUT_MODE to DIRECT as the driver handles custom
+ * VOUT format conversions internally.
+ */
+ return PB_VOUT_MODE_DIRECT;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int mp9945_read_word_data(struct i2c_client *client, int page, int phase,
+ int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp9945_data *data = to_mp9945_data(info);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = mp9945_read_vout(client, data);
+ break;
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ /* LSB: 1.95 (mV) */
+ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * 39, 20);
+ break;
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= GENMASK(9, 0);
+ if (ret > 0)
+ ret = (ret + data->vid_offset) * data->vid_resolution;
+ break;
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int mp9945_identify(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ struct mp9945_data *data = to_mp9945_data(info);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case VOUT_MODE_LINEAR16:
+ data->vout_mode = MP9945_VOUT_MODE_LINEAR16;
+ break;
+ case VOUT_MODE_VID:
+ data->vout_mode = MP9945_VOUT_MODE_VID;
+ break;
+ case VOUT_MODE_DIRECT:
+ data->vout_mode = MP9945_VOUT_MODE_DIRECT;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1);
+ if (ret < 0)
+ return ret;
+
+ data->vid_resolution = (FIELD_GET(BIT(2), ret)) ? 5 : 10;
+
+ ret = i2c_smbus_read_word_data(client, MFR_SVID_CFG_R1);
+ if (ret < 0)
+ return ret;
+
+ data->vid_offset = (FIELD_GET(BIT(15), ret)) ? 19 : 49;
+
+ return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+}
+
+static struct pmbus_driver_info mp9945_info = {
+ .pages = MP9945_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .format[PSC_TEMPERATURE] = linear,
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .func[0] = MP9945_RAIL1_FUNC,
+ .read_word_data = mp9945_read_word_data,
+ .read_byte_data = mp9945_read_byte_data,
+ .identify = mp9945_identify,
+};
+
+static int mp9945_probe(struct i2c_client *client)
+{
+ struct mp9945_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp9945_info, sizeof(mp9945_info));
+
+ /*
+ * Set page 0 before probe. The core reads paged registers which are
+ * only on page 0 for this device.
+ */
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+ if (ret < 0)
+ return ret;
+
+ return pmbus_do_probe(client, &data->info);
+}
+
+static const struct i2c_device_id mp9945_id[] = {
+ {"mp9945"},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp9945_id);
+
+static const struct of_device_id __maybe_unused mp9945_of_match[] = {
+ {.compatible = "mps,mp9945"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp9945_of_match);
+
+static struct i2c_driver mp9945_driver = {
+ .driver = {
+ .name = "mp9945",
+ .of_match_table = of_match_ptr(mp9945_of_match),
+ },
+ .probe = mp9945_probe,
+ .id_table = mp9945_id,
+};
+
+module_i2c_driver(mp9945_driver);
+
+MODULE_AUTHOR("Cosmo Chou <chou.cosmo@gmail.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP9945");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mpq7932.c b/drivers/hwmon/pmbus/mpq7932.c
index 6c62f01da7c6..8f10e37a7a76 100644
--- a/drivers/hwmon/pmbus/mpq7932.c
+++ b/drivers/hwmon/pmbus/mpq7932.c
@@ -21,6 +21,7 @@
#define MPQ7932_N_VOLTAGES 256
#define MPQ7932_VOUT_MAX 0xFF
#define MPQ7932_NUM_PAGES 6
+#define MPQ2286_NUM_PAGES 1
#define MPQ7932_TON_DELAY 0x60
#define MPQ7932_VOUT_STARTUP_SLEW 0xA3
@@ -34,7 +35,7 @@ struct mpq7932_data {
};
#if IS_ENABLED(CONFIG_SENSORS_MPQ7932_REGULATOR)
-static struct regulator_desc mpq7932_regulators_desc[] = {
+static const struct regulator_desc mpq7932_regulators_desc[] = {
PMBUS_REGULATOR_STEP("buck", 0, MPQ7932_N_VOLTAGES,
MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN),
PMBUS_REGULATOR_STEP("buck", 1, MPQ7932_N_VOLTAGES,
@@ -48,6 +49,11 @@ static struct regulator_desc mpq7932_regulators_desc[] = {
PMBUS_REGULATOR_STEP("buck", 5, MPQ7932_N_VOLTAGES,
MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN),
};
+
+static const struct regulator_desc mpq7932_regulators_desc_one[] = {
+ PMBUS_REGULATOR_STEP_ONE_NODE("buck", MPQ7932_N_VOLTAGES,
+ MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN),
+};
#endif
static int mpq7932_write_word_data(struct i2c_client *client, int page, int reg,
@@ -105,7 +111,7 @@ static int mpq7932_probe(struct i2c_client *client)
return -ENOMEM;
info = &data->info;
- info->pages = MPQ7932_NUM_PAGES;
+ info->pages = (int)(unsigned long)device_get_match_data(&client->dev);
info->format[PSC_VOLTAGE_OUT] = direct;
info->m[PSC_VOLTAGE_OUT] = 160;
info->b[PSC_VOLTAGE_OUT] = -33;
@@ -115,8 +121,11 @@ static int mpq7932_probe(struct i2c_client *client)
}
#if IS_ENABLED(CONFIG_SENSORS_MPQ7932_REGULATOR)
- info->num_regulators = ARRAY_SIZE(mpq7932_regulators_desc);
- info->reg_desc = mpq7932_regulators_desc;
+ info->num_regulators = info->pages;
+ if (info->num_regulators == 1)
+ info->reg_desc = mpq7932_regulators_desc_one;
+ else
+ info->reg_desc = mpq7932_regulators_desc;
#endif
info->read_word_data = mpq7932_read_word_data;
@@ -129,12 +138,14 @@ static int mpq7932_probe(struct i2c_client *client)
}
static const struct of_device_id mpq7932_of_match[] = {
- { .compatible = "mps,mpq7932"},
+ { .compatible = "mps,mpq2286", .data = (void *)MPQ2286_NUM_PAGES },
+ { .compatible = "mps,mpq7932", .data = (void *)MPQ7932_NUM_PAGES },
{},
};
MODULE_DEVICE_TABLE(of, mpq7932_of_match);
static const struct i2c_device_id mpq7932_id[] = {
+ { "mpq2286", },
{ "mpq7932", },
{ },
};
@@ -153,4 +164,4 @@ module_i2c_driver(mpq7932_regulator_driver);
MODULE_AUTHOR("Saravanan Sekar <saravanan@linumiz.com>");
MODULE_DESCRIPTION("MPQ7932 PMIC regulator driver");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c
new file mode 100644
index 000000000000..1f56aaf4dde8
--- /dev/null
+++ b/drivers/hwmon/pmbus/mpq8785.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MPQ8785 Step-Down Converter
+ */
+
+#include <linux/i2c.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define MPM82504_READ_TEMPERATURE_1_SIGN_POS 9
+
+enum chips { mpm3695, mpm3695_25, mpm82504, mpq8785 };
+
+static u16 voltage_scale_loop_max_val[] = {
+ [mpm3695] = GENMASK(9, 0),
+ [mpm3695_25] = GENMASK(11, 0),
+ [mpm82504] = GENMASK(9, 0),
+ [mpq8785] = GENMASK(10, 0),
+};
+
+static int mpq8785_identify(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ int vout_mode;
+
+ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
+ if (vout_mode < 0 || vout_mode == 0xff)
+ return vout_mode < 0 ? vout_mode : -ENODEV;
+ switch (vout_mode >> 5) {
+ case 0:
+ info->format[PSC_VOLTAGE_OUT] = linear;
+ break;
+ case 1:
+ case 2:
+ info->format[PSC_VOLTAGE_OUT] = direct;
+ info->m[PSC_VOLTAGE_OUT] = 64;
+ info->b[PSC_VOLTAGE_OUT] = 0;
+ info->R[PSC_VOLTAGE_OUT] = 1;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+};
+
+static int mpm82504_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ int ret;
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+
+ if (ret < 0 || reg != PMBUS_READ_TEMPERATURE_1)
+ return ret;
+
+ /* Fix PMBUS_READ_TEMPERATURE_1 signedness */
+ return sign_extend32(ret, MPM82504_READ_TEMPERATURE_1_SIGN_POS) & 0xffff;
+}
+
+static struct pmbus_driver_info mpq8785_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 4,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static const struct i2c_device_id mpq8785_id[] = {
+ { "mpm3695", mpm3695 },
+ { "mpm3695-25", mpm3695_25 },
+ { "mpm82504", mpm82504 },
+ { "mpq8785", mpq8785 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, mpq8785_id);
+
+static const struct of_device_id __maybe_unused mpq8785_of_match[] = {
+ { .compatible = "mps,mpm3695", .data = (void *)mpm3695 },
+ { .compatible = "mps,mpm3695-25", .data = (void *)mpm3695_25 },
+ { .compatible = "mps,mpm82504", .data = (void *)mpm82504 },
+ { .compatible = "mps,mpq8785", .data = (void *)mpq8785 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpq8785_of_match);
+
+static int mpq8785_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct pmbus_driver_info *info;
+ enum chips chip_id;
+ u32 voltage_scale;
+ int ret;
+
+ info = devm_kmemdup(dev, &mpq8785_info, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ if (dev->of_node)
+ chip_id = (kernel_ulong_t)of_device_get_match_data(dev);
+ else
+ chip_id = (kernel_ulong_t)i2c_get_match_data(client);
+
+ switch (chip_id) {
+ case mpm3695:
+ case mpm3695_25:
+ case mpm82504:
+ info->format[PSC_VOLTAGE_OUT] = direct;
+ info->m[PSC_VOLTAGE_OUT] = 8;
+ info->b[PSC_VOLTAGE_OUT] = 0;
+ info->R[PSC_VOLTAGE_OUT] = 2;
+ info->read_word_data = mpm82504_read_word_data;
+ break;
+ case mpq8785:
+ info->identify = mpq8785_identify;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille",
+ &voltage_scale)) {
+ if (voltage_scale > voltage_scale_loop_max_val[chip_id])
+ return -EINVAL;
+
+ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP,
+ voltage_scale);
+ if (ret)
+ return ret;
+ }
+
+ return pmbus_do_probe(client, info);
+};
+
+static struct i2c_driver mpq8785_driver = {
+ .driver = {
+ .name = "mpq8785",
+ .of_match_table = of_match_ptr(mpq8785_of_match),
+ },
+ .probe = mpq8785_probe,
+ .id_table = mpq8785_id,
+};
+
+module_i2c_driver(mpq8785_driver);
+
+MODULE_AUTHOR("Charles Hsu <ythsu0511@gmail.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MPQ8785");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/pim4328.c b/drivers/hwmon/pmbus/pim4328.c
index 31d9ae06379a..aa98284bbdd8 100644
--- a/drivers/hwmon/pmbus/pim4328.c
+++ b/drivers/hwmon/pmbus/pim4328.c
@@ -230,4 +230,4 @@ module_i2c_driver(pim4328_driver);
MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c
index c95433790b11..569b61dc1a32 100644
--- a/drivers/hwmon/pmbus/pli1209bc.c
+++ b/drivers/hwmon/pmbus/pli1209bc.c
@@ -54,30 +54,6 @@ static int pli1209bc_read_word_data(struct i2c_client *client, int page,
}
}
-static int pli1209bc_write_byte(struct i2c_client *client, int page, u8 reg)
-{
- int ret;
-
- switch (reg) {
- case PMBUS_CLEAR_FAULTS:
- ret = pmbus_write_byte(client, page, reg);
- /*
- * PLI1209 takes 230 usec to execute the CLEAR_FAULTS command.
- * During that time it's busy and NACKs all requests on the
- * SMBUS interface. It also NACKs reads on PMBUS_STATUS_BYTE
- * making it impossible to poll the BUSY flag.
- *
- * Just wait for not BUSY unconditionally.
- */
- usleep_range(250, 300);
- break;
- default:
- ret = -ENODATA;
- break;
- }
- return ret;
-}
-
#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR)
static const struct regulator_desc pli1209bc_reg_desc = {
.name = "vout2",
@@ -127,7 +103,7 @@ static struct pmbus_driver_info pli1209bc_info = {
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
| PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT,
.read_word_data = pli1209bc_read_word_data,
- .write_byte = pli1209bc_write_byte,
+ .write_delay = 250,
#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR)
.num_regulators = 1,
.reg_desc = &pli1209bc_reg_desc,
@@ -141,7 +117,7 @@ static int pli1209bc_probe(struct i2c_client *client)
}
static const struct i2c_device_id pli1209bc_id[] = {
- {"pli1209bc", 0},
+ {"pli1209bc"},
{}
};
@@ -169,4 +145,4 @@ module_i2c_driver(pli1209bc_driver);
MODULE_AUTHOR("Marcello Sylvester Bauer <sylv@sylv.io>");
MODULE_DESCRIPTION("PMBus driver for Vicor PLI1209BC");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/pm6764tr.c b/drivers/hwmon/pmbus/pm6764tr.c
index 2a16504c85b7..c96c0aecb920 100644
--- a/drivers/hwmon/pmbus/pm6764tr.c
+++ b/drivers/hwmon/pmbus/pm6764tr.c
@@ -48,7 +48,7 @@ static int pm6764tr_probe(struct i2c_client *client)
}
static const struct i2c_device_id pm6764tr_id[] = {
- {"pm6764tr", 0},
+ {"pm6764tr"},
{}
};
MODULE_DEVICE_TABLE(i2c, pm6764tr_id);
@@ -73,4 +73,4 @@ module_i2c_driver(pm6764tr_driver);
MODULE_AUTHOR("Charles Hsu");
MODULE_DESCRIPTION("PMBus driver for ST PM6764TR");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c
index ec40c5c59954..920cd5408141 100644
--- a/drivers/hwmon/pmbus/pmbus.c
+++ b/drivers/hwmon/pmbus/pmbus.c
@@ -103,6 +103,8 @@ static int pmbus_identify(struct i2c_client *client,
if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) {
int page;
+ info->pages = PMBUS_PAGES;
+
for (page = 1; page < PMBUS_PAGES; page++) {
if (pmbus_set_page(client, page, 0xff) < 0)
break;
@@ -261,4 +263,4 @@ module_i2c_driver(pmbus_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("Generic PMBus driver");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index b0832a4c690d..d2e9bfb5320f 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -244,6 +244,15 @@ enum pmbus_regs {
#define PB_OPERATION_CONTROL_ON BIT(7)
/*
+ * ON_OFF_CONFIG
+ */
+#define PB_ON_OFF_CONFIG_POWERUP_CONTROL BIT(4)
+#define PB_ON_OFF_CONFIG_OPERATION_REQ BIT(3)
+#define PB_ON_OFF_CONFIG_EN_PIN_REQ BIT(2)
+#define PB_ON_OFF_CONFIG_POLARITY_HIGH BIT(1)
+#define PB_ON_OFF_CONFIG_TURN_OFF_FAST BIT(0)
+
+/*
* WRITE_PROTECT
*/
#define PB_WP_ALL BIT(7) /* all but WRITE_PROTECT */
@@ -409,6 +418,12 @@ enum pmbus_sensor_classes {
enum pmbus_data_format { linear = 0, ieee754, direct, vid };
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
+/* PMBus revision identifiers */
+#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */
+#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */
+#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */
+#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */
+
struct pmbus_driver_info {
int pages; /* Total number of pages */
u8 phases[PMBUS_PAGES]; /* Number of phases per page */
@@ -457,11 +472,24 @@ struct pmbus_driver_info {
/* custom attributes */
const struct attribute_group **groups;
+
+ /*
+ * Some chips need a little delay between SMBus communication. When
+ * set, the generic PMBus helper functions will wait if necessary
+ * to meet this requirement. The access delay is honored after
+ * every SMBus operation. The write delay is only honored after
+ * SMBus write operations.
+ */
+ int access_delay; /* in microseconds */
+ int write_delay; /* in microseconds */
+ int page_change_delay; /* in microseconds */
};
/* Regulator ops */
extern const struct regulator_ops pmbus_regulator_ops;
+int pmbus_regulator_init_cb(struct regulator_dev *rdev,
+ struct regulator_config *config);
/* Macros for filling in array of struct regulator_desc */
#define PMBUS_REGULATOR_STEP(_name, _id, _voltages, _step, _min_uV) \
@@ -476,10 +504,39 @@ extern const struct regulator_ops pmbus_regulator_ops;
.n_voltages = _voltages, \
.uV_step = _step, \
.min_uV = _min_uV, \
+ .init_cb = pmbus_regulator_init_cb, \
}
#define PMBUS_REGULATOR(_name, _id) PMBUS_REGULATOR_STEP(_name, _id, 0, 0, 0)
+#define __PMBUS_REGULATOR_STEP_ONE(_name, _node, _voltages, _step, _min_uV) \
+ { \
+ .name = (_name), \
+ .of_match = of_match_ptr(_name), \
+ .regulators_node = of_match_ptr(_node), \
+ .ops = &pmbus_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .n_voltages = _voltages, \
+ .uV_step = _step, \
+ .min_uV = _min_uV, \
+ .init_cb = pmbus_regulator_init_cb, \
+ }
+
+/*
+ * _NODE macros are defined for historic reasons and MUST NOT be used in new
+ * drivers.
+ */
+#define PMBUS_REGULATOR_STEP_ONE_NODE(_name, _voltages, _step, _min_uV) \
+ __PMBUS_REGULATOR_STEP_ONE(_name, "regulators", _voltages, _step, _min_uV)
+
+#define PMBUS_REGULATOR_ONE_NODE(_name) PMBUS_REGULATOR_STEP_ONE_NODE(_name, 0, 0, 0)
+
+#define PMBUS_REGULATOR_STEP_ONE(_name, _voltages, _step, _min_uV) \
+ __PMBUS_REGULATOR_STEP_ONE(_name, NULL, _voltages, _step, _min_uV)
+
+#define PMBUS_REGULATOR_ONE(_name) PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0)
+
/* Function declarations */
void pmbus_clear_cache(struct i2c_client *client);
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 1363d9f89181..be6d05def115 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -7,6 +7,8 @@
*/
#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dcache.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
@@ -30,6 +32,16 @@
#define PMBUS_ATTR_ALLOC_SIZE 32
#define PMBUS_NAME_SIZE 24
+/*
+ * The type of operation used for picking the delay between
+ * successive pmbus operations.
+ */
+#define PMBUS_OP_WRITE BIT(0)
+#define PMBUS_OP_PAGE_CHANGE BIT(1)
+
+static int wp = -1;
+module_param(wp, int, 0444);
+
struct pmbus_sensor {
struct pmbus_sensor *next;
char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */
@@ -40,8 +52,7 @@ struct pmbus_sensor {
enum pmbus_sensor_classes class; /* sensor class */
bool update; /* runtime sensor update needed */
bool convert; /* Whether or not to apply linear/vid/direct */
- int data; /* Sensor data.
- Negative if there was a read error */
+ int data; /* Sensor data; negative if there was a read error */
};
#define to_pmbus_sensor(_attr) \
container_of(_attr, struct pmbus_sensor, attribute)
@@ -85,6 +96,8 @@ struct pmbus_data {
u32 flags; /* from platform data */
+ u8 revision; /* The PMBus revision the device is compliant with */
+
int exponent[PMBUS_PAGES];
/* linear mode: exponent for output voltages */
@@ -94,7 +107,6 @@ struct pmbus_data {
int num_attributes;
struct attribute_group group;
const struct attribute_group **groups;
- struct dentry *debugfs; /* debugfs device directory */
struct pmbus_sensor *sensors;
@@ -108,6 +120,8 @@ struct pmbus_data {
int vout_low[PMBUS_PAGES]; /* voltage low margin */
int vout_high[PMBUS_PAGES]; /* voltage high margin */
+
+ ktime_t next_access_backoff; /* Wait until at least this time */
};
struct pmbus_debugfs_entry {
@@ -145,7 +159,7 @@ void pmbus_clear_cache(struct i2c_client *client)
for (sensor = data->sensors; sensor; sensor = sensor->next)
sensor->data = -ENODATA;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_clear_cache, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_clear_cache, "PMBUS");
void pmbus_set_update(struct i2c_client *client, u8 reg, bool update)
{
@@ -156,7 +170,33 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update)
if (sensor->reg == reg)
sensor->update = update;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_set_update, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS");
+
+/* Some chips need a delay between accesses. */
+static void pmbus_wait(struct i2c_client *client)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get());
+
+ if (delay > 0)
+ fsleep(delay);
+}
+
+/* Sets the last operation timestamp for pmbus_wait */
+static void pmbus_update_ts(struct i2c_client *client, int op)
+{
+ struct pmbus_data *data = i2c_get_clientdata(client);
+ const struct pmbus_driver_info *info = data->info;
+ int delay = info->access_delay;
+
+ if (op & PMBUS_OP_WRITE)
+ delay = max(delay, info->write_delay);
+ if (op & PMBUS_OP_PAGE_CHANGE)
+ delay = max(delay, info->page_change_delay);
+
+ if (delay > 0)
+ data->next_access_backoff = ktime_add_us(ktime_get(), delay);
+}
int pmbus_set_page(struct i2c_client *client, int page, int phase)
{
@@ -168,11 +208,15 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL) &&
data->info->pages > 1 && page != data->currpage) {
+ pmbus_wait(client);
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
+ pmbus_update_ts(client, PMBUS_OP_WRITE | PMBUS_OP_PAGE_CHANGE);
if (rv < 0)
return rv;
+ pmbus_wait(client);
rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
+ pmbus_update_ts(client, 0);
if (rv < 0)
return rv;
@@ -183,8 +227,10 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
if (data->info->phases[page] && data->currphase != phase &&
!(data->info->func[page] & PMBUS_PHASE_VIRTUAL)) {
+ pmbus_wait(client);
rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE,
phase);
+ pmbus_update_ts(client, PMBUS_OP_WRITE);
if (rv)
return rv;
}
@@ -192,7 +238,7 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
return 0;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_set_page, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_set_page, "PMBUS");
int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
{
@@ -202,9 +248,13 @@ int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
if (rv < 0)
return rv;
- return i2c_smbus_write_byte(client, value);
+ pmbus_wait(client);
+ rv = i2c_smbus_write_byte(client, value);
+ pmbus_update_ts(client, PMBUS_OP_WRITE);
+
+ return rv;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, "PMBUS");
/*
* _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if
@@ -233,10 +283,13 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
if (rv < 0)
return rv;
- return i2c_smbus_write_word_data(client, reg, word);
-}
-EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, PMBUS);
+ pmbus_wait(client);
+ rv = i2c_smbus_write_word_data(client, reg, word);
+ pmbus_update_ts(client, PMBUS_OP_WRITE);
+ return rv;
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, "PMBUS");
static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg,
u16 word)
@@ -326,14 +379,14 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id,
u8 to;
from = _pmbus_read_byte_data(client, page,
- pmbus_fan_config_registers[id]);
+ pmbus_fan_config_registers[id]);
if (from < 0)
return from;
to = (from & ~mask) | (config & mask);
if (to != from) {
rv = _pmbus_write_byte_data(client, page,
- pmbus_fan_config_registers[id], to);
+ pmbus_fan_config_registers[id], to);
if (rv < 0)
return rv;
}
@@ -341,7 +394,7 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id,
return _pmbus_write_word_data(client, page,
pmbus_fan_command_registers[id], command);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_update_fan, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_update_fan, "PMBUS");
int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg)
{
@@ -351,9 +404,13 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg)
if (rv < 0)
return rv;
- return i2c_smbus_read_word_data(client, reg);
+ pmbus_wait(client);
+ rv = i2c_smbus_read_word_data(client, reg);
+ pmbus_update_ts(client, 0);
+
+ return rv;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, "PMBUS");
static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg)
{
@@ -410,9 +467,13 @@ int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg)
if (rv < 0)
return rv;
- return i2c_smbus_read_byte_data(client, reg);
+ pmbus_wait(client);
+ rv = i2c_smbus_read_byte_data(client, reg);
+ pmbus_update_ts(client, 0);
+
+ return rv;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, "PMBUS");
int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value)
{
@@ -422,9 +483,13 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value)
if (rv < 0)
return rv;
- return i2c_smbus_write_byte_data(client, reg, value);
+ pmbus_wait(client);
+ rv = i2c_smbus_write_byte_data(client, reg, value);
+ pmbus_update_ts(client, PMBUS_OP_WRITE);
+
+ return rv;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_write_byte_data, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_write_byte_data, "PMBUS");
int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
u8 mask, u8 value)
@@ -443,7 +508,7 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
return rv;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS");
static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
char *data_buf)
@@ -454,7 +519,11 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
if (rv < 0)
return rv;
- return i2c_smbus_read_block_data(client, reg, data_buf);
+ pmbus_wait(client);
+ rv = i2c_smbus_read_block_data(client, reg, data_buf);
+ pmbus_update_ts(client, 0);
+
+ return rv;
}
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
@@ -492,7 +561,7 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id,
}
config = _pmbus_read_byte_data(client, page,
- pmbus_fan_config_registers[id]);
+ pmbus_fan_config_registers[id]);
if (config < 0)
return config;
@@ -510,14 +579,14 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
{
return pmbus_get_fan_rate(client, page, id, mode, false);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_device, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_device, "PMBUS");
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
enum pmbus_fan_mode mode)
{
return pmbus_get_fan_rate(client, page, id, mode, true);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_cached, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_fan_rate_cached, "PMBUS");
static void pmbus_clear_fault_page(struct i2c_client *client, int page)
{
@@ -532,7 +601,7 @@ void pmbus_clear_faults(struct i2c_client *client)
for (i = 0; i < data->info->pages; i++)
pmbus_clear_fault_page(client, i);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_clear_faults, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_clear_faults, "PMBUS");
static int pmbus_check_status_cml(struct i2c_client *client)
{
@@ -587,13 +656,13 @@ bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg)
{
return pmbus_check_register(client, _pmbus_read_byte_data, page, reg);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_check_byte_register, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_check_byte_register, "PMBUS");
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg)
{
return pmbus_check_register(client, __pmbus_read_word_data, page, reg);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_check_word_register, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_check_word_register, "PMBUS");
static bool __maybe_unused pmbus_check_block_register(struct i2c_client *client,
int page, int reg)
@@ -617,7 +686,7 @@ const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client)
return data->info;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, "PMBUS");
static int pmbus_get_status(struct i2c_client *client, int page, int reg)
{
@@ -717,7 +786,7 @@ static s64 pmbus_reg2data_linear(struct pmbus_data *data,
if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */
exponent = data->exponent[sensor->page];
- mantissa = (u16) sensor->data;
+ mantissa = (u16)sensor->data;
} else { /* LINEAR11 */
exponent = ((s16)sensor->data) >> 11;
mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5;
@@ -1095,9 +1164,13 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
regval = status & mask;
if (regval) {
- ret = _pmbus_write_byte_data(client, page, reg, regval);
- if (ret)
- goto unlock;
+ if (data->revision >= PMBUS_REV_12) {
+ ret = _pmbus_write_byte_data(client, page, reg, regval);
+ if (ret)
+ goto unlock;
+ } else {
+ pmbus_clear_fault_page(client, page);
+ }
}
if (s1 && s2) {
s64 v1, v2;
@@ -1394,8 +1467,7 @@ static int pmbus_add_label(struct pmbus_data *data,
snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq);
if (!index) {
if (phase == 0xff)
- strncpy(label->label, lstring,
- sizeof(label->label) - 1);
+ strscpy(label->label, lstring);
else
snprintf(label->label, sizeof(label->label), "%s.%d",
lstring, phase);
@@ -1424,8 +1496,7 @@ struct pmbus_limit_attr {
u16 reg; /* Limit register */
u16 sbit; /* Alarm attribute status bit */
bool update; /* True if register needs updates */
- bool low; /* True if low limit; for limits with compare
- functions only */
+ bool low; /* True if low limit; for limits with compare functions only */
const char *attr; /* Attribute name */
const char *alarm; /* Alarm attribute name */
};
@@ -2136,8 +2207,8 @@ static const u32 pmbus_fan_status_flags[] = {
/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */
static int pmbus_add_fan_ctrl(struct i2c_client *client,
- struct pmbus_data *data, int index, int page, int id,
- u8 config)
+ struct pmbus_data *data, int index, int page,
+ int id, u8 config)
{
struct pmbus_sensor *sensor;
@@ -2149,7 +2220,7 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client,
return -ENOMEM;
if (!((data->info->func[page] & PMBUS_HAVE_PWM12) ||
- (data->info->func[page] & PMBUS_HAVE_PWM34)))
+ (data->info->func[page] & PMBUS_HAVE_PWM34)))
return 0;
sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
@@ -2450,9 +2521,11 @@ static int pmbus_read_coefficients(struct i2c_client *client,
data.block[1] = attr->reg;
data.block[2] = 0x01;
+ pmbus_wait(client);
rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS,
I2C_SMBUS_BLOCK_PROC_CALL, &data);
+ pmbus_update_ts(client, PMBUS_OP_WRITE);
if (rv < 0)
return rv;
@@ -2590,6 +2663,56 @@ static void pmbus_remove_pec(void *dev)
device_remove_file(dev, &dev_attr_pec);
}
+static void pmbus_init_wp(struct i2c_client *client, struct pmbus_data *data)
+{
+ int ret;
+
+ switch (wp) {
+ case 0:
+ _pmbus_write_byte_data(client, -1,
+ PMBUS_WRITE_PROTECT, 0);
+ break;
+
+ case 1:
+ _pmbus_write_byte_data(client, -1,
+ PMBUS_WRITE_PROTECT, PB_WP_VOUT);
+ break;
+
+ case 2:
+ _pmbus_write_byte_data(client, -1,
+ PMBUS_WRITE_PROTECT, PB_WP_OP);
+ break;
+
+ case 3:
+ _pmbus_write_byte_data(client, -1,
+ PMBUS_WRITE_PROTECT, PB_WP_ALL);
+ break;
+
+ default:
+ /* Ignore the other values */
+ break;
+ }
+
+ ret = _pmbus_read_byte_data(client, -1, PMBUS_WRITE_PROTECT);
+ if (ret < 0)
+ return;
+
+ switch (ret & PB_WP_ANY) {
+ case PB_WP_ALL:
+ data->flags |= PMBUS_OP_PROTECTED;
+ fallthrough;
+ case PB_WP_OP:
+ data->flags |= PMBUS_VOUT_PROTECTED;
+ fallthrough;
+ case PB_WP_VOUT:
+ data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
+ break;
+
+ default:
+ break;
+ }
+}
+
static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
struct pmbus_driver_info *info)
{
@@ -2604,7 +2727,10 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
/* Enable PEC if the controller and bus supports it */
if (!(data->flags & PMBUS_NO_CAPABILITY)) {
+ pmbus_wait(client);
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
+ pmbus_update_ts(client, 0);
+
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC))
client->flags |= I2C_CLIENT_PEC;
@@ -2617,10 +2743,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
* Bail out if both registers are not supported.
*/
data->read_status = pmbus_read_status_word;
+ pmbus_wait(client);
ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD);
+ pmbus_update_ts(client, 0);
+
if (ret < 0 || ret == 0xffff) {
data->read_status = pmbus_read_status_byte;
+ pmbus_wait(client);
ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE);
+ pmbus_update_ts(client, 0);
+
if (ret < 0 || ret == 0xff) {
dev_err(dev, "PMBus status register not found\n");
return -ENODEV;
@@ -2634,11 +2766,12 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
* faults, and we should not try it. Also, in that case, writes into
* limit registers need to be disabled.
*/
- if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) {
- ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT);
- if (ret > 0 && (ret & PB_WP_ANY))
- data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
- }
+ if (!(data->flags & PMBUS_NO_WRITE_PROTECT))
+ pmbus_init_wp(client, data);
+
+ ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION);
+ if (ret >= 0)
+ data->revision = ret;
if (data->info->pages)
pmbus_clear_faults(client);
@@ -2797,7 +2930,7 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags)
}
static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags,
- unsigned int *event, bool notify)
+ unsigned int *event, bool notify)
{
int i, status;
const struct pmbus_status_category *cat;
@@ -2826,7 +2959,6 @@ static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flag
if (notify && status)
pmbus_notify(data, page, cat->reg, status);
-
}
/*
@@ -2877,7 +3009,6 @@ static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flag
*event |= REGULATOR_EVENT_OVER_TEMP_WARN;
}
-
return 0;
}
@@ -3090,12 +3221,16 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
}
static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
- unsigned int selector)
+ unsigned int selector)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
+ struct pmbus_data *data = i2c_get_clientdata(client);
int val, low, high;
+ if (data->flags & PMBUS_VOUT_PROTECTED)
+ return 0;
+
if (selector >= rdev->desc->n_voltages ||
selector < rdev->desc->linear_min_sel)
return -EINVAL;
@@ -3128,7 +3263,23 @@ const struct regulator_ops pmbus_regulator_ops = {
.set_voltage = pmbus_regulator_set_voltage,
.list_voltage = pmbus_regulator_list_voltage,
};
-EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, "PMBUS");
+
+int pmbus_regulator_init_cb(struct regulator_dev *rdev,
+ struct regulator_config *config)
+{
+ struct pmbus_data *data = config->driver_data;
+ struct regulation_constraints *constraints = rdev->constraints;
+
+ if (data->flags & PMBUS_OP_PROTECTED)
+ constraints->valid_ops_mask &= ~REGULATOR_CHANGE_STATUS;
+
+ if (data->flags & PMBUS_VOUT_PROTECTED)
+ constraints->valid_ops_mask &= ~REGULATOR_CHANGE_VOLTAGE;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_regulator_init_cb, "PMBUS");
static int pmbus_regulator_register(struct pmbus_data *data)
{
@@ -3162,17 +3313,16 @@ static int pmbus_regulator_register(struct pmbus_data *data)
return 0;
}
-static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
+static void pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
{
- int j;
+ int j;
- for (j = 0; j < data->info->num_regulators; j++) {
- if (page == rdev_get_id(data->rdevs[j])) {
- regulator_notifier_call_chain(data->rdevs[j], event, NULL);
- break;
- }
+ for (j = 0; j < data->info->num_regulators; j++) {
+ if (page == rdev_get_id(data->rdevs[j])) {
+ regulator_notifier_call_chain(data->rdevs[j], event, NULL);
+ break;
}
- return 0;
+ }
}
#else
static int pmbus_regulator_register(struct pmbus_data *data)
@@ -3180,23 +3330,32 @@ static int pmbus_regulator_register(struct pmbus_data *data)
return 0;
}
-static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
+static void pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
{
- return 0;
}
#endif
static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, u8 val)
{
- return pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
+ int ret;
+
+ ret = _pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
+
+ /*
+ * Clear fault systematically in case writing PMBUS_SMBALERT_MASK
+ * is not supported by the chip.
+ */
+ pmbus_clear_fault_page(client, page);
+
+ return ret;
}
static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
{
struct pmbus_data *data = pdata;
struct i2c_client *client = to_i2c_client(data->dev);
-
int i, status, event;
+
mutex_lock(&data->update_lock);
for (i = 0; i < data->info->pages; i++) {
_pmbus_get_flags(data, i, &status, &event, true);
@@ -3260,7 +3419,6 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data)
static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */
-#if IS_ENABLED(CONFIG_DEBUG_FS)
static int pmbus_debugfs_get(void *data, u64 *val)
{
int rc;
@@ -3303,8 +3461,8 @@ static int pmbus_debugfs_get_status(void *data, u64 *val)
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status,
NULL, "0x%04llx\n");
-static ssize_t pmbus_debugfs_mfr_read(struct file *file, char __user *buf,
- size_t count, loff_t *ppos)
+static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
{
int rc;
struct pmbus_debugfs_entry *entry = file->private_data;
@@ -3329,51 +3487,98 @@ static ssize_t pmbus_debugfs_mfr_read(struct file *file, char __user *buf,
return simple_read_from_buffer(buf, count, ppos, data, rc);
}
-static const struct file_operations pmbus_debugfs_ops_mfr = {
+static const struct file_operations pmbus_debugfs_block_ops = {
.llseek = noop_llseek,
- .read = pmbus_debugfs_mfr_read,
+ .read = pmbus_debugfs_block_read,
.write = NULL,
.open = simple_open,
};
-static void pmbus_remove_debugfs(void *data)
+static void pmbus_remove_symlink(void *symlink)
{
- struct dentry *entry = data;
-
- debugfs_remove_recursive(entry);
+ debugfs_remove(symlink);
}
-static int pmbus_init_debugfs(struct i2c_client *client,
- struct pmbus_data *data)
+struct pmbus_debugfs_data {
+ u8 reg;
+ u32 flag;
+ const char *name;
+};
+
+static const struct pmbus_debugfs_data pmbus_debugfs_block_data[] = {
+ { .reg = PMBUS_MFR_ID, .name = "mfr_id" },
+ { .reg = PMBUS_MFR_MODEL, .name = "mfr_model" },
+ { .reg = PMBUS_MFR_REVISION, .name = "mfr_revision" },
+ { .reg = PMBUS_MFR_LOCATION, .name = "mfr_location" },
+ { .reg = PMBUS_MFR_DATE, .name = "mfr_date" },
+ { .reg = PMBUS_MFR_SERIAL, .name = "mfr_serial" },
+};
+
+static const struct pmbus_debugfs_data pmbus_debugfs_status_data[] = {
+ { .reg = PMBUS_STATUS_VOUT, .flag = PMBUS_HAVE_STATUS_VOUT, .name = "status%d_vout" },
+ { .reg = PMBUS_STATUS_IOUT, .flag = PMBUS_HAVE_STATUS_IOUT, .name = "status%d_iout" },
+ { .reg = PMBUS_STATUS_INPUT, .flag = PMBUS_HAVE_STATUS_INPUT, .name = "status%d_input" },
+ { .reg = PMBUS_STATUS_TEMPERATURE, .flag = PMBUS_HAVE_STATUS_TEMP,
+ .name = "status%d_temp" },
+ { .reg = PMBUS_STATUS_FAN_12, .flag = PMBUS_HAVE_STATUS_FAN12, .name = "status%d_fan12" },
+ { .reg = PMBUS_STATUS_FAN_34, .flag = PMBUS_HAVE_STATUS_FAN34, .name = "status%d_fan34" },
+ { .reg = PMBUS_STATUS_CML, .name = "status%d_cml" },
+ { .reg = PMBUS_STATUS_OTHER, .name = "status%d_other" },
+ { .reg = PMBUS_STATUS_MFR_SPECIFIC, .name = "status%d_mfr" },
+};
+
+static void pmbus_init_debugfs(struct i2c_client *client,
+ struct pmbus_data *data)
{
- int i, idx = 0;
- char name[PMBUS_NAME_SIZE];
+ struct dentry *symlink_d, *debugfs = client->debugfs;
struct pmbus_debugfs_entry *entries;
+ const char *pathname, *symlink;
+ char name[PMBUS_NAME_SIZE];
+ int page, i, idx = 0;
- if (!pmbus_debugfs_dir)
- return -ENODEV;
+ /*
+ * client->debugfs may be NULL or an ERR_PTR(). dentry_path_raw()
+ * does not check if its parameters are valid, so validate
+ * client->debugfs before using it.
+ */
+ if (!pmbus_debugfs_dir || IS_ERR_OR_NULL(debugfs))
+ return;
/*
- * Create the debugfs directory for this device. Use the hwmon device
- * name to avoid conflicts (hwmon numbers are globally unique).
+ * Backwards compatibility: Create symlink from /pmbus/<hwmon_device>
+ * to i2c debugfs directory.
*/
- data->debugfs = debugfs_create_dir(dev_name(data->hwmon_dev),
- pmbus_debugfs_dir);
- if (IS_ERR_OR_NULL(data->debugfs)) {
- data->debugfs = NULL;
- return -ENODEV;
- }
+ pathname = dentry_path_raw(debugfs, name, sizeof(name));
+ if (IS_ERR(pathname))
+ return;
+
+ /*
+ * The path returned by dentry_path_raw() starts with '/'. Prepend it
+ * with ".." to get the symlink relative to the pmbus root directory.
+ */
+ symlink = kasprintf(GFP_KERNEL, "..%s", pathname);
+ if (!symlink)
+ return;
+
+ symlink_d = debugfs_create_symlink(dev_name(data->hwmon_dev),
+ pmbus_debugfs_dir, symlink);
+ kfree(symlink);
+
+ devm_add_action_or_reset(data->dev, pmbus_remove_symlink, symlink_d);
/*
* Allocate the max possible entries we need.
- * 6 entries device-specific
- * 10 entries page-specific
+ * device specific:
+ * ARRAY_SIZE(pmbus_debugfs_block_data) + 2
+ * page specific:
+ * ARRAY_SIZE(pmbus_debugfs_status_data) + 1
*/
entries = devm_kcalloc(data->dev,
- 6 + data->info->pages * 10, sizeof(*entries),
- GFP_KERNEL);
+ ARRAY_SIZE(pmbus_debugfs_block_data) + 2 +
+ data->info->pages * (ARRAY_SIZE(pmbus_debugfs_status_data) + 1),
+ sizeof(*entries), GFP_KERNEL);
if (!entries)
- return -ENOMEM;
+ return;
/*
* Add device-specific entries.
@@ -3383,175 +3588,67 @@ static int pmbus_init_debugfs(struct i2c_client *client,
* assume that values of the following registers are the same for all
* pages and report values only for page 0.
*/
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_ID)) {
+ if (!(data->flags & PMBUS_NO_CAPABILITY) &&
+ pmbus_check_byte_register(client, 0, PMBUS_CAPABILITY)) {
entries[idx].client = client;
entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_ID;
- debugfs_create_file("mfr_id", 0444, data->debugfs,
+ entries[idx].reg = PMBUS_CAPABILITY;
+ debugfs_create_file("capability", 0444, debugfs,
&entries[idx++],
- &pmbus_debugfs_ops_mfr);
+ &pmbus_debugfs_ops);
}
-
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_MODEL)) {
+ if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) {
entries[idx].client = client;
entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_MODEL;
- debugfs_create_file("mfr_model", 0444, data->debugfs,
+ entries[idx].reg = PMBUS_REVISION;
+ debugfs_create_file("pmbus_revision", 0444, debugfs,
&entries[idx++],
- &pmbus_debugfs_ops_mfr);
+ &pmbus_debugfs_ops);
}
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_REVISION)) {
- entries[idx].client = client;
- entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_REVISION;
- debugfs_create_file("mfr_revision", 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops_mfr);
- }
-
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_LOCATION)) {
- entries[idx].client = client;
- entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_LOCATION;
- debugfs_create_file("mfr_location", 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops_mfr);
- }
-
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_DATE)) {
- entries[idx].client = client;
- entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_DATE;
- debugfs_create_file("mfr_date", 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops_mfr);
- }
+ for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) {
+ const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i];
- if (pmbus_check_block_register(client, 0, PMBUS_MFR_SERIAL)) {
- entries[idx].client = client;
- entries[idx].page = 0;
- entries[idx].reg = PMBUS_MFR_SERIAL;
- debugfs_create_file("mfr_serial", 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops_mfr);
+ if (pmbus_check_block_register(client, 0, d->reg)) {
+ entries[idx].client = client;
+ entries[idx].page = 0;
+ entries[idx].reg = d->reg;
+ debugfs_create_file(d->name, 0444, debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_block_ops);
+ }
}
/* Add page specific entries */
- for (i = 0; i < data->info->pages; ++i) {
+ for (page = 0; page < data->info->pages; ++page) {
/* Check accessibility of status register if it's not page 0 */
- if (!i || pmbus_check_status_register(client, i)) {
+ if (!page || pmbus_check_status_register(client, page)) {
/* No need to set reg as we have special read op. */
entries[idx].client = client;
- entries[idx].page = i;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d", i);
- debugfs_create_file(name, 0444, data->debugfs,
+ entries[idx].page = page;
+ scnprintf(name, PMBUS_NAME_SIZE, "status%d", page);
+ debugfs_create_file(name, 0444, debugfs,
&entries[idx++],
&pmbus_debugfs_ops_status);
}
- if (data->info->func[i] & PMBUS_HAVE_STATUS_VOUT) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_VOUT;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_vout", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (data->info->func[i] & PMBUS_HAVE_STATUS_IOUT) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_IOUT;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_iout", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (data->info->func[i] & PMBUS_HAVE_STATUS_INPUT) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_INPUT;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_input", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (data->info->func[i] & PMBUS_HAVE_STATUS_TEMP) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_TEMPERATURE;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_temp", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (pmbus_check_byte_register(client, i, PMBUS_STATUS_CML)) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_CML;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_cml", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (pmbus_check_byte_register(client, i, PMBUS_STATUS_OTHER)) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_OTHER;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_other", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (pmbus_check_byte_register(client, i,
- PMBUS_STATUS_MFR_SPECIFIC)) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_MFR_SPECIFIC;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_mfr", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN12) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_FAN_12;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan12", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
- }
-
- if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN34) {
- entries[idx].client = client;
- entries[idx].page = i;
- entries[idx].reg = PMBUS_STATUS_FAN_34;
- scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan34", i);
- debugfs_create_file(name, 0444, data->debugfs,
- &entries[idx++],
- &pmbus_debugfs_ops);
+ for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_status_data); i++) {
+ const struct pmbus_debugfs_data *d =
+ &pmbus_debugfs_status_data[i];
+
+ if ((data->info->func[page] & d->flag) ||
+ (!d->flag && pmbus_check_byte_register(client, page, d->reg))) {
+ entries[idx].client = client;
+ entries[idx].page = page;
+ entries[idx].reg = d->reg;
+ scnprintf(name, PMBUS_NAME_SIZE, d->name, page);
+ debugfs_create_file(name, 0444, debugfs,
+ &entries[idx++],
+ &pmbus_debugfs_ops);
+ }
}
}
-
- return devm_add_action_or_reset(data->dev,
- pmbus_remove_debugfs, data->debugfs);
-}
-#else
-static int pmbus_init_debugfs(struct i2c_client *client,
- struct pmbus_data *data)
-{
- return 0;
}
-#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info)
{
@@ -3623,8 +3720,8 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info)
data->groups[0] = &data->group;
memcpy(data->groups + 1, info->groups, sizeof(void *) * groups_num);
- data->hwmon_dev = devm_hwmon_device_register_with_groups(dev,
- name, data, data->groups);
+ data->hwmon_dev = devm_hwmon_device_register_with_groups(dev, name,
+ data, data->groups);
if (IS_ERR(data->hwmon_dev)) {
dev_err(dev, "Failed to register hwmon device\n");
return PTR_ERR(data->hwmon_dev);
@@ -3638,21 +3735,25 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info)
if (ret)
return ret;
- ret = pmbus_init_debugfs(client, data);
- if (ret)
- dev_warn(dev, "Failed to register debugfs\n");
+ pmbus_init_debugfs(client, data);
return 0;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_do_probe, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_do_probe, "PMBUS");
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
{
- struct pmbus_data *data = i2c_get_clientdata(client);
-
- return data->debugfs;
+ /*
+ * client->debugfs may be an ERR_PTR(). Returning that to
+ * the calling code would potentially require additional
+ * complexity in the calling code and otherwise add no
+ * value. Return NULL in that case.
+ */
+ if (IS_ERR_OR_NULL(client->debugfs))
+ return NULL;
+ return client->debugfs;
}
-EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, "PMBUS");
int pmbus_lock_interruptible(struct i2c_client *client)
{
@@ -3660,7 +3761,7 @@ int pmbus_lock_interruptible(struct i2c_client *client)
return mutex_lock_interruptible(&data->update_lock);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_lock_interruptible, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_lock_interruptible, "PMBUS");
void pmbus_unlock(struct i2c_client *client)
{
@@ -3668,7 +3769,7 @@ void pmbus_unlock(struct i2c_client *client)
mutex_unlock(&data->update_lock);
}
-EXPORT_SYMBOL_NS_GPL(pmbus_unlock, PMBUS);
+EXPORT_SYMBOL_NS_GPL(pmbus_unlock, "PMBUS");
static int __init pmbus_core_init(void)
{
diff --git a/drivers/hwmon/pmbus/pxe1610.c b/drivers/hwmon/pmbus/pxe1610.c
index e2790a682dc8..6a4a978eca7e 100644
--- a/drivers/hwmon/pmbus/pxe1610.c
+++ b/drivers/hwmon/pmbus/pxe1610.c
@@ -127,9 +127,9 @@ static int pxe1610_probe(struct i2c_client *client)
}
static const struct i2c_device_id pxe1610_id[] = {
- {"pxe1610", 0},
- {"pxe1110", 0},
- {"pxm1310", 0},
+ {"pxe1610"},
+ {"pxe1110"},
+ {"pxm1310"},
{}
};
@@ -148,4 +148,4 @@ module_i2c_driver(pxe1610_driver);
MODULE_AUTHOR("Vijay Khemka <vijaykhemka@fb.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon PXE1610, PXE1110 and PXM1310");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c
index a235c1cdf4fe..4d7086d83aa3 100644
--- a/drivers/hwmon/pmbus/q54sj108a2.c
+++ b/drivers/hwmon/pmbus/q54sj108a2.c
@@ -421,4 +421,4 @@ module_i2c_driver(q54sj108a2_driver);
MODULE_AUTHOR("Xiao.Ma <xiao.mx.ma@deltaww.com>");
MODULE_DESCRIPTION("PMBus driver for Delta Q54SJ108A2 series modules");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/stpddc60.c b/drivers/hwmon/pmbus/stpddc60.c
index be9076dcc2db..5cb905ed8ae5 100644
--- a/drivers/hwmon/pmbus/stpddc60.c
+++ b/drivers/hwmon/pmbus/stpddc60.c
@@ -18,8 +18,8 @@
#define STPDDC60_MFR_UV_LIMIT_OFFSET 0xe6
static const struct i2c_device_id stpddc60_id[] = {
- {"stpddc60", 0},
- {"bmr481", 0},
+ {"stpddc60"},
+ {"bmr481"},
{}
};
MODULE_DEVICE_TABLE(i2c, stpddc60_id);
@@ -246,4 +246,4 @@ module_i2c_driver(stpddc60_driver);
MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/tda38640.c b/drivers/hwmon/pmbus/tda38640.c
index 450b0273fb59..d902d39f49f4 100644
--- a/drivers/hwmon/pmbus/tda38640.c
+++ b/drivers/hwmon/pmbus/tda38640.c
@@ -15,9 +15,130 @@
#include "pmbus.h"
static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE_NODE("vout"),
};
+struct tda38640_data {
+ struct pmbus_driver_info info;
+ u32 en_pin_lvl;
+};
+
+#define to_tda38640_data(x) container_of(x, struct tda38640_data, info)
+
+/*
+ * Map PB_ON_OFF_CONFIG_POLARITY_HIGH to PB_OPERATION_CONTROL_ON.
+ */
+static int tda38640_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct tda38640_data *data = to_tda38640_data(info);
+ int ret, on_off_config, enabled;
+
+ if (reg != PMBUS_OPERATION)
+ return -ENODATA;
+
+ ret = pmbus_read_byte_data(client, page, reg);
+ if (ret < 0)
+ return ret;
+
+ on_off_config = pmbus_read_byte_data(client, page,
+ PMBUS_ON_OFF_CONFIG);
+ if (on_off_config < 0)
+ return on_off_config;
+
+ enabled = !!(on_off_config & PB_ON_OFF_CONFIG_POLARITY_HIGH);
+
+ enabled ^= data->en_pin_lvl;
+ if (enabled)
+ ret &= ~PB_OPERATION_CONTROL_ON;
+ else
+ ret |= PB_OPERATION_CONTROL_ON;
+
+ return ret;
+}
+
+/*
+ * Map PB_OPERATION_CONTROL_ON to PB_ON_OFF_CONFIG_POLARITY_HIGH.
+ */
+static int tda38640_write_byte_data(struct i2c_client *client, int page,
+ int reg, u8 byte)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct tda38640_data *data = to_tda38640_data(info);
+ int enable, ret;
+
+ if (reg != PMBUS_OPERATION)
+ return -ENODATA;
+
+ enable = !!(byte & PB_OPERATION_CONTROL_ON);
+
+ byte &= ~PB_OPERATION_CONTROL_ON;
+ ret = pmbus_write_byte_data(client, page, reg, byte);
+ if (ret < 0)
+ return ret;
+
+ enable ^= data->en_pin_lvl;
+
+ return pmbus_update_byte_data(client, page, PMBUS_ON_OFF_CONFIG,
+ PB_ON_OFF_CONFIG_POLARITY_HIGH,
+ enable ? 0 : PB_ON_OFF_CONFIG_POLARITY_HIGH);
+}
+
+static int svid_mode(struct i2c_client *client, struct tda38640_data *data)
+{
+ /* PMBUS_MFR_READ(0xD0) + MTP Address offset */
+ u8 write_buf[] = {0xd0, 0x44, 0x00};
+ u8 read_buf[2];
+ int ret, svid;
+ bool off, reg_en_pin_pol;
+
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = write_buf,
+ .len = sizeof(write_buf),
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = read_buf,
+ .len = sizeof(read_buf),
+ }
+ };
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c_transfer failed. %d", ret);
+ return ret;
+ }
+
+ /*
+ * 0x44[15] determines PMBus Operating Mode
+ * If bit is set then it is SVID mode.
+ */
+ svid = !!(read_buf[1] & BIT(7));
+
+ /*
+ * Determine EN pin level for use in SVID mode.
+ * This is done with help of STATUS_BYTE bit 6(OFF) & ON_OFF_CONFIG bit 2(EN pin polarity).
+ */
+ if (svid) {
+ ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE);
+ if (ret < 0)
+ return ret;
+ off = !!(ret & PB_STATUS_OFF);
+
+ ret = i2c_smbus_read_byte_data(client, PMBUS_ON_OFF_CONFIG);
+ if (ret < 0)
+ return ret;
+ reg_en_pin_pol = !!(ret & PB_ON_OFF_CONFIG_POLARITY_HIGH);
+ data->en_pin_lvl = off ^ reg_en_pin_pol;
+ }
+
+ return svid;
+}
+
static struct pmbus_driver_info tda38640_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = linear,
@@ -26,7 +147,6 @@ static struct pmbus_driver_info tda38640_info = {
.format[PSC_CURRENT_IN] = linear,
.format[PSC_POWER] = linear,
.format[PSC_TEMPERATURE] = linear,
-
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
| PMBUS_HAVE_IIN
@@ -41,11 +161,41 @@ static struct pmbus_driver_info tda38640_info = {
static int tda38640_probe(struct i2c_client *client)
{
- return pmbus_do_probe(client, &tda38640_info);
+ struct tda38640_data *data;
+ int svid;
+
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ memcpy(&data->info, &tda38640_info, sizeof(tda38640_info));
+
+ if (IS_ENABLED(CONFIG_SENSORS_TDA38640_REGULATOR) &&
+ of_property_read_bool(client->dev.of_node, "infineon,en-pin-fixed-level")) {
+ svid = svid_mode(client, data);
+ if (svid < 0) {
+ dev_err_probe(&client->dev, svid, "Could not determine operating mode.");
+ return svid;
+ }
+
+ /*
+ * Apply ON_OFF_CONFIG workaround as enabling the regulator using the
+ * OPERATION register doesn't work in SVID mode.
+ *
+ * One should configure PMBUS_ON_OFF_CONFIG here, but
+ * PB_ON_OFF_CONFIG_POWERUP_CONTROL and PB_ON_OFF_CONFIG_EN_PIN_REQ
+ * are ignored by the device.
+ * Only PB_ON_OFF_CONFIG_POLARITY_HIGH has an effect.
+ */
+ if (svid) {
+ data->info.read_byte_data = tda38640_read_byte_data;
+ data->info.write_byte_data = tda38640_write_byte_data;
+ }
+ }
+ return pmbus_do_probe(client, &data->info);
}
static const struct i2c_device_id tda38640_id[] = {
- {"tda38640", 0},
+ {"tda38640"},
{}
};
MODULE_DEVICE_TABLE(i2c, tda38640_id);
@@ -71,4 +221,4 @@ module_i2c_driver(tda38640_driver);
MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon TDA38640");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c
new file mode 100644
index 000000000000..c13edd7e1abf
--- /dev/null
+++ b/drivers/hwmon/pmbus/tps25990.c
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2024 BayLibre, SAS.
+// Author: Jerome Brunet <jbrunet@baylibre.com>
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "pmbus.h"
+
+#define TPS25990_READ_VAUX 0xd0
+#define TPS25990_READ_VIN_MIN 0xd1
+#define TPS25990_READ_VIN_PEAK 0xd2
+#define TPS25990_READ_IIN_PEAK 0xd4
+#define TPS25990_READ_PIN_PEAK 0xd5
+#define TPS25990_READ_TEMP_AVG 0xd6
+#define TPS25990_READ_TEMP_PEAK 0xd7
+#define TPS25990_READ_VOUT_MIN 0xda
+#define TPS25990_READ_VIN_AVG 0xdc
+#define TPS25990_READ_VOUT_AVG 0xdd
+#define TPS25990_READ_IIN_AVG 0xde
+#define TPS25990_READ_PIN_AVG 0xdf
+#define TPS25990_VIREF 0xe0
+#define TPS25990_PK_MIN_AVG 0xea
+#define PK_MIN_AVG_RST_PEAK BIT(7)
+#define PK_MIN_AVG_RST_AVG BIT(6)
+#define PK_MIN_AVG_RST_MIN BIT(5)
+#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0)
+#define TPS25990_MFR_WRITE_PROTECT 0xf8
+#define TPS25990_UNLOCKED BIT(7)
+
+#define TPS25990_8B_SHIFT 2
+#define TPS25990_VIN_OVF_NUM 525100
+#define TPS25990_VIN_OVF_DIV 10163
+#define TPS25990_VIN_OVF_OFF 155
+#define TPS25990_IIN_OCF_NUM 953800
+#define TPS25990_IIN_OCF_DIV 129278
+#define TPS25990_IIN_OCF_OFF 157
+
+#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \
+ PK_MIN_AVG_RST_AVG | \
+ PK_MIN_AVG_RST_MIN)
+
+/*
+ * Arbitrary default Rimon value: 1kOhm
+ * This correspond to an overcurrent limit of 55A, close to the specified limit
+ * of un-stacked TPS25990 and makes further calculation easier to setup in
+ * sensor.conf, if necessary
+ */
+#define TPS25990_DEFAULT_RIMON 1000000000
+
+static void tps25990_set_m(int *m, u32 rimon)
+{
+ u64 val = ((u64)*m) * rimon;
+
+ /* Make sure m fits the s32 type */
+ *m = DIV_ROUND_CLOSEST_ULL(val, 1000000);
+}
+
+static int tps25990_mfr_write_protect_set(struct i2c_client *client,
+ u8 protect)
+{
+ u8 val;
+
+ switch (protect) {
+ case 0:
+ val = 0xa2;
+ break;
+ case PB_WP_ALL:
+ val = 0x0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return pmbus_write_byte_data(client, -1, TPS25990_MFR_WRITE_PROTECT,
+ val);
+}
+
+static int tps25990_mfr_write_protect_get(struct i2c_client *client)
+{
+ int ret = pmbus_read_byte_data(client, -1, TPS25990_MFR_WRITE_PROTECT);
+
+ if (ret < 0)
+ return ret;
+
+ return (ret & TPS25990_UNLOCKED) ? 0 : PB_WP_ALL;
+}
+
+static int tps25990_read_word_data(struct i2c_client *client,
+ int page, int phase, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIRT_READ_VIN_MAX:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VIN_PEAK);
+ break;
+
+ case PMBUS_VIRT_READ_VIN_MIN:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VIN_MIN);
+ break;
+
+ case PMBUS_VIRT_READ_VIN_AVG:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VIN_AVG);
+ break;
+
+ case PMBUS_VIRT_READ_VOUT_MIN:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VOUT_MIN);
+ break;
+
+ case PMBUS_VIRT_READ_VOUT_AVG:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VOUT_AVG);
+ break;
+
+ case PMBUS_VIRT_READ_IIN_AVG:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_IIN_AVG);
+ break;
+
+ case PMBUS_VIRT_READ_IIN_MAX:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_IIN_PEAK);
+ break;
+
+ case PMBUS_VIRT_READ_TEMP_AVG:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_TEMP_AVG);
+ break;
+
+ case PMBUS_VIRT_READ_TEMP_MAX:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_TEMP_PEAK);
+ break;
+
+ case PMBUS_VIRT_READ_PIN_AVG:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_PIN_AVG);
+ break;
+
+ case PMBUS_VIRT_READ_PIN_MAX:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_PIN_PEAK);
+ break;
+
+ case PMBUS_VIRT_READ_VMON:
+ ret = pmbus_read_word_data(client, page, phase,
+ TPS25990_READ_VAUX);
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_PIN_OP_WARN_LIMIT:
+ /*
+ * These registers provide an 8 bits value instead of a
+ * 10bits one. Just shifting twice the register value is
+ * enough to make the sensor type conversion work, even
+ * if the datasheet provides different m, b and R for
+ * those.
+ */
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ break;
+ ret <<= TPS25990_8B_SHIFT;
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ break;
+ ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM,
+ TPS25990_VIN_OVF_DIV);
+ ret += TPS25990_VIN_OVF_OFF;
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT:
+ /*
+ * VIREF directly sets the over-current limit at which the eFuse
+ * will turn the FET off and trigger a fault. Expose it through
+ * this generic property instead of a manufacturer specific one.
+ */
+ ret = pmbus_read_byte_data(client, page, TPS25990_VIREF);
+ if (ret < 0)
+ break;
+ ret = DIV_ROUND_CLOSEST(ret * TPS25990_IIN_OCF_NUM,
+ TPS25990_IIN_OCF_DIV);
+ ret += TPS25990_IIN_OCF_OFF;
+ break;
+
+ case PMBUS_VIRT_SAMPLES:
+ ret = pmbus_read_byte_data(client, page, TPS25990_PK_MIN_AVG);
+ if (ret < 0)
+ break;
+ ret = 1 << FIELD_GET(PK_MIN_AVG_AVG_CNT, ret);
+ break;
+
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ case PMBUS_VIRT_RESET_IIN_HISTORY:
+ case PMBUS_VIRT_RESET_PIN_HISTORY:
+ case PMBUS_VIRT_RESET_VOUT_HISTORY:
+ ret = 0;
+ break;
+
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int tps25990_write_word_data(struct i2c_client *client,
+ int page, int reg, u16 value)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ case PMBUS_IIN_OC_WARN_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_PIN_OP_WARN_LIMIT:
+ value >>= TPS25990_8B_SHIFT;
+ value = clamp_val(value, 0, 0xff);
+ ret = pmbus_write_word_data(client, page, reg, value);
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ value -= TPS25990_VIN_OVF_OFF;
+ value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV,
+ TPS25990_VIN_OVF_NUM);
+ value = clamp_val(value, 0, 0xf);
+ ret = pmbus_write_word_data(client, page, reg, value);
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT:
+ value -= TPS25990_IIN_OCF_OFF;
+ value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV,
+ TPS25990_IIN_OCF_NUM);
+ value = clamp_val(value, 0, 0x3f);
+ ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value);
+ break;
+
+ case PMBUS_VIRT_SAMPLES:
+ value = clamp_val(value, 1, 1 << PK_MIN_AVG_AVG_CNT);
+ value = ilog2(value);
+ ret = pmbus_update_byte_data(client, page, TPS25990_PK_MIN_AVG,
+ PK_MIN_AVG_AVG_CNT,
+ FIELD_PREP(PK_MIN_AVG_AVG_CNT, value));
+ break;
+
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ case PMBUS_VIRT_RESET_IIN_HISTORY:
+ case PMBUS_VIRT_RESET_PIN_HISTORY:
+ case PMBUS_VIRT_RESET_VOUT_HISTORY:
+ /*
+ * TPS25990 has history resets based on MIN/AVG/PEAK instead of per
+ * sensor type. Exposing this quirk in hwmon is not desirable so
+ * reset MIN, AVG and PEAK together. Even is there effectively only
+ * one reset, which resets everything, expose the 5 entries so
+ * userspace is not required map a sensor type to another to trigger
+ * a reset
+ */
+ ret = pmbus_update_byte_data(client, 0, TPS25990_PK_MIN_AVG,
+ PK_MIN_AVG_RST_MASK,
+ PK_MIN_AVG_RST_MASK);
+ break;
+
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int tps25990_read_byte_data(struct i2c_client *client,
+ int page, int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_WRITE_PROTECT:
+ ret = tps25990_mfr_write_protect_get(client);
+ break;
+
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+static int tps25990_write_byte_data(struct i2c_client *client,
+ int page, int reg, u8 byte)
+{
+ int ret;
+
+ switch (reg) {
+ case PMBUS_WRITE_PROTECT:
+ ret = tps25990_mfr_write_protect_set(client, byte);
+ break;
+
+ default:
+ ret = -ENODATA;
+ break;
+ }
+
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR)
+static const struct regulator_desc tps25990_reg_desc[] = {
+ PMBUS_REGULATOR_ONE_NODE("vout"),
+};
+#endif
+
+static const struct pmbus_driver_info tps25990_base_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .m[PSC_VOLTAGE_IN] = 5251,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = -2,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .m[PSC_VOLTAGE_OUT] = 5251,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = -2,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_TEMPERATURE] = 140,
+ .b[PSC_TEMPERATURE] = 32100,
+ .R[PSC_TEMPERATURE] = -2,
+ /*
+ * Current and Power measurement depends on the ohm value
+ * of Rimon. m is multiplied by 1000 below to have an integer
+ * and -3 is added to R to compensate.
+ */
+ .format[PSC_CURRENT_IN] = direct,
+ .m[PSC_CURRENT_IN] = 9538,
+ .b[PSC_CURRENT_IN] = 0,
+ .R[PSC_CURRENT_IN] = -6,
+ .format[PSC_POWER] = direct,
+ .m[PSC_POWER] = 4901,
+ .b[PSC_POWER] = 0,
+ .R[PSC_POWER] = -7,
+ .func[0] = (PMBUS_HAVE_VIN |
+ PMBUS_HAVE_VOUT |
+ PMBUS_HAVE_VMON |
+ PMBUS_HAVE_IIN |
+ PMBUS_HAVE_PIN |
+ PMBUS_HAVE_TEMP |
+ PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_HAVE_STATUS_TEMP |
+ PMBUS_HAVE_SAMPLES),
+ .read_word_data = tps25990_read_word_data,
+ .write_word_data = tps25990_write_word_data,
+ .read_byte_data = tps25990_read_byte_data,
+ .write_byte_data = tps25990_write_byte_data,
+
+#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR)
+ .reg_desc = tps25990_reg_desc,
+ .num_regulators = ARRAY_SIZE(tps25990_reg_desc),
+#endif
+};
+
+static const struct i2c_device_id tps25990_i2c_id[] = {
+ { "tps25990" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id);
+
+static const struct of_device_id tps25990_of_match[] = {
+ { .compatible = "ti,tps25990" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tps25990_of_match);
+
+static int tps25990_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct pmbus_driver_info *info;
+ u32 rimon = TPS25990_DEFAULT_RIMON;
+ int ret;
+
+ ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon);
+ if (ret < 0 && ret != -EINVAL)
+ return dev_err_probe(dev, ret, "failed to get rimon\n");
+
+ info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ /* Adapt the current and power scale for each instance */
+ tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon);
+ tps25990_set_m(&info->m[PSC_POWER], rimon);
+
+ return pmbus_do_probe(client, info);
+}
+
+static struct i2c_driver tps25990_driver = {
+ .driver = {
+ .name = "tps25990",
+ .of_match_table = tps25990_of_match,
+ },
+ .probe = tps25990_probe,
+ .id_table = tps25990_i2c_id,
+};
+module_i2c_driver(tps25990_driver);
+
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_DESCRIPTION("PMBUS driver for TPS25990 eFuse");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/tps40422.c b/drivers/hwmon/pmbus/tps40422.c
index ea0074a6b9fc..7c9fedaa068c 100644
--- a/drivers/hwmon/pmbus/tps40422.c
+++ b/drivers/hwmon/pmbus/tps40422.c
@@ -31,7 +31,7 @@ static int tps40422_probe(struct i2c_client *client)
}
static const struct i2c_device_id tps40422_id[] = {
- {"tps40422", 0},
+ {"tps40422"},
{}
};
@@ -51,4 +51,4 @@ module_i2c_driver(tps40422_driver);
MODULE_AUTHOR("Zhu Laiwen <richard.zhu@nsn.com>");
MODULE_DESCRIPTION("PMBus driver for TI TPS40422");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c
index 5c9466244d70..ca2bfa25eb04 100644
--- a/drivers/hwmon/pmbus/tps53679.c
+++ b/drivers/hwmon/pmbus/tps53679.c
@@ -16,7 +16,7 @@
#include "pmbus.h"
enum chips {
- tps53647, tps53667, tps53676, tps53679, tps53681, tps53688
+ tps53647, tps53667, tps53676, tps53679, tps53681, tps53685, tps53688
};
#define TPS53647_PAGE_NUM 1
@@ -31,7 +31,8 @@ enum chips {
#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */
#define TPS53679_PAGE_NUM 2
-#define TPS53681_DEVICE_ID 0x81
+#define TPS53681_DEVICE_ID "\x81"
+#define TPS53685_DEVICE_ID "TIShP"
#define TPS53681_PMBUS_REVISION 0x33
@@ -86,10 +87,12 @@ static int tps53679_identify_phases(struct i2c_client *client,
}
static int tps53679_identify_chip(struct i2c_client *client,
- u8 revision, u16 id)
+ u8 revision, char *id)
{
u8 buf[I2C_SMBUS_BLOCK_MAX];
int ret;
+ int buf_len;
+ int id_len;
ret = pmbus_read_byte_data(client, 0, PMBUS_REVISION);
if (ret < 0)
@@ -102,8 +105,14 @@ static int tps53679_identify_chip(struct i2c_client *client,
ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
if (ret < 0)
return ret;
- if (ret != 1 || buf[0] != id) {
- dev_err(&client->dev, "Unexpected device ID 0x%x\n", buf[0]);
+
+ /* Adjust length if null terminator if present */
+ buf_len = (buf[ret - 1] != '\x00' ? ret : ret - 1);
+
+ id_len = strlen(id);
+
+ if (buf_len != id_len || strncmp(id, buf, id_len)) {
+ dev_err(&client->dev, "Unexpected device ID: %*ph\n", ret, buf);
return -ENODEV;
}
return 0;
@@ -117,7 +126,7 @@ static int tps53679_identify_chip(struct i2c_client *client,
*/
static int tps53679_identify_multiphase(struct i2c_client *client,
struct pmbus_driver_info *info,
- int pmbus_rev, int device_id)
+ int pmbus_rev, char *device_id)
{
int ret;
@@ -138,6 +147,16 @@ static int tps53679_identify(struct i2c_client *client,
return tps53679_identify_mode(client, info);
}
+static int tps53685_identify(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ info->func[1] |= PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
+ PMBUS_HAVE_STATUS_INPUT;
+ info->format[PSC_VOLTAGE_OUT] = linear;
+ return tps53679_identify_chip(client, TPS53681_PMBUS_REVISION,
+ TPS53685_DEVICE_ID);
+}
+
static int tps53681_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
@@ -263,6 +282,10 @@ static int tps53679_probe(struct i2c_client *client)
info->identify = tps53681_identify;
info->read_word_data = tps53681_read_word_data;
break;
+ case tps53685:
+ info->pages = TPS53679_PAGE_NUM;
+ info->identify = tps53685_identify;
+ break;
default:
return -ENODEV;
}
@@ -277,6 +300,7 @@ static const struct i2c_device_id tps53679_id[] = {
{"tps53676", tps53676},
{"tps53679", tps53679},
{"tps53681", tps53681},
+ {"tps53685", tps53685},
{"tps53688", tps53688},
{}
};
@@ -289,6 +313,7 @@ static const struct of_device_id __maybe_unused tps53679_of_match[] = {
{.compatible = "ti,tps53676", .data = (void *)tps53676},
{.compatible = "ti,tps53679", .data = (void *)tps53679},
{.compatible = "ti,tps53681", .data = (void *)tps53681},
+ {.compatible = "ti,tps53685", .data = (void *)tps53685},
{.compatible = "ti,tps53688", .data = (void *)tps53688},
{}
};
@@ -308,4 +333,4 @@ module_i2c_driver(tps53679_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/tps546d24.c b/drivers/hwmon/pmbus/tps546d24.c
index 69bbdb6c680b..44d7a6df1dbd 100644
--- a/drivers/hwmon/pmbus/tps546d24.c
+++ b/drivers/hwmon/pmbus/tps546d24.c
@@ -42,7 +42,7 @@ static int tps546d24_probe(struct i2c_client *client)
}
static const struct i2c_device_id tps546d24_id[] = {
- {"tps546d24", 0},
+ {"tps546d24"},
{}
};
MODULE_DEVICE_TABLE(i2c, tps546d24_id);
@@ -68,4 +68,4 @@ module_i2c_driver(tps546d24_driver);
MODULE_AUTHOR("Duke Du <dukedu83@gmail.com>");
MODULE_DESCRIPTION("PMBus driver for TI tps546d24");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c
index 8d9d422450e5..55e7af3a5f98 100644
--- a/drivers/hwmon/pmbus/ucd9000.c
+++ b/drivers/hwmon/pmbus/ucd9000.c
@@ -67,7 +67,6 @@ struct ucd9000_data {
struct gpio_chip gpio;
#endif
struct dentry *debugfs;
- ktime_t write_time;
};
#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info)
@@ -80,68 +79,11 @@ struct ucd9000_debugfs_entry {
* It has been observed that the UCD90320 randomly fails register access when
* doing another access right on the back of a register write. To mitigate this
* make sure that there is a minimum delay between a write access and the
- * following access. The 250us is based on experimental data. At a delay of
- * 200us the issue seems to go away. Add a bit of extra margin to allow for
+ * following access. The 500 is based on experimental data. At a delay of
+ * 350us the issue seems to go away. Add a bit of extra margin to allow for
* system to system differences.
*/
-#define UCD90320_WAIT_DELAY_US 250
-
-static inline void ucd90320_wait(const struct ucd9000_data *data)
-{
- s64 delta = ktime_us_delta(ktime_get(), data->write_time);
-
- if (delta < UCD90320_WAIT_DELAY_US)
- udelay(UCD90320_WAIT_DELAY_US - delta);
-}
-
-static int ucd90320_read_word_data(struct i2c_client *client, int page,
- int phase, int reg)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct ucd9000_data *data = to_ucd9000_data(info);
-
- if (reg >= PMBUS_VIRT_BASE)
- return -ENXIO;
-
- ucd90320_wait(data);
- return pmbus_read_word_data(client, page, phase, reg);
-}
-
-static int ucd90320_read_byte_data(struct i2c_client *client, int page, int reg)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct ucd9000_data *data = to_ucd9000_data(info);
-
- ucd90320_wait(data);
- return pmbus_read_byte_data(client, page, reg);
-}
-
-static int ucd90320_write_word_data(struct i2c_client *client, int page,
- int reg, u16 word)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct ucd9000_data *data = to_ucd9000_data(info);
- int ret;
-
- ucd90320_wait(data);
- ret = pmbus_write_word_data(client, page, reg, word);
- data->write_time = ktime_get();
-
- return ret;
-}
-
-static int ucd90320_write_byte(struct i2c_client *client, int page, u8 value)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct ucd9000_data *data = to_ucd9000_data(info);
- int ret;
-
- ucd90320_wait(data);
- ret = pmbus_write_byte(client, page, value);
- data->write_time = ktime_get();
-
- return ret;
-}
+#define UCD90320_WAIT_DELAY_US 500
static int ucd9000_get_fan_config(struct i2c_client *client, int fan)
{
@@ -270,8 +212,8 @@ static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset)
return !!(ret & UCD9000_GPIO_CONFIG_STATUS);
}
-static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
- int value)
+static int ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
{
struct i2c_client *client = gpiochip_get_data(gc);
int ret;
@@ -280,19 +222,19 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
if (ret < 0) {
dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n",
offset, ret);
- return;
+ return ret;
}
if (value) {
- if (ret & UCD9000_GPIO_CONFIG_STATUS)
- return;
+ if (ret & UCD9000_GPIO_CONFIG_OUT_VALUE)
+ return 0;
- ret |= UCD9000_GPIO_CONFIG_STATUS;
+ ret |= UCD9000_GPIO_CONFIG_OUT_VALUE;
} else {
- if (!(ret & UCD9000_GPIO_CONFIG_STATUS))
- return;
+ if (!(ret & UCD9000_GPIO_CONFIG_OUT_VALUE))
+ return 0;
- ret &= ~UCD9000_GPIO_CONFIG_STATUS;
+ ret &= ~UCD9000_GPIO_CONFIG_OUT_VALUE;
}
ret |= UCD9000_GPIO_CONFIG_ENABLE;
@@ -302,7 +244,7 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
if (ret < 0) {
dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n",
offset, ret);
- return;
+ return ret;
}
ret &= ~UCD9000_GPIO_CONFIG_ENABLE;
@@ -311,6 +253,8 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
if (ret < 0)
dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n",
offset, ret);
+
+ return ret;
}
static int ucd9000_gpio_get_direction(struct gpio_chip *gc,
@@ -667,10 +611,8 @@ static int ucd9000_probe(struct i2c_client *client)
info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12
| PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34;
} else if (mid->driver_data == ucd90320) {
- info->read_byte_data = ucd90320_read_byte_data;
- info->read_word_data = ucd90320_read_word_data;
- info->write_byte = ucd90320_write_byte;
- info->write_word_data = ucd90320_write_word_data;
+ /* Delay SMBus operations after a write */
+ info->write_delay = UCD90320_WAIT_DELAY_US;
}
ucd9000_probe_gpio(client, mid, data);
@@ -702,4 +644,4 @@ module_i2c_driver(ucd9000_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c
index 7920d1c06df0..f68adaf4a110 100644
--- a/drivers/hwmon/pmbus/ucd9200.c
+++ b/drivers/hwmon/pmbus/ucd9200.c
@@ -209,4 +209,4 @@ module_i2c_driver(ucd9200_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/xdp710.c b/drivers/hwmon/pmbus/xdp710.c
new file mode 100644
index 000000000000..660bbfe16e1e
--- /dev/null
+++ b/drivers/hwmon/pmbus/xdp710.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Infineon XDP710 Hot-Swap Controller
+ */
+
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define XDP710_REG_CFG 0xD3
+#define XDP710_V_SNS_CFG 0xD4
+#define XDP710_CS_RNG 0xD5
+
+/*
+ * The table to map configuration register values
+ * to sense resistor values
+ */
+static const int micro_ohm_rsense[] = {
+ 200, 250, 300, 330, 400, 470, 500, 600,
+ 670, 700, 750, 800, 900, 1000, 1100, 1200,
+ 1250, 1300, 1400, 1500, 1600, 1700, 1800, 1900,
+ 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700,
+ 2800, 3000, 3100, 3200, 3300, 3400, 3500, 3600,
+ 3700, 3800, 3900, 4000, 4100, 4200, 4300, 4400,
+ 4500, 4600, 4700, 4800, 4900, 5000, 5500, 6000,
+ 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000
+};
+
+static struct pmbus_driver_info xdp710_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 4653,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = -2,
+ .m[PSC_VOLTAGE_OUT] = 4653,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = -2,
+ .m[PSC_CURRENT_OUT] = 23165,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = -2,
+ .m[PSC_POWER] = 4211,
+ .b[PSC_POWER] = 0,
+ .R[PSC_POWER] = -2,
+ .m[PSC_TEMPERATURE] = 52,
+ .b[PSC_TEMPERATURE] = 14321,
+ .R[PSC_TEMPERATURE] = -1,
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static int xdp710_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ u8 cs_rng;
+ u8 vtlm_rng;
+ int rsense;
+ int ret;
+ int m = 0;
+
+ info = devm_kmemdup(&client->dev, &xdp710_info, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ ret = i2c_smbus_read_word_data(client, XDP710_CS_RNG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get CS_RNG");
+ return ret;
+ }
+ cs_rng = (ret >> 6) & GENMASK(1, 0);
+
+ ret = i2c_smbus_read_word_data(client, XDP710_V_SNS_CFG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get V_SNS_CFG");
+ return ret;
+ }
+ vtlm_rng = ret & GENMASK(1, 0);
+
+ ret = i2c_smbus_read_word_data(client, XDP710_REG_CFG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get REG_CFG");
+ return ret;
+ }
+ ret &= GENMASK(5, 0);
+ rsense = micro_ohm_rsense[ret];
+
+ info->m[PSC_VOLTAGE_IN] <<= vtlm_rng;
+ info->m[PSC_VOLTAGE_OUT] <<= vtlm_rng;
+
+ m = info->m[PSC_CURRENT_OUT];
+ info->m[PSC_CURRENT_OUT] = DIV_ROUND_CLOSEST(m * rsense >> cs_rng, 1000);
+
+ m = info->m[PSC_POWER];
+ info->m[PSC_POWER] = DIV_ROUND_CLOSEST(m * rsense >> cs_rng, 1000);
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id xdp710_of_match[] = {
+ { .compatible = "infineon,xdp710" },
+ {}
+};
+
+static const struct i2c_device_id xdp710_id[] = {
+ {"xdp710"},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, xdp710_id);
+
+static struct i2c_driver xdp710_driver = {
+ .driver = {
+ .name = "xdp710",
+ .of_match_table = xdp710_of_match,
+ },
+ .probe = xdp710_probe,
+ .id_table = xdp710_id,
+};
+module_i2c_driver(xdp710_driver);
+
+MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for XDP710 HSC");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c
index 37d08dd427d5..f3aa6339d60d 100644
--- a/drivers/hwmon/pmbus/xdpe12284.c
+++ b/drivers/hwmon/pmbus/xdpe12284.c
@@ -164,9 +164,9 @@ static int xdpe122_probe(struct i2c_client *client)
}
static const struct i2c_device_id xdpe122_id[] = {
- {"xdpe11280", 0},
- {"xdpe12254", 0},
- {"xdpe12284", 0},
+ {"xdpe11280"},
+ {"xdpe12254"},
+ {"xdpe12284"},
{}
};
@@ -194,4 +194,4 @@ module_i2c_driver(xdpe122_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE122 family");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/xdpe152c4.c b/drivers/hwmon/pmbus/xdpe152c4.c
index 235e6b41ae4c..67a3d5fe1daf 100644
--- a/drivers/hwmon/pmbus/xdpe152c4.c
+++ b/drivers/hwmon/pmbus/xdpe152c4.c
@@ -44,8 +44,8 @@ static int xdpe152_probe(struct i2c_client *client)
}
static const struct i2c_device_id xdpe152_id[] = {
- {"xdpe152c4", 0},
- {"xdpe15284", 0},
+ {"xdpe152c4"},
+ {"xdpe15284"},
{}
};
@@ -72,4 +72,4 @@ module_i2c_driver(xdpe152_driver);
MODULE_AUTHOR("Greg Schwendimann <greg.schwendimann@infineon.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE152 family");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c
index 83458df0d0cf..97be69630cfb 100644
--- a/drivers/hwmon/pmbus/zl6100.c
+++ b/drivers/hwmon/pmbus/zl6100.c
@@ -22,8 +22,6 @@ enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105,
struct zl6100_data {
int id;
- ktime_t access; /* chip access time */
- int delay; /* Delay between chip accesses in uS */
struct pmbus_driver_info info;
};
@@ -122,16 +120,6 @@ static u16 zl6100_d2l(long val)
return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
}
-/* Some chips need a delay between accesses */
-static inline void zl6100_wait(const struct zl6100_data *data)
-{
- if (data->delay) {
- s64 delta = ktime_us_delta(ktime_get(), data->access);
- if (delta < data->delay)
- udelay(data->delay - delta);
- }
-}
-
static int zl6100_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
@@ -174,9 +162,7 @@ static int zl6100_read_word_data(struct i2c_client *client, int page,
break;
}
- zl6100_wait(data);
ret = pmbus_read_word_data(client, page, phase, vreg);
- data->access = ktime_get();
if (ret < 0)
return ret;
@@ -195,14 +181,11 @@ static int zl6100_read_word_data(struct i2c_client *client, int page,
static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct zl6100_data *data = to_zl6100_data(info);
int ret, status;
if (page >= info->pages)
return -ENXIO;
- zl6100_wait(data);
-
switch (reg) {
case PMBUS_VIRT_STATUS_VMON:
ret = pmbus_read_byte_data(client, 0,
@@ -225,7 +208,6 @@ static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
ret = pmbus_read_byte_data(client, page, reg);
break;
}
- data->access = ktime_get();
return ret;
}
@@ -234,8 +216,7 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
u16 word)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct zl6100_data *data = to_zl6100_data(info);
- int ret, vreg;
+ int vreg;
if (page >= info->pages)
return -ENXIO;
@@ -265,27 +246,7 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
vreg = reg;
}
- zl6100_wait(data);
- ret = pmbus_write_word_data(client, page, vreg, word);
- data->access = ktime_get();
-
- return ret;
-}
-
-static int zl6100_write_byte(struct i2c_client *client, int page, u8 value)
-{
- const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
- struct zl6100_data *data = to_zl6100_data(info);
- int ret;
-
- if (page >= info->pages)
- return -ENXIO;
-
- zl6100_wait(data);
- ret = pmbus_write_byte(client, page, value);
- data->access = ktime_get();
-
- return ret;
+ return pmbus_write_word_data(client, page, vreg, word);
}
static const struct i2c_device_id zl6100_id[] = {
@@ -363,14 +324,7 @@ static int zl6100_probe(struct i2c_client *client)
* supported chips are known to require a wait time between I2C
* accesses.
*/
- data->delay = delay;
-
- /*
- * Since there was a direct I2C device access above, wait before
- * accessing the chip again.
- */
- data->access = ktime_get();
- zl6100_wait(data);
+ udelay(delay);
info = &data->info;
@@ -404,8 +358,7 @@ static int zl6100_probe(struct i2c_client *client)
if (ret < 0)
return ret;
- data->access = ktime_get();
- zl6100_wait(data);
+ udelay(delay);
if (ret & ZL8802_MFR_PHASES_MASK)
info->func[1] |= PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
@@ -418,8 +371,7 @@ static int zl6100_probe(struct i2c_client *client)
if (ret < 0)
return ret;
- data->access = ktime_get();
- zl6100_wait(data);
+ udelay(delay);
ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_CONFIG);
if (ret < 0)
@@ -428,8 +380,7 @@ static int zl6100_probe(struct i2c_client *client)
if (ret & ZL8802_MFR_XTEMP_ENABLE_2)
info->func[i] |= PMBUS_HAVE_TEMP2;
- data->access = ktime_get();
- zl6100_wait(data);
+ udelay(delay);
}
ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_GLOBAL_CONFIG);
if (ret < 0)
@@ -446,13 +397,12 @@ static int zl6100_probe(struct i2c_client *client)
info->func[0] |= PMBUS_HAVE_TEMP2;
}
- data->access = ktime_get();
- zl6100_wait(data);
+ udelay(delay);
+ info->access_delay = delay;
info->read_word_data = zl6100_read_word_data;
info->read_byte_data = zl6100_read_byte_data;
info->write_word_data = zl6100_write_word_data;
- info->write_byte = zl6100_write_byte;
return pmbus_do_probe(client, info);
}
@@ -470,4 +420,4 @@ module_i2c_driver(zl6100_driver);
MODULE_AUTHOR("Guenter Roeck");
MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS(PMBUS);
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c
new file mode 100644
index 000000000000..4e663d5b4e33
--- /dev/null
+++ b/drivers/hwmon/powerz.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Thomas Weißschuh <linux@weissschuh.net>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#define DRIVER_NAME "powerz"
+#define POWERZ_EP_CMD_OUT 0x01
+#define POWERZ_EP_DATA_IN 0x81
+
+struct powerz_sensor_data {
+ u8 _unknown_1[8];
+ __le32 V_bus;
+ __le32 I_bus;
+ __le32 V_bus_avg;
+ __le32 I_bus_avg;
+ u8 _unknown_2[8];
+ u8 temp[2];
+ __le16 V_cc1;
+ __le16 V_cc2;
+ __le16 V_dp;
+ __le16 V_dm;
+ __le16 V_dd;
+ u8 _unknown_3[4];
+} __packed;
+
+struct powerz_priv {
+ char transfer_buffer[64]; /* first member to satisfy DMA alignment */
+ struct mutex mutex;
+ struct completion completion;
+ struct urb *urb;
+ int status;
+};
+
+static const struct hwmon_channel_info *const powerz_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_AVERAGE,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_AVERAGE),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static int powerz_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type == hwmon_curr && attr == hwmon_curr_label) {
+ *str = "IBUS";
+ } else if (type == hwmon_in && attr == hwmon_in_label) {
+ if (channel == 0)
+ *str = "VBUS";
+ else if (channel == 1)
+ *str = "VCC1";
+ else if (channel == 2)
+ *str = "VCC2";
+ else if (channel == 3)
+ *str = "VDP";
+ else if (channel == 4)
+ *str = "VDM";
+ else if (channel == 5)
+ *str = "VDD";
+ else
+ return -EOPNOTSUPP;
+ } else if (type == hwmon_temp && attr == hwmon_temp_label) {
+ *str = "TEMP";
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static void powerz_usb_data_complete(struct urb *urb)
+{
+ struct powerz_priv *priv = urb->context;
+
+ complete(&priv->completion);
+}
+
+static void powerz_usb_cmd_complete(struct urb *urb)
+{
+ struct powerz_priv *priv = urb->context;
+
+ usb_fill_bulk_urb(urb, urb->dev,
+ usb_rcvbulkpipe(urb->dev, POWERZ_EP_DATA_IN),
+ priv->transfer_buffer, sizeof(priv->transfer_buffer),
+ powerz_usb_data_complete, priv);
+
+ priv->status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (priv->status)
+ complete(&priv->completion);
+}
+
+static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv)
+{
+ int ret;
+
+ priv->status = -ETIMEDOUT;
+ reinit_completion(&priv->completion);
+
+ priv->transfer_buffer[0] = 0x0c;
+ priv->transfer_buffer[1] = 0x00;
+ priv->transfer_buffer[2] = 0x02;
+ priv->transfer_buffer[3] = 0x00;
+
+ usb_fill_bulk_urb(priv->urb, udev,
+ usb_sndbulkpipe(udev, POWERZ_EP_CMD_OUT),
+ priv->transfer_buffer, 4, powerz_usb_cmd_complete,
+ priv);
+ ret = usb_submit_urb(priv->urb, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_interruptible_timeout
+ (&priv->completion, msecs_to_jiffies(5))) {
+ usb_kill_urb(priv->urb);
+ return -EIO;
+ }
+
+ if (priv->urb->actual_length < sizeof(struct powerz_sensor_data))
+ return -EIO;
+
+ return priv->status;
+}
+
+static int powerz_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct usb_interface *intf = to_usb_interface(dev->parent);
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct powerz_priv *priv = usb_get_intfdata(intf);
+ struct powerz_sensor_data *data;
+ int ret;
+
+ if (!priv)
+ return -EIO; /* disconnected */
+
+ mutex_lock(&priv->mutex);
+ ret = powerz_read_data(udev, priv);
+ if (ret)
+ goto out;
+
+ data = (struct powerz_sensor_data *)priv->transfer_buffer;
+
+ if (type == hwmon_curr) {
+ if (attr == hwmon_curr_input)
+ *val = ((s32)le32_to_cpu(data->I_bus)) / 1000;
+ else if (attr == hwmon_curr_average)
+ *val = ((s32)le32_to_cpu(data->I_bus_avg)) / 1000;
+ else
+ ret = -EOPNOTSUPP;
+ } else if (type == hwmon_in) {
+ if (attr == hwmon_in_input) {
+ if (channel == 0)
+ *val = le32_to_cpu(data->V_bus) / 1000;
+ else if (channel == 1)
+ *val = le16_to_cpu(data->V_cc1) / 10;
+ else if (channel == 2)
+ *val = le16_to_cpu(data->V_cc2) / 10;
+ else if (channel == 3)
+ *val = le16_to_cpu(data->V_dp) / 10;
+ else if (channel == 4)
+ *val = le16_to_cpu(data->V_dm) / 10;
+ else if (channel == 5)
+ *val = le16_to_cpu(data->V_dd) / 10;
+ else
+ ret = -EOPNOTSUPP;
+ } else if (attr == hwmon_in_average && channel == 0) {
+ *val = le32_to_cpu(data->V_bus_avg) / 1000;
+ } else {
+ ret = -EOPNOTSUPP;
+ }
+ } else if (type == hwmon_temp && attr == hwmon_temp_input) {
+ *val = data->temp[1] * 2000 + data->temp[0] * 1000 / 128;
+ } else {
+ ret = -EOPNOTSUPP;
+ }
+
+out:
+ mutex_unlock(&priv->mutex);
+ return ret;
+}
+
+static const struct hwmon_ops powerz_hwmon_ops = {
+ .visible = 0444,
+ .read = powerz_read,
+ .read_string = powerz_read_string,
+};
+
+static const struct hwmon_chip_info powerz_chip_info = {
+ .ops = &powerz_hwmon_ops,
+ .info = powerz_info,
+};
+
+static int powerz_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct powerz_priv *priv;
+ struct device *hwmon_dev;
+ struct device *parent;
+
+ parent = &intf->dev;
+
+ priv = devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!priv->urb)
+ return -ENOMEM;
+ mutex_init(&priv->mutex);
+ init_completion(&priv->completion);
+
+ hwmon_dev =
+ devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv,
+ &powerz_chip_info, NULL);
+ if (IS_ERR(hwmon_dev)) {
+ usb_free_urb(priv->urb);
+ return PTR_ERR(hwmon_dev);
+ }
+
+ usb_set_intfdata(intf, priv);
+
+ return 0;
+}
+
+static void powerz_disconnect(struct usb_interface *intf)
+{
+ struct powerz_priv *priv = usb_get_intfdata(intf);
+
+ mutex_lock(&priv->mutex);
+ usb_kill_urb(priv->urb);
+ usb_free_urb(priv->urb);
+ mutex_unlock(&priv->mutex);
+}
+
+static const struct usb_device_id powerz_id_table[] = {
+ { USB_DEVICE_INTERFACE_NUMBER(0x5FC9, 0x0061, 0x00) }, /* ChargerLAB POWER-Z KM002C */
+ { USB_DEVICE_INTERFACE_NUMBER(0x5FC9, 0x0063, 0x00) }, /* ChargerLAB POWER-Z KM003C */
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, powerz_id_table);
+
+static struct usb_driver powerz_driver = {
+ .name = DRIVER_NAME,
+ .id_table = powerz_id_table,
+ .probe = powerz_probe,
+ .disconnect = powerz_disconnect,
+};
+
+module_usb_driver(powerz_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");
+MODULE_DESCRIPTION("ChargerLAB POWER-Z USB-C tester");
diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c
index 4120cadb00ae..06a2c56016d1 100644
--- a/drivers/hwmon/powr1220.c
+++ b/drivers/hwmon/powr1220.c
@@ -16,7 +16,6 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/delay.h>
#define ADC_STEP_MV 2
@@ -75,7 +74,6 @@ enum powr1220_adc_values {
struct powr1220_data {
struct i2c_client *client;
- struct mutex update_lock;
u8 max_channels;
bool adc_valid[MAX_POWR1220_ADC_VALUES];
/* the next value is in jiffies */
@@ -111,8 +109,6 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
int result;
int adc_range = 0;
- mutex_lock(&data->update_lock);
-
if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) ||
!data->adc_valid[ch_num]) {
/*
@@ -128,8 +124,8 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
/* set the attenuator and mux */
result = i2c_smbus_write_byte_data(data->client, ADC_MUX,
adc_range | ch_num);
- if (result)
- goto exit;
+ if (result < 0)
+ return result;
/*
* wait at least Tconvert time (200 us) for the
@@ -140,14 +136,14 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
/* get the ADC reading */
result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_LOW);
if (result < 0)
- goto exit;
+ return result;
reading = result >> 4;
/* get the upper half of the reading */
result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_HIGH);
if (result < 0)
- goto exit;
+ return result;
reading |= result << 4;
@@ -163,10 +159,6 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
} else {
result = data->adc_values[ch_num];
}
-
-exit:
- mutex_unlock(&data->update_lock);
-
return result;
}
@@ -279,12 +271,11 @@ static const struct hwmon_chip_info powr1220_chip_info = {
.info = powr1220_info,
};
-static const struct i2c_device_id powr1220_ids[];
-
static int powr1220_probe(struct i2c_client *client)
{
struct powr1220_data *data;
struct device *hwmon_dev;
+ enum powr1xxx_chips chip;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
@@ -293,7 +284,8 @@ static int powr1220_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- switch (i2c_match_id(powr1220_ids, client)->driver_data) {
+ chip = (uintptr_t)i2c_get_match_data(client);
+ switch (chip) {
case powr1014:
data->max_channels = 10;
break;
@@ -302,7 +294,6 @@ static int powr1220_probe(struct i2c_client *client)
break;
}
- mutex_init(&data->update_lock);
data->client = client;
hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
@@ -323,7 +314,6 @@ static const struct i2c_device_id powr1220_ids[] = {
MODULE_DEVICE_TABLE(i2c, powr1220_ids);
static struct i2c_driver powr1220_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "powr1220",
},
diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c
new file mode 100644
index 000000000000..20e3cfa625f1
--- /dev/null
+++ b/drivers/hwmon/pt5161l.c
@@ -0,0 +1,639 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+/* Aries current average temp ADC code CSR */
+#define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c
+
+/* Device Load check register */
+#define ARIES_CODE_LOAD_REG 0x605
+/* Value indicating FW was loaded properly, [3:1] = 3'b111 */
+#define ARIES_LOAD_CODE 0xe
+
+/* Main Micro Heartbeat register */
+#define ARIES_MM_HEARTBEAT_ADDR 0x923
+
+/* Reg offset to specify Address for MM assisted accesses */
+#define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99
+/* Reg offset to specify Command for MM assisted accesses */
+#define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d
+/* Reg offset to MM SPARE 0 used specify Address[7:0] */
+#define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f
+/* Reg offset to MM SPARE 3 used specify Data Byte 0 */
+#define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2
+/* Wide register reads */
+#define ARIES_MM_RD_WIDE_REG_2B 0x1d
+#define ARIES_MM_RD_WIDE_REG_3B 0x1e
+#define ARIES_MM_RD_WIDE_REG_4B 0x1f
+#define ARIES_MM_RD_WIDE_REG_5B 0x20
+
+/* Time delay between checking MM status of EEPROM write (microseconds) */
+#define ARIES_MM_STATUS_TIME 5000
+
+/* AL Main SRAM DMEM offset (A0) */
+#define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024)
+/* SRAM read command */
+#define AL_TG_RD_LOC_IND_SRAM 0x16
+
+/* Offset for main micro FW info */
+#define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128)
+/* FW Info (Major) offset location in struct */
+#define ARIES_MM_FW_VERSION_MAJOR 0
+/* FW Info (Minor) offset location in struct */
+#define ARIES_MM_FW_VERSION_MINOR 1
+/* FW Info (Build no.) offset location in struct */
+#define ARIES_MM_FW_VERSION_BUILD 2
+
+#define ARIES_TEMP_CAL_CODE_DEFAULT 84
+
+/* Struct defining FW version loaded on an Aries device */
+struct pt5161l_fw_ver {
+ u8 major;
+ u8 minor;
+ u16 build;
+};
+
+/* Each client has this additional data */
+struct pt5161l_data {
+ struct i2c_client *client;
+ struct pt5161l_fw_ver fw_ver;
+ struct mutex lock; /* for atomic I2C transactions */
+ bool init_done;
+ bool code_load_okay; /* indicate if code load reg value is expected */
+ bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */
+ bool mm_wide_reg_access; /* MM assisted wide register access */
+};
+
+/*
+ * Write multiple data bytes to Aries over I2C
+ */
+static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address,
+ u8 len, u8 *val)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+ u8 remain_len = len;
+ u8 xfer_len, curr_len;
+ u8 buf[16];
+ u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
+ u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
+
+ while (remain_len > 0) {
+ if (remain_len > 4) {
+ curr_len = 4;
+ remain_len -= 4;
+ } else {
+ curr_len = remain_len;
+ remain_len = 0;
+ }
+
+ buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1);
+ buf[1] = (address >> 8) & 0xff;
+ buf[2] = address & 0xff;
+ memcpy(&buf[3], val, curr_len);
+
+ xfer_len = 3 + curr_len;
+ ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf);
+ if (ret)
+ return ret;
+
+ val += curr_len;
+ address += curr_len;
+ }
+
+ return 0;
+}
+
+/*
+ * Read multiple data bytes from Aries over I2C
+ */
+static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
+ u8 len, u8 *val)
+{
+ struct i2c_client *client = data->client;
+ int ret, tries;
+ u8 remain_len = len;
+ u8 curr_len;
+ u8 wbuf[16], rbuf[24];
+ u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
+ u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
+
+ while (remain_len > 0) {
+ if (remain_len > 16) {
+ curr_len = 16;
+ remain_len -= 16;
+ } else {
+ curr_len = remain_len;
+ remain_len = 0;
+ }
+
+ wbuf[0] = config | (curr_len - 1) << 1 |
+ ((address >> 16) & 0x1);
+ wbuf[1] = (address >> 8) & 0xff;
+ wbuf[2] = address & 0xff;
+
+ for (tries = 0; tries < 3; tries++) {
+ ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3,
+ wbuf);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_read_block_data(client, (cmd | 0x1),
+ rbuf);
+ if (ret == curr_len)
+ break;
+ }
+ if (tries >= 3)
+ return ret;
+
+ memcpy(val, rbuf, curr_len);
+ val += curr_len;
+ address += curr_len;
+ }
+
+ return 0;
+}
+
+static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address,
+ u8 width, u8 *val)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 status;
+
+ /*
+ * Safely access wide registers using mailbox method to prevent
+ * risking conflict with Aries firmware; otherwise fallback to
+ * legacy, less secure method.
+ */
+ if (data->mm_wide_reg_access) {
+ buf[0] = address & 0xff;
+ buf[1] = (address >> 8) & 0xff;
+ buf[2] = (address >> 16) & 0x1;
+ ret = pt5161l_write_block_data(data,
+ ARIES_MM_ASSIST_SPARE_0_OFFSET,
+ 3, buf);
+ if (ret)
+ return ret;
+
+ /* Set command based on width */
+ switch (width) {
+ case 2:
+ buf[0] = ARIES_MM_RD_WIDE_REG_2B;
+ break;
+ case 3:
+ buf[0] = ARIES_MM_RD_WIDE_REG_3B;
+ break;
+ case 4:
+ buf[0] = ARIES_MM_RD_WIDE_REG_4B;
+ break;
+ case 5:
+ buf[0] = ARIES_MM_RD_WIDE_REG_5B;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET,
+ 1, buf);
+ if (ret)
+ return ret;
+
+ status = 0xff;
+ for (tries = 0; tries < 100; tries++) {
+ ret = pt5161l_read_block_data(data,
+ ARIES_MM_ASSIST_CMD_OFFSET,
+ 1, &status);
+ if (ret)
+ return ret;
+
+ if (status == 0)
+ break;
+
+ usleep_range(ARIES_MM_STATUS_TIME,
+ ARIES_MM_STATUS_TIME + 1000);
+ }
+ if (status != 0)
+ return -ETIMEDOUT;
+
+ ret = pt5161l_read_block_data(data,
+ ARIES_MM_ASSIST_SPARE_3_OFFSET,
+ width, val);
+ if (ret)
+ return ret;
+ } else {
+ return pt5161l_read_block_data(data, address, width, val);
+ }
+
+ return 0;
+}
+
+/*
+ * Read multiple (up to eight) data bytes from micro SRAM over I2C
+ */
+static int
+pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data,
+ u32 address, u8 len, u8 *val)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 i, status;
+ u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET;
+ u32 eeprom_base, eeprom_addr;
+
+ /* No multi-byte indirect support here. Hence read a byte at a time */
+ eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET;
+ for (i = 0; i < len; i++) {
+ eeprom_addr = eeprom_base + i;
+ buf[0] = eeprom_addr & 0xff;
+ buf[1] = (eeprom_addr >> 8) & 0xff;
+ buf[2] = (eeprom_addr >> 16) & 0xff;
+ ret = pt5161l_write_block_data(data, uind_offs, 3, buf);
+ if (ret)
+ return ret;
+
+ buf[0] = AL_TG_RD_LOC_IND_SRAM;
+ ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf);
+ if (ret)
+ return ret;
+
+ status = 0xff;
+ for (tries = 0; tries < 255; tries++) {
+ ret = pt5161l_read_block_data(data, uind_offs + 4, 1,
+ &status);
+ if (ret)
+ return ret;
+
+ if (status == 0)
+ break;
+ }
+ if (status != 0)
+ return -ETIMEDOUT;
+
+ ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf);
+ if (ret)
+ return ret;
+
+ val[i] = buf[0];
+ }
+
+ return 0;
+}
+
+/*
+ * Check firmware load status
+ */
+static int pt5161l_fw_load_check(struct pt5161l_data *data)
+{
+ int ret;
+ u8 buf[8];
+
+ ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf);
+ if (ret)
+ return ret;
+
+ if (buf[0] < ARIES_LOAD_CODE) {
+ dev_dbg(&data->client->dev,
+ "Code Load reg unexpected. Not all modules are loaded %x\n",
+ buf[0]);
+ data->code_load_okay = false;
+ } else {
+ data->code_load_okay = true;
+ }
+
+ return 0;
+}
+
+/*
+ * Check main micro heartbeat
+ */
+static int pt5161l_heartbeat_check(struct pt5161l_data *data)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 heartbeat;
+ bool hb_changed = false;
+
+ ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf);
+ if (ret)
+ return ret;
+
+ heartbeat = buf[0];
+ for (tries = 0; tries < 100; tries++) {
+ ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1,
+ buf);
+ if (ret)
+ return ret;
+
+ if (buf[0] != heartbeat) {
+ hb_changed = true;
+ break;
+ }
+ }
+ data->mm_heartbeat_okay = hb_changed;
+
+ return 0;
+}
+
+/*
+ * Check the status of firmware
+ */
+static int pt5161l_fwsts_check(struct pt5161l_data *data)
+{
+ int ret;
+ u8 buf[8];
+ u8 major = 0, minor = 0;
+ u16 build = 0;
+
+ ret = pt5161l_fw_load_check(data);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_heartbeat_check(data);
+ if (ret)
+ return ret;
+
+ if (data->code_load_okay && data->mm_heartbeat_okay) {
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_MAJOR,
+ 1, &major);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_MINOR,
+ 1, &minor);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_BUILD,
+ 2, buf);
+ if (ret)
+ return ret;
+ build = buf[1] << 8 | buf[0];
+ }
+ data->fw_ver.major = major;
+ data->fw_ver.minor = minor;
+ data->fw_ver.build = build;
+
+ return 0;
+}
+
+static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor,
+ u16 build)
+{
+ u32 ver = major << 24 | minor << 16 | build;
+ u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 |
+ data->fw_ver.build;
+
+ if (curr_ver >= ver)
+ return true;
+
+ return false;
+}
+
+static int pt5161l_init_dev(struct pt5161l_data *data)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fwsts_check(data);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+
+ /* Firmware 2.2.0 enables safe access to wide registers */
+ if (pt5161l_fw_is_at_least(data, 2, 2, 0))
+ data->mm_wide_reg_access = true;
+
+ data->init_done = true;
+
+ return 0;
+}
+
+static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct pt5161l_data *data = dev_get_drvdata(dev);
+ int ret;
+ u8 buf[8];
+ u32 adc_code;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ if (!data->init_done) {
+ ret = pt5161l_init_dev(data);
+ if (ret)
+ return ret;
+ }
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_read_wide_reg(data,
+ ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4,
+ buf);
+ mutex_unlock(&data->lock);
+ if (ret) {
+ dev_dbg(dev, "Read adc_code failed %d\n", ret);
+ return ret;
+ }
+
+ adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
+ if (adc_code == 0 || adc_code >= 0x3ff) {
+ dev_dbg(dev, "Invalid adc_code %x\n", adc_code);
+ return -EIO;
+ }
+
+ *val = 110000 +
+ ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) *
+ -320);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t pt5161l_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *pt5161l_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops pt5161l_hwmon_ops = {
+ .is_visible = pt5161l_is_visible,
+ .read = pt5161l_read,
+};
+
+static const struct hwmon_chip_info pt5161l_chip_info = {
+ .ops = &pt5161l_hwmon_ops,
+ .info = pt5161l_info,
+};
+
+static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ char ver[32];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fwsts_check(data);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+
+ ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major,
+ data->fw_ver.minor, data->fw_ver.build);
+
+ return simple_read_from_buffer(buf, count, ppos, ver, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_fw_ver = {
+ .read = pt5161l_debugfs_read_fw_ver,
+ .open = simple_open,
+};
+
+static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file,
+ char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ bool status = false;
+ char health[16];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fw_load_check(data);
+ mutex_unlock(&data->lock);
+ if (ret == 0)
+ status = data->code_load_okay;
+
+ ret = snprintf(health, sizeof(health), "%s\n",
+ status ? "normal" : "abnormal");
+
+ return simple_read_from_buffer(buf, count, ppos, health, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = {
+ .read = pt5161l_debugfs_read_fw_load_sts,
+ .open = simple_open,
+};
+
+static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ bool status = false;
+ char health[16];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_heartbeat_check(data);
+ mutex_unlock(&data->lock);
+ if (ret == 0)
+ status = data->mm_heartbeat_okay;
+
+ ret = snprintf(health, sizeof(health), "%s\n",
+ status ? "normal" : "abnormal");
+
+ return simple_read_from_buffer(buf, count, ppos, health, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_hb_sts = {
+ .read = pt5161l_debugfs_read_hb_sts,
+ .open = simple_open,
+};
+
+static void pt5161l_init_debugfs(struct i2c_client *client, struct pt5161l_data *data)
+{
+ debugfs_create_file("fw_ver", 0444, client->debugfs, data,
+ &pt5161l_debugfs_ops_fw_ver);
+
+ debugfs_create_file("fw_load_status", 0444, client->debugfs, data,
+ &pt5161l_debugfs_ops_fw_load_sts);
+
+ debugfs_create_file("heartbeat_status", 0444, client->debugfs, data,
+ &pt5161l_debugfs_ops_hb_sts);
+}
+
+static int pt5161l_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct pt5161l_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ mutex_init(&data->lock);
+ pt5161l_init_dev(data);
+ dev_set_drvdata(dev, data);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data,
+ &pt5161l_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ pt5161l_init_debugfs(client, data);
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused pt5161l_of_match[] = {
+ { .compatible = "asteralabs,pt5161l" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pt5161l_of_match);
+
+static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = {
+ { "PT5161L", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match);
+
+static const struct i2c_device_id pt5161l_id[] = {
+ { "pt5161l" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pt5161l_id);
+
+static struct i2c_driver pt5161l_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "pt5161l",
+ .of_match_table = of_match_ptr(pt5161l_of_match),
+ .acpi_match_table = ACPI_PTR(pt5161l_acpi_match),
+ },
+ .probe = pt5161l_probe,
+ .id_table = pt5161l_id,
+};
+module_i2c_driver(pt5161l_driver);
+
+MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>");
+MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c
index 6e4516c2ab89..37269db2de84 100644
--- a/drivers/hwmon/pwm-fan.c
+++ b/drivers/hwmon/pwm-fan.c
@@ -7,12 +7,14 @@
* Author: Kamil Debski <k.debski@samsung.com>
*/
+#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
-#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
#include <linux/sysfs.h>
@@ -25,7 +27,6 @@ struct pwm_fan_tach {
int irq;
atomic_t pulses;
unsigned int rpm;
- u8 pulses_per_revolution;
};
enum pwm_fan_enable_mode {
@@ -48,6 +49,7 @@ struct pwm_fan_ctx {
int tach_count;
struct pwm_fan_tach *tachs;
+ u32 *pulses_per_revolution;
ktime_t sample_start;
struct timer_list rpm_timer;
@@ -59,6 +61,10 @@ struct pwm_fan_ctx {
struct hwmon_chip_info info;
struct hwmon_channel_info fan_channel;
+
+ u64 pwm_duty_cycle_from_stopped;
+ u32 pwm_usec_from_stopped;
+ u8 pwm_shutdown;
};
/* This handler assumes self resetting edge triggered interrupt. */
@@ -73,7 +79,7 @@ static irqreturn_t pulse_handler(int irq, void *dev_id)
static void sample_timer(struct timer_list *t)
{
- struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer);
+ struct pwm_fan_ctx *ctx = timer_container_of(ctx, t, rpm_timer);
unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start);
int i;
@@ -85,7 +91,7 @@ static void sample_timer(struct timer_list *t)
pulses = atomic_read(&tach->pulses);
atomic_sub(pulses, &tach->pulses);
tach->rpm = (unsigned int)(pulses * 1000 * 60) /
- (tach->pulses_per_revolution * delta);
+ (ctx->pulses_per_revolution[i] * delta);
}
ctx->sample_start = ktime_get();
@@ -151,7 +157,7 @@ static int pwm_fan_power_on(struct pwm_fan_ctx *ctx)
}
state->enabled = true;
- ret = pwm_apply_state(ctx->pwm, state);
+ ret = pwm_apply_might_sleep(ctx->pwm, state);
if (ret) {
dev_err(ctx->dev, "failed to enable PWM\n");
goto disable_regulator;
@@ -166,7 +172,7 @@ disable_regulator:
return ret;
}
-static int pwm_fan_power_off(struct pwm_fan_ctx *ctx)
+static int pwm_fan_power_off(struct pwm_fan_ctx *ctx, bool force_disable)
{
struct pwm_state *state = &ctx->pwm_state;
bool enable_regulator = false;
@@ -179,9 +185,10 @@ static int pwm_fan_power_off(struct pwm_fan_ctx *ctx)
state,
&enable_regulator);
- state->enabled = false;
+ if (force_disable)
+ state->enabled = false;
state->duty_cycle = 0;
- ret = pwm_apply_state(ctx->pwm, state);
+ ret = pwm_apply_might_sleep(ctx->pwm, state);
if (ret) {
dev_err(ctx->dev, "failed to disable PWM\n");
return ret;
@@ -197,7 +204,9 @@ static int pwm_fan_power_off(struct pwm_fan_ctx *ctx)
static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
{
struct pwm_state *state = &ctx->pwm_state;
+ unsigned long final_pwm = pwm;
unsigned long period;
+ bool update = false;
int ret = 0;
if (pwm > 0) {
@@ -206,13 +215,24 @@ static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
return 0;
period = state->period;
- state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
- ret = pwm_apply_state(ctx->pwm, state);
+ update = state->duty_cycle < ctx->pwm_duty_cycle_from_stopped;
+ if (update)
+ state->duty_cycle = ctx->pwm_duty_cycle_from_stopped;
+ else
+ state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
+ ret = pwm_apply_might_sleep(ctx->pwm, state);
if (ret)
return ret;
ret = pwm_fan_power_on(ctx);
+ if (!ret && update) {
+ pwm = final_pwm;
+ state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
+ usleep_range(ctx->pwm_usec_from_stopped,
+ ctx->pwm_usec_from_stopped * 2);
+ ret = pwm_apply_might_sleep(ctx->pwm, state);
+ }
} else {
- ret = pwm_fan_power_off(ctx);
+ ret = pwm_fan_power_off(ctx, false);
}
if (!ret)
ctx->pwm_value = pwm;
@@ -278,7 +298,7 @@ static int pwm_fan_update_enable(struct pwm_fan_ctx *ctx, long val)
state,
&enable_regulator);
- pwm_apply_state(ctx->pwm, state);
+ pwm_apply_might_sleep(ctx->pwm, state);
pwm_fan_switch_power(ctx, enable_regulator);
pwm_fan_update_state(ctx, 0);
}
@@ -421,16 +441,14 @@ static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
.set_cur_state = pwm_fan_set_cur_state,
};
-static int pwm_fan_of_get_cooling_data(struct device *dev,
- struct pwm_fan_ctx *ctx)
+static int pwm_fan_get_cooling_data(struct device *dev, struct pwm_fan_ctx *ctx)
{
- struct device_node *np = dev->of_node;
int num, i, ret;
- if (!of_property_present(np, "cooling-levels"))
+ if (!device_property_present(dev, "cooling-levels"))
return 0;
- ret = of_property_count_u32_elems(np, "cooling-levels");
+ ret = device_property_count_u32(dev, "cooling-levels");
if (ret <= 0) {
dev_err(dev, "Wrong data!\n");
return ret ? : -EINVAL;
@@ -442,8 +460,8 @@ static int pwm_fan_of_get_cooling_data(struct device *dev,
if (!ctx->pwm_fan_cooling_levels)
return -ENOMEM;
- ret = of_property_read_u32_array(np, "cooling-levels",
- ctx->pwm_fan_cooling_levels, num);
+ ret = device_property_read_u32_array(dev, "cooling-levels",
+ ctx->pwm_fan_cooling_levels, num);
if (ret) {
dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
return ret;
@@ -466,10 +484,15 @@ static void pwm_fan_cleanup(void *__ctx)
{
struct pwm_fan_ctx *ctx = __ctx;
- del_timer_sync(&ctx->rpm_timer);
- /* Switch off everything */
- ctx->enable_mode = pwm_disable_reg_disable;
- pwm_fan_power_off(ctx);
+ timer_delete_sync(&ctx->rpm_timer);
+ if (ctx->pwm_shutdown) {
+ ctx->enable_mode = pwm_enable_reg_enable;
+ __set_pwm(ctx, ctx->pwm_shutdown);
+ } else {
+ /* Switch off everything */
+ ctx->enable_mode = pwm_disable_reg_disable;
+ pwm_fan_power_off(ctx, true);
+ }
}
static int pwm_fan_probe(struct platform_device *pdev)
@@ -480,6 +503,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
struct device *hwmon;
int ret;
const struct hwmon_channel_info **channels;
+ u32 initial_pwm, pwm_min_from_stopped = 0;
+ u32 pwm_shutdown_percent = 0;
u32 *fan_channel_config;
int channel_count = 1; /* We always have a PWM channel. */
int i;
@@ -527,11 +552,21 @@ static int pwm_fan_probe(struct platform_device *pdev)
ctx->enable_mode = pwm_disable_reg_enable;
+ ret = pwm_fan_get_cooling_data(dev, ctx);
+ if (ret)
+ return ret;
+
+ /* use maximum cooling level if provided */
+ if (ctx->pwm_fan_cooling_levels)
+ initial_pwm = ctx->pwm_fan_cooling_levels[ctx->pwm_fan_max_state];
+ else
+ initial_pwm = MAX_PWM;
+
/*
* Set duty cycle to maximum allowed and enable PWM output as well as
* the regulator. In case of error nothing is changed
*/
- ret = set_pwm(ctx, MAX_PWM);
+ ret = set_pwm(ctx, initial_pwm);
if (ret) {
dev_err(dev, "Failed to configure PWM: %d\n", ret);
return ret;
@@ -562,6 +597,20 @@ static int pwm_fan_probe(struct platform_device *pdev)
if (!fan_channel_config)
return -ENOMEM;
ctx->fan_channel.config = fan_channel_config;
+
+ ctx->pulses_per_revolution = devm_kmalloc_array(dev,
+ ctx->tach_count,
+ sizeof(*ctx->pulses_per_revolution),
+ GFP_KERNEL);
+ if (!ctx->pulses_per_revolution)
+ return -ENOMEM;
+
+ /* Setup default pulses per revolution */
+ for (i = 0; i < ctx->tach_count; i++)
+ ctx->pulses_per_revolution[i] = 2;
+
+ device_property_read_u32_array(dev, "pulses-per-revolution",
+ ctx->pulses_per_revolution, ctx->tach_count);
}
channels = devm_kcalloc(dev, channel_count + 1,
@@ -573,14 +622,13 @@ static int pwm_fan_probe(struct platform_device *pdev)
for (i = 0; i < ctx->tach_count; i++) {
struct pwm_fan_tach *tach = &ctx->tachs[i];
- u32 ppr = 2;
tach->irq = platform_get_irq(pdev, i);
if (tach->irq == -EPROBE_DEFER)
return tach->irq;
if (tach->irq > 0) {
- ret = devm_request_irq(dev, tach->irq, pulse_handler, 0,
- pdev->name, tach);
+ ret = devm_request_irq(dev, tach->irq, pulse_handler,
+ IRQF_NO_THREAD, pdev->name, tach);
if (ret) {
dev_err(dev,
"Failed to request interrupt: %d\n",
@@ -589,12 +637,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
}
}
- of_property_read_u32_index(dev->of_node,
- "pulses-per-revolution",
- i,
- &ppr);
- tach->pulses_per_revolution = ppr;
- if (!tach->pulses_per_revolution) {
+ if (!ctx->pulses_per_revolution[i]) {
dev_err(dev, "pulses-per-revolution can't be zero.\n");
return -EINVAL;
}
@@ -602,7 +645,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
fan_channel_config[i] = HWMON_F_INPUT;
dev_dbg(dev, "tach%d: irq=%d, pulses_per_revolution=%d\n",
- i, tach->irq, tach->pulses_per_revolution);
+ i, tach->irq, ctx->pulses_per_revolution[i]);
}
if (ctx->tach_count > 0) {
@@ -612,6 +655,24 @@ static int pwm_fan_probe(struct platform_device *pdev)
channels[1] = &ctx->fan_channel;
}
+ ret = device_property_read_u32(dev, "fan-shutdown-percent",
+ &pwm_shutdown_percent);
+ if (!ret && pwm_shutdown_percent)
+ ctx->pwm_shutdown = (clamp(pwm_shutdown_percent, 0, 100) * 255) / 100;
+
+ ret = device_property_read_u32(dev, "fan-stop-to-start-percent",
+ &pwm_min_from_stopped);
+ if (!ret && pwm_min_from_stopped) {
+ ctx->pwm_duty_cycle_from_stopped =
+ DIV_ROUND_UP_ULL(pwm_min_from_stopped *
+ (ctx->pwm_state.period - 1),
+ 100);
+ }
+ ret = device_property_read_u32(dev, "fan-stop-to-start-us",
+ &ctx->pwm_usec_from_stopped);
+ if (ret)
+ ctx->pwm_usec_from_stopped = 250000;
+
ctx->info.ops = &pwm_fan_hwmon_ops;
ctx->info.info = channels;
@@ -622,10 +683,6 @@ static int pwm_fan_probe(struct platform_device *pdev)
return PTR_ERR(hwmon);
}
- ret = pwm_fan_of_get_cooling_data(dev, ctx);
- if (ret)
- return ret;
-
ctx->pwm_fan_state = ctx->pwm_fan_max_state;
if (IS_ENABLED(CONFIG_THERMAL)) {
cdev = devm_thermal_of_cooling_device_register(dev,
@@ -654,7 +711,7 @@ static int pwm_fan_suspend(struct device *dev)
{
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
- return pwm_fan_power_off(ctx);
+ return pwm_fan_power_off(ctx, true);
}
static int pwm_fan_resume(struct device *dev)
diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c
new file mode 100644
index 000000000000..e86e64c4d391
--- /dev/null
+++ b/drivers/hwmon/qnap-mcu-hwmon.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Driver for hwmon elements found on QNAP-MCU devices
+ *
+ * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
+ */
+
+#include <linux/hwmon.h>
+#include <linux/mfd/qnap-mcu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/thermal.h>
+
+struct qnap_mcu_hwmon {
+ struct qnap_mcu *mcu;
+ struct device *dev;
+
+ unsigned int pwm_min;
+ unsigned int pwm_max;
+
+ struct fwnode_handle *fan_node;
+ unsigned int fan_state;
+ unsigned int fan_max_state;
+ unsigned int *fan_cooling_levels;
+
+ struct thermal_cooling_device *cdev;
+ struct hwmon_chip_info info;
+};
+
+static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm)
+{
+ static const u8 cmd[] = { '@', 'F', 'A' };
+ u8 reply[6];
+ int ret;
+
+ /* poll the fan rpm */
+ ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
+ if (ret)
+ return ret;
+
+ /* First 2 bytes must mirror the sent command */
+ if (memcmp(cmd, reply, 2))
+ return -EIO;
+
+ return reply[4] * 30;
+}
+
+static int qnap_mcu_hwmon_get_pwm(struct qnap_mcu_hwmon *hwm)
+{
+ static const u8 cmd[] = { '@', 'F', 'Z', '0' }; /* 0 = fan-id? */
+ u8 reply[4];
+ int ret;
+
+ /* poll the fan pwm */
+ ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
+ if (ret)
+ return ret;
+
+ /* First 3 bytes must mirror the sent command */
+ if (memcmp(cmd, reply, 3))
+ return -EIO;
+
+ return reply[3];
+}
+
+static int qnap_mcu_hwmon_set_pwm(struct qnap_mcu_hwmon *hwm, u8 pwm)
+{
+ const u8 cmd[] = { '@', 'F', 'W', '0', pwm }; /* 0 = fan-id?, pwm 0-255 */
+
+ /* set the fan pwm */
+ return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm)
+{
+ static const u8 cmd[] = { '@', 'T', '3' };
+ u8 reply[4];
+ int ret;
+
+ /* poll the fan rpm */
+ ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
+ if (ret)
+ return ret;
+
+ /* First bytes must mirror the sent command */
+ if (memcmp(cmd, reply, sizeof(cmd)))
+ return -EIO;
+
+ /*
+ * There is an unknown bit set in bit7.
+ * Bits [6:0] report the actual temperature as returned by the
+ * original qnap firmware-tools, so just drop bit7 for now.
+ */
+ return (reply[3] & 0x7f) * 1000;
+}
+
+static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ if (val != 0)
+ val = clamp_val(val, hwm->pwm_min, hwm->pwm_max);
+
+ return qnap_mcu_hwmon_set_pwm(hwm, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ ret = qnap_mcu_hwmon_get_pwm(hwm);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_fan:
+ ret = qnap_mcu_hwmon_get_rpm(hwm);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+ case hwmon_temp:
+ ret = qnap_mcu_hwmon_get_temp(hwm);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t qnap_mcu_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ return 0444;
+
+ case hwmon_pwm:
+ return 0644;
+
+ case hwmon_fan:
+ return 0444;
+
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = {
+ .is_visible = qnap_mcu_hwmon_is_visible,
+ .read = qnap_mcu_hwmon_read,
+ .write = qnap_mcu_hwmon_write,
+};
+
+/* thermal cooling device callbacks */
+static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct qnap_mcu_hwmon *hwm = cdev->devdata;
+
+ if (!hwm)
+ return -EINVAL;
+
+ *state = hwm->fan_max_state;
+
+ return 0;
+}
+
+static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct qnap_mcu_hwmon *hwm = cdev->devdata;
+
+ if (!hwm)
+ return -EINVAL;
+
+ *state = hwm->fan_state;
+
+ return 0;
+}
+
+static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct qnap_mcu_hwmon *hwm = cdev->devdata;
+ int ret;
+
+ if (!hwm || state > hwm->fan_max_state)
+ return -EINVAL;
+
+ if (state == hwm->fan_state)
+ return 0;
+
+ ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->fan_cooling_levels[state]);
+ if (ret)
+ return ret;
+
+ hwm->fan_state = state;
+
+ return ret;
+}
+
+static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = {
+ .get_max_state = qnap_mcu_hwmon_get_max_state,
+ .get_cur_state = qnap_mcu_hwmon_get_cur_state,
+ .set_cur_state = qnap_mcu_hwmon_set_cur_state,
+};
+
+static void devm_fan_node_release(void *data)
+{
+ struct qnap_mcu_hwmon *hwm = data;
+
+ fwnode_handle_put(hwm->fan_node);
+}
+
+static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm)
+{
+ struct fwnode_handle *fwnode;
+ int num, i, ret;
+
+ fwnode = device_get_named_child_node(dev->parent, "fan-0");
+ if (!fwnode)
+ return 0;
+
+ /* if we found the fan-node, we're keeping it until device-unbind */
+ hwm->fan_node = fwnode;
+ ret = devm_add_action_or_reset(dev, devm_fan_node_release, hwm);
+ if (ret)
+ return ret;
+
+ num = fwnode_property_count_u32(fwnode, "cooling-levels");
+ if (num <= 0)
+ return dev_err_probe(dev, num ? : -EINVAL,
+ "Failed to count elements in 'cooling-levels'\n");
+
+ hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
+ GFP_KERNEL);
+ if (!hwm->fan_cooling_levels)
+ return -ENOMEM;
+
+ ret = fwnode_property_read_u32_array(fwnode, "cooling-levels",
+ hwm->fan_cooling_levels, num);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read 'cooling-levels'\n");
+
+ for (i = 0; i < num; i++) {
+ if (hwm->fan_cooling_levels[i] > hwm->pwm_max)
+ return dev_err_probe(dev, -EINVAL, "fan state[%d]:%d > %d\n", i,
+ hwm->fan_cooling_levels[i], hwm->pwm_max);
+ }
+
+ hwm->fan_max_state = num - 1;
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = {
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL
+};
+
+static int qnap_mcu_hwmon_probe(struct platform_device *pdev)
+{
+ struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
+ const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
+ struct qnap_mcu_hwmon *hwm;
+ struct thermal_cooling_device *cdev;
+ struct device *dev = &pdev->dev;
+ struct device *hwmon;
+ int ret;
+
+ hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL);
+ if (!hwm)
+ return -ENOMEM;
+
+ hwm->mcu = mcu;
+ hwm->dev = &pdev->dev;
+ hwm->pwm_min = variant->fan_pwm_min;
+ hwm->pwm_max = variant->fan_pwm_max;
+
+ platform_set_drvdata(pdev, hwm);
+
+ /*
+ * Set duty cycle to maximum allowed.
+ */
+ ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->pwm_max);
+ if (ret)
+ return ret;
+
+ hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops;
+ hwm->info.info = qnap_mcu_hwmon_channels;
+
+ ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm);
+ if (ret)
+ return ret;
+
+ hwm->fan_state = hwm->fan_max_state;
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu",
+ hwm, &hwm->info, NULL);
+ if (IS_ERR(hwmon))
+ return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n");
+
+ /*
+ * Only register cooling device when we found cooling-levels.
+ * qnap_mcu_hwmon_get_cooling_data() will fail when reading malformed
+ * levels and only succeed with either no or correct cooling levels.
+ */
+ if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) {
+ cdev = devm_thermal_of_cooling_device_register(dev,
+ to_of_node(hwm->fan_node), "qnap-mcu-hwmon",
+ hwm, &qnap_mcu_hwmon_cooling_ops);
+ if (IS_ERR(cdev))
+ return dev_err_probe(dev, PTR_ERR(cdev),
+ "Failed to register qnap-mcu-hwmon as cooling device\n");
+ hwm->cdev = cdev;
+ }
+
+ return 0;
+}
+
+static struct platform_driver qnap_mcu_hwmon_driver = {
+ .probe = qnap_mcu_hwmon_probe,
+ .driver = {
+ .name = "qnap-mcu-hwmon",
+ },
+};
+module_platform_driver(qnap_mcu_hwmon_driver);
+
+MODULE_ALIAS("platform:qnap-mcu-hwmon");
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("QNAP MCU hwmon driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
index 65cc52e47db0..a2938881ccd2 100644
--- a/drivers/hwmon/raspberrypi-hwmon.c
+++ b/drivers/hwmon/raspberrypi-hwmon.c
@@ -81,12 +81,6 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
return 0;
}
-static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
- u32 attr, int channel)
-{
- return 0444;
-}
-
static const struct hwmon_channel_info * const rpi_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_LCRIT_ALARM),
@@ -94,7 +88,7 @@ static const struct hwmon_channel_info * const rpi_info[] = {
};
static const struct hwmon_ops rpi_hwmon_ops = {
- .is_visible = rpi_is_visible,
+ .visible = 0444,
.read = rpi_read,
};
@@ -134,10 +128,32 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
return 0;
}
+static int rpi_hwmon_suspend(struct device *dev)
+{
+ struct rpi_hwmon_data *data = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&data->get_values_poll_work);
+
+ return 0;
+}
+
+static int rpi_hwmon_resume(struct device *dev)
+{
+ struct rpi_hwmon_data *data = dev_get_drvdata(dev);
+
+ get_values_poll(&data->get_values_poll_work.work);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(rpi_hwmon_pm_ops, rpi_hwmon_suspend,
+ rpi_hwmon_resume);
+
static struct platform_driver rpi_hwmon_driver = {
.probe = rpi_hwmon_probe,
.driver = {
.name = "raspberrypi-hwmon",
+ .pm = pm_ptr(&rpi_hwmon_pm_ops),
},
};
module_platform_driver(rpi_hwmon_driver);
diff --git a/drivers/hwmon/sa67mcu-hwmon.c b/drivers/hwmon/sa67mcu-hwmon.c
new file mode 100644
index 000000000000..22f703b7b256
--- /dev/null
+++ b/drivers/hwmon/sa67mcu-hwmon.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sl67mcu hardware monitoring driver
+ *
+ * Copyright 2025 Kontron Europe GmbH
+ */
+
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2))
+#define SA67MCU_TEMP(n) (0x04 + ((n) * 2))
+
+struct sa67mcu_hwmon {
+ struct regmap *regmap;
+ u32 offset;
+};
+
+static int sa67mcu_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *input)
+{
+ struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned int offset;
+ u8 reg[2];
+ int ret;
+
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ offset = hwmon->offset + SA67MCU_VOLTAGE(channel);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ offset = hwmon->offset + SA67MCU_TEMP(channel);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Reading the low byte will capture the value */
+ ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg));
+ if (ret)
+ return ret;
+
+ *input = reg[1] << 8 | reg[0];
+
+ /* Temperatures are s16 and in 0.1degC steps. */
+ if (type == hwmon_temp)
+ *input = sign_extend32(*input, 15) * 100;
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL
+};
+
+static const char *const sa67mcu_hwmon_in_labels[] = {
+ "VDDIN",
+ "VDD_RTC",
+};
+
+static int sa67mcu_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_label:
+ *str = sa67mcu_hwmon_in_labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_ops sa67mcu_hwmon_ops = {
+ .visible = 0444,
+ .read = sa67mcu_hwmon_read,
+ .read_string = sa67mcu_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = {
+ .ops = &sa67mcu_hwmon_ops,
+ .info = sa67mcu_hwmon_info,
+};
+
+static int sa67mcu_hwmon_probe(struct platform_device *pdev)
+{
+ struct sa67mcu_hwmon *hwmon;
+ struct device *hwmon_dev;
+ int ret;
+
+ if (!pdev->dev.parent)
+ return -ENODEV;
+
+ hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!hwmon->regmap)
+ return -ENODEV;
+
+ ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);
+ if (ret)
+ return -EINVAL;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "sa67mcu_hwmon", hwmon,
+ &sa67mcu_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ dev_err(&pdev->dev, "failed to register as hwmon device");
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id sa67mcu_hwmon_of_match[] = {
+ { .compatible = "kontron,sa67mcu-hwmon", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match);
+
+static struct platform_driver sa67mcu_hwmon_driver = {
+ .probe = sa67mcu_hwmon_probe,
+ .driver = {
+ .name = "sa67mcu-hwmon",
+ .of_match_table = sa67mcu_hwmon_of_match,
+ },
+};
+module_platform_driver(sa67mcu_hwmon_driver);
+
+MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver");
+MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sbrmi.c b/drivers/hwmon/sbrmi.c
deleted file mode 100644
index 484703f0ea5f..000000000000
--- a/drivers/hwmon/sbrmi.c
+++ /dev/null
@@ -1,358 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * sbrmi.c - hwmon driver for a SB-RMI mailbox
- * compliant AMD SoC device.
- *
- * Copyright (C) 2020-2021 Advanced Micro Devices, Inc.
- */
-
-#include <linux/delay.h>
-#include <linux/err.h>
-#include <linux/hwmon.h>
-#include <linux/i2c.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/of.h>
-
-/* Do not allow setting negative power limit */
-#define SBRMI_PWR_MIN 0
-/* Mask for Status Register bit[1] */
-#define SW_ALERT_MASK 0x2
-
-/* Software Interrupt for triggering */
-#define START_CMD 0x80
-#define TRIGGER_MAILBOX 0x01
-
-/*
- * SB-RMI supports soft mailbox service request to MP1 (power management
- * firmware) through SBRMI inbound/outbound message registers.
- * SB-RMI message IDs
- */
-enum sbrmi_msg_id {
- SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1,
- SBRMI_WRITE_PKG_PWR_LIMIT,
- SBRMI_READ_PKG_PWR_LIMIT,
- SBRMI_READ_PKG_MAX_PWR_LIMIT,
-};
-
-/* SB-RMI registers */
-enum sbrmi_reg {
- SBRMI_CTRL = 0x01,
- SBRMI_STATUS,
- SBRMI_OUTBNDMSG0 = 0x30,
- SBRMI_OUTBNDMSG1,
- SBRMI_OUTBNDMSG2,
- SBRMI_OUTBNDMSG3,
- SBRMI_OUTBNDMSG4,
- SBRMI_OUTBNDMSG5,
- SBRMI_OUTBNDMSG6,
- SBRMI_OUTBNDMSG7,
- SBRMI_INBNDMSG0,
- SBRMI_INBNDMSG1,
- SBRMI_INBNDMSG2,
- SBRMI_INBNDMSG3,
- SBRMI_INBNDMSG4,
- SBRMI_INBNDMSG5,
- SBRMI_INBNDMSG6,
- SBRMI_INBNDMSG7,
- SBRMI_SW_INTERRUPT,
-};
-
-/* Each client has this additional data */
-struct sbrmi_data {
- struct i2c_client *client;
- struct mutex lock;
- u32 pwr_limit_max;
-};
-
-struct sbrmi_mailbox_msg {
- u8 cmd;
- bool read;
- u32 data_in;
- u32 data_out;
-};
-
-static int sbrmi_enable_alert(struct i2c_client *client)
-{
- int ctrl;
-
- /*
- * Enable the SB-RMI Software alert status
- * by writing 0 to bit 4 of Control register(0x1)
- */
- ctrl = i2c_smbus_read_byte_data(client, SBRMI_CTRL);
- if (ctrl < 0)
- return ctrl;
-
- if (ctrl & 0x10) {
- ctrl &= ~0x10;
- return i2c_smbus_write_byte_data(client,
- SBRMI_CTRL, ctrl);
- }
-
- return 0;
-}
-
-static int rmi_mailbox_xfer(struct sbrmi_data *data,
- struct sbrmi_mailbox_msg *msg)
-{
- int i, ret, retry = 10;
- int sw_status;
- u8 byte;
-
- mutex_lock(&data->lock);
-
- /* Indicate firmware a command is to be serviced */
- ret = i2c_smbus_write_byte_data(data->client,
- SBRMI_INBNDMSG7, START_CMD);
- if (ret < 0)
- goto exit_unlock;
-
- /* Write the command to SBRMI::InBndMsg_inst0 */
- ret = i2c_smbus_write_byte_data(data->client,
- SBRMI_INBNDMSG0, msg->cmd);
- if (ret < 0)
- goto exit_unlock;
-
- /*
- * For both read and write the initiator (BMC) writes
- * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
- * SBRMI_x3C(MSB):SBRMI_x39(LSB)
- */
- for (i = 0; i < 4; i++) {
- byte = (msg->data_in >> i * 8) & 0xff;
- ret = i2c_smbus_write_byte_data(data->client,
- SBRMI_INBNDMSG1 + i, byte);
- if (ret < 0)
- goto exit_unlock;
- }
-
- /*
- * Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to
- * perform the requested read or write command
- */
- ret = i2c_smbus_write_byte_data(data->client,
- SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX);
- if (ret < 0)
- goto exit_unlock;
-
- /*
- * Firmware will write SBRMI::Status[SwAlertSts]=1 to generate
- * an ALERT (if enabled) to initiator (BMC) to indicate completion
- * of the requested command
- */
- do {
- sw_status = i2c_smbus_read_byte_data(data->client,
- SBRMI_STATUS);
- if (sw_status < 0) {
- ret = sw_status;
- goto exit_unlock;
- }
- if (sw_status & SW_ALERT_MASK)
- break;
- usleep_range(50, 100);
- } while (retry--);
-
- if (retry < 0) {
- dev_err(&data->client->dev,
- "Firmware fail to indicate command completion\n");
- ret = -EIO;
- goto exit_unlock;
- }
-
- /*
- * For a read operation, the initiator (BMC) reads the firmware
- * response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
- * {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
- */
- if (msg->read) {
- for (i = 0; i < 4; i++) {
- ret = i2c_smbus_read_byte_data(data->client,
- SBRMI_OUTBNDMSG1 + i);
- if (ret < 0)
- goto exit_unlock;
- msg->data_out |= ret << i * 8;
- }
- }
-
- /*
- * BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
- * ALERT to initiator
- */
- ret = i2c_smbus_write_byte_data(data->client, SBRMI_STATUS,
- sw_status | SW_ALERT_MASK);
-
-exit_unlock:
- mutex_unlock(&data->lock);
- return ret;
-}
-
-static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long *val)
-{
- struct sbrmi_data *data = dev_get_drvdata(dev);
- struct sbrmi_mailbox_msg msg = { 0 };
- int ret;
-
- if (type != hwmon_power)
- return -EINVAL;
-
- msg.read = true;
- switch (attr) {
- case hwmon_power_input:
- msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
- ret = rmi_mailbox_xfer(data, &msg);
- break;
- case hwmon_power_cap:
- msg.cmd = SBRMI_READ_PKG_PWR_LIMIT;
- ret = rmi_mailbox_xfer(data, &msg);
- break;
- case hwmon_power_cap_max:
- msg.data_out = data->pwr_limit_max;
- ret = 0;
- break;
- default:
- return -EINVAL;
- }
- if (ret < 0)
- return ret;
- /* hwmon power attributes are in microWatt */
- *val = (long)msg.data_out * 1000;
- return ret;
-}
-
-static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long val)
-{
- struct sbrmi_data *data = dev_get_drvdata(dev);
- struct sbrmi_mailbox_msg msg = { 0 };
-
- if (type != hwmon_power && attr != hwmon_power_cap)
- return -EINVAL;
- /*
- * hwmon power attributes are in microWatt
- * mailbox read/write is in mWatt
- */
- val /= 1000;
-
- val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
-
- msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
- msg.data_in = val;
- msg.read = false;
-
- return rmi_mailbox_xfer(data, &msg);
-}
-
-static umode_t sbrmi_is_visible(const void *data,
- enum hwmon_sensor_types type,
- u32 attr, int channel)
-{
- switch (type) {
- case hwmon_power:
- switch (attr) {
- case hwmon_power_input:
- case hwmon_power_cap_max:
- return 0444;
- case hwmon_power_cap:
- return 0644;
- }
- break;
- default:
- break;
- }
- return 0;
-}
-
-static const struct hwmon_channel_info * const sbrmi_info[] = {
- HWMON_CHANNEL_INFO(power,
- HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
- NULL
-};
-
-static const struct hwmon_ops sbrmi_hwmon_ops = {
- .is_visible = sbrmi_is_visible,
- .read = sbrmi_read,
- .write = sbrmi_write,
-};
-
-static const struct hwmon_chip_info sbrmi_chip_info = {
- .ops = &sbrmi_hwmon_ops,
- .info = sbrmi_info,
-};
-
-static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
-{
- struct sbrmi_mailbox_msg msg = { 0 };
- int ret;
-
- msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
- msg.read = true;
- ret = rmi_mailbox_xfer(data, &msg);
- if (ret < 0)
- return ret;
- data->pwr_limit_max = msg.data_out;
-
- return ret;
-}
-
-static int sbrmi_probe(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- struct device *hwmon_dev;
- struct sbrmi_data *data;
- int ret;
-
- data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- data->client = client;
- mutex_init(&data->lock);
-
- /* Enable alert for SB-RMI sequence */
- ret = sbrmi_enable_alert(client);
- if (ret < 0)
- return ret;
-
- /* Cache maximum power limit */
- ret = sbrmi_get_max_pwr_limit(data);
- if (ret < 0)
- return ret;
-
- hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
- &sbrmi_chip_info, NULL);
-
- return PTR_ERR_OR_ZERO(hwmon_dev);
-}
-
-static const struct i2c_device_id sbrmi_id[] = {
- {"sbrmi", 0},
- {}
-};
-MODULE_DEVICE_TABLE(i2c, sbrmi_id);
-
-static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
- {
- .compatible = "amd,sbrmi",
- },
- { },
-};
-MODULE_DEVICE_TABLE(of, sbrmi_of_match);
-
-static struct i2c_driver sbrmi_driver = {
- .class = I2C_CLASS_HWMON,
- .driver = {
- .name = "sbrmi",
- .of_match_table = of_match_ptr(sbrmi_of_match),
- },
- .probe = sbrmi_probe,
- .id_table = sbrmi_id,
-};
-
-module_i2c_driver(sbrmi_driver);
-
-MODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>");
-MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor");
-MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c
index dd85cf89f008..c5b2488c4c7f 100644
--- a/drivers/hwmon/sbtsi_temp.c
+++ b/drivers/hwmon/sbtsi_temp.c
@@ -12,8 +12,8 @@
#include <linux/init.h>
#include <linux/hwmon.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/bitfield.h>
/*
* SB-TSI registers only support SMBus byte data access. "_INT" registers are
@@ -29,15 +29,30 @@
#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
+/*
+ * Bit for reporting value with temperature measurement range.
+ * bit == 0: Use default temperature range (0C to 255.875C).
+ * bit == 1: Use extended temperature range (-49C to +206.875C).
+ */
+#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
+/*
+ * ReadOrder bit specifies the reading order of integer and decimal part of
+ * CPU temperature for atomic reads. If bit == 0, reading integer part triggers
+ * latching of the decimal part, so integer part should be read first.
+ * If bit == 1, read order should be reversed.
+ */
#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
+#define SBTSI_TEMP_EXT_RANGE_ADJ 49000
+
#define SBTSI_TEMP_MIN 0
#define SBTSI_TEMP_MAX 255875
/* Each client has this additional data */
struct sbtsi_data {
struct i2c_client *client;
- struct mutex lock;
+ bool ext_range_mode;
+ bool read_order;
};
/*
@@ -74,42 +89,24 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
- int err;
switch (attr) {
case hwmon_temp_input:
- /*
- * ReadOrder bit specifies the reading order of integer and
- * decimal part of CPU temp for atomic reads. If bit == 0,
- * reading integer part triggers latching of the decimal part,
- * so integer part should be read first. If bit == 1, read
- * order should be reversed.
- */
- err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
- if (err < 0)
- return err;
-
- mutex_lock(&data->lock);
- if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) {
+ if (data->read_order) {
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
} else {
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
}
- mutex_unlock(&data->lock);
break;
case hwmon_temp_max:
- mutex_lock(&data->lock);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC);
- mutex_unlock(&data->lock);
break;
case hwmon_temp_min:
- mutex_lock(&data->lock);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC);
- mutex_unlock(&data->lock);
break;
default:
return -EINVAL;
@@ -122,6 +119,8 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
return temp_dec;
*val = sbtsi_reg_to_mc(temp_int, temp_dec);
+ if (data->ext_range_mode)
+ *val -= SBTSI_TEMP_EXT_RANGE_ADJ;
return 0;
}
@@ -146,18 +145,16 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
return -EINVAL;
}
+ if (data->ext_range_mode)
+ val += SBTSI_TEMP_EXT_RANGE_ADJ;
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
- mutex_lock(&data->lock);
err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int);
if (err)
- goto exit;
+ return err;
- err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
-exit:
- mutex_unlock(&data->lock);
- return err;
+ return i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
}
static umode_t sbtsi_is_visible(const void *data,
@@ -203,22 +200,28 @@ static int sbtsi_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct sbtsi_data *data;
+ int err;
data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
- mutex_init(&data->lock);
- hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info,
- NULL);
+ err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
+ if (err < 0)
+ return err;
+ data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err);
+ data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ &sbtsi_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id sbtsi_id[] = {
- {"sbtsi", 0},
+ {"sbtsi"},
{}
};
MODULE_DEVICE_TABLE(i2c, sbtsi_id);
@@ -232,7 +235,6 @@ static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
MODULE_DEVICE_TABLE(of, sbtsi_of_match);
static struct i2c_driver sbtsi_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "sbtsi",
.of_match_table = of_match_ptr(sbtsi_of_match),
diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c
index 1bbda3b05532..33e997b5c1f5 100644
--- a/drivers/hwmon/sch5627.c
+++ b/drivers/hwmon/sch5627.c
@@ -6,9 +6,13 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/bits.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
+#include <linux/pm.h>
#include <linux/init.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
@@ -32,6 +36,10 @@
#define SCH5627_REG_PRIMARY_ID 0x3f
#define SCH5627_REG_CTRL 0x40
+#define SCH5627_CTRL_START BIT(0)
+#define SCH5627_CTRL_LOCK BIT(1)
+#define SCH5627_CTRL_VBAT BIT(4)
+
#define SCH5627_NO_TEMPS 8
#define SCH5627_NO_FANS 4
#define SCH5627_NO_IN 5
@@ -67,11 +75,9 @@ static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = {
"VCC", "VTT", "VBAT", "VTR", "V_IN" };
struct sch5627_data {
+ struct regmap *regmap;
unsigned short addr;
u8 control;
- u8 temp_max[SCH5627_NO_TEMPS];
- u8 temp_crit[SCH5627_NO_TEMPS];
- u16 fan_min[SCH5627_NO_FANS];
struct mutex update_lock;
unsigned long last_battery; /* In jiffies */
@@ -86,6 +92,36 @@ struct sch5627_data {
u16 in[SCH5627_NO_IN];
};
+static const struct regmap_range sch5627_tunables_ranges[] = {
+ regmap_reg_range(0x57, 0x57),
+ regmap_reg_range(0x59, 0x59),
+ regmap_reg_range(0x5B, 0x5B),
+ regmap_reg_range(0x5D, 0x5D),
+ regmap_reg_range(0x5F, 0x5F),
+ regmap_reg_range(0x61, 0x69),
+ regmap_reg_range(0x96, 0x9B),
+ regmap_reg_range(0xA0, 0xA3),
+ regmap_reg_range(0x184, 0x184),
+ regmap_reg_range(0x186, 0x186),
+ regmap_reg_range(0x1A8, 0x1A9),
+};
+
+static const struct regmap_access_table sch5627_tunables_table = {
+ .yes_ranges = sch5627_tunables_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sch5627_tunables_ranges),
+};
+
+static const struct regmap_config sch5627_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .wr_table = &sch5627_tunables_table,
+ .rd_table = &sch5627_tunables_table,
+ .cache_type = REGCACHE_MAPLE,
+ .use_single_read = true,
+ .use_single_write = true,
+ .can_sleep = true,
+};
+
static int sch5627_update_temp(struct sch5627_data *data)
{
int ret = 0;
@@ -147,7 +183,8 @@ static int sch5627_update_in(struct sch5627_data *data)
/* Trigger a Vbat voltage measurement every 5 minutes */
if (time_after(jiffies, data->last_battery + 300 * HZ)) {
- sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL, data->control | 0x10);
+ sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
+ data->control | SCH5627_CTRL_VBAT);
data->last_battery = jiffies;
}
@@ -171,38 +208,6 @@ abort:
return ret;
}
-static int sch5627_read_limits(struct sch5627_data *data)
-{
- int i, val;
-
- for (i = 0; i < SCH5627_NO_TEMPS; i++) {
- /*
- * Note what SMSC calls ABS, is what lm_sensors calls max
- * (aka high), and HIGH is what lm_sensors calls crit.
- */
- val = sch56xx_read_virtual_reg(data->addr,
- SCH5627_REG_TEMP_ABS[i]);
- if (val < 0)
- return val;
- data->temp_max[i] = val;
-
- val = sch56xx_read_virtual_reg(data->addr,
- SCH5627_REG_TEMP_HIGH[i]);
- if (val < 0)
- return val;
- data->temp_crit[i] = val;
- }
- for (i = 0; i < SCH5627_NO_FANS; i++) {
- val = sch56xx_read_virtual_reg16(data->addr,
- SCH5627_REG_FAN_MIN[i]);
- if (val < 0)
- return val;
- data->fan_min[i] = val;
- }
-
- return 0;
-}
-
static int reg_to_temp(u16 reg)
{
return (reg * 625) / 10 - 64000;
@@ -223,11 +228,65 @@ static int reg_to_rpm(u16 reg)
return 5400540 / reg;
}
+static u8 sch5627_temp_limit_to_reg(long value)
+{
+ long limit = (value / 1000) + 64;
+
+ return clamp_val(limit, 0, U8_MAX);
+}
+
+static u16 sch5627_rpm_to_reg(long value)
+{
+ long pulses;
+
+ if (value <= 0)
+ return U16_MAX - 1;
+
+ pulses = 5400540 / value;
+
+ return clamp_val(pulses, 1, U16_MAX - 1);
+}
+
static umode_t sch5627_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
int channel)
{
- if (type == hwmon_pwm && attr == hwmon_pwm_auto_channels_temp)
- return 0644;
+ const struct sch5627_data *data = drvdata;
+
+ /* Once the lock bit is set, the virtual registers become read-only
+ * until the next power cycle.
+ */
+ if (data->control & SCH5627_CTRL_LOCK)
+ return 0444;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_min:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_auto_channels_temp:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
return 0444;
}
@@ -236,24 +295,37 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at
long *val)
{
struct sch5627_data *data = dev_get_drvdata(dev);
- int ret;
+ int ret, value;
switch (type) {
case hwmon_temp:
- ret = sch5627_update_temp(data);
- if (ret < 0)
- return ret;
switch (attr) {
case hwmon_temp_input:
+ ret = sch5627_update_temp(data);
+ if (ret < 0)
+ return ret;
+
*val = reg_to_temp(data->temp[channel]);
return 0;
case hwmon_temp_max:
- *val = reg_to_temp_limit(data->temp_max[channel]);
+ ret = regmap_read(data->regmap, SCH5627_REG_TEMP_ABS[channel], &value);
+ if (ret < 0)
+ return ret;
+
+ *val = reg_to_temp_limit((u8)value);
return 0;
case hwmon_temp_crit:
- *val = reg_to_temp_limit(data->temp_crit[channel]);
+ ret = regmap_read(data->regmap, SCH5627_REG_TEMP_HIGH[channel], &value);
+ if (ret < 0)
+ return ret;
+
+ *val = reg_to_temp_limit((u8)value);
return 0;
case hwmon_temp_fault:
+ ret = sch5627_update_temp(data);
+ if (ret < 0)
+ return ret;
+
*val = (data->temp[channel] == 0);
return 0;
default:
@@ -261,23 +333,35 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at
}
break;
case hwmon_fan:
- ret = sch5627_update_fan(data);
- if (ret < 0)
- return ret;
switch (attr) {
case hwmon_fan_input:
+ ret = sch5627_update_fan(data);
+ if (ret < 0)
+ return ret;
+
ret = reg_to_rpm(data->fan[channel]);
if (ret < 0)
return ret;
+
*val = ret;
return 0;
case hwmon_fan_min:
- ret = reg_to_rpm(data->fan_min[channel]);
+ ret = sch56xx_regmap_read16(data->regmap, SCH5627_REG_FAN_MIN[channel],
+ &value);
if (ret < 0)
return ret;
+
+ ret = reg_to_rpm((u16)value);
+ if (ret < 0)
+ return ret;
+
*val = ret;
return 0;
case hwmon_fan_fault:
+ ret = sch5627_update_fan(data);
+ if (ret < 0)
+ return ret;
+
*val = (data->fan[channel] == 0xffff);
return 0;
default:
@@ -287,15 +371,11 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_auto_channels_temp:
- mutex_lock(&data->update_lock);
- ret = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel]);
- mutex_unlock(&data->update_lock);
-
+ ret = regmap_read(data->regmap, SCH5627_REG_PWM_MAP[channel], &value);
if (ret < 0)
return ret;
- *val = ret;
-
+ *val = value;
return 0;
default:
break;
@@ -345,9 +425,33 @@ static int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 a
long val)
{
struct sch5627_data *data = dev_get_drvdata(dev);
- int ret;
+ u16 fan;
+ u8 temp;
switch (type) {
+ case hwmon_temp:
+ temp = sch5627_temp_limit_to_reg(val);
+
+ switch (attr) {
+ case hwmon_temp_max:
+ return regmap_write(data->regmap, SCH5627_REG_TEMP_ABS[channel], temp);
+ case hwmon_temp_crit:
+ return regmap_write(data->regmap, SCH5627_REG_TEMP_HIGH[channel], temp);
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_min:
+ fan = sch5627_rpm_to_reg(val);
+
+ return sch56xx_regmap_write16(data->regmap, SCH5627_REG_FAN_MIN[channel],
+ fan);
+ default:
+ break;
+ }
+ break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_auto_channels_temp:
@@ -355,12 +459,7 @@ static int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 a
if (val > U8_MAX || val < 0)
return -EINVAL;
- mutex_lock(&data->update_lock);
- ret = sch56xx_write_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel],
- val);
- mutex_unlock(&data->update_lock);
-
- return ret;
+ return regmap_write(data->regmap, SCH5627_REG_PWM_MAP[channel], val);
default:
break;
}
@@ -422,7 +521,7 @@ static int sch5627_probe(struct platform_device *pdev)
{
struct sch5627_data *data;
struct device *hwmon_dev;
- int err, build_code, build_id, hwmon_rev, val;
+ int build_code, build_id, hwmon_rev, val;
data = devm_kzalloc(&pdev->dev, sizeof(struct sch5627_data),
GFP_KERNEL);
@@ -483,24 +582,21 @@ static int sch5627_probe(struct platform_device *pdev)
return val;
data->control = val;
- if (!(data->control & 0x01)) {
+ if (!(data->control & SCH5627_CTRL_START)) {
pr_err("hardware monitoring not enabled\n");
return -ENODEV;
}
+
+ data->regmap = devm_regmap_init_sch56xx(&pdev->dev, &data->update_lock, data->addr,
+ &sch5627_regmap_config);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
/* Trigger a Vbat voltage measurement, so that we get a valid reading
the first time we read Vbat */
- sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
- data->control | 0x10);
+ sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL, data->control | SCH5627_CTRL_VBAT);
data->last_battery = jiffies;
- /*
- * Read limits, we do this only once as reading a register on
- * the sch5627 is quite expensive (and they don't change).
- */
- err = sch5627_read_limits(data);
- if (err)
- return err;
-
pr_info("found %s chip at %#hx\n", DEVNAME, data->addr);
pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n",
build_code, build_id, hwmon_rev);
@@ -518,6 +614,30 @@ static int sch5627_probe(struct platform_device *pdev)
return 0;
}
+static int sch5627_suspend(struct device *dev)
+{
+ struct sch5627_data *data = dev_get_drvdata(dev);
+
+ regcache_cache_only(data->regmap, true);
+ regcache_mark_dirty(data->regmap);
+
+ return 0;
+}
+
+static int sch5627_resume(struct device *dev)
+{
+ struct sch5627_data *data = dev_get_drvdata(dev);
+
+ regcache_cache_only(data->regmap, false);
+ /* We must not access the virtual registers when the lock bit is set */
+ if (data->control & SCH5627_CTRL_LOCK)
+ return regcache_drop_region(data->regmap, 0, U16_MAX);
+
+ return regcache_sync(data->regmap);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(sch5627_dev_pm_ops, sch5627_suspend, sch5627_resume);
+
static const struct platform_device_id sch5627_device_id[] = {
{
.name = "sch5627",
@@ -529,6 +649,7 @@ MODULE_DEVICE_TABLE(platform, sch5627_device_id);
static struct platform_driver sch5627_driver = {
.driver = {
.name = DRVNAME,
+ .pm = pm_sleep_ptr(&sch5627_dev_pm_ops),
},
.probe = sch5627_probe,
.id_table = sch5627_device_id,
diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c
index 269757bc3a9e..d00bd5cc6b15 100644
--- a/drivers/hwmon/sch5636.c
+++ b/drivers/hwmon/sch5636.c
@@ -367,7 +367,7 @@ static struct sensor_device_attribute sch5636_fan_attr[] = {
SENSOR_ATTR_RO(fan8_alarm, fan_alarm, 7),
};
-static int sch5636_remove(struct platform_device *pdev)
+static void sch5636_remove(struct platform_device *pdev)
{
struct sch5636_data *data = platform_get_drvdata(pdev);
int i;
@@ -385,8 +385,6 @@ static int sch5636_remove(struct platform_device *pdev)
for (i = 0; i < SCH5636_NO_FANS * 3; i++)
device_remove_file(&pdev->dev,
&sch5636_fan_attr[i].dev_attr);
-
- return 0;
}
static int sch5636_probe(struct platform_device *pdev)
@@ -418,8 +416,7 @@ static int sch5636_probe(struct platform_device *pdev)
id[i] = '\0';
if (strcmp(id, "THS")) {
- pr_err("Unknown Fujitsu id: %02x%02x%02x\n",
- id[0], id[1], id[2]);
+ pr_err("Unknown Fujitsu id: %3pE (%3ph)\n", id, id);
err = -ENODEV;
goto error;
}
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c
index de3a0886c2f7..98e075e54e9d 100644
--- a/drivers/hwmon/sch56xx-common.c
+++ b/drivers/hwmon/sch56xx-common.c
@@ -7,10 +7,9 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
-#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/platform_device.h>
-#include <linux/dmi.h>
+#include <linux/regmap.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/acpi.h>
@@ -21,10 +20,7 @@
#include <linux/slab.h>
#include "sch56xx-common.h"
-static bool ignore_dmi;
-module_param(ignore_dmi, bool, 0);
-MODULE_PARM_DESC(ignore_dmi, "Omit DMI check for supported devices (default=0)");
-
+/* Insmod parameters */
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
@@ -64,6 +60,11 @@ struct sch56xx_watchdog_data {
u8 watchdog_output_enable;
};
+struct sch56xx_bus_context {
+ struct mutex *lock; /* Used to serialize access to the mailbox registers */
+ u16 addr;
+};
+
static struct platform_device *sch56xx_pdev;
/* Super I/O functions */
@@ -244,6 +245,107 @@ int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
EXPORT_SYMBOL(sch56xx_read_virtual_reg12);
/*
+ * Regmap support
+ */
+
+int sch56xx_regmap_read16(struct regmap *map, unsigned int reg, unsigned int *val)
+{
+ int lsb, msb, ret;
+
+ /* See sch56xx_read_virtual_reg16() */
+ ret = regmap_read(map, reg, &lsb);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(map, reg + 1, &msb);
+ if (ret < 0)
+ return ret;
+
+ *val = lsb | (msb << 8);
+
+ return 0;
+}
+EXPORT_SYMBOL(sch56xx_regmap_read16);
+
+int sch56xx_regmap_write16(struct regmap *map, unsigned int reg, unsigned int val)
+{
+ int ret;
+
+ ret = regmap_write(map, reg, val & 0xff);
+ if (ret < 0)
+ return ret;
+
+ return regmap_write(map, reg + 1, (val >> 8) & 0xff);
+}
+EXPORT_SYMBOL(sch56xx_regmap_write16);
+
+static int sch56xx_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct sch56xx_bus_context *bus = context;
+ int ret;
+
+ mutex_lock(bus->lock);
+ ret = sch56xx_write_virtual_reg(bus->addr, (u16)reg, (u8)val);
+ mutex_unlock(bus->lock);
+
+ return ret;
+}
+
+static int sch56xx_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct sch56xx_bus_context *bus = context;
+ int ret;
+
+ mutex_lock(bus->lock);
+ ret = sch56xx_read_virtual_reg(bus->addr, (u16)reg);
+ mutex_unlock(bus->lock);
+
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+
+ return 0;
+}
+
+static void sch56xx_free_context(void *context)
+{
+ kfree(context);
+}
+
+static const struct regmap_bus sch56xx_bus = {
+ .reg_write = sch56xx_reg_write,
+ .reg_read = sch56xx_reg_read,
+ .free_context = sch56xx_free_context,
+ .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+};
+
+struct regmap *devm_regmap_init_sch56xx(struct device *dev, struct mutex *lock, u16 addr,
+ const struct regmap_config *config)
+{
+ struct sch56xx_bus_context *context;
+ struct regmap *map;
+
+ if (config->reg_bits != 16 && config->val_bits != 8)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ context = kzalloc(sizeof(*context), GFP_KERNEL);
+ if (!context)
+ return ERR_PTR(-ENOMEM);
+
+ context->lock = lock;
+ context->addr = addr;
+
+ map = devm_regmap_init(dev, &sch56xx_bus, context, config);
+ if (IS_ERR(map))
+ kfree(context);
+
+ return map;
+}
+EXPORT_SYMBOL(devm_regmap_init_sch56xx);
+
+/*
* Watchdog routines
*/
@@ -442,10 +544,8 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision,
watchdog_set_drvdata(&data->wddev, data);
err = devm_watchdog_register_device(parent, &data->wddev);
- if (err) {
- pr_err("Registering watchdog chardev: %d\n", err);
+ if (err)
devm_kfree(parent, data);
- }
}
EXPORT_SYMBOL(sch56xx_watchdog_register);
@@ -523,66 +623,11 @@ static int __init sch56xx_device_add(int address, const char *name)
return PTR_ERR_OR_ZERO(sch56xx_pdev);
}
-static const struct dmi_system_id sch56xx_dmi_override_table[] __initconst = {
- {
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
- DMI_MATCH(DMI_PRODUCT_NAME, "CELSIUS W380"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
- DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO P710"),
- },
- },
- {
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
- DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO E9900"),
- },
- },
- { }
-};
-
-/* For autoloading only */
-static const struct dmi_system_id sch56xx_dmi_table[] __initconst = {
- {
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
- },
- },
- { }
-};
-MODULE_DEVICE_TABLE(dmi, sch56xx_dmi_table);
-
static int __init sch56xx_init(void)
{
- const char *name = NULL;
int address;
+ const char *name = NULL;
- if (!ignore_dmi) {
- if (!dmi_check_system(sch56xx_dmi_table))
- return -ENODEV;
-
- if (!dmi_check_system(sch56xx_dmi_override_table)) {
- /*
- * Some machines like the Esprimo P720 and Esprimo C700 have
- * onboard devices named " Antiope"/" Theseus" instead of
- * "Antiope"/"Theseus", so we need to check for both.
- */
- if (!dmi_find_device(DMI_DEV_TYPE_OTHER, "Antiope", NULL) &&
- !dmi_find_device(DMI_DEV_TYPE_OTHER, " Antiope", NULL) &&
- !dmi_find_device(DMI_DEV_TYPE_OTHER, "Theseus", NULL) &&
- !dmi_find_device(DMI_DEV_TYPE_OTHER, " Theseus", NULL))
- return -ENODEV;
- }
- }
-
- /*
- * Some devices like the Esprimo C700 have both onboard devices,
- * so we still have to check manually
- */
address = sch56xx_find(0x4e, &name);
if (address < 0)
address = sch56xx_find(0x2e, &name);
diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h
index e907d9da0dd5..601987c6b4cd 100644
--- a/drivers/hwmon/sch56xx-common.h
+++ b/drivers/hwmon/sch56xx-common.h
@@ -5,9 +5,15 @@
***************************************************************************/
#include <linux/mutex.h>
+#include <linux/regmap.h>
struct sch56xx_watchdog_data;
+struct regmap *devm_regmap_init_sch56xx(struct device *dev, struct mutex *lock, u16 addr,
+ const struct regmap_config *config);
+int sch56xx_regmap_read16(struct regmap *map, unsigned int reg, unsigned int *val);
+int sch56xx_regmap_write16(struct regmap *map, unsigned int reg, unsigned int val);
+
int sch56xx_read_virtual_reg(u16 addr, u16 reg);
int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val);
int sch56xx_read_virtual_reg16(u16 addr, u16 reg);
@@ -16,4 +22,3 @@ int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision,
struct mutex *io_lock, int check_enabled);
-void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data);
diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c
index 364199b332c0..eec223d174c0 100644
--- a/drivers/hwmon/scmi-hwmon.c
+++ b/drivers/hwmon/scmi-hwmon.c
@@ -240,6 +240,8 @@ static int scmi_hwmon_probe(struct scmi_device *sdev)
const struct hwmon_channel_info **ptr_scmi_ci;
const struct scmi_handle *handle = sdev->handle;
struct scmi_protocol_handle *ph;
+ u32 sensor_config = FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK,
+ SCMI_SENS_CFG_SENSOR_ENABLE);
if (!handle)
return -ENODEV;
@@ -339,6 +341,13 @@ static int scmi_hwmon_probe(struct scmi_device *sdev)
if (!sensor)
continue;
+ ret = sensor_ops->config_set(ph, i, sensor_config);
+ if (ret) {
+ dev_err(dev, "Error enabling sensor %s. err=%d\n",
+ sensor->name, ret);
+ continue;
+ }
+
/*
* Warn on any misconfiguration related to thermal zones but
* bail out of probing only on memory errors.
diff --git a/drivers/hwmon/sfctemp.c b/drivers/hwmon/sfctemp.c
index fb1da93383d7..b78b2c099a12 100644
--- a/drivers/hwmon/sfctemp.c
+++ b/drivers/hwmon/sfctemp.c
@@ -10,7 +10,6 @@
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
@@ -49,8 +48,6 @@
#define SFCTEMP_K1000 81100L
struct sfctemp {
- /* serialize access to hardware register and enabled below */
- struct mutex lock;
void __iomem *regs;
struct clk *clk_sense;
struct clk *clk_bus;
@@ -92,15 +89,14 @@ static void sfctemp_stop(struct sfctemp *sfctemp)
static int sfctemp_enable(struct sfctemp *sfctemp)
{
- int ret = 0;
+ int ret;
- mutex_lock(&sfctemp->lock);
if (sfctemp->enabled)
- goto done;
+ return 0;
ret = clk_prepare_enable(sfctemp->clk_bus);
if (ret)
- goto err;
+ return ret;
ret = reset_control_deassert(sfctemp->rst_bus);
if (ret)
goto err_disable_bus;
@@ -115,9 +111,7 @@ static int sfctemp_enable(struct sfctemp *sfctemp)
sfctemp_power_up(sfctemp);
sfctemp_run(sfctemp);
sfctemp->enabled = true;
-done:
- mutex_unlock(&sfctemp->lock);
- return ret;
+ return 0;
err_disable_sense:
clk_disable_unprepare(sfctemp->clk_sense);
@@ -125,16 +119,13 @@ err_assert_bus:
reset_control_assert(sfctemp->rst_bus);
err_disable_bus:
clk_disable_unprepare(sfctemp->clk_bus);
-err:
- mutex_unlock(&sfctemp->lock);
return ret;
}
static int sfctemp_disable(struct sfctemp *sfctemp)
{
- mutex_lock(&sfctemp->lock);
if (!sfctemp->enabled)
- goto done;
+ return 0;
sfctemp_stop(sfctemp);
sfctemp_power_down(sfctemp);
@@ -143,8 +134,6 @@ static int sfctemp_disable(struct sfctemp *sfctemp)
reset_control_assert(sfctemp->rst_bus);
clk_disable_unprepare(sfctemp->clk_bus);
sfctemp->enabled = false;
-done:
- mutex_unlock(&sfctemp->lock);
return 0;
}
@@ -155,22 +144,14 @@ static void sfctemp_disable_action(void *data)
static int sfctemp_convert(struct sfctemp *sfctemp, long *val)
{
- int ret;
-
- mutex_lock(&sfctemp->lock);
- if (!sfctemp->enabled) {
- ret = -ENODATA;
- goto out;
- }
+ if (!sfctemp->enabled)
+ return -ENODATA;
/* calculate temperature in milli Celcius */
*val = (long)((readl(sfctemp->regs) & SFCTEMP_DOUT_MSK) >> SFCTEMP_DOUT_POS)
* SFCTEMP_Y1000 / SFCTEMP_Z - SFCTEMP_K1000;
- ret = 0;
-out:
- mutex_unlock(&sfctemp->lock);
- return ret;
+ return 0;
}
static umode_t sfctemp_is_visible(const void *data, enum hwmon_sensor_types type,
@@ -263,7 +244,6 @@ static int sfctemp_probe(struct platform_device *pdev)
return -ENOMEM;
dev_set_drvdata(dev, sfctemp);
- mutex_init(&sfctemp->lock);
sfctemp->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sfctemp->regs))
diff --git a/drivers/hwmon/sg2042-mcu.c b/drivers/hwmon/sg2042-mcu.c
new file mode 100644
index 000000000000..105131c4acf7
--- /dev/null
+++ b/drivers/hwmon/sg2042-mcu.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Inochi Amaoto <inochiama@outlook.com>
+ *
+ * Sophgo power control mcu for SG2042
+ */
+
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+/* fixed MCU registers */
+#define REG_BOARD_TYPE 0x00
+#define REG_MCU_FIRMWARE_VERSION 0x01
+#define REG_PCB_VERSION 0x02
+#define REG_PWR_CTRL 0x03
+#define REG_SOC_TEMP 0x04
+#define REG_BOARD_TEMP 0x05
+#define REG_RST_COUNT 0x0a
+#define REG_UPTIME 0x0b
+#define REG_RESET_REASON 0x0d
+#define REG_MCU_TYPE 0x18
+#define REG_REPOWER_POLICY 0x65
+#define REG_CRITICAL_TEMP 0x66
+#define REG_REPOWER_TEMP 0x67
+
+#define REPOWER_POLICY_REBOOT 1
+#define REPOWER_POLICY_KEEP_OFF 2
+
+#define MCU_POWER_MAX 0xff
+
+#define DEFINE_MCU_DEBUG_ATTR(_name, _reg, _format) \
+ static int _name##_show(struct seq_file *seqf, \
+ void *unused) \
+ { \
+ struct sg2042_mcu_data *mcu = seqf->private; \
+ int ret; \
+ ret = i2c_smbus_read_byte_data(mcu->client, (_reg)); \
+ if (ret < 0) \
+ return ret; \
+ seq_printf(seqf, _format "\n", ret); \
+ return 0; \
+ } \
+ DEFINE_SHOW_ATTRIBUTE(_name) \
+
+struct sg2042_mcu_data {
+ struct i2c_client *client;
+ struct mutex mutex;
+};
+
+static ssize_t reset_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(mcu->client, REG_RST_COUNT);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t uptime_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ u8 time_val[2];
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(mcu->client, REG_UPTIME,
+ sizeof(time_val), time_val);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n",
+ (time_val[0]) | (time_val[1] << 8));
+}
+
+static ssize_t reset_reason_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(mcu->client, REG_RESET_REASON);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "0x%02x\n", ret);
+}
+
+static ssize_t repower_policy_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ int ret;
+ const char *action;
+
+ ret = i2c_smbus_read_byte_data(mcu->client, REG_REPOWER_POLICY);
+ if (ret < 0)
+ return ret;
+
+ if (ret == REPOWER_POLICY_REBOOT)
+ action = "repower";
+ else if (ret == REPOWER_POLICY_KEEP_OFF)
+ action = "keep";
+ else
+ action = "unknown";
+
+ return sprintf(buf, "%s\n", action);
+}
+
+static ssize_t repower_policy_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ u8 value;
+ int ret;
+
+ if (sysfs_streq("repower", buf))
+ value = REPOWER_POLICY_REBOOT;
+ else if (sysfs_streq("keep", buf))
+ value = REPOWER_POLICY_KEEP_OFF;
+ else
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte_data(mcu->client,
+ REG_REPOWER_POLICY, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_RO(reset_count);
+static DEVICE_ATTR_RO(uptime);
+static DEVICE_ATTR_RO(reset_reason);
+static DEVICE_ATTR_RW(repower_policy);
+
+DEFINE_MCU_DEBUG_ATTR(firmware_version, REG_MCU_FIRMWARE_VERSION, "0x%02x");
+DEFINE_MCU_DEBUG_ATTR(pcb_version, REG_PCB_VERSION, "0x%02x");
+DEFINE_MCU_DEBUG_ATTR(board_type, REG_BOARD_TYPE, "0x%02x");
+DEFINE_MCU_DEBUG_ATTR(mcu_type, REG_MCU_TYPE, "%d");
+
+static struct attribute *sg2042_mcu_attrs[] = {
+ &dev_attr_reset_count.attr,
+ &dev_attr_uptime.attr,
+ &dev_attr_reset_reason.attr,
+ &dev_attr_repower_policy.attr,
+ NULL
+};
+
+static const struct attribute_group sg2042_mcu_attr_group = {
+ .attrs = sg2042_mcu_attrs,
+};
+
+static const struct attribute_group *sg2042_mcu_groups[] = {
+ &sg2042_mcu_attr_group,
+ NULL
+};
+
+static const struct hwmon_channel_info * const sg2042_mcu_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_CRIT |
+ HWMON_T_CRIT_HYST,
+ HWMON_T_INPUT),
+ NULL
+};
+
+static int sg2042_mcu_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ int tmp;
+ u8 reg;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = channel ? REG_BOARD_TEMP : REG_SOC_TEMP;
+ break;
+ case hwmon_temp_crit:
+ reg = REG_CRITICAL_TEMP;
+ break;
+ case hwmon_temp_crit_hyst:
+ reg = REG_REPOWER_TEMP;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ tmp = i2c_smbus_read_byte_data(mcu->client, reg);
+ if (tmp < 0)
+ return tmp;
+ *val = tmp * 1000;
+
+ return 0;
+}
+
+static int sg2042_mcu_write(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct sg2042_mcu_data *mcu = dev_get_drvdata(dev);
+ int temp = val / 1000;
+ int hyst_temp, crit_temp;
+ u8 reg;
+
+ temp = clamp_val(temp, 0, MCU_POWER_MAX);
+
+ guard(mutex)(&mcu->mutex);
+
+ switch (attr) {
+ case hwmon_temp_crit:
+ hyst_temp = i2c_smbus_read_byte_data(mcu->client,
+ REG_REPOWER_TEMP);
+ if (hyst_temp < 0)
+ return hyst_temp;
+
+ crit_temp = temp;
+ reg = REG_CRITICAL_TEMP;
+ break;
+ case hwmon_temp_crit_hyst:
+ crit_temp = i2c_smbus_read_byte_data(mcu->client,
+ REG_CRITICAL_TEMP);
+ if (crit_temp < 0)
+ return crit_temp;
+
+ hyst_temp = temp;
+ reg = REG_REPOWER_TEMP;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * ensure hyst_temp is smaller to avoid MCU from
+ * keeping triggering repower event.
+ */
+ if (crit_temp < hyst_temp)
+ return -EINVAL;
+
+ return i2c_smbus_write_byte_data(mcu->client, reg, temp);
+}
+
+static umode_t sg2042_mcu_is_visible(const void *_data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ case hwmon_temp_crit:
+ case hwmon_temp_crit_hyst:
+ if (channel == 0)
+ return 0644;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct hwmon_ops sg2042_mcu_ops = {
+ .is_visible = sg2042_mcu_is_visible,
+ .read = sg2042_mcu_read,
+ .write = sg2042_mcu_write,
+};
+
+static const struct hwmon_chip_info sg2042_mcu_chip_info = {
+ .ops = &sg2042_mcu_ops,
+ .info = sg2042_mcu_info,
+};
+
+static void sg2042_mcu_debugfs_init(struct sg2042_mcu_data *mcu)
+{
+ debugfs_create_file("firmware_version", 0444, mcu->client->debugfs,
+ mcu, &firmware_version_fops);
+ debugfs_create_file("pcb_version", 0444, mcu->client->debugfs, mcu,
+ &pcb_version_fops);
+ debugfs_create_file("mcu_type", 0444, mcu->client->debugfs, mcu,
+ &mcu_type_fops);
+ debugfs_create_file("board_type", 0444, mcu->client->debugfs, mcu,
+ &board_type_fops);
+}
+
+static int sg2042_mcu_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sg2042_mcu_data *mcu;
+ struct device *hwmon_dev;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA))
+ return -ENODEV;
+
+ mcu = devm_kmalloc(dev, sizeof(*mcu), GFP_KERNEL);
+ if (!mcu)
+ return -ENOMEM;
+
+ mutex_init(&mcu->mutex);
+ mcu->client = client;
+
+ i2c_set_clientdata(client, mcu);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "sg2042_mcu",
+ mcu,
+ &sg2042_mcu_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ sg2042_mcu_debugfs_init(mcu);
+
+ return 0;
+}
+
+static const struct i2c_device_id sg2042_mcu_id[] = {
+ { "sg2042-hwmon-mcu" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id);
+
+static const struct of_device_id sg2042_mcu_of_id[] = {
+ { .compatible = "sophgo,sg2042-hwmon-mcu" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id);
+
+static struct i2c_driver sg2042_mcu_driver = {
+ .driver = {
+ .name = "sg2042-mcu",
+ .of_match_table = sg2042_mcu_of_id,
+ .dev_groups = sg2042_mcu_groups,
+ },
+ .probe = sg2042_mcu_i2c_probe,
+ .id_table = sg2042_mcu_id,
+};
+module_i2c_driver(sg2042_mcu_driver);
+
+MODULE_AUTHOR("Inochi Amaoto <inochiama@outlook.com>");
+MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c
index 32a41fc56fc9..3d55047e9baf 100644
--- a/drivers/hwmon/sht15.c
+++ b/drivers/hwmon/sht15.c
@@ -1017,7 +1017,7 @@ err_release_reg:
return ret;
}
-static int sht15_remove(struct platform_device *pdev)
+static void sht15_remove(struct platform_device *pdev)
{
struct sht15_data *data = platform_get_drvdata(pdev);
int ret;
@@ -1033,8 +1033,6 @@ static int sht15_remove(struct platform_device *pdev)
regulator_unregister_notifier(data->reg, &data->nb);
regulator_disable(data->reg);
}
-
- return 0;
}
static const struct platform_device_id sht15_device_ids[] = {
diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c
index 55c179475208..627d35070a42 100644
--- a/drivers/hwmon/sht21.c
+++ b/drivers/hwmon/sht21.c
@@ -199,10 +199,7 @@ static ssize_t eic_read(struct sht21 *sht21)
eic[6] = rx[0];
eic[7] = rx[1];
- ret = snprintf(sht21->eic, sizeof(sht21->eic),
- "%02x%02x%02x%02x%02x%02x%02x%02x\n",
- eic[0], eic[1], eic[2], eic[3],
- eic[4], eic[5], eic[6], eic[7]);
+ ret = snprintf(sht21->eic, sizeof(sht21->eic), "%8phN\n", eic);
out:
if (ret < 0)
sht21->eic[0] = 0;
@@ -278,13 +275,26 @@ static int sht21_probe(struct i2c_client *client)
/* Device ID table */
static const struct i2c_device_id sht21_id[] = {
- { "sht21", 0 },
+ { "sht20" },
+ { "sht21" },
+ { "sht25" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sht21_id);
+static const struct of_device_id sht21_of_match[] = {
+ { .compatible = "sensirion,sht20" },
+ { .compatible = "sensirion,sht21" },
+ { .compatible = "sensirion,sht25" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sht21_of_match);
+
static struct i2c_driver sht21_driver = {
- .driver.name = "sht21",
+ .driver = {
+ .name = "sht21",
+ .of_match_table = sht21_of_match,
+ },
.probe = sht21_probe,
.id_table = sht21_id,
};
diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c
index 79657910b79e..f36c0229328f 100644
--- a/drivers/hwmon/sht3x.c
+++ b/drivers/hwmon/sht3x.c
@@ -10,6 +10,7 @@
#include <asm/page.h>
#include <linux/crc8.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hwmon.h>
@@ -41,6 +42,7 @@ static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 };
/* other commands */
static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d };
static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 };
+static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 };
/* delays for single-shot mode i2c commands, both in us */
#define SHT3X_SINGLE_WAIT_TIME_HPM 15000
@@ -169,6 +171,7 @@ struct sht3x_data {
u32 wait_time; /* in us*/
unsigned long last_update; /* last update in periodic mode*/
enum sht3x_repeatability repeatability;
+ u32 serial_number;
/*
* cached values for temperature and humidity and limits
@@ -288,24 +291,26 @@ out:
return data;
}
-static int temp1_input_read(struct device *dev)
+static int temp1_input_read(struct device *dev, long *temp)
{
struct sht3x_data *data = sht3x_update_client(dev);
if (IS_ERR(data))
return PTR_ERR(data);
- return data->temperature;
+ *temp = data->temperature;
+ return 0;
}
-static int humidity1_input_read(struct device *dev)
+static int humidity1_input_read(struct device *dev, long *humidity)
{
struct sht3x_data *data = sht3x_update_client(dev);
if (IS_ERR(data))
return PTR_ERR(data);
- return data->humidity;
+ *humidity = data->humidity;
+ return 0;
}
/*
@@ -703,6 +708,7 @@ static int sht3x_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
enum sht3x_limits index;
+ int ret;
switch (type) {
case hwmon_chip:
@@ -717,10 +723,12 @@ static int sht3x_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
- *val = temp1_input_read(dev);
- break;
+ return temp1_input_read(dev, val);
case hwmon_temp_alarm:
- *val = temp1_alarm_read(dev);
+ ret = temp1_alarm_read(dev);
+ if (ret < 0)
+ return ret;
+ *val = ret;
break;
case hwmon_temp_max:
index = limit_max;
@@ -745,10 +753,12 @@ static int sht3x_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_humidity:
switch (attr) {
case hwmon_humidity_input:
- *val = humidity1_input_read(dev);
- break;
+ return humidity1_input_read(dev, val);
case hwmon_humidity_alarm:
- *val = humidity1_alarm_read(dev);
+ ret = humidity1_alarm_read(dev);
+ if (ret < 0)
+ return ret;
+ *val = ret;
break;
case hwmon_humidity_max:
index = limit_max;
@@ -831,6 +841,25 @@ static int sht3x_write(struct device *dev, enum hwmon_sensor_types type,
}
}
+static void sht3x_serial_number_read(struct sht3x_data *data)
+{
+ int ret;
+ char buffer[SHT3X_RESPONSE_LENGTH];
+ struct i2c_client *client = data->client;
+
+ ret = sht3x_read_from_command(client, data,
+ sht3x_cmd_read_serial_number,
+ buffer,
+ SHT3X_RESPONSE_LENGTH, 0);
+ if (ret)
+ return;
+
+ data->serial_number = (buffer[0] << 24) | (buffer[1] << 16) |
+ (buffer[3] << 8) | buffer[4];
+
+ debugfs_create_u32("serial_number", 0444, client->debugfs, &data->serial_number);
+}
+
static const struct hwmon_ops sht3x_ops = {
.is_visible = sht3x_is_visible,
.read = sht3x_read,
@@ -842,15 +871,6 @@ static const struct hwmon_chip_info sht3x_chip_info = {
.info = sht3x_channel_info,
};
-/* device ID table */
-static const struct i2c_device_id sht3x_ids[] = {
- {"sht3x", sht3x},
- {"sts3x", sts3x},
- {}
-};
-
-MODULE_DEVICE_TABLE(i2c, sht3x_ids);
-
static int sht3x_probe(struct i2c_client *client)
{
int ret;
@@ -880,7 +900,7 @@ static int sht3x_probe(struct i2c_client *client)
data->mode = 0;
data->last_update = jiffies - msecs_to_jiffies(3000);
data->client = client;
- data->chip_id = i2c_match_id(sht3x_ids, client)->driver_data;
+ data->chip_id = (uintptr_t)i2c_get_match_data(client);
crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL);
sht3x_select_command(data);
@@ -899,24 +919,30 @@ static int sht3x_probe(struct i2c_client *client)
if (ret)
return ret;
- hwmon_dev = devm_hwmon_device_register_with_info(dev,
- client->name,
- data,
- &sht3x_chip_info,
- sht3x_groups);
-
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ &sht3x_chip_info, sht3x_groups);
if (IS_ERR(hwmon_dev))
- dev_dbg(dev, "unable to register hwmon device\n");
+ return PTR_ERR(hwmon_dev);
+
+ sht3x_serial_number_read(data);
- return PTR_ERR_OR_ZERO(hwmon_dev);
+ return 0;
}
+/* device ID table */
+static const struct i2c_device_id sht3x_ids[] = {
+ {"sht3x", sht3x},
+ {"sts3x", sts3x},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, sht3x_ids);
+
static struct i2c_driver sht3x_i2c_driver = {
.driver.name = "sht3x",
.probe = sht3x_probe,
.id_table = sht3x_ids,
};
-
module_i2c_driver(sht3x_i2c_driver);
MODULE_AUTHOR("David Frey <david.frey@sensirion.com>");
diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c
index 7ee797410458..5abe1227e109 100644
--- a/drivers/hwmon/sht4x.c
+++ b/drivers/hwmon/sht4x.c
@@ -11,6 +11,7 @@
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/jiffies.h>
#include <linux/module.h>
@@ -31,6 +32,12 @@
*/
#define SHT4X_CMD_MEASURE_HPM 0b11111101
#define SHT4X_CMD_RESET 0b10010100
+#define SHT4X_CMD_HEATER_20_1 0b00011110
+#define SHT4X_CMD_HEATER_20_01 0b00010101
+#define SHT4X_CMD_HEATER_110_1 0b00101111
+#define SHT4X_CMD_HEATER_110_01 0b00100100
+#define SHT4X_CMD_HEATER_200_1 0b00111001
+#define SHT4X_CMD_HEATER_200_01 0b00110010
#define SHT4X_CMD_LEN 1
#define SHT4X_CRC8_LEN 1
@@ -48,7 +55,11 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table);
/**
* struct sht4x_data - All the data required to operate an SHT4X chip
* @client: the i2c client associated with the SHT4X
- * @lock: a mutex that is used to prevent parallel access to the i2c client
+ * @heating_complete: the time that the last heating finished
+ * @data_pending: true if and only if there are measurements to retrieve after heating
+ * @heater_power: the power at which the heater will be started
+ * @heater_time: the time for which the heater will remain turned on
+ * @valid: validity of fields below
* @update_interval: the minimum poll interval
* @last_updated: the previous time that the SHT4X was polled
* @temperature: the latest temperature value received from the SHT4X
@@ -56,7 +67,10 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table);
*/
struct sht4x_data {
struct i2c_client *client;
- struct mutex lock; /* atomic read data updates */
+ unsigned long heating_complete; /* in jiffies */
+ bool data_pending;
+ u32 heater_power; /* in milli-watts */
+ u32 heater_time; /* in milli-seconds */
bool valid; /* validity of fields below */
long update_interval; /* in milli-seconds */
long last_updated; /* in jiffies */
@@ -66,37 +80,46 @@ struct sht4x_data {
/**
* sht4x_read_values() - read and parse the raw data from the SHT4X
- * @sht4x_data: the struct sht4x_data to use for the lock
+ * @data: the struct sht4x_data to use for the lock
* Return: 0 if successful, -ERRNO if not
*/
static int sht4x_read_values(struct sht4x_data *data)
{
- int ret = 0;
+ int ret;
u16 t_ticks, rh_ticks;
unsigned long next_update;
struct i2c_client *client = data->client;
u8 crc;
u8 cmd[SHT4X_CMD_LEN] = {SHT4X_CMD_MEASURE_HPM};
u8 raw_data[SHT4X_RESPONSE_LENGTH];
+ unsigned long curr_jiffies;
- mutex_lock(&data->lock);
- next_update = data->last_updated +
- msecs_to_jiffies(data->update_interval);
+ curr_jiffies = jiffies;
+ if (time_before(curr_jiffies, data->heating_complete))
+ msleep(jiffies_to_msecs(data->heating_complete - curr_jiffies));
- if (data->valid && time_before_eq(jiffies, next_update))
- goto unlock;
+ if (data->data_pending &&
+ time_before(jiffies, data->heating_complete + data->update_interval)) {
+ data->data_pending = false;
+ } else {
+ next_update = data->last_updated +
+ msecs_to_jiffies(data->update_interval);
- ret = i2c_master_send(client, cmd, SHT4X_CMD_LEN);
- if (ret < 0)
- goto unlock;
+ if (data->valid && time_before_eq(jiffies, next_update))
+ return 0;
+
+ ret = i2c_master_send(client, cmd, SHT4X_CMD_LEN);
+ if (ret < 0)
+ return ret;
- usleep_range(SHT4X_MEAS_DELAY_HPM, SHT4X_MEAS_DELAY_HPM + SHT4X_DELAY_EXTRA);
+ usleep_range(SHT4X_MEAS_DELAY_HPM, SHT4X_MEAS_DELAY_HPM + SHT4X_DELAY_EXTRA);
+ }
ret = i2c_master_recv(client, raw_data, SHT4X_RESPONSE_LENGTH);
if (ret != SHT4X_RESPONSE_LENGTH) {
if (ret >= 0)
ret = -ENODATA;
- goto unlock;
+ return ret;
}
t_ticks = raw_data[0] << 8 | raw_data[1];
@@ -105,26 +128,20 @@ static int sht4x_read_values(struct sht4x_data *data)
crc = crc8(sht4x_crc8_table, &raw_data[0], SHT4X_WORD_LEN, CRC8_INIT_VALUE);
if (crc != raw_data[2]) {
dev_err(&client->dev, "data integrity check failed\n");
- ret = -EIO;
- goto unlock;
+ return -EIO;
}
crc = crc8(sht4x_crc8_table, &raw_data[3], SHT4X_WORD_LEN, CRC8_INIT_VALUE);
if (crc != raw_data[5]) {
dev_err(&client->dev, "data integrity check failed\n");
- ret = -EIO;
- goto unlock;
+ return -EIO;
}
data->temperature = ((21875 * (int32_t)t_ticks) >> 13) - 45000;
data->humidity = ((15625 * (int32_t)rh_ticks) >> 13) - 6000;
data->last_updated = jiffies;
data->valid = true;
- ret = 0;
-
-unlock:
- mutex_unlock(&data->lock);
- return ret;
+ return 0;
}
static ssize_t sht4x_interval_write(struct sht4x_data *data, long val)
@@ -214,6 +231,137 @@ static int sht4x_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
}
}
+static ssize_t heater_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", time_before(jiffies, data->heating_complete));
+}
+
+static ssize_t heater_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+ bool status;
+ ssize_t ret;
+ u8 cmd;
+ u32 heating_time_bound;
+
+ ret = kstrtobool(buf, &status);
+ if (ret)
+ return ret;
+ if (!status)
+ return -EINVAL;
+
+ if (data->heater_time == 100) {
+ if (data->heater_power == 20)
+ cmd = SHT4X_CMD_HEATER_20_01;
+ else if (data->heater_power == 110)
+ cmd = SHT4X_CMD_HEATER_110_01;
+ else /* data->heater_power == 200 */
+ cmd = SHT4X_CMD_HEATER_200_01;
+
+ heating_time_bound = 110;
+ } else { /* data->heater_time == 1000 */
+ if (data->heater_power == 20)
+ cmd = SHT4X_CMD_HEATER_20_1;
+ else if (data->heater_power == 110)
+ cmd = SHT4X_CMD_HEATER_110_1;
+ else /* data->heater_power == 200 */
+ cmd = SHT4X_CMD_HEATER_200_1;
+
+ heating_time_bound = 1100;
+ }
+
+ if (time_before(jiffies, data->heating_complete))
+ return -EBUSY;
+
+ ret = i2c_master_send(data->client, &cmd, SHT4X_CMD_LEN);
+ if (ret < 0)
+ return ret;
+
+ data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound);
+ data->data_pending = true;
+ return 0;
+}
+
+static ssize_t heater_power_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", data->heater_power);
+}
+
+static ssize_t heater_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+ u32 power;
+ ssize_t ret;
+
+ ret = kstrtou32(buf, 10, &power);
+ if (ret)
+ return ret;
+
+ if (power != 20 && power != 110 && power != 200)
+ return -EINVAL;
+
+ data->heater_power = power;
+
+ return count;
+}
+
+static ssize_t heater_time_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", data->heater_time);
+}
+
+static ssize_t heater_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct sht4x_data *data = dev_get_drvdata(dev);
+ u32 time;
+ ssize_t ret;
+
+ ret = kstrtou32(buf, 10, &time);
+ if (ret)
+ return ret;
+
+ if (time != 100 && time != 1000)
+ return -EINVAL;
+
+ data->heater_time = time;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(heater_enable);
+static DEVICE_ATTR_RW(heater_power);
+static DEVICE_ATTR_RW(heater_time);
+
+static struct attribute *sht4x_attrs[] = {
+ &dev_attr_heater_enable.attr,
+ &dev_attr_heater_power.attr,
+ &dev_attr_heater_time.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(sht4x);
+
static const struct hwmon_channel_info * const sht4x_info[] = {
HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
@@ -254,8 +402,9 @@ static int sht4x_probe(struct i2c_client *client)
data->update_interval = SHT4X_MIN_POLL_INTERVAL;
data->client = client;
-
- mutex_init(&data->lock);
+ data->heater_power = 200;
+ data->heater_time = 1000;
+ data->heating_complete = jiffies;
crc8_populate_msb(sht4x_crc8_table, SHT4X_CRC8_POLYNOMIAL);
@@ -269,13 +418,13 @@ static int sht4x_probe(struct i2c_client *client)
client->name,
data,
&sht4x_chip_info,
- NULL);
+ sht4x_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id sht4x_id[] = {
- { "sht4x", 0 },
+ { "sht4x" },
{ },
};
MODULE_DEVICE_TABLE(i2c, sht4x_id);
diff --git a/drivers/hwmon/shtc1.c b/drivers/hwmon/shtc1.c
index 1f96e94967ee..2ac906e8e173 100644
--- a/drivers/hwmon/shtc1.c
+++ b/drivers/hwmon/shtc1.c
@@ -186,8 +186,6 @@ static void shtc1_select_command(struct shtc1_data *data)
}
}
-static const struct i2c_device_id shtc1_id[];
-
static int shtc1_probe(struct i2c_client *client)
{
int ret;
@@ -195,7 +193,7 @@ static int shtc1_probe(struct i2c_client *client)
char id_reg_buf[2];
struct shtc1_data *data;
struct device *hwmon_dev;
- enum shtcx_chips chip = i2c_match_id(shtc1_id, client)->driver_data;
+ enum shtcx_chips chip = (uintptr_t)i2c_get_match_data(client);
struct i2c_adapter *adap = client->adapter;
struct device *dev = &client->dev;
struct device_node *np = dev->of_node;
@@ -238,7 +236,7 @@ static int shtc1_probe(struct i2c_client *client)
if (np) {
data->setup.blocking_io = of_property_read_bool(np, "sensirion,blocking-io");
- data->setup.high_precision = !of_property_read_bool(np, "sensicon,low-precision");
+ data->setup.high_precision = !of_property_read_bool(np, "sensirion,low-precision");
} else {
if (client->dev.platform_data)
data->setup = *(struct shtc1_platform_data *)dev->platform_data;
diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c
index 0a0479501e11..b7a7bcd6d3af 100644
--- a/drivers/hwmon/sis5595.c
+++ b/drivers/hwmon/sis5595.c
@@ -153,13 +153,9 @@ static inline s8 TEMP_TO_REG(long val)
}
/*
- * FAN DIV: 1, 2, 4, or 8 (defaults to 2)
- * REG: 0, 1, 2, or 3 (respectively) (defaults to 1)
+ * FAN DIV: 1, 2, 4, or 8
+ * REG: 0, 1, 2, or 3 (respectively)
*/
-static inline u8 DIV_TO_REG(int val)
-{
- return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1;
-}
#define DIV_FROM_REG(val) (1 << (val))
/*
@@ -709,7 +705,7 @@ exit_remove_files:
return err;
}
-static int sis5595_remove(struct platform_device *pdev)
+static void sis5595_remove(struct platform_device *pdev)
{
struct sis5595_data *data = platform_get_drvdata(pdev);
@@ -717,8 +713,6 @@ static int sis5595_remove(struct platform_device *pdev)
sysfs_remove_group(&pdev->dev.kobj, &sis5595_group);
sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_in4);
sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_temp1);
-
- return 0;
}
static const struct pci_device_id sis5595_pci_ids[] = {
diff --git a/drivers/hwmon/sl28cpld-hwmon.c b/drivers/hwmon/sl28cpld-hwmon.c
index e020f25c9300..454cc844fb9d 100644
--- a/drivers/hwmon/sl28cpld-hwmon.c
+++ b/drivers/hwmon/sl28cpld-hwmon.c
@@ -23,13 +23,6 @@ struct sl28cpld_hwmon {
u32 offset;
};
-static umode_t sl28cpld_hwmon_is_visible(const void *data,
- enum hwmon_sensor_types type,
- u32 attr, int channel)
-{
- return 0444;
-}
-
static int sl28cpld_hwmon_read(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long *input)
@@ -73,7 +66,7 @@ static const struct hwmon_channel_info * const sl28cpld_hwmon_info[] = {
};
static const struct hwmon_ops sl28cpld_hwmon_ops = {
- .is_visible = sl28cpld_hwmon_is_visible,
+ .visible = 0444,
.read = sl28cpld_hwmon_read,
};
diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c
index 37531b5c8254..595bceb78d76 100644
--- a/drivers/hwmon/smsc47m1.c
+++ b/drivers/hwmon/smsc47m1.c
@@ -33,7 +33,7 @@ static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");
-static struct platform_device *pdev;
+static struct platform_device *smsc47m1_pdev;
#define DRVNAME "smsc47m1"
enum chips { smsc47m1, smsc47m2 };
@@ -840,17 +840,21 @@ error_remove_files:
return err;
}
-static int __exit smsc47m1_remove(struct platform_device *pdev)
+static void __exit smsc47m1_remove(struct platform_device *pdev)
{
struct smsc47m1_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
smsc47m1_remove_files(&pdev->dev);
-
- return 0;
}
-static struct platform_driver smsc47m1_driver = {
+/*
+ * smsc47m1_remove() lives in .exit.text. For drivers registered via
+ * module_platform_driver_probe() this ok because they cannot get unbound at
+ * runtime. The driver needs to be marked with __refdata, otherwise modpost
+ * triggers a section mismatch warning.
+ */
+static struct platform_driver smsc47m1_driver __refdata = {
.driver = {
.name = DRVNAME,
},
@@ -860,50 +864,33 @@ static struct platform_driver smsc47m1_driver = {
static int __init smsc47m1_device_add(unsigned short address,
const struct smsc47m1_sio_data *sio_data)
{
- struct resource res = {
+ const struct resource res = {
.start = address,
.end = address + SMSC_EXTENT - 1,
.name = DRVNAME,
.flags = IORESOURCE_IO,
};
+ const struct platform_device_info pdevinfo = {
+ .name = DRVNAME,
+ .id = address,
+ .res = &res,
+ .num_res = 1,
+ .data = sio_data,
+ .size_data = sizeof(struct smsc47m1_sio_data),
+ };
int err;
err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL);
if (err)
- goto exit;
+ return err;
- pdev = platform_device_alloc(DRVNAME, address);
- if (!pdev) {
- err = -ENOMEM;
+ smsc47m1_pdev = platform_device_register_full(&pdevinfo);
+ if (IS_ERR(smsc47m1_pdev)) {
pr_err("Device allocation failed\n");
- goto exit;
- }
-
- err = platform_device_add_resources(pdev, &res, 1);
- if (err) {
- pr_err("Device resource addition failed (%d)\n", err);
- goto exit_device_put;
- }
-
- err = platform_device_add_data(pdev, sio_data,
- sizeof(struct smsc47m1_sio_data));
- if (err) {
- pr_err("Platform data allocation failed\n");
- goto exit_device_put;
- }
-
- err = platform_device_add(pdev);
- if (err) {
- pr_err("Device addition failed (%d)\n", err);
- goto exit_device_put;
+ return PTR_ERR(smsc47m1_pdev);
}
return 0;
-
-exit_device_put:
- platform_device_put(pdev);
-exit:
- return err;
}
static int __init sm_smsc47m1_init(void)
@@ -917,7 +904,7 @@ static int __init sm_smsc47m1_init(void)
return err;
address = err;
- /* Sets global pdev as a side effect */
+ /* Sets global smsc47m1_pdev as a side effect */
err = smsc47m1_device_add(address, &sio_data);
if (err)
return err;
@@ -929,7 +916,7 @@ static int __init sm_smsc47m1_init(void)
return 0;
exit_device:
- platform_device_unregister(pdev);
+ platform_device_unregister(smsc47m1_pdev);
smsc47m1_restore(&sio_data);
return err;
}
@@ -937,8 +924,8 @@ exit_device:
static void __exit sm_smsc47m1_exit(void)
{
platform_driver_unregister(&smsc47m1_driver);
- smsc47m1_restore(dev_get_platdata(&pdev->dev));
- platform_device_unregister(pdev);
+ smsc47m1_restore(dev_get_platdata(&smsc47m1_pdev->dev));
+ platform_device_unregister(smsc47m1_pdev);
}
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
diff --git a/drivers/hwmon/smsc47m192.c b/drivers/hwmon/smsc47m192.c
index d20800a1f02b..21103af4e139 100644
--- a/drivers/hwmon/smsc47m192.c
+++ b/drivers/hwmon/smsc47m192.c
@@ -618,7 +618,7 @@ static int smsc47m192_probe(struct i2c_client *client)
}
static const struct i2c_device_id smsc47m192_id[] = {
- { "smsc47m192", 0 },
+ { "smsc47m192" },
{ }
};
MODULE_DEVICE_TABLE(i2c, smsc47m192_id);
diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c
new file mode 100644
index 000000000000..5da44571b6a0
--- /dev/null
+++ b/drivers/hwmon/spd5118.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Jedec 5118 compliant temperature sensors
+ *
+ * Derived from https://github.com/Steve-Tech/SPD5118-DKMS
+ * Originally from T/2 driver at https://t2sde.org/packages/linux
+ * Copyright (c) 2023 René Rebe, ExactCODE GmbH; Germany.
+ *
+ * Copyright (c) 2024 Guenter Roeck
+ *
+ * Inspired by ee1004.c and jc42.c.
+ *
+ * SPD5118 compliant temperature sensors are typically used on DDR5
+ * memory modules.
+ */
+
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+/* Addresses to scan */
+static const unsigned short normal_i2c[] = {
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, I2C_CLIENT_END };
+
+/* SPD5118 registers. */
+#define SPD5118_REG_TYPE 0x00 /* MR0:MR1 */
+#define SPD5118_REG_REVISION 0x02 /* MR2 */
+#define SPD5118_REG_VENDOR 0x03 /* MR3:MR4 */
+#define SPD5118_REG_CAPABILITY 0x05 /* MR5 */
+#define SPD5118_REG_I2C_LEGACY_MODE 0x0B /* MR11 */
+#define SPD5118_REG_TEMP_CLR 0x13 /* MR19 */
+#define SPD5118_REG_ERROR_CLR 0x14 /* MR20 */
+#define SPD5118_REG_TEMP_CONFIG 0x1A /* MR26 */
+#define SPD5118_REG_TEMP_MAX 0x1c /* MR28:MR29 */
+#define SPD5118_REG_TEMP_MIN 0x1e /* MR30:MR31 */
+#define SPD5118_REG_TEMP_CRIT 0x20 /* MR32:MR33 */
+#define SPD5118_REG_TEMP_LCRIT 0x22 /* MR34:MR35 */
+#define SPD5118_REG_TEMP 0x31 /* MR49:MR50 */
+#define SPD5118_REG_TEMP_STATUS 0x33 /* MR51 */
+
+#define SPD5118_TEMP_STATUS_HIGH BIT(0)
+#define SPD5118_TEMP_STATUS_LOW BIT(1)
+#define SPD5118_TEMP_STATUS_CRIT BIT(2)
+#define SPD5118_TEMP_STATUS_LCRIT BIT(3)
+
+#define SPD5118_CAP_TS_SUPPORT BIT(1) /* temperature sensor support */
+
+#define SPD5118_TS_DISABLE BIT(0) /* temperature sensor disable */
+
+#define SPD5118_LEGACY_MODE_ADDR BIT(3)
+#define SPD5118_LEGACY_PAGE_MASK GENMASK(2, 0)
+#define SPD5118_LEGACY_MODE_MASK (SPD5118_LEGACY_MODE_ADDR | SPD5118_LEGACY_PAGE_MASK)
+
+#define SPD5118_NUM_PAGES 8
+#define SPD5118_PAGE_SIZE 128
+#define SPD5118_PAGE_SHIFT 7
+#define SPD5118_PAGE_MASK GENMASK(6, 0)
+#define SPD5118_EEPROM_BASE 0x80
+#define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES)
+
+#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6)
+#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1)
+
+/* Temperature unit in millicelsius */
+#define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4)
+/* Representable temperature range in millicelsius */
+#define SPD5118_TEMP_RANGE_MIN -256000
+#define SPD5118_TEMP_RANGE_MAX 255750
+
+struct spd5118_data {
+ struct regmap *regmap;
+ struct mutex nvmem_lock;
+ bool is_16bit;
+};
+
+/* hwmon */
+
+static int spd5118_temp_from_reg(u16 reg)
+{
+ int temp = sign_extend32(reg >> 2, 10);
+
+ return temp * SPD5118_TEMP_UNIT;
+}
+
+static u16 spd5118_temp_to_reg(long temp)
+{
+ temp = clamp_val(temp, SPD5118_TEMP_RANGE_MIN, SPD5118_TEMP_RANGE_MAX);
+ return (DIV_ROUND_CLOSEST(temp, SPD5118_TEMP_UNIT) & 0x7ff) << 2;
+}
+
+static int spd5118_read_temp(struct regmap *regmap, u32 attr, long *val)
+{
+ int reg, err;
+ u8 regval[2];
+ u16 temp;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = SPD5118_REG_TEMP;
+ break;
+ case hwmon_temp_max:
+ reg = SPD5118_REG_TEMP_MAX;
+ break;
+ case hwmon_temp_min:
+ reg = SPD5118_REG_TEMP_MIN;
+ break;
+ case hwmon_temp_crit:
+ reg = SPD5118_REG_TEMP_CRIT;
+ break;
+ case hwmon_temp_lcrit:
+ reg = SPD5118_REG_TEMP_LCRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ err = regmap_bulk_read(regmap, reg, regval, 2);
+ if (err)
+ return err;
+
+ temp = (regval[1] << 8) | regval[0];
+
+ *val = spd5118_temp_from_reg(temp);
+ return 0;
+}
+
+static int spd5118_read_alarm(struct regmap *regmap, u32 attr, long *val)
+{
+ unsigned int mask, regval;
+ int err;
+
+ switch (attr) {
+ case hwmon_temp_max_alarm:
+ mask = SPD5118_TEMP_STATUS_HIGH;
+ break;
+ case hwmon_temp_min_alarm:
+ mask = SPD5118_TEMP_STATUS_LOW;
+ break;
+ case hwmon_temp_crit_alarm:
+ mask = SPD5118_TEMP_STATUS_CRIT;
+ break;
+ case hwmon_temp_lcrit_alarm:
+ mask = SPD5118_TEMP_STATUS_LCRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ err = regmap_read(regmap, SPD5118_REG_TEMP_STATUS, &regval);
+ if (err < 0)
+ return err;
+ *val = !!(regval & mask);
+ if (*val)
+ return regmap_write(regmap, SPD5118_REG_TEMP_CLR, mask);
+ return 0;
+}
+
+static int spd5118_read_enable(struct regmap *regmap, long *val)
+{
+ u32 regval;
+ int err;
+
+ err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, &regval);
+ if (err < 0)
+ return err;
+ *val = !(regval & SPD5118_TS_DISABLE);
+ return 0;
+}
+
+static int spd5118_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct regmap *regmap = dev_get_drvdata(dev);
+
+ if (type != hwmon_temp)
+ return -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_max:
+ case hwmon_temp_min:
+ case hwmon_temp_crit:
+ case hwmon_temp_lcrit:
+ return spd5118_read_temp(regmap, attr, val);
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_lcrit_alarm:
+ return spd5118_read_alarm(regmap, attr, val);
+ case hwmon_temp_enable:
+ return spd5118_read_enable(regmap, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int spd5118_write_temp(struct regmap *regmap, u32 attr, long val)
+{
+ u8 regval[2];
+ u16 temp;
+ int reg;
+
+ switch (attr) {
+ case hwmon_temp_max:
+ reg = SPD5118_REG_TEMP_MAX;
+ break;
+ case hwmon_temp_min:
+ reg = SPD5118_REG_TEMP_MIN;
+ break;
+ case hwmon_temp_crit:
+ reg = SPD5118_REG_TEMP_CRIT;
+ break;
+ case hwmon_temp_lcrit:
+ reg = SPD5118_REG_TEMP_LCRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ temp = spd5118_temp_to_reg(val);
+ regval[0] = temp & 0xff;
+ regval[1] = temp >> 8;
+
+ return regmap_bulk_write(regmap, reg, regval, 2);
+}
+
+static int spd5118_write_enable(struct regmap *regmap, long val)
+{
+ if (val && val != 1)
+ return -EINVAL;
+
+ return regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG,
+ SPD5118_TS_DISABLE,
+ val ? 0 : SPD5118_TS_DISABLE);
+}
+
+static int spd5118_temp_write(struct regmap *regmap, u32 attr, long val)
+{
+ switch (attr) {
+ case hwmon_temp_max:
+ case hwmon_temp_min:
+ case hwmon_temp_crit:
+ case hwmon_temp_lcrit:
+ return spd5118_write_temp(regmap, attr, val);
+ case hwmon_temp_enable:
+ return spd5118_write_enable(regmap, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int spd5118_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct regmap *regmap = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return spd5118_temp_write(regmap, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t spd5118_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_lcrit:
+ case hwmon_temp_crit:
+ case hwmon_temp_enable:
+ return 0644;
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_crit_alarm:
+ case hwmon_temp_lcrit_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Bank and vendor id are 8-bit fields with seven data bits and odd parity.
+ * Vendor IDs 0 and 0x7f are invalid.
+ * See Jedec standard JEP106BJ for details and a list of assigned vendor IDs.
+ */
+static bool spd5118_vendor_valid(u8 bank, u8 id)
+{
+ if (parity8(bank) == 0 || parity8(id) == 0)
+ return false;
+
+ id &= 0x7f;
+ return id && id != 0x7f;
+}
+
+static const struct hwmon_channel_info *spd5118_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT |
+ HWMON_T_LCRIT | HWMON_T_LCRIT_ALARM |
+ HWMON_T_MIN | HWMON_T_MIN_ALARM |
+ HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_ENABLE),
+ NULL
+};
+
+static const struct hwmon_ops spd5118_hwmon_ops = {
+ .is_visible = spd5118_is_visible,
+ .read = spd5118_read,
+ .write = spd5118_write,
+};
+
+static const struct hwmon_chip_info spd5118_chip_info = {
+ .ops = &spd5118_hwmon_ops,
+ .info = spd5118_info,
+};
+
+/* nvmem */
+
+static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf,
+ unsigned int offset, size_t count)
+{
+ int page = offset >> SPD5118_PAGE_SHIFT;
+ struct regmap *regmap = data->regmap;
+ int err, addr;
+
+ offset &= SPD5118_PAGE_MASK;
+
+ /* Can't cross page boundaries */
+ if (offset + count > SPD5118_PAGE_SIZE)
+ count = SPD5118_PAGE_SIZE - offset;
+
+ if (data->is_16bit) {
+ addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) |
+ (PAGE_ADDR1_4(page) << 8);
+ } else {
+ addr = page * 0x100 + SPD5118_EEPROM_BASE;
+ }
+ err = regmap_bulk_read(regmap, addr + offset, buf, count);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static int spd5118_nvmem_read(void *priv, unsigned int off, void *val, size_t count)
+{
+ struct spd5118_data *data = priv;
+ char *buf = val;
+ int ret;
+
+ if (unlikely(!count))
+ return count;
+
+ if (off + count > SPD5118_EEPROM_SIZE)
+ return -EINVAL;
+
+ mutex_lock(&data->nvmem_lock);
+
+ while (count) {
+ ret = spd5118_nvmem_read_page(data, buf, off, count);
+ if (ret < 0) {
+ mutex_unlock(&data->nvmem_lock);
+ return ret;
+ }
+ buf += ret;
+ off += ret;
+ count -= ret;
+ }
+ mutex_unlock(&data->nvmem_lock);
+ return 0;
+}
+
+static int spd5118_nvmem_init(struct device *dev, struct spd5118_data *data)
+{
+ struct nvmem_config nvmem_config = {
+ .type = NVMEM_TYPE_EEPROM,
+ .name = dev_name(dev),
+ .id = NVMEM_DEVID_NONE,
+ .dev = dev,
+ .base_dev = dev,
+ .read_only = true,
+ .root_only = false,
+ .owner = THIS_MODULE,
+ .compat = true,
+ .reg_read = spd5118_nvmem_read,
+ .priv = data,
+ .stride = 1,
+ .word_size = 1,
+ .size = SPD5118_EEPROM_SIZE,
+ };
+ struct nvmem_device *nvmem;
+
+ nvmem = devm_nvmem_register(dev, &nvmem_config);
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+/* regmap */
+
+static bool spd5118_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPD5118_REG_I2C_LEGACY_MODE:
+ case SPD5118_REG_TEMP_CLR:
+ case SPD5118_REG_TEMP_CONFIG:
+ case SPD5118_REG_TEMP_MAX:
+ case SPD5118_REG_TEMP_MAX + 1:
+ case SPD5118_REG_TEMP_MIN:
+ case SPD5118_REG_TEMP_MIN + 1:
+ case SPD5118_REG_TEMP_CRIT:
+ case SPD5118_REG_TEMP_CRIT + 1:
+ case SPD5118_REG_TEMP_LCRIT:
+ case SPD5118_REG_TEMP_LCRIT + 1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool spd5118_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPD5118_REG_TEMP_CLR:
+ case SPD5118_REG_ERROR_CLR:
+ case SPD5118_REG_TEMP:
+ case SPD5118_REG_TEMP + 1:
+ case SPD5118_REG_TEMP_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_range_cfg spd5118_i2c_regmap_range_cfg[] = {
+ {
+ .selector_reg = SPD5118_REG_I2C_LEGACY_MODE,
+ .selector_mask = SPD5118_LEGACY_PAGE_MASK,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 0x100,
+ .range_min = 0,
+ .range_max = 0x7ff,
+ },
+};
+
+static const struct regmap_config spd5118_regmap8_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x7ff,
+ .writeable_reg = spd5118_writeable_reg,
+ .volatile_reg = spd5118_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+
+ .ranges = spd5118_i2c_regmap_range_cfg,
+ .num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg),
+};
+
+static const struct regmap_config spd5118_regmap16_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0x7ff,
+ .writeable_reg = spd5118_writeable_reg,
+ .volatile_reg = spd5118_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int spd5118_suspend(struct device *dev)
+{
+ struct spd5118_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
+
+ /*
+ * Make sure the configuration register in the regmap cache is current
+ * before bypassing it.
+ */
+ err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, &regval);
+ if (err < 0)
+ return err;
+
+ regcache_cache_bypass(regmap, true);
+ regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG, SPD5118_TS_DISABLE,
+ SPD5118_TS_DISABLE);
+ regcache_cache_bypass(regmap, false);
+
+ regcache_cache_only(regmap, true);
+ regcache_mark_dirty(regmap);
+
+ return 0;
+}
+
+static int spd5118_resume(struct device *dev)
+{
+ struct spd5118_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+
+ regcache_cache_only(regmap, false);
+ return regcache_sync(regmap);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume);
+
+static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
+ bool is_16bit)
+{
+ unsigned int capability, revision, vendor, bank;
+ struct spd5118_data *data;
+ struct device *hwmon_dev;
+ int err;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = regmap_read(regmap, SPD5118_REG_CAPABILITY, &capability);
+ if (err)
+ return err;
+ if (!(capability & SPD5118_CAP_TS_SUPPORT))
+ return -ENODEV;
+
+ data->is_16bit = is_16bit;
+
+ err = regmap_read(regmap, SPD5118_REG_REVISION, &revision);
+ if (err)
+ return err;
+
+ err = regmap_read(regmap, SPD5118_REG_VENDOR, &bank);
+ if (err)
+ return err;
+ err = regmap_read(regmap, SPD5118_REG_VENDOR + 1, &vendor);
+ if (err)
+ return err;
+ if (!spd5118_vendor_valid(bank, vendor))
+ return -ENODEV;
+
+ data->regmap = regmap;
+ mutex_init(&data->nvmem_lock);
+ dev_set_drvdata(dev, data);
+
+ err = spd5118_nvmem_init(dev, data);
+ /* Ignore if NVMEM support is disabled */
+ if (err && err != -EOPNOTSUPP) {
+ dev_err_probe(dev, err, "failed to register nvmem\n");
+ return err;
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "spd5118",
+ regmap, &spd5118_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ /*
+ * From JESD300-5B
+ * MR2 bits [5:4]: Major revision, 1..4
+ * MR2 bits [3:1]: Minor revision, 0..8? Probably a typo, assume 1..8
+ */
+ dev_info(dev, "DDR5 temperature sensor: vendor 0x%02x:0x%02x revision %d.%d\n",
+ bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1, ((revision >> 1) & 0x07) + 1);
+
+ return 0;
+}
+
+/* I2C */
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ int regval;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA))
+ return -ENODEV;
+
+ regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
+ if (regval != 0x5118)
+ return -ENODEV;
+
+ regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
+ if (regval < 0 || !spd5118_vendor_valid(regval & 0xff, regval >> 8))
+ return -ENODEV;
+
+ regval = i2c_smbus_read_byte_data(client, SPD5118_REG_CAPABILITY);
+ if (regval < 0)
+ return -ENODEV;
+ if (!(regval & SPD5118_CAP_TS_SUPPORT) || (regval & 0xfc))
+ return -ENODEV;
+
+ regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CLR);
+ if (regval)
+ return -ENODEV;
+ regval = i2c_smbus_read_byte_data(client, SPD5118_REG_ERROR_CLR);
+ if (regval)
+ return -ENODEV;
+
+ regval = i2c_smbus_read_byte_data(client, SPD5118_REG_REVISION);
+ if (regval < 0 || (regval & 0xc1))
+ return -ENODEV;
+
+ regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CONFIG);
+ if (regval < 0)
+ return -ENODEV;
+ if (regval & ~SPD5118_TS_DISABLE)
+ return -ENODEV;
+
+ strscpy(info->type, "spd5118", I2C_NAME_SIZE);
+ return 0;
+}
+
+static int spd5118_i2c_init(struct i2c_client *client)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ int err, regval, mode;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA))
+ return -ENODEV;
+
+ regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
+ if (regval < 0 || (regval && regval != 0x5118))
+ return -ENODEV;
+
+ /*
+ * If the device type registers return 0, it is possible that the chip
+ * has a non-zero page selected and takes the specification literally,
+ * i.e. disables access to volatile registers besides the page register
+ * if the page is not 0. The Renesas/ITD SPD5118 Hub Controller is known
+ * to show this behavior. Try to identify such chips.
+ */
+ if (!regval) {
+ /* Vendor ID registers must also be 0 */
+ regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
+ if (regval)
+ return -ENODEV;
+
+ /* The selected page in MR11 must not be 0 */
+ mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
+ if (mode < 0 || (mode & ~SPD5118_LEGACY_MODE_MASK) ||
+ !(mode & SPD5118_LEGACY_PAGE_MASK))
+ return -ENODEV;
+
+ err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE,
+ mode & SPD5118_LEGACY_MODE_ADDR);
+ if (err)
+ return -ENODEV;
+
+ /*
+ * If the device type registers are still bad after selecting
+ * page 0, this is not a SPD5118 device. Restore original
+ * legacy mode register value and abort.
+ */
+ regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
+ if (regval != 0x5118) {
+ i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode);
+ return -ENODEV;
+ }
+ }
+
+ /* We are reasonably sure that this is really a SPD5118 hub controller */
+ return 0;
+}
+
+/*
+ * 16-bit addressing note:
+ *
+ * If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses
+ * SMBus operations as alternative. To simulate a read operation with a 16-bit
+ * address, it writes the address using i2c_smbus_write_byte_data(), followed
+ * by one or more calls to i2c_smbus_read_byte() to read the data.
+ * Per spd5118 standard, a read operation after writing the address must start
+ * with <Sr> (Repeat Start). However, a SMBus read byte operation starts with
+ * <S> (Start). This resets the register address in the spd5118 chip. As result,
+ * i2c_smbus_read_byte() always returns data from register address 0x00.
+ *
+ * A working alternative to access chips with 16-bit register addresses in the
+ * absence of I2C_FUNC_I2C support is not known.
+ *
+ * For this reason, 16-bit addressing can only be supported with I2C if the
+ * adapter supports I2C_FUNC_I2C.
+ *
+ * For I2C, the addressing mode selected by the BIOS must not be changed.
+ * Experiments show that at least some PC BIOS versions will not change the
+ * addressing mode on a soft reboot and end up in setup, claiming that some
+ * configuration change happened. This will happen again after a power cycle,
+ * which does reset the addressing mode. To prevent this from happening,
+ * detect if 16-bit addressing is enabled and always use the currently
+ * configured addressing mode.
+ */
+
+static int spd5118_i2c_probe(struct i2c_client *client)
+{
+ const struct regmap_config *config;
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+ int err, mode;
+ bool is_16bit;
+
+ err = spd5118_i2c_init(client);
+ if (err)
+ return err;
+
+ mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
+ if (mode < 0)
+ return mode;
+
+ is_16bit = mode & SPD5118_LEGACY_MODE_ADDR;
+ if (is_16bit) {
+ /*
+ * See 16-bit addressing note above explaining why it is
+ * necessary to check for I2C_FUNC_I2C support here.
+ */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "Adapter does not support 16-bit register addresses\n");
+ return -ENODEV;
+ }
+ config = &spd5118_regmap16_config;
+ } else {
+ config = &spd5118_regmap8_config;
+ }
+
+ regmap = devm_regmap_init_i2c(client, config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
+
+ return spd5118_common_probe(dev, regmap, is_16bit);
+}
+
+static const struct i2c_device_id spd5118_i2c_id[] = {
+ { "spd5118" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, spd5118_i2c_id);
+
+static const struct of_device_id spd5118_of_ids[] = {
+ { .compatible = "jedec,spd5118", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, spd5118_of_ids);
+
+static struct i2c_driver spd5118_i2c_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "spd5118",
+ .of_match_table = spd5118_of_ids,
+ .pm = pm_sleep_ptr(&spd5118_pm_ops),
+ },
+ .probe = spd5118_i2c_probe,
+ .id_table = spd5118_i2c_id,
+ .detect = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? spd5118_detect : NULL,
+ .address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL,
+};
+
+module_i2c_driver(spd5118_i2c_driver);
+
+MODULE_AUTHOR("René Rebe <rene@exactcode.de>");
+MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
+MODULE_DESCRIPTION("SPD 5118 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/stts751.c b/drivers/hwmon/stts751.c
index 847c99376930..f9e8b2869164 100644
--- a/drivers/hwmon/stts751.c
+++ b/drivers/hwmon/stts751.c
@@ -72,12 +72,12 @@ static const int stts751_intervals[] = {
};
static const struct i2c_device_id stts751_id[] = {
- { "stts751", 0 },
+ { "stts751" },
{ }
};
static const struct of_device_id __maybe_unused stts751_of_match[] = {
- { .compatible = "stts751" },
+ { .compatible = "st,stts751" },
{ },
};
MODULE_DEVICE_TABLE(of, stts751_of_match);
@@ -91,7 +91,6 @@ struct stts751_priv {
int event_max, event_min;
int therm;
int hyst;
- bool smbus_timeout;
int temp;
unsigned long last_update, last_alert_update;
u8 config;
diff --git a/drivers/hwmon/surface_fan.c b/drivers/hwmon/surface_fan.c
new file mode 100644
index 000000000000..aafb4ac92e6c
--- /dev/null
+++ b/drivers/hwmon/surface_fan.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface Fan driver for Surface System Aggregator Module. It provides access
+ * to the fan's rpm through the hwmon system.
+ *
+ * Copyright (C) 2023 Ivor Wanders <ivor@iwanders.net>
+ */
+
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/surface_aggregator/device.h>
+#include <linux/types.h>
+
+// SSAM
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, {
+ .target_category = SSAM_SSH_TC_FAN,
+ .command_id = 0x01,
+});
+
+static int surface_fan_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct ssam_device *sdev = dev_get_drvdata(dev);
+ int ret;
+ __le16 value;
+
+ ret = __ssam_fan_rpm_get(sdev, &value);
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(value);
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *const surface_fan_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops surface_fan_hwmon_ops = {
+ .visible = 0444,
+ .read = surface_fan_hwmon_read,
+};
+
+static const struct hwmon_chip_info surface_fan_chip_info = {
+ .ops = &surface_fan_hwmon_ops,
+ .info = surface_fan_info,
+};
+
+static int surface_fan_probe(struct ssam_device *sdev)
+{
+ struct device *hdev;
+
+ hdev = devm_hwmon_device_register_with_info(&sdev->dev,
+ "surface_fan", sdev,
+ &surface_fan_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(hdev);
+}
+
+static const struct ssam_device_id ssam_fan_match[] = {
+ { SSAM_SDEV(FAN, SAM, 0x01, 0x01) },
+ {},
+};
+MODULE_DEVICE_TABLE(ssam, ssam_fan_match);
+
+static struct ssam_device_driver surface_fan = {
+ .probe = surface_fan_probe,
+ .match_table = ssam_fan_match,
+ .driver = {
+ .name = "surface_fan",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_ssam_device_driver(surface_fan);
+
+MODULE_AUTHOR("Ivor Wanders <ivor@iwanders.net>");
+MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c
new file mode 100644
index 000000000000..cd21f331f157
--- /dev/null
+++ b/drivers/hwmon/surface_temp.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM).
+ *
+ * Copyright (C) 2022-2023 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/device.h>
+
+/* -- SAM interface. -------------------------------------------------------- */
+
+/*
+ * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the
+ * presence of a sensor. So we have at most 16 possible sensors/channels.
+ */
+#define SSAM_TMP_SENSOR_MAX_COUNT 16
+
+/*
+ * All names observed so far are 6 characters long, but there's only
+ * zeros after the name, so perhaps they can be longer. This number reflects
+ * the maximum zero-padded space observed in the returned buffer.
+ */
+#define SSAM_TMP_SENSOR_NAME_LENGTH 18
+
+struct ssam_tmp_get_name_rsp {
+ __le16 unknown1;
+ char unknown2;
+ char name[SSAM_TMP_SENSOR_NAME_LENGTH];
+} __packed;
+
+static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21);
+
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, {
+ .target_category = SSAM_SSH_TC_TMP,
+ .command_id = 0x04,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, {
+ .target_category = SSAM_SSH_TC_TMP,
+ .command_id = 0x01,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, {
+ .target_category = SSAM_SSH_TC_TMP,
+ .command_id = 0x0e,
+});
+
+static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors)
+{
+ __le16 sensors_le;
+ int status;
+
+ status = __ssam_tmp_get_available_sensors(sdev, &sensors_le);
+ if (status)
+ return status;
+
+ *sensors = le16_to_cpu(sensors_le);
+ return 0;
+}
+
+static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature)
+{
+ __le16 temp_le;
+ int status;
+
+ status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le);
+ if (status)
+ return status;
+
+ /* Convert 1/10 °K to 1/1000 °C */
+ *temperature = (le16_to_cpu(temp_le) - 2731) * 100L;
+ return 0;
+}
+
+static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len)
+{
+ struct ssam_tmp_get_name_rsp name_rsp;
+ int status;
+
+ status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp);
+ if (status)
+ return status;
+
+ /*
+ * This should not fail unless the name in the returned struct is not
+ * null-terminated or someone changed something in the struct
+ * definitions above, since our buffer and struct have the same
+ * capacity by design. So if this fails, log an error message. Since
+ * the more likely cause is that the returned string isn't
+ * null-terminated, we might have received garbage (as opposed to just
+ * an incomplete string), so also fail the function.
+ */
+ status = strscpy(buf, name_rsp.name, buf_len);
+ if (status < 0) {
+ dev_err(&sdev->dev, "received non-null-terminated sensor name string\n");
+ return status;
+ }
+
+ return 0;
+}
+
+/* -- Driver.---------------------------------------------------------------- */
+
+struct ssam_temp {
+ struct ssam_device *sdev;
+ s16 sensors;
+ char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH];
+};
+
+static umode_t ssam_temp_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct ssam_temp *ssam_temp = data;
+
+ if (!(ssam_temp->sensors & BIT(channel)))
+ return 0;
+
+ return 0444;
+}
+
+static int ssam_temp_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *value)
+{
+ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev);
+
+ return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value);
+}
+
+static int ssam_temp_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ const struct ssam_temp *ssam_temp = dev_get_drvdata(dev);
+
+ *str = ssam_temp->names[channel];
+ return 0;
+}
+
+static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops ssam_temp_hwmon_ops = {
+ .is_visible = ssam_temp_hwmon_is_visible,
+ .read = ssam_temp_hwmon_read,
+ .read_string = ssam_temp_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = {
+ .ops = &ssam_temp_hwmon_ops,
+ .info = ssam_temp_hwmon_info,
+};
+
+static int ssam_temp_probe(struct ssam_device *sdev)
+{
+ struct ssam_temp *ssam_temp;
+ struct device *hwmon_dev;
+ s16 sensors;
+ int channel;
+ int status;
+
+ status = ssam_tmp_get_available_sensors(sdev, &sensors);
+ if (status)
+ return status;
+
+ ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL);
+ if (!ssam_temp)
+ return -ENOMEM;
+
+ ssam_temp->sdev = sdev;
+ ssam_temp->sensors = sensors;
+
+ /* Retrieve the name for each available sensor. */
+ for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) {
+ if (!(sensors & BIT(channel)))
+ continue;
+
+ status = ssam_tmp_get_name(sdev, channel + 1, ssam_temp->names[channel],
+ SSAM_TMP_SENSOR_NAME_LENGTH);
+ if (status)
+ return status;
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, "surface_thermal", ssam_temp,
+ &ssam_temp_hwmon_chip_info, NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct ssam_device_id ssam_temp_match[] = {
+ { SSAM_SDEV(TMP, SAM, 0x00, 0x02) },
+ { },
+};
+MODULE_DEVICE_TABLE(ssam, ssam_temp_match);
+
+static struct ssam_device_driver ssam_temp = {
+ .probe = ssam_temp_probe,
+ .match_table = ssam_temp_match,
+ .driver = {
+ .name = "surface_temp",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_ssam_device_driver(ssam_temp);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sy7636a-hwmon.c b/drivers/hwmon/sy7636a-hwmon.c
index ed110884786b..d51daaf63d63 100644
--- a/drivers/hwmon/sy7636a-hwmon.c
+++ b/drivers/hwmon/sy7636a-hwmon.c
@@ -66,18 +66,13 @@ static const struct hwmon_chip_info sy7636a_chip_info = {
static int sy7636a_sensor_probe(struct platform_device *pdev)
{
struct regmap *regmap = dev_get_regmap(pdev->dev.parent, NULL);
- struct regulator *regulator;
struct device *hwmon_dev;
int err;
if (!regmap)
return -EPROBE_DEFER;
- regulator = devm_regulator_get(&pdev->dev, "vcom");
- if (IS_ERR(regulator))
- return PTR_ERR(regulator);
-
- err = regulator_enable(regulator);
+ err = devm_regulator_get_enable(&pdev->dev, "vcom");
if (err)
return err;
@@ -104,3 +99,4 @@ module_platform_driver(sy7636a_sensor_driver);
MODULE_DESCRIPTION("SY7636A sensor driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sy7636a-temperature");
diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
index 42a9658f1bc2..39fe5836f237 100644
--- a/drivers/hwmon/tc654.c
+++ b/drivers/hwmon/tc654.c
@@ -550,8 +550,8 @@ static int tc654_probe(struct i2c_client *client)
}
static const struct i2c_device_id tc654_id[] = {
- {"tc654", 0},
- {"tc655", 0},
+ {"tc654"},
+ {"tc655"},
{}
};
diff --git a/drivers/hwmon/tc74.c b/drivers/hwmon/tc74.c
index 03950670bd78..9984373a25fb 100644
--- a/drivers/hwmon/tc74.c
+++ b/drivers/hwmon/tc74.c
@@ -151,7 +151,7 @@ static int tc74_probe(struct i2c_client *client)
}
static const struct i2c_device_id tc74_id[] = {
- { "tc74", 0 },
+ { "tc74" },
{}
};
MODULE_DEVICE_TABLE(i2c, tc74_id);
diff --git a/drivers/hwmon/thmc50.c b/drivers/hwmon/thmc50.c
index 68ba26bc9014..0cbdb91698b1 100644
--- a/drivers/hwmon/thmc50.c
+++ b/drivers/hwmon/thmc50.c
@@ -377,8 +377,6 @@ static void thmc50_init_client(struct thmc50_data *data)
i2c_smbus_write_byte_data(client, THMC50_REG_CONF, config);
}
-static const struct i2c_device_id thmc50_id[];
-
static int thmc50_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -391,7 +389,7 @@ static int thmc50_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- data->type = i2c_match_id(thmc50_id, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
mutex_init(&data->update_lock);
thmc50_init_client(data);
diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c
index 2506c78590af..5b10c395a84d 100644
--- a/drivers/hwmon/tmp102.c
+++ b/drivers/hwmon/tmp102.c
@@ -10,12 +10,11 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/of.h>
#define DRIVER_NAME "tmp102"
@@ -52,6 +51,7 @@
#define CONVERSION_TIME_MS 35 /* in milli-seconds */
struct tmp102 {
+ const char *label;
struct regmap *regmap;
u16 config_orig;
unsigned long ready_time;
@@ -69,6 +69,16 @@ static inline u16 tmp102_mC_to_reg(int val)
return (val * 128) / 1000;
}
+static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct tmp102 *tmp102 = dev_get_drvdata(dev);
+
+ *str = tmp102->label;
+
+ return 0;
+}
+
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *temp)
{
@@ -127,12 +137,18 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
+ const struct tmp102 *tmp102 = data;
+
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
return 0444;
+ case hwmon_temp_label:
+ if (tmp102->label)
+ return 0444;
+ return 0;
case hwmon_temp_max_hyst:
case hwmon_temp_max:
return 0644;
@@ -145,12 +161,13 @@ static const struct hwmon_channel_info * const tmp102_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp,
- HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST),
NULL
};
static const struct hwmon_ops tmp102_hwmon_ops = {
.is_visible = tmp102_is_visible,
+ .read_string = tmp102_read_string,
.read = tmp102_read,
.write = tmp102_write,
};
@@ -204,10 +221,16 @@ static int tmp102_probe(struct i2c_client *client)
return -ENODEV;
}
+ err = devm_regulator_get_enable_optional(dev, "vcc");
+ if (err < 0 && err != -ENODEV)
+ return dev_err_probe(dev, err, "Failed to enable regulator\n");
+
tmp102 = devm_kzalloc(dev, sizeof(*tmp102), GFP_KERNEL);
if (!tmp102)
return -ENOMEM;
+ of_property_read_string(dev->of_node, "label", &tmp102->label);
+
i2c_set_clientdata(client, tmp102);
tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config);
@@ -286,7 +309,7 @@ static int tmp102_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume);
static const struct i2c_device_id tmp102_id[] = {
- { "tmp102", 0 },
+ { "tmp102" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp102_id);
diff --git a/drivers/hwmon/tmp103.c b/drivers/hwmon/tmp103.c
index a84c29a3a765..221bba8a215d 100644
--- a/drivers/hwmon/tmp103.c
+++ b/drivers/hwmon/tmp103.c
@@ -14,11 +14,8 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/device.h>
-#include <linux/jiffies.h>
#include <linux/regmap.h>
#define TMP103_TEMP_REG 0x00
@@ -197,7 +194,7 @@ static int tmp103_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(tmp103_dev_pm_ops, tmp103_suspend, tmp103_resume);
static const struct i2c_device_id tmp103_id[] = {
- { "tmp103", 0 },
+ { "tmp103" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp103_id);
diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c
index d7a09ab2bc11..60a237cbedbc 100644
--- a/drivers/hwmon/tmp108.c
+++ b/drivers/hwmon/tmp108.c
@@ -8,14 +8,14 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/of.h>
#include <linux/i2c.h>
+#include <linux/i3c/device.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#define DRIVER_NAME "tmp108"
@@ -323,33 +323,23 @@ static const struct regmap_config tmp108_regmap_config = {
.use_single_write = true,
};
-static int tmp108_probe(struct i2c_client *client)
+static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char *name)
{
- struct device *dev = &client->dev;
struct device *hwmon_dev;
struct tmp108 *tmp108;
- int err;
u32 config;
+ int err;
- if (!i2c_check_functionality(client->adapter,
- I2C_FUNC_SMBUS_WORD_DATA)) {
- dev_err(dev,
- "adapter doesn't support SMBus word transactions\n");
- return -ENODEV;
- }
+ err = devm_regulator_get_enable(dev, "vcc");
+ if (err)
+ return dev_err_probe(dev, err, "Failed to enable regulator\n");
tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL);
if (!tmp108)
return -ENOMEM;
dev_set_drvdata(dev, tmp108);
-
- tmp108->regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config);
- if (IS_ERR(tmp108->regmap)) {
- err = PTR_ERR(tmp108->regmap);
- dev_err(dev, "regmap init failed: %d", err);
- return err;
- }
+ tmp108->regmap = regmap;
err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config);
if (err < 0) {
@@ -383,13 +373,30 @@ static int tmp108_probe(struct i2c_client *client)
return err;
}
- hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, name,
tmp108,
&tmp108_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
+static int tmp108_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA))
+ return dev_err_probe(dev, -ENODEV,
+ "adapter doesn't support SMBus word transactions\n");
+
+ regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed");
+
+ return tmp108_common_probe(dev, regmap, client->name);
+}
+
static int tmp108_suspend(struct device *dev)
{
struct tmp108 *tmp108 = dev_get_drvdata(dev);
@@ -413,30 +420,57 @@ static int tmp108_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume);
static const struct i2c_device_id tmp108_i2c_ids[] = {
- { "tmp108", 0 },
+ { "p3t1085" },
+ { "tmp108" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids);
-#ifdef CONFIG_OF
static const struct of_device_id tmp108_of_ids[] = {
+ { .compatible = "nxp,p3t1085", },
{ .compatible = "ti,tmp108", },
{}
};
MODULE_DEVICE_TABLE(of, tmp108_of_ids);
-#endif
static struct i2c_driver tmp108_driver = {
.driver = {
.name = DRIVER_NAME,
.pm = pm_sleep_ptr(&tmp108_dev_pm_ops),
- .of_match_table = of_match_ptr(tmp108_of_ids),
+ .of_match_table = tmp108_of_ids,
},
.probe = tmp108_probe,
.id_table = tmp108_i2c_ids,
};
-module_i2c_driver(tmp108_driver);
+static const struct i3c_device_id p3t1085_i3c_ids[] = {
+ I3C_DEVICE(0x011b, 0x1529, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(i3c, p3t1085_i3c_ids);
+
+static int p3t1085_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i3c(i3cdev, &tmp108_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to register i3c regmap\n");
+
+ return tmp108_common_probe(dev, regmap, "p3t1085_i3c");
+}
+
+static struct i3c_driver p3t1085_driver = {
+ .driver = {
+ .name = "p3t1085_i3c",
+ },
+ .probe = p3t1085_i3c_probe,
+ .id_table = p3t1085_i3c_ids,
+};
+
+module_i3c_i2c_driver(p3t1085_driver, &tmp108_driver)
MODULE_AUTHOR("John Muir <john@jmuir.com>");
MODULE_DESCRIPTION("Texas Instruments TMP108 temperature sensor driver");
diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c
index 91f2314568cf..fbaa34973694 100644
--- a/drivers/hwmon/tmp401.c
+++ b/drivers/hwmon/tmp401.c
@@ -24,7 +24,6 @@
#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -107,7 +106,6 @@ MODULE_DEVICE_TABLE(i2c, tmp401_id);
struct tmp401_data {
struct i2c_client *client;
struct regmap *regmap;
- struct mutex update_lock;
enum chips kind;
bool extended_range;
@@ -256,7 +254,7 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config tmp401_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = tmp401_regmap_is_volatile,
.reg_read = tmp401_reg_read,
.reg_write = tmp401_reg_write,
@@ -308,7 +306,9 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val
{
struct tmp401_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
+ unsigned int regs[2] = { TMP401_TEMP_MSB[3][channel], TMP401_TEMP_CRIT_HYST };
unsigned int regval;
+ u16 regvals[2];
int reg, ret;
switch (attr) {
@@ -325,20 +325,11 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val
*val = tmp401_register_to_temp(regval, data->extended_range);
break;
case hwmon_temp_crit_hyst:
- mutex_lock(&data->update_lock);
- reg = TMP401_TEMP_MSB[3][channel];
- ret = regmap_read(regmap, reg, &regval);
- if (ret < 0)
- goto unlock;
- *val = tmp401_register_to_temp(regval, data->extended_range);
- ret = regmap_read(regmap, TMP401_TEMP_CRIT_HYST, &regval);
- if (ret < 0)
- goto unlock;
- *val -= regval * 1000;
-unlock:
- mutex_unlock(&data->update_lock);
+ ret = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (ret < 0)
return ret;
+ *val = tmp401_register_to_temp(regvals[0], data->extended_range) -
+ (regvals[1] * 1000);
break;
case hwmon_temp_fault:
case hwmon_temp_min_alarm:
@@ -364,7 +355,6 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel,
unsigned int regval;
int reg, ret, temp;
- mutex_lock(&data->update_lock);
switch (attr) {
case hwmon_temp_min:
case hwmon_temp_max:
@@ -393,7 +383,6 @@ static int tmp401_temp_write(struct device *dev, u32 attr, int channel,
ret = -EOPNOTSUPP;
break;
}
- mutex_unlock(&data->update_lock);
return ret;
}
@@ -443,7 +432,6 @@ static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val
struct regmap *regmap = data->regmap;
int err;
- mutex_lock(&data->update_lock);
switch (attr) {
case hwmon_chip_update_interval:
err = tmp401_set_convrate(regmap, val);
@@ -463,8 +451,6 @@ static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val
err = -EOPNOTSUPP;
break;
}
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -692,8 +678,7 @@ static int tmp401_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
- mutex_init(&data->update_lock);
- data->kind = i2c_match_id(tmp401_id, client)->driver_data;
+ data->kind = (uintptr_t)i2c_get_match_data(client);
data->regmap = devm_regmap_init(dev, NULL, data, &tmp401_regmap_config);
if (IS_ERR(data->regmap))
diff --git a/drivers/hwmon/tmp421.c b/drivers/hwmon/tmp421.c
index 10b66c9ce045..2ea9d3e9553d 100644
--- a/drivers/hwmon/tmp421.c
+++ b/drivers/hwmon/tmp421.c
@@ -19,7 +19,6 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/sysfs.h>
@@ -99,7 +98,6 @@ struct tmp421_channel {
struct tmp421_data {
struct i2c_client *client;
- struct mutex update_lock;
u32 temp_config[MAX_CHANNELS + 1];
struct hwmon_channel_info temp_info;
const struct hwmon_channel_info *info[2];
@@ -130,38 +128,28 @@ static int tmp421_update_device(struct tmp421_data *data)
int ret = 0;
int i;
- mutex_lock(&data->update_lock);
-
if (time_after(jiffies, data->last_updated + (HZ / 2)) ||
!data->valid) {
+ data->valid = false;
ret = i2c_smbus_read_byte_data(client, TMP421_CONFIG_REG_1);
if (ret < 0)
- goto exit;
+ return ret;
data->config = ret;
for (i = 0; i < data->channels; i++) {
ret = i2c_smbus_read_byte_data(client, TMP421_TEMP_MSB[i]);
if (ret < 0)
- goto exit;
+ return ret;
data->channel[i].temp = ret << 8;
ret = i2c_smbus_read_byte_data(client, TMP421_TEMP_LSB[i]);
if (ret < 0)
- goto exit;
+ return ret;
data->channel[i].temp |= ret;
}
data->last_updated = jiffies;
data->valid = true;
}
-
-exit:
- mutex_unlock(&data->update_lock);
-
- if (ret < 0) {
- data->valid = false;
- return ret;
- }
-
return 0;
}
@@ -262,7 +250,6 @@ static umode_t tmp421_is_visible(const void *data, enum hwmon_sensor_types type,
switch (attr) {
case hwmon_temp_fault:
case hwmon_temp_input:
- return 0444;
case hwmon_temp_label:
return 0444;
case hwmon_temp_enable:
@@ -381,7 +368,11 @@ static int tmp421_probe_child_from_dt(struct i2c_client *client,
return -EINVAL;
}
- of_property_read_string(child, "label", &data->channel[i].label);
+ err = of_property_read_string(child, "label", &data->channel[i].label);
+ if (err == -ENODATA || err == -EILSEQ) {
+ dev_err(dev, "invalid label property in %pOFn\n", child);
+ return err;
+ }
if (data->channel[i].label)
data->temp_config[i] |= HWMON_T_LABEL;
@@ -410,18 +401,15 @@ static int tmp421_probe_from_dt(struct i2c_client *client, struct tmp421_data *d
{
struct device *dev = &client->dev;
const struct device_node *np = dev->of_node;
- struct device_node *child;
int err;
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
if (strcmp(child->name, "channel"))
continue;
err = tmp421_probe_child_from_dt(client, child, data);
- if (err) {
- of_node_put(child);
+ if (err)
return err;
- }
}
return 0;
@@ -445,12 +433,7 @@ static int tmp421_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- mutex_init(&data->update_lock);
- if (client->dev.of_node)
- data->channels = (unsigned long)
- of_device_get_match_data(&client->dev);
- else
- data->channels = i2c_match_id(tmp421_id, client)->driver_data;
+ data->channels = (unsigned long)i2c_get_match_data(client);
data->client = client;
for (i = 0; i < data->channels; i++) {
diff --git a/drivers/hwmon/tmp464.c b/drivers/hwmon/tmp464.c
index f58ca4c6acb6..98f2576d94c6 100644
--- a/drivers/hwmon/tmp464.c
+++ b/drivers/hwmon/tmp464.c
@@ -13,7 +13,6 @@
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -92,7 +91,6 @@ struct tmp464_channel {
struct tmp464_data {
struct regmap *regmap;
- struct mutex update_lock;
int channels;
s16 config_orig;
u16 open_reg;
@@ -147,11 +145,11 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val
{
struct tmp464_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
- unsigned int regval, regval2;
+ unsigned int regs[2];
+ unsigned int regval;
+ u16 regvals[2];
int err = 0;
- mutex_lock(&data->update_lock);
-
switch (attr) {
case hwmon_temp_max_alarm:
err = regmap_read(regmap, TMP464_THERM_STATUS_REG, &regval);
@@ -184,14 +182,12 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val
*val = !!(data->open_reg & BIT(channel + 7));
break;
case hwmon_temp_max_hyst:
- err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], &regval);
- if (err < 0)
- break;
- err = regmap_read(regmap, TMP464_TEMP_HYST_REG, &regval2);
+ regs[0] = TMP464_THERM_LIMIT[channel];
+ regs[1] = TMP464_TEMP_HYST_REG;
+ err = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (err < 0)
break;
- regval -= regval2;
- *val = temp_from_reg(regval);
+ *val = temp_from_reg(regvals[0] - regvals[1]);
break;
case hwmon_temp_max:
err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], &regval);
@@ -200,14 +196,12 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val
*val = temp_from_reg(regval);
break;
case hwmon_temp_crit_hyst:
- err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], &regval);
- if (err < 0)
- break;
- err = regmap_read(regmap, TMP464_TEMP_HYST_REG, &regval2);
+ regs[0] = TMP464_THERM2_LIMIT[channel];
+ regs[1] = TMP464_TEMP_HYST_REG;
+ err = regmap_multi_reg_read(regmap, regs, regvals, 2);
if (err < 0)
break;
- regval -= regval2;
- *val = temp_from_reg(regval);
+ *val = temp_from_reg(regvals[0] - regvals[1]);
break;
case hwmon_temp_crit:
err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], &regval);
@@ -239,8 +233,6 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val
break;
}
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -348,8 +340,6 @@ static int tmp464_write(struct device *dev, enum hwmon_sensor_types type,
struct tmp464_data *data = dev_get_drvdata(dev);
int err;
- mutex_lock(&data->update_lock);
-
switch (type) {
case hwmon_chip:
err = tmp464_chip_write(data, attr, channel, val);
@@ -362,8 +352,6 @@ static int tmp464_write(struct device *dev, enum hwmon_sensor_types type,
break;
}
- mutex_unlock(&data->update_lock);
-
return err;
}
@@ -565,18 +553,15 @@ static int tmp464_probe_child_from_dt(struct device *dev,
static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data)
{
const struct device_node *np = dev->of_node;
- struct device_node *child;
int err;
- for_each_child_of_node(np, child) {
+ for_each_child_of_node_scoped(np, child) {
if (strcmp(child->name, "channel"))
continue;
err = tmp464_probe_child_from_dt(dev, child, data);
- if (err) {
- of_node_put(child);
+ if (err)
return err;
- }
}
return 0;
@@ -664,12 +649,7 @@ static int tmp464_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- mutex_init(&data->update_lock);
-
- if (dev->of_node)
- data->channels = (int)(unsigned long)of_device_get_match_data(&client->dev);
- else
- data->channels = i2c_match_id(tmp464_id, client)->driver_data;
+ data->channels = (int)(unsigned long)i2c_get_match_data(client);
data->regmap = devm_regmap_init_i2c(client, &tmp464_regmap_config);
if (IS_ERR(data->regmap))
diff --git a/drivers/hwmon/tmp513.c b/drivers/hwmon/tmp513.c
index 9a180b1030c9..5acbfd7d088d 100644
--- a/drivers/hwmon/tmp513.c
+++ b/drivers/hwmon/tmp513.c
@@ -19,15 +19,20 @@
* the Free Software Foundation; version 2 of the License.
*/
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
-#include <linux/kernel.h>
+#include <linux/math.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/util_macros.h>
+#include <linux/types.h>
+#include <linux/units.h>
// Common register definition
#define TMP51X_SHUNT_CONFIG 0x00
@@ -73,9 +78,6 @@
#define TMP51X_PGA_DEFAULT 8
#define TMP51X_MAX_REGISTER_ADDR 0xFF
-#define TMP512_TEMP_CONFIG_DEFAULT 0xBF80
-#define TMP513_TEMP_CONFIG_DEFAULT 0xFF80
-
// Mask and shift
#define CURRENT_SENSE_VOLTAGE_320_MASK 0x1800
#define CURRENT_SENSE_VOLTAGE_160_MASK 0x1000
@@ -100,8 +102,8 @@
#define TMP51X_REMOTE_TEMP_LIMIT_2_POS 8
#define TMP513_REMOTE_TEMP_LIMIT_3_POS 7
-#define TMP51X_VBUS_RANGE_32V 32000000
-#define TMP51X_VBUS_RANGE_16V 16000000
+#define TMP51X_VBUS_RANGE_32V (32 * MICRO)
+#define TMP51X_VBUS_RANGE_16V (16 * MICRO)
// Max and Min value
#define MAX_BUS_VOLTAGE_32_LIMIT 32764
@@ -113,6 +115,17 @@
#define MAX_TEMP_HYST 127500
+#define TMP512_MAX_CHANNELS 3
+#define TMP513_MAX_CHANNELS 4
+
+#define TMP51X_TEMP_CONFIG_CONV_RATE GENMASK(9, 7)
+#define TMP51X_TEMP_CONFIG_RC BIT(10)
+#define TMP51X_TEMP_CHANNEL_MASK(n) (GENMASK((n) - 1, 0) << 11)
+#define TMP51X_TEMP_CONFIG_CONT BIT(15)
+#define TMP51X_TEMP_CONFIG_DEFAULT(n) \
+ (TMP51X_TEMP_CHANNEL_MASK(n) | TMP51X_TEMP_CONFIG_CONT | \
+ TMP51X_TEMP_CONFIG_CONV_RATE | TMP51X_TEMP_CONFIG_RC)
+
static const u8 TMP51X_TEMP_INPUT[4] = {
TMP51X_LOCAL_TEMP_RESULT,
TMP51X_REMOTE_TEMP_RESULT_1,
@@ -146,16 +159,12 @@ static const u8 TMP51X_CURR_INPUT[2] = {
TMP51X_BUS_CURRENT_RESULT
};
-static struct regmap_config tmp51x_regmap_config = {
+static const struct regmap_config tmp51x_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = TMP51X_MAX_REGISTER_ADDR,
};
-enum tmp51x_ids {
- tmp512, tmp513
-};
-
struct tmp51x_data {
u16 shunt_config;
u16 pga_gain;
@@ -169,11 +178,11 @@ struct tmp51x_data {
u32 curr_lsb_ua;
u32 pwr_lsb_uw;
- enum tmp51x_ids id;
+ u8 max_channels;
struct regmap *regmap;
};
-// Set the shift based on the gain 8=4, 4=3, 2=2, 1=1
+// Set the shift based on the gain: 8 -> 1, 4 -> 2, 2 -> 3, 1 -> 4
static inline u8 tmp51x_get_pga_shift(struct tmp51x_data *data)
{
return 5 - ffs(data->pga_gain);
@@ -195,8 +204,11 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
* 2's complement number shifted by one to four depending
* on the pga gain setting. 1lsb = 10uV
*/
- *val = sign_extend32(regval, 17 - tmp51x_get_pga_shift(data));
- *val = DIV_ROUND_CLOSEST(*val * 10000, data->shunt_uohms);
+ *val = sign_extend32(regval,
+ reg == TMP51X_SHUNT_CURRENT_RESULT ?
+ 16 - tmp51x_get_pga_shift(data) : 15);
+ *val = DIV_ROUND_CLOSEST(*val * 10 * (long)MILLI, (long)data->shunt_uohms);
+
break;
case TMP51X_BUS_VOLTAGE_RESULT:
case TMP51X_BUS_VOLTAGE_H_LIMIT:
@@ -211,8 +223,8 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
break;
case TMP51X_BUS_CURRENT_RESULT:
// Current = (ShuntVoltage * CalibrationRegister) / 4096
- *val = sign_extend32(regval, 16) * data->curr_lsb_ua;
- *val = DIV_ROUND_CLOSEST(*val, 1000);
+ *val = sign_extend32(regval, 15) * (long)data->curr_lsb_ua;
+ *val = DIV_ROUND_CLOSEST(*val, (long)MILLI);
break;
case TMP51X_LOCAL_TEMP_RESULT:
case TMP51X_REMOTE_TEMP_RESULT_1:
@@ -223,7 +235,7 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
case TMP51X_REMOTE_TEMP_LIMIT_2:
case TMP513_REMOTE_TEMP_LIMIT_3:
// 1lsb = 0.0625 degrees centigrade
- *val = sign_extend32(regval, 16) >> TMP51X_TEMP_SHIFT;
+ *val = sign_extend32(regval, 15) >> TMP51X_TEMP_SHIFT;
*val = DIV_ROUND_CLOSEST(*val * 625, 10);
break;
case TMP51X_N_FACTOR_AND_HYST_1:
@@ -252,7 +264,7 @@ static int tmp51x_set_value(struct tmp51x_data *data, u8 reg, long val)
* The user enter current value and we convert it to
* voltage. 1lsb = 10uV
*/
- val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10000);
+ val = DIV_ROUND_CLOSEST(val * (long)data->shunt_uohms, 10 * (long)MILLI);
max_val = U16_MAX >> tmp51x_get_pga_shift(data);
regval = clamp_val(val, -max_val, max_val);
break;
@@ -434,7 +446,7 @@ static umode_t tmp51x_is_visible(const void *_data,
switch (type) {
case hwmon_temp:
- if (data->id == tmp512 && channel == 3)
+ if (channel >= data->max_channels)
return 0;
switch (attr) {
case hwmon_temp_input:
@@ -542,18 +554,16 @@ static int tmp51x_calibrate(struct tmp51x_data *data)
if (data->shunt_uohms == 0)
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION, 0);
- max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * 1000 * 1000,
- data->shunt_uohms);
+ max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * MICRO, data->shunt_uohms);
/*
* Calculate the minimal bit resolution for the current and the power.
* Those values will be used during register interpretation.
*/
- data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * 1000, 32767);
+ data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * MILLI, 32767);
data->pwr_lsb_uw = 20 * data->curr_lsb_ua;
- div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms,
- 1000 * 1000);
+ div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms, MICRO);
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION,
DIV_ROUND_CLOSEST(40960, div));
@@ -585,7 +595,7 @@ static int tmp51x_init(struct tmp51x_data *data)
if (ret < 0)
return ret;
- if (data->id == tmp513) {
+ if (data->max_channels == TMP513_MAX_CHANNELS) {
ret = regmap_write(data->regmap, TMP513_N_FACTOR_3,
data->nfactor[2] << 8);
if (ret < 0)
@@ -601,22 +611,16 @@ static int tmp51x_init(struct tmp51x_data *data)
}
static const struct i2c_device_id tmp51x_id[] = {
- { "tmp512", tmp512 },
- { "tmp513", tmp513 },
+ { "tmp512", TMP512_MAX_CHANNELS },
+ { "tmp513", TMP513_MAX_CHANNELS },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp51x_id);
static const struct of_device_id tmp51x_of_match[] = {
- {
- .compatible = "ti,tmp512",
- .data = (void *)tmp512
- },
- {
- .compatible = "ti,tmp513",
- .data = (void *)tmp513
- },
- { },
+ { .compatible = "ti,tmp512", .data = (void *)TMP512_MAX_CHANNELS },
+ { .compatible = "ti,tmp513", .data = (void *)TMP513_MAX_CHANNELS },
+ { }
};
MODULE_DEVICE_TABLE(of, tmp51x_of_match);
@@ -628,9 +632,9 @@ static int tmp51x_vbus_range_to_reg(struct device *dev,
} else if (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_16V) {
data->shunt_config &= ~TMP51X_BUS_VOLTAGE_MASK;
} else {
- dev_err(dev, "ti,bus-range-microvolt is invalid: %u\n",
- data->vbus_range_uvolt);
- return -EINVAL;
+ return dev_err_probe(dev, -EINVAL,
+ "ti,bus-range-microvolt is invalid: %u\n",
+ data->vbus_range_uvolt);
}
return 0;
}
@@ -646,8 +650,8 @@ static int tmp51x_pga_gain_to_reg(struct device *dev, struct tmp51x_data *data)
} else if (data->pga_gain == 1) {
data->shunt_config |= CURRENT_SENSE_VOLTAGE_40_MASK;
} else {
- dev_err(dev, "ti,pga-gain is invalid: %u\n", data->pga_gain);
- return -EINVAL;
+ return dev_err_probe(dev, -EINVAL,
+ "ti,pga-gain is invalid: %u\n", data->pga_gain);
}
return 0;
}
@@ -655,7 +659,6 @@ static int tmp51x_pga_gain_to_reg(struct device *dev, struct tmp51x_data *data)
static int tmp51x_read_properties(struct device *dev, struct tmp51x_data *data)
{
int ret;
- u32 nfactor[3];
u32 val;
ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val);
@@ -673,16 +676,14 @@ static int tmp51x_read_properties(struct device *dev, struct tmp51x_data *data)
if (ret < 0)
return ret;
- ret = device_property_read_u32_array(dev, "ti,nfactor", nfactor,
- (data->id == tmp513) ? 3 : 2);
- if (ret >= 0)
- memcpy(data->nfactor, nfactor, (data->id == tmp513) ? 3 : 2);
+ device_property_read_u32_array(dev, "ti,nfactor", data->nfactor,
+ data->max_channels - 1);
// Check if shunt value is compatible with pga-gain
- if (data->shunt_uohms > data->pga_gain * 40 * 1000 * 1000) {
- dev_err(dev, "shunt-resistor: %u too big for pga_gain: %u\n",
- data->shunt_uohms, data->pga_gain);
- return -EINVAL;
+ if (data->shunt_uohms > data->pga_gain * 40 * MICRO) {
+ return dev_err_probe(dev, -EINVAL,
+ "shunt-resistor: %u too big for pga_gain: %u\n",
+ data->shunt_uohms, data->pga_gain);
}
return 0;
@@ -698,8 +699,7 @@ static void tmp51x_use_default(struct tmp51x_data *data)
static int tmp51x_configure(struct device *dev, struct tmp51x_data *data)
{
data->shunt_config = TMP51X_SHUNT_CONFIG_DEFAULT;
- data->temp_config = (data->id == tmp513) ?
- TMP513_TEMP_CONFIG_DEFAULT : TMP512_TEMP_CONFIG_DEFAULT;
+ data->temp_config = TMP51X_TEMP_CONFIG_DEFAULT(data->max_channels);
if (dev->of_node)
return tmp51x_read_properties(dev, data);
@@ -720,25 +720,20 @@ static int tmp51x_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
- data->id = (uintptr_t)i2c_get_match_data(client);
+ data->max_channels = (uintptr_t)i2c_get_match_data(client);
ret = tmp51x_configure(dev, data);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "error configuring the device\n");
data->regmap = devm_regmap_init_i2c(client, &tmp51x_regmap_config);
- if (IS_ERR(data->regmap)) {
- dev_err(dev, "failed to allocate register map\n");
- return PTR_ERR(data->regmap);
- }
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "failed to allocate register map\n");
ret = tmp51x_init(data);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return -ENODEV;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "error configuring the device\n");
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data,
diff --git a/drivers/hwmon/tps23861.c b/drivers/hwmon/tps23861.c
index d33ecbac00d6..4cb3960d5170 100644
--- a/drivers/hwmon/tps23861.c
+++ b/drivers/hwmon/tps23861.c
@@ -114,10 +114,9 @@ struct tps23861_data {
struct regmap *regmap;
u32 shunt_resistor;
struct i2c_client *client;
- struct dentry *debugfs_dir;
};
-static struct regmap_config tps23861_regmap_config = {
+static const struct regmap_config tps23861_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x6f,
@@ -132,7 +131,7 @@ static int tps23861_read_temp(struct tps23861_data *data, long *val)
if (err < 0)
return err;
- *val = (regval * TEMPERATURE_LSB) - 20000;
+ *val = ((long)regval * TEMPERATURE_LSB) - 20000;
return 0;
}
@@ -503,25 +502,6 @@ static int tps23861_port_status_show(struct seq_file *s, void *data)
DEFINE_SHOW_ATTRIBUTE(tps23861_port_status);
-static void tps23861_init_debugfs(struct tps23861_data *data,
- struct device *hwmon_dev)
-{
- const char *debugfs_name;
-
- debugfs_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "%s-%s",
- data->client->name, dev_name(hwmon_dev));
- if (!debugfs_name)
- return;
-
- data->debugfs_dir = debugfs_create_dir(debugfs_name, NULL);
-
- debugfs_create_file("port_status",
- 0400,
- data->debugfs_dir,
- data,
- &tps23861_port_status_fops);
-}
-
static int tps23861_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -562,18 +542,12 @@ static int tps23861_probe(struct i2c_client *client)
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
- tps23861_init_debugfs(data, hwmon_dev);
+ debugfs_create_file("port_status", 0400, client->debugfs, data,
+ &tps23861_port_status_fops);
return 0;
}
-static void tps23861_remove(struct i2c_client *client)
-{
- struct tps23861_data *data = i2c_get_clientdata(client);
-
- debugfs_remove_recursive(data->debugfs_dir);
-}
-
static const struct of_device_id __maybe_unused tps23861_of_match[] = {
{ .compatible = "ti,tps23861", },
{ },
@@ -582,7 +556,6 @@ MODULE_DEVICE_TABLE(of, tps23861_of_match);
static struct i2c_driver tps23861_driver = {
.probe = tps23861_probe,
- .remove = tps23861_remove,
.driver = {
.name = "tps23861",
.of_match_table = of_match_ptr(tps23861_of_match),
diff --git a/drivers/hwmon/tsc1641.c b/drivers/hwmon/tsc1641.c
new file mode 100644
index 000000000000..2b5d34bab146
--- /dev/null
+++ b/drivers/hwmon/tsc1641.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for ST Microelectronics TSC1641 I2C power monitor
+ *
+ * 60 V, 16-bit high-precision power monitor with I2C and MIPI I3C interface
+ * Datasheet: https://www.st.com/resource/en/datasheet/tsc1641.pdf
+ *
+ * Copyright (C) 2025 Igor Reznichenko <igor@reznichenko.net>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/util_macros.h>
+
+/* I2C registers */
+#define TSC1641_CONFIG 0x00
+#define TSC1641_SHUNT_VOLTAGE 0x01
+#define TSC1641_LOAD_VOLTAGE 0x02
+#define TSC1641_POWER 0x03
+#define TSC1641_CURRENT 0x04
+#define TSC1641_TEMP 0x05
+#define TSC1641_MASK 0x06
+#define TSC1641_FLAG 0x07
+#define TSC1641_RSHUNT 0x08 /* Shunt resistance */
+#define TSC1641_SOL 0x09
+#define TSC1641_SUL 0x0A
+#define TSC1641_LOL 0x0B
+#define TSC1641_LUL 0x0C
+#define TSC1641_POL 0x0D
+#define TSC1641_TOL 0x0E
+#define TSC1641_MANUF_ID 0xFE /* 0x0006 */
+#define TSC1641_DIE_ID 0xFF /* 0x1000 */
+#define TSC1641_MAX_REG 0xFF
+
+#define TSC1641_RSHUNT_DEFAULT 1000 /* 1mOhm */
+#define TSC1641_CONFIG_DEFAULT 0x003F /* Default mode and temperature sensor */
+#define TSC1641_MASK_DEFAULT 0xFC00 /* Unmask all alerts */
+
+/* Bit mask for conversion time in the configuration register */
+#define TSC1641_CONV_TIME_MASK GENMASK(7, 4)
+
+#define TSC1641_CONV_TIME_DEFAULT 1024
+#define TSC1641_MIN_UPDATE_INTERVAL 1024
+
+/* LSB value of different registers */
+#define TSC1641_VLOAD_LSB_MVOLT 2
+#define TSC1641_POWER_LSB_UWATT 25000
+#define TSC1641_VSHUNT_LSB_NVOLT 2500 /* Use nanovolts to make it integer */
+#define TSC1641_RSHUNT_LSB_UOHM 10
+#define TSC1641_TEMP_LSB_MDEGC 500
+
+/* Limits based on datasheet */
+#define TSC1641_RSHUNT_MIN_UOHM 100
+#define TSC1641_RSHUNT_MAX_UOHM 655350
+#define TSC1641_CURR_ABS_MAX_MAMP 819200 /* Max current at 100uOhm*/
+
+#define TSC1641_ALERT_POL_MASK BIT(1)
+#define TSC1641_ALERT_LATCH_EN_MASK BIT(0)
+
+/* Flags indicating alerts in TSC1641_FLAG register*/
+#define TSC1641_SAT_FLAG BIT(13)
+#define TSC1641_SHUNT_OV_FLAG BIT(6)
+#define TSC1641_SHUNT_UV_FLAG BIT(5)
+#define TSC1641_LOAD_OV_FLAG BIT(4)
+#define TSC1641_LOAD_UV_FLAG BIT(3)
+#define TSC1641_POWER_OVER_FLAG BIT(2)
+#define TSC1641_TEMP_OVER_FLAG BIT(1)
+
+static bool tsc1641_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TSC1641_CONFIG:
+ case TSC1641_MASK:
+ case TSC1641_RSHUNT:
+ case TSC1641_SOL:
+ case TSC1641_SUL:
+ case TSC1641_LOL:
+ case TSC1641_LUL:
+ case TSC1641_POL:
+ case TSC1641_TOL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tsc1641_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TSC1641_SHUNT_VOLTAGE:
+ case TSC1641_LOAD_VOLTAGE:
+ case TSC1641_POWER:
+ case TSC1641_CURRENT:
+ case TSC1641_TEMP:
+ case TSC1641_FLAG:
+ case TSC1641_MANUF_ID:
+ case TSC1641_DIE_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tsc1641_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .use_single_write = true,
+ .use_single_read = true,
+ .max_register = TSC1641_MAX_REG,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = tsc1641_volatile_reg,
+ .writeable_reg = tsc1641_writeable_reg,
+};
+
+struct tsc1641_data {
+ long rshunt_uohm;
+ long current_lsb_ua;
+ struct regmap *regmap;
+};
+
+/*
+ * Upper limit due to chip 16-bit shunt register, lower limit to
+ * prevent current and power registers overflow
+ */
+static inline int tsc1641_validate_shunt(u32 val)
+{
+ if (val < TSC1641_RSHUNT_MIN_UOHM || val > TSC1641_RSHUNT_MAX_UOHM)
+ return -EINVAL;
+ return 0;
+}
+
+static int tsc1641_set_shunt(struct tsc1641_data *data, u32 val)
+{
+ struct regmap *regmap = data->regmap;
+ long rshunt_reg;
+
+ /* RSHUNT register LSB is 10uOhm so need to divide further */
+ rshunt_reg = DIV_ROUND_CLOSEST(val, TSC1641_RSHUNT_LSB_UOHM);
+ /*
+ * Clamp value to the nearest multiple of TSC1641_RSHUNT_LSB_UOHM
+ * in case shunt value provided was not a multiple
+ */
+ data->rshunt_uohm = rshunt_reg * TSC1641_RSHUNT_LSB_UOHM;
+ data->current_lsb_ua = DIV_ROUND_CLOSEST(TSC1641_VSHUNT_LSB_NVOLT * 1000,
+ data->rshunt_uohm);
+
+ return regmap_write(regmap, TSC1641_RSHUNT, rshunt_reg);
+}
+
+/*
+ * Conversion times in uS, value in CONFIG[CT3:CT0] corresponds to index in this array
+ * See "Table 14. CT3 to CT0: conversion time" in:
+ * https://www.st.com/resource/en/datasheet/tsc1641.pdf
+ */
+static const int tsc1641_conv_times[] = { 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 };
+
+static int tsc1641_reg_to_upd_interval(u16 config)
+{
+ int idx = FIELD_GET(TSC1641_CONV_TIME_MASK, config);
+
+ idx = clamp_val(idx, 0, ARRAY_SIZE(tsc1641_conv_times) - 1);
+ int conv_time = tsc1641_conv_times[idx];
+
+ /* Don't support sub-millisecond update interval as it's not supported in hwmon */
+ conv_time = max(conv_time, TSC1641_MIN_UPDATE_INTERVAL);
+ /* Return nearest value in milliseconds */
+ return DIV_ROUND_CLOSEST(conv_time, 1000);
+}
+
+static u16 tsc1641_upd_interval_to_reg(long interval)
+{
+ /* Supported interval is 1ms - 33ms */
+ interval = clamp_val(interval, 1, 33);
+
+ int conv = interval * 1000;
+ int conv_bits = find_closest(conv, tsc1641_conv_times,
+ ARRAY_SIZE(tsc1641_conv_times));
+
+ return FIELD_PREP(TSC1641_CONV_TIME_MASK, conv_bits);
+}
+
+static int tsc1641_chip_write(struct device *dev, u32 attr, long val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return regmap_update_bits(data->regmap, TSC1641_CONFIG,
+ TSC1641_CONV_TIME_MASK,
+ tsc1641_upd_interval_to_reg(val));
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tsc1641_chip_read(struct device *dev, u32 attr, long *val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ u32 regval;
+ int ret;
+
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ ret = regmap_read(data->regmap, TSC1641_CONFIG, &regval);
+ if (ret)
+ return ret;
+
+ *val = tsc1641_reg_to_upd_interval(regval);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tsc1641_flag_read(struct regmap *regmap, u32 flag, long *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read_bypassed(regmap, TSC1641_FLAG, &regval);
+ if (ret)
+ return ret;
+
+ *val = !!(regval & flag);
+ return 0;
+}
+
+static int tsc1641_in_read(struct device *dev, u32 attr, long *val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+ int ret, reg;
+ long sat_flag;
+
+ switch (attr) {
+ case hwmon_in_input:
+ reg = TSC1641_LOAD_VOLTAGE;
+ break;
+ case hwmon_in_min:
+ reg = TSC1641_LUL;
+ break;
+ case hwmon_in_max:
+ reg = TSC1641_LOL;
+ break;
+ case hwmon_in_min_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_LOAD_UV_FLAG, val);
+ case hwmon_in_max_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_LOAD_OV_FLAG, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ /* Check if load voltage is out of range */
+ if (reg == TSC1641_LOAD_VOLTAGE) {
+ /* Register is 15-bit max */
+ if (regval & 0x8000)
+ return -ENODATA;
+
+ ret = tsc1641_flag_read(regmap, TSC1641_SAT_FLAG, &sat_flag);
+ if (ret)
+ return ret;
+ /* Out of range conditions per datasheet */
+ if (sat_flag && (regval == 0x7FFF || !regval))
+ return -ENODATA;
+ }
+
+ *val = regval * TSC1641_VLOAD_LSB_MVOLT;
+ return 0;
+}
+
+/* Chip supports bidirectional (positive or negative) current */
+static int tsc1641_curr_read(struct device *dev, u32 attr, long *val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ int regval;
+ int ret, reg;
+ long sat_flag;
+
+ /* Current limits are the shunt under/over voltage limits */
+ switch (attr) {
+ case hwmon_curr_input:
+ reg = TSC1641_CURRENT;
+ break;
+ case hwmon_curr_min:
+ reg = TSC1641_SUL;
+ break;
+ case hwmon_curr_max:
+ reg = TSC1641_SOL;
+ break;
+ case hwmon_curr_min_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_SHUNT_UV_FLAG, val);
+ case hwmon_curr_max_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_SHUNT_OV_FLAG, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ /*
+ * Current uses shunt voltage, so check if it's out of range.
+ * We report current register in sysfs to stay consistent with internal
+ * power calculations which use current register values
+ */
+ if (reg == TSC1641_CURRENT) {
+ ret = regmap_read(regmap, TSC1641_SHUNT_VOLTAGE, &regval);
+ if (ret)
+ return ret;
+
+ ret = tsc1641_flag_read(regmap, TSC1641_SAT_FLAG, &sat_flag);
+ if (ret)
+ return ret;
+
+ if (sat_flag && (regval == 0x7FFF || regval == 0x8000))
+ return -ENODATA;
+ }
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ /* Current in milliamps, signed */
+ *val = DIV_ROUND_CLOSEST((s16)regval * data->current_lsb_ua, 1000);
+ return 0;
+}
+
+static int tsc1641_power_read(struct device *dev, u32 attr, long *val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+ int ret, reg;
+
+ switch (attr) {
+ case hwmon_power_input:
+ reg = TSC1641_POWER;
+ break;
+ case hwmon_power_max:
+ reg = TSC1641_POL;
+ break;
+ case hwmon_power_max_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_POWER_OVER_FLAG, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ *val = regval * TSC1641_POWER_LSB_UWATT;
+ return 0;
+}
+
+static int tsc1641_temp_read(struct device *dev, u32 attr, long *val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+ int ret, reg;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = TSC1641_TEMP;
+ break;
+ case hwmon_temp_max:
+ reg = TSC1641_TOL;
+ break;
+ case hwmon_temp_max_alarm:
+ return tsc1641_flag_read(regmap, TSC1641_TEMP_OVER_FLAG, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ /* 0x8000 means that TEMP measurement not enabled */
+ if (reg == TSC1641_TEMP && regval == 0x8000)
+ return -ENODATA;
+
+ /* Both temperature and limit registers are signed */
+ *val = (s16)regval * TSC1641_TEMP_LSB_MDEGC;
+ return 0;
+}
+
+static int tsc1641_in_write(struct device *dev, u32 attr, long val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+ int reg;
+
+ switch (attr) {
+ case hwmon_in_min:
+ reg = TSC1641_LUL;
+ break;
+ case hwmon_in_max:
+ reg = TSC1641_LOL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ /* Clamp to full register range */
+ val = clamp_val(val, 0, TSC1641_VLOAD_LSB_MVOLT * USHRT_MAX);
+ regval = DIV_ROUND_CLOSEST(val, TSC1641_VLOAD_LSB_MVOLT);
+
+ return regmap_write(regmap, reg, regval);
+}
+
+static int tsc1641_curr_write(struct device *dev, u32 attr, long val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ int reg, regval;
+
+ switch (attr) {
+ case hwmon_curr_min:
+ reg = TSC1641_SUL;
+ break;
+ case hwmon_curr_max:
+ reg = TSC1641_SOL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Clamp to prevent over/underflow below */
+ val = clamp_val(val, -TSC1641_CURR_ABS_MAX_MAMP, TSC1641_CURR_ABS_MAX_MAMP);
+ /* Convert val in milliamps to register */
+ regval = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_ua);
+ /*
+ * Prevent signed 16-bit overflow.
+ * Integer arithmetic and shunt scaling can quantize values near 0x7FFF/0x8000,
+ * so reading and writing back may not preserve the exact original register value.
+ */
+ regval = clamp_val(regval, SHRT_MIN, SHRT_MAX);
+ /* SUL and SOL registers are signed */
+ return regmap_write(regmap, reg, regval & 0xFFFF);
+}
+
+static int tsc1641_power_write(struct device *dev, u32 attr, long val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int regval;
+
+ switch (attr) {
+ case hwmon_power_max:
+ /* Clamp to full register range */
+ val = clamp_val(val, 0, TSC1641_POWER_LSB_UWATT * USHRT_MAX);
+ regval = DIV_ROUND_CLOSEST(val, TSC1641_POWER_LSB_UWATT);
+ return regmap_write(regmap, TSC1641_POL, regval);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tsc1641_temp_write(struct device *dev, u32 attr, long val)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ int regval;
+
+ switch (attr) {
+ case hwmon_temp_max:
+ /* Clamp to full register range */
+ val = clamp_val(val, TSC1641_TEMP_LSB_MDEGC * SHRT_MIN,
+ TSC1641_TEMP_LSB_MDEGC * SHRT_MAX);
+ regval = DIV_ROUND_CLOSEST(val, TSC1641_TEMP_LSB_MDEGC);
+ /* TOL register is signed */
+ return regmap_write(regmap, TSC1641_TOL, regval & 0xFFFF);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t tsc1641_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ case hwmon_in_min:
+ case hwmon_in_max:
+ return 0644;
+ case hwmon_in_min_alarm:
+ case hwmon_in_max_alarm:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ return 0444;
+ case hwmon_curr_min:
+ case hwmon_curr_max:
+ return 0644;
+ case hwmon_curr_min_alarm:
+ case hwmon_curr_max_alarm:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_input:
+ return 0444;
+ case hwmon_power_max:
+ return 0644;
+ case hwmon_power_max_alarm:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ case hwmon_temp_max:
+ return 0644;
+ case hwmon_temp_max_alarm:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int tsc1641_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_chip:
+ return tsc1641_chip_read(dev, attr, val);
+ case hwmon_in:
+ return tsc1641_in_read(dev, attr, val);
+ case hwmon_curr:
+ return tsc1641_curr_read(dev, attr, val);
+ case hwmon_power:
+ return tsc1641_power_read(dev, attr, val);
+ case hwmon_temp:
+ return tsc1641_temp_read(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tsc1641_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_chip:
+ return tsc1641_chip_write(dev, attr, val);
+ case hwmon_in:
+ return tsc1641_in_write(dev, attr, val);
+ case hwmon_curr:
+ return tsc1641_curr_write(dev, attr, val);
+ case hwmon_power:
+ return tsc1641_power_write(dev, attr, val);
+ case hwmon_temp:
+ return tsc1641_temp_write(dev, attr, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info * const tsc1641_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM |
+ HWMON_I_MIN | HWMON_I_MIN_ALARM),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM |
+ HWMON_C_MIN | HWMON_C_MIN_ALARM),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
+ NULL
+};
+
+static ssize_t shunt_resistor_show(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%li\n", data->rshunt_uohm);
+}
+
+static ssize_t shunt_resistor_store(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ struct tsc1641_data *data = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ ret = tsc1641_validate_shunt(val);
+ if (ret < 0)
+ return ret;
+
+ ret = tsc1641_set_shunt(data, val);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+static const struct hwmon_ops tsc1641_hwmon_ops = {
+ .is_visible = tsc1641_is_visible,
+ .read = tsc1641_read,
+ .write = tsc1641_write,
+};
+
+static const struct hwmon_chip_info tsc1641_chip_info = {
+ .ops = &tsc1641_hwmon_ops,
+ .info = tsc1641_info,
+};
+
+static DEVICE_ATTR_RW(shunt_resistor);
+
+/* Shunt resistor value is exposed via sysfs attribute */
+static struct attribute *tsc1641_attrs[] = {
+ &dev_attr_shunt_resistor.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(tsc1641);
+
+static int tsc1641_init(struct device *dev, struct tsc1641_data *data)
+{
+ struct regmap *regmap = data->regmap;
+ bool active_high;
+ u32 shunt;
+ int ret;
+
+ if (device_property_read_u32(dev, "shunt-resistor-micro-ohms", &shunt) < 0)
+ shunt = TSC1641_RSHUNT_DEFAULT;
+
+ if (tsc1641_validate_shunt(shunt) < 0) {
+ dev_err(dev, "invalid shunt resistor value %u\n", shunt);
+ return -EINVAL;
+ }
+
+ ret = tsc1641_set_shunt(data, shunt);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(regmap, TSC1641_CONFIG, TSC1641_CONFIG_DEFAULT);
+ if (ret < 0)
+ return ret;
+
+ active_high = device_property_read_bool(dev, "st,alert-polarity-active-high");
+
+ return regmap_write(regmap, TSC1641_MASK, TSC1641_MASK_DEFAULT |
+ FIELD_PREP(TSC1641_ALERT_POL_MASK, active_high));
+}
+
+static int tsc1641_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct tsc1641_data *data;
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = devm_regmap_init_i2c(client, &tsc1641_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "failed to allocate register map\n");
+
+ ret = tsc1641_init(dev, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to configure device\n");
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &tsc1641_chip_info, tsc1641_groups);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n",
+ client->name, data->rshunt_uohm);
+
+ return 0;
+}
+
+static const struct i2c_device_id tsc1641_id[] = {
+ { "tsc1641", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tsc1641_id);
+
+static const struct of_device_id __maybe_unused tsc1641_of_match[] = {
+ { .compatible = "st,tsc1641" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tsc1641_of_match);
+
+static struct i2c_driver tsc1641_driver = {
+ .driver = {
+ .name = "tsc1641",
+ .of_match_table = of_match_ptr(tsc1641_of_match),
+ },
+ .probe = tsc1641_probe,
+ .id_table = tsc1641_id,
+};
+
+module_i2c_driver(tsc1641_driver);
+
+MODULE_AUTHOR("Igor Reznichenko <igor@reznichenko.net>");
+MODULE_DESCRIPTION("tsc1641 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/ultra45_env.c b/drivers/hwmon/ultra45_env.c
index 3b580f229887..e4f1bb538628 100644
--- a/drivers/hwmon/ultra45_env.c
+++ b/drivers/hwmon/ultra45_env.c
@@ -18,7 +18,7 @@
#define DRV_MODULE_VERSION "0.1"
-MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
+MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
MODULE_DESCRIPTION("Ultra45 environmental monitor driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_MODULE_VERSION);
@@ -291,7 +291,7 @@ out_iounmap:
goto out;
}
-static int env_remove(struct platform_device *op)
+static void env_remove(struct platform_device *op)
{
struct env *p = platform_get_drvdata(op);
@@ -300,8 +300,6 @@ static int env_remove(struct platform_device *op)
hwmon_device_unregister(p->hwmon_dev);
of_iounmap(&op->resource[0], p->regs, REG_SIZE);
}
-
- return 0;
}
static const struct of_device_id env_match[] = {
diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c
index d82a3b454d0e..a2e350f52a9e 100644
--- a/drivers/hwmon/vexpress-hwmon.c
+++ b/drivers/hwmon/vexpress-hwmon.c
@@ -72,7 +72,7 @@ static umode_t vexpress_hwmon_attr_is_visible(struct kobject *kobj,
struct device_attribute, attr);
if (dev_attr->show == vexpress_hwmon_label_show &&
- !of_get_property(dev->of_node, "label", NULL))
+ !of_property_present(dev->of_node, "label"))
return 0;
return attr->mode;
diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c
index e5d18dac8ee7..823bff2871e1 100644
--- a/drivers/hwmon/via-cputemp.c
+++ b/drivers/hwmon/via-cputemp.c
@@ -182,7 +182,7 @@ exit_remove:
return err;
}
-static int via_cputemp_remove(struct platform_device *pdev)
+static void via_cputemp_remove(struct platform_device *pdev)
{
struct via_cputemp_data *data = platform_get_drvdata(pdev);
@@ -190,7 +190,6 @@ static int via_cputemp_remove(struct platform_device *pdev)
if (data->vrm)
device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
- return 0;
}
static struct platform_driver via_cputemp_driver = {
diff --git a/drivers/hwmon/via686a.c b/drivers/hwmon/via686a.c
index 407933d6e425..bbaeb808cc15 100644
--- a/drivers/hwmon/via686a.c
+++ b/drivers/hwmon/via686a.c
@@ -786,14 +786,12 @@ exit_remove_files:
return err;
}
-static int via686a_remove(struct platform_device *pdev)
+static void via686a_remove(struct platform_device *pdev)
{
struct via686a_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&pdev->dev.kobj, &via686a_group);
-
- return 0;
}
static struct platform_driver via686a_driver = {
diff --git a/drivers/hwmon/vt1211.c b/drivers/hwmon/vt1211.c
index fcd4be7a5a85..1e52cabd6e24 100644
--- a/drivers/hwmon/vt1211.c
+++ b/drivers/hwmon/vt1211.c
@@ -142,9 +142,15 @@ struct vt1211_data {
* in5 (ix = 5) is special. It's the internal 3.3V so it's scaled in the
* driver according to the VT1211 BIOS porting guide
*/
-#define IN_FROM_REG(ix, reg) ((reg) < 3 ? 0 : (ix) == 5 ? \
- (((reg) - 3) * 15882 + 479) / 958 : \
- (((reg) - 3) * 10000 + 479) / 958)
+static int in_from_reg(int ix, int reg)
+{
+ if (reg < 3)
+ return 0;
+ if (ix == 5)
+ return ((reg - 3) * 15882 + 479) / 958;
+ return ((reg - 3) * 10000 + 479) / 958;
+}
+
#define IN_TO_REG(ix, val) (clamp_val((ix) == 5 ? \
((val) * 958 + 7941) / 15882 + 3 : \
((val) * 958 + 5000) / 10000 + 3, 0, 255))
@@ -156,10 +162,15 @@ struct vt1211_data {
* temp3-7 are thermistor based so the driver returns the voltage measured at
* the pin (range 0V - 2.2V).
*/
-#define TEMP_FROM_REG(ix, reg) ((ix) == 0 ? (reg) * 1000 : \
- (ix) == 1 ? (reg) < 51 ? 0 : \
- ((reg) - 51) * 1000 : \
- ((253 - (reg)) * 2200 + 105) / 210)
+static int temp_from_reg(int ix, int reg)
+{
+ if (ix == 0)
+ return reg * 1000;
+ if (ix == 1)
+ return reg < 51 ? 0 : (reg - 51) * 1000;
+ return ((253 - reg) * 2200 + 105) / 210;
+}
+
#define TEMP_TO_REG(ix, val) clamp_val( \
((ix) == 0 ? ((val) + 500) / 1000 : \
(ix) == 1 ? ((val) + 500) / 1000 + 51 : \
@@ -167,8 +178,14 @@ struct vt1211_data {
#define DIV_FROM_REG(reg) (1 << (reg))
-#define RPM_FROM_REG(reg, div) (((reg) == 0) || ((reg) == 255) ? 0 : \
- 1310720 / (reg) / DIV_FROM_REG(div))
+static int rpm_from_reg(int reg, int div)
+{
+ if (reg == 0 || reg == 255)
+ return 0;
+
+ return 1310720 / reg / DIV_FROM_REG(div);
+}
+
#define RPM_TO_REG(val, div) ((val) == 0 ? 255 : \
clamp_val((1310720 / (val) / \
DIV_FROM_REG(div)), 1, 254))
@@ -343,13 +360,13 @@ static ssize_t show_in(struct device *dev, struct device_attribute *attr,
switch (fn) {
case SHOW_IN_INPUT:
- res = IN_FROM_REG(ix, data->in[ix]);
+ res = in_from_reg(ix, data->in[ix]);
break;
case SHOW_SET_IN_MIN:
- res = IN_FROM_REG(ix, data->in_min[ix]);
+ res = in_from_reg(ix, data->in_min[ix]);
break;
case SHOW_SET_IN_MAX:
- res = IN_FROM_REG(ix, data->in_max[ix]);
+ res = in_from_reg(ix, data->in_max[ix]);
break;
case SHOW_IN_ALARM:
res = (data->alarms >> bitalarmin[ix]) & 1;
@@ -417,13 +434,13 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
switch (fn) {
case SHOW_TEMP_INPUT:
- res = TEMP_FROM_REG(ix, data->temp[ix]);
+ res = temp_from_reg(ix, data->temp[ix]);
break;
case SHOW_SET_TEMP_MAX:
- res = TEMP_FROM_REG(ix, data->temp_max[ix]);
+ res = temp_from_reg(ix, data->temp_max[ix]);
break;
case SHOW_SET_TEMP_MAX_HYST:
- res = TEMP_FROM_REG(ix, data->temp_hyst[ix]);
+ res = temp_from_reg(ix, data->temp_hyst[ix]);
break;
case SHOW_TEMP_ALARM:
res = (data->alarms >> bitalarmtemp[ix]) & 1;
@@ -493,10 +510,10 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *attr,
switch (fn) {
case SHOW_FAN_INPUT:
- res = RPM_FROM_REG(data->fan[ix], data->fan_div[ix]);
+ res = rpm_from_reg(data->fan[ix], data->fan_div[ix]);
break;
case SHOW_SET_FAN_MIN:
- res = RPM_FROM_REG(data->fan_min[ix], data->fan_div[ix]);
+ res = rpm_from_reg(data->fan_min[ix], data->fan_div[ix]);
break;
case SHOW_SET_FAN_DIV:
res = DIV_FROM_REG(data->fan_div[ix]);
@@ -751,7 +768,7 @@ static ssize_t show_pwm_auto_point_temp(struct device *dev,
int ix = sensor_attr_2->index;
int ap = sensor_attr_2->nr;
- return sprintf(buf, "%d\n", TEMP_FROM_REG(data->pwm_ctl[ix] & 7,
+ return sprintf(buf, "%d\n", temp_from_reg(data->pwm_ctl[ix] & 7,
data->pwm_auto_temp[ap]));
}
@@ -1208,14 +1225,12 @@ EXIT_DEV_REMOVE_SILENT:
return err;
}
-static int vt1211_remove(struct platform_device *pdev)
+static void vt1211_remove(struct platform_device *pdev)
{
struct vt1211_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
vt1211_remove_sysfs(pdev);
-
- return 0;
}
static struct platform_driver vt1211_driver = {
diff --git a/drivers/hwmon/vt8231.c b/drivers/hwmon/vt8231.c
index 16bc16d33cd1..5757a0979f3f 100644
--- a/drivers/hwmon/vt8231.c
+++ b/drivers/hwmon/vt8231.c
@@ -138,7 +138,12 @@ static inline u8 FAN_TO_REG(long rpm, int div)
return clamp_val(1310720 / (rpm * div), 1, 255);
}
-#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : 1310720 / ((val) * (div)))
+static int fan_from_reg(int val, int div)
+{
+ if (val == 0)
+ return 0;
+ return 1310720 / (val * div);
+}
struct vt8231_data {
unsigned short addr;
@@ -561,7 +566,7 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
int nr = sensor_attr->index;
struct vt8231_data *data = vt8231_update_device(dev);
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan[nr],
DIV_FROM_REG(data->fan_div[nr])));
}
@@ -571,7 +576,7 @@ static ssize_t fan_min_show(struct device *dev, struct device_attribute *attr,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
int nr = sensor_attr->index;
struct vt8231_data *data = vt8231_update_device(dev);
- return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr],
+ return sprintf(buf, "%d\n", fan_from_reg(data->fan_min[nr],
DIV_FROM_REG(data->fan_div[nr])));
}
@@ -613,9 +618,8 @@ static ssize_t fan_div_store(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
unsigned long val;
int nr = sensor_attr->index;
- int old = vt8231_read_value(data, VT8231_REG_FANDIV);
- long min = FAN_FROM_REG(data->fan_min[nr],
- DIV_FROM_REG(data->fan_div[nr]));
+ int old;
+ long min;
int err;
err = kstrtoul(buf, 10, &val);
@@ -623,6 +627,8 @@ static ssize_t fan_div_store(struct device *dev,
return err;
mutex_lock(&data->update_lock);
+ old = vt8231_read_value(data, VT8231_REG_FANDIV);
+ min = fan_from_reg(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]));
switch (val) {
case 1:
data->fan_div[nr] = 0;
@@ -892,7 +898,7 @@ exit_remove_files:
return err;
}
-static int vt8231_remove(struct platform_device *pdev)
+static void vt8231_remove(struct platform_device *pdev)
{
struct vt8231_data *data = platform_get_drvdata(pdev);
int i;
@@ -906,13 +912,11 @@ static int vt8231_remove(struct platform_device *pdev)
sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_temps[i]);
sysfs_remove_group(&pdev->dev.kobj, &vt8231_group);
-
- return 0;
}
static struct platform_driver vt8231_driver = {
- .driver = {
+ .driver = {
.name = DRIVER_NAME,
},
.probe = vt8231_probe,
diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c
index fe960c0a624f..a23edd35c19f 100644
--- a/drivers/hwmon/w83627ehf.c
+++ b/drivers/hwmon/w83627ehf.c
@@ -895,7 +895,7 @@ store_target_temp(struct device *dev, struct device_attribute *attr,
if (err < 0)
return err;
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 127);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 127000), 1000);
mutex_lock(&data->update_lock);
data->target_temp[nr] = val;
@@ -920,7 +920,7 @@ store_tolerance(struct device *dev, struct device_attribute *attr,
return err;
/* Limit the temp to 0C - 15C */
- val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 15);
+ val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 15000), 1000);
mutex_lock(&data->update_lock);
reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]);
@@ -1448,7 +1448,8 @@ w83627ehf_do_read_temp(struct w83627ehf_data *data, u32 attr,
return 0;
case hwmon_temp_alarm:
if (channel < 3) {
- int bit[] = { 4, 5, 13 };
+ static const int bit[] = { 4, 5, 13 };
+
*val = (data->alarms >> bit[channel]) & 1;
return 0;
}
@@ -1479,7 +1480,8 @@ w83627ehf_do_read_in(struct w83627ehf_data *data, u32 attr,
return 0;
case hwmon_in_alarm:
if (channel < 10) {
- int bit[] = { 0, 1, 2, 3, 8, 21, 20, 16, 17, 19 };
+ static const int bit[] = { 0, 1, 2, 3, 8, 21, 20, 16, 17, 19 };
+
*val = (data->alarms >> bit[channel]) & 1;
return 0;
}
@@ -1507,7 +1509,8 @@ w83627ehf_do_read_fan(struct w83627ehf_data *data, u32 attr,
return 0;
case hwmon_fan_alarm:
if (channel < 5) {
- int bit[] = { 6, 7, 11, 10, 23 };
+ static const int bit[] = { 6, 7, 11, 10, 23 };
+
*val = (data->alarms >> bit[channel]) & 1;
return 0;
}
diff --git a/drivers/hwmon/w83627hf.c b/drivers/hwmon/w83627hf.c
index b638d672ac45..95115d7b863e 100644
--- a/drivers/hwmon/w83627hf.c
+++ b/drivers/hwmon/w83627hf.c
@@ -1828,7 +1828,7 @@ static int w83627hf_probe(struct platform_device *pdev)
return err;
}
-static int w83627hf_remove(struct platform_device *pdev)
+static void w83627hf_remove(struct platform_device *pdev)
{
struct w83627hf_data *data = platform_get_drvdata(pdev);
@@ -1836,8 +1836,6 @@ static int w83627hf_remove(struct platform_device *pdev)
sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group);
sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group_opt);
-
- return 0;
}
static struct platform_driver w83627hf_driver = {
diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c
index 045eea8378c2..401a28f55f93 100644
--- a/drivers/hwmon/w83773g.c
+++ b/drivers/hwmon/w83773g.c
@@ -290,7 +290,6 @@ static int w83773_probe(struct i2c_client *client)
}
static struct i2c_driver w83773_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "w83773g",
.of_match_table = of_match_ptr(w83773_of_match),
diff --git a/drivers/hwmon/w83781d.c b/drivers/hwmon/w83781d.c
index b33f382f238d..f664c2152a6d 100644
--- a/drivers/hwmon/w83781d.c
+++ b/drivers/hwmon/w83781d.c
@@ -1192,8 +1192,6 @@ static void w83781d_remove_files(struct device *dev)
sysfs_remove_group(&dev->kobj, &w83781d_group_other);
}
-static const struct i2c_device_id w83781d_ids[];
-
static int w83781d_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -1208,7 +1206,7 @@ static int w83781d_probe(struct i2c_client *client)
mutex_init(&data->lock);
mutex_init(&data->update_lock);
- data->type = i2c_match_id(w83781d_ids, client)->driver_data;
+ data->type = (uintptr_t)i2c_get_match_data(client);
data->client = client;
/* attach secondary i2c lm75-like clients */
@@ -1816,16 +1814,13 @@ w83781d_isa_probe(struct platform_device *pdev)
return err;
}
-static int
-w83781d_isa_remove(struct platform_device *pdev)
+static void w83781d_isa_remove(struct platform_device *pdev)
{
struct w83781d_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
w83781d_remove_files(&pdev->dev);
device_remove_file(&pdev->dev, &dev_attr_name);
-
- return 0;
}
static struct platform_driver w83781d_isa_driver = {
@@ -1855,10 +1850,12 @@ w83781d_isa_found(unsigned short address)
}
}
-#define REALLY_SLOW_IO
/*
* We need the timeouts for at least some W83781D-like
* chips. But only if we read 'undefined' registers.
+ * There used to be a "#define REALLY_SLOW_IO" to enforce that, but
+ * this has been without any effect since more than a decade, so it
+ * has been dropped.
*/
val = inb_p(address + 1);
if (inb_p(address + 2) != val
@@ -1867,7 +1864,6 @@ w83781d_isa_found(unsigned short address)
pr_debug("Detection failed at step %d\n", 1);
goto release;
}
-#undef REALLY_SLOW_IO
/*
* We should be able to change the 7 LSB of the address port. The
diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c
index 9681eaa06c8e..996e36951f9d 100644
--- a/drivers/hwmon/w83791d.c
+++ b/drivers/hwmon/w83791d.c
@@ -218,9 +218,14 @@ static u8 fan_to_reg(long rpm, int div)
return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254);
}
-#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \
- ((val) == 255 ? 0 : \
- 1350000 / ((val) * (div))))
+static int fan_from_reg(int val, int div)
+{
+ if (val == 0)
+ return -1;
+ if (val == 255)
+ return 0;
+ return 1350000 / (val * div);
+}
/* for temp1 which is 8-bit resolution, LSB = 1 degree Celsius */
#define TEMP1_FROM_REG(val) ((val) * 1000)
@@ -328,7 +333,7 @@ static void w83791d_print_debug(struct w83791d_data *data, struct device *dev);
static void w83791d_init_client(struct i2c_client *client);
static const struct i2c_device_id w83791d_id[] = {
- { "w83791d", 0 },
+ { "w83791d" },
{ }
};
MODULE_DEVICE_TABLE(i2c, w83791d_id);
@@ -521,7 +526,7 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \
struct w83791d_data *data = w83791d_update_device(dev); \
int nr = sensor_attr->index; \
return sprintf(buf, "%d\n", \
- FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \
+ fan_from_reg(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \
}
show_fan_reg(fan);
@@ -585,10 +590,10 @@ static ssize_t store_fan_div(struct device *dev, struct device_attribute *attr,
if (err)
return err;
+ mutex_lock(&data->update_lock);
/* Save fan_min */
- min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]));
+ min = fan_from_reg(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]));
- mutex_lock(&data->update_lock);
data->fan_div[nr] = div_to_reg(nr, val);
switch (nr) {
diff --git a/drivers/hwmon/w83792d.c b/drivers/hwmon/w83792d.c
index 69ce379a9e13..b0b5f60eea53 100644
--- a/drivers/hwmon/w83792d.c
+++ b/drivers/hwmon/w83792d.c
@@ -296,7 +296,7 @@ static void w83792d_print_debug(struct w83792d_data *data, struct device *dev);
static void w83792d_init_client(struct i2c_client *client);
static const struct i2c_device_id w83792d_id[] = {
- { "w83792d", 0 },
+ { "w83792d" },
{ }
};
MODULE_DEVICE_TABLE(i2c, w83792d_id);
diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c
index 96bab94ba899..67728f60333f 100644
--- a/drivers/hwmon/w83793.c
+++ b/drivers/hwmon/w83793.c
@@ -291,7 +291,7 @@ static void w83793_update_nonvolatile(struct device *dev);
static struct w83793_data *w83793_update_device(struct device *dev);
static const struct i2c_device_id w83793_id[] = {
- { "w83793", 0 },
+ { "w83793" },
{ }
};
MODULE_DEVICE_TABLE(i2c, w83793_id);
@@ -1451,7 +1451,6 @@ static long watchdog_ioctl(struct file *filp, unsigned int cmd,
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
- .llseek = no_llseek,
.open = watchdog_open,
.release = watchdog_close,
.write = watchdog_write,
diff --git a/drivers/hwmon/w83795.c b/drivers/hwmon/w83795.c
index c446e00db658..5174db69db5e 100644
--- a/drivers/hwmon/w83795.c
+++ b/drivers/hwmon/w83795.c
@@ -2134,8 +2134,6 @@ static void w83795_apply_temp_config(struct w83795_data *data, u8 config,
}
}
-static const struct i2c_device_id w83795_id[];
-
static int w83795_probe(struct i2c_client *client)
{
int i;
@@ -2149,7 +2147,7 @@ static int w83795_probe(struct i2c_client *client)
return -ENOMEM;
i2c_set_clientdata(client, data);
- data->chip_type = i2c_match_id(w83795_id, client)->driver_data;
+ data->chip_type = (uintptr_t)i2c_get_match_data(client);
data->bank = i2c_smbus_read_byte_data(client, W83795_REG_BANKSEL);
mutex_init(&data->update_lock);
diff --git a/drivers/hwmon/w83l785ts.c b/drivers/hwmon/w83l785ts.c
index 9c11ed69c055..df77b53a1b2f 100644
--- a/drivers/hwmon/w83l785ts.c
+++ b/drivers/hwmon/w83l785ts.c
@@ -74,7 +74,7 @@ static struct w83l785ts_data *w83l785ts_update_device(struct device *dev);
*/
static const struct i2c_device_id w83l785ts_id[] = {
- { "w83l785ts", 0 },
+ { "w83l785ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, w83l785ts_id);
diff --git a/drivers/hwmon/w83l786ng.c b/drivers/hwmon/w83l786ng.c
index 75874cf7851c..1d9109ca1585 100644
--- a/drivers/hwmon/w83l786ng.c
+++ b/drivers/hwmon/w83l786ng.c
@@ -76,15 +76,25 @@ FAN_TO_REG(long rpm, int div)
return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254);
}
-#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \
- ((val) == 255 ? 0 : \
- 1350000 / ((val) * (div))))
+static int fan_from_reg(int val, int div)
+{
+ if (val == 0)
+ return -1;
+ if (val == 255)
+ return 0;
+ return 1350000 / (val * div);
+}
/* for temp */
#define TEMP_TO_REG(val) (clamp_val(((val) < 0 ? (val) + 0x100 * 1000 \
: (val)) / 1000, 0, 0xff))
-#define TEMP_FROM_REG(val) (((val) & 0x80 ? \
- (val) - 0x100 : (val)) * 1000)
+
+static int temp_from_reg(int val)
+{
+ if (val & 0x80)
+ return (val - 0x100) * 1000;
+ return val * 1000;
+}
/*
* The analog voltage inputs have 8mV LSB. Since the sysfs output is
@@ -280,7 +290,7 @@ static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \
int nr = to_sensor_dev_attr(attr)->index; \
struct w83l786ng_data *data = w83l786ng_update_device(dev); \
return sprintf(buf, "%d\n", \
- FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \
+ fan_from_reg(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \
}
show_fan_reg(fan);
@@ -347,7 +357,7 @@ store_fan_div(struct device *dev, struct device_attribute *attr,
/* Save fan_min */
mutex_lock(&data->update_lock);
- min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]));
+ min = fan_from_reg(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]));
data->fan_div[nr] = DIV_TO_REG(val);
@@ -409,7 +419,7 @@ show_temp(struct device *dev, struct device_attribute *attr, char *buf)
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83l786ng_data *data = w83l786ng_update_device(dev);
- return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr][index]));
+ return sprintf(buf, "%d\n", temp_from_reg(data->temp[nr][index]));
}
static ssize_t
@@ -741,7 +751,7 @@ w83l786ng_probe(struct i2c_client *client)
}
static const struct i2c_device_id w83l786ng_id[] = {
- { "w83l786ng", 0 },
+ { "w83l786ng" },
{ }
};
MODULE_DEVICE_TABLE(i2c, w83l786ng_id);
diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c
index 78d9f52e2a71..11c5d80428cd 100644
--- a/drivers/hwmon/xgene-hwmon.c
+++ b/drivers/hwmon/xgene-hwmon.c
@@ -57,12 +57,6 @@
(MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \
MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type)
-/* PCC defines */
-#define PCC_SIGNATURE_MASK 0x50424300
-#define PCCC_GENERATE_DB_INT BIT(15)
-#define PCCS_CMD_COMPLETE BIT(0)
-#define PCCS_SCI_DOORBEL BIT(1)
-#define PCCS_PLATFORM_NOTIFICATION BIT(3)
/*
* Arbitrary retries in case the remote processor is slow to respond
* to PCC commands
@@ -109,9 +103,7 @@ struct xgene_hwmon_dev {
struct device *hwmon_dev;
bool temp_critical_alarm;
- phys_addr_t comm_base_addr;
- void *pcc_comm_addr;
- u64 usecs_lat;
+ unsigned int usecs_lat;
};
/*
@@ -131,7 +123,8 @@ static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask)
static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
{
- struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
+ struct acpi_pcct_shared_memory __iomem *generic_comm_base =
+ ctx->pcc_chan->shmem;
u32 *ptr = (void *)(generic_comm_base + 1);
int rc, i;
u16 val;
@@ -142,15 +135,15 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
/* Write signature for subspace */
WRITE_ONCE(generic_comm_base->signature,
- cpu_to_le32(PCC_SIGNATURE_MASK | ctx->mbox_idx));
+ cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx));
/* Write to the shared command region */
WRITE_ONCE(generic_comm_base->command,
- cpu_to_le16(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT));
+ cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR));
/* Flip CMD COMPLETE bit */
val = le16_to_cpu(READ_ONCE(generic_comm_base->status));
- val &= ~PCCS_CMD_COMPLETE;
+ val &= ~PCC_STATUS_CMD_COMPLETE;
WRITE_ONCE(generic_comm_base->status, cpu_to_le16(val));
/* Copy the message to the PCC comm space */
@@ -529,7 +522,8 @@ static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg)
static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
{
struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl);
- struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
+ struct acpi_pcct_shared_memory __iomem *generic_comm_base =
+ ctx->pcc_chan->shmem;
struct slimpro_resp_msg amsg;
/*
@@ -544,7 +538,7 @@ static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
msg = generic_comm_base + 1;
/* Check if platform sends interrupt */
if (!xgene_word_tst_and_clr(&generic_comm_base->status,
- PCCS_SCI_DOORBEL))
+ PCC_STATUS_SCI_DOORBELL))
return;
/*
@@ -566,7 +560,7 @@ static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) {
/* Check if platform completes command */
if (xgene_word_tst_and_clr(&generic_comm_base->status,
- PCCS_CMD_COMPLETE)) {
+ PCC_STATUS_CMD_COMPLETE)) {
ctx->sync_msg.msg = ((u32 *)msg)[0];
ctx->sync_msg.param1 = ((u32 *)msg)[1];
ctx->sync_msg.param2 = ((u32 *)msg)[2];
@@ -655,7 +649,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
} else {
struct pcc_mbox_chan *pcc_chan;
const struct acpi_device_id *acpi_id;
- int version;
acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table,
&pdev->dev);
@@ -664,8 +657,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
goto out_mbox_free;
}
- version = (int)acpi_id->driver_data;
-
if (device_property_read_u32(&pdev->dev, "pcc-channel",
&ctx->mbox_idx)) {
dev_err(&pdev->dev, "no pcc-channel property\n");
@@ -692,34 +683,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
}
/*
- * This is the shared communication region
- * for the OS and Platform to communicate over.
- */
- ctx->comm_base_addr = pcc_chan->shmem_base_addr;
- if (ctx->comm_base_addr) {
- if (version == XGENE_HWMON_V2)
- ctx->pcc_comm_addr = (void __force *)devm_ioremap(&pdev->dev,
- ctx->comm_base_addr,
- pcc_chan->shmem_size);
- else
- ctx->pcc_comm_addr = devm_memremap(&pdev->dev,
- ctx->comm_base_addr,
- pcc_chan->shmem_size,
- MEMREMAP_WB);
- } else {
- dev_err(&pdev->dev, "Failed to get PCC comm region\n");
- rc = -ENODEV;
- goto out;
- }
-
- if (!ctx->pcc_comm_addr) {
- dev_err(&pdev->dev,
- "Failed to ioremap PCC comm region\n");
- rc = -ENOMEM;
- goto out;
- }
-
- /*
* pcc_chan->latency is just a Nominal value. In reality
* the remote processor could be much slower to reply.
* So add an arbitrary amount of wait on top of Nominal.
@@ -757,7 +720,7 @@ out_mbox_free:
return rc;
}
-static int xgene_hwmon_remove(struct platform_device *pdev)
+static void xgene_hwmon_remove(struct platform_device *pdev)
{
struct xgene_hwmon_dev *ctx = platform_get_drvdata(pdev);
@@ -768,8 +731,6 @@ static int xgene_hwmon_remove(struct platform_device *pdev)
mbox_free_channel(ctx->mbox_chan);
else
pcc_mbox_free_channel(ctx->pcc_chan);
-
- return 0;
}
static const struct of_device_id xgene_hwmon_of_match[] = {