mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-27 19:53:15 -04:00
Merge tag 'hwmon-for-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck:
"New drivers:
- Kandou KB9002 retimer
- PolarFire SoC temp/voltage sensor
- Eswin EIC7700 PVT sensor
- PMBus:
- Analog Devices MAX16545/MAX16550 and Volterra VT7505
- Monolithic MPQ82D00 and MPQ8646
- Silergy SQ24860
Added support to existing drivers:
- asus-ec-sensors: Support for ROG STRIX Z390-E GAMING, ProArt
Z690-CREATOR WIFI, ROG STRIX X870E-E GAMING WIFI7 R2, ROG CROSSHAIR
X870E HERO, and ROG Maximus Z790 Hero
- asus_rog_ryujin: Siupport for ROG Ryujin III
- ina2xx: Support for INA232
- k10temp: Per-CCD temperature monitoring for Zen5 Turin
- nct6775: List NCT5585D as supported chip
- nzxt-kraken3: Support for NZXT Kraken 2024 Elite
- sht3x: Support for GXCAS GXHT30
- tmp102: Add device IDs for TMP110 and TMP113
- yogafan: Support for LOQ 15IAX9, XiaoXin Pro 13ARE 2020, IdeaPad 3
15ALC6, Legion Pro 7 16AFR10H, Yoga Pro 7 14IAH10, Yoga 7 16ARP8,
and Lenovo LOQ 15IAX9
- PMBus:
- max20830: Support for max20830c and max20840c
- max34440: Support for MAX34452, and support for newer version of
max34451
- adm1275: Support for ROHM BD12780 and BD12790
Other notable changes:
- Constify various device attributes
- Remove redundant dev_err() and dev_err_probe() from various drivers
- applesmc: Convert to hwmon_device_register_with_info
- adt7470: Add thermal zone sensor support
- coretemp: Fix core_data leak on CPUs without PTS
- emc1403: Drop hysteresis for low limit temperature
- max6621: Fix various over- and underflow problems
- PMBus:
- Introduce pmbus_read_smbus_i2c_block_data() and use it in
various drivers
- Export and use pmbus_check_and_notify_faults()
- Let PMBus drivers report the supported PMBus revision
Various other minor fixes and improvements"
* tag 'hwmon-for-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (110 commits)
hwmon: (emc1403) Drop hysteresis for low limit temperature
hwmon: (coretemp) Fix core_data leak on CPUs without PTS
hwmon: (max6621) fix negative temperature offset and crit readings
hwmon: (max6621) fix temperature clamp range
hwmon: (asus_rog_ryujin) Add ROG Ryujin III White Edition
hwmon: (asus_rog_ryujin) Add ROG Ryujin III support
hwmon: (asus_rog_ryujin) Add per-device configuration
hwmon: (k10temp) Add per-CCD temperature monitoring for Zen5 Turin
hwmon: (tmp102) Add TMP113 device ID
hwmon: (tmp102) Add TMP110 device ID
hwmon: (nct6775) Add NCT5585D to list of supported chips
Documentation: hwmon: (nct6775) Add missing NCT6797D and NCT6798D
hwmon: (emc1403) Add regulator support
hwmon: (emc1403) Convert to use OF bindings
dt-bindings: hwmon: Document SMSC EMC1402/1403/1404/1428
hwmon: (asus-ec-sensors) add ROG STRIX Z390-E GAMING
hwmon: (sysfs) Allow drivers to register const attributes
hwmon: (corsair-psu) Update documentation
hwmon: (core) Use const APIs for the dynamically allocated sysfs attributes
hwmon: (core) Constify device attributes
...
This commit is contained in:
@@ -25,19 +25,38 @@ description: |
|
||||
https://www.silergy.com/
|
||||
download/downloadFile?id=5669&type=product&ftype=note
|
||||
|
||||
The BD12780 and BD12780A are hot-swap controllers from ROHM. They are
|
||||
functionally compatible with the ADM1278. The main difference between
|
||||
the BD12780A and the BD12780 is amount of configurable I2C addresses.
|
||||
|
||||
Datasheets:
|
||||
https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780muv-lb-e.pdf
|
||||
https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf
|
||||
|
||||
The BD12790 is a ROHM hot-swap controller, functionally similar to the
|
||||
ADM1272.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adm1075
|
||||
- adi,adm1272
|
||||
- adi,adm1273
|
||||
- adi,adm1275
|
||||
- adi,adm1276
|
||||
- adi,adm1278
|
||||
- adi,adm1281
|
||||
- adi,adm1293
|
||||
- adi,adm1294
|
||||
- silergy,mc09c
|
||||
oneOf:
|
||||
- enum:
|
||||
- adi,adm1075
|
||||
- adi,adm1272
|
||||
- adi,adm1273
|
||||
- adi,adm1275
|
||||
- adi,adm1276
|
||||
- adi,adm1278
|
||||
- adi,adm1281
|
||||
- adi,adm1293
|
||||
- adi,adm1294
|
||||
- rohm,bd12780
|
||||
- rohm,bd12790
|
||||
- silergy,mc09c
|
||||
|
||||
# Require BD12780 as a fall-back for BD12780A.
|
||||
- items:
|
||||
- const: rohm,bd12780a
|
||||
- const: rohm,bd12780
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@@ -88,6 +107,7 @@ allOf:
|
||||
enum:
|
||||
- adi,adm1272
|
||||
- adi,adm1273
|
||||
- rohm,bd12790
|
||||
then:
|
||||
properties:
|
||||
adi,volt-curr-sample-average:
|
||||
@@ -104,6 +124,7 @@ allOf:
|
||||
- adi,adm1281
|
||||
- adi,adm1293
|
||||
- adi,adm1294
|
||||
- rohm,bd12780
|
||||
- silergy,mc09c
|
||||
then:
|
||||
properties:
|
||||
|
||||
58
Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml
Normal file
58
Documentation/devicetree/bindings/hwmon/adi,adt7470.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/adi,adt7470.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices ADT7470 hwmon sensor
|
||||
|
||||
maintainers:
|
||||
- Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
|
||||
description:
|
||||
Multi-channel temperature monitor and PWM fan controller.
|
||||
It supports monitoring up to 10 external temperature sensors and
|
||||
controlling up to 4 fans. The fan control lines are modeled
|
||||
as standard PWM channels.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: adi,adt7470
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#pwm-cells":
|
||||
const: 3
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
description:
|
||||
Valid index values are 0 to 9, corresponding to temp1 through temp10.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
required:
|
||||
- "#thermal-sensor-cells"
|
||||
then:
|
||||
$ref: /schemas/thermal/thermal-sensor.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
hwmon@2f {
|
||||
compatible = "adi,adt7470";
|
||||
reg = <0x2f>;
|
||||
#pwm-cells = <3>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
@@ -45,6 +45,8 @@ properties:
|
||||
- const: low
|
||||
- const: high
|
||||
|
||||
label: true
|
||||
|
||||
vdd-supply:
|
||||
description:
|
||||
Dedicated, controllable supply-regulator to reset the device and
|
||||
@@ -55,6 +57,9 @@ required:
|
||||
- reg
|
||||
- vdd-supply
|
||||
|
||||
allOf:
|
||||
- $ref: hwmon-common.yaml#
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
@@ -72,6 +77,7 @@ examples:
|
||||
<5 IRQ_TYPE_EDGE_RISING>,
|
||||
<6 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "ready", "low", "high";
|
||||
label = "Room";
|
||||
vdd-supply = <®_vdd>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/eswin,eic7700-pvt.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: ESWIN EIC7700 PVT Sensor
|
||||
|
||||
maintainers:
|
||||
- Yulin Lu <luyulin@eswincomputing.com>
|
||||
- Huan He <hehuan1@eswincomputing.com>
|
||||
|
||||
description:
|
||||
ESWIN EIC7700 SoC integrates embedded voltage and temperature sensors to
|
||||
monitor the internal SoC environment. The system includes two PVT sensor
|
||||
instances. The PVT0 monitors the main SoC power domain. The PVT1 sensor
|
||||
monitors the DDR core power domain.
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/hwmon/hwmon-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: eswin,eic7700-pvt
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: PVT enable clock
|
||||
- description: APB bus clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: enable
|
||||
- const: apb
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
'#thermal-sensor-cells':
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- interrupts
|
||||
- resets
|
||||
- '#thermal-sensor-cells'
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
sensor@50b00000 {
|
||||
compatible = "eswin,eic7700-pvt";
|
||||
reg = <0x50b00000 0x10000>;
|
||||
clocks = <&clocks 244>, <&clocks 234>;
|
||||
clock-names = "enable", "apb";
|
||||
interrupts = <349>;
|
||||
interrupt-parent = <&plic>;
|
||||
label = "pvt0";
|
||||
resets = <&reset 111>;
|
||||
#thermal-sensor-cells = <0>;
|
||||
};
|
||||
...
|
||||
@@ -18,21 +18,12 @@ properties:
|
||||
const: hpe,gxp-fan-ctrl
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: Fan controller PWM
|
||||
- description: Programmable logic
|
||||
- description: Function 2
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: base
|
||||
- const: pl
|
||||
- const: fn2
|
||||
description: Fan controller PWM
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
@@ -40,6 +31,5 @@ examples:
|
||||
- |
|
||||
fan-controller@1000c00 {
|
||||
compatible = "hpe,gxp-fan-ctrl";
|
||||
reg = <0x1000c00 0x200>, <0xd1000000 0xff>, <0x80200000 0x100000>;
|
||||
reg-names = "base", "pl", "fn2";
|
||||
reg = <0x1000c00 0x200>;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,13 @@ allOf:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: adi,max20830
|
||||
oneOf:
|
||||
- const: adi,max20830
|
||||
- items:
|
||||
- enum:
|
||||
- adi,max20830c
|
||||
- adi,max20840c
|
||||
- const: adi,max20830
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@@ -44,15 +50,32 @@ properties:
|
||||
GPIO connected to the power-good status output pin.
|
||||
maxItems: 1
|
||||
|
||||
adi,vout-rfb1-ohms:
|
||||
description:
|
||||
Top feedback resistor (RFB1) value in ohms for VOUT sensing divider.
|
||||
When the desired output voltage is higher than VREF, a resistor divider
|
||||
is required. VOUT = VREF × (1 + RFB1/RFB2)
|
||||
|
||||
adi,vout-rfb2-ohms:
|
||||
description:
|
||||
Bottom feedback resistor (RFB2) value in ohms for VOUT sensing divider.
|
||||
Datasheet recommends that RFB2 does not exceed 2.5kΩ.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vddh-supply
|
||||
|
||||
dependencies:
|
||||
adi,vout-rfb1-ohms: ['adi,vout-rfb2-ohms']
|
||||
adi,vout-rfb2-ohms: ['adi,vout-rfb1-ohms']
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
@@ -61,6 +84,11 @@ examples:
|
||||
compatible = "adi,max20830";
|
||||
reg = <0x30>;
|
||||
vddh-supply = <&vddh>;
|
||||
avdd-supply = <&avdd>;
|
||||
ldoin-supply = <&ldoin>;
|
||||
pwr-good-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
|
||||
adi,vout-rfb1-ohms = <10000>;
|
||||
adi,vout-rfb2-ohms = <2000>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/pmbus/adi,vt7505.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices MAX16545/MAX16550 and Volterra VT7505 PMBus controllers
|
||||
|
||||
maintainers:
|
||||
- Sanman Pradhan <psanman@juniper.net>
|
||||
|
||||
description: |
|
||||
The Analog Devices MAX16545 and MAX16550, and the Volterra VT7505, are PMBus
|
||||
controllers that provide input/output voltage, input/output current,
|
||||
input power and temperature monitoring over the PMBus interface.
|
||||
|
||||
The MAX16545 uses the same programming model as the VT7505.
|
||||
|
||||
Datasheets:
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/max16545b-max16545c.pdf
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/max16550.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- adi,max16550
|
||||
- adi,vt7505
|
||||
- items:
|
||||
- const: adi,max16545
|
||||
- const: adi,vt7505
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
adi,rload-ohms:
|
||||
description:
|
||||
Resistance in ohms of the external telemetry load resistor connected
|
||||
between the ILOAD current-output pin and ground. This is not a
|
||||
current-path shunt resistor; it sets the scaling of the reported input
|
||||
current, output current and input power.
|
||||
minimum: 1
|
||||
maximum: 283383959
|
||||
default: 4750
|
||||
|
||||
adi,ocp-severe-filter-us:
|
||||
description:
|
||||
Deglitch time, in microseconds, applied to the severe
|
||||
overcurrent-protection comparator, programmed into the
|
||||
MFR_CONFIG[15:14] field. The MAX16550 and VT7505 expose this field.
|
||||
On the MAX16545 the severe overcurrent delay is fixed and these bits
|
||||
are reserved. If omitted, the existing hardware setting is retained.
|
||||
enum: [0, 1, 2, 10]
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
# The severe OCP deglitch filter is fixed on the MAX16545, so the property
|
||||
# is only valid for the MAX16550 and VT7505.
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: adi,max16545
|
||||
then:
|
||||
properties:
|
||||
adi,ocp-severe-filter-us: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
regulator@40 {
|
||||
compatible = "adi,vt7505";
|
||||
reg = <0x40>;
|
||||
adi,rload-ohms = <4750>;
|
||||
adi,ocp-severe-filter-us = <10>;
|
||||
};
|
||||
};
|
||||
@@ -18,6 +18,7 @@ properties:
|
||||
- mps,mpm3695
|
||||
- mps,mpm3695-25
|
||||
- mps,mpm82504
|
||||
- mps,mpq8646
|
||||
- mps,mpq8785
|
||||
|
||||
reg:
|
||||
@@ -52,7 +53,9 @@ allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
const: mps,mpq8785
|
||||
enum:
|
||||
- mps,mpq8646
|
||||
- mps,mpq8785
|
||||
then:
|
||||
properties:
|
||||
mps,vout-fb-divider-ratio-permille:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
|
||||
$id: http://devicetree.org/schemas/hwmon/pmbus/silergy,sq24860.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Silergy SQ24860 eFuse
|
||||
|
||||
maintainers:
|
||||
- Ziming Zhu <ziming.zhu@silergycorp.com>
|
||||
|
||||
description:
|
||||
The Silergy SQ24860 is an integrated, high-current circuit protection and
|
||||
power management device with PMBus interface.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: silergy,sq24860
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
silergy,rimon-micro-ohms:
|
||||
description:
|
||||
Micro-ohms value of the resistance installed between the IMON pin and
|
||||
the ground reference.
|
||||
|
||||
interrupts:
|
||||
description: PMBus SMBAlert interrupt.
|
||||
maxItems: 1
|
||||
|
||||
regulators:
|
||||
type: object
|
||||
description:
|
||||
List of regulators provided by this controller.
|
||||
|
||||
properties:
|
||||
vout:
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
type: object
|
||||
unevaluatedProperties: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- silergy,rimon-micro-ohms
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
hw-monitor@40 {
|
||||
compatible = "silergy,sq24860";
|
||||
reg = <0x40>;
|
||||
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <42 8>;
|
||||
silergy,rimon-micro-ohms = <1600000000>;
|
||||
|
||||
regulators {
|
||||
cpu0_vout: vout {
|
||||
regulator-name = "main_cpu0";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -46,12 +46,32 @@ properties:
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
ti,current-range:
|
||||
description: |
|
||||
Configure the current limit setting. When present, this property
|
||||
overrides the hardware setting of the physical CL pin by configuring
|
||||
the DEVICE_SETUP register.
|
||||
- "low": maps to 25 mV (LM25066) or 26 mV (LM5064, LM5066, LM5066i)
|
||||
- "high": maps to 46 mV (LM25066) or 50 mV (LM5064, LM5066, LM5066i)
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum:
|
||||
- low
|
||||
- high
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/hwmon/hwmon-common.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: ti,lm25056
|
||||
then:
|
||||
properties:
|
||||
ti,current-range: false
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
||||
71
Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml
Normal file
71
Documentation/devicetree/bindings/hwmon/sensirion,sht30.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/sensirion,sht30.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Sensirion SHT3x and GXCAS GXHT30 humidity and temperature sensors
|
||||
|
||||
maintainers:
|
||||
- Zaixiang Xu <zaixiang.xu.dev@gmail.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- sensirion,sht30
|
||||
- sensirion,sts30
|
||||
- items:
|
||||
- enum:
|
||||
- gxcas,gxht30
|
||||
- sensirion,sht31
|
||||
- sensirion,sht35
|
||||
- sensirion,sht85
|
||||
- const: sensirion,sht30
|
||||
- items:
|
||||
- enum:
|
||||
- sensirion,sts31
|
||||
- sensirion,sts32
|
||||
- sensirion,sts35
|
||||
- const: sensirion,sts30
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply:
|
||||
description: Regulator that provides power to the VDD pin.
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
description:
|
||||
GPIO connected to the nRESET pin. Active low.
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
description:
|
||||
Interrupt connected to the ALERT pin.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
humidity-sensor@44 {
|
||||
compatible = "gxcas,gxht30", "sensirion,sht30";
|
||||
reg = <0x44>;
|
||||
vdd-supply = <&vcc_3v3_reg>;
|
||||
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
||||
};
|
||||
203
Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml
Normal file
203
Documentation/devicetree/bindings/hwmon/smsc,emc1403.yaml
Normal file
@@ -0,0 +1,203 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/smsc,emc1403.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: SMSC EMC1402/1403/1404/1428 thermal sensors
|
||||
|
||||
maintainers:
|
||||
- Svyatoslav Ryhel <clamor95@gmail.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- smsc,emc1402
|
||||
- smsc,emc1403
|
||||
- smsc,emc1404
|
||||
- smsc,emc1428
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- smsc,emc1412
|
||||
- smsc,emc1422
|
||||
- smsc,emc1442
|
||||
- const: smsc,emc1402
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- smsc,emc1413
|
||||
- smsc,emc1423
|
||||
- const: smsc,emc1403
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- smsc,emc1414
|
||||
- smsc,emc1424
|
||||
- const: smsc,emc1404
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- smsc,emc1438
|
||||
- const: smsc,emc1428
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: Sensors "ALERT" pin output.
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
vdd-supply: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
patternProperties:
|
||||
"^channel@([0-7])$":
|
||||
type: object
|
||||
description: Represents channels of the device and their specific configuration.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description: The channel number. 0 is local channel, 1-7 are remote channels.
|
||||
items:
|
||||
minimum: 0
|
||||
maximum: 7
|
||||
|
||||
label:
|
||||
description: A descriptive name for this channel, like "ambient" or "psu".
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- smsc,emc1402
|
||||
then:
|
||||
patternProperties:
|
||||
"^channel@([0-7])$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
maximum: 1
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- smsc,emc1403
|
||||
then:
|
||||
patternProperties:
|
||||
"^channel@([0-7])$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
maximum: 2
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- smsc,emc1404
|
||||
then:
|
||||
patternProperties:
|
||||
"^channel@([0-7])$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
maximum: 3
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
sensor@4c {
|
||||
compatible = "smsc,emc1412", "smsc,emc1402";
|
||||
reg = <0x4c>;
|
||||
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
vdd-supply = <&vdd_3v3_sys>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
sensor@29 {
|
||||
compatible = "smsc,emc1428";
|
||||
reg = <0x29>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
channel@0 {
|
||||
reg = <0x0>;
|
||||
label = "local";
|
||||
};
|
||||
|
||||
channel@1 {
|
||||
reg = <0x1>;
|
||||
label = "chan1";
|
||||
};
|
||||
|
||||
channel@2 {
|
||||
reg = <0x2>;
|
||||
label = "chan2";
|
||||
};
|
||||
|
||||
channel@3 {
|
||||
reg = <0x3>;
|
||||
label = "chan3";
|
||||
};
|
||||
|
||||
channel@4 {
|
||||
reg = <0x4>;
|
||||
label = "chan4";
|
||||
};
|
||||
|
||||
channel@5 {
|
||||
reg = <0x5>;
|
||||
label = "chan5";
|
||||
};
|
||||
|
||||
channel@6 {
|
||||
reg = <0x6>;
|
||||
label = "chan6";
|
||||
};
|
||||
|
||||
channel@7 {
|
||||
reg = <0x7>;
|
||||
label = "chan7";
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -28,6 +28,7 @@ properties:
|
||||
- ti,ina228
|
||||
- ti,ina230
|
||||
- ti,ina231
|
||||
- ti,ina232
|
||||
- ti,ina233
|
||||
- ti,ina234
|
||||
- ti,ina237
|
||||
@@ -114,6 +115,7 @@ allOf:
|
||||
- ti,ina228
|
||||
- ti,ina230
|
||||
- ti,ina231
|
||||
- ti,ina232
|
||||
- ti,ina234
|
||||
- ti,ina237
|
||||
- ti,ina238
|
||||
@@ -136,6 +138,7 @@ allOf:
|
||||
- ti,ina226
|
||||
- ti,ina230
|
||||
- ti,ina231
|
||||
- ti,ina232
|
||||
- ti,ina234
|
||||
- ti,ina260
|
||||
- ti,ina700
|
||||
|
||||
@@ -196,6 +196,8 @@ properties:
|
||||
- isil,isl76682
|
||||
# JEDEC JESD300 (SPD5118) Hub and Serial Presence Detect
|
||||
- jedec,spd5118
|
||||
# Kandou KB9002 PCIe 5.0 retimer
|
||||
- kandou,kb9002
|
||||
# Linear Technology LTC2488
|
||||
- lineartechnology,ltc2488
|
||||
# Regulated 12V, 860W, Digital DC/DC Power Module
|
||||
@@ -362,6 +364,8 @@ properties:
|
||||
- mps,mp9941
|
||||
# Monolithic Power Systems Inc. digital step-down converter mp9945
|
||||
- mps,mp9945
|
||||
# Monolithic Power Systems Inc. digital step-down converter mpq82d00
|
||||
- mps,mpq82d00
|
||||
# Murata D1U74T-W power supply unit
|
||||
- murata,d1u74t
|
||||
# Temperature sensor with integrated fan control
|
||||
|
||||
@@ -698,6 +698,8 @@ patternProperties:
|
||||
description: Gateworks Corporation
|
||||
use "gateworks" vendor prefix
|
||||
deprecated: true
|
||||
"^gxcas,.*":
|
||||
description: Beijing Galaxy-CAS Technology Co., Ltd.
|
||||
"^hannstar,.*":
|
||||
description: HannStar Display Corporation
|
||||
"^haochuangyi,.*":
|
||||
@@ -889,6 +891,8 @@ patternProperties:
|
||||
description: JuTouch Technology Co., Ltd.
|
||||
"^kam,.*":
|
||||
description: Kamstrup A/S
|
||||
"^kandou,.*":
|
||||
description: Kandou AI
|
||||
"^karo,.*":
|
||||
description: Ka-Ro electronics GmbH
|
||||
"^keithkoep,.*":
|
||||
|
||||
@@ -67,6 +67,30 @@ Supported chips:
|
||||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1293_1294.pdf
|
||||
|
||||
* ROHM Semiconductor BD12780
|
||||
|
||||
Prefix: 'bd12780'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780muv-lb-e.pdf
|
||||
|
||||
* ROHM Semiconductor BD12780A
|
||||
|
||||
Prefix: 'bd12780'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf
|
||||
|
||||
* ROHM Semiconductor BD12790
|
||||
|
||||
Prefix: 'bd12790'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: -
|
||||
|
||||
* Silergy SQ24905C
|
||||
|
||||
Prefix: 'mc09c'
|
||||
|
||||
@@ -17,6 +17,7 @@ Supported boards:
|
||||
* ProArt X670E-CREATOR WIFI
|
||||
* ProArt X870E-CREATOR WIFI
|
||||
* ProArt B550-CREATOR
|
||||
* ProArt Z690-CREATOR WIFI
|
||||
* ROG CROSSHAIR VIII DARK HERO
|
||||
* ROG CROSSHAIR VIII HERO (WI-FI)
|
||||
* ROG CROSSHAIR VIII FORMULA
|
||||
@@ -25,11 +26,13 @@ Supported boards:
|
||||
* ROG CROSSHAIR X670E EXTREME
|
||||
* ROG CROSSHAIR X670E HERO
|
||||
* ROG CROSSHAIR X670E GENE
|
||||
* ROG CROSSHAIR X870E HERO
|
||||
* ROG MAXIMUS X HERO
|
||||
* ROG MAXIMUS XI HERO
|
||||
* ROG MAXIMUS XI HERO (WI-FI)
|
||||
* ROG MAXIMUS Z690 FORMULA
|
||||
* ROG MAXIMUS Z790 EXTREME
|
||||
* ROG MAXIMUS Z790 HERO
|
||||
* ROG STRIX B550-E GAMING
|
||||
* ROG STRIX B550-I GAMING
|
||||
* ROG STRIX B650E-E GAMING WIFI
|
||||
@@ -47,7 +50,9 @@ Supported boards:
|
||||
* 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 Z390-E GAMING
|
||||
* ROG STRIX Z390-F GAMING
|
||||
* ROG STRIX Z490-F GAMING
|
||||
* ROG STRIX Z690-A GAMING WIFI D4
|
||||
|
||||
@@ -6,6 +6,9 @@ Kernel driver asus_rog_ryujin
|
||||
Supported devices:
|
||||
|
||||
* ASUS ROG RYUJIN II 360
|
||||
* ASUS ROG RYUJIN III EXTREME
|
||||
* ASUS ROG RYUJIN III EVA EDITION
|
||||
* ASUS ROG RYUJIN III WHITE EDITION
|
||||
|
||||
Author: Aleksa Savic
|
||||
|
||||
@@ -16,10 +19,10 @@ This driver enables hardware monitoring support for the listed ASUS ROG RYUJIN
|
||||
all-in-one CPU liquid coolers. Available sensors are pump, internal and external
|
||||
(controller) fan speed in RPM, their duties in PWM, as well as coolant temperature.
|
||||
|
||||
Attaching external fans to the controller is optional and allows them to be
|
||||
controlled from the device. If not connected, the fan-related sensors will
|
||||
report zeroes. The controller is a separate hardware unit that comes bundled
|
||||
with the AIO and connects to it to allow fan control.
|
||||
The RYUJIN II includes a separate external fan controller. Attaching fans to
|
||||
the controller is optional and allows them to be controlled from the device.
|
||||
If not connected, the controller-related sensors will report zeroes. The
|
||||
RYUJIN III does not expose these controller channels.
|
||||
|
||||
The addressable LCD screen is not supported in this driver and should
|
||||
be controlled through userspace tools.
|
||||
@@ -33,15 +36,15 @@ supports hot swapping.
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
=========== =============================================
|
||||
=========== ==========================================================
|
||||
fan1_input Pump speed (in rpm)
|
||||
fan2_input Internal fan speed (in rpm)
|
||||
fan3_input External (controller) fan 1 speed (in rpm)
|
||||
fan4_input External (controller) fan 2 speed (in rpm)
|
||||
fan5_input External (controller) fan 3 speed (in rpm)
|
||||
fan6_input External (controller) fan 4 speed (in rpm)
|
||||
fan3_input External (controller) fan 1 speed (in rpm, RYUJIN II only)
|
||||
fan4_input External (controller) fan 2 speed (in rpm, RYUJIN II only)
|
||||
fan5_input External (controller) fan 3 speed (in rpm, RYUJIN II only)
|
||||
fan6_input External (controller) fan 4 speed (in rpm, RYUJIN II only)
|
||||
temp1_input Coolant temperature (in millidegrees Celsius)
|
||||
pwm1 Pump duty
|
||||
pwm2 Internal fan duty
|
||||
pwm3 External (controller) fan duty
|
||||
=========== =============================================
|
||||
pwm3 External (controller) fan duty (RYUJIN II only)
|
||||
=========== ==========================================================
|
||||
|
||||
@@ -70,4 +70,6 @@ humidity1_min_hyst: RW humidity low hystersis
|
||||
humidity1_max_hyst: RW humidity high hystersis
|
||||
humidity1_min_alarm: RO humidity low alarm indicator
|
||||
humidity1_max_alarm: RO humidity high alarm indicator
|
||||
humidity1_label: RO descriptive name for the sensor
|
||||
temp1_label: RO descriptive name for the sensor
|
||||
=============================== ======= ========================================
|
||||
|
||||
@@ -44,9 +44,9 @@ Temperature known as TjMax is the maximum junction temperature of processor,
|
||||
which depends on the CPU model. See table below. At this temperature, protection
|
||||
mechanism will perform actions to forcibly cool down the processor. Alarm
|
||||
may be raised, if the temperature grows enough (more than TjMax) to trigger
|
||||
the Out-Of-Spec bit. Following table summarizes the exported sysfs files:
|
||||
the Out-Of-Spec bit. The following table summarizes the exported sysfs files:
|
||||
|
||||
All Sysfs entries are named with their core_id (represented here by 'X').
|
||||
All sysfs entries are named with their core_id (represented here by 'X').
|
||||
|
||||
================= ========================================================
|
||||
tempX_input Core temperature (in millidegrees Celsius).
|
||||
|
||||
@@ -19,7 +19,7 @@ Supported devices:
|
||||
|
||||
Corsair HX1200i (Legacy, Series 2023 and Series 2025)
|
||||
|
||||
Corsair HX1500i (Legacy and Series 2023)
|
||||
Corsair HX1500i (Legacy, Series 2023 and Series 2025)
|
||||
|
||||
Corsair RM550i
|
||||
|
||||
@@ -93,8 +93,8 @@ Debugfs entries
|
||||
---------------
|
||||
|
||||
======================= ========================================================
|
||||
ocpmode Single or multi rail mode of the PCIe power connectors
|
||||
product Product name of the psu
|
||||
ocpmode Single or multi rail mode of the PCIe power connectors
|
||||
product Product name of the psu
|
||||
uptime Session uptime of the psu
|
||||
uptime_total Total uptime of the psu
|
||||
vendor Vendor name of the psu
|
||||
|
||||
@@ -71,10 +71,10 @@ and EMC14x8 support eight sensors (one internal, seven external).
|
||||
|
||||
The chips implement three limits for each sensor: low (tempX_min), high
|
||||
(tempX_max) and critical (tempX_crit.) The chips also implement an
|
||||
hysteresis mechanism which applies to all limits. The relative difference
|
||||
is stored in a single register on the chip, which means that the relative
|
||||
difference between the limit and its hysteresis is always the same for
|
||||
all three limits.
|
||||
hysteresis mechanism which applies to high and critical limits. The relative
|
||||
difference is stored in a single register on the chip, which means that the
|
||||
relative difference between the limit and its hysteresis is always the same
|
||||
for high and critical limits.
|
||||
|
||||
This implementation detail implies the following:
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ Hardware Monitoring Kernel Drivers
|
||||
jc42
|
||||
k10temp
|
||||
k8temp
|
||||
kb9002
|
||||
kbatt
|
||||
kfan
|
||||
lan966x
|
||||
@@ -201,6 +202,8 @@ Hardware Monitoring Kernel Drivers
|
||||
mp5990
|
||||
mp9941
|
||||
mp9945
|
||||
mpq82d00
|
||||
mpq8646
|
||||
mpq8785
|
||||
nct6683
|
||||
nct6775
|
||||
@@ -253,6 +256,7 @@ Hardware Monitoring Kernel Drivers
|
||||
smsc47m1
|
||||
sparx5-temp
|
||||
spd5118
|
||||
sq24860
|
||||
stpddc60
|
||||
surface_fan
|
||||
sy7636a-hwmon
|
||||
@@ -272,12 +276,14 @@ Hardware Monitoring Kernel Drivers
|
||||
tps53679
|
||||
tps546d24
|
||||
tsc1641
|
||||
tvs-mpfs
|
||||
twl4030-madc-hwmon
|
||||
ucd9000
|
||||
ucd9200
|
||||
vexpress
|
||||
via686a
|
||||
vt1211
|
||||
vt7505
|
||||
w83627ehf
|
||||
w83627hf
|
||||
w83773g
|
||||
|
||||
64
Documentation/hwmon/kb9002.rst
Normal file
64
Documentation/hwmon/kb9002.rst
Normal file
@@ -0,0 +1,64 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver kb9002
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Kandou KB9002
|
||||
|
||||
Prefix: 'kb9002'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: KA-015171-PD (available from Kandou under NDA)
|
||||
|
||||
Author: Andy Chung <andy.chung@amd.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The Kandou KB9002 is an 8-lane PCIe 5.0 retimer with an integrated
|
||||
microcontroller. It exposes an SMBus 3.0 target (with mandatory PEC)
|
||||
on its sideband interface. The internal firmware aggregates per-lane
|
||||
die temperatures and publishes the maximum value through a 16-bit
|
||||
addressed register window.
|
||||
|
||||
This driver reports that aggregated maximum as the only hwmon
|
||||
temperature channel. The running firmware version and the firmware
|
||||
boot status are exposed under debugfs.
|
||||
|
||||
sysfs interface
|
||||
---------------
|
||||
|
||||
================== ===============================================
|
||||
temp1_input Aggregated maximum die temperature across all
|
||||
active lanes (millidegrees Celsius).
|
||||
================== ===============================================
|
||||
|
||||
debugfs interface
|
||||
-----------------
|
||||
|
||||
Files live in the per-client debugfs directory created by the I2C
|
||||
core: ``/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/``.
|
||||
|
||||
================== ===============================================
|
||||
fw_ver Running firmware version in
|
||||
"major.minor.patch.suffix" format. Read-only.
|
||||
fw_load_status Firmware boot status: "normal" once firmware has
|
||||
finished initialising, "abnormal" otherwise.
|
||||
Read-only.
|
||||
================== ===============================================
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
The driver requires ``I2C_FUNC_SMBUS_BLOCK_DATA``,
|
||||
``I2C_FUNC_SMBUS_PEC`` and ``I2C_FUNC_I2C`` from the host adapter. The
|
||||
last is needed only during probe for the host-interface mode switch;
|
||||
runtime accesses use SMBus block transactions exclusively.
|
||||
|
||||
The retimer's SMBus address is configurable on three strap pins and
|
||||
ranges from 0x20 to 0x27. The address is selected through device tree
|
||||
(or i2c board info) the same way as any other I2C device; the driver
|
||||
does not auto-detect.
|
||||
@@ -13,6 +13,22 @@ Supported chips:
|
||||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max20830.pdf
|
||||
|
||||
* Analog Devices MAX20830C
|
||||
|
||||
Prefix: 'max20830c'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
* Analog Devices MAX20840C
|
||||
|
||||
Prefix: 'max20840c'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
Author:
|
||||
|
||||
- Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
|
||||
@@ -21,12 +37,13 @@ Author:
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Analog Devices MAX20830
|
||||
Step-Down Switching Regulator with PMBus Interface.
|
||||
This driver supports hardware monitoring for Analog Devices MAX20830, MAX20830C
|
||||
and MAX20840C. These are Step-Down Switching Regulator with PMBus Interface.
|
||||
|
||||
The MAX20830 is a 2.7V to 16V, 30A fully integrated step-down DC-DC switching
|
||||
regulator. Through the PMBus interface, the device can monitor input/output
|
||||
voltages, output current and temperature.
|
||||
MAX20830, and MAX20830C are 2.7V to 16V, 30A fully integrated step-down DC-DC
|
||||
switching regulators. MAX20840C is similar but can reach 40A. Through the PMBus
|
||||
interface, these devices can monitor input/output voltages, output current and
|
||||
temperature.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
|
||||
@@ -65,6 +65,16 @@ Supported chips:
|
||||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max34451.pdf
|
||||
|
||||
* Maxim MAX34452
|
||||
|
||||
PMBus 16-Channel V/I Monitor and 12-Channel Sequencer/Marginer
|
||||
|
||||
Prefixes: 'max34452'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: -
|
||||
|
||||
* Maxim MAX34460
|
||||
|
||||
PMBus 12-Channel Voltage Monitor & Sequencer
|
||||
@@ -94,11 +104,11 @@ Description
|
||||
This driver supports multiple devices: hardware monitoring for Maxim MAX34440
|
||||
PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply
|
||||
Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data
|
||||
Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34460, and
|
||||
MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The
|
||||
MAX34451 supports monitoring voltage or current of 12 channels based on GIN
|
||||
pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports 16
|
||||
voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitors both
|
||||
Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34452, MAX34460,
|
||||
and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The
|
||||
MAX34451 and MAX34452 support monitoring voltage or current of 16 channels based
|
||||
on GIN pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports
|
||||
16 voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitor both
|
||||
input and output of voltage and current.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
@@ -171,7 +181,7 @@ curr[1-6]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT
|
||||
register.
|
||||
curr[1-6]_max_alarm Current high alarm. From IOUT_OC_WARNING status.
|
||||
curr[1-6]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status.
|
||||
curr[1-4]_average Historical average current (MAX34446/34451 only).
|
||||
curr[1-4]_average Historical average current (MAX34446/34451/34452 only).
|
||||
curr[1-6]_highest Historical maximum current.
|
||||
curr[1-6]_reset_history Write any value to reset history.
|
||||
======================= ========================================================
|
||||
@@ -223,7 +233,7 @@ temp[1-8]_reset_history Write any value to reset history.
|
||||
|
||||
.. note::
|
||||
|
||||
- MAX34451 supports attribute groups in[1-16] (or curr[1-16] based on
|
||||
input pins) and temp[1-5].
|
||||
- MAX34451 and MAX34452 support attribute groups in[1-16] (or curr[1-16]
|
||||
based on input pins) and temp[1-5].
|
||||
- MAX34460 supports attribute groups in[1-12] and temp[1-5].
|
||||
- MAX34461 supports attribute groups in[1-16] and temp[1-5].
|
||||
|
||||
73
Documentation/hwmon/mpq82d00.rst
Normal file
73
Documentation/hwmon/mpq82d00.rst
Normal file
@@ -0,0 +1,73 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver mpq82d00
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* MPS mpq82d00
|
||||
|
||||
Prefix: 'mpq82d00'
|
||||
|
||||
Author:
|
||||
|
||||
Wensheng Wang <wenswang@yeah.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Monolithic Power Systems, Inc. (MPS)
|
||||
MPQ82D00 Step-Down Controller.
|
||||
|
||||
Device compliant with:
|
||||
|
||||
- PMBus rev 1.3 interface.
|
||||
|
||||
The driver exports the following attributes via the 'sysfs' files
|
||||
for input voltage:
|
||||
|
||||
**in1_input**
|
||||
|
||||
**in1_label**
|
||||
|
||||
**in1_alarm**
|
||||
|
||||
The driver provides the following attributes for output voltage:
|
||||
|
||||
**in2_input**
|
||||
|
||||
**in2_label**
|
||||
|
||||
**in2_alarm**
|
||||
|
||||
The driver provides the following attributes for output current:
|
||||
|
||||
**curr1_input**
|
||||
|
||||
**curr1_label**
|
||||
|
||||
**curr1_crit**
|
||||
|
||||
**curr1_crit_alarm**
|
||||
|
||||
The driver provides the following attributes for input power:
|
||||
|
||||
**power1_input**
|
||||
|
||||
**power1_label**
|
||||
|
||||
**power1_alarm**
|
||||
|
||||
The driver provides the following attributes for output power:
|
||||
|
||||
**power2_input**
|
||||
|
||||
**power2_label**
|
||||
|
||||
The driver provides the following attributes for temperature:
|
||||
|
||||
**temp1_input**
|
||||
|
||||
**temp1_crit**
|
||||
|
||||
**temp1_crit_alarm**
|
||||
300
Documentation/hwmon/mpq8646.rst
Normal file
300
Documentation/hwmon/mpq8646.rst
Normal file
@@ -0,0 +1,300 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
.. Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr>
|
||||
|
||||
Kernel driver mpq8646
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* MPS MPQ8646
|
||||
|
||||
Prefix: 'mpq8646'
|
||||
|
||||
Author:
|
||||
- Vincent Jardin <vjardin@free.fr>
|
||||
|
||||
Chip-identity
|
||||
-------------
|
||||
|
||||
Support the boards that are designed with the MPS MPQ8646 probed thanks
|
||||
to``MFR_MODEL`` register.
|
||||
|
||||
This driver targets the MPQ8646 silicon specifically.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MPQ8646 is a fully integrated, PMBus-compatible, high-frequency,
|
||||
synchronous buck converter. It offers a compact solution that
|
||||
achieves up high Amps output current per phase, with excellent load
|
||||
and line regulation over a wide input supply range. The chip
|
||||
operates at high efficiency over a wide output current load range.
|
||||
|
||||
The PMBus interface provides converter configurations and key
|
||||
parameters monitoring.
|
||||
|
||||
The device adopts MPS's proprietary multi-phase digital
|
||||
constant-on-time (MCOT) control, which provides fast transient
|
||||
response and eases loop stabilization. The MCOT scheme also allows
|
||||
multiple devices or channels to be connected in parallel with
|
||||
excellent current sharing and phase interleaving for high-current
|
||||
applications.
|
||||
|
||||
Fully integrated protection features include over-current
|
||||
protection (OCP), over-voltage protection (OVP), under-voltage
|
||||
protection (UVP), and over-temperature protection (OTP).
|
||||
|
||||
This device is compliant with:
|
||||
|
||||
- PMBus rev 1.3 interface.
|
||||
|
||||
The driver exports the following attributes via the 'sysfs' files
|
||||
for input voltage:
|
||||
|
||||
- in1_input
|
||||
- in1_label
|
||||
- in1_max
|
||||
- in1_max_alarm
|
||||
- in1_min
|
||||
- in1_min_alarm
|
||||
- in1_crit
|
||||
- in1_crit_alarm
|
||||
|
||||
The driver provides the following attributes for output voltage:
|
||||
|
||||
- in2_input
|
||||
- in2_label
|
||||
- in2_alarm
|
||||
- in2_max
|
||||
- in2_max_alarm
|
||||
- in2_min
|
||||
- in2_min_alarm
|
||||
- in2_crit
|
||||
- in2_crit_alarm
|
||||
- in2_lcrit
|
||||
- in2_lcrit_alarm
|
||||
|
||||
The driver provides the following attributes for output current:
|
||||
|
||||
- curr1_input
|
||||
- curr1_label
|
||||
- curr1_max
|
||||
- curr1_max_alarm
|
||||
- curr1_crit
|
||||
- curr1_crit_alarm
|
||||
|
||||
The driver provides the following attributes for temperature:
|
||||
|
||||
- temp1_input
|
||||
- temp1_max
|
||||
- temp1_max_alarm
|
||||
- temp1_crit
|
||||
- temp1_crit_alarm
|
||||
|
||||
Alarm acknowledgment
|
||||
---------------------
|
||||
|
||||
The hwmon-class ``inX_alarm``/``currX_alarm``/``tempX_alarm`` files are
|
||||
read-only in pmbus_core. The driver exposes the standard
|
||||
``PMBUS_VIRT_RESET_*_HISTORY`` virtual-register channel for fault
|
||||
acknowledgment: writing ``1`` to ``inX_reset_history`` /
|
||||
``currX_reset_history`` / ``tempX_reset_history`` sends the chip a
|
||||
``CLEAR_FAULTS`` (0x03) Send-Byte, which clears the latched
|
||||
``STATUS_WORD`` / ``STATUS_VOUT`` / ``STATUS_IOUT`` /
|
||||
``STATUS_INPUT`` / ``STATUS_TEMPERATURE`` bits the chip is currently
|
||||
exposing.
|
||||
|
||||
The MPS-specific NVM post-mortem register
|
||||
``PROTECTION_LAST`` (0xFB) is not cleared by this path; it can be read
|
||||
(decoded) via the ``protection_last`` debugfs entry described below.
|
||||
|
||||
Regulator framework integration
|
||||
-------------------------------
|
||||
|
||||
When ``CONFIG_REGULATOR=y`` is set, the chip is
|
||||
exposed under ``/sys/class/regulator/`` and accepts the standard
|
||||
regulator framework operations:
|
||||
|
||||
- ``regulator_enable()`` / ``regulator_disable()`` -> ``OPERATION`` (0x01)
|
||||
- ``regulator_set_voltage()`` -> ``VOUT_COMMAND`` (0x21)
|
||||
- ``regulator_get_voltage()`` -> ``READ_VOUT`` (0x8B)
|
||||
- ``regulator_is_enabled()`` -> ``OPERATION`` bit-decode
|
||||
|
||||
The single regulator descriptor is named ``"vout"`` (page 0). For a
|
||||
multi-page configuration the descriptor table can be extended in
|
||||
the driver.
|
||||
|
||||
In-driver alarm-poll fallback
|
||||
-----------------------------
|
||||
|
||||
On boards where the chip's ``SMBALERT#`` pin is unavailable to the
|
||||
SoC, ``pmbus_core::pmbus_irq_setup`` cannot deliver SMBALERT-driven
|
||||
``poll(POLLPRI)`` wakes or ``udev change@...`` events on the
|
||||
``inX_alarm`` files. The driver provides a ``delayed_work``-based
|
||||
polling fallback that
|
||||
periodically invokes the pmbus core fault check,
|
||||
``pmbus_check_and_notify_faults()``, which sends the notifications and
|
||||
then clears the latched faults, exactly like the ``SMBALERT#``
|
||||
interrupt path. The frequency of polling is tunable:
|
||||
|
||||
::
|
||||
|
||||
/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/alarm_poll_interval_ms
|
||||
|
||||
The default is 1000 ms.
|
||||
|
||||
Set it to 0 to disable. The worker self-suppresses
|
||||
when ``client->irq != 0`` (i.e. when DT supplies an
|
||||
``interrupts = <...>`` property on the regulator node), so adding
|
||||
``SMBALERT#`` wiring is a zero-driver-change uplift on a future
|
||||
board rev.
|
||||
|
||||
MPS post-mortem (PROTECTION_LAST)
|
||||
---------------------------------
|
||||
|
||||
The MPQ8646 silicon keeps a single 16-bit NVM-backed record of the
|
||||
last protection event in ``PROTECTION_LAST`` (0xFB). It survives
|
||||
chip power/reset. The following debugfs entries expose it:
|
||||
|
||||
::
|
||||
|
||||
/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/protection_last (RO)
|
||||
/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/status_decoded (RO)
|
||||
|
||||
``protection_last`` decodes the 16-bit value based on the datasheet
|
||||
fault names (``INIT_FAULT``, ``NVM_CRC_ERROR``, ``NVM_FAULT``,
|
||||
``OC_PHASE_FAULT``, ``OTP_SELF_FAULT``, ``SWITCH_PRD_FAULT``,
|
||||
``VIN_OV_FAULT``, ``VOUT_OV_FAULT``, ``VOUT_UV_FAULT``,
|
||||
``OC_TOT_FAULT``, ``VIN_UVLO_FAULT``, ``DRMOS_OTP``).
|
||||
|
||||
``status_decoded`` reads ``STATUS_WORD`` (0x79) and renders the
|
||||
16 bits with MPS-extension labels (bit12 = ``NVM_SUMMARY``,
|
||||
bit8 = ``WATCH_DOG``, bit0 = ``DRMOS_FAULT``) instead of the
|
||||
PMBus 1.3 spec generic names.
|
||||
|
||||
NVMEM snapshot
|
||||
--------------
|
||||
|
||||
When ``CONFIG_NVMEM=y`` is set, the chip's NVM-backed
|
||||
observability registers are exposed as a single 16-byte read-only
|
||||
``nvmem_device`` at ``/sys/bus/nvmem/devices/<i2c-name>/nvmem``.
|
||||
Layout (little-endian for 16-bit fields, zero-fill on per-entry read
|
||||
failure and for the reserved tail):
|
||||
|
||||
::
|
||||
|
||||
offset 0..1 PROTECTION_LAST (0xFB) word
|
||||
offset 2..3 MFR_RETRY_TIMES (0xF4) word
|
||||
offset 4..5 MFR_CONFIG_ID (0xC0) word
|
||||
offset 6..7 MFR_VBOOT_CFG (0xFC) word
|
||||
offset 8 MFR_SILICON_REV (0xC3) byte
|
||||
offset 9..15 reserved (zero)
|
||||
|
||||
Suitable for single-``cat`` post-mortem capture by a fleet daemon.
|
||||
|
||||
Diagnostics and introspection
|
||||
-----------------------------
|
||||
|
||||
When ``CONFIG_DEBUG_FS=y`` is set, the driver adds a read-only
|
||||
decode surface to the client's pmbus debugfs directory,
|
||||
``/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/``.
|
||||
The driver intentionally exposes no raw register poke/peek debugfs;
|
||||
use i2c-dev (``i2cget``/``i2cset``/``i2ctransfer``) for that.
|
||||
|
||||
Identity / observability (read-only):
|
||||
|
||||
========================== ================== =================================
|
||||
File PMBus / MFR cmd Description
|
||||
========================== ================== =================================
|
||||
``mfr_config_id`` 0xC0 (word) board / SKU NVM identifier
|
||||
``mfr_config_code_rev`` 0xC1 (word) NVM image revision (PART_RECOG +
|
||||
config code rev fields)
|
||||
``mfr_silicon_rev`` 0xC3 (byte) die revision
|
||||
``mfr_retry_times`` 0xF4 (word) per-fault-class recovery-mode
|
||||
configuration (NOT a retry
|
||||
count). Four 4-bit fields
|
||||
[15:12]=OTP, [11:8]=VOUT_OV,
|
||||
[7:4]=VOUT_UV, [3:0]=OCP.
|
||||
Each field: 0x0=latch-off,
|
||||
0x1..0xE=retry N times then
|
||||
latch off, 0xF=hiccup
|
||||
(retry indefinitely). The
|
||||
chip exposes no in-NVM
|
||||
retry-event counter; track
|
||||
transitions externally if
|
||||
needed (poll
|
||||
``protection_last`` or watch
|
||||
``inX_alarm`` / ``temp1_alarm``
|
||||
``poll(POLLPRI)`` wakes).
|
||||
``mfr_vboot_cfg`` 0xFC (word) ADDR/VBOOT latched at POR
|
||||
========================== ================== =================================
|
||||
|
||||
Timing / UVLO knobs (read-only):
|
||||
|
||||
========================== ================== =================================
|
||||
File PMBus cmd Description
|
||||
========================== ================== =================================
|
||||
``vin_on`` 0x35 (word) UVLO turn-on threshold
|
||||
``vin_off`` 0x36 (word) UVLO turn-off threshold
|
||||
``ton_delay`` 0x60 (word) soft-start delay
|
||||
``ton_rise`` 0x61 (word) soft-start ramp time
|
||||
``toff_delay`` 0x64 (word) soft-stop delay
|
||||
``toff_fall`` 0x65 (word) soft-stop ramp time
|
||||
========================== ================== =================================
|
||||
|
||||
The only writable entry is ``alarm_poll_interval_ms`` (the alarm-poll
|
||||
worker cadence, see above), it is driver-local and never touches the
|
||||
chip.
|
||||
|
||||
Unsafe provisioning
|
||||
-------------------
|
||||
|
||||
Available only when ``CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE=y``
|
||||
(``default n``, it is for boards bring up only). They WRITE to the regulator.
|
||||
On many designs the MPQ8646 powers the main CPU core rail, so a wrong write can
|
||||
brown out the board or persist a bad setpoint into finite-cycle NVM.
|
||||
WARNING: Some wrong register writes can and likely will physically damage or
|
||||
destroy the chip and/or the board. An explicit warning banner is printed at probe
|
||||
time when this option is enabled.
|
||||
|
||||
=============================== =============== =========================================================
|
||||
File PMBus / MFR cmd Description
|
||||
=============================== =============== =========================================================
|
||||
``store_all`` 0x15 STORE_USER_ALL Send-Byte (commit RAM config to NVM)
|
||||
``restore_all`` 0x16 RESTORE_USER_ALL Send-Byte (revert RAM to last-NVM image)
|
||||
``clear_protection_last`` 0x08 CLEAR_LAST_FAULT Send-Byte
|
||||
``clear_protection_last_force`` -- gated CLEAR_LAST_FAULT, see below
|
||||
``on_off_config`` 0x02 (byte) PMBus vs CTRL-pin on/off source + active polarity
|
||||
``vout_margin_high`` 0x25 (word) margin-high VOUT setpoint
|
||||
``vout_margin_low`` 0x26 (word) margin-low VOUT setpoint
|
||||
``mfr_pmbus_lock`` 0xEE (word) programmable PMBus write-lock
|
||||
``mfr_product_rev_user`` 0xC2 (word) user-programmable product revision
|
||||
=============================== =============== =========================================================
|
||||
|
||||
``clear_protection_last`` writes ``CLEAR_LAST_FAULT`` (0x08) Send-Byte.
|
||||
The chip silently no-ops unless ``MFR_CFG_EXT`` (0xF5) bit[6] is set.
|
||||
|
||||
``clear_protection_last_force`` performs the unlock with the following
|
||||
six-step dancing:
|
||||
|
||||
1. read ``WRITE_PROTECT`` (0x10) and ``MFR_CFG_EXT`` (0xF5) for restore
|
||||
2. clear ``WRITE_PROTECT`` if set
|
||||
3. set ``MFR_CFG_EXT`` bit[6] = 1, preserving other bits
|
||||
4. send ``CLEAR_LAST_FAULT`` (0x08)
|
||||
5. restore ``MFR_CFG_EXT`` (with retry to handle the chip's
|
||||
undocumented post-NVM-write busy window)
|
||||
6. restore ``WRITE_PROTECT``
|
||||
|
||||
Devicetree
|
||||
----------
|
||||
|
||||
The driver uses ``compatible = "mps,mpq8646"``. There are some few optional
|
||||
properties:
|
||||
|
||||
- ``mps,vout-fb-divider-ratio-permille`` : it writes ``VOUT_SCALE_LOOP``
|
||||
(0x29) at probe to compensate for an external resistor divider in
|
||||
the VOUT feedback path. The valid range is 11-bit.
|
||||
- ``interrupts = <...>`` : if the board routes the chip's
|
||||
``SMBALERT#`` pin to a SoC GPIO, declaring it here lights up
|
||||
``pmbus_core::pmbus_irq_setup`` and disables the in-driver
|
||||
alarm-poll fallback
|
||||
@@ -80,6 +80,30 @@ Supported chips:
|
||||
|
||||
Datasheet: Available from Nuvoton upon request
|
||||
|
||||
* Nuvoton NCT6797D
|
||||
|
||||
Prefix: 'nct6797'
|
||||
|
||||
Addresses scanned: ISA address retrieved from Super I/O registers
|
||||
|
||||
Datasheet: Available from Nuvoton upon request
|
||||
|
||||
* Nuvoton NCT6798D
|
||||
|
||||
Prefix: 'nct6798'
|
||||
|
||||
Addresses scanned: ISA address retrieved from Super I/O registers
|
||||
|
||||
Datasheet: Available from Nuvoton upon request
|
||||
|
||||
* Nuvoton NCT5585D (compatible to NCT6798D)
|
||||
|
||||
Prefix: 'nct6798'
|
||||
|
||||
Addresses scanned: ISA address retrieved from Super I/O registers
|
||||
|
||||
Datasheet: Not available
|
||||
|
||||
* Nuvoton NCT6796D-S/NCT6799D-R
|
||||
|
||||
Prefix: 'nct6799'
|
||||
|
||||
@@ -13,6 +13,7 @@ Supported devices:
|
||||
* NZXT Kraken Z73
|
||||
* NZXT Kraken 2023
|
||||
* NZXT Kraken 2023 Elite
|
||||
* NZXT Kraken 2024 Elite
|
||||
|
||||
Author: Jonas Malaco, Aleksa Savic
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Authors:
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supprts hardware monitoring for onboard MCU with
|
||||
This driver supports hardware monitoring for onboard MCU with
|
||||
i2c interface.
|
||||
|
||||
Usage Notes
|
||||
|
||||
@@ -31,6 +31,14 @@ Supported chips:
|
||||
|
||||
Datasheet: https://sensirion.com/media/documents/4B40CEF3/640B2346/Sensirion_Humidity_Sensors_SHT85_Datasheet.pdf
|
||||
|
||||
* GXCAS GXHT30
|
||||
|
||||
Prefix: 'sht3x'
|
||||
|
||||
Addresses scanned: none
|
||||
|
||||
Datasheet: https://www.galaxy-cas.com/uploads/files/202509/GXHT30_datasheet_V3.9_20250919170350.pdf
|
||||
|
||||
Author:
|
||||
|
||||
- David Frey <david.frey@sensirion.com>
|
||||
@@ -40,9 +48,12 @@ Description
|
||||
-----------
|
||||
|
||||
This driver implements support for the Sensirion SHT3x-DIS, STS3x-DIS and SHT85
|
||||
series of humidity and temperature sensors. Temperature is measured in degrees
|
||||
celsius, relative humidity is expressed as a percentage. In the sysfs interface,
|
||||
all values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
|
||||
series of humidity and temperature sensors, as well as the compatible GXCAS
|
||||
GXHT30. The GXHT30 is a drop-in replacement for the SHT30: instantiate it from
|
||||
devicetree with the "gxcas,gxht30", "sensirion,sht30" compatibles, or manually
|
||||
using the sht3x device name. Temperature is measured in degrees celsius,
|
||||
relative humidity is expressed as a percentage. In the sysfs interface, all
|
||||
values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
|
||||
|
||||
The device communicates with the I2C protocol. SHT3x sensors can have the I2C
|
||||
addresses 0x44 or 0x45 (0x4a or 0x4b for sts3x), depending on the wiring. SHT85
|
||||
|
||||
96
Documentation/hwmon/sq24860.rst
Normal file
96
Documentation/hwmon/sq24860.rst
Normal file
@@ -0,0 +1,96 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver sq24860
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Silergy SQ24860
|
||||
|
||||
Prefix: 'sq24860'
|
||||
|
||||
Author:
|
||||
|
||||
Ziming Zhu <ziming.zhu@silergycorp.com>
|
||||
|
||||
Description
|
||||
------------
|
||||
|
||||
This driver implements support for the Silergy SQ24860 eFuse. The device is an
|
||||
integrated circuit protection and power management device with a PMBus
|
||||
interface.
|
||||
|
||||
The device supports direct format for reading input voltage, output voltage,
|
||||
auxiliary voltage, input current, input power, and temperature.
|
||||
|
||||
The current and power measurement scale depends on the resistor connected
|
||||
between the IMON pin and ground. The resistor value can be configured with the
|
||||
``silergy,rimon-micro-ohms`` device tree property. See
|
||||
``Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml`` for details.
|
||||
|
||||
Due to the specificities of the chip, all history reset attributes are tied
|
||||
together. Resetting the history of one sensor resets the history of all sensors.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Limits are read-write; all other
|
||||
attributes are read-only.
|
||||
|
||||
======================= ======================================================
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage.
|
||||
in1_average Average measured input voltage.
|
||||
in1_min Minimum input voltage limit.
|
||||
in1_lcrit Critical low input voltage limit.
|
||||
in1_max Maximum input voltage limit.
|
||||
in1_crit Critical high input voltage limit.
|
||||
in1_min_alarm Input voltage low warning alarm.
|
||||
in1_lcrit_alarm Input voltage low fault alarm.
|
||||
in1_max_alarm Input voltage high warning alarm.
|
||||
in1_crit_alarm Input voltage high fault alarm.
|
||||
in1_highest Historical maximum input voltage.
|
||||
in1_lowest Historical minimum input voltage.
|
||||
in1_reset_history Write any value to reset history.
|
||||
|
||||
in2_label "vmon"
|
||||
in2_input Measured auxiliary input voltage.
|
||||
|
||||
in3_label "vout1"
|
||||
in3_input Measured output voltage.
|
||||
in3_average Average measured output voltage.
|
||||
in3_min Minimum output voltage limit.
|
||||
in3_min_alarm Output voltage low alarm.
|
||||
in3_lowest Historical minimum output voltage.
|
||||
in3_reset_history Write any value to reset history.
|
||||
|
||||
curr1_label "iin"
|
||||
curr1_input Measured input current.
|
||||
curr1_average Average measured input current.
|
||||
curr1_max Maximum input current warning limit.
|
||||
curr1_crit Critical input over-current fault limit.
|
||||
curr1_max_alarm Input current warning alarm.
|
||||
curr1_crit_alarm Input over-current fault alarm.
|
||||
curr1_highest Historical maximum input current.
|
||||
curr1_reset_history Write any value to reset history.
|
||||
|
||||
power1_label "pin"
|
||||
power1_input Measured input power.
|
||||
power1_average Average measured input power.
|
||||
power1_max Maximum input power warning limit.
|
||||
power1_alarm Input power warning alarm.
|
||||
power1_input_highest Historical maximum input power.
|
||||
power1_reset_history Write any value to reset history.
|
||||
|
||||
temp1_input Measured temperature.
|
||||
temp1_average Average measured temperature.
|
||||
temp1_max Maximum temperature warning limit.
|
||||
temp1_crit Critical temperature fault limit.
|
||||
temp1_max_alarm Temperature warning alarm.
|
||||
temp1_crit_alarm Temperature fault alarm.
|
||||
temp1_highest Historical maximum temperature.
|
||||
temp1_reset_history Write any value to reset history.
|
||||
|
||||
samples Number of samples used for average values.
|
||||
======================= ======================================================
|
||||
|
||||
53
Documentation/hwmon/tvs-mpfs.rst
Normal file
53
Documentation/hwmon/tvs-mpfs.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver tvs-mpfs
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* PolarFire SoC
|
||||
|
||||
Authors:
|
||||
|
||||
- Conor Dooley <conor.dooley@microchip.com>
|
||||
- Lars Randers <lranders@mail.dk>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for the temperature and voltage sensors on
|
||||
PolarFire SoC. The temperature reports how hot the die is, and the voltages are
|
||||
the SoC's 1.05, 1.8 and 2.5 volt rails respectively.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
update_interval has a permitted range of 0 to 8 milliseconds.
|
||||
|
||||
Temperatures are read in millidegrees Celsius, but the hardware measures in
|
||||
degrees Kelvin, storing the result as 11.4 fixed point data, for a maximum
|
||||
value of 2047.9375 degrees Kelvin.
|
||||
|
||||
Voltages are read in millivolts. The hardware measures in millivolts, storing
|
||||
the value as 12.3 fixed point data, for a maximum of 4095.875 millivolts.
|
||||
The minimum value reportable by the driver is 0 volts, although the hardware
|
||||
is capable of measuring negative values.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. update_interval is read-write, as are
|
||||
the enables. All other attributes are read only.
|
||||
|
||||
======================= ====================================================
|
||||
temp1_label Fixed name for channel.
|
||||
temp1_input Measured temperature for channel.
|
||||
temp1_enable Enable/disable for channel.
|
||||
|
||||
in[0-2]_label Fixed name for channel.
|
||||
in[0-2]_input Measured voltage for channel.
|
||||
in[0-2]_enable Enable/disable for channel.
|
||||
|
||||
update_interval The interval at which the chip will update readings.
|
||||
======================= ====================================================
|
||||
89
Documentation/hwmon/vt7505.rst
Normal file
89
Documentation/hwmon/vt7505.rst
Normal file
@@ -0,0 +1,89 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver vt7505
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Analog Devices MAX16545
|
||||
|
||||
Prefix: 'max16545'
|
||||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16545b-max16545c.pdf
|
||||
|
||||
* Analog Devices MAX16550
|
||||
|
||||
Prefix: 'max16550'
|
||||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max16550.pdf
|
||||
|
||||
* Volterra VT7505
|
||||
|
||||
Prefix: 'vt7505'
|
||||
|
||||
Datasheet: Not publicly available; register-compatible with the MAX16545.
|
||||
|
||||
Author: Georgi Vlaev <gvlaev@juniper.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for the Analog Devices MAX16545 and
|
||||
MAX16550, and the Volterra VT7505 PMBus controllers.
|
||||
|
||||
The devices report input and output voltage, input and output current,
|
||||
input power, and temperature. All values use the PMBus direct data format.
|
||||
Peak (maximum) values for input voltage, output voltage, output current,
|
||||
input power and temperature are exposed and can be reset. Writing to any
|
||||
``*_reset_history`` attribute clears all of the device's peak values.
|
||||
|
||||
The reported current and power values depend on the resistance of the
|
||||
external current-report resistor connected between the ILOAD pin and
|
||||
ground. This resistance can be provided via the ``adi,rload-ohms``
|
||||
device tree property; a default of 4750 ohms is used when it is absent.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The limit and alarm attributes are created by the PMBus core for the limit
|
||||
registers that the device implements. See
|
||||
Documentation/hwmon/sysfs-interface.rst for the meaning of each attribute.
|
||||
|
||||
======================= ========================================================
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage.
|
||||
in1_min Input undervoltage warning limit.
|
||||
in1_min_alarm Input undervoltage warning alarm.
|
||||
in1_lcrit Input undervoltage fault limit.
|
||||
in1_lcrit_alarm Input undervoltage fault alarm.
|
||||
in1_highest Historical maximum input voltage.
|
||||
in1_reset_history Write to reset all peak values.
|
||||
in2_label "vout1"
|
||||
in2_input Measured output voltage.
|
||||
in2_min Output undervoltage warning limit.
|
||||
in2_min_alarm Output undervoltage warning alarm.
|
||||
in2_highest Historical maximum output voltage.
|
||||
in2_reset_history Write to reset all peak values.
|
||||
curr1_label "iin"
|
||||
curr1_input Measured input current.
|
||||
curr1_alarm Input current alarm.
|
||||
curr2_label "iout1"
|
||||
curr2_input Measured output current.
|
||||
curr2_max Output overcurrent warning limit.
|
||||
curr2_max_alarm Output overcurrent warning alarm.
|
||||
curr2_highest Historical maximum output current.
|
||||
curr2_reset_history Write to reset all peak values.
|
||||
power1_label "pin"
|
||||
power1_input Measured input power.
|
||||
power1_max Input overpower warning limit.
|
||||
power1_alarm Input overpower warning alarm.
|
||||
power1_input_highest Historical maximum input power.
|
||||
power1_reset_history Write to reset all peak values.
|
||||
temp1_input Measured temperature.
|
||||
temp1_max Overtemperature warning limit.
|
||||
temp1_max_alarm Overtemperature warning alarm.
|
||||
temp1_crit Overtemperature fault limit.
|
||||
temp1_crit_alarm Overtemperature fault alarm.
|
||||
temp1_highest Historical maximum temperature.
|
||||
temp1_reset_history Write to reset all peak values.
|
||||
======================= ========================================================
|
||||
@@ -99,6 +99,11 @@ immediately to ensure the user knows the fan has stopped.
|
||||
82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1
|
||||
83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
|
||||
81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
|
||||
83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
|
||||
82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
|
||||
83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
|
||||
83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100
|
||||
83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100
|
||||
*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -117,7 +122,6 @@ METHODOLOGY & IDENTIFICATION:
|
||||
- 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
|
||||
----------
|
||||
|
||||
@@ -136,3 +140,24 @@ References
|
||||
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/lenovo/ideapad-laptop.c
|
||||
|
||||
5. **Lenovo Product Specifications Reference (PSREF):** Official hardware layout index
|
||||
and spec sheets for active and withdrawn Lenovo laptop models.
|
||||
https://psref.lenovo.com/l/withdrawn/
|
||||
|
||||
6. **Yogafan Master Quirk Database:** Master spreadsheet mapping Lenovo Product
|
||||
Specifications Reference (PSREF) to explicit EC offsets, register widths, paths, and multipliers.
|
||||
https://github.com/sergiomelas/lenovo-linux-drivers/blob/main/Lenovo_Drivers/Prototype/PSREF/yogafan_v3_quirks_database.ods
|
||||
|
||||
7. **Yogafan ACPI DSDT Repository:** Central repository containing user-contributed raw
|
||||
and decompiled ACPI DSDT firmware dumps used for path verification and hardware expansions.
|
||||
https://github.com/sergiomelas/lenovo-linux-drivers/tree/main/Lenovo_Drivers/Prototype/DSDT
|
||||
|
||||
Contributors & DSDT Providers:
|
||||
------------------------------
|
||||
- **Sarbajit Sarkar** (Lenovo LOQ 15IAX9)
|
||||
- **HinataKato** (XiaoXin Pro 13ARE 2020 - 83KF)
|
||||
- **PenPenIsGod** (IdeaPad 3 15ALC6 - 82KU & Legion Pro 7 16AFR10H - 83RU)
|
||||
- **unlockxiaom** (Legion Pro 7 16AFR10H - 83RU, Yoga Pro 7 14IAH10 - 83KF, ThinkCentre M80q)
|
||||
- **Phani Pavan K** (Yoga Pro 7 14IAH10 - 83KF)
|
||||
- **Splarkszter** (Yoga 7 16ARP8 - 83BS)
|
||||
|
||||
30
MAINTAINERS
30
MAINTAINERS
@@ -13919,6 +13919,13 @@ S: Maintained
|
||||
F: Documentation/hwmon/k8temp.rst
|
||||
F: drivers/hwmon/k8temp.c
|
||||
|
||||
KANDOU KB9002 PCIE RETIMER HWMON DRIVER
|
||||
M: Andy Chung <andy.chung@amd.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/kb9002.rst
|
||||
F: drivers/hwmon/kb9002.c
|
||||
|
||||
KASAN
|
||||
M: Andrey Ryabinin <ryabinin.a.a@gmail.com>
|
||||
R: Alexander Potapenko <glider@google.com>
|
||||
@@ -18315,6 +18322,20 @@ S: Maintained
|
||||
F: Documentation/hwmon/mp9945.rst
|
||||
F: drivers/hwmon/pmbus/mp9945.c
|
||||
|
||||
MPS MPQ82D00 DRIVER
|
||||
M: Wensheng Wang <wenswang@yeah.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/mpq82d00.rst
|
||||
F: drivers/hwmon/pmbus/mpq82d00.c
|
||||
|
||||
MPS MPQ8646 PMBUS DRIVER
|
||||
M: Vincent Jardin <vjardin@free.fr>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/mpq8646.rst
|
||||
F: drivers/hwmon/pmbus/mpq8646.c
|
||||
|
||||
MR800 AVERMEDIA USB FM RADIO DRIVER
|
||||
M: Alexey Klimov <alexey.klimov@linaro.org>
|
||||
L: linux-media@vger.kernel.org
|
||||
@@ -23256,6 +23277,7 @@ F: drivers/char/hw_random/mpfs-rng.c
|
||||
F: drivers/clk/microchip/clk-mpfs*.c
|
||||
F: drivers/firmware/microchip/mpfs-auto-update.c
|
||||
F: drivers/gpio/gpio-mpfs.c
|
||||
F: drivers/hwmon/tvs-mpfs.c
|
||||
F: drivers/i2c/busses/i2c-microchip-corei2c.c
|
||||
F: drivers/mailbox/mailbox-mpfs.c
|
||||
F: drivers/pci/controller/plda/pcie-microchip-host.c
|
||||
@@ -28950,6 +28972,14 @@ S: Maintained
|
||||
F: Documentation/hwmon/vt1211.rst
|
||||
F: drivers/hwmon/vt1211.c
|
||||
|
||||
VT7505 HARDWARE MONITOR DRIVER
|
||||
M: Sanman Pradhan <psanman@juniper.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/hwmon/pmbus/adi,vt7505.yaml
|
||||
F: Documentation/hwmon/vt7505.rst
|
||||
F: drivers/hwmon/pmbus/vt7505.c
|
||||
|
||||
VT8231 HARDWARE MONITOR DRIVER
|
||||
M: Roger Lucas <vt8231@hiddenengine.co.uk>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
||||
@@ -224,6 +224,7 @@ config SENSORS_ADT7462
|
||||
config SENSORS_ADT7470
|
||||
tristate "Analog Devices ADT7470"
|
||||
depends on I2C
|
||||
depends on PWM || PWM=n
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for the Analog Devices
|
||||
@@ -292,11 +293,11 @@ config SENSORS_ASC7621
|
||||
will be called asc7621.
|
||||
|
||||
config SENSORS_ASUS_ROG_RYUJIN
|
||||
tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver"
|
||||
tristate "ASUS ROG RYUJIN hardware monitoring driver"
|
||||
depends on HID
|
||||
help
|
||||
If you say yes here you get support for the fans and sensors of
|
||||
the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler.
|
||||
supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called asus_rog_ryujin.
|
||||
@@ -335,6 +336,17 @@ config SENSORS_K10TEMP
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called k10temp.
|
||||
|
||||
config SENSORS_KB9002
|
||||
tristate "Kandou KB9002 PCIe retimer"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the integrated
|
||||
temperature sensor and firmware version readout on Kandou
|
||||
KB9002 PCIe 5.0 retimers, accessed over SMBus.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called kb9002.
|
||||
|
||||
config SENSORS_KBATT
|
||||
tristate "KEBA battery controller support"
|
||||
depends on KEBA_CP500
|
||||
@@ -403,7 +415,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER
|
||||
config SENSORS_ARM_SCMI
|
||||
tristate "ARM SCMI Sensors"
|
||||
depends on ARM_SCMI_PROTOCOL
|
||||
depends on THERMAL || !THERMAL_OF
|
||||
help
|
||||
This driver provides support for temperature, voltage, current
|
||||
and power sensors available on SCMI based platforms. The actual
|
||||
@@ -415,7 +426,6 @@ config SENSORS_ARM_SCMI
|
||||
config SENSORS_ARM_SCPI
|
||||
tristate "ARM SCPI Sensors"
|
||||
depends on ARM_SCPI_PROTOCOL
|
||||
depends on THERMAL || !THERMAL_OF
|
||||
help
|
||||
This driver provides support for temperature, voltage, current
|
||||
and power sensors available on ARM Ltd's SCP based platforms. The
|
||||
@@ -435,7 +445,6 @@ config SENSORS_ASB100
|
||||
config SENSORS_ASPEED
|
||||
tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on THERMAL || THERMAL=n
|
||||
select REGMAP
|
||||
help
|
||||
This driver provides support for ASPEED AST2400/AST2500 PWM
|
||||
@@ -506,8 +515,8 @@ config SENSORS_CORSAIR_PSU
|
||||
If you say yes here you get support for Corsair PSUs with a HID
|
||||
interface.
|
||||
Currently this driver supports the (RM/HX)550i, (RM/HX)650i,
|
||||
(RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies
|
||||
by Corsair.
|
||||
(RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power
|
||||
supplies by Corsair.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called corsair-psu.
|
||||
@@ -788,7 +797,6 @@ config SENSORS_G762
|
||||
config SENSORS_GPIO_FAN
|
||||
tristate "GPIO fan"
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
If you say yes here you get support for fans connected to GPIO lines.
|
||||
|
||||
@@ -802,7 +810,7 @@ config SENSORS_GXP_FAN_CTRL
|
||||
If you say yes here you get support for GXP fan control functionality.
|
||||
|
||||
The GXP controls fan function via the CPLD through the use of PWM
|
||||
registers. This driver reports status and pwm setting of the fans.
|
||||
registers. This driver enables pwm setting of the fans.
|
||||
|
||||
config SENSORS_HIH6130
|
||||
tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor"
|
||||
@@ -942,6 +950,19 @@ config SENSORS_JC42
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called jc42.
|
||||
|
||||
config SENSORS_POLARFIRE_SOC_TVS
|
||||
tristate "PolarFire SoC (MPFS) temperature and voltage sensor"
|
||||
depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST
|
||||
depends on MFD_SYSCON
|
||||
help
|
||||
This driver adds support for the PolarFire SoC (MPFS) Temperature and
|
||||
Voltage Sensor.
|
||||
|
||||
To compile this driver as a module, choose M here. the
|
||||
module will be called tvs-mpfs.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config SENSORS_POWERZ
|
||||
tristate "ChargerLAB POWER-Z USB-C tester"
|
||||
depends on USB
|
||||
@@ -1357,7 +1378,6 @@ config SENSORS_MAX6639
|
||||
config SENSORS_MAX6650
|
||||
tristate "Maxim MAX6650 sensor chip"
|
||||
depends on I2C
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
If you say yes here you get support for the MAX6650 / MAX6651
|
||||
sensor chips.
|
||||
@@ -1732,7 +1752,6 @@ config SENSORS_PC87427
|
||||
config SENSORS_NTC_THERMISTOR
|
||||
tristate "NTC thermistor support"
|
||||
depends on IIO
|
||||
depends on THERMAL || !THERMAL_OF
|
||||
help
|
||||
This driver supports NTC thermistors sensor reading and its
|
||||
interpretation. The driver can also monitor the temperature and
|
||||
@@ -1933,7 +1952,6 @@ config SENSORS_PT5161L
|
||||
config SENSORS_PWM_FAN
|
||||
tristate "PWM fan"
|
||||
depends on PWM || COMPILE_TEST
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
If you say yes here you get support for fans connected to PWM lines.
|
||||
The driver uses the generic PWM interface, thus it will work on a
|
||||
@@ -1945,7 +1963,6 @@ config SENSORS_PWM_FAN
|
||||
config SENSORS_QNAP_MCU_HWMON
|
||||
tristate "QNAP MCU hardware monitoring"
|
||||
depends on MFD_QNAP_MCU
|
||||
depends on THERMAL || THERMAL=n
|
||||
help
|
||||
Say yes here to enable support for fan and temperature sensor
|
||||
connected to a QNAP MCU, as found in a number of QNAP network
|
||||
@@ -2010,8 +2027,9 @@ config SENSORS_SHT3x
|
||||
depends on I2C
|
||||
select CRC8
|
||||
help
|
||||
If you say yes here you get support for the Sensiron SHT30, SHT31 and
|
||||
SHT85 humidity and temperature sensors.
|
||||
If you say yes here you get support for the Sensiron SHT30, SHT31,
|
||||
SHT85 and the compatible GXCAS GXHT30 humidity and temperature
|
||||
sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sht3x.
|
||||
@@ -2070,6 +2088,17 @@ config SENSORS_DME1737
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called dme1737.
|
||||
|
||||
config SENSORS_EIC7700_PVT
|
||||
tristate "Eswin EIC7700 Voltage, Temperature sensor driver"
|
||||
depends on ARCH_ESWIN || COMPILE_TEST
|
||||
select POLYNOMIAL
|
||||
help
|
||||
If you say yes here you get support for Eswin EIC7700 PVT sensor
|
||||
embedded into the SoC.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called eic7700-pvt.
|
||||
|
||||
config SENSORS_EMC1403
|
||||
tristate "SMSC EMC1403/23 thermal sensor"
|
||||
depends on I2C
|
||||
|
||||
@@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
|
||||
obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o
|
||||
obj-$(CONFIG_SENSORS_DS620) += ds620.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o
|
||||
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
|
||||
obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o
|
||||
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
|
||||
@@ -112,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o
|
||||
obj-$(CONFIG_SENSORS_JC42) += jc42.o
|
||||
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
|
||||
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
|
||||
obj-$(CONFIG_SENSORS_KB9002) += kb9002.o
|
||||
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
|
||||
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
|
||||
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
|
||||
@@ -197,6 +199,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
|
||||
obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o
|
||||
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
|
||||
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
|
||||
obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define ACPI_POWER_METER_NAME "power_meter"
|
||||
#define ACPI_POWER_METER_DEVICE_NAME "Power Meter"
|
||||
#define ACPI_POWER_METER_CLASS "pwr_meter_resource"
|
||||
|
||||
#define NUM_SENSORS 17
|
||||
|
||||
@@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data)
|
||||
break;
|
||||
}
|
||||
|
||||
acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS,
|
||||
acpi_bus_generate_netlink_event("pwr_meter_resource",
|
||||
dev_name(&resource->acpi_dev->dev),
|
||||
event, 0);
|
||||
}
|
||||
@@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev)
|
||||
resource->sensors_valid = 0;
|
||||
resource->acpi_dev = device;
|
||||
mutex_init(&resource->lock);
|
||||
strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
|
||||
|
||||
platform_set_drvdata(pdev, resource);
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/util_macros.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
||||
@@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
|
||||
#define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2))
|
||||
|
||||
#define ADT7470_PWM_COUNT 4
|
||||
#define ADT7470_PWM_MAX 255
|
||||
#define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x))
|
||||
#define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x))
|
||||
#define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x))
|
||||
@@ -163,6 +167,7 @@ struct adt7470_data {
|
||||
char limits_valid;
|
||||
unsigned long sensors_last_updated; /* In jiffies */
|
||||
unsigned long limits_last_updated; /* In jiffies */
|
||||
bool use_pwm_framework;
|
||||
|
||||
int num_temp_sensors; /* -1 = probe */
|
||||
int temperatures_probed;
|
||||
@@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
val = clamp_val(val, 0, 255);
|
||||
val = clamp_val(val, 0, ADT7470_PWM_MAX);
|
||||
mutex_lock(&data->lock);
|
||||
err = regmap_write(data->regmap, ADT7470_REG_PWM(channel),
|
||||
val);
|
||||
@@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val
|
||||
return err;
|
||||
}
|
||||
|
||||
struct adt7470_pwm_wfhw {
|
||||
u8 val;
|
||||
};
|
||||
|
||||
static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const struct pwm_waveform *wf,
|
||||
void *_wfhw)
|
||||
{
|
||||
struct adt7470_data *data = pwmchip_get_drvdata(chip);
|
||||
struct adt7470_pwm_wfhw *wfhw = _wfhw;
|
||||
u64 actual_period;
|
||||
|
||||
if (wf->duty_length_ns == 0) {
|
||||
wfhw->val = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The PWM frequency (period) is a single chip-wide setting shared by
|
||||
* all 4 channels, so it cannot be changed on a per-pwm_device basis
|
||||
* through this API. The duty cycle is rounded against the currently
|
||||
* configured hardware period rather than the period requested in
|
||||
* @wf; round_waveform_fromhw() reports the actual resulting
|
||||
* waveform back so the core/consumer can detect a mismatch.
|
||||
*/
|
||||
actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq);
|
||||
|
||||
if (actual_period > wf->period_length_ns)
|
||||
/* period too short */
|
||||
return 1;
|
||||
|
||||
if (wf->duty_length_ns >= actual_period) {
|
||||
wfhw->val = ADT7470_PWM_MAX;
|
||||
} else {
|
||||
wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns,
|
||||
ADT7470_PWM_MAX,
|
||||
actual_period);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const void *_wfhw,
|
||||
struct pwm_waveform *wf)
|
||||
{
|
||||
struct adt7470_data *data = pwmchip_get_drvdata(chip);
|
||||
const struct adt7470_pwm_wfhw *wfhw = _wfhw;
|
||||
|
||||
wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq);
|
||||
wf->duty_offset_ns = 0;
|
||||
wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns,
|
||||
ADT7470_PWM_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adt7470_pwm_read_waveform(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
void *_wfhw)
|
||||
{
|
||||
struct adt7470_data *data = pwmchip_get_drvdata(chip);
|
||||
struct device *dev = regmap_get_device(data->regmap);
|
||||
struct adt7470_pwm_wfhw *wfhw = _wfhw;
|
||||
|
||||
data = adt7470_update_device(dev);
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
/*
|
||||
* No lock needed: like the other hwmon_ops read callbacks in this
|
||||
* driver (e.g. adt7470_pwm_read()), this only does a single byte
|
||||
* read from the cache populated by adt7470_update_device().
|
||||
*/
|
||||
wfhw->val = data->pwm[pwm->hwpwm];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adt7470_pwm_write_waveform(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const void *_wfhw)
|
||||
{
|
||||
struct adt7470_data *data = pwmchip_get_drvdata(chip);
|
||||
const struct adt7470_pwm_wfhw *wfhw = _wfhw;
|
||||
unsigned int pwm_auto_reg_mask;
|
||||
int err;
|
||||
|
||||
if (pwm->hwpwm % 2)
|
||||
pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK;
|
||||
else
|
||||
pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;
|
||||
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
if (data->pwm[pwm->hwpwm] == wfhw->val &&
|
||||
data->pwm_automatic[pwm->hwpwm] == 0)
|
||||
return 0;
|
||||
|
||||
/* Put the PWM channel in manual mode before updating it. */
|
||||
err = regmap_update_bits(data->regmap,
|
||||
ADT7470_REG_PWM_CFG(pwm->hwpwm),
|
||||
pwm_auto_reg_mask, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data->pwm_automatic[pwm->hwpwm] = 0;
|
||||
|
||||
err = regmap_write(data->regmap,
|
||||
ADT7470_REG_PWM(pwm->hwpwm), wfhw->val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data->pwm[pwm->hwpwm] = wfhw->val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops adt7470_pwm_ops = {
|
||||
.sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw),
|
||||
.round_waveform_tohw = adt7470_pwm_round_waveform_tohw,
|
||||
.round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw,
|
||||
.read_waveform = adt7470_pwm_read_waveform,
|
||||
.write_waveform = adt7470_pwm_write_waveform,
|
||||
};
|
||||
|
||||
static ssize_t pwm_max_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
@@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev,
|
||||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
temp = clamp_val(temp, 0, ADT7470_PWM_MAX);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->pwm_max[attr->index] = temp;
|
||||
@@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev,
|
||||
if (kstrtol(buf, 10, &temp))
|
||||
return -EINVAL;
|
||||
|
||||
temp = clamp_val(temp, 0, 255);
|
||||
temp = clamp_val(temp, 0, ADT7470_PWM_MAX);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->pwm_min[attr->index] = temp;
|
||||
@@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = {
|
||||
&dev_attr_alarm_mask.attr,
|
||||
&dev_attr_num_temp_sensors.attr,
|
||||
&dev_attr_auto_update_interval.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *adt7470_pwm_attrs[] = {
|
||||
&sensor_dev_attr_force_pwm_max.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
|
||||
@@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(adt7470);
|
||||
static const struct attribute_group adt7470_group = {
|
||||
.attrs = adt7470_attrs,
|
||||
};
|
||||
|
||||
static umode_t adt7470_pwm_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int index)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct adt7470_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->use_pwm_framework)
|
||||
return 0;
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
static const struct attribute_group adt7470_pwm_group = {
|
||||
.attrs = adt7470_pwm_attrs,
|
||||
.is_visible = adt7470_pwm_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group *adt7470_groups[] = {
|
||||
&adt7470_group,
|
||||
&adt7470_pwm_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long *val)
|
||||
@@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a
|
||||
static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct adt7470_data *data = _data;
|
||||
umode_t mode = 0;
|
||||
|
||||
switch (type) {
|
||||
@@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
/* Hide all pwm attributes if this device is exposed to the PWM
|
||||
* framework
|
||||
*/
|
||||
if (data->use_pwm_framework) {
|
||||
mode = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
case hwmon_pwm_enable:
|
||||
@@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = {
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info * const adt7470_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
|
||||
@@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client)
|
||||
|
||||
data->pwm_freq = (u32)freq_val;
|
||||
|
||||
data->use_pwm_framework = false;
|
||||
|
||||
if (device_property_present(dev, "#pwm-cells")) {
|
||||
if (IS_REACHABLE(CONFIG_PWM)) {
|
||||
struct pwm_chip *chip;
|
||||
|
||||
chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0);
|
||||
if (IS_ERR(chip))
|
||||
return PTR_ERR(chip);
|
||||
|
||||
chip->ops = &adt7470_pwm_ops;
|
||||
pwmchip_set_drvdata(chip, data);
|
||||
|
||||
err = devm_pwmchip_add(dev, chip);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "failed to register PWM chip\n");
|
||||
|
||||
data->use_pwm_framework = true;
|
||||
} else {
|
||||
dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Register sysfs hooks */
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&adt7470_chip_info,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/bits.h>
|
||||
#include <asm/barrier.h>
|
||||
|
||||
/* data port used by Apple SMC */
|
||||
#define APPLESMC_DATA_PORT 0x300
|
||||
@@ -133,6 +134,7 @@ static struct applesmc_registers {
|
||||
bool init_complete; /* true when fully initialized */
|
||||
struct applesmc_entry *cache; /* cached key entries */
|
||||
const char **index; /* temperature key index */
|
||||
char fan_positions[10][17]; /* cached fan position labels */
|
||||
} smcreg = {
|
||||
.mutex = __MUTEX_INITIALIZER(smcreg.mutex),
|
||||
};
|
||||
@@ -143,8 +145,8 @@ static s16 rest_x;
|
||||
static s16 rest_y;
|
||||
static u8 backlight_state[2];
|
||||
|
||||
static struct device *hwmon_dev;
|
||||
static struct input_dev *applesmc_idev;
|
||||
static struct device *hwmon_dev;
|
||||
|
||||
/*
|
||||
* Last index written to key_at_index sysfs file, and value to use for all other
|
||||
@@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index)
|
||||
__be32 be;
|
||||
int ret = 0;
|
||||
|
||||
if (cache->valid)
|
||||
/* Pairs with smp_store_release() to ensure cache contents are visible */
|
||||
if (smp_load_acquire(&cache->valid))
|
||||
return cache;
|
||||
|
||||
mutex_lock(&smcreg.mutex);
|
||||
@@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index)
|
||||
cache->len = info[0];
|
||||
memcpy(cache->type, &info[1], 4);
|
||||
cache->flags = info[5];
|
||||
cache->valid = true;
|
||||
/* Pairs with smp_load_acquire() to commit cache contents before setting valid */
|
||||
smp_store_release(&cache->valid, true);
|
||||
|
||||
out:
|
||||
mutex_unlock(&smcreg.mutex);
|
||||
@@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void)
|
||||
{
|
||||
struct applesmc_registers *s = &smcreg;
|
||||
bool left_light_sensor = false, right_light_sensor = false;
|
||||
unsigned int count;
|
||||
unsigned int count, i;
|
||||
u8 tmp[1];
|
||||
int ret;
|
||||
|
||||
@@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void)
|
||||
if (s->fan_count > 10)
|
||||
s->fan_count = 10;
|
||||
|
||||
for (i = 0; i < s->fan_count; i++) {
|
||||
char newkey[5];
|
||||
|
||||
scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i);
|
||||
ret = applesmc_read_key(newkey, s->fan_positions[i], 16);
|
||||
s->fan_positions[i][16] = 0;
|
||||
if (ret)
|
||||
scnprintf(s->fan_positions[i], 17, " Fan %d", i);
|
||||
}
|
||||
|
||||
ret = applesmc_get_lower_bound(&s->temp_begin, "T");
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev,
|
||||
return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right);
|
||||
}
|
||||
|
||||
/* Displays sensor key as label */
|
||||
static ssize_t applesmc_show_sensor_label(struct device *dev,
|
||||
struct device_attribute *devattr, char *sysfsbuf)
|
||||
{
|
||||
const char *key = smcreg.index[to_index(devattr)];
|
||||
|
||||
return sysfs_emit(sysfsbuf, "%s\n", key);
|
||||
}
|
||||
|
||||
/* Displays degree Celsius * 1000 */
|
||||
static ssize_t applesmc_show_temperature(struct device *dev,
|
||||
struct device_attribute *devattr, char *sysfsbuf)
|
||||
{
|
||||
const char *key = smcreg.index[to_index(devattr)];
|
||||
int ret;
|
||||
s16 value;
|
||||
int temp;
|
||||
|
||||
ret = applesmc_read_s16(key, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
temp = 250 * (value >> 6);
|
||||
|
||||
return sysfs_emit(sysfsbuf, "%d\n", temp);
|
||||
}
|
||||
|
||||
static ssize_t applesmc_show_fan_speed(struct device *dev,
|
||||
struct device_attribute *attr, char *sysfsbuf)
|
||||
{
|
||||
@@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev,
|
||||
return sysfs_emit(sysfsbuf, "%u\n", speed);
|
||||
}
|
||||
|
||||
static ssize_t applesmc_store_fan_speed(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *sysfsbuf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
unsigned long speed;
|
||||
char newkey[5];
|
||||
u8 buffer[2];
|
||||
|
||||
if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000)
|
||||
return -EINVAL; /* Bigger than a 14-bit value */
|
||||
|
||||
scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)],
|
||||
to_index(attr));
|
||||
|
||||
buffer[0] = (speed >> 6) & 0xff;
|
||||
buffer[1] = (speed << 2) & 0xff;
|
||||
ret = applesmc_write_key(newkey, buffer, 2);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
else
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t applesmc_show_fan_manual(struct device *dev,
|
||||
struct device_attribute *attr, char *sysfsbuf)
|
||||
{
|
||||
int ret;
|
||||
u16 manual = 0;
|
||||
u8 buffer[2];
|
||||
|
||||
ret = applesmc_read_key(FANS_MANUAL, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01;
|
||||
return sysfs_emit(sysfsbuf, "%d\n", manual);
|
||||
}
|
||||
|
||||
static ssize_t applesmc_store_fan_manual(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *sysfsbuf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
u8 buffer[2];
|
||||
unsigned long input;
|
||||
u16 val;
|
||||
|
||||
if (kstrtoul(sysfsbuf, 10, &input) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = applesmc_read_key(FANS_MANUAL, buffer, 2);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
val = (buffer[0] << 8 | buffer[1]);
|
||||
|
||||
if (input)
|
||||
val = val | (0x01 << to_index(attr));
|
||||
else
|
||||
val = val & ~(0x01 << to_index(attr));
|
||||
|
||||
buffer[0] = (val >> 8) & 0xFF;
|
||||
buffer[1] = val & 0xFF;
|
||||
|
||||
ret = applesmc_write_key(FANS_MANUAL, buffer, 2);
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
return ret;
|
||||
else
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t applesmc_show_fan_position(struct device *dev,
|
||||
struct device_attribute *attr, char *sysfsbuf)
|
||||
{
|
||||
int ret;
|
||||
char newkey[5];
|
||||
u8 buffer[17];
|
||||
|
||||
scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr));
|
||||
|
||||
ret = applesmc_read_key(newkey, buffer, 16);
|
||||
buffer[16] = 0;
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(sysfsbuf, "%s\n", buffer + 4);
|
||||
}
|
||||
|
||||
static ssize_t applesmc_calibrate_show(struct device *dev,
|
||||
struct device_attribute *attr, char *sysfsbuf)
|
||||
{
|
||||
@@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = {
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct applesmc_node_group fan_group[] = {
|
||||
{ "fan%d_label", applesmc_show_fan_position },
|
||||
{ "fan%d_input", applesmc_show_fan_speed, NULL, 0 },
|
||||
{ "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 },
|
||||
{ "fan%d_max", applesmc_show_fan_speed, NULL, 2 },
|
||||
{ "fan%d_safe", applesmc_show_fan_speed, NULL, 3 },
|
||||
{ "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 },
|
||||
{ "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct applesmc_node_group temp_group[] = {
|
||||
{ "temp%d_label", applesmc_show_sensor_label },
|
||||
{ "temp%d_input", applesmc_show_temperature },
|
||||
{ }
|
||||
};
|
||||
|
||||
/* Module stuff */
|
||||
|
||||
@@ -1307,8 +1186,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, applesmc_whitelist);
|
||||
|
||||
static struct applesmc_dev_attr *fan_safe_attrs;
|
||||
static struct attribute **fan_safe_attr_list;
|
||||
static struct attribute_group fan_safe_group;
|
||||
static const struct attribute_group *applesmc_extra_groups[2];
|
||||
|
||||
static u32 *applesmc_temp_config;
|
||||
static u32 *applesmc_fan_config;
|
||||
static u32 *applesmc_pwm_config;
|
||||
static struct hwmon_channel_info *applesmc_info_temp;
|
||||
static struct hwmon_channel_info *applesmc_info_fan;
|
||||
static struct hwmon_channel_info *applesmc_info_pwm;
|
||||
static const struct hwmon_channel_info **applesmc_info_arr;
|
||||
static struct hwmon_chip_info *applesmc_chip;
|
||||
|
||||
static void applesmc_free_hwmon(void)
|
||||
{
|
||||
kfree(applesmc_temp_config);
|
||||
kfree(applesmc_fan_config);
|
||||
kfree(applesmc_pwm_config);
|
||||
kfree(applesmc_info_temp);
|
||||
kfree(applesmc_info_fan);
|
||||
kfree(applesmc_info_pwm);
|
||||
kfree(applesmc_info_arr);
|
||||
kfree(applesmc_chip);
|
||||
kfree(fan_safe_attrs);
|
||||
kfree(fan_safe_attr_list);
|
||||
}
|
||||
|
||||
static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (attr == hwmon_temp_input || attr == hwmon_temp_label)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
case hwmon_fan_label:
|
||||
case hwmon_fan_max:
|
||||
return 0444;
|
||||
case hwmon_fan_min:
|
||||
case hwmon_fan_target:
|
||||
return 0644;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_enable)
|
||||
return 0644;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (attr == hwmon_temp_input) {
|
||||
const char *key = smcreg.index[channel];
|
||||
s16 value;
|
||||
|
||||
ret = applesmc_read_s16(key, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = 250 * (value >> 6);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input: {
|
||||
char key[5];
|
||||
u8 buffer[2];
|
||||
|
||||
scnprintf(key, sizeof(key), "F%dAc", channel);
|
||||
ret = applesmc_read_key(key, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = ((buffer[0] << 8 | buffer[1]) >> 2);
|
||||
return 0;
|
||||
}
|
||||
case hwmon_fan_min: {
|
||||
char key[5];
|
||||
u8 buffer[2];
|
||||
|
||||
scnprintf(key, sizeof(key), "F%dMn", channel);
|
||||
ret = applesmc_read_key(key, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = ((buffer[0] << 8 | buffer[1]) >> 2);
|
||||
return 0;
|
||||
}
|
||||
case hwmon_fan_max: {
|
||||
char key[5];
|
||||
u8 buffer[2];
|
||||
|
||||
scnprintf(key, sizeof(key), "F%dMx", channel);
|
||||
ret = applesmc_read_key(key, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = ((buffer[0] << 8 | buffer[1]) >> 2);
|
||||
return 0;
|
||||
}
|
||||
case hwmon_fan_target: {
|
||||
char key[5];
|
||||
u8 buffer[2];
|
||||
|
||||
scnprintf(key, sizeof(key), "F%dTg", channel);
|
||||
ret = applesmc_read_key(key, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = ((buffer[0] << 8 | buffer[1]) >> 2);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_enable) {
|
||||
u8 buffer[2];
|
||||
|
||||
ret = applesmc_read_key(FANS_MANUAL, buffer, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
if (attr == hwmon_fan_min || attr == hwmon_fan_target) {
|
||||
char key[5];
|
||||
u8 buffer[2];
|
||||
const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg";
|
||||
|
||||
if (val < 0 || val >= 0x4000)
|
||||
return -EINVAL;
|
||||
scnprintf(key, sizeof(key), fmt, channel);
|
||||
buffer[0] = (val >> 6) & 0xff;
|
||||
buffer[1] = (val << 2) & 0xff;
|
||||
return applesmc_write_key(key, buffer, 2);
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_enable) {
|
||||
u8 buffer[2];
|
||||
u16 manual_val;
|
||||
const struct applesmc_entry *entry;
|
||||
|
||||
if (val != 1 && val != 2)
|
||||
return -EINVAL;
|
||||
|
||||
entry = applesmc_get_entry_by_key(FANS_MANUAL);
|
||||
if (IS_ERR(entry))
|
||||
return PTR_ERR(entry);
|
||||
|
||||
mutex_lock(&smcreg.mutex);
|
||||
ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
manual_val = (buffer[0] << 8 | buffer[1]);
|
||||
if (val == 1)
|
||||
manual_val |= (0x01 << channel);
|
||||
else
|
||||
manual_val &= ~(0x01 << channel);
|
||||
buffer[0] = (manual_val >> 8) & 0xff;
|
||||
buffer[1] = manual_val & 0xff;
|
||||
ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2);
|
||||
out_unlock:
|
||||
mutex_unlock(&smcreg.mutex);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (attr == hwmon_temp_label) {
|
||||
*str = smcreg.index[channel];
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case hwmon_fan:
|
||||
if (attr == hwmon_fan_label) {
|
||||
*str = smcreg.fan_positions[channel] + 4;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops applesmc_hwmon_ops = {
|
||||
.is_visible = applesmc_hwmon_is_visible,
|
||||
.read = applesmc_hwmon_read,
|
||||
.write = applesmc_hwmon_write,
|
||||
.read_string = applesmc_hwmon_read_string,
|
||||
};
|
||||
|
||||
static int __init applesmc_init(void)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (!dmi_check_system(applesmc_whitelist)) {
|
||||
@@ -1343,17 +1452,97 @@ static int __init applesmc_init(void)
|
||||
if (ret)
|
||||
goto out_smcreg;
|
||||
|
||||
ret = applesmc_create_nodes(fan_group, smcreg.fan_count);
|
||||
if (ret)
|
||||
/* allocate hwmon channel configs */
|
||||
applesmc_temp_config = kcalloc(smcreg.index_count + 1,
|
||||
sizeof(*applesmc_temp_config), GFP_KERNEL);
|
||||
applesmc_fan_config = kcalloc(smcreg.fan_count + 1,
|
||||
sizeof(*applesmc_fan_config), GFP_KERNEL);
|
||||
applesmc_pwm_config = kcalloc(smcreg.fan_count + 1,
|
||||
sizeof(*applesmc_pwm_config), GFP_KERNEL);
|
||||
if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) {
|
||||
ret = -ENOMEM;
|
||||
goto out_info;
|
||||
}
|
||||
|
||||
ret = applesmc_create_nodes(temp_group, smcreg.index_count);
|
||||
if (ret)
|
||||
goto out_fans;
|
||||
for (i = 0; i < smcreg.index_count; i++)
|
||||
applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL;
|
||||
applesmc_temp_config[smcreg.index_count] = 0;
|
||||
|
||||
for (i = 0; i < smcreg.fan_count; i++) {
|
||||
applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN |
|
||||
HWMON_F_MAX | HWMON_F_TARGET;
|
||||
applesmc_pwm_config[i] = HWMON_PWM_ENABLE;
|
||||
}
|
||||
applesmc_fan_config[smcreg.fan_count] = 0;
|
||||
applesmc_pwm_config[smcreg.fan_count] = 0;
|
||||
|
||||
applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL);
|
||||
applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL);
|
||||
applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL);
|
||||
if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) {
|
||||
ret = -ENOMEM;
|
||||
goto out_info;
|
||||
}
|
||||
|
||||
applesmc_info_temp->type = hwmon_temp;
|
||||
applesmc_info_temp->config = applesmc_temp_config;
|
||||
|
||||
applesmc_info_fan->type = hwmon_fan;
|
||||
applesmc_info_fan->config = applesmc_fan_config;
|
||||
|
||||
applesmc_info_pwm->type = hwmon_pwm;
|
||||
applesmc_info_pwm->config = applesmc_pwm_config;
|
||||
|
||||
applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL);
|
||||
if (!applesmc_info_arr) {
|
||||
ret = -ENOMEM;
|
||||
goto out_info;
|
||||
}
|
||||
|
||||
applesmc_info_arr[0] = applesmc_info_temp;
|
||||
applesmc_info_arr[1] = applesmc_info_fan;
|
||||
applesmc_info_arr[2] = applesmc_info_pwm;
|
||||
applesmc_info_arr[3] = NULL;
|
||||
|
||||
applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL);
|
||||
if (!applesmc_chip) {
|
||||
ret = -ENOMEM;
|
||||
goto out_info;
|
||||
}
|
||||
|
||||
applesmc_chip->ops = &applesmc_hwmon_ops;
|
||||
applesmc_chip->info = applesmc_info_arr;
|
||||
|
||||
/* Create non-standard fanX_safe attributes group */
|
||||
fan_safe_attrs = kcalloc(smcreg.fan_count,
|
||||
sizeof(*fan_safe_attrs), GFP_KERNEL);
|
||||
fan_safe_attr_list = kcalloc(smcreg.fan_count + 1,
|
||||
sizeof(*fan_safe_attr_list), GFP_KERNEL);
|
||||
if (!fan_safe_attrs || !fan_safe_attr_list) {
|
||||
ret = -ENOMEM;
|
||||
goto out_info;
|
||||
}
|
||||
|
||||
for (i = 0; i < smcreg.fan_count; i++) {
|
||||
struct applesmc_dev_attr *node = &fan_safe_attrs[i];
|
||||
|
||||
scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1);
|
||||
node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */
|
||||
node->sda.dev_attr.show = applesmc_show_fan_speed;
|
||||
node->sda.dev_attr.store = NULL;
|
||||
sysfs_attr_init(&node->sda.dev_attr.attr);
|
||||
node->sda.dev_attr.attr.name = node->name;
|
||||
node->sda.dev_attr.attr.mode = 0444;
|
||||
fan_safe_attr_list[i] = &node->sda.dev_attr.attr;
|
||||
}
|
||||
fan_safe_attr_list[smcreg.fan_count] = NULL;
|
||||
fan_safe_group.attrs = fan_safe_attr_list;
|
||||
applesmc_extra_groups[0] = &fan_safe_group;
|
||||
applesmc_extra_groups[1] = NULL;
|
||||
|
||||
ret = applesmc_create_accelerometer();
|
||||
if (ret)
|
||||
goto out_temperature;
|
||||
goto out_info;
|
||||
|
||||
ret = applesmc_create_light_sensor();
|
||||
if (ret)
|
||||
@@ -1363,7 +1552,8 @@ static int __init applesmc_init(void)
|
||||
if (ret)
|
||||
goto out_light_sysfs;
|
||||
|
||||
hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL,
|
||||
applesmc_chip, applesmc_extra_groups);
|
||||
if (IS_ERR(hwmon_dev)) {
|
||||
ret = PTR_ERR(hwmon_dev);
|
||||
goto out_light_ledclass;
|
||||
@@ -1377,11 +1567,8 @@ static int __init applesmc_init(void)
|
||||
applesmc_release_light_sensor();
|
||||
out_accelerometer:
|
||||
applesmc_release_accelerometer();
|
||||
out_temperature:
|
||||
applesmc_destroy_nodes(temp_group);
|
||||
out_fans:
|
||||
applesmc_destroy_nodes(fan_group);
|
||||
out_info:
|
||||
applesmc_free_hwmon();
|
||||
applesmc_destroy_nodes(info_group);
|
||||
out_smcreg:
|
||||
applesmc_destroy_smcreg();
|
||||
@@ -1402,13 +1589,12 @@ static void __exit applesmc_exit(void)
|
||||
applesmc_release_key_backlight();
|
||||
applesmc_release_light_sensor();
|
||||
applesmc_release_accelerometer();
|
||||
applesmc_destroy_nodes(temp_group);
|
||||
applesmc_destroy_nodes(fan_group);
|
||||
applesmc_destroy_nodes(info_group);
|
||||
applesmc_destroy_smcreg();
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&applesmc_driver);
|
||||
release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS);
|
||||
applesmc_free_hwmon();
|
||||
}
|
||||
|
||||
module_init(applesmc_init);
|
||||
|
||||
@@ -185,6 +185,20 @@ enum ec_sensors {
|
||||
#define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2)
|
||||
#define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3)
|
||||
|
||||
/*
|
||||
* The values for temperature sensor readings without physical sensors connected.
|
||||
* The value varies across generations and is seemingly defined by the EC chip
|
||||
* used in the given board.
|
||||
*/
|
||||
static const s32 temperature_blank_values[] = {-62, -60, -40};
|
||||
|
||||
static const s32 environment_temp_sensors =
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 |
|
||||
SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
|
||||
SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT |
|
||||
SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 |
|
||||
SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3;
|
||||
|
||||
enum board_family {
|
||||
family_unknown,
|
||||
family_amd_400_series,
|
||||
@@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = {
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
|
||||
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
[ec_sensor_fan_water_flow] =
|
||||
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe),
|
||||
[ec_sensor_temp_water_in] =
|
||||
@@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = {
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_x870e_hero = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_vi_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
@@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = {
|
||||
.family = family_intel_700_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_z790_hero = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_intel_700_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_prime_x470_pro = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
@@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = {
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_art_z690_creator_wifi = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
|
||||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = {
|
||||
/* Board also has a nct6798 */
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME |
|
||||
@@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = {
|
||||
|
||||
static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_800_series,
|
||||
@@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
&board_info_pro_art_x670E_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI",
|
||||
&board_info_pro_art_x870E_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI",
|
||||
&board_info_pro_art_z690_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI",
|
||||
&board_info_pro_ws_trx50_sage_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A",
|
||||
@@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
&board_info_crosshair_x670e_gene),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
|
||||
&board_info_crosshair_x670e_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO",
|
||||
&board_info_crosshair_x870e_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO",
|
||||
&board_info_maximus_xi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)",
|
||||
@@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
&board_info_maximus_z690_formula),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME",
|
||||
&board_info_maximus_z790_extreme),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO",
|
||||
&board_info_maximus_z790_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
|
||||
&board_info_strix_b550_e_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING",
|
||||
@@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
&board_info_strix_x870_i_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI",
|
||||
&board_info_strix_x870e_e_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2",
|
||||
&board_info_strix_x870e_e_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7",
|
||||
&board_info_strix_x870e_h_gaming_wifi7),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING",
|
||||
&board_info_strix_z390_f_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING",
|
||||
&board_info_strix_z390_f_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING",
|
||||
@@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
};
|
||||
|
||||
struct ec_sensor {
|
||||
/* this is ec_sensors enum value */
|
||||
unsigned int info_index;
|
||||
s32 cached_value;
|
||||
};
|
||||
@@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index)
|
||||
return state->sensors_info + state->sensors[index].info_index;
|
||||
}
|
||||
|
||||
static enum ec_sensors
|
||||
get_ec_sensor_type(const struct ec_sensors_data *state, int index)
|
||||
{
|
||||
return state->sensors[index].info_index;
|
||||
}
|
||||
|
||||
static int find_ec_sensor_index(const struct ec_sensors_data *ec,
|
||||
enum hwmon_sensor_types type, int channel)
|
||||
{
|
||||
@@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_blank_temperature_value(s32 value)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) {
|
||||
if (value == temperature_blank_values[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now follow the functions that implement the hwmon interface
|
||||
*/
|
||||
@@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev,
|
||||
static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
const struct ec_sensor_info *sensor_info;
|
||||
enum ec_sensors ec_sensor;
|
||||
int ret;
|
||||
s32 value = 0;
|
||||
|
||||
@@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
}
|
||||
|
||||
ret = get_cached_value_or_update(dev, sidx, state, &value);
|
||||
if (!ret) {
|
||||
*val = scale_sensor_value(value,
|
||||
get_sensor_info(state, sidx)->type);
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
sensor_info = get_sensor_info(state, sidx);
|
||||
if (sensor_info->type == hwmon_temp) {
|
||||
ec_sensor = get_ec_sensor_type(state, sidx);
|
||||
if ((environment_temp_sensors & BIT(ec_sensor)) &&
|
||||
is_blank_temperature_value(value))
|
||||
return -ENODATA;
|
||||
}
|
||||
*val = scale_sensor_value(value, sensor_info->type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_hwmon_read_string(struct device *dev,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* hwmon driver for Asus ROG Ryujin II 360 AIO cooler.
|
||||
* hwmon driver for Asus ROG Ryujin AIO coolers.
|
||||
*
|
||||
* Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com>
|
||||
*/
|
||||
@@ -17,16 +17,37 @@
|
||||
|
||||
#define USB_VENDOR_ID_ASUS_ROG 0x0b05
|
||||
#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
|
||||
#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb
|
||||
#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade
|
||||
#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada
|
||||
|
||||
struct rog_ryujin_device_info {
|
||||
u8 temp_offset;
|
||||
u8 pump_speed_offset;
|
||||
u8 fan_speed_offset;
|
||||
u8 duty_channel;
|
||||
bool has_controller;
|
||||
};
|
||||
|
||||
static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = {
|
||||
.temp_offset = 3,
|
||||
.pump_speed_offset = 5,
|
||||
.fan_speed_offset = 7,
|
||||
.duty_channel = 0,
|
||||
.has_controller = true,
|
||||
};
|
||||
|
||||
static const struct rog_ryujin_device_info rog_ryujin_iii_info = {
|
||||
.temp_offset = 5,
|
||||
.pump_speed_offset = 7,
|
||||
.fan_speed_offset = 10,
|
||||
.duty_channel = 1,
|
||||
.has_controller = false,
|
||||
};
|
||||
|
||||
#define STATUS_VALIDITY 1500 /* ms */
|
||||
#define MAX_REPORT_LENGTH 65
|
||||
|
||||
/* Cooler status report offsets */
|
||||
#define RYUJIN_TEMP_SENSOR_1 3
|
||||
#define RYUJIN_TEMP_SENSOR_2 4
|
||||
#define RYUJIN_PUMP_SPEED 5
|
||||
#define RYUJIN_INTERNAL_FAN_SPEED 7
|
||||
|
||||
/* Cooler duty report offsets */
|
||||
#define RYUJIN_PUMP_DUTY 4
|
||||
#define RYUJIN_INTERNAL_FAN_DUTY 5
|
||||
@@ -81,6 +102,7 @@ static const char *const rog_ryujin_speed_label[] = {
|
||||
struct rog_ryujin_data {
|
||||
struct hid_device *hdev;
|
||||
struct device *hwmon_dev;
|
||||
const struct rog_ryujin_device_info *info;
|
||||
/* For reinitializing the completions below */
|
||||
spinlock_t status_report_request_lock;
|
||||
struct completion cooler_status_received;
|
||||
@@ -112,6 +134,8 @@ static int rog_ryujin_pwm_to_percent(long val)
|
||||
static umode_t rog_ryujin_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type, u32 attr, int channel)
|
||||
{
|
||||
const struct rog_ryujin_data *priv = data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
@@ -123,6 +147,8 @@ static umode_t rog_ryujin_is_visible(const void *data,
|
||||
}
|
||||
break;
|
||||
case hwmon_fan:
|
||||
if (channel >= 2 && !priv->info->has_controller)
|
||||
return 0;
|
||||
switch (attr) {
|
||||
case hwmon_fan_label:
|
||||
case hwmon_fan_input:
|
||||
@@ -132,6 +158,8 @@ static umode_t rog_ryujin_is_visible(const void *data,
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
if (channel >= 2 && !priv->info->has_controller)
|
||||
return 0;
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return 0644;
|
||||
@@ -198,12 +226,14 @@ static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Retrieve controller status (speeds) */
|
||||
ret =
|
||||
rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
|
||||
&priv->controller_status_received);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (priv->info->has_controller) {
|
||||
/* Retrieve controller status (speeds) */
|
||||
ret = rog_ryujin_execute_cmd(priv, get_controller_speed_cmd,
|
||||
GET_CMD_LENGTH,
|
||||
&priv->controller_status_received);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Retrieve cooler duty */
|
||||
ret =
|
||||
@@ -212,12 +242,14 @@ static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Retrieve controller duty */
|
||||
ret =
|
||||
rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
|
||||
&priv->controller_duty_received);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (priv->info->has_controller) {
|
||||
/* Retrieve controller duty */
|
||||
ret = rog_ryujin_execute_cmd(priv, get_controller_duty_cmd,
|
||||
GET_CMD_LENGTH,
|
||||
&priv->controller_duty_received);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->updated = jiffies;
|
||||
return 0;
|
||||
@@ -289,6 +321,7 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel
|
||||
return ret;
|
||||
|
||||
memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
|
||||
set_cmd[2] = priv->info->duty_channel;
|
||||
|
||||
/* Cooler duties are set as 0-100% */
|
||||
val = rog_ryujin_pwm_to_percent(val);
|
||||
@@ -394,10 +427,12 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
|
||||
|
||||
if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
|
||||
/* Received coolant temp and speeds of pump and internal fan */
|
||||
priv->temp_input[0] =
|
||||
data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;
|
||||
priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);
|
||||
priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);
|
||||
priv->temp_input[0] = data[priv->info->temp_offset] * 1000 +
|
||||
data[priv->info->temp_offset + 1] * 100;
|
||||
priv->speed_input[0] =
|
||||
get_unaligned_le16(data + priv->info->pump_speed_offset);
|
||||
priv->speed_input[1] =
|
||||
get_unaligned_le16(data + priv->info->fan_speed_offset);
|
||||
|
||||
if (!completion_done(&priv->cooler_status_received))
|
||||
complete_all(&priv->cooler_status_received);
|
||||
@@ -471,11 +506,15 @@ static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id
|
||||
struct rog_ryujin_data *priv;
|
||||
int ret;
|
||||
|
||||
if (!id->driver_data)
|
||||
return -EINVAL;
|
||||
|
||||
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->hdev = hdev;
|
||||
priv->info = (const struct rog_ryujin_device_info *)id->driver_data;
|
||||
hid_set_drvdata(hdev, priv);
|
||||
|
||||
/*
|
||||
@@ -546,7 +585,14 @@ static void rog_ryujin_remove(struct hid_device *hdev)
|
||||
}
|
||||
|
||||
static const struct hid_device_id rog_ryujin_table[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO),
|
||||
.driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME),
|
||||
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA),
|
||||
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE),
|
||||
.driver_data = (kernel_ulong_t)&rog_ryujin_iii_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -576,4 +622,4 @@ module_exit(rog_ryujin_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
|
||||
MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");
|
||||
MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers");
|
||||
|
||||
@@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev)
|
||||
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
||||
NULL, ctl);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"failed to request an irq\n");
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
#define CC2_START_CM 0xA0
|
||||
#define CC2_START_NOM 0x80
|
||||
@@ -83,6 +85,7 @@ struct cc2_data {
|
||||
struct i2c_client *client;
|
||||
struct regulator *regulator;
|
||||
const char *name;
|
||||
const char *label;
|
||||
int irq_ready;
|
||||
int irq_low;
|
||||
int irq_high;
|
||||
@@ -449,6 +452,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
switch (attr) {
|
||||
case hwmon_humidity_input:
|
||||
return 0444;
|
||||
case hwmon_humidity_label:
|
||||
return cc2->label ? 0444 : 0;
|
||||
case hwmon_humidity_min_alarm:
|
||||
return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
|
||||
case hwmon_humidity_max_alarm:
|
||||
@@ -466,6 +471,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
return 0444;
|
||||
case hwmon_temp_label:
|
||||
return cc2->label ? 0444 : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -552,6 +559,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
struct cc2_data *data = dev_get_drvdata(dev);
|
||||
|
||||
*str = data->label;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long *val)
|
||||
{
|
||||
@@ -670,8 +687,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *cc2_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL |
|
||||
HWMON_H_MIN | HWMON_H_MAX |
|
||||
HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
|
||||
HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
|
||||
NULL
|
||||
@@ -680,6 +698,7 @@ static const struct hwmon_channel_info *cc2_info[] = {
|
||||
static const struct hwmon_ops cc2_hwmon_ops = {
|
||||
.is_visible = cc2_is_visible,
|
||||
.read = cc2_read,
|
||||
.read_string = cc2_read_string,
|
||||
.write = cc2_write,
|
||||
};
|
||||
|
||||
@@ -710,6 +729,8 @@ static int cc2_probe(struct i2c_client *client)
|
||||
return dev_err_probe(dev, PTR_ERR(data->regulator),
|
||||
"Failed to get regulator\n");
|
||||
|
||||
device_property_read_string(dev, "label", &data->label);
|
||||
|
||||
ret = cc2_request_ready_irq(data, dev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to request ready irq\n");
|
||||
|
||||
@@ -68,7 +68,7 @@ enum coretemp_attr_index {
|
||||
* This value is passed as "id" field to rdmsr/wrmsr functions.
|
||||
* @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS,
|
||||
* from where the temperature values should be read.
|
||||
* @attr_size: Total number of pre-core attrs displayed in the sysfs.
|
||||
* @attr_size: Number of sysfs attributes in this temperature data group.
|
||||
*/
|
||||
struct temp_data {
|
||||
int temp;
|
||||
@@ -650,6 +650,7 @@ static void coretemp_device_remove(int zoneid)
|
||||
struct platform_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
ida_destroy(&pdata->ida);
|
||||
kfree(pdata->core_data);
|
||||
kfree(pdata);
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
|
||||
@@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = {
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */
|
||||
{ },
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/math.h>
|
||||
@@ -24,6 +25,7 @@
|
||||
|
||||
struct cros_ec_hwmon_priv {
|
||||
struct cros_ec_device *cros_ec;
|
||||
struct device *hwmon_dev;
|
||||
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
|
||||
u8 usable_fans;
|
||||
bool fan_control_supported;
|
||||
@@ -146,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp)
|
||||
temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
|
||||
}
|
||||
|
||||
/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */
|
||||
#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000)
|
||||
|
||||
static long cros_ec_hwmon_kelvin_to_millicelsius(long t)
|
||||
{
|
||||
return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS;
|
||||
}
|
||||
|
||||
static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
|
||||
{
|
||||
return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
|
||||
return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
|
||||
}
|
||||
|
||||
static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr)
|
||||
@@ -226,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel,
|
||||
cros_ec_hwmon_attr_to_thres(attr),
|
||||
&threshold);
|
||||
if (ret == 0)
|
||||
*val = kelvin_to_millicelsius(threshold);
|
||||
if (ret == 0) {
|
||||
/* Limit to sensible, non-overflowing values. */
|
||||
if (threshold > 255 + 273)
|
||||
*val = 255000;
|
||||
else
|
||||
*val = cros_ec_hwmon_kelvin_to_millicelsius(threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,6 +416,8 @@ static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cd
|
||||
u8 read_val;
|
||||
int ret;
|
||||
|
||||
guard(hwmon_lock)(priv->hwmon_priv->hwmon_dev);
|
||||
|
||||
ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -414,6 +431,8 @@ static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cd
|
||||
{
|
||||
const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
|
||||
|
||||
guard(hwmon_lock)(priv->hwmon_priv->hwmon_dev);
|
||||
|
||||
return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val);
|
||||
}
|
||||
|
||||
@@ -547,7 +566,6 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev)
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
|
||||
struct cros_ec_hwmon_priv *priv;
|
||||
struct device *hwmon_dev;
|
||||
u8 thermal_version;
|
||||
int ret;
|
||||
|
||||
@@ -570,13 +588,17 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev)
|
||||
priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
|
||||
priv->temp_threshold_supported = is_cros_ec_cmd_available(priv->cros_ec,
|
||||
EC_CMD_THERMAL_GET_THRESHOLD, 1);
|
||||
|
||||
priv->hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
|
||||
&cros_ec_hwmon_chip_info, NULL);
|
||||
if (IS_ERR(priv->hwmon_dev))
|
||||
return PTR_ERR(priv->hwmon_dev);
|
||||
|
||||
cros_ec_hwmon_register_fan_cooling_devices(dev, priv);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
|
||||
&cros_ec_hwmon_chip_info, NULL);
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
|
||||
@@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev)
|
||||
NULL, da9055_auxadc_irq,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
"adc-irq", hwmon);
|
||||
if (ret != 0) {
|
||||
dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n",
|
||||
ret);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055",
|
||||
hwmon,
|
||||
|
||||
509
drivers/hwmon/eic7700-pvt.c
Normal file
509
drivers/hwmon/eic7700-pvt.c
Normal file
@@ -0,0 +1,509 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ESWIN EIC7700 Voltage, Temperature sensor driver
|
||||
*
|
||||
* Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.
|
||||
*
|
||||
* Authors:
|
||||
* Yulin Lu <luyulin@eswincomputing.com>
|
||||
* Huan He <hehuan1@eswincomputing.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/polynomial.h>
|
||||
#include <linux/reset.h>
|
||||
#include "eic7700-pvt.h"
|
||||
|
||||
static const struct pvt_sensor_info pvt_info[] = {
|
||||
PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP),
|
||||
PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT),
|
||||
};
|
||||
|
||||
static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"};
|
||||
|
||||
/*
|
||||
* The original translation formulae of the temperature (in degrees of Celsius)
|
||||
* to PVT data and vice-versa are following:
|
||||
* N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) +
|
||||
* 1.6198e2,
|
||||
* T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) +
|
||||
* 3.2843e-1*(N^1) - 4.8690e1,
|
||||
* where T = [-40, 125]C and N = [27, 771].
|
||||
* They must be accordingly altered to be suitable for the integer arithmetics.
|
||||
* The technique is called 'factor redistribution', which just makes sure the
|
||||
* multiplications and divisions are made so to have a result of the operations
|
||||
* within the integer numbers limit. In addition we need to translate the
|
||||
* formulae to accept millidegrees of Celsius. Here what they look like after
|
||||
* the alterations:
|
||||
* N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T +
|
||||
* 16198e2) / 1e4,
|
||||
* T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N -
|
||||
* 48690,
|
||||
* where T = [-40000, 125000] mC and N = [27, 771].
|
||||
*/
|
||||
static const struct polynomial poly_N_to_temp = {
|
||||
.total_divider = 1,
|
||||
.terms = {
|
||||
{4, -18439, 1000, 1},
|
||||
{3, 80705, 1000, 1},
|
||||
{2, -185010, 1000, 1},
|
||||
{1, 328430, 1000, 1},
|
||||
{0, -48690, 1, 1}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Similar alterations are performed for the voltage conversion equations.
|
||||
* The original formulae are:
|
||||
* N = 1.3905e3*V - 5.7685e2,
|
||||
* V = (N + 5.7685e2) / 1.3905e3,
|
||||
* where V = [0.72, 0.88] V and N = [424, 646].
|
||||
* After the optimization they looks as follows:
|
||||
* N = (13905e-3*V - 5768.5) / 10,
|
||||
* V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10.
|
||||
* where V = [720, 880] mV and N = [424, 646].
|
||||
*/
|
||||
static const struct polynomial poly_N_to_volt = {
|
||||
.total_divider = 10,
|
||||
.terms = {
|
||||
{1, 100000, 13905, 1},
|
||||
{0, 57685000, 1, 13905}
|
||||
}
|
||||
};
|
||||
|
||||
static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data)
|
||||
{
|
||||
u32 old;
|
||||
|
||||
old = readl_relaxed(reg);
|
||||
writel((old & ~mask) | (data & mask), reg);
|
||||
|
||||
return old & mask;
|
||||
}
|
||||
|
||||
static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode)
|
||||
{
|
||||
u32 old;
|
||||
|
||||
mode = FIELD_PREP(PVT_MODE_MASK, mode);
|
||||
|
||||
old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0);
|
||||
eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode);
|
||||
eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old);
|
||||
}
|
||||
|
||||
static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val)
|
||||
{
|
||||
u32 old;
|
||||
|
||||
old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0);
|
||||
writel(val, pvt->regs + PVT_TRIM);
|
||||
eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old);
|
||||
}
|
||||
|
||||
static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data)
|
||||
{
|
||||
struct pvt_hwmon *pvt = data;
|
||||
u32 stat, val;
|
||||
int active;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM)) {
|
||||
active = pm_runtime_get_if_active(pvt->dev);
|
||||
if (active <= 0)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
stat = readl(pvt->regs + PVT_INT);
|
||||
if (!(stat & PVT_INT_STAT)) {
|
||||
if (IS_ENABLED(CONFIG_PM))
|
||||
pm_runtime_put(pvt->dev);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR);
|
||||
/*
|
||||
* Read the data, update the cache and notify a waiter of this event.
|
||||
*/
|
||||
val = readl(pvt->regs + PVT_DATA);
|
||||
WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val));
|
||||
complete(&pvt->conversion);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM))
|
||||
pm_runtime_put(pvt->dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_read_data(struct pvt_hwmon *pvt,
|
||||
enum pvt_sensor_type type, long *val)
|
||||
{
|
||||
unsigned long timeout;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Wait for PVT conversion to complete and update the data cache. The
|
||||
* data read procedure is following: set the requested PVT sensor mode,
|
||||
* enable conversion, wait until conversion is finished, then disable
|
||||
* conversion and IRQ, and read the cached data.
|
||||
*/
|
||||
reinit_completion(&pvt->conversion);
|
||||
|
||||
eic7700_pvt_set_mode(pvt, pvt_info[type].mode);
|
||||
eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN);
|
||||
|
||||
/*
|
||||
* Wait with timeout since in case if the sensor is suddenly powered
|
||||
* down the request won't be completed and the caller will hang up on
|
||||
* this procedure until the power is back up again. Multiply the
|
||||
* timeout by the factor of two to prevent a false timeout.
|
||||
*/
|
||||
timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout));
|
||||
ret = wait_for_completion_timeout(&pvt->conversion, timeout);
|
||||
|
||||
eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0);
|
||||
eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR);
|
||||
|
||||
if (!ret)
|
||||
synchronize_irq(pvt->irq);
|
||||
|
||||
data = READ_ONCE(pvt->data_cache);
|
||||
|
||||
if (!ret)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
if (type == PVT_TEMP)
|
||||
*val = polynomial_calc(&poly_N_to_temp, data);
|
||||
else
|
||||
*val = polynomial_calc(&poly_N_to_volt, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *pvt_channel_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
||||
static umode_t eic7700_pvt_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int ch)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_label:
|
||||
return 0444;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_label:
|
||||
return 0444;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_hwmon_read(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int ch, long *val)
|
||||
{
|
||||
struct pvt_hwmon *pvt = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(pvt->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret);
|
||||
pm_runtime_put_noidle(pvt->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
ret = eic7700_pvt_read_data(pvt, ch, val);
|
||||
break;
|
||||
default:
|
||||
ret = -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
if (attr == hwmon_in_input)
|
||||
ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val);
|
||||
else
|
||||
ret = -EOPNOTSUPP;
|
||||
break;
|
||||
default:
|
||||
ret = -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(pvt->dev);
|
||||
pm_runtime_put_autosuspend(pvt->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_hwmon_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int ch, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (attr == hwmon_temp_label) {
|
||||
*str = pvt_info[ch].label;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
if (attr == hwmon_in_label) {
|
||||
*str = pvt_info[PVT_VOLT + ch].label;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops pvt_hwmon_ops = {
|
||||
.is_visible = eic7700_pvt_hwmon_is_visible,
|
||||
.read = eic7700_pvt_hwmon_read,
|
||||
.read_string = eic7700_pvt_hwmon_read_string
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info pvt_hwmon_info = {
|
||||
.ops = &pvt_hwmon_ops,
|
||||
.info = pvt_channel_info
|
||||
};
|
||||
|
||||
static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pvt_hwmon *pvt;
|
||||
|
||||
pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL);
|
||||
if (!pvt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pvt->dev = dev;
|
||||
init_completion(&pvt->conversion);
|
||||
|
||||
return pvt;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt)
|
||||
{
|
||||
/*
|
||||
* Make sure controller are disabled so not to accidentally have ISR
|
||||
* executed before the driver data is fully initialized. Clear the IRQ
|
||||
* status as well.
|
||||
*/
|
||||
eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0);
|
||||
eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR);
|
||||
readl(pvt->regs + PVT_INT);
|
||||
readl(pvt->regs + PVT_DATA);
|
||||
|
||||
/* Setup default sensor mode and temperature trim. */
|
||||
eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode);
|
||||
|
||||
/*
|
||||
* Max conversion latency (~333 µs) derived from PVT spec:
|
||||
* maximum sampling rate = 3000 samples/sec.
|
||||
*/
|
||||
pvt->timeout = ns_to_ktime(PVT_TOUT_MIN);
|
||||
|
||||
eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(pvt->dev);
|
||||
int ret;
|
||||
|
||||
pvt->irq = platform_get_irq(pdev, 0);
|
||||
if (pvt->irq < 0)
|
||||
return pvt->irq;
|
||||
|
||||
ret = devm_request_threaded_irq(pvt->dev, pvt->irq,
|
||||
eic7700_pvt_hard_isr, NULL,
|
||||
IRQF_TRIGGER_HIGH, "pvt", pvt);
|
||||
if (ret) {
|
||||
dev_err(pvt->dev, "Couldn't request PVT IRQ\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt)
|
||||
{
|
||||
pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt",
|
||||
pvt, &pvt_hwmon_info,
|
||||
NULL);
|
||||
if (IS_ERR(pvt->hwmon)) {
|
||||
dev_err(pvt->dev, "Couldn't create hwmon device\n");
|
||||
return PTR_ERR(pvt->hwmon);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void eic7700_pvt_disable_pm_runtime(void *data)
|
||||
{
|
||||
struct pvt_hwmon *pvt = data;
|
||||
|
||||
pm_runtime_dont_use_autosuspend(pvt->dev);
|
||||
pm_runtime_disable(pvt->dev);
|
||||
|
||||
if (!pm_runtime_status_suspended(pvt->dev)) {
|
||||
clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks);
|
||||
pm_runtime_set_suspended(pvt->dev);
|
||||
}
|
||||
}
|
||||
|
||||
static int eic7700_pvt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct reset_control *rst;
|
||||
struct pvt_hwmon *pvt;
|
||||
int i, ret;
|
||||
|
||||
pvt = eic7700_pvt_create_data(pdev);
|
||||
if (IS_ERR(pvt))
|
||||
return PTR_ERR(pvt);
|
||||
|
||||
platform_set_drvdata(pdev, pvt);
|
||||
|
||||
pvt->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(pvt->regs))
|
||||
return PTR_ERR(pvt->regs);
|
||||
|
||||
for (i = 0; i < PVT_CLK_NUM; i++)
|
||||
pvt->clks[i].id = pvt_clk_names[i];
|
||||
|
||||
ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"Couldn't get clock descriptors\n");
|
||||
|
||||
rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL);
|
||||
if (IS_ERR(rst))
|
||||
return dev_err_probe(pvt->dev, PTR_ERR(rst),
|
||||
"Couldn't get reset control\n");
|
||||
|
||||
ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks);
|
||||
if (ret)
|
||||
return dev_err_probe(pvt->dev, ret,
|
||||
"Failed to enable clocks\n");
|
||||
|
||||
ret = eic7700_pvt_init_iface(pvt);
|
||||
if (ret) {
|
||||
clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM))
|
||||
clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks);
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, 3000);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
|
||||
ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime,
|
||||
pvt);
|
||||
if (ret) {
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"Can't register PM cleanup\n");
|
||||
}
|
||||
|
||||
ret = eic7700_pvt_request_irq(pvt);
|
||||
if (ret)
|
||||
goto err_put_pm_runtime;
|
||||
|
||||
ret = eic7700_pvt_create_hwmon(pvt);
|
||||
if (ret)
|
||||
goto err_put_pm_runtime;
|
||||
|
||||
pm_runtime_put_autosuspend(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_put_pm_runtime:
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct pvt_hwmon *pvt = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable clocks: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct pvt_hwmon *pvt = dev_get_drvdata(dev);
|
||||
|
||||
clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops eic7700_pvt_pm_ops = {
|
||||
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
||||
RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id pvt_of_match[] = {
|
||||
{ .compatible = "eswin,eic7700-pvt"},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pvt_of_match);
|
||||
|
||||
static struct platform_driver pvt_driver = {
|
||||
.probe = eic7700_pvt_probe,
|
||||
.driver = {
|
||||
.name = "eic7700-pvt",
|
||||
.of_match_table = pvt_of_match,
|
||||
.pm = pm_ptr(&eic7700_pvt_pm_ops),
|
||||
},
|
||||
};
|
||||
module_platform_driver(pvt_driver);
|
||||
|
||||
MODULE_AUTHOR("Yulin Lu <luyulin@eswincomputing.com>");
|
||||
MODULE_AUTHOR("Huan He <hehuan1@eswincomputing.com>");
|
||||
MODULE_DESCRIPTION("Eswin eic7700 PVT driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
99
drivers/hwmon/eic7700-pvt.h
Normal file
99
drivers/hwmon/eic7700-pvt.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* ESWIN EIC7700 Voltage, Temperature sensor driver
|
||||
*
|
||||
* Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd.
|
||||
*/
|
||||
#ifndef __HWMON_EIC7700_PVT_H__
|
||||
#define __HWMON_EIC7700_PVT_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
/* ESWIN EIC7700 PVT registers and their bitfields */
|
||||
#define PVT_TRIM 0x04
|
||||
#define PVT_MODE 0x08
|
||||
#define PVT_MODE_MASK GENMASK(2, 0)
|
||||
#define PVT_CTRL_MODE_TEMP 0x0
|
||||
#define PVT_CTRL_MODE_VOLT 0x4
|
||||
#define PVT_ENA 0x0c
|
||||
#define PVT_ENA_EN BIT(0)
|
||||
#define PVT_INT 0x10
|
||||
#define PVT_INT_STAT BIT(0)
|
||||
#define PVT_INT_CLR BIT(1)
|
||||
#define PVT_DATA 0x14
|
||||
#define PVT_DATA_OUT GENMASK(9, 0)
|
||||
|
||||
/*
|
||||
* PVT sensors-related limits and default values
|
||||
* @PVT_TEMP_CHS: Number of temperature hwmon channels.
|
||||
* @PVT_VOLT_CHS: Number of voltage hwmon channels.
|
||||
* @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value
|
||||
* when one is determined for ESWIN EIC7700 SoC).
|
||||
* @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds.
|
||||
*/
|
||||
#define PVT_TEMP_CHS 1
|
||||
#define PVT_VOLT_CHS 1
|
||||
#define PVT_TRIM_DEF 0
|
||||
#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000)
|
||||
|
||||
/*
|
||||
* enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT
|
||||
* sampling mode)
|
||||
* @PVT_TEMP: PVT Temperature sensor.
|
||||
* @PVT_VOLT: PVT Voltage sensor.
|
||||
*/
|
||||
enum pvt_sensor_type {
|
||||
PVT_TEMP = 0,
|
||||
PVT_VOLT
|
||||
};
|
||||
|
||||
#define PVT_CLK_NUM 2
|
||||
|
||||
/*
|
||||
* struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure
|
||||
* @channel: Sensor channel ID.
|
||||
* @label: hwmon sensor label.
|
||||
* @mode: PVT mode corresponding to the channel.
|
||||
* @type: Sensor type.
|
||||
*/
|
||||
struct pvt_sensor_info {
|
||||
int channel;
|
||||
const char *label;
|
||||
u32 mode;
|
||||
enum hwmon_sensor_types type;
|
||||
};
|
||||
|
||||
#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \
|
||||
{ \
|
||||
.channel = _ch, \
|
||||
.label = _label, \
|
||||
.mode = PVT_CTRL_MODE_ ##_mode, \
|
||||
.type = _type, \
|
||||
}
|
||||
|
||||
/*
|
||||
* struct pvt_hwmon - Eswin EIC7700 PVT private data
|
||||
* @dev: device structure of the PVT platform device.
|
||||
* @hwmon: hwmon device structure.
|
||||
* @regs: pointer to the Eswin EIC7700 PVT registers region.
|
||||
* @irq: PVT events IRQ number.
|
||||
* @clks: PVT clock descriptors.
|
||||
* @data_cache: data cache in raw format.
|
||||
* @conversion: data conversion completion.
|
||||
* @timeout: conversion timeout.
|
||||
*/
|
||||
struct pvt_hwmon {
|
||||
struct device *dev;
|
||||
struct device *hwmon;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
struct clk_bulk_data clks[PVT_CLK_NUM];
|
||||
u32 data_cache;
|
||||
struct completion conversion;
|
||||
ktime_t timeout;
|
||||
};
|
||||
|
||||
#endif /* __HWMON_EIC7700_PVT_H__ */
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
#define THERMAL_PID_REG 0xfd
|
||||
@@ -305,10 +306,9 @@ static int emc1403_get_hyst(struct thermal_data *data, int channel,
|
||||
ret = regmap_read(data->regmap, 0x21, &hyst);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (map == temp_min)
|
||||
*val = limit + hyst * 1000;
|
||||
else
|
||||
*val = limit - hyst * 1000;
|
||||
|
||||
*val = limit - hyst * 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -324,9 +324,6 @@ static int emc1403_temp_read(struct thermal_data *data, u32 attr, int channel, l
|
||||
case hwmon_temp_input:
|
||||
ret = emc1403_get_temp(data, channel, ema1403_temp_map[attr], val);
|
||||
break;
|
||||
case hwmon_temp_min_hyst:
|
||||
ret = emc1403_get_hyst(data, channel, temp_min, val);
|
||||
break;
|
||||
case hwmon_temp_max_hyst:
|
||||
ret = emc1403_get_hyst(data, channel, temp_max, val);
|
||||
break;
|
||||
@@ -548,7 +545,6 @@ static umode_t emc1403_temp_is_visible(const void *_data, u32 attr, int channel)
|
||||
case hwmon_temp_max_alarm:
|
||||
case hwmon_temp_crit_alarm:
|
||||
case hwmon_temp_fault:
|
||||
case hwmon_temp_min_hyst:
|
||||
case hwmon_temp_max_hyst:
|
||||
return 0444;
|
||||
case hwmon_temp_min:
|
||||
@@ -591,35 +587,35 @@ static const struct hwmon_channel_info * const emc1403_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MIN_HYST | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT
|
||||
),
|
||||
@@ -659,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client)
|
||||
{
|
||||
struct thermal_data *data;
|
||||
struct device *hwmon_dev;
|
||||
const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client);
|
||||
int ret;
|
||||
|
||||
ret = devm_regulator_get_enable(&client->dev, "vdd");
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to enable regulator\n");
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct thermal_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->chip = id->driver_data;
|
||||
data->chip = (uintptr_t)i2c_get_match_data(client);
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config);
|
||||
if (IS_ERR(data->regmap))
|
||||
return PTR_ERR(data->regmap);
|
||||
@@ -682,10 +684,20 @@ static const unsigned short emc1403_address_list[] = {
|
||||
0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END
|
||||
};
|
||||
|
||||
static const struct of_device_id emc1403_of_match[] = {
|
||||
{ .compatible = "smsc,emc1402", .data = (void *)emc1402 },
|
||||
{ .compatible = "smsc,emc1403", .data = (void *)emc1403 },
|
||||
{ .compatible = "smsc,emc1404", .data = (void *)emc1404 },
|
||||
{ .compatible = "smsc,emc1428", .data = (void *)emc1428 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, emc1403_of_match);
|
||||
|
||||
static struct i2c_driver sensor_emc1403 = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "emc1403",
|
||||
.of_match_table = emc1403_of_match,
|
||||
},
|
||||
.detect = emc1403_detect,
|
||||
.probe = emc1403_probe,
|
||||
|
||||
@@ -1,59 +1,16 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
|
||||
/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */
|
||||
#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */
|
||||
#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */
|
||||
#define POWER_BIT 24
|
||||
|
||||
struct gxp_fan_ctrl_drvdata {
|
||||
void __iomem *base;
|
||||
void __iomem *plreg;
|
||||
void __iomem *fn2;
|
||||
};
|
||||
|
||||
static bool fan_installed(struct device *dev, int fan)
|
||||
{
|
||||
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
u8 val;
|
||||
|
||||
val = readb(drvdata->plreg + OFS_FAN_INST);
|
||||
|
||||
return !!(val & BIT(fan));
|
||||
}
|
||||
|
||||
static long fan_failed(struct device *dev, int fan)
|
||||
{
|
||||
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
u8 val;
|
||||
|
||||
val = readb(drvdata->plreg + OFS_FAN_FAIL);
|
||||
|
||||
return !!(val & BIT(fan));
|
||||
}
|
||||
|
||||
static long fan_enabled(struct device *dev, int fan)
|
||||
{
|
||||
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Check the power status as if the platform is off the value
|
||||
* reported for the PWM will be incorrect. Report fan as
|
||||
* disabled.
|
||||
*/
|
||||
val = readl(drvdata->fn2 + OFS_SEVSTAT);
|
||||
|
||||
return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan));
|
||||
}
|
||||
|
||||
static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val)
|
||||
{
|
||||
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
@@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
}
|
||||
}
|
||||
|
||||
static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (attr) {
|
||||
case hwmon_fan_enable:
|
||||
*val = fan_enabled(dev, channel);
|
||||
return 0;
|
||||
case hwmon_fan_fault:
|
||||
*val = fan_failed(dev, channel);
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val)
|
||||
{
|
||||
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
u32 reg;
|
||||
|
||||
/*
|
||||
* Check the power status of the platform. If the platform is off
|
||||
* the value reported for the PWM will be incorrect. In this case
|
||||
* report a PWM of zero.
|
||||
*/
|
||||
|
||||
reg = readl(drvdata->fn2 + OFS_SEVSTAT);
|
||||
|
||||
if (reg & BIT(POWER_BIT))
|
||||
*val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0;
|
||||
else
|
||||
*val = 0;
|
||||
*val = readb(drvdata->base + channel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
return gxp_fan_read(dev, attr, channel, val);
|
||||
case hwmon_pwm:
|
||||
return gxp_pwm_read(dev, attr, channel, val);
|
||||
default:
|
||||
@@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data,
|
||||
umode_t mode = 0;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_enable:
|
||||
case hwmon_fan_fault:
|
||||
mode = 0444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
@@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = {
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = {
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_FAULT | HWMON_F_ENABLE),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
@@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev)
|
||||
return dev_err_probe(dev, PTR_ERR(drvdata->base),
|
||||
"failed to map base\n");
|
||||
|
||||
drvdata->plreg = devm_platform_ioremap_resource_byname(pdev,
|
||||
"pl");
|
||||
if (IS_ERR(drvdata->plreg))
|
||||
return dev_err_probe(dev, PTR_ERR(drvdata->plreg),
|
||||
"failed to map plreg\n");
|
||||
|
||||
drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev,
|
||||
"fn2");
|
||||
if (IS_ERR(drvdata->fn2))
|
||||
return dev_err_probe(dev, PTR_ERR(drvdata->fn2),
|
||||
"failed to map fn2\n");
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
|
||||
"hpe_gxp_fan_ctrl",
|
||||
drvdata,
|
||||
|
||||
@@ -57,8 +57,8 @@ struct hwmon_device_attribute {
|
||||
};
|
||||
|
||||
#define to_hwmon_attr(d) \
|
||||
container_of(d, struct hwmon_device_attribute, dev_attr)
|
||||
#define to_dev_attr(a) container_of(a, struct device_attribute, attr)
|
||||
container_of_const(d, struct hwmon_device_attribute, dev_attr)
|
||||
#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr)
|
||||
|
||||
/*
|
||||
* Thermal zone information
|
||||
@@ -71,27 +71,27 @@ struct hwmon_thermal_data {
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
name_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
name_show(struct device *dev, const struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name);
|
||||
}
|
||||
static DEVICE_ATTR_RO(name);
|
||||
static const DEVICE_ATTR_RO(name);
|
||||
|
||||
static ssize_t
|
||||
label_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
label_show(struct device *dev, const struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label);
|
||||
}
|
||||
static DEVICE_ATTR_RO(label);
|
||||
static const DEVICE_ATTR_RO(label);
|
||||
|
||||
static struct attribute *hwmon_dev_attrs[] = {
|
||||
static const struct attribute *const hwmon_dev_attrs[] = {
|
||||
&dev_attr_name.attr,
|
||||
&dev_attr_label.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
const struct attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct hwmon_device *hdev = to_hwmon_device(dev);
|
||||
@@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj,
|
||||
}
|
||||
|
||||
static const struct attribute_group hwmon_dev_attr_group = {
|
||||
.attrs = hwmon_dev_attrs,
|
||||
.is_visible = hwmon_dev_attr_is_visible,
|
||||
.attrs_const = hwmon_dev_attrs,
|
||||
.is_visible_const = hwmon_dev_attr_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group *hwmon_dev_attr_groups[] = {
|
||||
@@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static void hwmon_free_attrs(struct attribute **attrs)
|
||||
static void hwmon_free_attrs(const struct attribute *const *attrs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; attrs[i]; i++) {
|
||||
struct device_attribute *dattr = to_dev_attr(attrs[i]);
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr);
|
||||
const struct device_attribute *dattr = to_dev_attr(attrs[i]);
|
||||
const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr);
|
||||
|
||||
kfree(hattr);
|
||||
}
|
||||
@@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev)
|
||||
{
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
|
||||
if (hwdev->group.attrs)
|
||||
hwmon_free_attrs(hwdev->group.attrs);
|
||||
if (hwdev->group.attrs_const)
|
||||
hwmon_free_attrs(hwdev->group.attrs_const);
|
||||
kfree(hwdev->groups);
|
||||
kfree(hwdev->label);
|
||||
kfree(hwdev);
|
||||
@@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data)
|
||||
return dev->class == &hwmon_class;
|
||||
}
|
||||
|
||||
static ssize_t pec_show(struct device *dev, struct device_attribute *dummy,
|
||||
static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
@@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy,
|
||||
return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC));
|
||||
}
|
||||
|
||||
static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
|
||||
static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
@@ -390,7 +390,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
|
||||
return err;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(pec);
|
||||
static const DEVICE_ATTR_RW(pec);
|
||||
|
||||
static void hwmon_remove_pec(void *dev)
|
||||
{
|
||||
@@ -425,9 +425,9 @@ static int hwmon_pec_register(struct device *hdev)
|
||||
/* sysfs attribute management */
|
||||
|
||||
static ssize_t hwmon_attr_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
const struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
s64 val64;
|
||||
long val;
|
||||
@@ -450,10 +450,10 @@ static ssize_t hwmon_attr_show(struct device *dev,
|
||||
}
|
||||
|
||||
static ssize_t hwmon_attr_show_string(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
const struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
enum hwmon_sensor_types type = hattr->type;
|
||||
const char *s;
|
||||
@@ -473,10 +473,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev,
|
||||
}
|
||||
|
||||
static ssize_t hwmon_attr_store(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
const struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
long val;
|
||||
int ret;
|
||||
@@ -510,12 +510,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr)
|
||||
(type == hwmon_fan && attr == hwmon_fan_label);
|
||||
}
|
||||
|
||||
static struct attribute *hwmon_genattr(const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr,
|
||||
int index,
|
||||
const char *template,
|
||||
const struct hwmon_ops *ops)
|
||||
static const struct attribute *hwmon_genattr(const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr,
|
||||
int index,
|
||||
const char *template,
|
||||
const struct hwmon_ops *ops)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr;
|
||||
struct device_attribute *dattr;
|
||||
@@ -552,8 +552,8 @@ static struct attribute *hwmon_genattr(const void *drvdata,
|
||||
hattr->ops = ops;
|
||||
|
||||
dattr = &hattr->dev_attr;
|
||||
dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show;
|
||||
dattr->store = hwmon_attr_store;
|
||||
dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show;
|
||||
dattr->store_const = hwmon_attr_store;
|
||||
|
||||
a = &dattr->attr;
|
||||
sysfs_attr_init(a);
|
||||
@@ -831,7 +831,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
||||
}
|
||||
|
||||
static int hwmon_genattrs(const void *drvdata,
|
||||
struct attribute **attrs,
|
||||
const struct attribute **attrs,
|
||||
const struct hwmon_ops *ops,
|
||||
const struct hwmon_channel_info *info)
|
||||
{
|
||||
@@ -850,7 +850,7 @@ static int hwmon_genattrs(const void *drvdata,
|
||||
u32 attr;
|
||||
|
||||
while (attr_mask) {
|
||||
struct attribute *a;
|
||||
const struct attribute *a;
|
||||
|
||||
attr = __ffs(attr_mask);
|
||||
attr_mask &= ~BIT(attr);
|
||||
@@ -869,11 +869,11 @@ static int hwmon_genattrs(const void *drvdata,
|
||||
return aindex;
|
||||
}
|
||||
|
||||
static struct attribute **
|
||||
static const struct attribute **
|
||||
__hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip)
|
||||
{
|
||||
int ret, i, aindex = 0, nattrs = 0;
|
||||
struct attribute **attrs;
|
||||
const struct attribute **attrs;
|
||||
|
||||
for (i = 0; chip->info[i]; i++)
|
||||
nattrs += hwmon_num_channel_attrs(chip->info[i]);
|
||||
@@ -928,7 +928,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||
hdev = &hwdev->dev;
|
||||
|
||||
if (chip) {
|
||||
struct attribute **attrs;
|
||||
const struct attribute **attrs;
|
||||
int ngroups = 2; /* terminating NULL plus &hwdev->groups */
|
||||
|
||||
if (groups)
|
||||
@@ -947,7 +947,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||
goto free_hwmon;
|
||||
}
|
||||
|
||||
hwdev->group.attrs = attrs;
|
||||
hwdev->group.attrs_const = attrs;
|
||||
ngroups = 0;
|
||||
hwdev->groups[ngroups++] = &hwdev->group;
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ static const struct regmap_config ina2xx_regmap_config = {
|
||||
enum ina2xx_ids {
|
||||
ina219,
|
||||
ina226,
|
||||
ina232,
|
||||
ina234,
|
||||
ina260,
|
||||
sy24655
|
||||
@@ -197,6 +198,20 @@ static const struct ina2xx_config ina2xx_config[] = {
|
||||
.current_shift = 4,
|
||||
.has_update_interval = true,
|
||||
},
|
||||
[ina232] = {
|
||||
.config_default = INA226_CONFIG_DEFAULT,
|
||||
.calibration_value = 2048,
|
||||
.shunt_div = 400,
|
||||
.shunt_voltage_shift = 0,
|
||||
.bus_voltage_shift = 0,
|
||||
.bus_voltage_lsb = 1600,
|
||||
.power_lsb_factor = 32,
|
||||
.has_alerts = true,
|
||||
.has_ishunt = false,
|
||||
.has_power_average = false,
|
||||
.current_shift = 0,
|
||||
.has_update_interval = true,
|
||||
},
|
||||
[ina260] = {
|
||||
.config_default = INA260_CONFIG_DEFAULT,
|
||||
.shunt_div = 400,
|
||||
@@ -1014,6 +1029,7 @@ static const struct i2c_device_id ina2xx_id[] = {
|
||||
{ .name = "ina226", .driver_data = ina226 },
|
||||
{ .name = "ina230", .driver_data = ina226 },
|
||||
{ .name = "ina231", .driver_data = ina226 },
|
||||
{ .name = "ina232", .driver_data = ina232 },
|
||||
{ .name = "ina234", .driver_data = ina234 },
|
||||
{ .name = "ina260", .driver_data = ina260 },
|
||||
{ .name = "sy24655", .driver_data = sy24655 },
|
||||
@@ -1046,6 +1062,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
|
||||
.compatible = "ti,ina231",
|
||||
.data = (void *)ina226
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ina232",
|
||||
.data = (void *)ina232
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ina234",
|
||||
.data = (void *)ina234
|
||||
|
||||
@@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = {
|
||||
"Tccd10",
|
||||
"Tccd11",
|
||||
"Tccd12",
|
||||
"Tccd13",
|
||||
"Tccd14",
|
||||
"Tccd15",
|
||||
"Tccd16",
|
||||
};
|
||||
|
||||
static int k10temp_read_labels(struct device *dev,
|
||||
@@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
|
||||
if (*val < 0 && !data->disp_negative)
|
||||
*val = 0;
|
||||
break;
|
||||
case 2 ... 13: /* Tccd{1-12} */
|
||||
case 2 ... 17: /* Tccd{1-16} */
|
||||
ret = read_ccd_temp_reg(data, channel - 2, ®val);
|
||||
|
||||
if (ret)
|
||||
@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = {
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
NULL
|
||||
};
|
||||
@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
}
|
||||
} else if (boot_cpu_data.x86 == 0x1a) {
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x00 ... 0x2f: /* Zen5 Turin */
|
||||
data->ccd_offset = 0x1F0;
|
||||
k10temp_get_ccd_support(data, 16);
|
||||
break;
|
||||
case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */
|
||||
data->ccd_offset = 0x308;
|
||||
k10temp_get_ccd_support(data, 8);
|
||||
|
||||
460
drivers/hwmon/kb9002.c
Normal file
460
drivers/hwmon/kb9002.c
Normal file
@@ -0,0 +1,460 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Kandou KB9002 PCIe 5.0 retimer hwmon driver.
|
||||
*
|
||||
* The retimer exposes a system management bus (SMBus 3.0 with PEC)
|
||||
* target for firmware-managed status registers. This driver assumes
|
||||
* the chip is strapped to SMBus mode and exports the aggregated
|
||||
* maximum die temperature as hwmon temp1_input (millidegrees Celsius)
|
||||
* plus the firmware version and boot status under debugfs.
|
||||
*
|
||||
* The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch
|
||||
* the host interface) carries the 32-bit register address and data
|
||||
* big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read)
|
||||
* carries the register address and returned data little-endian.
|
||||
*
|
||||
* Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD).
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#define KB9002_DEV_NAME "kb9002"
|
||||
|
||||
/*
|
||||
* SMBus read command codes. Each read is a two-phase PEC-protected
|
||||
* transaction: prime writes the target address, data reads it back with
|
||||
* the register contents. FW reads use a 16-bit address, HW reads 32-bit.
|
||||
*/
|
||||
#define KB9002_CC_FW_READ_PRIME 0x82
|
||||
#define KB9002_CC_FW_READ_DATA 0x81
|
||||
#define KB9002_CC_HW_READ_PRIME 0x8a
|
||||
#define KB9002_CC_HW_READ_DATA 0x89
|
||||
|
||||
/* Firmware register offsets (16-bit). */
|
||||
#define KB9002_FW_REG_VID 0x0004
|
||||
#define KB9002_FW_REG_FW_VERSION 0x0500
|
||||
#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550
|
||||
|
||||
#define KB9002_VID_MASK GENMASK(31, 16)
|
||||
#define KB9002_VID_KANDOU 0x1e6f
|
||||
|
||||
/* Firmware boot status: 0xe8 in the top byte means init completed OK. */
|
||||
#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008
|
||||
#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8
|
||||
|
||||
/*
|
||||
* Hardware registers reached over raw I2C (32-bit addressing). The
|
||||
* host-interface bit selects SMBus (set) vs raw-I2C target; parts
|
||||
* strapped to raw I2C need it set before SMBus access works.
|
||||
*/
|
||||
#define KB9002_HW_REG_REVID 0x00480004
|
||||
#define KB9002_HW_REG_HOST_IF 0x00480008
|
||||
#define KB9002_HOST_IF_SMBUS BIT(1)
|
||||
|
||||
#define KB9002_REVID_MASK GENMASK(7, 0)
|
||||
#define KB9002_REVID_B0 0x10
|
||||
#define KB9002_REVID_B1 0x11
|
||||
|
||||
/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */
|
||||
#define KB9002_REVID_READ_RETRIES 16
|
||||
|
||||
/* Temperature: 32-bit Q16.16 absolute Kelvin. */
|
||||
#define KB9002_TEMP_FRAC_BITS 16
|
||||
#define KB9002_ABS_ZERO_MILLI_C (-273150)
|
||||
|
||||
/* Firmware takes up to ~2s to respond after a host-interface change. */
|
||||
#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC)
|
||||
#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC)
|
||||
|
||||
struct kb9002_data {
|
||||
struct i2c_client *client;
|
||||
struct device *hwmon_dev;
|
||||
};
|
||||
|
||||
/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */
|
||||
static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val)
|
||||
{
|
||||
u8 addr[4];
|
||||
u8 rbuf[4];
|
||||
struct i2c_msg msgs[2] = {
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = sizeof(addr),
|
||||
.buf = addr,
|
||||
},
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = sizeof(rbuf),
|
||||
.buf = rbuf,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
put_unaligned_be32(reg, addr);
|
||||
|
||||
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != ARRAY_SIZE(msgs))
|
||||
return -EIO;
|
||||
|
||||
*val = get_unaligned_be32(rbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */
|
||||
static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val)
|
||||
{
|
||||
u8 buf[8];
|
||||
struct i2c_msg msg = {
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = sizeof(buf),
|
||||
.buf = buf,
|
||||
};
|
||||
int ret;
|
||||
|
||||
put_unaligned_be32(reg, &buf[0]);
|
||||
put_unaligned_be32(val, &buf[4]);
|
||||
|
||||
ret = i2c_transfer(client->adapter, &msg, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != 1)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the silicon revision ID. A fresh FIFO may start with a stray
|
||||
* 0xff that shifts the result, so drain one byte between retries until
|
||||
* the top byte is no longer 0xff.
|
||||
*/
|
||||
static int kb9002_read_revid(struct i2c_client *client, u32 *revid)
|
||||
{
|
||||
u8 dummy;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) {
|
||||
ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid);
|
||||
if (ret)
|
||||
return ret;
|
||||
if ((*revid >> 24) != 0xff)
|
||||
return 0;
|
||||
/* Drain one byte from the chip to re-align the I2C FIFO. */
|
||||
i2c_master_recv(client, &dummy, 1);
|
||||
}
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a 32-bit firmware register over SMBus: block-write the 16-bit LE
|
||||
* address, then block-read the echoed address plus 4 LE data bytes.
|
||||
*/
|
||||
static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
u8 addr[2];
|
||||
u8 rbuf[I2C_SMBUS_BLOCK_MAX];
|
||||
int ret;
|
||||
|
||||
put_unaligned_le16(reg, addr);
|
||||
|
||||
ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME,
|
||||
sizeof(addr), addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < (int)(sizeof(addr) + sizeof(*val)))
|
||||
return -EIO;
|
||||
|
||||
*val = get_unaligned_le32(&rbuf[sizeof(addr)]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */
|
||||
static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
u8 addr[4];
|
||||
u8 rbuf[I2C_SMBUS_BLOCK_MAX];
|
||||
int ret;
|
||||
|
||||
put_unaligned_le32(reg, addr);
|
||||
|
||||
ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME,
|
||||
sizeof(addr), addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < (int)(sizeof(addr) + sizeof(*val)))
|
||||
return -EIO;
|
||||
|
||||
*val = get_unaligned_le32(&rbuf[sizeof(addr)]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch the host interface from raw-I2C to SMBus and wait for firmware
|
||||
* to come back up. Called only when SMBus access failed in probe, i.e.
|
||||
* the chip is strapped to raw-I2C mode. Confirms the revision, sets the
|
||||
* SMBus-mode bit, then polls until firmware responds again.
|
||||
*/
|
||||
static int kb9002_enable_smbus_target(struct kb9002_data *data)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
u32 revid;
|
||||
u32 val;
|
||||
int op_ret;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"raw I2C required to switch to SMBus mode\n");
|
||||
|
||||
ret = kb9002_read_revid(client, &revid);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"revision ID read failed\n");
|
||||
|
||||
switch (FIELD_GET(KB9002_REVID_MASK, revid)) {
|
||||
case KB9002_REVID_B0:
|
||||
case KB9002_REVID_B1:
|
||||
break;
|
||||
default:
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"unsupported revision ID 0x%08x\n", revid);
|
||||
}
|
||||
|
||||
ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"host interface read failed\n");
|
||||
|
||||
val |= KB9002_HOST_IF_SMBUS;
|
||||
|
||||
ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"host interface write failed\n");
|
||||
|
||||
/* Wait until firmware re-initialisation completes. */
|
||||
ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0,
|
||||
KB9002_FW_READY_POLL_US,
|
||||
KB9002_FW_READY_TIMEOUT_US, true,
|
||||
data, KB9002_FW_REG_VID, &val);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"firmware not responding over SMBus\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */
|
||||
static long kb9002_temp_to_milli_c(u32 raw)
|
||||
{
|
||||
s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS;
|
||||
|
||||
return (long)milli_k + KB9002_ABS_ZERO_MILLI_C;
|
||||
}
|
||||
|
||||
static int kb9002_read_temp(struct kb9002_data *data, long *val)
|
||||
{
|
||||
u32 raw;
|
||||
int ret;
|
||||
|
||||
ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = kb9002_temp_to_milli_c(raw);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t kb9002_is_visible(const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
return 0444;
|
||||
}
|
||||
|
||||
static int kb9002_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct kb9002_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (type == hwmon_temp && attr == hwmon_temp_input)
|
||||
return kb9002_read_temp(data, val);
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops kb9002_hwmon_ops = {
|
||||
.is_visible = kb9002_is_visible,
|
||||
.read = kb9002_read,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info * const kb9002_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info kb9002_chip_info = {
|
||||
.ops = &kb9002_hwmon_ops,
|
||||
.info = kb9002_hwmon_info,
|
||||
};
|
||||
|
||||
static int kb9002_fw_version_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct kb9002_data *data = s->private;
|
||||
u32 ver;
|
||||
int ret;
|
||||
|
||||
guard(hwmon_lock)(data->hwmon_dev);
|
||||
|
||||
ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
seq_printf(s, "%u.%02u.%02u.%u\n",
|
||||
(ver >> 24) & 0xff, (ver >> 16) & 0xff,
|
||||
(ver >> 8) & 0xff, (ver >> 0) & 0xff);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version);
|
||||
|
||||
static int kb9002_fw_load_status_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct kb9002_data *data = s->private;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
guard(hwmon_lock)(data->hwmon_dev);
|
||||
|
||||
ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
seq_printf(s, "%s\n",
|
||||
(status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ?
|
||||
"normal" : "abnormal");
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status);
|
||||
|
||||
static void kb9002_debugfs_init(struct kb9002_data *data)
|
||||
{
|
||||
struct dentry *dir = data->client->debugfs;
|
||||
|
||||
debugfs_create_file("fw_ver", 0444, dir, data,
|
||||
&kb9002_fw_version_fops);
|
||||
debugfs_create_file("fw_load_status", 0444, dir, data,
|
||||
&kb9002_fw_load_status_fops);
|
||||
}
|
||||
|
||||
static int kb9002_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct kb9002_data *data;
|
||||
struct device *hwmon_dev;
|
||||
u32 vid;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
I2C_FUNC_SMBUS_PEC))
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
|
||||
/* All firmware register accesses are PEC-protected. */
|
||||
client->flags |= I2C_CLIENT_PEC;
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
/*
|
||||
* Try SMBus first. If the chip is strapped to raw-I2C mode it
|
||||
* will not respond to SMBus framing, so fall back to switching
|
||||
* the host interface over raw I2C and retry.
|
||||
*/
|
||||
ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid);
|
||||
if (ret) {
|
||||
dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n",
|
||||
ret);
|
||||
ret = kb9002_enable_smbus_target(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"VID read failed after host-interface switch\n");
|
||||
}
|
||||
if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU)
|
||||
return dev_err_probe(dev, -ENODEV,
|
||||
"unexpected VID 0x%08x\n", vid);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME,
|
||||
data,
|
||||
&kb9002_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
data->hwmon_dev = hwmon_dev;
|
||||
kb9002_debugfs_init(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id kb9002_id[] = {
|
||||
{ .name = KB9002_DEV_NAME },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, kb9002_id);
|
||||
|
||||
static const struct of_device_id kb9002_of_match[] = {
|
||||
{ .compatible = "kandou,kb9002" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, kb9002_of_match);
|
||||
|
||||
static struct i2c_driver kb9002_driver = {
|
||||
.driver = {
|
||||
.name = KB9002_DEV_NAME,
|
||||
.of_match_table = kb9002_of_match,
|
||||
},
|
||||
.probe = kb9002_probe,
|
||||
.id_table = kb9002_id,
|
||||
};
|
||||
module_i2c_driver(kb9002_driver);
|
||||
|
||||
MODULE_AUTHOR("Andy Chung <andy.chung@amd.com>");
|
||||
MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -101,6 +101,7 @@
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
@@ -108,7 +109,7 @@
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
@@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lm90_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused lm90_of_match[] = {
|
||||
static const struct of_device_id lm90_of_match[] = {
|
||||
{
|
||||
.compatible = "adi,adm1032",
|
||||
.data = (void *)adm1032
|
||||
@@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data)
|
||||
|
||||
static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||
{
|
||||
struct device_node *np = client->dev.of_node;
|
||||
int config, convrate;
|
||||
|
||||
if (data->flags & LM90_HAVE_CONVRATE) {
|
||||
@@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||
|
||||
/* Check Temperature Range Select */
|
||||
if (data->flags & LM90_HAVE_EXTENDED_TEMP) {
|
||||
if (of_property_read_bool(np, "ti,extended-range-enable"))
|
||||
if (device_property_read_bool(&client->dev, "ti,extended-range-enable"))
|
||||
config |= 0x04;
|
||||
if (!(config & 0x04))
|
||||
data->flags &= ~LM90_HAVE_EXTENDED_TEMP;
|
||||
@@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static int lm90_probe_channel_from_dt(struct i2c_client *client,
|
||||
struct device_node *child,
|
||||
struct lm90_data *data)
|
||||
static int lm90_probe_channel(struct i2c_client *client,
|
||||
struct fwnode_handle *child,
|
||||
struct lm90_data *data)
|
||||
{
|
||||
u32 id;
|
||||
s32 val;
|
||||
int err;
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
err = of_property_read_u32(child, "reg", &id);
|
||||
err = fwnode_property_read_u32(child, "reg", &id);
|
||||
if (err) {
|
||||
dev_err(dev, "missing reg property of %pOFn\n", child);
|
||||
dev_err(dev, "missing reg property of %pfw\n", child);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (id >= MAX_CHANNELS) {
|
||||
dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child);
|
||||
dev_err(dev, "invalid reg property value %d in %pfw\n", id, child);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = of_property_read_string(child, "label", &data->channel_label[id]);
|
||||
err = fwnode_property_read_string(child, "label", &data->channel_label[id]);
|
||||
if (err == -ENODATA || err == -EILSEQ) {
|
||||
dev_err(dev, "invalid label property in %pOFn\n", child);
|
||||
dev_err(dev, "invalid label property in %pfw\n", child);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (data->channel_label[id])
|
||||
data->channel_config[id] |= HWMON_T_LABEL;
|
||||
|
||||
err = of_property_read_s32(child, "temperature-offset-millicelsius", &val);
|
||||
/*
|
||||
* fwnode_property_read_u32() has no signed equivalent.
|
||||
* temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to
|
||||
* preserve negative offsets values (same behavior as the old of_property_read_s32()).
|
||||
*/
|
||||
err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val);
|
||||
if (!err) {
|
||||
if (id == 0) {
|
||||
dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n");
|
||||
@@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm90_parse_dt_channel_info(struct i2c_client *client,
|
||||
struct lm90_data *data)
|
||||
static int lm90_parse_channel_info(struct i2c_client *client,
|
||||
struct lm90_data *data)
|
||||
{
|
||||
int err;
|
||||
struct device *dev = &client->dev;
|
||||
const struct device_node *np = dev->of_node;
|
||||
|
||||
for_each_child_of_node_scoped(np, child) {
|
||||
if (strcmp(child->name, "channel"))
|
||||
device_for_each_child_node_scoped(dev, child) {
|
||||
if (!fwnode_name_eq(child, "channel"))
|
||||
continue;
|
||||
|
||||
err = lm90_probe_channel_from_dt(client, child, data);
|
||||
err = lm90_probe_channel(client, child, data);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
@@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client)
|
||||
/* Set maximum conversion rate */
|
||||
data->max_convrate = lm90_params[data->kind].max_convrate;
|
||||
|
||||
/* Parse device-tree channel information */
|
||||
if (client->dev.of_node) {
|
||||
err = lm90_parse_dt_channel_info(client, data);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
/* Parse channel information */
|
||||
err = lm90_parse_channel_info(client, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Initialize the LM90 chip */
|
||||
err = lm90_init_client(client, data);
|
||||
@@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client)
|
||||
err = devm_request_threaded_irq(dev, client->irq,
|
||||
NULL, lm90_irq_thread,
|
||||
IRQF_ONESHOT, "lm90", client);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "cannot request IRQ %d\n", client->irq);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "lm90",
|
||||
.of_match_table = of_match_ptr(lm90_of_match),
|
||||
.of_match_table = lm90_of_match,
|
||||
.pm = pm_sleep_ptr(&lm90_pm_ops),
|
||||
},
|
||||
.probe = lm90_probe,
|
||||
|
||||
@@ -1772,6 +1772,7 @@ static const struct of_device_id ltc4283_of_match[] = {
|
||||
{ .compatible = "adi,ltc4283" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ltc4283_of_match);
|
||||
|
||||
static const struct i2c_device_id ltc4283_i2c_id[] = {
|
||||
{ "ltc4283" },
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
#define MAX6621_DRV_NAME "max6621"
|
||||
#define MAX6621_TEMP_INPUT_REG_NUM 9
|
||||
#define MAX6621_TEMP_INPUT_MIN -127000
|
||||
#define MAX6621_TEMP_INPUT_MAX 128000
|
||||
#define MAX6621_TEMP_INPUT_MIN -128000
|
||||
#define MAX6621_TEMP_INPUT_MAX 127000
|
||||
#define MAX6621_TEMP_ALERT_CHAN_SHIFT 1
|
||||
|
||||
#define MAX6621_TEMP_S0D0_REG 0x00
|
||||
@@ -239,7 +239,7 @@ max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = (regval >> MAX6621_REG_TEMP_SHIFT) *
|
||||
*val = ((s16)regval >> MAX6621_REG_TEMP_SHIFT) *
|
||||
1000L;
|
||||
|
||||
break;
|
||||
@@ -254,7 +254,7 @@ max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = regval * 1000L;
|
||||
*val = (s16)regval * 1000L;
|
||||
|
||||
break;
|
||||
case hwmon_temp_crit_alarm:
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
* (0xd451)
|
||||
* nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3
|
||||
* (0xd429)
|
||||
* nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3
|
||||
* nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3
|
||||
* nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3
|
||||
*
|
||||
|
||||
@@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = {
|
||||
[nct6795] = "NCT6795D",
|
||||
[nct6796] = "NCT6796D",
|
||||
[nct6797] = "NCT6797D",
|
||||
[nct6798] = "NCT6798D",
|
||||
[nct6798] = "NCT6798D/NCT5585D",
|
||||
[nct6799] = "NCT6796D-S/NCT6799D-R",
|
||||
};
|
||||
|
||||
|
||||
@@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
|
||||
sprintf(name, "NPCM7XX-FAN-MD%d", i);
|
||||
ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr,
|
||||
0, name, (void *)data);
|
||||
if (ret) {
|
||||
dev_err(dev, "register IRQ fan%d failed\n", i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for_each_child_of_node_scoped(np, child) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers.
|
||||
* hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and
|
||||
* 2024 Elite all in one coolers.
|
||||
* X53 and Z53 in code refer to all models in their respective series (shortened for brevity).
|
||||
* 2023 models use the Z53 code paths.
|
||||
*
|
||||
@@ -25,6 +26,7 @@
|
||||
#define USB_PRODUCT_ID_Z53 0x3008
|
||||
#define USB_PRODUCT_ID_KRAKEN2023 0x300E
|
||||
#define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C
|
||||
#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012
|
||||
|
||||
enum kinds { X53, Z53, KRAKEN2023 } __packed;
|
||||
enum pwm_enable { off, manual, curve } __packed;
|
||||
@@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id
|
||||
priv->kind = KRAKEN2023;
|
||||
device_name = "kraken2023elite";
|
||||
break;
|
||||
case USB_PRODUCT_ID_KRAKEN2024_ELITE:
|
||||
priv->kind = KRAKEN2023;
|
||||
device_name = "kraken2024elite";
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
goto fail_and_close;
|
||||
@@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ struct peci_sensor_data {
|
||||
|
||||
/**
|
||||
* peci_sensor_need_update() - check whether sensor update is needed or not
|
||||
* @sensor: pointer to sensor data struct
|
||||
* @state: pointer to sensor state struct
|
||||
*
|
||||
* Return: true if update is needed, false if not.
|
||||
*/
|
||||
@@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state)
|
||||
|
||||
/**
|
||||
* peci_sensor_mark_updated() - mark the sensor is updated
|
||||
* @sensor: pointer to sensor data struct
|
||||
* @state: pointer to sensor state struct
|
||||
*/
|
||||
static inline void peci_sensor_mark_updated(struct peci_sensor_state *state)
|
||||
{
|
||||
|
||||
@@ -52,8 +52,8 @@ config SENSORS_ADM1275
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281,
|
||||
ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and
|
||||
Digital Power Monitors.
|
||||
ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C
|
||||
Hot-Swap Controller and Digital Power Monitors.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called adm1275.
|
||||
@@ -394,10 +394,10 @@ config SENSORS_MAX20751
|
||||
be called max20751.
|
||||
|
||||
config SENSORS_MAX20830
|
||||
tristate "Analog Devices MAX20830"
|
||||
tristate "Analog Devices MAX20830 and compatibles"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices MAX20830.
|
||||
Devices MAX20830, MAX20830C, and MAX20840C.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called max20830.
|
||||
@@ -434,8 +434,9 @@ config SENSORS_MAX34440
|
||||
tristate "Maxim MAX34440 and compatibles"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Maxim
|
||||
MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461.
|
||||
Other compatible are ADPM12160, and ADPM12200.
|
||||
MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and
|
||||
MAX34461. Other compatible devices are ADPM12160, ADPM12200, and
|
||||
ADPM12250.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called max34440.
|
||||
@@ -607,6 +608,15 @@ config SENSORS_MPQ7932
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called mpq7932.
|
||||
|
||||
config SENSORS_MPQ82D00
|
||||
tristate "MPS MPQ82D00"
|
||||
help
|
||||
If you say yes here you get hardware monitoring functionality support
|
||||
for power management IC MPS MPQ82D00.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called mpq82d00.
|
||||
|
||||
config SENSORS_MPQ8785
|
||||
tristate "MPS MPQ8785"
|
||||
help
|
||||
@@ -616,6 +626,37 @@ config SENSORS_MPQ8785
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called mpq8785.
|
||||
|
||||
config SENSORS_MPQ8646
|
||||
tristate "MPS MPQ8646"
|
||||
depends on REGULATOR || !REGULATOR
|
||||
depends on NVMEM || !NVMEM
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for the
|
||||
Monolithic Power Systems MPQ8646.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called mpq8646.
|
||||
|
||||
config SENSORS_MPQ8646_DEBUG_UNSAFE
|
||||
bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)"
|
||||
depends on SENSORS_MPQ8646 && DEBUG_FS
|
||||
default n
|
||||
help
|
||||
Expose additional WRITE-able debugfs files in the client's
|
||||
pmbus debugfs directory,
|
||||
/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/
|
||||
|
||||
These are provisioning and bring-up aids. On many designs the
|
||||
MPQ8646 powers the SoC core rail, so a wrong write can brown out
|
||||
or permanently mis-provision the board.
|
||||
|
||||
WARNING: Wrong register writes can and likely will physically
|
||||
damage or destroy the chip and/or the board.
|
||||
|
||||
It shall never be set in a production, shipping, or default configuration.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config SENSORS_PIM4328
|
||||
tristate "Flex PIM4328 and compatibles"
|
||||
help
|
||||
@@ -677,6 +718,25 @@ config SENSORS_STEF48H28
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called stef48h28.
|
||||
|
||||
config SENSORS_SQ24860
|
||||
tristate "Silergy SQ24860"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Silergy
|
||||
SQ24860 eFuse.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called sq24860.
|
||||
|
||||
config SENSORS_SQ24860_REGULATOR
|
||||
bool "Regulator support for SQ24860"
|
||||
depends on SENSORS_SQ24860 && REGULATOR
|
||||
default SENSORS_SQ24860
|
||||
help
|
||||
If you say yes here you get regulator support for Silergy SQ24860.
|
||||
The regulator is registered through the PMBus regulator framework and
|
||||
can be used to control the output exposed by the device.
|
||||
This option is only useful if regulator framework support is needed.
|
||||
|
||||
config SENSORS_STPDDC60
|
||||
tristate "ST STPDDC60"
|
||||
help
|
||||
@@ -767,6 +827,15 @@ config SENSORS_UCD9200
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9200.
|
||||
|
||||
config SENSORS_VT7505
|
||||
tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called vt7505.
|
||||
|
||||
config SENSORS_XDP710
|
||||
tristate "Infineon XDP710 family"
|
||||
help
|
||||
@@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called xdpe1a2g7b.
|
||||
|
||||
config SENSORS_XDPE1A2G7B_REGULATOR
|
||||
bool "Regulator support for XDPE1A2G7B and compatibles"
|
||||
depends on SENSORS_XDPE1A2G7B && REGULATOR
|
||||
help
|
||||
If you say yes here you get regulator support for Infineon
|
||||
XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers.
|
||||
|
||||
This enables the controllers to be used as regulator devices,
|
||||
providing voltage control through the regulator framework.
|
||||
|
||||
config SENSORS_XDPE122
|
||||
tristate "Infineon XDPE122 family"
|
||||
help
|
||||
|
||||
@@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o
|
||||
obj-$(CONFIG_SENSORS_MP9941) += mp9941.o
|
||||
obj-$(CONFIG_SENSORS_MP9945) += mp9945.o
|
||||
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
|
||||
obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o
|
||||
obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o
|
||||
obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o
|
||||
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
|
||||
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
|
||||
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
|
||||
obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o
|
||||
obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o
|
||||
obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o
|
||||
obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o
|
||||
obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o
|
||||
obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o
|
||||
@@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
||||
obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o
|
||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
|
||||
obj-$(CONFIG_SENSORS_VT7505) += vt7505.o
|
||||
obj-$(CONFIG_SENSORS_XDP710) += xdp710.o
|
||||
obj-$(CONFIG_SENSORS_XDP720) += xdp720.o
|
||||
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
|
||||
|
||||
@@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = {
|
||||
{ .name = "acbel_fsg032" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id);
|
||||
|
||||
static struct pmbus_driver_info acbel_fsg032_info = {
|
||||
.pages = 1,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
|
||||
adm1293, adm1294, sq24905c };
|
||||
adm1293, adm1294, bd12780, bd12790, sq24905c };
|
||||
|
||||
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
|
||||
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
|
||||
@@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
|
||||
#define ADM1278_VOUT_EN BIT(1)
|
||||
|
||||
#define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT)
|
||||
/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */
|
||||
#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)
|
||||
|
||||
#define ADM1293_IRANGE_25 0
|
||||
#define ADM1293_IRANGE_50 BIT(6)
|
||||
@@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18)
|
||||
* and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m.
|
||||
*
|
||||
* Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V)
|
||||
* -> m = 6770, R=-2 / m = 4062, R=-2
|
||||
* Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV)
|
||||
* -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R
|
||||
* Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72
|
||||
* -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three
|
||||
* Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1
|
||||
*/
|
||||
static const struct coefficients bd12790_coefficients[] = {
|
||||
[0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
|
||||
[1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
|
||||
[2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */
|
||||
[3] = { 664, 20480, -1 }, /* current, vsense range 30mV */
|
||||
[4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
|
||||
[5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
|
||||
[6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */
|
||||
[7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */
|
||||
[8] = { 42, 31880, -1 }, /* temperature */
|
||||
};
|
||||
|
||||
static const struct coefficients adm1275_coefficients[] = {
|
||||
[0] = { 19199, 0, -2 }, /* voltage, vrange set */
|
||||
[1] = { 6720, 0, -1 }, /* voltage, vrange not set */
|
||||
@@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = {
|
||||
{ .name = "adm1281", .driver_data = adm1281 },
|
||||
{ .name = "adm1293", .driver_data = adm1293 },
|
||||
{ .name = "adm1294", .driver_data = adm1294 },
|
||||
{ .name = "bd12780", .driver_data = bd12780 },
|
||||
{ .name = "bd12790", .driver_data = bd12790 },
|
||||
{ .name = "mc09c", .driver_data = sq24905c },
|
||||
{ }
|
||||
};
|
||||
@@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id);
|
||||
|
||||
/* Enable VOUT & TEMP1 if not enabled (disabled by default) */
|
||||
static int adm1275_enable_vout_temp(struct adm1275_data *data,
|
||||
struct i2c_client *client, int config)
|
||||
struct i2c_client *client, int config,
|
||||
u16 defconfig)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) {
|
||||
config |= ADM1278_PMON_DEFCONFIG;
|
||||
if ((config & defconfig) != defconfig) {
|
||||
config |= defconfig;
|
||||
ret = adm1275_write_pmon_config(data, client, config);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n");
|
||||
@@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
u32 shunt;
|
||||
u32 avg;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer);
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read Manufacturer ID\n");
|
||||
return ret;
|
||||
}
|
||||
if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) &&
|
||||
(ret != 2 || strncmp(block_buffer, "SY", 2))) {
|
||||
(ret != 2 || strncmp(block_buffer, "SY", 2)) &&
|
||||
(ret != 4 || strncmp(block_buffer, "ROHM", 4))) {
|
||||
dev_err(&client->dev, "Unsupported Manufacturer ID\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read Manufacturer Model\n");
|
||||
return ret;
|
||||
@@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
|
||||
mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
|
||||
mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
|
||||
mid->driver_data == bd12780 || mid->driver_data == bd12790 ||
|
||||
mid->driver_data == sq24905c)
|
||||
config_read_fn = i2c_smbus_read_word_data;
|
||||
else
|
||||
@@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
data->have_power_sampling = true;
|
||||
|
||||
coefficients = adm1272_coefficients;
|
||||
|
||||
vindex = (config & ADM1275_VRANGE) ? 1 : 0;
|
||||
cindex = (config & ADM1272_IRANGE) ? 3 : 2;
|
||||
/* pindex depends on the combination of the above */
|
||||
@@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
ret = adm1275_enable_vout_temp(data, client, config);
|
||||
ret = adm1275_enable_vout_temp(data, client, config,
|
||||
ADM1278_PMON_DEFCONFIG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (config & ADM1278_VIN_EN)
|
||||
info->func[0] |= PMBUS_HAVE_VIN;
|
||||
break;
|
||||
|
||||
/*
|
||||
* The BD12790 is almost identical to the adm1272. Only the defconfig
|
||||
* and coefficients have minor differences.
|
||||
*/
|
||||
case bd12790:
|
||||
data->have_vout = true;
|
||||
data->have_pin_max = true;
|
||||
data->have_temp_max = true;
|
||||
data->have_power_sampling = true;
|
||||
|
||||
coefficients = bd12790_coefficients;
|
||||
|
||||
vindex = (config & ADM1275_VRANGE) ? 1 : 0;
|
||||
cindex = (config & ADM1272_IRANGE) ? 3 : 2;
|
||||
/* pindex depends on the combination of the above */
|
||||
switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) {
|
||||
case 0:
|
||||
default:
|
||||
pindex = 4;
|
||||
break;
|
||||
case ADM1275_VRANGE:
|
||||
pindex = 5;
|
||||
break;
|
||||
case ADM1272_IRANGE:
|
||||
pindex = 6;
|
||||
break;
|
||||
case ADM1275_VRANGE | ADM1272_IRANGE:
|
||||
pindex = 7;
|
||||
break;
|
||||
}
|
||||
tindex = 8;
|
||||
|
||||
info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
ret = adm1275_enable_vout_temp(data, client, config,
|
||||
BD12780_PMON_DEFCONFIG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
ret = adm1275_enable_vout_temp(data, client, config);
|
||||
ret = adm1275_enable_vout_temp(data, client, config,
|
||||
ADM1278_PMON_DEFCONFIG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (config & ADM1278_VIN_EN)
|
||||
info->func[0] |= PMBUS_HAVE_VIN;
|
||||
break;
|
||||
|
||||
/*
|
||||
* The BD12780 is almost functionally identical with the adm1278 above.
|
||||
* Only differences visible to the driver are lack of TSFILT bits and
|
||||
* different identification register contents.
|
||||
*/
|
||||
case bd12780:
|
||||
data->have_vout = true;
|
||||
data->have_pin_max = true;
|
||||
data->have_temp_max = true;
|
||||
data->have_power_sampling = true;
|
||||
|
||||
coefficients = adm1278_coefficients;
|
||||
vindex = 0;
|
||||
cindex = 1;
|
||||
pindex = 2;
|
||||
tindex = 3;
|
||||
|
||||
info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
ret = adm1275_enable_vout_temp(data, client, config,
|
||||
BD12780_PMON_DEFCONFIG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (config & ADM1278_VIN_EN)
|
||||
info->func[0] |= PMBUS_HAVE_VIN;
|
||||
|
||||
break;
|
||||
case adm1293:
|
||||
case adm1294:
|
||||
data->have_iout_min = true;
|
||||
@@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client)
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static const struct of_device_id adm1275_of_match[] = {
|
||||
{ .compatible = "adi,adm1075", },
|
||||
{ .compatible = "adi,adm1272", },
|
||||
{ .compatible = "adi,adm1273", },
|
||||
{ .compatible = "adi,adm1275", },
|
||||
{ .compatible = "adi,adm1276", },
|
||||
{ .compatible = "adi,adm1278", },
|
||||
{ .compatible = "adi,adm1281", },
|
||||
{ .compatible = "adi,adm1293", },
|
||||
{ .compatible = "adi,adm1294", },
|
||||
{ .compatible = "rohm,bd12780", },
|
||||
{ .compatible = "rohm,bd12790", },
|
||||
{ .compatible = "silergy,mc09c", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adm1275_of_match);
|
||||
|
||||
static struct i2c_driver adm1275_driver = {
|
||||
.driver = {
|
||||
.name = "adm1275",
|
||||
.of_match_table = adm1275_of_match,
|
||||
},
|
||||
.probe = adm1275_probe,
|
||||
.id_table = adm1275_id,
|
||||
|
||||
@@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client)
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||||
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_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) {
|
||||
dev_err(&client->dev, "MFR_MODEL unrecognised\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to read PMBUS_MFR_MODEL\n");
|
||||
if (ret != 1 || buf[0] != 0x2d)
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"MFR_MODEL unrecognised\n");
|
||||
|
||||
return pmbus_do_probe(client, &ir36021_info);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i };
|
||||
#define LM25066_READ_AVG_PIN 0xdf
|
||||
|
||||
#define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */
|
||||
#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */
|
||||
|
||||
#define LM25066_SAMPLES_FOR_AVG_MAX 4096
|
||||
|
||||
@@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client)
|
||||
|
||||
data->id = (enum chips)(unsigned long)i2c_get_match_data(client);
|
||||
|
||||
if (data->id != lm25056) {
|
||||
int config_new = config;
|
||||
const char *cl_setting;
|
||||
int ret;
|
||||
|
||||
if (!of_property_read_string(client->dev.of_node,
|
||||
"ti,current-range", &cl_setting)) {
|
||||
config_new |= LM25066_DEV_SETUP_CL_CFG;
|
||||
if (strcmp(cl_setting, "high") == 0) {
|
||||
if (data->id == lm25066)
|
||||
config_new |= LM25066_DEV_SETUP_CL;
|
||||
else
|
||||
config_new &= ~LM25066_DEV_SETUP_CL;
|
||||
} else if (strcmp(cl_setting, "low") == 0) {
|
||||
if (data->id == lm25066)
|
||||
config_new &= ~LM25066_DEV_SETUP_CL;
|
||||
else
|
||||
config_new |= LM25066_DEV_SETUP_CL;
|
||||
} else {
|
||||
dev_err(&client->dev,
|
||||
"invalid current-range setting: %s\n",
|
||||
cl_setting);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (config_new != config) {
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
LM25066_DEVICE_SETUP,
|
||||
config_new);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
config = config_new;
|
||||
}
|
||||
}
|
||||
|
||||
info = &data->info;
|
||||
|
||||
info->pages = 1;
|
||||
|
||||
@@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client)
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3)))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
for (id = <c2978_id[0]; strlen(id->name); id++) {
|
||||
@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client)
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
|
||||
sizeof(buf), buf);
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
|
||||
buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!strncmp(buf + 1, "LT7170", 6) ||
|
||||
!strncmp(buf + 1, "LT7170-1", 8))
|
||||
if (ret < 6)
|
||||
return -ENODEV;
|
||||
|
||||
if (!strncmp(buf, "LT7170", 6))
|
||||
return lt7170;
|
||||
if (!strncmp(buf + 1, "LT7171", 6) ||
|
||||
!strncmp(buf + 1, "LT7171-1", 8))
|
||||
if (!strncmp(buf, "LT7171", 6))
|
||||
return lt7171;
|
||||
|
||||
return -ENODEV;
|
||||
|
||||
@@ -7,12 +7,69 @@
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define MAX20830_IC_DEVICE_ID_LENGTH 9
|
||||
|
||||
struct max20830_data {
|
||||
struct pmbus_driver_info info;
|
||||
u32 vout_rfb1;
|
||||
u32 vout_rfb2;
|
||||
};
|
||||
|
||||
static const char * const supported_chip_ids[] = {
|
||||
"MAX20830",
|
||||
"MAX20830C",
|
||||
"MAX20840C",
|
||||
};
|
||||
|
||||
/*
|
||||
* MAX20830 only supports READ_VOUT for VOUT monitoring.
|
||||
*
|
||||
* Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not
|
||||
* supported by this driver and return -ENODATA. This means sysfs attributes
|
||||
* like in1_max, in1_crit, etc. will not be available. Only in1_input (the
|
||||
* scaled output voltage) is supported.
|
||||
*
|
||||
* MAX20830 uses an external resistor divider for voltage sensing:
|
||||
* - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin
|
||||
* - READ_VOUT reports the feedback voltage, which needs to be scaled for actual
|
||||
* output voltage
|
||||
*
|
||||
* Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2)
|
||||
*
|
||||
* If regulator support is added in the future, some adjustments are needed to
|
||||
* ensure correct feedback voltages are set.
|
||||
*/
|
||||
static int max20830_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct max20830_data *data = container_of(info, struct max20830_data, info);
|
||||
int ret;
|
||||
u64 temp;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_READ_VOUT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Apply voltage divider scaling if resistors are non-zero */
|
||||
if (data->vout_rfb1 && data->vout_rfb2) {
|
||||
temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2;
|
||||
temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2);
|
||||
ret = clamp_val(temp, 0, 0xFFFF);
|
||||
}
|
||||
return ret;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info max20830_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
@@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = {
|
||||
PMBUS_HAVE_TEMP |
|
||||
PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = max20830_read_word_data,
|
||||
.have_pmbus_revision = true,
|
||||
.pmbus_revision = PMBUS_REV_13,
|
||||
};
|
||||
|
||||
static int max20830_probe(struct i2c_client *client)
|
||||
{
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {};
|
||||
int ret;
|
||||
struct max20830_data *data;
|
||||
int i, ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) &&
|
||||
!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK))
|
||||
return -ENODEV;
|
||||
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Use i2c_smbus_read_block_data() if supported, otherwise fall back
|
||||
* to i2c_smbus_read_i2c_block_data() to support I2C controllers
|
||||
* which do not support SMBus block reads.
|
||||
*/
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
|
||||
/* Reads 9 Data bytes from MAX20830 */
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to read IC_DEVICE_ID\n");
|
||||
} else {
|
||||
/* Reads 1 length byte + 9 Data bytes from MAX20830 */
|
||||
ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
|
||||
MAX20830_IC_DEVICE_ID_LENGTH + 1,
|
||||
buf);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to read IC_DEVICE_ID\n");
|
||||
/*
|
||||
* Moves data forward, removing the length byte, this is to
|
||||
* match the format of i2c_smbus_read_block_data().
|
||||
* Also adjust return value to reflect length byte removal.
|
||||
*/
|
||||
memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH);
|
||||
ret = ret - 1;
|
||||
}
|
||||
data->info = max20830_info;
|
||||
|
||||
/*
|
||||
* MAX20830 IC_DEVICE_ID sends string data "MAX20830\0".
|
||||
* Return value should at least be 9 bytes of data.
|
||||
*/
|
||||
/* Read optional voltage divider resistor values */
|
||||
device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1);
|
||||
device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2);
|
||||
|
||||
ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to read IC_DEVICE_ID\n");
|
||||
|
||||
/* Verify we read the expected number of bytes */
|
||||
if (ret < MAX20830_IC_DEVICE_ID_LENGTH)
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n",
|
||||
ret);
|
||||
"IC_DEVICE_ID too short: expected %d bytes, got %d\n",
|
||||
MAX20830_IC_DEVICE_ID_LENGTH, ret);
|
||||
|
||||
/* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */
|
||||
buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0';
|
||||
if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1))
|
||||
/* Null-terminate the string */
|
||||
buf[ret] = '\0';
|
||||
|
||||
/* Verify the device ID matches what we expect */
|
||||
for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) {
|
||||
if (!strcmp(buf, supported_chip_ids[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
/* No match found - unsupported device */
|
||||
if (i == ARRAY_SIZE(supported_chip_ids))
|
||||
return dev_err_probe(&client->dev, -ENODEV,
|
||||
"Unsupported device: '%s'\n", buf);
|
||||
"Unsupported device: '%*pE'\n", ret, buf);
|
||||
|
||||
return pmbus_do_probe(client, &max20830_info);
|
||||
return pmbus_do_probe(client, &data->info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max20830_id[] = {
|
||||
|
||||
@@ -23,6 +23,7 @@ enum chips {
|
||||
max34441,
|
||||
max34446,
|
||||
max34451,
|
||||
max34452,
|
||||
max34460,
|
||||
max34461,
|
||||
};
|
||||
@@ -59,6 +60,7 @@ enum chips {
|
||||
#define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A
|
||||
|
||||
#define MAX34451ETNA6_MFR_REV 0x0012
|
||||
#define MAX34451ETNA8_MFR_REV 0x0014
|
||||
|
||||
#define MAX34451_MFR_CHANNEL_CONFIG 0xe4
|
||||
#define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f
|
||||
@@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
|
||||
case PMBUS_UT_FAULT_LIMIT:
|
||||
case PMBUS_MFR_MAX_TEMP_1:
|
||||
/*
|
||||
* MAX34451/ADPM family do not support VIN/IIN limit registers,
|
||||
* manufacturer-specific min/max registers, or undercurrent/
|
||||
* undertemperature fault limits. Accessing these triggers CML
|
||||
* error and asserts ALERT.
|
||||
* MAX34451/MAX34452/ADPM family do not support VIN/IIN limit
|
||||
* registers, manufacturer-specific min/max registers, or
|
||||
* undercurrent/undertemperature fault limits. Accessing these
|
||||
* triggers CML error and asserts ALERT.
|
||||
*/
|
||||
if (data->id == max34451 || data->id == adpm12160 ||
|
||||
data->id == adpm12200 || data->id == adpm12250)
|
||||
if (data->id == max34451 || data->id == max34452 ||
|
||||
data->id == adpm12160 || data->id == adpm12200 ||
|
||||
data->id == adpm12250)
|
||||
return -ENXIO;
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
case PMBUS_VOUT_OV_WARN_LIMIT:
|
||||
if (data->id == max34452)
|
||||
return -ENXIO;
|
||||
return -ENODATA;
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
MAX34440_MFR_VOUT_MIN);
|
||||
@@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
|
||||
break;
|
||||
case PMBUS_VIRT_READ_IOUT_AVG:
|
||||
if (data->id != max34446 && data->id != max34451 &&
|
||||
data->id != adpm12160 && data->id != adpm12200 &&
|
||||
data->id != adpm12250)
|
||||
data->id != max34452 && data->id != adpm12160 &&
|
||||
data->id != adpm12200 && data->id != adpm12250)
|
||||
return -ENXIO;
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
MAX34446_MFR_IOUT_AVG);
|
||||
@@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
|
||||
ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit,
|
||||
word);
|
||||
break;
|
||||
case PMBUS_VOUT_OV_WARN_LIMIT:
|
||||
if (data->id == max34452)
|
||||
return -ENXIO;
|
||||
return -ENODATA;
|
||||
case PMBUS_VIRT_RESET_POUT_HISTORY:
|
||||
ret = pmbus_write_word_data(client, page,
|
||||
MAX34446_MFR_POUT_PEAK, 0);
|
||||
@@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
|
||||
ret = pmbus_write_word_data(client, page,
|
||||
MAX34440_MFR_IOUT_PEAK, 0);
|
||||
if (!ret && (data->id == max34446 || data->id == max34451 ||
|
||||
data->id == adpm12160 || data->id == adpm12200 ||
|
||||
data->id == adpm12250))
|
||||
data->id == max34452 || data->id == adpm12160 ||
|
||||
data->id == adpm12200 || data->id == adpm12250))
|
||||
ret = pmbus_write_word_data(client, page,
|
||||
MAX34446_MFR_IOUT_AVG, 0);
|
||||
|
||||
@@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
case PMBUS_STATUS_BYTE:
|
||||
case PMBUS_STATUS_OTHER:
|
||||
/*
|
||||
* MAX34451/ADPM family do not support STATUS_BYTE or
|
||||
* MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or
|
||||
* STATUS_OTHER registers. Accessing them triggers CML
|
||||
* error and asserts ALERT.
|
||||
*/
|
||||
if (data->id == max34451 || data->id == adpm12160 ||
|
||||
data->id == adpm12200 || data->id == adpm12250)
|
||||
if (data->id == max34451 || data->id == max34452 ||
|
||||
data->id == adpm12160 || data->id == adpm12200 ||
|
||||
data->id == adpm12250)
|
||||
return -ENXIO;
|
||||
return -ENODATA;
|
||||
default:
|
||||
@@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page,
|
||||
case PMBUS_STATUS_BYTE:
|
||||
case PMBUS_STATUS_OTHER:
|
||||
/*
|
||||
* MAX34451/ADPM family do not support STATUS_BYTE or
|
||||
* MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or
|
||||
* STATUS_OTHER registers. Writing to them triggers CML
|
||||
* error and asserts ALERT.
|
||||
*/
|
||||
if (data->id == max34451 || data->id == adpm12160 ||
|
||||
data->id == adpm12200 || data->id == adpm12250)
|
||||
if (data->id == max34451 || data->id == max34452 ||
|
||||
data->id == adpm12160 || data->id == adpm12200 ||
|
||||
data->id == adpm12250)
|
||||
return -ENXIO;
|
||||
return -ENODATA;
|
||||
default:
|
||||
@@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
if (rv >= MAX34451ETNA6_MFR_REV) {
|
||||
if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) {
|
||||
max34451_na6 = true;
|
||||
data->info.format[PSC_VOLTAGE_IN] = direct;
|
||||
data->info.format[PSC_CURRENT_IN] = direct;
|
||||
data->info.m[PSC_VOLTAGE_IN] = 1;
|
||||
|
||||
data->info.b[PSC_VOLTAGE_IN] = 0;
|
||||
data->info.R[PSC_VOLTAGE_IN] = 3;
|
||||
data->info.m[PSC_CURRENT_IN] = 1;
|
||||
data->info.b[PSC_CURRENT_IN] = 0;
|
||||
data->info.R[PSC_CURRENT_IN] = 2;
|
||||
if (rv >= MAX34451ETNA8_MFR_REV) {
|
||||
data->info.m[PSC_VOLTAGE_IN] = 125;
|
||||
data->info.R[PSC_VOLTAGE_IN] = 0;
|
||||
data->info.m[PSC_VOLTAGE_OUT] = 125;
|
||||
data->info.R[PSC_VOLTAGE_OUT] = 0;
|
||||
data->info.m[PSC_CURRENT_IN] = 250;
|
||||
data->info.R[PSC_CURRENT_IN] = -1;
|
||||
data->info.m[PSC_CURRENT_OUT] = 250;
|
||||
data->info.R[PSC_CURRENT_OUT] = -1;
|
||||
} else {
|
||||
data->info.m[PSC_VOLTAGE_IN] = 1;
|
||||
data->info.R[PSC_VOLTAGE_IN] = 3;
|
||||
data->info.m[PSC_CURRENT_IN] = 1;
|
||||
data->info.R[PSC_CURRENT_IN] = 2;
|
||||
}
|
||||
data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT;
|
||||
data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT;
|
||||
}
|
||||
@@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34452] = {
|
||||
.pages = 21,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.m[PSC_VOLTAGE_OUT] = 1,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
.R[PSC_VOLTAGE_OUT] = 3,
|
||||
.m[PSC_CURRENT_OUT] = 1,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
.R[PSC_CURRENT_OUT] = 2,
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
.R[PSC_TEMPERATURE] = 2,
|
||||
/* func 0-15 is set dynamically before probing */
|
||||
.func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_byte_data = max34451_read_byte_data,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_byte_data = max34451_write_byte_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34460] = {
|
||||
.pages = 18,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
@@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client)
|
||||
data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT;
|
||||
data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT;
|
||||
|
||||
if (data->id == max34451) {
|
||||
if (data->id == max34451 || data->id == max34452) {
|
||||
rv = max34451_set_supported_funcs(client, data);
|
||||
if (rv)
|
||||
return rv;
|
||||
@@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = {
|
||||
{ .name = "max34441", .driver_data = max34441 },
|
||||
{ .name = "max34446", .driver_data = max34446 },
|
||||
{ .name = "max34451", .driver_data = max34451 },
|
||||
{ .name = "max34452", .driver_data = max34452 },
|
||||
{ .name = "max34460", .driver_data = max34460 },
|
||||
{ .name = "max34461", .driver_data = max34461 },
|
||||
{ }
|
||||
|
||||
@@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val)
|
||||
static u16 mp2975_data2reg_linear11(s64 val)
|
||||
{
|
||||
s16 exponent = 0, mantissa;
|
||||
bool negative = false;
|
||||
|
||||
/* simple case */
|
||||
if (val == 0)
|
||||
@@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val)
|
||||
/* Convert mantissa from milli-units to units */
|
||||
mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff);
|
||||
|
||||
/* restore sign */
|
||||
if (negative)
|
||||
mantissa = -mantissa;
|
||||
|
||||
/* Convert to 5 bit exponent, 11 bit mantissa */
|
||||
return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
|
||||
}
|
||||
|
||||
249
drivers/hwmon/pmbus/mpq82d00.c
Normal file
249
drivers/hwmon/pmbus/mpq82d00.c
Normal file
@@ -0,0 +1,249 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00)
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define MPQ82D00_VOUT_DIV 64
|
||||
|
||||
#define MPQ82D00_PAGE_NUM 1
|
||||
|
||||
#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \
|
||||
PMBUS_HAVE_STATUS_VOUT | \
|
||||
PMBUS_HAVE_STATUS_IOUT | \
|
||||
PMBUS_HAVE_STATUS_TEMP | \
|
||||
PMBUS_HAVE_STATUS_INPUT)
|
||||
|
||||
struct mpq82d00_data {
|
||||
struct pmbus_driver_info info;
|
||||
int vout_scale;
|
||||
};
|
||||
|
||||
#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info)
|
||||
|
||||
static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent)
|
||||
{
|
||||
s16 exponent, mantissa, target_exponent;
|
||||
|
||||
exponent = ((s16)word) >> 11;
|
||||
mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
|
||||
target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11;
|
||||
|
||||
/*
|
||||
* The MPQ82D00 does not support negtive limit value, if a negtive
|
||||
* limit value is written, the limit value will become to 0. And
|
||||
* the maximum positive limit value is limitted to 0x3FF.
|
||||
*/
|
||||
if (mantissa < 0) {
|
||||
mantissa = 0;
|
||||
} else {
|
||||
if (exponent > target_exponent) {
|
||||
mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ?
|
||||
mantissa << (exponent - target_exponent) :
|
||||
0x3FF;
|
||||
} else {
|
||||
mantissa = clamp_val(mantissa >> (target_exponent - exponent),
|
||||
0, 0x3FF);
|
||||
}
|
||||
}
|
||||
|
||||
return mantissa | ((expect_exponent << 11) & 0xf800);
|
||||
}
|
||||
|
||||
static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_MODE:
|
||||
/*
|
||||
* The MPQ82D00 does not follow standard PMBus protocol completely,
|
||||
* and the calculation of vout in this driver is based on direct
|
||||
* format. As a result, the format of vout is enforced to direct.
|
||||
*/
|
||||
ret = PB_VOUT_MODE_DIRECT;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* These registers are not explicitly handled by the driver,
|
||||
* as a result, return -ENODATA directly.
|
||||
*/
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpq82d00_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 mpq82d00_data *data = to_mpq82d00_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_WORD:
|
||||
case PMBUS_READ_VIN:
|
||||
case PMBUS_READ_IOUT:
|
||||
case PMBUS_READ_POUT:
|
||||
case PMBUS_READ_PIN:
|
||||
case PMBUS_READ_TEMPERATURE_1:
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
/*
|
||||
* These registers are not explicitly handled by the driver,
|
||||
* as a result, return -ENODATA directly.
|
||||
*/
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
case PMBUS_READ_VOUT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale,
|
||||
MPQ82D00_VOUT_DIV);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* The MPQ82D00 do not support other telemetry and limit
|
||||
* value reading, so, return -EINVAL directly.
|
||||
*/
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
/*
|
||||
* The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format,
|
||||
* and the exponent is a constant value(5'b00000), so the
|
||||
* exponent of word parameter should be converted to 5'b00000.
|
||||
*/
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
mpq82d00_linear_exp_transfer(word, 0x00));
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
/*
|
||||
* The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format,
|
||||
* and the exponent is a constant value(5'b11100), so the
|
||||
* exponent of word parameter should be converted to 5'b11100.
|
||||
*/
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
mpq82d00_linear_exp_transfer(word, 0x1C));
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* The MPQ82D00 do not support other limit value configuration,
|
||||
* so, return -EINVAL directly.
|
||||
*/
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info)
|
||||
{
|
||||
struct mpq82d00_data *data = to_mpq82d00_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE
|
||||
* bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5
|
||||
* is 0, it is linear mode, the vout scale is 1.953125mv/LSB.
|
||||
*/
|
||||
if (FIELD_GET(GENMASK(5, 5), ret))
|
||||
data->vout_scale = 320;
|
||||
else
|
||||
data->vout_scale = 125;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info mpq82d00_info = {
|
||||
.pages = MPQ82D00_PAGE_NUM,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.format[PSC_POWER] = linear,
|
||||
|
||||
.m[PSC_VOLTAGE_OUT] = 1,
|
||||
.R[PSC_VOLTAGE_OUT] = 3,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
|
||||
.func[0] = MPQ82D00_RAIL1_FUNC,
|
||||
.read_word_data = mpq82d00_read_word_data,
|
||||
.read_byte_data = mpq82d00_read_byte_data,
|
||||
.write_word_data = mpq82d00_write_word_data,
|
||||
.identify = mpq82d00_identify,
|
||||
};
|
||||
|
||||
static int mpq82d00_probe(struct i2c_client *client)
|
||||
{
|
||||
struct mpq82d00_data *data;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info));
|
||||
|
||||
return pmbus_do_probe(client, &data->info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mpq82d00_id[] = {
|
||||
{.name = "mpq82d00"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mpq82d00_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused mpq82d00_of_match[] = {
|
||||
{.compatible = "mps,mpq82d00"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mpq82d00_of_match);
|
||||
|
||||
static struct i2c_driver mpq82d00_driver = {
|
||||
.driver = {
|
||||
.name = "mpq82d00",
|
||||
.of_match_table = mpq82d00_of_match,
|
||||
},
|
||||
.probe = mpq82d00_probe,
|
||||
.id_table = mpq82d00_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mpq82d00_driver);
|
||||
|
||||
MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net");
|
||||
MODULE_DESCRIPTION("PMBus driver for MPS MPQ82D00 device");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
976
drivers/hwmon/pmbus/mpq8646.c
Normal file
976
drivers/hwmon/pmbus/mpq8646.c
Normal file
@@ -0,0 +1,976 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Driver for MPS MPQ8646 step-down converter.
|
||||
*
|
||||
* Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pmbus.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
/* Default cadence for the in-driver alarm-poll fallback (ms) */
|
||||
#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000
|
||||
|
||||
/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */
|
||||
#define MPS_CLEAR_LAST_FAULT 0x08
|
||||
#define MPS_MFR_CFG_EXT 0xF5
|
||||
#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6)
|
||||
#define MPS_PROTECTION_LAST 0xFB
|
||||
|
||||
/*
|
||||
* PMBus 1.3 NVM commit / revert commands. MPS equivalent of
|
||||
* STORE_ALL (15h) and RESTORE_ALL (16h).
|
||||
*/
|
||||
#define PMBUS_STORE_USER_ALL 0x15
|
||||
#define PMBUS_RESTORE_USER_ALL 0x16
|
||||
|
||||
/* PMBus 1.3 timing / UVLO command codes */
|
||||
#define PMBUS_VIN_ON 0x35
|
||||
#define PMBUS_VIN_OFF 0x36
|
||||
#define PMBUS_TON_DELAY 0x60
|
||||
#define PMBUS_TON_RISE 0x61
|
||||
#define PMBUS_TOFF_DELAY 0x64
|
||||
#define PMBUS_TOFF_FALL 0x65
|
||||
|
||||
/* MPS vendor-extended observability / identity registers */
|
||||
#define MPS_MFR_CONFIG_ID 0xC0
|
||||
#define MPS_MFR_CONFIG_CODE_REV 0xC1
|
||||
#define MPS_MFR_PRODUCT_REV_USER 0xC2
|
||||
#define MPS_MFR_SILICON_REV 0xC3
|
||||
#define MPS_MFR_RETRY_TIMES 0xF4
|
||||
#define MPS_MFR_VBOOT_CFG 0xFC
|
||||
|
||||
/*
|
||||
* MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate
|
||||
* subsequent PMBus writes
|
||||
* bits[1:0] = 00 -- unlocked (POR default)
|
||||
* 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21)
|
||||
* so the operator can still DVFS the rail
|
||||
* 11 -- lock all writes
|
||||
* A negative-going PG edge resets these bits to 00, the lock
|
||||
* is operationally reversible without a full chip POR.
|
||||
*/
|
||||
#define MPS_MFR_PMBUS_LOCK 0xEE
|
||||
|
||||
/*
|
||||
* Retry parameters for the MFR_CFG_EXT gate-close write after
|
||||
* CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this
|
||||
* silicon is about 1 ms; the datasheet does not have information.
|
||||
*/
|
||||
#define MPQ8646_NVM_RETRY_MAX 5
|
||||
#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000
|
||||
#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000
|
||||
|
||||
#define MPQ8646_DEBUG(client, fmt, ...) \
|
||||
dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__)
|
||||
|
||||
/*
|
||||
* Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon
|
||||
* The chip uses an 11-bit VOUT feedback-divider scale register.
|
||||
*/
|
||||
#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0)
|
||||
|
||||
/* Forward declaration */
|
||||
struct mpq8646_dbg_reg_ctx;
|
||||
|
||||
/* Per-instance state */
|
||||
struct mpq8646_priv {
|
||||
struct pmbus_driver_info info; /* must be first, container_of target */
|
||||
struct i2c_client *client;
|
||||
|
||||
/* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */
|
||||
struct mutex mps_lock;
|
||||
|
||||
/* Set alarm_poll_interval_ms = 0 to disable. */
|
||||
struct delayed_work alarm_poll_work;
|
||||
u32 alarm_poll_interval_ms;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* the only debugfs file that can re-arm the poll worker */
|
||||
struct dentry *dbg_poll;
|
||||
struct mpq8646_dbg_reg_ctx *dbg_reg_ctx;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
|
||||
return container_of(info, struct mpq8646_priv, info);
|
||||
}
|
||||
|
||||
/*
|
||||
* VID-mode m/b/R coefficients, same values as the mpq8785 driver for
|
||||
* the MPS VID encoding. Per the PMBus Direct formula used by
|
||||
* pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields
|
||||
* 1.5625 mV per LSB.
|
||||
*/
|
||||
#define MPQ8646_VID_M 64
|
||||
#define MPQ8646_VID_B 0
|
||||
#define MPQ8646_VID_R 1
|
||||
|
||||
static int mpq8646_identify(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
int vout_mode;
|
||||
|
||||
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
if (vout_mode < 0)
|
||||
return vout_mode;
|
||||
|
||||
switch (vout_mode & PB_VOUT_MODE_MODE_MASK) {
|
||||
case PB_VOUT_MODE_LINEAR:
|
||||
info->format[PSC_VOLTAGE_OUT] = linear;
|
||||
break;
|
||||
case PB_VOUT_MODE_VID:
|
||||
case PB_VOUT_MODE_DIRECT:
|
||||
info->format[PSC_VOLTAGE_OUT] = direct;
|
||||
info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M;
|
||||
info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B;
|
||||
info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg);
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_MODE:
|
||||
ret = pmbus_read_byte_data(client, page, reg);
|
||||
MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID)
|
||||
return PB_VOUT_MODE_DIRECT;
|
||||
|
||||
return ret;
|
||||
case PMBUS_STATUS_BYTE:
|
||||
case PMBUS_STATUS_CML:
|
||||
case PMBUS_STATUS_OTHER:
|
||||
case PMBUS_STATUS_MFR_SPECIFIC:
|
||||
case PMBUS_STATUS_FAN_12:
|
||||
case PMBUS_STATUS_FAN_34:
|
||||
return -ENXIO;
|
||||
case PMBUS_MFR_LOCATION:
|
||||
case PMBUS_MFR_DATE:
|
||||
case PMBUS_MFR_SERIAL:
|
||||
case PMBUS_IC_DEVICE_ID:
|
||||
case PMBUS_IC_DEVICE_REV:
|
||||
MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg);
|
||||
return -ENXIO;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reference: ltc2978.c::ltc2978_write_word_data which uses the
|
||||
* same virtual-register channel for its real chip-side peak reset.
|
||||
*/
|
||||
static int mpq8646_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
struct mpq8646_priv *priv = mpq8646_priv_from_client(client);
|
||||
int rc;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n",
|
||||
reg);
|
||||
rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS);
|
||||
if (rc < 0)
|
||||
MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc);
|
||||
return rc < 0 ? rc : 0;
|
||||
default:
|
||||
return -ENODATA; /* let pmbus_core do the direct write */
|
||||
}
|
||||
}
|
||||
|
||||
static int mpq8646_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n",
|
||||
page, phase, reg);
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_READ_VIN:
|
||||
case PMBUS_READ_VOUT:
|
||||
case PMBUS_READ_IOUT:
|
||||
case PMBUS_READ_TEMPERATURE_1:
|
||||
case PMBUS_STATUS_WORD:
|
||||
case PMBUS_VOUT_OV_FAULT_LIMIT:
|
||||
case PMBUS_VOUT_OV_WARN_LIMIT:
|
||||
case PMBUS_VOUT_UV_WARN_LIMIT:
|
||||
case PMBUS_VOUT_UV_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
case PMBUS_MFR_VIN_MAX:
|
||||
case PMBUS_MFR_VOUT_MAX:
|
||||
case PMBUS_MFR_IOUT_MAX:
|
||||
case PMBUS_MFR_MAX_TEMP_1:
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
return 0;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
rc = i2c_smbus_read_word_data(client, reg);
|
||||
|
||||
MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info mpq8646_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 4,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = 1,
|
||||
.m[PSC_CURRENT_OUT] = 16,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
.R[PSC_CURRENT_OUT] = 0,
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
.R[PSC_TEMPERATURE] = 0,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP |
|
||||
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_REGULATOR)
|
||||
static const struct regulator_desc mpq8646_reg_desc[] = {
|
||||
PMBUS_REGULATOR("vout", 0),
|
||||
};
|
||||
#endif /* CONFIG_REGULATOR */
|
||||
|
||||
#if IS_ENABLED(CONFIG_NVMEM)
|
||||
/*
|
||||
* Expose using
|
||||
* /sys/bus/nvmem/devices/<i2c-name>/nvmem
|
||||
*
|
||||
* Layout (16 bytes):
|
||||
* offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM
|
||||
* offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM
|
||||
* offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM
|
||||
* offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM
|
||||
* offset 8 MFR_SILICON_REV (0xC3) byte -- NVM
|
||||
* offset 9..15 reserved (zero-fill, leaves room for additions)
|
||||
*/
|
||||
#define MPQ8646_NVMEM_SIZE 16
|
||||
|
||||
struct mpq8646_nvmem_entry {
|
||||
unsigned int off;
|
||||
u8 reg;
|
||||
bool is_word;
|
||||
};
|
||||
|
||||
static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = {
|
||||
{ 0, MPS_PROTECTION_LAST, true },
|
||||
{ 2, MPS_MFR_RETRY_TIMES, true },
|
||||
{ 4, MPS_MFR_CONFIG_ID, true },
|
||||
{ 6, MPS_MFR_VBOOT_CFG, true },
|
||||
{ 8, MPS_MFR_SILICON_REV, false },
|
||||
};
|
||||
|
||||
static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val,
|
||||
size_t bytes)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
u8 *out = val;
|
||||
size_t i;
|
||||
|
||||
if (offset >= MPQ8646_NVMEM_SIZE)
|
||||
return -EINVAL;
|
||||
if (offset + bytes > MPQ8646_NVMEM_SIZE)
|
||||
bytes = MPQ8646_NVMEM_SIZE - offset;
|
||||
|
||||
memset(out, 0, bytes);
|
||||
|
||||
guard(mutex)(&priv->mps_lock);
|
||||
for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) {
|
||||
const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i];
|
||||
unsigned int e_start = e->off;
|
||||
unsigned int e_end = e_start + (e->is_word ? 2 : 1);
|
||||
u8 raw[2];
|
||||
int rc;
|
||||
unsigned int j;
|
||||
|
||||
if (e_end <= offset || e_start >= offset + bytes)
|
||||
continue; /* outside requested slice */
|
||||
|
||||
if (e->is_word)
|
||||
rc = i2c_smbus_read_word_data(priv->client, e->reg);
|
||||
else
|
||||
rc = i2c_smbus_read_byte_data(priv->client, e->reg);
|
||||
if (rc < 0)
|
||||
continue; /* leave the zero-fill in place */
|
||||
|
||||
raw[0] = rc & 0xff;
|
||||
raw[1] = (rc >> 8) & 0xff;
|
||||
|
||||
for (j = 0; j < e_end - e_start; j++) {
|
||||
unsigned int abs = e_start + j;
|
||||
|
||||
if (abs >= offset && abs < offset + bytes)
|
||||
out[abs - offset] = raw[j];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_NVMEM */
|
||||
|
||||
static const struct i2c_device_id mpq8646_id[] = {
|
||||
{ .name = "mpq8646" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mpq8646_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused mpq8646_of_match[] = {
|
||||
{ .compatible = "mps,mpq8646" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mpq8646_of_match);
|
||||
|
||||
static struct pmbus_platform_data mpq8646_no_pec_pdata = {
|
||||
.flags = PMBUS_NO_CAPABILITY,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/*
|
||||
* Read-only decode cases in the client's pmbus debugfs directory:
|
||||
* the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the
|
||||
* identity/timing registers.
|
||||
*/
|
||||
|
||||
struct mpq_status_bit {
|
||||
u16 mask;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static const struct mpq_status_bit mpq8646_status_word_bits[] = {
|
||||
{ PB_STATUS_VOUT, "VOUT" },
|
||||
{ PB_STATUS_IOUT_POUT, "IOUT_POUT" },
|
||||
{ PB_STATUS_INPUT, "INPUT" },
|
||||
{ PB_STATUS_WORD_MFR, "NVM_SUMMARY" },
|
||||
{ PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" },
|
||||
{ PB_STATUS_FANS, "FANS" },
|
||||
{ PB_STATUS_OTHER, "OTHER" },
|
||||
{ PB_STATUS_UNKNOWN, "WATCH_DOG" },
|
||||
{ PB_STATUS_BUSY, "BUSY" },
|
||||
{ PB_STATUS_OFF, "OFF" },
|
||||
{ PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" },
|
||||
{ PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" },
|
||||
{ PB_STATUS_VIN_UV, "VIN_UV_FAULT" },
|
||||
{ PB_STATUS_TEMPERATURE, "TEMP" },
|
||||
{ PB_STATUS_CML, "CML" },
|
||||
{ PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */
|
||||
static const struct mpq_status_bit mpq8646_protection_last_bits[] = {
|
||||
{ BIT(15), "INIT_FAULT" },
|
||||
{ BIT(14), "NVM_CRC_ERROR" },
|
||||
{ BIT(13), "NVM_FAULT" },
|
||||
{ BIT(12), "OC_PHASE_FAULT" },
|
||||
{ BIT(11), "OTP_SELF_FAULT" },
|
||||
{ BIT(9), "SWITCH_PRD_FAULT" },
|
||||
{ BIT(8), "VIN_OV_FAULT" },
|
||||
{ BIT(7), "VOUT_OV_FAULT" },
|
||||
{ BIT(6), "VOUT_UV_FAULT" },
|
||||
{ BIT(5), "OC_TOT_FAULT" },
|
||||
{ BIT(4), "VIN_UVLO_FAULT" },
|
||||
{ BIT(3), "DRMOS_OTP" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static void mpq8646_print_bits(struct seq_file *s, u16 v,
|
||||
const struct mpq_status_bit *tab)
|
||||
{
|
||||
const struct mpq_status_bit *t;
|
||||
bool first = true;
|
||||
|
||||
seq_printf(s, "0x%04x", v);
|
||||
if (!v) {
|
||||
seq_puts(s, " [clean]\n");
|
||||
return;
|
||||
}
|
||||
seq_puts(s, " [");
|
||||
for (t = tab; t->mask; t++) {
|
||||
if (v & t->mask) {
|
||||
if (!first)
|
||||
seq_putc(s, ' ');
|
||||
seq_puts(s, t->name);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
seq_puts(s, "]\n");
|
||||
}
|
||||
|
||||
static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct mpq8646_priv *priv = s->private;
|
||||
int rc;
|
||||
|
||||
scoped_guard(pmbus_lock, priv->client)
|
||||
rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD);
|
||||
if (rc < 0) {
|
||||
seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc);
|
||||
return 0;
|
||||
}
|
||||
seq_puts(s, "STATUS_WORD: ");
|
||||
mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits);
|
||||
seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n");
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded);
|
||||
|
||||
static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct mpq8646_priv *priv = s->private;
|
||||
int rc;
|
||||
|
||||
guard(pmbus_lock)(priv->client);
|
||||
scoped_guard(mutex, &priv->mps_lock)
|
||||
rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST);
|
||||
|
||||
if (rc < 0) {
|
||||
seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc);
|
||||
return 0;
|
||||
}
|
||||
seq_puts(s, "PROTECTION_LAST: ");
|
||||
mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits);
|
||||
seq_puts(s, "(NVM-backed, survives chip POR)\n");
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last);
|
||||
|
||||
struct mpq8646_dbg_reg {
|
||||
u8 reg;
|
||||
bool is_word;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = {
|
||||
/* MPS vendor extensions (read-only identity / observability) */
|
||||
{ MPS_MFR_CONFIG_ID, true, "mfr_config_id" },
|
||||
{ MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" },
|
||||
{ MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" },
|
||||
{ MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" },
|
||||
{ MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" },
|
||||
/* PMBus 1.3 standard timing / UVLO (read-only introspection) */
|
||||
{ PMBUS_VIN_ON, true, "vin_on" },
|
||||
{ PMBUS_VIN_OFF, true, "vin_off" },
|
||||
{ PMBUS_TON_DELAY, true, "ton_delay" },
|
||||
{ PMBUS_TON_RISE, true, "ton_rise" },
|
||||
{ PMBUS_TOFF_DELAY, true, "toff_delay" },
|
||||
{ PMBUS_TOFF_FALL, true, "toff_fall" },
|
||||
};
|
||||
|
||||
struct mpq8646_dbg_reg_ctx {
|
||||
struct mpq8646_priv *priv;
|
||||
const struct mpq8646_dbg_reg *desc;
|
||||
};
|
||||
|
||||
static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct mpq8646_dbg_reg_ctx *ctx = s->private;
|
||||
int rc;
|
||||
|
||||
guard(pmbus_lock)(ctx->priv->client);
|
||||
if (ctx->desc->is_word)
|
||||
rc = i2c_smbus_read_word_data(ctx->priv->client,
|
||||
ctx->desc->reg);
|
||||
else
|
||||
rc = i2c_smbus_read_byte_data(ctx->priv->client,
|
||||
ctx->desc->reg);
|
||||
|
||||
if (rc < 0) {
|
||||
seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n",
|
||||
ctx->desc->reg, ctx->desc->name, rc);
|
||||
return 0;
|
||||
}
|
||||
seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg);
|
||||
|
||||
#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE
|
||||
/*
|
||||
* Write/provisioning data, disabled by default: NVM commit and
|
||||
* revert, the CLEAR_LAST_FAULT sequences and a small set of named
|
||||
* writable registers.
|
||||
*/
|
||||
|
||||
static void mpq8646_unsafe_banner(struct mpq8646_priv *priv)
|
||||
{
|
||||
dev_warn(&priv->client->dev,
|
||||
"**********************************************************\n"
|
||||
"** WARNING WARNING WARNING WARNING WARNING WARNING **\n"
|
||||
"** **\n"
|
||||
"** The MPQ8646 provisioning debugfs writes are enabled. **\n"
|
||||
"** Wrong register writes can and likely will physically **\n"
|
||||
"** damage or destroy the chip and/or the board. **\n"
|
||||
"** **\n"
|
||||
"** If you see this message and you are not debugging **\n"
|
||||
"** the kernel, report this immediately to your system **\n"
|
||||
"** administrator! **\n"
|
||||
"** **\n"
|
||||
"** WARNING WARNING WARNING WARNING WARNING WARNING **\n"
|
||||
"**********************************************************\n");
|
||||
}
|
||||
|
||||
static int mpq8646_dbg_clear_protection_last(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
int rc;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
guard(pmbus_lock)(priv->client);
|
||||
scoped_guard(mutex, &priv->mps_lock)
|
||||
rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT);
|
||||
if (rc < 0)
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops,
|
||||
NULL, mpq8646_dbg_clear_protection_last, "%llu\n");
|
||||
|
||||
static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
int rc, ret;
|
||||
int wp_orig, cfg_orig;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
guard(pmbus_lock)(priv->client);
|
||||
guard(mutex)(&priv->mps_lock);
|
||||
|
||||
wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT);
|
||||
if (wp_orig < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n",
|
||||
wp_orig);
|
||||
return wp_orig;
|
||||
}
|
||||
cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT);
|
||||
if (cfg_orig < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n",
|
||||
cfg_orig);
|
||||
return cfg_orig;
|
||||
}
|
||||
|
||||
if (wp_orig != 0) {
|
||||
rc = i2c_smbus_write_byte_data(priv->client,
|
||||
PMBUS_WRITE_PROTECT, 0);
|
||||
if (rc < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: WP clear failed (%d), aborting\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT,
|
||||
(u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN);
|
||||
if (ret < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: gate open failed (%d)\n",
|
||||
ret);
|
||||
} else {
|
||||
ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT);
|
||||
if (ret < 0)
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n",
|
||||
ret);
|
||||
|
||||
for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) {
|
||||
rc = i2c_smbus_write_word_data(priv->client,
|
||||
MPS_MFR_CFG_EXT,
|
||||
(u16)cfg_orig);
|
||||
if (rc >= 0)
|
||||
break;
|
||||
usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN,
|
||||
MPQ8646_NVM_RETRY_DELAY_US_MAX);
|
||||
}
|
||||
if (rc < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n",
|
||||
rc);
|
||||
if (ret == 0)
|
||||
ret = rc;
|
||||
}
|
||||
}
|
||||
|
||||
if (wp_orig != 0) {
|
||||
rc = i2c_smbus_write_byte_data(priv->client,
|
||||
PMBUS_WRITE_PROTECT,
|
||||
(u8)wp_orig);
|
||||
if (rc < 0) {
|
||||
dev_warn(&priv->client->dev,
|
||||
"clear_protection_last_force: WP restore failed (%d)\n",
|
||||
rc);
|
||||
if (ret == 0)
|
||||
ret = rc;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops,
|
||||
NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n");
|
||||
|
||||
static int mpq8646_dbg_store_all(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
int rc;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
guard(pmbus_lock)(priv->client);
|
||||
scoped_guard(mutex, &priv->mps_lock)
|
||||
rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL);
|
||||
if (rc < 0)
|
||||
dev_warn(&priv->client->dev,
|
||||
"store_all: STORE_USER_ALL (0x15) write failed (%d)\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops,
|
||||
NULL, mpq8646_dbg_store_all, "%llu\n");
|
||||
|
||||
static int mpq8646_dbg_restore_all(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
int rc;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
guard(pmbus_lock)(priv->client);
|
||||
scoped_guard(mutex, &priv->mps_lock)
|
||||
rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL);
|
||||
if (rc < 0)
|
||||
dev_warn(&priv->client->dev,
|
||||
"restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops,
|
||||
NULL, mpq8646_dbg_restore_all, "%llu\n");
|
||||
|
||||
static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = {
|
||||
/* PMBus 1.3 control / margin */
|
||||
{ PMBUS_ON_OFF_CONFIG, false, "on_off_config" },
|
||||
{ PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" },
|
||||
{ PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" },
|
||||
/* MPS PMBus-level write-protect */
|
||||
{ MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" },
|
||||
/* MPS user-writable product revision */
|
||||
{ MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" },
|
||||
};
|
||||
|
||||
static int mpq8646_dbg_reg_get(void *data, u64 *val)
|
||||
{
|
||||
struct mpq8646_dbg_reg_ctx *ctx = data;
|
||||
int rc;
|
||||
|
||||
guard(pmbus_lock)(ctx->priv->client);
|
||||
if (ctx->desc->is_word)
|
||||
rc = i2c_smbus_read_word_data(ctx->priv->client,
|
||||
ctx->desc->reg);
|
||||
else
|
||||
rc = i2c_smbus_read_byte_data(ctx->priv->client,
|
||||
ctx->desc->reg);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
*val = rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpq8646_dbg_reg_set(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_dbg_reg_ctx *ctx = data;
|
||||
int rc;
|
||||
|
||||
guard(pmbus_lock)(ctx->priv->client);
|
||||
if (ctx->desc->is_word)
|
||||
rc = i2c_smbus_write_word_data(ctx->priv->client,
|
||||
ctx->desc->reg, (u16)val);
|
||||
else
|
||||
rc = i2c_smbus_write_byte_data(ctx->priv->client,
|
||||
ctx->desc->reg, (u8)val);
|
||||
return rc < 0 ? rc : 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops,
|
||||
mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n");
|
||||
|
||||
static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv,
|
||||
struct dentry *root)
|
||||
{
|
||||
struct mpq8646_dbg_reg_ctx *ctx;
|
||||
size_t i;
|
||||
|
||||
mpq8646_unsafe_banner(priv);
|
||||
|
||||
debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv,
|
||||
&mpq8646_dbg_clear_protection_last_fops);
|
||||
debugfs_create_file_unsafe("clear_protection_last_force", 0200, root,
|
||||
priv,
|
||||
&mpq8646_dbg_clear_protection_last_force_fops);
|
||||
debugfs_create_file_unsafe("store_all", 0200, root, priv,
|
||||
&mpq8646_dbg_store_all_fops);
|
||||
debugfs_create_file_unsafe("restore_all", 0200, root, priv,
|
||||
&mpq8646_dbg_restore_all_fops);
|
||||
|
||||
ctx = devm_kcalloc(&priv->client->dev,
|
||||
ARRAY_SIZE(mpq8646_dbg_regs_unsafe),
|
||||
sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return;
|
||||
for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) {
|
||||
ctx[i].priv = priv;
|
||||
ctx[i].desc = &mpq8646_dbg_regs_unsafe[i];
|
||||
debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name,
|
||||
0600, root, &ctx[i],
|
||||
&mpq8646_dbg_reg_rw_fops);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv,
|
||||
struct dentry *root) {}
|
||||
#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */
|
||||
|
||||
static int mpq8646_dbg_poll_interval_get(void *data, u64 *val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
|
||||
*val = priv->alarm_poll_interval_ms;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpq8646_dbg_poll_interval_set(void *data, u64 val)
|
||||
{
|
||||
struct mpq8646_priv *priv = data;
|
||||
bool was_off = !priv->alarm_poll_interval_ms;
|
||||
|
||||
priv->alarm_poll_interval_ms = (u32)val;
|
||||
|
||||
if (val && was_off && !priv->client->irq)
|
||||
schedule_delayed_work(&priv->alarm_poll_work,
|
||||
msecs_to_jiffies((u32)val));
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops,
|
||||
mpq8646_dbg_poll_interval_get,
|
||||
mpq8646_dbg_poll_interval_set, "%llu\n");
|
||||
|
||||
static void mpq8646_debugfs_register(struct mpq8646_priv *priv)
|
||||
{
|
||||
struct dentry *root;
|
||||
size_t i;
|
||||
|
||||
root = pmbus_get_debugfs_dir(priv->client);
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
/* MPS extensions: status decode + NVM-backed PROTECTION_LAST */
|
||||
debugfs_create_file("status_decoded", 0400, root, priv,
|
||||
&mpq8646_dbg_status_decoded_fops);
|
||||
debugfs_create_file("protection_last", 0400, root, priv,
|
||||
&mpq8646_dbg_protection_last_fops);
|
||||
priv->dbg_poll =
|
||||
debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600,
|
||||
root, priv,
|
||||
&mpq8646_dbg_poll_interval_fops);
|
||||
|
||||
priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev,
|
||||
ARRAY_SIZE(mpq8646_dbg_regs),
|
||||
sizeof(*priv->dbg_reg_ctx),
|
||||
GFP_KERNEL);
|
||||
if (!priv->dbg_reg_ctx)
|
||||
return;
|
||||
for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) {
|
||||
priv->dbg_reg_ctx[i].priv = priv;
|
||||
priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i];
|
||||
debugfs_create_file(mpq8646_dbg_regs[i].name, 0400,
|
||||
root, &priv->dbg_reg_ctx[i],
|
||||
&mpq8646_dbg_reg_fops);
|
||||
}
|
||||
|
||||
mpq8646_debugfs_register_unsafe(priv, root);
|
||||
}
|
||||
|
||||
static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv)
|
||||
{
|
||||
debugfs_remove(priv->dbg_poll);
|
||||
}
|
||||
#else
|
||||
static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {}
|
||||
static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {}
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
static void mpq8646_alarm_poll_work(struct work_struct *work)
|
||||
{
|
||||
struct mpq8646_priv *priv = container_of(to_delayed_work(work),
|
||||
struct mpq8646_priv,
|
||||
alarm_poll_work);
|
||||
|
||||
if (priv->client->irq)
|
||||
return; /* SMBALERT# wired; polling not needed */
|
||||
|
||||
if (!priv->alarm_poll_interval_ms)
|
||||
return; /* polling disabled; don't re-arm */
|
||||
|
||||
pmbus_check_and_notify_faults(priv->client);
|
||||
|
||||
schedule_delayed_work(&priv->alarm_poll_work,
|
||||
msecs_to_jiffies(priv->alarm_poll_interval_ms));
|
||||
}
|
||||
|
||||
static int mpq8646_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct pmbus_driver_info *info;
|
||||
struct mpq8646_priv *priv;
|
||||
u32 voltage_scale;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
priv->client = client;
|
||||
mutex_init(&priv->mps_lock);
|
||||
memcpy(&priv->info, &mpq8646_info, sizeof(priv->info));
|
||||
info = &priv->info;
|
||||
|
||||
info->identify = mpq8646_identify;
|
||||
info->read_byte_data = mpq8646_read_byte_data;
|
||||
info->read_word_data = mpq8646_read_word_data;
|
||||
info->write_word_data = mpq8646_write_word_data;
|
||||
dev->platform_data = &mpq8646_no_pec_pdata;
|
||||
|
||||
#if IS_ENABLED(CONFIG_REGULATOR)
|
||||
info->reg_desc = mpq8646_reg_desc;
|
||||
info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc);
|
||||
#endif
|
||||
|
||||
INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work);
|
||||
priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT;
|
||||
|
||||
if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille",
|
||||
&voltage_scale)) {
|
||||
if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP,
|
||||
voltage_scale);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pmbus_do_probe(client, info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mpq8646_debugfs_register(priv);
|
||||
|
||||
if (!client->irq)
|
||||
schedule_delayed_work(&priv->alarm_poll_work,
|
||||
msecs_to_jiffies(priv->alarm_poll_interval_ms));
|
||||
|
||||
#if IS_ENABLED(CONFIG_NVMEM)
|
||||
{
|
||||
struct nvmem_config cfg = {
|
||||
.dev = &client->dev,
|
||||
.name = dev_name(&client->dev),
|
||||
.owner = THIS_MODULE,
|
||||
.read_only = true,
|
||||
.root_only = true,
|
||||
.word_size = 1,
|
||||
.stride = 1,
|
||||
.size = MPQ8646_NVMEM_SIZE,
|
||||
.reg_read = mpq8646_nvmem_read,
|
||||
.priv = priv,
|
||||
};
|
||||
struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg);
|
||||
|
||||
if (IS_ERR(nv))
|
||||
dev_warn(&client->dev,
|
||||
"nvmem snapshot register failed (%pe)\n",
|
||||
nv);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
};
|
||||
|
||||
static void mpq8646_remove(struct i2c_client *client)
|
||||
{
|
||||
struct mpq8646_priv *priv = mpq8646_priv_from_client(client);
|
||||
|
||||
mpq8646_debugfs_unregister(priv);
|
||||
cancel_delayed_work_sync(&priv->alarm_poll_work);
|
||||
}
|
||||
|
||||
static struct i2c_driver mpq8646_driver = {
|
||||
.driver = {
|
||||
.name = "mpq8646",
|
||||
.of_match_table = of_match_ptr(mpq8646_of_match),
|
||||
},
|
||||
.probe = mpq8646_probe,
|
||||
.remove = mpq8646_remove,
|
||||
.id_table = mpq8646_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mpq8646_driver);
|
||||
|
||||
MODULE_AUTHOR("Vincent Jardin <vjardin@free.fr>");
|
||||
MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
@@ -420,10 +420,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid };
|
||||
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv };
|
||||
|
||||
/* PMBus revision identifiers */
|
||||
#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */
|
||||
#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */
|
||||
#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */
|
||||
#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */
|
||||
#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */
|
||||
#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */
|
||||
#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */
|
||||
#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */
|
||||
#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */
|
||||
#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */
|
||||
|
||||
/* Operation type flags for pmbus_update_ts */
|
||||
#define PMBUS_OP_WRITE BIT(0)
|
||||
@@ -488,6 +490,16 @@ struct pmbus_driver_info {
|
||||
int access_delay; /* in microseconds */
|
||||
int write_delay; /* in microseconds */
|
||||
int page_change_delay; /* in microseconds */
|
||||
|
||||
/*
|
||||
* Some chips do not support the PMBUS_REVISION command.
|
||||
* Drivers for such chips can report the supported PMBus revision here.
|
||||
*
|
||||
* Drivers must set have_pmbus_revision to true and provide the
|
||||
* supported PMBus version in pmbus_revision.
|
||||
*/
|
||||
bool have_pmbus_revision; /* true if pmbus_revision is valid */
|
||||
u8 pmbus_revision; /* PMBus revision */
|
||||
};
|
||||
|
||||
/* Regulator ops */
|
||||
@@ -549,6 +561,7 @@ 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_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf);
|
||||
int pmbus_read_word_data(struct i2c_client *client, int page, int phase,
|
||||
u8 reg);
|
||||
int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
|
||||
@@ -560,6 +573,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg,
|
||||
int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
|
||||
u8 mask, u8 value);
|
||||
void pmbus_clear_faults(struct i2c_client *client);
|
||||
void pmbus_check_and_notify_faults(struct i2c_client *client);
|
||||
bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
|
||||
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
|
||||
int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info);
|
||||
|
||||
@@ -96,7 +96,8 @@ struct pmbus_data {
|
||||
|
||||
u32 flags; /* from platform data */
|
||||
|
||||
u8 revision; /* The PMBus revision the device is compliant with */
|
||||
bool have_pmbus_revision;
|
||||
u8 revision; /* The PMBus revision the device is compliant with */
|
||||
|
||||
int exponent[PMBUS_PAGES];
|
||||
/* linear mode: exponent for output voltages */
|
||||
@@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS");
|
||||
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());
|
||||
s64 delay;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
delay = ktime_us_delta(data->next_access_backoff, ktime_get());
|
||||
|
||||
if (delay > 0)
|
||||
fsleep(delay);
|
||||
@@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS");
|
||||
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;
|
||||
int delay = info->access_delay;
|
||||
const struct pmbus_driver_info *info;
|
||||
int delay;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
info = data->info;
|
||||
delay = info->access_delay;
|
||||
|
||||
if (op & PMBUS_OP_WRITE)
|
||||
delay = max(delay, info->write_delay);
|
||||
@@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS");
|
||||
|
||||
/**
|
||||
* pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data
|
||||
* @client: Handle to slave device
|
||||
* @reg: Byte interpreted by slave
|
||||
* @data_buf: Byte array into which data will be read
|
||||
* Return: Negative errno or number of bytes read
|
||||
*
|
||||
* PMBus internal function to read a SMBus block from a PMBus chip.
|
||||
*
|
||||
* PMBus chips report various properties using SMBus block read operations.
|
||||
* However, not all I2C controllers support this operation.
|
||||
*
|
||||
* Execute SMBus block read if supported. If not supported, but SMBus I2C block
|
||||
* read is supported, use it instead. Note that at most 31 data bytes can be
|
||||
* read from the device if i2c_smbus_read_i2c_block_data() is used to read the
|
||||
* data. This is a SMBUs protocol limit which can not be avoided.
|
||||
*
|
||||
* Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor
|
||||
* I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported.
|
||||
*
|
||||
* Callers must hold pmbus_lock or execute calls from the probe function.
|
||||
*/
|
||||
int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf)
|
||||
{
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||||
int blen, len, ret;
|
||||
|
||||
if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_block_data(client, reg, data_buf);
|
||||
pmbus_update_ts(client, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
|
||||
dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the returned data is valid SMBus block data, the first byte
|
||||
* must be the data length.
|
||||
*
|
||||
* i2c_smbus_read_i2c_block_data() may return an error if the chip
|
||||
* sends NACK before the number of requested bytes is received.
|
||||
* Handle this by reading the data length first, then reading the
|
||||
* entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures
|
||||
* that requested number of bytes never exceeds the number of
|
||||
* bytes sent by the chip.
|
||||
*/
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf);
|
||||
pmbus_update_ts(client, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
len = buf[0];
|
||||
if (len == 0)
|
||||
return 0;
|
||||
blen = len;
|
||||
if (len >= I2C_SMBUS_BLOCK_MAX)
|
||||
len = I2C_SMBUS_BLOCK_MAX - 1;
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf);
|
||||
pmbus_update_ts(client, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (buf[0] != blen)
|
||||
return -EIO;
|
||||
memcpy(data_buf, buf + 1, len);
|
||||
return len;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS");
|
||||
|
||||
static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
|
||||
char *data_buf)
|
||||
{
|
||||
@@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_read_block_data(client, reg, data_buf);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
return rv;
|
||||
return pmbus_read_smbus_i2c_block_data(client, reg, data_buf);
|
||||
}
|
||||
|
||||
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
|
||||
@@ -2847,9 +2930,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
if (!(data->flags & PMBUS_NO_WRITE_PROTECT))
|
||||
pmbus_init_wp(client, data);
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION);
|
||||
if (ret >= 0)
|
||||
data->revision = ret;
|
||||
if (info->have_pmbus_revision) {
|
||||
data->have_pmbus_revision = true;
|
||||
data->revision = info->pmbus_revision;
|
||||
} else {
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION);
|
||||
if (ret >= 0) {
|
||||
data->have_pmbus_revision = true;
|
||||
data->revision = ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->info->pages)
|
||||
pmbus_clear_faults(client);
|
||||
@@ -3453,10 +3543,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
|
||||
void pmbus_check_and_notify_faults(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_data *data = pdata;
|
||||
struct i2c_client *client = to_i2c_client(data->dev);
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
int i, status, event;
|
||||
|
||||
guard(pmbus_lock)(client);
|
||||
@@ -3469,6 +3558,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
|
||||
}
|
||||
|
||||
pmbus_clear_faults(client);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS");
|
||||
|
||||
static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
|
||||
{
|
||||
struct pmbus_data *data = pdata;
|
||||
struct i2c_client *client = to_i2c_client(data->dev);
|
||||
|
||||
pmbus_check_and_notify_faults(client);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -3512,10 +3610,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data)
|
||||
/* Register notifiers */
|
||||
err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler,
|
||||
IRQF_ONESHOT, "pmbus-irq", data);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to request an irq %d\n", err);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3541,6 +3637,17 @@ static int pmbus_debugfs_get(void *data, u64 *val)
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL,
|
||||
"0x%02llx\n");
|
||||
|
||||
static int pmbus_debugfs_get_revision(void *data, u64 *val)
|
||||
{
|
||||
struct pmbus_data *pdata = data;
|
||||
|
||||
*val = pdata->revision;
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL,
|
||||
"0x%02llx\n");
|
||||
|
||||
static int pmbus_debugfs_get_status(void *data, u64 *val)
|
||||
{
|
||||
struct pmbus_debugfs_entry *entry = data;
|
||||
@@ -3696,14 +3803,9 @@ static void pmbus_init_debugfs(struct i2c_client *client,
|
||||
&entries[idx++],
|
||||
&pmbus_debugfs_ops);
|
||||
}
|
||||
if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) {
|
||||
entries[idx].client = client;
|
||||
entries[idx].page = 0;
|
||||
entries[idx].reg = PMBUS_REVISION;
|
||||
debugfs_create_file("pmbus_revision", 0444, debugfs,
|
||||
&entries[idx++],
|
||||
&pmbus_debugfs_ops);
|
||||
}
|
||||
if (data->have_pmbus_revision)
|
||||
debugfs_create_file("pmbus_revision", 0444, debugfs, data,
|
||||
&pmbus_debugfs_revision_ops);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) {
|
||||
const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i];
|
||||
|
||||
430
drivers/hwmon/pmbus/sq24860.c
Normal file
430
drivers/hwmon/pmbus/sq24860.c
Normal file
@@ -0,0 +1,430 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Author: Ziming Zhu <ziming.zhu@silergycorp.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
#define SQ24860_IIN_CAL_GAIN 0x38
|
||||
#define SQ24860_READ_VAUX 0xd0
|
||||
#define SQ24860_READ_VIN_MIN 0xd1
|
||||
#define SQ24860_READ_VIN_PEAK 0xd2
|
||||
#define SQ24860_READ_IIN_PEAK 0xd4
|
||||
#define SQ24860_READ_PIN_PEAK 0xd5
|
||||
#define SQ24860_READ_TEMP_AVG 0xd6
|
||||
#define SQ24860_READ_TEMP_PEAK 0xd7
|
||||
#define SQ24860_READ_VOUT_MIN 0xda
|
||||
#define SQ24860_READ_VIN_AVG 0xdc
|
||||
#define SQ24860_READ_VOUT_AVG 0xdd
|
||||
#define SQ24860_READ_IIN_AVG 0xde
|
||||
#define SQ24860_READ_PIN_AVG 0xdf
|
||||
#define SQ24860_VIREF 0xe0
|
||||
#define SQ24860_PK_MIN_AVG 0xea
|
||||
#define PK_MIN_AVG_RST_PEAK BIT(7)
|
||||
#define PK_MIN_AVG_RST_AVG BIT(6)
|
||||
#define PK_MIN_AVG_RST_MIN BIT(5)
|
||||
#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0)
|
||||
#define SQ24860_MFR_WRITE_PROTECT 0xf8
|
||||
#define SQ24860_UNLOCKED BIT(7)
|
||||
|
||||
#define SQ24860_8B_SHIFT 2
|
||||
#define SQ24860_IIN_OCF_NUM 1000000
|
||||
#define SQ24860_IIN_OCF_DIV 129278
|
||||
#define SQ24860_IIN_OCF_OFF 165
|
||||
|
||||
#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \
|
||||
PK_MIN_AVG_RST_AVG | \
|
||||
PK_MIN_AVG_RST_MIN)
|
||||
#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT))
|
||||
/*
|
||||
* Arbitrary default Rimon value: 1.6kOhm
|
||||
*/
|
||||
#define SQ24860_DEFAULT_RIMON 1600000000
|
||||
#define SQ24860_GIMON 18180
|
||||
|
||||
#define SQ24860_VAUX_DIV 20
|
||||
|
||||
static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon)
|
||||
{
|
||||
u64 temp = 6400ULL * 1000000000ULL * 1000ULL;
|
||||
u64 denom;
|
||||
u64 word;
|
||||
|
||||
if (!rimon)
|
||||
return -EINVAL;
|
||||
|
||||
denom = (u64)rimon * SQ24860_GIMON;
|
||||
word = div64_u64(temp, denom);
|
||||
if (!word || word > U16_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN,
|
||||
(u16)word);
|
||||
}
|
||||
|
||||
static int sq24860_mfr_write_protect_set(struct i2c_client *client,
|
||||
u8 protect)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
switch (protect) {
|
||||
case 0:
|
||||
val = 0xa2;
|
||||
break;
|
||||
case PB_WP_ALL:
|
||||
val = 0x0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT,
|
||||
val);
|
||||
}
|
||||
|
||||
static int sq24860_mfr_write_protect_get(struct i2c_client *client)
|
||||
{
|
||||
int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL;
|
||||
}
|
||||
|
||||
static int sq24860_read_word_data(struct i2c_client *client,
|
||||
int page, int phase, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VIN_MAX:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VIN_PEAK);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_VIN_MIN:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VIN_MIN);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_VIN_AVG:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VIN_AVG);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VOUT_MIN);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_VOUT_AVG:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VOUT_AVG);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_IIN_AVG:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_IIN_AVG);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_IIN_MAX:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_IIN_PEAK);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_TEMP_AVG:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_TEMP_AVG);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_TEMP_MAX:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_TEMP_PEAK);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_PIN_AVG:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_PIN_AVG);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_PIN_MAX:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_PIN_PEAK);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_READ_VMON:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
SQ24860_READ_VAUX);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV);
|
||||
break;
|
||||
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
case PMBUS_VOUT_UV_WARN_LIMIT:
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_PIN_OP_WARN_LIMIT:
|
||||
/*
|
||||
* These registers provide an 8 bits value instead of a
|
||||
* 10bits one. Just shifting twice the register value is
|
||||
* enough to make the sensor type conversion work, even
|
||||
* if the datasheet provides different m, b and R for
|
||||
* those.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret <<= SQ24860_8B_SHIFT;
|
||||
break;
|
||||
|
||||
case PMBUS_IIN_OC_FAULT_LIMIT:
|
||||
/*
|
||||
* VIREF directly sets the over-current limit at which the eFuse
|
||||
* will turn the FET off and trigger a fault. Expose it through
|
||||
* this generic property instead of a manufacturer specific one.
|
||||
*/
|
||||
ret = pmbus_read_byte_data(client, page, SQ24860_VIREF);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM,
|
||||
SQ24860_IIN_OCF_DIV);
|
||||
ret += SQ24860_IIN_OCF_OFF;
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_SAMPLES:
|
||||
ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret));
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sq24860_write_word_data(struct i2c_client *client,
|
||||
int page, int reg, u16 value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
case PMBUS_VOUT_UV_WARN_LIMIT:
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_PIN_OP_WARN_LIMIT:
|
||||
value = max_t(s16, (s16)value, 0);
|
||||
value >>= SQ24860_8B_SHIFT;
|
||||
value = clamp_val(value, 0, 0xff);
|
||||
ret = pmbus_write_word_data(client, page, reg, value);
|
||||
break;
|
||||
|
||||
case PMBUS_IIN_OC_FAULT_LIMIT:
|
||||
value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF);
|
||||
value -= SQ24860_IIN_OCF_OFF;
|
||||
value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV,
|
||||
SQ24860_IIN_OCF_NUM);
|
||||
value = clamp_val(value, 0, 0x3f);
|
||||
ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value);
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_SAMPLES:
|
||||
value = clamp_val(value, 1, SQ24860_MAX_SAMPLES);
|
||||
value = ilog2(value);
|
||||
ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG,
|
||||
PK_MIN_AVG_AVG_CNT,
|
||||
FIELD_PREP(PK_MIN_AVG_AVG_CNT, value));
|
||||
break;
|
||||
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
/*
|
||||
* SQ24860 has history resets based on MIN/AVG/PEAK instead of per
|
||||
* sensor type. Exposing this quirk in hwmon is not desirable so
|
||||
* reset MIN, AVG and PEAK together. Even is there effectively only
|
||||
* one reset, which resets everything, expose the 5 entries so
|
||||
* userspace is not required map a sensor type to another to trigger
|
||||
* a reset
|
||||
*/
|
||||
ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG,
|
||||
PK_MIN_AVG_RST_MASK,
|
||||
PK_MIN_AVG_RST_MASK);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sq24860_read_byte_data(struct i2c_client *client,
|
||||
int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_WRITE_PROTECT:
|
||||
ret = sq24860_mfr_write_protect_get(client);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sq24860_write_byte_data(struct i2c_client *client,
|
||||
int page, int reg, u8 byte)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_WRITE_PROTECT:
|
||||
ret = sq24860_mfr_write_protect_set(client, byte);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR)
|
||||
static const struct regulator_desc sq24860_reg_desc[] = {
|
||||
PMBUS_REGULATOR_ONE_NODE("vout"),
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct pmbus_driver_info sq24860_base_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 64,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = 0,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.m[PSC_VOLTAGE_OUT] = 64,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
.R[PSC_VOLTAGE_OUT] = 0,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
.R[PSC_TEMPERATURE] = 0,
|
||||
/*
|
||||
* Current and power measurements depend on the calibration gain
|
||||
* programmed from the board-specific IMON resistor value.
|
||||
*/
|
||||
.format[PSC_CURRENT_IN] = direct,
|
||||
.m[PSC_CURRENT_IN] = 16,
|
||||
.b[PSC_CURRENT_IN] = 0,
|
||||
.R[PSC_CURRENT_IN] = 0,
|
||||
.format[PSC_POWER] = direct,
|
||||
.m[PSC_POWER] = 2,
|
||||
.b[PSC_POWER] = 0,
|
||||
.R[PSC_POWER] = 0,
|
||||
.func[0] = PMBUS_HAVE_VIN |
|
||||
PMBUS_HAVE_VOUT |
|
||||
PMBUS_HAVE_VMON |
|
||||
PMBUS_HAVE_IIN |
|
||||
PMBUS_HAVE_PIN |
|
||||
PMBUS_HAVE_TEMP |
|
||||
PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_SAMPLES,
|
||||
.read_word_data = sq24860_read_word_data,
|
||||
.write_word_data = sq24860_write_word_data,
|
||||
.read_byte_data = sq24860_read_byte_data,
|
||||
.write_byte_data = sq24860_write_byte_data,
|
||||
|
||||
#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR)
|
||||
.reg_desc = sq24860_reg_desc,
|
||||
.num_regulators = ARRAY_SIZE(sq24860_reg_desc),
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct i2c_device_id sq24860_i2c_id[] = {
|
||||
{ "sq24860" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id);
|
||||
|
||||
static const struct of_device_id sq24860_of_match[] = {
|
||||
{ .compatible = "silergy,sq24860" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sq24860_of_match);
|
||||
|
||||
static int sq24860_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct pmbus_driver_info *info;
|
||||
u32 rimon;
|
||||
int ret;
|
||||
|
||||
if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon))
|
||||
rimon = SQ24860_DEFAULT_RIMON;
|
||||
ret = sq24860_write_iin_cal_gain(client, rimon);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to set gain\n");
|
||||
info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static struct i2c_driver sq24860_driver = {
|
||||
.driver = {
|
||||
.name = "sq24860",
|
||||
.of_match_table = sq24860_of_match,
|
||||
},
|
||||
.probe = sq24860_probe,
|
||||
.id_table = sq24860_i2c_id,
|
||||
};
|
||||
module_i2c_driver(sq24860_driver);
|
||||
|
||||
MODULE_AUTHOR("Ziming Zhu <ziming.zhu@silergycorp.com>");
|
||||
MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
287
drivers/hwmon/pmbus/vt7505.c
Normal file
287
drivers/hwmon/pmbus/vt7505.c
Normal file
@@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and
|
||||
* Volterra VT7505 PMBus controllers.
|
||||
*
|
||||
* Copyright 2026 Hewlett Packard Enterprise Development LP
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pmbus.h>
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
#define VT7505_MFR_CONFIG 0xd0
|
||||
#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14)
|
||||
|
||||
#define VT7505_MFR_PEAK_VIN 0xd1
|
||||
#define VT7505_MFR_PEAK_IOUT 0xd2
|
||||
#define VT7505_MFR_PEAK_PIN 0xd3
|
||||
#define VT7505_MFR_PEAK_TEMP 0xd4
|
||||
#define VT7505_MFR_CLEAR_PEAKS 0xd5
|
||||
#define VT7505_MFR_PEAK_VOUT 0xfd
|
||||
|
||||
#define VT7505_RLOAD_DEFAULT 4750
|
||||
/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */
|
||||
#define VT7505_RLOAD_MAX 283383959U
|
||||
|
||||
struct vt7505_chip_data {
|
||||
int temp_m;
|
||||
int temp_b;
|
||||
bool has_ocp_filter;
|
||||
};
|
||||
|
||||
static const struct vt7505_chip_data max16545_data = {
|
||||
.temp_m = 205,
|
||||
.temp_b = 6545,
|
||||
};
|
||||
|
||||
static const struct vt7505_chip_data max16550_data = {
|
||||
.temp_m = 199,
|
||||
.temp_b = 7046,
|
||||
.has_ocp_filter = true,
|
||||
};
|
||||
|
||||
static const struct vt7505_chip_data vt7505_data = {
|
||||
.temp_m = 205,
|
||||
.temp_b = 6545,
|
||||
.has_ocp_filter = true,
|
||||
};
|
||||
|
||||
static int vt7505_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VIN_MAX:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
VT7505_MFR_PEAK_VIN);
|
||||
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
VT7505_MFR_PEAK_IOUT);
|
||||
case PMBUS_VIRT_READ_PIN_MAX:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
VT7505_MFR_PEAK_PIN);
|
||||
case PMBUS_VIRT_READ_TEMP_MAX:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
VT7505_MFR_PEAK_TEMP);
|
||||
case PMBUS_VIRT_READ_VOUT_MAX:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
VT7505_MFR_PEAK_VOUT);
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
return 0;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static int vt7505_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
switch (reg) {
|
||||
/*
|
||||
* A single reset command clears all peak values. CLEAR_PEAKS is a
|
||||
* send-byte command; the device NAKs a word or byte-data write to it.
|
||||
*/
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
return pmbus_write_byte(client, page,
|
||||
VT7505_MFR_CLEAR_PEAKS);
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* None of these controllers implement the standard PMBus WRITE_PROTECT
|
||||
* (0x10) register, so tell the core not to access it.
|
||||
*/
|
||||
static struct pmbus_platform_data vt7505_pdata = {
|
||||
.flags = PMBUS_NO_WRITE_PROTECT,
|
||||
};
|
||||
|
||||
static int vt7505_set_ocp_filter(struct i2c_client *client)
|
||||
{
|
||||
u32 ocp_us;
|
||||
u8 field;
|
||||
int ret;
|
||||
u16 word;
|
||||
|
||||
if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us",
|
||||
&ocp_us))
|
||||
return 0;
|
||||
|
||||
switch (ocp_us) {
|
||||
case 0:
|
||||
field = 0;
|
||||
break;
|
||||
case 1:
|
||||
field = 1;
|
||||
break;
|
||||
case 2:
|
||||
field = 2;
|
||||
break;
|
||||
case 10:
|
||||
field = 3;
|
||||
break;
|
||||
default:
|
||||
return dev_err_probe(&client->dev, -EINVAL,
|
||||
"invalid adi,ocp-severe-filter-us value %u\n",
|
||||
ocp_us);
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"failed to read MFR_CONFIG\n");
|
||||
|
||||
word = ret & ~VT7505_CFG_OCP_S_FILT_MASK;
|
||||
word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field);
|
||||
|
||||
ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"failed to write MFR_CONFIG\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vt7505_set_m(int *m, u32 rload)
|
||||
{
|
||||
u64 val = (u64)*m * rload;
|
||||
|
||||
/* rload is range-checked in probe, so the result fits in int. */
|
||||
*m = DIV_ROUND_CLOSEST_ULL(val, 1000);
|
||||
}
|
||||
|
||||
static int vt7505_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
const struct vt7505_chip_data *chip;
|
||||
struct pmbus_driver_info *info;
|
||||
u32 rload;
|
||||
int ret;
|
||||
|
||||
chip = i2c_get_match_data(client);
|
||||
if (!chip)
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
dev->platform_data = &vt7505_pdata;
|
||||
|
||||
/*
|
||||
* The m coefficient used in the direct-format current and power
|
||||
* calculations depends on RLOAD, the external current-report resistor
|
||||
* connected between the ILOAD pin and ground. Use the default value if
|
||||
* none is specified.
|
||||
*/
|
||||
if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload))
|
||||
rload = VT7505_RLOAD_DEFAULT;
|
||||
|
||||
if (!rload || rload > VT7505_RLOAD_MAX)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"adi,rload-ohms must be 1-%u\n",
|
||||
VT7505_RLOAD_MAX);
|
||||
|
||||
info->pages = 1;
|
||||
info->read_word_data = vt7505_read_word_data;
|
||||
info->write_word_data = vt7505_write_word_data;
|
||||
|
||||
info->format[PSC_VOLTAGE_IN] = direct;
|
||||
info->format[PSC_VOLTAGE_OUT] = direct;
|
||||
info->format[PSC_CURRENT_IN] = direct;
|
||||
info->format[PSC_CURRENT_OUT] = direct;
|
||||
info->format[PSC_POWER] = direct;
|
||||
info->format[PSC_TEMPERATURE] = direct;
|
||||
|
||||
/*
|
||||
* Direct data format coefficients from the device datasheet ("PMBus
|
||||
* Equation Parameters"). The current and power m coefficients scale
|
||||
* with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving
|
||||
* m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The
|
||||
* temperature coefficients are chip specific (see the chip data).
|
||||
*/
|
||||
info->m[PSC_VOLTAGE_IN] = 7578;
|
||||
info->R[PSC_VOLTAGE_IN] = -2;
|
||||
info->m[PSC_VOLTAGE_OUT] = 7578;
|
||||
info->R[PSC_VOLTAGE_OUT] = -2;
|
||||
info->m[PSC_CURRENT_IN] = 3824;
|
||||
info->b[PSC_CURRENT_IN] = -4300;
|
||||
info->R[PSC_CURRENT_IN] = -3;
|
||||
info->m[PSC_CURRENT_OUT] = 3824;
|
||||
info->b[PSC_CURRENT_OUT] = -4300;
|
||||
info->R[PSC_CURRENT_OUT] = -3;
|
||||
info->m[PSC_POWER] = 895;
|
||||
info->b[PSC_POWER] = -9100;
|
||||
info->R[PSC_POWER] = -2;
|
||||
|
||||
vt7505_set_m(&info->m[PSC_CURRENT_IN], rload);
|
||||
vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload);
|
||||
vt7505_set_m(&info->m[PSC_POWER], rload);
|
||||
|
||||
info->m[PSC_TEMPERATURE] = chip->temp_m;
|
||||
info->b[PSC_TEMPERATURE] = chip->temp_b;
|
||||
info->R[PSC_TEMPERATURE] = -2;
|
||||
|
||||
info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_PIN;
|
||||
|
||||
/*
|
||||
* The severe OCP deglitch filter is programmable on the MAX16550 and
|
||||
* the VT7505, but fixed on the MAX16545.
|
||||
*/
|
||||
if (chip->has_ocp_filter) {
|
||||
ret = vt7505_set_ocp_filter(client);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id vt7505_id[] = {
|
||||
{ .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data },
|
||||
{ .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data },
|
||||
{ .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, vt7505_id);
|
||||
|
||||
static const struct of_device_id vt7505_of_match[] = {
|
||||
{ .compatible = "adi,max16545", .data = &max16545_data },
|
||||
{ .compatible = "adi,max16550", .data = &max16550_data },
|
||||
{ .compatible = "adi,vt7505", .data = &vt7505_data },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vt7505_of_match);
|
||||
|
||||
static struct i2c_driver vt7505_driver = {
|
||||
.driver = {
|
||||
.name = "vt7505",
|
||||
.of_match_table = vt7505_of_match,
|
||||
},
|
||||
.probe = vt7505_probe,
|
||||
.id_table = vt7505_id,
|
||||
};
|
||||
module_i2c_driver(vt7505_driver);
|
||||
|
||||
MODULE_AUTHOR("Georgi Vlaev <gvlaev@juniper.net>");
|
||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
@@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR)
|
||||
static const struct regulator_desc xdpe1a2g7b_reg_desc[] = {
|
||||
PMBUS_REGULATOR("vout", 0),
|
||||
PMBUS_REGULATOR("vout", 1),
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct pmbus_driver_info xdpe1a2g7b_info = {
|
||||
.pages = XDPE1A2G7B_PAGE_NUM,
|
||||
.identify = xdpe1a2g7b_identify,
|
||||
@@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = {
|
||||
.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,
|
||||
#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR)
|
||||
.num_regulators = XDPE1A2G7B_PAGE_NUM,
|
||||
.reg_desc = xdpe1a2g7b_reg_desc,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int xdpe1a2g7b_probe(struct i2c_client *client)
|
||||
|
||||
@@ -628,12 +628,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
if (tach->irq > 0) {
|
||||
ret = devm_request_irq(dev, tach->irq, pulse_handler,
|
||||
IRQF_NO_THREAD, pdev->name, tach);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to request interrupt: %d\n",
|
||||
ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx->pulses_per_revolution[i]) {
|
||||
|
||||
@@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev)
|
||||
IRQF_TRIGGER_FALLING,
|
||||
"sht15 data",
|
||||
data);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get irq for data line\n");
|
||||
if (ret)
|
||||
goto err_release_reg;
|
||||
}
|
||||
disable_irq_nosync(gpiod_to_irq(data->data));
|
||||
ret = sht15_connection_reset(data);
|
||||
if (ret)
|
||||
|
||||
@@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 };
|
||||
#define SHT3X_MAX_HUMIDITY 100000
|
||||
|
||||
enum sht3x_chips {
|
||||
sht3x,
|
||||
sht3x = 1,
|
||||
sts3x,
|
||||
};
|
||||
|
||||
@@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = {
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, sht3x_ids);
|
||||
|
||||
static const struct of_device_id sht3x_of_match[] = {
|
||||
{ .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x },
|
||||
{ .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, sht3x_of_match);
|
||||
|
||||
static struct i2c_driver sht3x_i2c_driver = {
|
||||
.driver.name = "sht3x",
|
||||
.driver = {
|
||||
.name = "sht3x",
|
||||
.of_match_table = sht3x_of_match,
|
||||
},
|
||||
.probe = sht3x_probe,
|
||||
.id_table = sht3x_ids,
|
||||
};
|
||||
|
||||
@@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume
|
||||
|
||||
static const struct i2c_device_id tmp102_id[] = {
|
||||
{ .name = "tmp102" },
|
||||
{ .name = "tmp110" },
|
||||
{ .name = "tmp113" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tmp102_id);
|
||||
|
||||
@@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client)
|
||||
info->type = hwmon_chip;
|
||||
info->config = data->chip_channel_config;
|
||||
|
||||
data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL;
|
||||
data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL;
|
||||
|
||||
info = &data->temp_info;
|
||||
info->type = hwmon_temp;
|
||||
|
||||
390
drivers/hwmon/tvs-mpfs.c
Normal file
390
drivers/hwmon/tvs-mpfs.c
Normal file
@@ -0,0 +1,390 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Author: Lars Randers <lranders@mail.dk>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define MPFS_TVS_CTRL 0x08
|
||||
#define MPFS_TVS_OUTPUT0 0x24
|
||||
#define MPFS_TVS_OUTPUT1 0x28
|
||||
|
||||
#define MPFS_TVS_CTRL_TEMP_VALID BIT(19)
|
||||
#define MPFS_TVS_CTRL_V2P5_VALID BIT(18)
|
||||
#define MPFS_TVS_CTRL_V1P8_VALID BIT(17)
|
||||
#define MPFS_TVS_CTRL_V1P05_VALID BIT(16)
|
||||
|
||||
#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3)
|
||||
#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2)
|
||||
#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1)
|
||||
#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0)
|
||||
#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0)
|
||||
|
||||
/*
|
||||
* For all of these the value in millivolts is stored in 16 bits, with an upper
|
||||
* sign bit and a lower 3 bits of decimal. These masks discard the sign bit and
|
||||
* decimal places, because if Linux is running these voltages cannot be negative
|
||||
* and so avoid having to convert to two's complement.
|
||||
*/
|
||||
#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19)
|
||||
#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3)
|
||||
#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3)
|
||||
|
||||
/*
|
||||
* The register map claims that the temperature is stored in bits 31:16, but
|
||||
* application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor"
|
||||
* says that 31 is reserved. Temperature is in kelvin, so what's probably a
|
||||
* sign bit has no value anyway.
|
||||
*/
|
||||
#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16)
|
||||
|
||||
#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8)
|
||||
#define MPFS_TVS_INTERVAL_OFFSET 8
|
||||
/* The interval register is in increments of 32 us */
|
||||
#define MPFS_TVS_INTERVAL_SCALE 32
|
||||
/* with 254 usable increments of 32 us available, 8 ms is the integer limit */
|
||||
#define MPFS_TVS_INTERVAL_MAX_MS 8
|
||||
|
||||
/* 273.1875 in 11.4 fixed-point notation */
|
||||
#define MPFS_TVS_K_TO_C 0x1113
|
||||
|
||||
enum mpfs_tvs_sensors {
|
||||
SENSOR_V1P05 = 0,
|
||||
SENSOR_V1P8,
|
||||
SENSOR_V2P5,
|
||||
};
|
||||
|
||||
static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" };
|
||||
|
||||
struct mpfs_tvs {
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr,
|
||||
int channel, long *val)
|
||||
{
|
||||
u32 tmp, control;
|
||||
|
||||
if (attr != hwmon_in_input && attr != hwmon_in_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_CTRL, &control);
|
||||
|
||||
switch (channel) {
|
||||
case SENSOR_V2P5:
|
||||
if (attr == hwmon_in_enable) {
|
||||
*val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(control & MPFS_TVS_CTRL_V2P5_VALID))
|
||||
return -ENODATA;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp);
|
||||
*val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp);
|
||||
break;
|
||||
case SENSOR_V1P8:
|
||||
if (attr == hwmon_in_enable) {
|
||||
*val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(control & MPFS_TVS_CTRL_V1P8_VALID))
|
||||
return -ENODATA;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp);
|
||||
*val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp);
|
||||
break;
|
||||
case SENSOR_V1P05:
|
||||
if (attr == hwmon_in_enable) {
|
||||
*val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(control & MPFS_TVS_CTRL_V1P05_VALID))
|
||||
return -ENODATA;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp);
|
||||
*val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr,
|
||||
int channel, long val)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
if (attr != hwmon_in_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (val > 1 || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
switch (channel) {
|
||||
case SENSOR_V2P5:
|
||||
tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val);
|
||||
regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
|
||||
MPFS_TVS_CTRL_V2P5_ENABLE, tmp);
|
||||
break;
|
||||
case SENSOR_V1P8:
|
||||
tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val);
|
||||
regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
|
||||
MPFS_TVS_CTRL_V1P8_ENABLE, tmp);
|
||||
break;
|
||||
case SENSOR_V1P05:
|
||||
tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val);
|
||||
regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
|
||||
MPFS_TVS_CTRL_V1P05_ENABLE, tmp);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val)
|
||||
{
|
||||
u32 tmp, control;
|
||||
|
||||
if (attr != hwmon_temp_input && attr != hwmon_temp_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_CTRL, &control);
|
||||
|
||||
if (attr == hwmon_temp_enable) {
|
||||
*val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(control & MPFS_TVS_CTRL_TEMP_VALID))
|
||||
return -ENODATA;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp);
|
||||
*val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp);
|
||||
*val -= MPFS_TVS_K_TO_C;
|
||||
*val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
if (attr != hwmon_temp_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (val > 1 || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val);
|
||||
regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
|
||||
MPFS_TVS_CTRL_TEMP_ENABLE, tmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
if (attr != hwmon_chip_update_interval)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp);
|
||||
*val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp);
|
||||
*val *= MPFS_TVS_INTERVAL_SCALE;
|
||||
*val = roundup(*val, 1000);
|
||||
*val /= 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val)
|
||||
{
|
||||
long temp = val;
|
||||
|
||||
if (attr != hwmon_chip_update_interval)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS);
|
||||
|
||||
temp *= 1000;
|
||||
temp /= MPFS_TVS_INTERVAL_SCALE;
|
||||
|
||||
temp <<= MPFS_TVS_INTERVAL_OFFSET;
|
||||
regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
|
||||
MPFS_TVS_INTERVAL_MASK, temp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t mpfs_tvs_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
if (type == hwmon_chip && attr == hwmon_chip_update_interval)
|
||||
return 0644;
|
||||
|
||||
if (type == hwmon_temp) {
|
||||
switch (attr) {
|
||||
case hwmon_temp_enable:
|
||||
return 0644;
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_label:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == hwmon_in) {
|
||||
switch (attr) {
|
||||
case hwmon_in_enable:
|
||||
return 0644;
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_label:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct mpfs_tvs *data = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return mpfs_tvs_temp_read(data, attr, val);
|
||||
case hwmon_in:
|
||||
return mpfs_tvs_voltage_read(data, attr, channel, val);
|
||||
case hwmon_chip:
|
||||
return mpfs_tvs_interval_read(data, attr, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct mpfs_tvs *data = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return mpfs_tvs_temp_write(data, attr, val);
|
||||
case hwmon_in:
|
||||
return mpfs_tvs_voltage_write(data, attr, channel, val);
|
||||
case hwmon_chip:
|
||||
return mpfs_tvs_interval_write(data, attr, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int mpfs_tvs_read_labels(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel,
|
||||
const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
*str = "Die Temp";
|
||||
return 0;
|
||||
case hwmon_in:
|
||||
*str = mpfs_tvs_voltage_labels[channel];
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_ops mpfs_tvs_ops = {
|
||||
.is_visible = mpfs_tvs_is_visible,
|
||||
.read_string = mpfs_tvs_read_labels,
|
||||
.read = mpfs_tvs_read,
|
||||
.write = mpfs_tvs_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *mpfs_tvs_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info mpfs_tvs_chip_info = {
|
||||
.ops = &mpfs_tvs_ops,
|
||||
.info = mpfs_tvs_info,
|
||||
};
|
||||
|
||||
static int mpfs_tvs_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *hwmon_dev;
|
||||
struct mpfs_tvs *data;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->regmap = device_node_to_regmap(pdev->dev.parent->of_node);
|
||||
if (IS_ERR(data->regmap))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap),
|
||||
"Failed to find syscon regmap\n");
|
||||
|
||||
/*
|
||||
* It's an MMIO regmap with no resources, there's nothing that can fail
|
||||
* and return an error
|
||||
*/
|
||||
regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs",
|
||||
data,
|
||||
&mpfs_tvs_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev),
|
||||
"hwmon device registration failed.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mpfs_tvs_driver = {
|
||||
.probe = mpfs_tvs_probe,
|
||||
.driver = {
|
||||
.name = "mpfs-tvs",
|
||||
},
|
||||
};
|
||||
module_platform_driver(mpfs_tvs_driver);
|
||||
|
||||
MODULE_AUTHOR("Lars Randers <lranders@mail.dk>");
|
||||
MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
|
||||
init_completion(&ctx->rd_complete);
|
||||
ctx->resp_pending = true;
|
||||
|
||||
/* Write signature for subspace */
|
||||
WRITE_ONCE(generic_comm_base->signature,
|
||||
cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx));
|
||||
|
||||
/* Write to the shared command region */
|
||||
WRITE_ONCE(generic_comm_base->command,
|
||||
cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR));
|
||||
|
||||
@@ -53,6 +53,7 @@ struct yoga_fan_data {
|
||||
};
|
||||
|
||||
/* Specific configurations mapped via DMI */
|
||||
|
||||
static const struct yogafan_config yoga_8bit_fans_cfg = {
|
||||
.multiplier = 100,
|
||||
.fan_count = 1,
|
||||
@@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = {
|
||||
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
|
||||
};
|
||||
|
||||
static const struct yogafan_config loq_15iax9_8bit_dual_cfg = {
|
||||
.multiplier = 100,
|
||||
.fan_count = 2,
|
||||
.paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" }
|
||||
};
|
||||
|
||||
static const struct yogafan_config xiaoxin_8bit_dual_cfg = {
|
||||
.multiplier = 100,
|
||||
.fan_count = 2,
|
||||
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
|
||||
};
|
||||
|
||||
static const struct yogafan_config yoga_pro_7_14iah10_cfg = {
|
||||
.multiplier = 100,
|
||||
.fan_count = 1,
|
||||
.paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL }
|
||||
};
|
||||
|
||||
static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
|
||||
{
|
||||
ktime_t now = ktime_get_boottime();
|
||||
@@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = {
|
||||
};
|
||||
|
||||
static const struct dmi_system_id yogafan_quirks[] = {
|
||||
{
|
||||
.ident = "Lenovo LOQ 15IAX9",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"),
|
||||
},
|
||||
.driver_data = (void *)&loq_15iax9_8bit_dual_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo XiaoXin Pro 13ARE 2020",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"),
|
||||
},
|
||||
.driver_data = (void *)&xiaoxin_8bit_dual_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo IdeaPad 3 15ALC6",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"),
|
||||
},
|
||||
.driver_data = (void *)&ideapad_8bit_fan0_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo Legion Pro 7 16AFR10H",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"),
|
||||
},
|
||||
.driver_data = (void *)&xiaoxin_8bit_dual_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo Yoga Pro 7 14IAH10",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"),
|
||||
},
|
||||
.driver_data = (void *)&yoga_pro_7_14iah10_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo Yoga 7 16ARP8",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"),
|
||||
},
|
||||
.driver_data = (void *)&xiaoxin_8bit_dual_cfg,
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo Yoga",
|
||||
.matches = {
|
||||
|
||||
@@ -15,10 +15,10 @@ struct sensor_device_attribute{
|
||||
int index;
|
||||
};
|
||||
#define to_sensor_dev_attr(_dev_attr) \
|
||||
container_of(_dev_attr, struct sensor_device_attribute, dev_attr)
|
||||
container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr)
|
||||
|
||||
#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \
|
||||
{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
|
||||
#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \
|
||||
{ .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \
|
||||
.index = _index }
|
||||
|
||||
#define SENSOR_ATTR_RO(_name, _func, _index) \
|
||||
@@ -49,11 +49,11 @@ struct sensor_device_attribute_2 {
|
||||
u8 nr;
|
||||
};
|
||||
#define to_sensor_dev_attr_2(_dev_attr) \
|
||||
container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr)
|
||||
container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr)
|
||||
|
||||
#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \
|
||||
{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
|
||||
.index = _index, \
|
||||
#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \
|
||||
{ .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \
|
||||
.index = _index, \
|
||||
.nr = _nr }
|
||||
|
||||
#define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \
|
||||
|
||||
Reference in New Issue
Block a user