diff options
Diffstat (limited to 'drivers/hwmon')
303 files changed, 34245 insertions, 10952 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 307477b8a371..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 @@ -734,6 +829,27 @@ config SENSORS_HIH6130 This driver can also be built as a module. If so, the module will be called hih6130. +config SENSORS_HS3001 + tristate "Renesas HS3001 humidity and temperature sensors" + depends on I2C + help + If you say yes here you get support for the Renesas HS3001, + to HS3004 humidity and temperature sensors. + + 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 @@ -800,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 @@ -829,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 @@ -852,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 @@ -922,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 @@ -996,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 @@ -1003,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 @@ -1050,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. @@ -1136,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. @@ -1143,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 @@ -1187,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. @@ -1311,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: @@ -1416,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. @@ -1426,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. @@ -1447,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. @@ -1477,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 @@ -1491,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 @@ -1518,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 @@ -1530,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 @@ -1543,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 @@ -1574,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 @@ -1632,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 @@ -1644,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 @@ -1672,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. @@ -1684,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) @@ -1694,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 @@ -1714,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 @@ -1739,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. @@ -1779,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. @@ -1799,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 @@ -1855,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 @@ -1889,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 @@ -1899,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 @@ -1915,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 @@ -1951,19 +2190,41 @@ config SENSORS_SFCTEMP This driver can also be built as a module. If so, the module will be called sfctemp. -config SENSORS_SMM665 - tristate "Summit Microelectronics SMM665" +config SENSORS_SG2042_MCU + tristate "Sophgo onboard MCU support" depends on I2C + depends on ARCH_SOPHGO || COMPILE_TEST help - If you say yes here you get support for the hardware monitoring - features of the Summit Microelectronics SMM665/SMM665B Six-Channel - Active DC Output Controller / Monitor. + Support for onboard MCU of Sophgo SG2042 SoCs. This mcu provides + power control and some basic information. - Other supported chips are SMM465, SMM665C, SMM764, and SMM766. - Support for those chips is untested. + This driver can be built as a module. If so, the module + will be called sg2042-mcu. - This driver can also be built as a module. If so, the module will - be called smm665. +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" @@ -1999,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. @@ -2022,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. @@ -2053,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 @@ -2098,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. @@ -2141,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. @@ -2148,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 @@ -2167,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. @@ -2177,6 +2487,7 @@ config SENSORS_VIA686A config SENSORS_VT1211 tristate "VIA VT1211" + depends on HAS_IOPORT depends on !PPC select HWMON_VID help @@ -2188,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 @@ -2296,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 @@ -2308,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 @@ -2412,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 3f4b0fda0998..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,12 +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 @@ -98,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 @@ -126,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 @@ -133,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 @@ -146,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 @@ -160,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 @@ -191,13 +214,15 @@ obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o obj-$(CONFIG_SENSORS_SHT4x) += sht4x.o obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o -obj-$(CONFIG_SENSORS_SMM665) += smm665.o obj-$(CONFIG_SENSORS_SMPRO) += smpro-hwmon.o 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 ffe81e445010..7a132accdf8a 100644 --- a/drivers/hwmon/ad7418.c +++ b/drivers/hwmon/ad7418.c @@ -16,7 +16,7 @@ #include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/delay.h> #include <linux/slab.h> @@ -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 = (enum chips)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), ®val); - 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 1932613ec095..436637264056 100644 --- a/drivers/hwmon/ads7828.c +++ b/drivers/hwmon/ads7828.c @@ -18,7 +18,7 @@ #include <linux/i2c.h> #include <linux/init.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_data/ads7828.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -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,11 +136,7 @@ static int ads7828_probe(struct i2c_client *client) } } - if (client->dev.of_node) - chip = (enum ads7828_chips) - 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 c0ce88324ea6..8cefa14e1633 100644 --- a/drivers/hwmon/adt7475.c +++ b/drivers/hwmon/adt7475.c @@ -10,7 +10,6 @@ */ #include <linux/module.h> -#include <linux/of_device.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/i2c.h> @@ -22,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 */ @@ -104,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 @@ -124,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 @@ -205,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]; @@ -216,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; @@ -274,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) @@ -370,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, @@ -1105,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); @@ -1295,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, @@ -1308,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, @@ -1390,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)); @@ -1628,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; @@ -1642,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) @@ -1652,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 = (enum chips)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) { @@ -1664,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); @@ -1683,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); @@ -1696,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 */ @@ -1711,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 */ @@ -1744,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: @@ -1776,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; @@ -1792,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" : "", @@ -1863,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), @@ -1961,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], ®val); 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, ®val); 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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 a997dbcb563f..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> @@ -13,13 +14,14 @@ #include <linux/crc16.h> #include <linux/debugfs.h> +#include <linux/delay.h> #include <linux/hid.h> #include <linux/hwmon.h> #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 @@ -33,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[] = { @@ -51,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" @@ -63,6 +67,8 @@ static const char *const aqc_device_names[] = { #define CTRL_REPORT_ID 0x03 #define AQUAERO_CTRL_REPORT_ID 0x0b +#define CTRL_REPORT_DELAY 200 /* ms */ + /* The HID report that the official software always sends * after writing values, currently same for all devices */ @@ -86,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 @@ -193,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 }; @@ -278,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" @@ -343,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", @@ -388,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", @@ -482,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; @@ -517,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; @@ -527,6 +559,9 @@ struct aqc_data { int secondary_ctrl_report_size; u8 *secondary_ctrl_report; + ktime_t last_ctrl_report_op; + int ctrl_report_delay; /* Delay between two ctrl report operations, in ms */ + int buffer_size; u8 *buffer; int checksum_start; @@ -560,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]; @@ -611,26 +646,44 @@ static int aqc_aquastreamxt_convert_fan_rpm(u16 val) return 0; } -/* Expects the mutex to be locked */ +static void aqc_delay_ctrl_report(struct aqc_data *priv) +{ + /* + * If previous read or write is too close to this one, delay the current operation + * to give the device enough time to process the previous one. + */ + if (priv->ctrl_report_delay) { + s64 delta = ktime_ms_delta(ktime_get(), priv->last_ctrl_report_op); + + if (delta < priv->ctrl_report_delay) + msleep(priv->ctrl_report_delay - delta); + } +} + static int aqc_get_ctrl_data(struct aqc_data *priv) { int ret; + aqc_delay_ctrl_report(priv); + memset(priv->buffer, 0x00, priv->buffer_size); ret = hid_hw_raw_request(priv->hdev, priv->ctrl_report_id, priv->buffer, priv->buffer_size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) ret = -ENODATA; + priv->last_ctrl_report_op = ktime_get(); + return ret; } -/* Expects the mutex to be locked */ static int aqc_send_ctrl_data(struct aqc_data *priv) { int ret; u16 checksum; + aqc_delay_ctrl_report(priv); + /* Checksum is not needed for Aquaero */ if (priv->kind != aquaero) { /* Init and xorout value for CRC-16/USB is 0xffff */ @@ -646,12 +699,16 @@ static int aqc_send_ctrl_data(struct aqc_data *priv) ret = hid_hw_raw_request(priv->hdev, priv->ctrl_report_id, priv->buffer, priv->buffer_size, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret < 0) - return ret; + goto record_access_and_ret; /* The official software sends this report after every change, so do it here as well */ ret = hid_hw_raw_request(priv->hdev, priv->secondary_ctrl_report_id, priv->secondary_ctrl_report, priv->secondary_ctrl_report_size, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + +record_access_and_ret: + priv->last_ctrl_report_op = ktime_get(); + return ret; } @@ -660,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: @@ -676,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; } @@ -686,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]) { @@ -701,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) @@ -787,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; @@ -799,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: @@ -883,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 */ @@ -931,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, @@ -1217,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, @@ -1404,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; @@ -1455,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; @@ -1524,6 +1576,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->buffer_size = AQUAERO_CTRL_REPORT_SIZE; priv->temp_ctrl_offset = AQUAERO_TEMP_CTRL_OFFSET; + priv->ctrl_report_delay = CTRL_REPORT_DELAY; priv->temp_label = label_temp_sensors; priv->virtual_temp_label = label_virtual_temp_sensors; @@ -1547,6 +1600,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->temp_ctrl_offset = D5NEXT_TEMP_CTRL_OFFSET; priv->buffer_size = D5NEXT_CTRL_REPORT_SIZE; + priv->ctrl_report_delay = CTRL_REPORT_DELAY; priv->power_cycle_count_offset = D5NEXT_POWER_CYCLES; @@ -1594,15 +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; @@ -1624,6 +1683,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->temp_ctrl_offset = QUADRO_TEMP_CTRL_OFFSET; priv->buffer_size = QUADRO_CTRL_REPORT_SIZE; + priv->ctrl_report_delay = CTRL_REPORT_DELAY; priv->flow_pulses_ctrl_offset = QUADRO_FLOW_PULSES_CTRL_OFFSET; priv->power_cycle_count_offset = QUADRO_POWER_CYCLES; @@ -1712,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; } @@ -1737,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; @@ -1767,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); @@ -1811,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/as370-hwmon.c b/drivers/hwmon/as370-hwmon.c index fffbf385a57f..316454bd983d 100644 --- a/drivers/hwmon/as370-hwmon.c +++ b/drivers/hwmon/as370-hwmon.c @@ -11,7 +11,8 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> #define CTRL 0x0 #define PD BIT(0) 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 d11f674e3dc3..aa159bf158a3 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -12,8 +12,7 @@ #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> -#include <linux/of_platform.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/reset.h> @@ -167,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; @@ -182,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]; @@ -191,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 }; @@ -528,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); @@ -545,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; @@ -738,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 @@ -875,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; } @@ -890,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; @@ -904,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)) @@ -933,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 f52a539eb33e..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, - .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + 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/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c index 8d402a627306..b77ebac2e0ce 100644 --- a/drivers/hwmon/bt1-pvt.c +++ b/drivers/hwmon/bt1-pvt.c @@ -891,15 +891,8 @@ static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev) static int pvt_request_regs(struct pvt_hwmon *pvt) { struct platform_device *pdev = to_platform_device(pvt->dev); - struct resource *res; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(pvt->dev, "Couldn't find PVT memresource\n"); - return -EINVAL; - } - - pvt->regs = devm_ioremap_resource(pvt->dev, res); + pvt->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(pvt->regs)) return PTR_ERR(pvt->regs); 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, ®_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, ®_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[] = { + ®s->eax, + ®s->ebx, + ®s->ecx, + ®s->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(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; + return dell_smm_call(data->ops, ®s) ? : (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(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : (regs.eax & 0xffff); + return dell_smm_call(data->ops, ®s) ? : (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(®s); + regs.eax = enable ? auto_fan : manual_fan; + return dell_smm_call(data->ops, ®s); } /* @@ -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(®s); + return dell_smm_call(data->ops, ®s); } -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(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : 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(®s); + rc = dell_smm_call(ops, ®s); 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(®s); + rc = dell_smm_call(data->ops, ®s); 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(®s); + rc = dell_smm_call(data->ops, ®s); 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], ®valh); + if (ret < 0) + return ret; + + reg = emc1403_temp_regs_low[channel][map]; + if (reg >= 0) { + ret = regmap_read(data->regmap, reg, ®vall); + 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, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(5 - 2 * channel)); + } else { + ret = regmap_read(data->regmap, 0x36, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(channel)); + } + break; + case hwmon_temp_max_alarm: + if (data->chip == emc1402) { + ret = regmap_read(data->regmap, 0x02, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(6 - 2 * channel)); + } else { + ret = regmap_read(data->regmap, 0x35, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(channel)); + } + break; + case hwmon_temp_crit_alarm: + if (data->chip == emc1402) { + ret = regmap_read(data->regmap, 0x02, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(channel)); + } else { + ret = regmap_read(data->regmap, 0x37, ®val); + if (ret < 0) + break; + *val = !!(regval & BIT(channel)); + } + break; + case hwmon_temp_fault: + ret = regmap_read(data->regmap, 0x1b, ®val); + 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 1b6ff4712138..4fa3aa1271da 100644 --- a/drivers/hwmon/g762.c +++ b/drivers/hwmon/g762.c @@ -39,14 +39,14 @@ #include <linux/kernel.h> #include <linux/clk.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_data/g762.h> #define DRVNAME "g762" static const struct i2c_device_id g762_id[] = { - { "g762", 0 }, - { "g763", 0 }, + { "g761" }, + { "g762" }, + { "g763" }, { } }; MODULE_DEVICE_TABLE(i2c, g762_id); @@ -70,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 */ @@ -116,6 +117,7 @@ enum g762_regs { struct g762_data { struct i2c_client *client; + bool internal_clock; struct clk *clk; /* update mutex */ @@ -567,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" }, { }, @@ -598,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"); @@ -617,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); @@ -1026,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) @@ -1057,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/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c index 2e05bc2f627a..00e057050437 100644 --- a/drivers/hwmon/gxp-fan-ctrl.c +++ b/drivers/hwmon/gxp-fan-ctrl.c @@ -6,7 +6,7 @@ #include <linux/hwmon.h> #include <linux/io.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ 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 ebe2fb513480..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 @@ -435,25 +499,11 @@ static union acpi_object *hp_wmi_get_wobj(const char *guid, u8 instance) /* hp_wmi_wobj_instance_count - find count of WMI object instances */ static u8 hp_wmi_wobj_instance_count(const char *guid) { - u8 hi = HP_WMI_MAX_INSTANCES; - union acpi_object *wobj; - u8 lo = 0; - u8 mid; + int count; - while (lo < hi) { - mid = (lo + hi) / 2; + count = wmi_instance_count(guid); - wobj = hp_wmi_get_wobj(guid, mid); - if (!wobj) { - hi = mid; - continue; - } - - lo = mid + 1; - kfree(wobj); - } - - return lo; + return clamp(count, 0, (int)HP_WMI_MAX_INSTANCES); } static int check_wobj(const union acpi_object *wobj, @@ -476,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; @@ -494,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; @@ -875,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; @@ -899,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. */ @@ -1010,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); @@ -1023,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: @@ -1119,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; @@ -1519,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; @@ -1552,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); @@ -1588,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); } @@ -1672,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; @@ -1927,7 +2000,7 @@ static bool add_event_handler(struct hp_wmi_sensors *state) static int hp_wmi_sensors_init(struct hp_wmi_sensors *state) { struct hp_wmi_info *connected[HP_WMI_MAX_INSTANCES]; - struct hp_wmi_platform_events *pevents; + struct hp_wmi_platform_events *pevents = NULL; struct device *dev = &state->wdev->dev; struct hp_wmi_info *info; struct device *hwdev; diff --git a/drivers/hwmon/hs3001.c b/drivers/hwmon/hs3001.c new file mode 100644 index 000000000000..50c6c15f8b18 --- /dev/null +++ b/drivers/hwmon/hs3001.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is a non-complete driver implementation for the + * HS3001 humidity and temperature sensor and compatibles. It does not include + * the configuration possibilities, where it needs to be set to 'programming mode' + * during power-up. + * + * + * Copyright (C) 2023 SYS TEC electronic AG + * Author: Andre Werner <andre.werner@systec-electronic.com> + */ + +#include <linux/bitfield.h> +#include <linux/delay.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/of_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* Measurement times */ +#define HS3001_WAKEUP_TIME 100 /* us */ +#define HS3001_8BIT_RESOLUTION 550 /* us */ +#define HS3001_10BIT_RESOLUTION 1310 /* us */ +#define HS3001_12BIT_RESOLUTION 4500 /* us */ +#define HS3001_14BIT_RESOLUTION 16900 /* us */ + +#define HS3001_RESPONSE_LENGTH 4 + +#define HS3001_FIXPOINT_ARITH 1000U + +#define HS3001_MASK_HUMIDITY_0X3FFF GENMASK(13, 0) +#define HS3001_MASK_STATUS_0XC0 GENMASK(7, 6) + +/* Definitions for Status Bits of A/D Data */ +#define HS3001_DATA_VALID 0x00 /* Valid Data */ +#define HS3001_DATA_STALE 0x01 /* Stale Data */ + +struct hs3001_data { + struct i2c_client *client; + u32 wait_time; /* in us */ + int temperature; /* in milli degree */ + u32 humidity; /* in milli % */ +}; + +static int hs3001_extract_temperature(u16 raw) +{ + /* fixpoint arithmetic 1 digit */ + u32 temp = (raw >> 2) * HS3001_FIXPOINT_ARITH * 165; + + temp /= (1 << 14) - 1; + + return (int)temp - 40 * HS3001_FIXPOINT_ARITH; +} + +static u32 hs3001_extract_humidity(u16 raw) +{ + u32 hum = (raw & HS3001_MASK_HUMIDITY_0X3FFF) * HS3001_FIXPOINT_ARITH * 100; + + return hum / (1 << 14) - 1; +} + +static int hs3001_data_fetch_command(struct i2c_client *client, + struct hs3001_data *data) +{ + int ret; + u8 buf[HS3001_RESPONSE_LENGTH]; + u8 hs3001_status; + + ret = i2c_master_recv(client, buf, HS3001_RESPONSE_LENGTH); + if (ret != HS3001_RESPONSE_LENGTH) { + ret = ret < 0 ? ret : -EIO; + dev_dbg(&client->dev, + "Error in i2c communication. Error code: %d.\n", ret); + return ret; + } + + hs3001_status = FIELD_GET(HS3001_MASK_STATUS_0XC0, buf[0]); + if (hs3001_status == HS3001_DATA_STALE) { + dev_dbg(&client->dev, "Sensor busy.\n"); + return -EBUSY; + } + if (hs3001_status != HS3001_DATA_VALID) { + dev_dbg(&client->dev, "Data invalid.\n"); + return -EIO; + } + + data->humidity = + hs3001_extract_humidity(be16_to_cpup((__be16 *)&buf[0])); + data->temperature = + hs3001_extract_temperature(be16_to_cpup((__be16 *)&buf[2])); + + return 0; +} + +static umode_t hs3001_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + /* Both, humidity and temperature can only be read. */ + return 0444; +} + +static int hs3001_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct hs3001_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + ret = i2c_master_send(client, NULL, 0); + if (ret < 0) + return ret; + + /* + * Sensor needs some time to process measurement depending on + * resolution (ref. datasheet) + */ + fsleep(data->wait_time); + + ret = hs3001_data_fetch_command(client, data); + if (ret < 0) + return ret; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + *val = data->temperature; + break; + default: + return -EINVAL; + } + break; + case hwmon_humidity: + switch (attr) { + case hwmon_humidity_input: + *val = data->humidity; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct hwmon_channel_info *hs3001_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT), + NULL +}; + +static const struct hwmon_ops hs3001_hwmon_ops = { + .is_visible = hs3001_is_visible, + .read = hs3001_read, +}; + +static const struct hwmon_chip_info hs3001_chip_info = { + .ops = &hs3001_hwmon_ops, + .info = hs3001_info, +}; + +/* device ID table */ +static const struct i2c_device_id hs3001_ids[] = { + { "hs3001" }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, hs3001_ids); + +static const struct of_device_id hs3001_of_match[] = { + {.compatible = "renesas,hs3001"}, + { }, +}; + +MODULE_DEVICE_TABLE(of, hs3001_of_match); + +static int hs3001_probe(struct i2c_client *client) +{ + struct hs3001_data *data; + struct device *hwmon_dev; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + + /* + * Measurement time = wake-up time + measurement time temperature + * + measurement time humidity. This is currently static, because + * enabling programming mode is not supported, yet. + */ + data->wait_time = (HS3001_WAKEUP_TIME + HS3001_14BIT_RESOLUTION + + HS3001_14BIT_RESOLUTION); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, + client->name, + data, + &hs3001_chip_info, + NULL); + + if (IS_ERR(hwmon_dev)) + return dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device.\n"); + + return 0; +} + +static struct i2c_driver hs3001_i2c_driver = { + .driver = { + .name = "hs3001", + .of_match_table = hs3001_of_match, + }, + .probe = hs3001_probe, + .id_table = hs3001_ids, +}; + +module_i2c_driver(hs3001_i2c_driver); + +MODULE_AUTHOR("Andre Werner <andre.werner@systec-electronic.com>"); +MODULE_DESCRIPTION("HS3001 humidity and temperature sensor base driver"); +MODULE_LICENSE("GPL"); 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, ®val); + 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, ®val); + if (err) + return err; + /* Adjust accuracy: LSB in units of 500 pV */ + lsb /= 8; + factor *= 2; + } else { + err = regmap_read(data->regmap, reg, ®val); + 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, ®val); + if (err) + return err; + lsb /= 16; /* Adjust accuracy */ + } else { + err = regmap_read(data->regmap, INA238_CURRENT, ®val); + 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, ®val); - 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, ®val); + 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, ®val); + 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, ®val); 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, ®val); 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, ®val); @@ -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, ®val); + 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 cfd7efef5cdf..69ac0468dee4 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -22,22 +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_device.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 @@ -52,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 */ @@ -113,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, }, }; @@ -167,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, ®val); + 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, ®val); 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 @@ -227,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 @@ -250,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; } @@ -262,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, ®val); + if (ret) + return ret; + + if (regval & mask) { + ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val); + 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, ®val); + 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, ®val); + ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, ®val); + 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, ®val); + 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, ®val); - if (ret) - goto abort; - if (regval & BIT(attr->index)) { - ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val); - 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, ®val); + 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, ®val); - 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, ®val); - 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 = (enum ina2xx_ids)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)) { @@ -656,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); @@ -687,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 }, @@ -712,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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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 5deff5e5f693..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; } @@ -221,6 +222,10 @@ static bool fix_pwm_polarity; * Super-I/O configuration space. */ #define IT87_REG_VID 0x0a + +/* Interface Selection register on other chips */ +#define IT87_REG_IFSEL 0x0a + /* * The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b * for fan divisors. Later IT8712F revisions must use 16-bit tachometer @@ -316,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) @@ -448,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] = { @@ -457,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 */ }, @@ -503,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 */ }, @@ -540,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) @@ -738,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; } @@ -754,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; } @@ -1159,28 +1164,66 @@ static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, 0); static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, 0); static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, 0); +static int get_temp_type(struct it87_data *data, int index) +{ + /* + * 2 is deprecated; + * 3 = thermal diode; + * 4 = thermistor; + * 5 = AMDTSI; + * 6 = Intel PECI; + * 0 = disabled + */ + u8 reg, extra; + int ttype, type = 0; + + /* Detect PECI vs. AMDTSI */ + ttype = 6; + if ((has_temp_peci(data, index)) || data->type == it8721 || + data->type == it8720) { + extra = it87_read_value(data, IT87_REG_IFSEL); + if ((extra & 0x70) == 0x40) + ttype = 5; + } + + reg = it87_read_value(data, IT87_REG_TEMP_ENABLE); + + /* Per chip special detection */ + switch (data->type) { + case it8622: + if (!(reg & 0xc0) && index == 3) + type = ttype; + break; + default: + break; + } + + if (type || index >= 3) + return type; + + extra = it87_read_value(data, IT87_REG_TEMP_EXTRA); + + if ((has_temp_peci(data, index) && (reg >> 6 == index + 1)) || + (has_temp_old_peci(data, index) && (extra & 0x80))) + type = ttype; /* Intel PECI or AMDTSI */ + else if (reg & BIT(index)) + type = 3; /* thermal diode */ + else if (reg & BIT(index + 3)) + type = 4; /* thermistor */ + + return type; +} + static ssize_t show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); - int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - u8 reg, extra; if (IS_ERR(data)) return PTR_ERR(data); - reg = data->sensor; /* In case value is updated while used */ - extra = data->extra; - - if ((has_temp_peci(data, nr) && (reg >> 6 == nr + 1)) || - (has_temp_old_peci(data, nr) && (extra & 0x80))) - return sprintf(buf, "6\n"); /* Intel PECI */ - if (reg & (1 << nr)) - return sprintf(buf, "3\n"); /* thermal diode */ - if (reg & (8 << nr)) - return sprintf(buf, "4\n"); /* thermistor */ - return sprintf(buf, "0\n"); /* disabled */ + return sprintf(buf, "%d\n", get_temp_type(data, sensor_attr->index)); } static ssize_t set_temp_type(struct device *dev, struct device_attribute *attr, @@ -2313,6 +2356,12 @@ static umode_t it87_temp_is_visible(struct kobject *kobj, if (!(data->has_temp & BIT(i))) return 0; + if (a == 3) { + if (get_temp_type(data, i) == 0) + return 0; + return attr->mode; + } + if (a == 5 && !has_temp_offset(data)) return 0; @@ -2618,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) @@ -2625,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 */ @@ -2718,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", @@ -3095,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; } @@ -3472,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)", @@ -3492,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) @@ -3593,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 @@ -3635,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, ®val); @@ -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 a267b11731a8..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 @@ -65,7 +62,7 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); #define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64 #define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4 -/* Common for Zen CPU families (Family 17h and 18h and 19h) */ +/* Common for Zen CPU families (Family 17h and 18h and 19h and 1Ah) */ #define ZEN_REPORTED_TEMP_CTRL_BASE 0x00059800 #define ZEN_CCD_TEMP(offset, x) (ZEN_REPORTED_TEMP_CTRL_BASE + \ @@ -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), - ®val); + ret = read_ccd_temp_reg(data, channel - 2, ®val); + + 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), ®val); + /* + * 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, ®val)) + continue; + if (regval & ZEN_CCD_TEMP_VALID) data->show_temp |= BIT(TCCD_BIT(i)); } @@ -418,66 +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) { + switch (boot_cpu_data.x86_model) { + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); break; } - } else { - data->read_htcreg = read_htcreg_pci; - data->read_tempreg = read_tempreg_pci; } for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) { @@ -511,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) }, @@ -521,6 +563,12 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M70H_DF_F3) }, { 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/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c index f8658359a098..7247c03e4f44 100644 --- a/drivers/hwmon/lan966x-hwmon.c +++ b/drivers/hwmon/lan966x-hwmon.c @@ -334,24 +334,6 @@ static struct regmap *lan966x_init_regmap(struct platform_device *pdev, return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); } -static void lan966x_clk_disable(void *data) -{ - struct lan966x_hwmon *hwmon = data; - - clk_disable_unprepare(hwmon->clk); -} - -static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) -{ - int ret; - - ret = clk_prepare_enable(hwmon->clk); - if (ret) - return ret; - - return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); -} - static int lan966x_hwmon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -363,15 +345,11 @@ static int lan966x_hwmon_probe(struct platform_device *pdev) if (!hwmon) return -ENOMEM; - hwmon->clk = devm_clk_get(dev, NULL); + hwmon->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(hwmon->clk)) return dev_err_probe(dev, PTR_ERR(hwmon->clk), "failed to get clock\n"); - ret = lan966x_clk_enable(dev, hwmon); - if (ret) - return dev_err_probe(dev, ret, "failed to enable clock\n"); - hwmon->clk_rate = clk_get_rate(hwmon->clk); hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); 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 6972454eb4e0..035176a98ce9 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -33,7 +33,7 @@ #include <linux/hwmon.h> #include <linux/err.h> #include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/sysfs.h> #include <linux/types.h> @@ -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 = (enum chips)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 72e634d1b857..eda93a8c23c9 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -7,13 +7,13 @@ #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_device.h> #include <linux/of.h> #include <linux/regmap.h> #include <linux/util_macros.h> @@ -26,6 +26,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ adt75, + as6200, at30ts74, ds1775, ds75, @@ -38,6 +39,8 @@ enum lm75_type { /* keep sorted in alphabetical order */ max6626, max31725, mcp980x, + p3t1750, + p3t1755, pct2075, stds75, stlm75, @@ -56,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 @@ -76,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 */ @@ -100,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]; }; /*-----------------------------------------------------------------------*/ @@ -129,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, @@ -209,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, @@ -256,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 */ @@ -318,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, @@ -367,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; } @@ -374,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; @@ -418,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; @@ -437,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, ®); - 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]; @@ -504,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: @@ -516,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 }; @@ -541,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, @@ -553,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 = (enum lm75_type)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. @@ -612,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); @@ -641,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, }, @@ -667,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, }, @@ -683,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[] = { { @@ -691,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 }, @@ -743,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 }, @@ -901,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 = { @@ -938,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 8540178f5b74..1c244ed75122 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -12,7 +12,7 @@ */ #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> @@ -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 = (enum chips)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 90101c236f35..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,8 +108,7 @@ #include <linux/hwmon.h> #include <linux/kstrtox.h> #include <linux/module.h> -#include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/workqueue.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 = (enum chips)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, ®val); + 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), ®val); + if (ret) + return ret; + *val = !!(regval & BIT(channel)); + break; + case hwmon_temp_crit_alarm: + ret = regmap_read(regmap, LM95234_REG_STS_TCRIT1, ®val); + 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, ®val); + if (ret) + return ret; + *val = (regval & BIT(channel)) ? 1 : 2; + break; + case hwmon_temp_offset: + ret = regmap_read(regmap, LM95234_REG_OFFSET(channel - 1), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 500; + break; + case hwmon_temp_fault: + ret = regmap_read(regmap, LM95234_REG_STS_FAULT, ®val); + 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), ®val); + 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), ®val); + 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, ®vall); - if (ret < 0) - return ret; - ret = regmap_read(regmap, regh, ®valh); + 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, - ®vall); - if (ret < 0) - return ret; - ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U, - ®valh); - 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, - ®valh); + ®val); 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, ®valh); + regs[0] = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + ret = regmap_read(regmap, regs[0], ®val); 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, - ®valh); + 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, - ®vall); - 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, ®valh); - if (ret < 0) - return ret; - ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, - ®vall); + 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, ®valh); + ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, ®val); 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, - ®vall); - if (ret < 0) - return ret; - ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFH, - ®valh); + 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, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); 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, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); 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, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); 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, ®val); - 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 6350904a8a8b..c1ba72f6132e 100644 --- a/drivers/hwmon/lochnagar-hwmon.c +++ b/drivers/hwmon/lochnagar-hwmon.c @@ -10,13 +10,11 @@ #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> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/regmap.h> @@ -43,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 { @@ -179,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) @@ -379,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, - <c2947_chip_info, - ltc2947_groups); + <c2947_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, ®vals, 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, ®_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, ®_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, ®_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 = <c2991_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, <c2991_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, + <c2991_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 = <c4282_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, ®_val); + if (ret) + return ret; + + st->energy_en = !FIELD_GET(LTC4282_METER_HALT_MASK, reg_val); + + ret = regmap_read(st->map, LTC4282_CTRL_MSB, ®_val); + if (ret) + return ret; + + *vin_mode = FIELD_GET(LTC4282_CTRL_VIN_MODE_MASK, reg_val); + + ret = regmap_read(st->map, LTC4282_ILIM_ADJUST, ®_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", + ÷r); + 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", + ÷r); + 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 = <c4282_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, + <c4282_power1_bad_fault_log); + debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st, + <c4282_fet_short_fault_log); + debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st, + <c4282_fet_bad_fault_log); + debugfs_create_file_unsafe("in1_crit_fault_log", 0400, i2c->debugfs, st, + <c4282_in1_crit_fault_log); + debugfs_create_file_unsafe("in1_lcrit_fault_log", 0400, i2c->debugfs, st, + <c4282_in1_lcrit_fault_log); + debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st, + <c4282_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, <c4282_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, + <c4282_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/ltq-cputemp.c b/drivers/hwmon/ltq-cputemp.c index 08e09a82acab..f7e4a4ca5239 100644 --- a/drivers/hwmon/ltq-cputemp.c +++ b/drivers/hwmon/ltq-cputemp.c @@ -9,8 +9,9 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/init.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/platform_device.h> #include <lantiq_soc.h> 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, ®val); + 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), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 1000; + break; + case hwmon_temp_min: + ret = regmap_read(regmap, MAX1668_REG_LIML(channel), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 1000; + break; + case hwmon_temp_max: + ret = regmap_read(regmap, MAX1668_REG_LIMH(channel), ®val); + 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, + ®val); + 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, + ®val); + 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 b1300ca9db1f..2f4b419b6c9e 100644 --- a/drivers/hwmon/max31730.c +++ b/drivers/hwmon/max31730.c @@ -11,7 +11,6 @@ #include <linux/init.h> #include <linux/hwmon.h> #include <linux/module.h> -#include <linux/of_device.h> #include <linux/of.h> #include <linux/slab.h> @@ -346,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 7f709fd1af89..a7066f3a0bb4 100644 --- a/drivers/hwmon/max6621.c +++ b/drivers/hwmon/max6621.c @@ -12,7 +12,7 @@ #include <linux/i2c.h> #include <linux/init.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regmap.h> #define MAX6621_DRV_NAME "max6621" @@ -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 caf527154fca..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; - temp = data->temp[attr->index] * 125; - return sprintf(buf, "%ld\n", temp); + *max = (long)val * 1000; + + 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; - return sprintf(buf, "%d\n", data->temp_fault[attr->index]); + *crit = (long)val * 1000; + + 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; + + res = regmap_read(data->regmap, MAX6639_REG_OT_LIMIT(channel), &val); + if (res < 0) + return res; + + *emerg = (long)val * 1000; - return sprintf(buf, "%d\n", (data->temp_therm[attr->index] * 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; + + res = regmap_write(data->regmap, MAX6639_REG_OT_LIMIT(channel), TEMP_LIMIT_TO_REG(val)); - return sprintf(buf, "%d\n", (data->temp_ot[attr->index] * 1000)); + 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; + + switch (attr) { + case hwmon_fan_pulses: + if (val <= 0 || val > 4) + return -EINVAL; - return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120); + /* 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; + } +} - 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; +static int max6639_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + unsigned int status; + int res; + + 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; - if (max6639_info) - rpm_range = rpm_range_to_reg(max6639_info->rpm_range); - data->rpm_range = rpm_range; + /* 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; + + 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"}, { } }; @@ -618,11 +789,17 @@ MODULE_DEVICE_TABLE(i2c, max6639_id); static DEFINE_SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume); +static const struct of_device_id max6639_of_match[] = { + { .compatible = "maxim,max6639", }, + { }, +}; + static struct i2c_driver max6639_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "max6639", .pm = pm_sleep_ptr(&max6639_pm_ops), + .of_match_table = max6639_of_match, }, .probe = max6639_probe, .id_table = max6639_id, 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 3a67778f111c..dd906cf491ca 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -6,19 +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/of_device.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 }; @@ -34,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 @@ -68,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 @@ -93,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, @@ -105,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, @@ -174,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], ®val); + if (ret) + return ret; + *val = ((int)regval - data->temp_offset) * 1000; + break; + case hwmon_temp_crit: + ret = regmap_read(regmap, MAX6697_REG_CRIT[channel], ®val); + if (ret) + return ret; + *val = ((int)regval - data->temp_offset) * 1000; + break; + case hwmon_temp_min: + ret = regmap_read(regmap, MAX6697_REG_MIN, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®); + 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, ®); + } + } 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 = (enum chips)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); } @@ -781,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, ®val); + 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, ®_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), + ®_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, ®_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 127e15ff3e76..bcddf6804d3a 100644 --- a/drivers/hwmon/mcp3021.c +++ b/drivers/hwmon/mcp3021.c @@ -20,7 +20,6 @@ #include <linux/err.h> #include <linux/device.h> #include <linux/of.h> -#include <linux/of_device.h> /* Vdd / reference voltage in millivolt */ #define MCP3021_VDD_REF_MAX 5500 @@ -117,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; @@ -150,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 c2a96468c9b4..137a90dd2075 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -12,7 +12,7 @@ #include <linux/regmap.h> #include <linux/thermal.h> -#define MLXREG_FAN_MAX_TACHO 14 +#define MLXREG_FAN_MAX_TACHO 24 #define MLXREG_FAN_MAX_PWM 4 #define MLXREG_FAN_PWM_NOT_CONNECTED 0xff #define MLXREG_FAN_MAX_STATE 10 @@ -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); @@ -300,6 +305,16 @@ static const struct hwmon_channel_info * const mlxreg_fan_hwmon_info[] = { 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_F_INPUT | HWMON_F_FAULT, + 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_F_INPUT | HWMON_F_FAULT, + 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, @@ -347,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; @@ -359,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, @@ -371,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, @@ -390,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, @@ -527,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); @@ -551,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 08ce4984151d..79bc67ffb998 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -33,7 +33,8 @@ * (0xd451) * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 * (0xd429) - * nct6799d 14 7 7 2+6 0xd802 0xc1 0x5ca3 + * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 + * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 * * #temp lists the number of monitored temperature sources (first value) plus * the number of directly connectable temperature sensors (second value). @@ -41,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> @@ -55,38 +59,38 @@ #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 */ -/* Voltage min/max registers for nr=7..14 are in bank 5 */ +/* + * Voltage min/max registers for nr=7..14 are in bank 5 + * min/max: 15-17 for NCT6799 only + */ static const u16 NCT6775_REG_IN_MAX[] = { 0x2b, 0x2d, 0x2f, 0x31, 0x33, 0x35, 0x37, 0x554, 0x556, 0x558, 0x55a, - 0x55c, 0x55e, 0x560, 0x562 }; + 0x55c, 0x55e, 0x560, 0x562, 0x564, 0x570, 0x572 }; static const u16 NCT6775_REG_IN_MIN[] = { 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x555, 0x557, 0x559, 0x55b, - 0x55d, 0x55f, 0x561, 0x563 }; + 0x55d, 0x55f, 0x561, 0x563, 0x565, 0x571, 0x573 }; static const u16 NCT6775_REG_IN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x550, 0x551, 0x552 }; @@ -97,31 +101,23 @@ static const u16 NCT6775_REG_IN[] = { static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; -/* 0..15 voltages, 16..23 fans, 24..29 temperatures, 30..31 intrusion */ - -static const s8 NCT6775_ALARM_BITS[] = { - 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ - 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - -1, /* unused */ - 6, 7, 11, -1, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, -1 }; /* intrusion0, intrusion1 */ +static const s8 NCT6775_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 8, 21, 20, 16, 17, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, -1, /* intr0-intr1 */ +}; static const u16 NCT6775_REG_BEEP[NUM_REG_BEEP] = { 0x56, 0x57, 0x453, 0x4e }; -/* - * 0..14 voltages, 15 global beep enable, 16..23 fans, 24..29 temperatures, - * 30..31 intrusion - */ -static const s8 NCT6775_BEEP_BITS[] = { - 0, 1, 2, 3, 8, 9, 10, 16, /* in0.. in7 */ - 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - 21, /* global beep enable */ - 6, 7, 11, 28, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, -1 }; /* intrusion0, intrusion1 */ +static const s8 NCT6775_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 8, 9, 10, 16, 17, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, 28, -1, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, -1, 21 /* intr0-intr1, beep_en */ +}; /* DC or PWM output fan configuration */ static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; @@ -255,31 +251,30 @@ static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 }; #define NCT6776_REG_FAN_STEP_UP_TIME NCT6775_REG_FAN_STEP_DOWN_TIME #define NCT6776_REG_FAN_STEP_DOWN_TIME NCT6775_REG_FAN_STEP_UP_TIME -static const s8 NCT6776_ALARM_BITS[] = { - 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ - 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - -1, /* unused */ - 6, 7, 11, 10, 23, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, 9 }; /* intrusion0, intrusion1 */ - -static const u16 NCT6776_REG_BEEP[NUM_REG_BEEP] = { 0xb2, 0xb3, 0xb4, 0xb5 }; - -static const s8 NCT6776_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ - 8, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - 24, /* global beep enable */ - 25, 26, 27, 28, 29, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 30, 31 }; /* intrusion0, intrusion1 */ +static const s8 NCT6776_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 8, 21, 20, 16, 17, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, 10, 23, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, 9, /* intr0-intr1 */ +}; + +/* 0xbf: nct6799 only */ +static const u16 NCT6776_REG_BEEP[NUM_REG_BEEP] = { 0xb2, 0xb3, 0xb4, 0xb5, 0xbf }; + +static const s8 NCT6776_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 25, 26, 27, 28, 29, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, 19, 20, 21, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 30, 31, 24 /* intr0-intr1, beep_en */ +}; 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 }; @@ -337,30 +332,35 @@ static const u16 NCT6776_REG_TSI_TEMP[] = { /* NCT6779 specific data */ +/* + * 15-17 for NCT6799 only, register labels are: + * CPUVC, VIN1, AVSB, 3VCC, VIN0, VIN8, VIN4, 3VSB + * VBAT, VTT, VIN5, VIN6, VIN2, VIN3, VIN7, VIN9 + * VHIF, VIN10 + */ static const u16 NCT6779_REG_IN[] = { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, - 0x488, 0x489, 0x48a, 0x48b, 0x48c, 0x48d, 0x48e }; + 0x488, 0x489, 0x48a, 0x48b, 0x48c, 0x48d, 0x48e, 0x48f, + 0x470, 0x471}; static const u16 NCT6779_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B, 0x568 }; -static const s8 NCT6779_ALARM_BITS[] = { - 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ - 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ - -1, /* unused */ - 6, 7, 11, 10, 23, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, 9 }; /* intrusion0, intrusion1 */ - -static const s8 NCT6779_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ - 8, 9, 10, 11, 12, 13, 14, /* in8..in14 */ - 24, /* global beep enable */ - 25, 26, 27, 28, 29, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, -1, -1, -1, -1, /* temp1..temp6 */ - 30, 31 }; /* intrusion0, intrusion1 */ +static const s8 NCT6779_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 8, 21, 20, 16, 17, 24, 25, 26, /* in0-in11 */ + 27, 28, 29, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, 10, 23, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, 9, /* intr0-intr1 */ +}; + +static const s8 NCT6779_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* in0-in11 */ + 12, 13, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 25, 26, 27, 28, 29, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 30, 31, 24 /* intr0-intr1, beep_en */ +}; static const u16 NCT6779_REG_FAN[] = { 0x4c0, 0x4c2, 0x4c4, 0x4c6, 0x4c8, 0x4ca, 0x4ce }; @@ -448,14 +448,13 @@ static const u16 NCT6791_REG_WEIGHT_DUTY_BASE[NUM_FAN] = { 0, 0x23e }; static const u16 NCT6791_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B, 0x568, 0x45D }; -static const s8 NCT6791_ALARM_BITS[] = { - 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ - 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ - -1, /* unused */ - 6, 7, 11, 10, 23, 33, /* fan1..fan6 */ - -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, 9 }; /* intrusion0, intrusion1 */ +static const s8 NCT6791_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 8, 21, 20, 16, 17, 24, 25, 26, /* in0-in11 */ + 27, 28, 29, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, 10, 23, 33, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, 9, /* intr0-intr1 */ +}; /* NCT6792/NCT6793 specific data */ @@ -618,6 +617,28 @@ static const char *const nct6796_temp_label[] = { static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b }; +static const u16 NCT6798_REG_TEMP[] = { + 0x27, 0x150, 0x670, 0x672, 0x674, 0x676, 0x678, 0x67a}; + +static const u16 NCT6798_REG_TEMP_SOURCE[] = { + 0x621, 0x622, 0xc26, 0xc27, 0xc28, 0xc29, 0xc2a, 0xc2b }; + +static const u16 NCT6798_REG_TEMP_MON[] = { + 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d, 0x4a0 }; +static const u16 NCT6798_REG_TEMP_OVER[] = { + 0x39, 0x155, 0xc1a, 0xc1b, 0xc1c, 0xc1d, 0xc1e, 0xc1f }; +static const u16 NCT6798_REG_TEMP_HYST[] = { + 0x3a, 0x153, 0xc20, 0xc21, 0xc22, 0xc23, 0xc24, 0xc25 }; + +static const u16 NCT6798_REG_TEMP_CRIT[32] = { + 0x135, 0x235, 0x335, 0x835, 0x935, 0xa35, 0xb35, 0 }; + +static const u16 NCT6798_REG_TEMP_ALTERNATE[32] = { + 0x490, 0x491, 0x492, 0x493, 0x494, 0x495, 0x496, 0, + 0, 0, 0, 0, 0x4a2, 0, 0, 0, + 0, 0x400, 0x401, 0x402, 0x404, 0x405, 0x406, 0x407, + 0x408, 0x419, 0x41a, 0x4f4, 0x4f5 }; + static const char *const nct6798_temp_label[] = { "", "SYSTIN", @@ -656,6 +677,26 @@ static const char *const nct6798_temp_label[] = { #define NCT6798_TEMP_MASK 0xbfff0ffe #define NCT6798_VIRT_TEMP_MASK 0x80000c00 +static const u16 NCT6799_REG_ALARM[NUM_REG_ALARM] = { + 0x459, 0x45A, 0x45B, 0x568, 0x45D, 0xc01 }; + +static const s8 NCT6799_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 8, -1, 20, 16, 17, 24, 25, 26, /* in0-in11 */ + 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 6, 7, 11, 10, 23, 33, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 4, 5, 40, 41, 42, 43, 44, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 12, 9, /* intr0-intr1 */ +}; + +static const s8 NCT6799_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* in0-in11 */ + 12, 13, 14, 15, 34, 35, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 25, 26, 27, 28, 29, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, 19, 20, 21, 22, 23, -1, -1, -1, -1, /* temp1-temp12 */ + 30, 31, 24 /* intr0-intr1, beep_en */ +}; + +/* PECI Calibration only for NCT6799D, not NCT6796D-S */ static const char *const nct6799_temp_label[] = { "", "SYSTIN", @@ -685,8 +726,8 @@ static const char *const nct6799_temp_label[] = { "Agent1 Dimm1", "BYTE_TEMP0", "BYTE_TEMP1", - "PECI Agent 0 Calibration", /* undocumented */ - "PECI Agent 1 Calibration", /* undocumented */ + "PECI/TSI Agent 0 Calibration", + "PECI/TSI Agent 1 Calibration", "", "Virtual_TEMP" }; @@ -726,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 }; @@ -763,27 +804,23 @@ static const u16 NCT6106_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184 }; static const u16 NCT6106_REG_ALARM[NUM_REG_ALARM] = { 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d }; -static const s8 NCT6106_ALARM_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - -1, /* unused */ - 32, 33, 34, -1, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 48, -1 /* intrusion0, intrusion1 */ +static const s8 NCT6106_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 4, 5, 7, 8, 9, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 32, 33, 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, 19, 20, 21, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 48, -1, /* intr0-intr1 */ }; static const u16 NCT6106_REG_BEEP[NUM_REG_BEEP] = { 0x3c0, 0x3c1, 0x3c2, 0x3c3, 0x3c4 }; -static const s8 NCT6106_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ - 32, /* global beep enable */ - 24, 25, 26, 27, 28, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 34, -1 /* intrusion0, intrusion1 */ +static const s8 NCT6106_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 24, 25, 26, 27, 28, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, 19, 20, 21, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 34, -1, 32 /* intr0-intr1, beep_en */ }; static const u16 NCT6106_REG_TEMP_ALTERNATE[32] = { @@ -843,24 +880,20 @@ static const u16 NCT6116_REG_AUTO_TEMP[] = { static const u16 NCT6116_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184, 0x1d4, 0x1e4 }; -static const s8 NCT6116_ALARM_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, -1, -1, -1, -1, -1, -1, /* in8..in9 */ - -1, /* unused */ - 32, 33, 34, 35, 36, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ - 48, -1 /* intrusion0, intrusion1 */ +static const s8 NCT6116_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 4, 5, 7, 8, 9, -1, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 48, -1, /* intr0-intr1 */ }; -static const s8 NCT6116_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ - 32, /* global beep enable */ - 24, 25, 26, 27, 28, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ - 34, -1 /* intrusion0, intrusion1 */ +static const s8 NCT6116_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 24, 25, 26, 27, 28, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 34, -1, 32 /* intr0-intr1, beep_en */ }; static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; @@ -958,12 +991,12 @@ static const u16 scale_in[15] = { /* * NCT6798 scaling: * CPUVC, IN1, AVSB, 3VCC, IN0, IN8, IN4, 3VSB, VBAT, VTT, IN5, IN6, IN2, - * IN3, IN7 - * Additional scales to be added later: IN9 (800), VHIF (1600) + * IN3, IN7, IN9, VHIF, IN10 + * 15-17 for NCT6799 only */ -static const u16 scale_in_6798[15] = { +static const u16 scale_in_6798[NUM_IN] = { 800, 800, 1600, 1600, 800, 800, 800, 1600, 1600, 1600, 1600, 1600, 800, - 800, 800 + 800, 800, 800, 1600, 800 }; static inline long in_from_reg(u8 reg, u8 nr, const u16 *scales) @@ -1581,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], ®); + 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], ®); + 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); @@ -1877,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; @@ -2221,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; @@ -2512,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; @@ -2830,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; @@ -2911,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; @@ -3037,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; @@ -3464,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; @@ -3546,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; @@ -3554,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; @@ -3621,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; @@ -3698,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; @@ -3773,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; @@ -3852,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; @@ -3862,13 +3915,9 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, case nct6795: case nct6796: case nct6797: - case nct6798: - case nct6799: data->in_num = 15; data->pwm_num = (data->kind == nct6796 || - data->kind == nct6797 || - data->kind == nct6798 || - data->kind == nct6799) ? 7 : 6; + data->kind == nct6797) ? 7 : 6; data->auto_pwm_num = 4; data->has_fan_div = false; data->temp_fixed_num = 6; @@ -3912,16 +3961,6 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, data->temp_mask = NCT6796_TEMP_MASK; data->virt_temp_mask = NCT6796_VIRT_TEMP_MASK; break; - case nct6798: - data->temp_label = nct6798_temp_label; - data->temp_mask = NCT6798_TEMP_MASK; - data->virt_temp_mask = NCT6798_VIRT_TEMP_MASK; - break; - case nct6799: - data->temp_label = nct6799_temp_label; - data->temp_mask = NCT6799_TEMP_MASK; - data->virt_temp_mask = NCT6799_VIRT_TEMP_MASK; - break; } data->REG_CONFIG = NCT6775_REG_CONFIG; @@ -3980,8 +4019,6 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, case nct6795: case nct6796: case nct6797: - case nct6798: - case nct6799: data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); break; @@ -3990,9 +4027,6 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, break; } - if (data->kind == nct6798 || data->kind == nct6799) - data->scale_in = scale_in_6798; - reg_temp = NCT6779_REG_TEMP; num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); if (data->kind == nct6791) { @@ -4005,10 +4039,101 @@ 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; break; + case nct6798: + case nct6799: + data->in_num = data->kind == nct6799 ? 18 : 15; + data->scale_in = scale_in_6798; + data->pwm_num = 7; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 6; + data->num_temp_alarms = 7; + data->num_temp_beeps = 8; + + data->ALARM_BITS = NCT6799_ALARM_BITS; + data->BEEP_BITS = NCT6799_BEEP_BITS; + + data->fan_from_reg = fan_from_reg_rpm; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + switch (data->kind) { + default: + case nct6798: + data->temp_label = nct6798_temp_label; + data->temp_mask = NCT6798_TEMP_MASK; + data->virt_temp_mask = NCT6798_VIRT_TEMP_MASK; + break; + case nct6799: + data->temp_label = nct6799_temp_label; + data->temp_mask = NCT6799_TEMP_MASK; + data->virt_temp_mask = NCT6799_VIRT_TEMP_MASK; + break; + } + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + data->REG_VIN = NCT6779_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6791_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6791_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK = NCT6779_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6798_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6791_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6791_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6791_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6791_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6799_REG_ALARM; + data->REG_BEEP = NCT6792_REG_BEEP; + data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); + + reg_temp = NCT6798_REG_TEMP; + num_reg_temp = ARRAY_SIZE(NCT6798_REG_TEMP); + reg_temp_mon = NCT6798_REG_TEMP_MON; + num_reg_temp_mon = ARRAY_SIZE(NCT6798_REG_TEMP_MON); + 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; + + break; default: return -ENODEV; } @@ -4086,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; } @@ -4099,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 a409d7a0b813..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", - "NCT6799D", + [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/nct6775.h b/drivers/hwmon/nct6775.h index a84c6ce7275d..296eff99d003 100644 --- a/drivers/hwmon/nct6775.h +++ b/drivers/hwmon/nct6775.h @@ -8,7 +8,7 @@ enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 }; enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; -#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ +#define NUM_TEMP 12 /* Max number of temp attribute sets w/ limits*/ #define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ #define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ @@ -16,6 +16,7 @@ enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; #define NUM_REG_BEEP 5 /* Max number of beep registers */ #define NUM_FAN 7 +#define NUM_IN 18 struct nct6775_data { int addr; /* IO base of hw monitor block */ @@ -97,7 +98,7 @@ struct nct6775_data { /* Register values */ u8 bank; /* current register bank */ u8 in_num; /* number of in inputs we have */ - u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ + u8 in[NUM_IN][3]; /* [0]=in, [1]=in_max, [2]=in_min */ const u16 *scale_in; /* internal scaling factors */ unsigned int rpm[NUM_FAN]; u16 fan_min[NUM_FAN]; @@ -166,7 +167,7 @@ struct nct6775_data { u16 have_temp; u16 have_temp_fixed; u16 have_tsi_temp; - u16 have_in; + u32 have_in; /* Remember extra register values over suspend/resume */ u8 vbat; @@ -239,10 +240,25 @@ nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group * #define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 -#define FAN_ALARM_BASE 16 -#define TEMP_ALARM_BASE 24 -#define INTRUSION_ALARM_BASE 30 -#define BEEP_ENABLE_BASE 15 +/* + * ALARM_BITS and BEEP_BITS store bit-index for the mask of the registers + * loaded into data->alarm and data->beep. + * + * Every input register (IN/TEMP/FAN) must have a corresponding + * ALARM/BEEP bit at the same index BITS[BASE + index] + * Set value to -1 to disable the visibility of that '*_alarm' attribute and + * to pad the bits until the next BASE + * + * Beep has an additional GLOBAL_BEEP_ENABLE bit + */ +#define VIN_ALARM_BASE 0 +#define FAN_ALARM_BASE 24 +#define TEMP_ALARM_BASE 36 +#define INTRUSION_ALARM_BASE 48 +#define BEEP_ENABLE_BASE 50 + +#define NUM_ALARM_BITS (INTRUSION_ALARM_BASE + 4) +#define NUM_BEEP_BITS (BEEP_ENABLE_BASE + 1) /* * Not currently used: 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), + ®val, 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), + ®val, 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), ®); + 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), ®val); + 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/nsa320-hwmon.c b/drivers/hwmon/nsa320-hwmon.c index ebe6b031e56f..18076ba7fc14 100644 --- a/drivers/hwmon/nsa320-hwmon.c +++ b/drivers/hwmon/nsa320-hwmon.c @@ -20,8 +20,6 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> -#include <linux/of_device.h> -#include <linux/of_platform.h> #include <linux/platform_device.h> /* Tests for error return values rely upon this value being < 0x80 */ @@ -193,7 +191,7 @@ static struct platform_driver nsa320_hwmon_driver = { .probe = nsa320_hwmon_probe, .driver = { .name = "nsa320-hwmon", - .of_match_table = of_match_ptr(of_nsa320_hwmon_match), + .of_match_table = of_nsa320_hwmon_match, }, }; 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 1e1cc67bcdea..000000000000 --- a/drivers/hwmon/oxp-sensors.c +++ /dev/null @@ -1,492 +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) -{ - const struct dmi_system_id *dmi_entry; - struct device *dev = &pdev->dev; - struct device *hwdev; - - /* - * 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; - - 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) -{ - 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 ed968401f93c..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; } @@ -219,19 +214,21 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) { int chan_rank_max = priv->gen_info->chan_rank_max; int dimm_idx_max = priv->gen_info->dimm_idx_max; - u32 chan_rank_empty = 0; - u32 dimm_mask = 0; - int chan_rank, dimm_idx, ret; + DECLARE_BITMAP(dimm_mask, DIMM_NUMS_MAX); + DECLARE_BITMAP(chan_rank_empty, CHAN_RANK_MAX); + + int chan_rank, dimm_idx, ret, i; u32 pcs; - BUILD_BUG_ON(BITS_PER_TYPE(chan_rank_empty) < CHAN_RANK_MAX); - BUILD_BUG_ON(BITS_PER_TYPE(dimm_mask) < DIMM_NUMS_MAX); if (chan_rank_max * dimm_idx_max > DIMM_NUMS_MAX) { WARN_ONCE(1, "Unsupported number of DIMMs - chan_rank_max: %d, dimm_idx_max: %d", chan_rank_max, dimm_idx_max); return -EINVAL; } + bitmap_zero(dimm_mask, DIMM_NUMS_MAX); + bitmap_zero(chan_rank_empty, CHAN_RANK_MAX); + for (chan_rank = 0; chan_rank < chan_rank_max; chan_rank++) { ret = peci_pcs_read(priv->peci_dev, PECI_PCS_DDR_DIMM_TEMP, chan_rank, &pcs); if (ret) { @@ -242,7 +239,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) * detection to be performed at a later point in time. */ if (ret == -EINVAL) { - chan_rank_empty |= BIT(chan_rank); + bitmap_set(chan_rank_empty, chan_rank, 1); continue; } @@ -251,7 +248,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) for (dimm_idx = 0; dimm_idx < dimm_idx_max; dimm_idx++) if (__dimm_temp(pcs, dimm_idx)) - dimm_mask |= BIT(chan_rank * dimm_idx_max + dimm_idx); + bitmap_set(dimm_mask, chan_rank * dimm_idx_max + dimm_idx, 1); } /* @@ -260,7 +257,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) * host platform boot. Retrying a couple of times lets us make sure * that the state is persistent. */ - if (chan_rank_empty == GENMASK(chan_rank_max - 1, 0)) { + if (bitmap_full(chan_rank_empty, chan_rank_max)) { if (priv->no_dimm_retry_count < NO_DIMM_RETRY_COUNT_MAX) { priv->no_dimm_retry_count++; @@ -274,14 +271,16 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) * It's possible that memory training is not done yet. In this case we * defer the detection to be performed at a later point in time. */ - if (!dimm_mask) { + if (bitmap_empty(dimm_mask, DIMM_NUMS_MAX)) { priv->no_dimm_retry_count = 0; return -EAGAIN; } - dev_dbg(priv->dev, "Scanned populated DIMMs: %#x\n", dimm_mask); + for_each_set_bit(i, dimm_mask, DIMM_NUMS_MAX) { + dev_dbg(priv->dev, "Found DIMM%#x\n", i); + } - bitmap_from_arr32(priv->dimm_mask, &dimm_mask, DIMM_NUMS_MAX); + bitmap_copy(priv->dimm_mask, dimm_mask, DIMM_NUMS_MAX); return 0; } @@ -343,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, @@ -503,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, ®_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, ®_val); if (ret) - return -ENODATA; /* Use default or previous value */ + return -ENODATA; /* * Device 26, Offset 224e0: IMC 0 channel 0 -> rank 0 @@ -530,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, ®_val); + if (ret || !(reg_val & BIT(31))) + return -ENODATA; + + ret = peci_ep_pci_local_read(priv->peci_dev, 0, 30, 0, 2, 0xd0, ®_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, @@ -572,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", @@ -597,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); @@ -612,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 270b6336b76d..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,22 @@ 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" + help + If you say yes here you get regulator support for MPS MP2975 + Dual Loop Digital Multi-Phase Controller. + config SENSORS_MP5023 tristate "MPS MP5023" help @@ -326,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 @@ -342,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 @@ -420,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 @@ -467,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 0a0ef4ce3493..9f07fb4abaff 100644 --- a/drivers/hwmon/pmbus/acbel-fsg032.c +++ b/drivers/hwmon/pmbus/acbel-fsg032.c @@ -3,14 +3,51 @@ * Copyright 2023 IBM Corp. */ +#include <linux/debugfs.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/i2c.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/pmbus.h> #include <linux/hwmon-sysfs.h> #include "pmbus.h" +#define ACBEL_MFR_FW_REVISION 0xd9 + +static ssize_t acbel_fsg032_debugfs_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + char out[8]; + int rc; + + rc = i2c_smbus_read_block_data(client, ACBEL_MFR_FW_REVISION, data); + if (rc < 0) + return rc; + + rc = snprintf(out, sizeof(out), "%*phN\n", min(rc, 3), data); + return simple_read_from_buffer(buf, count, ppos, out, rc); +} + +static const struct file_operations acbel_debugfs_ops = { + .llseek = noop_llseek, + .read = acbel_fsg032_debugfs_read, + .write = NULL, + .open = simple_open, +}; + +static void acbel_fsg032_init_debugfs(struct i2c_client *client) +{ + struct dentry *debugfs = pmbus_get_debugfs_dir(client); + + if (!debugfs) + return; + + debugfs_create_file("fw_version", 0444, debugfs, client, &acbel_debugfs_ops); +} + static const struct i2c_device_id acbel_fsg032_id[] = { { "acbel_fsg032" }, {} @@ -59,6 +96,7 @@ static int acbel_fsg032_probe(struct i2c_client *client) if (rc) return rc; + acbel_fsg032_init_debugfs(client); return 0; } @@ -82,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)<p8800_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 = <p8800_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 fa5070ae26bc..ddf9d9a2958c 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -17,12 +17,13 @@ enum chips {pfe1100, pfe3000}; /* - * Disable status check for pfe3000 devices, because some devices report - * communication error (invalid command) for VOUT_MODE command (0x20) - * although correct VOUT_MODE (0x16) is returned: it leads to incorrect - * exponent in linear mode. + * Disable status check because some devices report communication error + * (invalid command) for VOUT_MODE command (0x20) although the correct + * VOUT_MODE (0x16) is returned: it leads to incorrect exponent in linear + * mode. + * This affects both pfe3000 and pfe1100. */ -static struct pmbus_platform_data pfe3000_plat_data = { +static struct pmbus_platform_data pfe_plat_data = { .flags = PMBUS_SKIP_STATUS_CHECK, }; @@ -94,16 +95,15 @@ static int pfe_pmbus_probe(struct i2c_client *client) int model; model = (int)i2c_match_id(pfe_device_id, client)->driver_data; + client->dev.platform_data = &pfe_plat_data; /* * PFE3000-12-069RA devices may not stay in page 0 during device * probe which leads to probe failure (read status word failed). * So let's set the device to page 0 at the beginning. */ - if (model == pfe3000) { - client->dev.platform_data = &pfe3000_plat_data; + if (model == pfe3000) i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); - } return pmbus_do_probe(client, &pfe_driver_info[model]); } @@ -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 f7ff3e4650b7..325111a955e6 100644 --- a/drivers/hwmon/pmbus/dps920ab.c +++ b/drivers/hwmon/pmbus/dps920ab.c @@ -9,7 +9,7 @@ #include <linux/debugfs.h> #include <linux/i2c.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include "pmbus.h" struct dps920ab_data { @@ -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 c791925b8907..d05ef7a968a9 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,7 +13,7 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pmbus.h> #include "pmbus.h" @@ -489,7 +489,7 @@ static int ibm_cffps_probe(struct i2c_client *client) const struct i2c_device_id *id; if (md) { - vs = (enum versions)md; + vs = (uintptr_t)md; } else { id = i2c_match_id(ibm_cffps_id, client); if (id) @@ -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 871c322d3d51..7b4188e8bf48 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -6,7 +6,7 @@ * * VOUT_MODE is not supported by the device. The driver fakes VOUT linear16 * mode with exponent value -8 as direct mode with m=256/b=0/R=0. - * + * * The device supports VOUT_PEAK, IOUT_PEAK, and TEMPERATURE_PEAK, however * this driver does not currently support them. */ @@ -16,13 +16,13 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regulator/driver.h> #include "pmbus.h" #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, <3074_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, <c4286_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 7bcf27995033..95869d198ecf 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -15,7 +15,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pmbus.h> #include <linux/util_macros.h> #include "pmbus.h" @@ -114,6 +114,7 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, const struct pmbus_driver_info *info; const struct max20730_data *data; char tbuf[DEBUG_FS_DATA_MAX] = { 0 }; + char *result = tbuf; u16 val; info = pmbus_get_driver_info(psu->client); @@ -148,13 +149,13 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, >> MAX20730_MFR_DEVSET1_TSTAT_BIT_POS; if (val == 0) - len = strlcpy(tbuf, "2000\n", DEBUG_FS_DATA_MAX); + result = "2000\n"; else if (val == 1) - len = strlcpy(tbuf, "125\n", DEBUG_FS_DATA_MAX); + result = "125\n"; else if (val == 2) - len = strlcpy(tbuf, "62.5\n", DEBUG_FS_DATA_MAX); + result = "62.5\n"; else - len = strlcpy(tbuf, "32\n", DEBUG_FS_DATA_MAX); + result = "32\n"; break; case MAX20730_DEBUGFS_INTERNAL_GAIN: val = (data->mfr_devset1 & MAX20730_MFR_DEVSET1_RGAIN_MASK) @@ -163,35 +164,35 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, if (data->id == max20734) { /* AN6209 */ if (val == 0) - len = strlcpy(tbuf, "0.8\n", DEBUG_FS_DATA_MAX); + result = "0.8\n"; else if (val == 1) - len = strlcpy(tbuf, "3.2\n", DEBUG_FS_DATA_MAX); + result = "3.2\n"; else if (val == 2) - len = strlcpy(tbuf, "1.6\n", DEBUG_FS_DATA_MAX); + result = "1.6\n"; else - len = strlcpy(tbuf, "6.4\n", DEBUG_FS_DATA_MAX); + result = "6.4\n"; } else if (data->id == max20730 || data->id == max20710) { /* AN6042 or AN6140 */ if (val == 0) - len = strlcpy(tbuf, "0.9\n", DEBUG_FS_DATA_MAX); + result = "0.9\n"; else if (val == 1) - len = strlcpy(tbuf, "3.6\n", DEBUG_FS_DATA_MAX); + result = "3.6\n"; else if (val == 2) - len = strlcpy(tbuf, "1.8\n", DEBUG_FS_DATA_MAX); + result = "1.8\n"; else - len = strlcpy(tbuf, "7.2\n", DEBUG_FS_DATA_MAX); + result = "7.2\n"; } else if (data->id == max20743) { /* AN6042 */ if (val == 0) - len = strlcpy(tbuf, "0.45\n", DEBUG_FS_DATA_MAX); + result = "0.45\n"; else if (val == 1) - len = strlcpy(tbuf, "1.8\n", DEBUG_FS_DATA_MAX); + result = "1.8\n"; else if (val == 2) - len = strlcpy(tbuf, "0.9\n", DEBUG_FS_DATA_MAX); + result = "0.9\n"; else - len = strlcpy(tbuf, "3.6\n", DEBUG_FS_DATA_MAX); + result = "3.6\n"; } else { - len = strlcpy(tbuf, "Not supported\n", DEBUG_FS_DATA_MAX); + result = "Not supported\n"; } break; case MAX20730_DEBUGFS_BOOT_VOLTAGE: @@ -199,26 +200,26 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, >> MAX20730_MFR_DEVSET1_VBOOT_BIT_POS; if (val == 0) - len = strlcpy(tbuf, "0.6484\n", DEBUG_FS_DATA_MAX); + result = "0.6484\n"; else if (val == 1) - len = strlcpy(tbuf, "0.8984\n", DEBUG_FS_DATA_MAX); + result = "0.8984\n"; else if (val == 2) - len = strlcpy(tbuf, "1.0\n", DEBUG_FS_DATA_MAX); + result = "1.0\n"; else - len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + result = "Invalid\n"; break; case MAX20730_DEBUGFS_OUT_V_RAMP_RATE: val = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_VRATE) >> MAX20730_MFR_DEVSET2_VRATE_BIT_POS; if (val == 0) - len = strlcpy(tbuf, "4\n", DEBUG_FS_DATA_MAX); + result = "4\n"; else if (val == 1) - len = strlcpy(tbuf, "2\n", DEBUG_FS_DATA_MAX); + result = "2\n"; else if (val == 2) - len = strlcpy(tbuf, "1\n", DEBUG_FS_DATA_MAX); + result = "1\n"; else - len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + result = "Invalid\n"; break; case MAX20730_DEBUGFS_OC_PROTECT_MODE: ret = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_OCPM_MASK) @@ -230,13 +231,13 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, >> MAX20730_MFR_DEVSET2_SS_BIT_POS; if (val == 0) - len = strlcpy(tbuf, "0.75\n", DEBUG_FS_DATA_MAX); + result = "0.75\n"; else if (val == 1) - len = strlcpy(tbuf, "1.5\n", DEBUG_FS_DATA_MAX); + result = "1.5\n"; else if (val == 2) - len = strlcpy(tbuf, "3\n", DEBUG_FS_DATA_MAX); + result = "3\n"; else - len = strlcpy(tbuf, "6\n", DEBUG_FS_DATA_MAX); + result = "6\n"; break; case MAX20730_DEBUGFS_IMAX: ret = (data->mfr_devset2 & MAX20730_MFR_DEVSET2_IMAX_MASK) @@ -287,10 +288,11 @@ static ssize_t max20730_debugfs_read(struct file *file, char __user *buf, "%d.%d\n", ret / 10000, ret % 10000); break; default: - len = strlcpy(tbuf, "Invalid\n", DEBUG_FS_DATA_MAX); + result = "Invalid\n"; } - return simple_read_from_buffer(buf, count, ppos, tbuf, len); + len = strlen(result); + return simple_read_from_buffer(buf, count, ppos, result, len); } static const struct file_operations max20730_fops = { @@ -714,7 +716,7 @@ static int max20730_probe(struct i2c_client *client) } if (client->dev.of_node) - chip_id = (enum chips)of_device_get_match_data(dev); + chip_id = (uintptr_t)of_device_get_match_data(dev); else chip_id = i2c_match_id(max20730_id, client)->driver_data; @@ -785,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 2109b0458a8b..c31982d85196 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -5,11 +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 "pmbus.h" /* Vendor specific registers. */ @@ -34,6 +37,8 @@ #define MP2975_MFR_OVP_TH_SET 0xe5 #define MP2975_MFR_UVP_SET 0xe6 +#define MP2973_MFR_RESO_SET 0xc7 + #define MP2975_VOUT_FORMAT BIT(15) #define MP2975_VID_STEP_SEL_R1 BIT(4) #define MP2975_IMVP9_EN_R1 BIT(13) @@ -48,26 +53,75 @@ #define MP2975_SENSE_AMPL_HALF 2 #define MP2975_VIN_UV_LIMIT_UNIT 8 +#define MP2973_VOUT_FORMAT_R1 GENMASK(7, 6) +#define MP2973_VOUT_FORMAT_R2 GENMASK(4, 3) +#define MP2973_VOUT_FORMAT_DIRECT_R1 BIT(7) +#define MP2973_VOUT_FORMAT_LINEAR_R1 BIT(6) +#define MP2973_VOUT_FORMAT_DIRECT_R2 BIT(4) +#define MP2973_VOUT_FORMAT_LINEAR_R2 BIT(3) + +#define MP2973_MFR_VR_MULTI_CONFIG_R1 0x0d +#define MP2973_MFR_VR_MULTI_CONFIG_R2 0x1d +#define MP2973_VID_STEP_SEL_R1 BIT(4) +#define MP2973_IMVP9_EN_R1 BIT(14) +#define MP2973_VID_STEP_SEL_R2 BIT(3) +#define MP2973_IMVP9_EN_R2 BIT(13) + +#define MP2973_MFR_OCP_TOTAL_SET 0x5f +#define MP2973_OCP_TOTAL_CUR_MASK GENMASK(6, 0) +#define MP2973_MFR_OCP_LEVEL_RES BIT(15) + +#define MP2973_MFR_READ_IOUT_PK 0x90 +#define MP2973_MFR_READ_POUT_PK 0x91 + #define MP2975_MAX_PHASE_RAIL1 8 #define MP2975_MAX_PHASE_RAIL2 4 + +#define MP2973_MAX_PHASE_RAIL1 14 +#define MP2973_MAX_PHASE_RAIL2 6 + +#define MP2971_MAX_PHASE_RAIL1 8 +#define MP2971_MAX_PHASE_RAIL2 3 + #define MP2975_PAGE_NUM 2 #define MP2975_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | \ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | \ PMBUS_HAVE_POUT | PMBUS_PHASE_VIRTUAL) +enum chips { + mp2971, mp2973, mp2975 +}; + +static const int mp2975_max_phases[][MP2975_PAGE_NUM] = { + [mp2975] = { MP2975_MAX_PHASE_RAIL1, MP2975_MAX_PHASE_RAIL2 }, + [mp2973] = { MP2973_MAX_PHASE_RAIL1, MP2973_MAX_PHASE_RAIL2 }, + [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; int vout_scale; + int max_phases[MP2975_PAGE_NUM]; int vid_step[MP2975_PAGE_NUM]; int vref[MP2975_PAGE_NUM]; int vref_off[MP2975_PAGE_NUM]; int vout_max[MP2975_PAGE_NUM]; int vout_ov_fixed[MP2975_PAGE_NUM]; - int vout_format[MP2975_PAGE_NUM]; int curr_sense_gain[MP2975_PAGE_NUM]; }; +static const struct regulator_desc __maybe_unused mp2975_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; + #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) @@ -75,10 +129,9 @@ static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg) switch (reg) { case PMBUS_VOUT_MODE: /* - * Enforce VOUT direct format, since device allows to set the - * different formats for the different rails. Conversion from - * VID to direct provided by driver internally, in case it is - * necessary. + * 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: @@ -117,6 +170,41 @@ mp2975_vid2direct(int vrf, int val) return 0; } +#define MAX_LIN_MANTISSA (1023 * 1000) +#define MIN_LIN_MANTISSA (511 * 1000) + +/* Converts a milli-unit DIRECT value to LINEAR11 format */ +static u16 mp2975_data2reg_linear11(s64 val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + /* 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 mp2975_read_phase(struct i2c_client *client, struct mp2975_data *data, int page, int phase, u8 reg) @@ -214,6 +302,168 @@ mp2975_read_phases(struct i2c_client *client, struct mp2975_data *data, return ret; } +static int mp2973_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 mp2975_data *data = to_mp2975_data(info); + 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)); + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, reg, + GENMASK(7, 0)); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(ret, MP2975_VIN_UV_LIMIT_UNIT); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + /* + * MP2971 and mp2973 only supports tracking (ovp1) mode. + */ + ret = mp2975_read_word_helper(client, page, phase, + MP2975_MFR_OVP_TH_SET, + GENMASK(2, 0)); + if (ret < 0) + return ret; + + ret = data->vout_max[page] + 50 * (ret + 1); + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, reg, + GENMASK(8, 0)); + if (ret < 0) + return ret; + ret = mp2975_vid2direct(info->vrm_version[page], ret); + break; + case PMBUS_VIRT_READ_POUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + MP2973_MFR_READ_POUT_PK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, phase, + MP2973_MFR_READ_IOUT_PK); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = mp2975_read_word_helper(client, page, phase, + MP2973_MFR_OCP_TOTAL_SET, + GENMASK(15, 0)); + if (ret < 0) + return ret; + + if (ret & MP2973_MFR_OCP_LEVEL_RES) + ret = 2 * (ret & MP2973_OCP_TOTAL_CUR_MASK); + else + ret = ret & MP2973_OCP_TOTAL_CUR_MASK; + + ret = mp2975_data2reg_linear11(ret * info->phases[page] * 1000); + break; + case PMBUS_UT_WARN_LIMIT: + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_IIN_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_LV_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_POUT_OP_FAULT_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + return -ENXIO; + default: + return -ENODATA; + } + + 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) { @@ -260,24 +510,6 @@ static int mp2975_read_word_data(struct i2c_client *client, int page, ret = DIV_ROUND_CLOSEST(data->vref[page] * 10 - 50 * (ret + 1) * data->vout_scale, 10); break; - case PMBUS_READ_VOUT: - ret = mp2975_read_word_helper(client, page, phase, reg, - GENMASK(11, 0)); - if (ret < 0) - return ret; - - /* - * READ_VOUT can be provided in VID or direct format. The - * format type is specified by bit 15 of the register - * MP2975_MFR_DC_LOOP_CTRL. The driver enforces VOUT direct - * format, since device allows to set the different formats for - * the different rails and also all VOUT limits registers are - * provided in a direct format. In case format is VID - convert - * to direct. - */ - if (data->vout_format[page] == vid) - ret = mp2975_vid2direct(info->vrm_version[page], ret); - break; case PMBUS_VIRT_READ_POUT_MAX: ret = mp2975_read_word_helper(client, page, phase, MP2975_MFR_READ_POUT_PK, @@ -326,25 +558,25 @@ static int mp2975_read_word_data(struct i2c_client *client, int page, return ret; } -static int mp2975_identify_multiphase_rail2(struct i2c_client *client) +static int mp2975_identify_multiphase_rail2(struct i2c_client *client, + struct mp2975_data *data) { int ret; /* - * Identify multiphase for rail 2 - could be from 0 to 4. + * Identify multiphase for rail 2 - could be from 0 to data->max_phases[1]. * In case phase number is zero – only page zero is supported */ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); if (ret < 0) return ret; - /* Identify multiphase for rail 2 - could be from 0 to 4. */ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R2); if (ret < 0) return ret; ret &= GENMASK(2, 0); - return (ret >= 4) ? 4 : ret; + return (ret >= data->max_phases[1]) ? data->max_phases[1] : ret; } static void mp2975_set_phase_rail1(struct pmbus_driver_info *info) @@ -375,7 +607,7 @@ mp2975_identify_multiphase(struct i2c_client *client, struct mp2975_data *data, if (ret < 0) return ret; - /* Identify multiphase for rail 1 - could be from 1 to 8. */ + /* Identify multiphase for rail 1 - could be from 1 to data->max_phases[0]. */ ret = i2c_smbus_read_word_data(client, MP2975_MFR_VR_MULTI_CONFIG_R1); if (ret <= 0) return ret; @@ -383,21 +615,23 @@ mp2975_identify_multiphase(struct i2c_client *client, struct mp2975_data *data, info->phases[0] = ret & GENMASK(3, 0); /* - * The device provides a total of 8 PWM pins, and can be configured + * The device provides a total of $n PWM pins, and can be configured * to different phase count applications for rail 1 and rail 2. - * Rail 1 can be set to 8 phases, while rail 2 can only be set to 4 - * phases at most. When rail 1’s phase count is configured as 0, rail + * Rail 1 can be set to $n phases, while rail 2 can be set to less than + * that. When rail 1’s phase count is configured as 0, rail * 1 operates with 1-phase DCM. When rail 2 phase count is configured * as 0, rail 2 is disabled. */ - if (info->phases[0] > MP2975_MAX_PHASE_RAIL1) + if (info->phases[0] > data->max_phases[0]) return -EINVAL; - mp2975_set_phase_rail1(info); - num_phases2 = min(MP2975_MAX_PHASE_RAIL1 - info->phases[0], - MP2975_MAX_PHASE_RAIL2); - if (info->phases[1] && info->phases[1] <= num_phases2) - mp2975_set_phase_rail2(info, num_phases2); + if (data->chip_id == mp2975) { + mp2975_set_phase_rail1(info); + num_phases2 = min(data->max_phases[0] - info->phases[0], + data->max_phases[1]); + if (info->phases[1] && info->phases[1] <= num_phases2) + mp2975_set_phase_rail2(info, num_phases2); + } return 0; } @@ -451,6 +685,35 @@ mp2975_identify_rails_vid(struct i2c_client *client, struct mp2975_data *data, MP2975_MFR_VR_MULTI_CONFIG_R2, 1, MP2975_IMVP9_EN_R2, MP2975_VID_STEP_SEL_R2); + + return ret; +} + +static int +mp2973_identify_rails_vid(struct i2c_client *client, struct mp2975_data *data, + struct pmbus_driver_info *info) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return ret; + + /* Identify VID mode for rail 1. */ + ret = mp2975_identify_vid(client, data, info, + MP2973_MFR_VR_MULTI_CONFIG_R1, 0, + MP2973_IMVP9_EN_R1, MP2973_VID_STEP_SEL_R1); + + if (ret < 0) + return ret; + + /* Identify VID mode for rail 2, if connected. */ + if (info->phases[1]) + ret = mp2975_identify_vid(client, data, info, + MP2973_MFR_VR_MULTI_CONFIG_R2, 1, + MP2973_IMVP9_EN_R2, + MP2973_VID_STEP_SEL_R2); + return ret; } @@ -565,20 +828,37 @@ mp2975_vout_max_get(struct i2c_client *client, struct mp2975_data *data, } static int -mp2975_identify_vout_format(struct i2c_client *client, - struct mp2975_data *data, int page) +mp2975_set_vout_format(struct i2c_client *client, + struct mp2975_data *data, int page) { - int ret; - - ret = i2c_smbus_read_word_data(client, MP2975_MFR_DC_LOOP_CTRL); - if (ret < 0) - return ret; + int ret, i; - if (ret & MP2975_VOUT_FORMAT) - data->vout_format[page] = vid; - else - data->vout_format[page] = direct; - return 0; + /* Enable DIRECT VOUT format 1mV/LSB */ + if (data->chip_id == mp2975) { + ret = i2c_smbus_read_word_data(client, MP2975_MFR_DC_LOOP_CTRL); + if (ret < 0) + return ret; + if (ret & MP2975_VOUT_FORMAT) { + ret &= ~MP2975_VOUT_FORMAT; + ret = i2c_smbus_write_word_data(client, MP2975_MFR_DC_LOOP_CTRL, ret); + } + } else { + ret = i2c_smbus_read_word_data(client, MP2973_MFR_RESO_SET); + if (ret < 0) + return ret; + i = ret; + + if (page == 0) { + i &= ~MP2973_VOUT_FORMAT_R1; + i |= MP2973_VOUT_FORMAT_DIRECT_R1; + } else { + i &= ~MP2973_VOUT_FORMAT_R2; + i |= MP2973_VOUT_FORMAT_DIRECT_R2; + } + if (i != ret) + ret = i2c_smbus_write_word_data(client, MP2973_MFR_RESO_SET, i); + } + return ret; } static int @@ -600,7 +880,7 @@ mp2975_vout_ov_scale_get(struct i2c_client *client, struct mp2975_data *data, if (ret < 0) return ret; thres_dev = ret & MP2975_PRT_THRES_DIV_OV_EN ? MP2975_PROT_DEV_OV_ON : - MP2975_PROT_DEV_OV_OFF; + MP2975_PROT_DEV_OV_OFF; /* Select the gain of remote sense amplifier. */ ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP); @@ -624,10 +904,10 @@ mp2975_vout_per_rail_config_get(struct i2c_client *client, for (i = 0; i < data->info.pages; i++) { ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); if (ret < 0) - return ret; + continue; - /* Obtain voltage reference offsets. */ - ret = mp2975_vref_offset_get(client, data, i); + /* Set VOUT format for READ_VOUT command : direct. */ + ret = mp2975_set_vout_format(client, data, i); if (ret < 0) return ret; @@ -636,12 +916,12 @@ mp2975_vout_per_rail_config_get(struct i2c_client *client, if (ret < 0) return ret; - /* - * Get VOUT format for READ_VOUT command : VID or direct. - * Pages on same device can be configured with different - * formats. - */ - ret = mp2975_identify_vout_format(client, data, i); + /* Skip if reading Vref is unsupported */ + if (data->chip_id != mp2975) + continue; + + /* Obtain voltage reference offsets. */ + ret = mp2975_vref_offset_get(client, data, i); if (ret < 0) return ret; @@ -659,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, @@ -678,24 +958,67 @@ static struct pmbus_driver_info mp2975_info = { 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, + .reg_desc = mp2975_reg_desc, +#endif +}; + +static const struct pmbus_driver_info mp2973_info = { + .pages = 1, + .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, + .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; - memcpy(&data->info, &mp2975_info, sizeof(*info)); + data->chip_id = ddinfo->chip_id; + + memcpy(data->max_phases, mp2975_max_phases[data->chip_id], + sizeof(data->max_phases)); + + memcpy(&data->info, ddinfo->info, sizeof(data->info)); + info = &data->info; /* Identify multiphase configuration for rail 2. */ - ret = mp2975_identify_multiphase_rail2(client); + ret = mp2975_identify_multiphase_rail2(client, data); if (ret < 0) return ret; @@ -704,6 +1027,8 @@ static int mp2975_probe(struct i2c_client *client) data->info.pages = MP2975_PAGE_NUM; data->info.phases[1] = ret; data->info.func[1] = MP2975_RAIL2_FUNC; + if (IS_ENABLED(CONFIG_SENSORS_MP2975_REGULATOR)) + data->info.num_regulators = MP2975_PAGE_NUM; } /* Identify multiphase configuration. */ @@ -711,25 +1036,32 @@ static int mp2975_probe(struct i2c_client *client) if (ret) return ret; - /* Identify VID setting per rail. */ - ret = mp2975_identify_rails_vid(client, data, info); - if (ret < 0) - return ret; + if (data->chip_id == mp2975) { + /* Identify VID setting per rail. */ + ret = mp2975_identify_rails_vid(client, data, info); + if (ret < 0) + return ret; - /* Obtain current sense gain of power stage. */ - ret = mp2975_current_sense_gain_get(client, data); - if (ret) - return ret; + /* Obtain current sense gain of power stage. */ + ret = mp2975_current_sense_gain_get(client, data); + if (ret) + return ret; - /* Obtain voltage reference values. */ - ret = mp2975_vref_get(client, data, info); - if (ret) - return ret; + /* Obtain voltage reference values. */ + ret = mp2975_vref_get(client, data, info); + if (ret) + return ret; - /* Obtain vout over-voltage scales. */ - ret = mp2975_vout_ov_scale_get(client, data, info); - if (ret < 0) - return ret; + /* Obtain vout over-voltage scales. */ + ret = mp2975_vout_ov_scale_get(client, data, info); + if (ret < 0) + return ret; + } else { + /* Identify VID setting per rail. */ + ret = mp2973_identify_rails_vid(client, data, info); + if (ret < 0) + return ret; + } /* Obtain offsets, maximum and format for vout. */ ret = mp2975_vout_per_rail_config_get(client, data, info); @@ -739,23 +1071,26 @@ static int mp2975_probe(struct i2c_client *client) return pmbus_do_probe(client, info); } -static const struct i2c_device_id mp2975_id[] = { - {"mp2975", 0}, +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); -MODULE_DEVICE_TABLE(i2c, mp2975_id); - -static const struct of_device_id __maybe_unused mp2975_of_match[] = { - {.compatible = "mps,mp2975"}, +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(of, mp2975_of_match); +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, @@ -766,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 c4c4324d2b74..c466d67e9a8f 100644 --- a/drivers/hwmon/pmbus/mp5023.c +++ b/drivers/hwmon/pmbus/mp5023.c @@ -5,7 +5,7 @@ #include <linux/i2c.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include "pmbus.h" static struct pmbus_driver_info mp5023_info = { @@ -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 865d42edda1a..8f10e37a7a76 100644 --- a/drivers/hwmon/pmbus/mpq7932.c +++ b/drivers/hwmon/pmbus/mpq7932.c @@ -12,7 +12,7 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pmbus.h> #include "pmbus.h" @@ -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 7d8bd3167b21..569b61dc1a32 100644 --- a/drivers/hwmon/pmbus/pli1209bc.c +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -5,6 +5,7 @@ * Copyright (c) 2022 9elements GmbH */ +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/pmbus.h> @@ -102,6 +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_delay = 250, #if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) .num_regulators = 1, .reg_desc = &pli1209bc_reg_desc, @@ -115,7 +117,7 @@ static int pli1209bc_probe(struct i2c_client *client) } static const struct i2c_device_id pli1209bc_id[] = { - {"pli1209bc", 0}, + {"pli1209bc"}, {} }; @@ -143,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 69a4e62b6c8d..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) { @@ -561,7 +630,8 @@ static bool pmbus_check_register(struct i2c_client *client, rv = pmbus_check_status_cml(client); if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK)) data->read_status(client, -1); - pmbus_clear_fault_page(client, -1); + if (reg < PMBUS_VIRT_BASE) + pmbus_clear_fault_page(client, -1); return rv >= 0; } @@ -586,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) @@ -616,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) { @@ -716,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; @@ -1094,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; @@ -1393,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); @@ -1423,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 */ }; @@ -2135,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; @@ -2148,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, @@ -2449,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; @@ -2540,7 +2614,6 @@ static int pmbus_identify_common(struct i2c_client *client, } } - pmbus_clear_fault_page(client, page); return 0; } @@ -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)) { - entries[idx].client = client; - entries[idx].page = 0; - entries[idx].reg = PMBUS_MFR_ID; - debugfs_create_file("mfr_id", 0444, data->debugfs, - &entries[idx++], - &pmbus_debugfs_ops_mfr); - } - - if (pmbus_check_block_register(client, 0, PMBUS_MFR_MODEL)) { + 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_MODEL; - debugfs_create_file("mfr_model", 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_REVISION)) { + if (pmbus_check_byte_register(client, 0, PMBUS_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].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_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); - } + 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_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); - } - - 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 b830f3b02bcc..4d7086d83aa3 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -10,7 +10,7 @@ #include <linux/i2c.h> #include <linux/kstrtox.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include "pmbus.h" #define STORE_DEFAULT_ALL 0x11 @@ -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 ef99005a3af5..ca2bfa25eb04 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -12,11 +12,11 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #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) { @@ -235,7 +254,7 @@ static int tps53679_probe(struct i2c_client *client) enum chips chip_id; if (dev->of_node) - chip_id = (enum chips)of_device_get_match_data(dev); + chip_id = (uintptr_t)of_device_get_match_data(dev); else chip_id = i2c_match_id(tps53679_id, client)->driver_data; @@ -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 c404d306e8f7..55e7af3a5f98 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -10,7 +10,7 @@ #include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/init.h> #include <linux/err.h> #include <linux/slab.h> @@ -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, @@ -588,7 +532,7 @@ static int ucd9000_probe(struct i2c_client *client) } if (client->dev.of_node) - chip = (enum chips)of_device_get_match_data(&client->dev); + chip = (uintptr_t)of_device_get_match_data(&client->dev); else chip = mid->driver_data; @@ -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 a82847945508..f68adaf4a110 100644 --- a/drivers/hwmon/pmbus/ucd9200.c +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -7,7 +7,7 @@ #include <linux/kernel.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/init.h> #include <linux/err.h> #include <linux/slab.h> @@ -103,7 +103,7 @@ static int ucd9200_probe(struct i2c_client *client) } if (client->dev.of_node) - chip = (enum chips)of_device_get_match_data(&client->dev); + chip = (uintptr_t)of_device_get_match_data(&client->dev); else chip = mid->driver_data; @@ -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 b79cece4ac9a..c5b2488c4c7f 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -12,9 +12,8 @@ #include <linux/init.h> #include <linux/hwmon.h> #include <linux/module.h> -#include <linux/mutex.h> -#include <linux/of_device.h> #include <linux/of.h> +#include <linux/bitfield.h> /* * SB-TSI registers only support SMBus byte data access. "_INT" registers are @@ -30,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; }; /* @@ -75,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; @@ -123,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; } @@ -147,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, @@ -204,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); @@ -233,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/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c index 121e5e9f487f..a0d220609565 100644 --- a/drivers/hwmon/scpi-hwmon.c +++ b/drivers/hwmon/scpi-hwmon.c @@ -8,7 +8,7 @@ #include <linux/hwmon.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/scpi_protocol.h> #include <linux/slab.h> 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 bf18630619e0..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 @@ -147,8 +149,20 @@ static const u16 mode_to_update_interval[] = { 100, }; +static const struct hwmon_channel_info * const sht3x_channel_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | + HWMON_T_MIN_HYST | HWMON_T_MAX | + HWMON_T_MAX_HYST | HWMON_T_ALARM), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | + HWMON_H_MIN_HYST | HWMON_H_MAX | + HWMON_H_MAX_HYST | HWMON_H_ALARM), + NULL, +}; + struct sht3x_data { struct i2c_client *client; + enum sht3x_chips chip_id; struct mutex i2c_lock; /* lock for sending i2c commands */ struct mutex data_lock; /* lock for updating driver data */ @@ -157,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 @@ -276,27 +291,26 @@ out: return data; } -/* sysfs attributes */ -static ssize_t temp1_input_show(struct device *dev, - struct device_attribute *attr, char *buf) +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 sprintf(buf, "%d\n", data->temperature); + *temp = data->temperature; + return 0; } -static ssize_t humidity1_input_show(struct device *dev, - struct device_attribute *attr, char *buf) +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 sprintf(buf, "%u\n", data->humidity); + *humidity = data->humidity; + return 0; } /* @@ -332,33 +346,24 @@ static int limits_update(struct sht3x_data *data) return ret; } -static ssize_t temp1_limit_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int temp1_limit_read(struct device *dev, int index) { struct sht3x_data *data = dev_get_drvdata(dev); - u8 index = to_sensor_dev_attr(attr)->index; - int temperature_limit = data->temperature_limits[index]; - return sysfs_emit(buf, "%d\n", temperature_limit); + return data->temperature_limits[index]; } -static ssize_t humidity1_limit_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int humidity1_limit_read(struct device *dev, int index) { struct sht3x_data *data = dev_get_drvdata(dev); - u8 index = to_sensor_dev_attr(attr)->index; - u32 humidity_limit = data->humidity_limits[index]; - return sysfs_emit(buf, "%u\n", humidity_limit); + return data->humidity_limits[index]; } /* - * limit_store must only be called with data_lock held + * limit_write must only be called with data_lock held */ -static size_t limit_store(struct device *dev, - size_t count, +static size_t limit_write(struct device *dev, u8 index, int temperature, u32 humidity) @@ -379,7 +384,7 @@ static size_t limit_store(struct device *dev, * ST = (T + 45) / 175 * 2^16 * SRH = RH / 100 * 2^16 * adapted for fixed point arithmetic and packed the same as - * in limit_show() + * in limit_read() */ raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7); raw |= ((humidity * 42950) >> 16) & 0xfe00; @@ -400,50 +405,35 @@ static size_t limit_store(struct device *dev, data->temperature_limits[index] = temperature; data->humidity_limits[index] = humidity; - return count; + + return 0; } -static ssize_t temp1_limit_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) +static int temp1_limit_write(struct device *dev, int index, int val) { int temperature; int ret; struct sht3x_data *data = dev_get_drvdata(dev); - u8 index = to_sensor_dev_attr(attr)->index; - ret = kstrtoint(buf, 0, &temperature); - if (ret) - return ret; - - temperature = clamp_val(temperature, SHT3X_MIN_TEMPERATURE, + temperature = clamp_val(val, SHT3X_MIN_TEMPERATURE, SHT3X_MAX_TEMPERATURE); mutex_lock(&data->data_lock); - ret = limit_store(dev, count, index, temperature, + ret = limit_write(dev, index, temperature, data->humidity_limits[index]); mutex_unlock(&data->data_lock); return ret; } -static ssize_t humidity1_limit_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) +static int humidity1_limit_write(struct device *dev, int index, int val) { u32 humidity; int ret; struct sht3x_data *data = dev_get_drvdata(dev); - u8 index = to_sensor_dev_attr(attr)->index; - - ret = kstrtou32(buf, 0, &humidity); - if (ret) - return ret; - humidity = clamp_val(humidity, SHT3X_MIN_HUMIDITY, SHT3X_MAX_HUMIDITY); + humidity = clamp_val(val, SHT3X_MIN_HUMIDITY, SHT3X_MAX_HUMIDITY); mutex_lock(&data->data_lock); - ret = limit_store(dev, count, index, data->temperature_limits[index], + ret = limit_write(dev, index, data->temperature_limits[index], humidity); mutex_unlock(&data->data_lock); @@ -474,7 +464,6 @@ static void sht3x_select_command(struct sht3x_data *data) } static int status_register_read(struct device *dev, - struct device_attribute *attr, char *buffer, int length) { int ret; @@ -487,34 +476,30 @@ static int status_register_read(struct device *dev, return ret; } -static ssize_t temp1_alarm_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int temp1_alarm_read(struct device *dev) { char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; int ret; - ret = status_register_read(dev, attr, buffer, + ret = status_register_read(dev, buffer, SHT3X_WORD_LEN + SHT3X_CRC8_LEN); if (ret) return ret; - return sysfs_emit(buf, "%d\n", !!(buffer[0] & 0x04)); + return !!(buffer[0] & 0x04); } -static ssize_t humidity1_alarm_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int humidity1_alarm_read(struct device *dev) { char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; int ret; - ret = status_register_read(dev, attr, buffer, + ret = status_register_read(dev, buffer, SHT3X_WORD_LEN + SHT3X_CRC8_LEN); if (ret) return ret; - return sysfs_emit(buf, "%d\n", !!(buffer[0] & 0x08)); + return !!(buffer[0] & 0x08); } static ssize_t heater_enable_show(struct device *dev, @@ -524,7 +509,7 @@ static ssize_t heater_enable_show(struct device *dev, char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; int ret; - ret = status_register_read(dev, attr, buffer, + ret = status_register_read(dev, buffer, SHT3X_WORD_LEN + SHT3X_CRC8_LEN); if (ret) return ret; @@ -560,39 +545,28 @@ static ssize_t heater_enable_store(struct device *dev, return ret; } -static ssize_t update_interval_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int update_interval_read(struct device *dev) { struct sht3x_data *data = dev_get_drvdata(dev); - return sysfs_emit(buf, "%u\n", - mode_to_update_interval[data->mode]); + return mode_to_update_interval[data->mode]; } -static ssize_t update_interval_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) +static int update_interval_write(struct device *dev, int val) { - u16 update_interval; u8 mode; int ret; const char *command; struct sht3x_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; - ret = kstrtou16(buf, 0, &update_interval); - if (ret) - return ret; - - mode = get_mode_from_update_interval(update_interval); + mode = get_mode_from_update_interval(val); mutex_lock(&data->data_lock); /* mode did not change */ if (mode == data->mode) { mutex_unlock(&data->data_lock); - return count; + return 0; } mutex_lock(&data->i2c_lock); @@ -634,7 +608,7 @@ out: if (ret != SHT3X_CMD_LENGTH) return ret < 0 ? ret : -EIO; - return count; + return 0; } static ssize_t repeatability_show(struct device *dev, @@ -668,60 +642,234 @@ static ssize_t repeatability_store(struct device *dev, return count; } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp1_input, 0); -static SENSOR_DEVICE_ATTR_RO(humidity1_input, humidity1_input, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_max, temp1_limit, limit_max); -static SENSOR_DEVICE_ATTR_RW(humidity1_max, humidity1_limit, limit_max); -static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, temp1_limit, limit_max_hyst); -static SENSOR_DEVICE_ATTR_RW(humidity1_max_hyst, humidity1_limit, - limit_max_hyst); -static SENSOR_DEVICE_ATTR_RW(temp1_min, temp1_limit, limit_min); -static SENSOR_DEVICE_ATTR_RW(humidity1_min, humidity1_limit, limit_min); -static SENSOR_DEVICE_ATTR_RW(temp1_min_hyst, temp1_limit, limit_min_hyst); -static SENSOR_DEVICE_ATTR_RW(humidity1_min_hyst, humidity1_limit, - limit_min_hyst); -static SENSOR_DEVICE_ATTR_RO(temp1_alarm, temp1_alarm, 0); -static SENSOR_DEVICE_ATTR_RO(humidity1_alarm, humidity1_alarm, 0); static SENSOR_DEVICE_ATTR_RW(heater_enable, heater_enable, 0); -static SENSOR_DEVICE_ATTR_RW(update_interval, update_interval, 0); static SENSOR_DEVICE_ATTR_RW(repeatability, repeatability, 0); static struct attribute *sht3x_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_humidity1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_humidity1_max.dev_attr.attr, - &sensor_dev_attr_humidity1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, - &sensor_dev_attr_humidity1_min.dev_attr.attr, - &sensor_dev_attr_humidity1_min_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_alarm.dev_attr.attr, - &sensor_dev_attr_humidity1_alarm.dev_attr.attr, - &sensor_dev_attr_heater_enable.dev_attr.attr, - &sensor_dev_attr_update_interval.dev_attr.attr, - &sensor_dev_attr_repeatability.dev_attr.attr, - NULL -}; - -static struct attribute *sts3x_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_alarm.dev_attr.attr, &sensor_dev_attr_heater_enable.dev_attr.attr, - &sensor_dev_attr_update_interval.dev_attr.attr, &sensor_dev_attr_repeatability.dev_attr.attr, NULL }; ATTRIBUTE_GROUPS(sht3x); -ATTRIBUTE_GROUPS(sts3x); -static const struct i2c_device_id sht3x_ids[]; +static umode_t sht3x_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct sht3x_data *chip_data = data; + + 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_alarm: + return 0444; + case hwmon_temp_max: + case hwmon_temp_max_hyst: + case hwmon_temp_min: + case hwmon_temp_min_hyst: + return 0644; + default: + break; + } + break; + case hwmon_humidity: + if (chip_data->chip_id == sts3x) + break; + switch (attr) { + case hwmon_humidity_input: + case hwmon_humidity_alarm: + return 0444; + case hwmon_humidity_max: + case hwmon_humidity_max_hyst: + case hwmon_humidity_min: + case hwmon_humidity_min_hyst: + return 0644; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +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: + switch (attr) { + case hwmon_chip_update_interval: + *val = update_interval_read(dev); + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return temp1_input_read(dev, val); + case hwmon_temp_alarm: + ret = temp1_alarm_read(dev); + if (ret < 0) + return ret; + *val = ret; + break; + case hwmon_temp_max: + index = limit_max; + *val = temp1_limit_read(dev, index); + break; + case hwmon_temp_max_hyst: + index = limit_max_hyst; + *val = temp1_limit_read(dev, index); + break; + case hwmon_temp_min: + index = limit_min; + *val = temp1_limit_read(dev, index); + break; + case hwmon_temp_min_hyst: + index = limit_min_hyst; + *val = temp1_limit_read(dev, index); + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_humidity: + switch (attr) { + case hwmon_humidity_input: + return humidity1_input_read(dev, val); + case hwmon_humidity_alarm: + ret = humidity1_alarm_read(dev); + if (ret < 0) + return ret; + *val = ret; + break; + case hwmon_humidity_max: + index = limit_max; + *val = humidity1_limit_read(dev, index); + break; + case hwmon_humidity_max_hyst: + index = limit_max_hyst; + *val = humidity1_limit_read(dev, index); + break; + case hwmon_humidity_min: + index = limit_min; + *val = humidity1_limit_read(dev, index); + break; + case hwmon_humidity_min_hyst: + index = limit_min_hyst; + *val = humidity1_limit_read(dev, index); + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int sht3x_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + enum sht3x_limits index; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return update_interval_write(dev, val); + default: + return -EOPNOTSUPP; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_max: + index = limit_max; + break; + case hwmon_temp_max_hyst: + index = limit_max_hyst; + break; + case hwmon_temp_min: + index = limit_min; + break; + case hwmon_temp_min_hyst: + index = limit_min_hyst; + break; + default: + return -EOPNOTSUPP; + } + return temp1_limit_write(dev, index, val); + case hwmon_humidity: + switch (attr) { + case hwmon_humidity_max: + index = limit_max; + break; + case hwmon_humidity_max_hyst: + index = limit_max_hyst; + break; + case hwmon_humidity_min: + index = limit_min; + break; + case hwmon_humidity_min_hyst: + index = limit_min_hyst; + break; + default: + return -EOPNOTSUPP; + } + return humidity1_limit_write(dev, index, val); + default: + return -EOPNOTSUPP; + } +} + +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, + .write = sht3x_write, +}; + +static const struct hwmon_chip_info sht3x_chip_info = { + .ops = &sht3x_ops, + .info = sht3x_channel_info, +}; static int sht3x_probe(struct i2c_client *client) { @@ -730,7 +878,6 @@ static int sht3x_probe(struct i2c_client *client) struct device *hwmon_dev; struct i2c_adapter *adap = client->adapter; struct device *dev = &client->dev; - const struct attribute_group **attribute_groups; /* * we require full i2c support since the sht3x uses multi-byte read and @@ -753,6 +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 = (uintptr_t)i2c_get_match_data(client); crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL); sht3x_select_command(data); @@ -771,20 +919,14 @@ static int sht3x_probe(struct i2c_client *client) if (ret) return ret; - if (i2c_match_id(sht3x_ids, client)->driver_data == sts3x) - attribute_groups = sts3x_groups; - else - attribute_groups = sht3x_groups; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, - client->name, - data, - attribute_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 */ @@ -801,7 +943,6 @@ static struct i2c_driver sht3x_i2c_driver = { .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 b0b05fd12221..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[] = { @@ -798,7 +792,7 @@ static int sis5595_pci_probe(struct pci_dev *dev, { u16 address; u8 enable; - int *i; + int *i, err; for (i = blacklist; *i != 0; i++) { struct pci_dev *d; @@ -818,8 +812,8 @@ static int sis5595_pci_probe(struct pci_dev *dev, pci_write_config_word(dev, SIS5595_BASE_REG, force_addr); } - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(dev, SIS5595_BASE_REG, &address)) { + err = pci_read_config_word(dev, SIS5595_BASE_REG, &address); + if (err != PCIBIOS_SUCCESSFUL) { dev_err(&dev->dev, "Failed to read ISA address\n"); return -ENODEV; } @@ -836,22 +830,23 @@ static int sis5595_pci_probe(struct pci_dev *dev, return -ENODEV; } - if (PCIBIOS_SUCCESSFUL != - pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) { + err = pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable); + if (err != PCIBIOS_SUCCESSFUL) { dev_err(&dev->dev, "Failed to read enable register\n"); return -ENODEV; } if (!(enable & 0x80)) { - if ((PCIBIOS_SUCCESSFUL != - pci_write_config_byte(dev, SIS5595_ENABLE_REG, - enable | 0x80)) - || (PCIBIOS_SUCCESSFUL != - pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) - || (!(enable & 0x80))) { - /* doesn't work for some chips! */ - dev_err(&dev->dev, "Failed to enable HWM device\n"); - return -ENODEV; - } + err = pci_write_config_byte(dev, SIS5595_ENABLE_REG, enable | 0x80); + if (err != PCIBIOS_SUCCESSFUL) + goto enable_fail; + + err = pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable); + if (err != PCIBIOS_SUCCESSFUL) + goto enable_fail; + + /* doesn't work for some chips! */ + if (!(enable & 0x80)) + goto enable_fail; } if (platform_driver_register(&sis5595_driver)) { @@ -871,6 +866,10 @@ static int sis5595_pci_probe(struct pci_dev *dev, */ return -ENODEV; +enable_fail: + dev_err(&dev->dev, "Failed to enable HWM device\n"); + goto exit; + exit_unregister: pci_dev_put(dev); platform_driver_unregister(&sis5595_driver); 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/smm665.c b/drivers/hwmon/smm665.c deleted file mode 100644 index 026c76f8c22e..000000000000 --- a/drivers/hwmon/smm665.c +++ /dev/null @@ -1,706 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for SMM665 Power Controller / Monitor - * - * Copyright (C) 2010 Ericsson AB. - * - * This driver should also work for SMM465, SMM764, and SMM766, but is untested - * for those chips. Only monitoring functionality is implemented. - * - * Datasheets: - * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf - * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/err.h> -#include <linux/slab.h> -#include <linux/i2c.h> -#include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> -#include <linux/delay.h> -#include <linux/jiffies.h> - -/* Internal reference voltage (VREF, x 1000 */ -#define SMM665_VREF_ADC_X1000 1250 - -/* module parameters */ -static int vref = SMM665_VREF_ADC_X1000; -module_param(vref, int, 0); -MODULE_PARM_DESC(vref, "Reference voltage in mV"); - -enum chips { smm465, smm665, smm665c, smm764, smm766 }; - -/* - * ADC channel addresses - */ -#define SMM665_MISC16_ADC_DATA_A 0x00 -#define SMM665_MISC16_ADC_DATA_B 0x01 -#define SMM665_MISC16_ADC_DATA_C 0x02 -#define SMM665_MISC16_ADC_DATA_D 0x03 -#define SMM665_MISC16_ADC_DATA_E 0x04 -#define SMM665_MISC16_ADC_DATA_F 0x05 -#define SMM665_MISC16_ADC_DATA_VDD 0x06 -#define SMM665_MISC16_ADC_DATA_12V 0x07 -#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08 -#define SMM665_MISC16_ADC_DATA_AIN1 0x09 -#define SMM665_MISC16_ADC_DATA_AIN2 0x0a - -/* - * Command registers - */ -#define SMM665_MISC8_CMD_STS 0x80 -#define SMM665_MISC8_STATUS1 0x81 -#define SMM665_MISC8_STATUSS2 0x82 -#define SMM665_MISC8_IO_POLARITY 0x83 -#define SMM665_MISC8_PUP_POLARITY 0x84 -#define SMM665_MISC8_ADOC_STATUS1 0x85 -#define SMM665_MISC8_ADOC_STATUS2 0x86 -#define SMM665_MISC8_WRITE_PROT 0x87 -#define SMM665_MISC8_STS_TRACK 0x88 - -/* - * Configuration registers and register groups - */ -#define SMM665_ADOC_ENABLE 0x0d -#define SMM665_LIMIT_BASE 0x80 /* First limit register */ - -/* - * Limit register bit masks - */ -#define SMM665_TRIGGER_RST 0x8000 -#define SMM665_TRIGGER_HEALTHY 0x4000 -#define SMM665_TRIGGER_POWEROFF 0x2000 -#define SMM665_TRIGGER_SHUTDOWN 0x1000 -#define SMM665_ADC_MASK 0x03ff - -#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \ - | SMM665_TRIGGER_POWEROFF \ - | SMM665_TRIGGER_SHUTDOWN)) -/* - * Fault register bit definitions - * Values are merged from status registers 1/2, - * with status register 1 providing the upper 8 bits. - */ -#define SMM665_FAULT_A 0x0001 -#define SMM665_FAULT_B 0x0002 -#define SMM665_FAULT_C 0x0004 -#define SMM665_FAULT_D 0x0008 -#define SMM665_FAULT_E 0x0010 -#define SMM665_FAULT_F 0x0020 -#define SMM665_FAULT_VDD 0x0040 -#define SMM665_FAULT_12V 0x0080 -#define SMM665_FAULT_TEMP 0x0100 -#define SMM665_FAULT_AIN1 0x0200 -#define SMM665_FAULT_AIN2 0x0400 - -/* - * I2C Register addresses - * - * The configuration register needs to be the configured base register. - * The command/status register address is derived from it. - */ -#define SMM665_REGMASK 0x78 -#define SMM665_CMDREG_BASE 0x48 -#define SMM665_CONFREG_BASE 0x50 - -/* - * Equations given by chip manufacturer to calculate voltage/temperature values - * vref = Reference voltage on VREF_ADC pin (module parameter) - * adc = 10bit ADC value read back from registers - */ - -/* Voltage A-F and VDD */ -#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256) - -/* Voltage 12VIN */ -#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256) - -/* Voltage AIN1, AIN2 */ -#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512) - -/* Temp Sensor */ -#define SMM665_TEMP_ADC_TO_CELSIUS(adc) (((adc) <= 511) ? \ - ((int)(adc) * 1000 / 4) : \ - (((int)(adc) - 0x400) * 1000 / 4)) - -#define SMM665_NUM_ADC 11 - -/* - * Chip dependent ADC conversion time, in uS - */ -#define SMM665_ADC_WAIT_SMM665 70 -#define SMM665_ADC_WAIT_SMM766 185 - -struct smm665_data { - enum chips type; - int conversion_time; /* ADC conversion time */ - struct i2c_client *client; - struct mutex update_lock; - bool valid; - unsigned long last_updated; /* in jiffies */ - u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */ - u16 faults; /* fault status */ - /* The following values are in mV */ - int critical_min_limit[SMM665_NUM_ADC]; - int alarm_min_limit[SMM665_NUM_ADC]; - int critical_max_limit[SMM665_NUM_ADC]; - int alarm_max_limit[SMM665_NUM_ADC]; - struct i2c_client *cmdreg; -}; - -/* - * smm665_read16() - * - * Read 16 bit value from <reg>, <reg+1>. Upper 8 bits are in <reg>. - */ -static int smm665_read16(struct i2c_client *client, int reg) -{ - int rv, val; - - rv = i2c_smbus_read_byte_data(client, reg); - if (rv < 0) - return rv; - val = rv << 8; - rv = i2c_smbus_read_byte_data(client, reg + 1); - if (rv < 0) - return rv; - val |= rv; - return val; -} - -/* - * Read adc value. - */ -static int smm665_read_adc(struct smm665_data *data, int adc) -{ - struct i2c_client *client = data->cmdreg; - int rv; - int radc; - - /* - * Algorithm for reading ADC, per SMM665 datasheet - * - * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]} - * [wait conversion time] - * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]} - * - * To implement the first part of this exchange, - * do a full read transaction and expect a failure/Nack. - * This sets up the address pointer on the SMM665 - * and starts the ADC conversion. - * Then do a two-byte read transaction. - */ - rv = i2c_smbus_read_byte_data(client, adc << 3); - if (rv != -ENXIO) { - /* - * We expect ENXIO to reflect NACK - * (per Documentation/i2c/fault-codes.rst). - * Everything else is an error. - */ - dev_dbg(&client->dev, - "Unexpected return code %d when setting ADC index", rv); - return (rv < 0) ? rv : -EIO; - } - - udelay(data->conversion_time); - - /* - * Now read two bytes. - * - * Neither i2c_smbus_read_byte() nor - * i2c_smbus_read_block_data() worked here, - * so use i2c_smbus_read_word_swapped() instead. - * We could also try to use i2c_master_recv(), - * but that is not always supported. - */ - rv = i2c_smbus_read_word_swapped(client, 0); - if (rv < 0) { - dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv); - return rv; - } - /* - * Validate/verify readback adc channel (in bit 11..14). - */ - radc = (rv >> 11) & 0x0f; - if (radc != adc) { - dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d", - adc, radc); - return -EIO; - } - - return rv & SMM665_ADC_MASK; -} - -static struct smm665_data *smm665_update_device(struct device *dev) -{ - struct smm665_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - struct smm665_data *ret = data; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { - int i, val; - - /* - * read status registers - */ - val = smm665_read16(client, SMM665_MISC8_STATUS1); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->faults = val; - - /* Read adc registers */ - for (i = 0; i < SMM665_NUM_ADC; i++) { - val = smm665_read_adc(data, i); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->adc[i] = val; - } - data->last_updated = jiffies; - data->valid = true; - } -abort: - mutex_unlock(&data->update_lock); - return ret; -} - -/* Return converted value from given adc */ -static int smm665_convert(u16 adcval, int index) -{ - int val = 0; - - switch (index) { - case SMM665_MISC16_ADC_DATA_12V: - val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); - break; - - case SMM665_MISC16_ADC_DATA_VDD: - case SMM665_MISC16_ADC_DATA_A: - case SMM665_MISC16_ADC_DATA_B: - case SMM665_MISC16_ADC_DATA_C: - case SMM665_MISC16_ADC_DATA_D: - case SMM665_MISC16_ADC_DATA_E: - case SMM665_MISC16_ADC_DATA_F: - val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); - break; - - case SMM665_MISC16_ADC_DATA_AIN1: - case SMM665_MISC16_ADC_DATA_AIN2: - val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); - break; - - case SMM665_MISC16_ADC_DATA_INT_TEMP: - val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK); - break; - - default: - /* If we get here, the developer messed up */ - WARN_ON_ONCE(1); - break; - } - - return val; -} - -static int smm665_get_min(struct device *dev, int index) -{ - struct smm665_data *data = dev_get_drvdata(dev); - - return data->alarm_min_limit[index]; -} - -static int smm665_get_max(struct device *dev, int index) -{ - struct smm665_data *data = dev_get_drvdata(dev); - - return data->alarm_max_limit[index]; -} - -static int smm665_get_lcrit(struct device *dev, int index) -{ - struct smm665_data *data = dev_get_drvdata(dev); - - return data->critical_min_limit[index]; -} - -static int smm665_get_crit(struct device *dev, int index) -{ - struct smm665_data *data = dev_get_drvdata(dev); - - return data->critical_max_limit[index]; -} - -static ssize_t smm665_show_crit_alarm(struct device *dev, - struct device_attribute *da, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct smm665_data *data = smm665_update_device(dev); - int val = 0; - - if (IS_ERR(data)) - return PTR_ERR(data); - - if (data->faults & (1 << attr->index)) - val = 1; - - return sysfs_emit(buf, "%d\n", val); -} - -static ssize_t smm665_show_input(struct device *dev, - struct device_attribute *da, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct smm665_data *data = smm665_update_device(dev); - int adc = attr->index; - int val; - - if (IS_ERR(data)) - return PTR_ERR(data); - - val = smm665_convert(data->adc[adc], adc); - return sysfs_emit(buf, "%d\n", val); -} - -#define SMM665_SHOW(what) \ -static ssize_t smm665_show_##what(struct device *dev, \ - struct device_attribute *da, char *buf) \ -{ \ - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ - const int val = smm665_get_##what(dev, attr->index); \ - return snprintf(buf, PAGE_SIZE, "%d\n", val); \ -} - -SMM665_SHOW(min); -SMM665_SHOW(max); -SMM665_SHOW(lcrit); -SMM665_SHOW(crit); - -/* - * These macros are used below in constructing device attribute objects - * for use with sysfs_create_group() to make a sysfs device file - * for each register. - */ - -#define SMM665_ATTR(name, type, cmd_idx) \ - static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \ - smm665_show_##type, NULL, cmd_idx) - -/* Construct a sensor_device_attribute structure for each register */ - -/* Input voltages */ -SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V); -SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD); -SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A); -SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B); -SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C); -SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D); -SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E); -SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F); -SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1); -SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2); - -/* Input voltages min */ -SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V); -SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD); -SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A); -SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B); -SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C); -SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D); -SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E); -SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F); -SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1); -SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2); - -/* Input voltages max */ -SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V); -SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD); -SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A); -SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B); -SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C); -SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D); -SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E); -SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F); -SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1); -SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2); - -/* Input voltages lcrit */ -SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V); -SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD); -SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A); -SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B); -SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C); -SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D); -SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E); -SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F); -SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1); -SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2); - -/* Input voltages crit */ -SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V); -SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD); -SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A); -SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B); -SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C); -SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D); -SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E); -SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F); -SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1); -SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2); - -/* critical alarms */ -SMM665_ATTR(in1, crit_alarm, SMM665_FAULT_12V); -SMM665_ATTR(in2, crit_alarm, SMM665_FAULT_VDD); -SMM665_ATTR(in3, crit_alarm, SMM665_FAULT_A); -SMM665_ATTR(in4, crit_alarm, SMM665_FAULT_B); -SMM665_ATTR(in5, crit_alarm, SMM665_FAULT_C); -SMM665_ATTR(in6, crit_alarm, SMM665_FAULT_D); -SMM665_ATTR(in7, crit_alarm, SMM665_FAULT_E); -SMM665_ATTR(in8, crit_alarm, SMM665_FAULT_F); -SMM665_ATTR(in9, crit_alarm, SMM665_FAULT_AIN1); -SMM665_ATTR(in10, crit_alarm, SMM665_FAULT_AIN2); - -/* Temperature */ -SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP); -SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP); -SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP); -SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP); -SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP); -SMM665_ATTR(temp1, crit_alarm, SMM665_FAULT_TEMP); - -/* - * Finally, construct an array of pointers to members of the above objects, - * as required for sysfs_create_group() - */ -static struct attribute *smm665_attrs[] = { - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in1_min.dev_attr.attr, - &sensor_dev_attr_in1_max.dev_attr.attr, - &sensor_dev_attr_in1_lcrit.dev_attr.attr, - &sensor_dev_attr_in1_crit.dev_attr.attr, - &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in2_min.dev_attr.attr, - &sensor_dev_attr_in2_max.dev_attr.attr, - &sensor_dev_attr_in2_lcrit.dev_attr.attr, - &sensor_dev_attr_in2_crit.dev_attr.attr, - &sensor_dev_attr_in2_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in3_min.dev_attr.attr, - &sensor_dev_attr_in3_max.dev_attr.attr, - &sensor_dev_attr_in3_lcrit.dev_attr.attr, - &sensor_dev_attr_in3_crit.dev_attr.attr, - &sensor_dev_attr_in3_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in4_min.dev_attr.attr, - &sensor_dev_attr_in4_max.dev_attr.attr, - &sensor_dev_attr_in4_lcrit.dev_attr.attr, - &sensor_dev_attr_in4_crit.dev_attr.attr, - &sensor_dev_attr_in4_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in5_min.dev_attr.attr, - &sensor_dev_attr_in5_max.dev_attr.attr, - &sensor_dev_attr_in5_lcrit.dev_attr.attr, - &sensor_dev_attr_in5_crit.dev_attr.attr, - &sensor_dev_attr_in5_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in6_min.dev_attr.attr, - &sensor_dev_attr_in6_max.dev_attr.attr, - &sensor_dev_attr_in6_lcrit.dev_attr.attr, - &sensor_dev_attr_in6_crit.dev_attr.attr, - &sensor_dev_attr_in6_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in7_input.dev_attr.attr, - &sensor_dev_attr_in7_min.dev_attr.attr, - &sensor_dev_attr_in7_max.dev_attr.attr, - &sensor_dev_attr_in7_lcrit.dev_attr.attr, - &sensor_dev_attr_in7_crit.dev_attr.attr, - &sensor_dev_attr_in7_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in8_input.dev_attr.attr, - &sensor_dev_attr_in8_min.dev_attr.attr, - &sensor_dev_attr_in8_max.dev_attr.attr, - &sensor_dev_attr_in8_lcrit.dev_attr.attr, - &sensor_dev_attr_in8_crit.dev_attr.attr, - &sensor_dev_attr_in8_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in9_input.dev_attr.attr, - &sensor_dev_attr_in9_min.dev_attr.attr, - &sensor_dev_attr_in9_max.dev_attr.attr, - &sensor_dev_attr_in9_lcrit.dev_attr.attr, - &sensor_dev_attr_in9_crit.dev_attr.attr, - &sensor_dev_attr_in9_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_in10_input.dev_attr.attr, - &sensor_dev_attr_in10_min.dev_attr.attr, - &sensor_dev_attr_in10_max.dev_attr.attr, - &sensor_dev_attr_in10_lcrit.dev_attr.attr, - &sensor_dev_attr_in10_crit.dev_attr.attr, - &sensor_dev_attr_in10_crit_alarm.dev_attr.attr, - - &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_lcrit.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - - NULL, -}; - -ATTRIBUTE_GROUPS(smm665); - -static const struct i2c_device_id smm665_id[]; - -static int smm665_probe(struct i2c_client *client) -{ - struct i2c_adapter *adapter = client->adapter; - struct smm665_data *data; - struct device *hwmon_dev; - int i, ret; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA - | I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; - - if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0) - return -ENODEV; - - data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - i2c_set_clientdata(client, data); - mutex_init(&data->update_lock); - - data->client = client; - data->type = i2c_match_id(smm665_id, client)->driver_data; - data->cmdreg = i2c_new_dummy_device(adapter, (client->addr & ~SMM665_REGMASK) - | SMM665_CMDREG_BASE); - if (IS_ERR(data->cmdreg)) - return PTR_ERR(data->cmdreg); - - switch (data->type) { - case smm465: - case smm665: - data->conversion_time = SMM665_ADC_WAIT_SMM665; - break; - case smm665c: - case smm764: - case smm766: - data->conversion_time = SMM665_ADC_WAIT_SMM766; - break; - } - - ret = -ENODEV; - if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0) - goto out_unregister; - - /* - * Read limits. - * - * Limit registers start with register SMM665_LIMIT_BASE. - * Each channel uses 8 registers, providing four limit values - * per channel. Each limit value requires two registers, with the - * high byte in the first register and the low byte in the second - * register. The first two limits are under limit values, followed - * by two over limit values. - * - * Limit register order matches the ADC register order, so we use - * ADC register defines throughout the code to index limit registers. - * - * We save the first retrieved value both as "critical" and "alarm" - * value. The second value overwrites either the critical or the - * alarm value, depending on its configuration. This ensures that both - * critical and alarm values are initialized, even if both registers are - * configured as critical or non-critical. - */ - for (i = 0; i < SMM665_NUM_ADC; i++) { - int val; - - val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8); - if (unlikely(val < 0)) - goto out_unregister; - data->critical_min_limit[i] = data->alarm_min_limit[i] - = smm665_convert(val, i); - val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2); - if (unlikely(val < 0)) - goto out_unregister; - if (smm665_is_critical(val)) - data->critical_min_limit[i] = smm665_convert(val, i); - else - data->alarm_min_limit[i] = smm665_convert(val, i); - val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4); - if (unlikely(val < 0)) - goto out_unregister; - data->critical_max_limit[i] = data->alarm_max_limit[i] - = smm665_convert(val, i); - val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6); - if (unlikely(val < 0)) - goto out_unregister; - if (smm665_is_critical(val)) - data->critical_max_limit[i] = smm665_convert(val, i); - else - data->alarm_max_limit[i] = smm665_convert(val, i); - } - - hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, - client->name, data, - smm665_groups); - if (IS_ERR(hwmon_dev)) { - ret = PTR_ERR(hwmon_dev); - goto out_unregister; - } - - return 0; - -out_unregister: - i2c_unregister_device(data->cmdreg); - return ret; -} - -static void smm665_remove(struct i2c_client *client) -{ - struct smm665_data *data = i2c_get_clientdata(client); - - i2c_unregister_device(data->cmdreg); -} - -static const struct i2c_device_id smm665_id[] = { - {"smm465", smm465}, - {"smm665", smm665}, - {"smm665c", smm665c}, - {"smm764", smm764}, - {"smm766", smm766}, - {} -}; - -MODULE_DEVICE_TABLE(i2c, smm665_id); - -/* This is the driver that will be inserted */ -static struct i2c_driver smm665_driver = { - .driver = { - .name = "smm665", - }, - .probe = smm665_probe, - .remove = smm665_remove, - .id_table = smm665_id, -}; - -module_i2c_driver(smm665_driver); - -MODULE_AUTHOR("Guenter Roeck"); -MODULE_DESCRIPTION("SMM665 driver"); -MODULE_LICENSE("GPL"); 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); - if (ret < 0) - goto unlock; - *val = tmp401_register_to_temp(regval, data->extended_range); - ret = regmap_read(regmap, TMP401_TEMP_CRIT_HYST, ®val); - 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 3cde3916ab6d..2ea9d3e9553d 100644 --- a/drivers/hwmon/tmp421.c +++ b/drivers/hwmon/tmp421.c @@ -19,8 +19,7 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/err.h> -#include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/sysfs.h> /* Addresses to scan */ @@ -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 4b79c3f4d9fe..98f2576d94c6 100644 --- a/drivers/hwmon/tmp464.c +++ b/drivers/hwmon/tmp464.c @@ -13,8 +13,7 @@ #include <linux/i2c.h> #include <linux/init.h> #include <linux/module.h> -#include <linux/mutex.h> -#include <linux/of_device.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, ®val); @@ -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], ®val); - if (err < 0) - break; - err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + 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], ®val); @@ -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], ®val); - if (err < 0) - break; - err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + 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], ®val); @@ -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 bff10f4b56e1..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 == 4) + 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,28 +720,20 @@ static int tmp51x_probe(struct i2c_client *client) if (!data) return -ENOMEM; - if (client->dev.of_node) - data->id = (enum tmp51x_ids)device_get_match_data(&client->dev); - else - data->id = i2c_match_id(tmp51x_id, client)->driver_data; + 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 8fbbb29ae11d..4cb3960d5170 100644 --- a/drivers/hwmon/tps23861.c +++ b/drivers/hwmon/tps23861.c @@ -14,7 +14,7 @@ #include <linux/hwmon.h> #include <linux/i2c.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regmap.h> #define TEMPERATURE 0x2c @@ -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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + 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 b23cff2e3d5d..e4f1bb538628 100644 --- a/drivers/hwmon/ultra45_env.c +++ b/drivers/hwmon/ultra45_env.c @@ -9,7 +9,8 @@ #include <linux/types.h> #include <linux/slab.h> #include <linux/module.h> -#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/io.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> @@ -17,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); @@ -290,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); @@ -299,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 2ac5fb96bba4..a2e350f52a9e 100644 --- a/drivers/hwmon/vexpress-hwmon.c +++ b/drivers/hwmon/vexpress-hwmon.c @@ -13,7 +13,6 @@ #include <linux/hwmon-sysfs.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/vexpress.h> @@ -73,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 37d7374896f6..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 = { @@ -855,16 +853,17 @@ static int via686a_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { u16 address, val; + int ret; if (force_addr) { address = force_addr & ~(VIA686A_EXTENT - 1); dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", address); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(dev, VIA686A_BASE_REG, address | 1)) + ret = pci_write_config_word(dev, VIA686A_BASE_REG, address | 1); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; } - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(dev, VIA686A_BASE_REG, &val)) + ret = pci_read_config_word(dev, VIA686A_BASE_REG, &val); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; address = val & ~(VIA686A_EXTENT - 1); @@ -874,8 +873,8 @@ static int via686a_pci_probe(struct pci_dev *dev, return -ENODEV; } - if (PCIBIOS_SUCCESSFUL != - pci_read_config_word(dev, VIA686A_ENABLE_REG, &val)) + ret = pci_read_config_word(dev, VIA686A_ENABLE_REG, &val); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; if (!(val & 0x0001)) { if (!force_addr) { @@ -886,9 +885,8 @@ static int via686a_pci_probe(struct pci_dev *dev, } dev_warn(&dev->dev, "Enabling sensors\n"); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(dev, VIA686A_ENABLE_REG, - val | 0x0001)) + ret = pci_write_config_word(dev, VIA686A_ENABLE_REG, val | 0x1); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; } 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 b7c6392ba673..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, @@ -971,13 +975,15 @@ static int vt8231_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { u16 address, val; + int ret; + if (force_addr) { address = force_addr & 0xff00; dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", address); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(dev, VT8231_BASE_REG, address | 1)) + ret = pci_write_config_word(dev, VT8231_BASE_REG, address | 1); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; } @@ -997,9 +1003,8 @@ static int vt8231_pci_probe(struct pci_dev *dev, if (!(val & 0x0001)) { dev_warn(&dev->dev, "enabling sensors\n"); - if (PCIBIOS_SUCCESSFUL != - pci_write_config_word(dev, VT8231_ENABLE_REG, - val | 0x0001)) + ret = pci_write_config_word(dev, VT8231_ENABLE_REG, val | 0x1); + if (ret != PCIBIOS_SUCCESSFUL) return -ENODEV; } 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 7f3615f5587c..401a28f55f93 100644 --- a/drivers/hwmon/w83773g.c +++ b/drivers/hwmon/w83773g.c @@ -12,7 +12,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> /* W83773 has 3 channels */ @@ -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[] = { |
