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:
commit
4a110907a1
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
======================= =======================================================
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-----------
|
||||
|
|
|
@ -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**
|
|
@ -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.
|
||||
======================= ========================================================
|
27
MAINTAINERS
27
MAINTAINERS
|
@ -3013,6 +3013,20 @@ W: http://acpi4asus.sf.net
|
|||
F: drivers/platform/x86/asus*.c
|
||||
F: drivers/platform/x86/eeepc*.c
|
||||
|
||||
ASUS WMI HARDWARE MONITOR DRIVER
|
||||
M: Ed Brindley <kernel@maidavale.org>
|
||||
M: Denis Pauk <pauk.denis@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hwmon/asus_wmi_sensors.c
|
||||
|
||||
ASUS WMI EC HARDWARE MONITOR DRIVER
|
||||
M: Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
M: Denis Pauk <pauk.denis@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hwmon/asus_wmi_ec_sensors.c
|
||||
|
||||
ASUS WIRELESS RADIO CONTROL DRIVER
|
||||
M: João Paulo Rechi Vita <jprvita@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
|
@ -5440,6 +5454,12 @@ W: https://linuxtv.org
|
|||
T: git git://linuxtv.org/media_tree.git
|
||||
F: drivers/media/platform/sti/delta
|
||||
|
||||
DELTA AHE-50DC FAN CONTROL MODULE DRIVER
|
||||
M: Zev Weiss <zev@bewilderbeest.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hwmon/pmbus/delta-ahe50dc-fan.c
|
||||
|
||||
DELTA DPS920AB PSU DRIVER
|
||||
M: Robert Marko <robert.marko@sartura.hr>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
@ -13837,6 +13857,13 @@ S: Maintained
|
|||
F: Documentation/hwmon/nzxt-kraken2.rst
|
||||
F: drivers/hwmon/nzxt-kraken2.c
|
||||
|
||||
NZXT-SMART2 HARDWARE MONITORING DRIVER
|
||||
M: Aleksandr Mezin <mezin.alexander@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/nzxt-smart2.rst
|
||||
F: drivers/hwmon/nzxt-smart2.c
|
||||
|
||||
OBJAGG
|
||||
M: Jiri Pirko <jiri@nvidia.com>
|
||||
L: netdev@vger.kernel.org
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 :
|
||||
|
|
|
@ -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");
|
|
@ -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");
|
|
@ -113,12 +113,12 @@ MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"
|
|||
|
||||
struct smm_regs {
|
||||
unsigned int eax;
|
||||
unsigned int ebx __packed;
|
||||
unsigned int ecx __packed;
|
||||
unsigned int edx __packed;
|
||||
unsigned int esi __packed;
|
||||
unsigned int edi __packed;
|
||||
};
|
||||
unsigned int ebx;
|
||||
unsigned int ecx;
|
||||
unsigned int edx;
|
||||
unsigned int esi;
|
||||
unsigned int edi;
|
||||
} __packed;
|
||||
|
||||
static const char * const temp_labels[] = {
|
||||
"CPU",
|
||||
|
@ -449,13 +449,12 @@ static int i8k_get_power_status(void)
|
|||
* Procfs interface
|
||||
*/
|
||||
|
||||
static int
|
||||
i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg)
|
||||
static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int val = 0;
|
||||
int speed, err;
|
||||
unsigned char buff[16];
|
||||
struct dell_smm_data *data = PDE_DATA(file_inode(fp));
|
||||
int __user *argp = (int __user *)arg;
|
||||
int speed, err;
|
||||
int val = 0;
|
||||
|
||||
if (!argp)
|
||||
return -EINVAL;
|
||||
|
@ -468,15 +467,19 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd
|
|||
|
||||
val = (data->bios_version[0] << 16) |
|
||||
(data->bios_version[1] << 8) | data->bios_version[2];
|
||||
break;
|
||||
|
||||
if (copy_to_user(argp, &val, sizeof(val)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
case I8K_MACHINE_ID:
|
||||
if (restricted && !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
strscpy_pad(buff, data->bios_machineid, sizeof(buff));
|
||||
break;
|
||||
if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
case I8K_FN_STATUS:
|
||||
val = i8k_get_fn_status();
|
||||
break;
|
||||
|
@ -513,11 +516,13 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd
|
|||
if (copy_from_user(&speed, argp + 1, sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&data->i8k_mutex);
|
||||
err = i8k_set_fan(data, val, speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
val = i8k_get_fan_status(data, val);
|
||||
val = err;
|
||||
else
|
||||
val = i8k_get_fan_status(data, val);
|
||||
mutex_unlock(&data->i8k_mutex);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -527,39 +532,12 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd
|
|||
if (val < 0)
|
||||
return val;
|
||||
|
||||
switch (cmd) {
|
||||
case I8K_BIOS_VERSION:
|
||||
if (copy_to_user(argp, &val, 4))
|
||||
return -EFAULT;
|
||||
|
||||
break;
|
||||
case I8K_MACHINE_ID:
|
||||
if (copy_to_user(argp, buff, 16))
|
||||
return -EFAULT;
|
||||
|
||||
break;
|
||||
default:
|
||||
if (copy_to_user(argp, &val, sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
break;
|
||||
}
|
||||
if (copy_to_user(argp, &val, sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct dell_smm_data *data = PDE_DATA(file_inode(fp));
|
||||
long ret;
|
||||
|
||||
mutex_lock(&data->i8k_mutex);
|
||||
ret = i8k_ioctl_unlocked(fp, data, cmd, arg);
|
||||
mutex_unlock(&data->i8k_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print the information for /proc/i8k.
|
||||
*/
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,644 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Driver for Texas Instruments INA238 power monitor chip
|
||||
* Datasheet: https://www.ti.com/product/ina238
|
||||
*
|
||||
* Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com>
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/platform_data/ina2xx.h>
|
||||
|
||||
/* INA238 register definitions */
|
||||
#define INA238_CONFIG 0x0
|
||||
#define INA238_ADC_CONFIG 0x1
|
||||
#define INA238_SHUNT_CALIBRATION 0x2
|
||||
#define INA238_SHUNT_VOLTAGE 0x4
|
||||
#define INA238_BUS_VOLTAGE 0x5
|
||||
#define INA238_DIE_TEMP 0x6
|
||||
#define INA238_CURRENT 0x7
|
||||
#define INA238_POWER 0x8
|
||||
#define INA238_DIAG_ALERT 0xb
|
||||
#define INA238_SHUNT_OVER_VOLTAGE 0xc
|
||||
#define INA238_SHUNT_UNDER_VOLTAGE 0xd
|
||||
#define INA238_BUS_OVER_VOLTAGE 0xe
|
||||
#define INA238_BUS_UNDER_VOLTAGE 0xf
|
||||
#define INA238_TEMP_LIMIT 0x10
|
||||
#define INA238_POWER_LIMIT 0x11
|
||||
#define INA238_DEVICE_ID 0x3f
|
||||
|
||||
#define INA238_CONFIG_ADCRANGE BIT(4)
|
||||
|
||||
#define INA238_DIAG_ALERT_TMPOL BIT(7)
|
||||
#define INA238_DIAG_ALERT_SHNTOL BIT(6)
|
||||
#define INA238_DIAG_ALERT_SHNTUL BIT(5)
|
||||
#define INA238_DIAG_ALERT_BUSOL BIT(4)
|
||||
#define INA238_DIAG_ALERT_BUSUL BIT(3)
|
||||
#define INA238_DIAG_ALERT_POL BIT(2)
|
||||
|
||||
#define INA238_REGISTERS 0x11
|
||||
|
||||
#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */
|
||||
|
||||
/* Default configuration of device on reset. */
|
||||
#define INA238_CONFIG_DEFAULT 0
|
||||
/* 16 sample averaging, 1052us conversion time, continuous mode */
|
||||
#define INA238_ADC_CONFIG_DEFAULT 0xfb6a
|
||||
/* Configure alerts to be based on averaged value (SLOWALERT) */
|
||||
#define INA238_DIAG_ALERT_DEFAULT 0x2000
|
||||
/*
|
||||
* This driver uses a fixed calibration value in order to scale current/power
|
||||
* based on a fixed shunt resistor value. This allows for conversion within the
|
||||
* device to avoid integer limits whilst current/power accuracy is scaled
|
||||
* relative to the shunt resistor value within the driver. This is similar to
|
||||
* how the ina2xx driver handles current/power scaling.
|
||||
*
|
||||
* The end result of this is that increasing shunt values (from a fixed 20 mOhm
|
||||
* shunt) increase the effective current/power accuracy whilst limiting the
|
||||
* range and decreasing shunt values decrease the effective accuracy but
|
||||
* increase the range.
|
||||
*
|
||||
* The value of the Current register is calculated given the following:
|
||||
* Current (A) = (shunt voltage register * 5) * calibration / 81920
|
||||
*
|
||||
* The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4).
|
||||
* With the maximum current value of 0x7fff and a fixed shunt value results in
|
||||
* a calibration value of 16384 (0x4000).
|
||||
*
|
||||
* 0x7fff = (0x7fff * 5) * calibration / 81920
|
||||
* calibration = 0x4000
|
||||
*
|
||||
* Equivalent calibration is applied for the Power register (maximum value for
|
||||
* bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can
|
||||
* occur is ~16776192 uW (register value 0x147a8):
|
||||
*
|
||||
* This scaling means the resulting values for Current and Power registers need
|
||||
* to be scaled by the difference between the fixed shunt resistor and the
|
||||
* actual shunt resistor:
|
||||
*
|
||||
* shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb)
|
||||
*
|
||||
* Current (mA) = register value * 20000 / rshunt / 4 * gain
|
||||
* Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain
|
||||
*/
|
||||
#define INA238_CALIBRATION_VALUE 16384
|
||||
#define INA238_FIXED_SHUNT 20000
|
||||
|
||||
#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */
|
||||
#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */
|
||||
#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */
|
||||
|
||||
static struct regmap_config ina238_regmap_config = {
|
||||
.max_register = INA238_REGISTERS,
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
};
|
||||
|
||||
struct ina238_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex config_lock;
|
||||
struct regmap *regmap;
|
||||
u32 rshunt;
|
||||
int gain;
|
||||
};
|
||||
|
||||
static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
|
||||
{
|
||||
u8 data[3];
|
||||
int err;
|
||||
|
||||
/* 24-bit register read */
|
||||
err = i2c_smbus_read_i2c_block_data(client, reg, 3, data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (err != 3)
|
||||
return -EIO;
|
||||
*val = (data[0] << 16) | (data[1] << 8) | data[2];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int reg, mask;
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
switch (channel) {
|
||||
case 0:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
reg = INA238_SHUNT_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max:
|
||||
reg = INA238_SHUNT_OVER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_min:
|
||||
reg = INA238_SHUNT_UNDER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_SHNTOL;
|
||||
break;
|
||||
case hwmon_in_min_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_SHNTUL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
reg = INA238_BUS_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max:
|
||||
reg = INA238_BUS_OVER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_min:
|
||||
reg = INA238_BUS_UNDER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_BUSOL;
|
||||
break;
|
||||
case hwmon_in_min_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_BUSUL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
err = regmap_read(data->regmap, reg, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_max:
|
||||
case hwmon_in_min:
|
||||
/* signed register, value in mV */
|
||||
regval = (s16)regval;
|
||||
if (channel == 0)
|
||||
/* gain of 1 -> LSB / 4 */
|
||||
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) /
|
||||
(1000 * (4 - data->gain + 1));
|
||||
else
|
||||
*val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000;
|
||||
break;
|
||||
case hwmon_in_max_alarm:
|
||||
case hwmon_in_min_alarm:
|
||||
*val = !!(regval & mask);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_in(struct device *dev, u32 attr, int channel,
|
||||
long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
|
||||
if (attr != hwmon_in_max && attr != hwmon_in_min)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* convert decimal to register value */
|
||||
switch (channel) {
|
||||
case 0:
|
||||
/* signed value, clamp to max range +/-163 mV */
|
||||
regval = clamp_val(val, -163, 163);
|
||||
regval = (regval * 1000 * (4 - data->gain + 1)) /
|
||||
INA238_SHUNT_VOLTAGE_LSB;
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_max:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_SHUNT_OVER_VOLTAGE, regval);
|
||||
case hwmon_in_min:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_SHUNT_UNDER_VOLTAGE, regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case 1:
|
||||
/* signed value, positive values only. Clamp to max 102.396 V */
|
||||
regval = clamp_val(val, 0, 102396);
|
||||
regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB;
|
||||
regval = clamp_val(regval, 0, S16_MAX);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_max:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_BUS_OVER_VOLTAGE, regval);
|
||||
case hwmon_in_min:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_BUS_UNDER_VOLTAGE, regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int ina238_read_current(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
err = regmap_read(data->regmap, INA238_CURRENT, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Signed register, fixed 1mA current lsb. result in mA */
|
||||
*val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain,
|
||||
data->rshunt * 4);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
long long power;
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_power_input:
|
||||
err = ina238_read_reg24(data->client, INA238_POWER, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT *
|
||||
data->gain, 20 * data->rshunt);
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
case hwmon_power_max:
|
||||
err = regmap_read(data->regmap, INA238_POWER_LIMIT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Truncated 24-bit compare register, lower 8-bits are
|
||||
* truncated. Same conversion to/from uW as POWER register.
|
||||
*/
|
||||
power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT *
|
||||
data->gain, 20 * data->rshunt);
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
case hwmon_power_max_alarm:
|
||||
err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*val = !!(regval & INA238_DIAG_ALERT_POL);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_power(struct device *dev, u32 attr, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
long regval;
|
||||
|
||||
if (attr != hwmon_power_max)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* Unsigned postive values. Compared against the 24-bit power register,
|
||||
* lower 8-bits are truncated. Same conversion to/from uW as POWER
|
||||
* register.
|
||||
*/
|
||||
regval = clamp_val(val, 0, LONG_MAX);
|
||||
regval = div_u64(val * 20ULL * data->rshunt,
|
||||
1000ULL * INA238_FIXED_SHUNT * data->gain);
|
||||
regval = clamp_val(regval >> 8, 0, U16_MAX);
|
||||
|
||||
return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
|
||||
}
|
||||
|
||||
static int ina238_read_temp(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Signed, bits 15-4 of register, result in mC */
|
||||
*val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Signed, bits 15-4 of register, result in mC */
|
||||
*val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
|
||||
break;
|
||||
case hwmon_temp_max_alarm:
|
||||
err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*val = !!(regval & INA238_DIAG_ALERT_TMPOL);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_temp(struct device *dev, u32 attr, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
|
||||
if (attr != hwmon_temp_max)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Signed, bits 15-4 of register */
|
||||
regval = (val / INA238_DIE_TEMP_LSB) << 4;
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0;
|
||||
|
||||
return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
|
||||
}
|
||||
|
||||
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
return ina238_read_in(dev, attr, channel, val);
|
||||
case hwmon_curr:
|
||||
return ina238_read_current(dev, attr, val);
|
||||
case hwmon_power:
|
||||
return ina238_read_power(dev, attr, val);
|
||||
case hwmon_temp:
|
||||
return ina238_read_temp(dev, attr, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
mutex_lock(&data->config_lock);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
err = ina238_write_in(dev, attr, channel, val);
|
||||
break;
|
||||
case hwmon_power:
|
||||
err = ina238_write_power(dev, attr, val);
|
||||
break;
|
||||
case hwmon_temp:
|
||||
err = ina238_write_temp(dev, attr, val);
|
||||
break;
|
||||
default:
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->config_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static umode_t ina238_is_visible(const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_max_alarm:
|
||||
case hwmon_in_min_alarm:
|
||||
return 0444;
|
||||
case hwmon_in_max:
|
||||
case hwmon_in_min:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_power:
|
||||
switch (attr) {
|
||||
case hwmon_power_input:
|
||||
case hwmon_power_max_alarm:
|
||||
return 0444;
|
||||
case hwmon_power_max:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_max_alarm:
|
||||
return 0444;
|
||||
case hwmon_temp_max:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define INA238_HWMON_IN_CONFIG (HWMON_I_INPUT | \
|
||||
HWMON_I_MAX | HWMON_I_MAX_ALARM | \
|
||||
HWMON_I_MIN | HWMON_I_MIN_ALARM)
|
||||
|
||||
static const struct hwmon_channel_info *ina238_info[] = {
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
/* 0: shunt voltage */
|
||||
INA238_HWMON_IN_CONFIG,
|
||||
/* 1: bus voltage */
|
||||
INA238_HWMON_IN_CONFIG),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
/* 0: current through shunt */
|
||||
HWMON_C_INPUT),
|
||||
HWMON_CHANNEL_INFO(power,
|
||||
/* 0: power */
|
||||
HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
/* 0: die temperature */
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops ina238_hwmon_ops = {
|
||||
.is_visible = ina238_is_visible,
|
||||
.read = ina238_read,
|
||||
.write = ina238_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info ina238_chip_info = {
|
||||
.ops = &ina238_hwmon_ops,
|
||||
.info = ina238_info,
|
||||
};
|
||||
|
||||
static int ina238_probe(struct i2c_client *client)
|
||||
{
|
||||
struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct ina238_data *data;
|
||||
int config;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
mutex_init(&data->config_lock);
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
/* load shunt value */
|
||||
data->rshunt = INA238_RSHUNT_DEFAULT;
|
||||
if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
|
||||
data->rshunt = pdata->shunt_uohms;
|
||||
if (data->rshunt == 0) {
|
||||
dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* load shunt gain value */
|
||||
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
|
||||
data->gain = 4; /* Default of ADCRANGE = 0 */
|
||||
if (data->gain != 1 && data->gain != 4) {
|
||||
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Setup CONFIG register */
|
||||
config = INA238_CONFIG_DEFAULT;
|
||||
if (data->gain == 1)
|
||||
config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */
|
||||
ret = regmap_write(data->regmap, INA238_CONFIG, config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Setup ADC_CONFIG register */
|
||||
ret = regmap_write(data->regmap, INA238_ADC_CONFIG,
|
||||
INA238_ADC_CONFIG_DEFAULT);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Setup SHUNT_CALIBRATION register with fixed value */
|
||||
ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
|
||||
INA238_CALIBRATION_VALUE);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Setup alert/alarm configuration */
|
||||
ret = regmap_write(data->regmap, INA238_DIAG_ALERT,
|
||||
INA238_DIAG_ALERT_DEFAULT);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&ina238_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
|
||||
client->name, data->rshunt, data->gain);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ina238_id[] = {
|
||||
{ "ina238", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ina238_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused ina238_of_match[] = {
|
||||
{ .compatible = "ti,ina238" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ina238_of_match);
|
||||
|
||||
static struct i2c_driver ina238_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "ina238",
|
||||
.of_match_table = of_match_ptr(ina238_of_match),
|
||||
},
|
||||
.probe_new = ina238_probe,
|
||||
.id_table = ina238_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(ina238_driver);
|
||||
|
||||
MODULE_AUTHOR("Nathan Rossi <nathan.rossi@digi.com>");
|
||||
MODULE_DESCRIPTION("ina238 driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -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 },
|
||||
|
|
|
@ -76,26 +76,6 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
|
|||
#define ZEN_CUR_TEMP_SHIFT 21
|
||||
#define ZEN_CUR_TEMP_RANGE_SEL_MASK BIT(19)
|
||||
|
||||
#define ZEN_SVI_BASE 0x0005A000
|
||||
|
||||
/* F17h thermal registers through SMN */
|
||||
#define F17H_M01H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0xc)
|
||||
#define F17H_M01H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10)
|
||||
#define F17H_M31H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14)
|
||||
#define F17H_M31H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10)
|
||||
|
||||
#define F17H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */
|
||||
#define F17H_M01H_CFACTOR_ISOC 250000 /* 0.25A / LSB */
|
||||
#define F17H_M31H_CFACTOR_ICORE 1000000 /* 1A / LSB */
|
||||
#define F17H_M31H_CFACTOR_ISOC 310000 /* 0.31A / LSB */
|
||||
|
||||
/* F19h thermal registers through SMN */
|
||||
#define F19H_M01_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14)
|
||||
#define F19H_M01_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10)
|
||||
|
||||
#define F19H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */
|
||||
#define F19H_M01H_CFACTOR_ISOC 310000 /* 0.31A / LSB */
|
||||
|
||||
struct k10temp_data {
|
||||
struct pci_dev *pdev;
|
||||
void (*read_htcreg)(struct pci_dev *pdev, u32 *regval);
|
||||
|
@ -191,6 +171,10 @@ static const char *k10temp_temp_label[] = {
|
|||
"Tccd6",
|
||||
"Tccd7",
|
||||
"Tccd8",
|
||||
"Tccd9",
|
||||
"Tccd10",
|
||||
"Tccd11",
|
||||
"Tccd12",
|
||||
};
|
||||
|
||||
static int k10temp_read_labels(struct device *dev,
|
||||
|
@ -226,7 +210,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
|
|||
if (*val < 0)
|
||||
*val = 0;
|
||||
break;
|
||||
case 2 ... 9: /* Tccd{1-8} */
|
||||
case 2 ... 13: /* Tccd{1-12} */
|
||||
amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
|
||||
ZEN_CCD_TEMP(data->ccd_offset, channel - 2),
|
||||
®val);
|
||||
|
@ -361,6 +345,10 @@ static const struct hwmon_channel_info *k10temp_info[] = {
|
|||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
@ -457,6 +445,11 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|||
data->ccd_offset = 0x300;
|
||||
k10temp_get_ccd_support(pdev, data, 8);
|
||||
break;
|
||||
case 0x10 ... 0x1f:
|
||||
case 0xa0 ... 0xaf:
|
||||
data->ccd_offset = 0x300;
|
||||
k10temp_get_ccd_support(pdev, data, 12);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
data->read_htcreg = read_htcreg_pci;
|
||||
|
@ -497,6 +490,7 @@ static const struct pci_device_id k10temp_id_table[] = {
|
|||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
|
||||
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -9,18 +9,23 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/fixp-arith.h>
|
||||
|
||||
#include <linux/platform_data/ntc_thermistor.h>
|
||||
|
||||
#include <linux/iio/consumer.h>
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
|
||||
enum ntc_thermistor_type {
|
||||
TYPE_B57330V2103,
|
||||
TYPE_B57891S0103,
|
||||
TYPE_NCPXXWB473,
|
||||
TYPE_NCPXXWF104,
|
||||
TYPE_NCPXXWL333,
|
||||
TYPE_NCPXXXH103,
|
||||
};
|
||||
|
||||
struct ntc_compensation {
|
||||
int temp_c;
|
||||
unsigned int ohm;
|
||||
|
@ -40,6 +45,7 @@ enum {
|
|||
NTC_NCP15XH103,
|
||||
NTC_NCP18WB473,
|
||||
NTC_NCP21WB473,
|
||||
NTC_SSG1404001221,
|
||||
NTC_LAST,
|
||||
};
|
||||
|
||||
|
@ -53,6 +59,7 @@ static const struct platform_device_id ntc_thermistor_id[] = {
|
|||
[NTC_NCP15XH103] = { "ncp15xh103", TYPE_NCPXXXH103 },
|
||||
[NTC_NCP18WB473] = { "ncp18wb473", TYPE_NCPXXWB473 },
|
||||
[NTC_NCP21WB473] = { "ncp21wb473", TYPE_NCPXXWB473 },
|
||||
[NTC_SSG1404001221] = { "ssg1404-001221", TYPE_NCPXXWB473 },
|
||||
[NTC_LAST] = { },
|
||||
};
|
||||
|
||||
|
@ -313,16 +320,30 @@ static const struct ntc_type ntc_type[] = {
|
|||
NTC_TYPE(TYPE_NCPXXXH103, ncpXXxh103),
|
||||
};
|
||||
|
||||
/*
|
||||
* pullup_uV, pullup_ohm, pulldown_ohm, and connect are required.
|
||||
*
|
||||
* How to setup pullup_ohm, pulldown_ohm, and connect is
|
||||
* described at Documentation/hwmon/ntc_thermistor.rst
|
||||
*
|
||||
* pullup/down_ohm: 0 for infinite / not-connected
|
||||
*
|
||||
* chan: iio_channel pointer to communicate with the ADC which the
|
||||
* thermistor is using for conversion of the analog values.
|
||||
*/
|
||||
struct ntc_data {
|
||||
struct ntc_thermistor_platform_data *pdata;
|
||||
const struct ntc_compensation *comp;
|
||||
int n_comp;
|
||||
unsigned int pullup_uv;
|
||||
unsigned int pullup_ohm;
|
||||
unsigned int pulldown_ohm;
|
||||
enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect;
|
||||
struct iio_channel *chan;
|
||||
};
|
||||
|
||||
#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO)
|
||||
static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata)
|
||||
static int ntc_adc_iio_read(struct ntc_data *data)
|
||||
{
|
||||
struct iio_channel *channel = pdata->chan;
|
||||
struct iio_channel *channel = data->chan;
|
||||
int uv, ret;
|
||||
|
||||
ret = iio_read_channel_processed_scale(channel, &uv, 1000);
|
||||
|
@ -342,103 +363,13 @@ static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata)
|
|||
ret = iio_convert_raw_to_processed(channel, raw, &uv, 1000);
|
||||
if (ret < 0) {
|
||||
/* Assume 12 bit ADC with vref at pullup_uv */
|
||||
uv = (pdata->pullup_uv * (s64)raw) >> 12;
|
||||
uv = (data->pullup_uv * (s64)raw) >> 12;
|
||||
}
|
||||
}
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
static const struct of_device_id ntc_match[] = {
|
||||
{ .compatible = "epcos,b57330v2103",
|
||||
.data = &ntc_thermistor_id[NTC_B57330V2103]},
|
||||
{ .compatible = "epcos,b57891s0103",
|
||||
.data = &ntc_thermistor_id[NTC_B57891S0103] },
|
||||
{ .compatible = "murata,ncp03wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP03WB473] },
|
||||
{ .compatible = "murata,ncp03wf104",
|
||||
.data = &ntc_thermistor_id[NTC_NCP03WF104] },
|
||||
{ .compatible = "murata,ncp15wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP15WB473] },
|
||||
{ .compatible = "murata,ncp15wl333",
|
||||
.data = &ntc_thermistor_id[NTC_NCP15WL333] },
|
||||
{ .compatible = "murata,ncp15xh103",
|
||||
.data = &ntc_thermistor_id[NTC_NCP15XH103] },
|
||||
{ .compatible = "murata,ncp18wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP18WB473] },
|
||||
{ .compatible = "murata,ncp21wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP21WB473] },
|
||||
|
||||
/* Usage of vendor name "ntc" is deprecated */
|
||||
{ .compatible = "ntc,ncp03wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP03WB473] },
|
||||
{ .compatible = "ntc,ncp15wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP15WB473] },
|
||||
{ .compatible = "ntc,ncp15wl333",
|
||||
.data = &ntc_thermistor_id[NTC_NCP15WL333] },
|
||||
{ .compatible = "ntc,ncp18wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP18WB473] },
|
||||
{ .compatible = "ntc,ncp21wb473",
|
||||
.data = &ntc_thermistor_id[NTC_NCP21WB473] },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ntc_match);
|
||||
|
||||
static struct ntc_thermistor_platform_data *
|
||||
ntc_thermistor_parse_dt(struct device *dev)
|
||||
{
|
||||
struct iio_channel *chan;
|
||||
enum iio_chan_type type;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct ntc_thermistor_platform_data *pdata;
|
||||
int ret;
|
||||
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
chan = devm_iio_channel_get(dev, NULL);
|
||||
if (IS_ERR(chan))
|
||||
return ERR_CAST(chan);
|
||||
|
||||
ret = iio_get_channel_type(chan, &type);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (type != IIO_VOLTAGE)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uv))
|
||||
return ERR_PTR(-ENODEV);
|
||||
if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm))
|
||||
return ERR_PTR(-ENODEV);
|
||||
if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (of_find_property(np, "connected-positive", NULL))
|
||||
pdata->connect = NTC_CONNECTED_POSITIVE;
|
||||
else /* status change should be possible if not always on. */
|
||||
pdata->connect = NTC_CONNECTED_GROUND;
|
||||
|
||||
pdata->chan = chan;
|
||||
pdata->read_uv = ntc_adc_iio_read;
|
||||
|
||||
return pdata;
|
||||
}
|
||||
#else
|
||||
static struct ntc_thermistor_platform_data *
|
||||
ntc_thermistor_parse_dt(struct device *dev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ntc_match NULL
|
||||
|
||||
#endif
|
||||
|
||||
static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
|
||||
{
|
||||
if (divisor == 0 && dividend == 0)
|
||||
|
@ -450,24 +381,23 @@ static inline u64 div64_u64_safe(u64 dividend, u64 divisor)
|
|||
|
||||
static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv)
|
||||
{
|
||||
struct ntc_thermistor_platform_data *pdata = data->pdata;
|
||||
u32 puv = pdata->pullup_uv;
|
||||
u32 puv = data->pullup_uv;
|
||||
u64 n, puo, pdo;
|
||||
puo = pdata->pullup_ohm;
|
||||
pdo = pdata->pulldown_ohm;
|
||||
puo = data->pullup_ohm;
|
||||
pdo = data->pulldown_ohm;
|
||||
|
||||
if (uv == 0)
|
||||
return (pdata->connect == NTC_CONNECTED_POSITIVE) ?
|
||||
return (data->connect == NTC_CONNECTED_POSITIVE) ?
|
||||
INT_MAX : 0;
|
||||
if (uv >= puv)
|
||||
return (pdata->connect == NTC_CONNECTED_POSITIVE) ?
|
||||
return (data->connect == NTC_CONNECTED_POSITIVE) ?
|
||||
0 : INT_MAX;
|
||||
|
||||
if (pdata->connect == NTC_CONNECTED_POSITIVE && puo == 0)
|
||||
if (data->connect == NTC_CONNECTED_POSITIVE && puo == 0)
|
||||
n = div_u64(pdo * (puv - uv), uv);
|
||||
else if (pdata->connect == NTC_CONNECTED_GROUND && pdo == 0)
|
||||
else if (data->connect == NTC_CONNECTED_GROUND && pdo == 0)
|
||||
n = div_u64(puo * uv, puv - uv);
|
||||
else if (pdata->connect == NTC_CONNECTED_POSITIVE)
|
||||
else if (data->connect == NTC_CONNECTED_POSITIVE)
|
||||
n = div64_u64_safe(pdo * puo * (puv - uv),
|
||||
puo * uv - pdo * (puv - uv));
|
||||
else
|
||||
|
@ -567,16 +497,10 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data)
|
|||
{
|
||||
int read_uv;
|
||||
|
||||
if (data->pdata->read_ohm)
|
||||
return data->pdata->read_ohm();
|
||||
|
||||
if (data->pdata->read_uv) {
|
||||
read_uv = data->pdata->read_uv(data->pdata);
|
||||
if (read_uv < 0)
|
||||
return read_uv;
|
||||
return get_ohm_of_thermistor(data, read_uv);
|
||||
}
|
||||
return -EINVAL;
|
||||
read_uv = ntc_adc_iio_read(data);
|
||||
if (read_uv < 0)
|
||||
return read_uv;
|
||||
return get_ohm_of_thermistor(data, read_uv);
|
||||
}
|
||||
|
||||
static int ntc_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
|
@ -638,58 +562,74 @@ static const struct hwmon_chip_info ntc_chip_info = {
|
|||
.info = ntc_info,
|
||||
};
|
||||
|
||||
static int ntc_thermistor_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,
|
||||
|
|
|
@ -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);
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -120,6 +120,8 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
|
|||
data,
|
||||
&rpi_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(data->hwmon_dev))
|
||||
return PTR_ERR(data->hwmon_dev);
|
||||
|
||||
ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work,
|
||||
get_values_poll);
|
||||
|
@ -127,10 +129,9 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (!PTR_ERR_OR_ZERO(data->hwmon_dev))
|
||||
schedule_delayed_work(&data->get_values_poll_work, 2 * HZ);
|
||||
schedule_delayed_work(&data->get_values_poll_work, 2 * HZ);
|
||||
|
||||
return PTR_ERR_OR_ZERO(data->hwmon_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rpi_hwmon_driver = {
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
Loading…
Reference in New Issue