mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
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:
@@ -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";
|
||||
};
|
||||
...
|
||||
237
Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
Normal file
237
Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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>;
|
||||
};
|
||||
};
|
||||
@@ -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 */
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
57
Documentation/hwmon/aps-379.rst
Normal file
57
Documentation/hwmon/aps-379.rst
Normal 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
|
||||
=== ============================= ========= ======= =====
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
=============================== ======= =======================================
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
61
Documentation/hwmon/lattepanda-sigma-ec.rst
Normal file
61
Documentation/hwmon/lattepanda-sigma-ec.rst
Normal 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.
|
||||
@@ -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:
|
||||
|
||||
|
||||
111
Documentation/hwmon/mcp9982.rst
Normal file
111
Documentation/hwmon/mcp9982.rst
Normal 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.
|
||||
======================= ==================================================
|
||||
@@ -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
|
||||
=============================== ======= ===========================================
|
||||
|
||||
138
Documentation/hwmon/yogafan.rst
Normal file
138
Documentation/hwmon/yogafan.rst
Normal 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
|
||||
23
MAINTAINERS
23
MAINTAINERS
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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>");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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__ */
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ®val);
|
||||
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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
359
drivers/hwmon/lattepanda-sigma-ec.c
Normal file
359
drivers/hwmon/lattepanda-sigma-ec.c
Normal 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");
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
998
drivers/hwmon/mcp9982.c
Normal 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, ®_high);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(priv->regmap, address + 1, ®_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), ®_high);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
|
||||
®_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", ®_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");
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
155
drivers/hwmon/pmbus/aps-379.c
Normal file
155
drivers/hwmon/pmbus/aps-379.c
Normal 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");
|
||||
@@ -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;
|
||||
|
||||
/*
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
128
drivers/hwmon/pmbus/xdp720.c
Normal file
128
drivers/hwmon/pmbus/xdp720.c
Normal 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");
|
||||
119
drivers/hwmon/pmbus/xdpe1a2g7b.c
Normal file
119
drivers/hwmon/pmbus/xdpe1a2g7b.c
Normal 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");
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, ®val);
|
||||
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, ®val);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "error reading config register\n");
|
||||
|
||||
275
drivers/hwmon/yogafan.c
Normal file
275
drivers/hwmon/yogafan.c
Normal 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");
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user