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
 
 0c459759ca 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:
Linus Torvalds 2024-01-12 13:27:40 -08:00
commit 5dfec3cf3e
48 changed files with 3066 additions and 499 deletions

View File

@ -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.

View File

@ -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 */
};

View 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 */
};

View File

@ -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

View 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>;
};
};

View File

@ -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>;
};
};

View File

@ -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)

View File

@ -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
-----------------

View 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
================ =======================

View File

@ -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

View File

@ -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

View 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.
======================= =======================================================

View File

@ -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.

View 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**

View 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**

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/capability.h>
#include <linux/cpu.h>
#include <linux/ctype.h>
@ -34,8 +35,10 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wmi.h>
#include <linux/i8k.h>
#include <asm/unaligned.h>
#define I8K_SMM_FN_STATUS 0x0025
#define I8K_SMM_POWER_STATUS 0x0069
@ -66,9 +69,26 @@
#define I8K_POWER_AC 0x05
#define I8K_POWER_BATTERY 0x01
#define DELL_SMM_WMI_GUID "F1DDEE52-063C-4784-A11E-8A06684B9B01"
#define DELL_SMM_LEGACY_EXECUTE 0x1
#define DELL_SMM_NO_TEMP 10
#define DELL_SMM_NO_FANS 3
struct smm_regs {
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int esi;
unsigned int edi;
};
struct dell_smm_ops {
struct device *smm_dev;
int (*smm_call)(struct device *smm_dev, struct smm_regs *regs);
};
struct dell_smm_data {
struct mutex i8k_mutex; /* lock for sensors writes */
char bios_version[4];
@ -76,14 +96,11 @@ struct dell_smm_data {
uint i8k_fan_mult;
uint i8k_pwm_mult;
uint i8k_fan_max;
bool disallow_fan_type_call;
bool disallow_fan_support;
unsigned int manual_fan;
unsigned int auto_fan;
int temp_type[DELL_SMM_NO_TEMP];
bool fan[DELL_SMM_NO_FANS];
int fan_type[DELL_SMM_NO_FANS];
int *fan_nominal_speed[DELL_SMM_NO_FANS];
const struct dell_smm_ops *ops;
};
struct dell_smm_cooling_data {
@ -123,14 +140,9 @@ static uint fan_max;
module_param(fan_max, uint, 0);
MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
struct smm_regs {
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int esi;
unsigned int edi;
};
static bool disallow_fan_type_call, disallow_fan_support;
static unsigned int manual_fan, auto_fan;
static const char * const temp_labels[] = {
"CPU",
@ -171,12 +183,8 @@ static inline const char __init *i8k_get_dmi_data(int field)
*/
static int i8k_smm_func(void *par)
{
ktime_t calltime = ktime_get();
struct smm_regs *regs = par;
int eax = regs->eax;
int ebx = regs->ebx;
unsigned char carry;
long long duration;
/* SMM requires CPU 0 */
if (smp_processor_id() != 0)
@ -193,14 +201,7 @@ static int i8k_smm_func(void *par)
"+S" (regs->esi),
"+D" (regs->edi));
duration = ktime_us_delta(ktime_get(), calltime);
pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x carry: %d (took %7lld usecs)\n",
eax, ebx, regs->eax & 0xffff, carry, duration);
if (duration > DELL_SMM_MAX_DURATION)
pr_warn_once("SMM call took %lld usecs!\n", duration);
if (carry || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
if (carry)
return -EINVAL;
return 0;
@ -209,7 +210,7 @@ static int i8k_smm_func(void *par)
/*
* Call the System Management Mode BIOS.
*/
static int i8k_smm(struct smm_regs *regs)
static int i8k_smm_call(struct device *dummy, struct smm_regs *regs)
{
int ret;
@ -220,6 +221,134 @@ static int i8k_smm(struct smm_regs *regs)
return ret;
}
static const struct dell_smm_ops i8k_smm_ops = {
.smm_call = i8k_smm_call,
};
/*
* Call the System Management Mode BIOS over WMI.
*/
static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
{
__le32 value;
u32 reg_size;
if (length <= sizeof(reg_size))
return -ENODATA;
reg_size = get_unaligned_le32(buffer);
if (!reg_size || reg_size > sizeof(value))
return -ENOMSG;
if (length < sizeof(reg_size) + reg_size)
return -ENODATA;
memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0);
*reg = le32_to_cpu(value);
return reg_size + sizeof(reg_size);
}
static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
{
unsigned int *registers[] = {
&regs->eax,
&regs->ebx,
&regs->ecx,
&regs->edx
};
u32 offset = 0;
ssize_t ret;
int i;
for (i = 0; i < ARRAY_SIZE(registers); i++) {
if (offset >= length)
return -ENODATA;
ret = wmi_parse_register(buffer + offset, length - offset, registers[i]);
if (ret < 0)
return ret;
offset += ret;
}
if (offset != length)
return -ENOMSG;
return 0;
}
static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
u32 wmi_payload[] = {
sizeof(regs->eax),
regs->eax,
sizeof(regs->ebx),
regs->ebx,
sizeof(regs->ecx),
regs->ecx,
sizeof(regs->edx),
regs->edx
};
const struct acpi_buffer in = {
.length = sizeof(wmi_payload),
.pointer = &wmi_payload,
};
union acpi_object *obj;
acpi_status status;
int ret;
status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
if (ACPI_FAILURE(status))
return -EIO;
obj = out.pointer;
if (!obj)
return -ENODATA;
if (obj->type != ACPI_TYPE_BUFFER) {
ret = -ENOMSG;
goto err_free;
}
ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
err_free:
kfree(obj);
return ret;
}
static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)
{
unsigned int eax = regs->eax;
unsigned int ebx = regs->ebx;
long long duration;
ktime_t calltime;
int ret;
calltime = ktime_get();
ret = ops->smm_call(ops->smm_dev, regs);
duration = ktime_us_delta(ktime_get(), calltime);
pr_debug("SMM(0x%.4x 0x%.4x) = 0x%.4x status: %d (took %7lld usecs)\n",
eax, ebx, regs->eax & 0xffff, ret, duration);
if (duration > DELL_SMM_MAX_DURATION)
pr_warn_once("SMM call took %lld usecs!\n", duration);
if (ret < 0)
return ret;
if ((regs->eax & 0xffff) == 0xffff || regs->eax == eax)
return -EINVAL;
return 0;
}
/*
* Read the fan status.
*/
@ -230,10 +359,10 @@ static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
if (data->disallow_fan_support)
if (disallow_fan_support)
return -EINVAL;
return i8k_smm(&regs) ? : regs.eax & 0xff;
return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
@ -246,10 +375,10 @@ static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
if (data->disallow_fan_support)
if (disallow_fan_support)
return -EINVAL;
return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
}
/*
@ -262,10 +391,10 @@ static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan)
.ebx = fan,
};
if (data->disallow_fan_support || data->disallow_fan_type_call)
if (disallow_fan_support || disallow_fan_type_call)
return -EINVAL;
return i8k_smm(&regs) ? : regs.eax & 0xff;
return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
@ -280,17 +409,17 @@ static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
/*
* Read the fan nominal rpm for specific fan speed.
*/
static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_NOM_SPEED,
.ebx = fan | (speed << 8),
};
if (data->disallow_fan_support)
if (disallow_fan_support)
return -EINVAL;
return i8k_smm(&regs) ? : (regs.eax & 0xffff);
return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff);
}
/*
@ -300,11 +429,11 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl
{
struct smm_regs regs = { };
if (data->disallow_fan_support)
if (disallow_fan_support)
return -EINVAL;
regs.eax = enable ? data->auto_fan : data->manual_fan;
return i8k_smm(&regs);
regs.eax = enable ? auto_fan : manual_fan;
return dell_smm_call(data->ops, &regs);
}
/*
@ -314,41 +443,41 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
if (data->disallow_fan_support)
if (disallow_fan_support)
return -EINVAL;
speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
regs.ebx = fan | (speed << 8);
return i8k_smm(&regs);
return dell_smm_call(data->ops, &regs);
}
static int __init i8k_get_temp_type(u8 sensor)
static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP_TYPE,
.ebx = sensor,
};
return i8k_smm(&regs) ? : regs.eax & 0xff;
return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
/*
* Read the cpu temperature.
*/
static int _i8k_get_temp(u8 sensor)
static int _i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP,
.ebx = sensor,
};
return i8k_smm(&regs) ? : regs.eax & 0xff;
return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
}
static int i8k_get_temp(u8 sensor)
static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
{
int temp = _i8k_get_temp(sensor);
int temp = _i8k_get_temp(data, sensor);
/*
* Sometimes the temperature sensor returns 0x99, which is out of range.
@ -359,7 +488,7 @@ static int i8k_get_temp(u8 sensor)
*/
if (temp == 0x99) {
msleep(100);
temp = _i8k_get_temp(sensor);
temp = _i8k_get_temp(data, sensor);
}
/*
* Return -ENODATA for all invalid temperatures.
@ -375,12 +504,12 @@ static int i8k_get_temp(u8 sensor)
return temp;
}
static int __init i8k_get_dell_signature(int req_fn)
static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
{
struct smm_regs regs = { .eax = req_fn, };
int rc;
rc = i8k_smm(&regs);
rc = dell_smm_call(ops, &regs);
if (rc < 0)
return rc;
@ -392,12 +521,12 @@ static int __init i8k_get_dell_signature(int req_fn)
/*
* Read the Fn key status.
*/
static int i8k_get_fn_status(void)
static int i8k_get_fn_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
int rc;
rc = i8k_smm(&regs);
rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@ -416,12 +545,12 @@ static int i8k_get_fn_status(void)
/*
* Read the power status.
*/
static int i8k_get_power_status(void)
static int i8k_get_power_status(const struct dell_smm_data *data)
{
struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
int rc;
rc = i8k_smm(&regs);
rc = dell_smm_call(data->ops, &regs);
if (rc < 0)
return rc;
@ -464,15 +593,15 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return 0;
case I8K_FN_STATUS:
val = i8k_get_fn_status();
val = i8k_get_fn_status(data);
break;
case I8K_POWER_STATUS:
val = i8k_get_power_status();
val = i8k_get_power_status(data);
break;
case I8K_GET_TEMP:
val = i8k_get_temp(0);
val = i8k_get_temp(data, 0);
break;
case I8K_GET_SPEED:
@ -539,14 +668,14 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
int fn_key, cpu_temp, ac_power;
int left_fan, right_fan, left_speed, right_speed;
cpu_temp = i8k_get_temp(0); /* 11100 µs */
cpu_temp = i8k_get_temp(data, 0); /* 11100 µs */
left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */
right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */
left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */
right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */
fn_key = i8k_get_fn_status(); /* 750 µs */
fn_key = i8k_get_fn_status(data); /* 750 µs */
if (power_status)
ac_power = i8k_get_power_status(); /* 14700 µs */
ac_power = i8k_get_power_status(data); /* 14700 µs */
else
ac_power = -1;
@ -597,6 +726,11 @@ static void __init i8k_init_procfs(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
sizeof(data->bios_version));
strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
sizeof(data->bios_machineid));
/* Only register exit function if creation was successful */
if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data))
devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
@ -665,7 +799,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
switch (attr) {
case hwmon_temp_input:
/* _i8k_get_temp() is fine since we do not care about the actual value */
if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0)
if (data->temp_type[channel] >= 0 || _i8k_get_temp(data, channel) >= 0)
return 0444;
break;
@ -679,7 +813,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_fan:
if (data->disallow_fan_support)
if (disallow_fan_support)
break;
switch (attr) {
@ -689,7 +823,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_fan_label:
if (data->fan[channel] && !data->disallow_fan_type_call)
if (data->fan[channel] && !disallow_fan_type_call)
return 0444;
break;
@ -705,7 +839,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
}
break;
case hwmon_pwm:
if (data->disallow_fan_support)
if (disallow_fan_support)
break;
switch (attr) {
@ -715,7 +849,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
break;
case hwmon_pwm_enable:
if (data->auto_fan)
if (auto_fan)
/*
* There is no command for retrieve the current status
* from BIOS, and userspace/firmware itself can change
@ -747,7 +881,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
ret = i8k_get_temp(channel);
ret = i8k_get_temp(data, channel);
if (ret < 0)
return ret;
@ -955,7 +1089,7 @@ static const struct hwmon_chip_info dell_smm_chip_info = {
.info = dell_smm_info,
};
static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct thermal_cooling_device *cdev;
@ -986,7 +1120,7 @@ static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
return ret;
}
static int __init dell_smm_init_hwmon(struct device *dev)
static int dell_smm_init_hwmon(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct device *dell_smm_hwmon_dev;
@ -994,7 +1128,7 @@ static int __init dell_smm_init_hwmon(struct device *dev)
u8 i;
for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
data->temp_type[i] = i8k_get_temp_type(i);
data->temp_type[i] = i8k_get_temp_type(data, i);
if (data->temp_type[i] < 0)
continue;
@ -1052,41 +1186,25 @@ static int __init dell_smm_init_hwmon(struct device *dev)
return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
}
struct i8k_config_data {
uint fan_mult;
uint fan_max;
};
static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
{
struct dell_smm_data *data;
enum i8k_configs {
DELL_LATITUDE_D520,
DELL_PRECISION_490,
DELL_STUDIO,
DELL_XPS,
};
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/*
* Only use for machines which need some special configuration
* in order to work correctly (e.g. if autoconfig fails on this machines).
*/
mutex_init(&data->i8k_mutex);
dev_set_drvdata(dev, data);
static const struct i8k_config_data i8k_config_data[] __initconst = {
[DELL_LATITUDE_D520] = {
.fan_mult = 1,
.fan_max = I8K_FAN_TURBO,
},
[DELL_PRECISION_490] = {
.fan_mult = 1,
.fan_max = I8K_FAN_TURBO,
},
[DELL_STUDIO] = {
.fan_mult = 1,
.fan_max = I8K_FAN_HIGH,
},
[DELL_XPS] = {
.fan_mult = 1,
.fan_max = I8K_FAN_HIGH,
},
};
data->ops = ops;
/* All options must not be 0 */
data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
return 0;
}
static const struct dmi_system_id i8k_dmi_table[] __initconst = {
{
@ -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);

View File

@ -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);

View 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");

View File

@ -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);
}

View File

@ -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;

View File

@ -7,11 +7,11 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/regmap.h>
@ -25,6 +25,7 @@
enum lm75_type { /* keep sorted in alphabetical order */
adt75,
as6200,
at30ts74,
ds1775,
ds75,
@ -55,6 +56,7 @@ enum lm75_type { /* keep sorted in alphabetical order */
/**
* struct lm75_params - lm75 configuration parameters.
* @config_reg_16bits: Configure register size is 2 bytes.
* @set_mask: Bits to set in configuration register when configuring
* the chip.
* @clr_mask: Bits to clear in configuration register when configuring
@ -75,17 +77,20 @@ enum lm75_type { /* keep sorted in alphabetical order */
* @sample_times: All the possible sample times to be set. Mandatory if
* num_sample_times is larger than 1. If set, number of
* entries must match num_sample_times.
* @alarm: Alarm bit is supported.
*/
struct lm75_params {
u8 set_mask;
u8 clr_mask;
bool config_reg_16bits;
u16 set_mask;
u16 clr_mask;
u8 default_resolution;
u8 resolution_limits;
const u8 *resolutions;
unsigned int default_sample_time;
u8 num_sample_times;
const unsigned int *sample_times;
bool alarm;
};
/* Addresses scanned */
@ -104,8 +109,8 @@ struct lm75_data {
struct i2c_client *client;
struct regmap *regmap;
struct regulator *vs;
u8 orig_conf;
u8 current_conf;
u16 orig_conf;
u16 current_conf;
u8 resolution; /* In bits, 9 to 16 */
unsigned int sample_time; /* In ms */
enum lm75_type kind;
@ -128,6 +133,15 @@ static const struct lm75_params device_params[] = {
.default_resolution = 12,
.default_sample_time = MSEC_PER_SEC / 10,
},
[as6200] = {
.config_reg_16bits = true,
.set_mask = 0x94C0, /* 8 sample/s, 4 CF, positive polarity */
.default_resolution = 12,
.default_sample_time = 125,
.num_sample_times = 4,
.sample_times = (unsigned int []){ 125, 250, 1000, 4000 },
.alarm = true,
},
[at30ts74] = {
.set_mask = 3 << 5, /* 12-bit mode*/
.default_resolution = 12,
@ -255,8 +269,9 @@ static const struct lm75_params device_params[] = {
.resolutions = (u8 []) {9, 10, 11, 12 },
},
[tmp112] = {
.set_mask = 3 << 5, /* 8 samples / second */
.clr_mask = 1 << 7, /* no one-shot mode*/
.config_reg_16bits = true,
.set_mask = 0x60C0, /* 12-bit mode, 8 samples / second */
.clr_mask = 1 << 15, /* no one-shot mode*/
.default_resolution = 12,
.default_sample_time = 125,
.num_sample_times = 4,
@ -317,20 +332,23 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
}
static int lm75_write_config(struct lm75_data *data, u8 set_mask,
u8 clr_mask)
static int lm75_write_config(struct lm75_data *data, u16 set_mask,
u16 clr_mask)
{
u8 value;
unsigned int value;
clr_mask |= LM75_SHUTDOWN;
clr_mask |= LM75_SHUTDOWN << (8 * data->params->config_reg_16bits);
value = data->current_conf & ~clr_mask;
value |= set_mask;
if (data->current_conf != value) {
s32 err;
err = i2c_smbus_write_byte_data(data->client, LM75_REG_CONF,
value);
if (data->params->config_reg_16bits)
err = regmap_write(data->regmap, LM75_REG_CONF, value);
else
err = i2c_smbus_write_byte_data(data->client,
LM75_REG_CONF,
value);
if (err)
return err;
data->current_conf = value;
@ -338,6 +356,27 @@ static int lm75_write_config(struct lm75_data *data, u8 set_mask,
return 0;
}
static int lm75_read_config(struct lm75_data *data)
{
int ret;
unsigned int status;
if (data->params->config_reg_16bits) {
ret = regmap_read(data->regmap, LM75_REG_CONF, &status);
return ret ? ret : status;
}
return i2c_smbus_read_byte_data(data->client, LM75_REG_CONF);
}
static irqreturn_t lm75_alarm_handler(int irq, void *private)
{
struct device *hwmon_dev = private;
hwmon_notify_event(hwmon_dev, hwmon_temp, hwmon_temp_alarm, 0);
return IRQ_HANDLED;
}
static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@ -366,6 +405,9 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
case hwmon_temp_max_hyst:
reg = LM75_REG_HYST;
break;
case hwmon_temp_alarm:
reg = LM75_REG_CONF;
break;
default:
return -EINVAL;
}
@ -373,7 +415,17 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type,
if (err < 0)
return err;
*val = lm75_reg_to_mc(regval, data->resolution);
if (attr == hwmon_temp_alarm) {
switch (data->kind) {
case as6200:
*val = (regval >> 5) & 0x1;
break;
default:
return -EINVAL;
}
} else {
*val = lm75_reg_to_mc(regval, data->resolution);
}
break;
default:
return -EINVAL;
@ -436,6 +488,7 @@ static int lm75_update_interval(struct device *dev, long val)
data->resolution = data->params->resolutions[index];
break;
case tmp112:
case as6200:
err = regmap_read(data->regmap, LM75_REG_CONF, &reg);
if (err < 0)
return err;
@ -503,6 +556,10 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type,
case hwmon_temp_max:
case hwmon_temp_max_hyst:
return 0644;
case hwmon_temp_alarm:
if (config_data->params->alarm)
return 0444;
break;
}
break;
default:
@ -515,7 +572,8 @@ static const struct hwmon_channel_info * const lm75_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST),
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_ALARM),
NULL
};
@ -623,7 +681,7 @@ static int lm75_probe(struct i2c_client *client)
return err;
/* Cache original configuration */
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
status = lm75_read_config(data);
if (status < 0) {
dev_dbg(dev, "Can't read config? %d\n", status);
return status;
@ -646,6 +704,23 @@ static int lm75_probe(struct i2c_client *client)
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
if (client->irq) {
if (data->params->alarm) {
err = devm_request_threaded_irq(dev,
client->irq,
NULL,
&lm75_alarm_handler,
IRQF_ONESHOT,
client->name,
hwmon_dev);
if (err)
return err;
} else {
/* alarm is only supported for chips with alarm bit */
dev_err(dev, "alarm interrupt is not supported\n");
}
}
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
return 0;
@ -653,6 +728,7 @@ static int lm75_probe(struct i2c_client *client)
static const struct i2c_device_id lm75_ids[] = {
{ "adt75", adt75, },
{ "as6200", as6200, },
{ "at30ts74", at30ts74, },
{ "ds1775", ds1775, },
{ "ds75", ds75, },
@ -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

View File

@ -54,7 +54,6 @@
#define LTC2991_VCC_CH_NR 0
struct ltc2991_state {
struct device *dev;
struct regmap *regmap;
u32 r_sense_uohm[LTC2991_MAX_CHANNEL];
bool temp_en[LTC2991_MAX_CHANNEL];
@ -283,19 +282,19 @@ static const struct regmap_config ltc2991_regmap_config = {
.max_register = 0x1D,
};
static int ltc2991_init(struct ltc2991_state *st)
static int ltc2991_init(struct ltc2991_state *st, struct device *dev)
{
struct fwnode_handle *child;
int ret;
u32 val, addr;
u8 v5_v8_reg_data = 0, v1_v4_reg_data = 0;
ret = devm_regulator_get_enable(st->dev, "vcc");
ret = devm_regulator_get_enable(dev, "vcc");
if (ret)
return dev_err_probe(st->dev, ret,
return dev_err_probe(dev, ret,
"failed to enable regulator\n");
device_for_each_child_node(st->dev, child) {
device_for_each_child_node(dev, child) {
ret = fwnode_property_read_u32(child, "reg", &addr);
if (ret < 0) {
fwnode_handle_put(child);
@ -312,7 +311,7 @@ static int ltc2991_init(struct ltc2991_state *st)
&val);
if (!ret) {
if (!val)
return dev_err_probe(st->dev, -EINVAL,
return dev_err_probe(dev, -EINVAL,
"shunt resistor value cannot be zero\n");
st->r_sense_uohm[addr] = val;
@ -361,18 +360,18 @@ static int ltc2991_init(struct ltc2991_state *st)
ret = regmap_write(st->regmap, LTC2991_V5_V8_CTRL, v5_v8_reg_data);
if (ret)
return dev_err_probe(st->dev, ret,
return dev_err_probe(dev, ret,
"Error: Failed to set V5-V8 CTRL reg.\n");
ret = regmap_write(st->regmap, LTC2991_V1_V4_CTRL, v1_v4_reg_data);
if (ret)
return dev_err_probe(st->dev, ret,
return dev_err_probe(dev, ret,
"Error: Failed to set V1-V4 CTRL reg.\n");
ret = regmap_write(st->regmap, LTC2991_PWM_TH_LSB_T_INT,
LTC2991_REPEAT_ACQ_EN);
if (ret)
return dev_err_probe(st->dev, ret,
return dev_err_probe(dev, ret,
"Error: Failed to set continuous mode.\n");
/* Enable all channels and trigger conversions */
@ -392,12 +391,11 @@ static int ltc2991_i2c_probe(struct i2c_client *client)
if (!st)
return -ENOMEM;
st->dev = &client->dev;
st->regmap = devm_regmap_init_i2c(client, &ltc2991_regmap_config);
if (IS_ERR(st->regmap))
return PTR_ERR(st->regmap);
ret = ltc2991_init(st);
ret = ltc2991_init(st, &client->dev);
if (ret)
return ret;

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 };

View File

@ -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);

View File

@ -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 */

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View 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, &ltc4286_info, sizeof(*info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
/* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */
vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1);
if (vrange_oval < 0)
return dev_err_probe(&client->dev, vrange_oval,
"Failed to read manufacturer configuration one\n");
vrange_nval = vrange_oval;
if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) {
vrange_nval &=
~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */
info->m[PSC_VOLTAGE_IN] = 128;
info->m[PSC_VOLTAGE_OUT] = 128;
info->m[PSC_POWER] = 4 * rsense;
} else {
vrange_nval |=
VRANGE_SELECT_BIT; /* VRANGE_SELECT = 1, 102.4 volts */
info->m[PSC_POWER] = rsense;
}
if (vrange_nval != vrange_oval) {
/* Set MFR1 CONFIG register bit 1 VRANGE_SELECT */
ret = i2c_smbus_write_word_data(client, LTC4286_MFR_CONFIG1,
vrange_nval);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"Failed to set vrange\n");
}
info->m[PSC_CURRENT_OUT] = 1024 * rsense;
return pmbus_do_probe(client, info);
}
static const struct of_device_id ltc4286_of_match[] = {
{ .compatible = "lltc,ltc4286" },
{ .compatible = "lltc,ltc4287" },
{}
};
static struct i2c_driver ltc4286_driver = {
.driver = {
.name = "ltc4286",
.of_match_table = ltc4286_of_match,
},
.probe = ltc4286_probe,
.id_table = ltc4286_id,
};
module_i2c_driver(ltc4286_driver);
MODULE_AUTHOR("Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>");
MODULE_DESCRIPTION("PMBUS driver for LTC4286 and compatibles");
MODULE_LICENSE("GPL");

View 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);

View 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);

View File

@ -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)

View File

@ -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>");

View File

@ -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,

View File

@ -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
};