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

Pull hwmon updates from Guenter Roeck:
 "New drivers:
   - Lenovo Yoga/Legion fan monitoring (yogafan)
   - LattePanda Sigma EC
   - Infineon XDP720 eFuse
   - Microchip MCP998X

  New device support:
   - TI INA234
   - Infineon XDPE1A2G5B/7B
   - Renesas RAA228942 and RAA228943 (isl68137)
   - Delta Q54SN120A1 and Q54SW120A7 (pmbus)
   - TI TMP110 and TMP113 (tmp102)
   - Sony APS-379 (pmbus)
   - ITE IT8689E (it87)
   - ASUS ROG STRIX Z790-H, X470-F, and CROSSHAIR X670E (asus-ec-sensors)
   - GPD Win 5 (gpd-fan)

  Modernization and Cleanups:
   - Convert asus_atk0110 and acpi_power_meter ACPI drivers to platform
     drivers
   - Remove i2c_match_id() usage in many PMBus drivers
   - Use guard() for mutex protection in pmbus_core
   - Replace sprintf() with sysfs_emit() in ads7871, emc1403, max6650,
     ads7828, max31722, and tc74
   - Various markup and documentation improvements for yogafan and
     ltc4282

  Bug fixes:
   - Fix use-after-free and missing usb_kill_urb on disconnect in powerz
     driver
   - Avoid cacheline sharing for DMA buffer in powerz driver
   - Fix integer overflow in power calculation on 32-bit in isl28022
     driver
   - Fix bugs in pt5161l_read_block_data()
   - Propagate SPI errors and fix incorrect error codes in ads7871
     driver
   - Fix i2c_smbus_write_byte_data wrapper argument type in max31785
     driver

  Device tree bindings:
   - Convert npcm750-pwm-fan to DT schema
   - Add bindings for Infineon XDP720, Microchip MCP998X, Sony APS-379,
     Renesas RAA228942/3, Delta Q54SN120A1/7, XDPE1A2G5B/7B, Aosong
     AHT10/20, DHT20, and TI INA234
   - Adapt moortec,mr75203 bindings for T-Head TH1520"

* tag 'hwmon-for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (82 commits)
  hwmon: (ina233) Don't check for specific errors when parsing properties
  hwmon: (isl28022) Don't check for specific errors when parsing properties
  hwmon: (pmbus/tps25990) Don't check for specific errors when parsing properties
  hwmon: (nct6683) Add customer ID for ASRock B650I Lightning WiFi
  hwmon:(pmbus/xdp720) Add support for efuse xdp720
  dt-bindings: hwmon/pmbus: Add Infineon XDP720
  hwmon: add support for MCP998X
  dt-bindings: hwmon: add support for MCP998X
  hwmon: (powerz) Avoid cacheline sharing for DMA buffer
  hwmon: (isl28022) Fix integer overflow in power calculation on 32-bit
  hwmon: (pt5161l) Fix bugs in pt5161l_read_block_data()
  hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
  hwmon: (powerz) Fix use-after-free on USB disconnect
  hwmon: pmbus: Add support for Sony APS-379
  dt-bindings: trivial-devices: Add sony,aps-379
  hwmon: (yogafan) various markup improvements
  hwmon: (sparx5) Make it selectable for ARCH_LAN969X
  hwmon: (tmp102) add support for update interval
  hwmon: (yogafan) fix markup warning
  hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
  ...
This commit is contained in:
Linus Torvalds
2026-04-15 14:37:32 -07:00
76 changed files with 3857 additions and 2479 deletions

View File

@@ -1,105 +0,0 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/baikal,bt1-pvt.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Baikal-T1 PVT Sensor
maintainers:
- Serge Semin <fancer.lancer@gmail.com>
description: |
Baikal-T1 SoC provides an embedded process, voltage and temperature
sensor to monitor an internal SoC environment (chip temperature, supply
voltage and process monitor) and on time detect critical situations,
which may cause the system instability and even damages. The IP-block
is based on the Analog Bits PVT sensor, but is equipped with a dedicated
control wrapper, which provides a MMIO registers-based access to the
sensor core functionality (APB3-bus based) and exposes an additional
functions like thresholds/data ready interrupts, its status and masks,
measurements timeout. Its internal structure is depicted on the next
diagram:
Analog Bits core Bakal-T1 PVT control block
+--------------------+ +------------------------+
| Temperature sensor |-+ +------| Sensors control |
|--------------------| |<---En---| |------------------------|
| Voltage sensor |-|<--Mode--| +--->| Sampled data |
|--------------------| |<--Trim--+ | |------------------------|
| Low-Vt sensor |-| | +--| Thresholds comparator |
|--------------------| |---Data----| | |------------------------|
| High-Vt sensor |-| | +->| Interrupts status |
|--------------------| |--Valid--+-+ | |------------------------|
| Standard-Vt sensor |-+ +---+--| Interrupts mask |
+--------------------+ |------------------------|
^ | Interrupts timeout |
| +------------------------+
| ^ ^
Rclk-----+----------------------------------------+ |
APB3-------------------------------------------------+
This bindings describes the external Baikal-T1 PVT control interfaces
like MMIO registers space, interrupt request number and clocks source.
These are then used by the corresponding hwmon device driver to
implement the sysfs files-based access to the sensors functionality.
properties:
compatible:
const: baikal,bt1-pvt
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
items:
- description: PVT reference clock
- description: APB3 interface clock
clock-names:
items:
- const: ref
- const: pclk
"#thermal-sensor-cells":
description: Baikal-T1 can be referenced as the CPU thermal-sensor
const: 0
baikal,pvt-temp-offset-millicelsius:
description: |
Temperature sensor trimming factor. It can be used to manually adjust the
temperature measurements within 7.130 degrees Celsius.
default: 0
minimum: 0
maximum: 7130
additionalProperties: false
required:
- compatible
- reg
- interrupts
- clocks
- clock-names
examples:
- |
#include <dt-bindings/interrupt-controller/mips-gic.h>
pvt@1f200000 {
compatible = "baikal,bt1-pvt";
reg = <0x1f200000 0x1000>;
#thermal-sensor-cells = <0>;
interrupts = <GIC_SHARED 31 IRQ_TYPE_LEVEL_HIGH>;
baikal,pvt-temp-offset-millicelsius = <1000>;
clocks = <&ccu_sys>, <&ccu_sys>;
clock-names = "ref", "pclk";
};
...

View File

@@ -0,0 +1,237 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/microchip,mcp9982.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Microchip MCP998X/33 and MCP998XD/33D Temperature Monitor
maintainers:
- Victor Duicu <victor.duicu@microchip.com>
description: |
The MCP998X/33 and MCP998XD/33D family is a high-accuracy 2-wire
multichannel automotive temperature monitor.
The datasheet can be found here:
https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
properties:
compatible:
enum:
- microchip,mcp9933
- microchip,mcp9933d
- microchip,mcp9982
- microchip,mcp9982d
- microchip,mcp9983
- microchip,mcp9983d
- microchip,mcp9984
- microchip,mcp9984d
- microchip,mcp9985
- microchip,mcp9985d
reg:
maxItems: 1
interrupts:
minItems: 1
maxItems: 2
interrupt-names:
description:
The chip family has three different interrupt pins divided among them.
The chips without "D" have alert-therm and therm-addr.
The chips with "D" have alert-therm and sys-shtdwn.
minItems: 1
items:
- enum: [alert-therm, therm-addr, sys-shtdwn]
- enum: [therm-addr, sys-shtdwn]
"#address-cells":
const: 1
"#size-cells":
const: 0
microchip,enable-anti-parallel:
description:
Enable anti-parallel diode mode operation.
MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes
in anti-parallel connection on the same set of pins.
type: boolean
microchip,parasitic-res-on-channel1-2:
description:
Indicates that the chip and the diodes/transistors are sufficiently far
apart that a parasitic resistance is added to the wires, which can affect
the measurements. Due to the anti-parallel diode connections, channels
1 and 2 are affected together.
type: boolean
microchip,parasitic-res-on-channel3-4:
description:
Indicates that the chip and the diodes/transistors are sufficiently far
apart that a parasitic resistance is added to the wires, which can affect
the measurements. Due to the anti-parallel diode connections, channels
3 and 4 are affected together.
type: boolean
microchip,power-state:
description:
The chip can be set in Run state or Standby state. In Run state the ADC
is converting on all channels at the programmed conversion rate.
In Standby state the host must initiate a conversion cycle by writing
to the One-Shot register.
True value sets Run state.
Chips with "D" in the name can only be set in Run mode.
type: boolean
vdd-supply: true
patternProperties:
"^channel@[1-4]$":
description:
Represents the external temperature channels to which
a remote diode is connected.
type: object
properties:
reg:
items:
maxItems: 1
label:
description: Unique name to identify which channel this is.
required:
- reg
additionalProperties: false
required:
- compatible
- reg
- vdd-supply
allOf:
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9982d
- microchip,mcp9983d
- microchip,mcp9984d
- microchip,mcp9985d
- microchip,mcp9933d
then:
properties:
interrupt-names:
items:
enum:
- alert-therm
- sys-shtdwn
required:
- microchip,power-state
- microchip,parasitic-res-on-channel1-2
else:
properties:
microchip,power-state: true
interrupt-names:
items:
enum:
- alert-therm
- therm-addr
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9983d
- microchip,mcp9984d
- microchip,mcp9985d
then:
required:
- microchip,parasitic-res-on-channel3-4
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9982
- microchip,mcp9982d
then:
properties:
microchip,enable-anti-parallel: false
patternProperties:
"^channel@[2-4]$": false
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9983
- microchip,mcp9983d
then:
properties:
microchip,enable-anti-parallel: false
patternProperties:
"^channel@[3-4]$": false
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9933
- microchip,mcp9933d
then:
patternProperties:
"^channel@[3-4]$": false
- if:
properties:
compatible:
contains:
enum:
- microchip,mcp9984
- microchip,mcp9984d
then:
properties:
channel@4: false
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@4c {
compatible = "microchip,mcp9985";
reg = <0x4c>;
#address-cells = <1>;
#size-cells = <0>;
microchip,enable-anti-parallel;
microchip,parasitic-res-on-channel1-2;
microchip,parasitic-res-on-channel3-4;
vdd-supply = <&vdd>;
channel@1 {
reg = <1>;
label = "Room Temperature";
};
channel@2 {
reg = <2>;
label = "GPU Temperature";
};
};
};
...

View File

@@ -105,7 +105,7 @@ properties:
G coefficient for temperature equation.
Default for series 5 = 60000
Default for series 6 = 57400
multipleOf: 100
multipleOf: 10
minimum: 1000
$ref: /schemas/types.yaml#/definitions/uint32
@@ -131,7 +131,7 @@ properties:
J coefficient for temperature equation.
Default for series 5 = -100
Default for series 6 = 0
multipleOf: 100
multipleOf: 10
maximum: 0
$ref: /schemas/types.yaml#/definitions/int32

View File

@@ -1,88 +0,0 @@
Nuvoton NPCM PWM and Fan Tacho controller device
The Nuvoton BMC NPCM7XX supports 8 Pulse-width modulation (PWM)
controller outputs and 16 Fan tachometer controller inputs.
The Nuvoton BMC NPCM8XX supports 12 Pulse-width modulation (PWM)
controller outputs and 16 Fan tachometer controller inputs.
Required properties for pwm-fan node
- #address-cells : should be 1.
- #size-cells : should be 0.
- compatible : "nuvoton,npcm750-pwm-fan" for Poleg NPCM7XX.
: "nuvoton,npcm845-pwm-fan" for Arbel NPCM8XX.
- reg : specifies physical base address and size of the registers.
- reg-names : must contain:
* "pwm" for the PWM registers.
* "fan" for the Fan registers.
- clocks : phandle of reference clocks.
- clock-names : must contain
* "pwm" for PWM controller operating clock.
* "fan" for Fan controller operating clock.
- interrupts : contain the Fan interrupts with flags for falling edge.
- pinctrl-names : a pinctrl state named "default" must be defined.
- pinctrl-0 : phandle referencing pin configuration of the PWM and Fan
controller ports.
fan subnode format:
===================
Under fan subnode can be upto 8 child nodes, each child node representing a fan.
Each fan subnode must have one PWM channel and at least one Fan tach channel.
For PWM channel can be configured cooling-levels to create cooling device.
Cooling device could be bound to a thermal zone for the thermal control.
Required properties for each child node:
- reg : specify the PWM output channel.
integer value in the range 0 through 7, that represent
the PWM channel number that used.
- fan-tach-ch : specify the Fan tach input channel.
integer value in the range 0 through 15, that represent
the fan tach channel number that used.
At least one Fan tach input channel is required
Optional property for each child node:
- cooling-levels: PWM duty cycle values in a range from 0 to 255
which correspond to thermal cooling states.
Examples:
pwm_fan:pwm-fan-controller@103000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "nuvoton,npcm750-pwm-fan";
reg = <0x103000 0x2000>,
<0x180000 0x8000>;
reg-names = "pwm", "fan";
clocks = <&clk NPCM7XX_CLK_APB3>,
<&clk NPCM7XX_CLK_APB4>;
clock-names = "pwm","fan";
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pins &pwm1_pins &pwm2_pins
&fanin0_pins &fanin1_pins &fanin2_pins
&fanin3_pins &fanin4_pins>;
fan@0 {
reg = <0x00>;
fan-tach-ch = /bits/ 8 <0x00 0x01>;
cooling-levels = <127 255>;
};
fan@1 {
reg = <0x01>;
fan-tach-ch = /bits/ 8 <0x02 0x03>;
};
fan@2 {
reg = <0x02>;
fan-tach-ch = /bits/ 8 <0x04>;
};
};

View File

@@ -0,0 +1,139 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/nuvoton,npcm750-pwm-fan.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Nuvoton NPCM7xx/NPCM8xx PWM and Fan Tach Controller
maintainers:
- Tomer Maimon <tmaimon77@gmail.com>
description:
The NPCM7xx/NPCM8xx family includes a PWM and Fan Tachometer controller.
The controller provides up to 8 (NPCM7xx) or 12 (NPCM8xx) PWM channels and up
to 16 tachometer inputs. It is used for fan speed control and monitoring.
properties:
compatible:
enum:
- nuvoton,npcm750-pwm-fan
- nuvoton,npcm845-pwm-fan
reg:
maxItems: 2
description: Register addresses for PWM and Fan Tach units.
reg-names:
items:
- const: pwm
- const: fan
clocks:
maxItems: 2
description: Clocks for the PWM and Fan Tach modules.
clock-names:
items:
- const: pwm
- const: fan
interrupts:
description:
Contains the Fan interrupts with flags for falling edge.
For NPCM7XX, 8 interrupt lines are expected (one per PWM channel).
For NPCM8XX, 12 interrupt lines are expected (one per PWM channel).
minItems: 8
maxItems: 12
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^fan@[0-9a-f]+$":
type: object
$ref: fan-common.yaml#
unevaluatedProperties: false
properties:
reg:
description:
Specify the PWM output channel. Integer value in the range 0-7 for
NPCM7XX or 0-11 for NPCM8XX, representing the PWM channel number.
maximum: 11
fan-tach-ch:
$ref: /schemas/types.yaml#/definitions/uint8-array
description:
The tach channel(s) used for the fan.
Integer values in the range 0-15.
items:
maximum: 15
cooling-levels:
description:
PWM duty cycle values in a range from 0 to 255 which
correspond to thermal cooling states. This property enables
thermal zone integration for automatic fan speed control
based on temperature.
items:
maximum: 255
required:
- reg
- fan-tach-ch
required:
- compatible
- reg
- reg-names
- clocks
- clock-names
- interrupts
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/nuvoton,npcm7xx-clock.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
pwm_fan: pwm-fan@103000 {
compatible = "nuvoton,npcm750-pwm-fan";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x103000 0x2000>, <0x180000 0x8000>;
reg-names = "pwm", "fan";
clocks = <&clk NPCM7XX_CLK_APB3>, <&clk NPCM7XX_CLK_APB4>;
clock-names = "pwm", "fan";
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pins &fanin0_pins>;
fan@0 {
reg = <0>;
fan-tach-ch = <0 1>;
cooling-levels = <64 128 192 255>;
};
fan@1 {
reg = <1>;
fan-tach-ch = <2>;
};
};

View File

@@ -0,0 +1,59 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/pmbus/infineon,xdp720.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Infineon XDP720 Digital eFuse Controller
maintainers:
- Ashish Yadav <ashish.yadav@infineon.com>
description: |
The XDP720 is an eFuse with integrated current sensor and digital
controller. It provides accurate system telemetry (V, I, P, T) and
reports analog current at the IMON pin for post-processing.
Datasheet:
https://www.infineon.com/assets/row/public/documents/24/49/infineon-xdp720-001-datasheet-en.pdf
properties:
compatible:
enum:
- infineon,xdp720
reg:
maxItems: 1
infineon,rimon-micro-ohms:
description:
The value of the RIMON resistor, in micro ohms, required to enable
the system overcurrent protection.
vdd-vin-supply:
description:
Supply for the VDD_VIN pin (pin 9), the IC controller power supply.
Typically connected to the input bus (VIN) through a 100 ohm / 100 nF
RC filter.
required:
- compatible
- reg
- vdd-vin-supply
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
hwmon@11 {
compatible = "infineon,xdp720";
reg = <0x11>;
vdd-vin-supply = <&vdd_vin>;
infineon,rimon-micro-ohms = <1098000000>; /* 1.098k ohm */
};
};

View File

@@ -16,49 +16,56 @@ description: |
properties:
compatible:
enum:
- isil,isl68137
- renesas,isl68220
- renesas,isl68221
- renesas,isl68222
- renesas,isl68223
- renesas,isl68224
- renesas,isl68225
- renesas,isl68226
- renesas,isl68227
- renesas,isl68229
- renesas,isl68233
- renesas,isl68239
- renesas,isl69222
- renesas,isl69223
- renesas,isl69224
- renesas,isl69225
- renesas,isl69227
- renesas,isl69228
- renesas,isl69234
- renesas,isl69236
- renesas,isl69239
- renesas,isl69242
- renesas,isl69243
- renesas,isl69247
- renesas,isl69248
- renesas,isl69254
- renesas,isl69255
- renesas,isl69256
- renesas,isl69259
- isil,isl69260
- renesas,isl69268
- isil,isl69269
- renesas,isl69298
- renesas,raa228000
- renesas,raa228004
- renesas,raa228006
- renesas,raa228228
- renesas,raa228244
- renesas,raa228246
- renesas,raa229001
- renesas,raa229004
- renesas,raa229621
oneOf:
- enum:
- isil,isl68137
- renesas,isl68220
- renesas,isl68221
- renesas,isl68222
- renesas,isl68223
- renesas,isl68224
- renesas,isl68225
- renesas,isl68226
- renesas,isl68227
- renesas,isl68229
- renesas,isl68233
- renesas,isl68239
- renesas,isl69222
- renesas,isl69223
- renesas,isl69224
- renesas,isl69225
- renesas,isl69227
- renesas,isl69228
- renesas,isl69234
- renesas,isl69236
- renesas,isl69239
- renesas,isl69242
- renesas,isl69243
- renesas,isl69247
- renesas,isl69248
- renesas,isl69254
- renesas,isl69255
- renesas,isl69256
- renesas,isl69259
- isil,isl69260
- renesas,isl69268
- isil,isl69269
- renesas,isl69298
- renesas,raa228000
- renesas,raa228004
- renesas,raa228006
- renesas,raa228228
- renesas,raa228244
- renesas,raa228246
- renesas,raa229001
- renesas,raa229004
- renesas,raa229621
- items:
- enum:
- renesas,raa228942
- renesas,raa228943
- const: renesas,raa228244
reg:
maxItems: 1

View File

@@ -29,6 +29,7 @@ properties:
- ti,ina230
- ti,ina231
- ti,ina233
- ti,ina234
- ti,ina237
- ti,ina238
- ti,ina260
@@ -113,6 +114,7 @@ allOf:
- ti,ina228
- ti,ina230
- ti,ina231
- ti,ina234
- ti,ina237
- ti,ina238
- ti,ina260
@@ -134,6 +136,7 @@ allOf:
- ti,ina226
- ti,ina230
- ti,ina231
- ti,ina234
- ti,ina260
- ti,ina700
- ti,ina780

View File

@@ -59,6 +59,10 @@ properties:
- adi,lt7182s
# AMS iAQ-Core VOC Sensor
- ams,iaq-core
# Aosong temperature & humidity sensors with I2C interface
- aosong,aht10
- aosong,aht20
- aosong,dht20
# Arduino microcontroller interface over SPI on UnoQ board
- arduino,unoq-mcu
# Temperature monitoring of Astera Labs PT5161L PCIe retimer
@@ -97,6 +101,10 @@ properties:
- delta,dps920ab
# 1/4 Brick DC/DC Regulated Power Module
- delta,q54sj108a2
# 1300W 1/4 Brick DC/DC Regulated Power Module
- delta,q54sn120a1
# 2000W 1/4 Brick DC/DC Regulated Power Module
- delta,q54sw120a7
# Devantech SRF02 ultrasonic ranger in I2C mode
- devantech,srf02
# Devantech SRF08 ultrasonic ranger
@@ -157,6 +165,9 @@ properties:
- infineon,xdpe15284
# Infineon Multi-phase Digital VR Controller xdpe152c4
- infineon,xdpe152c4
# Infineon Multi-phase Digital VR Controller xdpe1a2g7b
- infineon,xdpe1a2g5b
- infineon,xdpe1a2g7b
# Injoinic IP5108 2.0A Power Bank IC with I2C
- injoinic,ip5108
# Injoinic IP5109 2.1A Power Bank IC with I2C
@@ -430,6 +441,8 @@ properties:
- smsc,emc6d103s
# Socionext Uniphier SMP control registers
- socionext,uniphier-smpctrl
# Sony APS-379 Power Supply
- sony,aps-379
# SparkFun Qwiic Joystick (COM-15168) with i2c interface
- sparkfun,qwiic-joystick
# STMicroelectronics Hot-swap controller stef48h28

View File

@@ -0,0 +1,57 @@
Kernel driver aps-379
=====================
Supported chips:
* Sony APS-379
Prefix: 'aps-379'
Addresses scanned: -
Authors:
- Chris Packham
Description
-----------
This driver implements support for the PMBus monitor on the Sony APS-379
modular power supply. The APS-379 deviates from the PMBus standard for the
READ_VOUT command by using the linear11 format instead of linear16.
The known supported PMBus commands are:
=== ============================= ========= ======= =====
Cmd Function Protocol Scaling Bytes
=== ============================= ========= ======= =====
01 On / Off Command (OPERATION) Byte R/W -- 1
10 WRITE_PROTECT Byte R/W -- 1
3B FAN_COMMAND_1 Word R/W -- 2
46 Current Limit (in percent) Word R/W 2^0 2
47 Current Limit Fault Response Byte R/W -- 1
79 Alarm Data Bits (STATUS_WORD) Word Rd -- 2
8B Output Voltage (READ_VOUT) Word Rd 2^-4 2
8C Output Current (READ_IOUT) Word Rd 2^-2 2
8D Power Supply Ambient Temp Word Rd 2^0 2
90 READ_FAN_SPEED_1 Word Rd 2^6 2
91 READ_FAN_SPEED_2 Word Rd 2^6 2
96 Output Wattage (READ_POUT) Word Rd 2^1 2
97 Input Wattage (READ_PIN) Word Rd 2^1 2
9A Unit Model Number (MFR_MODEL) Block R/W -- 10
9B Unit Revision Number Block R/W -- 10
9E Unit Serial Number Block R/W -- 8
99 Unit Manufacturer ID (MFR_ID) Block R/W -- 8
D0 Unit Run Time Information Block Rd -- 4
D5 Firmware Version Rd cust -- 8
B0 User Data 1 (USER_DATA_00) Block R/W -- 4
B1 User Data 2 (USER_DATA_01) Block R/W -- 4
B2 User Data 3 (USER_DATA_02) Block R/W -- 4
B3 User Data 4 (USER_DATA_03) Block R/W -- 4
B4 User Data 5 (USER_DATA_04) Block R/W -- 4
B5 User Data 6 (USER_DATA_05) Block R/W -- 4
B6 User Data 7 (USER_DATA_06) Block R/W -- 4
B7 User Data 8 (USER_DATA_07) Block R/W -- 4
F0 Calibration command Byte R/W -- 1
F1 Calibration data Word Wr 2^9 2
F2 Unlock Calibration Byte Wr -- 1
=== ============================= ========= ======= =====

View File

@@ -22,6 +22,7 @@ Supported boards:
* ROG CROSSHAIR VIII FORMULA
* ROG CROSSHAIR VIII HERO
* ROG CROSSHAIR VIII IMPACT
* ROG CROSSHAIR X670E EXTREME
* ROG CROSSHAIR X670E HERO
* ROG CROSSHAIR X670E GENE
* ROG MAXIMUS X HERO
@@ -32,6 +33,7 @@ Supported boards:
* ROG STRIX B550-I GAMING
* ROG STRIX B650E-I GAMING WIFI
* ROG STRIX B850-I GAMING WIFI
* ROG STRIX X470-F GAMING
* ROG STRIX X470-I GAMING
* ROG STRIX X570-E GAMING
* ROG STRIX X570-E GAMING WIFI II
@@ -48,6 +50,7 @@ Supported boards:
* ROG STRIX Z690-A GAMING WIFI D4
* ROG STRIX Z690-E GAMING WIFI
* ROG STRIX Z790-E GAMING WIFI II
* ROG STRIX Z790-H GAMING WIFI
* ROG STRIX Z790-I GAMING WIFI
* ROG ZENITH II EXTREME
* ROG ZENITH II EXTREME ALPHA

View File

@@ -1,117 +0,0 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel driver bt1-pvt
=====================
Supported chips:
* Baikal-T1 PVT sensor (in SoC)
Prefix: 'bt1-pvt'
Addresses scanned: -
Datasheet: Provided by BAIKAL ELECTRONICS upon request and under NDA
Authors:
Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
Serge Semin <Sergey.Semin@baikalelectronics.ru>
Description
-----------
This driver implements support for the hardware monitoring capabilities of the
embedded into Baikal-T1 process, voltage and temperature sensors. PVT IP-core
consists of one temperature and four voltage sensors, which can be used to
monitor the chip internal environment like heating, supply voltage and
transistors performance. The driver can optionally provide the hwmon alarms
for each sensor the PVT controller supports. The alarms functionality is made
compile-time configurable due to the hardware interface implementation
peculiarity, which is connected with an ability to convert data from only one
sensor at a time. Additional limitation is that the controller performs the
thresholds checking synchronously with the data conversion procedure. Due to
these in order to have the hwmon alarms automatically detected the driver code
must switch from one sensor to another, read converted data and manually check
the threshold status bits. Depending on the measurements timeout settings
(update_interval sysfs node value) this design may cause additional burden on
the system performance. So in case if alarms are unnecessary in your system
design it's recommended to have them disabled to prevent the PVT IRQs being
periodically raised to get the data cache/alarms status up to date. By default
in alarm-less configuration the data conversion is performed by the driver
on demand when read operation is requested via corresponding _input-file.
Temperature Monitoring
----------------------
Temperature is measured with 10-bit resolution and reported in millidegree
Celsius. The driver performs all the scaling by itself therefore reports true
temperatures that don't need any user-space adjustments. While the data
translation formulae isn't linear, which gives us non-linear discreteness,
it's close to one, but giving a bit better accuracy for higher temperatures.
The temperature input is mapped as follows (the last column indicates the input
ranges)::
temp1: CPU embedded diode -48.38C - +147.438C
In case if the alarms kernel config is enabled in the driver the temperature input
has associated min and max limits which trigger an alarm when crossed.
Voltage Monitoring
------------------
The voltage inputs are also sampled with 10-bit resolution and reported in
millivolts. But in this case the data translation formulae is linear, which
provides a constant measurements discreteness. The data scaling is also
performed by the driver, so returning true millivolts. The voltage inputs are
mapped as follows (the last column indicates the input ranges)::
in0: VDD (processor core) 0.62V - 1.168V
in1: Low-Vt (low voltage threshold) 0.62V - 1.168V
in2: High-Vt (high voltage threshold) 0.62V - 1.168V
in3: Standard-Vt (standard voltage threshold) 0.62V - 1.168V
In case if the alarms config is enabled in the driver the voltage inputs
have associated min and max limits which trigger an alarm when crossed.
Sysfs Attributes
----------------
Following is a list of all sysfs attributes that the driver provides, their
permissions and a short description:
=============================== ======= =======================================
Name Perm Description
=============================== ======= =======================================
update_interval RW Measurements update interval per
sensor.
temp1_type RO Sensor type (always 1 as CPU embedded
diode).
temp1_label RO CPU Core Temperature sensor.
temp1_input RO Measured temperature in millidegree
Celsius.
temp1_min RW Low limit for temp input.
temp1_max RW High limit for temp input.
temp1_min_alarm RO Temperature input alarm. Returns 1 if
temperature input went below min limit,
0 otherwise.
temp1_max_alarm RO Temperature input alarm. Returns 1 if
temperature input went above max limit,
0 otherwise.
temp1_offset RW Temperature offset in millidegree
Celsius which is added to the
temperature reading by the chip. It can
be used to manually adjust the
temperature measurements within 7.130
degrees Celsius.
in[0-3]_label RO CPU Voltage sensor (either core or
low/high/standard thresholds).
in[0-3]_input RO Measured voltage in millivolts.
in[0-3]_min RW Low limit for voltage input.
in[0-3]_max RW High limit for voltage input.
in[0-3]_min_alarm RO Voltage input alarm. Returns 1 if
voltage input went below min limit,
0 otherwise.
in[0-3]_max_alarm RO Voltage input alarm. Returns 1 if
voltage input went above max limit,
0 otherwise.
=============================== ======= =======================================

View File

@@ -74,6 +74,16 @@ Supported chips:
https://us1.silergy.com/
* Texas Instruments INA234
Prefix: 'ina234'
Addresses: I2C 0x40 - 0x43
Datasheet: Publicly available at the Texas Instruments website
https://www.ti.com/
Author: Lothar Felten <lothar.felten@gmail.com>
Description
@@ -89,7 +99,7 @@ interface. The INA220 monitors both shunt drop and supply voltage.
The INA226 is a current shunt and power monitor with an I2C interface.
The INA226 monitors both a shunt voltage drop and bus supply voltage.
INA230 and INA231 are high or low side current shunt and power monitors
INA230, INA231, and INA234 are high or low side current shunt and power monitors
with an I2C interface. The chips monitor both a shunt voltage drop and
bus supply voltage.
@@ -124,8 +134,17 @@ power1_input Power(uW) measurement channel
shunt_resistor Shunt resistance(uOhm) channel (not for ina260)
======================= ===============================================
Additional sysfs entries for ina226, ina230, ina231, ina260, and sy24655
------------------------------------------------------------------------
Additional sysfs entries
------------------------
Additional entries are available for the following chips:
* ina226
* ina230
* ina231
* ina234
* ina260
* sy24655
======================= ====================================================
curr1_lcrit Critical low current

View File

@@ -41,6 +41,7 @@ Hardware Monitoring Kernel Drivers
adt7475
aht10
amc6821
aps-379
aquacomputer_d5next
asb100
asc7621
@@ -52,7 +53,6 @@ Hardware Monitoring Kernel Drivers
bcm54140
bel-pfe
bpa-rs600
bt1-pvt
cgbc-hwmon
chipcap2
coretemp
@@ -111,6 +111,7 @@ Hardware Monitoring Kernel Drivers
kbatt
kfan
lan966x
lattepanda-sigma-ec
lineage-pem
lm25066
lm63
@@ -174,6 +175,7 @@ Hardware Monitoring Kernel Drivers
mc33xs2410_hwmon
mc34vr500
mcp3021
mcp9982
menf21bmc
mlxreg-fan
mp2856
@@ -282,4 +284,5 @@ Hardware Monitoring Kernel Drivers
xdp710
xdpe12284
xdpe152c4
yogafan
zl6100

View File

@@ -394,6 +394,26 @@ Supported chips:
Provided by Renesas upon request and NDA
* Renesas RAA228942
Prefix: 'raa228942'
Addresses scanned: -
Datasheet:
Provided by Renesas upon request and NDA
* Renesas RAA228943
Prefix: 'raa228943'
Addresses scanned: -
Datasheet:
Provided by Renesas upon request and NDA
* Renesas RAA229001
Prefix: 'raa229001'

View File

@@ -25,6 +25,14 @@ Supported chips:
Datasheet: Not publicly available
* IT8689E
Prefix: 'it8689'
Addresses scanned: from Super I/O config space (8 I/O ports)
Datasheet: Not publicly available
* IT8705F
Prefix: 'it87'
@@ -228,9 +236,9 @@ Description
-----------
This driver implements support for the IT8603E, IT8620E, IT8623E, IT8628E,
IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F,
IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E,
IT8792E/IT8795E, IT87952E and SiS950 chips.
IT8689E, IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F,
IT8728F, IT8732F, IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F,
IT8786E, IT8790E, IT8792E/IT8795E, IT87952E and SiS950 chips.
These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
joysticks and other miscellaneous stuff. For hardware monitoring, they
@@ -274,6 +282,9 @@ of the fan is not supported (value 0 of pwmX_enable).
The IT8620E and IT8628E are custom designs, hardware monitoring part is similar
to IT8728F. It only supports 16-bit fan mode. Both chips support up to 6 fans.
The IT8689E supports newer autopwm, 12mV ADC, 16-bit fans, six fans, six PWM
channels, PWM frequency 2, six temperature inputs, and AVCC3 (in9).
The IT8790E, IT8792E/IT8795E and IT87952E support up to 3 fans. 16-bit fan
mode is always enabled.
@@ -301,12 +312,15 @@ of 0.016 volt. IT8603E, IT8721F/IT8758E and IT8728F can measure between 0 and
2.8 volts with a resolution of 0.0109 volt. The battery voltage in8 does not
have limit registers.
On the IT8603E, IT8620E, IT8628E, IT8721F/IT8758E, IT8732F, IT8781F, IT8782F,
and IT8783E/F, some voltage inputs are internal and scaled inside the chip:
On the IT8603E, IT8620E, IT8628E, IT8689E, IT8721F/IT8758E, IT8732F, IT8781F,
IT8782F, and IT8783E/F, some voltage inputs are internal and scaled inside the
chip:
* in3 (optional)
* in7 (optional for IT8781F, IT8782F, and IT8783E/F)
* in8 (always)
* in9 (relevant for IT8603E only)
* in9 (IT8603E, IT8622E, and IT8689E: always AVCC3; others: optional)
The driver handles this transparently so user-space doesn't have to care.
The VID lines (IT8712F/IT8716F/IT8718F/IT8720F) encode the core voltage value:

View File

@@ -0,0 +1,61 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver lattepanda-sigma-ec
=================================
Supported systems:
* LattePanda Sigma (Intel 13th Gen i5-1340P)
DMI vendor: LattePanda
DMI product: LattePanda Sigma
BIOS version: 5.27 (verified)
Datasheet: Not available (EC registers discovered empirically)
Author: Mariano Abad <weimaraner@gmail.com>
Description
-----------
This driver provides hardware monitoring for the LattePanda Sigma
single-board computer made by DFRobot. The board uses an ITE IT8613E
Embedded Controller to manage a CPU cooling fan and thermal sensors.
The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with
``_STA`` returning 0, preventing the kernel's ACPI EC subsystem from
initializing. This driver reads the EC directly via the standard ACPI
EC I/O ports (``0x62`` data, ``0x66`` command/status).
Sysfs attributes
----------------
======================= ===============================================
``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F,
16-bit big-endian)
``fan1_label`` "CPU Fan"
``temp1_input`` Board/ambient temperature in millidegrees
Celsius (EC register 0x60, unsigned)
``temp1_label`` "Board Temp"
``temp2_input`` CPU proximity temperature in millidegrees
Celsius (EC register 0x70, unsigned)
``temp2_label`` "CPU Temp"
======================= ===============================================
Module parameters
-----------------
``force`` (bool, default false)
Force loading on BIOS versions other than 5.27. The driver still
requires DMI vendor and product name matching.
Known limitations
-----------------
* Fan speed control is not supported. The fan is always under EC
automatic control.
* The EC register map was verified only on BIOS version 5.27.
Other versions may use different register offsets; use the ``force``
parameter at your own risk.

View File

@@ -9,8 +9,7 @@ Supported chips:
Prefix: 'ltc4282'
Addresses scanned: - I2C 0x40 - 0x5A (7-bit)
Addresses scanned: - I2C 0x80 - 0xB4 with a step of 2 (8-bit)
Addresses scanned: -
Datasheet:

View File

@@ -0,0 +1,111 @@
.. SPDX-License-Identifier: GPL-2.0+
Kernel driver MCP998X
=====================
Supported chips:
* Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D
Prefix: 'mcp9982'
Datasheet:
https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
Authors:
- Victor Duicu <victor.duicu@microchip.com>
Description
-----------
This driver implements support for the MCP998X family containing: MCP9982,
MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D,
MCP9933 and MCP9933D.
The MCP998X Family is a high accuracy 2-wire multichannel automotive
temperature monitor.
The chips in the family have different numbers of external channels,
ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in
anti-parallel connection is supported by MCP9984/85/33 and
MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present
only in MCP998XD and MCP9933D.
Temperatures are read in millidegrees Celsius, ranging from -64 to
191.875 with 0.125 precision.
Each channel has a minimum, maximum, and critical limit alongside associated alarms.
The chips also implement a hysteresis mechanism which applies only to the maximum
and critical limits. The relative difference between a limit and its hysteresis
is the same for both and the value is kept in a single register.
The chips measure temperatures with a variable conversion rate.
Update_interval = Conversion/Second, so the available options are:
- 16000 (ms) = 1 conv/16 sec
- 8000 (ms) = 1 conv/8 sec
- 4000 (ms) = 1 conv/4 sec
- 2000 (ms) = 1 conv/2 sec
- 1000 (ms) = 1 conv/sec
- 500 (ms) = 2 conv/sec
- 250 (ms) = 4 conv/sec
- 125 (ms) = 8 conv/sec
- 64 (ms) = 16 conv/sec
- 32 (ms) = 32 conv/sec
- 16 (ms) = 64 conv/sec
Usage Notes
-----------
Parameters that can be configured in devicetree:
- anti-parallel diode mode operation
- resistance error correction on channels 1 and 2
- resistance error correction on channels 3 and 4
- power state
Chips 82/83 and 82D/83D do not support anti-parallel diode mode.
For chips with "D" in the name resistance error correction must be on.
Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
for details.
There are two power states:
- Active state: in which the chip is converting on all channels at the
programmed rate.
- Standby state: in which the host must initiate a conversion cycle.
Chips with "D" in the name work in Active state only and those without
can work in either state.
Chips with "D" in the name can't set update interval slower than 1 second.
Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable
while the others are read only. Setting tempX_crit_hyst writes the difference
between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value
applies automatically to the other limits. At power up the device starts with
a 10 degree hysteresis.
Sysfs entries
-------------
The following attributes are supported. The temperature limits and
update_interval are read-write. The attribute tempX_crit_hyst is read-write,
while tempX_max_hyst is read only. All other attributes are read only.
======================= ==================================================
temp[1-5]_label User name for channel.
temp[1-5]_input Measured temperature for channel.
temp[1-5]_crit Critical temperature limit.
temp[1-5]_crit_alarm Critical temperature limit alarm.
temp[1-5]_crit_hyst Critical temperature limit hysteresis.
temp[1-5]_max High temperature limit.
temp[1-5]_max_alarm High temperature limit alarm.
temp[1-5]_max_hyst High temperature limit hysteresis.
temp[1-5]_min Low temperature limit.
temp[1-5]_min_alarm Low temperature limit alarm.
update_interval The interval at which the chip will update readings.
======================= ==================================================

View File

@@ -11,6 +11,22 @@ Supported chips:
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp102.html
* Texas Instruments TMP110
Prefix: 'tmp110'
Addresses scanned: none
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp110.html
* Texas Instruments TMP113
Prefix: 'tmp113'
Addresses scanned: none
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp113.html
Author:
Steven King <sfking@fdwdc.com>
@@ -25,7 +41,25 @@ degree from -40 to +125 C. Resolution of the sensor is 0.0625 degree. The
operating temperature has a minimum of -55 C and a maximum of +150 C.
The TMP102 has a programmable update rate that can select between 8, 4, 1, and
0.5 Hz. (Currently the driver only supports the default of 4 Hz).
0.25 Hz.
The driver provides the common sysfs-interface for temperatures (see
Documentation/hwmon/sysfs-interface.rst under Temperatures).
The TMP110 and TMP113 are software compatible with TMP102, but have different
accuracy (maximum error) specifications. The TMP110 has an accuracy (maximum error)
of 1.0 degree, TMP113 has an accuracy (maximum error) of 0.3 degree, while TMP102
has an accuracy (maximum error) of 2.0 degree.
sysfs-Interface
---------------
The following list includes the sysfs attributes that the driver provides, their
permissions and a short description:
=============================== ======= ===========================================
Name Perm Description
=============================== ======= ===========================================
temp1_input: RO Temperature input
temp1_label: RO Descriptive name for the sensor
temp1_max: RW Maximum temperature
temp1_max_hyst: RW Maximum hysteresis temperature
update_interval RW Update conversions interval in milliseconds
=============================== ======= ===========================================

View File

@@ -0,0 +1,138 @@
.. SPDX-License-Identifier: GPL-2.0-only
===============================================================================================
Kernel driver yogafan
===============================================================================================
Supported chips:
* Lenovo Yoga, Legion, IdeaPad, Slim, Flex, and LOQ Embedded Controllers
* Prefix: 'yogafan'
* Addresses: ACPI handle (See Database Below)
Author: Sergio Melas <sergiomelas@gmail.com>
Description
-----------
This driver provides fan speed monitoring for modern Lenovo consumer laptops.
Most Lenovo laptops do not provide fan tachometer data through standard
ISA/LPC hardware monitoring chips. Instead, the data is stored in the
Embedded Controller (EC) and exposed via ACPI.
The driver implements a **Rate-Limited Lag (RLLag)** filter to handle
the low-resolution and jittery sampling found in Lenovo EC firmware.
Hardware Identification and Multiplier Logic
--------------------------------------------
The driver supports two distinct EC architectures. Differentiation is handled
deterministically via a DMI Product Family quirk table during the probe phase,
eliminating the need for runtime heuristics.
1. 8-bit EC Architecture (Multiplier: 100)
- **Families:** Yoga, IdeaPad, Slim, Flex.
- **Technical Detail:** These models allocate a single 8-bit register for
tachometer data. Since 8-bit fields are limited to a value of 255, the
BIOS stores fan speed in units of 100 RPM (e.g., 42 = 4200 RPM).
2. 16-bit EC Architecture (Multiplier: 1)
- **Families:** Legion, LOQ.
- **Technical Detail:** High-performance gaming models require greater
precision for fans exceeding 6000 RPM. These use a 16-bit word (2 bytes)
storing the raw RPM value directly.
Filter Details
--------------
The RLLag filter is a passive discrete-time first-order lag model that ensures:
- **Smoothing:** Low-resolution step increments are smoothed into 1-RPM increments.
- **Slew-Rate Limiting:** Prevents unrealistic readings by capping the change
to 1500 RPM/s, matching physical fan inertia.
- **Polling Independence:** The filter math scales based on the time delta
between userspace reads, ensuring a consistent physical curve regardless
of polling frequency.
Suspend and Resume
------------------
The driver utilizes the boottime clock (ktime_get_boottime()) to calculate the
sampling delta. This ensures that time spent in system suspend is accounted
for. If the delta exceeds 5 seconds (e.g., after waking the laptop), the
filter automatically resets to the current hardware value to prevent
reporting "ghost" RPM data from before the sleep state.
Usage
-----
The driver exposes standard hwmon sysfs attributes:
=============== ============================
Attribute Description
fanX_input Filtered fan speed in RPM.
=============== ============================
Note: If the hardware reports 0 RPM, the filter is bypassed and 0 is reported
immediately to ensure the user knows the fan has stopped.
====================================================================================================
LENOVO FAN CONTROLLER: MASTER REFERENCE DATABASE (2026)
====================================================================================================
::
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
*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
----------------------------------------------------------------------------------------------------
METHODOLOGY & IDENTIFICATION:
1. DSDT ANALYSIS (THE PATH):
BIOS ACPI tables were analyzed using 'iasl' and cross-referenced with
public dumps. Internal labels (FANS, FAN0, FA2S) are mapped to
EmbeddedControl OperationRegion offsets.
2. EC MEMORY MAPPING (THE OFFSET):
Validated by matching NBFC (NoteBook FanControl) XML logic with DSDT Field
definitions found in BIOS firmware.
3. DATA-WIDTH ANALYSIS (THE MULTIPLIER):
- 8-bit (Multiplier 100): Standard for Yoga/IdeaPad. Raw values (0-255).
- 16-bit (Multiplier 1): Standard for Legion/LOQ. Two registers (0xFE/0xFF).
References
----------
1. **ACPI Specification (Field Objects):** Documentation on how 8-bit vs 16-bit
fields are accessed in OperationRegions.
https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#field-objects
2. **NBFC Projects:** Community-driven reverse engineering
of Lenovo Legion/LOQ EC memory maps (16-bit raw registers).
https://github.com/hirschmann/nbfc/tree/master/Configs
3. **Linux Kernel Timekeeping API:** Documentation for ktime_get_boottime() and
handling deltas across suspend states.
https://www.kernel.org/doc/html/latest/core-api/timekeeping.html
4. **Lenovo IdeaPad Laptop Driver:** Reference for DMI-based hardware
feature gating in Lenovo laptops.
https://github.com/torvalds/linux/blob/master/drivers/platform/x86/ideapad-laptop.c

View File

@@ -14498,6 +14498,13 @@ F: drivers/net/wan/framer/
F: drivers/pinctrl/pinctrl-pef2256.c
F: include/linux/framer/
LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER
M: Mariano Abad <weimaraner@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/lattepanda-sigma-ec.rst
F: drivers/hwmon/lattepanda-sigma-ec.c
LASI 53c700 driver for PARISC
M: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
L: linux-scsi@vger.kernel.org
@@ -14947,6 +14954,14 @@ W: https://linuxtv.org
Q: http://patchwork.linuxtv.org/project/linux-media/list/
F: drivers/media/usb/dvb-usb-v2/lmedm04*
LENOVO YOGA FAN DRIVER
M: Sergio Melas <sergiomelas@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
W: https://github.com/sergiomelas
F: Documentation/hwmon/yogafan.rst
F: drivers/hwmon/yogafan.c
LOADPIN SECURITY MODULE
M: Kees Cook <kees@kernel.org>
S: Supported
@@ -17426,6 +17441,14 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/microchip,mcp3911.yaml
F: drivers/iio/adc/mcp3911.c
MICROCHIP MCP9982 TEMPERATURE DRIVER
M: Victor Duicu <victor.duicu@microchip.com>
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
F: Documentation/hwmon/mcp9982.rst
F: drivers/hwmon/mcp9982.c
MICROCHIP MMC/SD/SDIO MCI DRIVER
M: Aubin Constans <aubin.constans@microchip.com>
S: Maintained

View File

@@ -457,32 +457,6 @@ config SENSORS_ATXP1
This driver can also be built as a module. If so, the module
will be called atxp1.
config SENSORS_BT1_PVT
tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
depends on MIPS_BAIKAL_T1 || COMPILE_TEST
select POLYNOMIAL
help
If you say yes here you get support for Baikal-T1 PVT sensor
embedded into the SoC.
This driver can also be built as a module. If so, the module will be
called bt1-pvt.
config SENSORS_BT1_PVT_ALARMS
bool "Enable Baikal-T1 PVT sensor alarms"
depends on SENSORS_BT1_PVT
help
Baikal-T1 PVT IP-block provides threshold registers for each
supported sensor. But the corresponding interrupts might be
generated by the thresholds comparator only in synchronization with
a data conversion. Additionally there is only one sensor data can
be converted at a time. All of these makes the interface impossible
to be used for the hwmon alarms implementation without periodic
switch between the PVT sensors. By default the data conversion is
performed on demand from the user-space. If this config is enabled
the data conversion will be periodically performed and the data will be
saved in the internal driver cache.
config SENSORS_CGBC
tristate "Congatec Board Controller Sensors"
depends on MFD_CGBC
@@ -632,7 +606,7 @@ config SENSORS_I5K_AMB
config SENSORS_SPARX5
tristate "Sparx5 SoC temperature sensor"
depends on ARCH_SPARX5 || COMPILE_TEST
depends on ARCH_SPARX5 || ARCH_LAN969X || COMPILE_TEST
help
If you say yes here you get support for temperature monitoring
with the Microchip Sparx5 SoC.
@@ -801,7 +775,6 @@ config SENSORS_G762
config SENSORS_GPIO_FAN
tristate "GPIO fan"
depends on OF_GPIO
depends on GPIOLIB || COMPILE_TEST
depends on THERMAL || THERMAL=n
help
@@ -936,8 +909,8 @@ config SENSORS_IT87
If you say yes here you get support for ITE IT8705F, IT8712F, IT8716F,
IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F, IT8758E,
IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E,
IT8603E, IT8620E, IT8623E, and IT8628E sensor chips, and the SiS950
clone.
IT8603E, IT8620E, IT8623E, IT8628E, and IT8689E sensor chips, and
the SiS950 clone.
This driver can also be built as a module. If so, the module
will be called it87.
@@ -990,6 +963,23 @@ config SENSORS_LAN966X
This driver can also be built as a module. If so, the module
will be called lan966x-hwmon.
config SENSORS_LATTEPANDA_SIGMA_EC
tristate "LattePanda Sigma EC hardware monitoring"
depends on X86
depends on DMI
depends on HAS_IOPORT
help
If you say yes here you get support for the hardware monitoring
features of the Embedded Controller on LattePanda Sigma
single-board computers, including CPU fan speed (RPM) and
board and CPU temperatures.
The driver reads the EC directly via ACPI EC I/O ports and
uses DMI matching to ensure it only loads on supported hardware.
This driver can also be built as a module. If so, the module
will be called lattepanda-sigma-ec.
config SENSORS_LENOVO_EC
tristate "Sensor reader for Lenovo ThinkStations"
depends on X86
@@ -1388,6 +1378,17 @@ config SENSORS_MCP3021
This driver can also be built as a module. If so, the module
will be called mcp3021.
config SENSORS_MCP9982
tristate "Microchip Technology MCP9982 driver"
depends on I2C
select REGMAP_I2C
help
Say yes here to include support for Microchip Technology's MCP998X/33
and MCP998XD/33D Multichannel Automotive Temperature Monitor Family.
This driver can also be built as a module. If so, the module
will be called mcp9982.
config SENSORS_MLXREG_FAN
tristate "Mellanox FAN driver"
depends on MELLANOX_PLATFORM
@@ -2273,7 +2274,7 @@ config SENSORS_INA2XX
select REGMAP_I2C
help
If you say yes here you get support for INA219, INA220, INA226,
INA230, INA231, INA260, and SY24655 power monitor chips.
INA230, INA231, INA234, INA260, and SY24655 power monitor chips.
The INA2xx driver is configured for the default configuration of
the part as described in the datasheet.
@@ -2362,8 +2363,8 @@ config SENSORS_TMP102
depends on I2C
select REGMAP_I2C
help
If you say yes here you get support for Texas Instruments TMP102
sensor chips.
If you say yes here you get support for Texas Instruments TMP102,
TMP110 and TMP113 sensor chips.
This driver can also be built as a module. If so, the module
will be called tmp102.
@@ -2661,6 +2662,18 @@ config SENSORS_XGENE
If you say yes here you get support for the temperature
and power sensors for APM X-Gene SoC.
config SENSORS_YOGAFAN
tristate "Lenovo Yoga Fan Hardware Monitoring"
depends on ACPI && HWMON && DMI
help
If you say yes here you get support for fan speed monitoring
on Lenovo Yoga, Legion, IdeaPad, Slim and LOQ laptops.
The driver interfaces with the Embedded Controller via ACPI
and uses a Rate-Limited Lag filter to smooth RPM readings.
This driver can also be built as a module. If so, the module
will be called yogafan.
config SENSORS_INTEL_M10_BMC_HWMON
tristate "Intel MAX10 BMC Hardware Monitoring"
depends on MFD_INTEL_M10_BMC_CORE

View File

@@ -58,7 +58,6 @@ 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_BT1_PVT) += bt1-pvt.o
obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o
obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
@@ -114,6 +113,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
@@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
obj-$(CONFIG_SENSORS_MCP9982) += mcp9982.o
obj-$(CONFIG_SENSORS_TC654) += tc654.o
obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
@@ -245,6 +246,7 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o
obj-$(CONFIG_SENSORS_YOGAFAN) += yogafan.o
obj-$(CONFIG_SENSORS_OCC) += occ/
obj-$(CONFIG_SENSORS_PECI) += peci/

View File

@@ -18,6 +18,7 @@
#include <linux/time.h>
#include <linux/err.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
#define ACPI_POWER_METER_NAME "power_meter"
#define ACPI_POWER_METER_DEVICE_NAME "Power Meter"
@@ -814,16 +815,12 @@ end:
}
/* Handle ACPI event notifications */
static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data)
{
struct acpi_power_meter_resource *resource;
struct device *dev = data;
struct acpi_power_meter_resource *resource = dev_get_drvdata(dev);
int res;
if (!device || !acpi_driver_data(device))
return;
resource = acpi_driver_data(device);
guard(mutex)(&acpi_notify_lock);
switch (event) {
@@ -837,43 +834,43 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
remove_domain_devices(resource);
res = read_capabilities(resource);
if (res)
dev_err_once(&device->dev, "read capabilities failed.\n");
dev_err_once(dev, "read capabilities failed.\n");
res = read_domain_devices(resource);
if (res && res != -ENODEV)
dev_err_once(&device->dev, "read domain devices failed.\n");
dev_err_once(dev, "read domain devices failed.\n");
mutex_unlock(&resource->lock);
resource->hwmon_dev =
hwmon_device_register_with_info(&device->dev,
hwmon_device_register_with_info(dev,
ACPI_POWER_METER_NAME,
resource,
&power_meter_chip_info,
power_extra_groups);
if (IS_ERR(resource->hwmon_dev))
dev_err_once(&device->dev, "register hwmon device failed.\n");
dev_err_once(dev, "register hwmon device failed.\n");
break;
case METER_NOTIFY_TRIP:
sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME);
sysfs_notify(&dev->kobj, NULL, POWER_AVERAGE_NAME);
break;
case METER_NOTIFY_CAP:
mutex_lock(&resource->lock);
res = update_cap(resource);
if (res)
dev_err_once(&device->dev, "update cap failed when capping value is changed.\n");
dev_err_once(dev, "update cap failed when capping value is changed.\n");
mutex_unlock(&resource->lock);
sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME);
sysfs_notify(&dev->kobj, NULL, POWER_CAP_NAME);
break;
case METER_NOTIFY_INTERVAL:
sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME);
sysfs_notify(&dev->kobj, NULL, POWER_AVG_INTERVAL_NAME);
break;
case METER_NOTIFY_CAPPING:
mutex_lock(&resource->lock);
resource->power_alarm = true;
mutex_unlock(&resource->lock);
sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME);
dev_info(&device->dev, "Capping in progress.\n");
sysfs_notify(&dev->kobj, NULL, POWER_ALARM_NAME);
dev_info(dev, "Capping in progress.\n");
break;
default:
WARN(1, "Unexpected event %d\n", event);
@@ -881,16 +878,15 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
}
acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS,
dev_name(&device->dev), event, 0);
dev_name(&resource->acpi_dev->dev),
event, 0);
}
static int acpi_power_meter_add(struct acpi_device *device)
static int acpi_power_meter_probe(struct platform_device *pdev)
{
int res;
struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
struct acpi_power_meter_resource *resource;
if (!device)
return -EINVAL;
int res;
resource = kzalloc_obj(*resource);
if (!resource)
@@ -901,7 +897,8 @@ static int acpi_power_meter_add(struct acpi_device *device)
mutex_init(&resource->lock);
strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
device->driver_data = resource;
platform_set_drvdata(pdev, resource);
#if IS_REACHABLE(CONFIG_ACPI_IPMI)
/*
@@ -914,7 +911,7 @@ static int acpi_power_meter_add(struct acpi_device *device)
struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1);
if (ipi_device && acpi_wait_for_acpi_ipmi())
dev_warn(&device->dev, "Waiting for ACPI IPMI timeout");
dev_warn(&pdev->dev, "Waiting for ACPI IPMI timeout");
acpi_dev_put(ipi_device);
}
#endif
@@ -932,7 +929,7 @@ static int acpi_power_meter_add(struct acpi_device *device)
goto exit_free_capability;
resource->hwmon_dev =
hwmon_device_register_with_info(&device->dev,
hwmon_device_register_with_info(&pdev->dev,
ACPI_POWER_METER_NAME, resource,
&power_meter_chip_info,
power_extra_groups);
@@ -941,9 +938,16 @@ static int acpi_power_meter_add(struct acpi_device *device)
goto exit_remove;
}
res = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
acpi_power_meter_notify, &pdev->dev);
if (res)
goto exit_hwmon;
res = 0;
goto exit;
exit_hwmon:
hwmon_device_unregister(resource->hwmon_dev);
exit_remove:
remove_domain_devices(resource);
exit_free_capability:
@@ -954,14 +958,13 @@ exit:
return res;
}
static void acpi_power_meter_remove(struct acpi_device *device)
static void acpi_power_meter_remove(struct platform_device *pdev)
{
struct acpi_power_meter_resource *resource;
struct acpi_power_meter_resource *resource = platform_get_drvdata(pdev);
if (!device || !acpi_driver_data(device))
return;
acpi_dev_remove_notify_handler(resource->acpi_dev, ACPI_DEVICE_NOTIFY,
acpi_power_meter_notify);
resource = acpi_driver_data(device);
if (!IS_ERR(resource->hwmon_dev))
hwmon_device_unregister(resource->hwmon_dev);
@@ -973,14 +976,7 @@ static void acpi_power_meter_remove(struct acpi_device *device)
static int acpi_power_meter_resume(struct device *dev)
{
struct acpi_power_meter_resource *resource;
if (!dev)
return -EINVAL;
resource = acpi_driver_data(to_acpi_device(dev));
if (!resource)
return -EINVAL;
struct acpi_power_meter_resource *resource = dev_get_drvdata(dev);
free_capabilities(resource);
read_capabilities(resource);
@@ -991,16 +987,14 @@ static int acpi_power_meter_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL,
acpi_power_meter_resume);
static struct acpi_driver acpi_power_meter_driver = {
.name = "power_meter",
.class = ACPI_POWER_METER_CLASS,
.ids = power_meter_ids,
.ops = {
.add = acpi_power_meter_add,
.remove = acpi_power_meter_remove,
.notify = acpi_power_meter_notify,
},
.drv.pm = pm_sleep_ptr(&acpi_power_meter_pm),
static struct platform_driver acpi_power_meter_driver = {
.probe = acpi_power_meter_probe,
.remove = acpi_power_meter_remove,
.driver = {
.name = "acpi-power-meter",
.acpi_match_table = power_meter_ids,
.pm = &acpi_power_meter_pm,
},
};
/* Module init/exit routines */
@@ -1029,7 +1023,7 @@ static int __init acpi_power_meter_init(void)
dmi_check_system(pm_dmi_table);
result = acpi_bus_register_driver(&acpi_power_meter_driver);
result = platform_driver_register(&acpi_power_meter_driver);
if (result < 0)
return result;
@@ -1038,7 +1032,7 @@ static int __init acpi_power_meter_init(void)
static void __exit acpi_power_meter_exit(void)
{
acpi_bus_unregister_driver(&acpi_power_meter_driver);
platform_driver_unregister(&acpi_power_meter_driver);
}
MODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>");

View File

@@ -62,8 +62,8 @@ static ssize_t ads7828_in_show(struct device *dev,
if (err < 0)
return err;
return sprintf(buf, "%d\n",
DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000));
return sysfs_emit(buf, "%d\n",
DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000));
}
static SENSOR_DEVICE_ATTR_RO(in0_input, ads7828_in, 0);

View File

@@ -104,10 +104,14 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da,
*/
/*MUX_M3_BM forces single ended*/
/*This is also where the gain of the PGA would be set*/
ads7871_write_reg8(spi, REG_GAIN_MUX,
(MUX_CNV_BM | MUX_M3_BM | channel));
ret = ads7871_write_reg8(spi, REG_GAIN_MUX,
(MUX_CNV_BM | MUX_M3_BM | channel));
if (ret < 0)
return ret;
ret = ads7871_read_reg8(spi, REG_GAIN_MUX);
if (ret < 0)
return ret;
mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV);
/*
* on 400MHz arm9 platform the conversion
@@ -116,18 +120,22 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da,
while ((i < 2) && mux_cnv) {
i++;
ret = ads7871_read_reg8(spi, REG_GAIN_MUX);
if (ret < 0)
return ret;
mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV);
msleep_interruptible(1);
}
if (mux_cnv == 0) {
val = ads7871_read_reg16(spi, REG_LS_BYTE);
if (val < 0)
return val;
/*result in volts*10000 = (val/8192)*2.5*10000*/
val = ((val >> 2) * 25000) / 8192;
return sprintf(buf, "%d\n", val);
} else {
return -1;
return sysfs_emit(buf, "%d\n", val);
}
return -ETIMEDOUT;
}
static SENSOR_DEVICE_ATTR_RO(in0_input, voltage, 0);

View File

@@ -62,6 +62,15 @@ static const struct i2c_device_id aht10_id[] = {
};
MODULE_DEVICE_TABLE(i2c, aht10_id);
static const struct of_device_id aht10_of_match[] = {
{ .compatible = "aosong,aht10", .data = (void *)aht10 },
{ .compatible = "aosong,aht20", .data = (void *)aht20 },
{ .compatible = "aosong,dht20", .data = (void *)dht20 },
{}
};
MODULE_DEVICE_TABLE(of, aht10_of_match);
/**
* struct aht10_data - All the data required to operate an AHT10/AHT20 chip
* @client: the i2c client associated with the AHT10/AHT20
@@ -377,6 +386,7 @@ static int aht10_probe(struct i2c_client *client)
static struct i2c_driver aht10_driver = {
.driver = {
.name = "aht10",
.of_match_table = aht10_of_match,
},
.probe = aht10_probe,
.id_table = aht10_id,

View File

@@ -517,13 +517,6 @@ static int aspeed_pwm_tach_probe(struct platform_device *pdev)
return 0;
}
static void aspeed_pwm_tach_remove(struct platform_device *pdev)
{
struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev);
reset_control_assert(priv->reset);
}
static const struct of_device_id aspeed_pwm_tach_match[] = {
{
.compatible = "aspeed,ast2600-pwm-tach",
@@ -537,7 +530,6 @@ MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match);
static struct platform_driver aspeed_pwm_tach_driver = {
.probe = aspeed_pwm_tach_probe,
.remove = aspeed_pwm_tach_remove,
.driver = {
.name = "aspeed-g6-pwm-tach",
.of_match_table = aspeed_pwm_tach_match,

View File

@@ -456,6 +456,15 @@ static const struct ec_board_info board_info_crosshair_viii_impact = {
.family = family_amd_500_series,
};
static const struct ec_board_info board_info_crosshair_x670e_extreme = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_WATER_IN |
SENSOR_TEMP_WATER_OUT,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
.family = family_amd_600_series,
};
static const struct ec_board_info board_info_crosshair_x670e_gene = {
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
SENSOR_TEMP_T_SENSOR |
@@ -626,6 +635,14 @@ static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = {
.family = family_amd_800_series,
};
static const struct ec_board_info board_info_strix_x470_f_gaming = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
.family = family_amd_400_series,
};
static const struct ec_board_info board_info_strix_x470_i_gaming = {
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
@@ -750,6 +767,12 @@ static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = {
.family = family_intel_700_series,
};
static const struct ec_board_info board_info_strix_z790_h_gaming_wifi = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM,
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
.family = family_intel_700_series,
};
static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = {
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 |
SENSOR_TEMP_VRM,
@@ -825,6 +848,8 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_crosshair_viii_hero),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
&board_info_crosshair_viii_impact),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E EXTREME",
&board_info_crosshair_x670e_extreme),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE",
&board_info_crosshair_x670e_gene),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
@@ -845,6 +870,8 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_strix_b650e_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI",
&board_info_strix_b850_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING",
&board_info_strix_x470_f_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING",
&board_info_strix_x470_i_gaming),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING",
@@ -877,6 +904,8 @@ static const struct dmi_system_id dmi_table[] = {
&board_info_strix_z690_e_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II",
&board_info_strix_z790_e_gaming_wifi_ii),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-H GAMING WIFI",
&board_info_strix_z790_h_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI",
&board_info_strix_z790_i_gaming_wifi),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME",

View File

@@ -17,6 +17,7 @@
#include <linux/jiffies.h>
#include <linux/err.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#define ATK_HID "ATK0110"
@@ -107,7 +108,7 @@ enum atk_pack_member {
struct atk_data {
struct device *hwmon_dev;
acpi_handle atk_handle;
struct acpi_device *acpi_dev;
struct device *dev;
bool old_interface;
@@ -187,18 +188,17 @@ struct atk_acpi_input_buf {
u32 param2;
};
static int atk_add(struct acpi_device *device);
static void atk_remove(struct acpi_device *device);
static int atk_probe(struct platform_device *pdev);
static void atk_remove(struct platform_device *pdev);
static void atk_print_sensor(struct atk_data *data, union acpi_object *obj);
static int atk_read_value(struct atk_sensor_data *sensor, u64 *value);
static struct acpi_driver atk_driver = {
.name = ATK_HID,
.class = "hwmon",
.ids = atk_ids,
.ops = {
.add = atk_add,
.remove = atk_remove,
static struct platform_driver atk_driver = {
.probe = atk_probe,
.remove = atk_remove,
.driver = {
.name = ATK_HID,
.acpi_match_table = atk_ids,
},
};
@@ -327,7 +327,7 @@ static union acpi_object *atk_get_pack_member(struct atk_data *data,
*/
static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *tmp;
bool old_if = data->old_interface;
int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE :
@@ -422,7 +422,7 @@ static char const *atk_sensor_type(union acpi_object *flags)
static void atk_print_sensor(struct atk_data *data, union acpi_object *obj)
{
#ifdef DEBUG
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *flags;
union acpi_object *name;
union acpi_object *limit1;
@@ -449,7 +449,7 @@ static void atk_print_sensor(struct atk_data *data, union acpi_object *obj)
static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value)
{
struct atk_data *data = sensor->data;
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct acpi_object_list params;
union acpi_object id;
acpi_status status;
@@ -487,7 +487,7 @@ static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value)
static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct acpi_buffer buf;
acpi_status ret;
struct acpi_object_list params;
@@ -523,7 +523,7 @@ static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux)
static union acpi_object *atk_gitm(struct atk_data *data, u64 id)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct atk_acpi_input_buf buf;
union acpi_object tmp;
struct acpi_object_list params;
@@ -565,7 +565,7 @@ static union acpi_object *atk_gitm(struct atk_data *data, u64 id)
static union acpi_object *atk_sitm(struct atk_data *data,
struct atk_acpi_input_buf *buf)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct acpi_object_list params;
union acpi_object tmp;
struct acpi_buffer ret;
@@ -602,7 +602,7 @@ static union acpi_object *atk_sitm(struct atk_data *data,
static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value)
{
struct atk_data *data = sensor->data;
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *obj;
struct atk_acpi_ret_buffer *buf;
int err = 0;
@@ -819,7 +819,7 @@ static void atk_debugfs_cleanup(struct atk_data *data)
static int atk_add_sensor(struct atk_data *data, union acpi_object *obj)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *flags;
union acpi_object *name;
union acpi_object *limit1;
@@ -937,7 +937,7 @@ static int atk_add_sensor(struct atk_data *data, union acpi_object *obj)
static int atk_enumerate_old_hwmon(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct acpi_buffer buf;
union acpi_object *pack;
acpi_status status;
@@ -1012,7 +1012,7 @@ static int atk_enumerate_old_hwmon(struct atk_data *data)
static int atk_ec_present(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *pack;
union acpi_object *ec;
int ret;
@@ -1058,7 +1058,7 @@ static int atk_ec_present(struct atk_data *data)
static int atk_ec_enabled(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *obj;
struct atk_acpi_ret_buffer *buf;
int err;
@@ -1084,7 +1084,7 @@ static int atk_ec_enabled(struct atk_data *data)
static int atk_ec_ctl(struct atk_data *data, int enable)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *obj;
struct atk_acpi_input_buf sitm;
struct atk_acpi_ret_buffer *ec_ret;
@@ -1113,7 +1113,7 @@ static int atk_ec_ctl(struct atk_data *data, int enable)
static int atk_enumerate_new_hwmon(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
union acpi_object *pack;
int err;
int i;
@@ -1155,7 +1155,7 @@ static int atk_enumerate_new_hwmon(struct atk_data *data)
static int atk_init_attribute_groups(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
struct atk_sensor_data *s;
struct attribute **attrs;
int i = 0;
@@ -1181,7 +1181,7 @@ static int atk_init_attribute_groups(struct atk_data *data)
static int atk_register_hwmon(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
dev_dbg(dev, "registering hwmon device\n");
data->hwmon_dev = hwmon_device_register_with_groups(dev, "atk0110",
@@ -1193,7 +1193,7 @@ static int atk_register_hwmon(struct atk_data *data)
static int atk_probe_if(struct atk_data *data)
{
struct device *dev = &data->acpi_dev->dev;
struct device *dev = data->dev;
acpi_handle ret;
acpi_status status;
int err = 0;
@@ -1266,7 +1266,7 @@ static int atk_probe_if(struct atk_data *data)
return err;
}
static int atk_add(struct acpi_device *device)
static int atk_probe(struct platform_device *pdev)
{
acpi_status ret;
int err;
@@ -1274,14 +1274,14 @@ static int atk_add(struct acpi_device *device)
union acpi_object *obj;
struct atk_data *data;
dev_dbg(&device->dev, "adding...\n");
dev_dbg(&pdev->dev, "adding...\n");
data = devm_kzalloc(&device->dev, sizeof(*data), GFP_KERNEL);
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->acpi_dev = device;
data->atk_handle = device->handle;
data->dev = &pdev->dev;
data->atk_handle = ACPI_HANDLE(&pdev->dev);
INIT_LIST_HEAD(&data->sensor_list);
data->disable_ec = false;
@@ -1289,13 +1289,13 @@ static int atk_add(struct acpi_device *device)
ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL,
&buf, ACPI_TYPE_PACKAGE);
if (ret != AE_OK) {
dev_dbg(&device->dev, "atk: method MBIF not found\n");
dev_dbg(&pdev->dev, "atk: method MBIF not found\n");
} else {
obj = buf.pointer;
if (obj->package.count >= 2) {
union acpi_object *id = &obj->package.elements[1];
if (id->type == ACPI_TYPE_STRING)
dev_dbg(&device->dev, "board ID = %s\n",
dev_dbg(&pdev->dev, "board ID = %s\n",
id->string.pointer);
}
ACPI_FREE(buf.pointer);
@@ -1303,21 +1303,21 @@ static int atk_add(struct acpi_device *device)
err = atk_probe_if(data);
if (err) {
dev_err(&device->dev, "No usable hwmon interface detected\n");
dev_err(&pdev->dev, "No usable hwmon interface detected\n");
goto out;
}
if (data->old_interface) {
dev_dbg(&device->dev, "Using old hwmon interface\n");
dev_dbg(&pdev->dev, "Using old hwmon interface\n");
err = atk_enumerate_old_hwmon(data);
} else {
dev_dbg(&device->dev, "Using new hwmon interface\n");
dev_dbg(&pdev->dev, "Using new hwmon interface\n");
err = atk_enumerate_new_hwmon(data);
}
if (err < 0)
goto out;
if (err == 0) {
dev_info(&device->dev,
dev_info(&pdev->dev,
"No usable sensor detected, bailing out\n");
err = -ENODEV;
goto out;
@@ -1332,7 +1332,8 @@ static int atk_add(struct acpi_device *device)
atk_debugfs_init(data);
device->driver_data = data;
platform_set_drvdata(pdev, data);
return 0;
out:
if (data->disable_ec)
@@ -1340,12 +1341,11 @@ out:
return err;
}
static void atk_remove(struct acpi_device *device)
static void atk_remove(struct platform_device *pdev)
{
struct atk_data *data = device->driver_data;
dev_dbg(&device->dev, "removing...\n");
struct atk_data *data = platform_get_drvdata(pdev);
device->driver_data = NULL;
dev_dbg(&pdev->dev, "removing...\n");
atk_debugfs_cleanup(data);
@@ -1353,7 +1353,7 @@ static void atk_remove(struct acpi_device *device)
if (data->disable_ec) {
if (atk_ec_ctl(data, 0))
dev_err(&device->dev, "Failed to disable EC\n");
dev_err(&pdev->dev, "Failed to disable EC\n");
}
}
@@ -1370,16 +1370,16 @@ static int __init atk0110_init(void)
if (dmi_check_system(atk_force_new_if))
new_if = true;
ret = acpi_bus_register_driver(&atk_driver);
ret = platform_driver_register(&atk_driver);
if (ret)
pr_info("acpi_bus_register_driver failed: %d\n", ret);
pr_info("platform_driver_register failed: %d\n", ret);
return ret;
}
static void __exit atk0110_exit(void)
{
acpi_bus_unregister_driver(&atk_driver);
platform_driver_unregister(&atk_driver);
}
module_init(atk0110_init);

File diff suppressed because it is too large Load Diff

View File

@@ -1,247 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
*
* Baikal-T1 Process, Voltage, Temperature sensor driver
*/
#ifndef __HWMON_BT1_PVT_H__
#define __HWMON_BT1_PVT_H__
#include <linux/completion.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/mutex.h>
#include <linux/seqlock.h>
/* Baikal-T1 PVT registers and their bitfields */
#define PVT_CTRL 0x00
#define PVT_CTRL_EN BIT(0)
#define PVT_CTRL_MODE_FLD 1
#define PVT_CTRL_MODE_MASK GENMASK(3, PVT_CTRL_MODE_FLD)
#define PVT_CTRL_MODE_TEMP 0x0
#define PVT_CTRL_MODE_VOLT 0x1
#define PVT_CTRL_MODE_LVT 0x2
#define PVT_CTRL_MODE_HVT 0x4
#define PVT_CTRL_MODE_SVT 0x6
#define PVT_CTRL_TRIM_FLD 4
#define PVT_CTRL_TRIM_MASK GENMASK(8, PVT_CTRL_TRIM_FLD)
#define PVT_DATA 0x04
#define PVT_DATA_VALID BIT(10)
#define PVT_DATA_DATA_FLD 0
#define PVT_DATA_DATA_MASK GENMASK(9, PVT_DATA_DATA_FLD)
#define PVT_TTHRES 0x08
#define PVT_VTHRES 0x0C
#define PVT_LTHRES 0x10
#define PVT_HTHRES 0x14
#define PVT_STHRES 0x18
#define PVT_THRES_LO_FLD 0
#define PVT_THRES_LO_MASK GENMASK(9, PVT_THRES_LO_FLD)
#define PVT_THRES_HI_FLD 10
#define PVT_THRES_HI_MASK GENMASK(19, PVT_THRES_HI_FLD)
#define PVT_TTIMEOUT 0x1C
#define PVT_INTR_STAT 0x20
#define PVT_INTR_MASK 0x24
#define PVT_RAW_INTR_STAT 0x28
#define PVT_INTR_DVALID BIT(0)
#define PVT_INTR_TTHRES_LO BIT(1)
#define PVT_INTR_TTHRES_HI BIT(2)
#define PVT_INTR_VTHRES_LO BIT(3)
#define PVT_INTR_VTHRES_HI BIT(4)
#define PVT_INTR_LTHRES_LO BIT(5)
#define PVT_INTR_LTHRES_HI BIT(6)
#define PVT_INTR_HTHRES_LO BIT(7)
#define PVT_INTR_HTHRES_HI BIT(8)
#define PVT_INTR_STHRES_LO BIT(9)
#define PVT_INTR_STHRES_HI BIT(10)
#define PVT_INTR_ALL GENMASK(10, 0)
#define PVT_CLR_INTR 0x2C
/*
* PVT sensors-related limits and default values
* @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius.
* @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius.
* @PVT_TEMP_CHS: Number of temperature hwmon channels.
* @PVT_VOLT_MIN: Minimal voltage in mV.
* @PVT_VOLT_MAX: Maximal voltage in mV.
* @PVT_VOLT_CHS: Number of voltage hwmon channels.
* @PVT_DATA_MIN: Minimal PVT raw data value.
* @PVT_DATA_MAX: Maximal PVT raw data value.
* @PVT_TRIM_MIN: Minimal temperature sensor trim value.
* @PVT_TRIM_MAX: Maximal temperature sensor trim value.
* @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value
* when one is determined for Baikal-T1 SoC).
* @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor.
* @PVT_TRIM_STEP: Temperature stride corresponding to the trim value.
* @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds.
* @PVT_TOUT_DEF: Default data measurements timeout. In case if alarms are
* activated the PVT IRQ is enabled to be raised after each
* conversion in order to have the thresholds checked and the
* converted value cached. Too frequent conversions may cause
* the system CPU overload. Lets set the 50ms delay between
* them by default to prevent this.
*/
#define PVT_TEMP_MIN -48380L
#define PVT_TEMP_MAX 147438L
#define PVT_TEMP_CHS 1
#define PVT_VOLT_MIN 620L
#define PVT_VOLT_MAX 1168L
#define PVT_VOLT_CHS 4
#define PVT_DATA_MIN 0
#define PVT_DATA_MAX (PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD)
#define PVT_TRIM_MIN 0
#define PVT_TRIM_MAX (PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD)
#define PVT_TRIM_TEMP 7130
#define PVT_TRIM_STEP (PVT_TRIM_TEMP / PVT_TRIM_MAX)
#define PVT_TRIM_DEF 0
#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000)
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
# define PVT_TOUT_DEF 60000
#else
# define PVT_TOUT_DEF 0
#endif
/*
* enum pvt_sensor_type - Baikal-T1 PVT sensor types (correspond to each PVT
* sampling mode)
* @PVT_SENSOR*: helpers to traverse the sensors in loops.
* @PVT_TEMP: PVT Temperature sensor.
* @PVT_VOLT: PVT Voltage sensor.
* @PVT_LVT: PVT Low-Voltage threshold sensor.
* @PVT_HVT: PVT High-Voltage threshold sensor.
* @PVT_SVT: PVT Standard-Voltage threshold sensor.
*/
enum pvt_sensor_type {
PVT_SENSOR_FIRST,
PVT_TEMP = PVT_SENSOR_FIRST,
PVT_VOLT,
PVT_LVT,
PVT_HVT,
PVT_SVT,
PVT_SENSOR_LAST = PVT_SVT,
PVT_SENSORS_NUM
};
/*
* enum pvt_clock_type - Baikal-T1 PVT clocks.
* @PVT_CLOCK_APB: APB clock.
* @PVT_CLOCK_REF: PVT reference clock.
*/
enum pvt_clock_type {
PVT_CLOCK_APB,
PVT_CLOCK_REF,
PVT_CLOCK_NUM
};
/*
* struct pvt_sensor_info - Baikal-T1 PVT sensor informational structure
* @channel: Sensor channel ID.
* @label: hwmon sensor label.
* @mode: PVT mode corresponding to the channel.
* @thres_base: upper and lower threshold values of the sensor.
* @thres_sts_lo: low threshold status bitfield.
* @thres_sts_hi: high threshold status bitfield.
* @type: Sensor type.
* @attr_min_alarm: Min alarm attribute ID.
* @attr_min_alarm: Max alarm attribute ID.
*/
struct pvt_sensor_info {
int channel;
const char *label;
u32 mode;
unsigned long thres_base;
u32 thres_sts_lo;
u32 thres_sts_hi;
enum hwmon_sensor_types type;
u32 attr_min_alarm;
u32 attr_max_alarm;
};
#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres) \
{ \
.channel = _ch, \
.label = _label, \
.mode = PVT_CTRL_MODE_ ##_mode, \
.thres_base = PVT_ ##_thres, \
.thres_sts_lo = PVT_INTR_ ##_thres## _LO, \
.thres_sts_hi = PVT_INTR_ ##_thres## _HI, \
.type = _type, \
.attr_min_alarm = _type## _min, \
.attr_max_alarm = _type## _max, \
}
/*
* struct pvt_cache - PVT sensors data cache
* @data: data cache in raw format.
* @thres_sts_lo: low threshold status saved on the previous data conversion.
* @thres_sts_hi: high threshold status saved on the previous data conversion.
* @data_seqlock: cached data seq-lock.
* @conversion: data conversion completion.
*/
struct pvt_cache {
u32 data;
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
seqlock_t data_seqlock;
u32 thres_sts_lo;
u32 thres_sts_hi;
#else
struct completion conversion;
#endif
};
/*
* struct pvt_hwmon - Baikal-T1 PVT private data
* @dev: device structure of the PVT platform device.
* @hwmon: hwmon device structure.
* @regs: pointer to the Baikal-T1 PVT registers region.
* @irq: PVT events IRQ number.
* @clks: Array of the PVT clocks descriptor (APB/ref clocks).
* @ref_clk: Pointer to the reference clocks descriptor.
* @iface_mtx: Generic interface mutex (used to lock the alarm registers
* when the alarms enabled, or the data conversion interface
* if alarms are disabled).
* @sensor: current PVT sensor the data conversion is being performed for.
* @cache: data cache descriptor.
* @timeout: conversion timeout cache.
*/
struct pvt_hwmon {
struct device *dev;
struct device *hwmon;
void __iomem *regs;
int irq;
struct clk_bulk_data clks[PVT_CLOCK_NUM];
struct mutex iface_mtx;
enum pvt_sensor_type sensor;
struct pvt_cache cache[PVT_SENSORS_NUM];
ktime_t timeout;
};
/*
* struct pvt_poly_term - a term descriptor of the PVT data translation
* polynomial
* @deg: degree of the term.
* @coef: multiplication factor of the term.
* @divider: distributed divider per each degree.
* @divider_leftover: divider leftover, which couldn't be redistributed.
*/
struct pvt_poly_term {
unsigned int deg;
long coef;
long divider;
long divider_leftover;
};
/*
* struct pvt_poly - PVT data translation polynomial descriptor
* @total_divider: total data divider.
* @terms: polynomial terms up to a free one.
*/
struct pvt_poly {
long total_divider;
struct pvt_poly_term terms[];
};
#endif /* __HWMON_BT1_PVT_H__ */

View File

@@ -40,7 +40,7 @@ static ssize_t power_state_show(struct device *dev, struct device_attribute *att
retval = regmap_read(data->regmap, 0x03, &val);
if (retval < 0)
return retval;
return sprintf(buf, "%d\n", !!(val & BIT(6)));
return sysfs_emit(buf, "%d\n", !!(val & BIT(6)));
}
static ssize_t power_state_store(struct device *dev, struct device_attribute *attr,

View File

@@ -209,6 +209,14 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = &gpd_duo_drvdata,
},
{
// GPD Win 5 with AMD AI MAX 395
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-05"),
},
.driver_data = &gpd_duo_drvdata,
},
{
// GPD Pocket 4
.matches = {

View File

@@ -505,6 +505,7 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr)
(type == hwmon_curr && attr == hwmon_curr_label) ||
(type == hwmon_power && attr == hwmon_power_label) ||
(type == hwmon_energy && attr == hwmon_energy_label) ||
(type == hwmon_energy64 && attr == hwmon_energy_label) ||
(type == hwmon_humidity && attr == hwmon_humidity_label) ||
(type == hwmon_fan && attr == hwmon_fan_label);
}

View File

@@ -27,8 +27,6 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/platform_data/ina2xx.h>
/* register definitions */
#define INA209_CONFIGURATION 0x00
#define INA209_STATUS 0x01
@@ -487,7 +485,6 @@ static void ina209_restore_conf(struct i2c_client *client,
static int ina209_init_client(struct i2c_client *client,
struct ina209_data *data)
{
struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);
u32 shunt;
int reg;
@@ -501,12 +498,8 @@ static int ina209_init_client(struct i2c_client *client,
return reg;
data->config_orig = reg;
if (pdata) {
if (pdata->shunt_uohms <= 0)
return -EINVAL;
shunt = pdata->shunt_uohms;
} else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor",
&shunt)) {
if (!of_property_read_u32(client->dev.of_node, "shunt-resistor",
&shunt)) {
if (shunt == 0)
return -EINVAL;
} else {

View File

@@ -1,22 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Texas Instruments INA219, INA226 power monitor chips
*
* INA219:
* Zero Drift Bi-Directional Current/Power Monitor with I2C Interface
* Datasheet: https://www.ti.com/product/ina219
*
* INA220:
* Bi-Directional Current/Power Monitor with I2C Interface
* Datasheet: https://www.ti.com/product/ina220
*
* INA226:
* Bi-Directional Current/Power Monitor with I2C Interface
* Datasheet: https://www.ti.com/product/ina226
*
* INA230:
* Bi-directional Current/Power Monitor with I2C Interface
* Datasheet: https://www.ti.com/product/ina230
* Driver for Texas Instruments INA219, INA226 and register-layout compatible
* current/power monitor chips with I2C Interface
*
* Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.com>
* Thanks to Jan Volkering
@@ -49,7 +34,6 @@
/* INA226 register definitions */
#define INA226_MASK_ENABLE 0x06
#define INA226_ALERT_LIMIT 0x07
#define INA226_DIE_ID 0xFF
/* SY24655 register definitions */
#define SY24655_EIN 0x0A
@@ -135,18 +119,27 @@ static const struct regmap_config ina2xx_regmap_config = {
.writeable_reg = ina2xx_writeable_reg,
};
enum ina2xx_ids { ina219, ina226, ina260, sy24655 };
enum ina2xx_ids {
ina219,
ina226,
ina234,
ina260,
sy24655
};
struct ina2xx_config {
u16 config_default;
bool has_alerts; /* chip supports alerts and limits */
bool has_ishunt; /* chip has internal shunt resistor */
bool has_power_average; /* chip has internal shunt resistor */
bool has_power_average; /* chip supports average power */
bool has_update_interval;
int calibration_value;
int shunt_div;
int shunt_voltage_shift;
int bus_voltage_shift;
int bus_voltage_lsb; /* uV */
int power_lsb_factor;
int current_shift;
};
struct ina2xx_data {
@@ -165,44 +158,70 @@ static const struct ina2xx_config ina2xx_config[] = {
.config_default = INA219_CONFIG_DEFAULT,
.calibration_value = 4096,
.shunt_div = 100,
.shunt_voltage_shift = 0,
.bus_voltage_shift = 3,
.bus_voltage_lsb = 4000,
.power_lsb_factor = 20,
.has_alerts = false,
.has_ishunt = false,
.has_power_average = false,
.current_shift = 0,
.has_update_interval = false,
},
[ina226] = {
.config_default = INA226_CONFIG_DEFAULT,
.calibration_value = 2048,
.shunt_div = 400,
.shunt_voltage_shift = 0,
.bus_voltage_shift = 0,
.bus_voltage_lsb = 1250,
.power_lsb_factor = 25,
.has_alerts = true,
.has_ishunt = false,
.has_power_average = false,
.current_shift = 0,
.has_update_interval = true,
},
[ina234] = {
.config_default = INA226_CONFIG_DEFAULT,
.calibration_value = 2048,
.shunt_div = 25, /* 2.5 µV/LSB raw ADC reading from INA2XX_SHUNT_VOLTAGE */
.shunt_voltage_shift = 4,
.bus_voltage_shift = 4,
.bus_voltage_lsb = 25600,
.power_lsb_factor = 32,
.has_alerts = true,
.has_ishunt = false,
.has_power_average = false,
.current_shift = 4,
.has_update_interval = true,
},
[ina260] = {
.config_default = INA260_CONFIG_DEFAULT,
.shunt_div = 400,
.shunt_voltage_shift = 0,
.bus_voltage_shift = 0,
.bus_voltage_lsb = 1250,
.power_lsb_factor = 8,
.has_alerts = true,
.has_ishunt = true,
.has_power_average = false,
.current_shift = 0,
.has_update_interval = true,
},
[sy24655] = {
.config_default = SY24655_CONFIG_DEFAULT,
.calibration_value = 4096,
.shunt_div = 400,
.shunt_voltage_shift = 0,
.bus_voltage_shift = 0,
.bus_voltage_lsb = 1250,
.power_lsb_factor = 25,
.has_alerts = true,
.has_ishunt = false,
.has_power_average = true,
.current_shift = 0,
.has_update_interval = false,
},
};
@@ -255,7 +274,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
switch (reg) {
case INA2XX_SHUNT_VOLTAGE:
/* signed register */
val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div);
val = (s16)regval >> data->config->shunt_voltage_shift;
val = DIV_ROUND_CLOSEST(val, data->config->shunt_div);
break;
case INA2XX_BUS_VOLTAGE:
val = (regval >> data->config->bus_voltage_shift) *
@@ -267,7 +287,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
break;
case INA2XX_CURRENT:
/* signed register, result in mA */
val = (s16)regval * data->current_lsb_uA;
val = ((s16)regval >> data->config->current_shift) *
data->current_lsb_uA;
val = DIV_ROUND_CLOSEST(val, 1000);
break;
case INA2XX_CALIBRATION:
@@ -361,6 +382,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val)
case INA2XX_SHUNT_VOLTAGE:
val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div);
val *= data->config->shunt_div;
val <<= data->config->shunt_voltage_shift;
return clamp_val(val, 0, SHRT_MAX);
case INA2XX_BUS_VOLTAGE:
val = clamp_val(val, 0, 200000);
@@ -375,6 +397,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val)
val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000);
/* signed register, result in mA */
val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA);
val <<= data->config->current_shift;
return clamp_val(val, SHRT_MIN, SHRT_MAX);
default:
/* programmer goofed */
@@ -706,7 +729,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type
const struct ina2xx_data *data = _data;
bool has_alerts = data->config->has_alerts;
bool has_power_average = data->config->has_power_average;
enum ina2xx_ids chip = data->chip;
bool has_update_interval = data->config->has_update_interval;
switch (type) {
case hwmon_in:
@@ -768,7 +791,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
if (chip == ina226 || chip == ina260)
if (has_update_interval)
return 0644;
break;
default:
@@ -982,6 +1005,7 @@ static const struct i2c_device_id ina2xx_id[] = {
{ "ina226", ina226 },
{ "ina230", ina226 },
{ "ina231", ina226 },
{ "ina234", ina234 },
{ "ina260", ina260 },
{ "sy24655", sy24655 },
{ }
@@ -1013,6 +1037,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
.compatible = "ti,ina231",
.data = (void *)ina226
},
{
.compatible = "ti,ina234",
.data = (void *)ina234
},
{
.compatible = "ti,ina260",
.data = (void *)ina260

View File

@@ -9,6 +9,7 @@
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/regmap.h>
@@ -185,8 +186,8 @@ static int isl28022_read_power(struct device *dev, u32 attr, long *val)
ISL28022_REG_POWER, &regval);
if (err < 0)
return err;
*val = ((51200000L * ((long)data->gain)) /
(long)data->shunt) * (long)regval;
*val = min(div_u64(51200000ULL * data->gain * regval,
data->shunt), LONG_MAX);
break;
default:
return -EOPNOTSUPP;
@@ -337,21 +338,28 @@ DEFINE_SHOW_ATTRIBUTE(shunt_voltage);
*/
static int isl28022_read_properties(struct device *dev, struct isl28022_data *data)
{
const char *propname;
u32 val;
int err;
err = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val);
if (err == -EINVAL)
propname = "shunt-resistor-micro-ohms";
if (device_property_present(dev, propname)) {
err = device_property_read_u32(dev, propname, &val);
if (err)
return err;
} else {
val = 10000;
else if (err < 0)
return err;
}
data->shunt = val;
err = device_property_read_u32(dev, "renesas,shunt-range-microvolt", &val);
if (err == -EINVAL)
propname = "renesas,shunt-range-microvolt";
if (device_property_present(dev, propname)) {
err = device_property_read_u32(dev, propname, &val);
if (err)
return err;
} else {
val = 320000;
else if (err < 0)
return err;
}
switch (val) {
case 40000:
@@ -375,20 +383,19 @@ static int isl28022_read_properties(struct device *dev, struct isl28022_data *da
goto shunt_invalid;
break;
default:
return dev_err_probe(dev, -EINVAL,
"renesas,shunt-range-microvolt invalid value %d\n",
val);
return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val);
}
err = device_property_read_u32(dev, "renesas,average-samples", &val);
if (err == -EINVAL)
propname = "renesas,average-samples";
if (device_property_present(dev, propname)) {
err = device_property_read_u32(dev, propname, &val);
if (err)
return err;
} else {
val = 1;
else if (err < 0)
return err;
}
if (val > 128 || hweight32(val) != 1)
return dev_err_probe(dev, -EINVAL,
"renesas,average-samples invalid value %d\n",
val);
return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val);
data->average = val;

View File

@@ -16,6 +16,7 @@
* IT8622E Super I/O chip w/LPC interface
* IT8623E Super I/O chip w/LPC interface
* IT8628E Super I/O chip w/LPC interface
* IT8689E Super I/O chip w/LPC interface
* IT8705F Super I/O chip w/LPC interface
* IT8712F Super I/O chip w/LPC interface
* IT8716F Super I/O chip w/LPC interface
@@ -64,7 +65,7 @@
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
it8771, it8772, it8781, it8782, it8783, it8786, it8790,
it8792, it8603, it8620, it8622, it8628, it87952 };
it8792, it8603, it8620, it8622, it8628, it8689, it87952 };
static struct platform_device *it87_pdev[2];
@@ -162,6 +163,7 @@ static inline void superio_exit(int ioreg, bool noexit)
#define IT8622E_DEVID 0x8622
#define IT8623E_DEVID 0x8623
#define IT8628E_DEVID 0x8628
#define IT8689E_DEVID 0x8689
#define IT87952E_DEVID 0x8695
/* Logical device 4 (Environmental Monitor) registers */
@@ -502,6 +504,15 @@ static const struct it87_devices it87_devices[] = {
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
.peci_mask = 0x07,
},
[it8689] = {
.name = "it8689",
.model = "IT8689E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it87952] = {
.name = "it87952",
.model = "IT87952E",
@@ -2785,6 +2796,9 @@ static int __init it87_find(int sioaddr, unsigned short *address,
case IT8628E_DEVID:
sio_data->type = it8628;
break;
case IT8689E_DEVID:
sio_data->type = it8689;
break;
case IT87952E_DEVID:
sio_data->type = it87952;
break;
@@ -3000,6 +3014,51 @@ static int __init it87_find(int sioaddr, unsigned short *address,
else
sio_data->skip_in |= BIT(9);
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8689) {
int reg;
superio_select(sioaddr, GPIO);
/* Check for pwm5 */
reg = superio_inb(sioaddr, IT87_SIO_GPIO1_REG);
if (reg & BIT(6))
sio_data->skip_pwm |= BIT(4);
/* Check for fan4, fan5 */
reg = superio_inb(sioaddr, IT87_SIO_GPIO2_REG);
if (!(reg & BIT(5)))
sio_data->skip_fan |= BIT(3);
if (!(reg & BIT(4)))
sio_data->skip_fan |= BIT(4);
/* Check for pwm3, fan3 */
reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG);
if (reg & BIT(6))
sio_data->skip_pwm |= BIT(2);
if (reg & BIT(7))
sio_data->skip_fan |= BIT(2);
/* Check for pwm4 */
reg = superio_inb(sioaddr, IT87_SIO_GPIO4_REG);
if (reg & BIT(2))
sio_data->skip_pwm |= BIT(3);
/* Check for pwm2, fan2 */
reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG);
if (reg & BIT(1))
sio_data->skip_pwm |= BIT(1);
if (reg & BIT(2))
sio_data->skip_fan |= BIT(1);
/* Check for pwm6, fan6 */
if (!(reg & BIT(7))) {
sio_data->skip_pwm |= BIT(5);
sio_data->skip_fan |= BIT(5);
}
/* in9 (AVCC3) is always internal, no PINX2 check needed */
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8622) {

View File

@@ -0,0 +1,359 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hardware monitoring driver for LattePanda Sigma EC.
*
* The LattePanda Sigma is an x86 SBC made by DFRobot with an ITE IT8613E
* Embedded Controller that manages a CPU fan and thermal sensors.
*
* The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
* returning 0 and provides only stub ECRD/ECWT methods that return Zero
* for all registers. Since the kernel's ACPI EC subsystem never initializes,
* ec_read() is not available and direct port I/O to the standard ACPI EC
* ports (0x62/0x66) is used instead.
*
* Because ACPI never initializes the EC, there is no concurrent firmware
* access to these ports, and no ACPI Global Lock or namespace mutex is
* required. The hwmon with_info API serializes all sysfs callbacks,
* so no additional driver-level locking is needed.
*
* The EC register map was discovered by dumping all 256 registers,
* identifying those that change in real-time, and validating by physically
* stopping the fan and observing the RPM register drop to zero. The map
* has been verified on BIOS version 5.27; other versions may differ.
*
* Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
*/
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "lattepanda_sigma_ec"
/* EC I/O ports (standard ACPI EC interface) */
#define EC_DATA_PORT 0x62
#define EC_CMD_PORT 0x66 /* also status port */
/* EC commands */
#define EC_CMD_READ 0x80
/* EC status register bits */
#define EC_STATUS_OBF 0x01 /* Output Buffer Full */
#define EC_STATUS_IBF 0x02 /* Input Buffer Full */
/* EC register offsets for LattePanda Sigma (BIOS 5.27) */
#define EC_REG_FAN_RPM_HI 0x2E
#define EC_REG_FAN_RPM_LO 0x2F
#define EC_REG_TEMP_BOARD 0x60
#define EC_REG_TEMP_CPU 0x70
#define EC_REG_FAN_DUTY 0x93
/*
* EC polling uses udelay() because the EC typically responds within a
* few microseconds. The kernel's own ACPI EC driver (drivers/acpi/ec.c)
* likewise uses udelay() for busy-polling with a per-poll delay of 550us.
*
* usleep_range() was tested but caused EC protocol failures: the EC
* clears its status flags within microseconds, and sleeping for 50-100us
* between polls allowed the flags to transition past the expected state.
*
* The worst-case total busy-wait of 25ms covers EC recovery after errors.
* In practice the EC responds within 10us so the loop exits immediately.
*/
#define EC_TIMEOUT_US 25000
#define EC_POLL_US 1
static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force,
"Force loading on untested BIOS versions (default: false)");
static struct platform_device *lps_ec_pdev;
static int ec_wait_ibf_clear(void)
{
int i;
for (i = 0; i < EC_TIMEOUT_US; i++) {
if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF))
return 0;
udelay(EC_POLL_US);
}
return -ETIMEDOUT;
}
static int ec_wait_obf_set(void)
{
int i;
for (i = 0; i < EC_TIMEOUT_US; i++) {
if (inb(EC_CMD_PORT) & EC_STATUS_OBF)
return 0;
udelay(EC_POLL_US);
}
return -ETIMEDOUT;
}
static int ec_read_reg(u8 reg, u8 *val)
{
int ret;
ret = ec_wait_ibf_clear();
if (ret)
return ret;
outb(EC_CMD_READ, EC_CMD_PORT);
ret = ec_wait_ibf_clear();
if (ret)
return ret;
outb(reg, EC_DATA_PORT);
ret = ec_wait_obf_set();
if (ret)
return ret;
*val = inb(EC_DATA_PORT);
return 0;
}
/*
* Read a 16-bit big-endian value from two consecutive EC registers.
*
* The EC may update the register pair between reading the high and low
* bytes, which could produce a corrupted value if the high byte rolls
* over (e.g., 0x0100 -> 0x00FF read as 0x01FF). Guard against this by
* re-reading the high byte after reading the low byte. If the high byte
* changed, re-read the low byte to get a consistent pair.
* See also lm90_read16() which uses the same approach.
*/
static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val)
{
int ret;
u8 oldh, newh, lo;
ret = ec_read_reg(reg_hi, &oldh);
if (ret)
return ret;
ret = ec_read_reg(reg_lo, &lo);
if (ret)
return ret;
ret = ec_read_reg(reg_hi, &newh);
if (ret)
return ret;
if (oldh != newh) {
ret = ec_read_reg(reg_lo, &lo);
if (ret)
return ret;
}
*val = ((u16)newh << 8) | lo;
return 0;
}
static int
lps_ec_read_string(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel,
const char **str)
{
switch (type) {
case hwmon_fan:
*str = "CPU Fan";
return 0;
case hwmon_temp:
*str = channel == 0 ? "Board Temp" : "CPU Temp";
return 0;
default:
return -EOPNOTSUPP;
}
}
static umode_t
lps_ec_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_fan:
if (attr == hwmon_fan_input || attr == hwmon_fan_label)
return 0444;
break;
case hwmon_temp:
if (attr == hwmon_temp_input || attr == hwmon_temp_label)
return 0444;
break;
default:
break;
}
return 0;
}
static int
lps_ec_read(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
u16 rpm;
u8 v;
int ret;
switch (type) {
case hwmon_fan:
if (attr != hwmon_fan_input)
return -EOPNOTSUPP;
ret = ec_read_reg16(EC_REG_FAN_RPM_HI,
EC_REG_FAN_RPM_LO, &rpm);
if (ret)
return ret;
*val = rpm;
return 0;
case hwmon_temp:
if (attr != hwmon_temp_input)
return -EOPNOTSUPP;
ret = ec_read_reg(channel == 0 ? EC_REG_TEMP_BOARD
: EC_REG_TEMP_CPU,
&v);
if (ret)
return ret;
/* EC reports unsigned 8-bit temperature in degrees Celsius */
*val = (unsigned long)v * 1000;
return 0;
default:
return -EOPNOTSUPP;
}
}
static const struct hwmon_channel_info * const lps_ec_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
NULL
};
static const struct hwmon_ops lps_ec_ops = {
.is_visible = lps_ec_is_visible,
.read = lps_ec_read,
.read_string = lps_ec_read_string,
};
static const struct hwmon_chip_info lps_ec_chip_info = {
.ops = &lps_ec_ops,
.info = lps_ec_info,
};
static int lps_ec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *hwmon;
u8 test;
int ret;
if (!devm_request_region(dev, EC_DATA_PORT, 1, DRIVER_NAME))
return dev_err_probe(dev, -EBUSY,
"Failed to request EC data port 0x%x\n",
EC_DATA_PORT);
if (!devm_request_region(dev, EC_CMD_PORT, 1, DRIVER_NAME))
return dev_err_probe(dev, -EBUSY,
"Failed to request EC cmd port 0x%x\n",
EC_CMD_PORT);
/* Sanity check: verify EC is responsive */
ret = ec_read_reg(EC_REG_FAN_DUTY, &test);
if (ret)
return dev_err_probe(dev, ret,
"EC not responding on ports 0x%x/0x%x\n",
EC_DATA_PORT, EC_CMD_PORT);
hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, NULL,
&lps_ec_chip_info, NULL);
if (IS_ERR(hwmon))
return dev_err_probe(dev, PTR_ERR(hwmon),
"Failed to register hwmon device\n");
dev_info(dev, "EC hwmon registered (fan duty: %u%%)\n", test);
return 0;
}
/* DMI table with strict BIOS version match (override with force=1) */
static const struct dmi_system_id lps_ec_dmi_table[] = {
{
.ident = "LattePanda Sigma",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
DMI_MATCH(DMI_BIOS_VERSION, "5.27"),
},
},
{ } /* terminator */
};
MODULE_DEVICE_TABLE(dmi, lps_ec_dmi_table);
/* Loose table (vendor + product only) for use with force=1 */
static const struct dmi_system_id lps_ec_dmi_table_force[] = {
{
.ident = "LattePanda Sigma",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
},
},
{ } /* terminator */
};
static struct platform_driver lps_ec_driver = {
.probe = lps_ec_probe,
.driver = {
.name = DRIVER_NAME,
},
};
static int __init lps_ec_init(void)
{
int ret;
if (!dmi_check_system(lps_ec_dmi_table)) {
if (!force || !dmi_check_system(lps_ec_dmi_table_force))
return -ENODEV;
pr_warn("%s: BIOS version not verified, loading due to force=1\n",
DRIVER_NAME);
}
ret = platform_driver_register(&lps_ec_driver);
if (ret)
return ret;
lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1,
NULL, 0);
if (IS_ERR(lps_ec_pdev)) {
platform_driver_unregister(&lps_ec_driver);
return PTR_ERR(lps_ec_pdev);
}
return 0;
}
static void __exit lps_ec_exit(void)
{
platform_device_unregister(lps_ec_pdev);
platform_driver_unregister(&lps_ec_driver);
}
module_init(lps_ec_init);
module_exit(lps_ec_exit);
MODULE_AUTHOR("Mariano Abad <weimaraner@gmail.com>");
MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC");
MODULE_LICENSE("GPL");

View File

@@ -108,6 +108,7 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
#define PCT2075_REG_IDLE 0x04
struct lm75_data {
const char *label;
struct regmap *regmap;
u16 orig_conf;
u8 resolution; /* In bits, 9 to 16 */
@@ -363,6 +364,16 @@ static irqreturn_t lm75_alarm_handler(int irq, void *private)
return IRQ_HANDLED;
}
static int lm75_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
struct lm75_data *data = dev_get_drvdata(dev);
*str = data->label;
return 0;
}
static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@@ -534,6 +545,9 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type,
switch (attr) {
case hwmon_temp_input:
return 0444;
case hwmon_temp_label:
/* Hide label node if label is not provided */
return config_data->label ? 0444 : 0;
case hwmon_temp_max:
case hwmon_temp_max_hyst:
return 0644;
@@ -553,13 +567,14 @@ static const struct hwmon_channel_info * const lm75_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_ALARM),
NULL
};
static const struct hwmon_ops lm75_hwmon_ops = {
.is_visible = lm75_is_visible,
.read_string = lm75_read_string,
.read = lm75_read,
.write = lm75_write,
};
@@ -721,6 +736,9 @@ static int lm75_generic_probe(struct device *dev, const char *name,
/* needed by custom regmap callbacks */
dev_set_drvdata(dev, data);
/* Save the connected input label if available */
device_property_read_string(dev, "label", &data->label);
data->kind = kind;
data->regmap = regmap;

View File

@@ -1328,15 +1328,16 @@ static int ltc4282_setup(struct ltc4282_state *st, struct device *dev)
if (ret)
return ret;
/* default to 1 milli-ohm so we can probe without FW properties */
st->rsense = 1 * (NANO / MILLI);
ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
&st->rsense);
if (ret)
return dev_err_probe(dev, ret,
"Failed to read adi,rsense-nano-ohms\n");
if (st->rsense < CENTI)
return dev_err_probe(dev, -EINVAL,
"adi,rsense-nano-ohms too small (< %lu)\n",
CENTI);
if (!ret) {
if (st->rsense < CENTI)
return dev_err_probe(dev, -EINVAL,
"adi,rsense-nano-ohms too small (< %lu)\n",
CENTI);
}
/*
* The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which

View File

@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/sysfs.h>
#define MAX31722_REG_CFG 0x00
#define MAX31722_REG_TEMP_LSB 0x01
@@ -56,7 +57,7 @@ static ssize_t max31722_temp_show(struct device *dev,
if (ret < 0)
return ret;
/* Keep 12 bits and multiply by the scale of 62.5 millidegrees/bit. */
return sprintf(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32);
return sysfs_emit(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32);
}
static SENSOR_DEVICE_ATTR_RO(temp1_input, max31722_temp, 0);

View File

@@ -27,6 +27,7 @@
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/sysfs.h>
#include <linux/thermal.h>
/*
@@ -312,7 +313,7 @@ static ssize_t alarm_show(struct device *dev,
mutex_unlock(&data->update_lock);
}
return sprintf(buf, "%d\n", alarm);
return sysfs_emit(buf, "%d\n", alarm);
}
static SENSOR_DEVICE_ATTR_RO(gpio1_alarm, alarm, MAX6650_ALRM_GPIO1);

998
drivers/hwmon/mcp9982.c Normal file
View File

@@ -0,0 +1,998 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive
* Temperature Monitor Family
*
* Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
*
* Author: Victor Duicu <victor.duicu@microchip.com>
*
* Datasheet can be found here:
* https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/byteorder/generic.h>
#include <linux/delay.h>
#include <linux/device/devres.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/math.h>
#include <linux/minmax.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/time64.h>
#include <linux/util_macros.h>
/* MCP9982 Registers */
#define MCP9982_HIGH_BYTE_ADDR(index) (2 * (index))
#define MCP9982_ONE_SHOT_ADDR 0x0A
#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR 0x0B
#define MCP9982_INTERNAL_LOW_LIMIT_ADDR 0x0C
#define MCP9982_EXT_HIGH_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0D)
#define MCP9982_EXT_LOW_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0F)
#define MCP9982_THERM_LIMIT_ADDR(index) ((index) + 0x1D)
#define MCP9982_CFG_ADDR 0x22
#define MCP9982_CONV_ADDR 0x24
#define MCP9982_HYS_ADDR 0x25
#define MCP9982_CONSEC_ALRT_ADDR 0x26
#define MCP9982_ALRT_CFG_ADDR 0x27
#define MCP9982_RUNNING_AVG_ADDR 0x28
#define MCP9982_HOTTEST_CFG_ADDR 0x29
#define MCP9982_STATUS_ADDR 0x2A
#define MCP9982_EXT_FAULT_STATUS_ADDR 0x2B
#define MCP9982_HIGH_LIMIT_STATUS_ADDR 0x2C
#define MCP9982_LOW_LIMIT_STATUS_ADDR 0x2D
#define MCP9982_THERM_LIMIT_STATUS_ADDR 0x2E
#define MCP9982_HOTTEST_HIGH_BYTE_ADDR 0x2F
#define MCP9982_HOTTEST_LOW_BYTE_ADDR 0x30
#define MCP9982_HOTTEST_STATUS_ADDR 0x31
#define MCP9982_THERM_SHTDWN_CFG_ADDR 0x32
#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR 0x33
#define MCP9982_EXT_BETA_CFG_ADDR(index) ((index) + 0x33)
#define MCP9982_EXT_IDEAL_ADDR(index) ((index) + 0x35)
/* MCP9982 Bits */
#define MCP9982_CFG_MSKAL BIT(7)
#define MCP9982_CFG_RS BIT(6)
#define MCP9982_CFG_ATTHM BIT(5)
#define MCP9982_CFG_RECD12 BIT(4)
#define MCP9982_CFG_RECD34 BIT(3)
#define MCP9982_CFG_RANGE BIT(2)
#define MCP9982_CFG_DA_ENA BIT(1)
#define MCP9982_CFG_APDD BIT(0)
#define MCP9982_STATUS_BUSY BIT(5)
/* Constants and default values */
#define MCP9982_MAX_NUM_CHANNELS 5
#define MCP9982_BETA_AUTODETECT 16
#define MCP9982_IDEALITY_DEFAULT 18
#define MCP9982_OFFSET 64
#define MCP9982_DEFAULT_CONSEC_ALRT_VAL 112
#define MCP9982_DEFAULT_HYS_VAL 10
#define MCP9982_DEFAULT_CONV_VAL 6
#define MCP9982_WAKE_UP_TIME_US 125000
#define MCP9982_WAKE_UP_TIME_MAX_US 130000
#define MCP9982_HIGH_LIMIT_DEFAULT 85000
#define MCP9982_LOW_LIMIT_DEFAULT 0
static const struct hwmon_channel_info * const mcp9985_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
HWMON_T_CRIT_HYST,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
HWMON_T_CRIT_HYST,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
HWMON_T_CRIT_HYST,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
HWMON_T_CRIT_HYST,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
HWMON_T_CRIT_HYST),
HWMON_CHANNEL_INFO(chip,
HWMON_C_UPDATE_INTERVAL),
NULL
};
/**
* struct mcp9982_features - features of a mcp9982 instance
* @name: chip's name
* @phys_channels: number of physical channels supported by the chip
* @hw_thermal_shutdown: presence of hardware thermal shutdown circuitry
* @allow_apdd: whether the chip supports enabling APDD
* @has_recd34: whether the chip has the channels that are affected by recd34
*/
struct mcp9982_features {
const char *name;
u8 phys_channels;
bool hw_thermal_shutdown;
bool allow_apdd;
bool has_recd34;
};
static const struct mcp9982_features mcp9933_chip_config = {
.name = "mcp9933",
.phys_channels = 3,
.hw_thermal_shutdown = false,
.allow_apdd = true,
.has_recd34 = false,
};
static const struct mcp9982_features mcp9933d_chip_config = {
.name = "mcp9933d",
.phys_channels = 3,
.hw_thermal_shutdown = true,
.allow_apdd = true,
.has_recd34 = false,
};
static const struct mcp9982_features mcp9982_chip_config = {
.name = "mcp9982",
.phys_channels = 2,
.hw_thermal_shutdown = false,
.allow_apdd = false,
.has_recd34 = false,
};
static const struct mcp9982_features mcp9982d_chip_config = {
.name = "mcp9982d",
.phys_channels = 2,
.hw_thermal_shutdown = true,
.allow_apdd = false,
.has_recd34 = false,
};
static const struct mcp9982_features mcp9983_chip_config = {
.name = "mcp9983",
.phys_channels = 3,
.hw_thermal_shutdown = false,
.allow_apdd = false,
.has_recd34 = true,
};
static const struct mcp9982_features mcp9983d_chip_config = {
.name = "mcp9983d",
.phys_channels = 3,
.hw_thermal_shutdown = true,
.allow_apdd = false,
.has_recd34 = true,
};
static const struct mcp9982_features mcp9984_chip_config = {
.name = "mcp9984",
.phys_channels = 4,
.hw_thermal_shutdown = false,
.allow_apdd = true,
.has_recd34 = true,
};
static const struct mcp9982_features mcp9984d_chip_config = {
.name = "mcp9984d",
.phys_channels = 4,
.hw_thermal_shutdown = true,
.allow_apdd = true,
.has_recd34 = true,
};
static const struct mcp9982_features mcp9985_chip_config = {
.name = "mcp9985",
.phys_channels = 5,
.hw_thermal_shutdown = false,
.allow_apdd = true,
.has_recd34 = true,
};
static const struct mcp9982_features mcp9985d_chip_config = {
.name = "mcp9985d",
.phys_channels = 5,
.hw_thermal_shutdown = true,
.allow_apdd = true,
.has_recd34 = true,
};
static const unsigned int mcp9982_update_interval[11] = {
16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16
};
/* MCP9982 regmap configuration */
static const struct regmap_range mcp9982_regmap_wr_ranges[] = {
regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR),
regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR),
regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR),
regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)),
};
static const struct regmap_access_table mcp9982_regmap_wr_table = {
.yes_ranges = mcp9982_regmap_wr_ranges,
.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges),
};
static const struct regmap_range mcp9982_regmap_rd_ranges[] = {
regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR),
regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)),
};
static const struct regmap_access_table mcp9982_regmap_rd_table = {
.yes_ranges = mcp9982_regmap_rd_ranges,
.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges),
};
static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MCP9982_ONE_SHOT_ADDR:
case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
case MCP9982_EXT_LOW_LIMIT_ADDR(1):
case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1:
case MCP9982_EXT_LOW_LIMIT_ADDR(2):
case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1:
case MCP9982_EXT_LOW_LIMIT_ADDR(3):
case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1:
case MCP9982_EXT_LOW_LIMIT_ADDR(4):
case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1:
case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1:
case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1:
case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1:
case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1:
case MCP9982_THERM_LIMIT_ADDR(0):
case MCP9982_THERM_LIMIT_ADDR(1):
case MCP9982_THERM_LIMIT_ADDR(2):
case MCP9982_THERM_LIMIT_ADDR(3):
case MCP9982_THERM_LIMIT_ADDR(4):
case MCP9982_CFG_ADDR:
case MCP9982_CONV_ADDR:
case MCP9982_HYS_ADDR:
case MCP9982_CONSEC_ALRT_ADDR:
case MCP9982_ALRT_CFG_ADDR:
case MCP9982_RUNNING_AVG_ADDR:
case MCP9982_HOTTEST_CFG_ADDR:
case MCP9982_THERM_SHTDWN_CFG_ADDR:
return false;
default:
return true;
}
}
static const struct regmap_config mcp9982_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.rd_table = &mcp9982_regmap_rd_table,
.wr_table = &mcp9982_regmap_wr_table,
.volatile_reg = mcp9982_is_volatile_reg,
.max_register = MCP9982_EXT_IDEAL_ADDR(4),
.cache_type = REGCACHE_MAPLE,
};
/**
* struct mcp9982_priv - information about chip parameters
* @regmap: device register map
* @chip: pointer to structure holding chip features
* @labels: labels of the channels
* @interval_idx: index representing the current update interval
* @enabled_channel_mask: mask containing which channels should be enabled
* @num_channels: number of active physical channels
* @recd34_enable: state of Resistance Error Correction(REC) on channels 3 and 4
* @recd12_enable: state of Resistance Error Correction(REC) on channels 1 and 2
* @apdd_enable: state of anti-parallel diode mode
* @run_state: chip is in Run state, otherwise is in Standby state
*/
struct mcp9982_priv {
struct regmap *regmap;
const struct mcp9982_features *chip;
const char *labels[MCP9982_MAX_NUM_CHANNELS];
unsigned int interval_idx;
unsigned long enabled_channel_mask;
u8 num_channels;
bool recd34_enable;
bool recd12_enable;
bool apdd_enable;
bool run_state;
};
static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val)
{
unsigned int limit, reg_high, reg_low;
int ret;
switch (address) {
case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
case MCP9982_THERM_LIMIT_ADDR(0):
case MCP9982_THERM_LIMIT_ADDR(1):
case MCP9982_THERM_LIMIT_ADDR(2):
case MCP9982_THERM_LIMIT_ADDR(3):
case MCP9982_THERM_LIMIT_ADDR(4):
ret = regmap_read(priv->regmap, address, &limit);
if (ret)
return ret;
*val = ((int)limit - MCP9982_OFFSET) * 1000;
return 0;
case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
case MCP9982_EXT_LOW_LIMIT_ADDR(1):
case MCP9982_EXT_LOW_LIMIT_ADDR(2):
case MCP9982_EXT_LOW_LIMIT_ADDR(3):
case MCP9982_EXT_LOW_LIMIT_ADDR(4):
/*
* In order to keep consistency with reading temperature memory region we will use
* single byte I2C read.
*/
ret = regmap_read(priv->regmap, address, &reg_high);
if (ret)
return ret;
ret = regmap_read(priv->regmap, address + 1, &reg_low);
if (ret)
return ret;
*val = ((reg_high << 8) + reg_low) >> 5;
*val = (*val - (MCP9982_OFFSET << 3)) * 125;
return 0;
default:
return -EINVAL;
}
}
static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
{
struct mcp9982_priv *priv = dev_get_drvdata(dev);
unsigned int reg_high, reg_low, hyst, reg_status;
int ret;
u8 addr;
/*
* In Standby State the conversion cycle must be initated manually in
* order to read fresh temperature values and the status of the alarms.
*/
if (!priv->run_state) {
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_max_alarm:
case hwmon_temp_min_alarm:
case hwmon_temp_crit_alarm:
ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
if (ret)
return ret;
/*
* When the device is in Standby mode, 125 ms need
* to pass from writing in One Shot register before
* the conversion cycle begins.
*/
usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US);
ret = regmap_read_poll_timeout
(priv->regmap, MCP9982_STATUS_ADDR,
reg_status, !(reg_status & MCP9982_STATUS_BUSY),
MCP9982_WAKE_UP_TIME_US,
MCP9982_WAKE_UP_TIME_US * 10);
break;
}
break;
default:
break;
}
}
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
/*
* The only areas of memory that support SMBus block read are 80h->89h
* (temperature memory block) and 90h->97h(status memory block).
* In this context the read operation uses SMBus protocol and the first
* value returned will be the number of addresses that can be read.
* Temperature memory block is 10 bytes long and status memory block is 8
* bytes long.
*
* Depending on the read instruction used, the chip behaves differently:
* - regmap_bulk_read() when applied to the temperature memory block
* (80h->89h), the chip replies with SMBus block read, including count,
* additionally to the high and the low bytes. This function cannot be
* applied on the memory region 00h->09h(memory area which does not support
* block reads, returns wrong data) unless use_single_read is set in
* regmap_config.
*
* - regmap_multi_reg_read() when applied to the 00h->09h area uses I2C
* and returns only the high and low temperature bytes. When applied to
* the temperature memory block (80h->89h) returns the count till the end of
* the temperature memory block(aka SMBus count).
*
* - i2c_smbus_read_block_data() is not supported by all drivers.
*
* In order to keep consistency with reading limit memory region we will
* use single byte I2C read.
*
* Low register is latched when high temperature register is read.
*/
ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), &reg_high);
if (ret)
return ret;
ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
&reg_low);
if (ret)
return ret;
*val = ((reg_high << 8) + reg_low) >> 5;
*val = (*val - (MCP9982_OFFSET << 3)) * 125;
return 0;
case hwmon_temp_max:
if (channel)
addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
else
addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
return mcp9982_read_limit(priv, addr, val);
case hwmon_temp_max_alarm:
*val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR,
BIT(channel));
if (*val < 0)
return *val;
return 0;
case hwmon_temp_max_hyst:
if (channel)
addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
else
addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
ret = mcp9982_read_limit(priv, addr, val);
if (ret)
return ret;
ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
if (ret)
return ret;
*val -= hyst * 1000;
return 0;
case hwmon_temp_min:
if (channel)
addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
else
addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
return mcp9982_read_limit(priv, addr, val);
case hwmon_temp_min_alarm:
*val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR,
BIT(channel));
if (*val < 0)
return *val;
return 0;
case hwmon_temp_crit:
return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
case hwmon_temp_crit_alarm:
*val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
BIT(channel));
if (*val < 0)
return *val;
return 0;
case hwmon_temp_crit_hyst:
ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
if (ret)
return ret;
ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
if (ret)
return ret;
*val -= hyst * 1000;
return 0;
default:
return -EINVAL;
}
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
*val = mcp9982_update_interval[priv->interval_idx];
return 0;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
struct mcp9982_priv *priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_label:
*str = priv->labels[channel];
return 0;
default:
return -EOPNOTSUPP;
}
default:
return -EOPNOTSUPP;
}
}
static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
{
int ret;
unsigned int regh, regl;
switch (address) {
case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
case MCP9982_THERM_LIMIT_ADDR(0):
case MCP9982_THERM_LIMIT_ADDR(1):
case MCP9982_THERM_LIMIT_ADDR(2):
case MCP9982_THERM_LIMIT_ADDR(3):
case MCP9982_THERM_LIMIT_ADDR(4):
regh = DIV_ROUND_CLOSEST(val, 1000);
regh = clamp_val(regh, 0, 255);
return regmap_write(priv->regmap, address, regh);
case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
case MCP9982_EXT_LOW_LIMIT_ADDR(1):
case MCP9982_EXT_LOW_LIMIT_ADDR(2):
case MCP9982_EXT_LOW_LIMIT_ADDR(3):
case MCP9982_EXT_LOW_LIMIT_ADDR(4):
val = DIV_ROUND_CLOSEST(val, 125);
regh = (val >> 3) & 0xff;
regl = (val & 0x07) << 5;
/* Block writing is not supported by the chip. */
ret = regmap_write(priv->regmap, address, regh);
if (ret)
return ret;
return regmap_write(priv->regmap, address + 1, regl);
default:
return -EINVAL;
}
}
static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val)
{
int hyst, ret;
int limit;
val = DIV_ROUND_CLOSEST(val, 1000);
val = clamp_val(val, 0, 255);
/* Therm register is 8 bits and so it keeps only the integer part of the temperature. */
ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit);
if (ret)
return ret;
hyst = clamp_val(limit - val, 0, 255);
return regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst);
}
static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long val)
{
struct mcp9982_priv *priv = dev_get_drvdata(dev);
unsigned int idx;
u8 addr;
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
/*
* For MCP998XD and MCP9933D update interval
* can't be longer than 1 second.
*/
if (priv->chip->hw_thermal_shutdown)
val = clamp_val(val, 0, 1000);
idx = find_closest_descending(val, mcp9982_update_interval,
ARRAY_SIZE(mcp9982_update_interval));
priv->interval_idx = idx;
return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
default:
return -EINVAL;
}
case hwmon_temp:
val = clamp_val(val, -64000, 191875);
val = val + (MCP9982_OFFSET * 1000);
switch (attr) {
case hwmon_temp_max:
if (channel)
addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
else
addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
return mcp9982_write_limit(priv, addr, val);
case hwmon_temp_min:
if (channel)
addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
else
addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
return mcp9982_write_limit(priv, addr, val);
case hwmon_temp_crit:
return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
case hwmon_temp_crit_hyst:
return mcp9982_write_hyst(priv, channel, val);
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
const struct mcp9982_priv *priv = _data;
if (!test_bit(channel, &priv->enabled_channel_mask))
return 0;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_label:
if (priv->labels[channel])
return 0444;
else
return 0;
case hwmon_temp_input:
case hwmon_temp_min_alarm:
case hwmon_temp_max_alarm:
case hwmon_temp_max_hyst:
case hwmon_temp_crit_alarm:
return 0444;
case hwmon_temp_min:
case hwmon_temp_max:
case hwmon_temp_crit:
case hwmon_temp_crit_hyst:
return 0644;
default:
return 0;
}
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
return 0644;
default:
return 0;
}
default:
return 0;
}
}
static const struct hwmon_ops mcp9982_hwmon_ops = {
.is_visible = mcp9982_is_visible,
.read = mcp9982_read,
.read_string = mcp9982_read_label,
.write = mcp9982_write,
};
static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
{
long high_limit, low_limit;
unsigned int i;
int ret;
u8 val;
/* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
/* Chips with "D" work only in Run state. */
if (priv->chip->hw_thermal_shutdown && !priv->run_state)
return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
/* All chips with "D" in the name must have RECD12 enabled. */
if (priv->chip->hw_thermal_shutdown && !priv->recd12_enable)
return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD12.\n");
/* Chips 83D/84D/85D must have RECD34 enabled. */
if (priv->chip->hw_thermal_shutdown)
if ((priv->chip->has_recd34 && !priv->recd34_enable))
return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD34.\n");
/*
* Set default values in registers.
* APDD, RECD12 and RECD34 are active on 0.
*/
val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
if (ret)
return ret;
/*
* Read initial value from register.
* The convert register utilises only 4 out of 8 bits.
* Numerical values 0->10 set their respective update intervals,
* while numerical values 11->15 default to 1 second.
*/
ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx);
if (ret)
return ret;
if (priv->interval_idx >= 11)
priv->interval_idx = 4;
ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
if (ret)
return ret;
ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
if (ret)
return ret;
ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0);
if (ret)
return ret;
ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
if (ret)
return ret;
ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
if (ret)
return ret;
/*
* Only external channels 1 and 2 support beta compensation.
* Set beta auto-detection.
*/
for (i = 1; i < 3; i++)
if (test_bit(i, &priv->enabled_channel_mask)) {
ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
MCP9982_BETA_AUTODETECT);
if (ret)
return ret;
}
high_limit = MCP9982_HIGH_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
low_limit = MCP9982_LOW_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
/* Set default values for internal channel limits. */
if (test_bit(0, &priv->enabled_channel_mask)) {
ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR, high_limit);
if (ret)
return ret;
ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR, low_limit);
if (ret)
return ret;
ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0), high_limit);
if (ret)
return ret;
}
/* Set ideality factor and limits to default for external channels. */
for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
if (test_bit(i, &priv->enabled_channel_mask)) {
ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
MCP9982_IDEALITY_DEFAULT);
if (ret)
return ret;
ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i), high_limit);
if (ret)
return ret;
ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i), low_limit);
if (ret)
return ret;
ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i), high_limit);
if (ret)
return ret;
}
return 0;
}
static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
{
struct mcp9982_priv *priv = dev_get_drvdata(dev);
unsigned int reg_nr;
int ret;
/* Initialise internal channel( which is always present ). */
priv->labels[0] = "internal diode";
priv->enabled_channel_mask = 1;
/* Default values to work on systems without devicetree or firmware nodes. */
if (!dev_fwnode(dev)) {
priv->num_channels = device_nr_channels;
priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
priv->apdd_enable = false;
priv->recd12_enable = true;
priv->recd34_enable = true;
priv->run_state = true;
return 0;
}
priv->apdd_enable =
device_property_read_bool(dev, "microchip,enable-anti-parallel");
priv->recd12_enable =
device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
priv->recd34_enable =
device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
priv->run_state =
device_property_read_bool(dev, "microchip,power-state");
priv->num_channels = device_get_child_node_count(dev) + 1;
if (priv->num_channels > device_nr_channels)
return dev_err_probe(dev, -EINVAL,
"More channels than the chip supports.\n");
/* Read information about the external channels. */
device_for_each_named_child_node_scoped(dev, child, "channel") {
reg_nr = 0;
ret = fwnode_property_read_u32(child, "reg", &reg_nr);
if (ret || !reg_nr || reg_nr >= device_nr_channels)
return dev_err_probe(dev, -EINVAL,
"Channel reg is incorrectly set.\n");
fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
set_bit(reg_nr, &priv->enabled_channel_mask);
}
return 0;
}
static const struct hwmon_chip_info mcp998x_chip_info = {
.ops = &mcp9982_hwmon_ops,
.info = mcp9985_info,
};
static int mcp9982_probe(struct i2c_client *client)
{
const struct mcp9982_features *chip;
struct device *dev = &client->dev;
struct mcp9982_priv *priv;
struct device *hwmon_dev;
int ret;
priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
if (IS_ERR(priv->regmap))
return dev_err_probe(dev, PTR_ERR(priv->regmap),
"Cannot initialize register map.\n");
dev_set_drvdata(dev, priv);
chip = i2c_get_match_data(client);
if (!chip)
return -EINVAL;
priv->chip = chip;
ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
if (ret)
return ret;
ret = mcp9982_init(dev, priv);
if (ret)
return ret;
hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
&mcp998x_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id mcp9982_id[] = {
{ .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
{ .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
{ .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
{ .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
{ .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
{ .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
{ .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
{ .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
{ .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
{ .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
{ }
};
MODULE_DEVICE_TABLE(i2c, mcp9982_id);
static const struct of_device_id mcp9982_of_match[] = {
{
.compatible = "microchip,mcp9933",
.data = &mcp9933_chip_config,
}, {
.compatible = "microchip,mcp9933d",
.data = &mcp9933d_chip_config,
}, {
.compatible = "microchip,mcp9982",
.data = &mcp9982_chip_config,
}, {
.compatible = "microchip,mcp9982d",
.data = &mcp9982d_chip_config,
}, {
.compatible = "microchip,mcp9983",
.data = &mcp9983_chip_config,
}, {
.compatible = "microchip,mcp9983d",
.data = &mcp9983d_chip_config,
}, {
.compatible = "microchip,mcp9984",
.data = &mcp9984_chip_config,
}, {
.compatible = "microchip,mcp9984d",
.data = &mcp9984d_chip_config,
}, {
.compatible = "microchip,mcp9985",
.data = &mcp9985_chip_config,
}, {
.compatible = "microchip,mcp9985d",
.data = &mcp9985d_chip_config,
},
{ }
};
MODULE_DEVICE_TABLE(of, mcp9982_of_match);
static struct i2c_driver mcp9982_driver = {
.driver = {
.name = "mcp9982",
.of_match_table = mcp9982_of_match,
},
.probe = mcp9982_probe,
.id_table = mcp9982_id,
};
module_i2c_driver(mcp9982_driver);
MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>");
MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
MODULE_LICENSE("GPL");

View File

@@ -182,6 +182,7 @@ superio_exit(int ioreg)
#define NCT6683_CUSTOMER_ID_ASROCK3 0x1631
#define NCT6683_CUSTOMER_ID_ASROCK4 0x163e
#define NCT6683_CUSTOMER_ID_ASROCK5 0x1621
#define NCT6683_CUSTOMER_ID_ASROCK6 0x1633
#define NCT6683_REG_BUILD_YEAR 0x604
#define NCT6683_REG_BUILD_MONTH 0x605
@@ -1245,6 +1246,8 @@ static int nct6683_probe(struct platform_device *pdev)
break;
case NCT6683_CUSTOMER_ID_ASROCK5:
break;
case NCT6683_CUSTOMER_ID_ASROCK6:
break;
default:
if (!force)
return -ENODEV;

View File

@@ -1159,6 +1159,7 @@ static const char * const asus_wmi_boards[] = {
"Pro A520M-C",
"Pro A520M-C II",
"Pro B550M-C",
"Pro WS W480-ACE",
"Pro WS X570-ACE",
"ProArt B550-CREATOR",
"ProArt X570-CREATOR WIFI",
@@ -1258,6 +1259,7 @@ static const char * const asus_wmi_boards[] = {
"TUF Z390-PRO GAMING",
"TUF Z390M-PRO GAMING",
"TUF Z390M-PRO GAMING (WI-FI)",
"W480/SYS",
"WS Z390 PRO",
"Z490-GUNDAM (WI-FI)",
};
@@ -1270,6 +1272,7 @@ static const char * const asus_msi_boards[] = {
"EX-B760M-V5 D4",
"EX-H510M-V3",
"EX-H610M-V3 D4",
"G15CE",
"G15CF",
"PRIME A620M-A",
"PRIME B560-PLUS",
@@ -1320,6 +1323,8 @@ static const char * const asus_msi_boards[] = {
"PRIME X670-P",
"PRIME X670-P WIFI",
"PRIME X670E-PRO WIFI",
"PRIME X870-P",
"PRIME X870-P WIFI",
"PRIME Z590-A",
"PRIME Z590-P",
"PRIME Z590-P WIFI",
@@ -1362,11 +1367,18 @@ static const char * const asus_msi_boards[] = {
"ProArt B660-CREATOR D4",
"ProArt B760-CREATOR D4",
"ProArt X670E-CREATOR WIFI",
"ProArt X870E-CREATOR WIFI",
"ProArt Z690-CREATOR WIFI",
"ProArt Z790-CREATOR WIFI",
"ROG CROSSHAIR X670E EXTREME",
"ROG CROSSHAIR X670E GENE",
"ROG CROSSHAIR X670E HERO",
"ROG CROSSHAIR X870E APEX",
"ROG CROSSHAIR X870E DARK HERO",
"ROG CROSSHAIR X870E EXTREME",
"ROG CROSSHAIR X870E GLACIAL",
"ROG CROSSHAIR X870E HERO",
"ROG CROSSHAIR X870E HERO BTF",
"ROG MAXIMUS XIII APEX",
"ROG MAXIMUS XIII EXTREME",
"ROG MAXIMUS XIII EXTREME GLACIAL",
@@ -1404,6 +1416,11 @@ static const char * const asus_msi_boards[] = {
"ROG STRIX X670E-E GAMING WIFI",
"ROG STRIX X670E-F GAMING WIFI",
"ROG STRIX X670E-I GAMING WIFI",
"ROG STRIX X870-A GAMING WIFI",
"ROG STRIX X870-F GAMING WIFI",
"ROG STRIX X870-I GAMING WIFI",
"ROG STRIX X870E-E GAMING WIFI",
"ROG STRIX X870E-E GAMING WIFI7 R2",
"ROG STRIX X870E-H GAMING WIFI7",
"ROG STRIX Z590-A GAMING WIFI",
"ROG STRIX Z590-A GAMING WIFI II",
@@ -1451,6 +1468,9 @@ static const char * const asus_msi_boards[] = {
"TUF GAMING H770-PRO WIFI",
"TUF GAMING X670E-PLUS",
"TUF GAMING X670E-PLUS WIFI",
"TUF GAMING X870-PLUS WIFI",
"TUF GAMING X870-PRO WIFI7 W NEO",
"TUF GAMING X870E-PLUS WIFI7",
"TUF GAMING Z590-PLUS",
"TUF GAMING Z590-PLUS WIFI",
"TUF GAMING Z690-PLUS",
@@ -1460,6 +1480,9 @@ static const char * const asus_msi_boards[] = {
"TUF GAMING Z790-PLUS D4",
"TUF GAMING Z790-PLUS WIFI",
"TUF GAMING Z790-PLUS WIFI D4",
"X870 AYW GAMING WIFI W",
"X870 MAX GAMING WIFI7",
"X870 MAX GAMING WIFI7 W",
"Z590 WIFI GUNDAM EDITION",
};

View File

@@ -77,6 +77,15 @@ config SENSORS_ADP1050_REGULATOR
µModule regulators that can provide microprocessor power from 54V
power distribution architecture.
config SENSORS_APS_379
tristate "Sony APS-379 Power Supplies"
help
If you say yes here you get hardware monitoring support for Sony
APS-379 Power Supplies.
This driver can also be built as a module. If so, the module will
be called aps-379.
config SENSORS_BEL_PFE
tristate "Bel PFE Compatible Power Supplies"
help
@@ -702,6 +711,15 @@ config SENSORS_XDP710
This driver can also be built as a module. If so, the module will
be called xdp710.
config SENSORS_XDP720
tristate "Infineon XDP720 family"
help
If you say yes here you get hardware monitoring support for Infineon
XDP720.
This driver can also be built as a module. If so, the module will
be called xdp720.
config SENSORS_XDPE152
tristate "Infineon XDPE152 family"
help
@@ -711,6 +729,15 @@ config SENSORS_XDPE152
This driver can also be built as a module. If so, the module will
be called xdpe152c4.
config SENSORS_XDPE1A2G7B
tristate "Infineon XDPE1A2G7B"
help
If you say yes here you get hardware monitoring support for Infineon
XDPE1A2G5B and XDPE1A2G7B.
This driver can also be built as a module. If so, the module will
be called xdpe1a2g7b.
config SENSORS_XDPE122
tristate "Infineon XDPE122 family"
help

View File

@@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ACBEL_FSG032) += acbel-fsg032.o
obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
obj-$(CONFIG_SENSORS_ADP1050) += adp1050.o
obj-$(CONFIG_SENSORS_APS_379) += aps-379.o
obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o
obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o
@@ -68,8 +69,10 @@ obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
obj-$(CONFIG_SENSORS_XDP710) += xdp710.o
obj-$(CONFIG_SENSORS_XDP720) += xdp720.o
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o
obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o
obj-$(CONFIG_SENSORS_CRPS) += crps.o

View File

@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hardware monitoring driver for Sony APS-379 Power Supplies
*
* Copyright 2026 Allied Telesis Labs
*/
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pmbus.h>
#include "pmbus.h"
/*
* The VOUT format used by the chip is linear11, not linear16. Provide a hard
* coded VOUT_MODE that says VOUT is in linear mode with a fixed exponent of
* 2^-4.
*/
#define APS_379_VOUT_MODE ((u8)(-4 & 0x1f))
static int aps_379_read_byte_data(struct i2c_client *client, int page, int reg)
{
switch (reg) {
case PMBUS_VOUT_MODE:
return APS_379_VOUT_MODE;
default:
return -ENODATA;
}
}
/*
* The APS-379 uses linear11 format instead of linear16. We've reported the exponent
* via the PMBUS_VOUT_MODE so we just return the mantissa here.
*/
static int aps_379_read_vout(struct i2c_client *client)
{
int ret;
ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VOUT);
if (ret < 0)
return ret;
return clamp_val(sign_extend32(ret & 0x7ff, 10), 0, 0x3ff);
}
static int aps_379_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
switch (reg) {
case PMBUS_VOUT_UV_WARN_LIMIT:
case PMBUS_VOUT_OV_WARN_LIMIT:
case PMBUS_VOUT_UV_FAULT_LIMIT:
case PMBUS_VOUT_OV_FAULT_LIMIT:
case PMBUS_IOUT_OC_WARN_LIMIT:
case PMBUS_IOUT_UC_FAULT_LIMIT:
case PMBUS_UT_WARN_LIMIT:
case PMBUS_UT_FAULT_LIMIT:
case PMBUS_OT_WARN_LIMIT:
case PMBUS_OT_FAULT_LIMIT:
case PMBUS_PIN_OP_WARN_LIMIT:
case PMBUS_POUT_OP_WARN_LIMIT:
case PMBUS_MFR_IIN_MAX:
case PMBUS_MFR_PIN_MAX:
case PMBUS_MFR_VOUT_MIN:
case PMBUS_MFR_VOUT_MAX:
case PMBUS_MFR_IOUT_MAX:
case PMBUS_MFR_POUT_MAX:
case PMBUS_MFR_MAX_TEMP_1:
/* These commands return data but it is invalid/un-documented */
return -ENXIO;
case PMBUS_IOUT_OC_FAULT_LIMIT:
/*
* The standard requires this to be a value in Amps but it's
* actually a percentage of the rated output (123A for
* 110-240Vac, 110A for 90-100Vac) which we don't know. Ignore
* it rather than guessing.
*/
return -ENXIO;
case PMBUS_READ_VOUT:
return aps_379_read_vout(client);
default:
return -ENODATA;
}
}
static struct pmbus_driver_info aps_379_info = {
.pages = 1,
.format[PSC_VOLTAGE_OUT] = linear,
.format[PSC_CURRENT_OUT] = linear,
.format[PSC_POWER] = linear,
.format[PSC_TEMPERATURE] = linear,
.format[PSC_FAN] = linear,
.func[0] = PMBUS_HAVE_VOUT |
PMBUS_HAVE_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
PMBUS_HAVE_TEMP |
PMBUS_HAVE_FAN12,
.read_byte_data = aps_379_read_byte_data,
.read_word_data = aps_379_read_word_data,
};
static const struct i2c_device_id aps_379_id[] = {
{ "aps-379", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, aps_379_id);
static int aps_379_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 };
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA
| I2C_FUNC_SMBUS_READ_WORD_DATA
| I2C_FUNC_SMBUS_READ_BLOCK_DATA))
return -ENODEV;
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
if (ret < 0) {
dev_err(dev, "Failed to read Manufacturer Model\n");
return ret;
}
if (strncasecmp(buf, aps_379_id[0].name, strlen(aps_379_id[0].name)) != 0) {
buf[ret] = '\0';
dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
return -ENODEV;
}
return pmbus_do_probe(client, &aps_379_info);
}
static const struct of_device_id __maybe_unused aps_379_of_match[] = {
{ .compatible = "sony,aps-379" },
{},
};
MODULE_DEVICE_TABLE(of, aps_379_of_match);
static struct i2c_driver aps_379_driver = {
.driver = {
.name = "aps-379",
.of_match_table = of_match_ptr(aps_379_of_match),
},
.probe = aps_379_probe,
.id_table = aps_379_id,
};
module_i2c_driver(aps_379_driver);
MODULE_AUTHOR("Chris Packham");
MODULE_DESCRIPTION("PMBus driver for Sony APS-379");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@@ -88,13 +88,10 @@ static struct pmbus_driver_info pfe_driver_info[] = {
},
};
static const struct i2c_device_id pfe_device_id[];
static int pfe_pmbus_probe(struct i2c_client *client)
{
int model;
int model = (uintptr_t)i2c_get_match_data(client);
model = (int)i2c_match_id(pfe_device_id, client)->driver_data;
client->dev.platform_data = &pfe_plat_data;
/*

View File

@@ -222,12 +222,6 @@ static int fsp3y_detect(struct i2c_client *client)
return -ENODEV;
}
static const struct i2c_device_id fsp3y_id[] = {
{"ym2151e", ym2151e},
{"yh5151e", yh5151e},
{ }
};
static int fsp3y_probe(struct i2c_client *client)
{
struct fsp3y_data *data;
@@ -242,7 +236,7 @@ static int fsp3y_probe(struct i2c_client *client)
if (data->chip < 0)
return data->chip;
id = i2c_match_id(fsp3y_id, client);
id = i2c_client_get_device_id(client);
if (data->chip != id->driver_data)
dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n",
id->name, (int)id->driver_data, data->chip);
@@ -276,6 +270,11 @@ static int fsp3y_probe(struct i2c_client *client)
return pmbus_do_probe(client, &data->info);
}
static const struct i2c_device_id fsp3y_id[] = {
{"ym2151e", ym2151e},
{"yh5151e", yh5151e},
{ }
};
MODULE_DEVICE_TABLE(i2c, fsp3y_id);
static struct i2c_driver fsp3y_driver = {

View File

@@ -58,7 +58,7 @@ enum {
CFFPS_DEBUGFS_NUM_ENTRIES
};
enum versions { cffps1, cffps2, cffps_unknown };
enum versions { cffps_unknown, cffps1, cffps2 };
struct ibm_cffps {
enum versions version;
@@ -482,19 +482,9 @@ MODULE_DEVICE_TABLE(i2c, ibm_cffps_id);
static int ibm_cffps_probe(struct i2c_client *client)
{
int i, rc;
enum versions vs = cffps_unknown;
enum versions vs = (uintptr_t)i2c_get_match_data(client);
struct dentry *debugfs;
struct ibm_cffps *psu;
const void *md = of_device_get_match_data(&client->dev);
const struct i2c_device_id *id;
if (md) {
vs = (uintptr_t)md;
} else {
id = i2c_match_id(ibm_cffps_id, client);
if (id)
vs = (enum versions)id->driver_data;
}
if (vs == cffps_unknown) {
u16 ccin_revision = 0;
@@ -534,7 +524,7 @@ static int ibm_cffps_probe(struct i2c_client *client)
}
/* Set the client name to include the version number. */
snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1);
snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs);
}
client->dev.platform_data = &ibm_cffps_pdata;

View File

@@ -85,6 +85,7 @@ static int ina233_read_word_data(struct i2c_client *client, int page,
static int ina233_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
const char *propname;
int ret, m, R;
u32 rshunt;
u32 max_current;
@@ -114,27 +115,28 @@ static int ina233_probe(struct i2c_client *client)
/* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed. */
/* read rshunt value (uOhm) */
ret = device_property_read_u32(dev, "shunt-resistor", &rshunt);
if (ret) {
if (ret != -EINVAL)
return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n");
propname = "shunt-resistor";
if (device_property_present(dev, propname)) {
ret = device_property_read_u32(dev, propname, &rshunt);
if (ret)
return dev_err_probe(dev, ret, "%s property read fail.\n", propname);
} else {
rshunt = INA233_RSHUNT_DEFAULT;
}
if (!rshunt)
return dev_err_probe(dev, -EINVAL,
"Shunt resistor cannot be zero.\n");
return dev_err_probe(dev, -EINVAL, "%s cannot be zero.\n", propname);
/* read Maximum expected current value (uA) */
ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current);
if (ret) {
if (ret != -EINVAL)
return dev_err_probe(dev, ret,
"Maximum expected current property read fail.\n");
propname = "ti,maximum-expected-current-microamp";
if (device_property_present(dev, propname)) {
ret = device_property_read_u32(dev, propname, &max_current);
if (ret)
return dev_err_probe(dev, ret, "%s property read fail.\n", propname);
} else {
max_current = INA233_MAX_CURRENT_DEFAULT;
}
if (max_current < 32768)
return dev_err_probe(dev, -EINVAL,
"Maximum expected current cannot less then 32768.\n");
return dev_err_probe(dev, -EINVAL, "%s cannot be less than 32768.\n", propname);
/* Calculate Current_LSB according to the spec formula */
current_lsb = max_current / 32768;

View File

@@ -23,52 +23,6 @@
#define RAA_DMPVR2_READ_VMON 0xc8
#define MAX_CHANNELS 4
enum chips {
isl68137,
isl68220,
isl68221,
isl68222,
isl68223,
isl68224,
isl68225,
isl68226,
isl68227,
isl68229,
isl68233,
isl68239,
isl69222,
isl69223,
isl69224,
isl69225,
isl69227,
isl69228,
isl69234,
isl69236,
isl69239,
isl69242,
isl69243,
isl69247,
isl69248,
isl69254,
isl69255,
isl69256,
isl69259,
isl69260,
isl69268,
isl69269,
isl69298,
raa228000,
raa228004,
raa228006,
raa228228,
raa228244,
raa228246,
raa229001,
raa229004,
raa229141,
raa229621,
};
enum variants {
raa_dmpvr1_2rail,
raa_dmpvr2_1rail,
@@ -90,8 +44,6 @@ struct isl68137_data {
#define to_isl68137_data(x) container_of(x, struct isl68137_data, info)
static const struct i2c_device_id raa_dmpvr_id[];
static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client,
int page,
char *buf)
@@ -393,7 +345,7 @@ static int isl68137_probe(struct i2c_client *client)
memcpy(&data->info, &raa_dmpvr_info, sizeof(data->info));
info = &data->info;
switch (i2c_match_id(raa_dmpvr_id, client)->driver_data) {
switch ((uintptr_t)i2c_get_match_data(client)) {
case raa_dmpvr1_2rail:
info->pages = 2;
info->R[PSC_VOLTAGE_IN] = 3;
@@ -498,6 +450,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = {
{"raa228228", raa_dmpvr2_2rail_nontc},
{"raa228244", raa_dmpvr2_2rail_nontc},
{"raa228246", raa_dmpvr2_2rail_nontc},
{"raa228942", raa_dmpvr2_2rail_nontc},
{"raa228943", raa_dmpvr2_2rail_nontc},
{"raa229001", raa_dmpvr2_2rail},
{"raa229004", raa_dmpvr2_2rail},
{"raa229141", raa_dmpvr2_2rail_pmbus},

View File

@@ -733,7 +733,7 @@ static int ltc2978_probe(struct i2c_client *client)
return chip_id;
data->id = chip_id;
id = i2c_match_id(ltc2978_id, client);
id = i2c_client_get_device_id(client);
if (data->id != id->driver_data)
dev_warn(&client->dev,
"Device mismatch: Configured %s (%d), detected %d\n",

View File

@@ -318,7 +318,7 @@ static int max16601_probe(struct i2c_client *client)
if (chip_id < 0)
return chip_id;
id = i2c_match_id(max16601_id, client);
id = i2c_client_get_device_id(client);
if (chip_id != id->driver_data)
dev_warn(&client->dev,
"Device mismatch: Configured %s (%d), detected %d\n",

View File

@@ -715,10 +715,7 @@ static int max20730_probe(struct i2c_client *client)
return -ENODEV;
}
if (client->dev.of_node)
chip_id = (uintptr_t)of_device_get_match_data(dev);
else
chip_id = i2c_match_id(max20730_id, client)->driver_data;
chip_id = (uintptr_t)i2c_get_match_data(client);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)

View File

@@ -31,8 +31,6 @@ struct max31785_data {
struct pmbus_driver_info info;
};
#define to_max31785_data(x) container_of(x, struct max31785_data, info)
/*
* MAX31785 Driver Workaround
*
@@ -40,9 +38,8 @@ struct max31785_data {
* These issues are not indicated by the device itself, except for occasional
* NACK responses during master transactions. No error bits are set in STATUS_BYTE.
*
* To address this, we introduce a delay of 250us between consecutive accesses
* to the fan controller. This delay helps mitigate communication problems by
* allowing sufficient time between accesses.
* Keep minimal local delay handling for raw pre-probe SMBus accesses.
* Normal PMBus-mediated accesses use pmbus_driver_info.access_delay instead.
*/
static inline void max31785_wait(const struct max31785_data *data)
{
@@ -54,89 +51,40 @@ static inline void max31785_wait(const struct max31785_data *data)
}
static int max31785_i2c_write_byte_data(struct i2c_client *client,
struct max31785_data *driver_data,
int command, u16 data)
struct max31785_data *data,
int command, u8 value)
{
int rc;
max31785_wait(driver_data);
rc = i2c_smbus_write_byte_data(client, command, data);
driver_data->access = ktime_get();
max31785_wait(data);
rc = i2c_smbus_write_byte_data(client, command, value);
data->access = ktime_get();
return rc;
}
static int max31785_i2c_read_word_data(struct i2c_client *client,
struct max31785_data *driver_data,
struct max31785_data *data,
int command)
{
int rc;
max31785_wait(driver_data);
max31785_wait(data);
rc = i2c_smbus_read_word_data(client, command);
driver_data->access = ktime_get();
return rc;
}
static int _max31785_read_byte_data(struct i2c_client *client,
struct max31785_data *driver_data,
int page, int command)
{
int rc;
max31785_wait(driver_data);
rc = pmbus_read_byte_data(client, page, command);
driver_data->access = ktime_get();
return rc;
}
static int _max31785_write_byte_data(struct i2c_client *client,
struct max31785_data *driver_data,
int page, int command, u16 data)
{
int rc;
max31785_wait(driver_data);
rc = pmbus_write_byte_data(client, page, command, data);
driver_data->access = ktime_get();
return rc;
}
static int _max31785_read_word_data(struct i2c_client *client,
struct max31785_data *driver_data,
int page, int phase, int command)
{
int rc;
max31785_wait(driver_data);
rc = pmbus_read_word_data(client, page, phase, command);
driver_data->access = ktime_get();
return rc;
}
static int _max31785_write_word_data(struct i2c_client *client,
struct max31785_data *driver_data,
int page, int command, u16 data)
{
int rc;
max31785_wait(driver_data);
rc = pmbus_write_word_data(client, page, command, data);
driver_data->access = ktime_get();
data->access = ktime_get();
return rc;
}
static int max31785_read_byte_data(struct i2c_client *client, int page, int reg)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct max31785_data *driver_data = to_max31785_data(info);
switch (reg) {
case PMBUS_VOUT_MODE:
return -ENOTSUPP;
case PMBUS_FAN_CONFIG_12:
return _max31785_read_byte_data(client, driver_data,
page - MAX31785_NR_PAGES,
reg);
if (page < MAX31785_NR_PAGES)
return -ENODATA;
return pmbus_read_byte_data(client,
page - MAX31785_NR_PAGES,
reg);
}
return -ENODATA;
@@ -178,14 +126,28 @@ static int max31785_read_long_data(struct i2c_client *client, int page,
if (rc < 0)
return rc;
/*
* Ensure the raw transfer is properly spaced from the
* preceding PMBus transaction.
*/
pmbus_wait(client);
rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
if (rc < 0)
return rc;
/*
* Update PMBus core timing state for the raw transfer, even on error.
* Pass 0 as the operation mask since this is a raw read, intentionally
* neither PMBUS_OP_WRITE nor PMBUS_OP_PAGE_CHANGE.
*/
pmbus_update_ts(client, 0);
if (rc != ARRAY_SIZE(msg))
return rc < 0 ? rc : -EIO;
*data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |
(rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));
return rc;
return 0;
}
static int max31785_get_pwm(struct i2c_client *client, int page)
@@ -203,19 +165,16 @@ static int max31785_get_pwm(struct i2c_client *client, int page)
return rv;
}
static int max31785_get_pwm_mode(struct i2c_client *client,
struct max31785_data *driver_data, int page)
static int max31785_get_pwm_mode(struct i2c_client *client, int page)
{
int config;
int command;
config = _max31785_read_byte_data(client, driver_data, page,
PMBUS_FAN_CONFIG_12);
config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);
if (config < 0)
return config;
command = _max31785_read_word_data(client, driver_data, page, 0xff,
PMBUS_FAN_COMMAND_1);
command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1);
if (command < 0)
return command;
@@ -233,8 +192,6 @@ static int max31785_get_pwm_mode(struct i2c_client *client,
static int max31785_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 max31785_data *driver_data = to_max31785_data(info);
u32 val;
int rv;
@@ -263,7 +220,7 @@ static int max31785_read_word_data(struct i2c_client *client, int page,
rv = max31785_get_pwm(client, page);
break;
case PMBUS_VIRT_PWM_ENABLE_1:
rv = max31785_get_pwm_mode(client, driver_data, page);
rv = max31785_get_pwm_mode(client, page);
break;
default:
rv = -ENODATA;
@@ -294,35 +251,7 @@ static inline u32 max31785_scale_pwm(u32 sensor_val)
return (sensor_val * 100) / 255;
}
static int max31785_update_fan(struct i2c_client *client,
struct max31785_data *driver_data, int page,
u8 config, u8 mask, u16 command)
{
int from, rv;
u8 to;
from = _max31785_read_byte_data(client, driver_data, page,
PMBUS_FAN_CONFIG_12);
if (from < 0)
return from;
to = (from & ~mask) | (config & mask);
if (to != from) {
rv = _max31785_write_byte_data(client, driver_data, page,
PMBUS_FAN_CONFIG_12, to);
if (rv < 0)
return rv;
}
rv = _max31785_write_word_data(client, driver_data, page,
PMBUS_FAN_COMMAND_1, command);
return rv;
}
static int max31785_pwm_enable(struct i2c_client *client,
struct max31785_data *driver_data, int page,
static int max31785_pwm_enable(struct i2c_client *client, int page,
u16 word)
{
int config = 0;
@@ -351,23 +280,20 @@ static int max31785_pwm_enable(struct i2c_client *client,
return -EINVAL;
}
return max31785_update_fan(client, driver_data, page, config,
PB_FAN_1_RPM, rate);
return pmbus_update_fan(client, page, 0, config,
PB_FAN_1_RPM, rate);
}
static int max31785_write_word_data(struct i2c_client *client, int page,
int reg, u16 word)
{
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
struct max31785_data *driver_data = to_max31785_data(info);
switch (reg) {
case PMBUS_VIRT_PWM_1:
return max31785_update_fan(client, driver_data, page, 0,
PB_FAN_1_RPM,
max31785_scale_pwm(word));
return pmbus_update_fan(client, page, 0, 0,
PB_FAN_1_RPM,
max31785_scale_pwm(word));
case PMBUS_VIRT_PWM_ENABLE_1:
return max31785_pwm_enable(client, driver_data, page, word);
return max31785_pwm_enable(client, page, word);
default:
break;
}
@@ -391,6 +317,7 @@ static const struct pmbus_driver_info max31785_info = {
.read_byte_data = max31785_read_byte_data,
.read_word_data = max31785_read_word_data,
.write_byte = max31785_write_byte,
.access_delay = MAX31785_WAIT_DELAY_US,
/* RPM */
.format[PSC_FAN] = direct,
@@ -438,29 +365,29 @@ static const struct pmbus_driver_info max31785_info = {
};
static int max31785_configure_dual_tach(struct i2c_client *client,
struct pmbus_driver_info *info)
struct max31785_data *data)
{
struct pmbus_driver_info *info = &data->info;
int ret;
int i;
struct max31785_data *driver_data = to_max31785_data(info);
for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) {
ret = max31785_i2c_write_byte_data(client, driver_data,
ret = max31785_i2c_write_byte_data(client, data,
PMBUS_PAGE, i);
if (ret < 0)
return ret;
ret = max31785_i2c_read_word_data(client, driver_data,
ret = max31785_i2c_read_word_data(client, data,
MFR_FAN_CONFIG);
if (ret < 0)
return ret;
if (ret & MFR_FAN_CONFIG_DUAL_TACH) {
int virtual = MAX31785_NR_PAGES + i;
int vpage = MAX31785_NR_PAGES + i;
info->pages = virtual + 1;
info->func[virtual] |= PMBUS_HAVE_FAN12;
info->func[virtual] |= PMBUS_PAGE_VIRTUAL;
info->pages = vpage + 1;
info->func[vpage] |= PMBUS_HAVE_FAN12;
info->func[vpage] |= PMBUS_PAGE_VIRTUAL;
}
}
@@ -471,7 +398,7 @@ static int max31785_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct pmbus_driver_info *info;
struct max31785_data *driver_data;
struct max31785_data *data;
bool dual_tach = false;
int ret;
@@ -480,20 +407,20 @@ static int max31785_probe(struct i2c_client *client)
I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
driver_data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL);
if (!driver_data)
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
info = &driver_data->info;
driver_data->access = ktime_get();
data->access = ktime_get();
info = &data->info;
*info = max31785_info;
ret = max31785_i2c_write_byte_data(client, driver_data,
PMBUS_PAGE, 255);
ret = max31785_i2c_write_byte_data(client, data,
PMBUS_PAGE, 0xff);
if (ret < 0)
return ret;
ret = i2c_smbus_read_word_data(client, MFR_REVISION);
ret = max31785_i2c_read_word_data(client, data, MFR_REVISION);
if (ret < 0)
return ret;
@@ -509,11 +436,13 @@ static int max31785_probe(struct i2c_client *client)
}
if (dual_tach) {
ret = max31785_configure_dual_tach(client, info);
ret = max31785_configure_dual_tach(client, data);
if (ret < 0)
return ret;
}
max31785_wait(data);
return pmbus_do_probe(client, info);
}

View File

@@ -71,8 +71,6 @@ struct max34440_data {
#define to_max34440_data(x) container_of(x, struct max34440_data, info)
static const struct i2c_device_id max34440_id[];
static int max34440_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
@@ -628,7 +626,7 @@ static int max34440_probe(struct i2c_client *client)
GFP_KERNEL);
if (!data)
return -ENOMEM;
data->id = i2c_match_id(max34440_id, client)->driver_data;
data->id = (uintptr_t)i2c_get_match_data(client);
data->info = max34440_info[data->id];
data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT;
data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT;

View File

@@ -20,8 +20,6 @@ struct pmbus_device_info {
u32 flags;
};
static const struct i2c_device_id pmbus_id[];
/*
* Find sensor groups and status registers on each page.
*/
@@ -174,7 +172,7 @@ static int pmbus_probe(struct i2c_client *client)
if (!info)
return -ENOMEM;
device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data;
device_info = (struct pmbus_device_info *)i2c_get_match_data(client);
if (device_info->flags) {
pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
GFP_KERNEL);

View File

@@ -10,6 +10,7 @@
#define PMBUS_H
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/regulator/driver.h>
/*
@@ -416,7 +417,7 @@ enum pmbus_sensor_classes {
#define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */
enum pmbus_data_format { linear = 0, ieee754, direct, vid };
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv };
/* PMBus revision identifiers */
#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */
@@ -424,6 +425,10 @@ enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */
#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */
/* Operation type flags for pmbus_update_ts */
#define PMBUS_OP_WRITE BIT(0)
#define PMBUS_OP_PAGE_CHANGE BIT(1)
struct pmbus_driver_info {
int pages; /* Total number of pages */
u8 phases[PMBUS_PAGES]; /* Number of phases per page */
@@ -541,6 +546,8 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev,
void pmbus_clear_cache(struct i2c_client *client);
void pmbus_set_update(struct i2c_client *client, u8 reg, bool update);
void pmbus_wait(struct i2c_client *client);
void pmbus_update_ts(struct i2c_client *client, int op);
int pmbus_set_page(struct i2c_client *client, int page, int phase);
int pmbus_read_word_data(struct i2c_client *client, int page, int phase,
u8 reg);
@@ -563,7 +570,11 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,
int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id,
enum pmbus_fan_mode mode);
int pmbus_lock_interruptible(struct i2c_client *client);
void pmbus_lock(struct i2c_client *client);
void pmbus_unlock(struct i2c_client *client);
DEFINE_GUARD(pmbus_lock, struct i2c_client *, pmbus_lock(_T), pmbus_unlock(_T))
int pmbus_update_fan(struct i2c_client *client, int page, int id,
u8 config, u8 mask, u16 command);
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client);

View File

@@ -37,8 +37,7 @@
* The type of operation used for picking the delay between
* successive pmbus operations.
*/
#define PMBUS_OP_WRITE BIT(0)
#define PMBUS_OP_PAGE_CHANGE BIT(1)
/* PMBUS_OP_WRITE and PMBUS_OP_PAGE_CHANGE are defined in pmbus.h */
static int wp = -1;
module_param(wp, int, 0444);
@@ -179,7 +178,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update)
EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS");
/* Some chips need a delay between accesses. */
static void pmbus_wait(struct i2c_client *client)
void pmbus_wait(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);
s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get());
@@ -187,9 +186,10 @@ static void pmbus_wait(struct i2c_client *client)
if (delay > 0)
fsleep(delay);
}
EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS");
/* Sets the last operation timestamp for pmbus_wait */
static void pmbus_update_ts(struct i2c_client *client, int op)
void pmbus_update_ts(struct i2c_client *client, int op)
{
struct pmbus_data *data = i2c_get_clientdata(client);
const struct pmbus_driver_info *info = data->info;
@@ -203,6 +203,7 @@ static void pmbus_update_ts(struct i2c_client *client, int op)
if (delay > 0)
data->next_access_backoff = ktime_add_us(ktime_get(), delay);
}
EXPORT_SYMBOL_NS_GPL(pmbus_update_ts, "PMBUS");
int pmbus_set_page(struct i2c_client *client, int page, int phase)
{
@@ -891,6 +892,10 @@ static s64 pmbus_reg2data_vid(struct pmbus_data *data,
if (val >= 0x0 && val <= 0xd8)
rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100);
break;
case nvidia195mv:
if (val >= 0x01)
rv = 195 + (val - 1) * 5; /* VID step is 5mv */
break;
}
return rv;
}
@@ -1156,12 +1161,11 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
int ret, status;
u16 regval;
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
status = pmbus_get_status(client, page, reg);
if (status < 0) {
ret = status;
goto unlock;
}
if (status < 0)
return status;
if (s1)
pmbus_update_sensor_data(client, s1);
@@ -1173,7 +1177,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
if (data->revision >= PMBUS_REV_12) {
ret = _pmbus_write_byte_data(client, page, reg, regval);
if (ret)
goto unlock;
return ret;
} else {
pmbus_clear_fault_page(client, page);
}
@@ -1181,14 +1185,10 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
if (s1 && s2) {
s64 v1, v2;
if (s1->data < 0) {
ret = s1->data;
goto unlock;
}
if (s2->data < 0) {
ret = s2->data;
goto unlock;
}
if (s1->data < 0)
return s1->data;
if (s2->data < 0)
return s2->data;
v1 = pmbus_reg2data(data, s1);
v2 = pmbus_reg2data(data, s2);
@@ -1196,8 +1196,6 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
} else {
ret = !!regval;
}
unlock:
mutex_unlock(&data->update_lock);
return ret;
}
@@ -1227,16 +1225,16 @@ static ssize_t pmbus_show_sensor(struct device *dev,
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
struct pmbus_data *data = i2c_get_clientdata(client);
ssize_t ret;
s64 val;
mutex_lock(&data->update_lock);
pmbus_update_sensor_data(client, sensor);
if (sensor->data < 0)
ret = sensor->data;
else
ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor));
mutex_unlock(&data->update_lock);
return ret;
scoped_guard(pmbus_lock, client) {
pmbus_update_sensor_data(client, sensor);
if (sensor->data < 0)
return sensor->data;
val = pmbus_reg2data(data, sensor);
}
return sysfs_emit(buf, "%lld\n", val);
}
static ssize_t pmbus_set_sensor(struct device *dev,
@@ -1246,7 +1244,6 @@ static ssize_t pmbus_set_sensor(struct device *dev,
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
struct pmbus_sensor *sensor = to_pmbus_sensor(devattr);
ssize_t rv = count;
s64 val;
int ret;
u16 regval;
@@ -1254,15 +1251,15 @@ static ssize_t pmbus_set_sensor(struct device *dev,
if (kstrtos64(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
regval = pmbus_data2reg(data, sensor, val);
ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval);
if (ret < 0)
rv = ret;
else
sensor->data = -ENODATA;
mutex_unlock(&data->update_lock);
return rv;
return ret;
sensor->data = -ENODATA;
return count;
}
static ssize_t pmbus_show_label(struct device *dev,
@@ -1364,7 +1361,7 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
struct pmbus_data *pmbus_data = tdata->pmbus_data;
struct i2c_client *client = to_i2c_client(pmbus_data->dev);
struct device *dev = pmbus_data->hwmon_dev;
int ret = 0;
int _temp;
if (!dev) {
/* May not even get to hwmon yet */
@@ -1372,15 +1369,15 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
return 0;
}
mutex_lock(&pmbus_data->update_lock);
pmbus_update_sensor_data(client, sensor);
if (sensor->data < 0)
ret = sensor->data;
else
*temp = (int)pmbus_reg2data(pmbus_data, sensor);
mutex_unlock(&pmbus_data->update_lock);
scoped_guard(pmbus_lock, client) {
pmbus_update_sensor_data(client, sensor);
if (sensor->data < 0)
return sensor->data;
_temp = (int)pmbus_reg2data(pmbus_data, sensor);
}
return ret;
*temp = _temp;
return 0;
}
static const struct thermal_zone_device_ops pmbus_thermal_ops = {
@@ -2412,13 +2409,12 @@ static ssize_t pmbus_show_samples(struct device *dev,
int val;
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_samples_reg *reg = to_samples_reg(devattr);
struct pmbus_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg);
mutex_unlock(&data->update_lock);
if (val < 0)
return val;
scoped_guard(pmbus_lock, client) {
val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg);
if (val < 0)
return val;
}
return sysfs_emit(buf, "%d\n", val);
}
@@ -2431,14 +2427,13 @@ static ssize_t pmbus_set_samples(struct device *dev,
long val;
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_samples_reg *reg = to_samples_reg(devattr);
struct pmbus_data *data = i2c_get_clientdata(client);
if (kstrtol(buf, 0, &val) < 0)
return -EINVAL;
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
ret = _pmbus_write_word_data(client, reg->page, reg->attr->reg, val);
mutex_unlock(&data->update_lock);
return ret ? : count;
}
@@ -2950,14 +2945,9 @@ static int _pmbus_is_enabled(struct i2c_client *client, u8 page)
static int __maybe_unused pmbus_is_enabled(struct i2c_client *client, u8 page)
{
struct pmbus_data *data = i2c_get_clientdata(client);
int ret;
guard(pmbus_lock)(client);
mutex_lock(&data->update_lock);
ret = _pmbus_is_enabled(client, page);
mutex_unlock(&data->update_lock);
return ret;
return _pmbus_is_enabled(client, page);
}
#define to_dev_attr(_dev_attr) \
@@ -2987,14 +2977,13 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags)
}
}
static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags,
static int _pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags,
unsigned int *event, bool notify)
{
struct pmbus_data *data = i2c_get_clientdata(client);
int i, status;
const struct pmbus_status_category *cat;
const struct pmbus_status_assoc *bit;
struct device *dev = data->dev;
struct i2c_client *client = to_i2c_client(dev);
int func = data->info->func[page];
*flags = 0;
@@ -3070,16 +3059,12 @@ static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flag
return 0;
}
static int __maybe_unused pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags,
static int __maybe_unused pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags,
unsigned int *event, bool notify)
{
int ret;
guard(pmbus_lock)(client);
mutex_lock(&data->update_lock);
ret = _pmbus_get_flags(data, page, flags, event, notify);
mutex_unlock(&data->update_lock);
return ret;
return _pmbus_get_flags(client, page, flags, event, notify);
}
#if IS_ENABLED(CONFIG_REGULATOR)
@@ -3095,17 +3080,13 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int ret;
mutex_lock(&data->update_lock);
ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION,
PB_OPERATION_CONTROL_ON,
enable ? PB_OPERATION_CONTROL_ON : 0);
mutex_unlock(&data->update_lock);
guard(pmbus_lock)(client);
return ret;
return pmbus_update_byte_data(client, page, PMBUS_OPERATION,
PB_OPERATION_CONTROL_ON,
enable ? PB_OPERATION_CONTROL_ON : 0);
}
static int pmbus_regulator_enable(struct regulator_dev *rdev)
@@ -3122,54 +3103,41 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
int event;
return pmbus_get_flags(data, rdev_get_id(rdev), flags, &event, false);
return pmbus_get_flags(client, rdev_get_id(rdev), flags, &event, false);
}
static int pmbus_regulator_get_status(struct regulator_dev *rdev)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int status, ret;
int event;
mutex_lock(&data->update_lock);
status = pmbus_get_status(client, page, PMBUS_STATUS_WORD);
if (status < 0) {
ret = status;
goto unlock;
}
guard(pmbus_lock)(client);
if (status & PB_STATUS_OFF) {
ret = REGULATOR_STATUS_OFF;
goto unlock;
}
status = pmbus_get_status(client, page, PMBUS_STATUS_WORD);
if (status < 0)
return status;
if (status & PB_STATUS_OFF)
return REGULATOR_STATUS_OFF;
/* If regulator is ON & reports power good then return ON */
if (!(status & PB_STATUS_POWER_GOOD_N)) {
ret = REGULATOR_STATUS_ON;
goto unlock;
}
if (!(status & PB_STATUS_POWER_GOOD_N))
return REGULATOR_STATUS_ON;
ret = _pmbus_get_flags(data, rdev_get_id(rdev), &status, &event, false);
ret = _pmbus_get_flags(client, rdev_get_id(rdev), &status, &event, false);
if (ret)
goto unlock;
return ret;
if (status & (REGULATOR_ERROR_UNDER_VOLTAGE | REGULATOR_ERROR_OVER_CURRENT |
REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP)) {
ret = REGULATOR_STATUS_ERROR;
goto unlock;
}
REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP))
return REGULATOR_STATUS_ERROR;
ret = REGULATOR_STATUS_UNDEFINED;
unlock:
mutex_unlock(&data->update_lock);
return ret;
return REGULATOR_STATUS_UNDEFINED;
}
static int pmbus_regulator_get_low_margin(struct i2c_client *client, int page)
@@ -3234,19 +3202,16 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev)
.class = PSC_VOLTAGE_OUT,
.convert = true,
};
int ret;
int voltage;
mutex_lock(&data->update_lock);
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
if (s.data < 0) {
ret = s.data;
goto unlock;
scoped_guard(pmbus_lock, client) {
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
if (s.data < 0)
return s.data;
voltage = (int)pmbus_reg2data(data, &s);
}
ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
unlock:
mutex_unlock(&data->update_lock);
return ret;
return voltage * 1000; /* unit is uV */
}
static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
@@ -3263,22 +3228,18 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
};
int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
int low, high;
int ret;
*selector = 0;
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
low = pmbus_regulator_get_low_margin(client, s.page);
if (low < 0) {
ret = low;
goto unlock;
}
if (low < 0)
return low;
high = pmbus_regulator_get_high_margin(client, s.page);
if (high < 0) {
ret = high;
goto unlock;
}
if (high < 0)
return high;
/* Make sure we are within margins */
if (low > val)
@@ -3288,10 +3249,7 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
val = pmbus_data2reg(data, &s, val);
ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
unlock:
mutex_unlock(&data->update_lock);
return ret;
return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
}
static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
@@ -3301,7 +3259,6 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
int val, low, high;
int ret;
if (data->flags & PMBUS_VOUT_PROTECTED)
return 0;
@@ -3314,29 +3271,20 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
val = DIV_ROUND_CLOSEST(rdev->desc->min_uV +
(rdev->desc->uV_step * selector), 1000); /* convert to mV */
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev));
if (low < 0) {
ret = low;
goto unlock;
}
if (low < 0)
return low;
high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev));
if (high < 0) {
ret = high;
goto unlock;
}
if (high < 0)
return high;
if (val >= low && val <= high) {
ret = val * 1000; /* unit is uV */
goto unlock;
}
if (val >= low && val <= high)
return val * 1000; /* unit is uV */
ret = 0;
unlock:
mutex_unlock(&data->update_lock);
return ret;
return 0;
}
const struct regulator_ops pmbus_regulator_ops = {
@@ -3472,16 +3420,16 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
struct i2c_client *client = to_i2c_client(data->dev);
int i, status, event;
mutex_lock(&data->update_lock);
guard(pmbus_lock)(client);
for (i = 0; i < data->info->pages; i++) {
_pmbus_get_flags(data, i, &status, &event, true);
_pmbus_get_flags(client, i, &status, &event, true);
if (event)
pmbus_regulator_notify(data, i, event);
}
pmbus_clear_faults(client);
mutex_unlock(&data->update_lock);
return IRQ_HANDLED;
}
@@ -3537,15 +3485,13 @@ static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */
static int pmbus_debugfs_get(void *data, u64 *val)
{
int rc;
struct pmbus_debugfs_entry *entry = data;
struct pmbus_data *pdata = i2c_get_clientdata(entry->client);
struct i2c_client *client = entry->client;
int rc;
rc = mutex_lock_interruptible(&pdata->update_lock);
if (rc)
return rc;
rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg);
mutex_unlock(&pdata->update_lock);
guard(pmbus_lock)(client);
rc = _pmbus_read_byte_data(client, entry->page, entry->reg);
if (rc < 0)
return rc;
@@ -3558,15 +3504,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL,
static int pmbus_debugfs_get_status(void *data, u64 *val)
{
int rc;
struct pmbus_debugfs_entry *entry = data;
struct pmbus_data *pdata = i2c_get_clientdata(entry->client);
struct i2c_client *client = entry->client;
struct pmbus_data *pdata = i2c_get_clientdata(client);
int rc;
rc = mutex_lock_interruptible(&pdata->update_lock);
if (rc)
return rc;
rc = pdata->read_status(entry->client, entry->page);
mutex_unlock(&pdata->update_lock);
guard(pmbus_lock)(client);
rc = pdata->read_status(client, entry->page);
if (rc < 0)
return rc;
@@ -3582,17 +3527,14 @@ static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf,
{
int rc;
struct pmbus_debugfs_entry *entry = file->private_data;
struct pmbus_data *pdata = i2c_get_clientdata(entry->client);
struct i2c_client *client = entry->client;
char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
rc = mutex_lock_interruptible(&pdata->update_lock);
if (rc)
return rc;
rc = pmbus_read_block_data(entry->client, entry->page, entry->reg,
data);
mutex_unlock(&pdata->update_lock);
if (rc < 0)
return rc;
scoped_guard(pmbus_lock, client) {
rc = pmbus_read_block_data(client, entry->page, entry->reg, data);
if (rc < 0)
return rc;
}
/* Add newline at the end of a read data */
data[rc] = '\n';
@@ -3871,6 +3813,14 @@ struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
}
EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, "PMBUS");
void pmbus_lock(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
}
EXPORT_SYMBOL_NS_GPL(pmbus_lock, "PMBUS");
int pmbus_lock_interruptible(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);

View File

@@ -271,6 +271,8 @@ static const struct file_operations q54sj108a2_fops = {
static const struct i2c_device_id q54sj108a2_id[] = {
{ "q54sj108a2", q54sj108a2 },
{ "q54sn120a1", q54sj108a2 },
{ "q54sw120a7", q54sj108a2 },
{ },
};
@@ -280,6 +282,7 @@ static int q54sj108a2_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
const struct i2c_device_id *mid;
enum chips chip_id;
int ret, i;
struct dentry *debugfs;
@@ -292,10 +295,7 @@ static int q54sj108a2_probe(struct i2c_client *client)
I2C_FUNC_SMBUS_BLOCK_DATA))
return -ENODEV;
if (client->dev.of_node)
chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev);
else
chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data;
chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client);
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
if (ret < 0) {
@@ -316,8 +316,12 @@ static int q54sj108a2_probe(struct i2c_client *client)
dev_err(dev, "Failed to read Manufacturer Model\n");
return ret;
}
if (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) {
buf[ret] = '\0';
buf[ret] = '\0';
for (mid = q54sj108a2_id; mid->name[0]; mid++) {
if (!strncasecmp(mid->name, buf, strlen(mid->name)))
break;
}
if (!mid->name[0]) {
dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
return -ENODEV;
}
@@ -327,7 +331,10 @@ static int q54sj108a2_probe(struct i2c_client *client)
dev_err(dev, "Failed to read Manufacturer Revision\n");
return ret;
}
if (ret != 4 || buf[0] != 'S') {
/*
* accept manufacturer revision with optional NUL byte
*/
if (!(ret == 4 || ret == 5) || buf[0] != 'S') {
buf[ret] = '\0';
dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf);
return -ENODEV;
@@ -404,6 +411,8 @@ static int q54sj108a2_probe(struct i2c_client *client)
static const struct of_device_id q54sj108a2_of_match[] = {
{ .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 },
{ .compatible = "delta,q54sn120a1", .data = (void *)q54sj108a2 },
{ .compatible = "delta,q54sw120a7", .data = (void *)q54sj108a2 },
{ },
};

View File

@@ -402,12 +402,18 @@ static int tps25990_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct pmbus_driver_info *info;
u32 rimon = TPS25990_DEFAULT_RIMON;
const char *propname;
u32 rimon;
int ret;
ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon);
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(dev, ret, "failed to get rimon\n");
propname = "ti,rimon-micro-ohms";
if (device_property_present(dev, propname)) {
ret = device_property_read_u32(dev, propname, &rimon);
if (ret)
return dev_err_probe(dev, ret, "failed to get %s\n", propname);
} else {
rimon = TPS25990_DEFAULT_RIMON;
}
info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL);
if (!info)

View File

@@ -253,10 +253,7 @@ static int tps53679_probe(struct i2c_client *client)
struct pmbus_driver_info *info;
enum chips chip_id;
if (dev->of_node)
chip_id = (uintptr_t)of_device_get_match_data(dev);
else
chip_id = i2c_match_id(tps53679_id, client)->driver_data;
chip_id = (uintptr_t)i2c_get_match_data(client);
info = devm_kmemdup(dev, &tps53679_info, sizeof(*info), GFP_KERNEL);
if (!info)

View File

@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for Infineon XDP720 Digital eFuse Controller
*
* Copyright (c) 2026 Infineon Technologies. All rights reserved.
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of_device.h>
#include <linux/bitops.h>
#include <linux/math64.h>
#include "pmbus.h"
/*
* The IMON resistor required to generate the system overcurrent protection.
* Arbitrary default Rimon value: 2k Ohm
*/
#define XDP720_DEFAULT_RIMON 2000000000 /* 2k ohm */
#define XDP720_TELEMETRY_AVG 0xE9
static struct pmbus_driver_info xdp720_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_VOLTAGE_OUT] = direct,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_POWER] = direct,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_VOLTAGE_IN] = 4653,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = -2,
.m[PSC_VOLTAGE_OUT] = 4653,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = -2,
/*
* Current and Power measurement depends on the RIMON (kOhm) and
* GIMON(microA/A) values.
*/
.m[PSC_CURRENT_OUT] = 24668,
.b[PSC_CURRENT_OUT] = 0,
.R[PSC_CURRENT_OUT] = -4,
.m[PSC_POWER] = 4486,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = -1,
.m[PSC_TEMPERATURE] = 54,
.b[PSC_TEMPERATURE] = 22521,
.R[PSC_TEMPERATURE] = -1,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN |
PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_STATUS_TEMP,
};
static int xdp720_probe(struct i2c_client *client)
{
struct pmbus_driver_info *info;
int ret;
u32 rimon;
int gimon;
info = devm_kmemdup(&client->dev, &xdp720_info, sizeof(*info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
ret = devm_regulator_get_enable(&client->dev, "vdd-vin");
if (ret)
return dev_err_probe(&client->dev, ret,
"failed to enable vdd-vin supply\n");
ret = i2c_smbus_read_word_data(client, XDP720_TELEMETRY_AVG);
if (ret < 0) {
dev_err(&client->dev, "Can't get TELEMETRY_AVG\n");
return ret;
}
ret >>= 10; /* 10th bit of TELEMETRY_AVG REG for GIMON Value */
ret &= GENMASK(0, 0);
if (ret == 1)
gimon = 18200; /* output gain 18.2 microA/A */
else
gimon = 9100; /* output gain 9.1 microA/A */
if (of_property_read_u32(client->dev.of_node,
"infineon,rimon-micro-ohms", &rimon))
rimon = XDP720_DEFAULT_RIMON; /* Default if not set via DT */
if (rimon == 0)
return -EINVAL;
/* Adapt the current and power scale for each instance */
info->m[PSC_CURRENT_OUT] = DIV64_U64_ROUND_CLOSEST((u64)
info->m[PSC_CURRENT_OUT] * rimon * gimon, 1000000000000ULL);
info->m[PSC_POWER] = DIV64_U64_ROUND_CLOSEST((u64)
info->m[PSC_POWER] * rimon * gimon, 1000000000000000ULL);
return pmbus_do_probe(client, info);
}
static const struct of_device_id xdp720_of_match[] = {
{ .compatible = "infineon,xdp720" },
{}
};
MODULE_DEVICE_TABLE(of, xdp720_of_match);
static const struct i2c_device_id xdp720_id[] = {
{ "xdp720" },
{}
};
MODULE_DEVICE_TABLE(i2c, xdp720_id);
static struct i2c_driver xdp720_driver = {
.driver = {
.name = "xdp720",
.of_match_table = xdp720_of_match,
},
.probe = xdp720_probe,
.id_table = xdp720_id,
};
module_i2c_driver(xdp720_driver);
MODULE_AUTHOR("Ashish Yadav <ashish.yadav@infineon.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon XDP720 Digital eFuse Controller");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G5B
* and XDPE1A2G7B Controllers
*
* Copyright (c) 2026 Infineon Technologies. All rights reserved.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"
#define XDPE1A2G7B_PAGE_NUM 2
#define XDPE1A2G7B_NVIDIA_195MV 0x1E /* NVIDIA mode 1.95mV, VID step is 5mV */
static int xdpe1a2g7b_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
u8 vout_params;
int vout_mode;
/*
* XDPE1A2G5B and XDPE1A2G7B 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.
*/
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
if (vout_mode < 0)
return vout_mode;
switch (vout_mode >> 5) {
case 0:
info->format[PSC_VOLTAGE_OUT] = linear;
return 0;
case 1:
info->format[PSC_VOLTAGE_OUT] = vid;
vout_params = vout_mode & GENMASK(4, 0);
/* Check for VID Code Type */
switch (vout_params) {
case XDPE1A2G7B_NVIDIA_195MV:
/* VID vrm_version for PAGE0 and PAGE1 */
info->vrm_version[0] = nvidia195mv;
info->vrm_version[1] = nvidia195mv;
break;
default:
return -EINVAL;
}
break;
default:
return -ENODEV;
}
return 0;
}
static struct pmbus_driver_info xdpe1a2g7b_info = {
.pages = XDPE1A2G7B_PAGE_NUM,
.identify = xdpe1a2g7b_identify,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_TEMPERATURE] = linear,
.format[PSC_CURRENT_IN] = linear,
.format[PSC_CURRENT_OUT] = linear,
.format[PSC_POWER] = linear,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT,
};
static int xdpe1a2g7b_probe(struct i2c_client *client)
{
struct pmbus_driver_info *info;
info = devm_kmemdup(&client->dev, &xdpe1a2g7b_info, sizeof(*info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
return pmbus_do_probe(client, info);
}
static const struct i2c_device_id xdpe1a2g7b_id[] = {
{ "xdpe1a2g5b" },
{ "xdpe1a2g7b" },
{}
};
MODULE_DEVICE_TABLE(i2c, xdpe1a2g7b_id);
static const struct of_device_id __maybe_unused xdpe1a2g7b_of_match[] = {
{ .compatible = "infineon,xdpe1a2g5b" },
{ .compatible = "infineon,xdpe1a2g7b" },
{}
};
MODULE_DEVICE_TABLE(of, xdpe1a2g7b_of_match);
static struct i2c_driver xdpe1a2g7b_driver = {
.driver = {
.name = "xdpe1a2g7b",
.of_match_table = of_match_ptr(xdpe1a2g7b_of_match),
},
.probe = xdpe1a2g7b_probe,
.id_table = xdpe1a2g7b_id,
};
module_i2c_driver(xdpe1a2g7b_driver);
MODULE_AUTHOR("Ashish Yadav <ashish.yadav@infineon.com>");
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G5B/7B");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("PMBUS");

View File

@@ -6,6 +6,7 @@
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -33,7 +34,9 @@ struct powerz_sensor_data {
} __packed;
struct powerz_priv {
char transfer_buffer[64]; /* first member to satisfy DMA alignment */
__dma_from_device_group_begin();
char transfer_buffer[64];
__dma_from_device_group_end();
struct mutex mutex;
struct completion completion;
struct urb *urb;
@@ -106,8 +109,12 @@ static void powerz_usb_cmd_complete(struct urb *urb)
static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv)
{
long rc;
int ret;
if (!priv->urb)
return -ENODEV;
priv->status = -ETIMEDOUT;
reinit_completion(&priv->completion);
@@ -124,8 +131,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv)
if (ret)
return ret;
if (!wait_for_completion_interruptible_timeout
(&priv->completion, msecs_to_jiffies(5))) {
rc = wait_for_completion_interruptible_timeout(&priv->completion,
msecs_to_jiffies(5));
if (rc < 0) {
usb_kill_urb(priv->urb);
return rc;
}
if (rc == 0) {
usb_kill_urb(priv->urb);
return -EIO;
}
@@ -224,6 +237,8 @@ static int powerz_probe(struct usb_interface *intf,
mutex_init(&priv->mutex);
init_completion(&priv->completion);
usb_set_intfdata(intf, priv);
hwmon_dev =
devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv,
&powerz_chip_info, NULL);
@@ -232,8 +247,6 @@ static int powerz_probe(struct usb_interface *intf,
return PTR_ERR(hwmon_dev);
}
usb_set_intfdata(intf, priv);
return 0;
}
@@ -244,6 +257,7 @@ static void powerz_disconnect(struct usb_interface *intf)
mutex_lock(&priv->mutex);
usb_kill_urb(priv->urb);
usb_free_urb(priv->urb);
priv->urb = NULL;
mutex_unlock(&priv->mutex);
}

View File

@@ -121,7 +121,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
int ret, tries;
u8 remain_len = len;
u8 curr_len;
u8 wbuf[16], rbuf[24];
u8 wbuf[16], rbuf[I2C_SMBUS_BLOCK_MAX];
u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
@@ -151,7 +151,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
break;
}
if (tries >= 3)
return ret;
return ret < 0 ? ret : -EIO;
memcpy(val, rbuf, curr_len);
val += curr_len;

View File

@@ -92,7 +92,7 @@ static ssize_t temp_input_show(struct device *dev,
if (ret)
return ret;
return sprintf(buf, "%d\n", data->temp_input * 1000);
return sysfs_emit(buf, "%d\n", data->temp_input * 1000);
}
static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0);

View File

@@ -50,11 +50,16 @@
#define CONVERSION_TIME_MS 35 /* in milli-seconds */
#define NUM_SAMPLE_TIMES 4
#define DEFAULT_SAMPLE_TIME_MS 250
static const unsigned int *sample_times = (const unsigned int []){ 125, 250, 1000, 4000 };
struct tmp102 {
const char *label;
struct regmap *regmap;
u16 config_orig;
unsigned long ready_time;
u16 sample_time;
};
/* convert left adjusted 13-bit TMP102 register value to milliCelsius */
@@ -79,8 +84,20 @@ static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type,
return 0;
}
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *temp)
static int tmp102_read_chip(struct device *dev, u32 attr, long *val)
{
struct tmp102 *tmp102 = dev_get_drvdata(dev);
switch (attr) {
case hwmon_chip_update_interval:
*val = tmp102->sample_time;
return 0;
default:
return -EOPNOTSUPP;
}
}
static int tmp102_read_temp(struct device *dev, u32 attr, long *val)
{
struct tmp102 *tmp102 = dev_get_drvdata(dev);
unsigned int regval;
@@ -108,13 +125,54 @@ static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
err = regmap_read(tmp102->regmap, reg, &regval);
if (err < 0)
return err;
*temp = tmp102_reg_to_mC(regval);
*val = tmp102_reg_to_mC(regval);
return 0;
}
static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long temp)
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
case hwmon_chip:
return tmp102_read_chip(dev, attr, val);
case hwmon_temp:
return tmp102_read_temp(dev, attr, val);
default:
return -EOPNOTSUPP;
}
}
static int tmp102_update_interval(struct device *dev, long val)
{
struct tmp102 *tmp102 = dev_get_drvdata(dev);
u8 index;
s32 err;
index = find_closest(val, sample_times, NUM_SAMPLE_TIMES);
err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
(TMP102_CONF_CR1 | TMP102_CONF_CR0), (3 - index) << 6);
if (err < 0)
return err;
tmp102->sample_time = sample_times[index];
return 0;
}
static int tmp102_write_chip(struct device *dev, u32 attr, long val)
{
switch (attr) {
case hwmon_chip_update_interval:
return tmp102_update_interval(dev, val);
default:
return -EOPNOTSUPP;
}
return 0;
}
static int tmp102_write_temp(struct device *dev, u32 attr, long val)
{
struct tmp102 *tmp102 = dev_get_drvdata(dev);
int reg;
@@ -130,8 +188,22 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
return -EOPNOTSUPP;
}
temp = clamp_val(temp, -256000, 255000);
return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(temp));
val = clamp_val(val, -256000, 255000);
return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val));
}
static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
switch (type) {
case hwmon_chip:
return tmp102_write_chip(dev, attr, val);
case hwmon_temp:
return tmp102_write_temp(dev, attr, val);
default:
return -EOPNOTSUPP;
}
return 0;
}
static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
@@ -139,27 +211,39 @@ static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
{
const struct tmp102 *tmp102 = data;
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
return 0444;
case hwmon_temp_label:
if (tmp102->label)
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
return 0644;
default:
break;
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
return 0444;
return 0;
case hwmon_temp_max_hyst:
case hwmon_temp_max:
return 0644;
case hwmon_temp_label:
if (tmp102->label)
return 0444;
return 0;
case hwmon_temp_max_hyst:
case hwmon_temp_max:
return 0644;
default:
break;
}
break;
default:
return 0;
break;
}
return 0;
}
static const struct hwmon_channel_info * const tmp102_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ),
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST),
NULL
@@ -237,6 +321,8 @@ static int tmp102_probe(struct i2c_client *client)
if (IS_ERR(tmp102->regmap))
return PTR_ERR(tmp102->regmap);
tmp102->sample_time = DEFAULT_SAMPLE_TIME_MS;
err = regmap_read(tmp102->regmap, TMP102_CONF_REG, &regval);
if (err < 0) {
dev_err(dev, "error reading config register\n");

275
drivers/hwmon/yogafan.c Normal file
View File

@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver
*
* Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad
* laptops by interfacing with the Embedded Controller (EC) via ACPI.
*
* The driver implements a passive discrete-time first-order lag filter
* with slew-rate limiting (RLLag). This addresses low-resolution
* tachometer sampling in the EC by smoothing RPM readings based on
* the time delta (dt) between userspace requests, ensuring physical
* consistency without background task overhead or race conditions.
* The filter implements multirate filtering with autoreset in case
* of large sampling time.
*
* Copyright (C) 2021-2026 Sergio Melas <sergiomelas@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/math64.h>
/* Driver Configuration Constants */
#define DRVNAME "yogafan"
#define MAX_FANS 8
/* Filter Configuration Constants */
#define TAU_MS 1000 /* Time constant for the first-order lag (ms) */
#define MAX_SLEW_RPM_S 1500 /* Maximum allowed change in RPM per second */
#define MAX_SAMPLING 5000 /* Maximum allowed Ts for reset (ms) */
#define MIN_SAMPLING 100 /* Minimum interval between filter updates (ms) */
/* RPM Sanitation Constants */
#define RPM_FLOOR_LIMIT 50 /* Snap filtered value to 0 if raw is 0 */
struct yogafan_config {
int multiplier;
int fan_count;
const char *paths[2];
};
struct yoga_fan_data {
acpi_handle active_handles[MAX_FANS];
long filtered_val[MAX_FANS];
ktime_t last_sample[MAX_FANS];
int multiplier;
int fan_count;
};
/* Specific configurations mapped via DMI */
static const struct yogafan_config yoga_8bit_fans_cfg = {
.multiplier = 100,
.fan_count = 1,
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", NULL }
};
static const struct yogafan_config ideapad_8bit_fan0_cfg = {
.multiplier = 100,
.fan_count = 1,
.paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
};
static const struct yogafan_config legion_16bit_dual_cfg = {
.multiplier = 1,
.fan_count = 2,
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
};
static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
{
ktime_t now = ktime_get_boottime();
s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx]));
long delta, step, limit, alpha;
s64 temp_num;
if (raw_rpm < RPM_FLOOR_LIMIT) {
data->filtered_val[idx] = 0;
data->last_sample[idx] = now;
return;
}
if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) {
data->filtered_val[idx] = raw_rpm;
data->last_sample[idx] = now;
return;
}
if (dt_ms < MIN_SAMPLING)
return;
delta = raw_rpm - data->filtered_val[idx];
if (delta == 0) {
data->last_sample[idx] = now;
return;
}
temp_num = dt_ms << 12;
alpha = (long)div64_s64(temp_num, (s64)(TAU_MS + dt_ms));
step = (delta * alpha) >> 12;
if (step == 0 && delta != 0)
step = (delta > 0) ? 1 : -1;
limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000;
if (limit < 1)
limit = 1;
if (step > limit)
step = limit;
else if (step < -limit)
step = -limit;
data->filtered_val[idx] += step;
data->last_sample[idx] = now;
}
static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct yoga_fan_data *data = dev_get_drvdata(dev);
unsigned long long raw_acpi;
acpi_status status;
if (type != hwmon_fan || attr != hwmon_fan_input)
return -EOPNOTSUPP;
status = acpi_evaluate_integer(data->active_handles[channel], NULL, NULL, &raw_acpi);
if (ACPI_FAILURE(status))
return -EIO;
apply_rllag_filter(data, channel, (long)raw_acpi * data->multiplier);
*val = data->filtered_val[channel];
return 0;
}
static umode_t yoga_fan_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct yoga_fan_data *fan_data = data;
if (type == hwmon_fan && channel < fan_data->fan_count)
return 0444;
return 0;
}
static const struct hwmon_ops yoga_fan_hwmon_ops = {
.is_visible = yoga_fan_is_visible,
.read = yoga_fan_read,
};
static const struct hwmon_channel_info *yoga_fan_info[] = {
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT, HWMON_F_INPUT,
HWMON_F_INPUT, HWMON_F_INPUT,
HWMON_F_INPUT, HWMON_F_INPUT,
HWMON_F_INPUT, HWMON_F_INPUT),
NULL
};
static const struct hwmon_chip_info yoga_fan_chip_info = {
.ops = &yoga_fan_hwmon_ops,
.info = yoga_fan_info,
};
static const struct dmi_system_id yogafan_quirks[] = {
{
.ident = "Lenovo Yoga",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga"),
},
.driver_data = (void *)&yoga_8bit_fans_cfg,
},
{
.ident = "Lenovo Legion",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion"),
},
.driver_data = (void *)&legion_16bit_dual_cfg,
},
{
.ident = "Lenovo IdeaPad",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad"),
},
.driver_data = (void *)&ideapad_8bit_fan0_cfg,
},
{ }
};
MODULE_DEVICE_TABLE(dmi, yogafan_quirks);
static int yoga_fan_probe(struct platform_device *pdev)
{
const struct dmi_system_id *dmi_id;
const struct yogafan_config *cfg;
struct yoga_fan_data *data;
struct device *hwmon_dev;
int i;
dmi_id = dmi_first_match(yogafan_quirks);
if (!dmi_id)
return -ENODEV;
cfg = dmi_id->driver_data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->multiplier = cfg->multiplier;
for (i = 0; i < cfg->fan_count; i++) {
acpi_status status;
status = acpi_get_handle(NULL, (char *)cfg->paths[i],
&data->active_handles[data->fan_count]);
if (ACPI_SUCCESS(status))
data->fan_count++;
}
if (data->fan_count == 0)
return -ENODEV;
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DRVNAME,
data, &yoga_fan_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static struct platform_driver yoga_fan_driver = {
.driver = { .name = DRVNAME },
.probe = yoga_fan_probe,
};
static struct platform_device *yoga_fan_device;
static int __init yoga_fan_init(void)
{
int ret;
if (!dmi_check_system(yogafan_quirks))
return -ENODEV;
ret = platform_driver_register(&yoga_fan_driver);
if (ret)
return ret;
yoga_fan_device = platform_device_register_simple(DRVNAME, -1, NULL, 0);
if (IS_ERR(yoga_fan_device)) {
platform_driver_unregister(&yoga_fan_driver);
return PTR_ERR(yoga_fan_device);
}
return 0;
}
static void __exit yoga_fan_exit(void)
{
platform_device_unregister(yoga_fan_device);
platform_driver_unregister(&yoga_fan_driver);
}
module_init(yoga_fan_init);
module_exit(yoga_fan_exit);
MODULE_AUTHOR("Sergio Melas <sergiomelas@gmail.com>");
MODULE_DESCRIPTION("Lenovo Yoga/Legion Fan Monitor Driver");
MODULE_LICENSE("GPL");

View File

@@ -33,8 +33,6 @@
#include <linux/sched/task.h>
#include <linux/util_macros.h>
#include <linux/platform_data/ina2xx.h>
/* INA2XX registers definition */
#define INA2XX_CONFIG 0x00
#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */
@@ -980,16 +978,8 @@ static int ina2xx_probe(struct i2c_client *client)
mutex_init(&chip->state_lock);
if (of_property_read_u32(client->dev.of_node,
"shunt-resistor", &val) < 0) {
struct ina2xx_platform_data *pdata =
dev_get_platdata(&client->dev);
if (pdata)
val = pdata->shunt_uohms;
else
val = INA2XX_RSHUNT_DEFAULT;
}
if (of_property_read_u32(client->dev.of_node, "shunt-resistor", &val) < 0)
val = INA2XX_RSHUNT_DEFAULT;
ret = set_shunt_resistor(chip, val);
if (ret)

View File

@@ -1,16 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Driver for Texas Instruments INA219, INA226 power monitor chips
*
* Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.com>
*
* For further information, see the Documentation/hwmon/ina2xx.rst file.
*/
/**
* struct ina2xx_platform_data - ina2xx info
* @shunt_uohms shunt resistance in microohms
*/
struct ina2xx_platform_data {
long shunt_uohms;
};