hwmon updates for v6.8
- 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 0c459759ca97 hwmon: (pmbus) Add ltc4286 driver - 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: Use i2c_get_match_data() * nct6775: Use i2c_get_match_data(), and related fixes * max6650: Use i2c_get_match_data() * aspeed-pwm-tacho: Fix -Wstringop-overflow warning in aspeed_create_fan_tach_channel() -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmWcL28ACgkQyx8mb86f mYFAOA//TkRpehKzyaiv0EHkAvqjPj2/dOByZe+m/c3pkYEJVY6InEXdkpIeCvNk j81zo6u4j0nAaOryyKH8N809evfLNe9hRGJC0NGU2mcv0pAacDxQmGaV4O3pJp2h WtB6NdnSaj2iOp3Fel+cMwPjJeozdoxtpWXdrcJsh9OVZV7ie7vkjlq0Ggg+Xjig e+0P49pdM8PXRBr7CiOqFLoc9U+ft9E70VJschbpVOngK5DneX+e8hdZJZl4A+hv mE/zDSr0feYKCziSWt0dkle3UaMQXpIyW2k8n6kWDpLKlx4DaA/b/QabPHxxU6OO FRVU/2iTPr1u7Wwcr2783u/gY08MX/lXI9ONoZZC2wumTeF9KJK6DGL2PaadSv2S fqYLh8LnaQE6ugBZ9fSoFXlQjhckZWgzO51PwRvIpXNh4qmDJcJEEBQJoqTf1SMF f9B5ODGecg6ECn1//fnFGMfjUWxnPlBRcBMtNnpj7XpH0gQa4M3s9sO7EVbSHLYv 1FzoNIkOQYqvB9/44F+VTgxF85GzaYEVE+omp102s6JaIgrVV8T1zWaMtCvUkprC EcxKKvLu49MvzH1C0btCqeKX85KZRb+DK+AorKuFsXwZycIACqB2kxxQWcb7VuJu C2YcOwJp6xcfqGtdwdBpafY7QnZguVlBOtO6FhEFyfRzoWxIARQ= =TZuo -----END PGP SIGNATURE----- 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 ...
This commit is contained in:
commit
5dfec3cf3e
@ -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.
|
||||
|
@ -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 */
|
||||
};
|
60
Documentation/devicetree/bindings/hwmon/gpio-fan.yaml
Normal file
60
Documentation/devicetree/bindings/hwmon/gpio-fan.yaml
Normal file
@ -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 */
|
||||
};
|
@ -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
|
||||
|
||||
|
50
Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml
Normal file
50
Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml
Normal file
@ -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>;
|
||||
};
|
||||
};
|
@ -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>;
|
||||
};
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
-----------------
|
||||
|
47
Documentation/hwmon/gigabyte_waterforce.rst
Normal file
47
Documentation/hwmon/gigabyte_waterforce.rst
Normal file
@ -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
|
||||
================ =======================
|
@ -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
|
||||
|
@ -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
|
||||
|
95
Documentation/hwmon/ltc4286.rst
Normal file
95
Documentation/hwmon/ltc4286.rst
Normal file
@ -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.
|
||||
======================= =======================================================
|
@ -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.
|
||||
|
98
Documentation/hwmon/mp2856.rst
Normal file
98
Documentation/hwmon/mp2856.rst
Normal file
@ -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**
|
84
Documentation/hwmon/mp5990.rst
Normal file
84
Documentation/hwmon/mp5990.rst
Normal file
@ -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**
|
@ -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
|
||||
|
25
MAINTAINERS
25
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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[] = {
|
||||
®s->eax,
|
||||
®s->ebx,
|
||||
®s->ecx,
|
||||
®s->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(®s) ? : regs.eax & 0xff;
|
||||
return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
|
||||
return dell_smm_call(data->ops, ®s) ? : (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(®s) ? : regs.eax & 0xff;
|
||||
return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : (regs.eax & 0xffff);
|
||||
return dell_smm_call(data->ops, ®s) ? : (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(®s);
|
||||
regs.eax = enable ? auto_fan : manual_fan;
|
||||
return dell_smm_call(data->ops, ®s);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -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(®s);
|
||||
return dell_smm_call(data->ops, ®s);
|
||||
}
|
||||
|
||||
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(®s) ? : regs.eax & 0xff;
|
||||
return dell_smm_call(data->ops, ®s) ? : 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(®s) ? : regs.eax & 0xff;
|
||||
return dell_smm_call(data->ops, ®s) ? : 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(®s);
|
||||
rc = dell_smm_call(ops, ®s);
|
||||
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(®s);
|
||||
rc = dell_smm_call(data->ops, ®s);
|
||||
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(®s);
|
||||
rc = dell_smm_call(data->ops, ®s);
|
||||
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 = {
|
||||
{
|
||||
@ -1117,14 +1235,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.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 = {
|
||||
@ -1146,15 +1256,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.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 = {
|
||||
@ -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",
|
||||
@ -1197,6 +1296,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
|
||||
@ -1338,73 +1509,27 @@ 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)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&data->i8k_mutex);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
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;
|
||||
} else {
|
||||
dev_warn(&pdev->dev, "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;
|
||||
} else {
|
||||
dev_warn(&pdev->dev, "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
|
||||
*/
|
||||
id = dmi_first_match(i8k_dmi_table);
|
||||
if (id && id->driver_data) {
|
||||
const struct i8k_config_data *conf = id->driver_data;
|
||||
|
||||
if (!fan_mult && conf->fan_mult)
|
||||
fan_mult = conf->fan_mult;
|
||||
|
||||
if (!fan_max && conf->fan_max)
|
||||
fan_max = conf->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;
|
||||
|
||||
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");
|
||||
}
|
||||
ret = dell_smm_init_data(&pdev->dev, &i8k_smm_ops);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = dell_smm_init_hwmon(&pdev->dev);
|
||||
if (ret)
|
||||
@ -1423,34 +1548,135 @@ static struct platform_driver dell_smm_driver = {
|
||||
|
||||
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;
|
||||
|
||||
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 int __init i8k_init(void)
|
||||
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) {
|
||||
pr_notice("Disabling fan support due to BIOS bugs\n");
|
||||
disallow_fan_support = true;
|
||||
} else {
|
||||
pr_warn("Enabling fan support despite BIOS bugs\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
|
||||
if (!force) {
|
||||
pr_notice("Disabling fan type call due to BIOS bugs\n");
|
||||
disallow_fan_type_call = true;
|
||||
} else {
|
||||
pr_warn("Enabling fan type call despite BIOS bugs\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get DMI information
|
||||
* Set fan multiplier and maximal fan speed from DMI config.
|
||||
* Values specified in module parameters override values from DMI.
|
||||
*/
|
||||
id = dmi_first_match(i8k_config_dmi_table);
|
||||
if (id && id->driver_data) {
|
||||
config = id->driver_data;
|
||||
if (!fan_mult && config->fan_mult)
|
||||
fan_mult = config->fan_mult;
|
||||
|
||||
if (!fan_max && config->fan_max)
|
||||
fan_max = config->fan_max;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
pr_info("Enabling support for setting automatic/manual fan control\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int __init dell_smm_legacy_check(void)
|
||||
{
|
||||
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);
|
||||
|
@ -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);
|
||||
|
430
drivers/hwmon/gigabyte_waterforce.c
Normal file
430
drivers/hwmon/gigabyte_waterforce.c
Normal file
@ -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");
|
@ -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);
|
||||
string = element->type == ACPI_TYPE_BUFFER ?
|
||||
convert_raw_wmi_string(element->buffer.pointer) :
|
||||
element->string.pointer;
|
||||
|
||||
if (strcmp(string, nsensor->current_state)) {
|
||||
devm_kfree(dev, nsensor->current_state);
|
||||
nsensor->current_state = hp_wmi_strdup(dev, string);
|
||||
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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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, ®);
|
||||
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, },
|
||||
@ -689,6 +765,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
|
||||
.compatible = "adi,adt75",
|
||||
.data = (void *)adt75
|
||||
},
|
||||
{
|
||||
.compatible = "ams,as6200",
|
||||
.data = (void *)as6200
|
||||
},
|
||||
{
|
||||
.compatible = "atmel,at30ts74",
|
||||
.data = (void *)at30ts74
|
||||
|
@ -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, <c2991_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;
|
||||
|
||||
|
@ -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 int max31827_init_client(struct max31827_state *st)
|
||||
static ssize_t temp1_resolution_show(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
st->enable = true;
|
||||
struct max31827_state *st = dev_get_drvdata(dev);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG,
|
||||
MAX31827_CONFIGURATION_1SHOT_MASK |
|
||||
MAX31827_CONFIGURATION_CNV_RATE_MASK,
|
||||
MAX31827_DEVICE_ENABLE(1));
|
||||
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 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);
|
||||
|
||||
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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
175
drivers/hwmon/pmbus/ltc4286.c
Normal file
175
drivers/hwmon/pmbus/ltc4286.c
Normal file
@ -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, <c4286_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");
|
466
drivers/hwmon/pmbus/mp2856.c
Normal file
466
drivers/hwmon/pmbus/mp2856.c
Normal file
@ -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);
|
179
drivers/hwmon/pmbus/mp5990.c
Normal file
179
drivers/hwmon/pmbus/mp5990.c
Normal file
@ -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);
|
@ -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)
|
||||
|
@ -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>");
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user