mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
hwmon updates for v6.18-rc1
* New drivers - Driver for Kontron SMARC-sAM67 - Driver for GPD device sensors - Driver for MP29502 - Driver for MP2869, MP29608, MP29612 and MP29816 series * Added chip support to existing drivers - asus-ec-sensors: Add B650E-I Add PRIME Z270-A Add Pro WS WRX90E-SAGE SE Add ROG STRIX X670E-E GAMING WIFI Add ROG STRIX X870-I GAMING WIFI Add ROG STRIX X870E-E GAMING WIFI Add ROG STRIX Z690-E GAMING WIFI Add ROG STRIX Z790E GAMING WIFI II Add STRIX B850-I GAMING WIFI Add TUF GAMING X670E PLUS WIFI Add X670E-I GAMING WIFI Add Z790-I GAMING WIFI - dell-smm: Add support for Dell OptiPlex 7040 - ina238: Major cleanup, and Add support for INA700 Add support for INA780 - k10temp: Add device ID for Strix Halo Add support for AMD Family 1Ah-based models - lenovo-ec-sensors: Update P8 supprt - lm75: Add NXP P3T1750 support - pmbus/adm1275: Add sq24905c support - pmbus/isl68137: Add support for Renesas RAA228244 and RAA228246 - pmbus/mp5990: Add support for MP5998 - sht21: Add support for SHT20, SHT25 - sl28cpld: Add sa67mcu compatible * Other notable changes - core: Handle locking internally Introduce 64-bit energy attribute support - cros_ec: Register into thermal framework, improve PWM control - lm75: allow interrupt for ti,tmp75 - mlxreg-fan: Add support for new flavour of capability register - sbtsi_temp: AMD CPU extended temperature range support - sht21: Add devicetree support * Various other minor improvements and fixes -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmjZ2VAACgkQyx8mb86f mYHegQ//YImjHnlWcRbr8meprHHezIkGDA0nJEzjsCSD7owq00sOC4w4BGXf7MPV d1rK0VbAU2Up53j4xZKr7PnHzUHFil/K/9hDBHFfYd6fr570ADdMnewRmVoKuSuR JBeV0308y0WbeAhNOaIIcGx3/4wMgQklvUycjOZKUgBC2jY/vnuwlgrt21a4rbB/ c5yNUEfqk9thZY/xOBbbrcvej0RXZ/naV+tRgVYQUo6Ep1CUynxOr2VCUbWxoQWh lnekEhJuylbIDodAZ405Cpn3AuqyVypbKBjAyGWSwl92KuoYyBGJnbk/GYokWvCH ftMlLF+HUbNRd75W3mwxZOuSX5tIps2NP8aQrdRdOxhF6Ln5b49R4NLI7ZqnOVzO syRo96J1joMIGwfRga/b8dqCLYZ45fX2VbfisdhrzZ0OM3e7yg3fVyJRbxubP+n6 VxCQPfFUQoLKGOAiBfCA5yYCDE+qFXT1HBKORhtUF/wZnq6X7rYMtC0ci0TfcKo0 Ar0s4xnxX2avFhySvuJL7snv01oOR/9SkCrKY4LqXETMSvNwTNQjLKGo0uly+Mxs bs63JFnLtA1BHXvPuFklxDMAqQ4KZZAz1AznePFV7uFRk2VRbzKf1GkS4S9jVlc+ 2/nKb2gWBVdtAqone0D0dC9EoFNZwGqCPuymyTgEkYkO0vgZ2qs= =jb6g -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "New drivers: - Kontron SMARC-sAM67 - GPD device sensors - MP29502 - MP2869, MP29608, MP29612 and MP29816 series Added chip support to existing drivers: - asus-ec-sensors: Add B650E-I Add PRIME Z270-A Add Pro WS WRX90E-SAGE SE Add ROG STRIX X670E-E GAMING WIFI Add ROG STRIX X870-I GAMING WIFI Add ROG STRIX X870E-E GAMING WIFI Add ROG STRIX Z690-E GAMING WIFI Add ROG STRIX Z790E GAMING WIFI II Add STRIX B850-I GAMING WIFI Add TUF GAMING X670E PLUS WIFI Add X670E-I GAMING WIFI Add Z790-I GAMING WIFI - dell-smm: Add support for Dell OptiPlex 7040 - ina238: Major cleanup, and Add support for INA700 Add support for INA780 - k10temp: Add device ID for Strix Halo Add support for AMD Family 1Ah-based models - lenovo-ec-sensors: Update P8 supprt - lm75: Add NXP P3T1750 support - pmbus/adm1275: Add sq24905c support - pmbus/isl68137: Add support for Renesas RAA228244 and RAA228246 - pmbus/mp5990: Add support for MP5998 - sht21: Add support for SHT20, SHT25 - sl28cpld: Add sa67mcu compatible Other notable changes: - core: Handle locking internally Introduce 64-bit energy attribute support - cros_ec: Register into thermal framework, improve PWM control - lm75: allow interrupt for ti,tmp75 - mlxreg-fan: Add support for new flavour of capability register - sbtsi_temp: AMD CPU extended temperature range support - sht21: Add devicetree support Various other minor improvements and fixes" * tag 'hwmon-for-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (86 commits) dt-bindings: hwmon: (lm75) allow interrupt for ti,tmp75 hwmon: (mlxreg-fan) Add support for new flavour of capability register hwmon: (mlxreg-fan) Separate methods of fan setting coming from different subsystems hwmon: (cros_ec) register fans into thermal framework cooling devices hwmon: (cros_ec) add PWM control over fans platform/chrome: update pwm fan control host commands hwmon: add SMARC-sAM67 support dt-bindings: hwmon: sl28cpld: add sa67mcu compatible hwmon: (asus-ec-sensors) add TUF GAMING X670E PLUS WIFI hwmon: (dell-smm) Add support for Dell OptiPlex 7040 hwmon: (dell-smm) Add support for automatic fan mode hwmon: (gpd-fan) complete Kconfig dependencies hwmon: (asus-ec-sensors) increase timeout for locking ACPI mutex hwmon: (asus-ec-sensors) add ROG STRIX X870E-E GAMING WIFI hwmon: (dell-smm) Move clamping of fan speed out of i8k_set_fan() hwmon: (dell-smm) Remove Dell Precision 490 custom config data hwmon: (asus-ec-sensors) add ROG STRIX X670E-E GAMING WIFI hwmon: (gpd-fan) Fix range check for pwm input hwmon: (pmbus/mp5990) add support for MP5998 dt-bindings: trivial-devices: add mps,mp5998 ...
This commit is contained in:
commit
989253cc46
|
|
@ -18,6 +18,13 @@ description: |
|
|||
Datasheets:
|
||||
https://www.analog.com/en/products/adm1294.html
|
||||
|
||||
The SQ24905C is also a Hot-swap controller compatibility to the ADM1278,
|
||||
the PMBUS_MFR_MODEL is MC09C
|
||||
|
||||
Datasheets:
|
||||
https://www.silergy.com/
|
||||
download/downloadFile?id=5669&type=product&ftype=note
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
|
|
@ -30,6 +37,7 @@ properties:
|
|||
- adi,adm1281
|
||||
- adi,adm1293
|
||||
- adi,adm1294
|
||||
- silergy,mc09c
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
@ -96,6 +104,7 @@ allOf:
|
|||
- adi,adm1281
|
||||
- adi,adm1293
|
||||
- adi,adm1294
|
||||
- silergy,mc09c
|
||||
then:
|
||||
properties:
|
||||
adi,volt-curr-sample-average:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ description: |
|
|||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- kontron,sa67mcu-hwmon
|
||||
- kontron,sl28cpld-fan
|
||||
|
||||
reg:
|
||||
|
|
|
|||
30
Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml
Normal file
30
Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/lantiq,cputemp.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Lantiq cpu temperature sensor
|
||||
|
||||
maintainers:
|
||||
- Florian Eckert <fe@dev.tdt.de>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: lantiq,cputemp
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
cputemp@103040 {
|
||||
compatible = "lantiq,cputemp";
|
||||
reg = <0x103040 0x4>;
|
||||
};
|
||||
|
|
@ -28,6 +28,7 @@ properties:
|
|||
- maxim,max31725
|
||||
- maxim,max31726
|
||||
- maxim,mcp980x
|
||||
- nxp,p3t1750
|
||||
- nxp,p3t1755
|
||||
- nxp,pct2075
|
||||
- st,stds75
|
||||
|
|
@ -69,6 +70,7 @@ allOf:
|
|||
- ti,tmp100
|
||||
- ti,tmp101
|
||||
- ti,tmp112
|
||||
- ti,tmp75
|
||||
then:
|
||||
properties:
|
||||
interrupts: false
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
Lantiq cpu temperature sensor
|
||||
|
||||
Requires node properties:
|
||||
- compatible value :
|
||||
"lantiq,cputemp"
|
||||
|
||||
Example:
|
||||
cputemp@0 {
|
||||
compatible = "lantiq,cputemp";
|
||||
};
|
||||
|
|
@ -54,6 +54,8 @@ properties:
|
|||
- renesas,raa228004
|
||||
- renesas,raa228006
|
||||
- renesas,raa228228
|
||||
- renesas,raa228244
|
||||
- renesas,raa228246
|
||||
- renesas,raa229001
|
||||
- renesas,raa229004
|
||||
- renesas,raa229621
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ properties:
|
|||
it must be self resetting edge interrupts.
|
||||
maxItems: 1
|
||||
|
||||
fan-shutdown-percent:
|
||||
description:
|
||||
Fan RPM in percent set during shutdown. This is used to keep the fan
|
||||
running at fixed RPM after the kernel shut down, which is useful on
|
||||
hardware that does keep heating itself even after the kernel did shut
|
||||
down, for example from some sort of management core.
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
|
||||
fan-stop-to-start-percent:
|
||||
description:
|
||||
Minimum fan RPM in percent to start when stopped.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ properties:
|
|||
- ti,ina237
|
||||
- ti,ina238
|
||||
- ti,ina260
|
||||
- ti,ina700
|
||||
- ti,ina780
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
@ -114,10 +116,42 @@ allOf:
|
|||
- ti,ina237
|
||||
- ti,ina238
|
||||
- ti,ina260
|
||||
- ti,ina700
|
||||
- ti,ina780
|
||||
then:
|
||||
properties:
|
||||
ti,maximum-expected-current-microamp: false
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- silergy,sy24655
|
||||
- ti,ina209
|
||||
- ti,ina219
|
||||
- ti,ina220
|
||||
- ti,ina226
|
||||
- ti,ina230
|
||||
- ti,ina231
|
||||
- ti,ina260
|
||||
- ti,ina700
|
||||
- ti,ina780
|
||||
then:
|
||||
properties:
|
||||
ti,shunt-gain: false
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- ti,ina700
|
||||
- ti,ina780
|
||||
then:
|
||||
properties:
|
||||
shunt-resistor: false
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
label:
|
||||
description:
|
||||
A descriptive name for this channel, like "ambient" or "psu".
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
|
|
@ -45,6 +49,7 @@ examples:
|
|||
reg = <0x48>;
|
||||
interrupt-parent = <&gpio7>;
|
||||
interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
|
||||
label = "somelabel";
|
||||
vcc-supply = <&supply>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -293,10 +293,20 @@ properties:
|
|||
- mps,mp2856
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp2857
|
||||
- mps,mp2857
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp2869
|
||||
- mps,mp2869
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp2888
|
||||
- mps,mp2888
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp2891
|
||||
- mps,mp2891
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp29502
|
||||
- mps,mp29502
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp29608
|
||||
- mps,mp29608
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp29612
|
||||
- mps,mp29612
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp29816
|
||||
- mps,mp29816
|
||||
# Monolithic Power Systems Inc. multi-phase controller mp2993
|
||||
- mps,mp2993
|
||||
# Monolithic Power Systems Inc. hot-swap protection device
|
||||
|
|
@ -305,6 +315,8 @@ properties:
|
|||
- mps,mp5920
|
||||
# Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990
|
||||
- mps,mp5990
|
||||
# Monolithic Power Systems Inc. multi-phase hot-swap controller mp5998
|
||||
- mps,mp5998
|
||||
# Monolithic Power Systems Inc. digital step-down converter mp9941
|
||||
- mps,mp9941
|
||||
# Temperature sensor with integrated fan control
|
||||
|
|
@ -362,6 +374,9 @@ properties:
|
|||
# Sensirion low power multi-pixel gas sensor with I2C interface
|
||||
- sensirion,sgpc3
|
||||
# Sensirion temperature & humidity sensor with I2C interface
|
||||
- sensirion,sht20
|
||||
- sensirion,sht21
|
||||
- sensirion,sht25
|
||||
- sensirion,sht4x
|
||||
# Sensortek 3 axis accelerometer
|
||||
- sensortek,stk8312
|
||||
|
|
|
|||
|
|
@ -67,6 +67,14 @@ Supported chips:
|
|||
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1293_1294.pdf
|
||||
|
||||
* Silergy SQ24905C
|
||||
|
||||
Prefix: 'mc09c'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://www.silergy.com/download/downloadFile?id=5669&type=product&ftype=note
|
||||
|
||||
Author: Guenter Roeck <linux@roeck-us.net>
|
||||
|
||||
|
||||
|
|
@ -74,14 +82,14 @@ Description
|
|||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Analog Devices ADM1075, ADM1272,
|
||||
ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and ADM1294 Hot-Swap
|
||||
Controller and Digital Power Monitors.
|
||||
ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, ADM1294, and SQ24905C
|
||||
Hot-Swap Controller and Digital Power Monitors.
|
||||
|
||||
ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and
|
||||
ADM1294 are hot-swap controllers that allow a circuit board to be removed from
|
||||
or inserted into a live backplane. They also feature current and voltage
|
||||
readback via an integrated 12 bit analog-to-digital converter (ADC), accessed
|
||||
using a PMBus interface.
|
||||
ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293,
|
||||
ADM1294 and SQ24905C are hot-swap controllers that allow a circuit board to be
|
||||
removed from or inserted into a live backplane. They also feature current and
|
||||
voltage readback via an integrated 12 bit analog-to-digital converter (ADC),
|
||||
accessed using a PMBus interface.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
|
|
@ -160,5 +168,5 @@ temp1_highest Highest observed temperature.
|
|||
temp1_reset_history Write any value to reset history.
|
||||
|
||||
Temperature attributes are supported on ADM1272,
|
||||
ADM1273, ADM1278, and ADM1281.
|
||||
ADM1273, ADM1278, ADM1281 and SQ24905C.
|
||||
======================= =======================================================
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ Supported boards:
|
|||
* PRIME X470-PRO
|
||||
* PRIME X570-PRO
|
||||
* PRIME X670E-PRO WIFI
|
||||
* PRIME Z270-A
|
||||
* Pro WS X570-ACE
|
||||
* Pro WS WRX90E-SAGE SE
|
||||
* ProArt X570-CREATOR WIFI
|
||||
* ProArt X670E-CREATOR WIFI
|
||||
* ProArt X870E-CREATOR WIFI
|
||||
|
|
@ -25,16 +27,26 @@ Supported boards:
|
|||
* ROG MAXIMUS Z690 FORMULA
|
||||
* ROG STRIX B550-E GAMING
|
||||
* ROG STRIX B550-I GAMING
|
||||
* ROG STRIX B650E-I GAMING WIFI
|
||||
* ROG STRIX B850-I GAMING WIFI
|
||||
* ROG STRIX X570-E GAMING
|
||||
* ROG STRIX X570-E GAMING WIFI II
|
||||
* ROG STRIX X570-F GAMING
|
||||
* ROG STRIX X570-I GAMING
|
||||
* ROG STRIX X670E-E GAMING WIFI
|
||||
* ROG STRIX X670E-I GAMING WIFI
|
||||
* ROG STRIX X870-I GAMING WIFI
|
||||
* ROG STRIX X870E-E GAMING WIFI
|
||||
* ROG STRIX Z390-F GAMING
|
||||
* ROG STRIX Z490-F GAMING
|
||||
* ROG STRIX Z690-A GAMING WIFI D4
|
||||
* ROG STRIX Z690-E GAMING WIFI
|
||||
* ROG STRIX Z790-E GAMING WIFI II
|
||||
* ROG STRIX Z790-I GAMING WIFI
|
||||
* ROG ZENITH II EXTREME
|
||||
* ROG ZENITH II EXTREME ALPHA
|
||||
* TUF GAMING X670E PLUS
|
||||
* TUF GAMING X670E PLUS WIFI
|
||||
|
||||
Authors:
|
||||
- Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
|
|
|
|||
|
|
@ -23,4 +23,9 @@ ChromeOS embedded controller used in Chromebooks and other devices.
|
|||
|
||||
The channel labels exposed via hwmon are retrieved from the EC itself.
|
||||
|
||||
Fan and temperature readings are supported.
|
||||
Fan and temperature readings are supported. PWM fan control is also supported if
|
||||
the EC also supports setting fan PWM values and fan mode. Note that EC will
|
||||
switch fan control mode back to auto when suspended. This driver will restore
|
||||
the fan state to what they were before suspended when resumed.
|
||||
If a fan is controllable, this driver will register that fan as a cooling device
|
||||
in the thermal framework as well.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ curr1_label "iin"
|
|||
curr1_input Measured input current
|
||||
curr1_max Maximum input current
|
||||
curr1_max_alarm Input maximum current high alarm
|
||||
curr1_crit Critial high input current
|
||||
curr1_crit Critical high input current
|
||||
curr1_crit_alarm Input critical current high alarm
|
||||
curr1_rated_max Maximum rated input current
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ curr2_label "iout1"
|
|||
curr2_input Measured output current
|
||||
curr2_max Maximum output current
|
||||
curr2_max_alarm Output maximum current high alarm
|
||||
curr2_crit Critial high output current
|
||||
curr2_crit Critical high output current
|
||||
curr2_crit_alarm Output critical current high alarm
|
||||
curr2_rated_max Maximum rated output current
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fan[1-4]_min RO Minimal Fan speed in RPM
|
|||
fan[1-4]_max RO Maximal Fan speed in RPM
|
||||
fan[1-4]_target RO Expected Fan speed in RPM
|
||||
pwm[1-4] RW Control the fan PWM duty-cycle.
|
||||
pwm1_enable WO Enable or disable automatic BIOS fan
|
||||
pwm[1-4]_enable RW/WO Enable or disable automatic BIOS fan
|
||||
control (not supported on all laptops,
|
||||
see below for details).
|
||||
temp[1-10]_input RO Temperature reading in milli-degrees
|
||||
|
|
@ -49,26 +49,40 @@ temp[1-10]_label RO Temperature sensor label.
|
|||
Due to the nature of the SMM interface, each pwmX attribute controls
|
||||
fan number X.
|
||||
|
||||
Disabling automatic BIOS fan control
|
||||
------------------------------------
|
||||
Enabling/Disabling automatic BIOS fan control
|
||||
---------------------------------------------
|
||||
|
||||
On some laptops the BIOS automatically sets fan speed every few
|
||||
seconds. Therefore the fan speed set by mean of this driver is quickly
|
||||
overwritten.
|
||||
There exist two methods for enabling/disabling automatic BIOS fan control:
|
||||
|
||||
There is experimental support for disabling automatic BIOS fan
|
||||
control, at least on laptops where the corresponding SMM command is
|
||||
known, by writing the value ``1`` in the attribute ``pwm1_enable``
|
||||
(writing ``2`` enables automatic BIOS control again). Even if you have
|
||||
more than one fan, all of them are set to either enabled or disabled
|
||||
automatic fan control at the same time and, notwithstanding the name,
|
||||
``pwm1_enable`` sets automatic control for all fans.
|
||||
1. Separate SMM commands to enable/disable automatic BIOS fan control for all fans.
|
||||
|
||||
If ``pwm1_enable`` is not available, then it means that SMM codes for
|
||||
enabling and disabling automatic BIOS fan control are not whitelisted
|
||||
for your hardware. It is possible that codes that work for other
|
||||
laptops actually work for yours as well, or that you have to discover
|
||||
new codes.
|
||||
2. A special fan state that enables automatic BIOS fan control for a individual fan.
|
||||
|
||||
The driver cannot reliably detect what method should be used on a given
|
||||
device, so instead the following heuristic is used:
|
||||
|
||||
- use fan state 3 for enabling BIOS fan control if the maximum fan state
|
||||
setable by the user is smaller than 3 (default setting).
|
||||
|
||||
- use separate SMM commands if device is whitelisted to support them.
|
||||
|
||||
When using the first method, each fan will have a standard ``pwmX_enable``
|
||||
sysfs attribute. Writing ``1`` into this attribute will disable automatic
|
||||
BIOS fan control for the associated fan and set it to maximum speed. Enabling
|
||||
BIOS fan control again can be achieved by writing ``2`` into this attribute.
|
||||
Reading this sysfs attributes returns the current setting as reported by
|
||||
the underlying hardware.
|
||||
|
||||
When using the second method however, only the ``pwm1_enable`` sysfs attribute
|
||||
will be available to enable/disable automatic BIOS fan control globaly for all
|
||||
fans available on a given device. Additionally, this sysfs attribute is write-only
|
||||
as there exists no SMM command for reading the current fan control setting.
|
||||
|
||||
If no ``pwmX_enable`` attributes are available, then it means that the driver
|
||||
cannot use the first method and the SMM codes for enabling and disabling automatic
|
||||
BIOS fan control are not whitelisted for your device. It is possible that codes
|
||||
that work for other laptops actually work for yours as well, or that you have to
|
||||
discover new codes.
|
||||
|
||||
Check the list ``i8k_whitelist_fan_control`` in file
|
||||
``drivers/hwmon/dell-smm-hwmon.c`` in the kernel tree: as a first
|
||||
|
|
|
|||
78
Documentation/hwmon/gpd-fan.rst
Normal file
78
Documentation/hwmon/gpd-fan.rst
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver gpd-fan
|
||||
=========================
|
||||
|
||||
Author:
|
||||
- Cryolitia PukNgae <cryolitia@uniontech.com>
|
||||
|
||||
Description
|
||||
------------
|
||||
|
||||
Handheld devices from Shenzhen GPD Technology Co., Ltd. provide fan readings
|
||||
and fan control through their embedded controllers.
|
||||
|
||||
Supported devices
|
||||
-----------------
|
||||
|
||||
Currently the driver supports the following handhelds:
|
||||
|
||||
- GPD Win Mini (7840U)
|
||||
- GPD Win Mini (8840U)
|
||||
- GPD Win Mini (HX370)
|
||||
- GPD Pocket 4
|
||||
- GPD Duo
|
||||
- GPD Win Max 2 (6800U)
|
||||
- GPD Win Max 2 2023 (7840U)
|
||||
- GPD Win Max 2 2024 (8840U)
|
||||
- GPD Win Max 2 2025 (HX370)
|
||||
- GPD Win 4 (6800U)
|
||||
- GPD Win 4 (7840U)
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
gpd_fan_board
|
||||
Force specific which module quirk should be used.
|
||||
Use it like "gpd_fan_board=wm2".
|
||||
|
||||
- wm2
|
||||
- GPD Win 4 (7840U)
|
||||
- GPD Win Max 2 (6800U)
|
||||
- GPD Win Max 2 2023 (7840U)
|
||||
- GPD Win Max 2 2024 (8840U)
|
||||
- GPD Win Max 2 2025 (HX370)
|
||||
- win4
|
||||
- GPD Win 4 (6800U)
|
||||
- win_mini
|
||||
- GPD Win Mini (7840U)
|
||||
- GPD Win Mini (8840U)
|
||||
- GPD Win Mini (HX370)
|
||||
- GPD Pocket 4
|
||||
- GPD Duo
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported:
|
||||
|
||||
fan1_input
|
||||
Read Only. Reads current fan RPM.
|
||||
|
||||
pwm1_enable
|
||||
Read/Write. Enable manual fan control. Write "0" to disable control and run
|
||||
at full speed. Write "1" to set to manual, write "2" to let the EC control
|
||||
decide fan speed. Read this attribute to see current status.
|
||||
|
||||
NB:In consideration of the safety of the device, when setting to manual mode,
|
||||
the pwm speed will be set to the maximum value (255) by default. You can set
|
||||
a different value by writing pwm1 later.
|
||||
|
||||
pwm1
|
||||
Read/Write. Read this attribute to see current duty cycle in the range
|
||||
[0-255]. When pwm1_enable is set to "1" (manual) write any value in the
|
||||
range [0-255] to set fan speed.
|
||||
|
||||
NB: Many boards (except listed under wm2 above) don't support reading the
|
||||
current pwm value in auto mode. That will just return EOPNOTSUPP. In manual
|
||||
mode it will always return the real value.
|
||||
|
|
@ -42,6 +42,9 @@ register/unregister functions::
|
|||
|
||||
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
|
||||
|
||||
void hwmon_lock(struct device *dev);
|
||||
void hwmon_unlock(struct device *dev);
|
||||
|
||||
hwmon_device_register_with_info registers a hardware monitoring device.
|
||||
It creates the standard sysfs attributes in the hardware monitoring core,
|
||||
letting the driver focus on reading from and writing to the chip instead
|
||||
|
|
@ -79,6 +82,13 @@ devm_hwmon_sanitize_name is the resource managed version of
|
|||
hwmon_sanitize_name; the memory will be freed automatically on device
|
||||
removal.
|
||||
|
||||
When using ``[devm_]hwmon_device_register_with_info()`` to register the
|
||||
hardware monitoring device, accesses using the associated access functions
|
||||
are serialised by the hardware monitoring core. If a driver needs locking
|
||||
for other functions such as interrupt handlers or for attributes which are
|
||||
fully implemented in the driver, hwmon_lock() and hwmon_unlock() can be used
|
||||
to ensure that calls to those functions are serialized.
|
||||
|
||||
Using devm_hwmon_device_register_with_info()
|
||||
--------------------------------------------
|
||||
|
||||
|
|
@ -159,6 +169,7 @@ It contains following fields:
|
|||
hwmon_curr Current sensor
|
||||
hwmon_power Power sensor
|
||||
hwmon_energy Energy sensor
|
||||
hwmon_energy64 Energy sensor, reported as 64-bit signed value
|
||||
hwmon_humidity Humidity sensor
|
||||
hwmon_fan Fan speed sensor
|
||||
hwmon_pwm PWM control
|
||||
|
|
@ -288,6 +299,8 @@ Parameters:
|
|||
The sensor channel number.
|
||||
val:
|
||||
Pointer to attribute value.
|
||||
For hwmon_energy64, `'val`' is passed as `long *` but needs
|
||||
a typecast to `s64 *`.
|
||||
|
||||
Return value:
|
||||
0 on success, a negative error number otherwise.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,24 @@ Kernel driver ina238
|
|||
|
||||
Supported chips:
|
||||
|
||||
* Texas Instruments INA228
|
||||
|
||||
Prefix: 'ina228'
|
||||
|
||||
Addresses: I2C 0x40 - 0x4f
|
||||
|
||||
Datasheet:
|
||||
https://www.ti.com/lit/gpn/ina228
|
||||
|
||||
* Texas Instruments INA237
|
||||
|
||||
Prefix: 'ina237'
|
||||
|
||||
Addresses: I2C 0x40 - 0x4f
|
||||
|
||||
Datasheet:
|
||||
https://www.ti.com/lit/gpn/ina237
|
||||
|
||||
* Texas Instruments INA238
|
||||
|
||||
Prefix: 'ina238'
|
||||
|
|
@ -14,6 +32,16 @@ Supported chips:
|
|||
Datasheet:
|
||||
https://www.ti.com/lit/gpn/ina238
|
||||
|
||||
* Texas Instruments INA700
|
||||
|
||||
Datasheet:
|
||||
https://www.ti.com/product/ina700
|
||||
|
||||
* Texas Instruments INA780
|
||||
|
||||
Datasheet:
|
||||
https://www.ti.com/product/ina780a
|
||||
|
||||
* Silergy SQ52206
|
||||
|
||||
Prefix: 'SQ52206'
|
||||
|
|
@ -29,10 +57,20 @@ The INA238 is a current shunt, power and temperature monitor with an I2C
|
|||
interface. It includes a number of programmable functions including alerts,
|
||||
conversion rate, sample averaging and selectable shunt voltage accuracy.
|
||||
|
||||
The shunt value in micro-ohms can be set via platform data or device tree at
|
||||
compile-time or via the shunt_resistor attribute in sysfs at run-time. Please
|
||||
refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings
|
||||
if the device tree is used.
|
||||
The shunt value in micro-ohms can be set via device properties, either from
|
||||
platform code or from device tree data. Please refer to
|
||||
Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings if
|
||||
device tree is used.
|
||||
|
||||
INA237 is a functionally equivalent variant of INA238 with slightly
|
||||
different accuracy. INA228 is another variant of INA238 with higher ADC
|
||||
resolution. This chip also reports the energy.
|
||||
|
||||
INA700 and INA780 are variants of the chip series with built-in shunt resistor.
|
||||
They also report the energy.
|
||||
|
||||
SQ52206 is a mostly compatible chip from Sylergy. It reports the energy
|
||||
as well as the peak power consumption.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
|
@ -53,19 +91,19 @@ in1_max_alarm Maximum shunt voltage alarm
|
|||
power1_input Power measurement (uW)
|
||||
power1_max Maximum power threshold (uW)
|
||||
power1_max_alarm Maximum power alarm
|
||||
power1_input_highest Peak Power (uW)
|
||||
(SQ52206 only)
|
||||
|
||||
curr1_input Current measurement (mA)
|
||||
curr1_min Minimum current threshold (mA)
|
||||
curr1_min_alarm Minimum current alarm
|
||||
curr1_max Maximum current threshold (mA)
|
||||
curr1_max_alarm Maximum current alarm
|
||||
|
||||
energy1_input Energy measurement (uJ)
|
||||
(SQ52206, INA237, and INA780 only)
|
||||
|
||||
temp1_input Die temperature measurement (mC)
|
||||
temp1_max Maximum die temperature threshold (mC)
|
||||
temp1_max_alarm Maximum die temperature alarm
|
||||
======================= =======================================================
|
||||
|
||||
Additional sysfs entries for sq52206
|
||||
------------------------------------
|
||||
|
||||
======================= =======================================================
|
||||
energy1_input Energy measurement (uJ)
|
||||
|
||||
power1_input_highest Peak Power (uW)
|
||||
======================= =======================================================
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ Hardware Monitoring Kernel Drivers
|
|||
gigabyte_waterforce
|
||||
gsc-hwmon
|
||||
gl518sm
|
||||
gpd-fan
|
||||
gxp-fan-ctrl
|
||||
hih6130
|
||||
hp-wmi-sensors
|
||||
|
|
@ -173,8 +174,10 @@ Hardware Monitoring Kernel Drivers
|
|||
menf21bmc
|
||||
mlxreg-fan
|
||||
mp2856
|
||||
mp2869
|
||||
mp2888
|
||||
mp2891
|
||||
mp29502
|
||||
mp2975
|
||||
mp2993
|
||||
mp5023
|
||||
|
|
@ -211,6 +214,7 @@ Hardware Monitoring Kernel Drivers
|
|||
q54sj108a2
|
||||
qnap-mcu-hwmon
|
||||
raspberrypi-hwmon
|
||||
sa67
|
||||
sbrmi
|
||||
sbtsi_temp
|
||||
sch5627
|
||||
|
|
|
|||
|
|
@ -374,6 +374,26 @@ Supported chips:
|
|||
|
||||
Publicly available (after August 2020 launch) at the Renesas website
|
||||
|
||||
* Renesas RAA228244
|
||||
|
||||
Prefix: 'raa228244'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
Provided by Renesas upon request and NDA
|
||||
|
||||
* Renesas RAA228246
|
||||
|
||||
Prefix: 'raa228246'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
Provided by Renesas upon request and NDA
|
||||
|
||||
* Renesas RAA229001
|
||||
|
||||
Prefix: 'raa229001'
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ Supported chips:
|
|||
|
||||
https://www.ti.com/product/TMP1075
|
||||
|
||||
* NXP LM75B, P3T1755, PCT2075
|
||||
* NXP LM75B, P3T1750, P3T1755, PCT2075
|
||||
|
||||
Prefix: 'lm75b', 'p3t1755', 'pct2075'
|
||||
Prefix: 'lm75b', 'p3t1750', 'p3t1755', 'pct2075'
|
||||
|
||||
Addresses scanned: none
|
||||
|
||||
|
|
@ -131,6 +131,8 @@ Supported chips:
|
|||
|
||||
https://www.nxp.com/docs/en/data-sheet/LM75B.pdf
|
||||
|
||||
https://www.nxp.com/docs/en/data-sheet/P3T1750DP.pdf
|
||||
|
||||
https://www.nxp.com/docs/en/data-sheet/P3T1755.pdf
|
||||
|
||||
https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf
|
||||
|
|
|
|||
175
Documentation/hwmon/mp2869.rst
Normal file
175
Documentation/hwmon/mp2869.rst
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver mp2869
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* MPS mp2869
|
||||
|
||||
Prefix: 'mp2869'
|
||||
|
||||
* MPS mp29608
|
||||
|
||||
Prefix: 'mp29608'
|
||||
|
||||
* MPS mp29612
|
||||
|
||||
Prefix: 'mp29612'
|
||||
|
||||
* MPS mp29816
|
||||
|
||||
Prefix: 'mp29816'
|
||||
|
||||
Author:
|
||||
|
||||
Wensheng Wang <wenswang@yeah.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Monolithic Power Systems, Inc. (MPS)
|
||||
MP2869 Dual Loop Digital Multi-phase Controller.
|
||||
|
||||
Device compliant with:
|
||||
|
||||
- PMBus rev 1.3 interface.
|
||||
|
||||
The driver exports the following attributes via the 'sysfs' files
|
||||
for input voltage:
|
||||
|
||||
**in1_input**
|
||||
|
||||
**in1_label**
|
||||
|
||||
**in1_crit**
|
||||
|
||||
**in1_crit_alarm**
|
||||
|
||||
**in1_lcrit**
|
||||
|
||||
**in1_lcrit_alarm**
|
||||
|
||||
**in1_min**
|
||||
|
||||
**in1_min_alarm**
|
||||
|
||||
The driver provides the following attributes for output voltage:
|
||||
|
||||
**in2_input**
|
||||
|
||||
**in2_label**
|
||||
|
||||
**in2_crit**
|
||||
|
||||
**in2_crit_alarm**
|
||||
|
||||
**in2_lcrit**
|
||||
|
||||
**in2_lcrit_alarm**
|
||||
|
||||
**in3_input**
|
||||
|
||||
**in3_label**
|
||||
|
||||
**in3_crit**
|
||||
|
||||
**in3_crit_alarm**
|
||||
|
||||
**in3_lcrit**
|
||||
|
||||
**in3_lcrit_alarm**
|
||||
|
||||
The driver provides the following attributes for input current:
|
||||
|
||||
**curr1_input**
|
||||
|
||||
**curr1_label**
|
||||
|
||||
**curr2_input**
|
||||
|
||||
**curr2_label**
|
||||
|
||||
The driver provides the following attributes for output current:
|
||||
|
||||
**curr3_input**
|
||||
|
||||
**curr3_label**
|
||||
|
||||
**curr3_crit**
|
||||
|
||||
**curr3_crit_alarm**
|
||||
|
||||
**curr3_max**
|
||||
|
||||
**curr3_max_alarm**
|
||||
|
||||
**curr4_input**
|
||||
|
||||
**curr4_label**
|
||||
|
||||
**curr4_crit**
|
||||
|
||||
**curr4_crit_alarm**
|
||||
|
||||
**curr4_max**
|
||||
|
||||
**curr4_max_alarm**
|
||||
|
||||
The driver provides the following attributes for input power:
|
||||
|
||||
**power1_input**
|
||||
|
||||
**power1_label**
|
||||
|
||||
**power2_input**
|
||||
|
||||
**power2_label**
|
||||
|
||||
The driver provides the following attributes for output power:
|
||||
|
||||
**power3_input**
|
||||
|
||||
**power3_label**
|
||||
|
||||
**power3_input**
|
||||
|
||||
**power3_label**
|
||||
|
||||
**power3_max**
|
||||
|
||||
**power3_max_alarm**
|
||||
|
||||
**power4_input**
|
||||
|
||||
**power4_label**
|
||||
|
||||
**power4_input**
|
||||
|
||||
**power4_label**
|
||||
|
||||
**power4_max**
|
||||
|
||||
**power4_max_alarm**
|
||||
|
||||
The driver provides the following attributes for temperature:
|
||||
|
||||
**temp1_input**
|
||||
|
||||
**temp1_crit**
|
||||
|
||||
**temp1_crit_alarm**
|
||||
|
||||
**temp1_max**
|
||||
|
||||
**temp1_max_alarm**
|
||||
|
||||
**temp2_input**
|
||||
|
||||
**temp2_crit**
|
||||
|
||||
**temp2_crit_alarm**
|
||||
|
||||
**temp2_max**
|
||||
|
||||
**temp2_max_alarm**
|
||||
93
Documentation/hwmon/mp29502.rst
Normal file
93
Documentation/hwmon/mp29502.rst
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver mp29502
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* MPS mp29502
|
||||
|
||||
Prefix: 'mp29502'
|
||||
|
||||
Author:
|
||||
|
||||
Wensheng Wang <wenswang@yeah.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Monolithic Power Systems, Inc. (MPS)
|
||||
MP29502 Digital Multi-phase Controller.
|
||||
|
||||
Device compliant with:
|
||||
|
||||
- PMBus rev 1.3 interface.
|
||||
|
||||
The driver exports the following attributes via the 'sysfs' files
|
||||
for input voltage:
|
||||
|
||||
**in1_input**
|
||||
|
||||
**in1_label**
|
||||
|
||||
**in1_crit**
|
||||
|
||||
**in1_crit_alarm**
|
||||
|
||||
The driver provides the following attributes for output voltage:
|
||||
|
||||
**in2_input**
|
||||
|
||||
**in2_label**
|
||||
|
||||
**in2_crit**
|
||||
|
||||
**in2_crit_alarm**
|
||||
|
||||
**in2_lcrit**
|
||||
|
||||
**in2_lcrit_alarm**
|
||||
|
||||
The driver provides the following attributes for input current:
|
||||
|
||||
**curr1_input**
|
||||
|
||||
**curr1_label**
|
||||
|
||||
The driver provides the following attributes for output current:
|
||||
|
||||
**curr2_input**
|
||||
|
||||
**curr2_label**
|
||||
|
||||
**curr2_crit**
|
||||
|
||||
**curr2_crit_alarm**
|
||||
|
||||
**curr2_max**
|
||||
|
||||
**curr2_max_alarm**
|
||||
|
||||
The driver provides the following attributes for input power:
|
||||
|
||||
**power1_input**
|
||||
|
||||
**power1_label**
|
||||
|
||||
The driver provides the following attributes for output power:
|
||||
|
||||
**power2_input**
|
||||
|
||||
**power2_label**
|
||||
|
||||
The driver provides the following attributes for temperature:
|
||||
|
||||
**temp1_input**
|
||||
|
||||
**temp1_crit**
|
||||
|
||||
**temp1_crit_alarm**
|
||||
|
||||
**temp1_max**
|
||||
|
||||
**temp1_max_alarm**
|
||||
|
|
@ -9,9 +9,13 @@ Supported chips:
|
|||
|
||||
Prefix: 'mp5990'
|
||||
|
||||
* Datasheet
|
||||
Datasheet: Publicly available at the MPS website: https://www.monolithicpower.com/en/mp5990.html
|
||||
|
||||
Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5990.html
|
||||
* MPS MP5998
|
||||
|
||||
Prefix: 'mp5998'
|
||||
|
||||
Datasheet: Not publicly available
|
||||
|
||||
Author:
|
||||
|
||||
|
|
@ -21,7 +25,7 @@ Description
|
|||
-----------
|
||||
|
||||
This driver implements support for Monolithic Power Systems, Inc. (MPS)
|
||||
MP5990 Hot-Swap Controller.
|
||||
MP5990 and MP5998 Hot-Swap Controller.
|
||||
|
||||
Device compliant with:
|
||||
|
||||
|
|
@ -53,7 +57,7 @@ The driver provides the following attributes for output voltage:
|
|||
|
||||
**in2_alarm**
|
||||
|
||||
The driver provides the following attributes for output current:
|
||||
The driver provides the following attributes for current:
|
||||
|
||||
**curr1_input**
|
||||
|
||||
|
|
@ -63,6 +67,14 @@ The driver provides the following attributes for output current:
|
|||
|
||||
**curr1_max**
|
||||
|
||||
**curr2_input**
|
||||
|
||||
**curr2_label**
|
||||
|
||||
**curr2_max**
|
||||
|
||||
**curr2_max_alarm**
|
||||
|
||||
The driver provides the following attributes for input power:
|
||||
|
||||
**power1_input**
|
||||
|
|
@ -71,6 +83,16 @@ The driver provides the following attributes for input power:
|
|||
|
||||
**power1_alarm**
|
||||
|
||||
The driver provides the following attributes for output power:
|
||||
|
||||
**power2_input**
|
||||
|
||||
**power2_label**
|
||||
|
||||
**power2_max**
|
||||
|
||||
**power2_max_alarm**
|
||||
|
||||
The driver provides the following attributes for temperature:
|
||||
|
||||
**temp1_input**
|
||||
|
|
|
|||
41
Documentation/hwmon/sa67.rst
Normal file
41
Documentation/hwmon/sa67.rst
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
Kernel driver sa67mcu
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Kontron sa67mcu
|
||||
|
||||
Prefix: 'sa67mcu'
|
||||
|
||||
Datasheet: not available
|
||||
|
||||
Authors: Michael Walle <mwalle@kernel.org>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The sa67mcu is a board management controller which also exposes a hardware
|
||||
monitoring controller.
|
||||
|
||||
The controller has two voltage and one temperature sensor. The values are
|
||||
hold in two 8 bit registers to form one 16 bit value. Reading the lower byte
|
||||
will also capture the high byte to make the access atomic. The unit of the
|
||||
volatge sensors are 1mV and the unit of the temperature sensor is 0.1degC.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported.
|
||||
|
||||
======================= ========================================================
|
||||
in0_label "VDDIN"
|
||||
in0_input Measured VDDIN voltage.
|
||||
|
||||
in1_label "VDD_RTC"
|
||||
in1_input Measured VDD_RTC voltage.
|
||||
|
||||
temp1_input MCU temperature. Roughly the board temperature.
|
||||
======================= ========================================================
|
||||
|
||||
|
|
@ -3,6 +3,16 @@ Kernel driver sht21
|
|||
|
||||
Supported chips:
|
||||
|
||||
* Sensirion SHT20
|
||||
|
||||
Prefix: 'sht20'
|
||||
|
||||
Addresses scanned: none
|
||||
|
||||
Datasheet: Publicly available at the Sensirion website
|
||||
|
||||
https://www.sensirion.com/file/datasheet_sht20
|
||||
|
||||
* Sensirion SHT21
|
||||
|
||||
Prefix: 'sht21'
|
||||
|
|
@ -13,8 +23,6 @@ Supported chips:
|
|||
|
||||
https://www.sensirion.com/file/datasheet_sht21
|
||||
|
||||
|
||||
|
||||
* Sensirion SHT25
|
||||
|
||||
Prefix: 'sht25'
|
||||
|
|
@ -25,8 +33,6 @@ Supported chips:
|
|||
|
||||
https://www.sensirion.com/file/datasheet_sht25
|
||||
|
||||
|
||||
|
||||
Author:
|
||||
|
||||
Urs Fleisch <urs.fleisch@sensirion.com>
|
||||
|
|
@ -47,13 +53,11 @@ in the board setup code.
|
|||
sysfs-Interface
|
||||
---------------
|
||||
|
||||
temp1_input
|
||||
- temperature input
|
||||
|
||||
humidity1_input
|
||||
- humidity input
|
||||
eic
|
||||
- Electronic Identification Code
|
||||
=================== ============================================================
|
||||
temp1_input Temperature input
|
||||
humidity1_input Humidity input
|
||||
eic Electronic Identification Code
|
||||
=================== ============================================================
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
|
|
|||
26
MAINTAINERS
26
MAINTAINERS
|
|
@ -6280,9 +6280,8 @@ F: tools/testing/selftests/cgroup/test_kmem.c
|
|||
F: tools/testing/selftests/cgroup/test_memcontrol.c
|
||||
|
||||
CORETEMP HARDWARE MONITORING DRIVER
|
||||
M: Fenghua Yu <fenghua.yu@intel.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
S: Orphan
|
||||
F: Documentation/hwmon/coretemp.rst
|
||||
F: drivers/hwmon/coretemp.c
|
||||
|
||||
|
|
@ -10456,6 +10455,13 @@ F: drivers/phy/samsung/phy-gs101-ufs.c
|
|||
F: include/dt-bindings/clock/google,gs101.h
|
||||
K: [gG]oogle.?[tT]ensor
|
||||
|
||||
GPD FAN DRIVER
|
||||
M: Cryolitia PukNgae <cryolitia@uniontech.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/gpd-fan.rst
|
||||
F: drivers/hwmon/gpd-fan.c
|
||||
|
||||
GPD POCKET FAN DRIVER
|
||||
M: Hans de Goede <hansg@kernel.org>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
|
|
@ -10752,7 +10758,6 @@ W: http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
|
|||
F: drivers/platform/x86/hdaps.c
|
||||
|
||||
HARDWARE MONITORING
|
||||
M: Jean Delvare <jdelvare@suse.com>
|
||||
M: Guenter Roeck <linux@roeck-us.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
|
|
@ -17208,6 +17213,13 @@ S: Maintained
|
|||
F: Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml
|
||||
F: drivers/video/backlight/mp3309c.c
|
||||
|
||||
MPS MP2869 DRIVER
|
||||
M: Wensheng Wang <wenswang@yeah.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/mp2869.rst
|
||||
F: drivers/hwmon/pmbus/mp2869.c
|
||||
|
||||
MPS MP2891 DRIVER
|
||||
M: Noah Wang <noahwang.wang@outlook.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
|
@ -17215,6 +17227,13 @@ S: Maintained
|
|||
F: Documentation/hwmon/mp2891.rst
|
||||
F: drivers/hwmon/pmbus/mp2891.c
|
||||
|
||||
MPS MP29502 DRIVER
|
||||
M: Wensheng Wang <wenswang@yeah.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/mp29502.rst
|
||||
F: drivers/hwmon/pmbus/mp29502.c
|
||||
|
||||
MPS MP2993 DRIVER
|
||||
M: Noah Wang <noahwang.wang@outlook.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
|
@ -23249,6 +23268,7 @@ F: Documentation/devicetree/bindings/mfd/kontron,sl28cpld.yaml
|
|||
F: Documentation/devicetree/bindings/pwm/kontron,sl28cpld-pwm.yaml
|
||||
F: Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml
|
||||
F: drivers/gpio/gpio-sl28cpld.c
|
||||
F: drivers/hwmon/sa67mcu-hwmon.c
|
||||
F: drivers/hwmon/sl28cpld-hwmon.c
|
||||
F: drivers/irqchip/irq-sl28cpld.c
|
||||
F: drivers/pwm/pwm-sl28cpld.c
|
||||
|
|
|
|||
|
|
@ -769,6 +769,16 @@ config SENSORS_GL520SM
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called gl520sm.
|
||||
|
||||
config SENSORS_GPD
|
||||
tristate "GPD handhelds"
|
||||
depends on X86 && DMI && HAS_IOPORT
|
||||
help
|
||||
If you say yes here you get support for fan readings and
|
||||
control over GPD handheld devices.
|
||||
|
||||
Can also be built as a module. In that case it will be
|
||||
called gpd-fan.
|
||||
|
||||
config SENSORS_G760A
|
||||
tristate "GMT G760A"
|
||||
depends on I2C
|
||||
|
|
@ -1895,6 +1905,16 @@ config SENSORS_RASPBERRYPI_HWMON
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called raspberrypi-hwmon.
|
||||
|
||||
config SENSORS_SA67MCU
|
||||
tristate "Kontron sa67mcu hardware monitoring driver"
|
||||
depends on MFD_SL28CPLD || COMPILE_TEST
|
||||
help
|
||||
If you say yes here you get support for the voltage and temperature
|
||||
monitor of the sa67 board management controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sa67mcu-hwmon.
|
||||
|
||||
config SENSORS_SL28CPLD
|
||||
tristate "Kontron sl28cpld hardware monitoring driver"
|
||||
depends on MFD_SL28CPLD || COMPILE_TEST
|
||||
|
|
@ -1930,8 +1950,8 @@ config SENSORS_SHT21
|
|||
tristate "Sensiron humidity and temperature sensors. SHT21 and compat."
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for the Sensiron SHT21, SHT25
|
||||
humidity and temperature sensors.
|
||||
If you say yes here you get support for the Sensiron SHT20, SHT21,
|
||||
SHT25 humidity and temperature sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sht21.
|
||||
|
|
@ -2252,13 +2272,14 @@ config SENSORS_INA2XX
|
|||
will be called ina2xx.
|
||||
|
||||
config SENSORS_INA238
|
||||
tristate "Texas Instruments INA238"
|
||||
tristate "Texas Instruments INA238 and compatibles"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for the INA238 power monitor
|
||||
chip. This driver supports voltage, current, power and temperature
|
||||
measurements as well as alarm configuration.
|
||||
If you say yes here you get support for INA228, INA237, INA238,
|
||||
INA700, INA780, and SQ52206 power monitor chips. This driver supports
|
||||
voltage, current, power, energy, and temperature measurements as well
|
||||
as alarm configuration.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ina238.
|
||||
|
|
@ -2673,9 +2694,10 @@ config SENSORS_ASUS_EC
|
|||
depends on ACPI_EC
|
||||
help
|
||||
If you say yes here you get support for the ACPI embedded controller
|
||||
hardware monitoring interface found in ASUS motherboards. The driver
|
||||
currently supports B550/X570 boards, although other ASUS boards might
|
||||
provide this monitoring interface as well.
|
||||
hardware monitoring interface found in some ASUS motherboards. This is
|
||||
where such sensors as water flow and temperature, optional fans, and
|
||||
additional temperature sensors (T_Sensor, chipset temperatures)
|
||||
find themselves.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called asus_ec_sensors.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o
|
|||
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
||||
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
|
||||
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o
|
||||
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
|
||||
obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o
|
||||
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
|
||||
|
|
@ -196,6 +197,7 @@ obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
|
|||
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
|
||||
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
|
||||
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
|
||||
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
|
||||
|
|
|
|||
|
|
@ -49,15 +49,19 @@ static char *mutex_path_override;
|
|||
*/
|
||||
#define ASUS_EC_MAX_BANK 3
|
||||
|
||||
#define ACPI_LOCK_DELAY_MS 500
|
||||
#define ACPI_LOCK_DELAY_MS 800
|
||||
|
||||
/* ACPI mutex for locking access to the EC for the firmware */
|
||||
#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX"
|
||||
|
||||
#define ASUS_HW_ACCESS_MUTEX_RMTW_ASMX "\\RMTW.ASMX"
|
||||
|
||||
#define ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0 "\\_SB.PC00.LPCB.SIO1.MUT0"
|
||||
|
||||
#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0 "\\_SB_.PCI0.SBRG.SIO1.MUT0"
|
||||
|
||||
#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0 "\\_SB_.PCI0.LPCB.SIO1.MUT0"
|
||||
|
||||
#define MAX_IDENTICAL_BOARD_VARIATIONS 3
|
||||
|
||||
/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */
|
||||
|
|
@ -115,10 +119,18 @@ enum ec_sensors {
|
|||
ec_sensor_fan_cpu_opt,
|
||||
/* VRM heat sink fan [RPM] */
|
||||
ec_sensor_fan_vrm_hs,
|
||||
/* VRM east heat sink fan [RPM] */
|
||||
ec_sensor_fan_vrme_hs,
|
||||
/* VRM west heat sink fan [RPM] */
|
||||
ec_sensor_fan_vrmw_hs,
|
||||
/* Chipset fan [RPM] */
|
||||
ec_sensor_fan_chipset,
|
||||
/* Water flow sensor reading [RPM] */
|
||||
ec_sensor_fan_water_flow,
|
||||
/* USB4 fan [RPM] */
|
||||
ec_sensor_fan_usb4,
|
||||
/* M.2 fan [RPM] */
|
||||
ec_sensor_fan_m2,
|
||||
/* CPU current [A] */
|
||||
ec_sensor_curr_cpu,
|
||||
/* "Water_In" temperature sensor reading [℃] */
|
||||
|
|
@ -148,8 +160,12 @@ enum ec_sensors {
|
|||
#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core)
|
||||
#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt)
|
||||
#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs)
|
||||
#define SENSOR_FAN_VRME_HS BIT(ec_sensor_fan_vrme_hs)
|
||||
#define SENSOR_FAN_VRMW_HS BIT(ec_sensor_fan_vrmw_hs)
|
||||
#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset)
|
||||
#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow)
|
||||
#define SENSOR_FAN_USB4 BIT(ec_sensor_fan_usb4)
|
||||
#define SENSOR_FAN_M2 BIT(ec_sensor_fan_m2)
|
||||
#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu)
|
||||
#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in)
|
||||
#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out)
|
||||
|
|
@ -166,9 +182,12 @@ enum board_family {
|
|||
family_amd_500_series,
|
||||
family_amd_600_series,
|
||||
family_amd_800_series,
|
||||
family_amd_wrx_90,
|
||||
family_intel_200_series,
|
||||
family_intel_300_series,
|
||||
family_intel_400_series,
|
||||
family_intel_600_series
|
||||
family_intel_600_series,
|
||||
family_intel_700_series
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -275,6 +294,33 @@ static const struct ec_sensor_info sensors_family_amd_800[] = {
|
|||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_amd_wrx_90[] = {
|
||||
[ec_sensor_temp_cpu_package] =
|
||||
EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
[ec_sensor_fan_vrmw_hs] =
|
||||
EC_SENSOR("VRMW HS", hwmon_fan, 2, 0x00, 0xb4),
|
||||
[ec_sensor_fan_usb4] = EC_SENSOR("USB4", hwmon_fan, 2, 0x00, 0xb6),
|
||||
[ec_sensor_fan_vrme_hs] =
|
||||
EC_SENSOR("VRME HS", hwmon_fan, 2, 0x00, 0xbc),
|
||||
[ec_sensor_fan_m2] = EC_SENSOR("M.2", hwmon_fan, 2, 0x00, 0xbe),
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_intel_200[] = {
|
||||
[ec_sensor_temp_chipset] =
|
||||
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
|
||||
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
|
||||
[ec_sensor_temp_mb] =
|
||||
EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_intel_300[] = {
|
||||
[ec_sensor_temp_chipset] =
|
||||
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
|
||||
|
|
@ -323,6 +369,16 @@ static const struct ec_sensor_info sensors_family_intel_600[] = {
|
|||
EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_intel_700[] = {
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x09),
|
||||
[ec_sensor_temp_t_sensor_2] =
|
||||
EC_SENSOR("T_Sensor 2", hwmon_temp, 1, 0x01, 0x05),
|
||||
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
};
|
||||
|
||||
/* Shortcuts for common combinations */
|
||||
#define SENSOR_SET_TEMP_CHIPSET_CPU_MB \
|
||||
(SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB)
|
||||
|
|
@ -343,6 +399,52 @@ struct ec_board_info {
|
|||
enum board_family family;
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
|
||||
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
|
||||
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_impact = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_x670e_gene = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_x670e_hero = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_vi_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
|
|
@ -352,6 +454,22 @@ static const struct ec_board_info board_info_maximus_vi_hero = {
|
|||
.family = family_intel_300_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_xi_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_intel_300_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_z690_formula = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_prime_x470_pro = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
|
|
@ -376,6 +494,21 @@ static const struct ec_board_info board_info_prime_x670e_pro_wifi = {
|
|||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_prime_z270_a = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0,
|
||||
.family = family_intel_200_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_art_b550_creator = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_art_x570_creator_wifi = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
|
||||
|
|
@ -396,16 +529,17 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = {
|
|||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_art_b550_creator = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
static const struct ec_board_info board_info_pro_ws_wrx90e_sage_se = {
|
||||
/* Board also has a nct6798 with 7 more fans and temperatures */
|
||||
.sensors = SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_USB4 | SENSOR_FAN_M2 |
|
||||
SENSOR_FAN_VRME_HS | SENSOR_FAN_VRMW_HS,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_amd_wrx_90,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_pro_ws_x570_ace = {
|
||||
|
|
@ -416,68 +550,6 @@ static const struct ec_board_info board_info_pro_ws_x570_ace = {
|
|||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_x670e_hero = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_x670e_gene = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_dark_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
|
||||
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
|
||||
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_xi_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_intel_300_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_z690_formula = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_impact = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_b550_e_gaming = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
|
|
@ -495,6 +567,20 @@ static const struct ec_board_info board_info_strix_b550_i_gaming = {
|
|||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_b650e_i_gaming = {
|
||||
.sensors = SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_x570_e_gaming = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
|
|
@ -528,6 +614,35 @@ static const struct ec_board_info board_info_strix_x570_i_gaming = {
|
|||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_x670e_e_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0,
|
||||
.family = family_amd_800_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_z390_f_gaming = {
|
||||
.sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
|
|
@ -554,6 +669,35 @@ static const struct ec_board_info board_info_strix_z690_a_gaming_wifi_d4 = {
|
|||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_z690_e_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
|
||||
.family = family_intel_700_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 |
|
||||
SENSOR_TEMP_VRM,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0,
|
||||
.family = family_intel_700_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_zenith_ii_extreme = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
|
|
@ -566,15 +710,6 @@ static const struct ec_board_info board_info_zenith_ii_extreme = {
|
|||
.family = family_amd_500_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
|
||||
.sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE |
|
||||
SENSOR_TEMP_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_600_series,
|
||||
};
|
||||
|
||||
#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, board_info) \
|
||||
{ \
|
||||
.matches = { \
|
||||
|
|
@ -594,14 +729,18 @@ static const struct dmi_system_id dmi_table[] = {
|
|||
&board_info_prime_x570_pro),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X670E-PRO WIFI",
|
||||
&board_info_prime_x670e_pro_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z270-A",
|
||||
&board_info_prime_z270_a),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR",
|
||||
&board_info_pro_art_b550_creator),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI",
|
||||
&board_info_pro_art_x570_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X670E-CREATOR WIFI",
|
||||
&board_info_pro_art_x670E_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI",
|
||||
&board_info_pro_art_x870E_creator_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR",
|
||||
&board_info_pro_art_b550_creator),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS WRX90E-SAGE SE",
|
||||
&board_info_pro_ws_wrx90e_sage_se),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE",
|
||||
&board_info_pro_ws_x570_ace),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO",
|
||||
|
|
@ -612,22 +751,26 @@ static const struct dmi_system_id dmi_table[] = {
|
|||
&board_info_crosshair_viii_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO (WI-FI)",
|
||||
&board_info_crosshair_viii_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
|
||||
&board_info_crosshair_x670e_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
|
||||
&board_info_crosshair_viii_impact),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE",
|
||||
&board_info_crosshair_x670e_gene),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO",
|
||||
&board_info_crosshair_x670e_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO",
|
||||
&board_info_maximus_xi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)",
|
||||
&board_info_maximus_xi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA",
|
||||
&board_info_maximus_z690_formula),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
|
||||
&board_info_crosshair_viii_impact),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
|
||||
&board_info_strix_b550_e_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING",
|
||||
&board_info_strix_b550_i_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B650E-I GAMING WIFI",
|
||||
&board_info_strix_b650e_i_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI",
|
||||
&board_info_strix_b850_i_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING",
|
||||
&board_info_strix_x570_e_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING WIFI II",
|
||||
|
|
@ -636,18 +779,34 @@ static const struct dmi_system_id dmi_table[] = {
|
|||
&board_info_strix_x570_f_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING",
|
||||
&board_info_strix_x570_i_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-E GAMING WIFI",
|
||||
&board_info_strix_x670e_e_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI",
|
||||
&board_info_strix_x670e_i_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI",
|
||||
&board_info_strix_x870_i_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI",
|
||||
&board_info_strix_x870e_e_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING",
|
||||
&board_info_strix_z390_f_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING",
|
||||
&board_info_strix_z490_f_gaming),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4",
|
||||
&board_info_strix_z690_a_gaming_wifi_d4),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-E GAMING WIFI",
|
||||
&board_info_strix_z690_e_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II",
|
||||
&board_info_strix_z790_e_gaming_wifi_ii),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI",
|
||||
&board_info_strix_z790_i_gaming_wifi),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME",
|
||||
&board_info_zenith_ii_extreme),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME ALPHA",
|
||||
&board_info_zenith_ii_extreme),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS",
|
||||
&board_info_tuf_gaming_x670e_plus),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS WIFI",
|
||||
&board_info_tuf_gaming_x670e_plus),
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
@ -1115,6 +1274,12 @@ static int asus_ec_probe(struct platform_device *pdev)
|
|||
case family_amd_800_series:
|
||||
ec_data->sensors_info = sensors_family_amd_800;
|
||||
break;
|
||||
case family_amd_wrx_90:
|
||||
ec_data->sensors_info = sensors_family_amd_wrx_90;
|
||||
break;
|
||||
case family_intel_200_series:
|
||||
ec_data->sensors_info = sensors_family_intel_200;
|
||||
break;
|
||||
case family_intel_300_series:
|
||||
ec_data->sensors_info = sensors_family_intel_300;
|
||||
break;
|
||||
|
|
@ -1124,6 +1289,9 @@ static int asus_ec_probe(struct platform_device *pdev)
|
|||
case family_intel_600_series:
|
||||
ec_data->sensors_info = sensors_family_intel_600;
|
||||
break;
|
||||
case family_intel_700_series:
|
||||
ec_data->sensors_info = sensors_family_intel_700;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Unknown board family: %d",
|
||||
ec_data->board_info->family);
|
||||
|
|
|
|||
|
|
@ -122,29 +122,29 @@ static const struct tjmax tjmax_table[] = {
|
|||
};
|
||||
|
||||
struct tjmax_model {
|
||||
u8 model;
|
||||
u8 mask;
|
||||
u32 vfm;
|
||||
u8 stepping_mask;
|
||||
int tjmax;
|
||||
};
|
||||
|
||||
#define ANY 0xff
|
||||
|
||||
static const struct tjmax_model tjmax_model_table[] = {
|
||||
{ 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
|
||||
{ 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others
|
||||
* Note: Also matches 230 and 330,
|
||||
* which are covered by tjmax_table
|
||||
*/
|
||||
{ 0x26, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
|
||||
* Note: TjMax for E6xxT is 110C, but CPU type
|
||||
* is undetectable by software
|
||||
*/
|
||||
{ 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */
|
||||
{ 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
|
||||
{ 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
|
||||
* Also matches S12x0 (stepping 9), covered by
|
||||
* PCI table
|
||||
*/
|
||||
{ INTEL_ATOM_BONNELL, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */
|
||||
{ INTEL_ATOM_BONNELL, ANY, 90000 }, /* Z5xx, N2xx, possibly others
|
||||
* Note: Also matches 230 and 330,
|
||||
* which are covered by tjmax_table
|
||||
*/
|
||||
{ INTEL_ATOM_BONNELL_MID, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx)
|
||||
* Note: TjMax for E6xxT is 110C, but CPU type
|
||||
* is undetectable by software
|
||||
*/
|
||||
{ INTEL_ATOM_SALTWELL_MID, ANY, 90000 }, /* Atom Medfield (Z2460) */
|
||||
{ INTEL_ATOM_SALTWELL_TABLET, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */
|
||||
{ INTEL_ATOM_SALTWELL, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx)
|
||||
* Also matches S12x0 (stepping 9), covered by
|
||||
* PCI table
|
||||
*/
|
||||
};
|
||||
|
||||
static bool is_pkg_temp_data(struct temp_data *tdata)
|
||||
|
|
@ -180,6 +180,11 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
|||
}
|
||||
pci_dev_put(host_bridge);
|
||||
|
||||
/*
|
||||
* This is literally looking for "CPU XXX" in the model string.
|
||||
* Not checking it against the model as well. Just purely a
|
||||
* string search.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(tjmax_table); i++) {
|
||||
if (strstr(c->x86_model_id, tjmax_table[i].id))
|
||||
return tjmax_table[i].tjmax;
|
||||
|
|
@ -187,17 +192,18 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
|||
|
||||
for (i = 0; i < ARRAY_SIZE(tjmax_model_table); i++) {
|
||||
const struct tjmax_model *tm = &tjmax_model_table[i];
|
||||
if (c->x86_model == tm->model &&
|
||||
(tm->mask == ANY || c->x86_stepping == tm->mask))
|
||||
if (c->x86_vfm == tm->vfm &&
|
||||
(tm->stepping_mask == ANY ||
|
||||
tm->stepping_mask == c->x86_stepping))
|
||||
return tm->tjmax;
|
||||
}
|
||||
|
||||
/* Early chips have no MSR for TjMax */
|
||||
|
||||
if (c->x86_model == 0xf && c->x86_stepping < 4)
|
||||
if (c->x86_vfm == INTEL_CORE2_MEROM && c->x86_stepping < 4)
|
||||
usemsr_ee = 0;
|
||||
|
||||
if (c->x86_model > 0xe && usemsr_ee) {
|
||||
if (c->x86_vfm > INTEL_CORE_YONAH && usemsr_ee) {
|
||||
u8 platform_id;
|
||||
|
||||
/*
|
||||
|
|
@ -211,7 +217,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
|||
"Unable to access MSR 0x17, assuming desktop"
|
||||
" CPU\n");
|
||||
usemsr_ee = 0;
|
||||
} else if (c->x86_model < 0x17 && !(eax & 0x10000000)) {
|
||||
} else if (c->x86_vfm < INTEL_CORE2_PENRYN &&
|
||||
!(eax & 0x10000000)) {
|
||||
/*
|
||||
* Trust bit 28 up to Penryn, I could not find any
|
||||
* documentation on that; if you happen to know
|
||||
|
|
@ -226,7 +233,7 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
|||
* Mobile Penryn CPU seems to be platform ID 7 or 5
|
||||
* (guesswork)
|
||||
*/
|
||||
if (c->x86_model == 0x17 &&
|
||||
if (c->x86_vfm == INTEL_CORE2_PENRYN &&
|
||||
(platform_id == 5 || platform_id == 7)) {
|
||||
/*
|
||||
* If MSR EE bit is set, set it to 90 degrees C,
|
||||
|
|
@ -258,18 +265,6 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
|||
return tjmax;
|
||||
}
|
||||
|
||||
static bool cpu_has_tjmax(struct cpuinfo_x86 *c)
|
||||
{
|
||||
u8 model = c->x86_model;
|
||||
|
||||
return model > 0xe &&
|
||||
model != 0x1c &&
|
||||
model != 0x26 &&
|
||||
model != 0x27 &&
|
||||
model != 0x35 &&
|
||||
model != 0x36;
|
||||
}
|
||||
|
||||
static int get_tjmax(struct temp_data *tdata, struct device *dev)
|
||||
{
|
||||
struct cpuinfo_x86 *c = &cpu_data(tdata->cpu);
|
||||
|
|
@ -287,8 +282,7 @@ static int get_tjmax(struct temp_data *tdata, struct device *dev)
|
|||
*/
|
||||
err = rdmsr_safe_on_cpu(tdata->cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (err) {
|
||||
if (cpu_has_tjmax(c))
|
||||
dev_warn(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
|
||||
dev_warn_once(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu);
|
||||
} else {
|
||||
val = (eax >> 16) & 0xff;
|
||||
if (val)
|
||||
|
|
@ -460,7 +454,7 @@ static int chk_ucode_version(unsigned int cpu)
|
|||
* Readings might stop update when processor visited too deep sleep,
|
||||
* fixed for stepping D0 (6EC).
|
||||
*/
|
||||
if (c->x86_model == 0xe && c->x86_stepping < 0xc && c->microcode < 0x39) {
|
||||
if (c->x86_vfm == INTEL_CORE_YONAH && c->x86_stepping < 0xc && c->microcode < 0x39) {
|
||||
pr_err("Errata AE18 not fixed, update BIOS or microcode of the CPU!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
@ -580,7 +574,7 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
|
|||
* MSR_IA32_TEMPERATURE_TARGET register. Atoms don't have the register
|
||||
* at all.
|
||||
*/
|
||||
if (c->x86_model > 0xe && c->x86_model != 0x1c)
|
||||
if (c->x86_vfm > INTEL_CORE_YONAH && c->x86_vfm != INTEL_ATOM_BONNELL)
|
||||
if (get_ttarget(tdata, &pdev->dev) >= 0)
|
||||
tdata->attr_size++;
|
||||
|
||||
|
|
@ -793,7 +787,9 @@ static int __init coretemp_init(void)
|
|||
/*
|
||||
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
|
||||
* sensors. We check this bit only, all the early CPUs
|
||||
* without thermal sensors will be filtered out.
|
||||
* without thermal sensors will be filtered out. This
|
||||
* includes all the Family 5 and Family 15 (Pentium 4)
|
||||
* models, since they never set the CPUID bit.
|
||||
*/
|
||||
if (!x86_match_cpu(coretemp_ids))
|
||||
return -ENODEV;
|
||||
|
|
|
|||
|
|
@ -7,20 +7,34 @@
|
|||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/cros_ec_commands.h>
|
||||
#include <linux/platform_data/cros_ec_proto.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#define DRV_NAME "cros-ec-hwmon"
|
||||
|
||||
#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION 0
|
||||
#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION 1
|
||||
#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION 2
|
||||
|
||||
struct cros_ec_hwmon_priv {
|
||||
struct cros_ec_device *cros_ec;
|
||||
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
|
||||
u8 usable_fans;
|
||||
bool fan_control_supported;
|
||||
u8 manual_fans; /* bits to indicate whether the fan is set to manual */
|
||||
u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES];
|
||||
};
|
||||
|
||||
struct cros_ec_hwmon_cooling_priv {
|
||||
struct cros_ec_hwmon_priv *hwmon_priv;
|
||||
u8 index;
|
||||
};
|
||||
|
||||
static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
|
||||
|
|
@ -36,6 +50,42 @@ static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value)
|
||||
{
|
||||
struct ec_params_pwm_get_fan_duty req = {
|
||||
.fan_idx = index,
|
||||
};
|
||||
struct ec_response_pwm_get_fan_duty resp;
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION,
|
||||
EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index,
|
||||
u8 *control_method)
|
||||
{
|
||||
struct ec_params_auto_fan_ctrl_v2 req = {
|
||||
.cmd = EC_AUTO_FAN_CONTROL_CMD_GET,
|
||||
.fan_idx = index,
|
||||
};
|
||||
struct ec_response_auto_fan_control resp;
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
|
||||
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*control_method = resp.is_auto ? 2 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
|
||||
{
|
||||
unsigned int offset;
|
||||
|
|
@ -75,6 +125,8 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
{
|
||||
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
|
||||
int ret = -EOPNOTSUPP;
|
||||
u8 control_method;
|
||||
u8 pwm_value;
|
||||
u16 speed;
|
||||
u8 temp;
|
||||
|
||||
|
|
@ -92,6 +144,17 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
if (ret == 0)
|
||||
*val = cros_ec_hwmon_is_error_fan(speed);
|
||||
}
|
||||
} else if (type == hwmon_pwm) {
|
||||
if (attr == hwmon_pwm_enable) {
|
||||
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel,
|
||||
&control_method);
|
||||
if (ret == 0)
|
||||
*val = control_method;
|
||||
} else if (attr == hwmon_pwm_input) {
|
||||
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value);
|
||||
if (ret == 0)
|
||||
*val = pwm_value;
|
||||
}
|
||||
} else if (type == hwmon_temp) {
|
||||
if (attr == hwmon_temp_input) {
|
||||
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
|
||||
|
|
@ -124,6 +187,74 @@ static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types
|
|||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val)
|
||||
{
|
||||
struct ec_params_pwm_set_fan_duty_v1 req = {
|
||||
.fan_idx = index,
|
||||
.percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255),
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION,
|
||||
EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val)
|
||||
{
|
||||
u8 control_method;
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (control_method != 1)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val);
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val)
|
||||
{
|
||||
struct ec_params_auto_fan_ctrl_v2 req = {
|
||||
.fan_idx = index,
|
||||
.cmd = EC_AUTO_FAN_CONTROL_CMD_SET,
|
||||
};
|
||||
int ret;
|
||||
|
||||
/* No CrOS EC supports no fan speed control */
|
||||
if (val == 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
req.set_auto = (val != 1) ? true : false;
|
||||
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
|
||||
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long val)
|
||||
{
|
||||
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (type == hwmon_pwm) {
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val);
|
||||
case hwmon_pwm_enable:
|
||||
return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
|
|
@ -132,6 +263,9 @@ static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_type
|
|||
if (type == hwmon_fan) {
|
||||
if (priv->usable_fans & BIT(channel))
|
||||
return 0444;
|
||||
} else if (type == hwmon_pwm) {
|
||||
if (priv->fan_control_supported && priv->usable_fans & BIT(channel))
|
||||
return 0644;
|
||||
} else if (type == hwmon_temp) {
|
||||
if (priv->temp_sensor_names[channel])
|
||||
return 0444;
|
||||
|
|
@ -147,6 +281,11 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
|
|||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
|
|
@ -175,9 +314,46 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static int cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *val)
|
||||
{
|
||||
*val = 255;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *val)
|
||||
{
|
||||
const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
|
||||
u8 read_val;
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = read_val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long val)
|
||||
{
|
||||
const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
|
||||
|
||||
return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val);
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops = {
|
||||
.get_max_state = cros_ec_hwmon_cooling_get_max_state,
|
||||
.get_cur_state = cros_ec_hwmon_cooling_get_cur_state,
|
||||
.set_cur_state = cros_ec_hwmon_cooling_set_cur_state,
|
||||
};
|
||||
|
||||
static const struct hwmon_ops cros_ec_hwmon_ops = {
|
||||
.read = cros_ec_hwmon_read,
|
||||
.read_string = cros_ec_hwmon_read_string,
|
||||
.write = cros_ec_hwmon_write,
|
||||
.is_visible = cros_ec_hwmon_is_visible,
|
||||
};
|
||||
|
||||
|
|
@ -233,6 +409,65 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec,
|
||||
u16 cmd, u8 version)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_get_cmd_versions(cros_ec, cmd);
|
||||
return ret >= 0 && (ret & EC_VER_MASK(version));
|
||||
}
|
||||
|
||||
static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec)
|
||||
{
|
||||
return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY,
|
||||
CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) &&
|
||||
is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY,
|
||||
CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) &&
|
||||
is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL,
|
||||
CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION);
|
||||
}
|
||||
|
||||
static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev,
|
||||
struct cros_ec_hwmon_priv *priv)
|
||||
{
|
||||
struct cros_ec_hwmon_cooling_priv *cpriv;
|
||||
struct thermal_cooling_device *cdev;
|
||||
const char *type;
|
||||
size_t i;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_THERMAL))
|
||||
return;
|
||||
|
||||
if (!priv->fan_control_supported)
|
||||
return;
|
||||
|
||||
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
|
||||
if (!(priv->usable_fans & BIT(i)))
|
||||
continue;
|
||||
|
||||
cpriv = devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL);
|
||||
if (!cpriv)
|
||||
continue;
|
||||
|
||||
type = devm_kasprintf(dev, GFP_KERNEL, "%s-fan%zu", dev_name(dev), i);
|
||||
if (!type) {
|
||||
dev_warn(dev, "no memory to compose cooling device type for fan %zu\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
cpriv->hwmon_priv = priv;
|
||||
cpriv->index = i;
|
||||
cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv,
|
||||
&cros_ec_thermal_cooling_ops);
|
||||
if (IS_ERR(cdev)) {
|
||||
dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i,
|
||||
cdev);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
|
@ -259,13 +494,89 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev)
|
|||
|
||||
cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
|
||||
cros_ec_hwmon_probe_fans(priv);
|
||||
priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
|
||||
cros_ec_hwmon_register_fan_cooling_devices(dev, priv);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
|
||||
&cros_ec_hwmon_chip_info, NULL);
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
|
||||
u8 control_method;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
if (!priv->fan_control_supported)
|
||||
return 0;
|
||||
|
||||
/* EC sets fan control to auto after suspended, store settings before suspending. */
|
||||
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
|
||||
if (!(priv->usable_fans & BIT(i)))
|
||||
continue;
|
||||
|
||||
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i,
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (control_method != 1) {
|
||||
priv->manual_fans &= ~BIT(i);
|
||||
continue;
|
||||
} else {
|
||||
priv->manual_fans |= BIT(i);
|
||||
}
|
||||
|
||||
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]);
|
||||
/*
|
||||
* If storing the value failed, invalidate the stored mode value by setting it
|
||||
* to auto control. EC will automatically switch to auto mode for that fan after
|
||||
* suspended.
|
||||
*/
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i,
|
||||
ERR_PTR(ret));
|
||||
priv->manual_fans &= ~BIT(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_resume(struct platform_device *pdev)
|
||||
{
|
||||
const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
if (!priv->fan_control_supported)
|
||||
return 0;
|
||||
|
||||
/* EC sets fan control to auto after suspend, restore to settings before suspend. */
|
||||
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
|
||||
if (!(priv->manual_fans & BIT(i)))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Setting fan PWM value to EC will change the mode to manual for that fan in EC as
|
||||
* well, so we do not need to issue a separate fan mode to manual call.
|
||||
*/
|
||||
ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i,
|
||||
ERR_PTR(ret));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_ec_hwmon_id[] = {
|
||||
{ DRV_NAME, 0 },
|
||||
{}
|
||||
|
|
@ -274,6 +585,8 @@ static const struct platform_device_id cros_ec_hwmon_id[] = {
|
|||
static struct platform_driver cros_ec_hwmon_driver = {
|
||||
.driver.name = DRV_NAME,
|
||||
.probe = cros_ec_hwmon_probe,
|
||||
.suspend = pm_ptr(cros_ec_hwmon_suspend),
|
||||
.resume = pm_ptr(cros_ec_hwmon_resume),
|
||||
.id_table = cros_ec_hwmon_id,
|
||||
};
|
||||
module_platform_driver(cros_ec_hwmon_driver);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/kconfig.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
|
@ -446,7 +447,6 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
|
|||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
|
||||
regs.ebx = fan | (speed << 8);
|
||||
|
||||
return dell_smm_call(data->ops, ®s);
|
||||
|
|
@ -637,6 +637,8 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
|||
if (copy_from_user(&speed, argp + 1, sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
speed = clamp_val(speed, 0, data->i8k_fan_max);
|
||||
|
||||
mutex_lock(&data->i8k_mutex);
|
||||
err = i8k_set_fan(data, val, speed);
|
||||
if (err < 0)
|
||||
|
|
@ -762,6 +764,13 @@ static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned l
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* A fan state bigger than i8k_fan_max might indicate that
|
||||
* the fan is currently in automatic mode.
|
||||
*/
|
||||
if (ret > cdata->data->i8k_fan_max)
|
||||
return -ENODATA;
|
||||
|
||||
*state = ret;
|
||||
|
||||
return 0;
|
||||
|
|
@ -849,7 +858,14 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
|
|||
|
||||
break;
|
||||
case hwmon_pwm_enable:
|
||||
if (auto_fan)
|
||||
if (auto_fan) {
|
||||
/*
|
||||
* The setting affects all fans, so only create a
|
||||
* single attribute.
|
||||
*/
|
||||
if (channel != 1)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* There is no command for retrieve the current status
|
||||
* from BIOS, and userspace/firmware itself can change
|
||||
|
|
@ -857,6 +873,10 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
|
|||
* Thus we can only provide write-only access for now.
|
||||
*/
|
||||
return 0200;
|
||||
}
|
||||
|
||||
if (data->fan[channel] && data->i8k_fan_max < I8K_FAN_AUTO)
|
||||
return 0644;
|
||||
|
||||
break;
|
||||
default:
|
||||
|
|
@ -926,14 +946,28 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
|
|||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
ret = i8k_get_fan_status(data, channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
ret = i8k_get_fan_status(data, channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* A fan state bigger than i8k_fan_max might indicate that
|
||||
* the fan is currently in automatic mode.
|
||||
*/
|
||||
if (ret > data->i8k_fan_max)
|
||||
return -ENODATA;
|
||||
|
||||
*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
|
||||
|
||||
return 0;
|
||||
case hwmon_pwm_enable:
|
||||
if (ret == I8K_FAN_AUTO)
|
||||
*val = 2;
|
||||
else
|
||||
*val = 1;
|
||||
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -1020,16 +1054,32 @@ static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32
|
|||
|
||||
return 0;
|
||||
case hwmon_pwm_enable:
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
if (val == 1)
|
||||
switch (val) {
|
||||
case 1:
|
||||
enable = false;
|
||||
else
|
||||
break;
|
||||
case 2:
|
||||
enable = true;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&data->i8k_mutex);
|
||||
err = i8k_enable_fan_auto_mode(data, enable);
|
||||
if (auto_fan) {
|
||||
err = i8k_enable_fan_auto_mode(data, enable);
|
||||
} else {
|
||||
/*
|
||||
* When putting the fan into manual control mode we have to ensure
|
||||
* that the device does not overheat until the userspace fan control
|
||||
* software takes over. Because of this we set the fan speed to
|
||||
* i8k_fan_max when disabling automatic fan control.
|
||||
*/
|
||||
if (enable)
|
||||
err = i8k_set_fan(data, channel, I8K_FAN_AUTO);
|
||||
else
|
||||
err = i8k_set_fan(data, channel, data->i8k_fan_max);
|
||||
}
|
||||
mutex_unlock(&data->i8k_mutex);
|
||||
|
||||
if (err < 0)
|
||||
|
|
@ -1080,9 +1130,9 @@ static const struct hwmon_channel_info * const dell_smm_info[] = {
|
|||
),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
|
||||
HWMON_PWM_INPUT | HWMON_PWM_ENABLE
|
||||
),
|
||||
NULL
|
||||
};
|
||||
|
|
@ -1280,6 +1330,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell OptiPlex 7040",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7040"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell Precision",
|
||||
.matches = {
|
||||
|
|
@ -1331,7 +1388,6 @@ struct i8k_config_data {
|
|||
|
||||
enum i8k_configs {
|
||||
DELL_LATITUDE_D520,
|
||||
DELL_PRECISION_490,
|
||||
DELL_STUDIO,
|
||||
DELL_XPS,
|
||||
};
|
||||
|
|
@ -1341,10 +1397,6 @@ static const struct i8k_config_data i8k_config_data[] __initconst = {
|
|||
.fan_mult = 1,
|
||||
.fan_max = I8K_FAN_TURBO,
|
||||
},
|
||||
[DELL_PRECISION_490] = {
|
||||
.fan_mult = 1,
|
||||
.fan_max = I8K_FAN_TURBO,
|
||||
},
|
||||
[DELL_STUDIO] = {
|
||||
.fan_mult = 1,
|
||||
.fan_max = I8K_FAN_HIGH,
|
||||
|
|
@ -1364,15 +1416,6 @@ static const struct dmi_system_id i8k_config_dmi_table[] __initconst = {
|
|||
},
|
||||
.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
|
||||
},
|
||||
{
|
||||
.ident = "Dell Precision 490",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME,
|
||||
"Precision WorkStation 490"),
|
||||
},
|
||||
.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
|
||||
},
|
||||
{
|
||||
.ident = "Dell Studio",
|
||||
.matches = {
|
||||
|
|
|
|||
715
drivers/hwmon/gpd-fan.c
Normal file
715
drivers/hwmon/gpd-fan.c
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/* Platform driver for GPD devices that expose fan control via hwmon sysfs.
|
||||
*
|
||||
* Fan control is provided via pwm interface in the range [0-255].
|
||||
* Each model has a different range in the EC, the written value is scaled to
|
||||
* accommodate for that.
|
||||
*
|
||||
* Based on this repo:
|
||||
* https://github.com/Cryolitia/gpd-fan-driver
|
||||
*
|
||||
* Copyright (c) 2024 Cryolitia PukNgae
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define DRIVER_NAME "gpdfan"
|
||||
#define GPD_PWM_CTR_OFFSET 0x1841
|
||||
|
||||
static char *gpd_fan_board = "";
|
||||
module_param(gpd_fan_board, charp, 0444);
|
||||
|
||||
// EC read/write locker, protecting a sequence of EC operations
|
||||
static DEFINE_MUTEX(gpd_fan_sequence_lock);
|
||||
|
||||
enum gpd_board {
|
||||
win_mini,
|
||||
win4_6800u,
|
||||
win_max_2,
|
||||
duo,
|
||||
};
|
||||
|
||||
enum FAN_PWM_ENABLE {
|
||||
DISABLE = 0,
|
||||
MANUAL = 1,
|
||||
AUTOMATIC = 2,
|
||||
};
|
||||
|
||||
static struct {
|
||||
enum FAN_PWM_ENABLE pwm_enable;
|
||||
u8 pwm_value;
|
||||
|
||||
const struct gpd_fan_drvdata *drvdata;
|
||||
} gpd_driver_priv;
|
||||
|
||||
struct gpd_fan_drvdata {
|
||||
const char *board_name; // Board name for module param comparison
|
||||
const enum gpd_board board;
|
||||
|
||||
const u8 addr_port;
|
||||
const u8 data_port;
|
||||
const u16 manual_control_enable;
|
||||
const u16 rpm_read;
|
||||
const u16 pwm_write;
|
||||
const u16 pwm_max;
|
||||
};
|
||||
|
||||
static struct gpd_fan_drvdata gpd_win_mini_drvdata = {
|
||||
.board_name = "win_mini",
|
||||
.board = win_mini,
|
||||
|
||||
.addr_port = 0x4E,
|
||||
.data_port = 0x4F,
|
||||
.manual_control_enable = 0x047A,
|
||||
.rpm_read = 0x0478,
|
||||
.pwm_write = 0x047A,
|
||||
.pwm_max = 244,
|
||||
};
|
||||
|
||||
static struct gpd_fan_drvdata gpd_duo_drvdata = {
|
||||
.board_name = "duo",
|
||||
.board = duo,
|
||||
|
||||
.addr_port = 0x4E,
|
||||
.data_port = 0x4F,
|
||||
.manual_control_enable = 0x047A,
|
||||
.rpm_read = 0x0478,
|
||||
.pwm_write = 0x047A,
|
||||
.pwm_max = 244,
|
||||
};
|
||||
|
||||
static struct gpd_fan_drvdata gpd_win4_drvdata = {
|
||||
.board_name = "win4",
|
||||
.board = win4_6800u,
|
||||
|
||||
.addr_port = 0x2E,
|
||||
.data_port = 0x2F,
|
||||
.manual_control_enable = 0xC311,
|
||||
.rpm_read = 0xC880,
|
||||
.pwm_write = 0xC311,
|
||||
.pwm_max = 127,
|
||||
};
|
||||
|
||||
static struct gpd_fan_drvdata gpd_wm2_drvdata = {
|
||||
.board_name = "wm2",
|
||||
.board = win_max_2,
|
||||
|
||||
.addr_port = 0x4E,
|
||||
.data_port = 0x4F,
|
||||
.manual_control_enable = 0x0275,
|
||||
.rpm_read = 0x0218,
|
||||
.pwm_write = 0x1809,
|
||||
.pwm_max = 184,
|
||||
};
|
||||
|
||||
static const struct dmi_system_id dmi_table[] = {
|
||||
{
|
||||
// GPD Win Mini
|
||||
// GPD Win Mini with AMD Ryzen 8840U
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01")
|
||||
},
|
||||
.driver_data = &gpd_win_mini_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win Mini
|
||||
// GPD Win Mini with AMD Ryzen HX370
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02")
|
||||
},
|
||||
.driver_data = &gpd_win_mini_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win Mini
|
||||
// GPD Win Mini with AMD Ryzen HX370
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02-L")
|
||||
},
|
||||
.driver_data = &gpd_win_mini_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win 4 with AMD Ryzen 6800U
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
|
||||
DMI_MATCH(DMI_BOARD_VERSION, "Default string"),
|
||||
},
|
||||
.driver_data = &gpd_win4_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win 4 with Ryzen 7840U
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
|
||||
DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"),
|
||||
},
|
||||
// Since 7840U, win4 uses the same drvdata as wm2
|
||||
.driver_data = &gpd_wm2_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win 4 with Ryzen 7840U (another)
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"),
|
||||
DMI_MATCH(DMI_BOARD_VERSION, "Ver.1.0"),
|
||||
},
|
||||
.driver_data = &gpd_wm2_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win Max 2 with Ryzen 6800U
|
||||
// GPD Win Max 2 2023 with Ryzen 7840U
|
||||
// GPD Win Max 2 2024 with Ryzen 8840U
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
|
||||
},
|
||||
.driver_data = &gpd_wm2_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Win Max 2 with AMD Ryzen HX370
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"),
|
||||
},
|
||||
.driver_data = &gpd_wm2_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Duo
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01"),
|
||||
},
|
||||
.driver_data = &gpd_duo_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Duo (another)
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01-L"),
|
||||
},
|
||||
.driver_data = &gpd_duo_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Pocket 4
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04"),
|
||||
},
|
||||
.driver_data = &gpd_win_mini_drvdata,
|
||||
},
|
||||
{
|
||||
// GPD Pocket 4 (another)
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04-L"),
|
||||
},
|
||||
.driver_data = &gpd_win_mini_drvdata,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct gpd_fan_drvdata *gpd_module_drvdata[] = {
|
||||
&gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL
|
||||
};
|
||||
|
||||
// Helper functions to handle EC read/write
|
||||
static void gpd_ecram_read(u16 offset, u8 *val)
|
||||
{
|
||||
u16 addr_port = gpd_driver_priv.drvdata->addr_port;
|
||||
u16 data_port = gpd_driver_priv.drvdata->data_port;
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x11, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
outb((u8)((offset >> 8) & 0xFF), data_port);
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x10, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
outb((u8)(offset & 0xFF), data_port);
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x12, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
*val = inb(data_port);
|
||||
}
|
||||
|
||||
static void gpd_ecram_write(u16 offset, u8 value)
|
||||
{
|
||||
u16 addr_port = gpd_driver_priv.drvdata->addr_port;
|
||||
u16 data_port = gpd_driver_priv.drvdata->data_port;
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x11, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
outb((u8)((offset >> 8) & 0xFF), data_port);
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x10, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
outb((u8)(offset & 0xFF), data_port);
|
||||
|
||||
outb(0x2E, addr_port);
|
||||
outb(0x12, data_port);
|
||||
outb(0x2F, addr_port);
|
||||
outb(value, data_port);
|
||||
}
|
||||
|
||||
static int gpd_generic_read_rpm(void)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
u8 high, low;
|
||||
|
||||
gpd_ecram_read(drvdata->rpm_read, &high);
|
||||
gpd_ecram_read(drvdata->rpm_read + 1, &low);
|
||||
|
||||
return (u16)high << 8 | low;
|
||||
}
|
||||
|
||||
static void gpd_win4_init_ec(void)
|
||||
{
|
||||
u8 chip_id, chip_ver;
|
||||
|
||||
gpd_ecram_read(0x2000, &chip_id);
|
||||
|
||||
if (chip_id == 0x55) {
|
||||
gpd_ecram_read(0x1060, &chip_ver);
|
||||
gpd_ecram_write(0x1060, chip_ver | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
static int gpd_win4_read_rpm(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = gpd_generic_read_rpm();
|
||||
|
||||
if (ret == 0)
|
||||
// Re-init EC when speed is 0
|
||||
gpd_win4_init_ec();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpd_wm2_read_rpm(void)
|
||||
{
|
||||
for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET;
|
||||
pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) {
|
||||
u8 PWMCTR;
|
||||
|
||||
gpd_ecram_read(pwm_ctr_offset, &PWMCTR);
|
||||
|
||||
if (PWMCTR != 0xB8)
|
||||
gpd_ecram_write(pwm_ctr_offset, 0xB8);
|
||||
}
|
||||
|
||||
return gpd_generic_read_rpm();
|
||||
}
|
||||
|
||||
// Read value for fan1_input
|
||||
static int gpd_read_rpm(void)
|
||||
{
|
||||
switch (gpd_driver_priv.drvdata->board) {
|
||||
case win_mini:
|
||||
case duo:
|
||||
return gpd_generic_read_rpm();
|
||||
case win4_6800u:
|
||||
return gpd_win4_read_rpm();
|
||||
case win_max_2:
|
||||
return gpd_wm2_read_rpm();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpd_wm2_read_pwm(void)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
u8 var;
|
||||
|
||||
gpd_ecram_read(drvdata->pwm_write, &var);
|
||||
|
||||
// Match gpd_generic_write_pwm(u8) below
|
||||
return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1));
|
||||
}
|
||||
|
||||
// Read value for pwm1
|
||||
static int gpd_read_pwm(void)
|
||||
{
|
||||
switch (gpd_driver_priv.drvdata->board) {
|
||||
case win_mini:
|
||||
case duo:
|
||||
case win4_6800u:
|
||||
switch (gpd_driver_priv.pwm_enable) {
|
||||
case DISABLE:
|
||||
return 255;
|
||||
case MANUAL:
|
||||
return gpd_driver_priv.pwm_value;
|
||||
case AUTOMATIC:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case win_max_2:
|
||||
return gpd_wm2_read_pwm();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it.
|
||||
static inline u8 gpd_cast_pwm_range(u8 val)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
|
||||
return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1;
|
||||
}
|
||||
|
||||
static void gpd_generic_write_pwm(u8 val)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
u8 pwm_reg;
|
||||
|
||||
pwm_reg = gpd_cast_pwm_range(val);
|
||||
gpd_ecram_write(drvdata->pwm_write, pwm_reg);
|
||||
}
|
||||
|
||||
static void gpd_duo_write_pwm(u8 val)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
u8 pwm_reg;
|
||||
|
||||
pwm_reg = gpd_cast_pwm_range(val);
|
||||
gpd_ecram_write(drvdata->pwm_write, pwm_reg);
|
||||
gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg);
|
||||
}
|
||||
|
||||
// Write value for pwm1
|
||||
static int gpd_write_pwm(u8 val)
|
||||
{
|
||||
if (gpd_driver_priv.pwm_enable != MANUAL)
|
||||
return -EPERM;
|
||||
|
||||
switch (gpd_driver_priv.drvdata->board) {
|
||||
case duo:
|
||||
gpd_duo_write_pwm(val);
|
||||
break;
|
||||
case win_mini:
|
||||
case win4_6800u:
|
||||
case win_max_2:
|
||||
gpd_generic_write_pwm(val);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
|
||||
{
|
||||
switch (pwm_enable) {
|
||||
case DISABLE:
|
||||
gpd_generic_write_pwm(255);
|
||||
break;
|
||||
case MANUAL:
|
||||
gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable)
|
||||
{
|
||||
switch (pwm_enable) {
|
||||
case DISABLE:
|
||||
gpd_duo_write_pwm(255);
|
||||
break;
|
||||
case MANUAL:
|
||||
gpd_duo_write_pwm(gpd_driver_priv.pwm_value);
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable)
|
||||
{
|
||||
const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata;
|
||||
|
||||
switch (enable) {
|
||||
case DISABLE:
|
||||
gpd_generic_write_pwm(255);
|
||||
gpd_ecram_write(drvdata->manual_control_enable, 1);
|
||||
break;
|
||||
case MANUAL:
|
||||
gpd_generic_write_pwm(gpd_driver_priv.pwm_value);
|
||||
gpd_ecram_write(drvdata->manual_control_enable, 1);
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
gpd_ecram_write(drvdata->manual_control_enable, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write value for pwm1_enable
|
||||
static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable)
|
||||
{
|
||||
if (enable == MANUAL)
|
||||
// Set pwm_value to max firstly when switching to manual mode, in
|
||||
// consideration of device safety.
|
||||
gpd_driver_priv.pwm_value = 255;
|
||||
|
||||
switch (gpd_driver_priv.drvdata->board) {
|
||||
case win_mini:
|
||||
case win4_6800u:
|
||||
gpd_win_mini_set_pwm_enable(enable);
|
||||
break;
|
||||
case duo:
|
||||
gpd_duo_set_pwm_enable(enable);
|
||||
break;
|
||||
case win_max_2:
|
||||
gpd_wm2_set_pwm_enable(enable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
__always_unused int channel)
|
||||
{
|
||||
if (type == hwmon_fan && attr == hwmon_fan_input) {
|
||||
return 0444;
|
||||
} else if (type == hwmon_pwm) {
|
||||
switch (attr) {
|
||||
case hwmon_pwm_enable:
|
||||
case hwmon_pwm_input:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpd_fan_hwmon_read(__always_unused struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
__always_unused int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
if (attr == hwmon_fan_input) {
|
||||
ret = gpd_read_rpm();
|
||||
|
||||
if (ret < 0)
|
||||
goto OUT;
|
||||
|
||||
*val = ret;
|
||||
ret = 0;
|
||||
goto OUT;
|
||||
}
|
||||
} else if (type == hwmon_pwm) {
|
||||
switch (attr) {
|
||||
case hwmon_pwm_enable:
|
||||
*val = gpd_driver_priv.pwm_enable;
|
||||
ret = 0;
|
||||
goto OUT;
|
||||
case hwmon_pwm_input:
|
||||
ret = gpd_read_pwm();
|
||||
|
||||
if (ret < 0)
|
||||
goto OUT;
|
||||
|
||||
*val = ret;
|
||||
ret = 0;
|
||||
goto OUT;
|
||||
}
|
||||
}
|
||||
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
OUT:
|
||||
mutex_unlock(&gpd_fan_sequence_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpd_fan_hwmon_write(__always_unused struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
__always_unused int channel, long val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&gpd_fan_sequence_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (type == hwmon_pwm) {
|
||||
switch (attr) {
|
||||
case hwmon_pwm_enable:
|
||||
if (!in_range(val, 0, 3)) {
|
||||
ret = -EINVAL;
|
||||
goto OUT;
|
||||
}
|
||||
|
||||
gpd_driver_priv.pwm_enable = val;
|
||||
|
||||
gpd_set_pwm_enable(gpd_driver_priv.pwm_enable);
|
||||
ret = 0;
|
||||
goto OUT;
|
||||
case hwmon_pwm_input:
|
||||
if (!in_range(val, 0, 256)) {
|
||||
ret = -ERANGE;
|
||||
goto OUT;
|
||||
}
|
||||
|
||||
gpd_driver_priv.pwm_value = val;
|
||||
|
||||
ret = gpd_write_pwm(val);
|
||||
goto OUT;
|
||||
}
|
||||
}
|
||||
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
OUT:
|
||||
mutex_unlock(&gpd_fan_sequence_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops gpd_fan_ops = {
|
||||
.is_visible = gpd_fan_hwmon_is_visible,
|
||||
.read = gpd_fan_hwmon_read,
|
||||
.write = gpd_fan_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = {
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct hwmon_chip_info gpd_fan_chip_info = {
|
||||
.ops = &gpd_fan_ops,
|
||||
.info = gpd_fan_hwmon_channel_info
|
||||
};
|
||||
|
||||
static int gpd_fan_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct resource *region;
|
||||
const struct resource *res;
|
||||
const struct device *hwdev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (IS_ERR(res))
|
||||
return dev_err_probe(dev, PTR_ERR(res),
|
||||
"Failed to get platform resource\n");
|
||||
|
||||
region = devm_request_region(dev, res->start,
|
||||
resource_size(res), DRIVER_NAME);
|
||||
if (IS_ERR(region))
|
||||
return dev_err_probe(dev, PTR_ERR(region),
|
||||
"Failed to request region\n");
|
||||
|
||||
hwdev = devm_hwmon_device_register_with_info(dev,
|
||||
DRIVER_NAME,
|
||||
NULL,
|
||||
&gpd_fan_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwdev))
|
||||
return dev_err_probe(dev, PTR_ERR(region),
|
||||
"Failed to register hwmon device\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpd_fan_remove(__always_unused struct platform_device *pdev)
|
||||
{
|
||||
gpd_driver_priv.pwm_enable = AUTOMATIC;
|
||||
gpd_set_pwm_enable(AUTOMATIC);
|
||||
}
|
||||
|
||||
static struct platform_driver gpd_fan_driver = {
|
||||
.probe = gpd_fan_probe,
|
||||
.remove = gpd_fan_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *gpd_fan_platform_device;
|
||||
|
||||
static int __init gpd_fan_init(void)
|
||||
{
|
||||
const struct gpd_fan_drvdata *match = NULL;
|
||||
|
||||
for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) {
|
||||
if (strcmp(gpd_fan_board, (*p)->board_name) == 0) {
|
||||
match = *p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
const struct dmi_system_id *dmi_match =
|
||||
dmi_first_match(dmi_table);
|
||||
if (dmi_match)
|
||||
match = dmi_match->driver_data;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
return -ENODEV;
|
||||
|
||||
gpd_driver_priv.pwm_enable = AUTOMATIC;
|
||||
gpd_driver_priv.pwm_value = 255;
|
||||
gpd_driver_priv.drvdata = match;
|
||||
|
||||
struct resource gpd_fan_resources[] = {
|
||||
{
|
||||
.start = match->addr_port,
|
||||
.end = match->data_port,
|
||||
.flags = IORESOURCE_IO,
|
||||
},
|
||||
};
|
||||
|
||||
gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver,
|
||||
gpd_fan_probe,
|
||||
gpd_fan_resources,
|
||||
1, NULL, 0);
|
||||
|
||||
if (IS_ERR(gpd_fan_platform_device)) {
|
||||
pr_warn("Failed to create platform device\n");
|
||||
return PTR_ERR(gpd_fan_platform_device);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit gpd_fan_exit(void)
|
||||
{
|
||||
platform_device_unregister(gpd_fan_platform_device);
|
||||
platform_driver_unregister(&gpd_fan_driver);
|
||||
}
|
||||
|
||||
MODULE_DEVICE_TABLE(dmi, dmi_table);
|
||||
|
||||
module_init(gpd_fan_init);
|
||||
module_exit(gpd_fan_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Cryolitia PukNgae <cryolitia@uniontech.com>");
|
||||
MODULE_DESCRIPTION("GPD Devices fan control driver");
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include <linux/kstrtox.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
|
|
@ -36,6 +37,7 @@ struct hwmon_device {
|
|||
const char *label;
|
||||
struct device dev;
|
||||
const struct hwmon_chip_info *chip;
|
||||
struct mutex lock;
|
||||
struct list_head tzdata;
|
||||
struct attribute_group group;
|
||||
const struct attribute_group **groups;
|
||||
|
|
@ -165,6 +167,8 @@ static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
|
|||
int ret;
|
||||
long t;
|
||||
|
||||
guard(mutex)(&hwdev->lock);
|
||||
|
||||
ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
|
||||
tdata->index, &t);
|
||||
if (ret < 0)
|
||||
|
|
@ -193,6 +197,8 @@ static int hwmon_thermal_set_trips(struct thermal_zone_device *tz, int low, int
|
|||
if (!info[i])
|
||||
return 0;
|
||||
|
||||
guard(mutex)(&hwdev->lock);
|
||||
|
||||
if (info[i]->config[tdata->index] & HWMON_T_MIN) {
|
||||
err = chip->ops->write(tdata->dev, hwmon_temp,
|
||||
hwmon_temp_min, tdata->index, low);
|
||||
|
|
@ -330,8 +336,6 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
|
|||
* attached to an i2c client device.
|
||||
*/
|
||||
|
||||
static DEFINE_MUTEX(hwmon_pec_mutex);
|
||||
|
||||
static int hwmon_match_device(struct device *dev, const void *data)
|
||||
{
|
||||
return dev->class == &hwmon_class;
|
||||
|
|
@ -362,17 +366,16 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
|
|||
if (!hdev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&hwmon_pec_mutex);
|
||||
|
||||
/*
|
||||
* If there is no write function, we assume that chip specific
|
||||
* handling is not required.
|
||||
*/
|
||||
hwdev = to_hwmon_device(hdev);
|
||||
guard(mutex)(&hwdev->lock);
|
||||
if (hwdev->chip->ops->write) {
|
||||
err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
|
||||
if (err && err != -EOPNOTSUPP)
|
||||
goto unlock;
|
||||
goto put;
|
||||
}
|
||||
|
||||
if (!val)
|
||||
|
|
@ -381,8 +384,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
|
|||
client->flags |= I2C_CLIENT_PEC;
|
||||
|
||||
err = count;
|
||||
unlock:
|
||||
mutex_unlock(&hwmon_pec_mutex);
|
||||
put:
|
||||
put_device(hdev);
|
||||
|
||||
return err;
|
||||
|
|
@ -426,18 +428,25 @@ static ssize_t hwmon_attr_show(struct device *dev,
|
|||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
s64 val64;
|
||||
long val;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&hwdev->lock);
|
||||
|
||||
ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index,
|
||||
&val);
|
||||
(hattr->type == hwmon_energy64) ? (long *)&val64 : &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type),
|
||||
hattr->name, val);
|
||||
if (hattr->type != hwmon_energy64)
|
||||
val64 = val;
|
||||
|
||||
return sprintf(buf, "%ld\n", val);
|
||||
trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type),
|
||||
hattr->name, val64);
|
||||
|
||||
return sprintf(buf, "%lld\n", val64);
|
||||
}
|
||||
|
||||
static ssize_t hwmon_attr_show_string(struct device *dev,
|
||||
|
|
@ -445,10 +454,13 @@ static ssize_t hwmon_attr_show_string(struct device *dev,
|
|||
char *buf)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
enum hwmon_sensor_types type = hattr->type;
|
||||
const char *s;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&hwdev->lock);
|
||||
|
||||
ret = hattr->ops->read_string(dev, hattr->type, hattr->attr,
|
||||
hattr->index, &s);
|
||||
if (ret < 0)
|
||||
|
|
@ -465,6 +477,7 @@ static ssize_t hwmon_attr_store(struct device *dev,
|
|||
const char *buf, size_t count)
|
||||
{
|
||||
struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
long val;
|
||||
int ret;
|
||||
|
||||
|
|
@ -472,13 +485,15 @@ static ssize_t hwmon_attr_store(struct device *dev,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&hwdev->lock);
|
||||
|
||||
ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index,
|
||||
val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
trace_hwmon_attr_store(hattr->index + hwmon_attr_base(hattr->type),
|
||||
hattr->name, val);
|
||||
hattr->name, (s64)val);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
|
@ -734,6 +749,7 @@ static const char * const *__templates[] = {
|
|||
[hwmon_curr] = hwmon_curr_attr_templates,
|
||||
[hwmon_power] = hwmon_power_attr_templates,
|
||||
[hwmon_energy] = hwmon_energy_attr_templates,
|
||||
[hwmon_energy64] = hwmon_energy_attr_templates,
|
||||
[hwmon_humidity] = hwmon_humidity_attr_templates,
|
||||
[hwmon_fan] = hwmon_fan_attr_templates,
|
||||
[hwmon_pwm] = hwmon_pwm_attr_templates,
|
||||
|
|
@ -747,6 +763,7 @@ static const int __templates_size[] = {
|
|||
[hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates),
|
||||
[hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates),
|
||||
[hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates),
|
||||
[hwmon_energy64] = ARRAY_SIZE(hwmon_energy_attr_templates),
|
||||
[hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates),
|
||||
[hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates),
|
||||
[hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates),
|
||||
|
|
@ -785,6 +802,22 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_notify_event);
|
||||
|
||||
void hwmon_lock(struct device *dev)
|
||||
{
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
|
||||
mutex_lock(&hwdev->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_lock);
|
||||
|
||||
void hwmon_unlock(struct device *dev)
|
||||
{
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
|
||||
mutex_unlock(&hwdev->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_unlock);
|
||||
|
||||
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
||||
{
|
||||
int i, n;
|
||||
|
|
@ -945,6 +978,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||
tdev = tdev->parent;
|
||||
hdev->of_node = tdev ? tdev->of_node : NULL;
|
||||
hwdev->chip = chip;
|
||||
mutex_init(&hwdev->lock);
|
||||
dev_set_drvdata(hdev, drvdata);
|
||||
dev_set_name(hdev, HWMON_ID_FORMAT, id);
|
||||
err = device_register(hdev);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/platform_data/ina2xx.h>
|
||||
|
||||
/* INA238 register definitions */
|
||||
#define INA238_CONFIG 0x0
|
||||
#define INA238_ADC_CONFIG 0x1
|
||||
|
|
@ -53,7 +51,7 @@
|
|||
|
||||
#define INA238_REGISTERS 0x20
|
||||
|
||||
#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */
|
||||
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
|
||||
|
||||
/* Default configuration of device on reset. */
|
||||
#define INA238_CONFIG_DEFAULT 0
|
||||
|
|
@ -62,6 +60,7 @@
|
|||
#define INA238_ADC_CONFIG_DEFAULT 0xfb6a
|
||||
/* Configure alerts to be based on averaged value (SLOWALERT) */
|
||||
#define INA238_DIAG_ALERT_DEFAULT 0x2000
|
||||
#define INA238_DIAG_ALERT_APOL BIT(12)
|
||||
/*
|
||||
* This driver uses a fixed calibration value in order to scale current/power
|
||||
* based on a fixed shunt resistor value. This allows for conversion within the
|
||||
|
|
@ -69,46 +68,32 @@
|
|||
* relative to the shunt resistor value within the driver. This is similar to
|
||||
* how the ina2xx driver handles current/power scaling.
|
||||
*
|
||||
* The end result of this is that increasing shunt values (from a fixed 20 mOhm
|
||||
* shunt) increase the effective current/power accuracy whilst limiting the
|
||||
* range and decreasing shunt values decrease the effective accuracy but
|
||||
* increase the range.
|
||||
* To achieve the best possible dynamic range, the value of the shunt voltage
|
||||
* register should match the value of the current register. With that, the shunt
|
||||
* voltage of 0x7fff = 32,767 uV = 163,785 uV matches the maximum current,
|
||||
* and no accuracy is lost. Experiments with a real chip show that this is
|
||||
* achieved by setting the SHUNT_CAL register to a value of 0x1000 = 4,096.
|
||||
* Per datasheet,
|
||||
* SHUNT_CAL = 819.2 x 10^6 x CURRENT_LSB x Rshunt
|
||||
* = 819,200,000 x CURRENT_LSB x Rshunt
|
||||
* With SHUNT_CAL set to 4,096, we get
|
||||
* CURRENT_LSB = 4,096 / (819,200,000 x Rshunt)
|
||||
* Assuming an Rshunt value of 5 mOhm, we get
|
||||
* CURRENT_LSB = 4,096 / (819,200,000 x 0.005) = 1mA
|
||||
* and thus a dynamic range of 1mA ... 32,767mA, which is sufficient for most
|
||||
* applications. The actual dynamic range is of course determined by the actual
|
||||
* shunt resistor value.
|
||||
*
|
||||
* The value of the Current register is calculated given the following:
|
||||
* Current (A) = (shunt voltage register * 5) * calibration / 81920
|
||||
*
|
||||
* The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4).
|
||||
* With the maximum current value of 0x7fff and a fixed shunt value results in
|
||||
* a calibration value of 16384 (0x4000).
|
||||
*
|
||||
* 0x7fff = (0x7fff * 5) * calibration / 81920
|
||||
* calibration = 0x4000
|
||||
*
|
||||
* Equivalent calibration is applied for the Power register (maximum value for
|
||||
* bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can
|
||||
* occur is ~16776192 uW (register value 0x147a8):
|
||||
*
|
||||
* This scaling means the resulting values for Current and Power registers need
|
||||
* to be scaled by the difference between the fixed shunt resistor and the
|
||||
* actual shunt resistor:
|
||||
*
|
||||
* shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb)
|
||||
*
|
||||
* Current (mA) = register value * 20000 / rshunt / 4 * gain
|
||||
* Power (mW) = 0.2 * register value * 20000 / rshunt / 4 * gain
|
||||
* (Specific for SQ52206)
|
||||
* Power (mW) = 0.24 * register value * 20000 / rshunt / 4 * gain
|
||||
* Energy (uJ) = 16 * 0.24 * register value * 20000 / rshunt / 4 * gain * 1000
|
||||
* Power and energy values are scaled accordingly.
|
||||
*/
|
||||
#define INA238_CALIBRATION_VALUE 16384
|
||||
#define INA238_FIXED_SHUNT 20000
|
||||
#define INA238_CALIBRATION_VALUE 4096
|
||||
#define INA238_FIXED_SHUNT 5000
|
||||
|
||||
#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */
|
||||
#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */
|
||||
#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */
|
||||
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
|
||||
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
|
||||
#define INA228_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
|
||||
#define INA238_SHUNT_VOLTAGE_LSB 5000 /* 5 uV/lsb, in nV */
|
||||
#define INA238_BUS_VOLTAGE_LSB 3125000 /* 3.125 mV/lsb, in nV */
|
||||
#define SQ52206_BUS_VOLTAGE_LSB 3750000 /* 3.75 mV/lsb, in nV */
|
||||
|
||||
#define NUNIT_PER_MUNIT 1000000 /* n[AV] -> m[AV] */
|
||||
|
||||
static const struct regmap_config ina238_regmap_config = {
|
||||
.max_register = INA238_REGISTERS,
|
||||
|
|
@ -116,17 +101,17 @@ static const struct regmap_config ina238_regmap_config = {
|
|||
.val_bits = 16,
|
||||
};
|
||||
|
||||
enum ina238_ids { ina238, ina237, sq52206, ina228 };
|
||||
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
|
||||
|
||||
struct ina238_config {
|
||||
bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */
|
||||
bool has_power_highest; /* chip detection power peak */
|
||||
bool has_energy; /* chip detection energy */
|
||||
u8 temp_shift; /* fixed parameters for temp calculate */
|
||||
u32 power_calculate_factor; /* fixed parameters for power calculate */
|
||||
u8 temp_resolution; /* temperature register resolution in bit */
|
||||
u16 config_default; /* Power-on default state */
|
||||
int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */
|
||||
int temp_lsb; /* use for temperature calculate */
|
||||
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
|
||||
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
|
||||
int current_lsb; /* current LSB, in uA */
|
||||
};
|
||||
|
||||
struct ina238_data {
|
||||
|
|
@ -136,48 +121,68 @@ struct ina238_data {
|
|||
struct regmap *regmap;
|
||||
u32 rshunt;
|
||||
int gain;
|
||||
u32 voltage_lsb[2]; /* shunt, bus voltage LSB, in nV */
|
||||
int current_lsb; /* current LSB, in uA */
|
||||
int power_lsb; /* power LSB, in uW */
|
||||
int energy_lsb; /* energy LSB, in uJ */
|
||||
};
|
||||
|
||||
static const struct ina238_config ina238_config[] = {
|
||||
[ina238] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = false,
|
||||
[ina228] = {
|
||||
.has_20bit_voltage_current = true,
|
||||
.has_energy = true,
|
||||
.has_power_highest = false,
|
||||
.temp_shift = 4,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = INA238_DIE_TEMP_LSB,
|
||||
.temp_resolution = 16,
|
||||
},
|
||||
[ina237] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = false,
|
||||
.has_power_highest = false,
|
||||
.temp_shift = 4,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = INA238_DIE_TEMP_LSB,
|
||||
.temp_resolution = 12,
|
||||
},
|
||||
[ina238] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = false,
|
||||
.has_power_highest = false,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_resolution = 12,
|
||||
},
|
||||
[ina700] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = true,
|
||||
.has_power_highest = false,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_resolution = 12,
|
||||
.current_lsb = 480,
|
||||
},
|
||||
[ina780] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = true,
|
||||
.has_power_highest = false,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_resolution = 12,
|
||||
.current_lsb = 2400,
|
||||
},
|
||||
[sq52206] = {
|
||||
.has_20bit_voltage_current = false,
|
||||
.has_energy = true,
|
||||
.has_power_highest = true,
|
||||
.temp_shift = 0,
|
||||
.power_calculate_factor = 24,
|
||||
.config_default = SQ52206_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = SQ52206_DIE_TEMP_LSB,
|
||||
},
|
||||
[ina228] = {
|
||||
.has_20bit_voltage_current = true,
|
||||
.has_energy = true,
|
||||
.has_power_highest = false,
|
||||
.temp_shift = 0,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = INA228_DIE_TEMP_LSB,
|
||||
.temp_resolution = 16,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -232,45 +237,28 @@ static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *v
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ina228_read_shunt_voltage(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
int err;
|
||||
int reg = channel ? INA238_BUS_VOLTAGE : INA238_CURRENT;
|
||||
u32 lsb = data->voltage_lsb[channel];
|
||||
u32 factor = NUNIT_PER_MUNIT;
|
||||
int err, regval;
|
||||
|
||||
err = ina238_read_field_s20(data->client, INA238_SHUNT_VOLTAGE, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
if (data->config->has_20bit_voltage_current) {
|
||||
err = ina238_read_field_s20(data->client, reg, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
/* Adjust accuracy: LSB in units of 500 pV */
|
||||
lsb /= 8;
|
||||
factor *= 2;
|
||||
} else {
|
||||
err = regmap_read(data->regmap, reg, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
regval = (s16)regval;
|
||||
}
|
||||
|
||||
/*
|
||||
* gain of 1 -> LSB / 4
|
||||
* This field has 16 bit on ina238. ina228 adds another 4 bits of
|
||||
* precision. ina238 conversion factors can still be applied when
|
||||
* dividing by 16.
|
||||
*/
|
||||
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) * data->gain / (1000 * 4) / 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina228_read_bus_voltage(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
err = ina238_read_field_s20(data->client, INA238_BUS_VOLTAGE, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* gain of 1 -> LSB / 4
|
||||
* This field has 16 bit on ina238. ina228 adds another 4 bits of
|
||||
* precision. ina238 conversion factors can still be applied when
|
||||
* dividing by 16.
|
||||
*/
|
||||
*val = (regval * data->config->bus_voltage_lsb) / 1000 / 16;
|
||||
*val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, factor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -278,18 +266,16 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
|||
long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int reg, mask;
|
||||
int reg, mask = 0;
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
if (attr == hwmon_in_input)
|
||||
return ina228_read_voltage(data, channel, val);
|
||||
|
||||
switch (channel) {
|
||||
case 0:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
if (data->config->has_20bit_voltage_current)
|
||||
return ina228_read_shunt_voltage(dev, attr, channel, val);
|
||||
reg = INA238_SHUNT_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max:
|
||||
reg = INA238_SHUNT_OVER_VOLTAGE;
|
||||
break;
|
||||
|
|
@ -310,11 +296,6 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
|||
break;
|
||||
case 1:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
if (data->config->has_20bit_voltage_current)
|
||||
return ina228_read_bus_voltage(dev, attr, channel, val);
|
||||
reg = INA238_BUS_VOLTAGE;
|
||||
break;
|
||||
case hwmon_in_max:
|
||||
reg = INA238_BUS_OVER_VOLTAGE;
|
||||
break;
|
||||
|
|
@ -341,112 +322,126 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_max:
|
||||
case hwmon_in_min:
|
||||
/* signed register, value in mV */
|
||||
regval = (s16)regval;
|
||||
if (channel == 0)
|
||||
/* gain of 1 -> LSB / 4 */
|
||||
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) *
|
||||
data->gain / (1000 * 4);
|
||||
else
|
||||
*val = (regval * data->config->bus_voltage_lsb) / 1000;
|
||||
break;
|
||||
case hwmon_in_max_alarm:
|
||||
case hwmon_in_min_alarm:
|
||||
if (mask)
|
||||
*val = !!(regval & mask);
|
||||
break;
|
||||
}
|
||||
else
|
||||
*val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->voltage_lsb[channel],
|
||||
NUNIT_PER_MUNIT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_in(struct device *dev, u32 attr, int channel,
|
||||
long val)
|
||||
static int ina238_write_in(struct device *dev, u32 attr, int channel, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
static const int low_limits[2] = {-164, 0};
|
||||
static const int high_limits[2] = {164, 150000};
|
||||
static const u8 low_regs[2] = {INA238_SHUNT_UNDER_VOLTAGE, INA238_BUS_UNDER_VOLTAGE};
|
||||
static const u8 high_regs[2] = {INA238_SHUNT_OVER_VOLTAGE, INA238_BUS_OVER_VOLTAGE};
|
||||
int regval;
|
||||
|
||||
if (attr != hwmon_in_max && attr != hwmon_in_min)
|
||||
return -EOPNOTSUPP;
|
||||
/* Initial clamp to avoid overflows */
|
||||
val = clamp_val(val, low_limits[channel], high_limits[channel]);
|
||||
val = DIV_S64_ROUND_CLOSEST((s64)val * NUNIT_PER_MUNIT, data->voltage_lsb[channel]);
|
||||
/* Final clamp to register limits */
|
||||
regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
|
||||
|
||||
/* convert decimal to register value */
|
||||
switch (channel) {
|
||||
case 0:
|
||||
/* signed value, clamp to max range +/-163 mV */
|
||||
regval = clamp_val(val, -163, 163);
|
||||
regval = (regval * 1000 * 4) /
|
||||
(INA238_SHUNT_VOLTAGE_LSB * data->gain);
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xffff;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_max:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_SHUNT_OVER_VOLTAGE, regval);
|
||||
case hwmon_in_min:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_SHUNT_UNDER_VOLTAGE, regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case 1:
|
||||
/* signed value, positive values only. Clamp to max 102.396 V */
|
||||
regval = clamp_val(val, 0, 102396);
|
||||
regval = (regval * 1000) / data->config->bus_voltage_lsb;
|
||||
regval = clamp_val(regval, 0, S16_MAX);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_max:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_BUS_OVER_VOLTAGE, regval);
|
||||
case hwmon_in_min:
|
||||
return regmap_write(data->regmap,
|
||||
INA238_BUS_UNDER_VOLTAGE, regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
switch (attr) {
|
||||
case hwmon_in_min:
|
||||
return regmap_write(data->regmap, low_regs[channel], regval);
|
||||
case hwmon_in_max:
|
||||
return regmap_write(data->regmap, high_regs[channel], regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int ina238_read_current(struct device *dev, u32 attr, long *val)
|
||||
static int __ina238_read_curr(struct ina238_data *data, long *val)
|
||||
{
|
||||
u32 lsb = data->current_lsb;
|
||||
int err, regval;
|
||||
|
||||
if (data->config->has_20bit_voltage_current) {
|
||||
err = ina238_read_field_s20(data->client, INA238_CURRENT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
lsb /= 16; /* Adjust accuracy */
|
||||
} else {
|
||||
err = regmap_read(data->regmap, INA238_CURRENT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
regval = (s16)regval;
|
||||
}
|
||||
|
||||
*val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read_curr(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int reg, mask = 0;
|
||||
int regval;
|
||||
int err;
|
||||
|
||||
if (attr == hwmon_curr_input)
|
||||
return __ina238_read_curr(data, val);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
if (data->config->has_20bit_voltage_current) {
|
||||
err = ina238_read_field_s20(data->client, INA238_CURRENT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
} else {
|
||||
err = regmap_read(data->regmap, INA238_CURRENT, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
/* sign-extend */
|
||||
regval = (s16)regval;
|
||||
}
|
||||
|
||||
/* Signed register, fixed 1mA current lsb. result in mA */
|
||||
*val = div_s64((s64)regval * INA238_FIXED_SHUNT * data->gain,
|
||||
data->rshunt * 4);
|
||||
|
||||
/* Account for 4 bit offset */
|
||||
if (data->config->has_20bit_voltage_current)
|
||||
*val /= 16;
|
||||
case hwmon_curr_min:
|
||||
reg = INA238_SHUNT_UNDER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_curr_min_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_SHNTUL;
|
||||
break;
|
||||
case hwmon_curr_max:
|
||||
reg = INA238_SHUNT_OVER_VOLTAGE;
|
||||
break;
|
||||
case hwmon_curr_max_alarm:
|
||||
reg = INA238_DIAG_ALERT;
|
||||
mask = INA238_DIAG_ALERT_SHNTOL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
err = regmap_read(data->regmap, reg, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (mask)
|
||||
*val = !!(regval & mask);
|
||||
else
|
||||
*val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->current_lsb, 1000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_curr(struct device *dev, u32 attr, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
|
||||
/* Set baseline range to avoid over/underflows */
|
||||
val = clamp_val(val, -1000000, 1000000);
|
||||
/* Scale */
|
||||
val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb);
|
||||
/* Clamp to register size */
|
||||
regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_min:
|
||||
return regmap_write(data->regmap, INA238_SHUNT_UNDER_VOLTAGE,
|
||||
regval);
|
||||
case hwmon_curr_max:
|
||||
return regmap_write(data->regmap, INA238_SHUNT_OVER_VOLTAGE,
|
||||
regval);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
|
|
@ -460,9 +455,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
power = (long long)regval * data->power_lsb;
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
|
|
@ -471,9 +464,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
power = (long long)regval * data->power_lsb;
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
|
|
@ -486,8 +477,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
|||
* Truncated 24-bit compare register, lower 8-bits are
|
||||
* truncated. Same conversion to/from uW as POWER register.
|
||||
*/
|
||||
power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
power = ((long long)regval << 8) * data->power_lsb;
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
|
|
@ -505,13 +495,9 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_power(struct device *dev, u32 attr, long val)
|
||||
static int ina238_write_power_max(struct device *dev, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
long regval;
|
||||
|
||||
if (attr != hwmon_power_max)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* Unsigned postive values. Compared against the 24-bit power register,
|
||||
|
|
@ -519,12 +505,16 @@ static int ina238_write_power(struct device *dev, u32 attr, long val)
|
|||
* register.
|
||||
* The first clamp_val() is to establish a baseline to avoid overflows.
|
||||
*/
|
||||
regval = clamp_val(val, 0, LONG_MAX / 2);
|
||||
regval = div_u64(regval * 4 * 100 * data->rshunt, data->config->power_calculate_factor *
|
||||
1000ULL * INA238_FIXED_SHUNT * data->gain);
|
||||
regval = clamp_val(regval >> 8, 0, U16_MAX);
|
||||
val = clamp_val(val, 0, LONG_MAX / 2);
|
||||
val = DIV_ROUND_CLOSEST(val, data->power_lsb);
|
||||
val = clamp_val(val >> 8, 0, U16_MAX);
|
||||
|
||||
return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
|
||||
return regmap_write(data->regmap, INA238_POWER_LIMIT, val);
|
||||
}
|
||||
|
||||
static int ina238_temp_from_reg(s16 regval, u8 resolution)
|
||||
{
|
||||
return ((regval >> (16 - resolution)) * 1000) >> (resolution - 9);
|
||||
}
|
||||
|
||||
static int ina238_read_temp(struct device *dev, u32 attr, long *val)
|
||||
|
|
@ -538,17 +528,14 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
|
|||
err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
/* Signed, result in mC */
|
||||
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
|
||||
(s64)data->config->temp_lsb, 10000);
|
||||
*val = ina238_temp_from_reg(regval, data->config->temp_resolution);
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
/* Signed, result in mC */
|
||||
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
|
||||
(s64)data->config->temp_lsb, 10000);
|
||||
*val = ina238_temp_from_reg(regval, data->config->temp_resolution);
|
||||
break;
|
||||
case hwmon_temp_max_alarm:
|
||||
err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val);
|
||||
|
|
@ -564,39 +551,37 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_write_temp(struct device *dev, u32 attr, long val)
|
||||
static u16 ina238_temp_to_reg(long val, u8 resolution)
|
||||
{
|
||||
int fraction = 1000 - DIV_ROUND_CLOSEST(1000, BIT(resolution - 9));
|
||||
|
||||
val = clamp_val(val, -255000 - fraction, 255000 + fraction);
|
||||
|
||||
return (DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution)) & 0xffff;
|
||||
}
|
||||
|
||||
static int ina238_write_temp_max(struct device *dev, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int regval;
|
||||
|
||||
if (attr != hwmon_temp_max)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Signed */
|
||||
val = clamp_val(val, -40000, 125000);
|
||||
regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift;
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift);
|
||||
|
||||
regval = ina238_temp_to_reg(val, data->config->temp_resolution);
|
||||
return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
|
||||
}
|
||||
|
||||
static ssize_t energy1_input_show(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
static int ina238_read_energy(struct device *dev, s64 *energy)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
u64 regval;
|
||||
u64 energy;
|
||||
int ret;
|
||||
|
||||
ret = ina238_read_reg40(data->client, SQ52206_ENERGY, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* result in uJ */
|
||||
energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * 10 *
|
||||
data->config->power_calculate_factor, 4 * data->rshunt);
|
||||
|
||||
return sysfs_emit(buf, "%llu\n", energy);
|
||||
*energy = regval * data->energy_lsb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
|
|
@ -606,9 +591,11 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
case hwmon_in:
|
||||
return ina238_read_in(dev, attr, channel, val);
|
||||
case hwmon_curr:
|
||||
return ina238_read_current(dev, attr, val);
|
||||
return ina238_read_curr(dev, attr, val);
|
||||
case hwmon_power:
|
||||
return ina238_read_power(dev, attr, val);
|
||||
case hwmon_energy64:
|
||||
return ina238_read_energy(dev, (s64 *)val);
|
||||
case hwmon_temp:
|
||||
return ina238_read_temp(dev, attr, val);
|
||||
default:
|
||||
|
|
@ -629,11 +616,14 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
case hwmon_in:
|
||||
err = ina238_write_in(dev, attr, channel, val);
|
||||
break;
|
||||
case hwmon_curr:
|
||||
err = ina238_write_curr(dev, attr, val);
|
||||
break;
|
||||
case hwmon_power:
|
||||
err = ina238_write_power(dev, attr, val);
|
||||
err = ina238_write_power_max(dev, val);
|
||||
break;
|
||||
case hwmon_temp:
|
||||
err = ina238_write_temp(dev, attr, val);
|
||||
err = ina238_write_temp_max(dev, val);
|
||||
break;
|
||||
default:
|
||||
err = -EOPNOTSUPP;
|
||||
|
|
@ -650,6 +640,7 @@ static umode_t ina238_is_visible(const void *drvdata,
|
|||
{
|
||||
const struct ina238_data *data = drvdata;
|
||||
bool has_power_highest = data->config->has_power_highest;
|
||||
bool has_energy = data->config->has_energy;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
|
|
@ -667,7 +658,12 @@ static umode_t ina238_is_visible(const void *drvdata,
|
|||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
case hwmon_curr_max_alarm:
|
||||
case hwmon_curr_min_alarm:
|
||||
return 0444;
|
||||
case hwmon_curr_max:
|
||||
case hwmon_curr_min:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -685,6 +681,11 @@ static umode_t ina238_is_visible(const void *drvdata,
|
|||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_energy64:
|
||||
/* hwmon_energy_input */
|
||||
if (has_energy)
|
||||
return 0444;
|
||||
return 0;
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
|
|
@ -712,11 +713,14 @@ static const struct hwmon_channel_info * const ina238_info[] = {
|
|||
INA238_HWMON_IN_CONFIG),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
/* 0: current through shunt */
|
||||
HWMON_C_INPUT),
|
||||
HWMON_C_INPUT | HWMON_C_MIN | HWMON_C_MIN_ALARM |
|
||||
HWMON_C_MAX | HWMON_C_MAX_ALARM),
|
||||
HWMON_CHANNEL_INFO(power,
|
||||
/* 0: power */
|
||||
HWMON_P_INPUT | HWMON_P_MAX |
|
||||
HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST),
|
||||
HWMON_CHANNEL_INFO(energy64,
|
||||
HWMON_E_INPUT),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
/* 0: die temperature */
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
|
||||
|
|
@ -734,18 +738,8 @@ static const struct hwmon_chip_info ina238_chip_info = {
|
|||
.info = ina238_info,
|
||||
};
|
||||
|
||||
/* energy attributes are 5 bytes wide so we need u64 */
|
||||
static DEVICE_ATTR_RO(energy1_input);
|
||||
|
||||
static struct attribute *ina238_attrs[] = {
|
||||
&dev_attr_energy1_input.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ina238);
|
||||
|
||||
static int ina238_probe(struct i2c_client *client)
|
||||
{
|
||||
struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct ina238_data *data;
|
||||
|
|
@ -771,33 +765,48 @@ static int ina238_probe(struct i2c_client *client)
|
|||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
/* load shunt value */
|
||||
data->rshunt = INA238_RSHUNT_DEFAULT;
|
||||
if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata)
|
||||
data->rshunt = pdata->shunt_uohms;
|
||||
if (data->rshunt == 0) {
|
||||
dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* load shunt gain value */
|
||||
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
|
||||
data->gain = 4; /* Default of ADCRANGE = 0 */
|
||||
if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
|
||||
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Setup CONFIG register */
|
||||
config = data->config->config_default;
|
||||
if (chip == sq52206) {
|
||||
if (data->gain == 1)
|
||||
config |= SQ52206_CONFIG_ADCRANGE_HIGH; /* ADCRANGE = 10/11 is /1 */
|
||||
else if (data->gain == 2)
|
||||
config |= SQ52206_CONFIG_ADCRANGE_LOW; /* ADCRANGE = 01 is /2 */
|
||||
} else if (data->gain == 1) {
|
||||
config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */
|
||||
if (data->config->current_lsb) {
|
||||
data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB;
|
||||
data->current_lsb = data->config->current_lsb;
|
||||
} else {
|
||||
/* load shunt value */
|
||||
if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0)
|
||||
data->rshunt = INA238_RSHUNT_DEFAULT;
|
||||
if (data->rshunt == 0) {
|
||||
dev_err(dev, "invalid shunt resister value %u\n", data->rshunt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* load shunt gain value */
|
||||
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
|
||||
data->gain = 4; /* Default of ADCRANGE = 0 */
|
||||
if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
|
||||
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Setup SHUNT_CALIBRATION register with fixed value */
|
||||
ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
|
||||
INA238_CALIBRATION_VALUE);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (chip == sq52206) {
|
||||
if (data->gain == 1) /* ADCRANGE = 10/11 is /1 */
|
||||
config |= SQ52206_CONFIG_ADCRANGE_HIGH;
|
||||
else if (data->gain == 2) /* ADCRANGE = 01 is /2 */
|
||||
config |= SQ52206_CONFIG_ADCRANGE_LOW;
|
||||
} else if (data->gain == 1) { /* ADCRANGE = 1 is /1 */
|
||||
config |= INA238_CONFIG_ADCRANGE;
|
||||
}
|
||||
data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4;
|
||||
data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain,
|
||||
data->rshunt);
|
||||
}
|
||||
|
||||
ret = regmap_write(data->regmap, INA238_CONFIG, config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
|
|
@ -812,31 +821,33 @@ static int ina238_probe(struct i2c_client *client)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Setup SHUNT_CALIBRATION register with fixed value */
|
||||
ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION,
|
||||
INA238_CALIBRATION_VALUE);
|
||||
/* Setup alert/alarm configuration */
|
||||
config = INA238_DIAG_ALERT_DEFAULT;
|
||||
if (device_property_read_bool(dev, "ti,alert-polarity-active-high"))
|
||||
config |= INA238_DIAG_ALERT_APOL;
|
||||
|
||||
ret = regmap_write(data->regmap, INA238_DIAG_ALERT, config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Setup alert/alarm configuration */
|
||||
ret = regmap_write(data->regmap, INA238_DIAG_ALERT,
|
||||
INA238_DIAG_ALERT_DEFAULT);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
data->voltage_lsb[1] = data->config->bus_voltage_lsb;
|
||||
|
||||
data->power_lsb = DIV_ROUND_CLOSEST(data->current_lsb *
|
||||
data->config->power_calculate_factor,
|
||||
100);
|
||||
|
||||
data->energy_lsb = data->power_lsb * 16;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&ina238_chip_info,
|
||||
data->config->has_energy ?
|
||||
ina238_groups : NULL);
|
||||
&ina238_chip_info, NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
|
||||
client->name, data->rshunt, data->gain);
|
||||
if (data->rshunt)
|
||||
dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n",
|
||||
client->name, data->rshunt, data->gain);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -845,6 +856,8 @@ static const struct i2c_device_id ina238_id[] = {
|
|||
{ "ina228", ina228 },
|
||||
{ "ina237", ina237 },
|
||||
{ "ina238", ina238 },
|
||||
{ "ina700", ina700 },
|
||||
{ "ina780", ina780 },
|
||||
{ "sq52206", sq52206 },
|
||||
{ }
|
||||
};
|
||||
|
|
@ -863,6 +876,14 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = {
|
|||
.compatible = "ti,ina238",
|
||||
.data = (void *)ina238
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ina700",
|
||||
.data = (void *)ina700
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ina780",
|
||||
.data = (void *)ina780
|
||||
},
|
||||
{
|
||||
.compatible = "silergy,sq52206",
|
||||
.data = (void *)sq52206
|
||||
|
|
|
|||
|
|
@ -84,6 +84,13 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
|
|||
*/
|
||||
#define AMD_I3255_STR "3255"
|
||||
|
||||
/*
|
||||
* PCI Device IDs for AMD's Family 1Ah-based SOCs.
|
||||
* Defining locally as IDs are not shared.
|
||||
*/
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b
|
||||
|
||||
struct k10temp_data {
|
||||
struct pci_dev *pdev;
|
||||
void (*read_htcreg)(struct pci_dev *pdev, u32 *regval);
|
||||
|
|
@ -556,7 +563,10 @@ static const struct pci_device_id k10temp_id_table[] = {
|
|||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M78H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M00H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M20H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) },
|
||||
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
|
||||
{}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ enum systems {
|
|||
LENOVO_P8,
|
||||
};
|
||||
|
||||
static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31, 32};
|
||||
|
||||
static const char * const lenovo_px_ec_temp_label[] = {
|
||||
"CPU1",
|
||||
|
|
@ -84,9 +84,29 @@ static const char * const lenovo_px_ec_temp_label[] = {
|
|||
"PCI_Z3",
|
||||
"PCI_Z4",
|
||||
"AMB",
|
||||
"PSU1",
|
||||
"PSU2",
|
||||
};
|
||||
|
||||
static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
static int p8_temp_map[] = {0, 1, 2, 8, 9, 13, 14, 15, 16, 17, 19, 20, 33};
|
||||
|
||||
static const char * const lenovo_p8_ec_temp_label[] = {
|
||||
"CPU1",
|
||||
"CPU_DIMM_BANK1",
|
||||
"CPU_DIMM_BANK2",
|
||||
"M2_Z2R",
|
||||
"M2_Z3R",
|
||||
"DIMM_RIGHT",
|
||||
"DIMM_LEFT",
|
||||
"PCI_Z1",
|
||||
"PCI_Z2",
|
||||
"PCI_Z3",
|
||||
"AMB",
|
||||
"REAR_VR",
|
||||
"PSU",
|
||||
};
|
||||
|
||||
static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31};
|
||||
|
||||
static const char * const lenovo_gen_ec_temp_label[] = {
|
||||
"CPU1",
|
||||
|
|
@ -101,6 +121,7 @@ static const char * const lenovo_gen_ec_temp_label[] = {
|
|||
"PCI_Z3",
|
||||
"PCI_Z4",
|
||||
"AMB",
|
||||
"PSU",
|
||||
};
|
||||
|
||||
static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
|
@ -293,6 +314,8 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = {
|
|||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
|
||||
|
|
@ -327,6 +350,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = {
|
|||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
|
||||
|
|
@ -359,6 +383,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = {
|
|||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
|
||||
|
|
@ -388,6 +413,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = {
|
|||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,
|
||||
|
|
@ -545,9 +571,9 @@ static int lenovo_ec_probe(struct platform_device *pdev)
|
|||
break;
|
||||
case 3:
|
||||
ec_data->fan_labels = p8_ec_fan_label;
|
||||
ec_data->temp_labels = lenovo_gen_ec_temp_label;
|
||||
ec_data->temp_labels = lenovo_p8_ec_temp_label;
|
||||
ec_data->fan_map = p8_fan_map;
|
||||
ec_data->temp_map = gen_temp_map;
|
||||
ec_data->temp_map = p8_temp_map;
|
||||
lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8;
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ enum lm75_type { /* keep sorted in alphabetical order */
|
|||
max6626,
|
||||
max31725,
|
||||
mcp980x,
|
||||
p3t1750,
|
||||
p3t1755,
|
||||
pct2075,
|
||||
stds75,
|
||||
|
|
@ -222,6 +223,13 @@ static const struct lm75_params device_params[] = {
|
|||
.default_resolution = 9,
|
||||
.default_sample_time = MSEC_PER_SEC / 18,
|
||||
},
|
||||
[p3t1750] = {
|
||||
.clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
|
||||
.default_resolution = 12,
|
||||
.default_sample_time = 55,
|
||||
.num_sample_times = 4,
|
||||
.sample_times = (unsigned int []){ 28, 55, 110, 220 },
|
||||
},
|
||||
[p3t1755] = {
|
||||
.clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */
|
||||
.default_resolution = 12,
|
||||
|
|
@ -805,6 +813,7 @@ static const struct i2c_device_id lm75_i2c_ids[] = {
|
|||
{ "max31725", max31725, },
|
||||
{ "max31726", max31725, },
|
||||
{ "mcp980x", mcp980x, },
|
||||
{ "p3t1750", p3t1750, },
|
||||
{ "p3t1755", p3t1755, },
|
||||
{ "pct2075", pct2075, },
|
||||
{ "stds75", stds75, },
|
||||
|
|
@ -916,6 +925,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
|
|||
.compatible = "maxim,mcp980x",
|
||||
.data = (void *)mcp980x
|
||||
},
|
||||
{
|
||||
.compatible = "nxp,p3t1750",
|
||||
.data = (void *)p3t1750
|
||||
},
|
||||
{
|
||||
.compatible = "nxp,p3t1755",
|
||||
.data = (void *)p3t1755
|
||||
|
|
|
|||
|
|
@ -1693,8 +1693,7 @@ static int ltc4282_probe(struct i2c_client *i2c)
|
|||
|
||||
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return dev_err_probe(dev, -ENOMEM,
|
||||
"Failed to allocate memory\n");
|
||||
return -ENOMEM;
|
||||
|
||||
st->map = devm_regmap_init_i2c(i2c, <c4282_regmap_config);
|
||||
if (IS_ERR(st->map))
|
||||
|
|
|
|||
|
|
@ -63,12 +63,14 @@ struct mlxreg_fan;
|
|||
* @reg: register offset;
|
||||
* @mask: fault mask;
|
||||
* @prsnt: present register offset;
|
||||
* @shift: tacho presence bit shift;
|
||||
*/
|
||||
struct mlxreg_fan_tacho {
|
||||
bool connected;
|
||||
u32 reg;
|
||||
u32 mask;
|
||||
u32 prsnt;
|
||||
u32 shift;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -113,8 +115,8 @@ struct mlxreg_fan {
|
|||
int divider;
|
||||
};
|
||||
|
||||
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state);
|
||||
static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state, bool thermal);
|
||||
|
||||
static int
|
||||
mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
|
|
@ -143,8 +145,10 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
|||
/*
|
||||
* Map channel to presence bit - drawer can be equipped with
|
||||
* one or few FANs, while presence is indicated per drawer.
|
||||
* Shift channel value if necessary to align with register value.
|
||||
*/
|
||||
if (BIT(channel / fan->tachos_per_drwr) & regval) {
|
||||
if (BIT(rol32(channel, tacho->shift) / fan->tachos_per_drwr) &
|
||||
regval) {
|
||||
/* FAN is not connected - return zero for FAN speed. */
|
||||
*val = 0;
|
||||
return 0;
|
||||
|
|
@ -224,8 +228,9 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
|||
* last thermal state.
|
||||
*/
|
||||
if (pwm->last_hwmon_state >= pwm->last_thermal_state)
|
||||
return mlxreg_fan_set_cur_state(pwm->cdev,
|
||||
pwm->last_hwmon_state);
|
||||
return _mlxreg_fan_set_cur_state(pwm->cdev,
|
||||
pwm->last_hwmon_state,
|
||||
false);
|
||||
return 0;
|
||||
}
|
||||
return regmap_write(fan->regmap, pwm->reg, val);
|
||||
|
|
@ -357,9 +362,8 @@ static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
|
||||
static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state, bool thermal)
|
||||
{
|
||||
struct mlxreg_fan_pwm *pwm = cdev->devdata;
|
||||
struct mlxreg_fan *fan = pwm->fan;
|
||||
|
|
@ -369,7 +373,8 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
|||
return -EINVAL;
|
||||
|
||||
/* Save thermal state. */
|
||||
pwm->last_thermal_state = state;
|
||||
if (thermal)
|
||||
pwm->last_thermal_state = state;
|
||||
|
||||
state = max_t(unsigned long, state, pwm->last_hwmon_state);
|
||||
err = regmap_write(fan->regmap, pwm->reg,
|
||||
|
|
@ -381,6 +386,13 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
|
||||
{
|
||||
return _mlxreg_fan_set_cur_state(cdev, state, true);
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
|
||||
.get_max_state = mlxreg_fan_get_max_state,
|
||||
.get_cur_state = mlxreg_fan_get_cur_state,
|
||||
|
|
@ -400,7 +412,7 @@ static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan,
|
|||
return err;
|
||||
}
|
||||
|
||||
return !!(regval & data->bit);
|
||||
return data->slot ? (data->slot <= regval ? 1 : 0) : !!(regval & data->bit);
|
||||
}
|
||||
|
||||
static int mlxreg_pwm_connect_verify(struct mlxreg_fan *fan,
|
||||
|
|
@ -537,7 +549,15 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan,
|
|||
return err;
|
||||
}
|
||||
|
||||
drwr_avail = hweight32(regval);
|
||||
/*
|
||||
* The number of drawers could be specified in registers by counters for newer
|
||||
* systems, or by bitmasks for older systems. In case the data is provided by
|
||||
* counter, it is indicated through 'version' field.
|
||||
*/
|
||||
if (pdata->version)
|
||||
drwr_avail = regval;
|
||||
else
|
||||
drwr_avail = hweight32(regval);
|
||||
if (!tacho_avail || !drwr_avail || tacho_avail < drwr_avail) {
|
||||
dev_err(fan->dev, "Configuration is invalid: drawers num %d tachos num %d\n",
|
||||
drwr_avail, tacho_avail);
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@ static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val)
|
|||
|
||||
static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val)
|
||||
{
|
||||
u32 ret, tmp = 0;
|
||||
u32 tmp = 0;
|
||||
int ret;
|
||||
|
||||
ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank,
|
||||
reg, 0, &tmp);
|
||||
|
|
|
|||
|
|
@ -721,11 +721,6 @@ static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev)
|
|||
return init_device(drvdata, drvdata->update_interval);
|
||||
}
|
||||
|
||||
static void mutex_fini(void *lock)
|
||||
{
|
||||
mutex_destroy(lock);
|
||||
}
|
||||
|
||||
static int nzxt_smart2_hid_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
|
|
@ -741,8 +736,7 @@ static int nzxt_smart2_hid_probe(struct hid_device *hdev,
|
|||
|
||||
init_waitqueue_head(&drvdata->wq);
|
||||
|
||||
mutex_init(&drvdata->mutex);
|
||||
ret = devm_add_action_or_reset(&hdev->dev, mutex_fini, &drvdata->mutex);
|
||||
ret = devm_mutex_init(&hdev->dev, &drvdata->mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ config SENSORS_ADM1275
|
|||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281,
|
||||
ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors.
|
||||
ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and
|
||||
Digital Power Monitors.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called adm1275.
|
||||
|
|
@ -373,6 +374,15 @@ config SENSORS_MP2856
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called mp2856.
|
||||
|
||||
config SENSORS_MP2869
|
||||
tristate "MPS MP2869"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for MPS
|
||||
MP2869 Dual Loop Digital Multi-Phase Controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called mp2869.
|
||||
|
||||
config SENSORS_MP2888
|
||||
tristate "MPS MP2888"
|
||||
help
|
||||
|
|
@ -391,6 +401,15 @@ config SENSORS_MP2891
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called mp2891.
|
||||
|
||||
config SENSORS_MP29502
|
||||
tristate "MPS MP29502"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for MPS
|
||||
MP29502 Dual Loop Digital Multi-Phase Controller.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called mp29502.
|
||||
|
||||
config SENSORS_MP2975
|
||||
tristate "MPS MP2975"
|
||||
help
|
||||
|
|
|
|||
|
|
@ -37,8 +37,10 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
|||
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
|
||||
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
|
||||
obj-$(CONFIG_SENSORS_MP2856) += mp2856.o
|
||||
obj-$(CONFIG_SENSORS_MP2869) += mp2869.o
|
||||
obj-$(CONFIG_SENSORS_MP2888) += mp2888.o
|
||||
obj-$(CONFIG_SENSORS_MP2891) += mp2891.o
|
||||
obj-$(CONFIG_SENSORS_MP29502) += mp29502.o
|
||||
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
|
||||
obj-$(CONFIG_SENSORS_MP2993) += mp2993.o
|
||||
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
#include <linux/log2.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, adm1293, adm1294 };
|
||||
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
|
||||
adm1293, adm1294, sq24905c };
|
||||
|
||||
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
|
||||
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
|
||||
|
|
@ -486,6 +487,7 @@ static const struct i2c_device_id adm1275_id[] = {
|
|||
{ "adm1281", adm1281 },
|
||||
{ "adm1293", adm1293 },
|
||||
{ "adm1294", adm1294 },
|
||||
{ "mc09c", sq24905c },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adm1275_id);
|
||||
|
|
@ -532,7 +534,8 @@ static int adm1275_probe(struct i2c_client *client)
|
|||
dev_err(&client->dev, "Failed to read Manufacturer ID\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != 3 || strncmp(block_buffer, "ADI", 3)) {
|
||||
if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) &&
|
||||
(ret != 2 || strncmp(block_buffer, "SY", 2))) {
|
||||
dev_err(&client->dev, "Unsupported Manufacturer ID\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
@ -558,7 +561,8 @@ static int adm1275_probe(struct i2c_client *client)
|
|||
|
||||
if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
|
||||
mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
|
||||
mid->driver_data == adm1293 || mid->driver_data == adm1294)
|
||||
mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
|
||||
mid->driver_data == sq24905c)
|
||||
config_read_fn = i2c_smbus_read_word_data;
|
||||
else
|
||||
config_read_fn = i2c_smbus_read_byte_data;
|
||||
|
|
@ -708,6 +712,7 @@ static int adm1275_probe(struct i2c_client *client)
|
|||
break;
|
||||
case adm1278:
|
||||
case adm1281:
|
||||
case sq24905c:
|
||||
data->have_vout = true;
|
||||
data->have_pin_max = true;
|
||||
data->have_temp_max = true;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ enum chips {
|
|||
raa228004,
|
||||
raa228006,
|
||||
raa228228,
|
||||
raa228244,
|
||||
raa228246,
|
||||
raa229001,
|
||||
raa229004,
|
||||
raa229621,
|
||||
|
|
@ -464,6 +466,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = {
|
|||
{"raa228004", raa_dmpvr2_hv},
|
||||
{"raa228006", raa_dmpvr2_hv},
|
||||
{"raa228228", raa_dmpvr2_2rail_nontc},
|
||||
{"raa228244", raa_dmpvr2_2rail_nontc},
|
||||
{"raa228246", raa_dmpvr2_2rail_nontc},
|
||||
{"raa229001", raa_dmpvr2_2rail},
|
||||
{"raa229004", raa_dmpvr2_2rail},
|
||||
{"raa229621", raa_dmpvr2_2rail},
|
||||
|
|
@ -512,6 +516,8 @@ static const struct of_device_id isl68137_of_match[] = {
|
|||
{ .compatible = "renesas,raa228004", .data = (void *)raa_dmpvr2_hv },
|
||||
{ .compatible = "renesas,raa228006", .data = (void *)raa_dmpvr2_hv },
|
||||
{ .compatible = "renesas,raa228228", .data = (void *)raa_dmpvr2_2rail_nontc },
|
||||
{ .compatible = "renesas,raa228244", .data = (void *)raa_dmpvr2_2rail_nontc },
|
||||
{ .compatible = "renesas,raa228246", .data = (void *)raa_dmpvr2_2rail_nontc },
|
||||
{ .compatible = "renesas,raa229001", .data = (void *)raa_dmpvr2_2rail },
|
||||
{ .compatible = "renesas,raa229004", .data = (void *)raa_dmpvr2_2rail },
|
||||
{ .compatible = "renesas,raa229621", .data = (void *)raa_dmpvr2_2rail },
|
||||
|
|
|
|||
659
drivers/hwmon/pmbus/mp2869.c
Normal file
659
drivers/hwmon/pmbus/mp2869.c
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2869)
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
/*
|
||||
* Vender specific registers, the register MFR_SVI3_IOUT_PRT(0x67),
|
||||
* READ_PIN_EST(0x94)and READ_IIN_EST(0x95) redefine the standard
|
||||
* PMBUS register. The MFR_VOUT_LOOP_CTRL(0x29) is used to identify
|
||||
* the vout scale and the MFR_SVI3_IOUT_PRT(0x67) is used to identify
|
||||
* the iout scale. The READ_PIN_EST(0x94) is used to read input power
|
||||
* per rail. The MP2891 does not have standard READ_IIN register(0x89),
|
||||
* the iin telemetry can be obtained through the vendor redefined
|
||||
* register READ_IIN_EST(0x95).
|
||||
*/
|
||||
#define MFR_SVI3_IOUT_PRT 0x67
|
||||
#define MFR_READ_PIN_EST 0x94
|
||||
#define MFR_READ_IIN_EST 0x95
|
||||
#define MFR_TSNS_FLT_SET 0xBB
|
||||
|
||||
#define MP2869_VIN_OV_FAULT_GAIN 4
|
||||
#define MP2869_READ_VOUT_DIV 1024
|
||||
#define MP2869_READ_IOUT_DIV 32
|
||||
#define MP2869_OVUV_LIMIT_SCALE 10
|
||||
#define MP2869_OVUV_DELTA_SCALE 50
|
||||
#define MP2869_TEMP_LIMIT_OFFSET 40
|
||||
#define MP2869_IOUT_LIMIT_UINT 8
|
||||
#define MP2869_POUT_OP_GAIN 2
|
||||
|
||||
#define MP2869_PAGE_NUM 2
|
||||
|
||||
#define MP2869_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
|
||||
PMBUS_HAVE_IIN | \
|
||||
PMBUS_HAVE_STATUS_VOUT | \
|
||||
PMBUS_HAVE_STATUS_IOUT | \
|
||||
PMBUS_HAVE_STATUS_TEMP | \
|
||||
PMBUS_HAVE_STATUS_INPUT)
|
||||
|
||||
#define MP2869_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
|
||||
PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \
|
||||
PMBUS_HAVE_STATUS_VOUT | \
|
||||
PMBUS_HAVE_STATUS_IOUT | \
|
||||
PMBUS_HAVE_STATUS_TEMP | \
|
||||
PMBUS_HAVE_STATUS_INPUT)
|
||||
|
||||
struct mp2869_data {
|
||||
struct pmbus_driver_info info;
|
||||
bool mfr_thwn_flt_en;
|
||||
int vout_scale[MP2869_PAGE_NUM];
|
||||
int iout_scale[MP2869_PAGE_NUM];
|
||||
};
|
||||
|
||||
static const int mp2869_vout_sacle[8] = {6400, 5120, 2560, 2048, 1024,
|
||||
4, 2, 1};
|
||||
static const int mp2869_iout_sacle[8] = {32, 1, 2, 4, 8, 16, 32, 64};
|
||||
|
||||
#define to_mp2869_data(x) container_of(x, struct mp2869_data, info)
|
||||
|
||||
static u16 mp2869_reg2data_linear11(u16 word)
|
||||
{
|
||||
s16 exponent;
|
||||
s32 mantissa;
|
||||
s64 val;
|
||||
|
||||
exponent = ((s16)word) >> 11;
|
||||
mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
|
||||
val = mantissa;
|
||||
|
||||
if (exponent >= 0)
|
||||
val <<= exponent;
|
||||
else
|
||||
val >>= -exponent;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int
|
||||
mp2869_identify_thwn_flt(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->mfr_thwn_flt_en = FIELD_GET(GENMASK(13, 13), ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp2869_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The output voltage is equal to the READ_VOUT(0x8B) register value multiply
|
||||
* by vout_scale.
|
||||
* Obtain vout scale from the register PMBUS_VOUT_SCALE_LOOP, bits 12-10
|
||||
* PMBUS_VOUT_SCALE_LOOP[12:10]:
|
||||
* 000b - 6.25mV/LSB, 001b - 5mV/LSB, 010b - 2.5mV/LSB, 011b - 2mV/LSB
|
||||
* 100b - 1mV/Lsb, 101b - (1/256)mV/LSB, 110b - (1/512)mV/LSB,
|
||||
* 111b - (1/1024)mV/LSB
|
||||
*/
|
||||
data->vout_scale[page] = mp2869_vout_sacle[FIELD_GET(GENMASK(12, 10), ret)];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp2869_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The output current is equal to the READ_IOUT(0x8C) register value
|
||||
* multiply by iout_scale.
|
||||
* Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0].
|
||||
* The value is selected as below:
|
||||
* 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB,
|
||||
* 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB
|
||||
* 110b - 1A/LSB, 111b - 2A/LSB
|
||||
*/
|
||||
data->iout_scale[page] = mp2869_iout_sacle[FIELD_GET(GENMASK(2, 0), ret)];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp2869_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_MODE:
|
||||
/*
|
||||
* The calculation of vout in this driver is based on direct format.
|
||||
* As a result, the format of vout is enforced to direct.
|
||||
*/
|
||||
ret = PB_VOUT_MODE_DIRECT;
|
||||
break;
|
||||
case PMBUS_STATUS_BYTE:
|
||||
/*
|
||||
* If the tsns digital fault is enabled, the TEMPERATURE flag
|
||||
* of PMBUS_STATUS_BYTE should come from STATUS_MFR_SPECIFIC
|
||||
* register bit1.
|
||||
*/
|
||||
if (!data->mfr_thwn_flt_en)
|
||||
return -ENODATA;
|
||||
|
||||
ret = pmbus_read_byte_data(client, page, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & ~GENMASK(2, 2)) |
|
||||
FIELD_PREP(GENMASK(2, 2),
|
||||
FIELD_GET(GENMASK(1, 1),
|
||||
pmbus_read_byte_data(client, page,
|
||||
PMBUS_STATUS_MFR_SPECIFIC)));
|
||||
break;
|
||||
case PMBUS_STATUS_TEMPERATURE:
|
||||
/*
|
||||
* If the tsns digital fault is enabled, the OT Fault and OT Warning
|
||||
* flag of PMBUS_STATUS_TEMPERATURE should come from STATUS_MFR_SPECIFIC
|
||||
* register bit1.
|
||||
*/
|
||||
if (!data->mfr_thwn_flt_en)
|
||||
return -ENODATA;
|
||||
|
||||
ret = pmbus_read_byte_data(client, page, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & ~GENMASK(7, 6)) |
|
||||
FIELD_PREP(GENMASK(6, 6),
|
||||
FIELD_GET(GENMASK(1, 1),
|
||||
pmbus_read_byte_data(client, page,
|
||||
PMBUS_STATUS_MFR_SPECIFIC))) |
|
||||
FIELD_PREP(GENMASK(7, 7),
|
||||
FIELD_GET(GENMASK(1, 1),
|
||||
pmbus_read_byte_data(client, page,
|
||||
PMBUS_STATUS_MFR_SPECIFIC)));
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp2869_read_word_data(struct i2c_client *client, int page, int phase,
|
||||
int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_WORD:
|
||||
/*
|
||||
* If the tsns digital fault is enabled, the OT Fault flag
|
||||
* of PMBUS_STATUS_WORD should come from STATUS_MFR_SPECIFIC
|
||||
* register bit1.
|
||||
*/
|
||||
if (!data->mfr_thwn_flt_en)
|
||||
return -ENODATA;
|
||||
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & ~GENMASK(2, 2)) |
|
||||
FIELD_PREP(GENMASK(2, 2),
|
||||
FIELD_GET(GENMASK(1, 1),
|
||||
pmbus_read_byte_data(client, page,
|
||||
PMBUS_STATUS_MFR_SPECIFIC)));
|
||||
break;
|
||||
case PMBUS_READ_VIN:
|
||||
/*
|
||||
* The MP2869 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
|
||||
* 31.25mV/LSB. And the vin scale is set to 31.25mV/Lsb(using r/m/b scale)
|
||||
* in MP2869 pmbus_driver_info struct, so the word data bit0-bit10 can be
|
||||
* returned to pmbus core directly.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(10, 0), ret);
|
||||
break;
|
||||
case PMBUS_READ_IIN:
|
||||
/*
|
||||
* The MP2869 redefine the standard 0x95 register as iin telemetry
|
||||
* per rail.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
case PMBUS_READ_PIN:
|
||||
/*
|
||||
* The MP2869 redefine the standard 0x94 register as pin telemetry
|
||||
* per rail. The MP2869 MFR_READ_PIN_EST register is linear11 format,
|
||||
* but the pin scale is set to 1W/Lsb(using r/m/b scale). As a result,
|
||||
* the pin read from MP2869 should be converted to W, then return
|
||||
* the result to pmbus core.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mp2869_reg2data_linear11(ret);
|
||||
break;
|
||||
case PMBUS_READ_VOUT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page],
|
||||
MP2869_READ_VOUT_DIV);
|
||||
break;
|
||||
case PMBUS_READ_IOUT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page],
|
||||
MP2869_READ_IOUT_DIV);
|
||||
break;
|
||||
case PMBUS_READ_POUT:
|
||||
/*
|
||||
* The MP2869 PMBUS_READ_POUT register is linear11 format, but the pout
|
||||
* scale is set to 1W/Lsb(using r/m/b scale). As a result, the pout read
|
||||
* from MP2869 should be converted to W, then return the result to pmbus
|
||||
* core.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mp2869_reg2data_linear11(ret);
|
||||
break;
|
||||
case PMBUS_READ_TEMPERATURE_1:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(10, 0), ret);
|
||||
break;
|
||||
case PMBUS_VOUT_OV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(GENMASK(12, 9), ret))
|
||||
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE +
|
||||
(FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
|
||||
else
|
||||
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
|
||||
break;
|
||||
case PMBUS_VOUT_UV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(GENMASK(12, 9), ret))
|
||||
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE -
|
||||
(FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE;
|
||||
else
|
||||
ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE;
|
||||
break;
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
/*
|
||||
* The scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
|
||||
* is 1°C/LSB and they have 40°C offset.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & GENMASK(7, 0)) - MP2869_TEMP_LIMIT_OFFSET;
|
||||
break;
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & GENMASK(7, 0)) * MP2869_VIN_OV_FAULT_GAIN;
|
||||
break;
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(9, 0), ret);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] *
|
||||
MP2869_IOUT_LIMIT_UINT, MP2869_READ_IOUT_DIV);
|
||||
break;
|
||||
case PMBUS_POUT_OP_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & GENMASK(7, 0)) * MP2869_POUT_OP_GAIN;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp2869_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct mp2869_data *data = to_mp2869_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_UV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP2869 PMBUS_VOUT_UV_FAULT_LIMIT[8:0] is the limit value,
|
||||
* and bit9-bit15 should not be changed.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(GENMASK(12, 9), ret))
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(8, 0)) |
|
||||
FIELD_PREP(GENMASK(8, 0),
|
||||
DIV_ROUND_CLOSEST(word +
|
||||
(FIELD_GET(GENMASK(12, 9),
|
||||
ret) + 1) *
|
||||
MP2869_OVUV_DELTA_SCALE,
|
||||
MP2869_OVUV_LIMIT_SCALE)));
|
||||
else
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(8, 0)) |
|
||||
FIELD_PREP(GENMASK(8, 0),
|
||||
DIV_ROUND_CLOSEST(word,
|
||||
MP2869_OVUV_LIMIT_SCALE)));
|
||||
break;
|
||||
case PMBUS_VOUT_OV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP2869 PMBUS_VOUT_OV_FAULT_LIMIT[8:0] is the limit value,
|
||||
* and bit9-bit15 should not be changed.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(GENMASK(12, 9), ret))
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(8, 0)) |
|
||||
FIELD_PREP(GENMASK(8, 0),
|
||||
DIV_ROUND_CLOSEST(word -
|
||||
(FIELD_GET(GENMASK(12, 9),
|
||||
ret) + 1) *
|
||||
MP2869_OVUV_DELTA_SCALE,
|
||||
MP2869_OVUV_LIMIT_SCALE)));
|
||||
else
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(8, 0)) |
|
||||
FIELD_PREP(GENMASK(8, 0),
|
||||
DIV_ROUND_CLOSEST(word,
|
||||
MP2869_OVUV_LIMIT_SCALE)));
|
||||
break;
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
/*
|
||||
* If the tsns digital fault is enabled, the PMBUS_OT_FAULT_LIMIT and
|
||||
* PMBUS_OT_WARN_LIMIT can not be written.
|
||||
*/
|
||||
if (data->mfr_thwn_flt_en)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The MP2869 scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
|
||||
* have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15
|
||||
* should not be changed.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(7, 0)) |
|
||||
FIELD_PREP(GENMASK(7, 0),
|
||||
word + MP2869_TEMP_LIMIT_OFFSET));
|
||||
break;
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP2869 PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15
|
||||
* should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb,
|
||||
* but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data
|
||||
* should divide by MP2869_VIN_OV_FAULT_GAIN(4)
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(7, 0)) |
|
||||
FIELD_PREP(GENMASK(7, 0),
|
||||
DIV_ROUND_CLOSEST(word,
|
||||
MP2869_VIN_OV_FAULT_GAIN)));
|
||||
break;
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
/*
|
||||
* The PMBUS_VIN_UV_LIMIT[9:0] is the limit value, and bit10-bit15 should
|
||||
* not be changed. The scale of PMBUS_VIN_UV_LIMIT is 31.25mV/Lsb, and the
|
||||
* vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data can
|
||||
* be written directly.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(9, 0)) |
|
||||
FIELD_PREP(GENMASK(9, 0),
|
||||
word));
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
DIV_ROUND_CLOSEST(word * MP2869_READ_IOUT_DIV,
|
||||
MP2869_IOUT_LIMIT_UINT *
|
||||
data->iout_scale[page]));
|
||||
break;
|
||||
case PMBUS_POUT_OP_WARN_LIMIT:
|
||||
/*
|
||||
* The POUT_OP_WARN_LIMIT[11:0] is the limit value, and bit12-bit15 should
|
||||
* not be changed. The scale of POUT_OP_WARN_LIMIT is 2W/Lsb.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(11, 0)) |
|
||||
FIELD_PREP(GENMASK(11, 0),
|
||||
DIV_ROUND_CLOSEST(word,
|
||||
MP2869_POUT_OP_GAIN)));
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp2869_identify(struct i2c_client *client, struct pmbus_driver_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Identify whether tsns digital fault is enable */
|
||||
ret = mp2869_identify_thwn_flt(client, info, 1);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
/* Identify vout scale for rail1. */
|
||||
ret = mp2869_identify_vout_scale(client, info, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify vout scale for rail2. */
|
||||
ret = mp2869_identify_vout_scale(client, info, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify iout scale for rail 1. */
|
||||
ret = mp2869_identify_iout_scale(client, info, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify iout scale for rail 2. */
|
||||
return mp2869_identify_iout_scale(client, info, 1);
|
||||
}
|
||||
|
||||
static const struct pmbus_driver_info mp2869_info = {
|
||||
.pages = MP2869_PAGE_NUM,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.format[PSC_CURRENT_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.format[PSC_POWER] = direct,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
|
||||
.m[PSC_VOLTAGE_IN] = 32,
|
||||
.R[PSC_VOLTAGE_IN] = 0,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
|
||||
.m[PSC_VOLTAGE_OUT] = 1,
|
||||
.R[PSC_VOLTAGE_OUT] = 3,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
|
||||
.m[PSC_CURRENT_OUT] = 1,
|
||||
.R[PSC_CURRENT_OUT] = 0,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.R[PSC_TEMPERATURE] = 0,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
|
||||
.m[PSC_POWER] = 1,
|
||||
.R[PSC_POWER] = 0,
|
||||
.b[PSC_POWER] = 0,
|
||||
|
||||
.func[0] = MP2869_RAIL1_FUNC,
|
||||
.func[1] = MP2869_RAIL2_FUNC,
|
||||
.read_word_data = mp2869_read_word_data,
|
||||
.write_word_data = mp2869_write_word_data,
|
||||
.read_byte_data = mp2869_read_byte_data,
|
||||
.identify = mp2869_identify,
|
||||
};
|
||||
|
||||
static int mp2869_probe(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
struct mp2869_data *data;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct mp2869_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&data->info, &mp2869_info, sizeof(*info));
|
||||
info = &data->info;
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mp2869_id[] = {
|
||||
{"mp2869", 0},
|
||||
{"mp29608", 1},
|
||||
{"mp29612", 2},
|
||||
{"mp29816", 3},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp2869_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused mp2869_of_match[] = {
|
||||
{.compatible = "mps,mp2869", .data = (void *)0},
|
||||
{.compatible = "mps,mp29608", .data = (void *)1},
|
||||
{.compatible = "mps,mp29612", .data = (void *)2},
|
||||
{.compatible = "mps,mp29816", .data = (void *)3},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mp2869_of_match);
|
||||
|
||||
static struct i2c_driver mp2869_driver = {
|
||||
.driver = {
|
||||
.name = "mp2869",
|
||||
.of_match_table = mp2869_of_match,
|
||||
},
|
||||
.probe = mp2869_probe,
|
||||
.id_table = mp2869_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mp2869_driver);
|
||||
|
||||
MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net>");
|
||||
MODULE_DESCRIPTION("PMBus driver for MPS MP2869");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
670
drivers/hwmon/pmbus/mp29502.c
Normal file
670
drivers/hwmon/pmbus/mp29502.c
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP29502)
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define MFR_VOUT_SCALE_LOOP 0x29
|
||||
#define MFR_SVI3_IOUT_PRT 0x67
|
||||
#define MFR_READ_PIN_EST 0x94
|
||||
#define MFR_READ_IIN_EST 0x95
|
||||
#define MFR_VOUT_PROT1 0x3D
|
||||
#define MFR_VOUT_PROT2 0x51
|
||||
#define MFR_SLOPE_CNT_SET 0xA8
|
||||
#define MFR_TSNS_FLT_SET 0xBB
|
||||
|
||||
#define MP29502_VIN_OV_GAIN 4
|
||||
#define MP29502_TEMP_LIMIT_OFFSET 40
|
||||
#define MP29502_READ_VOUT_DIV 1024
|
||||
#define MP29502_READ_IOUT_DIV 32
|
||||
#define MP29502_IOUT_LIMIT_UINT 8
|
||||
#define MP29502_OVUV_LIMIT_SCALE 10
|
||||
#define MP28502_VOUT_OV_GAIN 512
|
||||
#define MP28502_VOUT_OV_SCALE 40
|
||||
#define MP29502_VOUT_UV_OFFSET 36
|
||||
#define MP29502_PIN_GAIN 2
|
||||
#define MP29502_IIN_DIV 2
|
||||
|
||||
#define MP29502_PAGE_NUM 1
|
||||
|
||||
#define MP29502_RAIL_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \
|
||||
PMBUS_HAVE_IIN | \
|
||||
PMBUS_HAVE_STATUS_VOUT | \
|
||||
PMBUS_HAVE_STATUS_IOUT | \
|
||||
PMBUS_HAVE_STATUS_TEMP | \
|
||||
PMBUS_HAVE_STATUS_INPUT)
|
||||
|
||||
struct mp29502_data {
|
||||
struct pmbus_driver_info info;
|
||||
int vout_scale;
|
||||
int vout_bottom_div;
|
||||
int vout_top_div;
|
||||
int ovp_div;
|
||||
int iout_scale;
|
||||
};
|
||||
|
||||
#define to_mp29502_data(x) container_of(x, struct mp29502_data, info)
|
||||
|
||||
static u16 mp29502_reg2data_linear11(u16 word)
|
||||
{
|
||||
s16 exponent;
|
||||
s32 mantissa;
|
||||
s64 val;
|
||||
|
||||
exponent = ((s16)word) >> 11;
|
||||
mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
|
||||
val = mantissa;
|
||||
|
||||
if (exponent >= 0)
|
||||
val <<= exponent;
|
||||
else
|
||||
val >>= -exponent;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int
|
||||
mp29502_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (FIELD_GET(GENMASK(12, 10), ret)) {
|
||||
case 0:
|
||||
data->vout_scale = 6400;
|
||||
break;
|
||||
case 1:
|
||||
data->vout_scale = 5120;
|
||||
break;
|
||||
case 2:
|
||||
data->vout_scale = 2560;
|
||||
break;
|
||||
case 3:
|
||||
data->vout_scale = 2048;
|
||||
break;
|
||||
case 4:
|
||||
data->vout_scale = 1024;
|
||||
break;
|
||||
case 5:
|
||||
data->vout_scale = 4;
|
||||
break;
|
||||
case 6:
|
||||
data->vout_scale = 2;
|
||||
break;
|
||||
case 7:
|
||||
data->vout_scale = 1;
|
||||
break;
|
||||
default:
|
||||
data->vout_scale = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp29502_identify_vout_divider(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->vout_bottom_div = FIELD_GET(GENMASK(11, 0), ret);
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->vout_top_div = FIELD_GET(GENMASK(14, 0), ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp29502_identify_ovp_divider(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_SLOPE_CNT_SET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->ovp_div = FIELD_GET(GENMASK(9, 0), ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mp29502_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
|
||||
int page)
|
||||
{
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret & GENMASK(2, 0)) {
|
||||
case 0:
|
||||
case 6:
|
||||
data->iout_scale = 32;
|
||||
break;
|
||||
case 1:
|
||||
data->iout_scale = 1;
|
||||
break;
|
||||
case 2:
|
||||
data->iout_scale = 2;
|
||||
break;
|
||||
case 3:
|
||||
data->iout_scale = 4;
|
||||
break;
|
||||
case 4:
|
||||
data->iout_scale = 8;
|
||||
break;
|
||||
case 5:
|
||||
data->iout_scale = 16;
|
||||
break;
|
||||
default:
|
||||
data->iout_scale = 64;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp29502_read_vout_ov_limit(struct i2c_client *client, struct mp29502_data *data)
|
||||
{
|
||||
int ret;
|
||||
int ov_value;
|
||||
|
||||
/*
|
||||
* This is because the vout ov fault limit value comes from
|
||||
* page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
|
||||
* value comes from page0 reg. So the page should be set to
|
||||
* 0 after the reading of vout ov limit.
|
||||
*/
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ov_value = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(12, 7), ret) *
|
||||
MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE,
|
||||
data->ovp_div);
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ov_value;
|
||||
}
|
||||
|
||||
static int mp29502_write_vout_ov_limit(struct i2c_client *client, u16 word,
|
||||
struct mp29502_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This is because the vout ov fault limit value comes from
|
||||
* page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit
|
||||
* value comes from page0 reg. So the page should be set to
|
||||
* 0 after the writing of vout ov limit.
|
||||
*/
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_word_data(client, MFR_TSNS_FLT_SET,
|
||||
(ret & ~GENMASK(12, 7)) |
|
||||
FIELD_PREP(GENMASK(12, 7),
|
||||
DIV_ROUND_CLOSEST(word * data->ovp_div,
|
||||
MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE)));
|
||||
|
||||
return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
}
|
||||
|
||||
static int mp29502_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VOUT_MODE:
|
||||
ret = PB_VOUT_MODE_DIRECT;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp29502_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_WORD:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
case PMBUS_READ_VIN:
|
||||
/*
|
||||
* The MP29502 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is
|
||||
* 125mV/LSB. And the vin scale is set to 125mV/Lsb(using r/m/b scale)
|
||||
* in MP29502 pmbus_driver_info struct, so the word data bit0-bit10 can
|
||||
* be returned to pmbus core directly.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(10, 0), ret);
|
||||
break;
|
||||
case PMBUS_READ_VOUT:
|
||||
/*
|
||||
* The MP29502 PMBUS_READ_VOUT[11:0] is the vout value, and vout
|
||||
* value is calculated based on vout scale and vout divider.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) *
|
||||
data->vout_scale *
|
||||
(data->vout_bottom_div +
|
||||
4 * data->vout_top_div),
|
||||
MP29502_READ_VOUT_DIV *
|
||||
data->vout_bottom_div);
|
||||
break;
|
||||
case PMBUS_READ_IIN:
|
||||
/*
|
||||
* The MP29502 MFR_READ_IIN_EST register is linear11 format, and the
|
||||
* exponent is not a constant value. But the iin scale is set to
|
||||
* 1A/Lsb(using r/m/b scale). As a result, the iin read from MP29502
|
||||
* should be calculated to A, then return the result to pmbus core.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret),
|
||||
MP29502_IIN_DIV);
|
||||
break;
|
||||
case PMBUS_READ_PIN:
|
||||
/*
|
||||
* The MP29502 MFR_READ_PIN_EST register is linear11 format, and the
|
||||
* exponent is not a constant value. But the pin scale is set to
|
||||
* 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
|
||||
* should be calculated to W, then return the result to pmbus core.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mp29502_reg2data_linear11(ret) * MP29502_PIN_GAIN;
|
||||
break;
|
||||
case PMBUS_READ_POUT:
|
||||
/*
|
||||
* The MP29502 PMBUS_READ_POUT register is linear11 format, and the
|
||||
* exponent is not a constant value. But the pout scale is set to
|
||||
* 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502
|
||||
* should be calculated to W, then return the result to pmbus core.
|
||||
* And the pout is calculated based on vout divider.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret) *
|
||||
(data->vout_bottom_div +
|
||||
4 * data->vout_top_div),
|
||||
data->vout_bottom_div);
|
||||
break;
|
||||
case PMBUS_READ_IOUT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale,
|
||||
MP29502_READ_IOUT_DIV);
|
||||
break;
|
||||
case PMBUS_READ_TEMPERATURE_1:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(10, 0), ret);
|
||||
break;
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP29502 PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but
|
||||
* the vin scale is set to 125mV/Lsb(using r/m/b scale),
|
||||
* so the word data should multiply by 4.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(7, 0), ret) * MP29502_VIN_OV_GAIN;
|
||||
break;
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP29502 PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT
|
||||
* scale is 125mV/Lsb, but the vin scale is set to 125mV/Lsb(using
|
||||
* r/m/b scale), so the word data bit0-bit9 can be returned to pmbus
|
||||
* core directly.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(GENMASK(9, 0), ret);
|
||||
break;
|
||||
case PMBUS_VOUT_OV_FAULT_LIMIT:
|
||||
/*
|
||||
* The MP29502 vout ov fault limit value comes from
|
||||
* page1 MFR_TSNS_FLT_SET[12:7].
|
||||
*/
|
||||
ret = mp29502_read_vout_ov_limit(client, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
case PMBUS_VOUT_UV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((FIELD_GET(GENMASK(8, 0), ret) *
|
||||
MP29502_OVUV_LIMIT_SCALE -
|
||||
MP29502_VOUT_UV_OFFSET) *
|
||||
(data->vout_bottom_div +
|
||||
4 * data->vout_top_div),
|
||||
data->vout_bottom_div);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) *
|
||||
data->iout_scale *
|
||||
MP29502_IOUT_LIMIT_UINT,
|
||||
MP29502_READ_IOUT_DIV);
|
||||
break;
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
/*
|
||||
* The scale of MP29502 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT
|
||||
* is 1°C/LSB and they have 40°C offset.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = (ret & GENMASK(7, 0)) - MP29502_TEMP_LIMIT_OFFSET;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp29502_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct mp29502_data *data = to_mp29502_data(info);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIN_OV_FAULT_LIMIT:
|
||||
/*
|
||||
* The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value,
|
||||
* and bit8-bit15 should not be changed. The scale of
|
||||
* PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but the vin
|
||||
* scale is set to 125mV/Lsb(using r/m/b scale), so
|
||||
* the word data should divide by 4.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(7, 0)) |
|
||||
FIELD_PREP(GENMASK(7, 0),
|
||||
DIV_ROUND_CLOSEST(word,
|
||||
MP29502_VIN_OV_GAIN)));
|
||||
break;
|
||||
case PMBUS_VIN_UV_WARN_LIMIT:
|
||||
case PMBUS_VIN_UV_FAULT_LIMIT:
|
||||
/*
|
||||
* The PMBUS_VIN_UV_WARN_LIMIT[9:0] and PMBUS_VIN_UV_FAULT_LIMIT[9:0]
|
||||
* are the limit value, and bit10-bit15 should not be changed.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(9, 0)) |
|
||||
FIELD_PREP(GENMASK(9, 0),
|
||||
word));
|
||||
break;
|
||||
case PMBUS_VOUT_OV_FAULT_LIMIT:
|
||||
ret = mp29502_write_vout_ov_limit(client, word, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
case PMBUS_VOUT_UV_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(8, 0)) |
|
||||
FIELD_PREP(GENMASK(8, 0),
|
||||
DIV_ROUND_CLOSEST(word *
|
||||
data->vout_bottom_div +
|
||||
MP29502_VOUT_UV_OFFSET *
|
||||
(data->vout_bottom_div +
|
||||
4 * data->vout_top_div),
|
||||
MP29502_OVUV_LIMIT_SCALE *
|
||||
(data->vout_bottom_div +
|
||||
4 * data->vout_top_div))));
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
DIV_ROUND_CLOSEST(word *
|
||||
MP29502_READ_IOUT_DIV,
|
||||
MP29502_IOUT_LIMIT_UINT *
|
||||
data->iout_scale));
|
||||
break;
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
case PMBUS_OT_WARN_LIMIT:
|
||||
/*
|
||||
* The PMBUS_OT_FAULT_LIMIT[7:0] and PMBUS_OT_WARN_LIMIT[7:0]
|
||||
* are the limit value, and bit8-bit15 should not be changed.
|
||||
*/
|
||||
ret = pmbus_read_word_data(client, page, 0xff, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pmbus_write_word_data(client, page, reg,
|
||||
(ret & ~GENMASK(7, 0)) |
|
||||
FIELD_PREP(GENMASK(7, 0),
|
||||
word + MP29502_TEMP_LIMIT_OFFSET));
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp29502_identify(struct i2c_client *client, struct pmbus_driver_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Identify vout scale */
|
||||
ret = mp29502_identify_vout_scale(client, info, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify vout divider. */
|
||||
ret = mp29502_identify_vout_divider(client, info, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify ovp divider. */
|
||||
ret = mp29502_identify_ovp_divider(client, info, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Identify iout scale */
|
||||
return mp29502_identify_iout_scale(client, info, 0);
|
||||
}
|
||||
|
||||
static const struct pmbus_driver_info mp29502_info = {
|
||||
.pages = MP29502_PAGE_NUM,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.format[PSC_CURRENT_IN] = direct,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.format[PSC_POWER] = direct,
|
||||
|
||||
.m[PSC_VOLTAGE_IN] = 8,
|
||||
.R[PSC_VOLTAGE_IN] = 0,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
|
||||
.m[PSC_VOLTAGE_OUT] = 1,
|
||||
.R[PSC_VOLTAGE_OUT] = 3,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.R[PSC_TEMPERATURE] = 0,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
|
||||
.m[PSC_CURRENT_IN] = 1,
|
||||
.R[PSC_CURRENT_IN] = 0,
|
||||
.b[PSC_CURRENT_IN] = 0,
|
||||
|
||||
.m[PSC_CURRENT_OUT] = 1,
|
||||
.R[PSC_CURRENT_OUT] = 0,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
|
||||
.m[PSC_POWER] = 1,
|
||||
.R[PSC_POWER] = 0,
|
||||
.b[PSC_POWER] = 0,
|
||||
|
||||
.func[0] = MP29502_RAIL_FUNC,
|
||||
.read_word_data = mp29502_read_word_data,
|
||||
.read_byte_data = mp29502_read_byte_data,
|
||||
.write_word_data = mp29502_write_word_data,
|
||||
.identify = mp29502_identify,
|
||||
};
|
||||
|
||||
static int mp29502_probe(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
struct mp29502_data *data;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct mp29502_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&data->info, &mp29502_info, sizeof(*info));
|
||||
info = &data->info;
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mp29502_id[] = {
|
||||
{"mp29502", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp29502_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused mp29502_of_match[] = {
|
||||
{.compatible = "mps,mp29502"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mp29502_of_match);
|
||||
|
||||
static struct i2c_driver mp29502_driver = {
|
||||
.driver = {
|
||||
.name = "mp29502",
|
||||
.of_match_table = mp29502_of_match,
|
||||
},
|
||||
.probe = mp29502_probe,
|
||||
.id_table = mp29502_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mp29502_driver);
|
||||
|
||||
MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net");
|
||||
MODULE_DESCRIPTION("PMBus driver for MPS MP29502");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
#include <linux/of_device.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { mp5990, mp5998 };
|
||||
|
||||
#define MP5990_EFUSE_CFG (0xC4)
|
||||
#define MP5990_VOUT_FORMAT BIT(9)
|
||||
|
||||
|
|
@ -110,10 +112,53 @@ static struct pmbus_driver_info mp5990_info = {
|
|||
.read_word_data = mp5990_read_word_data,
|
||||
};
|
||||
|
||||
static struct pmbus_driver_info mp5998_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.format[PSC_CURRENT_IN] = direct,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.format[PSC_POWER] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 64,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = 0,
|
||||
.m[PSC_VOLTAGE_OUT] = 64,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
.R[PSC_VOLTAGE_OUT] = 0,
|
||||
.m[PSC_CURRENT_IN] = 16,
|
||||
.b[PSC_CURRENT_IN] = 0,
|
||||
.R[PSC_CURRENT_IN] = 0,
|
||||
.m[PSC_CURRENT_OUT] = 16,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
.R[PSC_CURRENT_OUT] = 0,
|
||||
.m[PSC_POWER] = 2,
|
||||
.b[PSC_POWER] = 0,
|
||||
.R[PSC_POWER] = 0,
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
.R[PSC_TEMPERATURE] = 0,
|
||||
.func[0] =
|
||||
PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_byte_data = mp5990_read_byte_data,
|
||||
.read_word_data = mp5990_read_word_data,
|
||||
};
|
||||
|
||||
static const struct i2c_device_id mp5990_id[] = {
|
||||
{"mp5990", mp5990},
|
||||
{"mp5998", mp5998},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp5990_id);
|
||||
|
||||
static int mp5990_probe(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
struct mp5990_data *data;
|
||||
enum chips chip;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data),
|
||||
|
|
@ -121,7 +166,15 @@ static int mp5990_probe(struct i2c_client *client)
|
|||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&data->info, &mp5990_info, sizeof(*info));
|
||||
if (client->dev.of_node)
|
||||
chip = (uintptr_t)of_device_get_match_data(&client->dev);
|
||||
else
|
||||
chip = i2c_match_id(mp5990_id, client)->driver_data;
|
||||
|
||||
if (chip == mp5990)
|
||||
memcpy(&data->info, &mp5990_info, sizeof(*info));
|
||||
else
|
||||
memcpy(&data->info, &mp5998_info, sizeof(*info));
|
||||
info = &data->info;
|
||||
|
||||
/* Read Vout Config */
|
||||
|
|
@ -140,6 +193,9 @@ static int mp5990_probe(struct i2c_client *client)
|
|||
data->info.format[PSC_VOLTAGE_OUT] = linear;
|
||||
data->info.format[PSC_CURRENT_OUT] = linear;
|
||||
data->info.format[PSC_POWER] = linear;
|
||||
if (chip == mp5998)
|
||||
data->info.format[PSC_CURRENT_IN] = linear;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Can't get vout exponent.");
|
||||
|
|
@ -153,16 +209,11 @@ static int mp5990_probe(struct i2c_client *client)
|
|||
}
|
||||
|
||||
static const struct of_device_id mp5990_of_match[] = {
|
||||
{ .compatible = "mps,mp5990" },
|
||||
{ .compatible = "mps,mp5990", .data = (void *)mp5990 },
|
||||
{ .compatible = "mps,mp5998", .data = (void *)mp5998 },
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct i2c_device_id mp5990_id[] = {
|
||||
{"mp5990"},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp5990_id);
|
||||
|
||||
static struct i2c_driver mp5990_driver = {
|
||||
.driver = {
|
||||
.name = "mp5990",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ struct pwm_fan_ctx {
|
|||
|
||||
u64 pwm_duty_cycle_from_stopped;
|
||||
u32 pwm_usec_from_stopped;
|
||||
u8 pwm_shutdown;
|
||||
};
|
||||
|
||||
/* This handler assumes self resetting edge triggered interrupt. */
|
||||
|
|
@ -484,9 +485,14 @@ static void pwm_fan_cleanup(void *__ctx)
|
|||
struct pwm_fan_ctx *ctx = __ctx;
|
||||
|
||||
timer_delete_sync(&ctx->rpm_timer);
|
||||
/* Switch off everything */
|
||||
ctx->enable_mode = pwm_disable_reg_disable;
|
||||
pwm_fan_power_off(ctx, true);
|
||||
if (ctx->pwm_shutdown) {
|
||||
ctx->enable_mode = pwm_enable_reg_enable;
|
||||
__set_pwm(ctx, ctx->pwm_shutdown);
|
||||
} else {
|
||||
/* Switch off everything */
|
||||
ctx->enable_mode = pwm_disable_reg_disable;
|
||||
pwm_fan_power_off(ctx, true);
|
||||
}
|
||||
}
|
||||
|
||||
static int pwm_fan_probe(struct platform_device *pdev)
|
||||
|
|
@ -498,6 +504,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
|||
int ret;
|
||||
const struct hwmon_channel_info **channels;
|
||||
u32 initial_pwm, pwm_min_from_stopped = 0;
|
||||
u32 pwm_shutdown_percent = 0;
|
||||
u32 *fan_channel_config;
|
||||
int channel_count = 1; /* We always have a PWM channel. */
|
||||
int i;
|
||||
|
|
@ -648,6 +655,11 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
|||
channels[1] = &ctx->fan_channel;
|
||||
}
|
||||
|
||||
ret = device_property_read_u32(dev, "fan-shutdown-percent",
|
||||
&pwm_shutdown_percent);
|
||||
if (!ret && pwm_shutdown_percent)
|
||||
ctx->pwm_shutdown = (clamp(pwm_shutdown_percent, 0, 100) * 255) / 100;
|
||||
|
||||
ret = device_property_read_u32(dev, "fan-stop-to-start-percent",
|
||||
&pwm_min_from_stopped);
|
||||
if (!ret && pwm_min_from_stopped) {
|
||||
|
|
|
|||
161
drivers/hwmon/sa67mcu-hwmon.c
Normal file
161
drivers/hwmon/sa67mcu-hwmon.c
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* sl67mcu hardware monitoring driver
|
||||
*
|
||||
* Copyright 2025 Kontron Europe GmbH
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2))
|
||||
#define SA67MCU_TEMP(n) (0x04 + ((n) * 2))
|
||||
|
||||
struct sa67mcu_hwmon {
|
||||
struct regmap *regmap;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
static int sa67mcu_hwmon_read(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long *input)
|
||||
{
|
||||
struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
unsigned int offset;
|
||||
u8 reg[2];
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
offset = hwmon->offset + SA67MCU_VOLTAGE(channel);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
offset = hwmon->offset + SA67MCU_TEMP(channel);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* Reading the low byte will capture the value */
|
||||
ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*input = reg[1] << 8 | reg[0];
|
||||
|
||||
/* Temperatures are s16 and in 0.1degC steps. */
|
||||
if (type == hwmon_temp)
|
||||
*input = sign_extend32(*input, 15) * 100;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const sa67mcu_hwmon_in_labels[] = {
|
||||
"VDDIN",
|
||||
"VDD_RTC",
|
||||
};
|
||||
|
||||
static int sa67mcu_hwmon_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_label:
|
||||
*str = sa67mcu_hwmon_in_labels[channel];
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_ops sa67mcu_hwmon_ops = {
|
||||
.visible = 0444,
|
||||
.read = sa67mcu_hwmon_read,
|
||||
.read_string = sa67mcu_hwmon_read_string,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = {
|
||||
.ops = &sa67mcu_hwmon_ops,
|
||||
.info = sa67mcu_hwmon_info,
|
||||
};
|
||||
|
||||
static int sa67mcu_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sa67mcu_hwmon *hwmon;
|
||||
struct device *hwmon_dev;
|
||||
int ret;
|
||||
|
||||
if (!pdev->dev.parent)
|
||||
return -ENODEV;
|
||||
|
||||
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
|
||||
if (!hwmon)
|
||||
return -ENOMEM;
|
||||
|
||||
hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!hwmon->regmap)
|
||||
return -ENODEV;
|
||||
|
||||
ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
|
||||
"sa67mcu_hwmon", hwmon,
|
||||
&sa67mcu_hwmon_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
dev_err(&pdev->dev, "failed to register as hwmon device");
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id sa67mcu_hwmon_of_match[] = {
|
||||
{ .compatible = "kontron,sa67mcu-hwmon", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match);
|
||||
|
||||
static struct platform_driver sa67mcu_hwmon_driver = {
|
||||
.probe = sa67mcu_hwmon_probe,
|
||||
.driver = {
|
||||
.name = "sa67mcu-hwmon",
|
||||
.of_match_table = sa67mcu_hwmon_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(sa67mcu_hwmon_driver);
|
||||
|
||||
MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver");
|
||||
MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
/*
|
||||
* SB-TSI registers only support SMBus byte data access. "_INT" registers are
|
||||
|
|
@ -29,8 +30,22 @@
|
|||
#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
|
||||
#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
|
||||
|
||||
/*
|
||||
* Bit for reporting value with temperature measurement range.
|
||||
* bit == 0: Use default temperature range (0C to 255.875C).
|
||||
* bit == 1: Use extended temperature range (-49C to +206.875C).
|
||||
*/
|
||||
#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
|
||||
/*
|
||||
* ReadOrder bit specifies the reading order of integer and decimal part of
|
||||
* CPU temperature for atomic reads. If bit == 0, reading integer part triggers
|
||||
* latching of the decimal part, so integer part should be read first.
|
||||
* If bit == 1, read order should be reversed.
|
||||
*/
|
||||
#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
|
||||
|
||||
#define SBTSI_TEMP_EXT_RANGE_ADJ 49000
|
||||
|
||||
#define SBTSI_TEMP_MIN 0
|
||||
#define SBTSI_TEMP_MAX 255875
|
||||
|
||||
|
|
@ -38,6 +53,8 @@
|
|||
struct sbtsi_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
bool ext_range_mode;
|
||||
bool read_order;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -74,23 +91,11 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
{
|
||||
struct sbtsi_data *data = dev_get_drvdata(dev);
|
||||
s32 temp_int, temp_dec;
|
||||
int err;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
/*
|
||||
* ReadOrder bit specifies the reading order of integer and
|
||||
* decimal part of CPU temp for atomic reads. If bit == 0,
|
||||
* reading integer part triggers latching of the decimal part,
|
||||
* so integer part should be read first. If bit == 1, read
|
||||
* order should be reversed.
|
||||
*/
|
||||
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) {
|
||||
if (data->read_order) {
|
||||
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
|
||||
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
|
||||
} else {
|
||||
|
|
@ -122,6 +127,8 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
return temp_dec;
|
||||
|
||||
*val = sbtsi_reg_to_mc(temp_int, temp_dec);
|
||||
if (data->ext_range_mode)
|
||||
*val -= SBTSI_TEMP_EXT_RANGE_ADJ;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -146,6 +153,8 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (data->ext_range_mode)
|
||||
val += SBTSI_TEMP_EXT_RANGE_ADJ;
|
||||
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
|
||||
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
|
||||
|
||||
|
|
@ -203,6 +212,7 @@ static int sbtsi_probe(struct i2c_client *client)
|
|||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct sbtsi_data *data;
|
||||
int err;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
|
|
@ -211,8 +221,14 @@ static int sbtsi_probe(struct i2c_client *client)
|
|||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info,
|
||||
NULL);
|
||||
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err);
|
||||
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&sbtsi_chip_info, NULL);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,10 +544,8 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision,
|
|||
|
||||
watchdog_set_drvdata(&data->wddev, data);
|
||||
err = devm_watchdog_register_device(parent, &data->wddev);
|
||||
if (err) {
|
||||
pr_err("Registering watchdog chardev: %d\n", err);
|
||||
if (err)
|
||||
devm_kfree(parent, data);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_watchdog_register);
|
||||
|
||||
|
|
|
|||
|
|
@ -275,13 +275,26 @@ static int sht21_probe(struct i2c_client *client)
|
|||
|
||||
/* Device ID table */
|
||||
static const struct i2c_device_id sht21_id[] = {
|
||||
{ "sht20" },
|
||||
{ "sht21" },
|
||||
{ "sht25" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, sht21_id);
|
||||
|
||||
static const struct of_device_id sht21_of_match[] = {
|
||||
{ .compatible = "sensirion,sht20" },
|
||||
{ .compatible = "sensirion,sht21" },
|
||||
{ .compatible = "sensirion,sht25" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sht21_of_match);
|
||||
|
||||
static struct i2c_driver sht21_driver = {
|
||||
.driver.name = "sht21",
|
||||
.driver = {
|
||||
.name = "sht21",
|
||||
.of_match_table = sht21_of_match,
|
||||
},
|
||||
.probe = sht21_probe,
|
||||
.id_table = sht21_id,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -104,3 +104,4 @@ module_platform_driver(sy7636a_sensor_driver);
|
|||
|
||||
MODULE_DESCRIPTION("SY7636A sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:sy7636a-temperature");
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
#define CONVERSION_TIME_MS 35 /* in milli-seconds */
|
||||
|
||||
struct tmp102 {
|
||||
const char *label;
|
||||
struct regmap *regmap;
|
||||
u16 config_orig;
|
||||
unsigned long ready_time;
|
||||
|
|
@ -70,6 +71,16 @@ static inline u16 tmp102_mC_to_reg(int val)
|
|||
return (val * 128) / 1000;
|
||||
}
|
||||
|
||||
static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||
|
||||
*str = tmp102->label;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp102_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *temp)
|
||||
{
|
||||
|
|
@ -128,12 +139,18 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct tmp102 *tmp102 = data;
|
||||
|
||||
if (type != hwmon_temp)
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
return 0444;
|
||||
case hwmon_temp_label:
|
||||
if (tmp102->label)
|
||||
return 0444;
|
||||
return 0;
|
||||
case hwmon_temp_max_hyst:
|
||||
case hwmon_temp_max:
|
||||
return 0644;
|
||||
|
|
@ -146,12 +163,13 @@ static const struct hwmon_channel_info * const tmp102_info[] = {
|
|||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
|
||||
HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops tmp102_hwmon_ops = {
|
||||
.is_visible = tmp102_is_visible,
|
||||
.read_string = tmp102_read_string,
|
||||
.read = tmp102_read,
|
||||
.write = tmp102_write,
|
||||
};
|
||||
|
|
@ -213,6 +231,8 @@ static int tmp102_probe(struct i2c_client *client)
|
|||
if (!tmp102)
|
||||
return -ENOMEM;
|
||||
|
||||
of_property_read_string(dev->of_node, "label", &tmp102->label);
|
||||
|
||||
i2c_set_clientdata(client, tmp102);
|
||||
|
||||
tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ enum hwmon_sensor_types {
|
|||
hwmon_curr,
|
||||
hwmon_power,
|
||||
hwmon_energy,
|
||||
hwmon_energy64,
|
||||
hwmon_humidity,
|
||||
hwmon_fan,
|
||||
hwmon_pwm,
|
||||
|
|
@ -491,6 +492,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
|
|||
char *hwmon_sanitize_name(const char *name);
|
||||
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
|
||||
|
||||
void hwmon_lock(struct device *dev);
|
||||
void hwmon_unlock(struct device *dev);
|
||||
|
||||
/**
|
||||
* hwmon_is_bad_char - Is the char invalid in a hwmon name
|
||||
* @ch: the char to be considered
|
||||
|
|
|
|||
|
|
@ -1825,6 +1825,16 @@ struct ec_response_pwm_get_duty {
|
|||
uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */
|
||||
} __ec_align2;
|
||||
|
||||
#define EC_CMD_PWM_GET_FAN_DUTY 0x0027
|
||||
|
||||
struct ec_params_pwm_get_fan_duty {
|
||||
uint8_t fan_idx;
|
||||
} __ec_align1;
|
||||
|
||||
struct ec_response_pwm_get_fan_duty {
|
||||
uint32_t percent; /* Percentage of duty cycle, ranging from 0 ~ 100 */
|
||||
} __ec_align4;
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
* Lightbar commands. This looks worse than it is. Since we only use one HOST
|
||||
|
|
@ -3127,14 +3137,31 @@ struct ec_params_thermal_set_threshold_v1 {
|
|||
|
||||
/****************************************************************************/
|
||||
|
||||
/* Toggle automatic fan control */
|
||||
/* Set or get fan control mode */
|
||||
#define EC_CMD_THERMAL_AUTO_FAN_CTRL 0x0052
|
||||
|
||||
enum ec_auto_fan_ctrl_cmd {
|
||||
EC_AUTO_FAN_CONTROL_CMD_SET = 0,
|
||||
EC_AUTO_FAN_CONTROL_CMD_GET,
|
||||
};
|
||||
|
||||
/* Version 1 of input params */
|
||||
struct ec_params_auto_fan_ctrl_v1 {
|
||||
uint8_t fan_idx;
|
||||
} __ec_align1;
|
||||
|
||||
/* Version 2 of input params */
|
||||
struct ec_params_auto_fan_ctrl_v2 {
|
||||
uint8_t fan_idx;
|
||||
uint8_t cmd; /* enum ec_auto_fan_ctrl_cmd */
|
||||
uint8_t set_auto; /* only used with EC_AUTO_FAN_CONTROL_CMD_SET - bool
|
||||
*/
|
||||
} __ec_align4;
|
||||
|
||||
struct ec_response_auto_fan_control {
|
||||
uint8_t is_auto; /* bool */
|
||||
} __ec_align1;
|
||||
|
||||
/* Get/Set TMP006 calibration data */
|
||||
#define EC_CMD_TMP006_GET_CALIBRATION 0x0053
|
||||
#define EC_CMD_TMP006_SET_CALIBRATION 0x0054
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
DECLARE_EVENT_CLASS(hwmon_attr_class,
|
||||
|
||||
TP_PROTO(int index, const char *attr_name, long val),
|
||||
TP_PROTO(int index, const char *attr_name, long long val),
|
||||
|
||||
TP_ARGS(index, attr_name, val),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__field(int, index)
|
||||
__string(attr_name, attr_name)
|
||||
__field(long, val)
|
||||
__field(long long, val)
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
|
|
@ -25,20 +25,20 @@ DECLARE_EVENT_CLASS(hwmon_attr_class,
|
|||
__entry->val = val;
|
||||
),
|
||||
|
||||
TP_printk("index=%d, attr_name=%s, val=%ld",
|
||||
TP_printk("index=%d, attr_name=%s, val=%lld",
|
||||
__entry->index, __get_str(attr_name), __entry->val)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hwmon_attr_class, hwmon_attr_show,
|
||||
|
||||
TP_PROTO(int index, const char *attr_name, long val),
|
||||
TP_PROTO(int index, const char *attr_name, long long val),
|
||||
|
||||
TP_ARGS(index, attr_name, val)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(hwmon_attr_class, hwmon_attr_store,
|
||||
|
||||
TP_PROTO(int index, const char *attr_name, long val),
|
||||
TP_PROTO(int index, const char *attr_name, long long val),
|
||||
|
||||
TP_ARGS(index, attr_name, val)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@
|
|||
#define I8K_FAN_LOW 1
|
||||
#define I8K_FAN_HIGH 2
|
||||
#define I8K_FAN_TURBO 3
|
||||
/* Many machines treat this mode as some sort of automatic mode */
|
||||
#define I8K_FAN_AUTO 3
|
||||
#define I8K_FAN_MAX I8K_FAN_TURBO
|
||||
|
||||
#define I8K_VOL_UP 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user