summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-01-12 13:27:40 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-01-12 13:27:40 -0800
commit5dfec3cf3efbd897d774e3b5c08c2f0deaf9b5ad (patch)
treedf527b523e97fba2c54ba409468575a027225638
parent7912a6391f3ee7eb9f9a69227a209d502679bc0c (diff)
parent41c71105a845ec1458680f01644d032a5fbbe0d9 (diff)
Merge tag 'hwmon-for-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck: "New drivers: - pmbus: Support for MPS Multi-phase mp2856/mp2857 controller - pmbus: Support for MPS Multi-phase mp5990 - Driver for Gigabyte AORUS Waterforce AIO coolers Added support to existing drivers: - lm75: Support for AMS AS6200 temperature sensor - k10temp: Support for AMD Family 19h Model 8h - max31827: Support for max31828 and max31829 - sht3x: Support for sts3x - Add support for WMI SMM interface, and various related improvements. Add support for Optiplex 7000 - emc1403: Support for EMC1442 - npcm750-pwm-fan: Support for NPCM8xx - nct6775: Add support for 2 additional fan controls Minor improvements and bug fixes: - gigabyte_waterforce: Mark status report as received under a spinlock - aquacomputer_d5next: Remove unneeded CONFIG_DEBUG_FS #ifdef - gpio-fan: Convert txt bindings to yaml - smsc47m1: Various cleanups / improvements - corsair-cpro: use NULL instead of 0 - hp-wmi-sensors: Fix failure to load on EliteDesk 800 G6 - tmp513: Various cleanups - peci/dimmtemp: Bump timeout - pc87360: Bounds check data->innr usage - nct6775: Fix fan speed set failure in automatic mode - ABI: sysfs-class-hwmon: document various missing attributes - lm25066, max6650, nct6775: Use i2c_get_match_data() - aspeed-pwm-tacho: Fix -Wstringop-overflow warning" * tag 'hwmon-for-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (59 commits) hwmon: (gigabyte_waterforce) Mark status report as received under a spinlock hwmon: (lm75) Fix tmp112 default config hwmon: (lm75) Add AMS AS6200 temperature sensor dt-bindings: hwmon: (lm75) Add AMS AS6200 temperature sensor hwmon: (lm75) remove now-unused include hwmon: (pmbus) Add support for MPS Multi-phase mp2856/mp2857 controller dt-bindings: Add MP2856/MP2857 voltage regulator device hwmon: (aquacomputer_d5next) Remove unneeded CONFIG_DEBUG_FS #ifdef dt-bindings: hwmon: gpio-fan: Convert txt bindings to yaml hwmon: (k10temp) Add support for AMD Family 19h Model 8h hwmon: Add driver for Gigabyte AORUS Waterforce AIO coolers hwmon: (smsc47m1) Rename global platform device variable hwmon: (smsc47m1) Simplify device registration hwmon: (smsc47m1) Convert to platform remove callback returning void hwmon: (smsc47m1) Mark driver struct with __refdata to prevent section mismatch MAINTAINERS: Add maintainer for Baikal-T1 PVT hwmon driver hwmon: (sht3x) add sts3x support hwmon: (pmbus) Add ltc4286 driver dt-bindings: hwmon: Add lltc ltc4286 driver bindings hwmon: (max31827) Add custom attribute for resolution ...
-rw-r--r--Documentation/ABI/testing/sysfs-class-hwmon110
-rw-r--r--Documentation/devicetree/bindings/hwmon/gpio-fan.txt41
-rw-r--r--Documentation/devicetree/bindings/hwmon/gpio-fan.yaml60
-rw-r--r--Documentation/devicetree/bindings/hwmon/iio-hwmon.yaml2
-rw-r--r--Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml50
-rw-r--r--Documentation/devicetree/bindings/hwmon/lm75.yaml33
-rw-r--r--Documentation/devicetree/bindings/trivial-devices.yaml6
-rw-r--r--Documentation/hwmon/dell-smm-hwmon.rst38
-rw-r--r--Documentation/hwmon/gigabyte_waterforce.rst47
-rw-r--r--Documentation/hwmon/index.rst4
-rw-r--r--Documentation/hwmon/lm75.rst10
-rw-r--r--Documentation/hwmon/ltc4286.rst95
-rw-r--r--Documentation/hwmon/max31827.rst75
-rw-r--r--Documentation/hwmon/mp2856.rst98
-rw-r--r--Documentation/hwmon/mp5990.rst84
-rw-r--r--Documentation/hwmon/sht3x.rst29
-rw-r--r--MAINTAINERS25
-rw-r--r--drivers/hwmon/Kconfig11
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/aquacomputer_d5next.c10
-rw-r--r--drivers/hwmon/aspeed-pwm-tacho.c26
-rw-r--r--drivers/hwmon/corsair-cpro.c2
-rw-r--r--drivers/hwmon/dell-smm-hwmon.c604
-rw-r--r--drivers/hwmon/emc1403.c6
-rw-r--r--drivers/hwmon/gigabyte_waterforce.c430
-rw-r--r--drivers/hwmon/hp-wmi-sensors.c127
-rw-r--r--drivers/hwmon/k10temp.c1
-rw-r--r--drivers/hwmon/lm75.c114
-rw-r--r--drivers/hwmon/ltc2991.c20
-rw-r--r--drivers/hwmon/max31827.c273
-rw-r--r--drivers/hwmon/max6650.c8
-rw-r--r--drivers/hwmon/nct6775-core.c41
-rw-r--r--drivers/hwmon/nct6775-i2c.c14
-rw-r--r--drivers/hwmon/nct6775-platform.c26
-rw-r--r--drivers/hwmon/nct6775.h2
-rw-r--r--drivers/hwmon/npcm750-pwm-fan.c30
-rw-r--r--drivers/hwmon/pc87360.c6
-rw-r--r--drivers/hwmon/peci/dimmtemp.c2
-rw-r--r--drivers/hwmon/pmbus/Kconfig28
-rw-r--r--drivers/hwmon/pmbus/Makefile3
-rw-r--r--drivers/hwmon/pmbus/lm25066.c14
-rw-r--r--drivers/hwmon/pmbus/ltc4286.c175
-rw-r--r--drivers/hwmon/pmbus/mp2856.c466
-rw-r--r--drivers/hwmon/pmbus/mp5990.c179
-rw-r--r--drivers/hwmon/sht4x.c3
-rw-r--r--drivers/hwmon/smsc47m1.c67
-rw-r--r--drivers/hwmon/tmp513.c64
-rw-r--r--drivers/platform/x86/wmi.c1
48 files changed, 3064 insertions, 497 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon
index 638f4c6d4ec7..3dac923c9b0e 100644
--- a/Documentation/ABI/testing/sysfs-class-hwmon
+++ b/Documentation/ABI/testing/sysfs-class-hwmon
@@ -381,6 +381,15 @@ Description:
RW
+What: /sys/class/hwmon/hwmonX/tempY_max_alarm
+Description:
+ Maximum temperature alarm flag.
+
+ - 0: OK
+ - 1: temperature has reached tempY_max
+
+ RO
+
What: /sys/class/hwmon/hwmonX/tempY_min
Description:
Temperature min value.
@@ -389,6 +398,15 @@ Description:
RW
+What: /sys/class/hwmon/hwmonX/tempY_min_alarm
+Description:
+ Minimum temperature alarm flag.
+
+ - 0: OK
+ - 1: temperature has reached tempY_min
+
+ RO
+
What: /sys/class/hwmon/hwmonX/tempY_max_hyst
Description:
Temperature hysteresis value for max limit.
@@ -434,12 +452,7 @@ Description:
- 0: OK
- 1: temperature has reached tempY_crit
- RW
-
- Contrary to regular alarm flags which clear themselves
- automatically when read, this one sticks until cleared by
- the user. This is done by writing 0 to the file. Writing
- other values is unsupported.
+ RO
What: /sys/class/hwmon/hwmonX/tempY_crit_hyst
Description:
@@ -462,6 +475,15 @@ Description:
RW
+What: /sys/class/hwmon/hwmonX/tempY_emergency_alarm
+Description:
+ Emergency high temperature alarm flag.
+
+ - 0: OK
+ - 1: temperature has reached tempY_emergency
+
+ RO
+
What: /sys/class/hwmon/hwmonX/tempY_emergency_hyst
Description:
Temperature hysteresis value for emergency limit.
@@ -887,15 +909,15 @@ Description:
RW
-What: /sys/class/hwmon/hwmonX/humidityY_input
+What: /sys/class/hwmon/hwmonX/humidityY_alarm
Description:
- Humidity
+ Humidity limit detection
- Unit: milli-percent (per cent mille, pcm)
+ - 0: OK
+ - 1: Humidity limit has been reached
RO
-
What: /sys/class/hwmon/hwmonX/humidityY_enable
Description:
Enable or disable the sensors
@@ -908,6 +930,74 @@ Description:
RW
+What: /sys/class/hwmon/hwmonX/humidityY_fault
+Description:
+ Reports a humidity sensor failure.
+
+ - 1: Failed
+ - 0: Ok
+
+ RO
+
+What: /sys/class/hwmon/hwmonX/humidityY_input
+Description:
+ Humidity
+
+ Unit: milli-percent (per cent mille, pcm)
+
+ RO
+
+What: /sys/class/hwmon/hwmonX/humidityY_label
+Description:
+ Suggested humidity channel label.
+
+ Text string
+
+ Should only be created if the driver has hints about what
+ this humidity channel is being used for, and user-space
+ doesn't. In all other cases, the label is provided by
+ user-space.
+
+ RO
+
+What: /sys/class/hwmon/hwmonX/humidityY_max
+Description:
+ Humidity max value.
+
+ Unit: milli-percent (per cent mille, pcm)
+
+ RW
+
+What: /sys/class/hwmon/hwmonX/humidityY_max_hyst
+Description:
+ Humidity hysteresis value for max limit.
+
+ Unit: milli-percent (per cent mille, pcm)
+
+ Must be reported as an absolute humidity, NOT a delta
+ from the max value.
+
+ RW
+
+What: /sys/class/hwmon/hwmonX/humidityY_min
+Description:
+ Humidity min value.
+
+ Unit: milli-percent (per cent mille, pcm)
+
+ RW
+
+What: /sys/class/hwmon/hwmonX/humidityY_min_hyst
+Description:
+ Humidity hysteresis value for min limit.
+
+ Unit: milli-percent (per cent mille, pcm)
+
+ Must be reported as an absolute humidity, NOT a delta
+ from the min value.
+
+ RW
+
What: /sys/class/hwmon/hwmonX/humidityY_rated_min
Description:
Minimum rated humidity.
diff --git a/Documentation/devicetree/bindings/hwmon/gpio-fan.txt b/Documentation/devicetree/bindings/hwmon/gpio-fan.txt
deleted file mode 100644
index f4cfa350f6a1..000000000000
--- a/Documentation/devicetree/bindings/hwmon/gpio-fan.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-Bindings for fan connected to GPIO lines
-
-Required properties:
-- compatible : "gpio-fan"
-
-Optional properties:
-- gpios: Specifies the pins that map to bits in the control value,
- ordered MSB-->LSB.
-- gpio-fan,speed-map: A mapping of possible fan RPM speeds and the
- control value that should be set to achieve them. This array
- must have the RPM values in ascending order.
-- alarm-gpios: This pin going active indicates something is wrong with
- the fan, and a udev event will be fired.
-- #cooling-cells: If used as a cooling device, must be <2>
- Also see:
- Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml
- min and max states are derived from the speed-map of the fan.
-
-Note: At least one the "gpios" or "alarm-gpios" properties must be set.
-
-Examples:
-
- gpio_fan {
- compatible = "gpio-fan";
- gpios = <&gpio1 14 1
- &gpio1 13 1>;
- gpio-fan,speed-map = <0 0
- 3000 1
- 6000 2>;
- alarm-gpios = <&gpio1 15 1>;
- };
- gpio_fan_cool: gpio_fan {
- compatible = "gpio-fan";
- gpios = <&gpio2 14 1
- &gpio2 13 1>;
- gpio-fan,speed-map = <0 0>,
- <3000 1>,
- <6000 2>;
- alarm-gpios = <&gpio2 15 1>;
- #cooling-cells = <2>; /* min followed by max */
- };
diff --git a/Documentation/devicetree/bindings/hwmon/gpio-fan.yaml b/Documentation/devicetree/bindings/hwmon/gpio-fan.yaml
new file mode 100644
index 000000000000..7f30cfc87350
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/gpio-fan.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/gpio-fan.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Fan connected to GPIO lines
+
+maintainers:
+ - Rob Herring <robh@kernel.org>
+
+properties:
+ compatible:
+ const: gpio-fan
+
+ gpios:
+ description: |
+ Specifies the pins that map to bits in the control value,
+ ordered MSB-->LSB.
+ minItems: 1
+ maxItems: 7
+
+ alarm-gpios:
+ maxItems: 1
+
+ gpio-fan,speed-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ minItems: 2
+ maxItems: 127
+ items:
+ items:
+ - description: fan speed in RPMs
+ - description: control value
+ description: |
+ A mapping of possible fan RPM speeds and the
+ control value that should be set to achieve them. This array
+ must have the RPM values in ascending order.
+
+ '#cooling-cells':
+ const: 2
+
+required:
+ - compatible
+ - gpios
+ - gpio-fan,speed-map
+
+additionalProperties: false
+
+examples:
+ - |
+ gpio-fan {
+ compatible = "gpio-fan";
+ gpios = <&gpio2 14 1
+ &gpio2 13 1>;
+ gpio-fan,speed-map = < 0 0>,
+ <3000 1>,
+ <6000 2>;
+ alarm-gpios = <&gpio2 15 1>;
+ #cooling-cells = <2>; /* min followed by max */
+ };
diff --git a/Documentation/devicetree/bindings/hwmon/iio-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/iio-hwmon.yaml
index e5b24782f448..be5c7d4579bb 100644
--- a/Documentation/devicetree/bindings/hwmon/iio-hwmon.yaml
+++ b/Documentation/devicetree/bindings/hwmon/iio-hwmon.yaml
@@ -19,7 +19,7 @@ properties:
io-channels:
minItems: 1
- maxItems: 8 # Should be enough
+ maxItems: 51 # Should be enough
description: >
List of phandles to ADC channels to read the monitoring values
diff --git a/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml b/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml
new file mode 100644
index 000000000000..98ca163d3486
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/lltc,ltc4286.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LTC4286 power monitors
+
+maintainers:
+ - Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
+
+properties:
+ compatible:
+ enum:
+ - lltc,ltc4286
+ - lltc,ltc4287
+
+ reg:
+ maxItems: 1
+
+ adi,vrange-low-enable:
+ description:
+ This property is a bool parameter to represent the
+ voltage range is 25.6 volts or 102.4 volts for this chip.
+ The default is 102.4 volts.
+ type: boolean
+
+ shunt-resistor-micro-ohms:
+ description:
+ Resistor value micro-ohms.
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ power-monitor@40 {
+ compatible = "lltc,ltc4286";
+ reg = <0x40>;
+ adi,vrange-low-enable;
+ shunt-resistor-micro-ohms = <300>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/hwmon/lm75.yaml b/Documentation/devicetree/bindings/hwmon/lm75.yaml
index 0b69897f0c63..ed269e428a3d 100644
--- a/Documentation/devicetree/bindings/hwmon/lm75.yaml
+++ b/Documentation/devicetree/bindings/hwmon/lm75.yaml
@@ -14,6 +14,7 @@ properties:
compatible:
enum:
- adi,adt75
+ - ams,as6200
- atmel,at30ts74
- dallas,ds1775
- dallas,ds75
@@ -48,10 +49,28 @@ properties:
vs-supply:
description: phandle to the regulator that provides the +VS supply
+ interrupts:
+ maxItems: 1
+
required:
- compatible
- reg
+allOf:
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - ams,as6200
+ - ti,tmp100
+ - ti,tmp101
+ - ti,tmp112
+ then:
+ properties:
+ interrupts: false
+
additionalProperties: false
examples:
@@ -66,3 +85,17 @@ examples:
vs-supply = <&vs>;
};
};
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ temperature-sensor@48 {
+ compatible = "ams,as6200";
+ reg = <0x48>;
+ vs-supply = <&vs>;
+ interrupt-parent = <&gpio1>;
+ interrupts = <17 IRQ_TYPE_EDGE_BOTH>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
index c3190f2a168a..a5fb2fa22026 100644
--- a/Documentation/devicetree/bindings/trivial-devices.yaml
+++ b/Documentation/devicetree/bindings/trivial-devices.yaml
@@ -117,6 +117,10 @@ properties:
- fsl,mpl3115
# MPR121: Proximity Capacitive Touch Sensor Controller
- fsl,mpr121
+ # Monolithic Power Systems Inc. multi-phase controller mp2856
+ - mps,mp2856
+ # Monolithic Power Systems Inc. multi-phase controller mp2857
+ - mps,mp2857
# Monolithic Power Systems Inc. multi-phase controller mp2888
- mps,mp2888
# Monolithic Power Systems Inc. multi-phase controller mp2971
@@ -125,6 +129,8 @@ properties:
- mps,mp2973
# Monolithic Power Systems Inc. multi-phase controller mp2975
- mps,mp2975
+ # Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990
+ - mps,mp5990
# Honeywell Humidicon HIH-6130 humidity/temperature sensor
- honeywell,hi6130
# IBM Common Form Factor Power Supply Versions (all versions)
diff --git a/Documentation/hwmon/dell-smm-hwmon.rst b/Documentation/hwmon/dell-smm-hwmon.rst
index d8f1d6859b96..977263cb57a8 100644
--- a/Documentation/hwmon/dell-smm-hwmon.rst
+++ b/Documentation/hwmon/dell-smm-hwmon.rst
@@ -186,8 +186,7 @@ SMM Interface
The driver uses the SMM interface to send commands to the system BIOS.
This interface is normally used by Dell's 32-bit diagnostic program or
on newer notebook models by the buildin BIOS diagnostics.
-The SMM is triggered by writing to the special ioports ``0xb2`` and ``0x84``,
-and may cause short hangs when the BIOS code is taking too long to
+The SMM may cause short hangs when the BIOS code is taking too long to
execute.
The SMM handler inside the system BIOS looks at the contents of the
@@ -210,7 +209,40 @@ The SMM handler can signal a failure by either:
- setting the lower sixteen bits of ``eax`` to ``0xffff``
- not modifying ``eax`` at all
-- setting the carry flag
+- setting the carry flag (legacy SMM interface only)
+
+Legacy SMM Interface
+--------------------
+
+When using the legacy SMM interface, a SMM is triggered by writing the least significant byte
+of the command code to the special ioports ``0xb2`` and ``0x84``. This interface is not
+described inside the ACPI tables and can thus only be detected by issuing a test SMM call.
+
+WMI SMM Interface
+-----------------
+
+On modern Dell machines, the SMM calls are done over ACPI WMI:
+
+::
+
+ #pragma namespace("\\\\.\\root\\dcim\\sysman\\diagnostics")
+ [WMI, Provider("Provider_DiagnosticsServices"), Dynamic, Locale("MS\\0x409"),
+ Description("RunDellDiag"), guid("{F1DDEE52-063C-4784-A11E-8A06684B9B01}")]
+ class LegacyDiags {
+ [key, read] string InstanceName;
+ [read] boolean Active;
+
+ [WmiMethodId(1), Implemented, read, write, Description("Legacy Method ")]
+ void Execute([in, out] uint32 EaxLen, [in, out, WmiSizeIs("EaxLen") : ToInstance] uint8 EaxVal[],
+ [in, out] uint32 EbxLen, [in, out, WmiSizeIs("EbxLen") : ToInstance] uint8 EbxVal[],
+ [in, out] uint32 EcxLen, [in, out, WmiSizeIs("EcxLen") : ToInstance] uint8 EcxVal[],
+ [in, out] uint32 EdxLen, [in, out, WmiSizeIs("EdxLen") : ToInstance] uint8 EdxVal[]);
+ };
+
+Some machines support only the WMI SMM interface, while some machines support both interfaces.
+The driver automatically detects which interfaces are present and will use the WMI SMM interface
+if the legacy SMM interface is not present. The WMI SMM interface is usually slower than the
+legacy SMM interface since ACPI methods need to be called in order to trigger a SMM.
SMM command codes
-----------------
diff --git a/Documentation/hwmon/gigabyte_waterforce.rst b/Documentation/hwmon/gigabyte_waterforce.rst
new file mode 100644
index 000000000000..d47f3e8516ee
--- /dev/null
+++ b/Documentation/hwmon/gigabyte_waterforce.rst
@@ -0,0 +1,47 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver gigabyte_waterforce
+=================================
+
+Supported devices:
+
+* Gigabyte AORUS WATERFORCE X240
+* Gigabyte AORUS WATERFORCE X280
+* Gigabyte AORUS WATERFORCE X360
+
+Author: Aleksa Savic
+
+Description
+-----------
+
+This driver enables hardware monitoring support for the listed Gigabyte Waterforce
+all-in-one CPU liquid coolers. Available sensors are pump and fan speed in RPM, as
+well as coolant temperature. Also available through debugfs is the firmware version.
+
+Attaching a fan is optional and allows it to be controlled from the device. If
+it's not connected, the fan-related sensors will report zeroes.
+
+The addressable RGB LEDs and LCD screen are not supported in this driver and should
+be controlled through userspace tools.
+
+Usage notes
+-----------
+
+As these are USB HIDs, the driver can be loaded automatically by the kernel and
+supports hot swapping.
+
+Sysfs entries
+-------------
+
+=========== =============================================
+fan1_input Fan speed (in rpm)
+fan2_input Pump speed (in rpm)
+temp1_input Coolant temperature (in millidegrees Celsius)
+=========== =============================================
+
+Debugfs entries
+---------------
+
+================ =======================
+firmware_version Device firmware version
+================ =======================
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 72f4e6065bae..c7ed1f73ac06 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -73,6 +73,7 @@ Hardware Monitoring Kernel Drivers
ftsteutates
g760a
g762
+ gigabyte_waterforce
gsc-hwmon
gl518sm
gxp-fan-ctrl
@@ -128,6 +129,7 @@ Hardware Monitoring Kernel Drivers
ltc4245
ltc4260
ltc4261
+ ltc4286
max127
max15301
max16064
@@ -156,9 +158,11 @@ Hardware Monitoring Kernel Drivers
mcp3021
menf21bmc
mlxreg-fan
+ mp2856
mp2888
mp2975
mp5023
+ mp5990
nct6683
nct6775
nct7802
diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst
index 8d0ab4ad5fb5..6adab608dd05 100644
--- a/Documentation/hwmon/lm75.rst
+++ b/Documentation/hwmon/lm75.rst
@@ -133,6 +133,16 @@ Supported chips:
https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf
+ * AMS OSRAM AS6200
+
+ Prefix: 'as6200'
+
+ Addresses scanned: none
+
+ Datasheet: Publicly available at the AMS website
+
+ https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf
+
Author: Frodo Looijaard <frodol@dds.nl>
Description
diff --git a/Documentation/hwmon/ltc4286.rst b/Documentation/hwmon/ltc4286.rst
new file mode 100644
index 000000000000..2cd149676d86
--- /dev/null
+++ b/Documentation/hwmon/ltc4286.rst
@@ -0,0 +1,95 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver ltc4286
+=====================
+
+Supported chips:
+
+ * Analog Devices LTC4286
+
+ Prefix: 'ltc4286'
+
+ Addresses scanned: -
+
+ Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4286.pdf
+
+ * Analog Devices LTC4287
+
+ Prefix: 'ltc4287'
+
+ Addresses scanned: -
+
+ Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4287.pdf
+
+Author: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
+
+
+Description
+-----------
+
+This driver supports hardware monitoring for Analog Devices LTC4286
+and LTC4287 Hot-Swap Controller and Digital Power Monitors.
+
+LTC4286 and LTC4287 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.
+
+
+Usage Notes
+-----------
+
+This driver does not auto-detect devices. You will have to instantiate the
+devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
+details.
+
+The shunt value in micro-ohms can be set via device tree at compile-time. Please
+refer to the Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml for bindings
+if the device tree is used.
+
+
+Platform data support
+---------------------
+
+The driver supports standard PMBus driver platform data. Please see
+Documentation/hwmon/pmbus.rst for details.
+
+
+Sysfs entries
+-------------
+
+The following attributes are supported. Limits are read-write, history reset
+attributes are write-only, all other attributes are read-only.
+
+======================= =======================================================
+in1_label "vin"
+in1_input Measured voltage.
+in1_alarm Input voltage alarm.
+in1_min Minimum input voltage.
+in1_max Maximum input voltage.
+
+in2_label "vout1"
+in2_input Measured voltage.
+in2_alarm Output voltage alarm.
+in2_min Minimum output voltage.
+in2_max Maximum output voltage.
+
+curr1_label "iout1"
+curr1_input Measured current.
+curr1_alarm Output current alarm.
+curr1_max Maximum current.
+
+power1_label "pin"
+power1_input Input power.
+power1_alarm Input power alarm.
+power1_max Maximum poewr.
+
+temp1_input Chip temperature.
+temp1_min Minimum chip temperature.
+temp1_max Maximum chip temperature.
+temp1_crit Critical chip temperature.
+temp1_alarm Chip temperature alarm.
+======================= =======================================================
diff --git a/Documentation/hwmon/max31827.rst b/Documentation/hwmon/max31827.rst
index 9a1055a007cf..44ab9dc064cb 100644
--- a/Documentation/hwmon/max31827.rst
+++ b/Documentation/hwmon/max31827.rst
@@ -52,13 +52,21 @@ MAX31827 has low and over temperature alarms with an effective value and a
hysteresis value: -40 and -30 degrees for under temperature alarm and +100 and
+90 degrees for over temperature alarm.
-The alarm can be configured in comparator and interrupt mode. Currently only
-comparator mode is implemented. In Comparator mode, the OT/UT status bits have a
-value of 1 when the temperature rises above the TH value or falls below TL,
-which is also subject to the Fault Queue selection. OT status returns to 0 when
-the temperature drops below the TH_HYST value or when shutdown mode is entered.
-Similarly, UT status returns to 0 when the temperature rises above TL_HYST value
-or when shutdown mode is entered.
+The alarm can be configured in comparator and interrupt mode from the
+devicetree. In Comparator mode, the OT/UT status bits have a value of 1 when the
+temperature rises above the TH value or falls below TL, which is also subject to
+the Fault Queue selection. OT status returns to 0 when the temperature drops
+below the TH_HYST value or when shutdown mode is entered. Similarly, UT status
+returns to 0 when the temperature rises above TL_HYST value or when shutdown
+mode is entered.
+
+In interrupt mode exceeding TH also sets OT status to 1, which remains set until
+a read operation is performed on the configuration/status register (max or min
+attribute); at this point, it returns to 0. Once OT status is set to 1 from
+exceeding TH and reset, it is set to 1 again only when the temperature drops
+below TH_HYST. The output remains asserted until it is reset by a read. It is
+set again if the temperature rises above TH, and so on. The same logic applies
+to the operation of the UT status bit.
Putting the MAX31827 into shutdown mode also resets the OT/UT status bits. Note
that if the mode is changed while OT/UT status bits are set, an OT/UT status
@@ -68,13 +76,42 @@ clear the status bits before changing the operating mode.
The conversions can be manual with the one-shot functionality and automatic with
a set frequency. When powered on, the chip measures temperatures with 1 conv/s.
+The conversion rate can be modified with update_interval attribute of the chip.
+Conversion/second = 1/update_interval. Thus, the available options according to
+the data sheet are:
+
+- 64000 (ms) = 1 conv/64 sec
+- 32000 (ms) = 1 conv/32 sec
+- 16000 (ms) = 1 conv/16 sec
+- 4000 (ms) = 1 conv/4 sec
+- 1000 (ms) = 1 conv/sec (default)
+- 250 (ms) = 4 conv/sec
+- 125 (ms) = 8 conv/sec
+
Enabling the device when it is already enabled has the side effect of setting
the conversion frequency to 1 conv/s. The conversion time varies depending on
-the resolution. The conversion time doubles with every bit of increased
-resolution. For 10 bit resolution 35ms are needed, while for 12 bit resolution
-(default) 140ms. When chip is in shutdown mode and a read operation is
-requested, one-shot is triggered, the device waits for 140 (conversion time) ms,
-and only after that is the temperature value register read.
+the resolution.
+
+The conversion time doubles with every bit of increased resolution. The
+available resolutions are:
+
+- 8 bit -> 8.75 ms conversion time
+- 9 bit -> 17.5 ms conversion time
+- 10 bit -> 35 ms conversion time
+- 12 bit (default) -> 140 ms conversion time
+
+There is a temp1_resolution attribute which indicates the unit change in the
+input temperature in milli-degrees C.
+
+- 1000 mC -> 8 bit
+- 500 mC -> 9 bit
+- 250 mC -> 10 bit
+- 62 mC -> 12 bit (default) - actually this is 62.5, but the fil returns 62
+
+When chip is in shutdown mode and a read operation is requested, one-shot is
+triggered, the device waits for <conversion time> ms, and only after that is
+the temperature value register read. Note that the conversion times are rounded
+up to the nearest possible integer.
The LSB of the temperature values is 0.0625 degrees Celsius, but the values of
the temperatures are displayed in milli-degrees. This means, that some data is
@@ -83,8 +120,18 @@ in the writing of alarm values too. For positive numbers the user-input value
will always be rounded down to the nearest possible value, for negative numbers
the user-input will always be rounded up to the nearest possible value.
+Bus timeout resets the I2C-compatible interface when SCL is low for more than
+30ms (nominal).
+
+Alarm polarity determines if the active state of the alarm is low or high. The
+behavior for both settings is dependent on the Fault Queue setting. The ALARM
+pin is an open-drain output and requires a pullup resistor to operate.
+
+The Fault Queue bits select how many consecutive temperature faults must occur
+before overtemperature or undertemperature faults are indicated in the
+corresponding status bits.
+
Notes
-----
-Currently fault queue, alarm polarity and resolution cannot be modified.
-PEC is not implemented either.
+PEC is not implemented.
diff --git a/Documentation/hwmon/mp2856.rst b/Documentation/hwmon/mp2856.rst
new file mode 100644
index 000000000000..af625c22b6ea
--- /dev/null
+++ b/Documentation/hwmon/mp2856.rst
@@ -0,0 +1,98 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver mp2856
+====================
+
+Supported chips:
+
+ * MPS MP2856
+
+ Prefix: 'mp2856'
+
+ * MPS MP2857
+
+ Prefix: 'mp2857'
+
+Author:
+
+ Peter Yin <peter.yin@quantatw.com>
+
+Description
+-----------
+
+This driver implements support for Monolithic Power Systems, Inc. (MPS)
+vendor dual-loop, digital, multi-phase controller MP2856/MP2857
+
+This device:
+
+- Supports up to two power rail.
+- Supports two pages 0 and 1 for and also pages 2 for configuration.
+- Can configured VOUT readout in direct or VID format and allows
+ setting of different formats on rails 1 and 2. For VID the following
+ protocols are available: AMD SVI3 mode with 5-mV/LSB.
+
+Device supports:
+
+- SVID interface.
+- AVSBus interface.
+
+Device compliant with:
+
+- PMBus rev 1.3 interface.
+
+Device supports direct format for reading output current, output voltage,
+input and output power and temperature.
+Device supports linear format for reading input voltage and input power.
+Device supports VID and direct formats for reading output voltage.
+The below VID modes are supported: AMD SVI3.
+
+The driver provides the following sysfs attributes for current measurements:
+
+- indexes 1 for "iin";
+- indexes 2, 3 for "iout";
+
+**curr[1-3]_alarm**
+
+**curr[1-3]_input**
+
+**curr[1-3]_label**
+
+The driver provides the following sysfs attributes for voltage measurements.
+
+- indexes 1 for "vin";
+- indexes 2, 3 for "vout";
+
+**in[1-3]_crit**
+
+**in[1-3]_crit_alarm**
+
+**in[1-3]_input**
+
+**in[1-3]_label**
+
+**in[1-3]_lcrit**
+
+**in[1-3]_lcrit_alarm**
+
+The driver provides the following sysfs attributes for power measurements.
+
+- indexes 1 for "pin";
+- indexes 2, 3 for "pout";
+
+**power[1-3]_alarm**
+
+**power[1-3]_input**
+
+**power[1-3]_label**
+
+The driver provides the following sysfs attributes for temperature measurements.
+
+**temp[1-2]_crit**
+
+**temp[1-2]_crit_alarm**
+
+**temp[1-2]_input**
+
+**temp[1-2]_max**
+
+**temp[1-2]_max_alarm**
diff --git a/Documentation/hwmon/mp5990.rst b/Documentation/hwmon/mp5990.rst
new file mode 100644
index 000000000000..6f2f0c099d44
--- /dev/null
+++ b/Documentation/hwmon/mp5990.rst
@@ -0,0 +1,84 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver mp5990
+====================
+
+Supported chips:
+
+ * MPS MP5990
+
+ Prefix: 'mp5990'
+
+ * Datasheet
+
+ Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5990.html
+
+Author:
+
+ Peter Yin <peteryin.openbmc@gmail.com>
+
+Description
+-----------
+
+This driver implements support for Monolithic Power Systems, Inc. (MPS)
+MP5990 Hot-Swap Controller.
+
+Device compliant with:
+
+- PMBus rev 1.3 interface.
+
+Device supports direct and linear format for reading input voltage,
+output voltage, output current, input power and temperature.
+
+The driver exports the following attributes via the 'sysfs' files
+for input voltage:
+
+**in1_input**
+
+**in1_label**
+
+**in1_max**
+
+**in1_max_alarm**
+
+**in1_min**
+
+**in1_min_alarm**
+
+The driver provides the following attributes for output voltage:
+
+**in2_input**
+
+**in2_label**
+
+**in2_alarm**
+
+The driver provides the following attributes for output current:
+
+**curr1_input**
+
+**curr1_label**
+
+**curr1_alarm**
+
+**curr1_max**
+
+The driver provides the following attributes for input power:
+
+**power1_input**
+
+**power1_label**
+
+**power1_alarm**
+
+The driver provides the following attributes for temperature:
+
+**temp1_input**
+
+**temp1_max**
+
+**temp1_max_alarm**
+
+**temp1_crit**
+
+**temp1_crit_alarm**
diff --git a/Documentation/hwmon/sht3x.rst b/Documentation/hwmon/sht3x.rst
index 87864ffd1777..957c854f5d08 100644
--- a/Documentation/hwmon/sht3x.rst
+++ b/Documentation/hwmon/sht3x.rst
@@ -9,7 +9,19 @@ Supported chips:
Addresses scanned: none
- Datasheet: https://www.sensirion.com/file/datasheet_sht3x_digital
+ Datasheets:
+ - https://sensirion.com/media/documents/213E6A3B/63A5A569/Datasheet_SHT3x_DIS.pdf
+ - https://sensirion.com/media/documents/051DF50B/639C8101/Sensirion_Humidity_and_Temperature_Sensors_Datasheet_SHT33.pdf
+
+ * Sensirion STS3x-DIS
+
+ Prefix: 'sts3x'
+
+ Addresses scanned: none
+
+ Datasheets:
+ - https://sensirion.com/media/documents/1DA31AFD/61641F76/Sensirion_Temperature_Sensors_STS3x_Datasheet.pdf
+ - https://sensirion.com/media/documents/292A335C/65537BAF/Sensirion_Datasheet_STS32_STS33.pdf
Author:
@@ -19,16 +31,17 @@ Author:
Description
-----------
-This driver implements support for the Sensirion SHT3x-DIS chip, a humidity
-and temperature sensor. Temperature is measured in degrees celsius, relative
-humidity is expressed as a percentage. In the sysfs interface, all values are
-scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
+This driver implements support for the Sensirion SHT3x-DIS and STS3x-DIS
+series of humidity and temperature sensors. Temperature is measured in degrees
+celsius, relative humidity is expressed as a percentage. In the sysfs interface,
+all values are scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
The device communicates with the I2C protocol. Sensors can have the I2C
-addresses 0x44 or 0x45, depending on the wiring. See
-Documentation/i2c/instantiating-devices.rst for methods to instantiate the device.
+addresses 0x44 or 0x45 (0x4a or 0x4b for sts3x), depending on the wiring. See
+Documentation/i2c/instantiating-devices.rst for methods to instantiate the
+device.
-Even if sht3x sensor supports clock-strech(blocking mode) and non-strench
+Even if sht3x sensor supports clock-stretch (blocking mode) and non-stretch
(non-blocking mode) in single-shot mode, this driver only supports the latter.
The sht3x sensor supports a single shot mode as well as 5 periodic measure
diff --git a/MAINTAINERS b/MAINTAINERS
index ab5e7e7fba4c..463b9741be8f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3451,6 +3451,14 @@ F: drivers/video/backlight/
F: include/linux/backlight.h
F: include/linux/pwm_backlight.h
+BAIKAL-T1 PVT HARDWARE MONITOR DRIVER
+M: Serge Semin <fancer.lancer@gmail.com>
+L: linux-hwmon@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml
+F: Documentation/hwmon/bt1-pvt.rst
+F: drivers/hwmon/bt1-pvt.[ch]
+
BARCO P50 GPIO DRIVER
M: Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
M: Peter Korsgaard <peter.korsgaard@barco.com>
@@ -8948,6 +8956,13 @@ F: Documentation/filesystems/gfs2*
F: fs/gfs2/
F: include/uapi/linux/gfs2_ondisk.h
+GIGABYTE WATERFORCE SENSOR DRIVER
+M: Aleksa Savic <savicaleksa83@gmail.com>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: Documentation/hwmon/gigabyte_waterforce.rst
+F: drivers/hwmon/gigabyte_waterforce.c
+
GIGABYTE WMI DRIVER
M: Thomas Weißschuh <thomas@weissschuh.net>
L: platform-driver-x86@vger.kernel.org
@@ -12686,6 +12701,16 @@ S: Maintained
F: Documentation/hwmon/ltc4261.rst
F: drivers/hwmon/ltc4261.c
+LTC4286 HARDWARE MONITOR DRIVER
+M: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml
+F: Documentation/hwmon/ltc4286.rst
+F: drivers/hwmon/pmbus/Kconfig
+F: drivers/hwmon/pmbus/Makefile
+F: drivers/hwmon/pmbus/ltc4286.c
+
LTC4306 I2C MULTIPLEXER DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
L: linux-i2c@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index cf27523eed5a..a608264da87d 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -512,6 +512,7 @@ config SENSORS_DS1621
config SENSORS_DELL_SMM
tristate "Dell laptop SMM BIOS hwmon driver"
+ depends on ACPI_WMI
depends on X86
imply THERMAL
help
@@ -663,6 +664,16 @@ config SENSORS_FTSTEUTATES
This driver can also be built as a module. If so, the module
will be called ftsteutates.
+config SENSORS_GIGABYTE_WATERFORCE
+ tristate "Gigabyte Waterforce X240/X280/X360 AIO CPU coolers"
+ depends on USB_HID
+ help
+ If you say yes here you get support for hardware monitoring for the
+ Gigabyte Waterforce X240/X280/X360 all-in-one CPU liquid coolers.
+
+ This driver can also be built as a module. If so, the module
+ will be called gigabyte_waterforce.
+
config SENSORS_GL518SM
tristate "Genesys Logic GL518SM"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e84bd9685b5c..47be39af5c03 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o
obj-$(CONFIG_SENSORS_G760A) += g760a.o
obj-$(CONFIG_SENSORS_G762) += g762.o
+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
diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c
index 4fdd2e12427b..2efe97f8d003 100644
--- a/drivers/hwmon/aquacomputer_d5next.c
+++ b/drivers/hwmon/aquacomputer_d5next.c
@@ -1476,8 +1476,6 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
return 0;
}
-#ifdef CONFIG_DEBUG_FS
-
static int serial_number_show(struct seq_file *seqf, void *unused)
{
struct aqc_data *priv = seqf->private;
@@ -1527,14 +1525,6 @@ static void aqc_debugfs_init(struct aqc_data *priv)
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
}
-#else
-
-static void aqc_debugfs_init(struct aqc_data *priv)
-{
-}
-
-#endif
-
static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct aqc_data *priv;
diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c
index 997df4b40509..f6e1e55e8292 100644
--- a/drivers/hwmon/aspeed-pwm-tacho.c
+++ b/drivers/hwmon/aspeed-pwm-tacho.c
@@ -166,6 +166,8 @@
#define MAX_CDEV_NAME_LEN 16
+#define MAX_ASPEED_FAN_TACH_CHANNELS 16
+
struct aspeed_cooling_device {
char name[16];
struct aspeed_pwm_tacho_data *priv;
@@ -181,7 +183,7 @@ struct aspeed_pwm_tacho_data {
struct reset_control *rst;
unsigned long clk_freq;
bool pwm_present[8];
- bool fan_tach_present[16];
+ bool fan_tach_present[MAX_ASPEED_FAN_TACH_CHANNELS];
u8 type_pwm_clock_unit[3];
u8 type_pwm_clock_division_h[3];
u8 type_pwm_clock_division_l[3];
@@ -190,7 +192,7 @@ struct aspeed_pwm_tacho_data {
u16 type_fan_tach_unit[3];
u8 pwm_port_type[8];
u8 pwm_port_fan_ctrl[8];
- u8 fan_tach_ch_source[16];
+ u8 fan_tach_ch_source[MAX_ASPEED_FAN_TACH_CHANNELS];
struct aspeed_cooling_device *cdev[8];
const struct attribute_group *groups[3];
};
@@ -737,20 +739,27 @@ static void aspeed_create_pwm_port(struct aspeed_pwm_tacho_data *priv,
aspeed_set_pwm_port_fan_ctrl(priv, pwm_port, INIT_FAN_CTRL);
}
-static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv,
- u8 *fan_tach_ch,
- int count,
- u8 pwm_source)
+static int aspeed_create_fan_tach_channel(struct device *dev,
+ struct aspeed_pwm_tacho_data *priv,
+ u8 *fan_tach_ch,
+ int count,
+ u8 pwm_source)
{
u8 val, index;
for (val = 0; val < count; val++) {
index = fan_tach_ch[val];
+ if (index >= MAX_ASPEED_FAN_TACH_CHANNELS) {
+ dev_err(dev, "Invalid Fan Tach input channel %u\n.", index);
+ return -EINVAL;
+ }
aspeed_set_fan_tach_ch_enable(priv->regmap, index, true);
priv->fan_tach_present[index] = true;
priv->fan_tach_ch_source[index] = pwm_source;
aspeed_set_fan_tach_ch_source(priv->regmap, index, pwm_source);
}
+
+ return 0;
}
static int
@@ -874,7 +883,10 @@ static int aspeed_create_fan(struct device *dev,
fan_tach_ch, count);
if (ret)
return ret;
- aspeed_create_fan_tach_channel(priv, fan_tach_ch, count, pwm_port);
+
+ ret = aspeed_create_fan_tach_channel(dev, priv, fan_tach_ch, count, pwm_port);
+ if (ret)
+ return ret;
return 0;
}
diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c
index 463ab4296ede..a284a02839fb 100644
--- a/drivers/hwmon/corsair-cpro.c
+++ b/drivers/hwmon/corsair-cpro.c
@@ -524,7 +524,7 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret)
goto out_hw_close;
ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
- ccp, &ccp_chip_info, 0);
+ ccp, &ccp_chip_info, NULL);
if (IS_ERR(ccp->hwmon_dev)) {
ret = PTR_ERR(ccp->hwmon_dev);
goto out_hw_close;
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 44aaf9b9191d..6d8c0f328b7b 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/acpi.h>
#include <linux/capability.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
@@ -34,8 +35,10 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
+#include <linux/wmi.h>
#include <linux/i8k.h>
+#include <asm/unaligned.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
@@ -66,9 +69,26 @@
#define I8K_POWER_AC 0x05
#define I8K_POWER_BATTERY 0x01
+#define DELL_SMM_WMI_GUID "F1DDEE52-063C-4784-A11E-8A06684B9B01"
+#define DELL_SMM_LEGACY_EXECUTE 0x1
+
#define DELL_SMM_NO_TEMP 10
#define DELL_SMM_NO_FANS 3
+struct smm_regs {
+ unsigned int eax;
+ unsigned int ebx;
+ unsigned int ecx;
+ unsigned int edx;
+ unsigned int esi;
+ unsigned int edi;
+};
+
+struct dell_smm_ops {
+ struct device *smm_dev;
+ int (*smm_call)(struct device *smm_dev, struct smm_regs *regs);
+};
+
struct dell_smm_data {
struct mutex i8k_mutex; /* lock for sensors writes */
char bios_version[4];
@@ -76,14 +96,11 @@ struct dell_smm_data {
uint i8k_fan_mult;
uint i8k_pwm_mult;
uint i8k_fan_max;
- bool disallow_fan_type_call;
- bool disallow_fan_support;
- unsigned int manual_fan;
- unsigned int auto_fan;
int temp_type[DELL_SMM_NO_TEMP];
bool fan[DELL_SMM_NO_FANS];
int fan_type[DELL_SMM_NO_FANS];
int *fan_nominal_speed[DELL_SMM_NO_FANS];
+ const struct dell_smm_ops *ops;
};
struct dell_smm_cooling_data {
@@ -123,14 +140,9 @@ static uint fan_max;
module_param(fan_max, uint, 0);
MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
-struct smm_regs {
- unsigned int eax;
- unsigned int ebx;
- unsigned int ecx;
- unsigned int edx;
- unsigned int esi;
- unsigned int edi;
-};
+static bool disallow_fan_type_call, disallow_fan_support;
+
+static unsigned int manual_fan, auto_fan;
static const char * const temp_labels[] = {
"CPU",
@@ -171,12 +183,8 @@ static inline const char __init *i8k_get_dmi_data(int field)
*/
static int i8k_smm_func(void *par)
{
- ktime_t calltime = ktime_get();
struct smm_regs *regs = par;
- int eax = regs->eax;
- int ebx = regs->ebx;
unsigned char carry;
- long long duration;
/* SMM requires CPU 0 */
if (smp_processor_id() != 0)
@@ -193,14 +201,7 @@ static int i8k_smm_func(void *par)
"+S" (regs->esi),
"+D" (regs->edi));
- duration = ktime_us_delta(ktime_get(), calltime);
- pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x carry: %d (took %7lld usecs)\n",
- eax, ebx, regs->eax & 0xffff, carry, duration);
-
- if (duration > DELL_SMM_MAX_DURATION)
- pr_warn_once("SMM call took %lld usecs!\n", duration);
-
- if (carry || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+ if (carry)
return -EINVAL;
return 0;
@@ -209,7 +210,7 @@ static int i8k_smm_func(void *par)
/*
* Call the System Management Mode BIOS.
*/
-static int i8k_smm(struct smm_regs *regs)
+static int i8k_smm_call(struct device *dummy, struct smm_regs *regs)
{
int ret;
@@ -220,6 +221,134 @@ static int i8k_smm(struct smm_regs *regs)
return ret;
}
+static const struct dell_smm_ops i8k_smm_ops = {
+ .smm_call = i8k_smm_call,
+};
+
+/*
+ * Call the System Management Mode BIOS over WMI.
+ */
+static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
+{
+ __le32 value;
+ u32 reg_size;
+
+ if (length <= sizeof(reg_size))
+ return -ENODATA;
+
+ reg_size = get_unaligned_le32(buffer);
+ if (!reg_size || reg_size > sizeof(value))
+ return -ENOMSG;
+
+ if (length < sizeof(reg_size) + reg_size)
+ return -ENODATA;
+
+ memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0);
+ *reg = le32_to_cpu(value);
+
+ return reg_size + sizeof(reg_size);
+}
+
+static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
+{
+ unsigned int *registers[] = {
+ &regs->eax,
+ &regs->ebx,
+ &regs->ecx,
+ &regs->edx
+ };
+ u32 offset = 0;
+ ssize_t ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ if (offset >= length)
+ return -ENODATA;
+
+ ret = wmi_parse_register(buffer + offset, length - offset, registers[i]);
+ if (ret < 0)
+ return ret;
+
+ offset += ret;
+ }
+
+ if (offset != length)
+ return -ENOMSG;
+
+ return 0;
+}
+
+static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
+{
+ struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ u32 wmi_payload[] = {
+ sizeof(regs->eax),
+ regs->eax,
+ sizeof(regs->ebx),
+ regs->ebx,
+ sizeof(regs->ecx),
+ regs->ecx,
+ sizeof(regs->edx),
+ regs->edx
+ };
+ const struct acpi_buffer in = {
+ .length = sizeof(wmi_payload),
+ .pointer = &wmi_payload,
+ };
+ union acpi_object *obj;
+ acpi_status status;
+ int ret;
+
+ status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = out.pointer;
+ if (!obj)
+ return -ENODATA;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ ret = -ENOMSG;
+
+ goto err_free;
+ }
+
+ ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
+
+err_free:
+ kfree(obj);
+
+ return ret;
+}
+
+static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)
+{
+ unsigned int eax = regs->eax;
+ unsigned int ebx = regs->ebx;
+ long long duration;
+ ktime_t calltime;
+ int ret;
+
+ calltime = ktime_get();
+ ret = ops->smm_call(ops->smm_dev, regs);
+ duration = ktime_us_delta(ktime_get(), calltime);
+
+ pr_debug("SMM(0x%.4x 0x%.4x) = 0x%.4x status: %d (took %7lld usecs)\n",
+ eax, ebx, regs->eax & 0xffff, ret, duration);
+
+ if (duration > DELL_SMM_MAX_DURATION)
+ pr_warn_once("SMM call took %lld usecs!\n", duration);
+
+ if (ret < 0)
+ return ret;
+
+ if ((regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+ return -EINVAL;
+
+ return 0;
+}
+
/*
* Read the fan status.
*/
@@ -230,10 +359,10 @@ static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
@@ -246,10 +375,10 @@ static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
+ return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
}
/*
@@ -262,10 +391,10 @@ static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
- if (data->disallow_fan_support || data->disallow_fan_type_call)
+ if (disallow_fan_support || disallow_fan_type_call)
return -EINVAL;
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
@@ -280,17 +409,17 @@ static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
/*
* Read the fan nominal rpm for specific fan speed.
*/
-static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
+static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_NOM_SPEED,
.ebx = fan | (speed << 8),
};
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- return i8k_smm(&regs) ? : (regs.eax & 0xffff);
+ return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff);
}
/*
@@ -300,11 +429,11 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl
{
struct smm_regs regs = { };
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
return -EINVAL;
- regs.eax = enable ? data->auto_fan : data->manual_fan;
- return i8k_smm(&regs);
+ regs.eax = enable ? auto_fan : manual_fan;
+ return dell_smm_call(data->ops, &regs);
}
/*
@@ -314,41 +443,41 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
- if (data->disallow_fan_support)
+ 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 i8k_smm(&regs);
+ return dell_smm_call(data->ops, &regs);
}
-static int __init i8k_get_temp_type(u8 sensor)
+static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP_TYPE,
.ebx = sensor,
};
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
* Read the cpu temperature.
*/
-static int _i8k_get_temp(u8 sensor)
+static int _i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP,
.ebx = sensor,
};
- return i8k_smm(&regs) ? : regs.eax & 0xff;
+ return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
-static int i8k_get_temp(u8 sensor)
+static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
- int temp = _i8k_get_temp(sensor);
+ int temp = _i8k_get_temp(data, sensor);
/*
* Sometimes the temperature sensor returns 0x99, which is out of range.
@@ -359,7 +488,7 @@ static int i8k_get_temp(u8 sensor)
*/
if (temp == 0x99) {
msleep(100);
- temp = _i8k_get_temp(sensor);
+ temp = _i8k_get_temp(data, sensor);
}
/*
* Return -ENODATA for all invalid temperatures.
@@ -375,12 +504,12 @@ static int i8k_get_temp(u8 sensor)
return temp;
}
-static int __init i8k_get_dell_signature(int req_fn)
+static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
{
struct smm_regs regs = { .eax = req_fn, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(ops, &regs);
if (rc < 0)
return rc;
@@ -392,12 +521,12 @@ static int __init i8k_get_dell_signature(int req_fn)
/*
* Read the Fn key status.
*/
-static int i8k_get_fn_status(void)
+static int i8k_get_fn_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@@ -416,12 +545,12 @@ static int i8k_get_fn_status(void)
/*
* Read the power status.
*/
-static int i8k_get_power_status(void)
+static int i8k_get_power_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
int rc;
- rc = i8k_smm(&regs);
+ rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@@ -464,15 +593,15 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return 0;
case I8K_FN_STATUS:
- val = i8k_get_fn_status();
+ val = i8k_get_fn_status(data);
break;
case I8K_POWER_STATUS:
- val = i8k_get_power_status();
+ val = i8k_get_power_status(data);
break;
case I8K_GET_TEMP:
- val = i8k_get_temp(0);
+ val = i8k_get_temp(data, 0);
break;
case I8K_GET_SPEED:
@@ -539,14 +668,14 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
int fn_key, cpu_temp, ac_power;
int left_fan, right_fan, left_speed, right_speed;
- cpu_temp = i8k_get_temp(0); /* 11100 µs */
+ cpu_temp = i8k_get_temp(data, 0); /* 11100 µs */
left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */
right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */
left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */
right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */
- fn_key = i8k_get_fn_status(); /* 750 µs */
+ fn_key = i8k_get_fn_status(data); /* 750 µs */
if (power_status)
- ac_power = i8k_get_power_status(); /* 14700 µs */
+ ac_power = i8k_get_power_status(data); /* 14700 µs */
else
ac_power = -1;
@@ -597,6 +726,11 @@ static void __init i8k_init_procfs(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
+ strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+ sizeof(data->bios_version));
+ strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+ sizeof(data->bios_machineid));
+
/* Only register exit function if creation was successful */
if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data))
devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
@@ -665,7 +799,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
switch (attr) {
case hwmon_temp_input:
/* _i8k_get_temp() is fine since we do not care about the actual value */
- if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0)
+ if (data->temp_type[channel] >= 0 || _i8k_get_temp(data, channel) >= 0)
return 0444;
break;
@@ -679,7 +813,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_fan:
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
break;
switch (attr) {
@@ -689,7 +823,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_fan_label:
- if (data->fan[channel] && !data->disallow_fan_type_call)
+ if (data->fan[channel] && !disallow_fan_type_call)
return 0444;
break;
@@ -705,7 +839,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_pwm:
- if (data->disallow_fan_support)
+ if (disallow_fan_support)
break;
switch (attr) {
@@ -715,7 +849,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_pwm_enable:
- if (data->auto_fan)
+ if (auto_fan)
/*
* There is no command for retrieve the current status
* from BIOS, and userspace/firmware itself can change
@@ -747,7 +881,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
- ret = i8k_get_temp(channel);
+ ret = i8k_get_temp(data, channel);
if (ret < 0)
return ret;
@@ -955,7 +1089,7 @@ static const struct hwmon_chip_info dell_smm_chip_info = {
.info = dell_smm_info,
};
-static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
+static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct thermal_cooling_device *cdev;
@@ -986,7 +1120,7 @@ static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
return ret;
}
-static int __init dell_smm_init_hwmon(struct device *dev)
+static int dell_smm_init_hwmon(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct device *dell_smm_hwmon_dev;
@@ -994,7 +1128,7 @@ static int __init dell_smm_init_hwmon(struct device *dev)
u8 i;
for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
- data->temp_type[i] = i8k_get_temp_type(i);
+ data->temp_type[i] = i8k_get_temp_type(data, i);
if (data->temp_type[i] < 0)
continue;
@@ -1052,41 +1186,25 @@ static int __init dell_smm_init_hwmon(struct device *dev)
return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
}
-struct i8k_config_data {
- uint fan_mult;
- uint fan_max;
-};
+static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
+{
+ struct dell_smm_data *data;
-enum i8k_configs {
- DELL_LATITUDE_D520,
- DELL_PRECISION_490,
- DELL_STUDIO,
- DELL_XPS,
-};
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
-/*
- * Only use for machines which need some special configuration
- * in order to work correctly (e.g. if autoconfig fails on this machines).
- */
+ mutex_init(&data->i8k_mutex);
+ dev_set_drvdata(dev, data);
-static const struct i8k_config_data i8k_config_data[] __initconst = {
- [DELL_LATITUDE_D520] = {
- .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,
- },
- [DELL_XPS] = {
- .fan_mult = 1,
- .fan_max = I8K_FAN_HIGH,
- },
-};
+ data->ops = ops;
+ /* All options must not be 0 */
+ data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
+ data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
+ data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
+
+ return 0;
+}
static const struct dmi_system_id i8k_dmi_table[] __initconst = {
{
@@ -1118,14 +1236,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
},
},
{
- .ident = "Dell Latitude D520",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
- },
- .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
- },
- {
.ident = "Dell Latitude 2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1147,15 +1257,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
},
},
{
- .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 Precision",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -1175,7 +1276,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
},
- .driver_data = (void *)&i8k_config_data[DELL_STUDIO],
},
{
.ident = "Dell XPS M140",
@@ -1183,7 +1283,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
},
- .driver_data = (void *)&i8k_config_data[DELL_XPS],
},
{
.ident = "Dell XPS",
@@ -1198,6 +1297,78 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
/*
+ * Only use for machines which need some special configuration
+ * in order to work correctly (e.g. if autoconfig fails on this machines).
+ */
+struct i8k_config_data {
+ uint fan_mult;
+ uint fan_max;
+};
+
+enum i8k_configs {
+ DELL_LATITUDE_D520,
+ DELL_PRECISION_490,
+ DELL_STUDIO,
+ DELL_XPS,
+};
+
+static const struct i8k_config_data i8k_config_data[] __initconst = {
+ [DELL_LATITUDE_D520] = {
+ .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,
+ },
+ [DELL_XPS] = {
+ .fan_mult = 1,
+ .fan_max = I8K_FAN_HIGH,
+ },
+};
+
+static const struct dmi_system_id i8k_config_dmi_table[] __initconst = {
+ {
+ .ident = "Dell Latitude D520",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
+ },
+ .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 = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
+ },
+ .driver_data = (void *)&i8k_config_data[DELL_STUDIO],
+ },
+ {
+ .ident = "Dell XPS M140",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
+ },
+ .driver_data = (void *)&i8k_config_data[DELL_XPS],
+ },
+ { }
+};
+
+/*
* On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
* randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
* of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
@@ -1338,119 +1509,174 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
+ {
+ .ident = "Dell Optiplex 7000",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7000"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+ },
{ }
};
+/*
+ * Legacy SMM backend driver.
+ */
static int __init dell_smm_probe(struct platform_device *pdev)
{
- struct dell_smm_data *data;
- const struct dmi_system_id *id, *fan_control;
int ret;
- data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
- if (!data)
+ ret = dell_smm_init_data(&pdev->dev, &i8k_smm_ops);
+ if (ret < 0)
+ return ret;
+
+ ret = dell_smm_init_hwmon(&pdev->dev);
+ if (ret)
+ return ret;
+
+ i8k_init_procfs(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver dell_smm_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+static struct platform_device *dell_smm_device;
+
+/*
+ * WMI SMM backend driver.
+ */
+static int dell_smm_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct dell_smm_ops *ops;
+ int ret;
+
+ ops = devm_kzalloc(&wdev->dev, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
return -ENOMEM;
- mutex_init(&data->i8k_mutex);
- platform_set_drvdata(pdev, data);
+ ops->smm_call = wmi_smm_call;
+ ops->smm_dev = &wdev->dev;
+
+ if (dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG1) &&
+ dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG2))
+ return -ENODEV;
+
+ ret = dell_smm_init_data(&wdev->dev, ops);
+ if (ret < 0)
+ return ret;
+
+ return dell_smm_init_hwmon(&wdev->dev);
+}
+
+static const struct wmi_device_id dell_smm_wmi_id_table[] = {
+ { DELL_SMM_WMI_GUID, NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, dell_smm_wmi_id_table);
+
+static struct wmi_driver dell_smm_wmi_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = dell_smm_wmi_id_table,
+ .probe = dell_smm_wmi_probe,
+};
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static void __init dell_smm_init_dmi(void)
+{
+ struct i8k_fan_control_data *control;
+ struct i8k_config_data *config;
+ const struct dmi_system_id *id;
if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
if (!force) {
- dev_notice(&pdev->dev, "Disabling fan support due to BIOS bugs\n");
- data->disallow_fan_support = true;
+ pr_notice("Disabling fan support due to BIOS bugs\n");
+ disallow_fan_support = true;
} else {
- dev_warn(&pdev->dev, "Enabling fan support despite BIOS bugs\n");
+ pr_warn("Enabling fan support despite BIOS bugs\n");
}
}
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
if (!force) {
- dev_notice(&pdev->dev, "Disabling fan type call due to BIOS bugs\n");
- data->disallow_fan_type_call = true;
+ pr_notice("Disabling fan type call due to BIOS bugs\n");
+ disallow_fan_type_call = true;
} else {
- dev_warn(&pdev->dev, "Enabling fan type call despite BIOS bugs\n");
+ pr_warn("Enabling fan type call despite BIOS bugs\n");
}
}
- strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
- sizeof(data->bios_version));
- strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
- sizeof(data->bios_machineid));
-
/*
- * Set fan multiplier and maximal fan speed from dmi config
- * Values specified in module parameters override values from dmi
+ * Set fan multiplier and maximal fan speed from DMI config.
+ * Values specified in module parameters override values from DMI.
*/
- id = dmi_first_match(i8k_dmi_table);
+ id = dmi_first_match(i8k_config_dmi_table);
if (id && id->driver_data) {
- const struct i8k_config_data *conf = id->driver_data;
+ config = id->driver_data;
+ if (!fan_mult && config->fan_mult)
+ fan_mult = config->fan_mult;
- if (!fan_mult && conf->fan_mult)
- fan_mult = conf->fan_mult;
-
- if (!fan_max && conf->fan_max)
- fan_max = conf->fan_max;
+ if (!fan_max && config->fan_max)
+ fan_max = config->fan_max;
}
- /* All options must not be 0 */
- data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
- data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
- data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
-
- fan_control = dmi_first_match(i8k_whitelist_fan_control);
- if (fan_control && fan_control->driver_data) {
- const struct i8k_fan_control_data *control = fan_control->driver_data;
+ id = dmi_first_match(i8k_whitelist_fan_control);
+ if (id && id->driver_data) {
+ control = id->driver_data;
+ manual_fan = control->manual_fan;
+ auto_fan = control->auto_fan;
- data->manual_fan = control->manual_fan;
- data->auto_fan = control->auto_fan;
- dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
+ pr_info("Enabling support for setting automatic/manual fan control\n");
}
-
- ret = dell_smm_init_hwmon(&pdev->dev);
- if (ret)
- return ret;
-
- i8k_init_procfs(&pdev->dev);
-
- return 0;
}
-static struct platform_driver dell_smm_driver = {
- .driver = {
- .name = KBUILD_MODNAME,
- },
-};
-
-static struct platform_device *dell_smm_device;
-
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_init(void)
+static int __init dell_smm_legacy_check(void)
{
- /*
- * Get DMI information
- */
if (!dmi_check_system(i8k_dmi_table)) {
if (!ignore_dmi && !force)
return -ENODEV;
- pr_info("not running on a supported Dell system.\n");
+ pr_info("Probing for legacy SMM handler on unsupported machine\n");
pr_info("vendor=%s, model=%s, version=%s\n",
i8k_get_dmi_data(DMI_SYS_VENDOR),
i8k_get_dmi_data(DMI_PRODUCT_NAME),
i8k_get_dmi_data(DMI_BIOS_VERSION));
}
- /*
- * Get SMM Dell signature
- */
- if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
- i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+ if (dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG1) &&
+ dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG2)) {
if (!force)
return -ENODEV;
- pr_err("Unable to get Dell SMM signature\n");
+ pr_warn("Forcing legacy SMM calls on a possibly incompatible machine\n");
+ }
+
+ return 0;
+}
+
+static int __init i8k_init(void)
+{
+ int ret;
+
+ dell_smm_init_dmi();
+
+ ret = dell_smm_legacy_check();
+ if (ret < 0) {
+ /*
+ * On modern machines, SMM communication happens over WMI, meaning
+ * the SMM handler might not react to legacy SMM calls.
+ */
+ return wmi_driver_register(&dell_smm_wmi_driver);
}
dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
@@ -1461,8 +1687,12 @@ static int __init i8k_init(void)
static void __exit i8k_exit(void)
{
- platform_device_unregister(dell_smm_device);
- platform_driver_unregister(&dell_smm_driver);
+ if (dell_smm_device) {
+ platform_device_unregister(dell_smm_device);
+ platform_driver_unregister(&dell_smm_driver);
+ } else {
+ wmi_driver_unregister(&dell_smm_wmi_driver);
+ }
}
module_init(i8k_init);
diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c
index bb7c859e799d..1332e4ac078c 100644
--- a/drivers/hwmon/emc1403.c
+++ b/drivers/hwmon/emc1403.c
@@ -346,6 +346,9 @@ static int emc1403_detect(struct i2c_client *client,
case 0x27:
strscpy(info->type, "emc1424", I2C_NAME_SIZE);
break;
+ case 0x60:
+ strscpy(info->type, "emc1442", I2C_NAME_SIZE);
+ break;
default:
return -ENODEV;
}
@@ -430,7 +433,7 @@ static int emc1403_probe(struct i2c_client *client)
}
static const unsigned short emc1403_address_list[] = {
- 0x18, 0x1c, 0x29, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END
+ 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END
};
/* Last digit of chip name indicates number of channels */
@@ -444,6 +447,7 @@ static const struct i2c_device_id emc1403_idtable[] = {
{ "emc1422", emc1402 },
{ "emc1423", emc1403 },
{ "emc1424", emc1404 },
+ { "emc1442", emc1402 },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc1403_idtable);
diff --git a/drivers/hwmon/gigabyte_waterforce.c b/drivers/hwmon/gigabyte_waterforce.c
new file mode 100644
index 000000000000..85e523775714
--- /dev/null
+++ b/drivers/hwmon/gigabyte_waterforce.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for Gigabyte AORUS Waterforce AIO CPU coolers: X240, X280 and X360.
+ *
+ * Copyright 2023 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <asm/unaligned.h>
+
+#define DRIVER_NAME "gigabyte_waterforce"
+
+#define USB_VENDOR_ID_GIGABYTE 0x1044
+#define USB_PRODUCT_ID_WATERFORCE 0x7a4d /* Gigabyte AORUS WATERFORCE X240, X280 and X360 */
+
+#define STATUS_VALIDITY (2 * 1000) /* ms */
+#define MAX_REPORT_LENGTH 6144
+
+#define WATERFORCE_TEMP_SENSOR 0xD
+#define WATERFORCE_FAN_SPEED 0x02
+#define WATERFORCE_PUMP_SPEED 0x05
+#define WATERFORCE_FAN_DUTY 0x08
+#define WATERFORCE_PUMP_DUTY 0x09
+
+/* Control commands, inner offsets and lengths */
+static const u8 get_status_cmd[] = { 0x99, 0xDA };
+
+#define FIRMWARE_VER_START_OFFSET_1 2
+#define FIRMWARE_VER_START_OFFSET_2 3
+static const u8 get_firmware_ver_cmd[] = { 0x99, 0xD6 };
+
+/* Command lengths */
+#define GET_STATUS_CMD_LENGTH 2
+#define GET_FIRMWARE_VER_CMD_LENGTH 2
+
+static const char *const waterforce_temp_label[] = {
+ "Coolant temp"
+};
+
+static const char *const waterforce_speed_label[] = {
+ "Fan speed",
+ "Pump speed"
+};
+
+struct waterforce_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ struct dentry *debugfs;
+ /* For locking access to buffer */
+ struct mutex buffer_lock;
+ /* For queueing multiple readers */
+ struct mutex status_report_request_mutex;
+ /* For reinitializing the completion below */
+ spinlock_t status_report_request_lock;
+ struct completion status_report_received;
+ struct completion fw_version_processed;
+
+ /* Sensor data */
+ s32 temp_input[1];
+ u16 speed_input[2]; /* Fan and pump speed in RPM */
+ u8 duty_input[2]; /* Fan and pump duty in 0-100% */
+
+ u8 *buffer;
+ int firmware_version;
+ unsigned long updated; /* jiffies */
+};
+
+static umode_t waterforce_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_label:
+ case hwmon_fan_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Writes the command to the device with the rest of the report filled with zeroes */
+static int waterforce_write_expanded(struct waterforce_data *priv, const u8 *cmd, int cmd_length)
+{
+ int ret;
+
+ mutex_lock(&priv->buffer_lock);
+
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+
+ mutex_unlock(&priv->buffer_lock);
+ return ret;
+}
+
+static int waterforce_get_status(struct waterforce_data *priv)
+{
+ int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ goto unlock_and_return;
+ }
+
+ /*
+ * Disable raw event parsing for a moment to safely reinitialize the
+ * completion. Reinit is done because hidraw could have triggered
+ * the raw event parsing and marked the priv->status_report_received
+ * completion as done.
+ */
+ spin_lock_bh(&priv->status_report_request_lock);
+ reinit_completion(&priv->status_report_received);
+ spin_unlock_bh(&priv->status_report_request_lock);
+
+ /* Send command for getting status */
+ ret = waterforce_write_expanded(priv, get_status_cmd, GET_STATUS_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_received,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+
+unlock_and_return:
+ mutex_unlock(&priv->status_report_request_mutex);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int waterforce_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct waterforce_data *priv = dev_get_drvdata(dev);
+ int ret = waterforce_get_status(priv);
+
+ if (ret < 0)
+ return ret;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->speed_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ *val = DIV_ROUND_CLOSEST(priv->duty_input[channel] * 255, 100);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int waterforce_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = waterforce_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = waterforce_speed_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int waterforce_get_fw_ver(struct hid_device *hdev)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = waterforce_write_expanded(priv, get_firmware_ver_cmd, GET_FIRMWARE_VER_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct hwmon_ops waterforce_hwmon_ops = {
+ .is_visible = waterforce_is_visible,
+ .read = waterforce_read,
+ .read_string = waterforce_read_string
+};
+
+static const struct hwmon_channel_info *waterforce_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info waterforce_chip_info = {
+ .ops = &waterforce_hwmon_ops,
+ .info = waterforce_info,
+};
+
+static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
+ int size)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+
+ if (data[0] == get_firmware_ver_cmd[0] && data[1] == get_firmware_ver_cmd[1]) {
+ /* Received a firmware version report */
+ priv->firmware_version =
+ data[FIRMWARE_VER_START_OFFSET_1] * 10 + data[FIRMWARE_VER_START_OFFSET_2];
+
+ if (!completion_done(&priv->fw_version_processed))
+ complete_all(&priv->fw_version_processed);
+ return 0;
+ }
+
+ if (data[0] != get_status_cmd[0] || data[1] != get_status_cmd[1])
+ return 0;
+
+ priv->temp_input[0] = data[WATERFORCE_TEMP_SENSOR] * 1000;
+ priv->speed_input[0] = get_unaligned_le16(data + WATERFORCE_FAN_SPEED);
+ priv->speed_input[1] = get_unaligned_le16(data + WATERFORCE_PUMP_SPEED);
+ priv->duty_input[0] = data[WATERFORCE_FAN_DUTY];
+ priv->duty_input[1] = data[WATERFORCE_PUMP_DUTY];
+
+ spin_lock(&priv->status_report_request_lock);
+ if (!completion_done(&priv->status_report_received))
+ complete_all(&priv->status_report_received);
+ spin_unlock(&priv->status_report_request_lock);
+
+ priv->updated = jiffies;
+
+ return 0;
+}
+
+static int firmware_version_show(struct seq_file *seqf, void *unused)
+{
+ struct waterforce_data *priv = seqf->private;
+
+ seq_printf(seqf, "%u\n", priv->firmware_version);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware_version);
+
+static void waterforce_debugfs_init(struct waterforce_data *priv)
+{
+ char name[64];
+
+ if (!priv->firmware_version)
+ return; /* There's nothing to show in debugfs */
+
+ scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
+
+ priv->debugfs = debugfs_create_dir(name, NULL);
+ debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
+}
+
+static int waterforce_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct waterforce_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for waterforce_read() without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Enable hidraw so existing user-space tools can continue to work.
+ */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ mutex_init(&priv->status_report_request_mutex);
+ mutex_init(&priv->buffer_lock);
+ spin_lock_init(&priv->status_report_request_lock);
+ init_completion(&priv->status_report_received);
+ init_completion(&priv->fw_version_processed);
+
+ hid_device_io_start(hdev);
+ ret = waterforce_get_fw_ver(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "fw version request failed with %d\n", ret);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "waterforce",
+ priv, &waterforce_chip_info, NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ waterforce_debugfs_init(priv);
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void waterforce_remove(struct hid_device *hdev)
+{
+ struct waterforce_data *priv = hid_get_drvdata(hdev);
+
+ debugfs_remove_recursive(priv->debugfs);
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id waterforce_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_GIGABYTE, USB_PRODUCT_ID_WATERFORCE) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, waterforce_table);
+
+static struct hid_driver waterforce_driver = {
+ .name = "waterforce",
+ .id_table = waterforce_table,
+ .probe = waterforce_probe,
+ .remove = waterforce_remove,
+ .raw_event = waterforce_raw_event,
+};
+
+static int __init waterforce_init(void)
+{
+ return hid_register_driver(&waterforce_driver);
+}
+
+static void __exit waterforce_exit(void)
+{
+ hid_unregister_driver(&waterforce_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(waterforce_init);
+module_exit(waterforce_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for Gigabyte AORUS Waterforce AIO coolers");
diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c
index 17ae62f88bbf..b5325d0e72b9 100644
--- a/drivers/hwmon/hp-wmi-sensors.c
+++ b/drivers/hwmon/hp-wmi-sensors.c
@@ -17,6 +17,8 @@
* Available: https://github.com/linuxhw/ACPI
* [4] P. Rohár, "bmfdec - Decompile binary MOF file (BMF) from WMI buffer",
* 2017. [Online]. Available: https://github.com/pali/bmfdec
+ * [5] Microsoft Corporation, "Driver-Defined WMI Data Items", 2017. [Online].
+ * Available: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
*/
#include <linux/acpi.h>
@@ -24,6 +26,7 @@
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
+#include <linux/nls.h>
#include <linux/units.h>
#include <linux/wmi.h>
@@ -395,6 +398,50 @@ struct hp_wmi_sensors {
struct mutex lock; /* Lock polling WMI and driver state changes. */
};
+static bool is_raw_wmi_string(const u8 *pointer, u32 length)
+{
+ const u16 *ptr;
+ u16 len;
+
+ /* WMI strings are length-prefixed UTF-16 [5]. */
+ if (length <= sizeof(*ptr))
+ return false;
+
+ length -= sizeof(*ptr);
+ ptr = (const u16 *)pointer;
+ len = *ptr;
+
+ return len <= length && !(len & 1);
+}
+
+static char *convert_raw_wmi_string(const u8 *buf)
+{
+ const wchar_t *src;
+ unsigned int cps;
+ unsigned int len;
+ char *dst;
+ int i;
+
+ src = (const wchar_t *)buf;
+
+ /* Count UTF-16 code points. Exclude trailing null padding. */
+ cps = *src / sizeof(*src);
+ while (cps && !src[cps])
+ cps--;
+
+ /* Each code point becomes up to 3 UTF-8 characters. */
+ len = min(cps * 3, HP_WMI_MAX_STR_SIZE - 1);
+
+ dst = kmalloc((len + 1) * sizeof(*dst), GFP_KERNEL);
+ if (!dst)
+ return NULL;
+
+ i = utf16s_to_utf8s(++src, cps, UTF16_LITTLE_ENDIAN, dst, len);
+ dst[i] = '\0';
+
+ return dst;
+}
+
/* hp_wmi_strdup - devm_kstrdup, but length-limited */
static char *hp_wmi_strdup(struct device *dev, const char *src)
{
@@ -412,6 +459,23 @@ static char *hp_wmi_strdup(struct device *dev, const char *src)
return dst;
}
+/* hp_wmi_wstrdup - hp_wmi_strdup, but for a raw WMI string */
+static char *hp_wmi_wstrdup(struct device *dev, const u8 *buf)
+{
+ char *src;
+ char *dst;
+
+ src = convert_raw_wmi_string(buf);
+ if (!src)
+ return NULL;
+
+ dst = hp_wmi_strdup(dev, strim(src)); /* Note: Copy is trimmed. */
+
+ kfree(src);
+
+ return dst;
+}
+
/*
* hp_wmi_get_wobj - poll WMI for a WMI object instance
* @guid: WMI object GUID
@@ -462,8 +526,14 @@ static int check_wobj(const union acpi_object *wobj,
for (prop = 0; prop <= last_prop; prop++) {
type = elements[prop].type;
valid_type = property_map[prop];
- if (type != valid_type)
+ if (type != valid_type) {
+ if (type == ACPI_TYPE_BUFFER &&
+ valid_type == ACPI_TYPE_STRING &&
+ is_raw_wmi_string(elements[prop].buffer.pointer,
+ elements[prop].buffer.length))
+ continue;
return -EINVAL;
+ }
}
return 0;
@@ -480,7 +550,9 @@ static int extract_acpi_value(struct device *dev,
break;
case ACPI_TYPE_STRING:
- *out_string = hp_wmi_strdup(dev, strim(element->string.pointer));
+ *out_string = element->type == ACPI_TYPE_BUFFER ?
+ hp_wmi_wstrdup(dev, element->buffer.pointer) :
+ hp_wmi_strdup(dev, strim(element->string.pointer));
if (!*out_string)
return -ENOMEM;
break;
@@ -861,7 +933,9 @@ update_numeric_sensor_from_wobj(struct device *dev,
{
const union acpi_object *elements;
const union acpi_object *element;
- const char *string;
+ const char *new_string;
+ char *trimmed;
+ char *string;
bool is_new;
int offset;
u8 size;
@@ -885,11 +959,21 @@ update_numeric_sensor_from_wobj(struct device *dev,
offset = is_new ? size - 1 : -2;
element = &elements[HP_WMI_PROPERTY_CURRENT_STATE + offset];
- string = strim(element->string.pointer);
-
- if (strcmp(string, nsensor->current_state)) {
- devm_kfree(dev, nsensor->current_state);
- nsensor->current_state = hp_wmi_strdup(dev, string);
+ string = element->type == ACPI_TYPE_BUFFER ?
+ convert_raw_wmi_string(element->buffer.pointer) :
+ element->string.pointer;
+
+ if (string) {
+ trimmed = strim(string);
+ if (strcmp(trimmed, nsensor->current_state)) {
+ new_string = hp_wmi_strdup(dev, trimmed);
+ if (new_string) {
+ devm_kfree(dev, nsensor->current_state);
+ nsensor->current_state = new_string;
+ }
+ }
+ if (element->type == ACPI_TYPE_BUFFER)
+ kfree(string);
}
/* Old variant: -2 (not -1) because it lacks the Size property. */
@@ -996,11 +1080,15 @@ static int check_event_wobj(const union acpi_object *wobj)
HP_WMI_EVENT_PROPERTY_STATUS);
}
-static int populate_event_from_wobj(struct hp_wmi_event *event,
+static int populate_event_from_wobj(struct device *dev,
+ struct hp_wmi_event *event,
union acpi_object *wobj)
{
int prop = HP_WMI_EVENT_PROPERTY_NAME;
union acpi_object *element;
+ acpi_object_type type;
+ char *string;
+ u32 value;
int err;
err = check_event_wobj(wobj);
@@ -1009,20 +1097,24 @@ static int populate_event_from_wobj(struct hp_wmi_event *event,
element = wobj->package.elements;
- /* Extracted strings are NOT device-managed copies. */
-
for (; prop <= HP_WMI_EVENT_PROPERTY_CATEGORY; prop++, element++) {
+ type = hp_wmi_event_property_map[prop];
+
+ err = extract_acpi_value(dev, element, type, &value, &string);
+ if (err)
+ return err;
+
switch (prop) {
case HP_WMI_EVENT_PROPERTY_NAME:
- event->name = strim(element->string.pointer);
+ event->name = string;
break;
case HP_WMI_EVENT_PROPERTY_DESCRIPTION:
- event->description = strim(element->string.pointer);
+ event->description = string;
break;
case HP_WMI_EVENT_PROPERTY_CATEGORY:
- event->category = element->integer.value;
+ event->category = value;
break;
default:
@@ -1511,8 +1603,8 @@ static void hp_wmi_notify(u32 value, void *context)
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct hp_wmi_sensors *state = context;
struct device *dev = &state->wdev->dev;
+ struct hp_wmi_event event = {};
struct hp_wmi_info *fan_info;
- struct hp_wmi_event event;
union acpi_object *wobj;
acpi_status err;
int event_type;
@@ -1546,7 +1638,7 @@ static void hp_wmi_notify(u32 value, void *context)
wobj = out.pointer;
- err = populate_event_from_wobj(&event, wobj);
+ err = populate_event_from_wobj(dev, &event, wobj);
if (err) {
dev_warn(dev, "Bad event data (ACPI type %d)\n", wobj->type);
goto out_free_wobj;
@@ -1577,6 +1669,9 @@ static void hp_wmi_notify(u32 value, void *context)
out_free_wobj:
kfree(wobj);
+ devm_kfree(dev, event.name);
+ devm_kfree(dev, event.description);
+
out_unlock:
mutex_unlock(&state->lock);
}
diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c
index bae0becfa24b..8092312c0a87 100644
--- a/drivers/hwmon/k10temp.c
+++ b/drivers/hwmon/k10temp.c
@@ -455,6 +455,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
switch (boot_cpu_data.x86_model) {
case 0x0 ... 0x1: /* Zen3 SP3/TR */
+ case 0x8: /* Zen3 TR Chagall */
case 0x21: /* Zen3 Ryzen Desktop */
case 0x50 ... 0x5f: /* Green Sardine */
data->ccd_offset = 0x154;
diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c
index 5b2ea05c951e..e00750718536 100644
--- a/drivers/hwmon/lm75.c
+++ b/drivers/hwmon/lm75.c
@@ -7,11 +7,11 @@
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/regmap.h>
@@ -25,6 +25,7 @@
enum lm75_type { /* keep sorted in alphabetical order */
adt75,
+ as6200,
at30ts74,
ds1775,
ds75,
@@ -55,6 +56,7 @@ enum lm75_type { /* keep sorted in alphabetical order */
/**
* struct lm75_params - lm75 configuration parameters.
+ * @config_reg_16bits: Configure register size is 2 bytes.
* @set_mask: Bits to set in configuration register when configuring
* the chip.
* @clr_mask: Bits to clear in configuration register when configuring
@@ -75,17 +77,20 @@ enum lm75_type { /* keep sorted in alphabetical order */
* @sample_times: All the possible sample times to be set. Mandatory if
* num_sample_times is larger than 1. If set, number of
* entries must match num_sample_times.
+ * @alarm: Alarm bit is supported.
*/
struct lm75_params {
- u8 set_mask;
- u8 clr_mask;
+ bool config_reg_16bits;
+ u16 set_mask;
+ u16 clr_mask;
u8 default_resolution;
u8 resolution_limits;
const u8 *resolutions;
unsigned int default_sample_time;
u8 num_sample_times;
const unsigned int *sample_times;
+ bool alarm;
};
/* Addresses scanned */
@@ -104,8 +109,8 @@ struct lm75_data {
struct i2c_client *client;
struct regmap *regmap;
struct regulator *vs;
- u8 orig_conf;
- u8 current_conf;
+ u16 orig_conf;
+ u16 current_conf;
u8 resolution; /* In bits, 9 to 16 */
unsigned int sample_time; /* In ms */
enum lm75_type kind;
@@ -128,6 +133,15 @@ static const struct lm75_params device_params[] = {
.default_resolution = 12,
.default_sample_time = MSEC_PER_SEC / 10,
},
+ [as6200] = {
+ .config_reg_16bits = true,
+ .set_mask = 0x94C0, /* 8 sample/s, 4 CF, positive polarity */
+ .default_resolution = 12,
+ .default_sample_time = 125,
+ .num_sample_times = 4,
+ .sample_times = (unsigned int []){ 125, 250, 1000, 4000 },
+ .alarm = true,
+ },
[at30ts74] = {
.set_mask = 3 << 5, /* 12-bit mode*/
.default_resolution = 12,
@@ -255,8 +269,9 @@ static const struct lm75_params device_params[] = {
.resolutions = (u8 []) {9, 10, 11, 12 },
},
[tmp112] = {
- .set_mask = 3 << 5, /* 8 samples / second */
- .clr_mask = 1 << 7, /* no one-shot mode*/
+ .config_reg_16bits = true,
+ .set_mask = 0x60C0, /* 12-bit mode, 8 samples / second */
+ .clr_mask = 1 << 15, /* no one-shot mode*/
.default_resolution = 12,
.default_sample_time = 125,
.num_sample_times = 4,
@@ -317,20 +332,23 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
}
-static int lm75_write_config(struct lm75_data *data, u8 set_mask,
- u8 clr_mask)
+static int lm75_write_config(struct lm75_data *data, u16 set_mask,
+ u16 clr_mask)
{
- u8 value;
+ unsigned int value;
- clr_mask |= LM75_SHUTDOWN;
+ clr_mask |= LM75_SHUTDOWN << (8 * data->params->config_reg_16bits);
value = data->current_conf & ~clr_mask;
value |= set_mask;
if (data->current_conf != value) {
s32 err;
-
- err = i2c_smbus_write_byte_data(data->client, LM75_REG_CONF,
- value);
+ if (data->params->config_reg_16bits)
+ err = regmap_write(data->regmap, LM75_REG_CONF, value);
+ else
+ err = i2c_smbus_write_byte_data(data->client,
+ LM75_REG_CONF,
+ value);
if (err)
return err;
data->current_conf = value;
@@ -338,6 +356,27 @@ static int lm75_write_config(struct lm75_data *data, u8 set_mask,
return 0;
}
+static int lm75_read_config(struct lm75_data *data)
+{
+ int ret;
+ unsigned int status;
+
+ if (data->params->config_reg_16bits) {
+ ret = regmap_read(data->regmap, LM75_REG_CONF, &status);
+ return ret ? ret : status;
+ }
+
+ return i2c_smbus_read_byte_data(data->client, LM75_REG_CONF);
+}
+
+static irqreturn_t lm75_alarm_handler(int irq, void *private)
+{
+ struct device *hwmon_dev = private;
+
+ hwmon_notify_event(hwmon_dev, hwmon_temp, hwmon_temp_alarm, 0);
+ return IRQ_HANDLED;
+}
+
static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@@ -366,6 +405,9 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_temp_max_hyst:
reg = LM75_REG_HYST;
break;
+ case hwmon_temp_alarm:
+ reg = LM75_REG_CONF;
+ break;
default:
return -EINVAL;
}
@@ -373,7 +415,17 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
if (err < 0)
return err;
- *val = lm75_reg_to_mc(regval, data->resolution);
+ if (attr == hwmon_temp_alarm) {
+ switch (data->kind) {
+ case as6200:
+ *val = (regval >> 5) & 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ *val = lm75_reg_to_mc(regval, data->resolution);
+ }
break;
default:
return -EINVAL;
@@ -436,6 +488,7 @@ static int lm75_update_interval(struct device *dev, long val)
data->resolution = data->params->resolutions[index];
break;
case tmp112:
+ case as6200:
err = regmap_read(data->regmap, LM75_REG_CONF, &reg);
if (err < 0)
return err;
@@ -503,6 +556,10 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type,
case hwmon_temp_max:
case hwmon_temp_max_hyst:
return 0644;
+ case hwmon_temp_alarm:
+ if (config_data->params->alarm)
+ return 0444;
+ break;
}
break;
default:
@@ -515,7 +572,8 @@ static const struct hwmon_channel_info * const lm75_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
- HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
+ HWMON_T_ALARM),
NULL
};
@@ -623,7 +681,7 @@ static int lm75_probe(struct i2c_client *client)
return err;
/* Cache original configuration */
- status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
+ status = lm75_read_config(data);
if (status < 0) {
dev_dbg(dev, "Can't read config? %d\n", status);
return status;
@@ -646,6 +704,23 @@ static int lm75_probe(struct i2c_client *client)
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
+ if (client->irq) {
+ if (data->params->alarm) {
+ err = devm_request_threaded_irq(dev,
+ client->irq,
+ NULL,
+ &lm75_alarm_handler,
+ IRQF_ONESHOT,
+ client->name,
+ hwmon_dev);
+ if (err)
+ return err;
+ } else {
+ /* alarm is only supported for chips with alarm bit */
+ dev_err(dev, "alarm interrupt is not supported\n");
+ }
+ }
+
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
return 0;
@@ -653,6 +728,7 @@ static int lm75_probe(struct i2c_client *client)
static const struct i2c_device_id lm75_ids[] = {
{ "adt75", adt75, },
+ { "as6200", as6200, },
{ "at30ts74", at30ts74, },
{ "ds1775", ds1775, },
{ "ds75", ds75, },
@@ -690,6 +766,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
.data = (void *)adt75
},
{
+ .compatible = "ams,as6200",
+ .data = (void *)as6200
+ },
+ {
.compatible = "atmel,at30ts74",
.data = (void *)at30ts74
},
diff --git a/drivers/hwmon/ltc2991.c b/drivers/hwmon/ltc2991.c
index fc53fdcb2b6c..80a6e391f266 100644
--- a/drivers/hwmon/ltc2991.c
+++ b/drivers/hwmon/ltc2991.c
@@ -54,7 +54,6 @@
#define LTC2991_VCC_CH_NR 0
struct ltc2991_state {
- struct device *dev;
struct regmap *regmap;
u32 r_sense_uohm[LTC2991_MAX_CHANNEL];
bool temp_en[LTC2991_MAX_CHANNEL];
@@ -283,19 +282,19 @@ static const struct regmap_config ltc2991_regmap_config = {
.max_register = 0x1D,
};
-static int ltc2991_init(struct ltc2991_state *st)
+static int ltc2991_init(struct ltc2991_state *st, struct device *dev)
{
struct fwnode_handle *child;
int ret;
u32 val, addr;
u8 v5_v8_reg_data = 0, v1_v4_reg_data = 0;
- ret = devm_regulator_get_enable(st->dev, "vcc");
+ ret = devm_regulator_get_enable(dev, "vcc");
if (ret)
- return dev_err_probe(st->dev, ret,
+ return dev_err_probe(dev, ret,
"failed to enable regulator\n");
- device_for_each_child_node(st->dev, child) {
+ device_for_each_child_node(dev, child) {
ret = fwnode_property_read_u32(child, "reg", &addr);
if (ret < 0) {
fwnode_handle_put(child);
@@ -312,7 +311,7 @@ static int ltc2991_init(struct ltc2991_state *st)
&val);
if (!ret) {
if (!val)
- return dev_err_probe(st->dev, -EINVAL,
+ return dev_err_probe(dev, -EINVAL,
"shunt resistor value cannot be zero\n");
st->r_sense_uohm[addr] = val;
@@ -361,18 +360,18 @@ static int ltc2991_init(struct ltc2991_state *st)
ret = regmap_write(st->regmap, LTC2991_V5_V8_CTRL, v5_v8_reg_data);
if (ret)
- return dev_err_probe(st->dev, ret,
+ return dev_err_probe(dev, ret,
"Error: Failed to set V5-V8 CTRL reg.\n");
ret = regmap_write(st->regmap, LTC2991_V1_V4_CTRL, v1_v4_reg_data);
if (ret)
- return dev_err_probe(st->dev, ret,
+ return dev_err_probe(dev, ret,
"Error: Failed to set V1-V4 CTRL reg.\n");
ret = regmap_write(st->regmap, LTC2991_PWM_TH_LSB_T_INT,
LTC2991_REPEAT_ACQ_EN);
if (ret)
- return dev_err_probe(st->dev, ret,
+ return dev_err_probe(dev, ret,
"Error: Failed to set continuous mode.\n");
/* Enable all channels and trigger conversions */
@@ -392,12 +391,11 @@ static int ltc2991_i2c_probe(struct i2c_client *client)
if (!st)
return -ENOMEM;
- st->dev = &client->dev;
st->regmap = devm_regmap_init_i2c(client, &ltc2991_regmap_config);
if (IS_ERR(st->regmap))
return PTR_ERR(st->regmap);
- ret = ltc2991_init(st);
+ ret = ltc2991_init(st, &client->dev);
if (ret)
return ret;
diff --git a/drivers/hwmon/max31827.c b/drivers/hwmon/max31827.c
index a1ce65145669..4a8c3e37c5d3 100644
--- a/drivers/hwmon/max31827.c
+++ b/drivers/hwmon/max31827.c
@@ -11,6 +11,7 @@
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
+#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -23,15 +24,30 @@
#define MAX31827_CONFIGURATION_1SHOT_MASK BIT(0)
#define MAX31827_CONFIGURATION_CNV_RATE_MASK GENMASK(3, 1)
+#define MAX31827_CONFIGURATION_TIMEOUT_MASK BIT(5)
+#define MAX31827_CONFIGURATION_RESOLUTION_MASK GENMASK(7, 6)
+#define MAX31827_CONFIGURATION_ALRM_POL_MASK BIT(8)
+#define MAX31827_CONFIGURATION_COMP_INT_MASK BIT(9)
+#define MAX31827_CONFIGURATION_FLT_Q_MASK GENMASK(11, 10)
#define MAX31827_CONFIGURATION_U_TEMP_STAT_MASK BIT(14)
#define MAX31827_CONFIGURATION_O_TEMP_STAT_MASK BIT(15)
+#define MAX31827_ALRM_POL_LOW 0x0
+#define MAX31827_ALRM_POL_HIGH 0x1
+#define MAX31827_FLT_Q_1 0x0
+#define MAX31827_FLT_Q_4 0x2
+
+#define MAX31827_8_BIT_CNV_TIME 9
+#define MAX31827_9_BIT_CNV_TIME 18
+#define MAX31827_10_BIT_CNV_TIME 35
#define MAX31827_12_BIT_CNV_TIME 140
#define MAX31827_16_BIT_TO_M_DGR(x) (sign_extend32(x, 15) * 1000 / 16)
#define MAX31827_M_DGR_TO_16_BIT(x) (((x) << 4) / 1000)
#define MAX31827_DEVICE_ENABLE(x) ((x) ? 0xA : 0x0)
+enum chips { max31827 = 1, max31828, max31829 };
+
enum max31827_cnv {
MAX31827_CNV_1_DIV_64_HZ = 1,
MAX31827_CNV_1_DIV_32_HZ,
@@ -52,6 +68,27 @@ static const u16 max31827_conversions[] = {
[MAX31827_CNV_8_HZ] = 125,
};
+enum max31827_resolution {
+ MAX31827_RES_8_BIT = 0,
+ MAX31827_RES_9_BIT,
+ MAX31827_RES_10_BIT,
+ MAX31827_RES_12_BIT,
+};
+
+static const u16 max31827_resolutions[] = {
+ [MAX31827_RES_8_BIT] = 1000,
+ [MAX31827_RES_9_BIT] = 500,
+ [MAX31827_RES_10_BIT] = 250,
+ [MAX31827_RES_12_BIT] = 62,
+};
+
+static const u16 max31827_conv_times[] = {
+ [MAX31827_RES_8_BIT] = MAX31827_8_BIT_CNV_TIME,
+ [MAX31827_RES_9_BIT] = MAX31827_9_BIT_CNV_TIME,
+ [MAX31827_RES_10_BIT] = MAX31827_10_BIT_CNV_TIME,
+ [MAX31827_RES_12_BIT] = MAX31827_12_BIT_CNV_TIME,
+};
+
struct max31827_state {
/*
* Prevent simultaneous access to the i2c client.
@@ -59,6 +96,8 @@ struct max31827_state {
struct mutex lock;
struct regmap *regmap;
bool enable;
+ unsigned int resolution;
+ unsigned int update_interval;
};
static const struct regmap_config max31827_regmap = {
@@ -68,16 +107,16 @@ static const struct regmap_config max31827_regmap = {
};
static int shutdown_write(struct max31827_state *st, unsigned int reg,
- unsigned int val)
+ unsigned int mask, unsigned int val)
{
unsigned int cfg;
unsigned int cnv_rate;
int ret;
/*
- * Before the Temperature Threshold Alarm and Alarm Hysteresis Threshold
- * register values are changed over I2C, the part must be in shutdown
- * mode.
+ * Before the Temperature Threshold Alarm, Alarm Hysteresis Threshold
+ * and Resolution bits from Configuration register are changed over I2C,
+ * the part must be in shutdown mode.
*
* Mutex is used to ensure, that some other process doesn't change the
* configuration register.
@@ -85,7 +124,10 @@ static int shutdown_write(struct max31827_state *st, unsigned int reg,
mutex_lock(&st->lock);
if (!st->enable) {
- ret = regmap_write(st->regmap, reg, val);
+ if (!mask)
+ ret = regmap_write(st->regmap, reg, val);
+ else
+ ret = regmap_update_bits(st->regmap, reg, mask, val);
goto unlock;
}
@@ -100,7 +142,11 @@ static int shutdown_write(struct max31827_state *st, unsigned int reg,
if (ret)
goto unlock;
- ret = regmap_write(st->regmap, reg, val);
+ if (!mask)
+ ret = regmap_write(st->regmap, reg, val);
+ else
+ ret = regmap_update_bits(st->regmap, reg, mask, val);
+
if (ret)
goto unlock;
@@ -118,7 +164,7 @@ static int write_alarm_val(struct max31827_state *st, unsigned int reg,
{
val = MAX31827_M_DGR_TO_16_BIT(val);
- return shutdown_write(st, reg, val);
+ return shutdown_write(st, reg, 0, val);
}
static umode_t max31827_is_visible(const void *state,
@@ -188,9 +234,18 @@ static int max31827_read(struct device *dev, enum hwmon_sensor_types type,
mutex_unlock(&st->lock);
return ret;
}
-
- msleep(MAX31827_12_BIT_CNV_TIME);
+ msleep(max31827_conv_times[st->resolution]);
}
+
+ /*
+ * For 12-bit resolution the conversion time is 140 ms,
+ * thus an additional 15 ms is needed to complete the
+ * conversion: 125 ms + 15 ms = 140 ms
+ */
+ if (max31827_resolutions[st->resolution] == 12 &&
+ st->update_interval == 125)
+ usleep_range(15000, 20000);
+
ret = regmap_read(st->regmap, MAX31827_T_REG, &uval);
mutex_unlock(&st->lock);
@@ -341,17 +396,20 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
val < max31827_conversions[res])
res++;
- if (res == ARRAY_SIZE(max31827_conversions) ||
- val != max31827_conversions[res])
- return -EINVAL;
+ if (res == ARRAY_SIZE(max31827_conversions))
+ res = ARRAY_SIZE(max31827_conversions) - 1;
res = FIELD_PREP(MAX31827_CONFIGURATION_CNV_RATE_MASK,
res);
- return regmap_update_bits(st->regmap,
- MAX31827_CONFIGURATION_REG,
- MAX31827_CONFIGURATION_CNV_RATE_MASK,
- res);
+ ret = regmap_update_bits(st->regmap,
+ MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_CNV_RATE_MASK,
+ res);
+ if (ret)
+ return ret;
+
+ st->update_interval = val;
}
break;
@@ -359,17 +417,165 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type,
return -EOPNOTSUPP;
}
- return -EOPNOTSUPP;
+ return 0;
+}
+
+static ssize_t temp1_resolution_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct max31827_state *st = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, MAX31827_CONFIGURATION_REG, &val);
+ if (ret)
+ return ret;
+
+ val = FIELD_GET(MAX31827_CONFIGURATION_RESOLUTION_MASK, val);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", max31827_resolutions[val]);
}
-static int max31827_init_client(struct max31827_state *st)
+static ssize_t temp1_resolution_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
{
+ struct max31827_state *st = dev_get_drvdata(dev);
+ unsigned int idx = 0;
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ /*
+ * Convert the desired resolution into register
+ * bits. idx is already initialized with 0.
+ *
+ * This was inspired by lm73 driver.
+ */
+ while (idx < ARRAY_SIZE(max31827_resolutions) &&
+ val < max31827_resolutions[idx])
+ idx++;
+
+ if (idx == ARRAY_SIZE(max31827_resolutions))
+ idx = ARRAY_SIZE(max31827_resolutions) - 1;
+
+ st->resolution = idx;
+
+ ret = shutdown_write(st, MAX31827_CONFIGURATION_REG,
+ MAX31827_CONFIGURATION_RESOLUTION_MASK,
+ FIELD_PREP(MAX31827_CONFIGURATION_RESOLUTION_MASK,
+ idx));
+
+ return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RW(temp1_resolution);
+
+static struct attribute *max31827_attrs[] = {
+ &dev_attr_temp1_resolution.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(max31827);
+
+static const struct i2c_device_id max31827_i2c_ids[] = {
+ { "max31827", max31827 },
+ { "max31828", max31828 },
+ { "max31829", max31829 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids);
+
+static int max31827_init_client(struct max31827_state *st,
+ struct device *dev)
+{
+ struct fwnode_handle *fwnode;
+ unsigned int res = 0;
+ u32 data, lsb_idx;
+ enum chips type;
+ bool prop;
+ int ret;
+
+ fwnode = dev_fwnode(dev);
+
st->enable = true;
+ res |= MAX31827_DEVICE_ENABLE(1);
- return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG,
- MAX31827_CONFIGURATION_1SHOT_MASK |
- MAX31827_CONFIGURATION_CNV_RATE_MASK,
- MAX31827_DEVICE_ENABLE(1));
+ res |= MAX31827_CONFIGURATION_RESOLUTION_MASK;
+
+ prop = fwnode_property_read_bool(fwnode, "adi,comp-int");
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_COMP_INT_MASK, prop);
+
+ prop = fwnode_property_read_bool(fwnode, "adi,timeout-enable");
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_TIMEOUT_MASK, !prop);
+
+ type = (enum chips)(uintptr_t)device_get_match_data(dev);
+
+ if (fwnode_property_present(fwnode, "adi,alarm-pol")) {
+ ret = fwnode_property_read_u32(fwnode, "adi,alarm-pol", &data);
+ if (ret)
+ return ret;
+
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK, !!data);
+ } else {
+ /*
+ * Set default value.
+ */
+ switch (type) {
+ case max31827:
+ case max31828:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK,
+ MAX31827_ALRM_POL_LOW);
+ break;
+ case max31829:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK,
+ MAX31827_ALRM_POL_HIGH);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (fwnode_property_present(fwnode, "adi,fault-q")) {
+ ret = fwnode_property_read_u32(fwnode, "adi,fault-q", &data);
+ if (ret)
+ return ret;
+
+ /*
+ * Convert the desired fault queue into register bits.
+ */
+ if (data != 0)
+ lsb_idx = __ffs(data);
+
+ if (hweight32(data) != 1 || lsb_idx > 4) {
+ dev_err(dev, "Invalid data in adi,fault-q\n");
+ return -EINVAL;
+ }
+
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK, lsb_idx);
+ } else {
+ /*
+ * Set default value.
+ */
+ switch (type) {
+ case max31827:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK,
+ MAX31827_FLT_Q_1);
+ break;
+ case max31828:
+ case max31829:
+ res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK,
+ MAX31827_FLT_Q_4);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, res);
}
static const struct hwmon_channel_info *max31827_info[] = {
@@ -417,25 +623,30 @@ static int max31827_probe(struct i2c_client *client)
if (err)
return dev_err_probe(dev, err, "failed to enable regulator\n");
- err = max31827_init_client(st);
+ err = max31827_init_client(st, dev);
if (err)
return err;
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, st,
&max31827_chip_info,
- NULL);
+ max31827_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
-static const struct i2c_device_id max31827_i2c_ids[] = {
- { "max31827", 0 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids);
-
static const struct of_device_id max31827_of_match[] = {
- { .compatible = "adi,max31827" },
+ {
+ .compatible = "adi,max31827",
+ .data = (void *)max31827
+ },
+ {
+ .compatible = "adi,max31828",
+ .data = (void *)max31828
+ },
+ {
+ .compatible = "adi,max31829",
+ .data = (void *)max31829
+ },
{ }
};
MODULE_DEVICE_TABLE(of, max31827_of_match);
diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c
index cc8428a3045d..9649c6611d5f 100644
--- a/drivers/hwmon/max6650.c
+++ b/drivers/hwmon/max6650.c
@@ -26,7 +26,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/thermal.h>
/*
@@ -763,8 +763,6 @@ static int max6650_probe(struct i2c_client *client)
{
struct thermal_cooling_device *cooling_dev;
struct device *dev = &client->dev;
- const struct of_device_id *of_id =
- of_match_device(of_match_ptr(max6650_dt_match), dev);
struct max6650_data *data;
struct device *hwmon_dev;
int err;
@@ -776,8 +774,8 @@ static int max6650_probe(struct i2c_client *client)
data->client = client;
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
- data->nr_fans = of_id ? (int)(uintptr_t)of_id->data :
- i2c_match_id(max6650_id, client)->driver_data;
+
+ data->nr_fans = (uintptr_t)i2c_get_match_data(client);
/*
* Initialize the max6650 chip
diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c
index d928eb8ae5a3..8d2ef3145bca 100644
--- a/drivers/hwmon/nct6775-core.c
+++ b/drivers/hwmon/nct6775-core.c
@@ -63,19 +63,19 @@
/* used to set data->name = nct6775_device_names[data->sio_kind] */
static const char * const nct6775_device_names[] = {
- "nct6106",
- "nct6116",
- "nct6775",
- "nct6776",
- "nct6779",
- "nct6791",
- "nct6792",
- "nct6793",
- "nct6795",
- "nct6796",
- "nct6797",
- "nct6798",
- "nct6799",
+ [nct6106] = "nct6106",
+ [nct6116] = "nct6116",
+ [nct6775] = "nct6775",
+ [nct6776] = "nct6776",
+ [nct6779] = "nct6779",
+ [nct6791] = "nct6791",
+ [nct6792] = "nct6792",
+ [nct6793] = "nct6793",
+ [nct6795] = "nct6795",
+ [nct6796] = "nct6796",
+ [nct6797] = "nct6797",
+ [nct6798] = "nct6798",
+ [nct6799] = "nct6799",
};
/* Common and NCT6775 specific data */
@@ -767,9 +767,9 @@ static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 };
static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 };
static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 };
-static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 };
-static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 };
-static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c };
+static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3, 0, 0 };
+static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04, 0, 0 };
+static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c, 0xd8, 0xd9 };
static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 };
static const u16 NCT6106_REG_TEMP_SOURCE[] = {
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 };
@@ -2553,6 +2553,13 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf,
int err;
u16 reg;
+ /*
+ * The fan control mode should be set to manual if the user wants to adjust
+ * the fan speed. Otherwise, it will fail to set.
+ */
+ if (index == 0 && data->pwm_enable[nr] > manual)
+ return -EBUSY;
+
err = kstrtoul(buf, 10, &val);
if (err < 0)
return err;
@@ -3595,7 +3602,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data,
break;
case nct6116:
data->in_num = 9;
- data->pwm_num = 3;
+ data->pwm_num = 5;
data->auto_pwm_num = 4;
data->temp_fixed_num = 3;
data->num_temp_alarms = 3;
diff --git a/drivers/hwmon/nct6775-i2c.c b/drivers/hwmon/nct6775-i2c.c
index 87a4fc78c571..aff69fa50461 100644
--- a/drivers/hwmon/nct6775-i2c.c
+++ b/drivers/hwmon/nct6775-i2c.c
@@ -21,7 +21,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/regmap.h>
#include "nct6775.h"
@@ -155,23 +155,13 @@ static const struct regmap_config nct6775_i2c_regmap_config = {
static int nct6775_i2c_probe(struct i2c_client *client)
{
struct nct6775_data *data;
- const struct of_device_id *of_id;
- const struct i2c_device_id *i2c_id;
struct device *dev = &client->dev;
- of_id = of_match_device(nct6775_i2c_of_match, dev);
- i2c_id = i2c_match_id(nct6775_i2c_id, client);
-
- if (of_id && (unsigned long)of_id->data != i2c_id->driver_data)
- dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n",
- of_id->name, i2c_id->name);
-
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->kind = i2c_id->driver_data;
-
+ data->kind = (enum kinds)(uintptr_t)i2c_get_match_data(client);
data->read_only = true;
data->driver_data = client;
data->driver_init = nct6775_i2c_probe_init;
diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c
index 0adeeab7ee03..9aa4dcf4a6f3 100644
--- a/drivers/hwmon/nct6775-platform.c
+++ b/drivers/hwmon/nct6775-platform.c
@@ -23,19 +23,19 @@
enum sensor_access { access_direct, access_asuswmi };
static const char * const nct6775_sio_names[] __initconst = {
- "NCT6106D",
- "NCT6116D",
- "NCT6775F",
- "NCT6776D/F",
- "NCT6779D",
- "NCT6791D",
- "NCT6792D",
- "NCT6793D",
- "NCT6795D",
- "NCT6796D",
- "NCT6797D",
- "NCT6798D",
- "NCT6796D-S/NCT6799D-R",
+ [nct6106] = "NCT6106D",
+ [nct6116] = "NCT6116D",
+ [nct6775] = "NCT6775F",
+ [nct6776] = "NCT6776D/F",
+ [nct6779] = "NCT6779D",
+ [nct6791] = "NCT6791D",
+ [nct6792] = "NCT6792D",
+ [nct6793] = "NCT6793D",
+ [nct6795] = "NCT6795D",
+ [nct6796] = "NCT6796D",
+ [nct6797] = "NCT6797D",
+ [nct6798] = "NCT6798D",
+ [nct6799] = "NCT6796D-S/NCT6799D-R",
};
static unsigned short force_id;
diff --git a/drivers/hwmon/nct6775.h b/drivers/hwmon/nct6775.h
index 296eff99d003..d31e7a030216 100644
--- a/drivers/hwmon/nct6775.h
+++ b/drivers/hwmon/nct6775.h
@@ -4,7 +4,7 @@
#include <linux/types.h>
-enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792,
+enum kinds { nct6106 = 1, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792,
nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 };
enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 };
diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c
index 4702e4edc662..d9733da8ea34 100644
--- a/drivers/hwmon/npcm750-pwm-fan.c
+++ b/drivers/hwmon/npcm750-pwm-fan.c
@@ -46,9 +46,9 @@
#define NPCM7XX_PWM_CTRL_CH3_EN_BIT BIT(16)
/* Define the maximum PWM channel number */
-#define NPCM7XX_PWM_MAX_CHN_NUM 8
+#define NPCM7XX_PWM_MAX_CHN_NUM 12
#define NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE 4
-#define NPCM7XX_PWM_MAX_MODULES 2
+#define NPCM7XX_PWM_MAX_MODULES 3
/* Define the Counter Register, value = 100 for match 100% */
#define NPCM7XX_PWM_COUNTER_DEFAULT_NUM 255
@@ -171,6 +171,10 @@
#define FAN_PREPARE_TO_GET_FIRST_CAPTURE 0x01
#define FAN_ENOUGH_SAMPLE 0x02
+struct npcm_hwmon_info {
+ u32 pwm_max_channel;
+};
+
struct npcm7xx_fan_dev {
u8 fan_st_flg;
u8 fan_pls_per_rev;
@@ -204,6 +208,7 @@ struct npcm7xx_pwm_fan_data {
struct timer_list fan_timer;
struct npcm7xx_fan_dev fan_dev[NPCM7XX_FAN_MAX_CHN_NUM];
struct npcm7xx_cooling_device *cdev[NPCM7XX_PWM_MAX_CHN_NUM];
+ const struct npcm_hwmon_info *info;
u8 fan_select;
};
@@ -542,7 +547,7 @@ static umode_t npcm7xx_pwm_is_visible(const void *_data, u32 attr, int channel)
{
const struct npcm7xx_pwm_fan_data *data = _data;
- if (!data->pwm_present[channel])
+ if (!data->pwm_present[channel] || channel >= data->info->pwm_max_channel)
return 0;
switch (attr) {
@@ -638,6 +643,10 @@ static const struct hwmon_channel_info * const npcm7xx_info[] = {
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
HWMON_PWM_INPUT),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT,
@@ -670,6 +679,14 @@ static const struct hwmon_chip_info npcm7xx_chip_info = {
.info = npcm7xx_info,
};
+static const struct npcm_hwmon_info npxm7xx_hwmon_info = {
+ .pwm_max_channel = 8,
+};
+
+static const struct npcm_hwmon_info npxm8xx_hwmon_info = {
+ .pwm_max_channel = 12,
+};
+
static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data)
{
int m, ch;
@@ -925,6 +942,10 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
+ data->info = device_get_match_data(dev);
+ if (!data->info)
+ return -EINVAL;
+
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
if (!res) {
dev_err(dev, "pwm resource not found\n");
@@ -1017,7 +1038,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev)
}
static const struct of_device_id of_pwm_fan_match_table[] = {
- { .compatible = "nuvoton,npcm750-pwm-fan", },
+ { .compatible = "nuvoton,npcm750-pwm-fan", .data = &npxm7xx_hwmon_info},
+ { .compatible = "nuvoton,npcm845-pwm-fan", .data = &npxm8xx_hwmon_info},
{},
};
MODULE_DEVICE_TABLE(of, of_pwm_fan_match_table);
diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c
index 926ea1fe133c..9e9681b2e8c5 100644
--- a/drivers/hwmon/pc87360.c
+++ b/drivers/hwmon/pc87360.c
@@ -323,7 +323,11 @@ static struct pc87360_data *pc87360_update_device(struct device *dev)
}
/* Voltages */
- for (i = 0; i < data->innr; i++) {
+ /*
+ * The min() below does not have any practical meaning and is
+ * only needed to silence a warning observed with gcc 12+.
+ */
+ for (i = 0; i < min(data->innr, ARRAY_SIZE(data->in)); i++) {
data->in_status[i] = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS);
/* Clear bits */
diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c
index 5ca4d04e4b14..4a72e9712408 100644
--- a/drivers/hwmon/peci/dimmtemp.c
+++ b/drivers/hwmon/peci/dimmtemp.c
@@ -47,7 +47,7 @@
#define GET_TEMP_MAX(x) (((x) & DIMM_TEMP_MAX) >> 8)
#define GET_TEMP_CRIT(x) (((x) & DIMM_TEMP_CRIT) >> 16)
-#define NO_DIMM_RETRY_COUNT_MAX 5
+#define NO_DIMM_RETRY_COUNT_MAX 120
struct peci_dimmtemp;
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index b4e93bd5835e..294808f5240a 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -227,6 +227,16 @@ config SENSORS_LTC3815
This driver can also be built as a module. If so, the module will
be called ltc3815.
+config SENSORS_LTC4286
+ bool "Analog Devices LTC4286"
+ help
+ LTC4286 is an integrated solution for hot swap applications that
+ allows a board to be safely inserted and removed from a live
+ backplane.
+ This chip could be used to monitor voltage, current, ...etc.
+ If you say yes here you get hardware monitoring support for Analog
+ Devices LTC4286.
+
config SENSORS_MAX15301
tristate "Maxim MAX15301"
help
@@ -299,6 +309,15 @@ config SENSORS_MAX8688
This driver can also be built as a module. If so, the module will
be called max8688.
+config SENSORS_MP2856
+ tristate "MPS MP2856"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP2856 MP2857 Dual Loop Digital Multi-Phase Controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp2856.
+
config SENSORS_MP2888
tristate "MPS MP2888"
help
@@ -333,6 +352,15 @@ config SENSORS_MP5023
This driver can also be built as a module. If so, the module will
be called mp5023.
+config SENSORS_MP5990
+ tristate "MPS MP5990"
+ help
+ If you say yes here you get hardware monitoring support for MPS
+ MP5990.
+
+ This driver can also be built as a module. If so, the module will
+ be called mp5990.
+
config SENSORS_MPQ7932_REGULATOR
bool "Regulator support for MPQ7932"
depends on SENSORS_MPQ7932 && REGULATOR
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 84ee960a6c2d..cf8a76744545 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
+obj-$(CONFIG_SENSORS_LTC4286) += ltc4286.o
obj-$(CONFIG_SENSORS_MAX15301) += max15301.o
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
@@ -32,9 +33,11 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
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_MP2888) += mp2888.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
+obj-$(CONFIG_SENSORS_MP5990) += mp5990.o
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c
index 929fa6d34efd..3a20df5a43ec 100644
--- a/drivers/hwmon/pmbus/lm25066.c
+++ b/drivers/hwmon/pmbus/lm25066.c
@@ -14,10 +14,10 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/log2.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include "pmbus.h"
-enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i };
+enum chips { lm25056 = 1, lm25066, lm5064, lm5066, lm5066i };
#define LM25066_READ_VAUX 0xd0
#define LM25066_MFR_READ_IIN 0xd1
@@ -468,8 +468,6 @@ static int lm25066_probe(struct i2c_client *client)
struct lm25066_data *data;
struct pmbus_driver_info *info;
const struct __coeff *coeff;
- const struct of_device_id *of_id;
- const struct i2c_device_id *i2c_id;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA))
@@ -484,14 +482,8 @@ static int lm25066_probe(struct i2c_client *client)
if (config < 0)
return config;
- i2c_id = i2c_match_id(lm25066_id, client);
+ data->id = (enum chips)(unsigned long)i2c_get_match_data(client);
- of_id = of_match_device(lm25066_of_match, &client->dev);
- if (of_id && (unsigned long)of_id->data != i2c_id->driver_data)
- dev_notice(&client->dev, "Device mismatch: %s in device tree, %s detected\n",
- of_id->name, i2c_id->name);
-
- data->id = i2c_id->driver_data;
info = &data->info;
info->pages = 1;
diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c
new file mode 100644
index 000000000000..9e7ceeb7e789
--- /dev/null
+++ b/drivers/hwmon/pmbus/ltc4286.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include "pmbus.h"
+
+/* LTC4286 register */
+#define LTC4286_MFR_CONFIG1 0xF2
+
+/* LTC4286 configuration */
+#define VRANGE_SELECT_BIT BIT(1)
+
+#define LTC4286_MFR_ID_SIZE 3
+
+/*
+ * Initialize the MBR as default settings which is referred to LTC4286 datasheet
+ * (March 22, 2022 version) table 3 page 16
+ */
+static struct pmbus_driver_info ltc4286_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 32,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_VOLTAGE_OUT] = 32,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 1,
+ .m[PSC_CURRENT_OUT] = 1024,
+ .b[PSC_CURRENT_OUT] = 0,
+ /*
+ * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit.
+ * However, the rsense value that user input is micro ohm.
+ * Thus, the MBR setting which involves rsense should be shifted by 6 digits.
+ */
+ .R[PSC_CURRENT_OUT] = 3 - 6,
+ .m[PSC_POWER] = 1,
+ .b[PSC_POWER] = 0,
+ /*
+ * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit.
+ * However, the rsense value that user input is micro ohm.
+ * Thus, the MBR setting which involves rsense should be shifted by 6 digits.
+ */
+ .R[PSC_POWER] = 4 - 6,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 273,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static const struct i2c_device_id ltc4286_id[] = {
+ { "ltc4286", 0 },
+ { "ltc4287", 1 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ltc4286_id);
+
+static int ltc4286_probe(struct i2c_client *client)
+{
+ int ret;
+ const struct i2c_device_id *mid;
+ u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
+ struct pmbus_driver_info *info;
+ u32 rsense;
+ int vrange_nval, vrange_oval;
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer);
+ if (ret < 0) {
+ return dev_err_probe(&client->dev, ret,
+ "Failed to read manufacturer id\n");
+ }
+
+ /*
+ * Refer to ltc4286 datasheet page 20
+ * the manufacturer id is LTC
+ */
+ if (ret != LTC4286_MFR_ID_SIZE ||
+ strncmp(block_buffer, "LTC", LTC4286_MFR_ID_SIZE)) {
+ return dev_err_probe(&client->dev, -ENODEV,
+ "Manufacturer id mismatch\n");
+ }
+
+ ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
+ if (ret < 0) {
+ return dev_err_probe(&client->dev, ret,
+ "Failed to read manufacturer model\n");
+ }
+
+ for (mid = ltc4286_id; mid->name[0]; mid++) {
+ if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
+ break;
+ }
+ if (!mid->name[0])
+ return dev_err_probe(&client->dev, -ENODEV,
+ "Unsupported device\n");
+
+ if (of_property_read_u32(client->dev.of_node,
+ "shunt-resistor-micro-ohms", &rsense))
+ rsense = 300; /* 0.3 mOhm if not set via DT */
+
+ if (rsense == 0)
+ return -EINVAL;
+
+ /* Check for the latter MBR value won't overflow */
+ if (rsense > (INT_MAX / 1024))
+ return -EINVAL;
+
+ info = devm_kmemdup(&client->dev, &ltc4286_info, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ /* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */
+ vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1);
+ if (vrange_oval < 0)
+ return dev_err_probe(&client->dev, vrange_oval,
+ "Failed to read manufacturer configuration one\n");
+ vrange_nval = vrange_oval;
+
+ if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) {
+ vrange_nval &=
+ ~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */
+
+ info->m[PSC_VOLTAGE_IN] = 128;
+ info->m[PSC_VOLTAGE_OUT] = 128;
+ info->m[PSC_POWER] = 4 * rsense;
+ } else {
+ vrange_nval |=
+ VRANGE_SELECT_BIT; /* VRANGE_SELECT = 1, 102.4 volts */
+
+ info->m[PSC_POWER] = rsense;
+ }
+ if (vrange_nval != vrange_oval) {
+ /* Set MFR1 CONFIG register bit 1 VRANGE_SELECT */
+ ret = i2c_smbus_write_word_data(client, LTC4286_MFR_CONFIG1,
+ vrange_nval);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to set vrange\n");
+ }
+
+ info->m[PSC_CURRENT_OUT] = 1024 * rsense;
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id ltc4286_of_match[] = {
+ { .compatible = "lltc,ltc4286" },
+ { .compatible = "lltc,ltc4287" },
+ {}
+};
+
+static struct i2c_driver ltc4286_driver = {
+ .driver = {
+ .name = "ltc4286",
+ .of_match_table = ltc4286_of_match,
+ },
+ .probe = ltc4286_probe,
+ .id_table = ltc4286_id,
+};
+
+module_i2c_driver(ltc4286_driver);
+
+MODULE_AUTHOR("Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>");
+MODULE_DESCRIPTION("PMBUS driver for LTC4286 and compatibles");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/pmbus/mp2856.c b/drivers/hwmon/pmbus/mp2856.c
new file mode 100644
index 000000000000..6969350f5d7d
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp2856.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS2856/2857
+ * Monolithic Power Systems VR Controllers
+ *
+ * Copyright (C) 2023 Quanta Computer lnc.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include "pmbus.h"
+
+/* Vendor specific registers. */
+#define MP2856_MFR_VR_MULTI_CONFIG_R1 0x0d
+#define MP2856_MFR_VR_MULTI_CONFIG_R2 0x1d
+
+#define MP2856_MUL1_BOOT_SR_R2 0x10
+#define MP2856_VR_ACTIVE BIT(15)
+
+#define MP2856_MFR_VR_CONFIG2 0x5e
+#define MP2856_VOUT_MODE BIT(11)
+
+#define MP2856_MFR_VR_CONFIG1 0x68
+#define MP2856_DRMOS_KCS GENMASK(13, 12)
+
+#define MP2856_MFR_READ_CS1_2_R1 0x82
+#define MP2856_MFR_READ_CS3_4_R1 0x83
+#define MP2856_MFR_READ_CS5_6_R1 0x84
+#define MP2856_MFR_READ_CS7_8_R1 0x85
+#define MP2856_MFR_READ_CS9_10_R1 0x86
+#define MP2856_MFR_READ_CS11_12_R1 0x87
+
+#define MP2856_MFR_READ_CS1_2_R2 0x85
+#define MP2856_MFR_READ_CS3_4_R2 0x86
+#define MP2856_MFR_READ_CS5_6_R2 0x87
+
+#define MP2856_MAX_PHASE_RAIL1 8
+#define MP2856_MAX_PHASE_RAIL2 4
+
+#define MP2857_MAX_PHASE_RAIL1 12
+#define MP2857_MAX_PHASE_RAIL2 4
+
+#define MP2856_PAGE_NUM 2
+
+enum chips { mp2856 = 1, mp2857 };
+
+static const int mp2856_max_phases[][MP2856_PAGE_NUM] = {
+ [mp2856] = { MP2856_MAX_PHASE_RAIL1, MP2856_MAX_PHASE_RAIL2 },
+ [mp2857] = { MP2857_MAX_PHASE_RAIL1, MP2857_MAX_PHASE_RAIL2 },
+};
+
+static const struct i2c_device_id mp2856_id[] = {
+ {"mp2856", mp2856},
+ {"mp2857", mp2857},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, mp2856_id);
+
+struct mp2856_data {
+ struct pmbus_driver_info info;
+ int vout_format[MP2856_PAGE_NUM];
+ int curr_sense_gain[MP2856_PAGE_NUM];
+ int max_phases[MP2856_PAGE_NUM];
+ enum chips chip_id;
+};
+
+#define to_mp2856_data(x) container_of(x, struct mp2856_data, info)
+
+#define MAX_LIN_MANTISSA (1023 * 1000)
+#define MIN_LIN_MANTISSA (511 * 1000)
+
+static u16 val2linear11(s64 val)
+{
+ s16 exponent = 0, mantissa;
+ bool negative = false;
+
+ if (val == 0)
+ return 0;
+
+ if (val < 0) {
+ negative = true;
+ val = -val;
+ }
+
+ /* Reduce large mantissa until it fits into 10 bit */
+ while (val >= MAX_LIN_MANTISSA && exponent < 15) {
+ exponent++;
+ val >>= 1;
+ }
+ /* Increase small mantissa to improve precision */
+ while (val < MIN_LIN_MANTISSA && exponent > -15) {
+ exponent--;
+ val <<= 1;
+ }
+
+ /* Convert mantissa from milli-units to units */
+ mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff);
+
+ /* restore sign */
+ if (negative)
+ mantissa = -mantissa;
+
+ /* Convert to 5 bit exponent, 11 bit mantissa */
+ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
+}
+
+static int
+mp2856_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg,
+ u16 mask)
+{
+ int ret = pmbus_read_word_data(client, page, phase, reg);
+
+ return (ret > 0) ? ret & mask : ret;
+}
+
+static int
+mp2856_read_vout(struct i2c_client *client, struct mp2856_data *data, int page,
+ int phase, u8 reg)
+{
+ int ret;
+
+ ret = mp2856_read_word_helper(client, page, phase, reg,
+ GENMASK(9, 0));
+ if (ret < 0)
+ return ret;
+
+ /* convert vout result to direct format */
+ ret = (data->vout_format[page] == vid) ?
+ ((ret + 49) * 5) : ((ret * 1000) >> 8);
+
+ return ret;
+}
+
+static int
+mp2856_read_phase(struct i2c_client *client, struct mp2856_data *data,
+ int page, int phase, u8 reg)
+{
+ int ret;
+ int val;
+
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+
+ if (!((phase + 1) % MP2856_PAGE_NUM))
+ ret >>= 8;
+ ret &= 0xff;
+
+ /*
+ * Output value is calculated as: (READ_CSx * 12.5mV - 1.23V) / (Kcs * Rcs)
+ */
+ val = (ret * 125) - 12300;
+
+ return val2linear11(val);
+}
+
+static int
+mp2856_read_phases(struct i2c_client *client, struct mp2856_data *data,
+ int page, int phase)
+{
+ int ret;
+
+ if (page == 0) {
+ switch (phase) {
+ case 0 ... 1:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R1);
+ break;
+ case 2 ... 3:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS3_4_R1);
+ break;
+ case 4 ... 5:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS5_6_R1);
+ break;
+ case 6 ... 7:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS7_8_R1);
+ break;
+ default:
+ return -ENODATA;
+ }
+ } else {
+ switch (phase) {
+ case 0 ... 1:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R2);
+ break;
+ case 2 ... 3:
+ ret = mp2856_read_phase(client, data, page, phase,
+ MP2856_MFR_READ_CS1_2_R2);
+ break;
+ default:
+ return -ENODATA;
+ }
+ }
+ return ret;
+}
+
+static int
+mp2856_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 mp2856_data *data = to_mp2856_data(info);
+ int ret;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = mp2856_read_vout(client, data, page, phase, reg);
+ break;
+ case PMBUS_READ_IOUT:
+ if (phase != 0xff)
+ ret = mp2856_read_phases(client, data, page, phase);
+ else
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+static int
+mp2856_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ /* Enforce VOUT direct format. */
+ return PB_VOUT_MODE_DIRECT;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int
+mp2856_identify_multiphase(struct i2c_client *client, u8 reg, u8 max_phase,
+ u16 mask)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= mask;
+ return (ret >= max_phase) ? max_phase : ret;
+}
+
+static int
+mp2856_identify_multiphase_rail1(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int ret, i;
+
+ ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R1,
+ MP2856_MAX_PHASE_RAIL1, GENMASK(3, 0));
+ if (ret < 0)
+ return ret;
+
+ data->info.phases[0] = (ret > data->max_phases[0]) ?
+ data->max_phases[0] : ret;
+
+ for (i = 0 ; i < data->info.phases[0]; i++)
+ data->info.pfunc[i] |= PMBUS_HAVE_IOUT;
+
+ return 0;
+}
+
+static int
+mp2856_identify_multiphase_rail2(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int ret, i;
+
+ ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R2,
+ MP2856_MAX_PHASE_RAIL2, GENMASK(2, 0));
+ if (ret < 0)
+ return ret;
+
+ data->info.phases[1] = (ret > data->max_phases[1]) ?
+ data->max_phases[1] : ret;
+
+ for (i = 0 ; i < data->info.phases[0]; i++)
+ data->info.pfunc[i] |= PMBUS_HAVE_IOUT;
+
+ return 0;
+}
+
+static int
+mp2856_current_sense_gain_get(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int i, ret;
+
+ /*
+ * Obtain DrMOS current sense gain of power stage from the register
+ * MP2856_MFR_VR_CONFIG1, bits 13-12. The value is selected as below:
+ * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other
+ * values are invalid.
+ */
+ for (i = 0 ; i < data->info.pages; i++) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ if (ret < 0)
+ return ret;
+ ret = i2c_smbus_read_word_data(client,
+ MP2856_MFR_VR_CONFIG1);
+ if (ret < 0)
+ return ret;
+
+ switch ((ret & MP2856_DRMOS_KCS) >> 12) {
+ case 0:
+ data->curr_sense_gain[i] = 50;
+ break;
+ case 1:
+ data->curr_sense_gain[i] = 85;
+ break;
+ case 2:
+ data->curr_sense_gain[i] = 97;
+ break;
+ default:
+ data->curr_sense_gain[i] = 100;
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+mp2856_identify_vout_format(struct i2c_client *client,
+ struct mp2856_data *data)
+{
+ int i, ret;
+
+ for (i = 0; i < data->info.pages; i++) {
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_word_data(client, MP2856_MFR_VR_CONFIG2);
+ if (ret < 0)
+ return ret;
+
+ data->vout_format[i] = (ret & MP2856_VOUT_MODE) ? linear : vid;
+ }
+ return 0;
+}
+
+static bool
+mp2856_is_rail2_active(struct i2c_client *client)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
+ if (ret < 0)
+ return true;
+
+ ret = i2c_smbus_read_word_data(client, MP2856_MUL1_BOOT_SR_R2);
+ if (ret < 0)
+ return true;
+
+ return (ret & MP2856_VR_ACTIVE) ? true : false;
+}
+
+static struct pmbus_driver_info mp2856_info = {
+ .pages = MP2856_PAGE_NUM,
+ .format[PSC_VOLTAGE_IN] = linear,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_TEMPERATURE] = linear,
+ .format[PSC_CURRENT_IN] = linear,
+ .format[PSC_CURRENT_OUT] = linear,
+ .format[PSC_POWER] = linear,
+ .m[PSC_VOLTAGE_OUT] = 1,
+ .R[PSC_VOLTAGE_OUT] = 3,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT |
+ PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
+ .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP,
+ .read_byte_data = mp2856_read_byte_data,
+ .read_word_data = mp2856_read_word_data,
+};
+
+static int mp2856_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp2856_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp2856_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client);
+
+ memcpy(data->max_phases, mp2856_max_phases[data->chip_id],
+ sizeof(data->max_phases));
+
+ memcpy(&data->info, &mp2856_info, sizeof(*info));
+ info = &data->info;
+
+ /* Identify multiphase configuration. */
+ ret = mp2856_identify_multiphase_rail1(client, data);
+ if (ret < 0)
+ return ret;
+
+ if (mp2856_is_rail2_active(client)) {
+ ret = mp2856_identify_multiphase_rail2(client, data);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* rail2 is not active */
+ info->pages = 1;
+ }
+
+ /* Obtain current sense gain of power stage. */
+ ret = mp2856_current_sense_gain_get(client, data);
+ if (ret)
+ return ret;
+
+ /* Identify vout format. */
+ ret = mp2856_identify_vout_format(client, data);
+ if (ret)
+ return ret;
+
+ /* set the device to page 0 */
+ i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
+
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id __maybe_unused mp2856_of_match[] = {
+ {.compatible = "mps,mp2856", .data = (void *)mp2856},
+ {.compatible = "mps,mp2857", .data = (void *)mp2857},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2856_of_match);
+
+static struct i2c_driver mp2856_driver = {
+ .driver = {
+ .name = "mp2856",
+ .of_match_table = mp2856_of_match,
+ },
+ .probe = mp2856_probe,
+ .id_table = mp2856_id,
+};
+
+module_i2c_driver(mp2856_driver);
+
+MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MP2856/MP2857 device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/mp5990.c b/drivers/hwmon/pmbus/mp5990.c
new file mode 100644
index 000000000000..1dfbab25a064
--- /dev/null
+++ b/drivers/hwmon/pmbus/mp5990.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MP5990 Hot-Swap Controller
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+#define MP5990_EFUSE_CFG (0xC4)
+#define MP5990_VOUT_FORMAT BIT(9)
+
+struct mp5990_data {
+ struct pmbus_driver_info info;
+ u8 vout_mode;
+ u8 vout_linear_exponent;
+};
+
+#define to_mp5990_data(x) container_of(x, struct mp5990_data, info)
+
+static int mp5990_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+ struct mp5990_data *data = to_mp5990_data(info);
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ if (data->vout_mode == linear) {
+ /*
+ * The VOUT format used by the chip is linear11,
+ * not linear16. Report that VOUT is in linear mode
+ * and return exponent value extracted while probing
+ * the chip.
+ */
+ return data->vout_linear_exponent;
+ }
+
+ /*
+ * The datasheet does not support the VOUT command,
+ * but the device responds with a default value of 0x17.
+ * In the standard, 0x17 represents linear mode.
+ * Therefore, we should report that VOUT is in direct
+ * format when the chip is configured for it.
+ */
+ return PB_VOUT_MODE_DIRECT;
+
+ default:
+ return -ENODATA;
+ }
+}
+
+static int mp5990_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 mp5990_data *data = to_mp5990_data(info);
+ int ret;
+ s32 mantissa;
+
+ switch (reg) {
+ case PMBUS_READ_VOUT:
+ ret = pmbus_read_word_data(client, page, phase, reg);
+ if (ret < 0)
+ return ret;
+ /*
+ * Because the VOUT format used by the chip is linear11 and not
+ * linear16, we disregard bits[15:11]. The exponent is reported
+ * as part of the VOUT_MODE command.
+ */
+ if (data->vout_mode == linear) {
+ mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5;
+ ret = mantissa;
+ }
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+}
+
+static struct pmbus_driver_info mp5990_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_VOLTAGE_OUT] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_POWER] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 32,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 0,
+ .m[PSC_VOLTAGE_OUT] = 32,
+ .b[PSC_VOLTAGE_OUT] = 0,
+ .R[PSC_VOLTAGE_OUT] = 0,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_POWER] = 1,
+ .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_PIN |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+ .read_byte_data = mp5990_read_byte_data,
+ .read_word_data = mp5990_read_word_data,
+};
+
+static int mp5990_probe(struct i2c_client *client)
+{
+ struct pmbus_driver_info *info;
+ struct mp5990_data *data;
+ int ret;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ memcpy(&data->info, &mp5990_info, sizeof(*info));
+ info = &data->info;
+
+ /* Read Vout Config */
+ ret = i2c_smbus_read_word_data(client, MP5990_EFUSE_CFG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get vout mode.");
+ return ret;
+ }
+
+ /*
+ * EFUSE_CFG (0xC4) bit9=1 is linear mode, bit=0 is direct mode.
+ */
+ if (ret & MP5990_VOUT_FORMAT) {
+ data->vout_mode = linear;
+ data->info.format[PSC_VOLTAGE_IN] = linear;
+ data->info.format[PSC_VOLTAGE_OUT] = linear;
+ data->info.format[PSC_CURRENT_OUT] = linear;
+ data->info.format[PSC_POWER] = linear;
+ ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT);
+ if (ret < 0) {
+ dev_err(&client->dev, "Can't get vout exponent.");
+ return ret;
+ }
+ data->vout_linear_exponent = (u8)((ret >> 11) & 0x1f);
+ } else {
+ data->vout_mode = direct;
+ }
+ return pmbus_do_probe(client, info);
+}
+
+static const struct of_device_id mp5990_of_match[] = {
+ { .compatible = "mps,mp5990" },
+ {}
+};
+
+static const struct i2c_device_id mp5990_id[] = {
+ {"mp5990", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mp5990_id);
+
+static struct i2c_driver mp5990_driver = {
+ .driver = {
+ .name = "mp5990",
+ .of_match_table = mp5990_of_match,
+ },
+ .probe = mp5990_probe,
+ .id_table = mp5990_id,
+};
+module_i2c_driver(mp5990_driver);
+
+MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>");
+MODULE_DESCRIPTION("PMBus driver for MP5990 HSC");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c
index 7ee797410458..4883755d4b1e 100644
--- a/drivers/hwmon/sht4x.c
+++ b/drivers/hwmon/sht4x.c
@@ -49,6 +49,7 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table);
* struct sht4x_data - All the data required to operate an SHT4X chip
* @client: the i2c client associated with the SHT4X
* @lock: a mutex that is used to prevent parallel access to the i2c client
+ * @valid: validity of fields below
* @update_interval: the minimum poll interval
* @last_updated: the previous time that the SHT4X was polled
* @temperature: the latest temperature value received from the SHT4X
@@ -66,7 +67,7 @@ struct sht4x_data {
/**
* sht4x_read_values() - read and parse the raw data from the SHT4X
- * @sht4x_data: the struct sht4x_data to use for the lock
+ * @data: the struct sht4x_data to use for the lock
* Return: 0 if successful, -ERRNO if not
*/
static int sht4x_read_values(struct sht4x_data *data)
diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c
index 37531b5c8254..0d46edbcb144 100644
--- a/drivers/hwmon/smsc47m1.c
+++ b/drivers/hwmon/smsc47m1.c
@@ -33,7 +33,7 @@ static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");
-static struct platform_device *pdev;
+static struct platform_device *smsc47m1_pdev;
#define DRVNAME "smsc47m1"
enum chips { smsc47m1, smsc47m2 };
@@ -840,70 +840,57 @@ error_remove_files:
return err;
}
-static int __exit smsc47m1_remove(struct platform_device *pdev)
+static void __exit smsc47m1_remove(struct platform_device *pdev)
{
struct smsc47m1_data *data = platform_get_drvdata(pdev);
hwmon_device_unregister(data->hwmon_dev);
smsc47m1_remove_files(&pdev->dev);
-
- return 0;
}
-static struct platform_driver smsc47m1_driver = {
+/*
+ * smsc47m1_remove() lives in .exit.text. For drivers registered via
+ * module_platform_driver_probe() this ok because they cannot get unbound at
+ * runtime. The driver needs to be marked with __refdata, otherwise modpost
+ * triggers a section mismatch warning.
+ */
+static struct platform_driver smsc47m1_driver __refdata = {
.driver = {
.name = DRVNAME,
},
- .remove = __exit_p(smsc47m1_remove),
+ .remove_new = __exit_p(smsc47m1_remove),
};
static int __init smsc47m1_device_add(unsigned short address,
const struct smsc47m1_sio_data *sio_data)
{
- struct resource res = {
+ const struct resource res = {
.start = address,
.end = address + SMSC_EXTENT - 1,
.name = DRVNAME,
.flags = IORESOURCE_IO,
};
+ const struct platform_device_info pdevinfo = {
+ .name = DRVNAME,
+ .id = address,
+ .res = &res,
+ .num_res = 1,
+ .data = sio_data,
+ .size_data = sizeof(struct smsc47m1_sio_data),
+ };
int err;
err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL);
if (err)
- goto exit;
+ return err;
- pdev = platform_device_alloc(DRVNAME, address);
- if (!pdev) {
- err = -ENOMEM;
+ smsc47m1_pdev = platform_device_register_full(&pdevinfo);
+ if (IS_ERR(smsc47m1_pdev)) {
pr_err("Device allocation failed\n");
- goto exit;
- }
-
- err = platform_device_add_resources(pdev, &res, 1);
- if (err) {
- pr_err("Device resource addition failed (%d)\n", err);
- goto exit_device_put;
- }
-
- err = platform_device_add_data(pdev, sio_data,
- sizeof(struct smsc47m1_sio_data));
- if (err) {
- pr_err("Platform data allocation failed\n");
- goto exit_device_put;
- }
-
- err = platform_device_add(pdev);
- if (err) {
- pr_err("Device addition failed (%d)\n", err);
- goto exit_device_put;
+ return PTR_ERR(smsc47m1_pdev);
}
return 0;
-
-exit_device_put:
- platform_device_put(pdev);
-exit:
- return err;
}
static int __init sm_smsc47m1_init(void)
@@ -917,7 +904,7 @@ static int __init sm_smsc47m1_init(void)
return err;
address = err;
- /* Sets global pdev as a side effect */
+ /* Sets global smsc47m1_pdev as a side effect */
err = smsc47m1_device_add(address, &sio_data);
if (err)
return err;
@@ -929,7 +916,7 @@ static int __init sm_smsc47m1_init(void)
return 0;
exit_device:
- platform_device_unregister(pdev);
+ platform_device_unregister(smsc47m1_pdev);
smsc47m1_restore(&sio_data);
return err;
}
@@ -937,8 +924,8 @@ exit_device:
static void __exit sm_smsc47m1_exit(void)
{
platform_driver_unregister(&smsc47m1_driver);
- smsc47m1_restore(dev_get_platdata(&pdev->dev));
- platform_device_unregister(pdev);
+ smsc47m1_restore(dev_get_platdata(&smsc47m1_pdev->dev));
+ platform_device_unregister(smsc47m1_pdev);
}
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
diff --git a/drivers/hwmon/tmp513.c b/drivers/hwmon/tmp513.c
index 8a7cf08733c6..ea6f4416c124 100644
--- a/drivers/hwmon/tmp513.c
+++ b/drivers/hwmon/tmp513.c
@@ -19,15 +19,20 @@
* the Free Software Foundation; version 2 of the License.
*/
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
-#include <linux/kernel.h>
+#include <linux/math.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
-#include <linux/util_macros.h>
+#include <linux/types.h>
+#include <linux/units.h>
// Common register definition
#define TMP51X_SHUNT_CONFIG 0x00
@@ -97,8 +102,8 @@
#define TMP51X_REMOTE_TEMP_LIMIT_2_POS 8
#define TMP513_REMOTE_TEMP_LIMIT_3_POS 7
-#define TMP51X_VBUS_RANGE_32V 32000000
-#define TMP51X_VBUS_RANGE_16V 16000000
+#define TMP51X_VBUS_RANGE_32V (32 * MICRO)
+#define TMP51X_VBUS_RANGE_16V (16 * MICRO)
// Max and Min value
#define MAX_BUS_VOLTAGE_32_LIMIT 32764
@@ -200,7 +205,7 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
* on the pga gain setting. 1lsb = 10uV
*/
*val = sign_extend32(regval, 17 - tmp51x_get_pga_shift(data));
- *val = DIV_ROUND_CLOSEST(*val * 10000, data->shunt_uohms);
+ *val = DIV_ROUND_CLOSEST(*val * 10 * MILLI, data->shunt_uohms);
break;
case TMP51X_BUS_VOLTAGE_RESULT:
case TMP51X_BUS_VOLTAGE_H_LIMIT:
@@ -216,7 +221,7 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
case TMP51X_BUS_CURRENT_RESULT:
// Current = (ShuntVoltage * CalibrationRegister) / 4096
*val = sign_extend32(regval, 16) * data->curr_lsb_ua;
- *val = DIV_ROUND_CLOSEST(*val, 1000);
+ *val = DIV_ROUND_CLOSEST(*val, MILLI);
break;
case TMP51X_LOCAL_TEMP_RESULT:
case TMP51X_REMOTE_TEMP_RESULT_1:
@@ -256,7 +261,7 @@ static int tmp51x_set_value(struct tmp51x_data *data, u8 reg, long val)
* The user enter current value and we convert it to
* voltage. 1lsb = 10uV
*/
- val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10000);
+ val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10 * MILLI);
max_val = U16_MAX >> tmp51x_get_pga_shift(data);
regval = clamp_val(val, -max_val, max_val);
break;
@@ -546,18 +551,16 @@ static int tmp51x_calibrate(struct tmp51x_data *data)
if (data->shunt_uohms == 0)
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION, 0);
- max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * 1000 * 1000,
- data->shunt_uohms);
+ max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * MICRO, data->shunt_uohms);
/*
* Calculate the minimal bit resolution for the current and the power.
* Those values will be used during register interpretation.
*/
- data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * 1000, 32767);
+ data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * MILLI, 32767);
data->pwr_lsb_uw = 20 * data->curr_lsb_ua;
- div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms,
- 1000 * 1000);
+ div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms, MICRO);
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION,
DIV_ROUND_CLOSEST(40960, div));
@@ -626,9 +629,9 @@ static int tmp51x_vbus_range_to_reg(struct device *dev,
} else if (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_16V) {
data->shunt_config &= ~TMP51X_BUS_VOLTAGE_MASK;
} else {
- dev_err(dev, "ti,bus-range-microvolt is invalid: %u\n",
- data->vbus_range_uvolt);
- return -EINVAL;
+ return dev_err_probe(dev, -EINVAL,
+ "ti,bus-range-microvolt is invalid: %u\n",
+ data->vbus_range_uvolt);
}
return 0;
}
@@ -644,8 +647,8 @@ static int tmp51x_pga_gain_to_reg(struct device *dev, struct tmp51x_data *data)
} else if (data->pga_gain == 1) {
data->shunt_config |= CURRENT_SENSE_VOLTAGE_40_MASK;
} else {
- dev_err(dev, "ti,pga-gain is invalid: %u\n", data->pga_gain);
- return -EINVAL;
+ return dev_err_probe(dev, -EINVAL,
+ "ti,pga-gain is invalid: %u\n", data->pga_gain);
}
return 0;
}
@@ -674,10 +677,10 @@ static int tmp51x_read_properties(struct device *dev, struct tmp51x_data *data)
data->max_channels - 1);
// Check if shunt value is compatible with pga-gain
- if (data->shunt_uohms > data->pga_gain * 40 * 1000 * 1000) {
- dev_err(dev, "shunt-resistor: %u too big for pga_gain: %u\n",
- data->shunt_uohms, data->pga_gain);
- return -EINVAL;
+ if (data->shunt_uohms > data->pga_gain * 40 * MICRO) {
+ return dev_err_probe(dev, -EINVAL,
+ "shunt-resistor: %u too big for pga_gain: %u\n",
+ data->shunt_uohms, data->pga_gain);
}
return 0;
@@ -717,22 +720,17 @@ static int tmp51x_probe(struct i2c_client *client)
data->max_channels = (uintptr_t)i2c_get_match_data(client);
ret = tmp51x_configure(dev, data);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "error configuring the device\n");
data->regmap = devm_regmap_init_i2c(client, &tmp51x_regmap_config);
- if (IS_ERR(data->regmap)) {
- dev_err(dev, "failed to allocate register map\n");
- return PTR_ERR(data->regmap);
- }
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "failed to allocate register map\n");
ret = tmp51x_init(data);
- if (ret < 0) {
- dev_err(dev, "error configuring the device: %d\n", ret);
- return -ENODEV;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "error configuring the device\n");
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data,
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index a7cfcbf92432..bd271a5730aa 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -92,6 +92,7 @@ static const char * const allow_duplicates[] = {
"8A42EA14-4F2A-FD45-6422-0087F7A7E608", /* dell-wmi-ddv */
"44FADEB1-B204-40F2-8581-394BBDC1B651", /* intel-wmi-sbl-fw-update */
"86CCFD48-205E-4A77-9C48-2021CBEDE341", /* intel-wmi-thunderbolt */
+ "F1DDEE52-063C-4784-A11E-8A06684B9B01", /* dell-smm-hwmon */
NULL
};