diff options
39 files changed, 4118 insertions, 736 deletions
diff --git a/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml b/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml index 9e77cee07dbc..3d3b139a91a2 100644 --- a/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml +++ b/Documentation/devicetree/bindings/hwmon/ntc-thermistor.yaml @@ -76,6 +76,7 @@ properties: - const: murata,ncp15wl333 - const: murata,ncp03wf104 - const: murata,ncp15xh103 + - const: samsung,1404-001221 # Deprecated "ntp," compatible strings - const: ntc,ncp15wb473 deprecated: true diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 6f0443322a36..47af97bb4ced 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -26,6 +26,7 @@ properties: - ti,ina226 - ti,ina230 - ti,ina231 + - ti,ina238 reg: maxItems: 1 @@ -35,6 +36,27 @@ properties: Shunt resistor value in micro-Ohm. $ref: /schemas/types.yaml#/definitions/uint32 + ti,shunt-gain: + description: | + Programmable gain divisor for the shunt voltage accuracy and range. This + property only applies to devices that have configurable PGA/ADCRANGE. The + gain value is used configure the gain and to convert the shunt voltage, + current and power register values when reading measurements from the + device. + + For devices that have a configurable PGA (e.g. INA209, INA219, INA220), + the gain value maps directly with the PG bits of the config register. + + For devices that have ADCRANGE configuration (e.g. INA238) a shunt-gain + value of 1 maps to ADCRANGE=1 where no gain divisor is applied to the + shunt voltage, and a value of 4 maps to ADCRANGE=0 such that a wider + voltage range is used. + + The default value is device dependent, and is defined by the reset value + of PGA/ADCRANGE in the respective configuration registers. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8] + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 791079021f1b..c451ae82d8d7 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -73,6 +73,8 @@ properties: - dallas,ds4510 # Digital Thermometer and Thermostat - dallas,ds75 + # Delta AHE-50DC Open19 power shelf fan control module + - delta,ahe50dc-fan # Delta Electronics DPS-650-AB power supply - delta,dps650ab # Delta Electronics DPS920AB 920W 54V Power Supply @@ -121,8 +123,14 @@ properties: - ibm,cffps2 # Infineon IR36021 digital POL buck controller - infineon,ir36021 + # Infineon IR38060 Voltage Regulator + - infineon,ir38060 # Infineon IR38064 Voltage Regulator - infineon,ir38064 + # Infineon IR38164 Voltage Regulator + - infineon,ir38164 + # Infineon IR38263 Voltage Regulator + - infineon,ir38263 # Infineon SLB9635 (Soft-) I2C TPM (old protocol, max 100khz) - infineon,slb9635tt # Infineon SLB9645 I2C TPM (new protocol, max 400khz) diff --git a/Documentation/hwmon/asus_wmi_ec_sensors.rst b/Documentation/hwmon/asus_wmi_ec_sensors.rst new file mode 100644 index 000000000000..1b287f229e86 --- /dev/null +++ b/Documentation/hwmon/asus_wmi_ec_sensors.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_ec_sensors +================================= + +Supported boards: + * PRIME X570-PRO, + * Pro WS X570-ACE, + * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII FORMULA, + * ROG CROSSHAIR VIII HERO, + * ROG STRIX B550-E GAMING, + * ROG STRIX B550-I GAMING, + * ROG STRIX X570-E GAMING. + +Authors: + - Eugene Shalygin <eugene.shalygin@gmail.com> + +Description: +------------ +ASUS mainboards publish hardware monitoring information via Super I/O +chip and the ACPI embedded controller (EC) registers. Some of the sensors +are only available via the EC. + +ASUS WMI interface provides a method (BREC) to read data from EC registers, +which is utilized by this driver to publish those sensor readings to the +HWMON system. The driver is aware of and reads the following sensors: + +1. Chipset (PCH) temperature +2. CPU package temperature +3. Motherboard temperature +4. Readings from the T_Sensor header +5. VRM temperature +6. CPU_Opt fan RPM +7. Chipset fan RPM +8. Readings from the "Water flow meter" header (RPM) +9. Readings from the "Water In" and "Water Out" temperature headers +10. CPU current diff --git a/Documentation/hwmon/asus_wmi_sensors.rst b/Documentation/hwmon/asus_wmi_sensors.rst new file mode 100644 index 000000000000..8f2096cf5183 --- /dev/null +++ b/Documentation/hwmon/asus_wmi_sensors.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_sensors +================================= + +Supported boards: + * PRIME X399-A, + * PRIME X470-PRO, + * ROG CROSSHAIR VI EXTREME, + * ROG CROSSHAIR VI HERO, + * ROG CROSSHAIR VI HERO (WI-FI AC), + * ROG CROSSHAIR VII HERO, + * ROG CROSSHAIR VII HERO (WI-FI), + * ROG STRIX B450-E GAMING, + * ROG STRIX B450-F GAMING, + * ROG STRIX B450-I GAMING, + * ROG STRIX X399-E GAMING, + * ROG STRIX X470-F GAMING, + * ROG STRIX X470-I GAMING, + * ROG ZENITH EXTREME, + * ROG ZENITH EXTREME ALPHA. + +Authors: + - Ed Brindley <kernel@maidavale.org> + +Description: +------------ +ASUS mainboards publish hardware monitoring information via WMI interface. + +ASUS WMI interface provides a methods to get list of sensors and values of +such, which is utilized by this driver to publish those sensor readings to the +HWMON system. + +The driver is aware of and reads the following sensors: + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * VDDP Voltage, + * 1.8V PLL Voltage, + * +12V Voltage, + * +5V Voltage, + * 3VSB Voltage, + * VBAT Voltage, + * AVCC3 Voltage, + * SB 1.05V Voltage, + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * CPU Fan RPM, + * Chassis Fan 1 RPM, + * Chassis Fan 2 RPM, + * Chassis Fan 3 RPM, + * HAMP Fan RPM, + * Water Pump RPM, + * CPU OPT RPM, + * Water Flow RPM, + * AIO Pump RPM, + * CPU Temperature, + * CPU Socket Temperature, + * Motherboard Temperature, + * Chipset Temperature, + * Tsensor 1 Temperature, + * CPU VRM Temperature, + * Water In, + * Water Out, + * CPU VRM Output Current. + +Known Issues: + * The WMI implementation in some of Asus' BIOSes is buggy. This can result in + fans stopping, fans getting stuck at max speed, or temperature readouts + getting stuck. This is not an issue with the driver, but the BIOS. The Prime + X470 Pro seems particularly bad for this. The more frequently the WMI + interface is polled the greater the potential for this to happen. Until you + have subjected your computer to an extended soak test while polling the + sensors frequently, don't leave you computer unattended. Upgrading to new + BIOS version with method version greater than or equal to two should + rectify the issue. + * A few boards report 12v voltages to be ~10v. diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst new file mode 100644 index 000000000000..d9f479984420 --- /dev/null +++ b/Documentation/hwmon/ina238.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver ina238 +==================== + +Supported chips: + + * Texas Instruments INA238 + + Prefix: 'ina238' + + Addresses: I2C 0x40 - 0x4f + + Datasheet: + https://www.ti.com/lit/gpn/ina238 + +Author: Nathan Rossi <nathan.rossi@digi.com> + +Description +----------- + +The INA238 is a current shunt, power and temperature monitor with an I2C +interface. It includes a number of programmable functions including alerts, +conversion rate, sample averaging and selectable shunt voltage accuracy. + +The shunt value in micro-ohms can be set via platform data or device tree at +compile-time or via the shunt_resistor attribute in sysfs at run-time. Please +refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings +if the device tree is used. + +Sysfs entries +------------- + +======================= ======================================================= +in0_input Shunt voltage (mV) +in0_min Minimum shunt voltage threshold (mV) +in0_min_alarm Minimum shunt voltage alarm +in0_max Maximum shunt voltage threshold (mV) +in0_max_alarm Maximum shunt voltage alarm + +in1_input Bus voltage (mV) +in1_min Minimum bus voltage threshold (mV) +in1_min_alarm Minimum shunt voltage alarm +in1_max Maximum bus voltage threshold (mV) +in1_max_alarm Maximum shunt voltage alarm + +power1_input Power measurement (uW) +power1_max Maximum power threshold (uW) +power1_max_alarm Maximum power alarm + +curr1_input Current measurement (mA) + +temp1_input Die temperature measurement (mC) +temp1_max Maximum die temperature threshold (mC) +temp1_max_alarm Maximum die temperature alarm +======================= ======================================================= diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 7046bf1870d9..df20022c741f 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -43,6 +43,8 @@ Hardware Monitoring Kernel Drivers asb100 asc7621 aspeed-pwm-tacho + asus_wmi_ec_sensors + asus_wmi_sensors bcm54140 bel-pfe bpa-rs600 @@ -76,6 +78,7 @@ Hardware Monitoring Kernel Drivers ibmpowernv ina209 ina2xx + ina238 ina3221 intel-m10-bmc-hwmon ir35221 @@ -142,6 +145,7 @@ Hardware Monitoring Kernel Drivers mlxreg-fan mp2888 mp2975 + mp5023 nct6683 nct6775 nct7802 @@ -150,6 +154,7 @@ Hardware Monitoring Kernel Drivers nsa320 ntc_thermistor nzxt-kraken2 + nzxt-smart2 occ pc87360 pc87427 diff --git a/Documentation/hwmon/ir38064.rst b/Documentation/hwmon/ir38064.rst index c455d755a267..e1148f21ea2a 100644 --- a/Documentation/hwmon/ir38064.rst +++ b/Documentation/hwmon/ir38064.rst @@ -3,14 +3,38 @@ Kernel driver ir38064 Supported chips: + * Infineon IR38060 + + Prefix: 'IR38060' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38060M-DS-v03_16-EN.pdf?fileId=5546d4625c167129015c3291ea9a4cee + * Infineon IR38064 Prefix: 'ir38064' Addresses scanned: - - Datasheet: Publicly available at the Infineon webiste + Datasheet: Publicly available at the Infineon website https://www.infineon.com/dgdl/Infineon-IR38064MTRPBF-DS-v03_07-EN.pdf?fileId=5546d462584d1d4a0158db0d9efb67ca + * Infineon IR38164 + + Prefix: 'ir38164' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38164M-DS-v02_02-EN.pdf?fileId=5546d462636cc8fb01640046efea1248 + + * Infineon ir38263 + + Prefix: 'ir38263' + Addresses scanned: - + + Datasheet: Publicly available at the Infineon website + https://www.infineon.com/dgdl/Infineon-IR38263M-DataSheet-v03_05-EN.pdf?fileId=5546d4625b62cd8a015bcf81f90a6e52 + Authors: - Maxim Sloyko <maxims@google.com> - Patrick Venture <venture@google.com> @@ -18,7 +42,7 @@ Authors: Description ----------- -IR38064 is a Single-input Voltage, Synchronous Buck Regulator, DC-DC Converter. +IR38x6x are a Single-input Voltage, Synchronous Buck Regulator, DC-DC Converter. Usage Notes ----------- diff --git a/Documentation/hwmon/mp5023.rst b/Documentation/hwmon/mp5023.rst new file mode 100644 index 000000000000..af5ab1345a91 --- /dev/null +++ b/Documentation/hwmon/mp5023.rst @@ -0,0 +1,84 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mp5023 +==================== + +Supported chips: + + * MPS MP5023 + + Prefix: 'mp5023' + + * Datasheet + + Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5023.html + +Author: + + Howard Chiu <howard.chiu@quantatw.com> + +Description +----------- + +This driver implements support for Monolithic Power Systems, Inc. (MPS) +MP5023 Hot-Swap Controller. + +Device complaint with: + +- PMBus rev 1.3 interface. + +Device supports direct format for reading input voltage, output voltage, +output current, input power and temperature. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_max** + +**in1_max_alarm** + +**in1_min** + +**in1_min_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_alarm** + +The driver provides the following attributes for output current: + +**curr1_input** + +**curr1_label** + +**curr1_alarm** + +**curr1_max** + +The driver provides the following attributes for input power: + +**power1_input** + +**power1_label** + +**power1_alarm** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_max** + +**temp1_max_alarm** + +**temp1_crit** + +**temp1_crit_alarm** diff --git a/Documentation/hwmon/nzxt-smart2.rst b/Documentation/hwmon/nzxt-smart2.rst new file mode 100644 index 000000000000..d9d1b2742665 --- /dev/null +++ b/Documentation/hwmon/nzxt-smart2.rst @@ -0,0 +1,62 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver nzxt-smart2 +========================= + +Supported devices: + +- NZXT RGB & Fan controller +- NZXT Smart Device v2 + +Description +----------- + +This driver implements monitoring and control of fans plugged into the device. +Besides typical speed monitoring and PWM duty cycle control, voltage and current +is reported for every fan. + +The device also has two connectors for RGB LEDs; support for them isn't +implemented (mainly because there is no standardized sysfs interface). + +Also, the device has a noise sensor, but the sensor seems to be completely +useless (and very imprecise), so support for it isn't implemented too. + +Usage Notes +----------- + +The device should be autodetected, and the driver should load automatically. + +If fans are plugged in/unplugged while the system is powered on, the driver +must be reloaded to detect configuration changes; otherwise, new fans can't +be controlled (`pwm*` changes will be ignored). It is necessary because the +device has a dedicated "detect fans" command, and currently, it is executed only +during initialization. Speed, voltage, current monitoring will work even without +reload. As an alternative to reloading the module, a userspace tool (like +`liquidctl`_) can be used to run "detect fans" command through hidraw interface. + +The driver coexists with userspace tools that access the device through hidraw +interface with no known issues. + +.. _liquidctl: https://github.com/liquidctl/liquidctl + +Sysfs entries +------------- + +======================= ======================================================== +fan[1-3]_input Fan speed monitoring (in rpm). +curr[1-3]_input Current supplied to the fan (in milliamperes). +in[0-2]_input Voltage supplied to the fan (in millivolts). +pwm[1-3] Controls fan speed: PWM duty cycle for PWM-controlled + fans, voltage for other fans. Voltage can be changed in + 9-12 V range, but the value of the sysfs attribute is + always in 0-255 range (1 = 9V, 255 = 12V). Setting the + attribute to 0 turns off the fan completely. +pwm[1-3]_enable 1 if the fan can be controlled by writing to the + corresponding pwm* attribute, 0 otherwise. The device + can control only the fans it detected itself, so the + attribute is read-only. +pwm[1-3]_mode Read-only, 1 for PWM-controlled fans, 0 for other fans + (or if no fan connected). +update_interval The interval at which all inputs are updated (in + milliseconds). The default is 1000ms. Minimum is 250ms. +======================= ======================================================== diff --git a/MAINTAINERS b/MAINTAINERS index 38a0a612ccda..36f192a9f204 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3013,6 +3013,20 @@ W: http://acpi4asus.sf.net F: drivers/platform/x86/asus*.c F: drivers/platform/x86/eeepc*.c +ASUS WMI HARDWARE MONITOR DRIVER +M: Ed Brindley <kernel@maidavale.org> +M: Denis Pauk <pauk.denis@gmail.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_wmi_sensors.c + +ASUS WMI EC HARDWARE MONITOR DRIVER +M: Eugene Shalygin <eugene.shalygin@gmail.com> +M: Denis Pauk <pauk.denis@gmail.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_wmi_ec_sensors.c + ASUS WIRELESS RADIO CONTROL DRIVER M: João Paulo Rechi Vita <jprvita@gmail.com> L: platform-driver-x86@vger.kernel.org @@ -5440,6 +5454,12 @@ W: https://linuxtv.org T: git git://linuxtv.org/media_tree.git F: drivers/media/platform/sti/delta +DELTA AHE-50DC FAN CONTROL MODULE DRIVER +M: Zev Weiss <zev@bewilderbeest.net> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/pmbus/delta-ahe50dc-fan.c + DELTA DPS920AB PSU DRIVER M: Robert Marko <robert.marko@sartura.hr> L: linux-hwmon@vger.kernel.org @@ -13837,6 +13857,13 @@ S: Maintained F: Documentation/hwmon/nzxt-kraken2.rst F: drivers/hwmon/nzxt-kraken2.c +NZXT-SMART2 HARDWARE MONITORING DRIVER +M: Aleksandr Mezin <mezin.alexander@gmail.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/nzxt-smart2.rst +F: drivers/hwmon/nzxt-smart2.c + OBJAGG M: Jiri Pirko <jiri@nvidia.com> L: netdev@vger.kernel.org diff --git a/arch/x86/kernel/amd_nb.c b/arch/x86/kernel/amd_nb.c index f814d5fc333e..020c906f7934 100644 --- a/arch/x86/kernel/amd_nb.c +++ b/arch/x86/kernel/amd_nb.c @@ -19,12 +19,14 @@ #define PCI_DEVICE_ID_AMD_17H_M10H_ROOT 0x15d0 #define PCI_DEVICE_ID_AMD_17H_M30H_ROOT 0x1480 #define PCI_DEVICE_ID_AMD_17H_M60H_ROOT 0x1630 +#define PCI_DEVICE_ID_AMD_19H_M10H_ROOT 0x14a4 #define PCI_DEVICE_ID_AMD_17H_DF_F4 0x1464 #define PCI_DEVICE_ID_AMD_17H_M10H_DF_F4 0x15ec #define PCI_DEVICE_ID_AMD_17H_M30H_DF_F4 0x1494 #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F4 0x144c #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F4 0x1444 #define PCI_DEVICE_ID_AMD_19H_DF_F4 0x1654 +#define PCI_DEVICE_ID_AMD_19H_M10H_DF_F4 0x14b1 #define PCI_DEVICE_ID_AMD_19H_M40H_ROOT 0x14b5 #define PCI_DEVICE_ID_AMD_19H_M40H_DF_F4 0x167d #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F4 0x166e @@ -39,6 +41,7 @@ static const struct pci_device_id amd_root_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M10H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M30H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_ROOT) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_ROOT) }, {} }; @@ -61,6 +64,7 @@ static const struct pci_device_id amd_nb_misc_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, {} @@ -78,6 +82,7 @@ static const struct pci_device_id amd_nb_link_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F4) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F4) }, diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64bd3dfba2c4..8df25f1079ba 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1414,8 +1414,8 @@ config SENSORS_PC87427 will be called pc87427. config SENSORS_NTC_THERMISTOR - tristate "NTC thermistor support from Murata" - depends on !OF || IIO=n || IIO + tristate "NTC thermistor support" + depends on IIO depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its @@ -1513,6 +1513,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_SMART2 + tristate "NZXT RGB & Fan Controller/Smart Device v2" + depends on USB_HID + help + If you say yes here you get support for hardware monitoring for the + NZXT RGB & Fan Controller/Smart Device v2. + + This driver can also be built as a module. If so, the module + will be called nzxt-smart2. + source "drivers/hwmon/occ/Kconfig" config SENSORS_PCF8591 @@ -1872,6 +1882,18 @@ config SENSORS_INA2XX This driver can also be built as a module. If so, the module will be called ina2xx. +config SENSORS_INA238 + tristate "Texas Instruments INA238" + 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. + + This driver can also be built as a module. If so, the module + will be called ina238. + config SENSORS_INA3221 tristate "Texas Instruments INA3221 Triple Power Monitor" depends on I2C @@ -1939,6 +1961,7 @@ config SENSORS_TMP108 config SENSORS_TMP401 tristate "Texas Instruments TMP401 and compatibles" depends on I2C + select REGMAP help If you say yes here you get support for Texas Instruments TMP401, TMP411, TMP431, TMP432, and TMP435 temperature sensor chips. @@ -2215,6 +2238,30 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. +config SENSORS_ASUS_WMI + tristate "ASUS WMI X370/X470/B450/X399" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI hardware monitoring + interface found in X370/X470/B450/X399 ASUS motherboards. This driver + will provide readings of fans, voltages and temperatures through the system + firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors. + +config SENSORS_ASUS_WMI_EC + tristate "ASUS WMI B550/X570" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in B550/X570 ASUS motherboards. + This driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors_ec. + endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index baee6a8d4dd1..185f946d698b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o +obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. @@ -90,6 +92,7 @@ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o obj-$(CONFIG_SENSORS_INA209) += ina209.o 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_IT87) += it87.o @@ -157,6 +160,7 @@ 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_SMART2) += nzxt-smart2.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/adm1021.c b/drivers/hwmon/adm1021.c index 38b447c6e8cd..91ecfee243bf 100644 --- a/drivers/hwmon/adm1021.c +++ b/drivers/hwmon/adm1021.c @@ -324,7 +324,7 @@ static int adm1021_detect(struct i2c_client *client, { struct i2c_adapter *adapter = client->adapter; const char *type_name; - int conv_rate, status, config, man_id, dev_id; + 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"); @@ -349,9 +349,19 @@ static int adm1021_detect(struct i2c_client *client, if (man_id < 0 || dev_id < 0) return -ENODEV; - if (man_id == 0x4d && dev_id == 0x01) + 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) { + } else if (man_id == 0x41) { if ((dev_id & 0xF0) == 0x30) type_name = "adm1023"; else if ((dev_id & 0xF0) == 0x00) @@ -395,13 +405,18 @@ static int adm1021_detect(struct i2c_client *client, /* * LM84 Mfr ID is in a different place, - * and it has more unused bits. + * 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; diff --git a/drivers/hwmon/adm1031.c b/drivers/hwmon/adm1031.c index 257ec53ae723..ac841fa3a369 100644 --- a/drivers/hwmon/adm1031.c +++ b/drivers/hwmon/adm1031.c @@ -242,9 +242,8 @@ static int FAN_TO_REG(int reg, int div) static int AUTO_TEMP_MAX_TO_REG(int val, int reg, int pwm) { int ret; - int range = val - AUTO_TEMP_MIN_FROM_REG(reg); + int range = ((val - AUTO_TEMP_MIN_FROM_REG(reg)) * 10) / (16 - pwm); - range = ((val - AUTO_TEMP_MIN_FROM_REG(reg))*10)/(16 - pwm); ret = ((reg & 0xf8) | (range < 10000 ? 0 : range < 20000 ? 1 : diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c new file mode 100644 index 000000000000..22a1459305a7 --- /dev/null +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS B550/X570 motherboards that publish sensor + * values via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com> + * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> + * + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional Fan RPM + * - Chipset Fan RPM + * - Water Flow Fan RPM + * - CPU current + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/nls.h> +#include <linux/units.h> +#include <linux/wmi.h> + +#include <asm/unaligned.h> + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */ +/* From the ASUS DSDT source */ +#define ASUSWMI_BREC_REGISTERS_MAX 16 +#define ASUSWMI_MAX_BUF_LEN 128 +#define SENSOR_LABEL_LEN 16 + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct asus_wmi_ec_sensor_address { + u8 index; + u8 bank; + u8 size; +}; + +#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) { \ + .size = size_i, \ + .bank = bank_i, \ + .index = index_i, \ +} + +struct ec_sensor_info { + struct asus_wmi_ec_sensor_address addr; + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + .label = sensor_label, \ + .type = sensor_type, \ +} + +enum known_ec_sensor { + SENSOR_TEMP_CHIPSET, + SENSOR_TEMP_CPU, + SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, + SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_FAN_CHIPSET, + SENSOR_FAN_VRM_HS, + SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_TEMP_WATER_IN, + SENSOR_TEMP_WATER_OUT, + SENSOR_MAX +}; + +/* All known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [SENSOR_FAN_VRM_HS] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +struct asus_wmi_data { + const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1]; +}; + +/* boards with EC support */ +static struct asus_wmi_data sensors_board_PW_X570_P = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_PW_X570_A = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_R_C8H = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without chipset fan */ +static struct asus_wmi_data sensors_board_R_C8DH = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without water */ +static struct asus_wmi_data sensors_board_R_C8F = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_I_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_VRM_HS, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_X570_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = sensors, \ +} + +static const struct dmi_system_id asus_wmi_ec_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &sensors_board_RS_B550_I_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table); + +struct ec_sensor { + enum known_ec_sensor info_index; + long cached_value; +}; + +/** + * struct asus_wmi_ec_info - sensor info. + * @sensors: list of sensors. + * @read_arg: UTF-16LE string to pass to BRxx() WMI function. + * @read_buffer: decoded output from WMI result. + * @nr_sensors: number of board EC sensors. + * @nr_registers: number of EC registers to read (sensor might span more than 1 register). + * @last_updated: in jiffies. + */ +struct asus_wmi_ec_info { + struct ec_sensor sensors[SENSOR_MAX]; + char read_arg[(ASUSWMI_BREC_REGISTERS_MAX * 4 + 1) * 2]; + u8 read_buffer[ASUSWMI_BREC_REGISTERS_MAX]; + unsigned int nr_sensors; + unsigned int nr_registers; + unsigned long last_updated; +}; + +struct asus_wmi_sensors { + struct asus_wmi_ec_info ec; + /* lock access to internal cache */ + struct mutex lock; +}; + +static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec, + const enum known_ec_sensor *bsi) +{ + struct ec_sensor *s = ec->sensors; + int i; + + ec->nr_sensors = 0; + ec->nr_registers = 0; + + for (i = 0; bsi[i] != SENSOR_MAX; i++) { + s[i].info_index = bsi[i]; + ec->nr_sensors++; + ec->nr_registers += known_ec_sensors[bsi[i]].addr.size; + } + + return 0; +} + +/* + * The next four functions convert to or from BRxx string argument format. + * The format of the string is as follows: + * - The string consists of two-byte UTF-16LE characters. + * - The value of the very first byte in the string is equal to the total + * length of the next string in bytes, thus excluding the first two-byte + * character. + * - The rest of the string encodes the pairs of (bank, index) pairs, where + * both values are byte-long (0x00 to 0xFF). + * - Numbers are encoded as UTF-16LE hex values. + */ +static int asus_wmi_ec_decode_reply_buffer(const u8 *in, u32 length, u8 *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + u32 len = min_t(u32, get_unaligned_le16(in), length - 2); + + utf16s_to_utf8s((wchar_t *)(in + 2), len / 2, UTF16_LITTLE_ENDIAN, buffer, sizeof(buffer)); + + return hex2bin(out, buffer, len / 4); +} + +static void asus_wmi_ec_encode_registers(const u8 *in, u32 len, char *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + + bin2hex(buffer, in, len); + + utf8s_to_utf16s(buffer, len * 2, UTF16_LITTLE_ENDIAN, (wchar_t *)(out + 2), len * 2); + + put_unaligned_le16(len * 4, out); +} + +static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec) +{ + u8 registers[ASUSWMI_BREC_REGISTERS_MAX * 2]; + const struct ec_sensor_info *si; + int i, j, offset; + + offset = 0; + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + for (j = 0; j < si->addr.size; j++) { + registers[offset++] = si->addr.bank; + registers[offset++] = si->addr.index + j; + } + } + + asus_wmi_ec_encode_registers(registers, offset, ec->read_arg); +} + +static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret; + + /* The first byte of the BRxx() argument string has to be the string size. */ + input.length = query[0] + 2; + input.pointer = query; + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) { + ret = -EIO; + goto out_free_obj; + } + + ret = asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, obj->buffer.length, out); + +out_free_obj: + ACPI_FREE(obj); + return ret; +} + +static inline long get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + switch (si->addr.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } +} + +static void asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + u8 i_sensor; + u8 *data; + + data = ec->read_buffer; + for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) { + s = &ec->sensors[i_sensor]; + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.size; + } +} + +static long asus_wmi_ec_scale_sensor_value(long value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + case hwmon_in: + return value * MILLI; + default: + return value; + } +} + +static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec, + enum hwmon_sensor_types type, int channel) +{ + int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (known_ec_sensors[ec->sensors[i].info_index].type == type) { + if (channel == 0) + return i; + + channel--; + } + } + return -EINVAL; +} + +static int asus_wmi_ec_get_cached_value_or_update(struct asus_wmi_sensors *sensor_data, + int sensor_index, + long *value) +{ + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, ec->last_updated + HZ)) { + ret = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC, + ec->read_arg, ec->read_buffer); + if (ret) + goto unlock; + + asus_wmi_ec_update_ec_sensors(ec); + ec->last_updated = jiffies; + } + + *value = ec->sensors[sensor_index].cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ + +static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret, sidx, info_index; + long value = 0; + + sidx = asus_wmi_ec_find_sensor_index(ec, type, channel); + if (sidx < 0) + return sidx; + + ret = asus_wmi_ec_get_cached_value_or_update(sensor_data, sidx, &value); + if (ret) + return ret; + + info_index = ec->sensors[sidx].info_index; + *val = asus_wmi_ec_scale_sensor_value(value, known_ec_sensors[info_index].type); + + return ret; +} + +static int asus_wmi_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int sensor_index; + + sensor_index = asus_wmi_ec_find_sensor_index(ec, type, channel); + *str = known_ec_sensors[ec->sensors[sensor_index].info_index].label; + + return 0; +} + +static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_ec_info *ec = &sensor_data->ec; + int index; + + index = asus_wmi_ec_find_sensor_index(ec, type, channel); + + return index < 0 ? 0 : 0444; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +static const struct hwmon_ops asus_wmi_ec_hwmon_ops = { + .is_visible = asus_wmi_ec_hwmon_is_visible, + .read = asus_wmi_ec_hwmon_read, + .read_string = asus_wmi_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_ec_chip_info = { + .ops = &asus_wmi_ec_hwmon_ops, +}; + +static int asus_wmi_ec_configure_sensor_setup(struct device *dev, + const enum known_ec_sensor *bsi) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + const struct hwmon_channel_info **asus_wmi_ci; + int nr_count[hwmon_max] = {}, nr_types = 0; + const struct hwmon_chip_info *chip_info; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, ret; + + ret = asus_wmi_ec_fill_board_sensors(ec, bsi); + if (ret) + return ret; + + if (!sensor_data->ec.nr_sensors) + return -ENODEV; + + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + if (!nr_count[si->type]) + nr_types++; + nr_count[si->type]++; + } + + if (nr_count[hwmon_temp]) { + nr_count[hwmon_chip]++; + nr_types++; + } + + /* + * If we can get values for all the registers in a single query, + * the query will not change from call to call. + */ + asus_wmi_ec_make_block_read_query(ec); + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*asus_wmi_ci), GFP_KERNEL); + if (!asus_wmi_ci) + return -ENOMEM; + + asus_wmi_ec_chip_info.info = asus_wmi_ci; + chip_info = &asus_wmi_ec_chip_info; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (ret) + return ret; + + *asus_wmi_ci++ = asus_wmi_hwmon_chan++; + } + + dev_dbg(dev, "board has %d EC sensors that span %d registers", + ec->nr_sensors, ec->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_ec_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct asus_wmi_data *board_sensors; + const struct dmi_system_id *dmi_id; + const enum known_ec_sensor *bsi; + struct device *dev = &wdev->dev; + + dmi_id = dmi_first_match(asus_wmi_ec_dmi_table); + if (!dmi_id) + return -ENODEV; + + board_sensors = dmi_id->driver_data; + bsi = board_sensors->known_board_sensors; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_ec_configure_sensor_setup(dev, bsi); +} + +static const struct wmi_device_id asus_ec_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_ec_sensors", + }, + .id_table = asus_ec_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); +MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>"); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c new file mode 100644 index 000000000000..c80eee874b6c --- /dev/null +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS motherboards that provides sensor readouts via WMI + * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. + * + * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> + * + * WMI interface provides: + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - VDDP Voltage, + * - 1.8V PLL Voltage, + * - +12V Voltage, + * - +5V Voltage, + * - 3VSB Voltage, + * - VBAT Voltage, + * - AVCC3 Voltage, + * - SB 1.05V Voltage, + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - CPU Fan RPM, + * - Chassis Fan 1 RPM, + * - Chassis Fan 2 RPM, + * - Chassis Fan 3 RPM, + * - HAMP Fan RPM, + * - Water Pump RPM, + * - CPU OPT RPM, + * - Water Flow RPM, + * - AIO Pump RPM, + * - CPU Temperature, + * - CPU Socket Temperature, + * - Motherboard Temperature, + * - Chipset Temperature, + * - Tsensor 1 Temperature, + * - CPU VRM Temperature, + * - Water In, + * - Water Out, + * - CPU VRM Output Current. + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/units.h> +#include <linux/wmi.h> + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ +#define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ +#define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ +#define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ +#define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ + +#define ASUS_WMI_MAX_STR_SIZE 32 + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ +} + +static const struct dmi_system_id asus_wmi_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA"), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); + +enum asus_wmi_sensor_class { + VOLTAGE = 0x0, + TEMPERATURE_C = 0x1, + FAN_RPM = 0x2, + CURRENT = 0x3, + WATER_FLOW = 0x4, +}; + +enum asus_wmi_location { + CPU = 0x0, + CPU_SOC = 0x1, + DRAM = 0x2, + MOTHERBOARD = 0x3, + CHIPSET = 0x4, + AUX = 0x5, + VRM = 0x6, + COOLER = 0x7 +}; + +enum asus_wmi_type { + SIGNED_INT = 0x0, + UNSIGNED_INT = 0x1, + SCALED = 0x3, +}; + +enum asus_wmi_source { + SIO = 0x1, + EC = 0x2 +}; + +static enum hwmon_sensor_types asus_data_types[] = { + [VOLTAGE] = hwmon_in, + [TEMPERATURE_C] = hwmon_temp, + [FAN_RPM] = hwmon_fan, + [CURRENT] = hwmon_curr, + [WATER_FLOW] = hwmon_fan, +}; + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +/** + * struct asus_wmi_sensor_info - sensor info. + * @id: sensor id. + * @data_type: sensor class e.g. voltage, temp etc. + * @location: sensor location. + * @name: sensor name. + * @source: sensor source. + * @type: sensor type signed, unsigned etc. + * @cached_value: cached sensor value. + */ +struct asus_wmi_sensor_info { + u32 id; + int data_type; + int location; + char name[ASUS_WMI_MAX_STR_SIZE]; + int source; + int type; + long cached_value; +}; + +struct asus_wmi_wmi_info { + unsigned long source_last_updated[3]; /* in jiffies */ + int sensor_count; + + const struct asus_wmi_sensor_info **info[hwmon_max]; + struct asus_wmi_sensor_info **info_by_id; +}; + +struct asus_wmi_sensors { + struct asus_wmi_wmi_info wmi; + /* lock access to internal cache */ + struct mutex lock; +}; + +/* + * Universal method for calling WMI method + */ +static int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) +{ + struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; + acpi_status status; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, output); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * Gets the version of the ASUS sensors interface implemented + */ +static int asus_wmi_get_version(u32 *version) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *version = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +/* + * Gets the number of sensor items + */ +static int asus_wmi_get_item_count(u32 *count) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *count = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +/* + * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc + */ +static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) +{ + union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, &output); + if (err) + return err; + + s->id = index; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_PACKAGE) { + err = -EIO; + goto out_free_obj; + } + + if (obj->package.count != 5) { + err = -EIO; + goto out_free_obj; + } + + name_obj = obj->package.elements[0]; + if (name_obj.type != ACPI_TYPE_STRING) { + err = -EIO; + goto out_free_obj; + } + + strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1); + + data_type_obj = obj->package.elements[1]; + if (data_type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->data_type = data_type_obj.integer.value; + + location_obj = obj->package.elements[2]; + if (location_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->location = location_obj.integer.value; + + source_obj = obj->package.elements[3]; + if (source_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->source = source_obj.integer.value; + + type_obj = obj->package.elements[4]; + if (type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + s->type = type_obj.integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_buffer(int source) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {source, 0}; + + return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, &output); +} + +static int asus_wmi_get_sensor_value(u8 index, long *value) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *value = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) +{ + struct asus_wmi_sensor_info *sensor; + long value = 0; + int ret; + int i; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + sensor = sensor_data->wmi.info_by_id[i]; + if (sensor && sensor->source == source) { + ret = asus_wmi_get_sensor_value(sensor->id, &value); + if (ret) + return ret; + + sensor->cached_value = value; + } + } + + return 0; +} + +static int asus_wmi_scale_sensor_value(u32 value, int data_type) +{ + /* FAN_RPM and WATER_FLOW don't need scaling */ + switch (data_type) { + case VOLTAGE: + /* value in microVolts */ + return DIV_ROUND_CLOSEST(value, KILO); + case TEMPERATURE_C: + /* value in Celsius */ + return value * MILLIDEGREE_PER_DEGREE; + case CURRENT: + /* value in Amperes */ + return value * MILLI; + } + return value; +} + +static int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, + struct asus_wmi_sensors *sensor_data, + u32 *value) +{ + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { + ret = asus_wmi_update_buffer(sensor->source); + if (ret) + goto unlock; + + ret = asus_wmi_update_values_for_source(sensor->source, sensor_data); + if (ret) + goto unlock; + + sensor_data->wmi.source_last_updated[sensor->source] = jiffies; + } + + *value = sensor->cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ +static int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + const struct asus_wmi_sensor_info *sensor; + u32 value = 0; + int ret; + + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + + sensor = *(sensor_data->wmi.info[type] + channel); + + ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, &value); + if (ret) + return ret; + + *val = asus_wmi_scale_sensor_value(value, sensor->data_type); + + return ret; +} + +static int asus_wmi_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + *str = sensor->name; + + return 0; +} + +static umode_t asus_wmi_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + if (sensor) + return 0444; + + return 0; +} + +static const struct hwmon_ops asus_wmi_hwmon_ops = { + .is_visible = asus_wmi_hwmon_is_visible, + .read = asus_wmi_hwmon_read, + .read_string = asus_wmi_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_chip_info = { + .ops = &asus_wmi_hwmon_ops, + .info = NULL, +}; + +static int asus_wmi_configure_sensor_setup(struct device *dev, + struct asus_wmi_sensors *sensor_data) +{ + const struct hwmon_channel_info **ptr_asus_wmi_ci; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + int nr_count[hwmon_max] = {}, nr_types = 0; + struct asus_wmi_sensor_info *temp_sensor; + const struct hwmon_chip_info *chip_info; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, idx; + int err; + + temp_sensor = devm_kcalloc(dev, 1, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + return err; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + if (!nr_count[type]) + nr_types++; + nr_count[type]++; + break; + } + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, + sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); + if (!ptr_asus_wmi_ci) + return -ENOMEM; + + asus_wmi_chip_info.info = ptr_asus_wmi_ci; + chip_info = &asus_wmi_chip_info; + + sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, + sizeof(*sensor_data->wmi.info_by_id), + GFP_KERNEL); + + if (!sensor_data->wmi.info_by_id) + return -ENOMEM; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (err) + return err; + + *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; + + sensor_data->wmi.info[type] = devm_kcalloc(dev, + nr_count[type], + sizeof(*sensor_data->wmi.info), + GFP_KERNEL); + if (!sensor_data->wmi.info[type]) + return -ENOMEM; + } + + for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { + temp_sensor = devm_kzalloc(dev, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + continue; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + idx = --nr_count[type]; + *(sensor_data->wmi.info[type] + idx) = temp_sensor; + sensor_data->wmi.info_by_id[i] = temp_sensor; + break; + } + } + + dev_dbg(dev, "board has %d sensors", + sensor_data->wmi.sensor_count); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct device *dev = &wdev->dev; + u32 version = 0; + + if (!dmi_check_system(asus_wmi_dmi_table)) + return -ENODEV; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + if (asus_wmi_get_version(&version)) + return -ENODEV; + + if (asus_wmi_get_item_count(&sensor_data->wmi.sensor_count)) + return -ENODEV; + + if (sensor_data->wmi.sensor_count <= 0 || version < 2) { + dev_info(dev, "version: %u with %d sensors is unsupported\n", + version, sensor_data->wmi.sensor_count); + + return -ENODEV; + } + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_configure_sensor_setup(dev, sensor_data); +} + +static const struct wmi_device_id asus_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_sensors", + }, + .id_table = asus_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 5596c211f38d..d401f9acf450 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -113,12 +113,12 @@ MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)" struct smm_regs { unsigned int eax; - unsigned int ebx __packed; - unsigned int ecx __packed; - unsigned int edx __packed; - unsigned int esi __packed; - unsigned int edi __packed; -}; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int esi; + unsigned int edi; +} __packed; static const char * const temp_labels[] = { "CPU", @@ -449,13 +449,12 @@ static int i8k_get_power_status(void) * Procfs interface */ -static int -i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg) +static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { - int val = 0; - int speed, err; - unsigned char buff[16]; + struct dell_smm_data *data = PDE_DATA(file_inode(fp)); int __user *argp = (int __user *)arg; + int speed, err; + int val = 0; if (!argp) return -EINVAL; @@ -468,15 +467,19 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd val = (data->bios_version[0] << 16) | (data->bios_version[1] << 8) | data->bios_version[2]; - break; + if (copy_to_user(argp, &val, sizeof(val))) + return -EFAULT; + + return 0; case I8K_MACHINE_ID: if (restricted && !capable(CAP_SYS_ADMIN)) return -EPERM; - strscpy_pad(buff, data->bios_machineid, sizeof(buff)); - break; + if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid))) + return -EFAULT; + return 0; case I8K_FN_STATUS: val = i8k_get_fn_status(); break; @@ -513,11 +516,13 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; + mutex_lock(&data->i8k_mutex); err = i8k_set_fan(data, val, speed); if (err < 0) - return err; - - val = i8k_get_fan_status(data, val); + val = err; + else + val = i8k_get_fan_status(data, val); + mutex_unlock(&data->i8k_mutex); break; default: @@ -527,39 +532,12 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (val < 0) return val; - switch (cmd) { - case I8K_BIOS_VERSION: - if (copy_to_user(argp, &val, 4)) - return -EFAULT; - - break; - case I8K_MACHINE_ID: - if (copy_to_user(argp, buff, 16)) - return -EFAULT; - - break; - default: - if (copy_to_user(argp, &val, sizeof(int))) - return -EFAULT; - - break; - } + if (copy_to_user(argp, &val, sizeof(int))) + return -EFAULT; return 0; } -static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) -{ - struct dell_smm_data *data = PDE_DATA(file_inode(fp)); - long ret; - - mutex_lock(&data->i8k_mutex); - ret = i8k_ioctl_unlocked(fp, data, cmd, arg); - mutex_unlock(&data->i8k_mutex); - - return ret; -} - /* * Print the information for /proc/i8k. */ diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 4673d403759a..938a8b9ec70d 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -49,6 +49,7 @@ #define SIO_F81768D_ID 0x1210 /* Chipset ID */ #define SIO_F81865_ID 0x0704 /* Chipset ID */ #define SIO_F81866_ID 0x1010 /* Chipset ID */ +#define SIO_F81966_ID 0x1502 /* Chipset ID */ #define REGION_LENGTH 8 #define ADDR_REG_OFFSET 5 @@ -2672,6 +2673,7 @@ static int __init f71882fg_find(int sioaddr, struct f71882fg_sio_data *sio_data) sio_data->type = f81865f; break; case SIO_F81866_ID: + case SIO_F81966_ID: sio_data->type = f81866a; break; default: diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c new file mode 100644 index 000000000000..50eb9c5e132e --- /dev/null +++ b/drivers/hwmon/ina238.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments INA238 power monitor chip + * Datasheet: https://www.ti.com/product/ina238 + * + * Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com> + */ + +#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.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 INA238_SHUNT_VOLTAGE 0x4 +#define INA238_BUS_VOLTAGE 0x5 +#define INA238_DIE_TEMP 0x6 +#define INA238_CURRENT 0x7 +#define INA238_POWER 0x8 +#define INA238_DIAG_ALERT 0xb +#define INA238_SHUNT_OVER_VOLTAGE 0xc +#define INA238_SHUNT_UNDER_VOLTAGE 0xd +#define INA238_BUS_OVER_VOLTAGE 0xe +#define INA238_BUS_UNDER_VOLTAGE 0xf +#define INA238_TEMP_LIMIT 0x10 +#define INA238_POWER_LIMIT 0x11 +#define INA238_DEVICE_ID 0x3f + +#define INA238_CONFIG_ADCRANGE BIT(4) + +#define INA238_DIAG_ALERT_TMPOL BIT(7) +#define INA238_DIAG_ALERT_SHNTOL BIT(6) +#define INA238_DIAG_ALERT_SHNTUL BIT(5) +#define INA238_DIAG_ALERT_BUSOL BIT(4) +#define INA238_DIAG_ALERT_BUSUL BIT(3) +#define INA238_DIAG_ALERT_POL BIT(2) + +#define INA238_REGISTERS 0x11 + +#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */ + +/* Default configuration of device on reset. */ +#define INA238_CONFIG_DEFAULT 0 +/* 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 +/* + * 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 + * device to avoid integer limits whilst current/power accuracy is scaled + * 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: + * + * 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 + */ +#define INA238_CALIBRATION_VALUE 16384 +#define INA238_FIXED_SHUNT 20000 + +#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 */ + +static struct regmap_config ina238_regmap_config = { + .max_register = INA238_REGISTERS, + .reg_bits = 8, + .val_bits = 16, +}; + +struct ina238_data { + struct i2c_client *client; + struct mutex config_lock; + struct regmap *regmap; + u32 rshunt; + int gain; +}; + +static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) +{ + u8 data[3]; + int err; + + /* 24-bit register read */ + err = i2c_smbus_read_i2c_block_data(client, reg, 3, data); + if (err < 0) + return err; + if (err != 3) + return -EIO; + *val = (data[0] << 16) | (data[1] << 8) | data[2]; + + 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 regval; + int err; + + 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; + case hwmon_in_min: + reg = INA238_SHUNT_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTUL; + break; + default: + return -EOPNOTSUPP; + } + break; + case 1: + switch (attr) { + case hwmon_in_input: + reg = INA238_BUS_VOLTAGE; + break; + case hwmon_in_max: + reg = INA238_BUS_OVER_VOLTAGE; + break; + case hwmon_in_min: + reg = INA238_BUS_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSUL; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + err = regmap_read(data->regmap, reg, ®val); + 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: + *val = !!(regval & mask); + break; + } + + return 0; +} + +static int ina238_write_in(struct device *dev, u32 attr, int channel, + long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + 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); + + 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; + } + default: + return -EOPNOTSUPP; + } +} + +static int ina238_read_current(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_curr_input: + err = regmap_read(data->regmap, INA238_CURRENT, ®val); + if (err < 0) + return err; + + /* Signed register, fixed 1mA current lsb. result in mA */ + *val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain, + data->rshunt * 4); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_read_power(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + long long power; + int regval; + int err; + + switch (attr) { + case hwmon_power_input: + err = ina238_read_reg24(data->client, INA238_POWER, ®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); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max: + err = regmap_read(data->regmap, INA238_POWER_LIMIT, ®val); + if (err) + return err; + + /* + * 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); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_POL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_power(struct device *dev, u32 attr, 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. + */ + 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); + + return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); +} + +static int ina238_read_temp(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_temp_input: + 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; + 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; + break; + case hwmon_temp_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_TMPOL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_temp(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + if (attr != hwmon_temp_max) + return -EOPNOTSUPP; + + /* Signed, bits 15-4 of register */ + regval = (val / INA238_DIE_TEMP_LSB) << 4; + regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0; + + return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); +} + +static int ina238_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return ina238_read_in(dev, attr, channel, val); + case hwmon_curr: + return ina238_read_current(dev, attr, val); + case hwmon_power: + return ina238_read_power(dev, attr, val); + case hwmon_temp: + return ina238_read_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina238_write(struct device *dev, enum hwmon_sensor_types type, + 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; + case hwmon_power: + err = ina238_write_power(dev, attr, val); + break; + case hwmon_temp: + err = ina238_write_temp(dev, attr, val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + 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) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + return 0444; + case hwmon_in_max: + case hwmon_in_min: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_max_alarm: + return 0444; + case hwmon_power_max: + return 0644; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_max: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +#define INA238_HWMON_IN_CONFIG (HWMON_I_INPUT | \ + HWMON_I_MAX | HWMON_I_MAX_ALARM | \ + HWMON_I_MIN | HWMON_I_MIN_ALARM) + +static const struct hwmon_channel_info *ina238_info[] = { + HWMON_CHANNEL_INFO(in, + /* 0: shunt voltage */ + INA238_HWMON_IN_CONFIG, + /* 1: bus voltage */ + INA238_HWMON_IN_CONFIG), + HWMON_CHANNEL_INFO(curr, + /* 0: current through shunt */ + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 0: power */ + HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM), + HWMON_CHANNEL_INFO(temp, + /* 0: die temperature */ + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), + NULL +}; + +static const struct hwmon_ops ina238_hwmon_ops = { + .is_visible = ina238_is_visible, + .read = ina238_read, + .write = ina238_write, +}; + +static const struct hwmon_chip_info ina238_chip_info = { + .ops = &ina238_hwmon_ops, + .info = ina238_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; + int config; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->config_lock); + + data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + 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; + } + + /* 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 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); + return -ENODEV; + } + + /* Setup ADC_CONFIG register */ + ret = regmap_write(data->regmap, INA238_ADC_CONFIG, + INA238_ADC_CONFIG_DEFAULT); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + 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); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &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); + + return 0; +} + +static const struct i2c_device_id ina238_id[] = { + { "ina238", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina238_id); + +static const struct of_device_id __maybe_unused ina238_of_match[] = { + { .compatible = "ti,ina238" }, + { }, +}; +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), + }, + .probe_new = ina238_probe, + .id_table = ina238_id, +}; + +module_i2c_driver(ina238_driver); + +MODULE_AUTHOR("Nathan Rossi <nathan.rossi@digi.com>"); +MODULE_DESCRIPTION("ina238 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 4a03d010ec5a..cb347a6bd8d9 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -137,6 +137,9 @@ static const unsigned short normal_i2c[] = { #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 @@ -181,6 +184,7 @@ static struct jc42_chips jc42_chips[] = { { 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 }, { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3618a924e78e..4e239bd75b1d 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -76,26 +76,6 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); #define ZEN_CUR_TEMP_SHIFT 21 #define ZEN_CUR_TEMP_RANGE_SEL_MASK BIT(19) -#define ZEN_SVI_BASE 0x0005A000 - -/* F17h thermal registers through SMN */ -#define F17H_M01H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0xc) -#define F17H_M01H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) -#define F17H_M31H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F17H_M31H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F17H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M01H_CFACTOR_ISOC 250000 /* 0.25A / LSB */ -#define F17H_M31H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M31H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - -/* F19h thermal registers through SMN */ -#define F19H_M01_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F19H_M01_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F19H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F19H_M01H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - struct k10temp_data { struct pci_dev *pdev; void (*read_htcreg)(struct pci_dev *pdev, u32 *regval); @@ -191,6 +171,10 @@ static const char *k10temp_temp_label[] = { "Tccd6", "Tccd7", "Tccd8", + "Tccd9", + "Tccd10", + "Tccd11", + "Tccd12", }; static int k10temp_read_labels(struct device *dev, @@ -226,7 +210,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, if (*val < 0) *val = 0; break; - case 2 ... 9: /* Tccd{1-8} */ + 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); @@ -361,6 +345,10 @@ static const struct hwmon_channel_info *k10temp_info[] = { 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 }; @@ -457,6 +445,11 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->ccd_offset = 0x300; k10temp_get_ccd_support(pdev, data, 8); break; + case 0x10 ... 0x1f: + case 0xa0 ... 0xaf: + data->ccd_offset = 0x300; + k10temp_get_ccd_support(pdev, data, 12); + break; } } else { data->read_htcreg = read_htcreg_pci; @@ -497,6 +490,7 @@ static const struct pci_device_id k10temp_id_table[] = { { 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_19H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c index 868243dba1ee..1ba1e3145969 100644 --- a/drivers/hwmon/mr75203.c +++ b/drivers/hwmon/mr75203.c @@ -93,7 +93,7 @@ #define VM_CH_REQ BIT(21) #define IP_TMR 0x05 -#define POWER_DELAY_CYCLE_256 0x80 +#define POWER_DELAY_CYCLE_256 0x100 #define POWER_DELAY_CYCLE_64 0x40 #define PVT_POLL_DELAY_US 20 diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 57ce8633a725..fd3f91cb01c6 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -3154,10 +3154,8 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, if (err < 0) return err; - high = fan_from_reg16(data->target_speed[nr], - data->fan_div[nr]) + val; - low = fan_from_reg16(data->target_speed[nr], - data->fan_div[nr]) - val; + high = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) + val; + low = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) - val; if (low <= 0) low = 1; if (high < low) @@ -4995,11 +4993,13 @@ static const char * const asus_wmi_boards[] = { "ROG CROSSHAIR VIII FORMULA", "ROG CROSSHAIR VIII HERO", "ROG CROSSHAIR VIII IMPACT", + "ROG STRIX B550-A GAMING", "ROG STRIX B550-E GAMING", "ROG STRIX B550-F GAMING", "ROG STRIX B550-F GAMING (WI-FI)", "ROG STRIX B550-I GAMING", "ROG STRIX X570-F GAMING", + "ROG STRIX X570-I GAMING", "ROG STRIX Z390-E GAMING", "ROG STRIX Z490-I GAMING", "TUF GAMING B550M-PLUS", @@ -5038,7 +5038,7 @@ static int __init sensors_nct6775_init(void) board_name); if (err >= 0) { /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp)) { + if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { pr_info("Using Asus WMI to access %#x chip.\n", tmp); access = access_asuswmi; } else { diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index cf26c44f2b88..414204f5704c 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -9,18 +9,23 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/math64.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/err.h> -#include <linux/of.h> -#include <linux/of_device.h> #include <linux/fixp-arith.h> - -#include <linux/platform_data/ntc_thermistor.h> - #include <linux/iio/consumer.h> - #include <linux/hwmon.h> +enum ntc_thermistor_type { + TYPE_B57330V2103, + TYPE_B57891S0103, + TYPE_NCPXXWB473, + TYPE_NCPXXWF104, + TYPE_NCPXXWL333, + TYPE_NCPXXXH103, +}; + struct ntc_compensation { int temp_c; unsigned int ohm; @@ -40,6 +45,7 @@ enum { NTC_NCP15XH103, NTC_NCP18WB473, NTC_NCP21WB473, + NTC_SSG1404001221, NTC_LAST, }; @@ -53,6 +59,7 @@ static const struct platform_device_id ntc_thermistor_id[] = { [NTC_NCP15XH103] = { "ncp15xh103", TYPE_NCPXXXH103 }, [NTC_NCP18WB473] = { "ncp18wb473", TYPE_NCPXXWB473 }, [NTC_NCP21WB473] = { "ncp21wb473", TYPE_NCPXXWB473 }, + [NTC_SSG1404001221] = { "ssg1404-001221", TYPE_NCPXXWB473 }, [NTC_LAST] = { }, }; @@ -313,16 +320,30 @@ static const struct ntc_type ntc_type[] = { NTC_TYPE(TYPE_NCPXXXH103, ncpXXxh103), }; +/* + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required. + * + * How to setup pullup_ohm, pulldown_ohm, and connect is + * described at Documentation/hwmon/ntc_thermistor.rst + * + * pullup/down_ohm: 0 for infinite / not-connected + * + * chan: iio_channel pointer to communicate with the ADC which the + * thermistor is using for conversion of the analog values. + */ struct ntc_data { - struct ntc_thermistor_platform_data *pdata; const struct ntc_compensation *comp; int n_comp; + unsigned int pullup_uv; + unsigned int pullup_ohm; + unsigned int pulldown_ohm; + enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + struct iio_channel *chan; }; -#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO) -static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) +static int ntc_adc_iio_read(struct ntc_data *data) { - struct iio_channel *channel = pdata->chan; + struct iio_channel *channel = data->chan; int uv, ret; ret = iio_read_channel_processed_scale(channel, &uv, 1000); @@ -342,103 +363,13 @@ static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) ret = iio_convert_raw_to_processed(channel, raw, &uv, 1000); if (ret < 0) { /* Assume 12 bit ADC with vref at pullup_uv */ - uv = (pdata->pullup_uv * (s64)raw) >> 12; + uv = (data->pullup_uv * (s64)raw) >> 12; } } return uv; } -static const struct of_device_id ntc_match[] = { - { .compatible = "epcos,b57330v2103", - .data = &ntc_thermistor_id[NTC_B57330V2103]}, - { .compatible = "epcos,b57891s0103", - .data = &ntc_thermistor_id[NTC_B57891S0103] }, - { .compatible = "murata,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "murata,ncp03wf104", - .data = &ntc_thermistor_id[NTC_NCP03WF104] }, - { .compatible = "murata,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "murata,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "murata,ncp15xh103", - .data = &ntc_thermistor_id[NTC_NCP15XH103] }, - { .compatible = "murata,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "murata,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - - /* Usage of vendor name "ntc" is deprecated */ - { .compatible = "ntc,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "ntc,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "ntc,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "ntc,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "ntc,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - { }, -}; -MODULE_DEVICE_TABLE(of, ntc_match); - -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) -{ - struct iio_channel *chan; - enum iio_chan_type type; - struct device_node *np = dev->of_node; - struct ntc_thermistor_platform_data *pdata; - int ret; - - if (!np) - return NULL; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - chan = devm_iio_channel_get(dev, NULL); - if (IS_ERR(chan)) - return ERR_CAST(chan); - - ret = iio_get_channel_type(chan, &type); - if (ret < 0) - return ERR_PTR(ret); - - if (type != IIO_VOLTAGE) - return ERR_PTR(-EINVAL); - - if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uv)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm)) - return ERR_PTR(-ENODEV); - - if (of_find_property(np, "connected-positive", NULL)) - pdata->connect = NTC_CONNECTED_POSITIVE; - else /* status change should be possible if not always on. */ - pdata->connect = NTC_CONNECTED_GROUND; - - pdata->chan = chan; - pdata->read_uv = ntc_adc_iio_read; - - return pdata; -} -#else -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) -{ - return NULL; -} - -#define ntc_match NULL - -#endif - static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { if (divisor == 0 && dividend == 0) @@ -450,24 +381,23 @@ static inline u64 div64_u64_safe(u64 dividend, u64 divisor) static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv) { - struct ntc_thermistor_platform_data *pdata = data->pdata; - u32 puv = pdata->pullup_uv; + u32 puv = data->pullup_uv; u64 n, puo, pdo; - puo = pdata->pullup_ohm; - pdo = pdata->pulldown_ohm; + puo = data->pullup_ohm; + pdo = data->pulldown_ohm; if (uv == 0) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? INT_MAX : 0; if (uv >= puv) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? 0 : INT_MAX; - if (pdata->connect == NTC_CONNECTED_POSITIVE && puo == 0) + if (data->connect == NTC_CONNECTED_POSITIVE && puo == 0) n = div_u64(pdo * (puv - uv), uv); - else if (pdata->connect == NTC_CONNECTED_GROUND && pdo == 0) + else if (data->connect == NTC_CONNECTED_GROUND && pdo == 0) n = div_u64(puo * uv, puv - uv); - else if (pdata->connect == NTC_CONNECTED_POSITIVE) + else if (data->connect == NTC_CONNECTED_POSITIVE) n = div64_u64_safe(pdo * puo * (puv - uv), puo * uv - pdo * (puv - uv)); else @@ -567,16 +497,10 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) { int read_uv; - if (data->pdata->read_ohm) - return data->pdata->read_ohm(); - - if (data->pdata->read_uv) { - read_uv = data->pdata->read_uv(data->pdata); - if (read_uv < 0) - return read_uv; - return get_ohm_of_thermistor(data, read_uv); - } - return -EINVAL; + read_uv = ntc_adc_iio_read(data); + if (read_uv < 0) + return read_uv; + return get_ohm_of_thermistor(data, read_uv); } static int ntc_read(struct device *dev, enum hwmon_sensor_types type, @@ -638,58 +562,74 @@ static const struct hwmon_chip_info ntc_chip_info = { .info = ntc_info, }; -static int ntc_thermistor_probe(struct platform_device *pdev) +static int ntc_thermistor_parse_props(struct device *dev, + struct ntc_data *data) { - struct device *dev = &pdev->dev; - const struct of_device_id *of_id = - of_match_device(of_match_ptr(ntc_match), dev); - const struct platform_device_id *pdev_id; - struct ntc_thermistor_platform_data *pdata; - struct device *hwmon_dev; - struct ntc_data *data; + struct iio_channel *chan; + enum iio_chan_type type; + int ret; - pdata = ntc_thermistor_parse_dt(dev); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - else if (pdata == NULL) - pdata = dev_get_platdata(dev); + chan = devm_iio_channel_get(dev, NULL); + if (IS_ERR(chan)) + return PTR_ERR(chan); - if (!pdata) { - dev_err(dev, "No platform init data supplied.\n"); - return -ENODEV; - } + ret = iio_get_channel_type(chan, &type); + if (ret < 0) + return ret; - /* Either one of the two is required. */ - if (!pdata->read_uv && !pdata->read_ohm) { - dev_err(dev, - "Both read_uv and read_ohm missing. Need either one of the two.\n"); + if (type != IIO_VOLTAGE) return -EINVAL; - } - if (pdata->read_uv && pdata->read_ohm) { - dev_warn(dev, - "Only one of read_uv and read_ohm is needed; ignoring read_uv.\n"); - pdata->read_uv = NULL; - } + ret = device_property_read_u32(dev, "pullup-uv", &data->pullup_uv); + if (ret) + return dev_err_probe(dev, ret, "pullup-uv not specified\n"); - if (pdata->read_uv && (pdata->pullup_uv == 0 || - (pdata->pullup_ohm == 0 && pdata->connect == - NTC_CONNECTED_GROUND) || - (pdata->pulldown_ohm == 0 && pdata->connect == - NTC_CONNECTED_POSITIVE) || - (pdata->connect != NTC_CONNECTED_POSITIVE && - pdata->connect != NTC_CONNECTED_GROUND))) { - dev_err(dev, "Required data to use read_uv not supplied.\n"); - return -EINVAL; - } + ret = device_property_read_u32(dev, "pullup-ohm", &data->pullup_ohm); + if (ret) + return dev_err_probe(dev, ret, "pullup-ohm not specified\n"); + + ret = device_property_read_u32(dev, "pulldown-ohm", &data->pulldown_ohm); + if (ret) + return dev_err_probe(dev, ret, "pulldown-ohm not specified\n"); + + if (device_property_read_bool(dev, "connected-positive")) + data->connect = NTC_CONNECTED_POSITIVE; + else /* status change should be possible if not always on. */ + data->connect = NTC_CONNECTED_GROUND; + + data->chan = chan; + + return 0; +} - data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL); +static int ntc_thermistor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct platform_device_id *pdev_id; + struct device *hwmon_dev; + struct ntc_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + ret = ntc_thermistor_parse_props(dev, data); + if (ret) + return ret; + + if (data->pullup_uv == 0 || + (data->pullup_ohm == 0 && data->connect == + NTC_CONNECTED_GROUND) || + (data->pulldown_ohm == 0 && data->connect == + NTC_CONNECTED_POSITIVE) || + (data->connect != NTC_CONNECTED_POSITIVE && + data->connect != NTC_CONNECTED_GROUND)) { + dev_err(dev, "Required data to use NTC driver not supplied.\n"); + return -EINVAL; + } - data->pdata = pdata; + pdev_id = device_get_match_data(dev); if (pdev_id->driver_data >= ARRAY_SIZE(ntc_type)) { dev_err(dev, "Unknown device type: %lu(%s)\n", @@ -714,10 +654,47 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return 0; } +static const struct of_device_id ntc_match[] = { + { .compatible = "epcos,b57330v2103", + .data = &ntc_thermistor_id[NTC_B57330V2103]}, + { .compatible = "epcos,b57891s0103", + .data = &ntc_thermistor_id[NTC_B57891S0103] }, + { .compatible = "murata,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "murata,ncp03wf104", + .data = &ntc_thermistor_id[NTC_NCP03WF104] }, + { .compatible = "murata,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "murata,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "murata,ncp15xh103", + .data = &ntc_thermistor_id[NTC_NCP15XH103] }, + { .compatible = "murata,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "murata,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { .compatible = "samsung,1404-001221", + .data = &ntc_thermistor_id[NTC_SSG1404001221] }, + + /* Usage of vendor name "ntc" is deprecated */ + { .compatible = "ntc,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "ntc,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "ntc,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "ntc,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "ntc,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { }, +}; +MODULE_DEVICE_TABLE(of, ntc_match); + static struct platform_driver ntc_thermistor_driver = { .driver = { .name = "ntc-thermistor", - .of_match_table = of_match_ptr(ntc_match), + .of_match_table = ntc_match, }, .probe = ntc_thermistor_probe, .id_table = ntc_thermistor_id, diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c new file mode 100644 index 000000000000..dd892ff5a3e8 --- /dev/null +++ b/drivers/hwmon/nzxt-smart2.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Reverse-engineered NZXT RGB & Fan Controller/Smart Device v2 driver. + * + * Copyright (c) 2021 Aleksandr Mezin + */ + +#include <linux/hid.h> +#include <linux/hwmon.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +/* + * The device has only 3 fan channels/connectors. But all HID reports have + * space reserved for up to 8 channels. + */ +#define FAN_CHANNELS 3 +#define FAN_CHANNELS_MAX 8 + +#define UPDATE_INTERVAL_DEFAULT_MS 1000 + +/* These strings match labels on the device exactly */ +static const char *const fan_label[] = { + "FAN 1", + "FAN 2", + "FAN 3", +}; + +static const char *const curr_label[] = { + "FAN 1 Current", + "FAN 2 Current", + "FAN 3 Current", +}; + +static const char *const in_label[] = { + "FAN 1 Voltage", + "FAN 2 Voltage", + "FAN 3 Voltage", +}; + +enum { + INPUT_REPORT_ID_FAN_CONFIG = 0x61, + INPUT_REPORT_ID_FAN_STATUS = 0x67, +}; + +enum { + FAN_STATUS_REPORT_SPEED = 0x02, + FAN_STATUS_REPORT_VOLTAGE = 0x04, +}; + +enum { + FAN_TYPE_NONE = 0, + FAN_TYPE_DC = 1, + FAN_TYPE_PWM = 2, +}; + +struct unknown_static_data { + /* + * Some configuration data? Stays the same after fan speed changes, + * changes in fan configuration, reboots and driver reloads. + * + * The same data in multiple report types. + * + * Byte 12 seems to be the number of fan channels, but I am not sure. + */ + u8 unknown1[14]; +} __packed; + +/* + * The device sends this input report in response to "detect fans" command: + * a 2-byte output report { 0x60, 0x03 }. + */ +struct fan_config_report { + /* report_id should be INPUT_REPORT_ID_FAN_CONFIG = 0x61 */ + u8 report_id; + /* Always 0x03 */ + u8 magic; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; +} __packed; + +/* + * The device sends these reports at a fixed interval (update interval) - + * one report with type = FAN_STATUS_REPORT_SPEED, and one report with type = + * FAN_STATUS_REPORT_VOLTAGE per update interval. + */ +struct fan_status_report { + /* report_id should be INPUT_REPORT_ID_STATUS = 0x67 */ + u8 report_id; + /* FAN_STATUS_REPORT_SPEED = 0x02 or FAN_STATUS_REPORT_VOLTAGE = 0x04 */ + u8 type; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; + + union { + /* When type == FAN_STATUS_REPORT_SPEED */ + struct { + /* + * Fan speed, in RPM. Zero for channels without fans + * connected. + */ + __le16 fan_rpm[FAN_CHANNELS_MAX]; + /* + * Fan duty cycle, in percent. Non-zero even for + * channels without fans connected. + */ + u8 duty_percent[FAN_CHANNELS_MAX]; + /* + * Exactly the same values as duty_percent[], non-zero + * for disconnected fans too. + */ + u8 duty_percent_dup[FAN_CHANNELS_MAX]; + /* "Case Noise" in db */ + u8 noise_db; + } __packed fan_speed; + /* When type == FAN_STATUS_REPORT_VOLTAGE */ + struct { + /* + * Voltage, in millivolts. Non-zero even when fan is + * not connected. + */ + __le16 fan_in[FAN_CHANNELS_MAX]; + /* + * Current, in milliamperes. Near-zero when + * disconnected. + */ + __le16 fan_current[FAN_CHANNELS_MAX]; + } __packed fan_voltage; + } __packed; +} __packed; + +#define OUTPUT_REPORT_SIZE 64 + +enum { + OUTPUT_REPORT_ID_INIT_COMMAND = 0x60, + OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62, +}; + +enum { + INIT_COMMAND_SET_UPDATE_INTERVAL = 0x02, + INIT_COMMAND_DETECT_FANS = 0x03, +}; + +/* + * This output report sets pwm duty cycle/target fan speed for one or more + * channels. + */ +struct set_fan_speed_report { + /* report_id should be OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62 */ + u8 report_id; + /* Should be 0x01 */ + u8 magic; + /* To change fan speed on i-th channel, set i-th bit here */ + u8 channel_bit_mask; + /* + * Fan duty cycle/target speed in percent. For voltage-controlled fans, + * the minimal voltage (duty_percent = 1) is about 9V. + * Setting duty_percent to 0 (if the channel is selected in + * channel_bit_mask) turns off the fan completely (regardless of the + * control mode). + */ + u8 duty_percent[FAN_CHANNELS_MAX]; +} __packed; + +struct drvdata { + struct hid_device *hid; + struct device *hwmon; + + u8 fan_duty_percent[FAN_CHANNELS]; + u16 fan_rpm[FAN_CHANNELS]; + bool pwm_status_received; + + u16 fan_in[FAN_CHANNELS]; + u16 fan_curr[FAN_CHANNELS]; + bool voltage_status_received; + + u8 fan_type[FAN_CHANNELS]; + bool fan_config_received; + + /* + * wq is used to wait for *_received flags to become true. + * All accesses to *_received flags and fan_* arrays are performed with + * wq.lock held. + */ + wait_queue_head_t wq; + /* + * mutex is used to: + * 1) Prevent concurrent conflicting changes to update interval and pwm + * values (after sending an output hid report, the corresponding field + * in drvdata must be updated, and only then new output reports can be + * sent). + * 2) Synchronize access to output_buffer (well, the buffer is here, + * because synchronization is necessary anyway - so why not get rid of + * a kmalloc?). + */ + struct mutex mutex; + long update_interval; + u8 output_buffer[OUTPUT_REPORT_SIZE]; +}; + +static long scale_pwm_value(long val, long orig_max, long new_max) +{ + if (val <= 0) + return 0; + + /* + * Positive values should not become zero: 0 completely turns off the + * fan. + */ + return max(1L, DIV_ROUND_CLOSEST(min(val, orig_max) * new_max, orig_max)); +} + +static void handle_fan_config_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_config_report *report = data; + int i; + + if (size < sizeof(struct fan_config_report)) + return; + + if (report->magic != 0x03) + return; + + spin_lock(&drvdata->wq.lock); + + for (i = 0; i < FAN_CHANNELS; i++) + drvdata->fan_type[i] = report->fan_type[i]; + + drvdata->fan_config_received = true; + wake_up_all_locked(&drvdata->wq); + spin_unlock(&drvdata->wq.lock); +} + +static void handle_fan_status_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_status_report *report = data; + int i; + + if (size < sizeof(struct fan_status_report)) + return; + + spin_lock(&drvdata->wq.lock); + + /* + * The device sends INPUT_REPORT_ID_FAN_CONFIG = 0x61 report in response + * to "detect fans" command. Only accept other data after getting 0x61, + * to make sure that fan detection is complete. In particular, fan + * detection resets pwm values. + */ + if (!drvdata->fan_config_received) { + spin_unlock(&drvdata->wq.lock); + return; + } + + for (i = 0; i < FAN_CHANNELS; i++) { + if (drvdata->fan_type[i] == report->fan_type[i]) + continue; + + /* + * This should not happen (if my expectations about the device + * are correct). + * + * Even if the userspace sends fan detect command through + * hidraw, fan config report should arrive first. + */ + hid_warn_once(drvdata->hid, + "Fan %d type changed unexpectedly from %d to %d", + i, drvdata->fan_type[i], report->fan_type[i]); + drvdata->fan_type[i] = report->fan_type[i]; + } + + switch (report->type) { + case FAN_STATUS_REPORT_SPEED: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_rpm[i] = + get_unaligned_le16(&report->fan_speed.fan_rpm[i]); + drvdata->fan_duty_percent[i] = + report->fan_speed.duty_percent[i]; + } + + drvdata->pwm_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + + case FAN_STATUS_REPORT_VOLTAGE: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_in[i] = + get_unaligned_le16(&report->fan_voltage.fan_in[i]); + drvdata->fan_curr[i] = + get_unaligned_le16(&report->fan_voltage.fan_current[i]); + } + + drvdata->voltage_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + } + + spin_unlock(&drvdata->wq.lock); +} + +static umode_t nzxt_smart2_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + return 0644; + + default: + return 0444; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + + default: + return 0444; + } + + default: + return 0444; + } +} + +static int nzxt_smart2_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int res = -EINVAL; + + if (type == hwmon_chip) { + switch (attr) { + case hwmon_chip_update_interval: + *val = drvdata->update_interval; + return 0; + + default: + return -EINVAL; + } + } + + spin_lock_irq(&drvdata->wq.lock); + + switch (type) { + case hwmon_pwm: + /* + * fancontrol: + * 1) remembers pwm* values when it starts + * 2) needs pwm*_enable to be 1 on controlled fans + * So make sure we have correct data before allowing pwm* reads. + * Returning errors for pwm of fan speed read can even cause + * fancontrol to shut down. So the wait is unavoidable. + */ + switch (attr) { + case hwmon_pwm_enable: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + break; + + case hwmon_pwm_mode: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] == FAN_TYPE_PWM; + break; + + case hwmon_pwm_input: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = scale_pwm_value(drvdata->fan_duty_percent[channel], + 100, 255); + break; + } + break; + + case hwmon_fan: + /* + * It's not strictly necessary to wait for *_received in the + * remaining cases (fancontrol doesn't care about them). But I'm + * doing it to have consistent behavior. + */ + if (attr == hwmon_fan_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_rpm[channel]; + } + break; + + case hwmon_in: + if (attr == hwmon_in_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_in[channel]; + } + break; + + case hwmon_curr: + if (attr == hwmon_curr_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_curr[channel]; + } + break; + + default: + break; + } + +unlock: + spin_unlock_irq(&drvdata->wq.lock); + return res; +} + +static int send_output_report(struct drvdata *drvdata, const void *data, + size_t data_size) +{ + int ret; + + if (data_size > sizeof(drvdata->output_buffer)) + return -EINVAL; + + memcpy(drvdata->output_buffer, data, data_size); + + if (data_size < sizeof(drvdata->output_buffer)) + memset(drvdata->output_buffer + data_size, 0, + sizeof(drvdata->output_buffer) - data_size); + + ret = hid_hw_output_report(drvdata->hid, drvdata->output_buffer, + sizeof(drvdata->output_buffer)); + return ret < 0 ? ret : 0; +} + +static int set_pwm(struct drvdata *drvdata, int channel, long val) +{ + int ret; + u8 duty_percent = scale_pwm_value(val, 255, 100); + + struct set_fan_speed_report report = { + .report_id = OUTPUT_REPORT_ID_SET_FAN_SPEED, + .magic = 1, + .channel_bit_mask = 1 << channel + }; + + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + report.duty_percent[channel] = duty_percent; + ret = send_output_report(drvdata, &report, sizeof(report)); + if (ret) + goto unlock; + + /* + * pwmconfig and fancontrol scripts expect pwm writes to take effect + * immediately (i. e. read from pwm* sysfs should return the value + * written into it). The device seems to always accept pwm values - even + * when there is no fan connected - so update pwm status without waiting + * for a report, to make pwmconfig and fancontrol happy. Worst case - + * if the device didn't accept new pwm value for some reason (never seen + * this in practice) - it will be reported incorrectly only until next + * update. This avoids "fan stuck" messages from pwmconfig, and + * fancontrol setting fan speed to 100% during shutdown. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_duty_percent[channel] = duty_percent; + spin_unlock_bh(&drvdata->wq.lock); + +unlock: + mutex_unlock(&drvdata->mutex); + return ret; +} + +/* + * Workaround for fancontrol/pwmconfig trying to write to pwm*_enable even if it + * already is 1 and read-only. Otherwise, fancontrol won't restore pwm on + * shutdown properly. + */ +static int set_pwm_enable(struct drvdata *drvdata, int channel, long val) +{ + long expected_val; + int res; + + spin_lock_irq(&drvdata->wq.lock); + + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) { + spin_unlock_irq(&drvdata->wq.lock); + return res; + } + + expected_val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + + spin_unlock_irq(&drvdata->wq.lock); + + return (val == expected_val) ? 0 : -EOPNOTSUPP; +} + +/* + * Control byte | Actual update interval in seconds + * 0xff | 65.5 + * 0xf7 | 63.46 + * 0x7f | 32.74 + * 0x3f | 16.36 + * 0x1f | 8.17 + * 0x0f | 4.07 + * 0x07 | 2.02 + * 0x03 | 1.00 + * 0x02 | 0.744 + * 0x01 | 0.488 + * 0x00 | 0.25 + */ +static u8 update_interval_to_control_byte(long interval) +{ + if (interval <= 250) + return 0; + + return clamp_val(1 + DIV_ROUND_CLOSEST(interval - 488, 256), 0, 255); +} + +static long control_byte_to_update_interval(u8 control_byte) +{ + if (control_byte == 0) + return 250; + + return 488 + (control_byte - 1) * 256; +} + +static int set_update_interval(struct drvdata *drvdata, long val) +{ + u8 control = update_interval_to_control_byte(val); + u8 report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_SET_UPDATE_INTERVAL, + 0x01, + 0xe8, + control, + 0x01, + 0xe8, + control, + }; + int ret; + + ret = send_output_report(drvdata, report, sizeof(report)); + if (ret) + return ret; + + drvdata->update_interval = control_byte_to_update_interval(control); + return 0; +} + +static int init_device(struct drvdata *drvdata, long update_interval) +{ + int ret; + static const u8 detect_fans_report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_DETECT_FANS, + }; + + ret = send_output_report(drvdata, detect_fans_report, + sizeof(detect_fans_report)); + if (ret) + return ret; + + return set_update_interval(drvdata, update_interval); +} + +static int nzxt_smart2_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + return set_pwm_enable(drvdata, channel, val); + + case hwmon_pwm_input: + return set_pwm(drvdata, channel, val); + + default: + return -EINVAL; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + ret = set_update_interval(drvdata, val); + + mutex_unlock(&drvdata->mutex); + return ret; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int nzxt_smart2_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_fan: + *str = fan_label[channel]; + return 0; + case hwmon_curr: + *str = curr_label[channel]; + return 0; + case hwmon_in: + *str = in_label[channel]; + return 0; + default: + return -EINVAL; + } +} + +static const struct hwmon_ops nzxt_smart2_hwmon_ops = { + .is_visible = nzxt_smart2_hwmon_is_visible, + .read = nzxt_smart2_hwmon_read, + .read_string = nzxt_smart2_hwmon_read_string, + .write = nzxt_smart2_hwmon_write, +}; + +static const struct hwmon_channel_info *nzxt_smart2_channel_info[] = { + HWMON_CHANNEL_INFO(fan, 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_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(in, 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(chip, HWMON_C_UPDATE_INTERVAL), + NULL +}; + +static const struct hwmon_chip_info nzxt_smart2_chip_info = { + .ops = &nzxt_smart2_hwmon_ops, + .info = nzxt_smart2_channel_info, +}; + +static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + u8 report_id = *data; + + switch (report_id) { + case INPUT_REPORT_ID_FAN_CONFIG: + handle_fan_config_report(drvdata, data, size); + break; + + case INPUT_REPORT_ID_FAN_STATUS: + handle_fan_status_report(drvdata, data, size); + break; + } + + return 0; +} + +static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + /* + * Userspace is still frozen (so no concurrent sysfs attribute access + * is possible), but raw_event can already be called concurrently. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_config_received = false; + drvdata->pwm_status_received = false; + drvdata->voltage_status_received = false; + spin_unlock_bh(&drvdata->wq.lock); + + return init_device(drvdata, drvdata->update_interval); +} + +static int nzxt_smart2_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(struct drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->hid = hdev; + hid_set_drvdata(hdev, drvdata); + + init_waitqueue_head(&drvdata->wq); + + mutex_init(&drvdata->mutex); + devm_add_action(&hdev->dev, (void (*)(void *))mutex_destroy, + &drvdata->mutex); + + ret = hid_parse(hdev); + if (ret) + return ret; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + return ret; + + ret = hid_hw_open(hdev); + if (ret) + goto out_hw_stop; + + hid_device_io_start(hdev); + + init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); + + drvdata->hwmon = + hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, + &nzxt_smart2_chip_info, NULL); + if (IS_ERR(drvdata->hwmon)) { + ret = PTR_ERR(drvdata->hwmon); + goto out_hw_close; + } + + return 0; + +out_hw_close: + hid_hw_close(hdev); + +out_hw_stop: + hid_hw_stop(hdev); + return ret; +} + +static void nzxt_smart2_hid_remove(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + hwmon_device_unregister(drvdata->hwmon); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id nzxt_smart2_hid_id_table[] = { + { HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */ + {}, +}; + +static struct hid_driver nzxt_smart2_hid_driver = { + .name = "nzxt-smart2", + .id_table = nzxt_smart2_hid_id_table, + .probe = nzxt_smart2_hid_probe, + .remove = nzxt_smart2_hid_remove, + .raw_event = nzxt_smart2_hid_raw_event, +#ifdef CONFIG_PM + .reset_resume = nzxt_smart2_hid_reset_resume, +#endif +}; + +static int __init nzxt_smart2_init(void) +{ + return hid_register_driver(&nzxt_smart2_hid_driver); +} + +static void __exit nzxt_smart2_exit(void) +{ + hid_unregister_driver(&nzxt_smart2_hid_driver); +} + +MODULE_DEVICE_TABLE(hid, nzxt_smart2_hid_id_table); +MODULE_AUTHOR("Aleksandr Mezin <mezin.alexander@gmail.com>"); +MODULE_DESCRIPTION("Driver for NZXT RGB & Fan Controller/Smart Device V2"); +MODULE_LICENSE("GPL"); + +/* + * With module_init()/module_hid_driver() and the driver built into the kernel: + * + * Driver 'nzxt_smart2' was unable to register with bus_type 'hid' because the + * bus was not initialized. + */ +late_initcall(nzxt_smart2_init); +module_exit(nzxt_smart2_exit); diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ffb609cee3a4..41f6cbf96d3b 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -66,6 +66,16 @@ config SENSORS_BPA_RS600 This driver can also be built as a module. If so, the module will be called bpa-rs600. +config SENSORS_DELTA_AHE50DC_FAN + tristate "Delta AHE-50DC fan control module" + help + If you say yes here you get hardware monitoring support for + the integrated fan control module of the Delta AHE-50DC + Open19 power shelf. + + This driver can also be built as a module. If so, the module + will be called delta-ahe50dc-fan. + config SENSORS_FSP_3Y tristate "FSP/3Y-Power power supplies" help @@ -123,14 +133,20 @@ config SENSORS_IR36021 be called ir36021. config SENSORS_IR38064 - tristate "Infineon IR38064" + tristate "Infineon IR38064 and compatibles" help If you say yes here you get hardware monitoring support for Infineon - IR38064. + IR38060, IR38064, IR38164 and IR38263. This driver can also be built as a module. If so, the module will be called ir38064. +config SENSORS_IR38064_REGULATOR + bool "Regulator support for IR38064 and compatibles" + depends on SENSORS_IR38064 && REGULATOR + help + Uses the IR38064 or compatible as regulator. + config SENSORS_IRPS5401 tristate "Infineon IRPS5401" help @@ -276,6 +292,15 @@ config SENSORS_MP2975 This driver can also be built as a module. If so, the module will be called mp2975. +config SENSORS_MP5023 + tristate "MPS MP5023" + help + If you say yes here you get hardware monitoring support for MPS + MP5023. + + This driver can also be built as a module. If so, the module will + be called mp5023. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 0ed4d596a948..e5935f70c9e0 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.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 @@ -31,6 +32,7 @@ obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +obj-$(CONFIG_SENSORS_MP5023) += mp5023.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o diff --git a/drivers/hwmon/pmbus/delta-ahe50dc-fan.c b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c new file mode 100644 index 000000000000..40dffd9c4cbf --- /dev/null +++ b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Delta AHE-50DC power shelf fan control module driver + * + * Copyright 2021 Zev Weiss <zev@bewilderbeest.net> + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> + +#include "pmbus.h" + +#define AHE50DC_PMBUS_READ_TEMP4 0xd0 + +static int ahe50dc_fan_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + /* temp1 in (virtual) page 1 is remapped to mfr-specific temp4 */ + if (page == 1) { + if (reg == PMBUS_READ_TEMPERATURE_1) + return i2c_smbus_read_word_data(client, AHE50DC_PMBUS_READ_TEMP4); + return -EOPNOTSUPP; + } + + /* + * There's a fairly limited set of commands this device actually + * supports, so here we block attempts to read anything else (which + * return 0xffff and would cause confusion elsewhere). + */ + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_FAN_COMMAND_1: + case PMBUS_FAN_COMMAND_2: + case PMBUS_FAN_COMMAND_3: + case PMBUS_FAN_COMMAND_4: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + case PMBUS_READ_VIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_FAN_SPEED_2: + case PMBUS_READ_FAN_SPEED_3: + case PMBUS_READ_FAN_SPEED_4: + return -ENODATA; + default: + return -EOPNOTSUPP; + } +} + +static struct pmbus_driver_info ahe50dc_fan_info = { + .pages = 2, + .format[PSC_FAN] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .func[0] = PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_VIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_FAN34 | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_STATUS_FAN34 | PMBUS_PAGE_VIRTUAL, + .func[1] = PMBUS_HAVE_TEMP | PMBUS_PAGE_VIRTUAL, + .read_word_data = ahe50dc_fan_read_word_data, +}; + +/* + * CAPABILITY returns 0xff, which appears to be this device's way indicating + * it doesn't support something (and if we enable I2C_CLIENT_PEC on seeing bit + * 7 being set it generates bad PECs, so let's not go there). + */ +static struct pmbus_platform_data ahe50dc_fan_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int ahe50dc_fan_probe(struct i2c_client *client) +{ + client->dev.platform_data = &ahe50dc_fan_data; + return pmbus_do_probe(client, &ahe50dc_fan_info); +} + +static const struct i2c_device_id ahe50dc_fan_id[] = { + { "ahe50dc_fan" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ahe50dc_fan_id); + +static const struct of_device_id __maybe_unused ahe50dc_fan_of_match[] = { + { .compatible = "delta,ahe50dc-fan" }, + { } +}; +MODULE_DEVICE_TABLE(of, ahe50dc_fan_of_match); + +static struct i2c_driver ahe50dc_fan_driver = { + .driver = { + .name = "ahe50dc_fan", + .of_match_table = of_match_ptr(ahe50dc_fan_of_match), + }, + .probe_new = ahe50dc_fan_probe, + .id_table = ahe50dc_fan_id, +}; +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); diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 1fb7f1248639..0ea7e1c18bdc 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -16,8 +16,16 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_device.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), +}; +#endif /* CONFIG_SENSORS_IR38064_REGULATOR */ + static struct pmbus_driver_info ir38064_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, @@ -33,6 +41,10 @@ static struct pmbus_driver_info ir38064_info = { | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) + .num_regulators = 1, + .reg_desc = ir38064_reg_desc, +#endif }; static int ir38064_probe(struct i2c_client *client) @@ -41,16 +53,30 @@ static int ir38064_probe(struct i2c_client *client) } static const struct i2c_device_id ir38064_id[] = { + {"ir38060", 0}, {"ir38064", 0}, + {"ir38164", 0}, + {"ir38263", 0}, {} }; MODULE_DEVICE_TABLE(i2c, ir38064_id); +static const struct of_device_id ir38064_of_match[] = { + { .compatible = "infineon,ir38060" }, + { .compatible = "infineon,ir38064" }, + { .compatible = "infineon,ir38164" }, + { .compatible = "infineon,ir38263" }, + {} +}; + +MODULE_DEVICE_TABLE(of, ir38064_of_match); + /* This is the driver that will be inserted */ static struct i2c_driver ir38064_driver = { .driver = { .name = "ir38064", + .of_match_table = of_match_ptr(ir38064_of_match), }, .probe_new = ir38064_probe, .id_table = ir38064_id, @@ -59,6 +85,6 @@ static struct i2c_driver ir38064_driver = { module_i2c_driver(ir38064_driver); MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); -MODULE_DESCRIPTION("PMBus driver for Infineon IR38064"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and compatible chips"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/mp5023.c b/drivers/hwmon/pmbus/mp5023.c new file mode 100644 index 000000000000..791a06c3c54a --- /dev/null +++ b/drivers/hwmon/pmbus/mp5023.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MP5023 Hot-Swap Controller + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +static struct pmbus_driver_info mp5023_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] = 2, + .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, +}; + +static int mp5023_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &mp5023_info); +} + +static const struct of_device_id __maybe_unused mp5023_of_match[] = { + { .compatible = "mps,mp5023", }, + {} +}; + +MODULE_DEVICE_TABLE(of, mp5023_of_match); + +static struct i2c_driver mp5023_driver = { + .driver = { + .name = "mp5023", + .of_match_table = of_match_ptr(mp5023_of_match), + }, + .probe_new = mp5023_probe, +}; + +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); diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c index 573f53d52912..1650d3b4c26e 100644 --- a/drivers/hwmon/raspberrypi-hwmon.c +++ b/drivers/hwmon/raspberrypi-hwmon.c @@ -120,6 +120,8 @@ static int rpi_hwmon_probe(struct platform_device *pdev) data, &rpi_chip_info, NULL); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work, get_values_poll); @@ -127,10 +129,9 @@ static int rpi_hwmon_probe(struct platform_device *pdev) return ret; platform_set_drvdata(pdev, data); - if (!PTR_ERR_OR_ZERO(data->hwmon_dev)) - schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); + schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); - return PTR_ERR_OR_ZERO(data->hwmon_dev); + return 0; } static struct platform_driver rpi_hwmon_driver = { diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 3415d7a0e0fc..c19df3ade48e 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -281,9 +281,16 @@ static const struct i2c_device_id sht4x_id[] = { }; MODULE_DEVICE_TABLE(i2c, sht4x_id); +static const struct of_device_id sht4x_of_match[] = { + { .compatible = "sensirion,sht4x" }, + { } +}; +MODULE_DEVICE_TABLE(of, sht4x_of_match); + static struct i2c_driver sht4x_driver = { .driver = { .name = "sht4x", + .of_match_table = sht4x_of_match, }, .probe = sht4x_probe, .id_table = sht4x_id, diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index b31f4964f852..b86d9df7105d 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -18,17 +18,15 @@ * and thus has 16 bits registers for its value and limit instead of 8 bits. */ -#include <linux/module.h> -#include <linux/init.h> #include <linux/bitops.h> -#include <linux/slab.h> -#include <linux/jiffies.h> +#include <linux/err.h> #include <linux/i2c.h> #include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> -#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> #include <linux/mutex.h> -#include <linux/sysfs.h> +#include <linux/regmap.h> +#include <linux/slab.h> /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d, @@ -41,44 +39,19 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; * reading and writing */ #define TMP401_STATUS 0x02 -#define TMP401_CONFIG_READ 0x03 -#define TMP401_CONFIG_WRITE 0x09 -#define TMP401_CONVERSION_RATE_READ 0x04 -#define TMP401_CONVERSION_RATE_WRITE 0x0A +#define TMP401_CONFIG 0x03 +#define TMP401_CONVERSION_RATE 0x04 #define TMP401_TEMP_CRIT_HYST 0x21 #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF -static const u8 TMP401_TEMP_MSB_READ[7][2] = { - { 0x00, 0x01 }, /* temp */ - { 0x06, 0x08 }, /* low limit */ - { 0x05, 0x07 }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP401_TEMP_MSB_WRITE[7][2] = { - { 0, 0 }, /* temp (unused) */ - { 0x0C, 0x0E }, /* low limit */ - { 0x0B, 0x0D }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP432_TEMP_MSB_READ[4][3] = { +static const u8 TMP401_TEMP_MSB[7][3] = { { 0x00, 0x01, 0x23 }, /* temp */ { 0x06, 0x08, 0x16 }, /* low limit */ { 0x05, 0x07, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ -}; - -static const u8 TMP432_TEMP_MSB_WRITE[4][3] = { - { 0, 0, 0 }, /* temp - unused */ - { 0x0C, 0x0E, 0x16 }, /* low limit */ - { 0x0B, 0x0D, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ + { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ + { 0x30, 0x34, 0x00 }, /* lowest */ + { 0x32, 0xf6, 0x00 }, /* highest */ }; /* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ @@ -131,311 +104,323 @@ MODULE_DEVICE_TABLE(i2c, tmp401_id); struct tmp401_data { struct i2c_client *client; - const struct attribute_group *groups[3]; + struct regmap *regmap; struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ enum chips kind; - unsigned int update_interval; /* in milliseconds */ + bool extended_range; - /* register values */ - u8 status[4]; - u8 config; - u16 temp[7][3]; - u8 temp_crit_hyst; + /* hwmon API configuration data */ + u32 chip_channel_config[4]; + struct hwmon_channel_info chip_info; + u32 temp_channel_config[4]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; }; -/* - * Sysfs attr show / store functions - */ - -static int tmp401_register_to_temp(u16 reg, u8 config) -{ - int temp = reg; - - if (config & TMP401_CONFIG_RANGE) - temp -= 64 * 256; - - return DIV_ROUND_CLOSEST(temp * 125, 32); -} +/* regmap */ -static u16 tmp401_temp_to_register(long temp, u8 config, int zbits) +static bool tmp401_regmap_is_volatile(struct device *dev, unsigned int reg) { - if (config & TMP401_CONFIG_RANGE) { - temp = clamp_val(temp, -64000, 191000); - temp += 64000; - } else - temp = clamp_val(temp, 0, 127000); - - return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; -} - -static int tmp401_update_device_reg16(struct i2c_client *client, - struct tmp401_data *data) -{ - int i, j, val; - int num_regs = data->kind == tmp411 ? 6 : 4; - int num_sensors = data->kind == tmp432 ? 3 : 2; - - for (i = 0; i < num_sensors; i++) { /* local / r1 / r2 */ - for (j = 0; j < num_regs; j++) { /* temp / low / ... */ - u8 regaddr; - - regaddr = data->kind == tmp432 ? - TMP432_TEMP_MSB_READ[j][i] : - TMP401_TEMP_MSB_READ[j][i]; - if (j == 3) { /* crit is msb only */ - val = i2c_smbus_read_byte_data(client, regaddr); - } else { - val = i2c_smbus_read_word_swapped(client, - regaddr); - } - if (val < 0) - return val; - - data->temp[j][i] = j == 3 ? val << 8 : val; - } + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 2: /* status */ + case 0x10: /* remote temp lsb */ + case 0x15: /* local temp lsb */ + case 0x1b: /* status (tmp432) */ + case 0x23 ... 0x24: /* remote temp 2 msb / lsb */ + case 0x30 ... 0x37: /* lowest/highest temp; status (tmp432) */ + return true; + default: + return false; } - return 0; } -static struct tmp401_data *tmp401_update_device(struct device *dev) +static int tmp401_reg_read(void *context, unsigned int reg, unsigned int *val) { - struct tmp401_data *data = dev_get_drvdata(dev); + struct tmp401_data *data = context; struct i2c_client *client = data->client; - struct tmp401_data *ret = data; - int i, val; - unsigned long next_update; - - mutex_lock(&data->update_lock); + int regval; - next_update = data->last_updated + - msecs_to_jiffies(data->update_interval); - if (time_after(jiffies, next_update) || !data->valid) { - if (data->kind != tmp432) { - /* - * The driver uses the TMP432 status format internally. - * Convert status to TMP432 format for other chips. - */ - val = i2c_smbus_read_byte_data(client, TMP401_STATUS); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->status[0] = - (val & TMP401_STATUS_REMOTE_OPEN) >> 1; - data->status[1] = - ((val & TMP401_STATUS_REMOTE_LOW) >> 2) | - ((val & TMP401_STATUS_LOCAL_LOW) >> 5); - data->status[2] = - ((val & TMP401_STATUS_REMOTE_HIGH) >> 3) | - ((val & TMP401_STATUS_LOCAL_HIGH) >> 6); - data->status[3] = val & (TMP401_STATUS_LOCAL_CRIT - | TMP401_STATUS_REMOTE_CRIT); - } else { - for (i = 0; i < ARRAY_SIZE(data->status); i++) { - val = i2c_smbus_read_byte_data(client, - TMP432_STATUS_REG[i]); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->status[i] = val; - } - } - - val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->config = val; - val = tmp401_update_device_reg16(client, data); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 5: /* local temp high limit msb */ + case 6: /* local temp low limit msb */ + case 7: /* remote temp ligh limit msb */ + case 8: /* remote temp low limit msb */ + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + case 0x23: /* remote temp 2 msb */ + case 0x30: /* local temp minimum, tmp411 */ + case 0x32: /* local temp maximum, tmp411 */ + case 0x34: /* remote temp minimum, tmp411 */ + case 0xf6: /* remote temp maximum, tmp411 (really 0x36) */ + /* work around register overlap between TMP411 and TMP432 */ + if (reg == 0xf6) + reg = 0x36; + regval = i2c_smbus_read_word_swapped(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval << 8; + break; + case 0x1b: + case 0x35 ... 0x37: + if (data->kind == tmp432) { + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; } - val = i2c_smbus_read_byte_data(client, TMP401_TEMP_CRIT_HYST); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + /* simulate TMP432 status registers */ + regval = i2c_smbus_read_byte_data(client, TMP401_STATUS); + if (regval < 0) + return regval; + *val = 0; + switch (reg) { + case 0x1b: /* open / fault */ + if (regval & TMP401_STATUS_REMOTE_OPEN) + *val |= BIT(1); + break; + case 0x35: /* high limit */ + if (regval & TMP401_STATUS_LOCAL_HIGH) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_HIGH) + *val |= BIT(1); + break; + case 0x36: /* low limit */ + if (regval & TMP401_STATUS_LOCAL_LOW) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_LOW) + *val |= BIT(1); + break; + case 0x37: /* therm / crit limit */ + if (regval & TMP401_STATUS_LOCAL_CRIT) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_CRIT) + *val |= BIT(1); + break; } - data->temp_crit_hyst = val; - - data->last_updated = jiffies; - data->valid = true; + break; + default: + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; } - -abort: - mutex_unlock(&data->update_lock); - return ret; + return 0; } -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) +static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); + struct tmp401_data *data = context; + struct i2c_client *client = data->client; - return sprintf(buf, "%d\n", - tmp401_register_to_temp(data->temp[nr][index], data->config)); + switch (reg) { + case 0x05: /* local temp high limit msb */ + case 0x06: /* local temp low limit msb */ + case 0x07: /* remote temp ligh limit msb */ + case 0x08: /* remote temp low limit msb */ + reg += 6; /* adjust for register write address */ + fallthrough; + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + return i2c_smbus_write_word_swapped(client, reg, val); + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + return i2c_smbus_write_byte_data(client, reg, val >> 8); + case TMP401_CONVERSION_RATE: + case TMP401_CONFIG: + reg += 6; /* adjust for register write address */ + fallthrough; + default: + return i2c_smbus_write_byte_data(client, reg, val); + } } -static ssize_t temp_crit_hyst_show(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - temp -= data->temp_crit_hyst * 1000; - mutex_unlock(&data->update_lock); +static const struct regmap_config tmp401_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tmp401_regmap_is_volatile, + .reg_read = tmp401_reg_read, + .reg_write = tmp401_reg_write, +}; - return sprintf(buf, "%d\n", temp); -} +/* temperature conversion */ -static ssize_t status_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int tmp401_register_to_temp(u16 reg, bool extended) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int mask = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); + int temp = reg; - if (IS_ERR(data)) - return PTR_ERR(data); + if (extended) + temp -= 64 * 256; - return sprintf(buf, "%d\n", !!(data->status[nr] & mask)); + return DIV_ROUND_CLOSEST(temp * 125, 32); } -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) +static u16 tmp401_temp_to_register(long temp, bool extended, int zbits) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - u16 reg; - u8 regaddr; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - reg = tmp401_temp_to_register(val, data->config, nr == 3 ? 8 : 4); - - mutex_lock(&data->update_lock); - - regaddr = data->kind == tmp432 ? TMP432_TEMP_MSB_WRITE[nr][index] - : TMP401_TEMP_MSB_WRITE[nr][index]; - if (nr == 3) { /* crit is msb only */ - i2c_smbus_write_byte_data(client, regaddr, reg >> 8); + if (extended) { + temp = clamp_val(temp, -64000, 191000); + temp += 64000; } else { - /* Hardware expects big endian data --> use _swapped */ - i2c_smbus_write_word_swapped(client, regaddr, reg); + temp = clamp_val(temp, 0, 127000); } - data->temp[nr][index] = reg; - - mutex_unlock(&data->update_lock); - return count; + return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; } -static ssize_t temp_crit_hyst_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - long val; - u8 reg; - - if (IS_ERR(data)) - return PTR_ERR(data); - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - if (data->config & TMP401_CONFIG_RANGE) - val = clamp_val(val, -64000, 191000); - else - val = clamp_val(val, 0, 127000); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - val = clamp_val(val, temp - 255000, temp); - reg = ((temp - val) + 500) / 1000; - - i2c_smbus_write_byte_data(data->client, TMP401_TEMP_CRIT_HYST, - reg); +/* hwmon API functions */ - data->temp_crit_hyst = reg; +static const u8 tmp401_temp_reg_index[] = { + [hwmon_temp_input] = 0, + [hwmon_temp_min] = 1, + [hwmon_temp_max] = 2, + [hwmon_temp_crit] = 3, + [hwmon_temp_lowest] = 4, + [hwmon_temp_highest] = 5, +}; - mutex_unlock(&data->update_lock); +static const u8 tmp401_status_reg_index[] = { + [hwmon_temp_fault] = 0, + [hwmon_temp_min_alarm] = 1, + [hwmon_temp_max_alarm] = 2, + [hwmon_temp_crit_alarm] = 3, +}; - return count; +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 regval; + int reg, ret; + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_lowest: + case hwmon_temp_highest: + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; + *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); + if (ret < 0) + return ret; + break; + case hwmon_temp_fault: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + reg = TMP432_STATUS_REG[tmp401_status_reg_index[attr]]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; + *val = !!(regval & BIT(channel)); + break; + default: + return -EOPNOTSUPP; + } + return 0; } -/* - * Resets the historical measurements of minimum and maximum temperatures. - * This is done by writing any value to any of the minimum/maximum registers - * (0x30-0x37). - */ -static ssize_t reset_temp_history_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int tmp401_temp_write(struct device *dev, u32 attr, int channel, + long val) { struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; + struct regmap *regmap = data->regmap; + unsigned int regval; + int reg, ret, temp; - if (val != 1) { - dev_err(dev, - "temp_reset_history value %ld not supported. Use 1 to reset the history!\n", - val); - return -EINVAL; - } mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_TEMP_MSB_WRITE[5][0], val); - data->valid = false; + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; + regval = tmp401_temp_to_register(val, data->extended_range, + attr == hwmon_temp_crit ? 8 : 4); + ret = regmap_write(regmap, reg, regval); + break; + case hwmon_temp_crit_hyst: + if (data->extended_range) + val = clamp_val(val, -64000, 191000); + else + val = clamp_val(val, 0, 127000); + + reg = TMP401_TEMP_MSB[3][channel]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + break; + temp = tmp401_register_to_temp(regval, data->extended_range); + val = clamp_val(val, temp - 255000, temp); + regval = ((temp - val) + 500) / 1000; + ret = regmap_write(regmap, TMP401_TEMP_CRIT_HYST, regval); + break; + default: + ret = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - - return count; + return ret; } -static ssize_t update_interval_show(struct device *dev, - struct device_attribute *attr, char *buf) +static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val) { struct tmp401_data *data = dev_get_drvdata(dev); + u32 regval; + int ret; + + switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE, ®val); + if (ret < 0) + return ret; + *val = (1 << (7 - regval)) * 125; + break; + case hwmon_chip_temp_reset_history: + *val = 0; + break; + default: + return -EOPNOTSUPP; + } - return sprintf(buf, "%u\n", data->update_interval); + return 0; } -static ssize_t update_interval_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int tmp401_set_convrate(struct regmap *regmap, long val) { - struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - unsigned long val; - int err, rate; - - err = kstrtoul(buf, 10, &val); - if (err) - return err; + int rate; /* * For valid rates, interval can be calculated as @@ -447,153 +432,137 @@ static ssize_t update_interval_store(struct device *dev, */ val = clamp_val(val, 125, 16000); rate = 7 - __fls(val * 4 / (125 * 3)); + return regmap_write(regmap, TMP401_CONVERSION_RATE, rate); +} + +static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + int err; + mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); - data->update_interval = (1 << (7 - rate)) * 125; + switch (attr) { + case hwmon_chip_update_interval: + err = tmp401_set_convrate(regmap, val); + break; + case hwmon_chip_temp_reset_history: + if (val != 1) { + err = -EINVAL; + break; + } + /* + * Reset history by writing any value to any of the + * minimum/maximum registers (0x30-0x37). + */ + err = regmap_write(regmap, 0x30, 0); + break; + default: + err = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - return count; + return err; } -static SENSOR_DEVICE_ATTR_2_RO(temp1_input, temp, 0, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_min, temp, 1, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp, 2, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_crit, temp, 3, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, temp_crit_hyst, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_min_alarm, status, 1, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, status, 2, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_crit_alarm, status, 3, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp2_input, temp, 0, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_min, temp, 1, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp, 2, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_crit, temp, 3, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, temp_crit_hyst, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, status, 0, TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_min_alarm, status, 1, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, status, 2, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, status, 3, - TMP432_STATUS_REMOTE1); - -static DEVICE_ATTR_RW(update_interval); - -static struct attribute *tmp401_attributes[] = { - &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_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_min_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_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - - &dev_attr_update_interval.attr, - - NULL -}; - -static const struct attribute_group tmp401_group = { - .attrs = tmp401_attributes, -}; - -/* - * Additional features of the TMP411 chip. - * The TMP411 stores the minimum and maximum - * temperature measured since power-on, chip-reset, or - * minimum and maximum register reset for both the local - * and remote channels. - */ -static SENSOR_DEVICE_ATTR_2_RO(temp1_lowest, temp, 4, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_highest, temp, 5, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp2_lowest, temp, 4, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_highest, temp, 5, 1); -static SENSOR_DEVICE_ATTR_WO(temp_reset_history, reset_temp_history, 0); - -static struct attribute *tmp411_attributes[] = { - &sensor_dev_attr_temp1_highest.dev_attr.attr, - &sensor_dev_attr_temp1_lowest.dev_attr.attr, - &sensor_dev_attr_temp2_highest.dev_attr.attr, - &sensor_dev_attr_temp2_lowest.dev_attr.attr, - &sensor_dev_attr_temp_reset_history.dev_attr.attr, - NULL -}; +static int tmp401_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_read(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static const struct attribute_group tmp411_group = { - .attrs = tmp411_attributes, -}; +static int tmp401_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_write(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static SENSOR_DEVICE_ATTR_2_RO(temp3_input, temp, 0, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_min, temp, 1, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_max, temp, 2, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_crit, temp, 3, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, temp_crit_hyst, 2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, status, 0, TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_min_alarm, status, 1, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, status, 2, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, status, 3, - TMP432_STATUS_REMOTE2); - -static struct attribute *tmp432_attributes[] = { - &sensor_dev_attr_temp3_input.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_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - - NULL -}; +static umode_t tmp401_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: + case hwmon_chip_temp_reset_history: + return 0644; + default: + break; + } + break; + 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: + case hwmon_temp_lowest: + case hwmon_temp_highest: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + default: + break; + } + break; + default: + break; + } + return 0; +} -static const struct attribute_group tmp432_group = { - .attrs = tmp432_attributes, +static const struct hwmon_ops tmp401_ops = { + .is_visible = tmp401_is_visible, + .read = tmp401_read, + .write = tmp401_write, }; -/* - * Begin non sysfs callback code (aka Real code) - */ +/* chip initialization, detect, probe */ -static int tmp401_init_client(struct tmp401_data *data, - struct i2c_client *client) +static int tmp401_init_client(struct tmp401_data *data) { - int config, config_orig, status = 0; + struct regmap *regmap = data->regmap; + u32 config, config_orig; + int ret; - /* Set the conversion rate to 2 Hz */ - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, 5); - data->update_interval = 500; + /* Set conversion rate to 2 Hz */ + ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5); + if (ret < 0) + return ret; /* Start conversions (disable shutdown if necessary) */ - config = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (config < 0) - return config; + ret = regmap_read(regmap, TMP401_CONFIG, &config); + if (ret < 0) + return ret; config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; + data->extended_range = !!(config & TMP401_CONFIG_RANGE); + if (config != config_orig) - status = i2c_smbus_write_byte_data(client, - TMP401_CONFIG_WRITE, - config); + ret = regmap_write(regmap, TMP401_CONFIG, config); - return status; + return ret; } static int tmp401_detect(struct i2c_client *client, @@ -651,11 +620,11 @@ static int tmp401_detect(struct i2c_client *client, return -ENODEV; } - reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG); if (reg & 0x1b) return -ENODEV; - reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE); /* Datasheet says: 0x1-0x6 */ if (reg > 15) return -ENODEV; @@ -671,9 +640,10 @@ static int tmp401_probe(struct i2c_client *client) "TMP401", "TMP411", "TMP431", "TMP432", "TMP435" }; struct device *dev = &client->dev; + struct hwmon_channel_info *info; struct device *hwmon_dev; struct tmp401_data *data; - int groups = 0, status; + int status; data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL); if (!data) @@ -683,24 +653,53 @@ static int tmp401_probe(struct i2c_client *client) mutex_init(&data->update_lock); data->kind = i2c_match_id(tmp401_id, client)->driver_data; - /* Initialize the TMP401 chip */ - status = tmp401_init_client(data, client); - if (status < 0) - return status; + data->regmap = devm_regmap_init(dev, NULL, data, &tmp401_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); - /* Register sysfs hooks */ - data->groups[groups++] = &tmp401_group; + /* initialize configuration data */ + data->chip.ops = &tmp401_ops; + data->chip.info = data->info; - /* Register additional tmp411 sysfs hooks */ - if (data->kind == tmp411) - data->groups[groups++] = &tmp411_group; + data->info[0] = &data->chip_info; + data->info[1] = &data->temp_info; - /* Register additional tmp432 sysfs hooks */ - if (data->kind == tmp432) - data->groups[groups++] = &tmp432_group; + info = &data->chip_info; + info->type = hwmon_chip; + info->config = data->chip_channel_config; + + data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; + info->config = data->temp_channel_config; + + data->temp_channel_config[0] = 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; + data->temp_channel_config[1] = 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; + + if (data->kind == tmp411) { + data->temp_channel_config[0] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->temp_channel_config[1] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->chip_channel_config[0] |= HWMON_C_TEMP_RESET_HISTORY; + } + + if (data->kind == tmp432) { + data->temp_channel_config[2] = 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; + } + + /* Initialize the TMP401 chip */ + status = tmp401_init_client(data); + if (status < 0) + return status; - 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, + &data->chip, NULL); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 30aae8642069..5cde837bfd09 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -659,8 +659,10 @@ static int xgene_hwmon_probe(struct platform_device *pdev) acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); - if (!acpi_id) - return -EINVAL; + if (!acpi_id) { + rc = -EINVAL; + goto out_mbox_free; + } version = (int)acpi_id->driver_data; diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 1e8d6ea8992e..fad1f1df26df 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -403,7 +403,7 @@ struct hwmon_ops { }; /** - * Channel information + * struct hwmon_channel_info - Channel information * @type: Channel type. * @config: Pointer to NULL-terminated list of channel parameters. * Use for per-channel attributes. @@ -422,7 +422,7 @@ struct hwmon_channel_info { }) /** - * Chip configuration + * struct hwmon_chip_info - Chip configuration * @ops: Pointer to hwmon operations. * @info: Null-terminated list of channel information. */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 011f2f1ea5bb..b5248f27910e 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -555,6 +555,7 @@ #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443 #define PCI_DEVICE_ID_AMD_19H_DF_F3 0x1653 +#define PCI_DEVICE_ID_AMD_19H_M10H_DF_F3 0x14b0 #define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d #define PCI_DEVICE_ID_AMD_CNB17H_F3 0x1703 diff --git a/include/linux/platform_data/ntc_thermistor.h b/include/linux/platform_data/ntc_thermistor.h deleted file mode 100644 index b324d03e580c..000000000000 --- a/include/linux/platform_data/ntc_thermistor.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * ntc_thermistor.h - NTC Thermistors - * - * Copyright (C) 2010 Samsung Electronics - * MyungJoo Ham <myungjoo.ham@samsung.com> - */ -#ifndef _LINUX_NTC_H -#define _LINUX_NTC_H - -struct iio_channel; - -enum ntc_thermistor_type { - TYPE_B57330V2103, - TYPE_B57891S0103, - TYPE_NCPXXWB473, - TYPE_NCPXXWF104, - TYPE_NCPXXWL333, - TYPE_NCPXXXH103, -}; - -struct ntc_thermistor_platform_data { - /* - * One (not both) of read_uV and read_ohm should be provided and only - * one of the two should be provided. - * Both functions should return negative value for an error case. - * - * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required to use - * read_uV() - * - * How to setup pullup_ohm, pulldown_ohm, and connect is - * described at Documentation/hwmon/ntc_thermistor.rst - * - * pullup/down_ohm: 0 for infinite / not-connected - * - * chan: iio_channel pointer to communicate with the ADC which the - * thermistor is using for conversion of the analog values. - */ - int (*read_uv)(struct ntc_thermistor_platform_data *); - unsigned int pullup_uv; - - unsigned int pullup_ohm; - unsigned int pulldown_ohm; - enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; - struct iio_channel *chan; - - int (*read_ohm)(void); -}; - -#endif /* _LINUX_NTC_H */ |