hwmon updates for v6.18-rc1

* New drivers
 
   - Driver for Kontron SMARC-sAM67
 
   - Driver for GPD device sensors
 
   - Driver for MP29502
 
   - Driver for MP2869, MP29608, MP29612 and MP29816 series
 
 * Added chip support to existing drivers
 
   - asus-ec-sensors:
 	Add B650E-I
 	Add PRIME Z270-A
 	Add Pro WS WRX90E-SAGE SE
 	Add ROG STRIX X670E-E GAMING WIFI
 	Add ROG STRIX X870-I GAMING WIFI
 	Add ROG STRIX X870E-E GAMING WIFI
 	Add ROG STRIX Z690-E GAMING WIFI
 	Add ROG STRIX Z790E GAMING WIFI II
 	Add STRIX B850-I GAMING WIFI
 	Add TUF GAMING X670E PLUS WIFI
 	Add X670E-I GAMING WIFI
 	Add Z790-I GAMING WIFI
 
   - dell-smm: Add support for Dell OptiPlex 7040
 
   - ina238: Major cleanup, and
   	Add support for INA700
 	Add support for INA780
 
   - k10temp:
   	Add device ID for Strix Halo
 	Add support for AMD Family 1Ah-based models
 
   - lenovo-ec-sensors: Update P8 supprt
 
   - lm75: Add NXP P3T1750 support
 
   - pmbus/adm1275: Add sq24905c support
 
   - pmbus/isl68137: Add support for Renesas RAA228244 and RAA228246
 
   - pmbus/mp5990: Add support for MP5998
 
   - sht21: Add support for SHT20, SHT25
 
   - sl28cpld: Add sa67mcu compatible
 
 * Other notable changes
 
   - core:
 	Handle locking internally
 	Introduce 64-bit energy attribute support
 
   - cros_ec: Register into thermal framework, improve PWM control
 
   - lm75: allow interrupt for ti,tmp75
 
   - mlxreg-fan: Add support for new flavour of capability register
 
   - sbtsi_temp: AMD CPU extended temperature range support
 
   - sht21: Add devicetree support
 
 * Various other minor improvements and fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmjZ2VAACgkQyx8mb86f
 mYHegQ//YImjHnlWcRbr8meprHHezIkGDA0nJEzjsCSD7owq00sOC4w4BGXf7MPV
 d1rK0VbAU2Up53j4xZKr7PnHzUHFil/K/9hDBHFfYd6fr570ADdMnewRmVoKuSuR
 JBeV0308y0WbeAhNOaIIcGx3/4wMgQklvUycjOZKUgBC2jY/vnuwlgrt21a4rbB/
 c5yNUEfqk9thZY/xOBbbrcvej0RXZ/naV+tRgVYQUo6Ep1CUynxOr2VCUbWxoQWh
 lnekEhJuylbIDodAZ405Cpn3AuqyVypbKBjAyGWSwl92KuoYyBGJnbk/GYokWvCH
 ftMlLF+HUbNRd75W3mwxZOuSX5tIps2NP8aQrdRdOxhF6Ln5b49R4NLI7ZqnOVzO
 syRo96J1joMIGwfRga/b8dqCLYZ45fX2VbfisdhrzZ0OM3e7yg3fVyJRbxubP+n6
 VxCQPfFUQoLKGOAiBfCA5yYCDE+qFXT1HBKORhtUF/wZnq6X7rYMtC0ci0TfcKo0
 Ar0s4xnxX2avFhySvuJL7snv01oOR/9SkCrKY4LqXETMSvNwTNQjLKGo0uly+Mxs
 bs63JFnLtA1BHXvPuFklxDMAqQ4KZZAz1AznePFV7uFRk2VRbzKf1GkS4S9jVlc+
 2/nKb2gWBVdtAqone0D0dC9EoFNZwGqCPuymyTgEkYkO0vgZ2qs=
 =jb6g
 -----END PGP SIGNATURE-----

Merge tag 'hwmon-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:
 "New drivers:

   - Kontron SMARC-sAM67

   - GPD device sensors

   - MP29502

   - MP2869, MP29608, MP29612 and MP29816 series

  Added chip support to existing drivers:

   - asus-ec-sensors:
	Add B650E-I
	Add PRIME Z270-A
	Add Pro WS WRX90E-SAGE SE
	Add ROG STRIX X670E-E GAMING WIFI
	Add ROG STRIX X870-I GAMING WIFI
	Add ROG STRIX X870E-E GAMING WIFI
	Add ROG STRIX Z690-E GAMING WIFI
	Add ROG STRIX Z790E GAMING WIFI II
	Add STRIX B850-I GAMING WIFI
	Add TUF GAMING X670E PLUS WIFI
	Add X670E-I GAMING WIFI
	Add Z790-I GAMING WIFI

   - dell-smm: Add support for Dell OptiPlex 7040

   - ina238: Major cleanup, and
  	Add support for INA700
	Add support for INA780

   - k10temp:
  	Add device ID for Strix Halo
	Add support for AMD Family 1Ah-based models

   - lenovo-ec-sensors: Update P8 supprt

   - lm75: Add NXP P3T1750 support

   - pmbus/adm1275: Add sq24905c support

   - pmbus/isl68137: Add support for Renesas RAA228244 and RAA228246

   - pmbus/mp5990: Add support for MP5998

   - sht21: Add support for SHT20, SHT25

   - sl28cpld: Add sa67mcu compatible

  Other notable changes:

   - core:
	Handle locking internally
	Introduce 64-bit energy attribute support

   - cros_ec: Register into thermal framework, improve PWM control

   - lm75: allow interrupt for ti,tmp75

   - mlxreg-fan: Add support for new flavour of capability register

   - sbtsi_temp: AMD CPU extended temperature range support

   - sht21: Add devicetree support

  Various other minor improvements and fixes"

* tag 'hwmon-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (86 commits)
  dt-bindings: hwmon: (lm75) allow interrupt for ti,tmp75
  hwmon: (mlxreg-fan) Add support for new flavour of capability register
  hwmon: (mlxreg-fan) Separate methods of fan setting coming from different subsystems
  hwmon: (cros_ec) register fans into thermal framework cooling devices
  hwmon: (cros_ec) add PWM control over fans
  platform/chrome: update pwm fan control host commands
  hwmon: add SMARC-sAM67 support
  dt-bindings: hwmon: sl28cpld: add sa67mcu compatible
  hwmon: (asus-ec-sensors) add TUF GAMING X670E PLUS WIFI
  hwmon: (dell-smm) Add support for Dell OptiPlex 7040
  hwmon: (dell-smm) Add support for automatic fan mode
  hwmon: (gpd-fan) complete Kconfig dependencies
  hwmon: (asus-ec-sensors) increase timeout for locking ACPI mutex
  hwmon: (asus-ec-sensors) add ROG STRIX X870E-E GAMING WIFI
  hwmon: (dell-smm) Move clamping of fan speed out of i8k_set_fan()
  hwmon: (dell-smm) Remove Dell Precision 490 custom config data
  hwmon: (asus-ec-sensors) add ROG STRIX X670E-E GAMING WIFI
  hwmon: (gpd-fan) Fix range check for pwm input
  hwmon: (pmbus/mp5990) add support for MP5998
  dt-bindings: trivial-devices: add mps,mp5998
  ...
This commit is contained in:
Linus Torvalds 2025-10-01 09:42:51 -07:00
commit 989253cc46
61 changed files with 4283 additions and 594 deletions

View File

@ -18,6 +18,13 @@ description: |
Datasheets:
https://www.analog.com/en/products/adm1294.html
The SQ24905C is also a Hot-swap controller compatibility to the ADM1278,
the PMBUS_MFR_MODEL is MC09C
Datasheets:
https://www.silergy.com/
download/downloadFile?id=5669&type=product&ftype=note
properties:
compatible:
enum:
@ -30,6 +37,7 @@ properties:
- adi,adm1281
- adi,adm1293
- adi,adm1294
- silergy,mc09c
reg:
maxItems: 1
@ -96,6 +104,7 @@ allOf:
- adi,adm1281
- adi,adm1293
- adi,adm1294
- silergy,mc09c
then:
properties:
adi,volt-curr-sample-average:

View File

@ -16,6 +16,7 @@ description: |
properties:
compatible:
enum:
- kontron,sa67mcu-hwmon
- kontron,sl28cpld-fan
reg:

View File

@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/lantiq,cputemp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Lantiq cpu temperature sensor
maintainers:
- Florian Eckert <fe@dev.tdt.de>
properties:
compatible:
const: lantiq,cputemp
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
cputemp@103040 {
compatible = "lantiq,cputemp";
reg = <0x103040 0x4>;
};

View File

@ -28,6 +28,7 @@ properties:
- maxim,max31725
- maxim,max31726
- maxim,mcp980x
- nxp,p3t1750
- nxp,p3t1755
- nxp,pct2075
- st,stds75
@ -69,6 +70,7 @@ allOf:
- ti,tmp100
- ti,tmp101
- ti,tmp112
- ti,tmp75
then:
properties:
interrupts: false

View File

@ -1,10 +0,0 @@
Lantiq cpu temperature sensor
Requires node properties:
- compatible value :
"lantiq,cputemp"
Example:
cputemp@0 {
compatible = "lantiq,cputemp";
};

View File

@ -54,6 +54,8 @@ properties:
- renesas,raa228004
- renesas,raa228006
- renesas,raa228228
- renesas,raa228244
- renesas,raa228246
- renesas,raa229001
- renesas,raa229004
- renesas,raa229621

View File

@ -31,6 +31,15 @@ properties:
it must be self resetting edge interrupts.
maxItems: 1
fan-shutdown-percent:
description:
Fan RPM in percent set during shutdown. This is used to keep the fan
running at fixed RPM after the kernel shut down, which is useful on
hardware that does keep heating itself even after the kernel did shut
down, for example from some sort of management core.
minimum: 0
maximum: 100
fan-stop-to-start-percent:
description:
Minimum fan RPM in percent to start when stopped.

View File

@ -32,6 +32,8 @@ properties:
- ti,ina237
- ti,ina238
- ti,ina260
- ti,ina700
- ti,ina780
reg:
maxItems: 1
@ -114,10 +116,42 @@ allOf:
- ti,ina237
- ti,ina238
- ti,ina260
- ti,ina700
- ti,ina780
then:
properties:
ti,maximum-expected-current-microamp: false
- if:
properties:
compatible:
contains:
enum:
- silergy,sy24655
- ti,ina209
- ti,ina219
- ti,ina220
- ti,ina226
- ti,ina230
- ti,ina231
- ti,ina260
- ti,ina700
- ti,ina780
then:
properties:
ti,shunt-gain: false
- if:
properties:
compatible:
contains:
enum:
- ti,ina700
- ti,ina780
then:
properties:
shunt-resistor: false
unevaluatedProperties: false
examples:

View File

@ -20,6 +20,10 @@ properties:
reg:
maxItems: 1
label:
description:
A descriptive name for this channel, like "ambient" or "psu".
"#thermal-sensor-cells":
const: 1
@ -45,6 +49,7 @@ examples:
reg = <0x48>;
interrupt-parent = <&gpio7>;
interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
label = "somelabel";
vcc-supply = <&supply>;
#thermal-sensor-cells = <1>;
};

View File

@ -293,10 +293,20 @@ properties:
- mps,mp2856
# Monolithic Power Systems Inc. multi-phase controller mp2857
- mps,mp2857
# Monolithic Power Systems Inc. multi-phase controller mp2869
- mps,mp2869
# Monolithic Power Systems Inc. multi-phase controller mp2888
- mps,mp2888
# Monolithic Power Systems Inc. multi-phase controller mp2891
- mps,mp2891
# Monolithic Power Systems Inc. multi-phase controller mp29502
- mps,mp29502
# Monolithic Power Systems Inc. multi-phase controller mp29608
- mps,mp29608
# Monolithic Power Systems Inc. multi-phase controller mp29612
- mps,mp29612
# Monolithic Power Systems Inc. multi-phase controller mp29816
- mps,mp29816
# Monolithic Power Systems Inc. multi-phase controller mp2993
- mps,mp2993
# Monolithic Power Systems Inc. hot-swap protection device
@ -305,6 +315,8 @@ properties:
- mps,mp5920
# Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990
- mps,mp5990
# Monolithic Power Systems Inc. multi-phase hot-swap controller mp5998
- mps,mp5998
# Monolithic Power Systems Inc. digital step-down converter mp9941
- mps,mp9941
# Temperature sensor with integrated fan control
@ -362,6 +374,9 @@ properties:
# Sensirion low power multi-pixel gas sensor with I2C interface
- sensirion,sgpc3
# Sensirion temperature & humidity sensor with I2C interface
- sensirion,sht20
- sensirion,sht21
- sensirion,sht25
- sensirion,sht4x
# Sensortek 3 axis accelerometer
- sensortek,stk8312

View File

@ -67,6 +67,14 @@ Supported chips:
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1293_1294.pdf
* Silergy SQ24905C
Prefix: 'mc09c'
Addresses scanned: -
Datasheet: https://www.silergy.com/download/downloadFile?id=5669&type=product&ftype=note
Author: Guenter Roeck <linux@roeck-us.net>
@ -74,14 +82,14 @@ Description
-----------
This driver supports hardware monitoring for Analog Devices ADM1075, ADM1272,
ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and ADM1294 Hot-Swap
Controller and Digital Power Monitors.
ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, ADM1294, and SQ24905C
Hot-Swap Controller and Digital Power Monitors.
ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and
ADM1294 are hot-swap controllers that allow a circuit board to be removed from
or inserted into a live backplane. They also feature current and voltage
readback via an integrated 12 bit analog-to-digital converter (ADC), accessed
using a PMBus interface.
ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293,
ADM1294 and SQ24905C are hot-swap controllers that allow a circuit board to be
removed from or inserted into a live backplane. They also feature current and
voltage readback via an integrated 12 bit analog-to-digital converter (ADC),
accessed using a PMBus interface.
The driver is a client driver to the core PMBus driver. Please see
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
@ -160,5 +168,5 @@ temp1_highest Highest observed temperature.
temp1_reset_history Write any value to reset history.
Temperature attributes are supported on ADM1272,
ADM1273, ADM1278, and ADM1281.
ADM1273, ADM1278, ADM1281 and SQ24905C.
======================= =======================================================

View File

@ -8,7 +8,9 @@ Supported boards:
* PRIME X470-PRO
* PRIME X570-PRO
* PRIME X670E-PRO WIFI
* PRIME Z270-A
* Pro WS X570-ACE
* Pro WS WRX90E-SAGE SE
* ProArt X570-CREATOR WIFI
* ProArt X670E-CREATOR WIFI
* ProArt X870E-CREATOR WIFI
@ -25,16 +27,26 @@ Supported boards:
* ROG MAXIMUS Z690 FORMULA
* ROG STRIX B550-E GAMING
* ROG STRIX B550-I GAMING
* ROG STRIX B650E-I GAMING WIFI
* ROG STRIX B850-I GAMING WIFI
* ROG STRIX X570-E GAMING
* ROG STRIX X570-E GAMING WIFI II
* ROG STRIX X570-F GAMING
* ROG STRIX X570-I GAMING
* ROG STRIX X670E-E GAMING WIFI
* ROG STRIX X670E-I GAMING WIFI
* ROG STRIX X870-I GAMING WIFI
* ROG STRIX X870E-E GAMING WIFI
* ROG STRIX Z390-F GAMING
* ROG STRIX Z490-F GAMING
* ROG STRIX Z690-A GAMING WIFI D4
* ROG STRIX Z690-E GAMING WIFI
* ROG STRIX Z790-E GAMING WIFI II
* ROG STRIX Z790-I GAMING WIFI
* ROG ZENITH II EXTREME
* ROG ZENITH II EXTREME ALPHA
* TUF GAMING X670E PLUS
* TUF GAMING X670E PLUS WIFI
Authors:
- Eugene Shalygin <eugene.shalygin@gmail.com>

View File

@ -23,4 +23,9 @@ ChromeOS embedded controller used in Chromebooks and other devices.
The channel labels exposed via hwmon are retrieved from the EC itself.
Fan and temperature readings are supported.
Fan and temperature readings are supported. PWM fan control is also supported if
the EC also supports setting fan PWM values and fan mode. Note that EC will
switch fan control mode back to auto when suspended. This driver will restore
the fan state to what they were before suspended when resumed.
If a fan is controllable, this driver will register that fan as a cooling device
in the thermal framework as well.

View File

@ -43,7 +43,7 @@ curr1_label "iin"
curr1_input Measured input current
curr1_max Maximum input current
curr1_max_alarm Input maximum current high alarm
curr1_crit Critial high input current
curr1_crit Critical high input current
curr1_crit_alarm Input critical current high alarm
curr1_rated_max Maximum rated input current
@ -51,7 +51,7 @@ curr2_label "iout1"
curr2_input Measured output current
curr2_max Maximum output current
curr2_max_alarm Output maximum current high alarm
curr2_crit Critial high output current
curr2_crit Critical high output current
curr2_crit_alarm Output critical current high alarm
curr2_rated_max Maximum rated output current

View File

@ -38,7 +38,7 @@ fan[1-4]_min RO Minimal Fan speed in RPM
fan[1-4]_max RO Maximal Fan speed in RPM
fan[1-4]_target RO Expected Fan speed in RPM
pwm[1-4] RW Control the fan PWM duty-cycle.
pwm1_enable WO Enable or disable automatic BIOS fan
pwm[1-4]_enable RW/WO Enable or disable automatic BIOS fan
control (not supported on all laptops,
see below for details).
temp[1-10]_input RO Temperature reading in milli-degrees
@ -49,26 +49,40 @@ temp[1-10]_label RO Temperature sensor label.
Due to the nature of the SMM interface, each pwmX attribute controls
fan number X.
Disabling automatic BIOS fan control
------------------------------------
Enabling/Disabling automatic BIOS fan control
---------------------------------------------
On some laptops the BIOS automatically sets fan speed every few
seconds. Therefore the fan speed set by mean of this driver is quickly
overwritten.
There exist two methods for enabling/disabling automatic BIOS fan control:
There is experimental support for disabling automatic BIOS fan
control, at least on laptops where the corresponding SMM command is
known, by writing the value ``1`` in the attribute ``pwm1_enable``
(writing ``2`` enables automatic BIOS control again). Even if you have
more than one fan, all of them are set to either enabled or disabled
automatic fan control at the same time and, notwithstanding the name,
``pwm1_enable`` sets automatic control for all fans.
1. Separate SMM commands to enable/disable automatic BIOS fan control for all fans.
If ``pwm1_enable`` is not available, then it means that SMM codes for
enabling and disabling automatic BIOS fan control are not whitelisted
for your hardware. It is possible that codes that work for other
laptops actually work for yours as well, or that you have to discover
new codes.
2. A special fan state that enables automatic BIOS fan control for a individual fan.
The driver cannot reliably detect what method should be used on a given
device, so instead the following heuristic is used:
- use fan state 3 for enabling BIOS fan control if the maximum fan state
setable by the user is smaller than 3 (default setting).
- use separate SMM commands if device is whitelisted to support them.
When using the first method, each fan will have a standard ``pwmX_enable``
sysfs attribute. Writing ``1`` into this attribute will disable automatic
BIOS fan control for the associated fan and set it to maximum speed. Enabling
BIOS fan control again can be achieved by writing ``2`` into this attribute.
Reading this sysfs attributes returns the current setting as reported by
the underlying hardware.
When using the second method however, only the ``pwm1_enable`` sysfs attribute
will be available to enable/disable automatic BIOS fan control globaly for all
fans available on a given device. Additionally, this sysfs attribute is write-only
as there exists no SMM command for reading the current fan control setting.
If no ``pwmX_enable`` attributes are available, then it means that the driver
cannot use the first method and the SMM codes for enabling and disabling automatic
BIOS fan control are not whitelisted for your device. It is possible that codes
that work for other laptops actually work for yours as well, or that you have to
discover new codes.
Check the list ``i8k_whitelist_fan_control`` in file
``drivers/hwmon/dell-smm-hwmon.c`` in the kernel tree: as a first

View File

@ -0,0 +1,78 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver gpd-fan
=========================
Author:
- Cryolitia PukNgae <cryolitia@uniontech.com>
Description
------------
Handheld devices from Shenzhen GPD Technology Co., Ltd. provide fan readings
and fan control through their embedded controllers.
Supported devices
-----------------
Currently the driver supports the following handhelds:
- GPD Win Mini (7840U)
- GPD Win Mini (8840U)
- GPD Win Mini (HX370)
- GPD Pocket 4
- GPD Duo
- GPD Win Max 2 (6800U)
- GPD Win Max 2 2023 (7840U)
- GPD Win Max 2 2024 (8840U)
- GPD Win Max 2 2025 (HX370)
- GPD Win 4 (6800U)
- GPD Win 4 (7840U)
Module parameters
-----------------
gpd_fan_board
Force specific which module quirk should be used.
Use it like "gpd_fan_board=wm2".
- wm2
- GPD Win 4 (7840U)
- GPD Win Max 2 (6800U)
- GPD Win Max 2 2023 (7840U)
- GPD Win Max 2 2024 (8840U)
- GPD Win Max 2 2025 (HX370)
- win4
- GPD Win 4 (6800U)
- win_mini
- GPD Win Mini (7840U)
- GPD Win Mini (8840U)
- GPD Win Mini (HX370)
- GPD Pocket 4
- GPD Duo
Sysfs entries
-------------
The following attributes are supported:
fan1_input
Read Only. Reads current fan RPM.
pwm1_enable
Read/Write. Enable manual fan control. Write "0" to disable control and run
at full speed. Write "1" to set to manual, write "2" to let the EC control
decide fan speed. Read this attribute to see current status.
NBIn consideration of the safety of the device, when setting to manual mode,
the pwm speed will be set to the maximum value (255) by default. You can set
a different value by writing pwm1 later.
pwm1
Read/Write. Read this attribute to see current duty cycle in the range
[0-255]. When pwm1_enable is set to "1" (manual) write any value in the
range [0-255] to set fan speed.
NB: Many boards (except listed under wm2 above) don't support reading the
current pwm value in auto mode. That will just return EOPNOTSUPP. In manual
mode it will always return the real value.

View File

@ -42,6 +42,9 @@ register/unregister functions::
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
void hwmon_lock(struct device *dev);
void hwmon_unlock(struct device *dev);
hwmon_device_register_with_info registers a hardware monitoring device.
It creates the standard sysfs attributes in the hardware monitoring core,
letting the driver focus on reading from and writing to the chip instead
@ -79,6 +82,13 @@ devm_hwmon_sanitize_name is the resource managed version of
hwmon_sanitize_name; the memory will be freed automatically on device
removal.
When using ``[devm_]hwmon_device_register_with_info()`` to register the
hardware monitoring device, accesses using the associated access functions
are serialised by the hardware monitoring core. If a driver needs locking
for other functions such as interrupt handlers or for attributes which are
fully implemented in the driver, hwmon_lock() and hwmon_unlock() can be used
to ensure that calls to those functions are serialized.
Using devm_hwmon_device_register_with_info()
--------------------------------------------
@ -159,6 +169,7 @@ It contains following fields:
hwmon_curr Current sensor
hwmon_power Power sensor
hwmon_energy Energy sensor
hwmon_energy64 Energy sensor, reported as 64-bit signed value
hwmon_humidity Humidity sensor
hwmon_fan Fan speed sensor
hwmon_pwm PWM control
@ -288,6 +299,8 @@ Parameters:
The sensor channel number.
val:
Pointer to attribute value.
For hwmon_energy64, `'val`' is passed as `long *` but needs
a typecast to `s64 *`.
Return value:
0 on success, a negative error number otherwise.

View File

@ -5,6 +5,24 @@ Kernel driver ina238
Supported chips:
* Texas Instruments INA228
Prefix: 'ina228'
Addresses: I2C 0x40 - 0x4f
Datasheet:
https://www.ti.com/lit/gpn/ina228
* Texas Instruments INA237
Prefix: 'ina237'
Addresses: I2C 0x40 - 0x4f
Datasheet:
https://www.ti.com/lit/gpn/ina237
* Texas Instruments INA238
Prefix: 'ina238'
@ -14,6 +32,16 @@ Supported chips:
Datasheet:
https://www.ti.com/lit/gpn/ina238
* Texas Instruments INA700
Datasheet:
https://www.ti.com/product/ina700
* Texas Instruments INA780
Datasheet:
https://www.ti.com/product/ina780a
* Silergy SQ52206
Prefix: 'SQ52206'
@ -29,10 +57,20 @@ 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.
The shunt value in micro-ohms can be set via device properties, either from
platform code or from device tree data. Please refer to
Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings if
device tree is used.
INA237 is a functionally equivalent variant of INA238 with slightly
different accuracy. INA228 is another variant of INA238 with higher ADC
resolution. This chip also reports the energy.
INA700 and INA780 are variants of the chip series with built-in shunt resistor.
They also report the energy.
SQ52206 is a mostly compatible chip from Sylergy. It reports the energy
as well as the peak power consumption.
Sysfs entries
-------------
@ -53,19 +91,19 @@ in1_max_alarm Maximum shunt voltage alarm
power1_input Power measurement (uW)
power1_max Maximum power threshold (uW)
power1_max_alarm Maximum power alarm
power1_input_highest Peak Power (uW)
(SQ52206 only)
curr1_input Current measurement (mA)
curr1_min Minimum current threshold (mA)
curr1_min_alarm Minimum current alarm
curr1_max Maximum current threshold (mA)
curr1_max_alarm Maximum current alarm
energy1_input Energy measurement (uJ)
(SQ52206, INA237, and INA780 only)
temp1_input Die temperature measurement (mC)
temp1_max Maximum die temperature threshold (mC)
temp1_max_alarm Maximum die temperature alarm
======================= =======================================================
Additional sysfs entries for sq52206
------------------------------------
======================= =======================================================
energy1_input Energy measurement (uJ)
power1_input_highest Peak Power (uW)
======================= =======================================================

View File

@ -82,6 +82,7 @@ Hardware Monitoring Kernel Drivers
gigabyte_waterforce
gsc-hwmon
gl518sm
gpd-fan
gxp-fan-ctrl
hih6130
hp-wmi-sensors
@ -173,8 +174,10 @@ Hardware Monitoring Kernel Drivers
menf21bmc
mlxreg-fan
mp2856
mp2869
mp2888
mp2891
mp29502
mp2975
mp2993
mp5023
@ -211,6 +214,7 @@ Hardware Monitoring Kernel Drivers
q54sj108a2
qnap-mcu-hwmon
raspberrypi-hwmon
sa67
sbrmi
sbtsi_temp
sch5627

View File

@ -374,6 +374,26 @@ Supported chips:
Publicly available (after August 2020 launch) at the Renesas website
* Renesas RAA228244
Prefix: 'raa228244'
Addresses scanned: -
Datasheet:
Provided by Renesas upon request and NDA
* Renesas RAA228246
Prefix: 'raa228246'
Addresses scanned: -
Datasheet:
Provided by Renesas upon request and NDA
* Renesas RAA229001
Prefix: 'raa229001'

View File

@ -121,9 +121,9 @@ Supported chips:
https://www.ti.com/product/TMP1075
* NXP LM75B, P3T1755, PCT2075
* NXP LM75B, P3T1750, P3T1755, PCT2075
Prefix: 'lm75b', 'p3t1755', 'pct2075'
Prefix: 'lm75b', 'p3t1750', 'p3t1755', 'pct2075'
Addresses scanned: none
@ -131,6 +131,8 @@ Supported chips:
https://www.nxp.com/docs/en/data-sheet/LM75B.pdf
https://www.nxp.com/docs/en/data-sheet/P3T1750DP.pdf
https://www.nxp.com/docs/en/data-sheet/P3T1755.pdf
https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf

View File

@ -0,0 +1,175 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver mp2869
====================
Supported chips:
* MPS mp2869
Prefix: 'mp2869'
* MPS mp29608
Prefix: 'mp29608'
* MPS mp29612
Prefix: 'mp29612'
* MPS mp29816
Prefix: 'mp29816'
Author:
Wensheng Wang <wenswang@yeah.net>
Description
-----------
This driver implements support for Monolithic Power Systems, Inc. (MPS)
MP2869 Dual Loop Digital Multi-phase Controller.
Device compliant with:
- PMBus rev 1.3 interface.
The driver exports the following attributes via the 'sysfs' files
for input voltage:
**in1_input**
**in1_label**
**in1_crit**
**in1_crit_alarm**
**in1_lcrit**
**in1_lcrit_alarm**
**in1_min**
**in1_min_alarm**
The driver provides the following attributes for output voltage:
**in2_input**
**in2_label**
**in2_crit**
**in2_crit_alarm**
**in2_lcrit**
**in2_lcrit_alarm**
**in3_input**
**in3_label**
**in3_crit**
**in3_crit_alarm**
**in3_lcrit**
**in3_lcrit_alarm**
The driver provides the following attributes for input current:
**curr1_input**
**curr1_label**
**curr2_input**
**curr2_label**
The driver provides the following attributes for output current:
**curr3_input**
**curr3_label**
**curr3_crit**
**curr3_crit_alarm**
**curr3_max**
**curr3_max_alarm**
**curr4_input**
**curr4_label**
**curr4_crit**
**curr4_crit_alarm**
**curr4_max**
**curr4_max_alarm**
The driver provides the following attributes for input power:
**power1_input**
**power1_label**
**power2_input**
**power2_label**
The driver provides the following attributes for output power:
**power3_input**
**power3_label**
**power3_input**
**power3_label**
**power3_max**
**power3_max_alarm**
**power4_input**
**power4_label**
**power4_input**
**power4_label**
**power4_max**
**power4_max_alarm**
The driver provides the following attributes for temperature:
**temp1_input**
**temp1_crit**
**temp1_crit_alarm**
**temp1_max**
**temp1_max_alarm**
**temp2_input**
**temp2_crit**
**temp2_crit_alarm**
**temp2_max**
**temp2_max_alarm**

View File

@ -0,0 +1,93 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver mp29502
=====================
Supported chips:
* MPS mp29502
Prefix: 'mp29502'
Author:
Wensheng Wang <wenswang@yeah.net>
Description
-----------
This driver implements support for Monolithic Power Systems, Inc. (MPS)
MP29502 Digital Multi-phase Controller.
Device compliant with:
- PMBus rev 1.3 interface.
The driver exports the following attributes via the 'sysfs' files
for input voltage:
**in1_input**
**in1_label**
**in1_crit**
**in1_crit_alarm**
The driver provides the following attributes for output voltage:
**in2_input**
**in2_label**
**in2_crit**
**in2_crit_alarm**
**in2_lcrit**
**in2_lcrit_alarm**
The driver provides the following attributes for input current:
**curr1_input**
**curr1_label**
The driver provides the following attributes for output current:
**curr2_input**
**curr2_label**
**curr2_crit**
**curr2_crit_alarm**
**curr2_max**
**curr2_max_alarm**
The driver provides the following attributes for input power:
**power1_input**
**power1_label**
The driver provides the following attributes for output power:
**power2_input**
**power2_label**
The driver provides the following attributes for temperature:
**temp1_input**
**temp1_crit**
**temp1_crit_alarm**
**temp1_max**
**temp1_max_alarm**

View File

@ -9,9 +9,13 @@ Supported chips:
Prefix: 'mp5990'
* Datasheet
Datasheet: Publicly available at the MPS website: https://www.monolithicpower.com/en/mp5990.html
Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5990.html
* MPS MP5998
Prefix: 'mp5998'
Datasheet: Not publicly available
Author:
@ -21,7 +25,7 @@ Description
-----------
This driver implements support for Monolithic Power Systems, Inc. (MPS)
MP5990 Hot-Swap Controller.
MP5990 and MP5998 Hot-Swap Controller.
Device compliant with:
@ -53,7 +57,7 @@ The driver provides the following attributes for output voltage:
**in2_alarm**
The driver provides the following attributes for output current:
The driver provides the following attributes for current:
**curr1_input**
@ -63,6 +67,14 @@ The driver provides the following attributes for output current:
**curr1_max**
**curr2_input**
**curr2_label**
**curr2_max**
**curr2_max_alarm**
The driver provides the following attributes for input power:
**power1_input**
@ -71,6 +83,16 @@ The driver provides the following attributes for input power:
**power1_alarm**
The driver provides the following attributes for output power:
**power2_input**
**power2_label**
**power2_max**
**power2_max_alarm**
The driver provides the following attributes for temperature:
**temp1_input**

View File

@ -0,0 +1,41 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel driver sa67mcu
=====================
Supported chips:
* Kontron sa67mcu
Prefix: 'sa67mcu'
Datasheet: not available
Authors: Michael Walle <mwalle@kernel.org>
Description
-----------
The sa67mcu is a board management controller which also exposes a hardware
monitoring controller.
The controller has two voltage and one temperature sensor. The values are
hold in two 8 bit registers to form one 16 bit value. Reading the lower byte
will also capture the high byte to make the access atomic. The unit of the
volatge sensors are 1mV and the unit of the temperature sensor is 0.1degC.
Sysfs entries
-------------
The following attributes are supported.
======================= ========================================================
in0_label "VDDIN"
in0_input Measured VDDIN voltage.
in1_label "VDD_RTC"
in1_input Measured VDD_RTC voltage.
temp1_input MCU temperature. Roughly the board temperature.
======================= ========================================================

View File

@ -3,6 +3,16 @@ Kernel driver sht21
Supported chips:
* Sensirion SHT20
Prefix: 'sht20'
Addresses scanned: none
Datasheet: Publicly available at the Sensirion website
https://www.sensirion.com/file/datasheet_sht20
* Sensirion SHT21
Prefix: 'sht21'
@ -13,8 +23,6 @@ Supported chips:
https://www.sensirion.com/file/datasheet_sht21
* Sensirion SHT25
Prefix: 'sht25'
@ -25,8 +33,6 @@ Supported chips:
https://www.sensirion.com/file/datasheet_sht25
Author:
Urs Fleisch <urs.fleisch@sensirion.com>
@ -47,13 +53,11 @@ in the board setup code.
sysfs-Interface
---------------
temp1_input
- temperature input
humidity1_input
- humidity input
eic
- Electronic Identification Code
=================== ============================================================
temp1_input Temperature input
humidity1_input Humidity input
eic Electronic Identification Code
=================== ============================================================
Notes
-----

View File

@ -6280,9 +6280,8 @@ F: tools/testing/selftests/cgroup/test_kmem.c
F: tools/testing/selftests/cgroup/test_memcontrol.c
CORETEMP HARDWARE MONITORING DRIVER
M: Fenghua Yu <fenghua.yu@intel.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
S: Orphan
F: Documentation/hwmon/coretemp.rst
F: drivers/hwmon/coretemp.c
@ -10456,6 +10455,13 @@ F: drivers/phy/samsung/phy-gs101-ufs.c
F: include/dt-bindings/clock/google,gs101.h
K: [gG]oogle.?[tT]ensor
GPD FAN DRIVER
M: Cryolitia PukNgae <cryolitia@uniontech.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/gpd-fan.rst
F: drivers/hwmon/gpd-fan.c
GPD POCKET FAN DRIVER
M: Hans de Goede <hansg@kernel.org>
L: platform-driver-x86@vger.kernel.org
@ -10752,7 +10758,6 @@ W: http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
F: drivers/platform/x86/hdaps.c
HARDWARE MONITORING
M: Jean Delvare <jdelvare@suse.com>
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
@ -17208,6 +17213,13 @@ S: Maintained
F: Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml
F: drivers/video/backlight/mp3309c.c
MPS MP2869 DRIVER
M: Wensheng Wang <wenswang@yeah.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/mp2869.rst
F: drivers/hwmon/pmbus/mp2869.c
MPS MP2891 DRIVER
M: Noah Wang <noahwang.wang@outlook.com>
L: linux-hwmon@vger.kernel.org
@ -17215,6 +17227,13 @@ S: Maintained
F: Documentation/hwmon/mp2891.rst
F: drivers/hwmon/pmbus/mp2891.c
MPS MP29502 DRIVER
M: Wensheng Wang <wenswang@yeah.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/mp29502.rst
F: drivers/hwmon/pmbus/mp29502.c
MPS MP2993 DRIVER
M: Noah Wang <noahwang.wang@outlook.com>
L: linux-hwmon@vger.kernel.org
@ -23249,6 +23268,7 @@ F: Documentation/devicetree/bindings/mfd/kontron,sl28cpld.yaml
F: Documentation/devicetree/bindings/pwm/kontron,sl28cpld-pwm.yaml
F: Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml
F: drivers/gpio/gpio-sl28cpld.c
F: drivers/hwmon/sa67mcu-hwmon.c
F: drivers/hwmon/sl28cpld-hwmon.c
F: drivers/irqchip/irq-sl28cpld.c
F: drivers/pwm/pwm-sl28cpld.c

View File

@ -769,6 +769,16 @@ config SENSORS_GL520SM
This driver can also be built as a module. If so, the module
will be called gl520sm.
config SENSORS_GPD
tristate "GPD handhelds"
depends on X86 && DMI && HAS_IOPORT
help
If you say yes here you get support for fan readings and
control over GPD handheld devices.
Can also be built as a module. In that case it will be
called gpd-fan.
config SENSORS_G760A
tristate "GMT G760A"
depends on I2C
@ -1895,6 +1905,16 @@ config SENSORS_RASPBERRYPI_HWMON
This driver can also be built as a module. If so, the module
will be called raspberrypi-hwmon.
config SENSORS_SA67MCU
tristate "Kontron sa67mcu hardware monitoring driver"
depends on MFD_SL28CPLD || COMPILE_TEST
help
If you say yes here you get support for the voltage and temperature
monitor of the sa67 board management controller.
This driver can also be built as a module. If so, the module
will be called sa67mcu-hwmon.
config SENSORS_SL28CPLD
tristate "Kontron sl28cpld hardware monitoring driver"
depends on MFD_SL28CPLD || COMPILE_TEST
@ -1930,8 +1950,8 @@ config SENSORS_SHT21
tristate "Sensiron humidity and temperature sensors. SHT21 and compat."
depends on I2C
help
If you say yes here you get support for the Sensiron SHT21, SHT25
humidity and temperature sensors.
If you say yes here you get support for the Sensiron SHT20, SHT21,
SHT25 humidity and temperature sensors.
This driver can also be built as a module. If so, the module
will be called sht21.
@ -2252,13 +2272,14 @@ config SENSORS_INA2XX
will be called ina2xx.
config SENSORS_INA238
tristate "Texas Instruments INA238"
tristate "Texas Instruments INA238 and compatibles"
depends on I2C
select REGMAP_I2C
help
If you say yes here you get support for the INA238 power monitor
chip. This driver supports voltage, current, power and temperature
measurements as well as alarm configuration.
If you say yes here you get support for INA228, INA237, INA238,
INA700, INA780, and SQ52206 power monitor chips. This driver supports
voltage, current, power, energy, and temperature measurements as well
as alarm configuration.
This driver can also be built as a module. If so, the module
will be called ina238.
@ -2673,9 +2694,10 @@ config SENSORS_ASUS_EC
depends on ACPI_EC
help
If you say yes here you get support for the ACPI embedded controller
hardware monitoring interface found in ASUS motherboards. The driver
currently supports B550/X570 boards, although other ASUS boards might
provide this monitoring interface as well.
hardware monitoring interface found in some ASUS motherboards. This is
where such sensors as water flow and temperature, optional fans, and
additional temperature sensors (T_Sensor, chipset temperatures)
find themselves.
This driver can also be built as a module. If so, the module
will be called asus_ec_sensors.

View File

@ -88,6 +88,7 @@ obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
@ -196,6 +197,7 @@ obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o

View File

@ -49,15 +49,19 @@ static char *mutex_path_override;
*/
#define ASUS_EC_MAX_BANK 3
#define ACPI_LOCK_DELAY_MS 500
#define ACPI_LOCK_DELAY_MS 800
/* ACPI mutex for locking access to the EC for the firmware */
#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX"
#define ASUS_HW_ACCESS_MUTEX_RMTW_ASMX "\\RMTW.ASMX"
#define ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0 "\\_SB.PC00.LPCB.SIO1.MUT0"
#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0 "\\_SB_.PCI0.SBRG.SIO1.MUT0"
#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0 "\\_SB_.PCI0.LPCB.SIO1.MUT0"
#define MAX_IDENTICAL_BOARD_VARIATIONS 3
/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */
@ -115,10 +119,18 @@ enum ec_sensors {
ec_sensor_fan_cpu_opt,
/* VRM heat sink fan [RPM] */
ec_sensor_fan_vrm_hs,
/* VRM east heat sink fan [RPM] */
ec_sensor_fan_vrme_hs,
/* VRM west heat sink fan [RPM] */
ec_sensor_fan_vrmw_hs,
/* Chipset fan [RPM] */
ec_sensor_fan_chipset,
/* Water flow sensor reading [RPM] */
ec_sensor_fan_water_flow,
/* USB4 fan [RPM] */
ec_sensor_fan_usb4,
/* M.2 fan [RPM] */
ec_sensor_fan_m2,
/* CPU current [A] */
ec_sensor_curr_cpu,
/* "Water_In" temperature sensor reading [℃] */
@ -148,8 +160,12 @@ enum ec_sensors {
#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core)
#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt)
#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs)
#define SENSOR_FAN_VRME_HS BIT(ec_sensor_fan_vrme_hs)
#define SENSOR_FAN_VRMW_HS BIT(ec_sensor_fan_vrmw_hs)
#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset)
#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow)
#define SENSOR_FAN_USB4 BIT(ec_sensor_fan_usb4)
#define SENSOR_FAN_M2 BIT(ec_sensor_fan_m2)
#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu)
#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in)
#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out)
@ -166,9 +182,12 @@ enum board_family {
family_amd_500_series,
family_amd_600_series,
family_amd_800_series,
family_amd_wrx_90,
family_intel_200_series,
family_intel_300_series,
family_intel_400_series,
family_intel_600_series
family_intel_600_series,
family_intel_700_series
};
/*
@ -275,6 +294,33 @@ static const struct ec_sensor_info sensors_family_amd_800[] = {
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
};
static const struct ec_sensor_info sensors_family_amd_wrx_90[] = {
[ec_sensor_temp_cpu_package] =
EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
[ec_sensor_fan_cpu_opt] =
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
[ec_sensor_fan_vrmw_hs] =
EC_SENSOR("VRMW HS", hwmon_fan, 2, 0x00, 0xb4),
[ec_sensor_fan_usb4] = EC_SENSOR("USB4", hwmon_fan, 2, 0x00, 0xb6),
[ec_sensor_fan_vrme_hs] =
EC_SENSOR("VRME HS", hwmon_fan, 2, 0x00, 0xbc),
[ec_sensor_fan_m2] = EC_SENSOR("M.2", hwmon_fan, 2, 0x00, 0xbe),
[ec_sensor_temp_t_sensor] =
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04),
};
static const struct ec_sensor_info sensors_family_intel_200[] = {
[ec_sensor_temp_chipset] =
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
[ec_sensor_temp_mb] =
EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
[ec_sensor_temp_t_sensor] =
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
[ec_sensor_fan_cpu_opt] =
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
};
static const struct ec_sensor_info sensors_family_intel_300[] = {
[ec_sensor_temp_chipset] =
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
@ -323,6 +369,16 @@ static const struct ec_sensor_info sensors_family_intel_600[] = {
EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02),
};
static const struct ec_sensor_info sensors_family_intel_700[] = {
[ec_sensor_temp_t_sensor] =
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x09),
[ec_sensor_temp_t_sensor_2] =
EC_SENSOR("T_Sensor 2", hwmon_temp, 1, 0x01, 0x05),
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
[ec_sensor_fan_cpu_opt] =
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
};
/* Shortcuts for common combinations */
#define SENSOR_SET_TEMP_CHIPSET_CPU_MB \
(SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB)
@ -343,6 +399,52 @@ struct ec_board_info {
enum board_family family;
};
static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_viii_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_viii_impact = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_x670e_gene = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_crosshair_x670e_hero = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_SET_TEMP_WATER,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_maximus_vi_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
@ -352,6 +454,22 @@ static const struct ec_board_info board_info_maximus_vi_hero = {
.family = family_intel_300_series,
};
static const struct ec_board_info board_info_maximus_xi_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_intel_300_series,
};
static const struct ec_board_info board_info_maximus_z690_formula = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
.family = family_intel_600_series,
};
static const struct ec_board_info board_info_prime_x470_pro = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
@ -376,6 +494,21 @@ static const struct ec_board_info board_info_prime_x670e_pro_wifi = {
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_prime_z270_a = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0,
.family = family_intel_200_series,
};
static const struct ec_board_info board_info_pro_art_b550_creator = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_FAN_CPU_OPT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_pro_art_x570_creator_wifi = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
@ -396,16 +529,17 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
.family = family_amd_800_series,
};
static const struct ec_board_info board_info_pro_art_b550_creator = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_FAN_CPU_OPT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
static const struct ec_board_info board_info_pro_ws_wrx90e_sage_se = {
/* Board also has a nct6798 with 7 more fans and temperatures */
.sensors = SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_USB4 | SENSOR_FAN_M2 |
SENSOR_FAN_VRME_HS | SENSOR_FAN_VRMW_HS,
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
.family = family_amd_wrx_90,
};
static const struct ec_board_info board_info_pro_ws_x570_ace = {
@ -416,68 +550,6 @@ static const struct ec_board_info board_info_pro_ws_x570_ace = {
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_x670e_hero = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_SET_TEMP_WATER,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_crosshair_x670e_gene = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_viii_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_maximus_xi_hero = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_intel_300_series,
};
static const struct ec_board_info board_info_maximus_z690_formula = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
.family = family_intel_600_series,
};
static const struct ec_board_info board_info_crosshair_viii_impact = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_strix_b550_e_gaming = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
@ -495,6 +567,20 @@ static const struct ec_board_info board_info_strix_b550_i_gaming = {
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_strix_b650e_i_gaming = {
.sensors = SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR |
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_IN_CPU_CORE,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_800_series,
};
static const struct ec_board_info board_info_strix_x570_e_gaming = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
@ -528,6 +614,35 @@ static const struct ec_board_info board_info_strix_x570_i_gaming = {
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_strix_x670e_e_gaming_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
.family = family_amd_800_series,
};
static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_FAN_CPU_OPT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
.family = family_amd_800_series,
};
static const struct ec_board_info board_info_strix_z390_f_gaming = {
.sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR |
@ -554,6 +669,35 @@ static const struct ec_board_info board_info_strix_z690_a_gaming_wifi_d4 = {
.family = family_intel_600_series,
};
static const struct ec_board_info board_info_strix_z690_e_gaming_wifi = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
.family = family_intel_600_series,
};
static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
SENSOR_FAN_CPU_OPT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
.family = family_intel_700_series,
};
static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 |
SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
.family = family_intel_700_series,
};
static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
SENSOR_FAN_CPU_OPT,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_zenith_ii_extreme = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
@ -566,15 +710,6 @@ static const struct ec_board_info board_info_zenith_ii_extreme = {
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
SENSOR_FAN_CPU_OPT,
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
.family = family_amd_600_series,
};
#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, board_info) \
{ \
.matches = { \
@ -594,14 +729,18 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_prime_x570_pro),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X670E-PRO WIFI",
&board_info_prime_x670e_pro_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z270-A",
&board_info_prime_z270_a),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR",
&board_info_pro_art_b550_creator),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI",
&board_info_pro_art_x570_creator_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X670E-CREATOR WIFI",
&board_info_pro_art_x670E_creator_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI",
&board_info_pro_art_x870E_creator_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR",
&board_info_pro_art_b550_creator),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS WRX90E-SAGE SE",
&board_info_pro_ws_wrx90e_sage_se),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE",
&board_info_pro_ws_x570_ace),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO",
@ -612,22 +751,26 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_crosshair_viii_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO (WI-FI)",
&board_info_crosshair_viii_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
&board_info_crosshair_x670e_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
&board_info_crosshair_viii_impact),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE",
&board_info_crosshair_x670e_gene),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
&board_info_crosshair_x670e_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO",
&board_info_maximus_xi_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)",
&board_info_maximus_xi_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA",
&board_info_maximus_z690_formula),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
&board_info_crosshair_viii_impact),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
&board_info_strix_b550_e_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING",
&board_info_strix_b550_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B650E-I GAMING WIFI",
&board_info_strix_b650e_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI",
&board_info_strix_b850_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING",
&board_info_strix_x570_e_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING WIFI II",
@ -636,18 +779,34 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_strix_x570_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING",
&board_info_strix_x570_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-E GAMING WIFI",
&board_info_strix_x670e_e_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI",
&board_info_strix_x670e_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI",
&board_info_strix_x870_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI",
&board_info_strix_x870e_e_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING",
&board_info_strix_z390_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING",
&board_info_strix_z490_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4",
&board_info_strix_z690_a_gaming_wifi_d4),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-E GAMING WIFI",
&board_info_strix_z690_e_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II",
&board_info_strix_z790_e_gaming_wifi_ii),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI",
&board_info_strix_z790_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME",
&board_info_zenith_ii_extreme),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME ALPHA",
&board_info_zenith_ii_extreme),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS",
&board_info_tuf_gaming_x670e_plus),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS WIFI",
&board_info_tuf_gaming_x670e_plus),
{},
};
@ -1115,6 +1274,12 @@ static int asus_ec_probe(struct platform_device *pdev)
case family_amd_800_series:
ec_data->sensors_info = sensors_family_amd_800;
break;
case family_amd_wrx_90:
ec_data->sensors_info = sensors_family_amd_wrx_90;
break;
case family_intel_200_series:
ec_data->sensors_info = sensors_family_intel_200;
break;
case family_intel_300_series:
ec_data->sensors_info = sensors_family_intel_300;
break;
@ -1124,6 +1289,9 @@ static int asus_ec_probe(struct platform_device *pdev)
case family_intel_600_series:
ec_data->sensors_info = sensors_family_intel_600;
break;
case family_intel_700_series:
ec_data->sensors_info = sensors_family_intel_700;
break;
default:
dev_err(dev, "Unknown board family: %d",
ec_data->board_info->family);

View File

@ -122,29 +122,29 @@ static const struct tjmax tjmax_table[] = {
};
struct tjmax_model {
u8 model;
u8 mask;
u32 vfm;
u8 stepping_mask;
int tjmax;
};
#define ANY 0xff
static const struct tjmax_model tjmax_model_table[] = {
{ 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
{ 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others
* Note: Also matches 230 and 330,
* which are covered by tjmax_table
*/
{ 0x26, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
* Note: TjMax for E6xxT is 110C, but CPU type
* is undetectable by software
*/
{ 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */
{ 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
{ 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
* Also matches S12x0 (stepping 9), covered by
* PCI table
*/
{ INTEL_ATOM_BONNELL, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
{ INTEL_ATOM_BONNELL, ANY, 90000 }, /* Z5xx, N2xx, possibly others
* Note: Also matches 230 and 330,
* which are covered by tjmax_table
*/
{ INTEL_ATOM_BONNELL_MID, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
* Note: TjMax for E6xxT is 110C, but CPU type
* is undetectable by software
*/
{ INTEL_ATOM_SALTWELL_MID, ANY, 90000 }, /* Atom Medfield (Z2460) */
{ INTEL_ATOM_SALTWELL_TABLET, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
{ INTEL_ATOM_SALTWELL, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
* Also matches S12x0 (stepping 9), covered by
* PCI table
*/
};
static bool is_pkg_temp_data(struct temp_data *tdata)
@ -180,6 +180,11 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
}
pci_dev_put(host_bridge);
/*
* This is literally looking for "CPU XXX" in the model string.
* Not checking it against the model as well. Just purely a
* string search.
*/
for (i = 0; i < ARRAY_SIZE(tjmax_table); i++) {
if (strstr(c->x86_model_id, tjmax_table[i].id))
return tjmax_table[i].tjmax;
@ -187,17 +192,18 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
for (i = 0; i < ARRAY_SIZE(tjmax_model_table); i++) {
const struct tjmax_model *tm = &tjmax_model_table[i];
if (c->x86_model == tm->model &&
(tm->mask == ANY || c->x86_stepping == tm->mask))
if (c->x86_vfm == tm->vfm &&
(tm->stepping_mask == ANY ||
tm->stepping_mask == c->x86_stepping))
return tm->tjmax;
}
/* Early chips have no MSR for TjMax */
if (c->x86_model == 0xf && c->x86_stepping < 4)
if (c->x86_vfm == INTEL_CORE2_MEROM && c->x86_stepping < 4)
usemsr_ee = 0;
if (c->x86_model > 0xe && usemsr_ee) {
if (c->x86_vfm > INTEL_CORE_YONAH && usemsr_ee) {
u8 platform_id;
/*
@ -211,7 +217,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
"Unable to access MSR 0x17, assuming desktop"
" CPU\n");
usemsr_ee = 0;
} else if (c->x86_model < 0x17 && !(eax & 0x10000000)) {
} else if (c->x86_vfm < INTEL_CORE2_PENRYN &&
!(eax & 0x10000000)) {
/*
* Trust bit 28 up to Penryn, I could not find any
* documentation on that; if you happen to know
@ -226,7 +233,7 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
* Mobile Penryn CPU seems to be platform ID 7 or 5
* (guesswork)
*/
if (c->x86_model == 0x17 &&
if (c->x86_vfm == INTEL_CORE2_PENRYN &&
(platform_id == 5 || platform_id == 7)) {
/*
* If MSR EE bit is set, set it to 90 degrees C,
@ -258,18 +265,6 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
return tjmax;
}
static bool cpu_has_tjmax(struct cpuinfo_x86 *c)
{
u8 model = c->x86_model;
return model > 0xe &&
model != 0x1c &&
model != 0x26 &&
model != 0x27 &&
model != 0x35 &&
model != 0x36;
}
static int get_tjmax(struct temp_data *tdata, struct device *dev)
{
struct cpuinfo_x86 *c = &cpu_data(tdata->cpu);
@ -287,8 +282,7 @@ static int get_tjmax(struct temp_data *tdata, struct device *dev)
*/
err = rdmsr_safe_on_cpu(tdata->cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err) {
if (cpu_has_tjmax(c))
dev_warn(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
dev_warn_once(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
} else {
val = (eax >> 16) & 0xff;
if (val)
@ -460,7 +454,7 @@ static int chk_ucode_version(unsigned int cpu)
* Readings might stop update when processor visited too deep sleep,
* fixed for stepping D0 (6EC).
*/
if (c->x86_model == 0xe && c->x86_stepping < 0xc && c->microcode < 0x39) {
if (c->x86_vfm == INTEL_CORE_YONAH && c->x86_stepping < 0xc && c->microcode < 0x39) {
pr_err("Errata AE18 not fixed, update BIOS or microcode of the CPU!\n");
return -ENODEV;
}
@ -580,7 +574,7 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
* MSR_IA32_TEMPERATURE_TARGET register. Atoms don't have the register
* at all.
*/
if (c->x86_model > 0xe && c->x86_model != 0x1c)
if (c->x86_vfm > INTEL_CORE_YONAH && c->x86_vfm != INTEL_ATOM_BONNELL)
if (get_ttarget(tdata, &pdev->dev) >= 0)
tdata->attr_size++;
@ -793,7 +787,9 @@ static int __init coretemp_init(void)
/*
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
* sensors. We check this bit only, all the early CPUs
* without thermal sensors will be filtered out.
* without thermal sensors will be filtered out. This
* includes all the Family 5 and Family 15 (Pentium 4)
* models, since they never set the CPUID bit.
*/
if (!x86_match_cpu(coretemp_ids))
return -ENODEV;

View File

@ -7,20 +7,34 @@
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/units.h>
#define DRV_NAME "cros-ec-hwmon"
#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION 0
#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION 1
#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION 2
struct cros_ec_hwmon_priv {
struct cros_ec_device *cros_ec;
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
u8 usable_fans;
bool fan_control_supported;
u8 manual_fans; /* bits to indicate whether the fan is set to manual */
u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES];
};
struct cros_ec_hwmon_cooling_priv {
struct cros_ec_hwmon_priv *hwmon_priv;
u8 index;
};
static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
@ -36,6 +50,42 @@ static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index
return 0;
}
static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value)
{
struct ec_params_pwm_get_fan_duty req = {
.fan_idx = index,
};
struct ec_response_pwm_get_fan_duty resp;
int ret;
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION,
EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp));
if (ret < 0)
return ret;
*pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100);
return 0;
}
static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index,
u8 *control_method)
{
struct ec_params_auto_fan_ctrl_v2 req = {
.cmd = EC_AUTO_FAN_CONTROL_CMD_GET,
.fan_idx = index,
};
struct ec_response_auto_fan_control resp;
int ret;
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp));
if (ret < 0)
return ret;
*control_method = resp.is_auto ? 2 : 1;
return 0;
}
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
{
unsigned int offset;
@ -75,6 +125,8 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
{
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
int ret = -EOPNOTSUPP;
u8 control_method;
u8 pwm_value;
u16 speed;
u8 temp;
@ -92,6 +144,17 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
if (ret == 0)
*val = cros_ec_hwmon_is_error_fan(speed);
}
} else if (type == hwmon_pwm) {
if (attr == hwmon_pwm_enable) {
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel,
&control_method);
if (ret == 0)
*val = control_method;
} else if (attr == hwmon_pwm_input) {
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value);
if (ret == 0)
*val = pwm_value;
}
} else if (type == hwmon_temp) {
if (attr == hwmon_temp_input) {
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
@ -124,6 +187,74 @@ static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types
return -EOPNOTSUPP;
}
static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val)
{
struct ec_params_pwm_set_fan_duty_v1 req = {
.fan_idx = index,
.percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255),
};
int ret;
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION,
EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0);
if (ret < 0)
return ret;
return 0;
}
static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val)
{
u8 control_method;
int ret;
ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method);
if (ret)
return ret;
if (control_method != 1)
return -EOPNOTSUPP;
return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val);
}
static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val)
{
struct ec_params_auto_fan_ctrl_v2 req = {
.fan_idx = index,
.cmd = EC_AUTO_FAN_CONTROL_CMD_SET,
};
int ret;
/* No CrOS EC supports no fan speed control */
if (val == 0)
return -EOPNOTSUPP;
req.set_auto = (val != 1) ? true : false;
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0);
if (ret < 0)
return ret;
return 0;
}
static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
if (type == hwmon_pwm) {
switch (attr) {
case hwmon_pwm_input:
return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val);
case hwmon_pwm_enable:
return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val);
default:
return -EOPNOTSUPP;
}
}
return -EOPNOTSUPP;
}
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
@ -132,6 +263,9 @@ static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_type
if (type == hwmon_fan) {
if (priv->usable_fans & BIT(channel))
return 0444;
} else if (type == hwmon_pwm) {
if (priv->fan_control_supported && priv->usable_fans & BIT(channel))
return 0644;
} else if (type == hwmon_temp) {
if (priv->temp_sensor_names[channel])
return 0444;
@ -147,6 +281,11 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
@ -175,9 +314,46 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
NULL
};
static int cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *val)
{
*val = 255;
return 0;
}
static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *val)
{
const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
u8 read_val;
int ret;
ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val);
if (ret)
return ret;
*val = read_val;
return 0;
}
static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long val)
{
const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val);
}
static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops = {
.get_max_state = cros_ec_hwmon_cooling_get_max_state,
.get_cur_state = cros_ec_hwmon_cooling_get_cur_state,
.set_cur_state = cros_ec_hwmon_cooling_set_cur_state,
};
static const struct hwmon_ops cros_ec_hwmon_ops = {
.read = cros_ec_hwmon_read,
.read_string = cros_ec_hwmon_read_string,
.write = cros_ec_hwmon_write,
.is_visible = cros_ec_hwmon_is_visible,
};
@ -233,6 +409,65 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
}
}
static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec,
u16 cmd, u8 version)
{
int ret;
ret = cros_ec_get_cmd_versions(cros_ec, cmd);
return ret >= 0 && (ret & EC_VER_MASK(version));
}
static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec)
{
return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY,
CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) &&
is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY,
CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) &&
is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL,
CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION);
}
static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev,
struct cros_ec_hwmon_priv *priv)
{
struct cros_ec_hwmon_cooling_priv *cpriv;
struct thermal_cooling_device *cdev;
const char *type;
size_t i;
if (!IS_ENABLED(CONFIG_THERMAL))
return;
if (!priv->fan_control_supported)
return;
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
if (!(priv->usable_fans & BIT(i)))
continue;
cpriv = devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL);
if (!cpriv)
continue;
type = devm_kasprintf(dev, GFP_KERNEL, "%s-fan%zu", dev_name(dev), i);
if (!type) {
dev_warn(dev, "no memory to compose cooling device type for fan %zu\n", i);
continue;
}
cpriv->hwmon_priv = priv;
cpriv->index = i;
cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv,
&cros_ec_thermal_cooling_ops);
if (IS_ERR(cdev)) {
dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i,
cdev);
continue;
}
}
}
static int cros_ec_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -259,13 +494,89 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev)
cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
cros_ec_hwmon_probe_fans(priv);
priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
cros_ec_hwmon_register_fan_cooling_devices(dev, priv);
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
&cros_ec_hwmon_chip_info, NULL);
platform_set_drvdata(pdev, priv);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
{
struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
u8 control_method;
size_t i;
int ret;
if (!priv->fan_control_supported)
return 0;
/* EC sets fan control to auto after suspended, store settings before suspending. */
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
if (!(priv->usable_fans & BIT(i)))
continue;
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method);
if (ret) {
dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i,
ret);
continue;
}
if (control_method != 1) {
priv->manual_fans &= ~BIT(i);
continue;
} else {
priv->manual_fans |= BIT(i);
}
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]);
/*
* If storing the value failed, invalidate the stored mode value by setting it
* to auto control. EC will automatically switch to auto mode for that fan after
* suspended.
*/
if (ret) {
dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i,
ERR_PTR(ret));
priv->manual_fans &= ~BIT(i);
continue;
}
}
return 0;
}
static int cros_ec_hwmon_resume(struct platform_device *pdev)
{
const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
size_t i;
int ret;
if (!priv->fan_control_supported)
return 0;
/* EC sets fan control to auto after suspend, restore to settings before suspend. */
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
if (!(priv->manual_fans & BIT(i)))
continue;
/*
* Setting fan PWM value to EC will change the mode to manual for that fan in EC as
* well, so we do not need to issue a separate fan mode to manual call.
*/
ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]);
if (ret)
dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i,
ERR_PTR(ret));
}
return 0;
}
static const struct platform_device_id cros_ec_hwmon_id[] = {
{ DRV_NAME, 0 },
{}
@ -274,6 +585,8 @@ static const struct platform_device_id cros_ec_hwmon_id[] = {
static struct platform_driver cros_ec_hwmon_driver = {
.driver.name = DRV_NAME,
.probe = cros_ec_hwmon_probe,
.suspend = pm_ptr(cros_ec_hwmon_suspend),
.resume = pm_ptr(cros_ec_hwmon_resume),
.id_table = cros_ec_hwmon_id,
};
module_platform_driver(cros_ec_hwmon_driver);

View File

@ -24,6 +24,7 @@
#include <linux/init.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
@ -446,7 +447,6 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
if (disallow_fan_support)
return -EINVAL;
speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
regs.ebx = fan | (speed << 8);
return dell_smm_call(data->ops, &regs);
@ -637,6 +637,8 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (copy_from_user(&speed, argp + 1, sizeof(int)))
return -EFAULT;
speed = clamp_val(speed, 0, data->i8k_fan_max);
mutex_lock(&data->i8k_mutex);
err = i8k_set_fan(data, val, speed);
if (err < 0)
@ -762,6 +764,13 @@ static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned l
if (ret < 0)
return ret;
/*
* A fan state bigger than i8k_fan_max might indicate that
* the fan is currently in automatic mode.
*/
if (ret > cdata->data->i8k_fan_max)
return -ENODATA;
*state = ret;
return 0;
@ -849,7 +858,14 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_pwm_enable:
if (auto_fan)
if (auto_fan) {
/*
* The setting affects all fans, so only create a
* single attribute.
*/
if (channel != 1)
return 0;
/*
* There is no command for retrieve the current status
* from BIOS, and userspace/firmware itself can change
@ -857,6 +873,10 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
* Thus we can only provide write-only access for now.
*/
return 0200;
}
if (data->fan[channel] && data->i8k_fan_max < I8K_FAN_AUTO)
return 0644;
break;
default:
@ -926,14 +946,28 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
}
break;
case hwmon_pwm:
ret = i8k_get_fan_status(data, channel);
if (ret < 0)
return ret;
switch (attr) {
case hwmon_pwm_input:
ret = i8k_get_fan_status(data, channel);
if (ret < 0)
return ret;
/*
* A fan state bigger than i8k_fan_max might indicate that
* the fan is currently in automatic mode.
*/
if (ret > data->i8k_fan_max)
return -ENODATA;
*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
return 0;
case hwmon_pwm_enable:
if (ret == I8K_FAN_AUTO)
*val = 2;
else
*val = 1;
return 0;
default:
break;
@ -1020,16 +1054,32 @@ static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32
return 0;
case hwmon_pwm_enable:
if (!val)
return -EINVAL;
if (val == 1)
switch (val) {
case 1:
enable = false;
else
break;
case 2:
enable = true;
break;
default:
return -EINVAL;
}
mutex_lock(&data->i8k_mutex);
err = i8k_enable_fan_auto_mode(data, enable);
if (auto_fan) {
err = i8k_enable_fan_auto_mode(data, enable);
} else {
/*
* When putting the fan into manual control mode we have to ensure
* that the device does not overheat until the userspace fan control
* software takes over. Because of this we set the fan speed to
* i8k_fan_max when disabling automatic fan control.
*/
if (enable)
err = i8k_set_fan(data, channel, I8K_FAN_AUTO);
else
err = i8k_set_fan(data, channel, data->i8k_fan_max);
}
mutex_unlock(&data->i8k_mutex);
if (err < 0)
@ -1080,9 +1130,9 @@ static const struct hwmon_channel_info * const dell_smm_info[] = {
),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE
),
NULL
};
@ -1280,6 +1330,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"),
},
},
{
.ident = "Dell OptiPlex 7040",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7040"),
},
},
{
.ident = "Dell Precision",
.matches = {
@ -1331,7 +1388,6 @@ struct i8k_config_data {
enum i8k_configs {
DELL_LATITUDE_D520,
DELL_PRECISION_490,
DELL_STUDIO,
DELL_XPS,
};
@ -1341,10 +1397,6 @@ static const struct i8k_config_data i8k_config_data[] __initconst = {
.fan_mult = 1,
.fan_max = I8K_FAN_TURBO,
},
[DELL_PRECISION_490] = {
.fan_mult = 1,
.fan_max = I8K_FAN_TURBO,
},
[DELL_STUDIO] = {
.fan_mult = 1,
.fan_max = I8K_FAN_HIGH,
@ -1364,15 +1416,6 @@ static const struct dmi_system_id i8k_config_dmi_table[] __initconst = {
},
.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
},
{
.ident = "Dell Precision 490",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME,
"Precision WorkStation 490"),
},
.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
},
{
.ident = "Dell Studio",
.matches = {

715
drivers/hwmon/gpd-fan.c Normal file
View File

@ -0,0 +1,715 @@
// SPDX-License-Identifier: GPL-2.0+
/* Platform driver for GPD devices that expose fan control via hwmon sysfs.
*
* Fan control is provided via pwm interface in the range [0-255].
* Each model has a different range in the EC, the written value is scaled to
* accommodate for that.
*
* Based on this repo:
* https://github.com/Cryolitia/gpd-fan-driver
*
* Copyright (c) 2024 Cryolitia PukNgae
*/
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "gpdfan"
#define GPD_PWM_CTR_OFFSET 0x1841
static char *gpd_fan_board = "";
module_param(gpd_fan_board, charp, 0444);
// EC read/write locker, protecting a sequence of EC operations
static DEFINE_MUTEX(gpd_fan_sequence_lock);
enum gpd_board {
win_mini,
win4_6800u,
win_max_2,
duo,
};
enum FAN_PWM_ENABLE {
DISABLE = 0,
MANUAL = 1,
AUTOMATIC = 2,
};
static struct {
enum FAN_PWM_ENABLE pwm_enable;
u8 pwm_value;
const struct gpd_fan_drvdata *drvdata;
} gpd_driver_priv;
struct gpd_fan_drvdata {
const char *board_name; // Board name for module param comparison
const enum gpd_board board;
const u8 addr_port;
const u8 data_port;
const u16 manual_control_enable;
const u16 rpm_read;
const u16 pwm_write;
const u16 pwm_max;
};
static struct gpd_fan_drvdata gpd_win_mini_drvdata = {
.board_name = "win_mini",
.board = win_mini,
.addr_port = 0x4E,
.data_port = 0x4F,
.manual_control_enable = 0x047A,
.rpm_read = 0x0478,
.pwm_write = 0x047A,
.pwm_max = 244,
};
static struct gpd_fan_drvdata gpd_duo_drvdata = {
.board_name = "duo",
.board = duo,
.addr_port = 0x4E,
.data_port = 0x4F,
.manual_control_enable = 0x047A,
.rpm_read = 0x0478,
.pwm_write = 0x047A,
.pwm_max = 244,
};
static struct gpd_fan_drvdata gpd_win4_drvdata = {
.board_name = "win4",
.board = win4_6800u,
.addr_port = 0x2E,
.data_port = 0x2F,
.manual_control_enable = 0xC311,
.rpm_read = 0xC880,
.pwm_write = 0xC311,
.pwm_max = 127,
};
static struct gpd_fan_drvdata gpd_wm2_drvdata = {
.board_name = "wm2",
.board = win_max_2,
.addr_port = 0x4E,
.data_port = 0x4F,
.manual_control_enable = 0x0275,
.rpm_read = 0x0218,
.pwm_write = 0x1809,
.pwm_max = 184,
};
static const struct dmi_system_id dmi_table[] = {
{
// GPD Win Mini
// GPD Win Mini with AMD Ryzen 8840U
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01")
},
.driver_data = &gpd_win_mini_drvdata,
},
{
// GPD Win Mini
// GPD Win Mini with AMD Ryzen HX370
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02")
},
.driver_data = &gpd_win_mini_drvdata,
},
{
// GPD Win Mini
// GPD Win Mini with AMD Ryzen HX370
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02-L")
},
.driver_data = &gpd_win_mini_drvdata,
},
{
// GPD Win 4 with AMD Ryzen 6800U
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
DMI_MATCH(DMI_BOARD_VERSION, "Default string"),
},
.driver_data = &gpd_win4_drvdata,
},
{
// GPD Win 4 with Ryzen 7840U
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"),
},
// Since 7840U, win4 uses the same drvdata as wm2
.driver_data = &gpd_wm2_drvdata,
},
{
// GPD Win 4 with Ryzen 7840U (another)
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
DMI_MATCH(DMI_BOARD_VERSION, "Ver.1.0"),
},
.driver_data = &gpd_wm2_drvdata,
},
{
// GPD Win Max 2 with Ryzen 6800U
// GPD Win Max 2 2023 with Ryzen 7840U
// GPD Win Max 2 2024 with Ryzen 8840U
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
},
.driver_data = &gpd_wm2_drvdata,
},
{
// GPD Win Max 2 with AMD Ryzen HX370
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"),
},
.driver_data = &gpd_wm2_drvdata,
},
{
// GPD Duo
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01"),
},
.driver_data = &gpd_duo_drvdata,
},
{
// GPD Duo (another)
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01-L"),
},
.driver_data = &gpd_duo_drvdata,
},
{
// GPD Pocket 4
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04"),
},
.driver_data = &gpd_win_mini_drvdata,
},
{
// GPD Pocket 4 (another)
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04-L"),
},
.driver_data = &gpd_win_mini_drvdata,
},
{}
};
static const struct gpd_fan_drvdata *gpd_module_drvdata[] = {
&gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL
};
// Helper functions to handle EC read/write
static void gpd_ecram_read(u16 offset, u8 *val)
{
u16 addr_port = gpd_driver_priv.drvdata->addr_port;
u16 data_port = gpd_driver_priv.drvdata->data_port;
outb(0x2E, addr_port);
outb(0x11, data_port);
outb(0x2F, addr_port);
outb((u8)((offset >> 8) & 0xFF), data_port);
outb(0x2E, addr_port);
outb(0x10, data_port);
outb(0x2F, addr_port);
outb((u8)(offset & 0xFF), data_port);
outb(0x2E, addr_port);
outb(0x12, data_port);
outb(0x2F, addr_port);
*val = inb(data_port);
}
static void gpd_ecram_write(u16 offset, u8 value)
{
u16 addr_port = gpd_driver_priv.drvdata->addr_port;
u16 data_port = gpd_driver_priv.drvdata->data_port;
outb(0x2E, addr_port);
outb(0x11, data_port);
outb(0x2F, addr_port);
outb((u8)((offset >> 8) & 0xFF), data_port);
outb(0x2E, addr_port);
outb(0x10, data_port);
outb(0x2F, addr_port);
outb((u8)(offset & 0xFF), data_port);
outb(0x2E, addr_port);
outb(0x12, data_port);
outb(0x2F, addr_port);
outb(value, data_port);
}
static int gpd_generic_read_rpm(void)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
u8 high, low;
gpd_ecram_read(drvdata->rpm_read, &high);
gpd_ecram_read(drvdata->rpm_read + 1, &low);
return (u16)high << 8 | low;
}
static void gpd_win4_init_ec(void)
{
u8 chip_id, chip_ver;
gpd_ecram_read(0x2000, &chip_id);
if (chip_id == 0x55) {
gpd_ecram_read(0x1060, &chip_ver);
gpd_ecram_write(0x1060, chip_ver | 0x80);
}
}
static int gpd_win4_read_rpm(void)
{
int ret;
ret = gpd_generic_read_rpm();
if (ret == 0)
// Re-init EC when speed is 0
gpd_win4_init_ec();
return ret;
}
static int gpd_wm2_read_rpm(void)
{
for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET;
pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) {
u8 PWMCTR;
gpd_ecram_read(pwm_ctr_offset, &PWMCTR);
if (PWMCTR != 0xB8)
gpd_ecram_write(pwm_ctr_offset, 0xB8);
}
return gpd_generic_read_rpm();
}
// Read value for fan1_input
static int gpd_read_rpm(void)
{
switch (gpd_driver_priv.drvdata->board) {
case win_mini:
case duo:
return gpd_generic_read_rpm();
case win4_6800u:
return gpd_win4_read_rpm();
case win_max_2:
return gpd_wm2_read_rpm();
}
return 0;
}
static int gpd_wm2_read_pwm(void)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
u8 var;
gpd_ecram_read(drvdata->pwm_write, &var);
// Match gpd_generic_write_pwm(u8) below
return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1));
}
// Read value for pwm1
static int gpd_read_pwm(void)
{
switch (gpd_driver_priv.drvdata->board) {
case win_mini:
case duo:
case win4_6800u:
switch (gpd_driver_priv.pwm_enable) {
case DISABLE:
return 255;
case MANUAL:
return gpd_driver_priv.pwm_value;
case AUTOMATIC:
return -EOPNOTSUPP;
}
break;
case win_max_2:
return gpd_wm2_read_pwm();
}
return 0;
}
// PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it.
static inline u8 gpd_cast_pwm_range(u8 val)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1;
}
static void gpd_generic_write_pwm(u8 val)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
u8 pwm_reg;
pwm_reg = gpd_cast_pwm_range(val);
gpd_ecram_write(drvdata->pwm_write, pwm_reg);
}
static void gpd_duo_write_pwm(u8 val)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
u8 pwm_reg;
pwm_reg = gpd_cast_pwm_range(val);
gpd_ecram_write(drvdata->pwm_write, pwm_reg);
gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg);
}
// Write value for pwm1
static int gpd_write_pwm(u8 val)
{
if (gpd_driver_priv.pwm_enable != MANUAL)
return -EPERM;
switch (gpd_driver_priv.drvdata->board) {
case duo:
gpd_duo_write_pwm(val);
break;
case win_mini:
case win4_6800u:
case win_max_2:
gpd_generic_write_pwm(val);
break;
}
return 0;
}
static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
{
switch (pwm_enable) {
case DISABLE:
gpd_generic_write_pwm(255);
break;
case MANUAL:
gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
break;
case AUTOMATIC:
gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
break;
}
}
static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
{
switch (pwm_enable) {
case DISABLE:
gpd_duo_write_pwm(255);
break;
case MANUAL:
gpd_duo_write_pwm(gpd_driver_priv.pwm_value);
break;
case AUTOMATIC:
gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
break;
}
}
static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable)
{
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
switch (enable) {
case DISABLE:
gpd_generic_write_pwm(255);
gpd_ecram_write(drvdata->manual_control_enable, 1);
break;
case MANUAL:
gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
gpd_ecram_write(drvdata->manual_control_enable, 1);
break;
case AUTOMATIC:
gpd_ecram_write(drvdata->manual_control_enable, 0);
break;
}
}
// Write value for pwm1_enable
static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable)
{
if (enable == MANUAL)
// Set pwm_value to max firstly when switching to manual mode, in
// consideration of device safety.
gpd_driver_priv.pwm_value = 255;
switch (gpd_driver_priv.drvdata->board) {
case win_mini:
case win4_6800u:
gpd_win_mini_set_pwm_enable(enable);
break;
case duo:
gpd_duo_set_pwm_enable(enable);
break;
case win_max_2:
gpd_wm2_set_pwm_enable(enable);
break;
}
}
static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata,
enum hwmon_sensor_types type, u32 attr,
__always_unused int channel)
{
if (type == hwmon_fan && attr == hwmon_fan_input) {
return 0444;
} else if (type == hwmon_pwm) {
switch (attr) {
case hwmon_pwm_enable:
case hwmon_pwm_input:
return 0644;
default:
return 0;
}
}
return 0;
}
static int gpd_fan_hwmon_read(__always_unused struct device *dev,
enum hwmon_sensor_types type, u32 attr,
__always_unused int channel, long *val)
{
int ret;
ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
if (ret)
return ret;
if (type == hwmon_fan) {
if (attr == hwmon_fan_input) {
ret = gpd_read_rpm();
if (ret < 0)
goto OUT;
*val = ret;
ret = 0;
goto OUT;
}
} else if (type == hwmon_pwm) {
switch (attr) {
case hwmon_pwm_enable:
*val = gpd_driver_priv.pwm_enable;
ret = 0;
goto OUT;
case hwmon_pwm_input:
ret = gpd_read_pwm();
if (ret < 0)
goto OUT;
*val = ret;
ret = 0;
goto OUT;
}
}
ret = -EOPNOTSUPP;
OUT:
mutex_unlock(&gpd_fan_sequence_lock);
return ret;
}
static int gpd_fan_hwmon_write(__always_unused struct device *dev,
enum hwmon_sensor_types type, u32 attr,
__always_unused int channel, long val)
{
int ret;
ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
if (ret)
return ret;
if (type == hwmon_pwm) {
switch (attr) {
case hwmon_pwm_enable:
if (!in_range(val, 0, 3)) {
ret = -EINVAL;
goto OUT;
}
gpd_driver_priv.pwm_enable = val;
gpd_set_pwm_enable(gpd_driver_priv.pwm_enable);
ret = 0;
goto OUT;
case hwmon_pwm_input:
if (!in_range(val, 0, 256)) {
ret = -ERANGE;
goto OUT;
}
gpd_driver_priv.pwm_value = val;
ret = gpd_write_pwm(val);
goto OUT;
}
}
ret = -EOPNOTSUPP;
OUT:
mutex_unlock(&gpd_fan_sequence_lock);
return ret;
}
static const struct hwmon_ops gpd_fan_ops = {
.is_visible = gpd_fan_hwmon_is_visible,
.read = gpd_fan_hwmon_read,
.write = gpd_fan_hwmon_write,
};
static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
NULL
};
static struct hwmon_chip_info gpd_fan_chip_info = {
.ops = &gpd_fan_ops,
.info = gpd_fan_hwmon_channel_info
};
static int gpd_fan_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct resource *region;
const struct resource *res;
const struct device *hwdev;
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (IS_ERR(res))
return dev_err_probe(dev, PTR_ERR(res),
"Failed to get platform resource\n");
region = devm_request_region(dev, res->start,
resource_size(res), DRIVER_NAME);
if (IS_ERR(region))
return dev_err_probe(dev, PTR_ERR(region),
"Failed to request region\n");
hwdev = devm_hwmon_device_register_with_info(dev,
DRIVER_NAME,
NULL,
&gpd_fan_chip_info,
NULL);
if (IS_ERR(hwdev))
return dev_err_probe(dev, PTR_ERR(region),
"Failed to register hwmon device\n");
return 0;
}
static void gpd_fan_remove(__always_unused struct platform_device *pdev)
{
gpd_driver_priv.pwm_enable = AUTOMATIC;
gpd_set_pwm_enable(AUTOMATIC);
}
static struct platform_driver gpd_fan_driver = {
.probe = gpd_fan_probe,
.remove = gpd_fan_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
static struct platform_device *gpd_fan_platform_device;
static int __init gpd_fan_init(void)
{
const struct gpd_fan_drvdata *match = NULL;
for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) {
if (strcmp(gpd_fan_board, (*p)->board_name) == 0) {
match = *p;
break;
}
}
if (!match) {
const struct dmi_system_id *dmi_match =
dmi_first_match(dmi_table);
if (dmi_match)
match = dmi_match->driver_data;
}
if (!match)
return -ENODEV;
gpd_driver_priv.pwm_enable = AUTOMATIC;
gpd_driver_priv.pwm_value = 255;
gpd_driver_priv.drvdata = match;
struct resource gpd_fan_resources[] = {
{
.start = match->addr_port,
.end = match->data_port,
.flags = IORESOURCE_IO,
},
};
gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver,
gpd_fan_probe,
gpd_fan_resources,
1, NULL, 0);
if (IS_ERR(gpd_fan_platform_device)) {
pr_warn("Failed to create platform device\n");
return PTR_ERR(gpd_fan_platform_device);
}
return 0;
}
static void __exit gpd_fan_exit(void)
{
platform_device_unregister(gpd_fan_platform_device);
platform_driver_unregister(&gpd_fan_driver);
}
MODULE_DEVICE_TABLE(dmi, dmi_table);
module_init(gpd_fan_init);
module_exit(gpd_fan_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cryolitia PukNgae <cryolitia@uniontech.com>");
MODULE_DESCRIPTION("GPD Devices fan control driver");

View File

@ -19,6 +19,7 @@
#include <linux/kstrtox.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/property.h>
#include <linux/slab.h>
@ -36,6 +37,7 @@ struct hwmon_device {
const char *label;
struct device dev;
const struct hwmon_chip_info *chip;
struct mutex lock;
struct list_head tzdata;
struct attribute_group group;
const struct attribute_group **groups;
@ -165,6 +167,8 @@ static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
int ret;
long t;
guard(mutex)(&hwdev->lock);
ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
tdata->index, &t);
if (ret < 0)
@ -193,6 +197,8 @@ static int hwmon_thermal_set_trips(struct thermal_zone_device *tz, int low, int
if (!info[i])
return 0;
guard(mutex)(&hwdev->lock);
if (info[i]->config[tdata->index] & HWMON_T_MIN) {
err = chip->ops->write(tdata->dev, hwmon_temp,
hwmon_temp_min, tdata->index, low);
@ -330,8 +336,6 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
* attached to an i2c client device.
*/
static DEFINE_MUTEX(hwmon_pec_mutex);
static int hwmon_match_device(struct device *dev, const void *data)
{
return dev->class == &hwmon_class;
@ -362,17 +366,16 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
if (!hdev)
return -ENODEV;
mutex_lock(&hwmon_pec_mutex);
/*
* If there is no write function, we assume that chip specific
* handling is not required.
*/
hwdev = to_hwmon_device(hdev);
guard(mutex)(&hwdev->lock);
if (hwdev->chip->ops->write) {
err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
if (err && err != -EOPNOTSUPP)
goto unlock;
goto put;
}
if (!val)
@ -381,8 +384,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
client->flags |= I2C_CLIENT_PEC;
err = count;
unlock:
mutex_unlock(&hwmon_pec_mutex);
put:
put_device(hdev);
return err;
@ -426,18 +428,25 @@ static ssize_t hwmon_attr_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
struct hwmon_device *hwdev = to_hwmon_device(dev);
s64 val64;
long val;
int ret;
guard(mutex)(&hwdev->lock);
ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index,
&val);
(hattr->type == hwmon_energy64) ? (long *)&val64 : &val);
if (ret < 0)
return ret;
trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type),
hattr->name, val);
if (hattr->type != hwmon_energy64)
val64 = val;
return sprintf(buf, "%ld\n", val);
trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type),
hattr->name, val64);
return sprintf(buf, "%lld\n", val64);
}
static ssize_t hwmon_attr_show_string(struct device *dev,
@ -445,10 +454,13 @@ static ssize_t hwmon_attr_show_string(struct device *dev,
char *buf)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
struct hwmon_device *hwdev = to_hwmon_device(dev);
enum hwmon_sensor_types type = hattr->type;
const char *s;
int ret;
guard(mutex)(&hwdev->lock);
ret = hattr->ops->read_string(dev, hattr->type, hattr->attr,
hattr->index, &s);
if (ret < 0)
@ -465,6 +477,7 @@ static ssize_t hwmon_attr_store(struct device *dev,
const char *buf, size_t count)
{
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
struct hwmon_device *hwdev = to_hwmon_device(dev);
long val;
int ret;
@ -472,13 +485,15 @@ static ssize_t hwmon_attr_store(struct device *dev,
if (ret < 0)
return ret;
guard(mutex)(&hwdev->lock);
ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index,
val);
if (ret < 0)
return ret;
trace_hwmon_attr_store(hattr->index + hwmon_attr_base(hattr->type),
hattr->name, val);
hattr->name, (s64)val);
return count;
}
@ -734,6 +749,7 @@ static const char * const *__templates[] = {
[hwmon_curr] = hwmon_curr_attr_templates,
[hwmon_power] = hwmon_power_attr_templates,
[hwmon_energy] = hwmon_energy_attr_templates,
[hwmon_energy64] = hwmon_energy_attr_templates,
[hwmon_humidity] = hwmon_humidity_attr_templates,
[hwmon_fan] = hwmon_fan_attr_templates,
[hwmon_pwm] = hwmon_pwm_attr_templates,
@ -747,6 +763,7 @@ static const int __templates_size[] = {
[hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates),
[hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates),
[hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates),
[hwmon_energy64] = ARRAY_SIZE(hwmon_energy_attr_templates),
[hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates),
[hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates),
[hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates),
@ -785,6 +802,22 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
}
EXPORT_SYMBOL_GPL(hwmon_notify_event);
void hwmon_lock(struct device *dev)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
mutex_lock(&hwdev->lock);
}
EXPORT_SYMBOL_GPL(hwmon_lock);
void hwmon_unlock(struct device *dev)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
mutex_unlock(&hwdev->lock);
}
EXPORT_SYMBOL_GPL(hwmon_unlock);
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
{
int i, n;
@ -945,6 +978,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
tdev = tdev->parent;
hdev->of_node = tdev ? tdev->of_node : NULL;
hwdev->chip = chip;
mutex_init(&hwdev->lock);
dev_set_drvdata(hdev, drvdata);
dev_set_name(hdev, HWMON_ID_FORMAT, id);
err = device_register(hdev);

View File

@ -16,8 +16,6 @@
#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
@ -53,7 +51,7 @@
#define INA238_REGISTERS 0x20
#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
/* Default configuration of device on reset. */
#define INA238_CONFIG_DEFAULT 0
@ -62,6 +60,7 @@
#define INA238_ADC_CONFIG_DEFAULT 0xfb6a
/* Configure alerts to be based on averaged value (SLOWALERT) */
#define INA238_DIAG_ALERT_DEFAULT 0x2000
#define INA238_DIAG_ALERT_APOL BIT(12)
/*
* This driver uses a fixed calibration value in order to scale current/power
* based on a fixed shunt resistor value. This allows for conversion within the
@ -69,46 +68,32 @@
* 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.
* To achieve the best possible dynamic range, the value of the shunt voltage
* register should match the value of the current register. With that, the shunt
* voltage of 0x7fff = 32,767 uV = 163,785 uV matches the maximum current,
* and no accuracy is lost. Experiments with a real chip show that this is
* achieved by setting the SHUNT_CAL register to a value of 0x1000 = 4,096.
* Per datasheet,
* SHUNT_CAL = 819.2 x 10^6 x CURRENT_LSB x Rshunt
* = 819,200,000 x CURRENT_LSB x Rshunt
* With SHUNT_CAL set to 4,096, we get
* CURRENT_LSB = 4,096 / (819,200,000 x Rshunt)
* Assuming an Rshunt value of 5 mOhm, we get
* CURRENT_LSB = 4,096 / (819,200,000 x 0.005) = 1mA
* and thus a dynamic range of 1mA ... 32,767mA, which is sufficient for most
* applications. The actual dynamic range is of course determined by the actual
* shunt resistor value.
*
* 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 (mW) = 0.2 * register value * 20000 / rshunt / 4 * gain
* (Specific for SQ52206)
* Power (mW) = 0.24 * register value * 20000 / rshunt / 4 * gain
* Energy (uJ) = 16 * 0.24 * register value * 20000 / rshunt / 4 * gain * 1000
* Power and energy values are scaled accordingly.
*/
#define INA238_CALIBRATION_VALUE 16384
#define INA238_FIXED_SHUNT 20000
#define INA238_CALIBRATION_VALUE 4096
#define INA238_FIXED_SHUNT 5000
#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */
#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */
#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
#define INA228_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
#define INA238_SHUNT_VOLTAGE_LSB 5000 /* 5 uV/lsb, in nV */
#define INA238_BUS_VOLTAGE_LSB 3125000 /* 3.125 mV/lsb, in nV */
#define SQ52206_BUS_VOLTAGE_LSB 3750000 /* 3.75 mV/lsb, in nV */
#define NUNIT_PER_MUNIT 1000000 /* n[AV] -> m[AV] */
static const struct regmap_config ina238_regmap_config = {
.max_register = INA238_REGISTERS,
@ -116,17 +101,17 @@ static const struct regmap_config ina238_regmap_config = {
.val_bits = 16,
};
enum ina238_ids { ina238, ina237, sq52206, ina228 };
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
struct ina238_config {
bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */
bool has_power_highest; /* chip detection power peak */
bool has_energy; /* chip detection energy */
u8 temp_shift; /* fixed parameters for temp calculate */
u32 power_calculate_factor; /* fixed parameters for power calculate */
u8 temp_resolution; /* temperature register resolution in bit */
u16 config_default; /* Power-on default state */
int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */
int temp_lsb; /* use for temperature calculate */
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
};
struct ina238_data {
@ -136,48 +121,68 @@ struct ina238_data {
struct regmap *regmap;
u32 rshunt;
int gain;
u32 voltage_lsb[2]; /* shunt, bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
int power_lsb; /* power LSB, in uW */
int energy_lsb; /* energy LSB, in uJ */
};
static const struct ina238_config ina238_config[] = {
[ina238] = {
.has_20bit_voltage_current = false,
.has_energy = false,
[ina228] = {
.has_20bit_voltage_current = true,
.has_energy = true,
.has_power_highest = false,
.temp_shift = 4,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_lsb = INA238_DIE_TEMP_LSB,
.temp_resolution = 16,
},
[ina237] = {
.has_20bit_voltage_current = false,
.has_energy = false,
.has_power_highest = false,
.temp_shift = 4,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_lsb = INA238_DIE_TEMP_LSB,
.temp_resolution = 12,
},
[ina238] = {
.has_20bit_voltage_current = false,
.has_energy = false,
.has_power_highest = false,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
},
[ina700] = {
.has_20bit_voltage_current = false,
.has_energy = true,
.has_power_highest = false,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 480,
},
[ina780] = {
.has_20bit_voltage_current = false,
.has_energy = true,
.has_power_highest = false,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 2400,
},
[sq52206] = {
.has_20bit_voltage_current = false,
.has_energy = true,
.has_power_highest = true,
.temp_shift = 0,
.power_calculate_factor = 24,
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_lsb = SQ52206_DIE_TEMP_LSB,
},
[ina228] = {
.has_20bit_voltage_current = true,
.has_energy = true,
.has_power_highest = false,
.temp_shift = 0,
.power_calculate_factor = 20,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_lsb = INA228_DIE_TEMP_LSB,
.temp_resolution = 16,
},
};
@ -232,45 +237,28 @@ static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *v
return 0;
}
static int ina228_read_shunt_voltage(struct device *dev, u32 attr, int channel,
long *val)
static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
int err;
int reg = channel ? INA238_BUS_VOLTAGE : INA238_CURRENT;
u32 lsb = data->voltage_lsb[channel];
u32 factor = NUNIT_PER_MUNIT;
int err, regval;
err = ina238_read_field_s20(data->client, INA238_SHUNT_VOLTAGE, &regval);
if (err)
return err;
if (data->config->has_20bit_voltage_current) {
err = ina238_read_field_s20(data->client, reg, &regval);
if (err)
return err;
/* Adjust accuracy: LSB in units of 500 pV */
lsb /= 8;
factor *= 2;
} else {
err = regmap_read(data->regmap, reg, &regval);
if (err)
return err;
regval = (s16)regval;
}
/*
* gain of 1 -> LSB / 4
* This field has 16 bit on ina238. ina228 adds another 4 bits of
* precision. ina238 conversion factors can still be applied when
* dividing by 16.
*/
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) * data->gain / (1000 * 4) / 16;
return 0;
}
static int ina228_read_bus_voltage(struct device *dev, u32 attr, int channel,
long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
int err;
err = ina238_read_field_s20(data->client, INA238_BUS_VOLTAGE, &regval);
if (err)
return err;
/*
* gain of 1 -> LSB / 4
* This field has 16 bit on ina238. ina228 adds another 4 bits of
* precision. ina238 conversion factors can still be applied when
* dividing by 16.
*/
*val = (regval * data->config->bus_voltage_lsb) / 1000 / 16;
*val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, factor);
return 0;
}
@ -278,18 +266,16 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int reg, mask;
int reg, mask = 0;
int regval;
int err;
if (attr == hwmon_in_input)
return ina228_read_voltage(data, channel, val);
switch (channel) {
case 0:
switch (attr) {
case hwmon_in_input:
if (data->config->has_20bit_voltage_current)
return ina228_read_shunt_voltage(dev, attr, channel, val);
reg = INA238_SHUNT_VOLTAGE;
break;
case hwmon_in_max:
reg = INA238_SHUNT_OVER_VOLTAGE;
break;
@ -310,11 +296,6 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
break;
case 1:
switch (attr) {
case hwmon_in_input:
if (data->config->has_20bit_voltage_current)
return ina228_read_bus_voltage(dev, attr, channel, val);
reg = INA238_BUS_VOLTAGE;
break;
case hwmon_in_max:
reg = INA238_BUS_OVER_VOLTAGE;
break;
@ -341,112 +322,126 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
if (err < 0)
return err;
switch (attr) {
case hwmon_in_input:
case hwmon_in_max:
case hwmon_in_min:
/* signed register, value in mV */
regval = (s16)regval;
if (channel == 0)
/* gain of 1 -> LSB / 4 */
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) *
data->gain / (1000 * 4);
else
*val = (regval * data->config->bus_voltage_lsb) / 1000;
break;
case hwmon_in_max_alarm:
case hwmon_in_min_alarm:
if (mask)
*val = !!(regval & mask);
break;
}
else
*val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->voltage_lsb[channel],
NUNIT_PER_MUNIT);
return 0;
}
static int ina238_write_in(struct device *dev, u32 attr, int channel,
long val)
static int ina238_write_in(struct device *dev, u32 attr, int channel, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
static const int low_limits[2] = {-164, 0};
static const int high_limits[2] = {164, 150000};
static const u8 low_regs[2] = {INA238_SHUNT_UNDER_VOLTAGE, INA238_BUS_UNDER_VOLTAGE};
static const u8 high_regs[2] = {INA238_SHUNT_OVER_VOLTAGE, INA238_BUS_OVER_VOLTAGE};
int regval;
if (attr != hwmon_in_max && attr != hwmon_in_min)
return -EOPNOTSUPP;
/* Initial clamp to avoid overflows */
val = clamp_val(val, low_limits[channel], high_limits[channel]);
val = DIV_S64_ROUND_CLOSEST((s64)val * NUNIT_PER_MUNIT, data->voltage_lsb[channel]);
/* Final clamp to register limits */
regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
/* 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) /
(INA238_SHUNT_VOLTAGE_LSB * data->gain);
regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xffff;
switch (attr) {
case hwmon_in_max:
return regmap_write(data->regmap,
INA238_SHUNT_OVER_VOLTAGE, regval);
case hwmon_in_min:
return regmap_write(data->regmap,
INA238_SHUNT_UNDER_VOLTAGE, regval);
default:
return -EOPNOTSUPP;
}
case 1:
/* signed value, positive values only. Clamp to max 102.396 V */
regval = clamp_val(val, 0, 102396);
regval = (regval * 1000) / data->config->bus_voltage_lsb;
regval = clamp_val(regval, 0, S16_MAX);
switch (attr) {
case hwmon_in_max:
return regmap_write(data->regmap,
INA238_BUS_OVER_VOLTAGE, regval);
case hwmon_in_min:
return regmap_write(data->regmap,
INA238_BUS_UNDER_VOLTAGE, regval);
default:
return -EOPNOTSUPP;
}
switch (attr) {
case hwmon_in_min:
return regmap_write(data->regmap, low_regs[channel], regval);
case hwmon_in_max:
return regmap_write(data->regmap, high_regs[channel], regval);
default:
return -EOPNOTSUPP;
}
}
static int ina238_read_current(struct device *dev, u32 attr, long *val)
static int __ina238_read_curr(struct ina238_data *data, long *val)
{
u32 lsb = data->current_lsb;
int err, regval;
if (data->config->has_20bit_voltage_current) {
err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
if (err)
return err;
lsb /= 16; /* Adjust accuracy */
} else {
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
if (err)
return err;
regval = (s16)regval;
}
*val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, 1000);
return 0;
}
static int ina238_read_curr(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int reg, mask = 0;
int regval;
int err;
if (attr == hwmon_curr_input)
return __ina238_read_curr(data, val);
switch (attr) {
case hwmon_curr_input:
if (data->config->has_20bit_voltage_current) {
err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
if (err)
return err;
} else {
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
if (err < 0)
return err;
/* sign-extend */
regval = (s16)regval;
}
/* Signed register, fixed 1mA current lsb. result in mA */
*val = div_s64((s64)regval * INA238_FIXED_SHUNT * data->gain,
data->rshunt * 4);
/* Account for 4 bit offset */
if (data->config->has_20bit_voltage_current)
*val /= 16;
case hwmon_curr_min:
reg = INA238_SHUNT_UNDER_VOLTAGE;
break;
case hwmon_curr_min_alarm:
reg = INA238_DIAG_ALERT;
mask = INA238_DIAG_ALERT_SHNTUL;
break;
case hwmon_curr_max:
reg = INA238_SHUNT_OVER_VOLTAGE;
break;
case hwmon_curr_max_alarm:
reg = INA238_DIAG_ALERT;
mask = INA238_DIAG_ALERT_SHNTOL;
break;
default:
return -EOPNOTSUPP;
}
err = regmap_read(data->regmap, reg, &regval);
if (err < 0)
return err;
if (mask)
*val = !!(regval & mask);
else
*val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->current_lsb, 1000);
return 0;
}
static int ina238_write_curr(struct device *dev, u32 attr, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
/* Set baseline range to avoid over/underflows */
val = clamp_val(val, -1000000, 1000000);
/* Scale */
val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb);
/* Clamp to register size */
regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
switch (attr) {
case hwmon_curr_min:
return regmap_write(data->regmap, INA238_SHUNT_UNDER_VOLTAGE,
regval);
case hwmon_curr_max:
return regmap_write(data->regmap, INA238_SHUNT_OVER_VOLTAGE,
regval);
default:
return -EOPNOTSUPP;
}
}
static int ina238_read_power(struct device *dev, u32 attr, long *val)
{
struct ina238_data *data = dev_get_drvdata(dev);
@ -460,9 +455,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
if (err)
return err;
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
power = (long long)regval * data->power_lsb;
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@ -471,9 +464,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
if (err)
return err;
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
power = (long long)regval * data->power_lsb;
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@ -486,8 +477,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
* Truncated 24-bit compare register, lower 8-bits are
* truncated. Same conversion to/from uW as POWER register.
*/
power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain *
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
power = ((long long)regval << 8) * data->power_lsb;
/* Clamp value to maximum value of long */
*val = clamp_val(power, 0, LONG_MAX);
break;
@ -505,13 +495,9 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
return 0;
}
static int ina238_write_power(struct device *dev, u32 attr, long val)
static int ina238_write_power_max(struct device *dev, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
long regval;
if (attr != hwmon_power_max)
return -EOPNOTSUPP;
/*
* Unsigned postive values. Compared against the 24-bit power register,
@ -519,12 +505,16 @@ static int ina238_write_power(struct device *dev, u32 attr, long val)
* register.
* The first clamp_val() is to establish a baseline to avoid overflows.
*/
regval = clamp_val(val, 0, LONG_MAX / 2);
regval = div_u64(regval * 4 * 100 * data->rshunt, data->config->power_calculate_factor *
1000ULL * INA238_FIXED_SHUNT * data->gain);
regval = clamp_val(regval >> 8, 0, U16_MAX);
val = clamp_val(val, 0, LONG_MAX / 2);
val = DIV_ROUND_CLOSEST(val, data->power_lsb);
val = clamp_val(val >> 8, 0, U16_MAX);
return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
return regmap_write(data->regmap, INA238_POWER_LIMIT, val);
}
static int ina238_temp_from_reg(s16 regval, u8 resolution)
{
return ((regval >> (16 - resolution)) * 1000) >> (resolution - 9);
}
static int ina238_read_temp(struct device *dev, u32 attr, long *val)
@ -538,17 +528,14 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
err = regmap_read(data->regmap, INA238_DIE_TEMP, &regval);
if (err)
return err;
/* Signed, result in mC */
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
(s64)data->config->temp_lsb, 10000);
*val = ina238_temp_from_reg(regval, data->config->temp_resolution);
break;
case hwmon_temp_max:
err = regmap_read(data->regmap, INA238_TEMP_LIMIT, &regval);
if (err)
return err;
/* Signed, result in mC */
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
(s64)data->config->temp_lsb, 10000);
*val = ina238_temp_from_reg(regval, data->config->temp_resolution);
break;
case hwmon_temp_max_alarm:
err = regmap_read(data->regmap, INA238_DIAG_ALERT, &regval);
@ -564,39 +551,37 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
return 0;
}
static int ina238_write_temp(struct device *dev, u32 attr, long val)
static u16 ina238_temp_to_reg(long val, u8 resolution)
{
int fraction = 1000 - DIV_ROUND_CLOSEST(1000, BIT(resolution - 9));
val = clamp_val(val, -255000 - fraction, 255000 + fraction);
return (DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution)) & 0xffff;
}
static int ina238_write_temp_max(struct device *dev, long val)
{
struct ina238_data *data = dev_get_drvdata(dev);
int regval;
if (attr != hwmon_temp_max)
return -EOPNOTSUPP;
/* Signed */
val = clamp_val(val, -40000, 125000);
regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift;
regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift);
regval = ina238_temp_to_reg(val, data->config->temp_resolution);
return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
}
static ssize_t energy1_input_show(struct device *dev,
struct device_attribute *da, char *buf)
static int ina238_read_energy(struct device *dev, s64 *energy)
{
struct ina238_data *data = dev_get_drvdata(dev);
int ret;
u64 regval;
u64 energy;
int ret;
ret = ina238_read_reg40(data->client, SQ52206_ENERGY, &regval);
if (ret)
return ret;
/* result in uJ */
energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * 10 *
data->config->power_calculate_factor, 4 * data->rshunt);
return sysfs_emit(buf, "%llu\n", energy);
*energy = regval * data->energy_lsb;
return 0;
}
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
@ -606,9 +591,11 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
return ina238_read_current(dev, attr, val);
return ina238_read_curr(dev, attr, val);
case hwmon_power:
return ina238_read_power(dev, attr, val);
case hwmon_energy64:
return ina238_read_energy(dev, (s64 *)val);
case hwmon_temp:
return ina238_read_temp(dev, attr, val);
default:
@ -629,11 +616,14 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
case hwmon_in:
err = ina238_write_in(dev, attr, channel, val);
break;
case hwmon_curr:
err = ina238_write_curr(dev, attr, val);
break;
case hwmon_power:
err = ina238_write_power(dev, attr, val);
err = ina238_write_power_max(dev, val);
break;
case hwmon_temp:
err = ina238_write_temp(dev, attr, val);
err = ina238_write_temp_max(dev, val);
break;
default:
err = -EOPNOTSUPP;
@ -650,6 +640,7 @@ static umode_t ina238_is_visible(const void *drvdata,
{
const struct ina238_data *data = drvdata;
bool has_power_highest = data->config->has_power_highest;
bool has_energy = data->config->has_energy;
switch (type) {
case hwmon_in:
@ -667,7 +658,12 @@ static umode_t ina238_is_visible(const void *drvdata,
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
case hwmon_curr_max_alarm:
case hwmon_curr_min_alarm:
return 0444;
case hwmon_curr_max:
case hwmon_curr_min:
return 0644;
default:
return 0;
}
@ -685,6 +681,11 @@ static umode_t ina238_is_visible(const void *drvdata,
default:
return 0;
}
case hwmon_energy64:
/* hwmon_energy_input */
if (has_energy)
return 0444;
return 0;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
@ -712,11 +713,14 @@ static const struct hwmon_channel_info * const ina238_info[] = {
INA238_HWMON_IN_CONFIG),
HWMON_CHANNEL_INFO(curr,
/* 0: current through shunt */
HWMON_C_INPUT),
HWMON_C_INPUT | HWMON_C_MIN | HWMON_C_MIN_ALARM |
HWMON_C_MAX | HWMON_C_MAX_ALARM),
HWMON_CHANNEL_INFO(power,
/* 0: power */
HWMON_P_INPUT | HWMON_P_MAX |
HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST),
HWMON_CHANNEL_INFO(energy64,
HWMON_E_INPUT),
HWMON_CHANNEL_INFO(temp,
/* 0: die temperature */
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
@ -734,18 +738,8 @@ static const struct hwmon_chip_info ina238_chip_info = {
.info = ina238_info,
};
/* energy attributes are 5 bytes wide so we need u64 */
static DEVICE_ATTR_RO(energy1_input);
static struct attribute *ina238_attrs[] = {
&dev_attr_energy1_input.attr,
NULL,
};
ATTRIBUTE_GROUPS(ina238);
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;
@ -771,33 +765,48 @@ static int ina238_probe(struct i2c_client *client)
return PTR_ERR(data->regmap);
}
/* load shunt value */
data->rshunt = INA238_RSHUNT_DEFAULT;
if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
data->rshunt = pdata->shunt_uohms;
if (data->rshunt == 0) {
dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
return -EINVAL;
}
/* load shunt gain value */
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
data->gain = 4; /* Default of ADCRANGE = 0 */
if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
return -EINVAL;
}
/* Setup CONFIG register */
config = data->config->config_default;
if (chip == sq52206) {
if (data->gain == 1)
config |= SQ52206_CONFIG_ADCRANGE_HIGH; /* ADCRANGE = 10/11 is /1 */
else if (data->gain == 2)
config |= SQ52206_CONFIG_ADCRANGE_LOW; /* ADCRANGE = 01 is /2 */
} else if (data->gain == 1) {
config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */
if (data->config->current_lsb) {
data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB;
data->current_lsb = data->config->current_lsb;
} else {
/* load shunt value */
if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0)
data->rshunt = INA238_RSHUNT_DEFAULT;
if (data->rshunt == 0) {
dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
return -EINVAL;
}
/* load shunt gain value */
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
data->gain = 4; /* Default of ADCRANGE = 0 */
if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
return -EINVAL;
}
/* Setup SHUNT_CALIBRATION register with fixed value */
ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
INA238_CALIBRATION_VALUE);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
}
if (chip == sq52206) {
if (data->gain == 1) /* ADCRANGE = 10/11 is /1 */
config |= SQ52206_CONFIG_ADCRANGE_HIGH;
else if (data->gain == 2) /* ADCRANGE = 01 is /2 */
config |= SQ52206_CONFIG_ADCRANGE_LOW;
} else if (data->gain == 1) { /* ADCRANGE = 1 is /1 */
config |= INA238_CONFIG_ADCRANGE;
}
data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4;
data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain,
data->rshunt);
}
ret = regmap_write(data->regmap, INA238_CONFIG, config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
@ -812,31 +821,33 @@ static int ina238_probe(struct i2c_client *client)
return -ENODEV;
}
/* Setup SHUNT_CALIBRATION register with fixed value */
ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
INA238_CALIBRATION_VALUE);
/* Setup alert/alarm configuration */
config = INA238_DIAG_ALERT_DEFAULT;
if (device_property_read_bool(dev, "ti,alert-polarity-active-high"))
config |= INA238_DIAG_ALERT_APOL;
ret = regmap_write(data->regmap, INA238_DIAG_ALERT, config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
}
/* 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;
}
data->voltage_lsb[1] = data->config->bus_voltage_lsb;
data->power_lsb = DIV_ROUND_CLOSEST(data->current_lsb *
data->config->power_calculate_factor,
100);
data->energy_lsb = data->power_lsb * 16;
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
&ina238_chip_info,
data->config->has_energy ?
ina238_groups : NULL);
&ina238_chip_info, NULL);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
client->name, data->rshunt, data->gain);
if (data->rshunt)
dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
client->name, data->rshunt, data->gain);
return 0;
}
@ -845,6 +856,8 @@ static const struct i2c_device_id ina238_id[] = {
{ "ina228", ina228 },
{ "ina237", ina237 },
{ "ina238", ina238 },
{ "ina700", ina700 },
{ "ina780", ina780 },
{ "sq52206", sq52206 },
{ }
};
@ -863,6 +876,14 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = {
.compatible = "ti,ina238",
.data = (void *)ina238
},
{
.compatible = "ti,ina700",
.data = (void *)ina700
},
{
.compatible = "ti,ina780",
.data = (void *)ina780
},
{
.compatible = "silergy,sq52206",
.data = (void *)sq52206

View File

@ -84,6 +84,13 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
*/
#define AMD_I3255_STR "3255"
/*
* PCI Device IDs for AMD's Family 1Ah-based SOCs.
* Defining locally as IDs are not shared.
*/
#define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb
#define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b
struct k10temp_data {
struct pci_dev *pdev;
void (*read_htcreg)(struct pci_dev *pdev, u32 *regval);
@ -556,7 +563,10 @@ static const struct pci_device_id k10temp_id_table[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M78H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M00H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M20H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) },
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{}
};

View File

@ -66,7 +66,7 @@ enum systems {
LENOVO_P8,
};
static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31, 32};
static const char * const lenovo_px_ec_temp_label[] = {
"CPU1",
@ -84,9 +84,29 @@ static const char * const lenovo_px_ec_temp_label[] = {
"PCI_Z3",
"PCI_Z4",
"AMB",
"PSU1",
"PSU2",
};
static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
static int p8_temp_map[] = {0, 1, 2, 8, 9, 13, 14, 15, 16, 17, 19, 20, 33};
static const char * const lenovo_p8_ec_temp_label[] = {
"CPU1",
"CPU_DIMM_BANK1",
"CPU_DIMM_BANK2",
"M2_Z2R",
"M2_Z3R",
"DIMM_RIGHT",
"DIMM_LEFT",
"PCI_Z1",
"PCI_Z2",
"PCI_Z3",
"AMB",
"REAR_VR",
"PSU",
};
static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31};
static const char * const lenovo_gen_ec_temp_label[] = {
"CPU1",
@ -101,6 +121,7 @@ static const char * const lenovo_gen_ec_temp_label[] = {
"PCI_Z3",
"PCI_Z4",
"AMB",
"PSU",
};
static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
@ -293,6 +314,8 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = {
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
@ -327,6 +350,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = {
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
@ -359,6 +383,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = {
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
@ -388,6 +413,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = {
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
@ -545,9 +571,9 @@ static int lenovo_ec_probe(struct platform_device *pdev)
break;
case 3:
ec_data->fan_labels = p8_ec_fan_label;
ec_data->temp_labels = lenovo_gen_ec_temp_label;
ec_data->temp_labels = lenovo_p8_ec_temp_label;
ec_data->fan_map = p8_fan_map;
ec_data->temp_map = gen_temp_map;
ec_data->temp_map = p8_temp_map;
lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8;
break;
default:

View File

@ -39,6 +39,7 @@ enum lm75_type { /* keep sorted in alphabetical order */
max6626,
max31725,
mcp980x,
p3t1750,
p3t1755,
pct2075,
stds75,
@ -222,6 +223,13 @@ static const struct lm75_params device_params[] = {
.default_resolution = 9,
.default_sample_time = MSEC_PER_SEC / 18,
},
[p3t1750] = {
.clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
.default_resolution = 12,
.default_sample_time = 55,
.num_sample_times = 4,
.sample_times = (unsigned int []){ 28, 55, 110, 220 },
},
[p3t1755] = {
.clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
.default_resolution = 12,
@ -805,6 +813,7 @@ static const struct i2c_device_id lm75_i2c_ids[] = {
{ "max31725", max31725, },
{ "max31726", max31725, },
{ "mcp980x", mcp980x, },
{ "p3t1750", p3t1750, },
{ "p3t1755", p3t1755, },
{ "pct2075", pct2075, },
{ "stds75", stds75, },
@ -916,6 +925,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
.compatible = "maxim,mcp980x",
.data = (void *)mcp980x
},
{
.compatible = "nxp,p3t1750",
.data = (void *)p3t1750
},
{
.compatible = "nxp,p3t1755",
.data = (void *)p3t1755

View File

@ -1693,8 +1693,7 @@ static int ltc4282_probe(struct i2c_client *i2c)
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return dev_err_probe(dev, -ENOMEM,
"Failed to allocate memory\n");
return -ENOMEM;
st->map = devm_regmap_init_i2c(i2c, &ltc4282_regmap_config);
if (IS_ERR(st->map))

View File

@ -63,12 +63,14 @@ struct mlxreg_fan;
* @reg: register offset;
* @mask: fault mask;
* @prsnt: present register offset;
* @shift: tacho presence bit shift;
*/
struct mlxreg_fan_tacho {
bool connected;
u32 reg;
u32 mask;
u32 prsnt;
u32 shift;
};
/*
@ -113,8 +115,8 @@ struct mlxreg_fan {
int divider;
};
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state);
static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state, bool thermal);
static int
mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
@ -143,8 +145,10 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
/*
* Map channel to presence bit - drawer can be equipped with
* one or few FANs, while presence is indicated per drawer.
* Shift channel value if necessary to align with register value.
*/
if (BIT(channel / fan->tachos_per_drwr) & regval) {
if (BIT(rol32(channel, tacho->shift) / fan->tachos_per_drwr) &
regval) {
/* FAN is not connected - return zero for FAN speed. */
*val = 0;
return 0;
@ -224,8 +228,9 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
* last thermal state.
*/
if (pwm->last_hwmon_state >= pwm->last_thermal_state)
return mlxreg_fan_set_cur_state(pwm->cdev,
pwm->last_hwmon_state);
return _mlxreg_fan_set_cur_state(pwm->cdev,
pwm->last_hwmon_state,
false);
return 0;
}
return regmap_write(fan->regmap, pwm->reg, val);
@ -357,9 +362,8 @@ static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev,
return 0;
}
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state, bool thermal)
{
struct mlxreg_fan_pwm *pwm = cdev->devdata;
struct mlxreg_fan *fan = pwm->fan;
@ -369,7 +373,8 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
return -EINVAL;
/* Save thermal state. */
pwm->last_thermal_state = state;
if (thermal)
pwm->last_thermal_state = state;
state = max_t(unsigned long, state, pwm->last_hwmon_state);
err = regmap_write(fan->regmap, pwm->reg,
@ -381,6 +386,13 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
return 0;
}
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
return _mlxreg_fan_set_cur_state(cdev, state, true);
}
static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
.get_max_state = mlxreg_fan_get_max_state,
.get_cur_state = mlxreg_fan_get_cur_state,
@ -400,7 +412,7 @@ static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan,
return err;
}
return !!(regval & data->bit);
return data->slot ? (data->slot <= regval ? 1 : 0) : !!(regval & data->bit);
}
static int mlxreg_pwm_connect_verify(struct mlxreg_fan *fan,
@ -537,7 +549,15 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan,
return err;
}
drwr_avail = hweight32(regval);
/*
* The number of drawers could be specified in registers by counters for newer
* systems, or by bitmasks for older systems. In case the data is provided by
* counter, it is indicated through 'version' field.
*/
if (pdata->version)
drwr_avail = regval;
else
drwr_avail = hweight32(regval);
if (!tacho_avail || !drwr_avail || tacho_avail < drwr_avail) {
dev_err(fan->dev, "Configuration is invalid: drawers num %d tachos num %d\n",
drwr_avail, tacho_avail);

View File

@ -167,7 +167,8 @@ static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val)
static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val)
{
u32 ret, tmp = 0;
u32 tmp = 0;
int ret;
ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank,
reg, 0, &tmp);

View File

@ -721,11 +721,6 @@ static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev)
return init_device(drvdata, drvdata->update_interval);
}
static void mutex_fini(void *lock)
{
mutex_destroy(lock);
}
static int nzxt_smart2_hid_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@ -741,8 +736,7 @@ static int nzxt_smart2_hid_probe(struct hid_device *hdev,
init_waitqueue_head(&drvdata->wq);
mutex_init(&drvdata->mutex);
ret = devm_add_action_or_reset(&hdev->dev, mutex_fini, &drvdata->mutex);
ret = devm_mutex_init(&hdev->dev, &drvdata->mutex);
if (ret)
return ret;

View File

@ -52,7 +52,8 @@ config SENSORS_ADM1275
help
If you say yes here you get hardware monitoring support for Analog
Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281,
ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors.
ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and
Digital Power Monitors.
This driver can also be built as a module. If so, the module will
be called adm1275.
@ -373,6 +374,15 @@ config SENSORS_MP2856
This driver can also be built as a module. If so, the module will
be called mp2856.
config SENSORS_MP2869
tristate "MPS MP2869"
help
If you say yes here you get hardware monitoring support for MPS
MP2869 Dual Loop Digital Multi-Phase Controller.
This driver can also be built as a module. If so, the module will
be called mp2869.
config SENSORS_MP2888
tristate "MPS MP2888"
help
@ -391,6 +401,15 @@ config SENSORS_MP2891
This driver can also be built as a module. If so, the module will
be called mp2891.
config SENSORS_MP29502
tristate "MPS MP29502"
help
If you say yes here you get hardware monitoring support for MPS
MP29502 Dual Loop Digital Multi-Phase Controller.
This driver can also be built as a module. If so, the module will
be called mp29502.
config SENSORS_MP2975
tristate "MPS MP2975"
help

View File

@ -37,8 +37,10 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
obj-$(CONFIG_SENSORS_MP2856) += mp2856.o
obj-$(CONFIG_SENSORS_MP2869) += mp2869.o
obj-$(CONFIG_SENSORS_MP2888) += mp2888.o
obj-$(CONFIG_SENSORS_MP2891) += mp2891.o
obj-$(CONFIG_SENSORS_MP29502) += mp29502.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_MP2993) += mp2993.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o

View File

@ -18,7 +18,8 @@
#include <linux/log2.h>
#include "pmbus.h"
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, adm1293, adm1294 };
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
adm1293, adm1294, sq24905c };
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
@ -486,6 +487,7 @@ static const struct i2c_device_id adm1275_id[] = {
{ "adm1281", adm1281 },
{ "adm1293", adm1293 },
{ "adm1294", adm1294 },
{ "mc09c", sq24905c },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1275_id);
@ -532,7 +534,8 @@ static int adm1275_probe(struct i2c_client *client)
dev_err(&client->dev, "Failed to read Manufacturer ID\n");
return ret;
}
if (ret != 3 || strncmp(block_buffer, "ADI", 3)) {
if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) &&
(ret != 2 || strncmp(block_buffer, "SY", 2))) {
dev_err(&client->dev, "Unsupported Manufacturer ID\n");
return -ENODEV;
}
@ -558,7 +561,8 @@ static int adm1275_probe(struct i2c_client *client)
if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
mid->driver_data == adm1293 || mid->driver_data == adm1294)
mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
mid->driver_data == sq24905c)
config_read_fn = i2c_smbus_read_word_data;
else
config_read_fn = i2c_smbus_read_byte_data;
@ -708,6 +712,7 @@ static int adm1275_probe(struct i2c_client *client)
break;
case adm1278:
case adm1281:
case sq24905c:
data->have_vout = true;
data->have_pin_max = true;
data->have_temp_max = true;

View File

@ -61,6 +61,8 @@ enum chips {
raa228004,
raa228006,
raa228228,
raa228244,
raa228246,
raa229001,
raa229004,
raa229621,
@ -464,6 +466,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = {
{"raa228004", raa_dmpvr2_hv},
{"raa228006", raa_dmpvr2_hv},
{"raa228228", raa_dmpvr2_2rail_nontc},
{"raa228244", raa_dmpvr2_2rail_nontc},
{"raa228246", raa_dmpvr2_2rail_nontc},
{"raa229001", raa_dmpvr2_2rail},
{"raa229004", raa_dmpvr2_2rail},
{"raa229621", raa_dmpvr2_2rail},
@ -512,6 +516,8 @@ static const struct of_device_id isl68137_of_match[] = {
{ .compatible = "renesas,raa228004", .data = (void *)raa_dmpvr2_hv },
{ .compatible = "renesas,raa228006", .data = (void *)raa_dmpvr2_hv },
{ .compatible = "renesas,raa228228", .data = (void *)raa_dmpvr2_2rail_nontc },
{ .compatible = "renesas,raa228244", .data = (void *)raa_dmpvr2_2rail_nontc },
{ .compatible = "renesas,raa228246", .data = (void *)raa_dmpvr2_2rail_nontc },
{ .compatible = "renesas,raa229001", .data = (void *)raa_dmpvr2_2rail },
{ .compatible = "renesas,raa229004", .data = (void *)raa_dmpvr2_2rail },
{ .compatible = "renesas,raa229621", .data = (void *)raa_dmpvr2_2rail },

View File

@ -0,0 +1,659 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2869)
*/
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include "pmbus.h"
/*
* Vender specific registers, the register MFR_SVI3_IOUT_PRT(0x67),
* READ_PIN_EST(0x94)and READ_IIN_EST(0x95) redefine the standard
* PMBUS register. The MFR_VOUT_LOOP_CTRL(0x29) is used to identify
* the vout scale and the MFR_SVI3_IOUT_PRT(0x67) is used to identify
* the iout scale. The READ_PIN_EST(0x94) is used to read input power
* per rail. The MP2891 does not have standard READ_IIN register(0x89),
* the iin telemetry can be obtained through the vendor redefined
* register READ_IIN_EST(0x95).
*/
#define MFR_SVI3_IOUT_PRT 0x67
#define MFR_READ_PIN_EST 0x94
#define MFR_READ_IIN_EST 0x95
#define MFR_TSNS_FLT_SET 0xBB
#define MP2869_VIN_OV_FAULT_GAIN 4
#define MP2869_READ_VOUT_DIV 1024
#define MP2869_READ_IOUT_DIV 32
#define MP2869_OVUV_LIMIT_SCALE 10
#define MP2869_OVUV_DELTA_SCALE 50
#define MP2869_TEMP_LIMIT_OFFSET 40
#define MP2869_IOUT_LIMIT_UINT 8
#define MP2869_POUT_OP_GAIN 2
#define MP2869_PAGE_NUM 2
#define MP2869_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
PMBUS_HAVE_IIN | \
PMBUS_HAVE_STATUS_VOUT | \
PMBUS_HAVE_STATUS_IOUT | \
PMBUS_HAVE_STATUS_TEMP | \
PMBUS_HAVE_STATUS_INPUT)
#define MP2869_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \
PMBUS_HAVE_STATUS_VOUT | \
PMBUS_HAVE_STATUS_IOUT | \
PMBUS_HAVE_STATUS_TEMP | \
PMBUS_HAVE_STATUS_INPUT)
struct mp2869_data {
struct pmbus_driver_info info;
bool mfr_thwn_flt_en;
int vout_scale[MP2869_PAGE_NUM];
int iout_scale[MP2869_PAGE_NUM];
};
static const int mp2869_vout_sacle[8] = {6400, 5120, 2560, 2048, 1024,
4, 2, 1};
static const int mp2869_iout_sacle[8] = {32, 1, 2, 4, 8, 16, 32, 64};
#define to_mp2869_data(x) container_of(x, struct mp2869_data, info)
static u16 mp2869_reg2data_linear11(u16 word)
{
s16 exponent;
s32 mantissa;
s64 val;
exponent = ((s16)word) >> 11;
mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
val = mantissa;
if (exponent >= 0)
val <<= exponent;
else
val >>= -exponent;
return val;
}
static int
mp2869_identify_thwn_flt(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp2869_data *data = to_mp2869_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
if (ret < 0)
return ret;
data->mfr_thwn_flt_en = FIELD_GET(GENMASK(13, 13), ret);
return 0;
}
static int
mp2869_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp2869_data *data = to_mp2869_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP);
if (ret < 0)
return ret;
/*
* The output voltage is equal to the READ_VOUT(0x8B) register value multiply
* by vout_scale.
* Obtain vout scale from the register PMBUS_VOUT_SCALE_LOOP, bits 12-10
* PMBUS_VOUT_SCALE_LOOP[12:10]:
* 000b - 6.25mV/LSB, 001b - 5mV/LSB, 010b - 2.5mV/LSB, 011b - 2mV/LSB
* 100b - 1mV/Lsb, 101b - (1/256)mV/LSB, 110b - (1/512)mV/LSB,
* 111b - (1/1024)mV/LSB
*/
data->vout_scale[page] = mp2869_vout_sacle[FIELD_GET(GENMASK(12, 10), ret)];
return 0;
}
static int
mp2869_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp2869_data *data = to_mp2869_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
if (ret < 0)
return ret;
/*
* The output current is equal to the READ_IOUT(0x8C) register value
* multiply by iout_scale.
* Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0].
* The value is selected as below:
* 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB,
* 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB
* 110b - 1A/LSB, 111b - 2A/LSB
*/
data->iout_scale[page] = mp2869_iout_sacle[FIELD_GET(GENMASK(2, 0), ret)];
return 0;
}
static int mp2869_read_byte_data(struct i2c_client *client, int page, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp2869_data *data = to_mp2869_data(info);
int ret;
switch (reg) {
case PMBUS_VOUT_MODE:
/*
* The calculation of vout in this driver is based on direct format.
* As a result, the format of vout is enforced to direct.
*/
ret = PB_VOUT_MODE_DIRECT;
break;
case PMBUS_STATUS_BYTE:
/*
* If the tsns digital fault is enabled, the TEMPERATURE flag
* of PMBUS_STATUS_BYTE should come from STATUS_MFR_SPECIFIC
* register bit1.
*/
if (!data->mfr_thwn_flt_en)
return -ENODATA;
ret = pmbus_read_byte_data(client, page, reg);
if (ret < 0)
return ret;
ret = (ret & ~GENMASK(2, 2)) |
FIELD_PREP(GENMASK(2, 2),
FIELD_GET(GENMASK(1, 1),
pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC)));
break;
case PMBUS_STATUS_TEMPERATURE:
/*
* If the tsns digital fault is enabled, the OT Fault and OT Warning
* flag of PMBUS_STATUS_TEMPERATURE should come from STATUS_MFR_SPECIFIC
* register bit1.
*/
if (!data->mfr_thwn_flt_en)
return -ENODATA;
ret = pmbus_read_byte_data(client, page, reg);
if (ret < 0)
return ret;
ret = (ret & ~GENMASK(7, 6)) |
FIELD_PREP(GENMASK(6, 6),
FIELD_GET(GENMASK(1, 1),
pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC))) |
FIELD_PREP(GENMASK(7, 7),
FIELD_GET(GENMASK(1, 1),
pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC)));
break;
default:
ret = -ENODATA;
break;
}
return ret;
}
static int mp2869_read_word_data(struct i2c_client *client, int page, int phase,
int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp2869_data *data = to_mp2869_data(info);
int ret;
switch (reg) {
case PMBUS_STATUS_WORD:
/*
* If the tsns digital fault is enabled, the OT Fault flag
* of PMBUS_STATUS_WORD should come from STATUS_MFR_SPECIFIC
* register bit1.
*/
if (!data->mfr_thwn_flt_en)
return -ENODATA;
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = (ret & ~GENMASK(2, 2)) |
FIELD_PREP(GENMASK(2, 2),
FIELD_GET(GENMASK(1, 1),
pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC)));
break;
case PMBUS_READ_VIN:
/*
* The MP2869 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
* 31.25mV/LSB. And the vin scale is set to 31.25mV/Lsb(using r/m/b scale)
* in MP2869 pmbus_driver_info struct, so the word data bit0-bit10 can be
* returned to pmbus core directly.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(10, 0), ret);
break;
case PMBUS_READ_IIN:
/*
* The MP2869 redefine the standard 0x95 register as iin telemetry
* per rail.
*/
ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
if (ret < 0)
return ret;
break;
case PMBUS_READ_PIN:
/*
* The MP2869 redefine the standard 0x94 register as pin telemetry
* per rail. The MP2869 MFR_READ_PIN_EST register is linear11 format,
* but the pin scale is set to 1W/Lsb(using r/m/b scale). As a result,
* the pin read from MP2869 should be converted to W, then return
* the result to pmbus core.
*/
ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
if (ret < 0)
return ret;
ret = mp2869_reg2data_linear11(ret);
break;
case PMBUS_READ_VOUT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page],
MP2869_READ_VOUT_DIV);
break;
case PMBUS_READ_IOUT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page],
MP2869_READ_IOUT_DIV);
break;
case PMBUS_READ_POUT:
/*
* The MP2869 PMBUS_READ_POUT register is linear11 format, but the pout
* scale is set to 1W/Lsb(using r/m/b scale). As a result, the pout read
* from MP2869 should be converted to W, then return the result to pmbus
* core.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = mp2869_reg2data_linear11(ret);
break;
case PMBUS_READ_TEMPERATURE_1:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(10, 0), ret);
break;
case PMBUS_VOUT_OV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
if (FIELD_GET(GENMASK(12, 9), ret))
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE +
(FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
else
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
break;
case PMBUS_VOUT_UV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
if (FIELD_GET(GENMASK(12, 9), ret))
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE -
(FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
else
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
break;
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_OT_WARN_LIMIT:
/*
* The scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
* is 1°C/LSB and they have 40°C offset.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = (ret & GENMASK(7, 0)) - MP2869_TEMP_LIMIT_OFFSET;
break;
case PMBUS_VIN_OV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = (ret & GENMASK(7, 0)) * MP2869_VIN_OV_FAULT_GAIN;
break;
case PMBUS_VIN_UV_WARN_LIMIT:
case PMBUS_VIN_UV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(9, 0), ret);
break;
case PMBUS_IOUT_OC_FAULT_LIMIT:
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] *
MP2869_IOUT_LIMIT_UINT, MP2869_READ_IOUT_DIV);
break;
case PMBUS_POUT_OP_WARN_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = (ret & GENMASK(7, 0)) * MP2869_POUT_OP_GAIN;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int mp2869_write_word_data(struct i2c_client *client, int page, int reg,
u16 word)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp2869_data *data = to_mp2869_data(info);
int ret;
switch (reg) {
case PMBUS_VOUT_UV_FAULT_LIMIT:
/*
* The MP2869 PMBUS_VOUT_UV_FAULT_LIMIT[8:0] is the limit value,
* and bit9-bit15 should not be changed.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
if (FIELD_GET(GENMASK(12, 9), ret))
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(8, 0)) |
FIELD_PREP(GENMASK(8, 0),
DIV_ROUND_CLOSEST(word +
(FIELD_GET(GENMASK(12, 9),
ret) + 1) *
MP2869_OVUV_DELTA_SCALE,
MP2869_OVUV_LIMIT_SCALE)));
else
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(8, 0)) |
FIELD_PREP(GENMASK(8, 0),
DIV_ROUND_CLOSEST(word,
MP2869_OVUV_LIMIT_SCALE)));
break;
case PMBUS_VOUT_OV_FAULT_LIMIT:
/*
* The MP2869 PMBUS_VOUT_OV_FAULT_LIMIT[8:0] is the limit value,
* and bit9-bit15 should not be changed.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
if (FIELD_GET(GENMASK(12, 9), ret))
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(8, 0)) |
FIELD_PREP(GENMASK(8, 0),
DIV_ROUND_CLOSEST(word -
(FIELD_GET(GENMASK(12, 9),
ret) + 1) *
MP2869_OVUV_DELTA_SCALE,
MP2869_OVUV_LIMIT_SCALE)));
else
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(8, 0)) |
FIELD_PREP(GENMASK(8, 0),
DIV_ROUND_CLOSEST(word,
MP2869_OVUV_LIMIT_SCALE)));
break;
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_OT_WARN_LIMIT:
/*
* If the tsns digital fault is enabled, the PMBUS_OT_FAULT_LIMIT and
* PMBUS_OT_WARN_LIMIT can not be written.
*/
if (data->mfr_thwn_flt_en)
return -EINVAL;
/*
* The MP2869 scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
* have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15
* should not be changed.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(7, 0)) |
FIELD_PREP(GENMASK(7, 0),
word + MP2869_TEMP_LIMIT_OFFSET));
break;
case PMBUS_VIN_OV_FAULT_LIMIT:
/*
* The MP2869 PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
* should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb,
* but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data
* should divide by MP2869_VIN_OV_FAULT_GAIN(4)
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(7, 0)) |
FIELD_PREP(GENMASK(7, 0),
DIV_ROUND_CLOSEST(word,
MP2869_VIN_OV_FAULT_GAIN)));
break;
case PMBUS_VIN_UV_WARN_LIMIT:
case PMBUS_VIN_UV_FAULT_LIMIT:
/*
* The PMBUS_VIN_UV_LIMIT[9:0] is the limit value, and bit10-bit15 should
* not be changed. The scale of PMBUS_VIN_UV_LIMIT is 31.25mV/Lsb, and the
* vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data can
* be written directly.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(9, 0)) |
FIELD_PREP(GENMASK(9, 0),
word));
break;
case PMBUS_IOUT_OC_FAULT_LIMIT:
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_write_word_data(client, page, reg,
DIV_ROUND_CLOSEST(word * MP2869_READ_IOUT_DIV,
MP2869_IOUT_LIMIT_UINT *
data->iout_scale[page]));
break;
case PMBUS_POUT_OP_WARN_LIMIT:
/*
* The POUT_OP_WARN_LIMIT[11:0] is the limit value, and bit12-bit15 should
* not be changed. The scale of POUT_OP_WARN_LIMIT is 2W/Lsb.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(11, 0)) |
FIELD_PREP(GENMASK(11, 0),
DIV_ROUND_CLOSEST(word,
MP2869_POUT_OP_GAIN)));
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int mp2869_identify(struct i2c_client *client, struct pmbus_driver_info *info)
{
int ret;
/* Identify whether tsns digital fault is enable */
ret = mp2869_identify_thwn_flt(client, info, 1);
if (ret < 0)
return 0;
/* Identify vout scale for rail1. */
ret = mp2869_identify_vout_scale(client, info, 0);
if (ret < 0)
return ret;
/* Identify vout scale for rail2. */
ret = mp2869_identify_vout_scale(client, info, 1);
if (ret < 0)
return ret;
/* Identify iout scale for rail 1. */
ret = mp2869_identify_iout_scale(client, info, 0);
if (ret < 0)
return ret;
/* Identify iout scale for rail 2. */
return mp2869_identify_iout_scale(client, info, 1);
}
static const struct pmbus_driver_info mp2869_info = {
.pages = MP2869_PAGE_NUM,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_CURRENT_IN] = linear,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_TEMPERATURE] = direct,
.format[PSC_POWER] = direct,
.format[PSC_VOLTAGE_OUT] = direct,
.m[PSC_VOLTAGE_IN] = 32,
.R[PSC_VOLTAGE_IN] = 0,
.b[PSC_VOLTAGE_IN] = 0,
.m[PSC_VOLTAGE_OUT] = 1,
.R[PSC_VOLTAGE_OUT] = 3,
.b[PSC_VOLTAGE_OUT] = 0,
.m[PSC_CURRENT_OUT] = 1,
.R[PSC_CURRENT_OUT] = 0,
.b[PSC_CURRENT_OUT] = 0,
.m[PSC_TEMPERATURE] = 1,
.R[PSC_TEMPERATURE] = 0,
.b[PSC_TEMPERATURE] = 0,
.m[PSC_POWER] = 1,
.R[PSC_POWER] = 0,
.b[PSC_POWER] = 0,
.func[0] = MP2869_RAIL1_FUNC,
.func[1] = MP2869_RAIL2_FUNC,
.read_word_data = mp2869_read_word_data,
.write_word_data = mp2869_write_word_data,
.read_byte_data = mp2869_read_byte_data,
.identify = mp2869_identify,
};
static int mp2869_probe(struct i2c_client *client)
{
struct pmbus_driver_info *info;
struct mp2869_data *data;
data = devm_kzalloc(&client->dev, sizeof(struct mp2869_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
memcpy(&data->info, &mp2869_info, sizeof(*info));
info = &data->info;
return pmbus_do_probe(client, info);
}
static const struct i2c_device_id mp2869_id[] = {
{"mp2869", 0},
{"mp29608", 1},
{"mp29612", 2},
{"mp29816", 3},
{}
};
MODULE_DEVICE_TABLE(i2c, mp2869_id);
static const struct of_device_id __maybe_unused mp2869_of_match[] = {
{.compatible = "mps,mp2869", .data = (void *)0},
{.compatible = "mps,mp29608", .data = (void *)1},
{.compatible = "mps,mp29612", .data = (void *)2},
{.compatible = "mps,mp29816", .data = (void *)3},
{}
};
MODULE_DEVICE_TABLE(of, mp2869_of_match);
static struct i2c_driver mp2869_driver = {
.driver = {
.name = "mp2869",
.of_match_table = mp2869_of_match,
},
.probe = mp2869_probe,
.id_table = mp2869_id,
};
module_i2c_driver(mp2869_driver);
MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net>");
MODULE_DESCRIPTION("PMBus driver for MPS MP2869");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@ -0,0 +1,670 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP29502)
*/
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include "pmbus.h"
#define MFR_VOUT_SCALE_LOOP 0x29
#define MFR_SVI3_IOUT_PRT 0x67
#define MFR_READ_PIN_EST 0x94
#define MFR_READ_IIN_EST 0x95
#define MFR_VOUT_PROT1 0x3D
#define MFR_VOUT_PROT2 0x51
#define MFR_SLOPE_CNT_SET 0xA8
#define MFR_TSNS_FLT_SET 0xBB
#define MP29502_VIN_OV_GAIN 4
#define MP29502_TEMP_LIMIT_OFFSET 40
#define MP29502_READ_VOUT_DIV 1024
#define MP29502_READ_IOUT_DIV 32
#define MP29502_IOUT_LIMIT_UINT 8
#define MP29502_OVUV_LIMIT_SCALE 10
#define MP28502_VOUT_OV_GAIN 512
#define MP28502_VOUT_OV_SCALE 40
#define MP29502_VOUT_UV_OFFSET 36
#define MP29502_PIN_GAIN 2
#define MP29502_IIN_DIV 2
#define MP29502_PAGE_NUM 1
#define MP29502_RAIL_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
PMBUS_HAVE_IIN | \
PMBUS_HAVE_STATUS_VOUT | \
PMBUS_HAVE_STATUS_IOUT | \
PMBUS_HAVE_STATUS_TEMP | \
PMBUS_HAVE_STATUS_INPUT)
struct mp29502_data {
struct pmbus_driver_info info;
int vout_scale;
int vout_bottom_div;
int vout_top_div;
int ovp_div;
int iout_scale;
};
#define to_mp29502_data(x) container_of(x, struct mp29502_data, info)
static u16 mp29502_reg2data_linear11(u16 word)
{
s16 exponent;
s32 mantissa;
s64 val;
exponent = ((s16)word) >> 11;
mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
val = mantissa;
if (exponent >= 0)
val <<= exponent;
else
val >>= -exponent;
return val;
}
static int
mp29502_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp29502_data *data = to_mp29502_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP);
if (ret < 0)
return ret;
switch (FIELD_GET(GENMASK(12, 10), ret)) {
case 0:
data->vout_scale = 6400;
break;
case 1:
data->vout_scale = 5120;
break;
case 2:
data->vout_scale = 2560;
break;
case 3:
data->vout_scale = 2048;
break;
case 4:
data->vout_scale = 1024;
break;
case 5:
data->vout_scale = 4;
break;
case 6:
data->vout_scale = 2;
break;
case 7:
data->vout_scale = 1;
break;
default:
data->vout_scale = 1;
break;
}
return 0;
}
static int
mp29502_identify_vout_divider(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp29502_data *data = to_mp29502_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT1);
if (ret < 0)
return ret;
data->vout_bottom_div = FIELD_GET(GENMASK(11, 0), ret);
ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT2);
if (ret < 0)
return ret;
data->vout_top_div = FIELD_GET(GENMASK(14, 0), ret);
return 0;
}
static int
mp29502_identify_ovp_divider(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp29502_data *data = to_mp29502_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_SLOPE_CNT_SET);
if (ret < 0)
return ret;
data->ovp_div = FIELD_GET(GENMASK(9, 0), ret);
return 0;
}
static int
mp29502_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
int page)
{
struct mp29502_data *data = to_mp29502_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
if (ret < 0)
return ret;
switch (ret & GENMASK(2, 0)) {
case 0:
case 6:
data->iout_scale = 32;
break;
case 1:
data->iout_scale = 1;
break;
case 2:
data->iout_scale = 2;
break;
case 3:
data->iout_scale = 4;
break;
case 4:
data->iout_scale = 8;
break;
case 5:
data->iout_scale = 16;
break;
default:
data->iout_scale = 64;
break;
}
return 0;
}
static int mp29502_read_vout_ov_limit(struct i2c_client *client, struct mp29502_data *data)
{
int ret;
int ov_value;
/*
* This is because the vout ov fault limit value comes from
* page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
* value comes from page0 reg. So the page should be set to
* 0 after the reading of vout ov limit.
*/
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
if (ret < 0)
return ret;
ov_value = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(12, 7), ret) *
MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE,
data->ovp_div);
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
if (ret < 0)
return ret;
return ov_value;
}
static int mp29502_write_vout_ov_limit(struct i2c_client *client, u16 word,
struct mp29502_data *data)
{
int ret;
/*
* This is because the vout ov fault limit value comes from
* page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
* value comes from page0 reg. So the page should be set to
* 0 after the writing of vout ov limit.
*/
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
if (ret < 0)
return ret;
ret = i2c_smbus_write_word_data(client, MFR_TSNS_FLT_SET,
(ret & ~GENMASK(12, 7)) |
FIELD_PREP(GENMASK(12, 7),
DIV_ROUND_CLOSEST(word * data->ovp_div,
MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE)));
return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
}
static int mp29502_read_byte_data(struct i2c_client *client, int page, int reg)
{
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
if (ret < 0)
return ret;
switch (reg) {
case PMBUS_VOUT_MODE:
ret = PB_VOUT_MODE_DIRECT;
break;
default:
ret = -ENODATA;
break;
}
return ret;
}
static int mp29502_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp29502_data *data = to_mp29502_data(info);
int ret;
switch (reg) {
case PMBUS_STATUS_WORD:
ret = -ENODATA;
break;
case PMBUS_READ_VIN:
/*
* The MP29502 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
* 125mV/LSB. And the vin scale is set to 125mV/Lsb(using r/m/b scale)
* in MP29502 pmbus_driver_info struct, so the word data bit0-bit10 can
* be returned to pmbus core directly.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(10, 0), ret);
break;
case PMBUS_READ_VOUT:
/*
* The MP29502 PMBUS_READ_VOUT[11:0] is the vout value, and vout
* value is calculated based on vout scale and vout divider.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) *
data->vout_scale *
(data->vout_bottom_div +
4 * data->vout_top_div),
MP29502_READ_VOUT_DIV *
data->vout_bottom_div);
break;
case PMBUS_READ_IIN:
/*
* The MP29502 MFR_READ_IIN_EST register is linear11 format, and the
* exponent is not a constant value. But the iin scale is set to
* 1A/Lsb(using r/m/b scale). As a result, the iin read from MP29502
* should be calculated to A, then return the result to pmbus core.
*/
ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret),
MP29502_IIN_DIV);
break;
case PMBUS_READ_PIN:
/*
* The MP29502 MFR_READ_PIN_EST register is linear11 format, and the
* exponent is not a constant value. But the pin scale is set to
* 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
* should be calculated to W, then return the result to pmbus core.
*/
ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
if (ret < 0)
return ret;
ret = mp29502_reg2data_linear11(ret) * MP29502_PIN_GAIN;
break;
case PMBUS_READ_POUT:
/*
* The MP29502 PMBUS_READ_POUT register is linear11 format, and the
* exponent is not a constant value. But the pout scale is set to
* 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
* should be calculated to W, then return the result to pmbus core.
* And the pout is calculated based on vout divider.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret) *
(data->vout_bottom_div +
4 * data->vout_top_div),
data->vout_bottom_div);
break;
case PMBUS_READ_IOUT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale,
MP29502_READ_IOUT_DIV);
break;
case PMBUS_READ_TEMPERATURE_1:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(10, 0), ret);
break;
case PMBUS_VIN_OV_FAULT_LIMIT:
/*
* The MP29502 PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but
* the vin scale is set to 125mV/Lsb(using r/m/b scale),
* so the word data should multiply by 4.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(7, 0), ret) * MP29502_VIN_OV_GAIN;
break;
case PMBUS_VIN_UV_WARN_LIMIT:
case PMBUS_VIN_UV_FAULT_LIMIT:
/*
* The MP29502 PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT
* scale is 125mV/Lsb, but the vin scale is set to 125mV/Lsb(using
* r/m/b scale), so the word data bit0-bit9 can be returned to pmbus
* core directly.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = FIELD_GET(GENMASK(9, 0), ret);
break;
case PMBUS_VOUT_OV_FAULT_LIMIT:
/*
* The MP29502 vout ov fault limit value comes from
* page1 MFR_TSNS_FLT_SET[12:7].
*/
ret = mp29502_read_vout_ov_limit(client, data);
if (ret < 0)
return ret;
break;
case PMBUS_VOUT_UV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((FIELD_GET(GENMASK(8, 0), ret) *
MP29502_OVUV_LIMIT_SCALE -
MP29502_VOUT_UV_OFFSET) *
(data->vout_bottom_div +
4 * data->vout_top_div),
data->vout_bottom_div);
break;
case PMBUS_IOUT_OC_FAULT_LIMIT:
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) *
data->iout_scale *
MP29502_IOUT_LIMIT_UINT,
MP29502_READ_IOUT_DIV);
break;
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_OT_WARN_LIMIT:
/*
* The scale of MP29502 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
* is 1°C/LSB and they have 40°C offset.
*/
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
return ret;
ret = (ret & GENMASK(7, 0)) - MP29502_TEMP_LIMIT_OFFSET;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int mp29502_write_word_data(struct i2c_client *client, int page, int reg,
u16 word)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct mp29502_data *data = to_mp29502_data(info);
int ret;
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
if (ret < 0)
return ret;
switch (reg) {
case PMBUS_VIN_OV_FAULT_LIMIT:
/*
* The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value,
* and bit8-bit15 should not be changed. The scale of
* PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but the vin
* scale is set to 125mV/Lsb(using r/m/b scale), so
* the word data should divide by 4.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(7, 0)) |
FIELD_PREP(GENMASK(7, 0),
DIV_ROUND_CLOSEST(word,
MP29502_VIN_OV_GAIN)));
break;
case PMBUS_VIN_UV_WARN_LIMIT:
case PMBUS_VIN_UV_FAULT_LIMIT:
/*
* The PMBUS_VIN_UV_WARN_LIMIT[9:0] and PMBUS_VIN_UV_FAULT_LIMIT[9:0]
* are the limit value, and bit10-bit15 should not be changed.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(9, 0)) |
FIELD_PREP(GENMASK(9, 0),
word));
break;
case PMBUS_VOUT_OV_FAULT_LIMIT:
ret = mp29502_write_vout_ov_limit(client, word, data);
if (ret < 0)
return ret;
break;
case PMBUS_VOUT_UV_FAULT_LIMIT:
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(8, 0)) |
FIELD_PREP(GENMASK(8, 0),
DIV_ROUND_CLOSEST(word *
data->vout_bottom_div +
MP29502_VOUT_UV_OFFSET *
(data->vout_bottom_div +
4 * data->vout_top_div),
MP29502_OVUV_LIMIT_SCALE *
(data->vout_bottom_div +
4 * data->vout_top_div))));
break;
case PMBUS_IOUT_OC_FAULT_LIMIT:
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_write_word_data(client, page, reg,
DIV_ROUND_CLOSEST(word *
MP29502_READ_IOUT_DIV,
MP29502_IOUT_LIMIT_UINT *
data->iout_scale));
break;
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_OT_WARN_LIMIT:
/*
* The PMBUS_OT_FAULT_LIMIT[7:0] and PMBUS_OT_WARN_LIMIT[7:0]
* are the limit value, and bit8-bit15 should not be changed.
*/
ret = pmbus_read_word_data(client, page, 0xff, reg);
if (ret < 0)
return ret;
ret = pmbus_write_word_data(client, page, reg,
(ret & ~GENMASK(7, 0)) |
FIELD_PREP(GENMASK(7, 0),
word + MP29502_TEMP_LIMIT_OFFSET));
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int mp29502_identify(struct i2c_client *client, struct pmbus_driver_info *info)
{
int ret;
/* Identify vout scale */
ret = mp29502_identify_vout_scale(client, info, 0);
if (ret < 0)
return ret;
/* Identify vout divider. */
ret = mp29502_identify_vout_divider(client, info, 1);
if (ret < 0)
return ret;
/* Identify ovp divider. */
ret = mp29502_identify_ovp_divider(client, info, 1);
if (ret < 0)
return ret;
/* Identify iout scale */
return mp29502_identify_iout_scale(client, info, 0);
}
static const struct pmbus_driver_info mp29502_info = {
.pages = MP29502_PAGE_NUM,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_TEMPERATURE] = direct,
.format[PSC_CURRENT_IN] = direct,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_VOLTAGE_OUT] = direct,
.format[PSC_POWER] = direct,
.m[PSC_VOLTAGE_IN] = 8,
.R[PSC_VOLTAGE_IN] = 0,
.b[PSC_VOLTAGE_IN] = 0,
.m[PSC_VOLTAGE_OUT] = 1,
.R[PSC_VOLTAGE_OUT] = 3,
.b[PSC_VOLTAGE_OUT] = 0,
.m[PSC_TEMPERATURE] = 1,
.R[PSC_TEMPERATURE] = 0,
.b[PSC_TEMPERATURE] = 0,
.m[PSC_CURRENT_IN] = 1,
.R[PSC_CURRENT_IN] = 0,
.b[PSC_CURRENT_IN] = 0,
.m[PSC_CURRENT_OUT] = 1,
.R[PSC_CURRENT_OUT] = 0,
.b[PSC_CURRENT_OUT] = 0,
.m[PSC_POWER] = 1,
.R[PSC_POWER] = 0,
.b[PSC_POWER] = 0,
.func[0] = MP29502_RAIL_FUNC,
.read_word_data = mp29502_read_word_data,
.read_byte_data = mp29502_read_byte_data,
.write_word_data = mp29502_write_word_data,
.identify = mp29502_identify,
};
static int mp29502_probe(struct i2c_client *client)
{
struct pmbus_driver_info *info;
struct mp29502_data *data;
data = devm_kzalloc(&client->dev, sizeof(struct mp29502_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
memcpy(&data->info, &mp29502_info, sizeof(*info));
info = &data->info;
return pmbus_do_probe(client, info);
}
static const struct i2c_device_id mp29502_id[] = {
{"mp29502", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, mp29502_id);
static const struct of_device_id __maybe_unused mp29502_of_match[] = {
{.compatible = "mps,mp29502"},
{}
};
MODULE_DEVICE_TABLE(of, mp29502_of_match);
static struct i2c_driver mp29502_driver = {
.driver = {
.name = "mp29502",
.of_match_table = mp29502_of_match,
},
.probe = mp29502_probe,
.id_table = mp29502_id,
};
module_i2c_driver(mp29502_driver);
MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net");
MODULE_DESCRIPTION("PMBus driver for MPS MP29502");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@ -8,6 +8,8 @@
#include <linux/of_device.h>
#include "pmbus.h"
enum chips { mp5990, mp5998 };
#define MP5990_EFUSE_CFG (0xC4)
#define MP5990_VOUT_FORMAT BIT(9)
@ -110,10 +112,53 @@ static struct pmbus_driver_info mp5990_info = {
.read_word_data = mp5990_read_word_data,
};
static struct pmbus_driver_info mp5998_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_VOLTAGE_OUT] = direct,
.format[PSC_CURRENT_IN] = direct,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_POWER] = direct,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_VOLTAGE_IN] = 64,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = 0,
.m[PSC_VOLTAGE_OUT] = 64,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = 0,
.m[PSC_CURRENT_IN] = 16,
.b[PSC_CURRENT_IN] = 0,
.R[PSC_CURRENT_IN] = 0,
.m[PSC_CURRENT_OUT] = 16,
.b[PSC_CURRENT_OUT] = 0,
.R[PSC_CURRENT_OUT] = 0,
.m[PSC_POWER] = 2,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = 0,
.m[PSC_TEMPERATURE] = 1,
.b[PSC_TEMPERATURE] = 0,
.R[PSC_TEMPERATURE] = 0,
.func[0] =
PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
.read_byte_data = mp5990_read_byte_data,
.read_word_data = mp5990_read_word_data,
};
static const struct i2c_device_id mp5990_id[] = {
{"mp5990", mp5990},
{"mp5998", mp5998},
{ }
};
MODULE_DEVICE_TABLE(i2c, mp5990_id);
static int mp5990_probe(struct i2c_client *client)
{
struct pmbus_driver_info *info;
struct mp5990_data *data;
enum chips chip;
int ret;
data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data),
@ -121,7 +166,15 @@ static int mp5990_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
memcpy(&data->info, &mp5990_info, sizeof(*info));
if (client->dev.of_node)
chip = (uintptr_t)of_device_get_match_data(&client->dev);
else
chip = i2c_match_id(mp5990_id, client)->driver_data;
if (chip == mp5990)
memcpy(&data->info, &mp5990_info, sizeof(*info));
else
memcpy(&data->info, &mp5998_info, sizeof(*info));
info = &data->info;
/* Read Vout Config */
@ -140,6 +193,9 @@ static int mp5990_probe(struct i2c_client *client)
data->info.format[PSC_VOLTAGE_OUT] = linear;
data->info.format[PSC_CURRENT_OUT] = linear;
data->info.format[PSC_POWER] = linear;
if (chip == mp5998)
data->info.format[PSC_CURRENT_IN] = linear;
ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
if (ret < 0) {
dev_err(&client->dev, "Can't get vout exponent.");
@ -153,16 +209,11 @@ static int mp5990_probe(struct i2c_client *client)
}
static const struct of_device_id mp5990_of_match[] = {
{ .compatible = "mps,mp5990" },
{ .compatible = "mps,mp5990", .data = (void *)mp5990 },
{ .compatible = "mps,mp5998", .data = (void *)mp5998 },
{}
};
static const struct i2c_device_id mp5990_id[] = {
{"mp5990"},
{ }
};
MODULE_DEVICE_TABLE(i2c, mp5990_id);
static struct i2c_driver mp5990_driver = {
.driver = {
.name = "mp5990",

View File

@ -64,6 +64,7 @@ struct pwm_fan_ctx {
u64 pwm_duty_cycle_from_stopped;
u32 pwm_usec_from_stopped;
u8 pwm_shutdown;
};
/* This handler assumes self resetting edge triggered interrupt. */
@ -484,9 +485,14 @@ static void pwm_fan_cleanup(void *__ctx)
struct pwm_fan_ctx *ctx = __ctx;
timer_delete_sync(&ctx->rpm_timer);
/* Switch off everything */
ctx->enable_mode = pwm_disable_reg_disable;
pwm_fan_power_off(ctx, true);
if (ctx->pwm_shutdown) {
ctx->enable_mode = pwm_enable_reg_enable;
__set_pwm(ctx, ctx->pwm_shutdown);
} else {
/* Switch off everything */
ctx->enable_mode = pwm_disable_reg_disable;
pwm_fan_power_off(ctx, true);
}
}
static int pwm_fan_probe(struct platform_device *pdev)
@ -498,6 +504,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
int ret;
const struct hwmon_channel_info **channels;
u32 initial_pwm, pwm_min_from_stopped = 0;
u32 pwm_shutdown_percent = 0;
u32 *fan_channel_config;
int channel_count = 1; /* We always have a PWM channel. */
int i;
@ -648,6 +655,11 @@ static int pwm_fan_probe(struct platform_device *pdev)
channels[1] = &ctx->fan_channel;
}
ret = device_property_read_u32(dev, "fan-shutdown-percent",
&pwm_shutdown_percent);
if (!ret && pwm_shutdown_percent)
ctx->pwm_shutdown = (clamp(pwm_shutdown_percent, 0, 100) * 255) / 100;
ret = device_property_read_u32(dev, "fan-stop-to-start-percent",
&pwm_min_from_stopped);
if (!ret && pwm_min_from_stopped) {

View File

@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sl67mcu hardware monitoring driver
*
* Copyright 2025 Kontron Europe GmbH
*/
#include <linux/bitfield.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#define SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2))
#define SA67MCU_TEMP(n) (0x04 + ((n) * 2))
struct sa67mcu_hwmon {
struct regmap *regmap;
u32 offset;
};
static int sa67mcu_hwmon_read(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long *input)
{
struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev);
unsigned int offset;
u8 reg[2];
int ret;
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_input:
offset = hwmon->offset + SA67MCU_VOLTAGE(channel);
break;
default:
return -EOPNOTSUPP;
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
offset = hwmon->offset + SA67MCU_TEMP(channel);
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
/* Reading the low byte will capture the value */
ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg));
if (ret)
return ret;
*input = reg[1] << 8 | reg[0];
/* Temperatures are s16 and in 0.1degC steps. */
if (type == hwmon_temp)
*input = sign_extend32(*input, 15) * 100;
return 0;
}
static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
NULL
};
static const char *const sa67mcu_hwmon_in_labels[] = {
"VDDIN",
"VDD_RTC",
};
static int sa67mcu_hwmon_read_string(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_label:
*str = sa67mcu_hwmon_in_labels[channel];
return 0;
default:
return -EOPNOTSUPP;
}
default:
return -EOPNOTSUPP;
}
}
static const struct hwmon_ops sa67mcu_hwmon_ops = {
.visible = 0444,
.read = sa67mcu_hwmon_read,
.read_string = sa67mcu_hwmon_read_string,
};
static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = {
.ops = &sa67mcu_hwmon_ops,
.info = sa67mcu_hwmon_info,
};
static int sa67mcu_hwmon_probe(struct platform_device *pdev)
{
struct sa67mcu_hwmon *hwmon;
struct device *hwmon_dev;
int ret;
if (!pdev->dev.parent)
return -ENODEV;
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
if (!hwmon)
return -ENOMEM;
hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!hwmon->regmap)
return -ENODEV;
ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);
if (ret)
return -EINVAL;
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
"sa67mcu_hwmon", hwmon,
&sa67mcu_hwmon_chip_info,
NULL);
if (IS_ERR(hwmon_dev))
dev_err(&pdev->dev, "failed to register as hwmon device");
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct of_device_id sa67mcu_hwmon_of_match[] = {
{ .compatible = "kontron,sa67mcu-hwmon", },
{}
};
MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match);
static struct platform_driver sa67mcu_hwmon_driver = {
.probe = sa67mcu_hwmon_probe,
.driver = {
.name = "sa67mcu-hwmon",
.of_match_table = sa67mcu_hwmon_of_match,
},
};
module_platform_driver(sa67mcu_hwmon_driver);
MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver");
MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>");
MODULE_LICENSE("GPL");

View File

@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/bitfield.h>
/*
* SB-TSI registers only support SMBus byte data access. "_INT" registers are
@ -29,8 +30,22 @@
#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
/*
* Bit for reporting value with temperature measurement range.
* bit == 0: Use default temperature range (0C to 255.875C).
* bit == 1: Use extended temperature range (-49C to +206.875C).
*/
#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
/*
* ReadOrder bit specifies the reading order of integer and decimal part of
* CPU temperature for atomic reads. If bit == 0, reading integer part triggers
* latching of the decimal part, so integer part should be read first.
* If bit == 1, read order should be reversed.
*/
#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
#define SBTSI_TEMP_EXT_RANGE_ADJ 49000
#define SBTSI_TEMP_MIN 0
#define SBTSI_TEMP_MAX 255875
@ -38,6 +53,8 @@
struct sbtsi_data {
struct i2c_client *client;
struct mutex lock;
bool ext_range_mode;
bool read_order;
};
/*
@ -74,23 +91,11 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
int err;
switch (attr) {
case hwmon_temp_input:
/*
* ReadOrder bit specifies the reading order of integer and
* decimal part of CPU temp for atomic reads. If bit == 0,
* reading integer part triggers latching of the decimal part,
* so integer part should be read first. If bit == 1, read
* order should be reversed.
*/
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
return err;
mutex_lock(&data->lock);
if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) {
if (data->read_order) {
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
} else {
@ -122,6 +127,8 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
return temp_dec;
*val = sbtsi_reg_to_mc(temp_int, temp_dec);
if (data->ext_range_mode)
*val -= SBTSI_TEMP_EXT_RANGE_ADJ;
return 0;
}
@ -146,6 +153,8 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
return -EINVAL;
}
if (data->ext_range_mode)
val += SBTSI_TEMP_EXT_RANGE_ADJ;
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
@ -203,6 +212,7 @@ static int sbtsi_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct sbtsi_data *data;
int err;
data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL);
if (!data)
@ -211,8 +221,14 @@ static int sbtsi_probe(struct i2c_client *client)
data->client = client;
mutex_init(&data->lock);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info,
NULL);
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
return err;
data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err);
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
&sbtsi_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}

View File

@ -544,10 +544,8 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision,
watchdog_set_drvdata(&data->wddev, data);
err = devm_watchdog_register_device(parent, &data->wddev);
if (err) {
pr_err("Registering watchdog chardev: %d\n", err);
if (err)
devm_kfree(parent, data);
}
}
EXPORT_SYMBOL(sch56xx_watchdog_register);

View File

@ -275,13 +275,26 @@ static int sht21_probe(struct i2c_client *client)
/* Device ID table */
static const struct i2c_device_id sht21_id[] = {
{ "sht20" },
{ "sht21" },
{ "sht25" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sht21_id);
static const struct of_device_id sht21_of_match[] = {
{ .compatible = "sensirion,sht20" },
{ .compatible = "sensirion,sht21" },
{ .compatible = "sensirion,sht25" },
{ }
};
MODULE_DEVICE_TABLE(of, sht21_of_match);
static struct i2c_driver sht21_driver = {
.driver.name = "sht21",
.driver = {
.name = "sht21",
.of_match_table = sht21_of_match,
},
.probe = sht21_probe,
.id_table = sht21_id,
};

View File

@ -104,3 +104,4 @@ module_platform_driver(sy7636a_sensor_driver);
MODULE_DESCRIPTION("SY7636A sensor driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sy7636a-temperature");

View File

@ -53,6 +53,7 @@
#define CONVERSION_TIME_MS 35 /* in milli-seconds */
struct tmp102 {
const char *label;
struct regmap *regmap;
u16 config_orig;
unsigned long ready_time;
@ -70,6 +71,16 @@ static inline u16 tmp102_mC_to_reg(int val)
return (val * 128) / 1000;
}
static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
struct tmp102 *tmp102 = dev_get_drvdata(dev);
*str = tmp102->label;
return 0;
}
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *temp)
{
@ -128,12 +139,18 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct tmp102 *tmp102 = data;
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
return 0444;
case hwmon_temp_label:
if (tmp102->label)
return 0444;
return 0;
case hwmon_temp_max_hyst:
case hwmon_temp_max:
return 0644;
@ -146,12 +163,13 @@ static const struct hwmon_channel_info * const tmp102_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST),
NULL
};
static const struct hwmon_ops tmp102_hwmon_ops = {
.is_visible = tmp102_is_visible,
.read_string = tmp102_read_string,
.read = tmp102_read,
.write = tmp102_write,
};
@ -213,6 +231,8 @@ static int tmp102_probe(struct i2c_client *client)
if (!tmp102)
return -ENOMEM;
of_property_read_string(dev->of_node, "label", &tmp102->label);
i2c_set_clientdata(client, tmp102);
tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config);

View File

@ -24,6 +24,7 @@ enum hwmon_sensor_types {
hwmon_curr,
hwmon_power,
hwmon_energy,
hwmon_energy64,
hwmon_humidity,
hwmon_fan,
hwmon_pwm,
@ -491,6 +492,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
char *hwmon_sanitize_name(const char *name);
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
void hwmon_lock(struct device *dev);
void hwmon_unlock(struct device *dev);
/**
* hwmon_is_bad_char - Is the char invalid in a hwmon name
* @ch: the char to be considered

View File

@ -1825,6 +1825,16 @@ struct ec_response_pwm_get_duty {
uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */
} __ec_align2;
#define EC_CMD_PWM_GET_FAN_DUTY 0x0027
struct ec_params_pwm_get_fan_duty {
uint8_t fan_idx;
} __ec_align1;
struct ec_response_pwm_get_fan_duty {
uint32_t percent; /* Percentage of duty cycle, ranging from 0 ~ 100 */
} __ec_align4;
/*****************************************************************************/
/*
* Lightbar commands. This looks worse than it is. Since we only use one HOST
@ -3127,14 +3137,31 @@ struct ec_params_thermal_set_threshold_v1 {
/****************************************************************************/
/* Toggle automatic fan control */
/* Set or get fan control mode */
#define EC_CMD_THERMAL_AUTO_FAN_CTRL 0x0052
enum ec_auto_fan_ctrl_cmd {
EC_AUTO_FAN_CONTROL_CMD_SET = 0,
EC_AUTO_FAN_CONTROL_CMD_GET,
};
/* Version 1 of input params */
struct ec_params_auto_fan_ctrl_v1 {
uint8_t fan_idx;
} __ec_align1;
/* Version 2 of input params */
struct ec_params_auto_fan_ctrl_v2 {
uint8_t fan_idx;
uint8_t cmd; /* enum ec_auto_fan_ctrl_cmd */
uint8_t set_auto; /* only used with EC_AUTO_FAN_CONTROL_CMD_SET - bool
*/
} __ec_align4;
struct ec_response_auto_fan_control {
uint8_t is_auto; /* bool */
} __ec_align1;
/* Get/Set TMP006 calibration data */
#define EC_CMD_TMP006_GET_CALIBRATION 0x0053
#define EC_CMD_TMP006_SET_CALIBRATION 0x0054

View File

@ -9,14 +9,14 @@
DECLARE_EVENT_CLASS(hwmon_attr_class,
TP_PROTO(int index, const char *attr_name, long val),
TP_PROTO(int index, const char *attr_name, long long val),
TP_ARGS(index, attr_name, val),
TP_STRUCT__entry(
__field(int, index)
__string(attr_name, attr_name)
__field(long, val)
__field(long long, val)
),
TP_fast_assign(
@ -25,20 +25,20 @@ DECLARE_EVENT_CLASS(hwmon_attr_class,
__entry->val = val;
),
TP_printk("index=%d, attr_name=%s, val=%ld",
TP_printk("index=%d, attr_name=%s, val=%lld",
__entry->index, __get_str(attr_name), __entry->val)
);
DEFINE_EVENT(hwmon_attr_class, hwmon_attr_show,
TP_PROTO(int index, const char *attr_name, long val),
TP_PROTO(int index, const char *attr_name, long long val),
TP_ARGS(index, attr_name, val)
);
DEFINE_EVENT(hwmon_attr_class, hwmon_attr_store,
TP_PROTO(int index, const char *attr_name, long val),
TP_PROTO(int index, const char *attr_name, long long val),
TP_ARGS(index, attr_name, val)
);

View File

@ -36,6 +36,8 @@
#define I8K_FAN_LOW 1
#define I8K_FAN_HIGH 2
#define I8K_FAN_TURBO 3
/* Many machines treat this mode as some sort of automatic mode */
#define I8K_FAN_AUTO 3
#define I8K_FAN_MAX I8K_FAN_TURBO
#define I8K_VOL_UP 1