hwmon updates for v5.17

New drivers:
 - PMBus driver for MPS Multi-phase mp5023
 - PMBus driver for Delta AHE-50DC fan control module
 - Driver for NZXT RGB&Fan Controller/Smart Device v2
 - Driver for Texas Instruments INA238
 - Driver to support X370 Asus WMI
 - Driver to support B550 Asus WMI
 
 Other notable changes:
 - Cleanup of ntc_thermistor driver, and added support for Samsung 1404-001221 NTC
 - Improve detection of LM84, MAX1617, and MAX1617A in adm1021 driver
 - Clean up tmp401 driver, and convert to with_info API
 - Add support for regulators and IR38060, IR38164 IR38263 to ir38064 PMBus driver
 - Add support for AMD Family 19h Models 10h-1Fh and A0h-AFh to k10temp driver
 - Add support for F81966 to f71882fg driver
 - Add support for ONSEMI N34TS04 to jc42 driver
 - Clean up and simplify dell-smm driver
 - Add support for ROG STRIX B550-A/X570-I GAMING to nct6775 driver
 
 Various other minor improvements and fixes.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmHbsioACgkQyx8mb86f
 mYGL2A//RPRWUgnBqzT6dwrhqiRzYFJXbqFPUIEIeBBFTZ/QbyQWazvQYlxOieDm
 8Y8NZlgqCP2UJ1pYI7jh7g7n6GFp4stNHgAO9GsGbkQNZYCR7eyRCSxVTB9oW+L4
 Cxt1sXclUwcQXi5h3cMZbBTebAcKwUtg81QaIkOBjpfmLzP6PisS0rB6Dy9WjWnF
 aXTNH6wHNY/xqOFpUuS5DkS+FT5eu35kP9DDLvF8yHLUdvn1JzwMOVNV/p0H1VZ8
 ZmOyhgDqqwcaYJlHIY5R6M5u1sH/r4I+zUgQPzmuzw3CLYSa3C/yhnmagRAqheT2
 HguOJClQuSilO49W4w3iiOOOUy6Qf082Mta62RjQT6/+po1FYNQTfYbAyYR1CkMH
 sj0c9TvSOzSXpy2W0jTmCGL38VLy54lXMq+xM2o+dVzSKpN3JyuGpBLt730Z25on
 gTRNO8DpytLe2EPZ32R5hu1UcoMe1O+X2tCctyyC0RZMwKwWRcC5N3Kvx98ea61L
 dkvjQyTzfI8nZoUoh0ML15OhhOTJV10fQt6NB6oA9/iFjPblE9YqU6jeN/2gCTFL
 SkDKUO35vD8d6aBl7YtRNSSdjF1wuC22GSPpj47vh4eM3MU9eCL2Qyqd6hksFTFF
 wYm9PzvKxmPSlsCvifZj/oKdFODOgG06+7tGZki/gfCv66uJl4U=
 =bh5w
 -----END PGP SIGNATURE-----

Merge tag 'hwmon-for-v5.17' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:
 "New drivers:

   - PMBus driver for MPS Multi-phase mp5023

   - PMBus driver for Delta AHE-50DC fan control module

   - Driver for NZXT RGB&Fan Controller/Smart Device v2

   - Driver for Texas Instruments INA238

   - Driver to support X370 Asus WMI

   - Driver to support B550 Asus WMI

  Other notable changes:

   - Cleanup of ntc_thermistor driver, and added support for Samsung
     1404-001221 NTC

   - Improve detection of LM84, MAX1617, and MAX1617A in adm1021 driver

   - Clean up tmp401 driver, and convert to with_info API

   - Add support for regulators and IR38060, IR38164 IR38263 to ir38064
     PMBus driver

   - Add support for AMD Family 19h Models 10h-1Fh and A0h-AFh to
     k10temp driver

   - Add support for F81966 to f71882fg driver

   - Add support for ONSEMI N34TS04 to jc42 driver

   - Clean up and simplify dell-smm driver

   - Add support for ROG STRIX B550-A/X570-I GAMING to nct6775 driver

  And various other minor improvements and fixes"

* tag 'hwmon-for-v5.17' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (49 commits)
  hwmon: (nzxt-smart2) make array detect_fans_report static const
  hwmon: (xgene-hwmon) Add free before exiting xgene_hwmon_probe
  hwmon: (nzxt-smart2) Fix "unused function" warning
  hwmon: (dell-smm) Pack the whole smm_regs struct
  hwmon: (nct6775) Additional check for ChipID before ASUS WMI usage
  hwmon: (mr75203) fix wrong power-up delay value
  hwmon/pmbus: (ir38064) Fix spelling mistake "comaptible" -> "compatible"
  hwmon/pmbus: (ir38064) Expose a regulator
  hwmon/pmbus: (ir38064) Add of_match_table
  hwmon/pmbus: (ir38064) Add support for IR38060, IR38164 IR38263
  hwmon: add driver for NZXT RGB&Fan Controller/Smart Device v2.
  hwmon: (nct6775) add ROG STRIX B550-A/X570-I GAMING
  hwmon: (pmbus) Add support for MPS Multi-phase mp5023
  dt-bindings: add Delta AHE-50DC fan control module
  hwmon: (pmbus) Add Delta AHE-50DC fan control module driver
  hwmon: prefix kernel-doc comments for structs with struct
  hwmon: (ntc_thermistor) Add Samsung 1404-001221 NTC
  hwmon: (ntc_thermistor) Drop OF dependency
  hwmon: (dell-smm) Unify i8k_ioctl() and i8k_ioctl_unlocked()
  hwmon: (dell-smm) Simplify ioctl handler
  ...
This commit is contained in:
Linus Torvalds 2022-01-11 10:25:36 -08:00
commit 4a110907a1
39 changed files with 4136 additions and 754 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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
======================= =======================================================

View File

@ -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

View File

@ -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
-----------

View File

@ -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**

View File

@ -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.
======================= ========================================================

View File

@ -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

View File

@ -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) },

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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 :

View File

@ -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");

View File

@ -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");

View File

@ -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 = 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;
}
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.
*/

View File

@ -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:

644
drivers/hwmon/ina238.c Normal file
View File

@ -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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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");

View File

@ -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 },

View File

@ -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),
&regval);
@ -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) },

View File

@ -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

View File

@ -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 {

View File

@ -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);
read_uv = ntc_adc_iio_read(data);
if (read_uv < 0)
return read_uv;
return get_ohm_of_thermistor(data, read_uv);
}
return -EINVAL;
}
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_parse_props(struct device *dev,
struct ntc_data *data)
{
struct iio_channel *chan;
enum iio_chan_type type;
int ret;
chan = devm_iio_channel_get(dev, NULL);
if (IS_ERR(chan))
return PTR_ERR(chan);
ret = iio_get_channel_type(chan, &type);
if (ret < 0)
return ret;
if (type != IIO_VOLTAGE)
return -EINVAL;
ret = device_property_read_u32(dev, "pullup-uv", &data->pullup_uv);
if (ret)
return dev_err_probe(dev, ret, "pullup-uv not specified\n");
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;
}
static int ntc_thermistor_probe(struct platform_device *pdev)
{
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;
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);
if (!pdata) {
dev_err(dev, "No platform init data supplied.\n");
return -ENODEV;
}
/* 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");
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;
}
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;
}
data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL);
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;
data->pdata = pdata;
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;
}
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,

829
drivers/hwmon/nzxt-smart2.c Normal file
View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
return PTR_ERR_OR_ZERO(data->hwmon_dev);
return 0;
}
static struct platform_driver rpi_hwmon_driver = {

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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.
*/

View File

@ -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

View File

@ -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 */