hwmon updates for v7.4

* New drivers
 
   - Axiado AX3000 and AX3005 PWM fan controller
 
   - AX3000/AX3005 TSADC
 
   - HONOR FMI-XX
 
   - Minisforum UM780 XTX EC monitoring and fan control
 
   - pmbus: MAX20826, MAX20855B, MAX20908, MAX20912, MAX20826, and MAX20916
 
   - pmbus: Infineon TDA38740/TDA38725
 
 * Added support to existing drivers
 
   - asus-ec-sensors: ROG STRIX X670E-A GAMING WIFI and
     ROG STRIX Z490-A GAMING
 
   - asus_rog_ryujin: ROG Ryujin III 360
 
   - dell-smm: Dell 16 DC16250, Dell OptiPlex 7090, Dell Precision 3650
     Tower, Add Latitude 5420
 
   - it87: IT8613E
 
   - k10temp: 1ah/80h devices. Drop support for Hygon devices (support to be
     re-added with separate patches later)
 
   - lm63: Sensylink CTF2301
 
   - nct6683: MSI MAG B550M MORTAR MAX WIFI, MSI MAG B650 TOMAHAWK WIFI,
     MSI MAG B650M MORTAR WIFI (MS-7D76), and MSI MAG B850M MORTAR WIFI
 
   - nct6775: NCT6122D and NCT6126D
 
   - pmbus/isl68137: Renesas RAA229639 and RAA229640
 
   - pmbus/max34440: ADPM12300
 
   - pmbus/tps25990: TPS1689
 
   - pmbus/tps53679: TPS53622, TPS53659 and TPS536C7
 
   - pmbus/xdpe1a2g7b: xdpe1a2g7c
 
   - sht4x: Sensirion STS4x
 
   - socfpga: Agilex 5
 
   - yogafan: Lenovo Yoga Pro 9 16IMH9, Yoga 14cACN 2021, IdeaPad 3 15ALC6 Ub,
     Yoga 740-15IML
 
 * Core updates
 
   - Support for currX_emergency and inX_[l]emergency attributes
 
 * Notable bug fixes and changes
 
   - Add missing locks to corsair-cpro, ftsteutates, nct7904, and npcm750-pwm-fan
     drivers
 
   - Fix jiffies wraparound in tmp102 and tmp108 drivers
 
   - Documentation updates (document missing chip support, typo fixes,
     clarifications)
 
   - pmbus/ltc4286: Add writable shunt_resistor sysfs attribute
 
   - coretemp: Document sysfs numbering and label meaning, fix handling of
     isolated SMT siblings, read TjMax only when refreshing the temperature,
     refresh the temperature on the first read, and return -ENODATA for
     invalid temperature readings
 
   - spd5118: Avoid probing when 16-bit addressing is enabled, and select
     page 0 unconditionally during probe
 
   - arctic_fan_controller: Default PWM cache to MCU 40%, Dual-license
     GPL-2.0-or-later OR BSD-2-Clause, and update maintainer address
 
 * Various other minor bug fixes and improvements
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmrYzwAACgkQyx8mb86f
 mYFHsBAAgX61R4qrYoUFDnJQRKoE4629TZX6cd3mVaIXRA68HKqRVrj8Gh3gKlB9
 S9wh56eWA9k4rkIXdsBl+tmpJJPjSnxMY0O+BGnX4e5f+hvjhYxXcq1TBomUkMEe
 ojLefp985Fj49QBeQoeJZF7Ps041q4KUGzef5N8hQ9VzNbo8OzlSCK4qyM0PHh4t
 gq9xVwa+JYfinHyDF42ZWkA1dcASaeTI1OilDzGEwX1zkHmKgSJ7o7KOJB6X6lXx
 Bluf9afB5KJTv7zLc5g1IhPyIKLKSEKubX3nLX8QPtk1UgfWq/aOmDZbrHL2Mv3M
 NYHgWVue5RtyxxCQbsHQTtX3nJI7VIdWZm6HjNyKO7dJ2+HsAn7b3yqidO7J3Yep
 yQqrKd6S3HIpWlLlu7K9ecaT/ZWfyR2WLF1ps0k7xtHYh1qHfJtu4wdSSNZ757+v
 QJNWnvJYN5l9vWvnTLibgOlSt6tppiLU57L+vGBmwHdnbq9kNhhklwKHEdrmHptD
 RUWsf495UBRiD8tMveNOEmE03v6QOIUJY9Eovvi9wvl7fzOypOW/egZxDL12PKF4
 EpeWANjGWrWZAP3ipB4nY1g8oNIgn0yjNj2LEJBdebN+6BEaI75Ck2KimJMtitTR
 h1DKqnTK6fgYvKRnhzo9LhANEvplDXGoof0k4UqmNeuA8gZo1UI=
 =NNwk
 -----END PGP SIGNATURE-----

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

Pull hwmon updates from Guenter Roeck:
 "New drivers:
   - Axiado AX3000 and AX3005 PWM fan controller
   - AX3000/AX3005 TSADC
   - HONOR FMI-XX
   - Minisforum UM780 XTX EC monitoring and fan control
   - pmbus: MAX20826, MAX20855B, MAX20908, MAX20912, MAX20826, and
     MAX20916
   - pmbus: Infineon TDA38740/TDA38725

  Added support to existing drivers:
   - asus-ec-sensors: ROG STRIX X670E-A GAMING WIFI and ROG STRIX Z490-A
     GAMING
   - asus_rog_ryujin: ROG Ryujin III 360
   - dell-smm: Dell 16 DC16250, Dell OptiPlex 7090, Dell Precision 3650
     Tower, Add Latitude 5420
   - it87: IT8613E
   - k10temp: 1ah/80h devices. Drop support for Hygon devices (support
     to be re-added with separate patches later)
   - lm63: Sensylink CTF2301
   - nct6683: MSI MAG B550M MORTAR MAX WIFI, MSI MAG B650 TOMAHAWK WIFI,
     MSI MAG B650M MORTAR WIFI (MS-7D76), and MSI MAG B850M MORTAR WIFI
   - nct6775: NCT6122D and NCT6126D
   - pmbus/isl68137: Renesas RAA229639 and RAA229640
   - pmbus/max34440: ADPM12300
   - pmbus/tps25990: TPS1689
   - pmbus/tps53679: TPS53622, TPS53659 and TPS536C7
   - pmbus/xdpe1a2g7b: xdpe1a2g7c
   - sht4x: Sensirion STS4x
   - socfpga: Agilex 5
   - yogafan: Lenovo Yoga Pro 9 16IMH9, Yoga 14cACN 2021, IdeaPad 3
     15ALC6 Ub, Yoga 740-15IML

  Core updates:
   - Support for currX_emergency and inX_[l]emergency attributes

  Notable bug fixes and changes:
   - Add missing locks to corsair-cpro, ftsteutates, nct7904, and
     npcm750-pwm-fan drivers
   - Fix jiffies wraparound in tmp102 and tmp108 drivers
   - Documentation updates (document missing chip support, typo fixes,
     clarifications)
   - pmbus/ltc4286: Add writable shunt_resistor sysfs attribute
   - coretemp: Document sysfs numbering and label meaning, fix handling
     of isolated SMT siblings, read TjMax only when refreshing the
     temperature, refresh the temperature on the first read, and return
     -ENODATA for invalid temperature readings
   - spd5118: Avoid probing when 16-bit addressing is enabled, and
     select page 0 unconditionally during probe
   - arctic_fan_controller: Default PWM cache to MCU 40%, Dual-license
     GPL-2.0-or-later OR BSD-2-Clause, and update maintainer address

  Various other minor bug fixes and improvements"

* tag 'hwmon-for-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (97 commits)
  hwmon: (npcm750-pwm-fan) serialize PWM register updates
  hwmon: (nct7904) serialise the watchdog and sensors
  hwmon: (ftsteutates) serialise the watchdog and sensors
  hwmon: (acpi_power_meter) Fix 18.45 TW bug
  hwmon: (sht3x) Return the consumed count for heater writes
  hwmon: (mcp9982) Update the interval cache only after successful writes
  hwmon: (gigabyte_waterforce) reject short input reports
  hwmon: (coretemp) Fix handling of isolated SMT siblings
  hwmon: (coretemp) Document sysfs numbering and label meaning
  hwmon: (asus_wmi_sensors) Reword BIOS upgrade advice
  hwmon: Replace __MODULE_STRING() with __stringify()
  hwmon:(pmbus/xdpe1a2g7b) Add support for xdpe1a2g7c controller
  dt-bindings: trivial-devices: Add support for XDPE1A2G7C
  hwmon: (pmbus/isl68137) Add Renesas RAA229639 and RAA229640
  dt-bindings: hwmon: pmbus/isl68137: Add Renesas RAA229639 and RAA229640
  hwmon: (coretemp) Read TjMax only when refreshing the temperature
  hwmon: (pmbus/ibm-cffps) Avoid format truncation warnings
  hwmon: (dell-smm) Add Dell 16 DC16250 to fan control whitelist
  hwmon: (nct6683) Add more MSI boards to list of tested boards
  hwmon: (nct6683) Add MSI B850M to list of tested boards
  ...
This commit is contained in:
Linus Torvalds 2026-10-26 06:59:21 -10:00
commit 7313d94c67
105 changed files with 5315 additions and 458 deletions

View File

@ -61,6 +61,18 @@ Description:
take drastic action such as power down or reset. At the very
least, it should report a fault.
What: /sys/class/hwmon/hwmonX/inY_lemergency
Description:
Voltage emergency min value.
Unit: millivolt
RW
If voltage drops to or below this limit, the system is
expected to take drastic action such as immediate power
down or reset. At the very least, it should report a fault.
What: /sys/class/hwmon/hwmonX/inY_max
Description:
Voltage max value.
@ -81,6 +93,18 @@ Description:
take drastic action such as power down or reset. At the very
least, it should report a fault.
What: /sys/class/hwmon/hwmonX/inY_emergency
Description:
Voltage emergency max value.
Unit: millivolt
RW
If voltage reaches or exceeds this limit, the system is expected
to take drastic action such as immediate power down or reset.
At the very least, it should report a fault.
What: /sys/class/hwmon/hwmonX/inY_input
Description:
Voltage input value.
@ -647,6 +671,18 @@ Description:
RW
What: /sys/class/hwmon/hwmonX/currY_emergency
Description:
Current emergency high value.
Unit: milliampere
RW
If a current reaches or exceeds this limit, the system is
expected to take drastic action such as immediate power down
or reset. At the very least, it should report a fault.
What: /sys/class/hwmon/hwmonX/currY_input
Description:
Current input value

View File

@ -0,0 +1,72 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/axiado,ax3000-pwm-fan.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Axiado AX3000 and AX3005 PWM Fan Controller
maintainers:
- Petar Stepanovic <pstepanovic@axiado.com>
- Akhila Kavi <akavi@axiado.com>
- Prasad Bolisetty <pbolisetty@axiado.com>
description:
The Axiado AX3000 and AX3005 fan controllers use a hardware tachometer
block to measure fan speed. Fan speed is controlled through a PWM signal
supplied by an external PWM controller.
properties:
compatible:
oneOf:
- const: axiado,ax3000-pwm-fan
- items:
- const: axiado,ax3005-pwm-fan
- const: axiado,ax3000-pwm-fan
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
maxItems: 1
fan:
type: object
$ref: fan-common.yaml#
unevaluatedProperties: false
properties:
pulses-per-revolution:
minimum: 1
default: 2
required:
- pwms
required:
- compatible
- reg
- interrupts
- clocks
- fan
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
fan-controller@33001400 {
compatible = "axiado,ax3000-pwm-fan";
reg = <0x33001400 0x400>;
interrupts = <GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk>;
fan {
pwms = <&pwm0 0 400>;
pulses-per-revolution = <2>;
};
};
...

View File

@ -0,0 +1,45 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/axiado,ax3000-tsadc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Axiado AX3000 and AX3005 TSADC
maintainers:
- Petar Stepanovic <pstepanovic@axiado.com>
- Akhila Kavi <akavi@axiado.com>
- Prasad Bolisetty <pbolisetty@axiado.com>
description: |
The Axiado AX3000 and AX3005 Temperature Sensor ADC (TSADC) controllers
monitor the on-die temperature of the SoC.
properties:
compatible:
oneOf:
- const: axiado,ax3000-tsadc
- items:
- const: axiado,ax3005-tsadc
- const: axiado,ax3000-tsadc
reg:
maxItems: 1
clocks:
maxItems: 1
required:
- compatible
- reg
- clocks
additionalProperties: false
examples:
- |
temperature-sensor@33021000 {
compatible = "axiado,ax3000-tsadc";
reg = <0x33021000 0x100>;
clocks = <&refclk>;
};

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/national,lm63.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: LM63-compatible temperature sensors with integrated fan control
maintainers:
- Guenter Roeck <linux@roeck-us.net>
description: |
The LM63 family provides local and remote temperature monitoring together
with fan speed monitoring and PWM fan control. Supported devices include
the National Semiconductor LM63, LM64 and LM96163, and the Sensylink
CTF2301.
allOf:
- $ref: hwmon-common.yaml#
properties:
compatible:
enum:
- national,lm63
- national,lm64
- national,lm96163
- sensylink,ctf2301
reg:
maxItems: 1
interrupts:
maxItems: 1
"#cooling-cells":
const: 2
required:
- compatible
- reg
unevaluatedProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@4c {
compatible = "national,lm63";
reg = <0x4c>;
#cooling-cells = <2>;
};
};

View File

@ -113,6 +113,23 @@ allOf:
properties:
ti,extended-range-enable: false
- if:
not:
properties:
compatible:
contains:
enum:
- adi,adt7481
- dallas,max6695
- dallas,max6696
then:
patternProperties:
"^channel@[0-1]$":
properties:
reg:
enum: [0, 1]
"channel@2": false
- if:
properties:
compatible:
@ -134,6 +151,11 @@ allOf:
"^channel@([0-2])$":
properties:
temperature-offset-millicelsius: false
else:
patternProperties:
"channel@0":
properties:
temperature-offset-millicelsius: false
- if:
properties:
@ -149,7 +171,7 @@ allOf:
- onnn,nct1008
then:
patternProperties:
"^channel@([0-2])$":
"^channel@([1-2])$":
properties:
temperature-offset-millicelsius:
maximum: 127750
@ -172,7 +194,7 @@ allOf:
- winbond,w83l771
then:
patternProperties:
"^channel@([0-2])$":
"^channel@([1-2])$":
properties:
temperature-offset-millicelsius:
maximum: 127875
@ -186,7 +208,7 @@ allOf:
- ti,tmp461
then:
patternProperties:
"^channel@([0-2])$":
"^channel@([1-2])$":
properties:
temperature-offset-millicelsius:
maximum: 127937

View File

@ -0,0 +1,102 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/pmbus/adi,max20826.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Analog Devices MAX20826 PMBus Voltage Regulator
maintainers:
- Nuno Sá <nuno.sa@analog.com>
description:
Analog Devices MAX20826 and similar devices are Dual-Output Multiphase
High-Current Controller.
properties:
compatible:
enum:
- adi,max20826
- adi,max20855b
- adi,max20908
- adi,max20912
- adi,max20916
reg:
maxItems: 1
vdd3p3-supply:
description:
3.3V bias supply. It is the input of the internal 1.8V linear regulator
which supplies the controller.
avren-gpios:
description: GPIO pin to enable/disable the output voltage on Rail A.
maxItems: 1
bvren-gpios:
description: GPIO pin to enable/disable the output voltage on Rail B.
maxItems: 1
adi,rail-a-high-speed:
type: boolean
description:
The output voltage of Rail A is controlled by the high-speed processor
voltage-control interface the device powered up in (PWMVID, SVID, SVI3 or
AVSBus). If not given, PMBus controls the output voltage of Rail A.
adi,rail-b-high-speed:
type: boolean
description:
Same as adi,rail-a-high-speed but for Rail B.
regulators:
type: object
description:
List of regulators provided by this controller. vout0 is Rail A and
vout1 is Rail B.
patternProperties:
"^vout[0-1]$":
$ref: /schemas/regulator/regulator.yaml#
type: object
unevaluatedProperties: false
additionalProperties: false
required:
- compatible
- reg
- vdd3p3-supply
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
regulator@64 {
compatible = "adi,max20826";
reg = <0x64>;
vdd3p3-supply = <&vdd3p3>;
avren-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
bvren-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
adi,rail-a-high-speed;
regulators {
vout0 {
regulator-name = "vcore_a";
};
vout1 {
regulator-name = "vcore_b";
};
};
};
};

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/pmbus/infineon,tda38740.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Infineon TDA38740 and TDA38725 PMBus Synchronous Buck Regulator
maintainers:
- Colin Huang <u8813345@gmail.com>
description: |
The Infineon TDA38740/TDA38725 is a 40A/25A single-voltage synchronous
buck regulator with a PMBus interface designed for industrial use.
Datasheet:
https://www.infineon.com/row/public/documents/24/49/infineon-tda38740-tda38725-datasheet-en.pdf
properties:
compatible:
oneOf:
- const: infineon,tda38740
- items:
- const: infineon,tda38725
- const: infineon,tda38740
reg:
maxItems: 1
interrupts:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
regulator@40 {
compatible = "infineon,tda38725",
"infineon,tda38740";
reg = <0x40>;
interrupt-parent = <&sgpiom0>;
interrupts = <10 IRQ_TYPE_LEVEL_LOW>;
};
};

View File

@ -65,6 +65,8 @@ properties:
- enum:
- renesas,raa228942
- renesas,raa228943
- renesas,raa229639
- renesas,raa229640
- const: renesas,raa228244
reg:

View File

@ -5,18 +5,20 @@
$id: http://devicetree.org/schemas/hwmon/pmbus/ti,tps25990.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments TPS25990 Stackable eFuse
title: Texas Instruments Stackable eFuses
maintainers:
- Jerome Brunet <jbrunet@baylibre.com>
description:
The TI TPS25990 is an integrated, high-current circuit
The TI TPS25990 and TPS1689 are integrated, high-current circuit
protection and power management device with PMBUS interface
properties:
compatible:
const: ti,tps25990
enum:
- ti,tps1689
- ti,tps25990
reg:
maxItems: 1

View File

@ -4,15 +4,21 @@
$id: http://devicetree.org/schemas/hwmon/ti,tmp102.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TMP102 temperature sensor
title: TMP102/TMP103/TMP110/TMP113 temperature sensor
maintainers:
- Krzysztof Kozlowski <krzk@kernel.org>
properties:
compatible:
enum:
- ti,tmp102
oneOf:
- items:
- enum:
- ti,tmp110
- ti,tmp113
- const: ti,tmp102
- const: ti,tmp102
- const: ti,tmp103
interrupts:
maxItems: 1
@ -25,10 +31,10 @@ properties:
A descriptive name for this channel, like "ambient" or "psu".
"#thermal-sensor-cells":
const: 1
enum: [0, 1]
vcc-supply:
description: Power supply for tmp102
description: Power supply for the sensor
required:
- compatible

View File

@ -50,6 +50,9 @@ properties:
minimum: 0
maximum: 15
"#thermal-sensor-cells":
const: 1
allOf:
- if:
properties:

View File

@ -176,6 +176,7 @@ properties:
# Infineon Multi-phase Digital VR Controller xdpe1a2g7b
- infineon,xdpe1a2g5b
- infineon,xdpe1a2g7b
- infineon,xdpe1a2g7c
# Injoinic IP5108 2.0A Power Bank IC with I2C
- injoinic,ip5108
# Injoinic IP5109 2.1A Power Bank IC with I2C
@ -370,16 +371,10 @@ properties:
- mps,mpq82d00
# Murata D1U74T-W power supply unit
- murata,d1u74t
# Temperature sensor with integrated fan control
- national,lm63
# Temperature sensor with integrated fan control
- national,lm64
# Temperature sensor
- national,lm95235
# Temperature sensor
- national,lm95245
# Temperature sensor with integrated fan control
- national,lm96163
# Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
- national,lm80
# Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
@ -429,6 +424,8 @@ properties:
- sensirion,sht21
- sensirion,sht25
- sensirion,sht4x
# Sensirion temperature sensor with I2C interface
- sensirion,sts40
# Sensortek 3 axis accelerometer
- sensortek,stk8312
# Sensortek 3 axis accelerometer
@ -506,16 +503,18 @@ properties:
- ti,lm74
# Temperature sensor with integrated fan control
- ti,lm96000
# Low Power Digital Temperature Sensor with SMBUS/Two Wire Serial Interface
- ti,tmp103
# Thermometer with SPI interface
- ti,tmp121
- ti,tmp122
- ti,tmp125
# TI DC-DC converter on PMBus
- ti,tps40400
# TI Dual channel DCAP+ multiphase controller TPS53622
- ti,tps53622
# TI DCAP+ multiphase controller
- ti,tps53647
# TI Dual channel DCAP+ multiphase controller TPS53659
- ti,tps53659
# TI DCAP+ multiphase controller
- ti,tps53667
# TI Dual channel DCAP+ multiphase controller TPS53676 with AVSBus
@ -528,6 +527,8 @@ properties:
- ti,tps53685
# TI Dual channel DCAP+ multiphase controller TPS53688
- ti,tps53688
# TI Dual channel DCAP+ multiphase controller TPS536C7
- ti,tps536c7
# TI DC-DC converters on PMBus
- ti,tps544b20
- ti,tps544b25

View File

@ -1511,6 +1511,8 @@ patternProperties:
description: Sensirion AG
"^sensortek,.*":
description: Sensortek Technology Corporation
"^sensylink,.*":
description: Sensylink Microelectronics Technology Co., Ltd.
"^sercomm,.*":
description: Sercomm (Suzhou) Corporation
"^sff,.*":

View File

@ -1,4 +1,4 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
.. SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
Kernel driver arctic_fan_controller
=====================================
@ -29,18 +29,16 @@ Usage notes
Since it is a USB device, hotplug is supported. The device is autodetected.
The device does not support GET_REPORT, so the driver cannot read back the
current hardware PWM state at probe time. The cached PWM values (readable
via pwm[1-10]) start at 0 and reflect only values that have been
successfully written. Because each OUT report carries all 10 channel values,
writing a single channel also sends the cached values for all other channels.
Users should set all channels to the desired values before relying on the
cached state.
current hardware PWM state at probe time. Each OUT report carries all 10
channels, so pwm[1-10] is a host cache. It starts at the MCU factory
default of 40% (sysfs 102). After a successful write, the cache reflects
that value. Users should set all channels to the desired values before
relying on the cached state.
On system suspend, the device may lose power and reset its PWM channels to
hardware defaults. The driver clears its cached duty values on resume so
that reads reflect the unknown hardware state rather than stale pre-suspend
values. Userspace is responsible for re-applying the desired duty cycles
after resume.
On system suspend, the device may lose power and reset PWM to the factory
default. The driver restores the cache to 40% on resume. If the device
kept power across suspend, userspace should re-apply the desired duty
cycles.
Sysfs entries
-------------
@ -51,6 +49,6 @@ pwm[1-10] PWM duty cycle (0-255). Write: sends an OUT report setting the
duty cycle (scaled from 0-255 to 0-100% for the device);
the cached value is updated only after the device ACKs the
command with a success status. Read: returns the last
successfully written value; initialized to 0 at driver load
and after resume (hardware state unknown).
successfully written value; initialized to 102 (40%) at
driver load and after resume (MCU factory default).
================ ==============================================================

View File

@ -45,6 +45,7 @@ Supported boards:
* ROG STRIX X570-E GAMING WIFI II
* ROG STRIX X570-F GAMING
* ROG STRIX X570-I GAMING
* ROG STRIX X670E-A GAMING WIFI
* ROG STRIX X670E-E GAMING WIFI
* ROG STRIX X670E-I GAMING WIFI
* ROG STRIX X870-F GAMING WIFI
@ -54,6 +55,7 @@ Supported boards:
* ROG STRIX X870E-H GAMING WIFI7
* ROG STRIX Z390-E GAMING
* ROG STRIX Z390-F GAMING
* ROG STRIX Z490-A GAMING
* ROG STRIX Z490-F GAMING
* ROG STRIX Z690-A GAMING WIFI D4
* ROG STRIX Z690-E GAMING WIFI

View File

@ -6,6 +6,7 @@ Kernel driver asus_rog_ryujin
Supported devices:
* ASUS ROG RYUJIN II 360
* ASUS ROG RYUJIN III 360
* ASUS ROG RYUJIN III EXTREME
* ASUS ROG RYUJIN III EVA EDITION
* ASUS ROG RYUJIN III WHITE EDITION

View File

@ -72,7 +72,6 @@ Known Issues:
X470 Pro seems particularly bad for this. The more frequently the WMI
interface is polled the greater the potential for this to happen. Until you
have subjected your computer to an extended soak test while polling the
sensors frequently, don't leave you computer unattended. Upgrading to new
BIOS version with method version greater than or equal to two should
rectify the issue.
sensors frequently, don't leave your computer unattended. Upgrading to a
more recent BIOS version might help.
* A few boards report 12v voltages to be ~10v.

View File

@ -0,0 +1,38 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver axiado-pwm-fan
============================
Supported chips:
* Axiado AX3000
* Axiado AX3005
Datasheet: Not publicly available
Author:
* Petar Stepanovic <pstepanovic@axiado.com>
Description
-----------
The Axiado AX3000 and AX3005 PWM fan controllers measure the rotational
speed of one fan using a hardware tachometer block. Fan speed is controlled
through a PWM signal supplied by an external PWM controller.
The number of tachometer pulses generated per fan revolution is configured
through the ``pulses-per-revolution`` devicetree property. If the property
is not specified, the driver uses two pulses per revolution.
Sysfs attributes
----------------
The driver provides the following standard hwmon attributes:
=============== ====== =====================================================
fan1_input RO Fan speed in revolutions per minute (RPM).
pwm1 RW Relative PWM control value from 0 to 255. A value of
255 selects the maximum PWM duty cycle.
=============== ====== =====================================================

View File

@ -0,0 +1,41 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver axiado-tsadc
==========================
Supported chips:
* Axiado AX3000 and AX3005 on-chip temperature sensor (TSADC)
Prefix: 'axiado_tsadc'
Addresses scanned: -
Datasheet: Not publicly available
Authors:
- Petar Stepanovic <pstepanovic@axiado.com>
Description
-----------
This driver supports the Temperature Sensor ADC (TSADC) block found on Axiado
AX3000 and AX3005 SoCs. The block monitors the on-die temperature of the SoC.
An SoC may instantiate several TSADC blocks at different locations on the die.
Each block is described by its own device tree node and is registered as a
separate hwmon device with a single temperature channel.
The sensor is configured for continuous conversion at probe time, averaging
16 samples per conversion. The averaged 12-bit ADC code is converted to
millidegrees Celsius using a lookup table with linear interpolation between
entries. The table covers -40 to +125 degrees Celsius; readings outside that
range are clamped to the nearest supported temperature.
sysfs entries
-------------
======================= =======================================================
temp1_input Die temperature in millidegrees Celsius (read-only)
======================= =======================================================

View File

@ -46,18 +46,25 @@ mechanism will perform actions to forcibly cool down the processor. Alarm
may be raised, if the temperature grows enough (more than TjMax) to trigger
the Out-Of-Spec bit. The following table summarizes the exported sysfs files:
All sysfs entries are named with their core_id (represented here by 'X').
Per-core sysfs entries are numbered core_id + 2 (represented here by 'X').
The package entries, if available, are always temp1_*.
================= ========================================================
tempX_input Core temperature (in millidegrees Celsius).
Reading fails with -ENODATA while the CPU reports the
temperature as invalid. Some CPUs do this when the
temperature is too low to be measured.
tempX_max Maximum recommended operating temperature (Tcontrol).
All cooling devices should be turned on.
tempX_crit Maximum junction temperature (in millidegrees Celsius).
tempX_crit_alarm Set when Out-of-spec bit is set, never clears.
Correct CPU operation is no longer guaranteed.
tempX_label Contains string "Core X", where X is processor
number. For Package temp, this will be "Package id Y",
where Y is the package number.
tempX_label Contains string "Core N", where N is the core_id
(X - 2), as shown in
/sys/devices/system/cpu/cpu*/topology/core_id. This
is not the logical CPU number, and core_id values
may be sparse. For Package temp, this will be
"Package id Y", where Y is the package number.
================= ========================================================
On modern CPUs (Nehalem and newer), TjMax is read from the

View File

@ -0,0 +1,32 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel driver honor-fmi
=======================
Supported systems:
* HONOR FMI-XX
Author: Nikita Dubrovskih <testname142@gmail.com>
Description
-----------
The driver provides read-only monitoring of the fan speed on the HONOR FMI-XX.
The system firmware implements a ``GFNS`` ACPI method which returns the speed
of one of two firmware fan channels in RPM. Embedded Controller access and
serialization are handled by the firmware method.
The driver does not expose fan control or direct Embedded Controller access.
Sysfs entries
-------------
The following attributes are supported:
======================= ======= =============================================
Name Perm Description
======================= ======= =============================================
``fan1_input`` RO Fan channel 0 speed in RPM
``fan2_input`` RO Fan channel 1 speed in RPM
======================= ======= =============================================

View File

@ -51,6 +51,8 @@ Hardware Monitoring Kernel Drivers
asus_ec_sensors
asus_rog_ryujin
asus_wmi_sensors
axiado-pwm-fan
axiado-tsadc
bcm54140
bel-pfe
bpa-rs600
@ -90,6 +92,7 @@ Hardware Monitoring Kernel Drivers
gxp-fan-ctrl
hac300s
hih6130
honor-fmi
hp-wmi-sensors
hs3001
htu31
@ -165,6 +168,7 @@ Hardware Monitoring Kernel Drivers
max197
max20730
max20751
max20826
max20830
max20860a
max31722
@ -186,6 +190,7 @@ Hardware Monitoring Kernel Drivers
mcp3021
mcp9982
menf21bmc
minisforum-um780xtx
mlxreg-fan
mp2856
mp2869
@ -263,6 +268,7 @@ Hardware Monitoring Kernel Drivers
sy7636a-hwmon
tc654
tc74
tda38740
thmc50
tmp102
tmp103

View File

@ -11,6 +11,14 @@ Supported chips:
Datasheet: Not publicly available
* IT8613E
Prefix: 'it8613'
Addresses scanned: from Super I/O config space (8 I/O ports)
Datasheet: Not publicly available
* IT8620E
Prefix: 'it8620'

View File

@ -28,8 +28,6 @@ Supported chips:
* AMD Family 17h processors: "Zen", "Zen 2"
* AMD Family 18h processors: "Hygon Dhyana"
* AMD Family 19h processors: "Zen 3"
Prefix: 'k10temp'
@ -128,7 +126,7 @@ it.
Models from 17h family report relative temperature, the driver aims to
compensate and report the real temperature.
On Family 17h and Family 18h CPUs, additional temperature sensors may report
Core Complex Die (CCD) temperatures. Up to 8 such temperatures are reported
as temp{3..10}_input, labeled Tccd{1..8}. Actual support depends on the CPU
On Family 17h CPUs, additional temperature sensors may report Core Complex
Die (CCD) temperatures. Up to 8 such temperatures are reported as
temp{3..10}_input, labeled Tccd{1..8}. Actual support depends on the CPU
variant.

View File

@ -33,6 +33,16 @@ Supported chips:
http://www.national.com/pf/LM/LM96163.html
* Sensylink CTF2301
Prefix: 'ctf2301'
Addresses scanned: none
Datasheet: https://www.sensylink.com/upload/1/net.sensylink.portal/1689557281035.pdf
Register description: https://github.com/TroyMitchell911/ctf2301-datasheet
Author: Jean Delvare <jdelvare@suse.de>
@ -62,6 +72,8 @@ value have to be masked out. The value is still 16 bit in width.
All temperature values are given in degrees Celsius. Resolution is 1.0
degree for the local temperature, 0.125 degree for the remote temperature.
The CTF2301 local temperature input and limit have a resolution of 0.0625
degree.
The fan speed is measured using a tachometer. Contrary to most chips which
store the value in an 8-bit register and have a selectable clock divider
@ -93,3 +105,13 @@ support these GPIO lines at present.
The LM96163 is an enhanced version of LM63 with improved temperature accuracy
and better PWM resolution. For LM96163, the external temperature sensor type is
configurable as CPU embedded diode(1) or 3904 transistor(2).
The CTF2301 is register-compatible with the LM63 family and provides 12 fan
control lookup table entries. It supports 8-bit PWM resolution when configured
for a 22.5 kHz PWM frequency.
Device tree nodes may declare ``#cooling-cells`` to register the fan controller
with the thermal framework. The cooling state is mapped to the existing
``pwm1`` range from 0 to 255. The driver keeps the current automatic or manual
fan control mode during probe and switches to manual control when the thermal
framework first requests a cooling state.

View File

@ -0,0 +1,139 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver max20826
======================
Supported chips:
* Analog Devices MAX20826
Prefix: 'max20826'
Addresses scanned: -
Datasheet: Datasheet is not publicly available.
* Analog Devices MAX20855B
Prefix: 'max20855b'
Addresses scanned: -
Datasheet: Datasheet is not publicly available.
* Analog Devices MAX20908
Prefix: 'max20908'
Addresses scanned: -
Datasheet: Datasheet is not publicly available.
* Analog Devices MAX20912
Prefix: 'max20912'
Addresses scanned: -
Datasheet: Datasheet is not publicly available.
* Analog Devices MAX20916
Prefix: 'max20916'
Addresses scanned: -
Datasheet: Datasheet is not publicly available.
Author:
- Nuno Sá <nuno.sa@analog.com>
Description
-----------
This driver supports hardware monitoring for Analog Devices MAX20826,
MAX20855B, MAX20908, MAX20912, and MAX20916 multiphase voltage regulator
controllers with PMBus interface.
The devices are dual-loop, multiphase controllers. Depending on the device and
configuration, the high-speed processor voltage-control interface can be
Nvidia PWMVID, Intel SVID, AMD SVI3, or AVSBus. PMBus is used for monitoring,
configuration, status, and fault reporting.
The driver detects whether the device uses PMBus page mode or direct address
mode. In direct address mode, rail B is accessed at the rail A I2C address plus
one. If rail B is present, the driver exposes a second PMBus page.
The driver detects the active number of phases and exposes per-phase input and
output current attributes through the PMBus virtual phase support.
Usage Notes
-----------
This driver does not auto-detect devices. You will have to instantiate the
devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
details.
The optional ``avren`` and ``bvren`` GPIOs may be provided to control the rail A
and rail B hardware enable pins. If regulator support is enabled, the rails are
also registered through the PMBus regulator framework.
The source of the output voltage of each rail, i.e. either the high-speed
processor voltage-control interface or PMBus, is normally selected once, before
the processor boots. Hence it is described in firmware with the
``adi,rail-a-high-speed`` and ``adi,rail-b-high-speed`` properties. If the
property is not given for a rail, PMBus controls its output voltage.
Sysfs entries
-------------
The following attributes are supported. Limits, alarms, and per-phase entries
are exposed depending on device capabilities, rail configuration, and detected
phase count.
=========================== ================================================
in1_label "vin"
in1_input Measured input voltage
in1_alarm Input voltage alarm
in[2-3]_label "vout[1-2]"
in[2-3]_input Measured output voltage
in[2-3]_alarm Output voltage alarm
currX_label "iinN", "iinN.P", "ioutN", or "ioutN.P"
currX_input Measured input/output current
currX_alarm Current alarm
powerX_label "pinN" or "poutN"
powerX_input Measured input/output power
temp[1-2]_input Measured temperature
temp[1-2]_alarm Temperature alarm
=========================== ================================================
Notes
-----
``N`` is the rail number, starting at 1. ``P`` is the phase number, starting at
0. The exact ``currX`` indices depend on the number of present rails and on the
detected phase count.
Debugfs entries
---------------
In addition to the entries provided by the PMBus core, the following entries are
available for debug purposes only. They are not part of any ABI and may change
or go away at any time.
=========================== ================================================
in[2-3]_high_speed_en Enable high-speed voltage-control interface
in_high_speed_bus Active high-speed voltage-control interface
=========================== ================================================
``in[2-3]_high_speed_en`` is a per-rail read/write entry matching the standard
hwmon voltage channels for the output rails. ``in2_high_speed_en`` controls rail
A / ``vout1``. ``in3_high_speed_en`` controls rail B / ``vout2`` and is only
present if rail B is detected. Writing 1 selects the high-speed voltage-control
interface for that rail; writing 0 selects PMBus voltage control.
``in_high_speed_bus`` is a read-only entry reporting the high-speed interface
used by the device, for example ``Nvidia PWMVID``, ``Intel SVID``, ``AMD SVI3``,
or ``AVSBus``.

View File

@ -27,6 +27,14 @@ Supported chips:
Datasheet: -
* ADI ADPM12300
Prefixes: 'adpm12300'
Addresses scanned: -
Datasheet: -
* Maxim MAX34440
Prefixes: 'max34440'
@ -105,11 +113,11 @@ This driver supports multiple devices: hardware monitoring for Maxim MAX34440
PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply
Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data
Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34452, MAX34460,
and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The
MAX34451 and MAX34452 support monitoring voltage or current of 16 channels based
on GIN pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports
16 voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitor both
input and output of voltage and current.
and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, ADPM12250 and
ADPM12300. The MAX34451 and MAX34452 support monitoring voltage or current of 16
channels based on GIN pins. The MAX34460 supports 12 voltage channels, and the
MAX34461 supports 16 voltage channels. The ADPM12160, ADPM12200, ADPM12250 and
ADPM12300 also monitor both input and output of voltage and current.
The driver is a client driver to the core PMBus driver. Please see
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
@ -167,8 +175,8 @@ in[1-6]_reset_history Write any value to reset history.
.. note::
- MAX34446 only supports in[1-4].
- ADPM12160, ADPM12200, and ADPM12250 only supports in[1-2]. Label is "vin1"
and "vout1" respectively.
- ADPM12160, ADPM12200, ADPM12250, and ADPM12300 only supports in[1-2].
Label is "vin1" and "vout1" respectively.
Curr
~~~~
@ -190,9 +198,9 @@ curr[1-6]_reset_history Write any value to reset history.
- in6 and curr6 attributes only exist for MAX34440.
- MAX34446 only supports curr[1-4].
- For ADPM12160, ADPM12200, and ADPM12250, curr[1] is "iin1"
- For ADPM12160, ADPM12200, ADPM12250, and ADPM12300, curr[1] is "iin1"
- For ADPM12160, and ADPM12200 curr[2-6] are "iout[1-5]".
- For ADPM12250, curr[2-4] are "iout[1-3]".
- For ADPM12250, and ADPM12300 curr[2-4] are "iout[1-3]".
Power
~~~~~
@ -228,7 +236,7 @@ temp[1-8]_reset_history Write any value to reset history.
.. note::
- temp7 and temp8 attributes only exist for MAX34440.
- MAX34446 only supports temp[1-3].
- ADPM12160, ADPM12200, and ADPM12250 only supports temp[1].
- ADPM12160, ADPM12200, ADPM12250, and ADPM12300 only supports temp[1].
.. note::

View File

@ -0,0 +1,68 @@
.. SPDX-License-Identifier: GPL-2.0-only
====================================
Kernel driver minisforum-um780xtx
====================================
Supported systems:
* Minisforum UM780 XTX
* DMI product name: ``Venus series``
* Mainboard: ``F7BSD``, revision ``1.1``
* BIOS version: ``1.06``
Author: Sebastián Peyrott <speyrott@gmail.com>
Description
-----------
This driver exposes hardware monitoring and fan-control data cached by the
IT5571E embedded controller. It uses the ACPI EC transport and only binds to
the exact system and firmware identity listed above.
The two temperature channels are the values used by the EC's fan-control
loops. Their physical sensor placement is not known. The CPU channel is the
filtered AMD SB-TSI temperature, while the system channel is an external
thermistor input.
The fan tachometers are read through OEM EC commands. Since each byte is
returned by a separate command, the driver uses a high-low-high sequence and
retries if the high byte changes.
Sysfs entries
-------------
========================== ==============================================
``temp1_input`` CPU-fan control temperature
``temp2_input`` System-fan control temperature
``fan1_input`` CPU fan speed in RPM
``fan2_input`` System fan speed in RPM
``pwm1_enable`` CPU profile: 2 is OEM B1, 3 is OEM B2
``pwm2_auto_point1_temp`` Off-to-low system-fan transition temperature
``pwm2_auto_point2_temp`` Low-to-high system-fan transition temperature
========================== ==============================================
CPU fan profiles
----------------
Values 2 and 3 of ``pwm1_enable`` select the two complete automatic profiles
implemented by the firmware. Writing either value reloads the whole CPU fan
curve, including firmware state which is not visible through the ACPI EC
window. Other values are rejected.
System fan thresholds
---------------------
The two writable system-fan temperatures are rounded to whole degrees Celsius
and clamped to preserve strict ordering between both visible thresholds and
the firmware's internal third threshold. The third threshold does not select
a new PWM target and is therefore not exposed as another auto point.
Resume state preservation
-------------------------
Fan settings are held in EC RAM. On the supported firmware, initialization
during s2idle resume reloads the factory system-fan curve. The driver retains
the last coherent CPU profile and system-fan thresholds selected through its
interface and restores them synchronously at resume.

View File

@ -67,6 +67,10 @@ ASRock X670E NCT6686D EC firmware version 1.0 build 05/19/22
ASRock B650 Steel Legend WiFi NCT6686D EC firmware version 1.0 build 11/09/23
ASRock Z590 Taichi NCT6686D EC firmware version 1.0 build 01/25/21
MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20
MSI B550M NCT6687D EC firmware version 1.0 build 05/07/20
MSI B650 NCT6687D EC firmware version 0.0 build 09/27/22
MSI B650M NCT6687D EC firmware version 0.0 build 09/27/22
MSI B850M NCT6687D EC firmware version 0.0 build 03/10/25
MSI X670-P NCT6687D EC firmware version 0.0 build 09/27/22
MSI X870E NCT6687D EC firmware version 0.0 build 11/13/24
=============================== ===============================================

View File

@ -16,6 +16,38 @@ Supported chips:
Datasheet: Available from the Nuvoton web site
* Nuvoton NCT6112D/NCT6114D/NCT6116D
Prefix: 'nct6116'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: Available from the Nuvoton web site
* Nuvoton NCT6122D/NCT6126D
Prefix: 'nct6126'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: Available from the Nuvoton web site
* Nuvoton NCT6112D/NCT6114D/NCT6116D
Prefix: 'nct6116'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: Available from the Nuvoton web site
* Nuvoton NCT6122D/NCT6126D
Prefix: 'nct6126'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: Available from the Nuvoton web site
* Nuvoton NCT5572D/NCT6771F/NCT6772F/NCT6775F/W83677HG-I
Prefix: 'nct6775'

View File

@ -15,6 +15,16 @@ Supported Chips:
English: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT4x_Datasheet.pdf
* Sensirion STS4X
Prefix: 'sts4x'
Addresses scanned: None
Datasheet:
English: https://sensirion.com/resource/datasheet/sts4x
Author: Navin Sankar Velliangiri <navin@linumiz.com>
@ -22,9 +32,10 @@ Description
-----------
This driver implements support for the Sensirion SHT4x chip, a humidity
and temperature sensor. Temperature is measured in degree celsius, relative
humidity is expressed as a percentage. In sysfs interface, all values are
scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
and temperature sensor, and the Sensirion STS4x chip, a temperature sensor.
Temperature is measured in degree celsius, relative humidity is expressed as a
percentage (on SHT4x only). In sysfs interface, all values are scaled by 1000,
i.e. the value for 31.5 degrees celsius is 31500.
Usage Notes
-----------

View File

@ -7,6 +7,7 @@ Supported chips:
* Altera Stratix 10 SoC FPGA
* Altera Agilex SoC FPGA
* Altera Agilex 5 SoC FPGA
Authors:
- Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
@ -32,3 +33,54 @@ driver based on the service layer compatible string:
* intel,stratix10-svc
* intel,agilex-svc
* intel,agilex5-svc
Channel mappings are fixed in the driver (not described in DT). The tables
below list the SDM page/channel encodings used for each family.
Temperature channels
~~~~~~~~~~~~~~~~~~~~
========== ==== ======= =================================
Family Page Channel Label
========== ==== ======= =================================
Stratix 10 0 0 Main Die SDM
Agilex 0 0 Main Die SDM
Agilex 1 0 Main Die corner bottom left max
Agilex 2 0 Main Die corner top left max
Agilex 3 0 Main Die corner bottom right max
Agilex 4 0 Main Die corner top right max
Agilex 5 0 0 Main Die SDM
Agilex 5 1 0 Main Die corner bottom left max
Agilex 5 3 0 Main Die corner bottom right max
Agilex 5 4 0 Main Die corner top right max
========== ==== ======= =================================
Agilex 5 omits SDM temperature channel 2 (top-left corner on Agilex)
because that sensor is not present in hardware. The remaining sensors keep
the same channel numbers as Agilex.
Voltage channels
~~~~~~~~~~~~~~~~
========== ==== ======= =================
Family Page Channel Label
========== ==== ======= =================
Stratix 10 0 2 0.8V VCC
Stratix 10 0 3 1.8V VCCIO_SDM
Stratix 10 0 6 0.9V VCCERAM
Agilex 0 2 0.8V VCC
Agilex 0 3 1.8V VCCIO_SDM
Agilex 0 4 1.8V VCCPT
Agilex 0 5 1.2V VCCCRCORE
Agilex 0 6 0.9V VCCH
Agilex 0 7 0.8V VCCL
Agilex 5 0 2 0.8V VCC
Agilex 5 0 3 1.8V VCCIO_SDM
Agilex 5 0 4 1.8V VCCPT
Agilex 5 0 5 1.2V VCCCRCORE
Agilex 5 0 6 0.9V VCCH
Agilex 5 0 7 0.8V VCCL
========== ==== ======= =================
Agilex 5 reuses the Agilex voltage SDM page/channel layout and labels.

View File

@ -47,7 +47,7 @@ There is only one value per file, unlike the older /proc specification.
The common scheme for files naming is: <type><number>_<item>. Usual
types for sensor chips are "in" (voltage), "temp" (temperature) and
"fan" (fan). Usual items are "input" (measured value), "max" (high
threshold, "min" (low threshold). Numbering usually starts from 1,
threshold), "min" (low threshold). Numbering usually starts from 1,
except for voltages which start from 0 (because most data sheets use
this). A number is always used for elements that can be present more
than once, even if there is a single element of the given type on the
@ -121,12 +121,18 @@ Voltages
`in[0-*]_lcrit`
Voltage critical min value.
`in[0-*]_lemergency`
Voltage emergency min value.
`in[0-*]_max`
Voltage max value.
`in[0-*]_crit`
Voltage critical max value.
`in[0-*]_emergency`
Voltage emergency max value.
`in[0-*]_input`
Voltage input value.
@ -332,6 +338,9 @@ Currents
`curr[1-*]_crit`
Current critical high value.
`curr[1-*]_emergency`
Current emergency high value.
`curr[1-*]_input`
Current input value.
@ -527,12 +536,15 @@ implementation.
+-------------------------------+-----------------------+
| **`in[0-*]_min_alarm`, | Limit alarm |
| `in[0-*]_max_alarm`, | |
| `in[0-*]_lemergency_alarm`, | |
| `in[0-*]_lcrit_alarm`, | - 0: no alarm |
| `in[0-*]_crit_alarm`, | - 1: alarm |
| `in[0-*]_emergency_alarm`, | |
| `curr[1-*]_min_alarm`, | |
| `curr[1-*]_max_alarm`, | RO |
| `curr[1-*]_lcrit_alarm`, | |
| `curr[1-*]_crit_alarm`, | |
| `curr[1-*]_emergency_alarm`, | |
| `power[1-*]_cap_alarm`, | |
| `power[1-*]_max_alarm`, | |
| `power[1-*]_crit_alarm`, | |

View File

@ -0,0 +1,65 @@
.. SPDX-License-Identifier: GPL-2.0-only
====================================
Kernel driver tda38740
====================================
Supported chips:
* Infineon TDA38740
* Infineon TDA38725
Prefix: 'tda38740'
Addresses scanned: -
Datasheet:
https://www.infineon.com/row/public/documents/24/49/infineon-tda38740-tda38725-datasheet-en.pdf
Author: Colin Huang <u8813345@gmail.com>
Description
-----------
The TDA38740 and TDA38725 are single-output synchronous buck regulators with a
PMBus interface. They expose basic voltage, current, power and temperature
telemetry through the standard PMBus hwmon interfaces.
Sysfs entries
-------------
======================= =======================================================
curr1_alarm Input current alarm
curr1_input Measured input current
curr1_label "iin"
curr2_crit Critical maximum output current
curr2_crit_alarm Output current critical high alarm
curr2_input Measured output current
curr2_label "iout"
in1_crit Critical maximum input voltage
in1_crit_alarm Input voltage critical high alarm
in1_input Measured input voltage
in1_label "vin"
in2_crit Critical maximum output voltage
in2_crit_alarm Output voltage critical high alarm
in2_input Measured output voltage
in2_label "vout"
in2_lcrit Critical minimum output voltage
in2_lcrit_alarm Output voltage critical low alarm
power1_alarm Input power alarm
power1_input Measured input power
power1_label "pin"
power2_input Measured output power
power2_label "pout"
temp1_crit Critical high temperature
temp1_crit_alarm Chip temperature critical high alarm
temp1_input Measured temperature
temp1_max Maximum temperature
temp1_max_alarm Chip temperature high alarm
======================= =======================================================

View File

@ -9,26 +9,31 @@ Supported chips:
Prefix: 'tps25990'
* Datasheet
Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990
Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990
* TI TPS1689
Prefix: 'tps1689'
Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps1689
Author:
Jerome Brunet <jbrunet@baylibre.com>
Stoyan Bogdanov <sbogdanov@baylibre.com>
Description
-----------
This driver implements support for TI TPS25990 eFuse.
This driver implements support for TI TPS25990 and TI TPS1689 eFuse chips.
This is an integrated, high-current circuit protection and power
management device with PMBUS interface
Device compliant with:
Devices are compliant with:
- PMBus rev 1.3 interface.
Device supports direct format for reading input voltages,
Devices supports direct format for reading input voltages,
output voltage, input current, input power and temperature.
Due to the specificities of the chip, all history reset attributes

View File

@ -3,6 +3,14 @@ Kernel driver tps53679
Supported chips:
* Texas Instruments TPS53622
Prefix: 'tps53622'
Addresses scanned: -
Datasheet: https://www.ti.com/lit/gpn/TPS53622
* Texas Instruments TPS53647
Prefix: 'tps53647'
@ -11,6 +19,14 @@ Supported chips:
Datasheet: https://www.ti.com/lit/gpn/tps53647
* Texas Instruments TPS53659
Prefix: 'tps53659'
Addresses scanned: -
Datasheet: https://www.ti.com/lit/gpn/TPS53659
* Texas Instruments TPS53667
Prefix: 'tps53667'
@ -59,6 +75,14 @@ Supported chips:
Datasheet: Available under NDA
* Texas Instruments TPS536C7
Prefix: 'tps536c7'
Addresses scanned: -
Datasheet: https://www.ti.com/lit/gpn/TPS536C7
Authors:
Vadim Pasternak <vadimp@mellanox.com>
@ -69,7 +93,12 @@ Description
-----------
Chips in this series are multi-phase step-down converters with one or two
output channels and up to 8 phases per channel.
output channels and up to 12 phases in total, depending on the device.
For TPS536C7 only aggregate per-channel telemetry is exposed; the
individual per-phase output currents the hardware can report are not.
Attributes for the second output channel are present only when channel B
is configured.
Usage Notes
@ -108,7 +137,7 @@ in1_crit_alarm Input voltage critical high alarm.
in[N]_label "vout[1-2]"
- TPS53647, TPS53667: N=2
- TPS53679, TPS53588: N=2,3
- TPS53622, TPS53659, TPS53679, TPS53688, TPS536C7: N=2,3
in[N]_input Measured output voltage.
@ -135,7 +164,7 @@ in[N]_crit_alarm Output voltage critical high alarm.
temp[N]_input Measured temperature.
- TPS53647, TPS53667: N=1
- TPS53679, TPS53681, TPS53588: N=1,2
- TPS53622, TPS53659, TPS53679, TPS53681, TPS53688, TPS536C7: N=1,2
temp[N]_max Maximum temperature.
@ -152,7 +181,7 @@ power1_input Measured input power.
power[N]_label "pout[1-2]".
- TPS53647, TPS53667: N=2
- TPS53676, TPS53679, TPS53681, TPS53588: N=2,3
- TPS53622, TPS53659, TPS53676, TPS53679, TPS53681, TPS53688, TPS536C7: N=2,3
power[N]_input Measured output power.
@ -175,7 +204,7 @@ curr[N]_label "iout[1-2]" or "iout1.[0-5]".
telemetry supported on TPS53676 and TPS53681 only.
- TPS53647, TPS53667: N=2
- TPS53679, TPS53588: N=2,3
- TPS53622, TPS53659, TPS53679, TPS53688, TPS536C7: N=2,3
- TPS53676: N=2-8
- TPS53681: N=2-9

View File

@ -38,4 +38,4 @@ via a custom interface, abstracted by the "vexpress_config" API.
As these devices are non-discoverable, they must be described in a Device
Tree passed to the kernel. Details of the DT binding for them can be found
in Documentation/devicetree/bindings/hwmon/vexpress.txt.
in Documentation/devicetree/bindings/arm/vexpress-config.yaml.

View File

@ -85,27 +85,30 @@ immediately to ensure the user knows the fan has stopped.
::
MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier
----------------------------------------------------------------------------------------------------
82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1
83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100
83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
----------------------------------------------------------------------------------------------------
MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier
-------------------------------------------------------------------------------------------------------
82N7 | Yoga 14cACN 2021 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
81TD | Yoga 740-15IML | 0x06 | \_SB.PCI0.LPCB.EC0.FANS | 8-bit | 100
82KU | IdeaPad 3 15ALC6 Ub | 0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83E2 | Yoga Pro 7 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
83DN | Yoga Pro 9 16IMH9 | 0x06/0xFE | \_SB.PC00.LPCB.EC0.FANS/FA2S | 8-bit | 100
82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1
83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100
83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
-------------------------------------------------------------------------------------------------------
METHODOLOGY & IDENTIFICATION:
@ -158,6 +161,7 @@ Contributors & DSDT Providers:
- **Sarbajit Sarkar** (Lenovo LOQ 15IAX9)
- **HinataKato** (XiaoXin Pro 13ARE 2020 - 83KF)
- **PenPenIsGod** (IdeaPad 3 15ALC6 - 82KU & Legion Pro 7 16AFR10H - 83RU)
- **wizard-28** (IdeaPad 3 15ALC6 Ub - 82KU)
- **unlockxiaom** (Legion Pro 7 16AFR10H - 83RU, Yoga Pro 7 14IAH10 - 83KF, ThinkCentre M80q)
- **Phani Pavan K** (Yoga Pro 7 14IAH10 - 83KF)
- **Splarkszter** (Yoga 7 16ARP8 - 83BS)

View File

@ -2228,7 +2228,7 @@ F: drivers/net/arcnet/
F: include/uapi/linux/if_arcnet.h
ARCTIC FAN CONTROLLER DRIVER
M: Aureo Serrano de Souza <aureo.serrano@arctic.de>
M: ARCTIC (HK) <arctic.it.hk@arctic.de>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/arctic_fan_controller.rst
@ -4589,6 +4589,16 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
F: drivers/pwm/pwm-axi-pwmgen.c
AXIADO PWM FAN CONTROLLER DRIVER
M: Petar Stepanovic <pstepanovic@axiado.com>
M: Akhila Kavi <akavi@axiado.com>
M: Prasad Bolisetty <pbolisetty@axiado.com>
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml
F: Documentation/hwmon/axiado-pwm-fan.rst
F: drivers/hwmon/axiado-pwm-fan.c
AXIADO SPI DB DRIVER
M: Vladimir Moravcevic <vmoravcevic@axiado.com>
M: Tzu-Hao Wei <twei@axiado.com>
@ -4600,6 +4610,16 @@ F: Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml
F: drivers/spi/spi-axiado.c
F: drivers/spi/spi-axiado.h
AXIADO TSADC DRIVER
M: Petar Stepanovic <pstepanovic@axiado.com>
M: Akhila Kavi <akavi@axiado.com>
M: Prasad Bolisetty <pbolisetty@axiado.com>
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/axiado,ax3000-tsadc.yaml
F: Documentation/hwmon/axiado-tsadc.rst
F: drivers/hwmon/axiado-tsadc.c
AYANEO PLATFORM EC DRIVER
M: Antheas Kapenekakis <lkml@antheas.dev>
L: platform-driver-x86@vger.kernel.org
@ -12173,6 +12193,13 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml
F: drivers/iio/pressure/mprls0025pa*
HONOR FMI-XX HARDWARE MONITOR DRIVER
M: Nikita Dubrovskih <testname142@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/honor-fmi.rst
F: drivers/hwmon/honor-fmi.c
HP BIOSCFG DRIVER
M: Jorge Lopez <jorge.lopez2@hp.com>
L: platform-driver-x86@vger.kernel.org
@ -16259,6 +16286,15 @@ F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max17616.yaml
F: Documentation/hwmon/max17616.rst
F: drivers/hwmon/pmbus/max17616.c
MAX20826 HARDWARE MONITOR CONTROLLER DRIVER
M: Nuno Sá <nuno.sa@analog.com>
L: linux-hwmon@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml
F: Documentation/hwmon/max20826.rst
F: drivers/hwmon/pmbus/max20826.c
MAX20830 HARDWARE MONITOR DRIVER
M: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
L: linux-hwmon@vger.kernel.org
@ -18488,6 +18524,13 @@ F: include/linux/min_heap.h
F: lib/min_heap.c
F: lib/tests/min_heap_kunit.c
MINISFORUM UM780 XTX EC HARDWARE MONITOR DRIVER
M: Sebastián Peyrott <speyrott@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/minisforum-um780xtx.rst
F: drivers/hwmon/minisforum-um780xtx.c
MIPI CCS, SMIA AND SMIA++ IMAGE SENSOR DRIVER
M: Sakari Ailus <sakari.ailus@linux.intel.com>
L: linux-media@vger.kernel.org

View File

@ -311,6 +311,29 @@ config SENSORS_AXI_FAN_CONTROL
This driver can also be built as a module. If so, the module
will be called axi-fan-control
config SENSORS_AXIADO_PWM_FAN
tristate "Axiado PWM fan controller"
depends on ARCH_AXIADO || COMPILE_TEST
depends on PWM
help
This driver supports the Axiado PWM fan controller. It uses a PWM
output to control fan speed and a tachometer interrupt to report fan
speed as RPM through the hardware monitoring sysfs interface.
This driver can also be built as a module. If so, the module will be
called axiado-pwm-fan.
config SENSORS_AXIADO_TSADC
tristate "Axiado AX3000 TSADC temperature sensor"
depends on ARCH_AXIADO || COMPILE_TEST
help
If you say yes here you get support for the on-chip temperature
sensor (TSADC) found on Axiado AX3000 and AX3005 SoCs. The die
temperature is reported through the hwmon sysfs interface.
This driver can also be built as a module. If so, the module
will be called axiado-tsadc.
config SENSORS_K8TEMP
tristate "AMD Athlon64/FX or Opteron temperature sensor"
depends on X86 && PCI
@ -1490,6 +1513,22 @@ config SENSORS_MENF21BMC_HWMON
This driver can also be built as a module. If so the module
will be called menf21bmc_hwmon.
config SENSORS_MINISFORUM_UM780XTX
tristate "Minisforum UM780 XTX EC hardware monitoring"
depends on ACPI_EC && DMI && X86
help
If you say yes here you get support for the hardware monitoring
features of the embedded controller in the Minisforum UM780 XTX.
This includes CPU and system fan speeds, their control temperatures,
two CPU fan profiles, system fan temperature thresholds, and
preservation of the selected settings across suspend and resume.
This driver only binds to explicitly supported board and firmware
versions.
This driver can also be built as a module. If so, the module
will be called minisforum-um780xtx.
config SENSORS_MR75203
tristate "Moortec Semiconductor MR75203 PVT Controller"
select REGMAP_MMIO
@ -1521,10 +1560,10 @@ config SENSORS_LM63
depends on I2C
help
If you say yes here you get support for the National
Semiconductor LM63, LM64, and LM96163 remote diode digital temperature
sensors with integrated fan control. Such chips are found
on the Tyan S4882 (Thunder K8QS Pro) motherboard, among
others.
Semiconductor LM63, LM64, LM96163, and Sensylink CTF2301
remote diode digital temperature sensors with integrated fan
control. Such chips are found on the Tyan S4882 (Thunder K8QS
Pro) motherboard, among others.
This driver can also be built as a module. If so, the module
will be called lm63.
@ -2827,6 +2866,16 @@ config SENSORS_ASUS_EC
This driver can also be built as a module. If so, the module
will be called asus_ec_sensors.
config SENSORS_HONOR_FMI
tristate "HONOR FMI-XX fan monitor"
depends on X86 && ACPI
help
If you say yes here, you get support for fan speed monitoring on
the HONOR FMI-XX laptop through its firmware ACPI method.
This driver can also be built as a module. If so, the module
will be called honor-fmi.
config SENSORS_HP_WMI
tristate "HP WMI Sensors"
depends on ACPI_WMI

View File

@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o
obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o
obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o
obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o
obj-$(CONFIG_SENSORS_HONOR_FMI) += honor-fmi.o
obj-$(CONFIG_SENSORS_HP_WMI) += hp-wmi-sensors.o
# Native drivers
@ -59,6 +60,8 @@ obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_AXIADO_PWM_FAN) += axiado-pwm-fan.o
obj-$(CONFIG_SENSORS_AXIADO_TSADC) += axiado-tsadc.o
obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o
obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
@ -180,6 +183,7 @@ obj-$(CONFIG_SENSORS_TC654) += tc654.o
obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
obj-$(CONFIG_SENSORS_MINISFORUM_UM780XTX) += minisforum-um780xtx.o
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o

View File

@ -475,7 +475,7 @@ abituguru_detect_bank1_sensor_type(struct abituguru_data *data,
u8 val, test_flag, buf[3];
int i, ret = -ENODEV; /* error is the most common used retval :| */
/* If overriden by the user return the user selected type */
/* If overridden by the user return the user selected type */
if (bank1_types[sensor_addr] >= ABIT_UGURU_IN_SENSOR &&
bank1_types[sensor_addr] <= ABIT_UGURU_NC) {
ABIT_UGURU_DEBUG(2, "assuming sensor type %d for bank1 sensor "
@ -1262,7 +1262,7 @@ static int abituguru_probe(struct platform_device *pdev)
/*
* El weirdo probe order, to keep the sysfs order identical to the
* BIOS and window-appliction listing order.
* BIOS and window-application listing order.
*/
static const u8 probe_order[ABIT_UGURU_MAX_BANK1_SENSORS] = {
0x00, 0x01, 0x03, 0x04, 0x0A, 0x08, 0x0E, 0x02,

View File

@ -10,6 +10,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/limits.h>
#include <linux/mutex.h>
#include <linux/dmi.h>
#include <linux/slab.h>
@ -30,7 +31,12 @@
#define POWER_METER_CAN_NOTIFY (1 << 3)
#define POWER_METER_IS_BATTERY (1 << 8)
#define UNKNOWN_HYSTERESIS 0xFFFFFFFF
#define UNKNOWN_POWER 0xFFFFFFFF
/*
* ACPI integers can either be 32-bit or 64-bit wide, so
* we have to check for both values.
*/
#define UNKNOWN_POWER_32 U32_MAX
#define UNKNOWN_POWER_64 U64_MAX
#define METER_NOTIFY_CONFIG 0x80
#define METER_NOTIFY_TRIP 0x81
@ -54,8 +60,8 @@ static int can_cap_in_hardware(void)
}
static const struct acpi_device_id power_meter_ids[] = {
{"ACPI000D", 0},
{"", 0},
{ .id = "ACPI000D" },
{ }
};
MODULE_DEVICE_TABLE(acpi, power_meter_ids);
@ -452,7 +458,7 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type,
ret = update_meter(res);
if (ret)
return ret;
if (res->power == UNKNOWN_POWER)
if (res->power == UNKNOWN_POWER_32 || res->power == UNKNOWN_POWER_64)
return -ENODATA;
*val = res->power * 1000;
break;

View File

@ -105,7 +105,7 @@ static int adm1177_read(struct device *dev, enum hwmon_sensor_types type,
return ret;
dummy = (data[0] << 4) | (data[2] >> 4);
/*
* convert to millivolts based on resistor devision
* convert to millivolts based on resistor division
* (V_fullscale / 4096) * raw
*/
if (st->vrange_high)

View File

@ -338,7 +338,7 @@ int adt7x10_probe(struct device *dev, const char *name, int irq,
data->oldconfig = config;
/*
* Set to 16 bit resolution, continous conversion and comparator mode.
* Set to 16 bit resolution, continuous conversion and comparator mode.
*/
data->config = data->oldconfig;
data->config &= ~(ADT7X10_MODE_MASK | ADT7X10_CT_POLARITY |

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
/*
* Linux hwmon driver for ARCTIC Fan Controller
*
@ -35,6 +35,8 @@
* Measured over 500 iterations: max ~563 ms. Keep 1 s as margin.
*/
#define ARCTIC_ACK_TIMEOUT_MS 1000
/* MCU factory default; 40% of 0-255 is 102. */
#define ARCTIC_PWM_DEFAULT 102
struct arctic_fan_data {
struct hid_device *hdev;
@ -164,7 +166,7 @@ static int arctic_fan_write(struct device *dev, enum hwmon_sensor_types type,
/*
* Build the buffer and arm write_pending under in_report_lock so that
* reset_resume() cannot clear pwm_duty[] between the pwm_duty[] read
* reset_resume() cannot replace pwm_duty[] between the pwm_duty[] read
* and the buffer write, and raw_event() cannot deliver a stale ACK
* from a previous write into this write's completion.
*
@ -256,14 +258,14 @@ static int arctic_fan_reset_resume(struct hid_device *hdev)
unsigned long flags;
/*
* The device resets its PWM channels to hardware defaults on power
* loss during suspend. Clear the cached duty values so they reflect
* the unknown hardware state, consistent with probe-time behaviour
* (the device has no GET_REPORT support). Hold in_report_lock so
* this does not race with a concurrent pwm read or write callback.
* The device resets its PWM channels to the MCU factory default
* (40%) on power loss during suspend. Restore the cache to that
* same default, consistent with probe-time behaviour (the device
* has no GET_REPORT support). Hold in_report_lock so this does
* not race with a concurrent pwm read or write callback.
*/
spin_lock_irqsave(&priv->in_report_lock, flags);
memset(priv->pwm_duty, 0, sizeof(priv->pwm_duty));
memset(priv->pwm_duty, ARCTIC_PWM_DEFAULT, sizeof(priv->pwm_duty));
spin_unlock_irqrestore(&priv->in_report_lock, flags);
return 0;
}
@ -288,6 +290,8 @@ static int arctic_fan_probe(struct hid_device *hdev,
priv->hdev = hdev;
spin_lock_init(&priv->in_report_lock);
init_completion(&priv->in_report_received);
/* Same MCU factory default as reset_resume(); see ARCTIC_PWM_DEFAULT above. */
memset(priv->pwm_duty, ARCTIC_PWM_DEFAULT, sizeof(priv->pwm_duty));
hid_set_drvdata(hdev, priv);
ret = hid_hw_start(hdev, HID_CONNECT_DRIVER);
@ -371,4 +375,4 @@ module_hid_driver(arctic_fan_driver);
MODULE_AUTHOR("Aureo Serrano de Souza <aureo.serrano@arctic.de>");
MODULE_DESCRIPTION("HID hwmon driver for ARCTIC Fan Controller");
MODULE_LICENSE("GPL");
MODULE_LICENSE("Dual BSD/GPL");

View File

@ -962,6 +962,8 @@ 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-A GAMING WIFI",
&board_info_strix_x670e_e_gaming_wifi),
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",
@ -980,6 +982,8 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_strix_z390_f_gaming),
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-A GAMING",
&board_info_strix_z490_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",

View File

@ -141,8 +141,8 @@ typedef ssize_t (*sysfs_show_func)(struct device *dev,
struct device_attribute *attr, char *buf);
static const struct acpi_device_id atk_ids[] = {
{ATK_HID, 0},
{"", 0},
{ .id = ATK_HID },
{ }
};
MODULE_DEVICE_TABLE(acpi, atk_ids);

View File

@ -20,6 +20,7 @@
#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb
#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade
#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada
#define USB_PRODUCT_ID_RYUJIN_III 0x1aa2
struct rog_ryujin_device_info {
u8 temp_offset;
@ -613,6 +614,8 @@ static const struct hid_device_id rog_ryujin_table[] = {
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE),
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III),
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
{ }
};

View File

@ -573,7 +573,7 @@ static int asus_wmi_configure_sensor_setup(struct device *dev,
sensor_data->wmi.info[type] = devm_kcalloc(dev,
nr_count[type],
sizeof(*sensor_data->wmi.info),
sizeof(*sensor_data->wmi.info[type]),
GFP_KERNEL);
if (!sensor_data->wmi.info[type])
return -ENOMEM;

View File

@ -0,0 +1,416 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021-2026 Axiado Corporation.
*/
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define AX_TACH_PWM_VAL_MAX 255
/* TACH Register offsets */
#define AX_TACH_CTRL_REG 0x00
#define AX_TACH_TIMER_COUNT_REG 0x04
#define AX_TACH_COUNT_REG 0x08
#define AX_TACH_INT_STATUS_REG 0x0c
#define AX_TACH_CTRL_ENABLE BIT(0)
#define AX_TACH_CTRL_INT_ENABLE BIT(1)
#define AX_TACH_INT_PENDING BIT(0)
struct axiado_pwm_fan_tach {
int irq;
u32 pulses_per_revolution;
u32 timer_count;
u32 count;
};
struct axiado_pwm_fan_ctx {
/* Serializes PWM updates with PM and shutdown operations. */
struct mutex pwm_lock;
/* Protects tachometer count updated from interrupt context. */
spinlock_t tach_lock;
struct pwm_device *pwm;
struct pwm_state pwm_state;
void __iomem *tach_base;
struct axiado_pwm_fan_tach tach;
unsigned int pwm_value;
};
static const struct hwmon_channel_info * const pwm_fan_info[] = {
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
NULL
};
static irqreturn_t axiado_tach_irq_handler(int irq, void *dev)
{
struct axiado_pwm_fan_ctx *ctx = dev;
u32 status;
status = ioread32(ctx->tach_base + AX_TACH_INT_STATUS_REG);
if (!(status & AX_TACH_INT_PENDING))
return IRQ_NONE;
scoped_guard(spinlock_irqsave, &ctx->tach_lock) {
ctx->tach.count = ioread32(ctx->tach_base + AX_TACH_COUNT_REG);
}
iowrite32(AX_TACH_INT_PENDING, ctx->tach_base + AX_TACH_INT_STATUS_REG);
return IRQ_HANDLED;
}
static int axiado_set_pwm(struct axiado_pwm_fan_ctx *ctx, unsigned int pwm)
{
struct pwm_state state;
int ret;
guard(mutex)(&ctx->pwm_lock);
if (ctx->pwm_value == pwm)
return 0;
state = ctx->pwm_state;
state.duty_cycle = DIV_ROUND_UP_ULL((u64)pwm * state.period,
AX_TACH_PWM_VAL_MAX);
/*
* Keep the PWM enabled even at zero duty cycle so the output stays
* driven low instead of being left floating by the PWM hardware.
*/
state.enabled = true;
ret = pwm_apply_might_sleep(ctx->pwm, &state);
if (ret)
return ret;
ctx->pwm_state = state;
ctx->pwm_value = pwm;
return 0;
}
static int axiado_pwm_fan_write(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
if (type != hwmon_pwm || attr != hwmon_pwm_input)
return -EOPNOTSUPP;
if (val < 0 || val > AX_TACH_PWM_VAL_MAX)
return -EINVAL;
return axiado_set_pwm(ctx, val);
}
static unsigned int axiado_tach_get_rpm(struct axiado_pwm_fan_ctx *ctx)
{
u32 pulses_per_revolution = ctx->tach.pulses_per_revolution;
u64 pulses, rpm;
scoped_guard(spinlock_irqsave, &ctx->tach_lock)
pulses = ctx->tach.count;
if (!pulses_per_revolution)
return 0;
rpm = pulses * 60;
do_div(rpm, pulses_per_revolution);
return rpm;
}
static int axiado_pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
switch (type) {
case hwmon_pwm:
if (attr != hwmon_pwm_input)
return -EOPNOTSUPP;
*val = ctx->pwm_value;
return 0;
case hwmon_fan:
if (attr != hwmon_fan_input)
return -EOPNOTSUPP;
*val = axiado_tach_get_rpm(ctx);
return 0;
default:
return -EOPNOTSUPP;
}
}
static umode_t axiado_pwm_fan_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
if (type == hwmon_fan && attr == hwmon_fan_input)
return 0444;
if (type == hwmon_pwm && attr == hwmon_pwm_input)
return 0644;
return 0;
}
static const struct hwmon_ops pwm_fan_hwmon_ops = {
.is_visible = axiado_pwm_fan_is_visible,
.read = axiado_pwm_fan_read,
.write = axiado_pwm_fan_write,
};
static const struct hwmon_chip_info pwm_fan_chip_info = {
.ops = &pwm_fan_hwmon_ops,
.info = pwm_fan_info,
};
static int axiado_pwm_apply_disabled(struct axiado_pwm_fan_ctx *ctx)
{
struct pwm_state state;
guard(mutex)(&ctx->pwm_lock);
if (!ctx->pwm_state.enabled)
return 0;
state = ctx->pwm_state;
state.duty_cycle = 0;
state.enabled = false;
return pwm_apply_might_sleep(ctx->pwm, &state);
}
static void axiado_pwm_disable(void *data)
{
struct axiado_pwm_fan_ctx *ctx = data;
axiado_pwm_apply_disabled(ctx);
}
static void axiado_tach_enable(struct axiado_pwm_fan_ctx *ctx)
{
iowrite32(AX_TACH_INT_PENDING,
ctx->tach_base + AX_TACH_INT_STATUS_REG);
iowrite32(ctx->tach.timer_count,
ctx->tach_base + AX_TACH_TIMER_COUNT_REG);
iowrite32(AX_TACH_CTRL_ENABLE | AX_TACH_CTRL_INT_ENABLE,
ctx->tach_base + AX_TACH_CTRL_REG);
}
static void axiado_tach_disable(void *data)
{
struct axiado_pwm_fan_ctx *ctx = data;
iowrite32(0, ctx->tach_base + AX_TACH_CTRL_REG);
iowrite32(AX_TACH_INT_PENDING,
ctx->tach_base + AX_TACH_INT_STATUS_REG);
}
static int axiado_pwm_fan_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *fan_node;
struct axiado_pwm_fan_ctx *ctx;
unsigned long tach_clk_rate;
struct device *hwmon;
struct clk *tach_clk;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->pwm_lock);
spin_lock_init(&ctx->tach_lock);
ctx->tach_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctx->tach_base))
return PTR_ERR(ctx->tach_base);
fan_node = device_get_named_child_node(dev, "fan");
if (!fan_node)
return dev_err_probe(dev, -EINVAL,
"Missing fan child node\n");
ctx->pwm = devm_fwnode_pwm_get(dev, fan_node, NULL);
if (IS_ERR(ctx->pwm)) {
ret = dev_err_probe(dev, PTR_ERR(ctx->pwm),
"Could not get PWM\n");
goto put_fan_node;
}
ctx->tach.pulses_per_revolution = 2;
ret = fwnode_property_read_u32(fan_node, "pulses-per-revolution",
&ctx->tach.pulses_per_revolution);
if (ret && ret != -EINVAL) {
ret = dev_err_probe(dev, ret,
"Failed to read pulses-per-revolution\n");
goto put_fan_node;
}
if (!ctx->tach.pulses_per_revolution) {
ret = dev_err_probe(dev, -EINVAL,
"pulses-per-revolution cannot be zero\n");
goto put_fan_node;
}
fwnode_handle_put(fan_node);
platform_set_drvdata(pdev, ctx);
pwm_init_state(ctx->pwm, &ctx->pwm_state);
if (!ctx->pwm_state.period)
return dev_err_probe(dev, -EINVAL, "PWM period is zero\n");
tach_clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(tach_clk))
return dev_err_probe(dev, PTR_ERR(tach_clk),
"Failed to get tachometer clock\n");
tach_clk_rate = clk_get_rate(tach_clk);
if (!tach_clk_rate || tach_clk_rate > U32_MAX)
return dev_err_probe(dev, -EINVAL,
"Invalid tachometer clock rate: %lu\n",
tach_clk_rate);
ctx->tach.timer_count = tach_clk_rate;
ctx->tach.irq = platform_get_irq(pdev, 0);
if (ctx->tach.irq < 0)
return dev_err_probe(dev, ctx->tach.irq,
"Failed to get tachometer IRQ\n");
ret = devm_request_irq(dev, ctx->tach.irq, axiado_tach_irq_handler, 0,
dev_name(dev), ctx);
if (ret)
return dev_err_probe(dev, ret, "Failed to request tach IRQ\n");
ret = devm_add_action_or_reset(dev, axiado_tach_disable, ctx);
if (ret)
return ret;
dev_dbg(dev, "Fan tachometer: irq=%d, pulses_per_revolution=%u\n",
ctx->tach.irq, ctx->tach.pulses_per_revolution);
axiado_tach_enable(ctx);
ret = axiado_set_pwm(ctx, AX_TACH_PWM_VAL_MAX);
if (ret)
return dev_err_probe(dev, ret, "Failed to configure PWM\n");
ret = devm_add_action_or_reset(dev, axiado_pwm_disable, ctx);
if (ret)
return dev_err_probe(dev, ret,
"Failed to add PWM disable action\n");
hwmon = devm_hwmon_device_register_with_info(dev, "axpwmfan", ctx,
&pwm_fan_chip_info, NULL);
if (IS_ERR(hwmon))
return dev_err_probe(dev, PTR_ERR(hwmon),
"Failed to register hwmon device\n");
return 0;
put_fan_node:
fwnode_handle_put(fan_node);
return ret;
}
static int axiado_pwm_fan_disable(struct device *dev)
{
struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
int ret;
ret = axiado_pwm_apply_disabled(ctx);
if (ret)
return ret;
axiado_tach_disable(ctx);
synchronize_irq(ctx->tach.irq);
return 0;
}
static void axiado_pwm_fan_shutdown(struct platform_device *pdev)
{
struct axiado_pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
/* Best effort during shutdown. */
axiado_pwm_apply_disabled(ctx);
axiado_tach_disable(ctx);
synchronize_irq(ctx->tach.irq);
}
static int axiado_pwm_fan_suspend(struct device *dev)
{
return axiado_pwm_fan_disable(dev);
}
static int axiado_pwm_fan_resume(struct device *dev)
{
struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
int ret;
axiado_tach_enable(ctx);
scoped_guard(mutex, &ctx->pwm_lock)
ret = pwm_apply_might_sleep(ctx->pwm, &ctx->pwm_state);
if (ret)
axiado_tach_disable(ctx);
return ret;
}
static DEFINE_SIMPLE_DEV_PM_OPS(axiado_pwm_fan_pm,
axiado_pwm_fan_suspend,
axiado_pwm_fan_resume);
static const struct of_device_id axiado_pwm_fan_match[] = {
{ .compatible = "axiado,ax3000-pwm-fan" },
{ }
};
MODULE_DEVICE_TABLE(of, axiado_pwm_fan_match);
static struct platform_driver axiado_pwm_fan_driver = {
.probe = axiado_pwm_fan_probe,
.shutdown = axiado_pwm_fan_shutdown,
.driver = {
.name = "axiado-pwm-fan",
.pm = pm_sleep_ptr(&axiado_pwm_fan_pm),
.of_match_table = axiado_pwm_fan_match,
},
};
module_platform_driver(axiado_pwm_fan_driver);
MODULE_AUTHOR("Axiado Corporation");
MODULE_DESCRIPTION("Axiado PWM fan controller driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2026 Axiado Corporation
*
* Driver for the Axiado on-chip temperature sensor (TSADC)
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
/* TSADC register offsets */
#define AXIADO_TSADC_MODE 0x0000
#define AXIADO_TSADC_FUNC_CTRL 0x0004
#define AXIADO_TSADC_AVG_DOUT 0x000c
#define AXIADO_TSADC_INTR_MASK 0x0010
#define AXIADO_TSADC_ANA_REG 0x0020
#define AXIADO_TSADC_SEL 0x0028
#define AXIADO_TSADC_TSEN_EN 0x002c
#define AXIADO_TSADC_EN 0x0030
/* TSADC mode register: continuous conversion */
#define AXIADO_TSADC_MODE_CONT 0x2
/*
* TSADC functional control register: bit 0 starts the conversion and
* bits [31:16] specify the number of samples. Average over 16 samples.
*/
#define AXIADO_TSADC_FUNC_CTRL_START BIT(0)
#define AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES GENMASK(31, 16)
#define AXIADO_TSADC_FUNC_CTRL_CLR 0x0
#define AXIADO_TSADC_FUNC_CTRL_SET \
(FIELD_PREP(AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES, 16) | \
AXIADO_TSADC_FUNC_CTRL_START)
/* TSADC average output register: 12-bit ADC result */
#define AXIADO_TSADC_AVG_DOUT_MASK GENMASK(11, 0)
/* Interrupt mask bits */
#define AXIADO_TSADC_INTR_BIST_DONE BIT(0)
#define AXIADO_TSADC_INTR_LOOP_DONE BIT(1)
#define AXIADO_TSADC_INTR_THRESHOLD BIT(2)
#define AXIADO_TSADC_INTR_MASK_ALL \
(AXIADO_TSADC_INTR_BIST_DONE | \
AXIADO_TSADC_INTR_LOOP_DONE | \
AXIADO_TSADC_INTR_THRESHOLD)
/* TSADC analog block configuration required for temperature measurement */
#define AXIADO_TSADC_ANA_REG_INIT 0x6
/* TSADC SEL: select temperature measurement */
#define AXIADO_TSADC_SEL_TEMP 0x00
/* TSADC EN / TSEN_EN field values */
#define AXIADO_TSADC_DIS 0x00
#define AXIADO_TSADC_ENA 0x01
struct axiado_tsadc {
void __iomem *regs;
};
/*
* Lookup table: raw ADC output (12-bit) vs temperature in millidegrees
* Celsius. Raw values decrease as temperature increases, from -40°C
* to 125°C in 5°C steps.
*/
struct axiado_tsadc_point {
u32 raw;
int temp_mc;
};
static const struct axiado_tsadc_point axiado_tsadc_table[] = {
{ .raw = 2776, .temp_mc = -40000 },
{ .raw = 2748, .temp_mc = -35000 },
{ .raw = 2721, .temp_mc = -30000 },
{ .raw = 2694, .temp_mc = -25000 },
{ .raw = 2667, .temp_mc = -20000 },
{ .raw = 2640, .temp_mc = -15000 },
{ .raw = 2612, .temp_mc = -10000 },
{ .raw = 2584, .temp_mc = -5000 },
{ .raw = 2556, .temp_mc = 0 },
{ .raw = 2528, .temp_mc = 5000 },
{ .raw = 2500, .temp_mc = 10000 },
{ .raw = 2472, .temp_mc = 15000 },
{ .raw = 2444, .temp_mc = 20000 },
{ .raw = 2416, .temp_mc = 25000 },
{ .raw = 2389, .temp_mc = 30000 },
{ .raw = 2362, .temp_mc = 35000 },
{ .raw = 2335, .temp_mc = 40000 },
{ .raw = 2307, .temp_mc = 45000 },
{ .raw = 2279, .temp_mc = 50000 },
{ .raw = 2251, .temp_mc = 55000 },
{ .raw = 2223, .temp_mc = 60000 },
{ .raw = 2195, .temp_mc = 65000 },
{ .raw = 2167, .temp_mc = 70000 },
{ .raw = 2138, .temp_mc = 75000 },
{ .raw = 2110, .temp_mc = 80000 },
{ .raw = 2081, .temp_mc = 85000 },
{ .raw = 2052, .temp_mc = 90000 },
{ .raw = 2024, .temp_mc = 95000 },
{ .raw = 1996, .temp_mc = 100000 },
{ .raw = 1967, .temp_mc = 105000 },
{ .raw = 1938, .temp_mc = 110000 },
{ .raw = 1909, .temp_mc = 115000 },
{ .raw = 1880, .temp_mc = 120000 },
{ .raw = 1852, .temp_mc = 125000 },
};
/* Convert raw ADC code to millidegrees Celsius with linear interpolation. */
static int axiado_tsadc_raw_to_mc(u32 raw, long *val)
{
int i, n = ARRAY_SIZE(axiado_tsadc_table);
long num, denom;
if (raw >= axiado_tsadc_table[0].raw) {
*val = axiado_tsadc_table[0].temp_mc;
return 0;
}
if (raw <= axiado_tsadc_table[n - 1].raw) {
*val = axiado_tsadc_table[n - 1].temp_mc;
return 0;
}
/* Find i such that axiado_tsadc_table[i].raw >= raw > axiado_tsadc_table[i+1].raw */
for (i = 0; i < n - 1; i++) {
if (raw >= axiado_tsadc_table[i + 1].raw)
break;
}
if (raw == axiado_tsadc_table[i].raw) {
*val = axiado_tsadc_table[i].temp_mc;
return 0;
}
/* Linear interpolation within the interval */
num = (long)(axiado_tsadc_table[i + 1].temp_mc - axiado_tsadc_table[i].temp_mc) *
(axiado_tsadc_table[i].raw - raw);
denom = axiado_tsadc_table[i].raw - axiado_tsadc_table[i + 1].raw;
*val = axiado_tsadc_table[i].temp_mc + num / denom;
return 0;
}
static int axiado_tsadc_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct axiado_tsadc *tsadc = dev_get_drvdata(dev);
u32 raw;
if (type != hwmon_temp)
return -EOPNOTSUPP;
switch (attr) {
case hwmon_temp_input:
if (ioread32(tsadc->regs + AXIADO_TSADC_SEL) != AXIADO_TSADC_SEL_TEMP ||
ioread32(tsadc->regs + AXIADO_TSADC_EN) != AXIADO_TSADC_ENA)
return -ENODATA;
raw = ioread32(tsadc->regs + AXIADO_TSADC_AVG_DOUT) &
AXIADO_TSADC_AVG_DOUT_MASK;
return axiado_tsadc_raw_to_mc(raw, val);
default:
return -EOPNOTSUPP;
}
}
static umode_t axiado_tsadc_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
if (type == hwmon_temp) {
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
break;
}
}
return 0;
}
static const struct hwmon_channel_info * const axiado_tsadc_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
NULL
};
static const struct hwmon_ops axiado_tsadc_hwmon_ops = {
.is_visible = axiado_tsadc_is_visible,
.read = axiado_tsadc_read,
};
static const struct hwmon_chip_info axiado_tsadc_chip_info = {
.ops = &axiado_tsadc_hwmon_ops,
.info = axiado_tsadc_info,
};
static void axiado_tsadc_disable(void *data)
{
struct axiado_tsadc *tsadc = data;
iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_TSEN_EN);
iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_EN);
}
static int axiado_tsadc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axiado_tsadc *tsadc;
struct device *hwmon_dev;
struct clk *refclk;
int ret;
tsadc = devm_kzalloc(dev, sizeof(*tsadc), GFP_KERNEL);
if (!tsadc)
return -ENOMEM;
tsadc->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(tsadc->regs))
return dev_err_probe(dev, PTR_ERR(tsadc->regs),
"failed to map registers\n");
refclk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(refclk))
return dev_err_probe(dev, PTR_ERR(refclk),
"failed to enable reference clock\n");
/* Mask all interrupts before setup */
iowrite32(AXIADO_TSADC_INTR_MASK_ALL, tsadc->regs + AXIADO_TSADC_INTR_MASK);
iowrite32(AXIADO_TSADC_ANA_REG_INIT, tsadc->regs + AXIADO_TSADC_ANA_REG);
iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_EN);
iowrite32(AXIADO_TSADC_SEL_TEMP, tsadc->regs + AXIADO_TSADC_SEL);
iowrite32(AXIADO_TSADC_MODE_CONT, tsadc->regs + AXIADO_TSADC_MODE);
iowrite32(AXIADO_TSADC_FUNC_CTRL_CLR, tsadc->regs + AXIADO_TSADC_FUNC_CTRL);
iowrite32(AXIADO_TSADC_FUNC_CTRL_SET, tsadc->regs + AXIADO_TSADC_FUNC_CTRL);
/* Enable the bandgap circuit once the block is fully configured */
iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_TSEN_EN);
ret = devm_add_action_or_reset(dev, axiado_tsadc_disable, tsadc);
if (ret)
return ret;
hwmon_dev = devm_hwmon_device_register_with_info(dev, "axiado_tsadc",
tsadc,
&axiado_tsadc_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct of_device_id axiado_tsadc_match[] = {
{ .compatible = "axiado,ax3000-tsadc" },
{ }
};
MODULE_DEVICE_TABLE(of, axiado_tsadc_match);
static struct platform_driver axiado_tsadc_driver = {
.driver = {
.name = "axiado-tsadc",
.of_match_table = axiado_tsadc_match,
},
.probe = axiado_tsadc_probe,
};
module_platform_driver(axiado_tsadc_driver);
MODULE_DESCRIPTION("Axiado TSADC temperature sensor driver");
MODULE_AUTHOR("Axiado Corporation");
MODULE_LICENSE("GPL");

View File

@ -60,6 +60,8 @@ enum coretemp_attr_index {
/*
* Per-Core Temperature Data
* @temp_valid: Whether the CPU reported @temp as valid. @temp is not
* reported when this is false.
* @tjmax: The static tjmax value when tjmax cannot be retrieved from
* IA32_TEMPERATURE_TARGET MSR.
* @last_updated: The time when the current temperature value was updated
@ -72,6 +74,7 @@ enum coretemp_attr_index {
*/
struct temp_data {
int temp;
bool temp_valid;
int tjmax;
unsigned long last_updated;
unsigned int cpu;
@ -398,26 +401,32 @@ static ssize_t show_temp(struct device *dev,
{
struct msr val;
struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TEMP]);
int tjmax;
int tjmax, temp;
bool valid;
mutex_lock(&tdata->update_lock);
tjmax = get_tjmax(tdata, dev);
/* Check whether the time interval has elapsed */
if (time_after(jiffies, tdata->last_updated + HZ)) {
tjmax = get_tjmax(tdata, dev);
rdmsrq_on_cpu(tdata->cpu, tdata->status_reg, &val.q);
/*
* Ignore the valid bit. In all observed cases the register
* value is either low or zero if the valid bit is 0.
* Return it instead of reporting an error which doesn't
* really help at all.
* Bit 31 indicates whether the reading is valid. Some CPUs
* clear it while the temperature is too low to be measured.
* Such a reading is not a temperature. Do not report it.
*/
tdata->temp_valid = (val.l >> 31) & 1;
tdata->temp = tjmax - ((val.l >> 16) & 0xff) * 1000;
tdata->last_updated = jiffies;
}
valid = tdata->temp_valid;
temp = tdata->temp;
mutex_unlock(&tdata->update_lock);
return sprintf(buf, "%d\n", tdata->temp);
if (!valid)
return -ENODATA;
return sprintf(buf, "%d\n", temp);
}
static int create_core_attrs(struct temp_data *tdata, struct device *dev)
@ -511,6 +520,11 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag)
tdata->cpu = cpu;
tdata->cpu_core_id = topology_core_id(cpu);
tdata->attr_size = MAX_CORE_ATTRS;
/*
* A zero timestamp does not look stale on 32-bit, where jiffies
* starts just short of wrapping. Backdate it instead.
*/
tdata->last_updated = jiffies - HZ - 1;
mutex_init(&tdata->update_lock);
return tdata;
}
@ -554,9 +568,6 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
u64 val;
int err;
if (!housekeeping_cpu(cpu, HK_TYPE_MISC))
return 0;
tdata = init_temp_data(pdata, cpu, pkg_flag);
if (!tdata)
return -ENOMEM;
@ -676,6 +687,14 @@ static int coretemp_cpu_online(unsigned int cpu)
if (!cpu_has(c, X86_FEATURE_DTHERM))
return -ENODEV;
/*
* Keep isolated CPUs out of pdata->cpumask. Besides tracking the CPUs
* available to this driver, the mask is used to select the CPU on which
* to read the temperature MSRs.
*/
if (!housekeeping_cpu(cpu, HK_TYPE_MISC))
return 0;
pdata = platform_get_drvdata(pdev);
if (!pdata->hwmon_dev) {
struct device *hwmon;
@ -705,8 +724,8 @@ static int coretemp_cpu_online(unsigned int cpu)
}
/*
* Check whether a thread sibling is already online. If not add the
* interface for this CPU core.
* Check whether a housekeeping thread sibling is already online. If
* not, add the interface for this CPU core.
*/
if (!cpumask_intersects(&pdata->cpumask, topology_sibling_cpumask(cpu)))
coretemp_add_core(pdev, cpu, 0);
@ -736,9 +755,9 @@ static int coretemp_cpu_offline(unsigned int cpu)
cpumask_clear_cpu(cpu, &pd->cpumask);
/*
* If this is the last thread sibling, remove the CPU core
* interface, If there is still a sibling online, transfer the
* target cpu of that core interface to it.
* If this is the last housekeeping thread sibling, remove the CPU core
* interface. If there is still a housekeeping sibling online, transfer
* the target CPU of that core interface to it.
*/
target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu));
if (target >= nr_cpu_ids) {
@ -750,7 +769,8 @@ static int coretemp_cpu_offline(unsigned int cpu)
}
/*
* If all cores in this pkg are offline, remove the interface.
* If all housekeeping cores in this pkg are offline, remove the
* interface.
*/
tdata = get_temp_data(pd, -1);
if (cpumask_empty(&pd->cpumask)) {
@ -763,7 +783,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
/*
* Check whether this core is the target for the package
* interface. We need to assign it to some other cpu.
* interface. We need to assign it to some other housekeeping CPU.
*/
if (tdata && tdata->cpu == cpu) {
target = cpumask_first(&pd->cpumask);

View File

@ -135,9 +135,9 @@ static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2,
* the raw event parsing and marked the ccp->wait_input_report
* completion as done.
*/
spin_lock_bh(&ccp->wait_input_report_lock);
spin_lock_irq(&ccp->wait_input_report_lock);
reinit_completion(&ccp->wait_input_report);
spin_unlock_bh(&ccp->wait_input_report_lock);
spin_unlock_irq(&ccp->wait_input_report_lock);
ret = hid_hw_output_report(ccp->hdev, ccp->cmd_buffer, OUT_BUFFER_SIZE);
if (ret < 0)
@ -156,15 +156,16 @@ static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2,
static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
{
struct ccp_device *ccp = hid_get_drvdata(hdev);
unsigned long flags;
/* only copy buffer when requested */
spin_lock(&ccp->wait_input_report_lock);
spin_lock_irqsave(&ccp->wait_input_report_lock, flags);
if (!completion_done(&ccp->wait_input_report)) {
memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
ccp->buffer_recv_size = size;
complete_all(&ccp->wait_input_report);
}
spin_unlock(&ccp->wait_input_report_lock);
spin_unlock_irqrestore(&ccp->wait_input_report_lock, flags);
return 0;
}

View File

@ -1543,6 +1543,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
{
.ident = "Dell Latitude 5420",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5420"),
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{
.ident = "Dell Latitude 5480",
.matches = {
@ -1631,6 +1639,22 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
{
.ident = "Dell Precision 3650 Tower",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 3650 Tower"),
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{
.ident = "Dell Optiplex 7090",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7090"),
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{
.ident = "Dell XPS 9315",
.matches = {
@ -1655,6 +1679,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{
.ident = "Dell 16 DC16250",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell 16 DC16250"),
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{ }
};

View File

@ -32,7 +32,7 @@ static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 };
#define REG_PRODUCT_ID 0xfd
#define REG_MFG_ID 0xfe
/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */
/* equation 4 from datasheet: rpm = (3932160 * multiplier) / count */
#define FAN_RPM_FACTOR 3932160
/*

View File

@ -35,6 +35,7 @@
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/kref.h>
#include <linux/stringify.h>
/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
@ -43,7 +44,7 @@ static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
__stringify(WATCHDOG_NOWAYOUT) ")");
enum chips { fscpos, fscher, fscscy, fschrc, fschmd, fschds, fscsyl };

View File

@ -61,6 +61,7 @@ enum WATCHDOG_RESOLUTION {
struct fts_data {
struct i2c_client *client;
struct device *hwmon_dev;
unsigned long last_updated; /* in jiffies */
struct watchdog_device wdd;
enum WATCHDOG_RESOLUTION resolution;
@ -240,6 +241,8 @@ static int fts_wd_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
resolution = minutes;
}
guard(hwmon_lock)(data->hwmon_dev);
ret = fts_wd_set_resolution(data, resolution);
if (ret < 0)
return ret;
@ -252,6 +255,8 @@ static int fts_wd_start(struct watchdog_device *wdd)
{
struct fts_data *data = watchdog_get_drvdata(wdd);
guard(hwmon_lock)(data->hwmon_dev);
return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET,
wdd->timeout / (u8)data->resolution);
}
@ -261,6 +266,9 @@ static int fts_wd_stop(struct watchdog_device *wdd)
struct fts_data *data;
data = watchdog_get_drvdata(wdd);
guard(hwmon_lock)(data->hwmon_dev);
return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, 0);
}
@ -276,11 +284,11 @@ static const struct watchdog_ops fts_wd_ops = {
.set_timeout = fts_wd_set_timeout,
};
static int fts_watchdog_init(struct fts_data *data)
static int fts_watchdog_read_state(struct fts_data *data)
{
int timeout, ret;
watchdog_set_drvdata(&data->wdd, data);
guard(hwmon_lock)(data->hwmon_dev);
timeout = fts_read_byte(data->client, FTS_WATCHDOG_TIME_PRESET);
if (timeout < 0)
@ -302,6 +310,19 @@ static int fts_watchdog_init(struct fts_data *data)
set_bit(WDOG_HW_RUNNING, &data->wdd.status);
}
return 0;
}
static int fts_watchdog_init(struct fts_data *data)
{
int ret;
watchdog_set_drvdata(&data->wdd, data);
ret = fts_watchdog_read_state(data);
if (ret < 0)
return ret;
/* Register our watchdog part */
data->wdd.info = &fts_wd_info;
data->wdd.ops = &fts_wd_ops;
@ -629,6 +650,9 @@ static int fts_probe(struct i2c_client *client)
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
/* The watchdog code takes its lock */
data->hwmon_dev = hwmon_dev;
err = fts_watchdog_init(data);
if (err)
return err;

View File

@ -257,6 +257,9 @@ static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *repo
{
struct waterforce_data *priv = hid_get_drvdata(hdev);
if (size <= FIRMWARE_VER_START_OFFSET_2)
return 0;
if (data[0] == get_firmware_ver_cmd[0] && data[1] == get_firmware_ver_cmd[1]) {
/* Received a firmware version report */
priv->firmware_version =
@ -269,6 +272,8 @@ static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *repo
if (data[0] != get_status_cmd[0] || data[1] != get_status_cmd[1])
return 0;
if (size <= WATERFORCE_TEMP_SENSOR)
return 0;
priv->temp_input[0] = data[WATERFORCE_TEMP_SENSOR] * 1000;
priv->speed_input[0] = get_unaligned_le16(data + WATERFORCE_FAN_SPEED);

View File

@ -168,7 +168,7 @@ static ssize_t hih6130_temperature_show(struct device *dev,
ret = hih6130_update_measurements(dev);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", hih6130->temperature);
return sysfs_emit(buf, "%d\n", hih6130->temperature);
}
/**
@ -189,7 +189,7 @@ static ssize_t hih6130_humidity_show(struct device *dev,
ret = hih6130_update_measurements(dev);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", hih6130->humidity);
return sysfs_emit(buf, "%d\n", hih6130->humidity);
}
/* sysfs attributes */

178
drivers/hwmon/honor-fmi.c Normal file
View File

@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Read-only fan monitoring for the HONOR FMI-XX.
*
* The firmware-provided \GFNS ACPI method accepts a three-byte buffer.
* Byte 2 selects fan 0 or 1. It returns a status byte followed by a
* little-endian 16-bit fan speed in RPM. The method owns all Embedded
* Controller access and serialization; this driver deliberately exposes no
* fan control interface.
*/
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define HONOR_FMI_GFNS_RESULT_SIZE 3
struct honor_fmi_data {
acpi_handle gfns;
};
static const struct dmi_system_id honor_fmi_dmi_table[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HONOR"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FMI-XX"),
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, honor_fmi_dmi_table);
static int honor_fmi_read_rpm(struct honor_fmi_data *data, int channel,
long *rpm)
{
union acpi_object input = {
.buffer = {
.type = ACPI_TYPE_BUFFER,
.length = 3,
},
};
struct acpi_object_list arguments = {
.count = 1,
.pointer = &input,
};
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *result;
u8 input_bytes[3] = { 0, 0, channel };
acpi_status status;
int ret = 0;
input.buffer.pointer = input_bytes;
status = acpi_evaluate_object(data->gfns, NULL, &arguments, &output);
if (ACPI_FAILURE(status))
return -EIO;
result = output.pointer;
if (!result || result->type != ACPI_TYPE_BUFFER ||
result->buffer.length < HONOR_FMI_GFNS_RESULT_SIZE) {
ret = -EPROTO;
goto out_free;
}
if (result->buffer.pointer[0]) {
ret = -EIO;
goto out_free;
}
*rpm = result->buffer.pointer[1] |
(result->buffer.pointer[2] << 8);
out_free:
kfree(output.pointer);
return ret;
}
static umode_t honor_fmi_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
return 0444;
}
static int honor_fmi_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *value)
{
struct honor_fmi_data *data = dev_get_drvdata(dev);
return honor_fmi_read_rpm(data, channel, value);
}
static const struct hwmon_ops honor_fmi_hwmon_ops = {
.is_visible = honor_fmi_is_visible,
.read = honor_fmi_read,
};
static const struct hwmon_channel_info * const honor_fmi_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
NULL
};
static const struct hwmon_chip_info honor_fmi_chip_info = {
.ops = &honor_fmi_hwmon_ops,
.info = honor_fmi_hwmon_info,
};
static int honor_fmi_probe(struct platform_device *pdev)
{
struct honor_fmi_data *data;
struct device *hwmon_dev;
acpi_status status;
if (!dmi_check_system(honor_fmi_dmi_table))
return -ENODEV;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
status = acpi_get_handle(NULL, "\\GFNS", &data->gfns);
if (ACPI_FAILURE(status))
return dev_err_probe(&pdev->dev, -ENODEV,
"firmware does not provide \\GFNS\n");
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "honor_fmi",
data,
&honor_fmi_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static struct platform_driver honor_fmi_driver = {
.probe = honor_fmi_probe,
.driver = {
.name = "honor-fmi-hwmon",
},
};
static struct platform_device *honor_fmi_device;
static int __init honor_fmi_init(void)
{
int ret;
if (!dmi_check_system(honor_fmi_dmi_table))
return -ENODEV;
ret = platform_driver_register(&honor_fmi_driver);
if (ret)
return ret;
honor_fmi_device = platform_device_register_simple("honor-fmi-hwmon",
PLATFORM_DEVID_NONE,
NULL, 0);
if (IS_ERR(honor_fmi_device)) {
platform_driver_unregister(&honor_fmi_driver);
return PTR_ERR(honor_fmi_device);
}
return 0;
}
static void __exit honor_fmi_exit(void)
{
platform_device_unregister(honor_fmi_device);
platform_driver_unregister(&honor_fmi_driver);
}
module_init(honor_fmi_init);
module_exit(honor_fmi_exit);
MODULE_AUTHOR("Nikita Dubrovskih <testname142@gmail.com>");
MODULE_DESCRIPTION("HONOR FMI-XX fan speed monitor");
MODULE_LICENSE("GPL");

View File

@ -1804,7 +1804,7 @@ static int init_numeric_sensors(struct hp_wmi_sensors *state,
if (!info_map[type]) {
info_map[type] = devm_kcalloc(dev, channel_count[type],
sizeof(*info_map),
sizeof(*info_map[type]),
GFP_KERNEL);
if (!info_map[type])
return -ENOMEM;

View File

@ -625,6 +625,8 @@ static const char * const hwmon_in_attr_templates[] = {
[hwmon_in_max] = "in%d_max",
[hwmon_in_lcrit] = "in%d_lcrit",
[hwmon_in_crit] = "in%d_crit",
[hwmon_in_lemergency] = "in%d_lemergency",
[hwmon_in_emergency] = "in%d_emergency",
[hwmon_in_average] = "in%d_average",
[hwmon_in_lowest] = "in%d_lowest",
[hwmon_in_highest] = "in%d_highest",
@ -635,6 +637,8 @@ static const char * const hwmon_in_attr_templates[] = {
[hwmon_in_max_alarm] = "in%d_max_alarm",
[hwmon_in_lcrit_alarm] = "in%d_lcrit_alarm",
[hwmon_in_crit_alarm] = "in%d_crit_alarm",
[hwmon_in_lemergency_alarm] = "in%d_lemergency_alarm",
[hwmon_in_emergency_alarm] = "in%d_emergency_alarm",
[hwmon_in_rated_min] = "in%d_rated_min",
[hwmon_in_rated_max] = "in%d_rated_max",
[hwmon_in_beep] = "in%d_beep",
@ -648,6 +652,7 @@ static const char * const hwmon_curr_attr_templates[] = {
[hwmon_curr_max] = "curr%d_max",
[hwmon_curr_lcrit] = "curr%d_lcrit",
[hwmon_curr_crit] = "curr%d_crit",
[hwmon_curr_emergency] = "curr%d_emergency",
[hwmon_curr_average] = "curr%d_average",
[hwmon_curr_lowest] = "curr%d_lowest",
[hwmon_curr_highest] = "curr%d_highest",
@ -658,6 +663,7 @@ static const char * const hwmon_curr_attr_templates[] = {
[hwmon_curr_max_alarm] = "curr%d_max_alarm",
[hwmon_curr_lcrit_alarm] = "curr%d_lcrit_alarm",
[hwmon_curr_crit_alarm] = "curr%d_crit_alarm",
[hwmon_curr_emergency_alarm] = "curr%d_emergency_alarm",
[hwmon_curr_rated_min] = "curr%d_rated_min",
[hwmon_curr_rated_max] = "curr%d_rated_max",
[hwmon_curr_beep] = "curr%d_beep",

View File

@ -36,6 +36,7 @@
* IT8790E Super I/O chip w/LPC interface
* IT8792E Super I/O chip w/LPC interface
* IT87952E Super I/O chip w/LPC interface
* IT8613E Super I/O chip w/LPC interface
* Sis950 A clone of the IT8705F
*
* Copyright (C) 2001 Chris Gauthron
@ -65,7 +66,7 @@
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
it8771, it8772, it8781, it8782, it8783, it8786, it8790,
it8792, it8603, it8620, it8622, it8628, it8689, it87952 };
it8792, it8603, it8613, it8620, it8622, it8628, it8689, it87952 };
static struct platform_device *it87_pdev[2];
@ -159,6 +160,7 @@ static inline void superio_exit(int ioreg, bool noexit)
#define IT8786E_DEVID 0x8786
#define IT8790E_DEVID 0x8790
#define IT8603E_DEVID 0x8603
#define IT8613E_DEVID 0x8613
#define IT8620E_DEVID 0x8620
#define IT8622E_DEVID 0x8622
#define IT8623E_DEVID 0x8623
@ -252,6 +254,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf };
static const u8 IT87_REG_PWM_8665[] = { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 };
static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
@ -283,6 +286,7 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_TEMP 6
#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
#define NUM_TEMP_LIMIT 3
#define IT87_PWM_OLD_NUM_TEMP 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@ -292,6 +296,8 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
const u8 *reg_pwm;
u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
u8 smbus_bitmap; /* SMBus enable bits in extra config register */
@ -328,6 +334,8 @@ struct it87_devices {
#define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
#define FEAT_11MV_ADC BIT(25)
static const struct it87_devices it87_devices[] = {
[it87] = {
@ -335,12 +343,16 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8712] = {
.name = "it8712",
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8716] = {
.name = "it8716",
@ -348,6 +360,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8718] = {
.name = "it8718",
@ -355,6 +369,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8720] = {
@ -363,6 +379,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8721] = {
@ -372,6 +390,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x05,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@ -382,6 +402,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8732] = {
@ -391,6 +413,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@ -404,6 +428,8 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8772] = {
@ -416,6 +442,8 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8781] = {
@ -424,6 +452,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8782] = {
@ -432,6 +462,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8783] = {
@ -440,6 +472,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8786] = {
@ -448,6 +482,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8790] = {
@ -456,6 +492,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8792] = {
@ -465,6 +503,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@ -474,6 +514,20 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 4,
.peci_mask = 0x07,
},
[it8613] = {
.name = "it8613",
.model = "IT8613E",
/* Only three temperature inputs are currently known. */
.features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_NEW_TEMPMAP,
.reg_pwm = IT87_REG_PWM_8665,
.num_temp_map = 6,
.peci_mask = 0x07,
},
[it8620] = {
@ -483,6 +537,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8622] = {
@ -492,6 +548,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
.reg_pwm = IT87_REG_PWM_8665,
.num_temp_map = 4,
.peci_mask = 0x07,
.smbus_bitmap = BIT(1) | BIT(2),
},
@ -502,6 +560,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
[it8689] = {
@ -511,6 +571,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it87952] = {
@ -520,6 +582,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
.reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@ -528,6 +592,7 @@ static const struct it87_devices it87_devices[] = {
#define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS)
#define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC)
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
#define has_11mv_adc(data) ((data)->features & FEAT_11MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
#define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET)
@ -558,8 +623,10 @@ static const struct it87_devices it87_devices[] = {
#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V)
#define has_noconf(data) ((data)->features & FEAT_NOCONF)
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
FEAT_10_9MV_ADC))
FEAT_10_9MV_ADC | \
FEAT_11MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
struct it87_sio_data {
int sioaddr;
@ -589,6 +656,8 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
const u8 *reg_pwm;
u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@ -635,7 +704,9 @@ struct it87_data {
u8 has_pwm; /* Bitfield, pwm control enabled */
u8 pwm_ctrl[NUM_PWM]; /* Register value */
u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */
u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */
u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping */
u8 pwm_temp_map_mask;
u8 pwm_temp_map_shift;
/* Automatic fan speed control registers */
u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */
@ -658,6 +729,8 @@ static int adc_lsb(const struct it87_data *data, int nr)
lsb = 120;
else if (has_10_9mv_adc(data))
lsb = 109;
else if (has_11mv_adc(data))
lsb = 110;
else
lsb = 160;
if (data->in_scaled & BIT(nr))
@ -717,6 +790,77 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg)
return (reg & 0x7f) << 1;
}
static inline u8 pwm_temp_map_get(const struct it87_data *data, u8 ctrl)
{
return (ctrl >> data->pwm_temp_map_shift) &
data->pwm_temp_map_mask;
}
static inline u8 pwm_temp_map_set(const struct it87_data *data, u8 ctrl,
u8 map)
{
ctrl &= ~(data->pwm_temp_map_mask << data->pwm_temp_map_shift);
return ctrl | ((map & data->pwm_temp_map_mask)
<< data->pwm_temp_map_shift);
}
static inline u8 pwm_num_temp_map(const struct it87_data *data)
{
return data->num_temp_map;
}
static inline bool uses_global_temp_map(const struct it87_data *data)
{
return has_new_tempmap(data) ||
pwm_num_temp_map(data) != IT87_PWM_OLD_NUM_TEMP;
}
static unsigned int pwm_temp_channel(const struct it87_data *data,
int nr, u8 map)
{
if (uses_global_temp_map(data)) {
u8 num = pwm_num_temp_map(data);
if (map >= num)
map = 0;
return map;
}
if (map >= IT87_PWM_OLD_NUM_TEMP)
map = 0;
if (nr >= IT87_PWM_OLD_NUM_TEMP)
map += IT87_PWM_OLD_NUM_TEMP;
return map;
}
static int pwm_temp_map_from_channel(const struct it87_data *data, int nr,
unsigned int channel, u8 *map)
{
if (uses_global_temp_map(data)) {
u8 num = pwm_num_temp_map(data);
if (channel >= num)
return -EINVAL;
*map = channel;
return 0;
}
if (nr >= IT87_PWM_OLD_NUM_TEMP) {
if (channel < IT87_PWM_OLD_NUM_TEMP ||
channel >= 2 * IT87_PWM_OLD_NUM_TEMP)
return -EINVAL;
channel -= IT87_PWM_OLD_NUM_TEMP;
} else {
if (channel >= IT87_PWM_OLD_NUM_TEMP)
return -EINVAL;
}
*map = channel;
return 0;
}
static int DIV_TO_REG(int val)
{
int answer = 0;
@ -728,6 +872,11 @@ static int DIV_TO_REG(int val)
#define DIV_FROM_REG(val) BIT(val)
static inline u16 it87_reg_pwm(const struct it87_data *data, int nr)
{
return data->reg_pwm[nr];
}
/*
* PWM base frequencies. The frequency has to be divided by either 128 or 256,
* depending on the chip type, to calculate the actual PWM frequency.
@ -808,16 +957,23 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
{
data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr));
if (has_newer_autopwm(data)) {
data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
data->pwm_temp_map[nr] =
pwm_temp_map_get(data, data->pwm_ctrl[nr]);
if (uses_global_temp_map(data) &&
data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
data->pwm_temp_map[nr] = 0;
data->pwm_duty[nr] = it87_read_value(data,
IT87_REG_PWM_DUTY[nr]);
} else {
if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */
data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
else /* Manual mode */
data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
} else if (data->pwm_ctrl[nr] & 0x80) { /* Automatic mode */
data->pwm_temp_map[nr] =
pwm_temp_map_get(data, data->pwm_ctrl[nr]);
if (uses_global_temp_map(data) &&
data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
data->pwm_temp_map[nr] = 0;
} else { /* Manual mode */
data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
}
if (has_old_autopwm(data)) {
@ -1567,27 +1723,32 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
data->pwm_duty[nr]);
/* and set manual mode */
if (has_newer_autopwm(data)) {
ctrl = (data->pwm_ctrl[nr] & 0x7c) |
data->pwm_temp_map[nr];
ctrl = pwm_temp_map_set(data,
data->pwm_ctrl[nr] &
~0x80,
data->pwm_temp_map[nr]);
} else {
ctrl = data->pwm_duty[nr];
}
data->pwm_ctrl[nr] = ctrl;
it87_write_value(data, IT87_REG_PWM[nr], ctrl);
it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
}
} else {
u8 ctrl;
if (has_newer_autopwm(data)) {
ctrl = (data->pwm_ctrl[nr] & 0x7c) |
data->pwm_temp_map[nr];
ctrl = pwm_temp_map_set(data,
data->pwm_ctrl[nr] & ~0x80,
data->pwm_temp_map[nr]);
if (val != 1)
ctrl |= 0x80;
} else {
ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80);
ctrl = val == 1 ? data->pwm_duty[nr] :
pwm_temp_map_set(data, 0x80,
data->pwm_temp_map[nr]);
}
data->pwm_ctrl[nr] = ctrl;
it87_write_value(data, IT87_REG_PWM[nr], ctrl);
it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
if (has_fanctl_onoff(data) && nr < 3) {
/* set SmartGuardian mode */
@ -1638,7 +1799,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
*/
if (!(data->pwm_ctrl[nr] & 0x80)) {
data->pwm_ctrl[nr] = data->pwm_duty[nr];
it87_write_value(data, IT87_REG_PWM[nr],
it87_write_value(data, it87_reg_pwm(data, nr),
data->pwm_ctrl[nr]);
}
}
@ -1693,18 +1854,14 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
int map;
unsigned int channel;
if (IS_ERR(data))
return PTR_ERR(data);
map = data->pwm_temp_map[nr];
if (map >= 3)
map = 0; /* Should never happen */
if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */
map += 3;
channel = pwm_temp_channel(data, nr, data->pwm_temp_map[nr]);
return sprintf(buf, "%d\n", (int)BIT(map));
return sprintf(buf, "%d\n", (int)BIT(channel));
}
static ssize_t set_pwm_temp_map(struct device *dev,
@ -1716,42 +1873,33 @@ static ssize_t set_pwm_temp_map(struct device *dev,
int nr = sensor_attr->index;
long val;
int err;
u8 reg;
unsigned int channel;
u8 map;
if (kstrtol(buf, 10, &val) < 0)
if (kstrtol(buf, 10, &val) < 0 || val <= 0 || !is_power_of_2(val))
return -EINVAL;
if (nr >= 3)
val -= 3;
switch (val) {
case BIT(0):
reg = 0x00;
break;
case BIT(1):
reg = 0x01;
break;
case BIT(2):
reg = 0x02;
break;
default:
channel = __ffs(val);
if (pwm_temp_map_from_channel(data, nr, channel, &map))
return -EINVAL;
}
err = it87_lock(data);
if (err)
return err;
it87_update_pwm_ctrl(data, nr);
data->pwm_temp_map[nr] = reg;
data->pwm_temp_map[nr] = map;
/*
* If we are in automatic mode, write the temp mapping immediately;
* otherwise, just store it for later use.
* Newer controllers keep the duty cycle in a separate register, so
* their temperature mapping can be updated in any mode. On older
* controllers, defer the update until automatic mode is enabled.
*/
if (data->pwm_ctrl[nr] & 0x80) {
data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) |
data->pwm_temp_map[nr];
it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]);
if (has_newer_autopwm(data) || (data->pwm_ctrl[nr] & 0x80)) {
data->pwm_ctrl[nr] = pwm_temp_map_set(data,
data->pwm_ctrl[nr],
data->pwm_temp_map[nr]);
it87_write_value(data, it87_reg_pwm(data, nr),
data->pwm_ctrl[nr]);
}
it87_unlock(data);
return count;
@ -2790,6 +2938,9 @@ static int __init it87_find(int sioaddr, unsigned short *address,
case IT8623E_DEVID:
sio_data->type = it8603;
break;
case IT8613E_DEVID:
sio_data->type = it8613;
break;
case IT8620E_DEVID:
sio_data->type = it8620;
break;
@ -2967,6 +3118,43 @@ static int __init it87_find(int sioaddr, unsigned short *address,
sio_data->skip_in |= BIT(5); /* No VIN5 */
sio_data->skip_in |= BIT(6); /* No VIN6 */
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8613) {
int reg27, reg29, reg2a;
superio_select(sioaddr, GPIO);
/* Check for pwm3, fan3, pwm5, fan5 */
reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG);
if (!(reg27 & BIT(1)))
sio_data->skip_fan |= BIT(4);
if (reg27 & BIT(3))
sio_data->skip_pwm |= BIT(4);
if (reg27 & BIT(6))
sio_data->skip_pwm |= BIT(2);
if (reg27 & BIT(7))
sio_data->skip_fan |= BIT(2);
/* Check for pwm2, fan2 */
reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG);
if (reg29 & BIT(1))
sio_data->skip_pwm |= BIT(1);
if (reg29 & BIT(2))
sio_data->skip_fan |= BIT(1);
/* Check for pwm4, fan4 */
reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG);
if (!(reg2a & BIT(0)) || (reg29 & BIT(7))) {
sio_data->skip_fan |= BIT(3);
sio_data->skip_pwm |= BIT(3);
}
sio_data->skip_pwm |= BIT(0); /* No pwm1 */
sio_data->skip_fan |= BIT(0); /* No fan1 */
sio_data->skip_in |= BIT(3); /* No VIN3 */
sio_data->skip_in |= BIT(6); /* No VIN6 */
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8620 || sio_data->type == it8628) {
@ -3350,7 +3538,10 @@ static void it87_init_device(struct platform_device *pdev)
* manual duty cycle.
*/
for (i = 0; i < NUM_AUTO_PWM; i++) {
data->pwm_temp_map[i] = i;
if (uses_global_temp_map(data))
data->pwm_temp_map[i] = 0;
else
data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP;
data->pwm_duty[i] = 0x7f; /* Full speed */
data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
}
@ -3461,6 +3652,7 @@ static int it87_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
struct it87_sio_data *sio_data = dev_get_platdata(dev);
const struct it87_devices *chip;
int enable_pwm_interface;
struct device *hwmon_dev;
int err;
@ -3483,9 +3675,19 @@ static int it87_probe(struct platform_device *pdev)
data->type = sio_data->type;
data->smbus_bitmap = sio_data->smbus_bitmap;
data->ec_special_config = sio_data->ec_special_config;
data->features = it87_devices[sio_data->type].features;
data->peci_mask = it87_devices[sio_data->type].peci_mask;
data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask;
chip = &it87_devices[sio_data->type];
data->features = chip->features;
data->reg_pwm = chip->reg_pwm;
data->peci_mask = chip->peci_mask;
data->old_peci_mask = chip->old_peci_mask;
data->num_temp_map = chip->num_temp_map;
if (has_new_tempmap(data)) {
data->pwm_temp_map_mask = 0x07;
data->pwm_temp_map_shift = 3;
} else {
data->pwm_temp_map_mask = 0x03;
data->pwm_temp_map_shift = 0;
}
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.

View File

@ -64,7 +64,7 @@ MODULE_PARM_DESC(force, "force loading on processors with erratum 319");
#define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64
#define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4
/* Common for Zen CPU families (Family 17h and 18h and 19h and 1Ah) */
/* Common for Zen CPU families (Family 17h, 19h and 1Ah) */
#define ZEN_REPORTED_TEMP_CTRL_BASE 0x00059800
#define ZEN_CCD_TEMP(offset, x) (ZEN_REPORTED_TEMP_CTRL_BASE + \
@ -94,6 +94,7 @@ MODULE_PARM_DESC(force, "force loading on processors with erratum 319");
* Defining locally as IDs are not shared.
*/
#define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb
#define PCI_DEVICE_ID_AMD_1AH_M80H_DF_F3 0x1243
#define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b
struct k10temp_data {
@ -475,7 +476,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
data->read_tempreg = read_tempreg_pci;
}
if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
if (boot_cpu_data.x86 == 0x17) {
switch (boot_cpu_data.x86_model) {
case 0x1: /* Zen */
case 0x8: /* Zen+ */
@ -582,8 +583,8 @@ static const struct pci_device_id k10temp_id_table[] = {
{ 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_M80H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) },
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{}
};
MODULE_DEVICE_TABLE(pci, k10temp_id_table);

View File

@ -35,13 +35,14 @@
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/sysfs.h>
#include <linux/thermal.h>
#include <linux/types.h>
/*
* Addresses to scan
* Address is fully defined internally and cannot be changed except for
* LM64 which has one pin dedicated to address selection.
* LM63 and LM96163 have address 0x4c.
* LM63, LM96163, and CTF2301 have address 0x4c.
* LM64 can have address 0x18 or 0x4e.
*/
@ -70,6 +71,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END };
#define LM63_REG_LOCAL_TEMP 0x00
#define LM63_REG_LOCAL_HIGH 0x05
#define CTF2301_REG_LOCAL_TEMP_LSB 0x15
#define CTF2301_REG_LOCAL_HIGH_LSB 0x06
#define LM63_REG_REMOTE_TEMP_MSB 0x01
#define LM63_REG_REMOTE_TEMP_LSB 0x10
#define LM63_REG_REMOTE_OFFSET_MSB 0x11
@ -94,6 +98,7 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END };
#define LM63_PWM_BASE_FAST_HZ 180000
#define LM63_PWM_BASE_SLOW_HZ 700
#define LM63_MAX_PWM 255
#define LM63_MAX_CONVRATE 9
@ -121,6 +126,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END };
127000), 1000)
#define TEMP8U_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, \
255000), 1000)
#define TEMP12_FROM_REG(reg) DIV_ROUND_CLOSEST((s16)(reg) * 1000, 256)
#define TEMP12_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \
127937) * 16, 1000) * 16)
#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125)
#define TEMP11_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \
127875), 125) * 32)
@ -132,7 +140,11 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END };
#define UPDATE_INTERVAL(max, rate) \
((1000 << (LM63_MAX_CONVRATE - (rate))) / (max))
enum chips { lm63, lm64, lm96163 };
enum chips { ctf2301, lm63, lm64, lm96163 };
static const unsigned int ctf2301_update_intervals[] = {
20000, 10000, 4902, 2463, 1000, 615, 307, 154, 107
};
/*
* Client data (each client gets its own)
@ -164,6 +176,8 @@ struct lm63_data {
1: local high limit
2: remote critical limit
3-14: lookup table */
s16 temp1_input; /* local input for chips with extended resolution */
s16 temp1_max; /* local limit for chips with extended resolution */
s16 temp11[4]; /* 0: remote input
1: remote low limit
2: remote high limit
@ -229,6 +243,7 @@ static struct lm63_data *lm63_update_device(struct device *dev)
struct lm63_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
unsigned long next_update;
int msb, lsb;
mutex_lock(&data->update_lock);
@ -253,10 +268,26 @@ static struct lm63_data *lm63_update_device(struct device *dev)
data->pwm1[0] = i2c_smbus_read_byte_data(client,
LM63_REG_PWM_VALUE);
data->temp8[0] = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_TEMP);
data->temp8[1] = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_HIGH);
if (data->kind == ctf2301) {
msb = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_TEMP);
lsb = i2c_smbus_read_byte_data(client,
CTF2301_REG_LOCAL_TEMP_LSB);
if (msb >= 0 && lsb >= 0)
data->temp1_input = ((u16)(u8)msb << 8) | lsb;
msb = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_HIGH);
lsb = i2c_smbus_read_byte_data(client,
CTF2301_REG_LOCAL_HIGH_LSB);
if (msb >= 0 && lsb >= 0)
data->temp1_max = ((u16)(u8)msb << 8) | lsb;
} else {
data->temp8[0] = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_TEMP);
data->temp8[1] = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_HIGH);
}
/* order matters for temp2_input */
data->temp11[0] = i2c_smbus_read_byte_data(client,
@ -276,7 +307,7 @@ static struct lm63_data *lm63_update_device(struct device *dev)
| i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_OFFSET_LSB);
if (data->kind == lm96163)
if (data->kind == lm96163 || data->kind == ctf2301)
data->temp11u = (i2c_smbus_read_byte_data(client,
LM96163_REG_REMOTE_TEMP_U_MSB) << 8)
| i2c_smbus_read_byte_data(client,
@ -366,6 +397,49 @@ static ssize_t set_fan(struct device *dev, struct device_attribute *dummy,
return count;
}
static int lm63_pwm_from_reg(struct lm63_data *data, u8 reg)
{
if (data->pwm_highres)
return reg;
return reg >= 2 * data->pwm1_freq ?
LM63_MAX_PWM :
(reg * LM63_MAX_PWM + data->pwm1_freq) / (2 * data->pwm1_freq);
}
static u8 lm63_pwm_to_reg(struct lm63_data *data, unsigned long val)
{
val = clamp_val(val, 0, LM63_MAX_PWM);
if (data->pwm_highres)
return val;
return (val * data->pwm1_freq * 2 + LM63_MAX_PWM / 2) / LM63_MAX_PWM;
}
static void lm63_update_pwm_highres(struct lm63_data *data,
int config_enhanced)
{
data->pwm_highres = !(data->config_fan & 0x08) &&
data->pwm1_freq == 8 &&
(config_enhanced & 0x10);
}
static int lm63_write_pwm(struct lm63_data *data, int nr, unsigned long val)
{
struct i2c_client *client = data->client;
u8 reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE;
u8 regval = lm63_pwm_to_reg(data, val);
int ret;
ret = i2c_smbus_write_byte_data(client, reg, regval);
if (ret)
return ret;
data->pwm1[nr] = regval;
return 0;
}
static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr,
char *buf)
{
@ -375,12 +449,7 @@ static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr,
int pwm;
mutex_lock(&data->update_lock);
if (data->pwm_highres)
pwm = data->pwm1[nr];
else
pwm = data->pwm1[nr] >= 2 * data->pwm1_freq ?
255 : (data->pwm1[nr] * 255 + data->pwm1_freq) /
(2 * data->pwm1_freq);
pwm = lm63_pwm_from_reg(data, data->pwm1[nr]);
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", pwm);
@ -391,11 +460,9 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm63_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int nr = attr->index;
unsigned long val;
int err;
u8 reg;
if (!(data->config_fan & 0x20)) /* register is read-only */
return -EPERM;
@ -404,15 +471,11 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr,
if (err)
return err;
reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE;
val = clamp_val(val, 0, 255);
mutex_lock(&data->update_lock);
data->pwm1[nr] = data->pwm_highres ? val :
(val * data->pwm1_freq * 2 + 127) / 255;
i2c_smbus_write_byte_data(client, reg, data->pwm1[nr]);
err = lm63_write_pwm(data, nr, val);
mutex_unlock(&data->update_lock);
return count;
return err ? err : count;
}
static ssize_t pwm1_enable_show(struct device *dev,
@ -485,7 +548,9 @@ static ssize_t pwm1_freq_store(struct device *dev,
struct lm63_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
unsigned long val, pfr_fast, pfr_slow, err_fast, err_slow, pfr;
u8 config_fan;
bool slow_clock;
int config_enhanced = 0;
int ret;
ret = kstrtoul(buf, 10, &val);
@ -511,35 +576,58 @@ static ssize_t pwm1_freq_store(struct device *dev,
mutex_lock(&data->update_lock);
ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG_FAN);
if (ret < 0) {
mutex_unlock(&data->update_lock);
return ret;
}
data->config_fan = ret;
if (ret < 0)
goto unlock;
config_fan = ret;
data->config_fan = config_fan;
if (!(data->config_fan & 0x20)) { /* register is read-only */
mutex_unlock(&data->update_lock);
return -EPERM;
if (!(config_fan & 0x20)) { /* register is read-only */
ret = -EPERM;
goto unlock;
}
if (data->kind == lm96163) {
ret = i2c_smbus_read_byte_data(client, LM96163_REG_CONFIG_ENHANCED);
if (ret < 0) {
mutex_unlock(&data->update_lock);
return ret;
if (data->kind == lm96163 || data->kind == ctf2301) {
ret = i2c_smbus_read_byte_data(client,
LM96163_REG_CONFIG_ENHANCED);
if (ret < 0)
goto unlock;
config_enhanced = ret;
lm63_update_pwm_highres(data, config_enhanced);
if (data->kind == ctf2301) {
if (!slow_clock && pfr == 8)
config_enhanced |= 0x10;
else
config_enhanced &= ~0x10;
ret = i2c_smbus_write_byte_data(client,
LM96163_REG_CONFIG_ENHANCED,
config_enhanced);
if (ret)
goto unlock;
lm63_update_pwm_highres(data, config_enhanced);
}
data->pwm_highres = !slow_clock && pfr == 8 && (ret & 0x10);
}
if (slow_clock)
data->config_fan |= 0x08;
config_fan |= 0x08;
else
data->config_fan &= ~0x08;
i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, data->config_fan);
i2c_smbus_write_byte_data(client, LM63_REG_PWM_FREQ, pfr);
config_fan &= ~0x08;
ret = i2c_smbus_write_byte_data(client, LM63_REG_PWM_FREQ, pfr);
if (ret)
goto unlock;
data->pwm1_freq = pfr;
lm63_update_pwm_highres(data, config_enhanced);
ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN,
config_fan);
if (ret)
goto unlock;
data->config_fan = config_fan;
lm63_update_pwm_highres(data, config_enhanced);
unlock:
mutex_unlock(&data->update_lock);
return count;
return ret ? ret : count;
}
/*
@ -554,7 +642,17 @@ static ssize_t show_local_temp8(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm63_data *data = lm63_update_device(dev);
return sprintf(buf, "%d\n", TEMP8_FROM_REG(data->temp8[attr->index]));
int temp;
mutex_lock(&data->update_lock);
if (data->kind == ctf2301)
temp = TEMP12_FROM_REG(attr->index ? data->temp1_max :
data->temp1_input);
else
temp = TEMP8_FROM_REG(data->temp8[attr->index]);
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", temp);
}
static ssize_t show_remote_temp8(struct device *dev,
@ -594,6 +692,21 @@ static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr,
return err;
mutex_lock(&data->update_lock);
if (data->kind == ctf2301 && nr == 1) {
temp = TEMP12_TO_REG(val);
err = i2c_smbus_write_byte_data(client, LM63_REG_LOCAL_HIGH,
temp >> 8);
if (!err)
err = i2c_smbus_write_byte_data(client,
CTF2301_REG_LOCAL_HIGH_LSB,
temp & 0xf0);
if (!err)
data->temp1_max = temp;
mutex_unlock(&data->update_lock);
return err ? err : count;
}
switch (nr) {
case 2:
reg = LM63_REG_REMOTE_TCRIT;
@ -610,10 +723,12 @@ static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr,
reg = LM63_REG_LUT_TEMP(nr - 3);
temp = lut_temp_to_reg(data, val);
}
data->temp8[nr] = temp;
i2c_smbus_write_byte_data(client, reg, temp);
err = i2c_smbus_write_byte_data(client, reg, temp);
if (!err)
data->temp8[nr] = temp;
mutex_unlock(&data->update_lock);
return count;
return err ? err : count;
}
static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr,
@ -787,11 +902,40 @@ static ssize_t temp2_crit_hyst_store(struct device *dev,
* Set conversion rate.
* client->update_lock must be held when calling this function.
*/
static void lm63_set_convrate(struct lm63_data *data, unsigned int interval)
static unsigned int lm63_update_interval(struct lm63_data *data, u8 rate)
{
if (data->kind == ctf2301)
return ctf2301_update_intervals[min_t(unsigned int, rate,
ARRAY_SIZE(ctf2301_update_intervals) - 1)];
return UPDATE_INTERVAL(data->max_convrate_hz, rate);
}
static int lm63_set_convrate(struct lm63_data *data, unsigned int interval)
{
struct i2c_client *client = data->client;
unsigned int update_interval;
int i;
unsigned int update_interval, error, best_error = ~0U;
unsigned int best = 0;
unsigned int i;
int ret;
if (data->kind == ctf2301) {
for (i = 0; i < ARRAY_SIZE(ctf2301_update_intervals); i++) {
error = abs_diff(interval, ctf2301_update_intervals[i]);
if (error < best_error) {
best_error = error;
best = i;
}
}
ret = i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE,
best);
if (ret)
return ret;
data->update_interval = ctf2301_update_intervals[best];
return 0;
}
/* Shift calculations to avoid rounding errors */
interval <<= 6;
@ -803,8 +947,12 @@ static void lm63_set_convrate(struct lm63_data *data, unsigned int interval)
if (interval >= update_interval * 3 / 4)
break;
i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, i);
ret = i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, i);
if (ret)
return ret;
data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz, i);
return 0;
}
static ssize_t update_interval_show(struct device *dev,
@ -828,10 +976,10 @@ static ssize_t update_interval_store(struct device *dev,
return err;
mutex_lock(&data->update_lock);
lm63_set_convrate(data, clamp_val(val, 0, 100000));
err = lm63_set_convrate(data, clamp_val(val, 0, 100000));
mutex_unlock(&data->update_lock);
return count;
return err ? err : count;
}
static ssize_t temp2_type_show(struct device *dev,
@ -996,6 +1144,75 @@ static DEVICE_ATTR_RO(alarms);
static DEVICE_ATTR_RW(update_interval);
static int lm63_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = LM63_MAX_PWM;
return 0;
}
static int lm63_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct lm63_data *data = cdev->devdata;
int ret;
mutex_lock(&data->update_lock);
ret = i2c_smbus_read_byte_data(data->client, LM63_REG_PWM_VALUE);
if (ret < 0)
goto unlock;
data->pwm1[0] = ret;
*state = lm63_pwm_from_reg(data, data->pwm1[0]);
unlock:
mutex_unlock(&data->update_lock);
return ret < 0 ? ret : 0;
}
static int lm63_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct lm63_data *data = cdev->devdata;
int config_fan;
int ret;
if (state > LM63_MAX_PWM)
return -EINVAL;
mutex_lock(&data->update_lock);
config_fan = i2c_smbus_read_byte_data(data->client,
LM63_REG_CONFIG_FAN);
if (config_fan < 0) {
ret = config_fan;
goto unlock;
}
data->config_fan = config_fan;
if (!(config_fan & 0x20)) {
config_fan |= 0x20;
ret = i2c_smbus_write_byte_data(data->client,
LM63_REG_CONFIG_FAN,
config_fan);
if (ret)
goto unlock;
data->config_fan = config_fan;
}
ret = lm63_write_pwm(data, 0, state);
unlock:
mutex_unlock(&data->update_lock);
return ret;
}
static const struct thermal_cooling_device_ops lm63_cooling_ops = {
.get_max_state = lm63_get_max_state,
.get_cur_state = lm63_get_cur_state,
.set_cur_state = lm63_set_cur_state,
};
static struct attribute *lm63_attributes[] = {
&sensor_dev_attr_pwm1.dev_attr.attr,
&dev_attr_pwm1_enable.attr,
@ -1078,8 +1295,8 @@ static const struct attribute_group lm63_group_extra_lut = {
* On LM63, temp2_crit can be set only once, which should be job
* of the bootloader.
* On LM64, temp2_crit can always be set.
* On LM96163, temp2_crit can be set if bit 1 of the configuration
* register is true.
* On LM96163 and CTF2301, temp2_crit can be set if bit 1 of the
* configuration register is true.
*/
static umode_t lm63_attribute_mode(struct kobject *kobj,
struct attribute *attr, int index)
@ -1089,7 +1306,8 @@ static umode_t lm63_attribute_mode(struct kobject *kobj,
if (attr == &sensor_dev_attr_temp2_crit.dev_attr.attr
&& (data->kind == lm64 ||
(data->kind == lm96163 && (data->config & 0x02))))
((data->kind == lm96163 || data->kind == ctf2301) &&
(data->config & 0x02))))
return attr->mode | S_IWUSR;
return attr->mode;
@ -1164,29 +1382,48 @@ static int lm63_detect(struct i2c_client *client,
* Ideally we shouldn't have to initialize anything, since the BIOS
* should have taken care of everything
*/
static void lm63_init_client(struct lm63_data *data)
static int lm63_init_client(struct lm63_data *data)
{
struct i2c_client *client = data->client;
struct device *dev = &client->dev;
u8 convrate;
int ret;
data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1);
data->config_fan = i2c_smbus_read_byte_data(client,
LM63_REG_CONFIG_FAN);
ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1);
if (ret < 0)
return ret;
data->config = ret;
ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG_FAN);
if (ret < 0)
return ret;
data->config_fan = ret;
/* Start converting if needed */
if (data->config & 0x40) { /* standby */
dev_dbg(dev, "Switching to operational mode\n");
data->config &= 0xA7;
i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1,
data->config);
ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1,
data->config);
if (ret)
return ret;
}
/* Tachometer is always enabled on LM64 */
if (data->kind == lm64)
data->config |= 0x04;
if (data->kind == ctf2301 && !client->irq && !(data->config & 0x04)) {
dev_dbg(dev, "Configuring ALERT/TACH pin for tachometer input\n");
data->config |= 0x04;
ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1,
data->config);
if (ret)
return ret;
}
/* We may need pwm1_freq before ever updating the client data */
data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ) & 0x1f;
ret = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ);
if (ret < 0)
return ret;
data->pwm1_freq = ret & 0x1f;
if (data->pwm1_freq == 0)
data->pwm1_freq = 1;
@ -1199,25 +1436,38 @@ static void lm63_init_client(struct lm63_data *data)
case lm96163:
data->max_convrate_hz = LM96163_MAX_CONVRATE_HZ;
data->lut_size = 12;
data->trutherm
= i2c_smbus_read_byte_data(client,
LM96163_REG_TRUTHERM) & 0x02;
ret = i2c_smbus_read_byte_data(client, LM96163_REG_TRUTHERM);
if (ret < 0)
return ret;
data->trutherm = ret & 0x02;
break;
case ctf2301:
data->lut_size = 12;
break;
}
convrate = i2c_smbus_read_byte_data(client, LM63_REG_CONVRATE);
if (unlikely(convrate > LM63_MAX_CONVRATE))
ret = i2c_smbus_read_byte_data(client, LM63_REG_CONVRATE);
if (ret < 0)
return ret;
convrate = ret;
if (data->kind == ctf2301 &&
convrate >= ARRAY_SIZE(ctf2301_update_intervals))
convrate = ARRAY_SIZE(ctf2301_update_intervals) - 1;
else if (unlikely(convrate > LM63_MAX_CONVRATE))
convrate = LM63_MAX_CONVRATE;
data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz,
convrate);
data->update_interval = lm63_update_interval(data, convrate);
/*
* For LM96163, check if high resolution PWM
* For LM96163 and CTF2301, check if high resolution PWM
* and unsigned temperature format is enabled.
*/
if (data->kind == lm96163) {
u8 config_enhanced
= i2c_smbus_read_byte_data(client,
LM96163_REG_CONFIG_ENHANCED);
if (data->kind == lm96163 || data->kind == ctf2301) {
u8 config_enhanced;
ret = i2c_smbus_read_byte_data(client,
LM96163_REG_CONFIG_ENHANCED);
if (ret < 0)
return ret;
config_enhanced = ret;
if (config_enhanced & 0x20)
data->lut_temp_highres = true;
if ((config_enhanced & 0x10)
@ -1238,6 +1488,8 @@ static void lm63_init_client(struct lm63_data *data)
dev_dbg(dev, "PWM output active %s, %s mode\n",
(data->config_fan & 0x10) ? "low" : "high",
(data->config_fan & 0x20) ? "manual" : "auto");
return 0;
}
static const struct i2c_device_id lm63_id[];
@ -1245,9 +1497,11 @@ static const struct i2c_device_id lm63_id[];
static int lm63_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct thermal_cooling_device *cdev;
struct device *hwmon_dev;
struct lm63_data *data;
int groups = 0;
int ret;
data = devm_kzalloc(dev, sizeof(struct lm63_data), GFP_KERNEL);
if (!data)
@ -1262,7 +1516,9 @@ static int lm63_probe(struct i2c_client *client)
data->temp2_offset = 16000;
/* Initialize chip */
lm63_init_client(data);
ret = lm63_init_client(data);
if (ret)
return dev_err_probe(dev, ret, "failed to initialize device\n");
/* Register sysfs hooks */
data->groups[groups++] = &lm63_group;
@ -1272,11 +1528,26 @@ static int lm63_probe(struct i2c_client *client)
if (data->kind == lm96163) {
data->groups[groups++] = &lm63_group_temp2_type;
data->groups[groups++] = &lm63_group_extra_lut;
} else if (data->kind == ctf2301) {
data->groups[groups++] = &lm63_group_extra_lut;
}
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
data, data->groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
if (IS_ENABLED(CONFIG_THERMAL_OF) &&
of_property_present(dev->of_node, "#cooling-cells")) {
cdev = devm_thermal_of_cooling_device_register(dev, 0,
client->name,
data,
&lm63_cooling_ops);
if (IS_ERR(cdev))
return PTR_ERR(cdev);
}
return 0;
}
/*
@ -1287,6 +1558,7 @@ static const struct i2c_device_id lm63_id[] = {
{ .name = "lm63", .driver_data = lm63 },
{ .name = "lm64", .driver_data = lm64 },
{ .name = "lm96163", .driver_data = lm96163 },
{ .name = "ctf2301", .driver_data = ctf2301 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm63_id);
@ -1304,6 +1576,10 @@ static const struct of_device_id __maybe_unused lm63_of_match[] = {
.compatible = "national,lm96163",
.data = (void *)lm96163
},
{
.compatible = "sensylink,ctf2301",
.data = (void *)ctf2301
},
{ },
};
MODULE_DEVICE_TABLE(of, lm63_of_match);

View File

@ -96,21 +96,21 @@ static ssize_t temp1_input_show(struct device *dev,
*/
switch (p_lm70->chip) {
case LM70_CHIP_LM70:
val = ((int)raw / 32) * 250;
val = ((int)raw >> 5) * 250;
break;
case LM70_CHIP_TMP121:
case LM70_CHIP_TMP122:
case LM70_CHIP_LM74:
val = ((int)raw / 8) * 625 / 10;
val = ((int)raw >> 3) * 625 / 10;
break;
case LM70_CHIP_LM71:
val = ((int)raw / 4) * 3125 / 100;
val = ((int)raw >> 2) * 3125 / 100;
break;
case LM70_CHIP_TMP125:
val = (sign_extend32(raw, 14) / 32) * 250;
val = (sign_extend32(raw, 14) >> 5) * 250;
break;
}

View File

@ -1445,7 +1445,7 @@ static void lm85_init_client(struct i2c_client *client)
static int lm85_is_fake(struct i2c_client *client)
{
/*
* Differenciate between real LM96000 and Winbond WPCD377I. The latter
* Differentiate between real LM96000 and Winbond WPCD377I. The latter
* emulate the former except that it has no hardware monitoring function
* so the readings are always 0.
*/

View File

@ -2712,6 +2712,11 @@ static int lm90_probe_channel(struct i2c_client *client,
return -EINVAL;
}
if (id == 2 && !(data->flags & LM90_HAVE_TEMP3)) {
dev_err(dev, "channel %d is not supported for this chip in %pfw\n", id, child);
return -EINVAL;
}
err = fwnode_property_read_string(child, "label", &data->channel_label[id]);
if (err == -ENODATA || err == -EILSEQ) {
dev_err(dev, "invalid label property in %pfw\n", child);

View File

@ -96,9 +96,9 @@ static int temp_from_reg_unsigned(u8 val_h, u8 val_l)
static int temp_from_reg_signed(u8 val_h, u8 val_l)
{
if (val_h & 0x80)
return (val_h - 0x100) * 1000;
return temp_from_reg_unsigned(val_h, val_l);
s16 val_hl = (val_h << 8) | val_l;
return val_hl * 1000 / 256;
}
static int lm95245_read_conversion_rate(struct lm95245_data *data)

View File

@ -624,6 +624,7 @@ static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 a
{
struct mcp9982_priv *priv = dev_get_drvdata(dev);
unsigned int idx;
int ret;
u8 addr;
switch (type) {
@ -640,9 +641,13 @@ static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 a
idx = find_closest_descending(val, mcp9982_update_interval,
ARRAY_SIZE(mcp9982_update_interval));
ret = regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
if (ret)
return ret;
priv->interval_idx = idx;
return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
return 0;
default:
return -EINVAL;
}

View File

@ -0,0 +1,503 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Minisforum UM780 XTX (F7BSD) embedded-controller hwmon driver.
*
* Copyright (C) 2026 Sebastián Peyrott <speyrott@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#define UM780XTX_EC_TEMP_SYS 0x05
#define UM780XTX_EC_TEMP_CPU 0x09
#define UM780XTX_EC_CPU_PROFILE 0x2f
#define UM780XTX_EC_SYS_POINT1 0x31
#define UM780XTX_EC_SYS_POINT2 0x34
#define UM780XTX_EC_SYS_POINT3 0x37
#define UM780XTX_EC_PROFILE_B1 0xb1
#define UM780XTX_EC_PROFILE_B2 0xb2
#define UM780XTX_EC_FAN1_LO 0xb6
#define UM780XTX_EC_FAN1_HI 0xb7
#define UM780XTX_EC_FAN2_LO 0xb9
#define UM780XTX_EC_FAN2_HI 0xba
#define UM780XTX_EC_MAX_RPM 9000
#define UM780XTX_EC_RPM_RETRIES 3
static struct platform_device *um780xtx_pdev;
struct um780xtx_data {
struct device *hwmon_dev;
u8 saved_profile;
u8 saved_sys_point1;
u8 saved_sys_point2;
u8 sys_point3;
};
static const struct dmi_system_id um780xtx_dmi_table[] = {
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR,
"Micro Computer (HK) Tech Limited"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Venus series"),
DMI_EXACT_MATCH(DMI_BOARD_VENDOR,
"Shenzhen Meigao Electronic Equipment Co.,Ltd"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "F7BSD"),
},
},
{ }
};
MODULE_DEVICE_TABLE(dmi, um780xtx_dmi_table);
static bool um780xtx_firmware_match(void)
{
const char *board_version = dmi_get_system_info(DMI_BOARD_VERSION);
const char *bios_version = dmi_get_system_info(DMI_BIOS_VERSION);
return board_version && bios_version &&
!strcmp(board_version, "1.1") && !strcmp(bios_version, "1.06");
}
static int um780xtx_oem_read(u8 command, u8 *value)
{
return ec_transaction(command, NULL, 0, value, 1);
}
static int um780xtx_read_rpm(u8 command_hi, u8 command_lo, long *rpm)
{
u8 hi_before;
u8 hi_after;
u8 lo;
unsigned int value;
int attempt;
int ret;
for (attempt = 0; attempt < UM780XTX_EC_RPM_RETRIES; attempt++) {
ret = um780xtx_oem_read(command_hi, &hi_before);
if (ret)
return ret;
ret = um780xtx_oem_read(command_lo, &lo);
if (ret)
return ret;
ret = um780xtx_oem_read(command_hi, &hi_after);
if (ret)
return ret;
if (hi_before != hi_after)
continue;
value = (hi_after << 8) | lo;
if (value > UM780XTX_EC_MAX_RPM)
continue;
*rpm = value;
return 0;
}
return -EIO;
}
static int um780xtx_read_profile(long *mode)
{
u8 profile;
int ret;
ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
if (ret)
return ret;
if (profile == UM780XTX_EC_PROFILE_B1) {
*mode = 2;
return 0;
}
if (profile == UM780XTX_EC_PROFILE_B2) {
*mode = 3;
return 0;
}
return -ENODATA;
}
static int um780xtx_write_profile(struct um780xtx_data *data, long mode)
{
u8 expected;
u8 profile;
int ret;
if (mode != 2 && mode != 3)
return -EINVAL;
expected = mode == 2 ? UM780XTX_EC_PROFILE_B1 : UM780XTX_EC_PROFILE_B2;
ret = ec_transaction(expected, NULL, 0, NULL, 0);
if (ret)
return ret;
ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
if (ret)
return ret;
if (profile != expected)
return -EIO;
data->saved_profile = profile;
return 0;
}
static const u8 um780xtx_sys_point_offsets[] = {
UM780XTX_EC_SYS_POINT1,
UM780XTX_EC_SYS_POINT2,
};
static ssize_t um780xtx_sys_point_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
u8 value;
int ret;
guard(hwmon_lock)(dev);
ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &value);
if (ret)
return ret;
return sysfs_emit(buf, "%u\n", value * 1000);
}
static ssize_t um780xtx_sys_point_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct um780xtx_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
u8 points[2];
u8 readback;
long value, lo, hi;
int ret;
ret = kstrtol(buf, 10, &value);
if (ret)
return ret;
guard(hwmon_lock)(dev);
/* Validate and clamp against the live peer threshold. */
ret = ec_read(UM780XTX_EC_SYS_POINT1, &points[0]);
if (ret)
return ret;
ret = ec_read(UM780XTX_EC_SYS_POINT2, &points[1]);
if (ret)
return ret;
if (points[0] >= points[1] || points[1] >= data->sys_point3)
return -EIO;
if (!sattr->index) {
lo = 0;
hi = points[1] - 1;
} else {
lo = points[0] + 1;
hi = data->sys_point3 - 1;
}
value = DIV_ROUND_CLOSEST(clamp_val(value, 0, 255000), 1000);
points[sattr->index] = clamp_val(value, lo, hi);
ret = ec_write(um780xtx_sys_point_offsets[sattr->index],
points[sattr->index]);
if (ret)
return ret;
ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &readback);
if (ret)
return ret;
if (readback != points[sattr->index])
return -EIO;
data->saved_sys_point1 = points[0];
data->saved_sys_point2 = points[1];
return count;
}
static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point1_temp,
um780xtx_sys_point_temp, 0);
static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point2_temp,
um780xtx_sys_point_temp, 1);
static struct attribute *um780xtx_extra_attrs[] = {
&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
NULL
};
static const struct attribute_group um780xtx_extra_group = {
.attrs = um780xtx_extra_attrs,
};
static const struct attribute_group *um780xtx_extra_groups[] = {
&um780xtx_extra_group,
NULL
};
static umode_t um780xtx_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_temp || type == hwmon_fan)
return 0444;
if (type == hwmon_pwm && attr == hwmon_pwm_enable)
return 0644;
return 0;
}
static int um780xtx_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *value)
{
u8 raw;
int ret;
if (type == hwmon_temp) {
ret = ec_read(channel ? UM780XTX_EC_TEMP_SYS :
UM780XTX_EC_TEMP_CPU, &raw);
if (ret)
return ret;
*value = raw * 1000L;
return 0;
}
if (type == hwmon_fan) {
if (!channel)
ret = um780xtx_read_rpm(UM780XTX_EC_FAN1_HI,
UM780XTX_EC_FAN1_LO, value);
else
ret = um780xtx_read_rpm(UM780XTX_EC_FAN2_HI,
UM780XTX_EC_FAN2_LO, value);
return ret;
}
if (type == hwmon_pwm)
return um780xtx_read_profile(value);
return -EOPNOTSUPP;
}
static int um780xtx_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long value)
{
struct um780xtx_data *data = dev_get_drvdata(dev);
if (type == hwmon_pwm && attr == hwmon_pwm_enable)
return um780xtx_write_profile(data, value);
return -EOPNOTSUPP;
}
static int um780xtx_read_string(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
static const char * const labels[] = { "CPU", "SYS" };
if (type == hwmon_temp && attr == hwmon_temp_label)
*str = labels[channel];
else if (type == hwmon_fan && attr == hwmon_fan_label)
*str = labels[channel];
else
return -EOPNOTSUPP;
return 0;
}
static const struct hwmon_ops um780xtx_hwmon_ops = {
.is_visible = um780xtx_is_visible,
.read = um780xtx_read,
.write = um780xtx_write,
.read_string = um780xtx_read_string,
};
static const struct hwmon_channel_info * const um780xtx_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
NULL
};
static const struct hwmon_chip_info um780xtx_chip_info = {
.ops = &um780xtx_hwmon_ops,
.info = um780xtx_info,
};
static int um780xtx_write_sys_point(u8 offset, u8 value)
{
u8 readback;
int ret;
ret = ec_write(offset, value);
if (ret)
return ret;
ret = ec_read(offset, &readback);
if (ret)
return ret;
return readback == value ? 0 : -EIO;
}
static int um780xtx_restore_sys_points(struct um780xtx_data *data,
u8 current_point2)
{
u8 point1 = data->saved_sys_point1;
u8 point2 = data->saved_sys_point2;
int ret;
/* Keep strict ordering valid after each individual EC write. */
if (point1 >= current_point2) {
ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2);
if (ret)
return ret;
return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1,
point1);
}
ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1, point1);
if (ret)
return ret;
return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2);
}
static int um780xtx_read_initial_state(struct um780xtx_data *data)
{
u8 profile;
u8 point1;
u8 point2;
int ret;
ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
if (ret)
return ret;
if (profile != UM780XTX_EC_PROFILE_B1 &&
profile != UM780XTX_EC_PROFILE_B2)
return -EINVAL;
ret = ec_read(UM780XTX_EC_SYS_POINT1, &point1);
if (ret)
return ret;
ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2);
if (ret)
return ret;
if (point1 >= point2 || point2 >= data->sys_point3)
return -EINVAL;
data->saved_profile = profile;
data->saved_sys_point1 = point1;
data->saved_sys_point2 = point2;
return 0;
}
static int um780xtx_restore_state(struct um780xtx_data *data)
{
u8 readback;
u8 point2;
int ret;
ret = ec_transaction(data->saved_profile, NULL, 0, NULL, 0);
if (ret)
return ret;
ret = ec_read(UM780XTX_EC_CPU_PROFILE, &readback);
if (ret)
return ret;
if (readback != data->saved_profile)
return -EIO;
ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2);
if (ret)
return ret;
return um780xtx_restore_sys_points(data, point2);
}
static int um780xtx_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct um780xtx_data *data;
struct device *hwmon;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
ret = ec_read(UM780XTX_EC_SYS_POINT3, &data->sys_point3);
if (ret)
return dev_err_probe(dev, ret,
"failed to read system fan limit\n");
ret = um780xtx_read_initial_state(data);
if (ret)
return dev_err_probe(dev, ret,
"failed to read initial fan settings\n");
hwmon = devm_hwmon_device_register_with_info(dev, "um780xtx_ec", data,
&um780xtx_chip_info,
um780xtx_extra_groups);
if (IS_ERR(hwmon))
return PTR_ERR(hwmon);
data->hwmon_dev = hwmon;
return 0;
}
static int um780xtx_resume(struct device *dev)
{
struct um780xtx_data *data = dev_get_drvdata(dev);
guard(hwmon_lock)(data->hwmon_dev);
return um780xtx_restore_state(data);
}
static DEFINE_SIMPLE_DEV_PM_OPS(um780xtx_pm_ops, NULL, um780xtx_resume);
static struct platform_driver um780xtx_driver = {
.driver = {
.name = "um780xtx-ec-hwmon",
.pm = pm_sleep_ptr(&um780xtx_pm_ops),
},
.probe = um780xtx_probe,
};
static int __init um780xtx_init(void)
{
int ret;
if (!dmi_check_system(um780xtx_dmi_table) ||
!um780xtx_firmware_match() || !ec_get_handle())
return -ENODEV;
ret = platform_driver_register(&um780xtx_driver);
if (ret)
return ret;
um780xtx_pdev = platform_device_register_simple("um780xtx-ec-hwmon",
PLATFORM_DEVID_NONE,
NULL, 0);
if (IS_ERR(um780xtx_pdev)) {
ret = PTR_ERR(um780xtx_pdev);
platform_driver_unregister(&um780xtx_driver);
return ret;
}
return 0;
}
static void __exit um780xtx_exit(void)
{
platform_device_unregister(um780xtx_pdev);
platform_driver_unregister(&um780xtx_driver);
}
module_init(um780xtx_init);
module_exit(um780xtx_exit);
MODULE_AUTHOR("Sebastián Peyrott <speyrott@gmail.com>");
MODULE_DESCRIPTION("Minisforum UM780 XTX EC hwmon driver");
MODULE_LICENSE("GPL");

View File

@ -21,6 +21,8 @@
* Chip #vin #fan #pwm #temp chip IDs man ID
* nct6106d 9 3 3 6+3 0xc450 0xc1 0x5ca3
* nct6116d 9 5 5 3+3 0xd280 0xc1 0x5ca3
* nct6126d 10 5 5 3+3 0xd283 0xc1 0x5ca3
* (0xd284)
* nct6775f 9 4 3 6+3 0xb470 0xc1 0x5ca3
* nct6776f 9 5 3 6+3 0xc330 0xc1 0x5ca3
* nct6779d 15 5 5 2+6 0xc560 0xc1 0x5ca3
@ -66,6 +68,7 @@
static const char * const nct6775_device_names[] = {
[nct6106] = "nct6106",
[nct6116] = "nct6116",
[nct6126] = "nct6126",
[nct6775] = "nct6775",
[nct6776] = "nct6776",
[nct6779] = "nct6779",
@ -897,6 +900,31 @@ static const s8 NCT6116_BEEP_BITS[NUM_BEEP_BITS] = {
static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b };
/* NCT6122D/NCT6126D specific data */
static const u16 NCT6126_REG_IN[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
static const u16 NCT6126_REG_IN_MAX[] = {
0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2 };
static const u16 NCT6126_REG_IN_MIN[] = {
0x91, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9d, 0x9f, 0xa1, 0xa3 };
static const s8 NCT6126_ALARM_BITS[NUM_ALARM_BITS] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, /* in0-in11 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */
32, 33, 34, 35, 36, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */
16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */
48, -1, /* intr0-intr1 */
};
static const s8 NCT6126_BEEP_BITS[NUM_BEEP_BITS] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* in0-in11 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */
24, 25, 26, 27, 28, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */
16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */
34, -1, 32 /* intr0-intr1, beep_en */
};
static enum pwm_enable reg_to_pwm_enable(int pwm, int mode)
{
if (mode == 0 && pwm == 255)
@ -998,6 +1026,14 @@ static const u16 scale_in_6798[NUM_IN] = {
800, 800, 800, 1600, 800
};
/*
* NCT6126 scaling:
* CPUVCORE, VIN0, AVSB, 3VCC, VIN1, VIN2, VHIF, 3VSB, VBAT, VTT
*/
static const u16 scale_in_6126[10] = {
800, 800, 1600, 1600, 800, 800, 1600, 1600, 1600, 800
};
static inline long in_from_reg(u8 reg, u8 nr, const u16 *scales)
{
return DIV_ROUND_CLOSEST(reg * scales[nr], 100);
@ -1156,6 +1192,7 @@ bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg)
reg == 0xe0 || reg == 0xe2 || reg == 0xe4 ||
reg == 0x111 || reg == 0x121 || reg == 0x131;
case nct6116:
case nct6126:
return reg == 0x20 || reg == 0x22 || reg == 0x24 ||
reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b ||
reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 ||
@ -1538,6 +1575,7 @@ static int nct6775_update_pwm_limits(struct device *dev)
break;
case nct6106:
case nct6116:
case nct6126:
case nct6779:
case nct6791:
case nct6792:
@ -3203,6 +3241,7 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr,
break; /* always enabled, nothing to do */
case nct6106:
case nct6116:
case nct6126:
case nct6779:
case nct6791:
case nct6792:
@ -3677,6 +3716,83 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L;
reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H;
break;
case nct6126:
data->in_num = 10;
data->scale_in = scale_in_6126;
data->pwm_num = 5;
data->auto_pwm_num = 4;
data->temp_fixed_num = 3;
data->num_temp_alarms = 3;
data->num_temp_beeps = 3;
data->fan_from_reg = fan_from_reg13;
data->fan_from_reg_min = fan_from_reg13;
data->temp_label = nct6776_temp_label;
data->temp_mask = NCT6776_TEMP_MASK;
data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK;
data->REG_VBAT = NCT6106_REG_VBAT;
data->REG_DIODE = NCT6106_REG_DIODE;
data->DIODE_MASK = NCT6106_DIODE_MASK;
data->REG_VIN = NCT6126_REG_IN;
data->REG_IN_MINMAX[0] = NCT6126_REG_IN_MIN;
data->REG_IN_MINMAX[1] = NCT6126_REG_IN_MAX;
data->REG_TARGET = NCT6116_REG_TARGET;
data->REG_FAN = NCT6116_REG_FAN;
data->REG_FAN_MODE = NCT6116_REG_FAN_MODE;
data->REG_FAN_MIN = NCT6116_REG_FAN_MIN;
data->REG_FAN_PULSES = NCT6116_REG_FAN_PULSES;
data->FAN_PULSE_SHIFT = NCT6116_FAN_PULSE_SHIFT;
data->REG_FAN_TIME[0] = NCT6116_REG_FAN_STOP_TIME;
data->REG_FAN_TIME[1] = NCT6116_REG_FAN_STEP_UP_TIME;
data->REG_FAN_TIME[2] = NCT6116_REG_FAN_STEP_DOWN_TIME;
data->REG_TOLERANCE_H = NCT6116_REG_TOLERANCE_H;
data->REG_PWM[0] = NCT6116_REG_PWM;
data->REG_PWM[1] = NCT6116_REG_FAN_START_OUTPUT;
data->REG_PWM[2] = NCT6116_REG_FAN_STOP_OUTPUT;
data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP;
data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE;
data->REG_PWM_READ = NCT6106_REG_PWM_READ;
data->REG_PWM_MODE = NCT6106_REG_PWM_MODE;
data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK;
data->REG_AUTO_TEMP = NCT6116_REG_AUTO_TEMP;
data->REG_AUTO_PWM = NCT6116_REG_AUTO_PWM;
data->REG_CRITICAL_TEMP = NCT6116_REG_CRITICAL_TEMP;
data->REG_CRITICAL_TEMP_TOLERANCE =
NCT6116_REG_CRITICAL_TEMP_TOLERANCE;
data->REG_CRITICAL_PWM_ENABLE = NCT6116_REG_CRITICAL_PWM_ENABLE;
data->CRITICAL_PWM_ENABLE_MASK =
NCT6106_CRITICAL_PWM_ENABLE_MASK;
data->REG_CRITICAL_PWM = NCT6116_REG_CRITICAL_PWM;
data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET;
data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE;
data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL;
data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL;
data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP;
data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL;
data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE;
data->REG_ALARM = NCT6106_REG_ALARM;
data->ALARM_BITS = NCT6126_ALARM_BITS;
data->REG_BEEP = NCT6106_REG_BEEP;
data->BEEP_BITS = NCT6126_BEEP_BITS;
data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP;
reg_temp = NCT6106_REG_TEMP;
reg_temp_mon = NCT6106_REG_TEMP_MON;
num_reg_temp = 3;
num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP);
reg_temp_over = NCT6106_REG_TEMP_OVER;
reg_temp_hyst = NCT6106_REG_TEMP_HYST;
reg_temp_config = NCT6106_REG_TEMP_CONFIG;
num_reg_temp_config = 3;
reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE;
reg_temp_crit = NCT6106_REG_TEMP_CRIT;
reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L;
reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H;
break;
case nct6775:
data->in_num = 9;

View File

@ -25,6 +25,7 @@ enum sensor_access { access_direct, access_asuswmi };
static const char * const nct6775_sio_names[] __initconst = {
[nct6106] = "NCT6106D",
[nct6116] = "NCT6116D",
[nct6126] = "NCT6122D/NCT6126D",
[nct6775] = "NCT6775F",
[nct6776] = "NCT6776D/F",
[nct6779] = "NCT6779D",
@ -76,6 +77,9 @@ MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal");
#define SIO_NCT6106_ID 0xc450
#define SIO_NCT6116_ID 0xd280
#define SIO_NCT6122_ID 0xd2a0
#define SIO_NCT6126_A_ID 0xd283
#define SIO_NCT6126_B_ID 0xd284
#define SIO_NCT6775_ID 0xb470
#define SIO_NCT6776_ID 0xc330
#define SIO_NCT6779_ID 0xc560
@ -565,6 +569,25 @@ nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio
pwm3pin = fan3pin && (cr24 & 0x08);
pwm4pin = fan4pin;
pwm5pin = fan5pin;
} else if (data->kind == nct6126) {
int cr1a = sio_data->sio_inb(sio_data, 0x1a);
int cr1b = sio_data->sio_inb(sio_data, 0x1b);
int cr24 = sio_data->sio_inb(sio_data, 0x24);
int cr2a = sio_data->sio_inb(sio_data, 0x2a);
int cr2b = sio_data->sio_inb(sio_data, 0x2b);
int cr2f = sio_data->sio_inb(sio_data, 0x2f);
fan3pin = !(cr2b & 0x10);
fan4pin = (cr2b & 0x80) || /* pin 1(2) */
(!(cr2f & 0x10) && (cr1a & 0x04)); /* pin 65(66) */
fan5pin = (cr2b & 0x80) || /* pin 126(127) */
(!(cr1b & 0x03) && (cr2a & 0x02)); /* pin 94(96) */
pwm3pin = fan3pin && (cr24 & 0x08);
pwm4pin = fan4pin;
pwm5pin = fan5pin;
fan4min = fan4pin;
} else {
/*
* NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D,
@ -861,6 +884,7 @@ static int nct6775_platform_probe_init(struct nct6775_data *data)
break;
case nct6106:
case nct6116:
case nct6126:
case nct6779:
case nct6791:
case nct6792:
@ -892,6 +916,7 @@ static int nct6775_platform_probe_init(struct nct6775_data *data)
switch (data->kind) {
case nct6106:
case nct6116:
case nct6126:
tmp |= 0xe0;
break;
case nct6775:
@ -1006,7 +1031,13 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
sio_data->kind = nct6106;
break;
case SIO_NCT6116_ID:
sio_data->kind = nct6116;
if (val == SIO_NCT6126_A_ID || val == SIO_NCT6126_B_ID)
sio_data->kind = nct6126;
else
sio_data->kind = nct6116;
break;
case SIO_NCT6122_ID:
sio_data->kind = nct6126;
break;
case SIO_NCT6775_ID:
sio_data->kind = nct6775;

View File

@ -4,8 +4,8 @@
#include <linux/types.h>
enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792,
nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 };
enum kinds { nct6106, nct6116, nct6126, nct6775, nct6776, nct6779, nct6791,
nct6792, nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 };
enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 };
#define NUM_TEMP 12 /* Max number of temp attribute sets w/ limits*/

View File

@ -23,6 +23,7 @@
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/watchdog.h>
#include <linux/stringify.h>
#define VENDOR_ID_REG 0x7A /* Any bank */
#define NUVOTON_ID 0x50
@ -113,12 +114,12 @@
static int timeout;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1 <= timeout <= 255, default="
__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
__stringify(WATCHDOG_TIMEOUT) ".");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
__stringify(WATCHDOG_NOWAYOUT) ")");
static const unsigned short normal_i2c[] = {
0x2d, 0x2e, I2C_CLIENT_END
@ -126,6 +127,7 @@ static const unsigned short normal_i2c[] = {
struct nct7904_data {
struct i2c_client *client;
struct device *hwmon_dev;
struct watchdog_device wdt;
int bank_sel;
u32 fanin_mask;
@ -913,6 +915,8 @@ static int nct7904_wdt_start(struct watchdog_device *wdt)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
guard(hwmon_lock)(data->hwmon_dev);
/* Enable soft watchdog timer */
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
}
@ -921,6 +925,8 @@ static int nct7904_wdt_stop(struct watchdog_device *wdt)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
guard(hwmon_lock)(data->hwmon_dev);
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
}
@ -939,6 +945,8 @@ static int nct7904_wdt_set_timeout(struct watchdog_device *wdt,
*/
wdt->timeout = timeout / 60 * 60;
guard(hwmon_lock)(data->hwmon_dev);
return nct7904_write_reg(data, BANK_0, WDT_TIMER_REG,
wdt->timeout / 60);
}
@ -954,6 +962,8 @@ static int nct7904_wdt_ping(struct watchdog_device *wdt)
struct nct7904_data *data = watchdog_get_drvdata(wdt);
int ret;
guard(hwmon_lock)(data->hwmon_dev);
/* Disable soft watchdog timer */
ret = nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
if (ret < 0)
@ -973,6 +983,8 @@ static unsigned int nct7904_wdt_get_timeleft(struct watchdog_device *wdt)
struct nct7904_data *data = watchdog_get_drvdata(wdt);
int ret;
guard(hwmon_lock)(data->hwmon_dev);
ret = nct7904_read_reg(data, BANK_0, WDT_TIMER_REG);
if (ret < 0)
return 0;
@ -1127,6 +1139,9 @@ static int nct7904_probe(struct i2c_client *client)
if (ret)
return ret;
/* The watchdog callbacks take its lock */
data->hwmon_dev = hwmon_dev;
/* Watchdog initialization */
data->wdt.ops = &nct7904_wdt_ops;
data->wdt.info = &nct7904_wdt_info;

View File

@ -7,6 +7,7 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
@ -197,6 +198,8 @@ struct npcm7xx_pwm_fan_data {
int pwm_modules;
struct clk *pwm_clk;
struct clk *fan_clk;
/* serializes CMR/CR updates from hwmon and the cooling devices */
struct mutex pwm_lock[NPCM7XX_PWM_MAX_MODULES];
spinlock_t fan_lock[NPCM7XX_FAN_MAX_MODULE];
int fan_irq[NPCM7XX_FAN_MAX_MODULE];
bool pwm_present[NPCM7XX_PWM_MAX_CHN_NUM];
@ -216,6 +219,8 @@ static int npcm7xx_pwm_config_set(struct npcm7xx_pwm_fan_data *data,
u32 module = (channel / NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE);
u32 tmp_buf, ctrl_en_bit, env_bit;
guard(mutex)(&data->pwm_lock[module]);
/*
* Config PWM Comparator register for setting duty cycle
*/
@ -986,6 +991,12 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
output_freq = npcm7xx_pwm_init(data);
npcm7xx_fan_init(data);
for (i = 0; i < ARRAY_SIZE(data->pwm_lock); i++) {
ret = devm_mutex_init(dev, &data->pwm_lock[i]);
if (ret)
return ret;
}
for (i = 0; i < NPCM7XX_FAN_MAX_MODULE; i++) {
spin_lock_init(&data->fan_lock[i]);

View File

@ -393,6 +393,25 @@ config SENSORS_MAX20751
This driver can also be built as a module. If so, the module will
be called max20751.
config SENSORS_MAX20826
tristate "Analog Devices MAX20826 and similar devices"
help
If you say yes here you get hardware monitoring support for Analog
Devices MAX20826 and similar devices.
This driver can also be built as a module. If so, the module will
be called max20826.
config SENSORS_MAX20826_REGULATOR
bool "Regulator support for Analog Devices MAX20826 and similar devices"
depends on SENSORS_MAX20826 && REGULATOR
help
If you say yes here you get regulator support for Analog Devices
MAX20826 and similar sensors.
This enables the MAX20826 to be used as a regulator device,
providing voltage control through the regulator framework.
config SENSORS_MAX20830
tristate "Analog Devices MAX20830 and compatibles"
help
@ -435,8 +454,8 @@ config SENSORS_MAX34440
help
If you say yes here you get hardware monitoring support for Maxim
MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and
MAX34461. Other compatible devices are ADPM12160, ADPM12200, and
ADPM12250.
MAX34461. Other compatible devices are ADPM12160, ADPM12200,
ADPM12250, and ADPM12300.
This driver can also be built as a module. If so, the module will
be called max34440.
@ -763,6 +782,15 @@ config SENSORS_TDA38640_REGULATOR
If you say yes here you get regulator support for Infineon
TDA38640 as regulator.
config SENSORS_TDA38740
tristate "Infineon TDA38725/TDA38740"
help
If you say yes here you get hardware monitoring support for Infineon
TDA38725 and TDA38740.
This driver can also be built as a module. If so, the module will
be called tda38740.
config SENSORS_TPS25990
tristate "TI TPS25990"
help
@ -790,10 +818,11 @@ config SENSORS_TPS40422
be called tps40422.
config SENSORS_TPS53679
tristate "TI TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, TPS53688"
tristate "TI TPS536xx family"
help
If you say yes here you get hardware monitoring support for TI
TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, and TPS53688.
TPS53622, TPS53647, TPS53659, TPS53667, TPS53676, TPS53679, TPS53681,
TPS53685, TPS53688, and TPS536C7.
This driver can also be built as a module. If so, the module will
be called tps53679.
@ -867,7 +896,7 @@ config SENSORS_XDPE1A2G7B
tristate "Infineon XDPE1A2G7B"
help
If you say yes here you get hardware monitoring support for Infineon
XDPE1A2G5B and XDPE1A2G7B.
XDPE1A2G5B,XDPE1A2G7B and XDPE1A2G7C.
This driver can also be built as a module. If so, the module will
be called xdpe1a2g7b.
@ -877,7 +906,7 @@ config SENSORS_XDPE1A2G7B_REGULATOR
depends on SENSORS_XDPE1A2G7B && REGULATOR
help
If you say yes here you get regulator support for Infineon
XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers.
XDPE1A2G5B,XDPE1A2G7B and XDPE1A2G7C multi-phase digital controllers.
This enables the controllers to be used as regulator devices,
providing voltage control through the regulator framework.

View File

@ -38,6 +38,7 @@ obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
obj-$(CONFIG_SENSORS_MAX17616) += max17616.o
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
obj-$(CONFIG_SENSORS_MAX20826) += max20826.o
obj-$(CONFIG_SENSORS_MAX20830) += max20830.o
obj-$(CONFIG_SENSORS_MAX20860A) += max20860a.o
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
@ -70,6 +71,7 @@ obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o
obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o
obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o
obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o
obj-$(CONFIG_SENSORS_TDA38740) += tda38740.o
obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o

View File

@ -166,7 +166,7 @@ static ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf,
if (rc < 0)
goto unlock;
snprintf(&data[i * 2], 3, "%02X", rc);
snprintf(&data[i * 2], 3, "%02X", (u8)rc);
}
rc = i * 2;
@ -177,7 +177,7 @@ static ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf,
if (rc < 0)
goto unlock;
snprintf(&data[i * 4], 5, "%04X", rc);
snprintf(&data[i * 4], 5, "%04X", (u16)rc);
}
rc = i * 4;

View File

@ -456,6 +456,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = {
{ .name = "raa229004", .driver_data = raa_dmpvr2_2rail },
{ .name = "raa229141", .driver_data = raa_dmpvr2_2rail_pmbus },
{ .name = "raa229621", .driver_data = raa_dmpvr2_2rail },
{ .name = "raa229639", .driver_data = raa_dmpvr2_2rail_nontc },
{ .name = "raa229640", .driver_data = raa_dmpvr2_2rail_nontc },
{ }
};
@ -506,6 +508,8 @@ static const struct of_device_id isl68137_of_match[] = {
{ .compatible = "renesas,raa229001", .data = (void *)raa_dmpvr2_2rail },
{ .compatible = "renesas,raa229004", .data = (void *)raa_dmpvr2_2rail },
{ .compatible = "renesas,raa229621", .data = (void *)raa_dmpvr2_2rail },
{ .compatible = "renesas,raa229639", .data = (void *)raa_dmpvr2_2rail_nontc },
{ .compatible = "renesas,raa229640", .data = (void *)raa_dmpvr2_2rail_nontc },
{ },
};

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/err.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
@ -16,6 +17,85 @@
#define LTC4286_MFR_ID_SIZE 3
struct ltc4286_data {
struct pmbus_driver_info info;
u32 rsense;
bool vrange_low_enable;
};
#define to_ltc4286_data(x) container_of((x), struct ltc4286_data, info)
static ssize_t ltc4286_rsense_show(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev->parent);
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct ltc4286_data *data = to_ltc4286_data(info);
u32 rsense;
int ret;
ret = pmbus_lock_interruptible(client);
if (ret)
return ret;
rsense = data->rsense;
pmbus_unlock(client);
return sysfs_emit(buf, "%u\n", rsense);
}
static ssize_t ltc4286_rsense_store(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev->parent);
const struct pmbus_driver_info *info_ro = pmbus_get_driver_info(client);
struct ltc4286_data *data = to_ltc4286_data(info_ro);
struct pmbus_driver_info *info = &data->info;
u32 rsense;
int ret;
ret = kstrtou32(buf, 10, &rsense);
if (ret)
return ret;
if (rsense == 0)
return -EINVAL;
if (rsense > (INT_MAX / 1024))
return -EINVAL;
ret = pmbus_lock_interruptible(client);
if (ret)
return ret;
data->rsense = rsense;
info->m[PSC_CURRENT_OUT] = 1024 * rsense;
info->m[PSC_POWER] = data->vrange_low_enable ? 4 * rsense : rsense;
pmbus_unlock(client);
return count;
}
static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ltc4286_rsense, 0);
static struct attribute *ltc4286_attrs[] = {
&sensor_dev_attr_shunt_resistor.dev_attr.attr,
NULL,
};
static const struct attribute_group ltc4286_group = {
.attrs = ltc4286_attrs,
};
static const struct attribute_group *ltc4286_attribute_groups[] = {
&ltc4286_group,
NULL,
};
/*
* Initialize the MBR as default settings which is referred to LTC4286 datasheet
* (March 22, 2022 version) table 3 page 16
@ -55,6 +135,7 @@ static struct pmbus_driver_info ltc4286_info = {
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
.groups = ltc4286_attribute_groups,
};
static const struct i2c_device_id ltc4286_id[] = {
@ -69,6 +150,7 @@ static int ltc4286_probe(struct i2c_client *client)
int ret;
const struct i2c_device_id *mid;
u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
struct ltc4286_data *data;
struct pmbus_driver_info *info;
u32 rsense;
int vrange_nval, vrange_oval;
@ -114,11 +196,14 @@ static int ltc4286_probe(struct i2c_client *client)
if (rsense > (INT_MAX / 1024))
return -EINVAL;
info = devm_kmemdup(&client->dev, &ltc4286_info, sizeof(*info),
GFP_KERNEL);
if (!info)
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->info = ltc4286_info;
data->rsense = rsense;
info = &data->info;
/* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */
vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1);
if (vrange_oval < 0)
@ -126,7 +211,10 @@ static int ltc4286_probe(struct i2c_client *client)
"Failed to read manufacturer configuration one\n");
vrange_nval = vrange_oval;
if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) {
data->vrange_low_enable =
device_property_read_bool(&client->dev, "adi,vrange-low-enable");
if (data->vrange_low_enable) {
vrange_nval &=
~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */

File diff suppressed because it is too large Load Diff

View File

@ -812,10 +812,12 @@ static int max34440_probe(struct i2c_client *client)
return pmbus_do_probe(client, &data->info);
}
/* ADPM12300 is just ADPM12250 with higher power capacity. Reuses driver data */
static const struct i2c_device_id max34440_id[] = {
{ .name = "adpm12160", .driver_data = adpm12160 },
{ .name = "adpm12200", .driver_data = adpm12200 },
{ .name = "adpm12250", .driver_data = adpm12250 },
{ .name = "adpm12300", .driver_data = adpm12250 },
{ .name = "max34440", .driver_data = max34440 },
{ .name = "max34441", .driver_data = max34441 },
{ .name = "max34446", .driver_data = max34446 },

View File

@ -272,14 +272,14 @@ static int mp2888_write_word_data(struct i2c_client *client, int page, int reg,
word = clamp_val(word, 0, GENMASK(7, 0));
break;
case PMBUS_IOUT_OC_WARN_LIMIT:
/* Fix limit according to total curent resolution. */
/* Fix limit according to total current resolution. */
word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 8) :
DIV_ROUND_CLOSEST(word, 4);
/* Drop unused bits 15:10. */
word = clamp_val(word, 0, GENMASK(9, 0));
break;
case PMBUS_POUT_OP_WARN_LIMIT:
/* Fix limit according to total curent resolution. */
/* Fix limit according to total current resolution. */
word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 4) :
DIV_ROUND_CLOSEST(word, 2);
/* Drop unused bits 15:10. */

View File

@ -461,6 +461,13 @@ struct pmbus_driver_info {
int (*read_byte_data)(struct i2c_client *client, int page, int reg);
int (*read_word_data)(struct i2c_client *client, int page, int phase,
int reg);
/*
* data_buf is at least I2C_SMBUS_BLOCK_MAX bytes long. The callback
* must never return more than I2C_SMBUS_BLOCK_MAX bytes of data and
* returns the number of bytes written into data_buf.
*/
int (*read_block_data)(struct i2c_client *client, int page, u8 reg,
char *data_buf);
int (*write_byte_data)(struct i2c_client *client, int page, int reg,
u8 byte);
int (*write_word_data)(struct i2c_client *client, int page, int reg,

View File

@ -616,6 +616,26 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
return pmbus_read_smbus_i2c_block_data(client, reg, data_buf);
}
/*
* _pmbus_read_block_data() is similar to pmbus_read_block_data(), but checks if
* a device specific mapping function exists and calls it if necessary.
*/
static int _pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
char *data_buf)
{
struct pmbus_data *data = i2c_get_clientdata(client);
const struct pmbus_driver_info *info = data->info;
int status;
if (info->read_block_data) {
status = info->read_block_data(client, page, reg, data_buf);
if (status != -ENODATA)
return status;
}
return pmbus_read_block_data(client, page, reg, data_buf);
}
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
int reg)
{
@ -761,7 +781,7 @@ static bool __maybe_unused pmbus_check_block_register(struct i2c_client *client,
struct pmbus_data *data = i2c_get_clientdata(client);
char data_buf[I2C_SMBUS_BLOCK_MAX + 2];
rv = pmbus_read_block_data(client, page, reg, data_buf);
rv = _pmbus_read_block_data(client, page, reg, data_buf);
if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK))
rv = pmbus_check_status_cml(client);
if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK))
@ -2962,6 +2982,11 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
}
for (page = 0; page < info->pages; page++) {
if (info->phases[page] > PMBUS_PHASES) {
dev_err(dev, "Bad number of PMBus phases for page %d: %d\n",
page, info->phases[page]);
return -ENODEV;
}
ret = pmbus_identify_common(client, data, page);
if (ret < 0) {
dev_err(dev, "Failed to identify chip capabilities\n");
@ -3679,7 +3704,7 @@ static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf,
char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
scoped_guard(pmbus_lock, client) {
rc = pmbus_read_block_data(client, entry->page, entry->reg, data);
rc = _pmbus_read_block_data(client, entry->page, entry->reg, data);
if (rc < 0)
return rc;
}

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for Infineon TDA38725/TDA38740
*
* Copyright (c) 2023 9elements GmbH
*
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"
static struct pmbus_driver_info tda38740_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = linear,
.format[PSC_CURRENT_OUT] = linear,
.format[PSC_CURRENT_IN] = linear,
.format[PSC_POWER] = linear,
.format[PSC_TEMPERATURE] = linear,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
| PMBUS_HAVE_IIN
| PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
| PMBUS_HAVE_POUT | PMBUS_HAVE_PIN,
};
static int tda38740_probe(struct i2c_client *client)
{
return pmbus_do_probe(client, &tda38740_info);
}
static const struct i2c_device_id tda38740_id[] = {
{ .name = "tda38725"},
{ .name = "tda38740"},
{}
};
MODULE_DEVICE_TABLE(i2c, tda38740_id);
static const struct of_device_id tda38740_of_match[] = {
{ .compatible = "infineon,tda38725"},
{ .compatible = "infineon,tda38740"},
{}
};
MODULE_DEVICE_TABLE(of, tda38740_of_match);
static struct i2c_driver tda38740_driver = {
.driver = {
.name = "tda38740",
.of_match_table = tda38740_of_match,
},
.probe = tda38740_probe,
.id_table = tda38740_id,
};
module_i2c_driver(tda38740_driver);
MODULE_DESCRIPTION("PMBus driver for Infineon TDA38725/TDA38740");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@ -47,6 +47,22 @@
PK_MIN_AVG_RST_AVG | \
PK_MIN_AVG_RST_MIN)
#define TPS1689_VIN_OV_RANGE_SEL_MASK GENMASK(7, 6)
#define TPS1689_VIN_VOV_MASK GENMASK(5, 0)
#define TPS1689_VIN_SCALING 251
#define TPS1689_VIN_VOV_STEP_MV 250
#define TPS1689_VIN_RANGE_SPAN_MV 16000
enum chips {
tps1689,
tps25990,
};
struct tps25990_data {
struct pmbus_driver_info info;
enum chips chip_id;
};
/*
* Arbitrary default Rimon value: 1kOhm
* This correspond to an overcurrent limit of 55A, close to the specified limit
@ -96,6 +112,8 @@ static int tps25990_mfr_write_protect_get(struct i2c_client *client)
static int tps25990_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 tps25990_data *data = container_of(info, struct tps25990_data, info);
int ret;
switch (reg) {
@ -184,9 +202,18 @@ static int tps25990_read_word_data(struct i2c_client *client,
ret = pmbus_read_word_data(client, page, phase, reg);
if (ret < 0)
break;
ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM,
TPS25990_VIN_OVF_DIV);
ret += TPS25990_VIN_OVF_OFF;
if (data->chip_id == tps25990) {
ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM,
TPS25990_VIN_OVF_DIV);
ret += TPS25990_VIN_OVF_OFF;
} else if (data->chip_id == tps1689) {
int rng = (FIELD_GET(TPS1689_VIN_OV_RANGE_SEL_MASK, ret) + 1) *
TPS1689_VIN_RANGE_SPAN_MV;
int vov = FIELD_GET(TPS1689_VIN_VOV_MASK, ret) * TPS1689_VIN_VOV_STEP_MV;
ret = DIV_ROUND_CLOSEST(rng + vov - TPS1689_VIN_RANGE_SPAN_MV,
TPS1689_VIN_SCALING);
}
break;
case PMBUS_IIN_OC_FAULT_LIMIT:
@ -229,6 +256,8 @@ static int tps25990_read_word_data(struct i2c_client *client,
static int tps25990_write_word_data(struct i2c_client *client,
int page, int reg, u16 value)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct tps25990_data *data = container_of(info, struct tps25990_data, info);
int ret;
switch (reg) {
@ -240,26 +269,52 @@ static int tps25990_write_word_data(struct i2c_client *client,
case PMBUS_OT_WARN_LIMIT:
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_PIN_OP_WARN_LIMIT:
value >>= TPS25990_8B_SHIFT;
value = clamp_val((s16)value, 0, S16_MAX) >> TPS25990_8B_SHIFT;
value = clamp_val(value, 0, 0xff);
ret = pmbus_write_word_data(client, page, reg, value);
break;
case PMBUS_VIN_OV_FAULT_LIMIT:
value -= TPS25990_VIN_OVF_OFF;
value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV,
TPS25990_VIN_OVF_NUM);
value = clamp_val(value, 0, 0xf);
if ((s16)value < 0)
return -EINVAL;
if (data->chip_id == tps25990) {
int tmp = (int)value - TPS25990_VIN_OVF_OFF;
tmp = clamp_val(tmp, 0, INT_MAX);
value = DIV_ROUND_CLOSEST((unsigned int)tmp * TPS25990_VIN_OVF_DIV,
TPS25990_VIN_OVF_NUM);
value = clamp_val(value, 0, 0xf);
} else if (data->chip_id == tps1689) {
u32 scaled_value = value * TPS1689_VIN_SCALING + TPS1689_VIN_RANGE_SPAN_MV;
u32 rng_idx = scaled_value / TPS1689_VIN_RANGE_SPAN_MV;
u32 ov_set;
rng_idx = clamp_val(rng_idx, 1,
FIELD_MAX(TPS1689_VIN_OV_RANGE_SEL_MASK) + 1);
ov_set = scaled_value - (TPS1689_VIN_RANGE_SPAN_MV * rng_idx);
ov_set = min_t(u32, ov_set / TPS1689_VIN_VOV_STEP_MV,
FIELD_MAX(TPS1689_VIN_VOV_MASK));
value = FIELD_PREP(TPS1689_VIN_OV_RANGE_SEL_MASK, rng_idx - 1) |
FIELD_PREP(TPS1689_VIN_VOV_MASK, ov_set);
}
ret = pmbus_write_word_data(client, page, reg, value);
break;
case PMBUS_IIN_OC_FAULT_LIMIT:
value -= TPS25990_IIN_OCF_OFF;
value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV,
case PMBUS_IIN_OC_FAULT_LIMIT: {
int tmp;
if ((s16)value < 0)
return -EINVAL;
tmp = (int)value - TPS25990_IIN_OCF_OFF;
tmp = clamp_val(tmp, 0, INT_MAX);
value = DIV_ROUND_CLOSEST((unsigned int)tmp * TPS25990_IIN_OCF_DIV,
TPS25990_IIN_OCF_NUM);
value = clamp_val(value, 0, 0x3f);
ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value);
break;
}
case PMBUS_VIRT_SAMPLES:
value = clamp_val(value, 1, 1 << PK_MIN_AVG_AVG_CNT);
@ -337,63 +392,120 @@ static const struct regulator_desc tps25990_reg_desc[] = {
};
#endif
static const struct pmbus_driver_info tps25990_base_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.m[PSC_VOLTAGE_IN] = 5251,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = -2,
.format[PSC_VOLTAGE_OUT] = direct,
.m[PSC_VOLTAGE_OUT] = 5251,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = -2,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_TEMPERATURE] = 140,
.b[PSC_TEMPERATURE] = 32100,
.R[PSC_TEMPERATURE] = -2,
/*
* Current and Power measurement depends on the ohm value
* of Rimon. m is multiplied by 1000 below to have an integer
* and -3 is added to R to compensate.
*/
.format[PSC_CURRENT_IN] = direct,
.m[PSC_CURRENT_IN] = 9538,
.b[PSC_CURRENT_IN] = 0,
.R[PSC_CURRENT_IN] = -6,
.format[PSC_POWER] = direct,
.m[PSC_POWER] = 4901,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = -7,
.func[0] = (PMBUS_HAVE_VIN |
PMBUS_HAVE_VOUT |
PMBUS_HAVE_VMON |
PMBUS_HAVE_IIN |
PMBUS_HAVE_PIN |
PMBUS_HAVE_TEMP |
PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_SAMPLES),
.read_word_data = tps25990_read_word_data,
.write_word_data = tps25990_write_word_data,
.read_byte_data = tps25990_read_byte_data,
.write_byte_data = tps25990_write_byte_data,
static const struct pmbus_driver_info tps25990_base_info[] = {
[tps1689] = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.m[PSC_VOLTAGE_IN] = 3984,
.b[PSC_VOLTAGE_IN] = -63750,
.R[PSC_VOLTAGE_IN] = -3,
.format[PSC_VOLTAGE_OUT] = direct,
.m[PSC_VOLTAGE_OUT] = 1166,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = -2,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_TEMPERATURE] = 140,
.b[PSC_TEMPERATURE] = 32103,
.R[PSC_TEMPERATURE] = -2,
/*
* Current and Power measurement depends on the ohm value
* of Rimon. m is multiplied by 1000 below to have an integer
* and -3 is added to R to compensate.
*/
.format[PSC_CURRENT_IN] = direct,
.m[PSC_CURRENT_IN] = 9548,
.b[PSC_CURRENT_IN] = 0,
.R[PSC_CURRENT_IN] = -6,
.format[PSC_CURRENT_OUT] = direct,
.m[PSC_CURRENT_OUT] = 24347,
.b[PSC_CURRENT_OUT] = 0,
.R[PSC_CURRENT_OUT] = -3,
.format[PSC_POWER] = direct,
.m[PSC_POWER] = 2775,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = -4,
.func[0] = (PMBUS_HAVE_VIN |
PMBUS_HAVE_VOUT |
PMBUS_HAVE_VMON |
PMBUS_HAVE_IIN |
PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN |
PMBUS_HAVE_TEMP |
PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_SAMPLES),
.read_word_data = tps25990_read_word_data,
.write_word_data = tps25990_write_word_data,
.read_byte_data = tps25990_read_byte_data,
.write_byte_data = tps25990_write_byte_data,
#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR)
.reg_desc = tps25990_reg_desc,
.num_regulators = ARRAY_SIZE(tps25990_reg_desc),
.reg_desc = tps25990_reg_desc,
.num_regulators = ARRAY_SIZE(tps25990_reg_desc),
#endif
},
[tps25990] = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.m[PSC_VOLTAGE_IN] = 5251,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = -2,
.format[PSC_VOLTAGE_OUT] = direct,
.m[PSC_VOLTAGE_OUT] = 5251,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = -2,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_TEMPERATURE] = 140,
.b[PSC_TEMPERATURE] = 32100,
.R[PSC_TEMPERATURE] = -2,
/*
* Current and Power measurement depends on the ohm value
* of Rimon. m is multiplied by 1000 below to have an integer
* and -3 is added to R to compensate.
*/
.format[PSC_CURRENT_IN] = direct,
.m[PSC_CURRENT_IN] = 9538,
.b[PSC_CURRENT_IN] = 0,
.R[PSC_CURRENT_IN] = -6,
.format[PSC_POWER] = direct,
.m[PSC_POWER] = 4901,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = -7,
.func[0] = (PMBUS_HAVE_VIN |
PMBUS_HAVE_VOUT |
PMBUS_HAVE_VMON |
PMBUS_HAVE_IIN |
PMBUS_HAVE_PIN |
PMBUS_HAVE_TEMP |
PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_SAMPLES),
.read_word_data = tps25990_read_word_data,
.write_word_data = tps25990_write_word_data,
.read_byte_data = tps25990_read_byte_data,
.write_byte_data = tps25990_write_byte_data,
#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR)
.reg_desc = tps25990_reg_desc,
.num_regulators = ARRAY_SIZE(tps25990_reg_desc),
#endif
},
};
static const struct i2c_device_id tps25990_i2c_id[] = {
{ .name = "tps25990" },
{ }
{ .name = "tps1689", .driver_data = tps1689 },
{ .name = "tps25990", .driver_data = tps25990 },
{}
};
MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id);
static const struct of_device_id tps25990_of_match[] = {
{ .compatible = "ti,tps25990" },
{ .compatible = "ti,tps1689", .data = (void *)tps1689 },
{ .compatible = "ti,tps25990", .data = (void *)tps25990 },
{}
};
MODULE_DEVICE_TABLE(of, tps25990_of_match);
@ -401,8 +513,9 @@ MODULE_DEVICE_TABLE(of, tps25990_of_match);
static int tps25990_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct pmbus_driver_info *info;
struct tps25990_data *data;
const char *propname;
enum chips chip_id;
u32 rimon;
int ret;
@ -415,15 +528,21 @@ static int tps25990_probe(struct i2c_client *client)
rimon = TPS25990_DEFAULT_RIMON;
}
info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL);
if (!info)
chip_id = (enum chips)(unsigned long)i2c_get_match_data(client);
data = devm_kzalloc(dev, sizeof(struct tps25990_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/* Adapt the current and power scale for each instance */
tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon);
tps25990_set_m(&info->m[PSC_POWER], rimon);
data->info = tps25990_base_info[chip_id];
data->chip_id = chip_id;
return pmbus_do_probe(client, info);
/* Adapt the current and power scale for each instance */
tps25990_set_m(&data->info.m[PSC_CURRENT_IN], rimon);
tps25990_set_m(&data->info.m[PSC_CURRENT_OUT], rimon);
tps25990_set_m(&data->info.m[PSC_POWER], rimon);
return pmbus_do_probe(client, &data->info);
}
static struct i2c_driver tps25990_driver = {

View File

@ -16,13 +16,21 @@
#include "pmbus.h"
enum chips {
tps53647, tps53667, tps53676, tps53679, tps53681, tps53685, tps53688
tps53622, tps53647, tps53659, tps53667, tps53676, tps53679, tps53681,
tps53685, tps53688, tps536c7
};
#define TPS53647_PAGE_NUM 1
#define TPS53676_USER_DATA_03 0xb3
#define TPS536XX_PHASE_CONFIG 0xb3
#define TPS53676_MAX_PHASES 7
#define TPS536C7_MAX_PHASES 12
#define TPS536XX_PHASE_ENABLE BIT(7)
#define TPS536XX_PHASE_PAGE BIT(4)
#define TPS53676_DEVICE_ID "TI\x53\x67\x60\x00"
#define TPS536C7_DEVICE_ID "TI\x53\x6c\x70\x00"
#define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */
#define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */
@ -165,34 +173,72 @@ static int tps53681_identify(struct i2c_client *client,
TPS53681_DEVICE_ID);
}
static int tps53676_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
static int tps536xx_read_block(struct i2c_client *client, u8 reg, u8 *buf,
bool retry_without_pec)
{
int ret = i2c_smbus_read_block_data(client, reg, buf);
/*
* Some TPS536C7 samples return an invalid PEC on the device-ID and
* phase-configuration block reads. Retry once without PEC; ordinary
* telemetry keeps using PEC.
*/
if (ret == -EBADMSG && retry_without_pec &&
(client->flags & I2C_CLIENT_PEC)) {
client->flags &= ~I2C_CLIENT_PEC;
ret = i2c_smbus_read_block_data(client, reg, buf);
client->flags |= I2C_CLIENT_PEC;
}
return ret;
}
static int tps536xx_read_phases(struct i2c_client *client,
const char *device_id, int max_phases,
bool retry_without_pec, int *phases_a,
int *phases_b)
{
u8 buf[I2C_SMBUS_BLOCK_MAX];
int phases_a = 0, phases_b = 0;
int i, ret;
ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
ret = tps536xx_read_block(client, PMBUS_IC_DEVICE_ID, buf,
retry_without_pec);
if (ret < 0)
return ret;
if (ret != 6 || memcmp(buf, "TI\x53\x67\x60\x00", 6)) {
if (ret != 6 || memcmp(buf, device_id, 6)) {
dev_err(&client->dev, "Unexpected device ID: %*ph\n", ret, buf);
return -ENODEV;
}
ret = i2c_smbus_read_block_data(client, TPS53676_USER_DATA_03, buf);
ret = tps536xx_read_block(client, TPS536XX_PHASE_CONFIG, buf,
retry_without_pec);
if (ret < 0)
return ret;
if (ret != 24)
return -EIO;
for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) {
if (buf[i + 1] & 0x80) {
if (buf[i] & BIT(4))
phases_b++;
*phases_a = 0;
*phases_b = 0;
for (i = 0; i < 2 * max_phases; i += 2) {
if (buf[i + 1] & TPS536XX_PHASE_ENABLE) {
if (buf[i] & TPS536XX_PHASE_PAGE)
(*phases_b)++;
else
phases_a++;
(*phases_a)++;
}
}
return 0;
}
static int tps53676_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
int phases_a, phases_b, ret;
ret = tps536xx_read_phases(client, TPS53676_DEVICE_ID,
TPS53676_MAX_PHASES, false,
&phases_a, &phases_b);
if (ret)
return ret;
info->format[PSC_VOLTAGE_OUT] = linear;
info->pages = 1;
@ -213,6 +259,69 @@ static int tps53676_identify(struct i2c_client *client,
return 0;
}
static int tps536c7_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
int phases_a, phases_b, page, ret;
ret = tps536xx_read_phases(client, TPS536C7_DEVICE_ID,
TPS536C7_MAX_PHASES, true,
&phases_a, &phases_b);
if (ret)
return ret;
if (!phases_a) {
dev_err(&client->dev,
"TPS536C7 without channel A is not supported\n");
return -EOPNOTSUPP;
}
info->format[PSC_VOLTAGE_OUT] = linear;
/*
* TPS536C7 can place up to 12 phases on channel A, which exceeds
* PMBUS_PHASES. Report aggregate per-channel telemetry only and do
* not populate info->phases[].
*/
info->pages = phases_b ? 2 : 1;
/*
* pmbus_set_page() does not update the PAGE register on single-page
* devices, so select page 0 explicitly in case the boot firmware
* left the device on another page.
*/
if (info->pages == 1) {
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
if (ret < 0)
return ret;
}
/*
* With info->phases[] left unset the PMBus core never programs the
* PHASE selector, so make sure each page reports the aggregate
* current (PHASE = 0xff) rather than whatever a previous boot left.
*/
for (page = 0; page < info->pages; page++) {
ret = pmbus_read_byte_data(client, page, PMBUS_PHASE);
if (ret < 0)
return ret;
if (ret == 0xff)
continue;
ret = pmbus_write_byte_data(client, page, PMBUS_PHASE, 0xff);
if (ret < 0)
return ret;
/* PHASE may be write-protected; confirm it actually changed. */
ret = pmbus_read_byte_data(client, page, PMBUS_PHASE);
if (ret < 0)
return ret;
if (ret != 0xff) {
dev_err(&client->dev,
"failed to select aggregate PHASE on page %d\n",
page);
return -EIO;
}
}
return 0;
}
static int tps53681_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
@ -277,6 +386,8 @@ static int tps53679_probe(struct i2c_client *client)
case tps53676:
info->identify = tps53676_identify;
break;
case tps53622:
case tps53659:
case tps53679:
case tps53688:
info->pages = TPS53679_PAGE_NUM;
@ -292,6 +403,9 @@ static int tps53679_probe(struct i2c_client *client)
info->pages = TPS53679_PAGE_NUM;
info->identify = tps53685_identify;
break;
case tps536c7:
info->identify = tps536c7_identify;
break;
default:
return -ENODEV;
}
@ -301,26 +415,32 @@ static int tps53679_probe(struct i2c_client *client)
static const struct i2c_device_id tps53679_id[] = {
{ .name = "bmr474", .driver_data = tps53676 },
{ .name = "tps53622", .driver_data = tps53622 },
{ .name = "tps53647", .driver_data = tps53647 },
{ .name = "tps53659", .driver_data = tps53659 },
{ .name = "tps53667", .driver_data = tps53667 },
{ .name = "tps53676", .driver_data = tps53676 },
{ .name = "tps53679", .driver_data = tps53679 },
{ .name = "tps53681", .driver_data = tps53681 },
{ .name = "tps53685", .driver_data = tps53685 },
{ .name = "tps53688", .driver_data = tps53688 },
{ .name = "tps536c7", .driver_data = tps536c7 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tps53679_id);
static const struct of_device_id __maybe_unused tps53679_of_match[] = {
{.compatible = "ti,tps53622", .data = (void *)tps53622},
{.compatible = "ti,tps53647", .data = (void *)tps53647},
{.compatible = "ti,tps53659", .data = (void *)tps53659},
{.compatible = "ti,tps53667", .data = (void *)tps53667},
{.compatible = "ti,tps53676", .data = (void *)tps53676},
{.compatible = "ti,tps53679", .data = (void *)tps53679},
{.compatible = "ti,tps53681", .data = (void *)tps53681},
{.compatible = "ti,tps53685", .data = (void *)tps53685},
{.compatible = "ti,tps53688", .data = (void *)tps53688},
{.compatible = "ti,tps536c7", .data = (void *)tps536c7},
{}
};
MODULE_DEVICE_TABLE(of, tps53679_of_match);

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G5B
* and XDPE1A2G7B Controllers
* Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G5B,
* XDPE1A2G7B and XDPE1A2G7C Controllers
*
* Copyright (c) 2026 Infineon Technologies. All rights reserved.
*/
@ -23,7 +23,7 @@ static int xdpe1a2g7b_identify(struct i2c_client *client,
int vout_mode;
/*
* XDPE1A2G5B and XDPE1A2G7B support both Linear and NVIDIA PWM VID data
* XDPE1A2G5B and XDPE1A2G7B/7C support both Linear and NVIDIA PWM VID data
* formats via VOUT_MODE. Note that the device pages/loops are not fully
* independent: configuration is shared, so programming each page/loop
* separately is not supported.
@ -100,6 +100,7 @@ static int xdpe1a2g7b_probe(struct i2c_client *client)
static const struct i2c_device_id xdpe1a2g7b_id[] = {
{ .name = "xdpe1a2g5b" },
{ .name = "xdpe1a2g7b" },
{ .name = "xdpe1a2g7c" },
{ }
};
@ -108,6 +109,7 @@ MODULE_DEVICE_TABLE(i2c, xdpe1a2g7b_id);
static const struct of_device_id __maybe_unused xdpe1a2g7b_of_match[] = {
{ .compatible = "infineon,xdpe1a2g5b" },
{ .compatible = "infineon,xdpe1a2g7b" },
{ .compatible = "infineon,xdpe1a2g7c" },
{}
};
@ -125,6 +127,6 @@ static struct i2c_driver xdpe1a2g7b_driver = {
module_i2c_driver(xdpe1a2g7b_driver);
MODULE_AUTHOR("Ashish Yadav <ashish.yadav@infineon.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G5B/7B");
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G5B/7B/7C");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@ -611,8 +611,8 @@ static const struct of_device_id __maybe_unused pt5161l_of_match[] = {
MODULE_DEVICE_TABLE(of, pt5161l_of_match);
static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = {
{ "PT5161L", 0 },
{},
{ .id = "PT5161L" },
{ }
};
MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match);

View File

@ -619,7 +619,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
}
channels = devm_kcalloc(dev, channel_count + 1,
sizeof(struct hwmon_channel_info *), GFP_KERNEL);
sizeof(*channels), GFP_KERNEL);
if (!channels)
return -ENOMEM;

View File

@ -18,13 +18,14 @@
#include <linux/watchdog.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/stringify.h>
#include "sch56xx-common.h"
/* Insmod parameters */
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
__stringify(WATCHDOG_NOWAYOUT) ")");
#define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */
#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */

View File

@ -305,7 +305,8 @@ static int scmi_hwmon_probe(struct scmi_device *sdev)
scmi_sensors->info[type] =
devm_kcalloc(dev, nr_count[type],
sizeof(*scmi_sensors->info), GFP_KERNEL);
sizeof(*scmi_sensors->info[type]),
GFP_KERNEL);
if (!scmi_sensors->info[type])
return -ENOMEM;
}

View File

@ -543,7 +543,10 @@ static ssize_t heater_enable_store(struct device *dev,
mutex_unlock(&data->i2c_lock);
return ret;
if (ret != SHT3X_CMD_LENGTH)
return ret < 0 ? ret : -EIO;
return count;
}
static int update_interval_read(struct device *dev)

View File

@ -43,6 +43,7 @@
#define SHT4X_CRC8_LEN 1
#define SHT4X_WORD_LEN 2
#define SHT4X_RESPONSE_LENGTH 6
#define STS4X_RESPONSE_LENGTH 3
#define SHT4X_CRC8_POLYNOMIAL 0x31
#define SHT4X_CRC8_INIT 0xff
#define SHT4X_MIN_TEMPERATURE -45000
@ -52,9 +53,15 @@
DECLARE_CRC8_TABLE(sht4x_crc8_table);
enum sht4x_chips {
sht4x,
sts4x,
};
/**
* struct sht4x_data - All the data required to operate an SHT4X chip
* @client: the i2c client associated with the SHT4X
* @chip_id: the chip type (sht4x or sts4x)
* @heating_complete: the time that the last heating finished
* @data_pending: true if and only if there are measurements to retrieve after heating
* @heater_power: the power at which the heater will be started
@ -67,7 +74,8 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table);
*/
struct sht4x_data {
struct i2c_client *client;
unsigned long heating_complete; /* in jiffies */
enum sht4x_chips chip_id;
u64 heating_complete; /* in jiffies */
bool data_pending;
u32 heater_power; /* in milli-watts */
u32 heater_time; /* in milli-seconds */
@ -92,14 +100,18 @@ static int sht4x_read_values(struct sht4x_data *data)
u8 crc;
u8 cmd[SHT4X_CMD_LEN] = {SHT4X_CMD_MEASURE_HPM};
u8 raw_data[SHT4X_RESPONSE_LENGTH];
unsigned long curr_jiffies;
size_t response_length = data->chip_id == sts4x ?
STS4X_RESPONSE_LENGTH : SHT4X_RESPONSE_LENGTH;
curr_jiffies = jiffies;
if (time_before(curr_jiffies, data->heating_complete))
msleep(jiffies_to_msecs(data->heating_complete - curr_jiffies));
if (data->chip_id != sts4x) {
u64 curr_jiffies = get_jiffies_64();
if (time_before64(curr_jiffies, data->heating_complete))
msleep(jiffies_to_msecs(data->heating_complete - curr_jiffies));
}
if (data->data_pending &&
time_before(jiffies, data->heating_complete + data->update_interval)) {
time_before64(get_jiffies_64(), data->heating_complete + data->update_interval)) {
data->data_pending = false;
} else {
next_update = data->last_updated +
@ -115,15 +127,14 @@ static int sht4x_read_values(struct sht4x_data *data)
usleep_range(SHT4X_MEAS_DELAY_HPM, SHT4X_MEAS_DELAY_HPM + SHT4X_DELAY_EXTRA);
}
ret = i2c_master_recv(client, raw_data, SHT4X_RESPONSE_LENGTH);
if (ret != SHT4X_RESPONSE_LENGTH) {
ret = i2c_master_recv(client, raw_data, response_length);
if (ret != response_length) {
if (ret >= 0)
ret = -ENODATA;
return ret;
}
t_ticks = raw_data[0] << 8 | raw_data[1];
rh_ticks = raw_data[3] << 8 | raw_data[4];
crc = crc8(sht4x_crc8_table, &raw_data[0], SHT4X_WORD_LEN, CRC8_INIT_VALUE);
if (crc != raw_data[2]) {
@ -131,14 +142,19 @@ static int sht4x_read_values(struct sht4x_data *data)
return -EIO;
}
crc = crc8(sht4x_crc8_table, &raw_data[3], SHT4X_WORD_LEN, CRC8_INIT_VALUE);
if (crc != raw_data[5]) {
dev_err(&client->dev, "data integrity check failed\n");
return -EIO;
data->temperature = ((21875 * (int32_t)t_ticks) >> 13) - 45000;
if (data->chip_id != sts4x) {
rh_ticks = raw_data[3] << 8 | raw_data[4];
crc = crc8(sht4x_crc8_table, &raw_data[3], SHT4X_WORD_LEN, CRC8_INIT_VALUE);
if (crc != raw_data[5]) {
dev_err(&client->dev, "data integrity check failed\n");
return -EIO;
}
data->humidity = ((15625 * (int32_t)rh_ticks) >> 13) - 6000;
}
data->temperature = ((21875 * (int32_t)t_ticks) >> 13) - 45000;
data->humidity = ((15625 * (int32_t)rh_ticks) >> 13) - 6000;
data->last_updated = jiffies;
data->valid = true;
return 0;
@ -190,9 +206,14 @@ static umode_t sht4x_hwmon_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct sht4x_data *chip_data = data;
switch (type) {
case hwmon_temp:
return 0444;
case hwmon_humidity:
if (chip_data->chip_id == sts4x)
return 0;
return 0444;
case hwmon_chip:
return 0644;
@ -237,7 +258,9 @@ static ssize_t heater_enable_show(struct device *dev,
{
struct sht4x_data *data = dev_get_drvdata(dev);
return sysfs_emit(buf, "%u\n", time_before(jiffies, data->heating_complete));
guard(hwmon_lock)(dev);
return sysfs_emit(buf, "%u\n", time_before64(get_jiffies_64(), data->heating_complete));
}
static ssize_t heater_enable_store(struct device *dev,
@ -279,14 +302,16 @@ static ssize_t heater_enable_store(struct device *dev,
guard(hwmon_lock)(dev);
if (time_before(jiffies, data->heating_complete))
if (time_before64(get_jiffies_64(), data->heating_complete))
return -EBUSY;
ret = i2c_master_send(data->client, &cmd, SHT4X_CMD_LEN);
if (ret < 0)
return ret;
if (ret != SHT4X_CMD_LEN)
return -EIO;
data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound);
data->heating_complete = get_jiffies_64() + msecs_to_jiffies(heating_time_bound);
data->data_pending = true;
return count;
}
@ -388,6 +413,7 @@ static const struct hwmon_chip_info sht4x_chip_info = {
static int sht4x_probe(struct i2c_client *client)
{
const struct attribute_group **groups = NULL;
struct device *device = &client->dev;
struct device *hwmon_dev;
struct sht4x_data *data;
@ -406,11 +432,15 @@ static int sht4x_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
data->chip_id = (uintptr_t)i2c_get_match_data(client);
data->update_interval = SHT4X_MIN_POLL_INTERVAL;
data->client = client;
data->heater_power = 200;
data->heater_time = 1000;
data->heating_complete = jiffies;
if (data->chip_id != sts4x) {
data->heater_power = 200;
data->heater_time = 1000;
data->heating_complete = get_jiffies_64();
groups = sht4x_groups;
}
crc8_populate_msb(sht4x_crc8_table, SHT4X_CRC8_POLYNOMIAL);
@ -424,19 +454,21 @@ static int sht4x_probe(struct i2c_client *client)
client->name,
data,
&sht4x_chip_info,
sht4x_groups);
groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id sht4x_id[] = {
{ .name = "sht4x" },
{ .name = "sht4x", .driver_data = sht4x },
{ .name = "sts4x", .driver_data = sts4x },
{ }
};
MODULE_DEVICE_TABLE(i2c, sht4x_id);
static const struct of_device_id sht4x_of_match[] = {
{ .compatible = "sensirion,sht4x" },
{ .compatible = "sensirion,sht4x", .data = (void *)sht4x },
{ .compatible = "sensirion,sts40", .data = (void *)sts4x },
{ }
};
MODULE_DEVICE_TABLE(of, sht4x_of_match);

View File

@ -433,6 +433,26 @@ static const struct socfpga_hwmon_board_data agilex_hwmon_board = {
.num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels),
};
/*
* Agilex 5 exposes the SDM and three corner temperature sensors. Channel 2
* (top-left corner on Agilex) is not present in hardware, so the SDM channel
* numbering keeps the gap (0, 1, 3, 4) rather than renumbering.
*/
static const struct socfpga_hwmon_channel agilex5_hwmon_temp_channels[] = {
{ SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" },
{ SOCFPGA_HWMON_CHAN(1, 0), "Main Die corner bottom left max" },
{ SOCFPGA_HWMON_CHAN(3, 0), "Main Die corner bottom right max" },
{ SOCFPGA_HWMON_CHAN(4, 0), "Main Die corner top right max" },
};
/* Agilex 5 reuses the Agilex voltage SDM page/channel encoding and labels. */
static const struct socfpga_hwmon_board_data agilex5_hwmon_board = {
.temp = agilex5_hwmon_temp_channels,
.num_temp = ARRAY_SIZE(agilex5_hwmon_temp_channels),
.volt = agilex_hwmon_volt_channels,
.num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels),
};
static const struct socfpga_hwmon_board_data *
socfpga_hwmon_get_board(struct device *dev)
{
@ -443,6 +463,8 @@ socfpga_hwmon_get_board(struct device *dev)
if (of_device_is_compatible(np, "intel,stratix10-svc"))
return &s10_hwmon_board;
if (of_device_is_compatible(np, "intel,agilex5-svc"))
return &agilex5_hwmon_board;
if (of_device_is_compatible(np, "intel,agilex-svc"))
return &agilex_hwmon_board;

View File

@ -16,6 +16,7 @@
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/i3c/device.h>
@ -637,44 +638,54 @@ static int spd5118_i2c_init(struct i2c_client *client)
I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
/* Early check to avoid obviously unsupported I2C devices */
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
if (regval < 0 || (regval && regval != 0x5118))
if (regval < 0)
return regval;
/*
* Some SPD5118 devices report 0x0 when page 0 is not selected,
* so we only fail here if the register value is not 0x0 or 0x5118.
*/
if (regval && regval != 0x5118)
return -ENODEV;
/*
* If the device type registers return 0, it is possible that the chip
* has a non-zero page selected and takes the specification literally,
* We must select page 0 to ensure that we can reliably read the volatile
* registers on chips that take the specification literally,
* i.e. disables access to volatile registers besides the page register
* if the page is not 0. The Renesas/ITD SPD5118 Hub Controller is known
* to show this behavior. Try to identify such chips.
* to show this behavior.
*
* We must also perform an unconditional register write to detect if
* the i2c controller blocks write accesses to the SPD device. Some Intel
* controllers might be configured by the BIOS to do this.
*/
if (!regval) {
/* Vendor ID registers must also be 0 */
regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
if (regval)
return -ENODEV;
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
if (mode < 0)
return mode;
/* The selected page in MR11 must not be 0 */
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
if (mode < 0 || (mode & ~SPD5118_LEGACY_MODE_MASK) ||
!(mode & SPD5118_LEGACY_PAGE_MASK))
return -ENODEV;
/* 16-bit addressing is not supported */
if (mode & SPD5118_LEGACY_MODE_ADDR) {
dev_notice(&client->dev,
"Unable to access device due to 16-bit addressing being enabled\n");
return -ENODEV;
}
err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE,
mode & SPD5118_LEGACY_MODE_ADDR);
if (err)
return -ENODEV;
err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE,
mode & ~SPD5118_LEGACY_PAGE_MASK);
if (err < 0)
return err;
/*
* If the device type registers are still bad after selecting
* page 0, this is not a SPD5118 device. Restore original
* legacy mode register value and abort.
*/
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
if (regval != 0x5118) {
i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode);
return -ENODEV;
}
/* We only need to access SPD5118_REG_TYPE again if regval was 0x0 */
if (regval == 0x5118)
return 0;
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
if (regval != 0x5118) {
/* Restore original register content */
i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode);
return -ENODEV;
}
/* We are reasonably sure that this is really a SPD5118 hub controller */

View File

@ -58,7 +58,7 @@ struct tmp102 {
const char *label;
struct regmap *regmap;
u16 config_orig;
unsigned long ready_time;
u64 ready_time;
u16 sample_time;
};
@ -106,7 +106,7 @@ static int tmp102_read_temp(struct device *dev, u32 attr, long *val)
switch (attr) {
case hwmon_temp_input:
/* Is it too early to return a conversion ? */
if (time_before(jiffies, tmp102->ready_time)) {
if (time_before64(get_jiffies_64(), tmp102->ready_time)) {
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
return -EAGAIN;
}
@ -354,7 +354,7 @@ static int tmp102_probe(struct i2c_client *client)
* Mark that we are not ready with data until the first
* conversion is complete
*/
tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS);
tmp102->ready_time = get_jiffies_64() + msecs_to_jiffies(CONVERSION_TIME_MS);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
tmp102,
@ -387,7 +387,7 @@ static int tmp102_resume(struct device *dev)
err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
TMP102_CONF_SD, 0);
tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS);
tmp102->ready_time = get_jiffies_64() + msecs_to_jiffies(CONVERSION_TIME_MS);
return err;
}

Some files were not shown because too many files have changed in this diff Show More