hwmon updates for v5.18

New drivers
 - Driver for Texas Instruments TMP464 and TMP468
 - Driver for Vicor PLI1209BC Digital Supervisor
 - Driver for ASUS EC
 
 Improvements to existing drivers:
 - adt7x10: Convert to use regmap, convert to use with_info API,
   use hwmon_notify_event, and other cleanup
 - aquacomputer_d5next: Add support for Aquacomputer Farbwerk 360
 - asus_wmi_sensors: Add ASUS ROG STRIX B450-F GAMING II
 - asus_wmi_ec_sensors: Support T_Sensor on Prime X570-Pro
   Deprecate driver (replaced by new driver)
 - axi-fan-control: Use hwmon_notify_event
 - dell-smm: Clean up CONFIG_I8K, disable fan type support for
   Inspiron 3505, various other cleanup
 - hwmon core: Report attribute name with udev events,
   Add "label" attribute to ABI,
   Add support for pwm auto channels attribute
 - max6639: Add regulator support
 - lm70: Add support for TI TMP125
 - lm83: Cleanup, convert to use with_info API
 - mlxreg-fan: Use pwm attribute for setting fan speed low limit
 - nct6775: Sdd ASUS ROG STRIX Z390/Z490/X570-* / PRIME X570-P,
   PRIME B550-PLUS, ASUS Pro B550M-C/PRIME B550M-A,
   and support for TSI temperature registers
 - occ: Add various new sysfs attributes
 - pmbus core: Handle VIN unit off status,
   Add regulator supply into macro,
   Add get_error_flags support to regulator ops
 - pmbus/adm1275: Allow setting sample averaging
 - pmbus/lm25066: Add regulator support
 - pmbus/xdpe12284: Add support for xdpe11280 and register as regulator
 - powr1220: Convert to with_info API,
   Add support for Lattice's POWR1014 power manager IC
 - sch56xx: Cleanup and minor improvements
 - sch5627: Add pwmX_auto_channels_temp support
 - tc654: Add thermal_cooling device support
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmI3tLEACgkQyx8mb86f
 mYGztw//T9YdVs+oBImLAF2Q3NXzvCNWSdO7ZUzaxUTDw60lGHZpjMYNt/m+k3j/
 vdjVFpyyvJW0D1iDxHd5EIO7IVhSsd01VYlSCMELF2NYug12XzAAofzbGsEJih65
 DISIsPp1h+Ddc6XQsuuh7A3etDQhPu+YWnuIpSgEI33wK5+U7zxl5CIR9o5asfmf
 ZkGjQoN5DjYxqB4MpdTisHz7JC8YAxdXk2ZxqFlZ8yoptB7kLMjbhIFx/PGwI1Os
 TEoVEcp8n4KnirVpwwx5wFustX0Abd4Radm9iUTrRhJHHYsVP5RVwYpfs3FhgccW
 k69wJ32Hx2fyRWT6/wo88+VJ/T1hPSTB1wMRHDLmJJzLBZsXIfFcf7Hxr/A+N0/U
 D88JtWvu2GyJQt5k54IU1RwvN0cBz6J0X3PE7nldaR7lAE1tqF98KC57Esr0vtYu
 TLxz/ISBva9mwwWH6Gar1X+kiODhTiHQRTDWhl6vIbAUCcpTjMyNxppAxSFNciGU
 S7LDvuPY10S3DHJ0sqLVeANgF5Q/GLBGMI3iamVD08U8dgcEsrycUzs98LAYONyi
 d3+3AUUl60OAWlrk43OOLzYllGrCy3OhKNWzmVXbM8Ue9Fb1cet7UD2fAg8WlOVX
 kXhtNdXkPk//65NVIikI8owMcQg6iC+zFESPkFlaXQfnhA6KyqQ=
 =4u33
 -----END PGP SIGNATURE-----

Merge tag 'hwmon-for-v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:
 "New drivers:

   - Texas Instruments TMP464 and TMP468 driver

   - Vicor PLI1209BC Digital Supervisor driver

   - ASUS EC driver

  Improvements to existing drivers:

   - adt7x10:
       - Convert to use regmap
       - convert to use with_info API
       - use hwmon_notify_event
       - other cleanup

   - aquacomputer_d5next:
       - Add support for Aquacomputer Farbwerk 360

   - asus_wmi_sensors:
       - Add ASUS ROG STRIX B450-F GAMING II

   - asus_wmi_ec_sensors:
       - Support T_Sensor on Prime X570-Pro
       - Deprecate driver (replaced by new driver)

   - axi-fan-control:
       - Use hwmon_notify_event

   - dell-smm:
       - Clean up CONFIG_I8K
       - disable fan type support for Inspiron 3505
       - various other cleanup

   - hwmon core:
       - Report attribute name with udev events
       - Add "label" attribute to ABI,
       - Add support for pwm auto channels attribute

   - max6639:
       - Add regulator support

   - lm70:
       - Add support for TI TMP125

   - lm83:
       - Cleanup, convert to use with_info API

   - mlxreg-fan:
       - Use pwm attribute for setting fan speed low limit

   - nct6775:
       - Add board ID's for ASUS ROG STRIX Z390/Z490/X570-* / PRIME
         X570-P, PRIME B550-PLUS, ASUS Pro B550M-C/PRIME B550M-A
       - Add support for TSI temperature registers

   - occ:
       - Add various new sysfs attributes

   - pmbus core:
       - Handle VIN unit off status
       - Add regulator supply into macro
       - Add get_error_flags support to regulator ops

   - pmbus/adm1275:
       - Allow setting sample averaging

   - pmbus/lm25066:
       - Add regulator support

   - pmbus/xdpe12284:
       - Add support for xdpe11280
       - register as regulator

   - powr1220:
       - Convert to with_info API
       - Add support for Lattice's POWR1014 power manager IC

   - sch56xx:
       - Cleanup and minor improvements

   - sch5627:
       - Add pwmX_auto_channels_temp support

   - tc654:
       - Add thermal_cooling device support"

* tag 'hwmon-for-v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (86 commits)
  hwmon: (dell-smm) Add Inspiron 3505 to fan type blacklist
  hwmon: (pmbus) Add Vin unit off handling
  hwmon: (scpi-hwmon): Use of_device_get_match_data()
  hwmon: (axi-fan-control) Use hwmon_notify_event
  hwmon: (vexpress-hwmon) Use of_device_get_match_data()
  hwmon: Add driver for Texas Instruments TMP464 and TMP468
  dt-bindings: hwmon: add tmp464.yaml
  dt-bindings: hwmon: Add sample averaging properties for ADM1275
  hwmon: (adm1275) Allow setting sample averaging
  hwmon: (xdpe12284) Add regulator support
  hwmon: (xdpe12284) Add support for xdpe11280
  dt-bindings: trivial-devices: Add xdpe11280
  hwmon: (aquacomputer_d5next) Add support for Aquacomputer Farbwerk 360
  hwmon: (sch5627) Add pwmX_auto_channels_temp support
  hwmon: (core) Add support for pwm auto channels attribute
  hwmon: (lm70) Add ti,tmp125 support
  dt-bindings: Add ti,tmp125 temperature sensor binding
  hwmon: (pmbus/pli1209bc) Add regulator support
  hwmon: (pmbus) Add support for pli1209bc
  dt-bindings:trivial-devices: Add pli1209bc
  ...
This commit is contained in:
Linus Torvalds 2022-03-21 18:08:52 -07:00
commit fd27687791
59 changed files with 4174 additions and 990 deletions

View File

@ -0,0 +1,10 @@
What: /proc/i8k
Date: November 2001
KernelVersion: 2.4.14
Contact: Pali Rohár <pali@kernel.org>
Description: Legacy interface for getting/setting sensor information like
fan speed, temperature, serial number, hotkey status etc
on Dell Laptops.
Since the driver is now using the standard hwmon sysfs interface,
the procfs interface is deprecated.
Users: https://github.com/vitorafsr/i8kutils

View File

@ -9,6 +9,14 @@ Description:
RO
What: /sys/class/hwmon/hwmonX/label
Description:
A descriptive label that allows to uniquely identify a
device within the system.
The contents of the label are free-form.
RO
What: /sys/class/hwmon/hwmonX/update_interval
Description:
The interval at which the chip will update readings.

View File

@ -944,6 +944,30 @@
dump out devices still on the deferred probe list after
retrying.
dell_smm_hwmon.ignore_dmi=
[HW] Continue probing hardware even if DMI data
indicates that the driver is running on unsupported
hardware.
dell_smm_hwmon.force=
[HW] Activate driver even if SMM BIOS signature does
not match list of supported models and enable otherwise
blacklisted features.
dell_smm_hwmon.power_status=
[HW] Report power status in /proc/i8k
(disabled by default).
dell_smm_hwmon.restricted=
[HW] Allow controlling fans only if SYS_ADMIN
capability is set.
dell_smm_hwmon.fan_mult=
[HW] Factor to multiply fan speed with.
dell_smm_hwmon.fan_max=
[HW] Maximum configurable fan speed.
dfltcc= [HW,S390]
Format: { on | off | def_only | inf_only | always }
on: s390 zlib hardware support for compression on
@ -1703,17 +1727,6 @@
i810= [HW,DRM]
i8k.ignore_dmi [HW] Continue probing hardware even if DMI data
indicates that the driver is running on unsupported
hardware.
i8k.force [HW] Activate i8k driver even if SMM BIOS signature
does not match list of supported models.
i8k.power_status
[HW] Report power status in /proc/i8k
(disabled by default)
i8k.restricted [HW] Allow controlling fans only if SYS_ADMIN
capability is set.
i915.invert_brightness=
[DRM] Invert the sense of the variable that is used to
set the brightness of the panel backlight. Normally a

View File

@ -37,6 +37,72 @@ properties:
description:
Shunt resistor value in micro-Ohm.
adi,volt-curr-sample-average:
description: |
Number of samples to be used to report voltage and current values.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 4, 8, 16, 32, 64, 128]
adi,power-sample-average:
description: |
Number of samples to be used to report power values.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 4, 8, 16, 32, 64, 128]
allOf:
- if:
properties:
compatible:
contains:
enum:
- adi,adm1075
- adi,adm1276
then:
properties:
adi,volt-curr-sample-average:
default: 128
adi,power-sample-average: false
- if:
properties:
compatible:
contains:
enum:
- adi,adm1275
then:
properties:
adi,volt-curr-sample-average:
default: 16
adi,power-sample-average: false
- if:
properties:
compatible:
contains:
enum:
- adi,adm1272
then:
properties:
adi,volt-curr-sample-average:
default: 128
adi,power-sample-average:
default: 128
- if:
properties:
compatible:
contains:
enum:
- adi,adm1278
- adi,adm1293
- adi,adm1294
then:
properties:
adi,volt-curr-sample-average:
default: 128
adi,power-sample-average:
default: 1
required:
- compatible
- reg
@ -53,5 +119,7 @@ examples:
compatible = "adi,adm1272";
reg = <0x10>;
shunt-resistor-micro-ohms = <500>;
adi,volt-curr-sample-average = <128>;
adi,power-sample-average = <128>;
};
};

View File

@ -60,7 +60,6 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/tegra-gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
@ -71,8 +70,7 @@ examples:
compatible = "onnn,nct1008";
reg = <0x4c>;
vcc-supply = <&palmas_ldo6_reg>;
interrupt-parent = <&gpio>;
interrupts = <TEGRA_GPIO(O, 4) IRQ_TYPE_LEVEL_LOW>;
interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
#thermal-sensor-cells = <1>;
};
};

View File

@ -0,0 +1,114 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/ti,tmp464.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TMP464 and TMP468 temperature sensors
maintainers:
- Agathe Porte <agathe.porte@nokia.com>
description: |
±0.0625°C Remote and Local temperature sensor
https://www.ti.com/lit/ds/symlink/tmp464.pdf
https://www.ti.com/lit/ds/symlink/tmp468.pdf
properties:
compatible:
enum:
- ti,tmp464
- ti,tmp468
reg:
maxItems: 1
'#address-cells':
const: 1
'#size-cells':
const: 0
required:
- compatible
- reg
additionalProperties: false
patternProperties:
"^channel@([0-8])$":
type: object
description: |
Represents channels of the device and their specific configuration.
properties:
reg:
description: |
The channel number. 0 is local channel, 1-8 are remote channels.
items:
minimum: 0
maximum: 8
label:
description: |
A descriptive name for this channel, like "ambient" or "psu".
ti,n-factor:
description: |
The value (two's complement) to be programmed in the channel specific N correction register.
For remote channels only.
$ref: /schemas/types.yaml#/definitions/int32
items:
minimum: -128
maximum: 127
required:
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
sensor@4b {
compatible = "ti,tmp464";
reg = <0x4b>;
};
};
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
sensor@4b {
compatible = "ti,tmp464";
reg = <0x4b>;
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0x0>;
label = "local";
};
channel@1 {
reg = <0x1>;
ti,n-factor = <(-10)>;
label = "external";
};
channel@2 {
reg = <0x2>;
ti,n-factor = <0x10>;
label = "somelabel";
};
channel@3 {
reg = <0x3>;
status = "disabled";
};
};
};

View File

@ -137,6 +137,8 @@ properties:
- infineon,slb9645tt
# Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor
- infineon,tlv493d-a1b6
# Infineon Multi-phase Digital VR Controller xdpe11280
- infineon,xdpe11280
# Infineon Multi-phase Digital VR Controller xdpe12254
- infineon,xdpe12254
# Infineon Multi-phase Digital VR Controller xdpe12284
@ -337,6 +339,7 @@ properties:
# Thermometer with SPI interface
- ti,tmp121
- ti,tmp122
- ti,tmp125
# Digital Temperature Sensor
- ti,tmp275
# TI DC-DC converter on PMBus
@ -354,6 +357,8 @@ properties:
- ti,tps544c25
# Winbond/Nuvoton H/W Monitor
- winbond,w83793
# Vicor Corporation Digital Supervisor
- vicor,pli1209bc
# i2c trusted platform module (TPM)
- winbond,wpct301

View File

@ -1298,6 +1298,8 @@ patternProperties:
description: Vertexcom Technologies, Inc.
"^via,.*":
description: VIA Technologies, Inc.
"^vicor,.*":
description: Vicor Corporation
"^videostrong,.*":
description: Videostrong Technology Co., Ltd.
"^virtio,.*":

View File

@ -6,22 +6,21 @@ Kernel driver aquacomputer-d5next
Supported devices:
* Aquacomputer D5 Next watercooling pump
* Aquacomputer Farbwerk 360 RGB controller
Author: Aleksa Savic
Description
-----------
This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling
pump, which communicates through a proprietary USB HID protocol.
This driver exposes hardware sensors of listed Aquacomputer devices, which
communicate through proprietary USB HID protocols.
Available sensors are pump and fan speed, power, voltage and current, as
well as coolant temperature. Also available through debugfs are the serial
number, firmware version and power-on count.
Attaching a fan is optional and allows it to be controlled using temperature
curves directly from the pump. If it's not connected, the fan-related sensors
will report zeroes.
For the D5 Next pump, available sensors are pump and fan speed, power, voltage
and current, as well as coolant temperature. Also available through debugfs are
the serial number, firmware version and power-on count. Attaching a fan to it is
optional and allows it to be controlled using temperature curves directly from the
pump. If it's not connected, the fan-related sensors will report zeroes.
The pump can be configured either through software or via its physical
interface. Configuring the pump through this driver is not implemented, as it
@ -29,33 +28,31 @@ seems to require sending it a complete configuration. That includes addressable
RGB LEDs, for which there is no standard sysfs interface. Thus, that task is
better suited for userspace tools.
The Farbwerk 360 exposes four temperature sensors. Depending on the device,
not all sysfs and debugfs entries will be available.
Usage notes
-----------
The pump communicates via HID reports. The driver is loaded automatically by
The devices communicate via HID reports. The driver is loaded automatically by
the kernel and supports hotswapping.
Sysfs entries
-------------
============ =============================================
temp1_input Coolant temperature (in millidegrees Celsius)
fan1_input Pump speed (in RPM)
fan2_input Fan speed (in RPM)
power1_input Pump power (in micro Watts)
power2_input Fan power (in micro Watts)
in0_input Pump voltage (in milli Volts)
in1_input Fan voltage (in milli Volts)
in2_input +5V rail voltage (in milli Volts)
curr1_input Pump current (in milli Amperes)
curr2_input Fan current (in milli Amperes)
============ =============================================
================ =============================================
temp[1-4]_input Temperature sensors (in millidegrees Celsius)
fan[1-2]_input Pump/fan speed (in RPM)
power[1-2]_input Pump/fan power (in micro Watts)
in[0-2]_input Pump/fan voltage (in milli Volts)
curr[1-2]_input Pump/fan current (in milli Amperes)
================ =============================================
Debugfs entries
---------------
================ ===============================================
serial_number Serial number of the pump
================ =================================================
serial_number Serial number of the device
firmware_version Version of installed firmware
power_cycles Count of how many times the pump was powered on
================ ===============================================
power_cycles Count of how many times the device was powered on
================ =================================================

View File

@ -0,0 +1,54 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver asus_ec_sensors
=================================
Supported boards:
* PRIME X570-PRO,
* Pro WS X570-ACE,
* ROG CROSSHAIR VIII DARK HERO,
* ROG CROSSHAIR VIII HERO (WI-FI)
* ROG CROSSHAIR VIII FORMULA,
* ROG CROSSHAIR VIII HERO,
* ROG CROSSHAIR VIII IMPACT,
* ROG STRIX B550-E GAMING,
* ROG STRIX B550-I GAMING,
* ROG STRIX X570-E GAMING,
* ROG STRIX X570-F GAMING,
* ROG STRIX X570-I GAMING
Authors:
- Eugene Shalygin <eugene.shalygin@gmail.com>
Description:
------------
ASUS mainboards publish hardware monitoring information via Super I/O
chip and the ACPI embedded controller (EC) registers. Some of the sensors
are only available via the EC.
The driver is aware of and reads the following sensors:
1. Chipset (PCH) temperature
2. CPU package temperature
3. Motherboard temperature
4. Readings from the T_Sensor header
5. VRM temperature
6. CPU_Opt fan RPM
7. VRM heatsink fan RPM
8. Chipset fan RPM
9. Readings from the "Water flow meter" header (RPM)
10. Readings from the "Water In" and "Water Out" temperature headers
11. CPU current
12. CPU core voltage
Sensor values are read from EC registers, and to avoid race with the board
firmware the driver acquires ACPI mutex, the one used by the WMI when its
methods access the EC.
Module Parameters
-----------------
* mutex_path: string
The driver holds path to the ACPI mutex for each board (actually,
the path is mostly identical for them). If ASUS changes this path
in a future BIOS update, this parameter can be used to override
the stored in the driver value until it gets updated.

View File

@ -165,3 +165,183 @@ obtain the same information and to control the fan status. The ioctl
interface can be accessed from C programs or from shell using the
i8kctl utility. See the source file of ``i8kutils`` for more
information on how to use the ioctl interface.
SMM Interface
-------------
.. warning:: The SMM interface was reverse-engineered by trial-and-error
since Dell did not provide any Documentation,
please keep that in mind.
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
execute.
The SMM handler inside the system BIOS looks at the contents of the
``eax``, ``ebx``, ``ecx``, ``edx``, ``esi`` and ``edi`` registers.
Each register has a special purpose:
=============== ==================================
Register Purpose
=============== ==================================
eax Holds the command code before SMM,
holds the first result after SMM.
ebx Holds the arguments.
ecx Unknown, set to 0.
edx Holds the second result after SMM.
esi Unknown, set to 0.
edi Unknown, set to 0.
=============== ==================================
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
SMM command codes
-----------------
=============== ======================= ================================================
Command Code Command Name Description
=============== ======================= ================================================
``0x0025`` Get Fn key status Returns the Fn key pressed after SMM:
- 9th bit in ``eax`` indicates Volume up
- 10th bit in ``eax`` indicates Volume down
- both bits indicate Volume mute
``0xa069`` Get power status Returns current power status after SMM:
- 1st bit in ``eax`` indicates Battery connected
- 3th bit in ``eax`` indicates AC connected
``0x00a3`` Get fan state Returns current fan state after SMM:
- 1st byte in ``eax`` holds the current
fan state (0 - 2 or 3)
``0x01a3`` Set fan state Sets the fan speed:
- 1st byte in ``ebx`` holds the fan number
- 2nd byte in ``ebx`` holds the desired
fan state (0 - 2 or 3)
``0x02a3`` Get fan speed Returns the current fan speed in RPM:
- 1st byte in ``ebx`` holds the fan number
- 1st word in ``eax`` holds the current
fan speed in RPM (after SMM)
``0x03a3`` Get fan type Returns the fan type:
- 1st byte in ``ebx`` holds the fan number
- 1st byte in ``eax`` holds the
fan type (after SMM):
- 5th bit indicates docking fan
- 1 indicates Processor fan
- 2 indicates Motherboard fan
- 3 indicates Video fan
- 4 indicates Power supply fan
- 5 indicates Chipset fan
- 6 indicates other fan type
``0x04a3`` Get nominal fan speed Returns the nominal RPM in each fan state:
- 1st byte in ``ebx`` holds the fan number
- 2nd byte in ``ebx`` holds the fan state
in question (0 - 2 or 3)
- 1st word in ``eax`` holds the nominal
fan speed in RPM (after SMM)
``0x05a3`` Get fan speed tolerance Returns the speed tolerance for each fan state:
- 1st byte in ``ebx`` holds the fan number
- 2nd byte in ``ebx`` holds the fan state
in question (0 - 2 or 3)
- 1st byte in ``eax`` returns the speed
tolerance
``0x10a3`` Get sensor temperature Returns the measured temperature:
- 1st byte in ``ebx`` holds the sensor number
- 1st byte in ``eax`` holds the measured
temperature (after SMM)
``0x11a3`` Get sensor type Returns the sensor type:
- 1st byte in ``ebx`` holds the sensor number
- 1st byte in ``eax`` holds the
temperature type (after SMM):
- 1 indicates CPU sensor
- 2 indicates GPU sensor
- 3 indicates SODIMM sensor
- 4 indicates other sensor type
- 5 indicates Ambient sensor
- 6 indicates other sensor type
``0xfea3`` Get SMM signature Returns Dell signature if interface
is supported (after SMM):
- ``eax`` holds 1145651527
(0x44494147 or "DIAG")
- ``edx`` holds 1145392204
(0x44454c4c or "DELL")
``0xffa3`` Get SMM signature Same as ``0xfea3``, check both.
=============== ======================= ================================================
There are additional commands for enabling (``0x31a3`` or ``0x35a3``) and
disabling (``0x30a3`` or ``0x34a3``) automatic fan speed control.
The commands are however causing severe sideeffects on many machines, so
they are not used by default.
On several machines (Inspiron 3505, Precision 490, Vostro 1720, ...), the
fans supports a 4th "magic" state, which signals the BIOS that automatic
fan control should be enabled for a specific fan.
However there are also some machines who do support a 4th regular fan state too,
but in case of the "magic" state, the nominal RPM reported for this state is a
placeholder value, which however is not always detectable.
Firmware Bugs
-------------
The SMM calls can behave erratic on some machines:
======================================================= =================
Firmware Bug Affected Machines
======================================================= =================
Reading of fan states return spurious errors. Precision 490
Reading of fan types causes erratic fan behaviour. Studio XPS 8000
Studio XPS 8100
Inspiron 580
Fan-related SMM calls take too long (about 500ms). Inspiron 7720
Vostro 3360
XPS 13 9333
XPS 15 L502X
======================================================= =================
In case you experience similar issues on your Dell machine, please
submit a bugreport on bugzilla to we can apply workarounds.
Limitations
-----------
The SMM calls can take too long to execute on some machines, causing
short hangs and/or audio glitches.
Also the fan state needs to be restored after suspend, as well as
the automatic mode settings.
When reading a temperature sensor, values above 127 degrees indicate
a BIOS read error or a deactivated sensor.

View File

@ -43,6 +43,7 @@ Hardware Monitoring Kernel Drivers
asb100
asc7621
aspeed-pwm-tacho
asus_ec_sensors
asus_wmi_ec_sensors
asus_wmi_sensors
bcm54140
@ -160,6 +161,7 @@ Hardware Monitoring Kernel Drivers
pc87427
pcf8591
pim4328
pli1209bc
pm6764tr
pmbus
powr1220
@ -193,6 +195,7 @@ Hardware Monitoring Kernel Drivers
tmp108
tmp401
tmp421
tmp464
tmp513
tps23861
tps40422

View File

@ -15,6 +15,10 @@ Supported chips:
Information: https://www.ti.com/product/tmp122
* Texas Instruments TMP125
Information: https://www.ti.com/product/tmp125
* National Semiconductor LM71
Datasheet: https://www.ti.com/product/LM71
@ -53,6 +57,9 @@ The LM74 and TMP121/TMP122/TMP123/TMP124 are very similar; main difference is
The TMP122/TMP124 also feature configurable temperature thresholds.
The TMP125 is less accurate and provides 10-bit temperature data
with 0.25 degrees Celsius resolution.
The LM71 is also very similar; main difference is 14-bit temperature
data (0.03125 degrees celsius resolution).

View File

@ -9,7 +9,7 @@ Supported chips:
Addresses scanned: I2C 0x2c, 0x2e, 0x2f
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6639.pdf
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX6639-MAX6639F.pdf
Authors:
- He Changqing <hechangqing@semptian.com>

View File

@ -0,0 +1,75 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver pli1209bc
=======================
Supported chips:
* Digital Supervisor PLI1209BC
Prefix: 'pli1209bc'
Addresses scanned: 0x50 - 0x5F
Datasheet: https://www.vicorpower.com/documents/datasheets/ds-PLI1209BCxyzz-VICOR.pdf
Authors:
- Marcello Sylvester Bauer <sylv@sylv.io>
Description
-----------
The Vicor PLI1209BC is an isolated digital power system supervisor that provides
a communication interface between a host processor and one Bus Converter Module
(BCM). The PLI communicates with a system controller via a PMBus compatible
interface over an isolated UART interface. Through the PLI, the host processor
can configure, set protection limits, and monitor the BCM.
Sysfs entries
-------------
======================= ========================================================
in1_label "vin2"
in1_input Input voltage.
in1_rated_min Minimum rated input voltage.
in1_rated_max Maximum rated input voltage.
in1_max Maximum input voltage.
in1_max_alarm Input voltage high alarm.
in1_crit Critical input voltage.
in1_crit_alarm Input voltage critical alarm.
in2_label "vout2"
in2_input Output voltage.
in2_rated_min Minimum rated output voltage.
in2_rated_max Maximum rated output voltage.
in2_alarm Output voltage alarm
curr1_label "iin2"
curr1_input Input current.
curr1_max Maximum input current.
curr1_max_alarm Maximum input current high alarm.
curr1_crit Critical input current.
curr1_crit_alarm Input current critical alarm.
curr2_label "iout2"
curr2_input Output current.
curr2_crit Critical output current.
curr2_crit_alarm Output current critical alarm.
curr2_max Maximum output current.
curr2_max_alarm Output current high alarm.
power1_label "pin2"
power1_input Input power.
power1_alarm Input power alarm.
power2_label "pout2"
power2_input Output power.
power2_rated_max Maximum rated output power.
temp1_input Die temperature.
temp1_alarm Die temperature alarm.
temp1_max Maximum die temperature.
temp1_max_alarm Die temperature high alarm.
temp1_crit Critical die temperature.
temp1_crit_alarm Die temperature critical alarm.
======================= ========================================================

View File

@ -20,6 +20,10 @@ Description
SMSC SCH5627 Super I/O chips include complete hardware monitoring
capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures.
In addition, the SCH5627 exports data describing which temperature sensors
affect the speed of each fan. Setting pwmX_auto_channels_temp to 0 forces
the corresponding fan to full speed until another value is written.
The SMSC SCH5627 hardware monitoring part also contains an integrated
watchdog. In order for this watchdog to function some motherboard specific
initialization most be done by the BIOS, so if the watchdog is not enabled

View File

@ -99,6 +99,10 @@ Global attributes
`name`
The chip name.
`label`
A descriptive label that allows to uniquely identify a device
within the system.
`update_interval`
The interval at which the chip will update readings.

View File

@ -0,0 +1,73 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver tmp464
====================
Supported chips:
* Texas Instruments TMP464
Prefix: 'tmp464'
Addresses scanned: I2C 0x48, 0x49, 0x4a and 0x4b
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp464.html
* Texas Instruments TMP468
Prefix: 'tmp468'
Addresses scanned: I2C 0x48, 0x49, 0x4a and 0x4b
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp468.html
Authors:
Agathe Porte <agathe.porte@nokia.com>
Guenter Roeck <linux@roeck-us.net>
Description
-----------
This driver implements support for Texas Instruments TMP464 and TMP468
temperature sensor chips. TMP464 provides one local and four remote
sensors. TMP468 provides one local and eight remote sensors.
Temperature is measured in degrees Celsius. The chips are wired over
I2C/SMBus and specified over a temperature range of -40 to +125 degrees
Celsius. Resolution for both the local and remote channels is 0.0625
degree C.
The chips support only temperature measurements. The driver exports
temperature values, limits, and alarms via the following sysfs files:
**temp[1-9]_input**
**temp[1-9]_max**
**temp[1-9]_max_hyst**
**temp[1-9]_max_alarm**
**temp[1-9]_crit**
**temp[1-9]_crit_alarm**
**temp[1-9]_crit_hyst**
**temp[2-9]_offset**
**temp[2-9]_fault**
Each sensor can be individually disabled via Devicetree or from sysfs
via:
**temp[1-9]_enable**
If labels were specified in Devicetree, additional sysfs files will
be present:
**temp[1-9]_label**
The update interval is configurable with the following sysfs attribute.
**update_interval**

View File

@ -5,6 +5,10 @@ Kernel driver xdpe122
Supported chips:
* Infineon XDPE11280
Prefix: 'xdpe11280'
* Infineon XDPE12254
Prefix: 'xdpe12254'
@ -20,10 +24,10 @@ Authors:
Description
-----------
This driver implements support for Infineon Multi-phase XDPE122 family
dual loop voltage regulators.
The family includes XDPE12284 and XDPE12254 devices.
The devices from this family complaint with:
This driver implements support for Infineon Multi-phase XDPE112 and XDPE122
family dual loop voltage regulators.
These families include XDPE11280, XDPE12284 and XDPE12254 devices.
The devices from this family compliant with:
- Intel VR13 and VR13HC rev 1.3, IMVP8 rev 1.2 and IMPVP9 rev 1.3 DC-DC
converter specification.

View File

@ -3057,6 +3057,12 @@ L: linux-hwmon@vger.kernel.org
S: Maintained
F: drivers/hwmon/asus_wmi_ec_sensors.c
ASUS EC HARDWARE MONITOR DRIVER
M: Eugene Shalygin <eugene.shalygin@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: drivers/hwmon/asus-ec-sensors.c
ASUS WIRELESS RADIO CONTROL DRIVER
M: João Paulo Rechi Vita <jprvita@gmail.com>
L: platform-driver-x86@vger.kernel.org
@ -5412,6 +5418,7 @@ F: drivers/platform/x86/dell/dell-rbtn.*
DELL LAPTOP SMM DRIVER
M: Pali Rohár <pali@kernel.org>
S: Maintained
F: Documentation/ABI/obsolete/procfs-i8k
F: drivers/hwmon/dell-smm-hwmon.c
F: include/uapi/linux/i8k.h
@ -19500,6 +19507,15 @@ S: Maintained
F: Documentation/hwmon/tmp401.rst
F: drivers/hwmon/tmp401.c
TMP464 HARDWARE MONITOR DRIVER
M: Agathe Porte <agathe.porte@nokia.com>
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml
F: Documentation/hwmon/tmp464.rst
F: drivers/hwmon/tmp464.c
TMP513 HARDWARE MONITOR DRIVER
M: Eric Tremblay <etremblay@distech-controls.com>
L: linux-hwmon@vger.kernel.org

View File

@ -1276,23 +1276,6 @@ config TOSHIBA
Say Y if you intend to run this kernel on a Toshiba portable.
Say N otherwise.
config I8K
tristate "Dell i8k legacy laptop support"
depends on HWMON
depends on PROC_FS
select SENSORS_DELL_SMM
help
This option enables legacy /proc/i8k userspace interface in hwmon
dell-smm-hwmon driver. Character file /proc/i8k reports bios version,
temperature and allows controlling fan speeds of Dell laptops via
System Management Mode. For old Dell laptops (like Dell Inspiron 8000)
it reports also power and hotkey status. For fan speed control is
needed userspace package i8kutils.
Say Y if you intend to run this kernel on old Dell laptops or want to
use userspace package i8kutils.
Say N otherwise.
config X86_REBOOTFIXUPS
bool "Enable X86 board specific fixups for reboot"
depends on X86_32

View File

@ -174,6 +174,7 @@ config SENSORS_ADM9240
config SENSORS_ADT7X10
tristate
select REGMAP
help
This module contains common code shared by the ADT7310/ADT7320 and
ADT7410/ADT7420 temperature monitoring chip drivers.
@ -505,6 +506,21 @@ config SENSORS_DELL_SMM
When option I8K is also enabled this driver provides legacy /proc/i8k
userspace interface for i8kutils package.
config I8K
bool "Legacy /proc/i8k interface of Dell laptop SMM BIOS hwmon driver"
depends on SENSORS_DELL_SMM
depends on PROC_FS
help
This option enables the legacy /proc/i8k userspace interface of the
dell-smm-hwmon driver. The character file /proc/i8k exposes the BIOS
version, temperatures and allows control of fan speeds of some Dell
laptops. Sometimes it also reports power and hotkey status.
This interface is required to run programs from the i8kutils package.
Say Y if you intend to run userspace programs that use this interface.
Say N otherwise.
config SENSORS_DA9052_ADC
tristate "Dialog DA9052/DA9053 ADC"
depends on PMIC_DA9052
@ -1208,8 +1224,8 @@ config SENSORS_LM70
depends on SPI_MASTER
help
If you say yes here you get support for the National Semiconductor
LM70, LM71, LM74 and Texas Instruments TMP121/TMP123 digital tempera-
ture sensor chips.
LM70, LM71, LM74 and Texas Instruments TMP121/TMP123, TMP122/TMP124,
TMP125 digital temperature sensor chips.
This driver can also be built as a module. If so, the module
will be called lm70.
@ -1288,6 +1304,7 @@ config SENSORS_LM80
config SENSORS_LM83
tristate "National Semiconductor LM83 and compatibles"
depends on I2C
select REGMAP
help
If you say yes here you get support for National Semiconductor
LM82 and LM83 sensor chips.
@ -1979,6 +1996,17 @@ config SENSORS_TMP421
This driver can also be built as a module. If so, the module
will be called tmp421.
config SENSORS_TMP464
tristate "Texas Instruments TMP464 and compatible"
depends on I2C
select REGMAP_I2C
help
If you say yes here you get support for Texas Instruments TMP464
and TMP468 temperature sensor chips.
This driver can also be built as a module. If so, the module
will be called tmp464.
config SENSORS_TMP513
tristate "Texas Instruments TMP513 and compatibles"
depends on I2C
@ -2252,16 +2280,31 @@ config SENSORS_ASUS_WMI
config SENSORS_ASUS_WMI_EC
tristate "ASUS WMI B550/X570"
depends on ACPI_WMI
depends on ACPI_WMI && SENSORS_ASUS_EC=n
help
If you say yes here you get support for the ACPI embedded controller
hardware monitoring interface found in B550/X570 ASUS motherboards.
This driver will provide readings of fans, voltages and temperatures
through the system firmware.
This driver is deprecated in favor of the ASUS EC Sensors driver
which provides fully compatible output.
This driver can also be built as a module. If so, the module
will be called asus_wmi_sensors_ec.
config SENSORS_ASUS_EC
tristate "ASUS EC Sensors"
depends on X86
help
If you say yes here you get support for the ACPI embedded controller
hardware monitoring interface found in ASUS motherboards. The driver
currently supports B550/X570 boards, although other ASUS boards might
provide this monitoring interface as well.
This driver can also be built as a module. If so, the module
will be called asus_ec_sensors.
endif # ACPI
endif # HWMON

View File

@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
# APCI drivers
obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o
obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o
obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o
obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o
obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o
@ -194,6 +195,7 @@ obj-$(CONFIG_SENSORS_TMP103) += tmp103.o
obj-$(CONFIG_SENSORS_TMP108) += tmp108.o
obj-$(CONFIG_SENSORS_TMP401) += tmp401.o
obj-$(CONFIG_SENSORS_TMP421) += tmp421.o
obj-$(CONFIG_SENSORS_TMP464) += tmp464.o
obj-$(CONFIG_SENSORS_TMP513) += tmp513.o
obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o
obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o

View File

@ -8,6 +8,7 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <asm/unaligned.h>
@ -38,16 +39,13 @@ static const u8 adt7310_reg_table[] = {
#define AD7310_COMMAND(reg) (adt7310_reg_table[(reg)] << ADT7310_CMD_REG_OFFSET)
static int adt7310_spi_read_word(struct device *dev, u8 reg)
static int adt7310_spi_read_word(struct spi_device *spi, u8 reg)
{
struct spi_device *spi = to_spi_device(dev);
return spi_w8r16be(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ);
}
static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data)
static int adt7310_spi_write_word(struct spi_device *spi, u8 reg, u16 data)
{
struct spi_device *spi = to_spi_device(dev);
u8 buf[3];
buf[0] = AD7310_COMMAND(reg);
@ -56,17 +54,13 @@ static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data)
return spi_write(spi, buf, sizeof(buf));
}
static int adt7310_spi_read_byte(struct device *dev, u8 reg)
static int adt7310_spi_read_byte(struct spi_device *spi, u8 reg)
{
struct spi_device *spi = to_spi_device(dev);
return spi_w8r8(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ);
}
static int adt7310_spi_write_byte(struct device *dev, u8 reg,
u8 data)
static int adt7310_spi_write_byte(struct spi_device *spi, u8 reg, u8 data)
{
struct spi_device *spi = to_spi_device(dev);
u8 buf[2];
buf[0] = AD7310_COMMAND(reg);
@ -75,23 +69,77 @@ static int adt7310_spi_write_byte(struct device *dev, u8 reg,
return spi_write(spi, buf, sizeof(buf));
}
static const struct adt7x10_ops adt7310_spi_ops = {
.read_word = adt7310_spi_read_word,
.write_word = adt7310_spi_write_word,
.read_byte = adt7310_spi_read_byte,
.write_byte = adt7310_spi_write_byte,
static bool adt7310_regmap_is_volatile(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_STATUS:
return true;
default:
return false;
}
}
static int adt7310_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct spi_device *spi = context;
int regval;
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_T_ALARM_HIGH:
case ADT7X10_T_ALARM_LOW:
case ADT7X10_T_CRIT:
regval = adt7310_spi_read_word(spi, reg);
break;
default:
regval = adt7310_spi_read_byte(spi, reg);
break;
}
if (regval < 0)
return regval;
*val = regval;
return 0;
}
static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct spi_device *spi = context;
int ret;
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_T_ALARM_HIGH:
case ADT7X10_T_ALARM_LOW:
case ADT7X10_T_CRIT:
ret = adt7310_spi_write_word(spi, reg, val);
break;
default:
ret = adt7310_spi_write_byte(spi, reg, val);
break;
}
return ret;
}
static const struct regmap_config adt7310_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = adt7310_regmap_is_volatile,
.reg_read = adt7310_reg_read,
.reg_write = adt7310_reg_write,
};
static int adt7310_spi_probe(struct spi_device *spi)
{
return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq,
&adt7310_spi_ops);
}
struct regmap *regmap;
static int adt7310_spi_remove(struct spi_device *spi)
{
adt7x10_remove(&spi->dev, spi->irq);
return 0;
regmap = devm_regmap_init(&spi->dev, NULL, spi, &adt7310_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq,
regmap);
}
static const struct spi_device_id adt7310_id[] = {
@ -107,7 +155,6 @@ static struct spi_driver adt7310_driver = {
.pm = ADT7X10_DEV_PM_OPS,
},
.probe = adt7310_spi_probe,
.remove = adt7310_spi_remove,
.id_table = adt7310_id,
};
module_spi_driver(adt7310_driver);

View File

@ -9,49 +9,82 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include "adt7x10.h"
static int adt7410_i2c_read_word(struct device *dev, u8 reg)
static bool adt7410_regmap_is_volatile(struct device *dev, unsigned int reg)
{
return i2c_smbus_read_word_swapped(to_i2c_client(dev), reg);
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_STATUS:
return true;
default:
return false;
}
}
static int adt7410_i2c_write_word(struct device *dev, u8 reg, u16 data)
static int adt7410_reg_read(void *context, unsigned int reg, unsigned int *val)
{
return i2c_smbus_write_word_swapped(to_i2c_client(dev), reg, data);
struct i2c_client *client = context;
int regval;
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_T_ALARM_HIGH:
case ADT7X10_T_ALARM_LOW:
case ADT7X10_T_CRIT:
regval = i2c_smbus_read_word_swapped(client, reg);
break;
default:
regval = i2c_smbus_read_byte_data(client, reg);
break;
}
if (regval < 0)
return regval;
*val = regval;
return 0;
}
static int adt7410_i2c_read_byte(struct device *dev, u8 reg)
static int adt7410_reg_write(void *context, unsigned int reg, unsigned int val)
{
return i2c_smbus_read_byte_data(to_i2c_client(dev), reg);
struct i2c_client *client = context;
int ret;
switch (reg) {
case ADT7X10_TEMPERATURE:
case ADT7X10_T_ALARM_HIGH:
case ADT7X10_T_ALARM_LOW:
case ADT7X10_T_CRIT:
ret = i2c_smbus_write_word_swapped(client, reg, val);
break;
default:
ret = i2c_smbus_write_byte_data(client, reg, val);
break;
}
return ret;
}
static int adt7410_i2c_write_byte(struct device *dev, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(to_i2c_client(dev), reg, data);
}
static const struct adt7x10_ops adt7410_i2c_ops = {
.read_word = adt7410_i2c_read_word,
.write_word = adt7410_i2c_write_word,
.read_byte = adt7410_i2c_read_byte,
.write_byte = adt7410_i2c_write_byte,
static const struct regmap_config adt7410_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = ADT7X10_ID,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = adt7410_regmap_is_volatile,
.reg_read = adt7410_reg_read,
.reg_write = adt7410_reg_write,
};
static int adt7410_i2c_probe(struct i2c_client *client)
{
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
struct regmap *regmap;
return adt7x10_probe(&client->dev, NULL, client->irq, &adt7410_i2c_ops);
}
regmap = devm_regmap_init(&client->dev, NULL, client,
&adt7410_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
static int adt7410_i2c_remove(struct i2c_client *client)
{
adt7x10_remove(&client->dev, client->irq);
return 0;
return adt7x10_probe(&client->dev, client->name, client->irq, regmap);
}
static const struct i2c_device_id adt7410_ids[] = {
@ -68,7 +101,6 @@ static struct i2c_driver adt7410_driver = {
.pm = ADT7X10_DEV_PM_OPS,
},
.probe_new = adt7410_i2c_probe,
.remove = adt7410_i2c_remove,
.id_table = adt7410_ids,
.address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b),
};

View File

@ -8,16 +8,17 @@
* and adt7410.c from iio-staging by Sonic Zhang <sonic.zhang@analog.com>
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
#include "adt7x10.h"
@ -53,80 +54,57 @@
/* Each client has this additional data */
struct adt7x10_data {
const struct adt7x10_ops *ops;
const char *name;
struct device *hwmon_dev;
struct regmap *regmap;
struct mutex update_lock;
u8 config;
u8 oldconfig;
bool valid; /* true if registers valid */
unsigned long last_updated; /* In jiffies */
s16 temp[4]; /* Register values,
0 = input
1 = high
2 = low
3 = critical */
u8 hyst; /* hysteresis offset */
bool valid; /* true if temperature valid */
};
static int adt7x10_read_byte(struct device *dev, u8 reg)
{
struct adt7x10_data *d = dev_get_drvdata(dev);
return d->ops->read_byte(dev, reg);
}
enum {
adt7x10_temperature = 0,
adt7x10_t_alarm_high,
adt7x10_t_alarm_low,
adt7x10_t_crit,
};
static int adt7x10_write_byte(struct device *dev, u8 reg, u8 data)
{
struct adt7x10_data *d = dev_get_drvdata(dev);
return d->ops->write_byte(dev, reg, data);
}
static int adt7x10_read_word(struct device *dev, u8 reg)
{
struct adt7x10_data *d = dev_get_drvdata(dev);
return d->ops->read_word(dev, reg);
}
static int adt7x10_write_word(struct device *dev, u8 reg, u16 data)
{
struct adt7x10_data *d = dev_get_drvdata(dev);
return d->ops->write_word(dev, reg, data);
}
static const u8 ADT7X10_REG_TEMP[4] = {
ADT7X10_TEMPERATURE, /* input */
ADT7X10_T_ALARM_HIGH, /* high */
ADT7X10_T_ALARM_LOW, /* low */
ADT7X10_T_CRIT, /* critical */
static const u8 ADT7X10_REG_TEMP[] = {
[adt7x10_temperature] = ADT7X10_TEMPERATURE, /* input */
[adt7x10_t_alarm_high] = ADT7X10_T_ALARM_HIGH, /* high */
[adt7x10_t_alarm_low] = ADT7X10_T_ALARM_LOW, /* low */
[adt7x10_t_crit] = ADT7X10_T_CRIT, /* critical */
};
static irqreturn_t adt7x10_irq_handler(int irq, void *private)
{
struct device *dev = private;
int status;
struct adt7x10_data *d = dev_get_drvdata(dev);
unsigned int status;
int ret;
status = adt7x10_read_byte(dev, ADT7X10_STATUS);
if (status < 0)
ret = regmap_read(d->regmap, ADT7X10_STATUS, &status);
if (ret < 0)
return IRQ_HANDLED;
if (status & ADT7X10_STAT_T_HIGH)
sysfs_notify(&dev->kobj, NULL, "temp1_max_alarm");
hwmon_notify_event(dev, hwmon_temp, hwmon_temp_max_alarm, 0);
if (status & ADT7X10_STAT_T_LOW)
sysfs_notify(&dev->kobj, NULL, "temp1_min_alarm");
hwmon_notify_event(dev, hwmon_temp, hwmon_temp_min_alarm, 0);
if (status & ADT7X10_STAT_T_CRIT)
sysfs_notify(&dev->kobj, NULL, "temp1_crit_alarm");
hwmon_notify_event(dev, hwmon_temp, hwmon_temp_crit_alarm, 0);
return IRQ_HANDLED;
}
static int adt7x10_temp_ready(struct device *dev)
static int adt7x10_temp_ready(struct regmap *regmap)
{
int i, status;
unsigned int status;
int i, ret;
for (i = 0; i < 6; i++) {
status = adt7x10_read_byte(dev, ADT7X10_STATUS);
if (status < 0)
return status;
ret = regmap_read(regmap, ADT7X10_STATUS, &status);
if (ret < 0)
return ret;
if (!(status & ADT7X10_STAT_NOT_RDY))
return 0;
msleep(60);
@ -134,71 +112,10 @@ static int adt7x10_temp_ready(struct device *dev)
return -ETIMEDOUT;
}
static int adt7x10_update_temp(struct device *dev)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|| !data->valid) {
int temp;
dev_dbg(dev, "Starting update\n");
ret = adt7x10_temp_ready(dev); /* check for new value */
if (ret)
goto abort;
temp = adt7x10_read_word(dev, ADT7X10_REG_TEMP[0]);
if (temp < 0) {
ret = temp;
dev_dbg(dev, "Failed to read value: reg %d, error %d\n",
ADT7X10_REG_TEMP[0], ret);
goto abort;
}
data->temp[0] = temp;
data->last_updated = jiffies;
data->valid = true;
}
abort:
mutex_unlock(&data->update_lock);
return ret;
}
static int adt7x10_fill_cache(struct device *dev)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
int ret;
int i;
for (i = 1; i < ARRAY_SIZE(data->temp); i++) {
ret = adt7x10_read_word(dev, ADT7X10_REG_TEMP[i]);
if (ret < 0) {
dev_dbg(dev, "Failed to read value: reg %d, error %d\n",
ADT7X10_REG_TEMP[i], ret);
return ret;
}
data->temp[i] = ret;
}
ret = adt7x10_read_byte(dev, ADT7X10_T_HYST);
if (ret < 0) {
dev_dbg(dev, "Failed to read value: reg %d, error %d\n",
ADT7X10_T_HYST, ret);
return ret;
}
data->hyst = ret;
return 0;
}
static s16 ADT7X10_TEMP_TO_REG(long temp)
{
return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN,
ADT7X10_TEMP_MAX) * 128, 1000);
ADT7X10_TEMP_MAX) * 128, 1000);
}
static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg)
@ -215,170 +132,233 @@ static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg)
/*-----------------------------------------------------------------------*/
/* sysfs attributes for hwmon */
static ssize_t adt7x10_temp_show(struct device *dev,
struct device_attribute *da, char *buf)
static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct adt7x10_data *data = dev_get_drvdata(dev);
if (attr->index == 0) {
int ret;
ret = adt7x10_update_temp(dev);
if (ret)
return ret;
}
return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data,
data->temp[attr->index]));
}
static ssize_t adt7x10_temp_store(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct adt7x10_data *data = dev_get_drvdata(dev);
int nr = attr->index;
long temp;
unsigned int regval;
int ret;
ret = kstrtol(buf, 10, &temp);
mutex_lock(&data->update_lock);
if (index == adt7x10_temperature && !data->valid) {
/* wait for valid temperature */
ret = adt7x10_temp_ready(data->regmap);
if (ret) {
mutex_unlock(&data->update_lock);
return ret;
}
data->valid = true;
}
mutex_unlock(&data->update_lock);
ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &regval);
if (ret)
return ret;
mutex_lock(&data->update_lock);
data->temp[nr] = ADT7X10_TEMP_TO_REG(temp);
ret = adt7x10_write_word(dev, ADT7X10_REG_TEMP[nr], data->temp[nr]);
if (ret)
count = ret;
mutex_unlock(&data->update_lock);
return count;
*val = ADT7X10_REG_TO_TEMP(data, regval);
return 0;
}
static ssize_t adt7x10_t_hyst_show(struct device *dev,
struct device_attribute *da, char *buf)
static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct adt7x10_data *data = dev_get_drvdata(dev);
int nr = attr->index;
int hyst;
int ret;
hyst = (data->hyst & ADT7X10_T_HYST_MASK) * 1000;
mutex_lock(&data->update_lock);
ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[index],
ADT7X10_TEMP_TO_REG(temp));
mutex_unlock(&data->update_lock);
return ret;
}
static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val)
{
int hyst, temp, ret;
mutex_lock(&data->update_lock);
ret = regmap_read(data->regmap, ADT7X10_T_HYST, &hyst);
if (ret) {
mutex_unlock(&data->update_lock);
return ret;
}
ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &temp);
mutex_unlock(&data->update_lock);
if (ret)
return ret;
hyst = (hyst & ADT7X10_T_HYST_MASK) * 1000;
/*
* hysteresis is stored as a 4 bit offset in the device, convert it
* to an absolute value
*/
if (nr == 2) /* min has positive offset, others have negative */
/* min has positive offset, others have negative */
if (index == adt7x10_t_alarm_low)
hyst = -hyst;
return sprintf(buf, "%d\n",
ADT7X10_REG_TO_TEMP(data, data->temp[nr]) - hyst);
*val = ADT7X10_REG_TO_TEMP(data, temp) - hyst;
return 0;
}
static ssize_t adt7x10_t_hyst_store(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
static int adt7x10_hyst_write(struct adt7x10_data *data, long hyst)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
unsigned int regval;
int limit, ret;
long hyst;
ret = kstrtol(buf, 10, &hyst);
if (ret)
return ret;
mutex_lock(&data->update_lock);
/* convert absolute hysteresis value to a 4 bit delta value */
limit = ADT7X10_REG_TO_TEMP(data, data->temp[1]);
hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX);
data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000),
0, ADT7X10_T_HYST_MASK);
ret = adt7x10_write_byte(dev, ADT7X10_T_HYST, data->hyst);
if (ret)
return ret;
ret = regmap_read(data->regmap, ADT7X10_T_ALARM_HIGH, &regval);
if (ret < 0)
goto abort;
return count;
limit = ADT7X10_REG_TO_TEMP(data, regval);
hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX);
regval = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), 0,
ADT7X10_T_HYST_MASK);
ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval);
abort:
mutex_unlock(&data->update_lock);
return ret;
}
static ssize_t adt7x10_alarm_show(struct device *dev,
struct device_attribute *da, char *buf)
static int adt7x10_alarm_read(struct adt7x10_data *data, int index, long *val)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
unsigned int status;
int ret;
ret = adt7x10_read_byte(dev, ADT7X10_STATUS);
ret = regmap_read(data->regmap, ADT7X10_STATUS, &status);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", !!(ret & attr->index));
*val = !!(status & index);
return 0;
}
static ssize_t name_show(struct device *dev, struct device_attribute *da,
char *buf)
static umode_t adt7x10_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (attr) {
case hwmon_temp_max:
case hwmon_temp_min:
case hwmon_temp_crit:
case hwmon_temp_max_hyst:
return 0644;
case hwmon_temp_input:
case hwmon_temp_min_alarm:
case hwmon_temp_max_alarm:
case hwmon_temp_crit_alarm:
case hwmon_temp_min_hyst:
case hwmon_temp_crit_hyst:
return 0444;
default:
break;
}
return 0;
}
static int adt7x10_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", data->name);
switch (attr) {
case hwmon_temp_input:
return adt7x10_temp_read(data, adt7x10_temperature, val);
case hwmon_temp_max:
return adt7x10_temp_read(data, adt7x10_t_alarm_high, val);
case hwmon_temp_min:
return adt7x10_temp_read(data, adt7x10_t_alarm_low, val);
case hwmon_temp_crit:
return adt7x10_temp_read(data, adt7x10_t_crit, val);
case hwmon_temp_max_hyst:
return adt7x10_hyst_read(data, adt7x10_t_alarm_high, val);
case hwmon_temp_min_hyst:
return adt7x10_hyst_read(data, adt7x10_t_alarm_low, val);
case hwmon_temp_crit_hyst:
return adt7x10_hyst_read(data, adt7x10_t_crit, val);
case hwmon_temp_min_alarm:
return adt7x10_alarm_read(data, ADT7X10_STAT_T_LOW, val);
case hwmon_temp_max_alarm:
return adt7x10_alarm_read(data, ADT7X10_STAT_T_HIGH, val);
case hwmon_temp_crit_alarm:
return adt7x10_alarm_read(data, ADT7X10_STAT_T_CRIT, val);
default:
return -EOPNOTSUPP;
}
}
static SENSOR_DEVICE_ATTR_RO(temp1_input, adt7x10_temp, 0);
static SENSOR_DEVICE_ATTR_RW(temp1_max, adt7x10_temp, 1);
static SENSOR_DEVICE_ATTR_RW(temp1_min, adt7x10_temp, 2);
static SENSOR_DEVICE_ATTR_RW(temp1_crit, adt7x10_temp, 3);
static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, adt7x10_t_hyst, 1);
static SENSOR_DEVICE_ATTR_RO(temp1_min_hyst, adt7x10_t_hyst, 2);
static SENSOR_DEVICE_ATTR_RO(temp1_crit_hyst, adt7x10_t_hyst, 3);
static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, adt7x10_alarm,
ADT7X10_STAT_T_LOW);
static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, adt7x10_alarm,
ADT7X10_STAT_T_HIGH);
static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, adt7x10_alarm,
ADT7X10_STAT_T_CRIT);
static DEVICE_ATTR_RO(name);
static int adt7x10_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
static struct attribute *adt7x10_attributes[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr,
&sensor_dev_attr_temp1_min.dev_attr.attr,
&sensor_dev_attr_temp1_crit.dev_attr.attr,
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
&sensor_dev_attr_temp1_min_hyst.dev_attr.attr,
&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
NULL
switch (attr) {
case hwmon_temp_max:
return adt7x10_temp_write(data, adt7x10_t_alarm_high, val);
case hwmon_temp_min:
return adt7x10_temp_write(data, adt7x10_t_alarm_low, val);
case hwmon_temp_crit:
return adt7x10_temp_write(data, adt7x10_t_crit, val);
case hwmon_temp_max_hyst:
return adt7x10_hyst_write(data, val);
default:
return -EOPNOTSUPP;
}
}
static const struct hwmon_channel_info *adt7x10_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_MIN_HYST |
HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM |
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM),
NULL,
};
static const struct attribute_group adt7x10_group = {
.attrs = adt7x10_attributes,
static const struct hwmon_ops adt7x10_hwmon_ops = {
.is_visible = adt7x10_is_visible,
.read = adt7x10_read,
.write = adt7x10_write,
};
static const struct hwmon_chip_info adt7x10_chip_info = {
.ops = &adt7x10_hwmon_ops,
.info = adt7x10_info,
};
static void adt7x10_restore_config(void *private)
{
struct adt7x10_data *data = private;
regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig);
}
int adt7x10_probe(struct device *dev, const char *name, int irq,
const struct adt7x10_ops *ops)
struct regmap *regmap)
{
struct adt7x10_data *data;
unsigned int config;
struct device *hdev;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->ops = ops;
data->name = name;
data->regmap = regmap;
dev_set_drvdata(dev, data);
mutex_init(&data->update_lock);
/* configure as specified */
ret = adt7x10_read_byte(dev, ADT7X10_CONFIG);
ret = regmap_read(regmap, ADT7X10_CONFIG, &config);
if (ret < 0) {
dev_dbg(dev, "Can't read config? %d\n", ret);
return ret;
}
data->oldconfig = ret;
data->oldconfig = config;
/*
* Set to 16 bit resolution, continous conversion and comparator mode.
@ -389,92 +369,49 @@ int adt7x10_probe(struct device *dev, const char *name, int irq,
data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE;
if (data->config != data->oldconfig) {
ret = adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config);
ret = regmap_write(regmap, ADT7X10_CONFIG, data->config);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, adt7x10_restore_config, data);
if (ret)
return ret;
}
dev_dbg(dev, "Config %02x\n", data->config);
ret = adt7x10_fill_cache(dev);
if (ret)
goto exit_restore;
/* Register sysfs hooks */
ret = sysfs_create_group(&dev->kobj, &adt7x10_group);
if (ret)
goto exit_restore;
/*
* The I2C device will already have it's own 'name' attribute, but for
* the SPI device we need to register it. name will only be non NULL if
* the device doesn't register the 'name' attribute on its own.
*/
if (name) {
ret = device_create_file(dev, &dev_attr_name);
if (ret)
goto exit_remove;
}
data->hwmon_dev = hwmon_device_register(dev);
if (IS_ERR(data->hwmon_dev)) {
ret = PTR_ERR(data->hwmon_dev);
goto exit_remove_name;
}
hdev = devm_hwmon_device_register_with_info(dev, name, data,
&adt7x10_chip_info, NULL);
if (IS_ERR(hdev))
return PTR_ERR(hdev);
if (irq > 0) {
ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(dev), dev);
ret = devm_request_threaded_irq(dev, irq, NULL,
adt7x10_irq_handler,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
dev_name(dev), hdev);
if (ret)
goto exit_hwmon_device_unregister;
return ret;
}
return 0;
exit_hwmon_device_unregister:
hwmon_device_unregister(data->hwmon_dev);
exit_remove_name:
if (name)
device_remove_file(dev, &dev_attr_name);
exit_remove:
sysfs_remove_group(&dev->kobj, &adt7x10_group);
exit_restore:
adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig);
return ret;
}
EXPORT_SYMBOL_GPL(adt7x10_probe);
void adt7x10_remove(struct device *dev, int irq)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
if (irq > 0)
free_irq(irq, dev);
hwmon_device_unregister(data->hwmon_dev);
if (data->name)
device_remove_file(dev, &dev_attr_name);
sysfs_remove_group(&dev->kobj, &adt7x10_group);
if (data->oldconfig != data->config)
adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig);
}
EXPORT_SYMBOL_GPL(adt7x10_remove);
#ifdef CONFIG_PM_SLEEP
static int adt7x10_suspend(struct device *dev)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
return adt7x10_write_byte(dev, ADT7X10_CONFIG,
data->config | ADT7X10_PD);
return regmap_write(data->regmap, ADT7X10_CONFIG,
data->config | ADT7X10_PD);
}
static int adt7x10_resume(struct device *dev)
{
struct adt7x10_data *data = dev_get_drvdata(dev);
return adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config);
return regmap_write(data->regmap, ADT7X10_CONFIG, data->config);
}
SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume);

View File

@ -17,16 +17,8 @@
struct device;
struct adt7x10_ops {
int (*read_byte)(struct device *, u8 reg);
int (*write_byte)(struct device *, u8 reg, u8 data);
int (*read_word)(struct device *, u8 reg);
int (*write_word)(struct device *, u8 reg, u16 data);
};
int adt7x10_probe(struct device *dev, const char *name, int irq,
const struct adt7x10_ops *ops);
void adt7x10_remove(struct device *dev, int irq);
struct regmap *regmap);
#ifdef CONFIG_PM_SLEEP
extern const struct dev_pm_ops adt7x10_dev_pm_ops;

View File

@ -1,32 +1,41 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* hwmon driver for Aquacomputer D5 Next watercooling pump
* hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360)
*
* The D5 Next sends HID reports (with ID 0x01) every second to report sensor values
* (coolant temperature, pump and fan speed, voltage, current and power). It responds to
* Get_Report requests, but returns a dummy value of no use.
* Aquacomputer devices send HID reports (with ID 0x01) every second to report
* sensor values.
*
* Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/debugfs.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <asm/unaligned.h>
#define DRIVER_NAME "aquacomputer-d5next"
#define USB_VENDOR_ID_AQUACOMPUTER 0x0c70
#define USB_PRODUCT_ID_D5NEXT 0xf00e
#define USB_PRODUCT_ID_FARBWERK360 0xf010
#define D5NEXT_STATUS_REPORT_ID 0x01
#define D5NEXT_STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */
enum kinds { d5next, farbwerk360 };
static const char *const aqc_device_names[] = {
[d5next] = "d5next",
[farbwerk360] = "farbwerk360"
};
#define DRIVER_NAME "aquacomputer_d5next"
#define STATUS_REPORT_ID 0x01
#define STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */
#define SERIAL_FIRST_PART 3
#define SERIAL_SECOND_PART 5
#define FIRMWARE_VERSION 13
/* Register offsets for the D5 Next pump */
#define D5NEXT_SERIAL_FIRST_PART 3
#define D5NEXT_SERIAL_SECOND_PART 5
#define D5NEXT_FIRMWARE_VERSION 13
#define D5NEXT_POWER_CYCLES 24
#define D5NEXT_COOLANT_TEMP 87
@ -44,76 +53,118 @@
#define D5NEXT_PUMP_CURRENT 112
#define D5NEXT_FAN_CURRENT 99
/* Labels for provided values */
/* Register offsets for the Farbwerk 360 RGB controller */
#define FARBWERK360_NUM_SENSORS 4
#define FARBWERK360_SENSOR_START 0x32
#define FARBWERK360_SENSOR_SIZE 0x02
#define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF
#define L_COOLANT_TEMP "Coolant temp"
/* Labels for D5 Next */
#define L_D5NEXT_COOLANT_TEMP "Coolant temp"
#define L_PUMP_SPEED "Pump speed"
#define L_FAN_SPEED "Fan speed"
#define L_PUMP_POWER "Pump power"
#define L_FAN_POWER "Fan power"
#define L_PUMP_VOLTAGE "Pump voltage"
#define L_FAN_VOLTAGE "Fan voltage"
#define L_5V_VOLTAGE "+5V voltage"
#define L_PUMP_CURRENT "Pump current"
#define L_FAN_CURRENT "Fan current"
static const char *const label_speeds[] = {
L_PUMP_SPEED,
L_FAN_SPEED,
static const char *const label_d5next_speeds[] = {
"Pump speed",
"Fan speed"
};
static const char *const label_power[] = {
L_PUMP_POWER,
L_FAN_POWER,
static const char *const label_d5next_power[] = {
"Pump power",
"Fan power"
};
static const char *const label_voltages[] = {
L_PUMP_VOLTAGE,
L_FAN_VOLTAGE,
L_5V_VOLTAGE,
static const char *const label_d5next_voltages[] = {
"Pump voltage",
"Fan voltage",
"+5V voltage"
};
static const char *const label_current[] = {
L_PUMP_CURRENT,
L_FAN_CURRENT,
static const char *const label_d5next_current[] = {
"Pump current",
"Fan current"
};
struct d5next_data {
/* Labels for Farbwerk 360 temperature sensors */
static const char *const label_temp_sensors[] = {
"Sensor 1",
"Sensor 2",
"Sensor 3",
"Sensor 4"
};
struct aqc_data {
struct hid_device *hdev;
struct device *hwmon_dev;
struct dentry *debugfs;
s32 temp_input;
enum kinds kind;
const char *name;
/* General info, same across all devices */
u32 serial_number[2];
u16 firmware_version;
/* D5 Next specific - how many times the device was powered on */
u32 power_cycles;
/* Sensor values */
s32 temp_input[4];
u16 speed_input[2];
u32 power_input[2];
u16 voltage_input[3];
u16 current_input[2];
u32 serial_number[2];
u16 firmware_version;
u32 power_cycles; /* How many times the device was powered on */
unsigned long updated;
};
static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
return 0444;
const struct aqc_data *priv = data;
switch (type) {
case hwmon_temp:
switch (priv->kind) {
case d5next:
if (channel == 0)
return 0444;
break;
case farbwerk360:
return 0444;
default:
break;
}
break;
case hwmon_fan:
case hwmon_power:
case hwmon_in:
case hwmon_curr:
switch (priv->kind) {
case d5next:
return 0444;
default:
break;
}
break;
default:
break;
}
return 0;
}
static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct d5next_data *priv = dev_get_drvdata(dev);
struct aqc_data *priv = dev_get_drvdata(dev);
if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL))
if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL))
return -ENODATA;
switch (type) {
case hwmon_temp:
*val = priv->temp_input;
if (priv->temp_input[channel] == -ENODATA)
return -ENODATA;
*val = priv->temp_input[channel];
break;
case hwmon_fan:
*val = priv->speed_input[channel];
@ -134,24 +185,59 @@ static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 att
return 0;
}
static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
struct aqc_data *priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_temp:
*str = L_COOLANT_TEMP;
switch (priv->kind) {
case d5next:
*str = L_D5NEXT_COOLANT_TEMP;
break;
case farbwerk360:
*str = label_temp_sensors[channel];
break;
default:
break;
}
break;
case hwmon_fan:
*str = label_speeds[channel];
switch (priv->kind) {
case d5next:
*str = label_d5next_speeds[channel];
break;
default:
break;
}
break;
case hwmon_power:
*str = label_power[channel];
switch (priv->kind) {
case d5next:
*str = label_d5next_power[channel];
break;
default:
break;
}
break;
case hwmon_in:
*str = label_voltages[channel];
switch (priv->kind) {
case d5next:
*str = label_d5next_voltages[channel];
break;
default:
break;
}
break;
case hwmon_curr:
*str = label_current[channel];
switch (priv->kind) {
case d5next:
*str = label_d5next_current[channel];
break;
default:
break;
}
break;
default:
return -EOPNOTSUPP;
@ -160,60 +246,89 @@ static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type,
return 0;
}
static const struct hwmon_ops d5next_hwmon_ops = {
.is_visible = d5next_is_visible,
.read = d5next_read,
.read_string = d5next_read_string,
static const struct hwmon_ops aqc_hwmon_ops = {
.is_visible = aqc_is_visible,
.read = aqc_read,
.read_string = aqc_read_string,
};
static const struct hwmon_channel_info *d5next_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(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL),
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
static const struct hwmon_channel_info *aqc_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_LABEL,
HWMON_P_INPUT | HWMON_P_LABEL),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL),
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL),
NULL
};
static const struct hwmon_chip_info d5next_chip_info = {
.ops = &d5next_hwmon_ops,
.info = d5next_info,
static const struct hwmon_chip_info aqc_chip_info = {
.ops = &aqc_hwmon_ops,
.info = aqc_info,
};
static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
int size)
{
struct d5next_data *priv;
int i, sensor_value;
struct aqc_data *priv;
if (report->id != D5NEXT_STATUS_REPORT_ID)
if (report->id != STATUS_REPORT_ID)
return 0;
priv = hid_get_drvdata(hdev);
/* Info provided with every report */
priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART);
priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART);
priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION);
priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
priv->serial_number[0] = get_unaligned_be16(data + SERIAL_FIRST_PART);
priv->serial_number[1] = get_unaligned_be16(data + SERIAL_SECOND_PART);
priv->firmware_version = get_unaligned_be16(data + FIRMWARE_VERSION);
/* Sensor readings */
switch (priv->kind) {
case d5next:
priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
priv->temp_input[0] = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
break;
case farbwerk360:
/* Temperature sensor readings */
for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) {
sensor_value = get_unaligned_be16(data + FARBWERK360_SENSOR_START +
i * FARBWERK360_SENSOR_SIZE);
if (sensor_value == FARBWERK360_SENSOR_DISCONNECTED)
priv->temp_input[i] = -ENODATA;
else
priv->temp_input[i] = sensor_value * 10;
}
break;
default:
break;
}
priv->updated = jiffies;
@ -224,7 +339,7 @@ static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report,
static int serial_number_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
struct aqc_data *priv = seqf->private;
seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]);
@ -234,7 +349,7 @@ DEFINE_SHOW_ATTRIBUTE(serial_number);
static int firmware_version_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
struct aqc_data *priv = seqf->private;
seq_printf(seqf, "%u\n", priv->firmware_version);
@ -244,7 +359,7 @@ DEFINE_SHOW_ATTRIBUTE(firmware_version);
static int power_cycles_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
struct aqc_data *priv = seqf->private;
seq_printf(seqf, "%u\n", priv->power_cycles);
@ -252,29 +367,32 @@ static int power_cycles_show(struct seq_file *seqf, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(power_cycles);
static void d5next_debugfs_init(struct d5next_data *priv)
static void aqc_debugfs_init(struct aqc_data *priv)
{
char name[32];
char name[64];
scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
scnprintf(name, sizeof(name), "%s_%s-%s", "aquacomputer", priv->name,
dev_name(&priv->hdev->dev));
priv->debugfs = debugfs_create_dir(name, NULL);
debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
if (priv->kind == d5next)
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
}
#else
static void d5next_debugfs_init(struct d5next_data *priv)
static void aqc_debugfs_init(struct aqc_data *priv)
{
}
#endif
static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct d5next_data *priv;
struct aqc_data *priv;
int ret;
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
@ -284,7 +402,7 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL;
priv->updated = jiffies - STATUS_UPDATE_INTERVAL;
ret = hid_parse(hdev);
if (ret)
@ -298,15 +416,28 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret)
goto fail_and_stop;
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv,
&d5next_chip_info, NULL);
switch (hdev->product) {
case USB_PRODUCT_ID_D5NEXT:
priv->kind = d5next;
break;
case USB_PRODUCT_ID_FARBWERK360:
priv->kind = farbwerk360;
break;
default:
break;
}
priv->name = aqc_device_names[priv->kind];
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv,
&aqc_chip_info, NULL);
if (IS_ERR(priv->hwmon_dev)) {
ret = PTR_ERR(priv->hwmon_dev);
goto fail_and_close;
}
d5next_debugfs_init(priv);
aqc_debugfs_init(priv);
return 0;
@ -317,9 +448,9 @@ fail_and_stop:
return ret;
}
static void d5next_remove(struct hid_device *hdev)
static void aqc_remove(struct hid_device *hdev)
{
struct d5next_data *priv = hid_get_drvdata(hdev);
struct aqc_data *priv = hid_get_drvdata(hdev);
debugfs_remove_recursive(priv->debugfs);
hwmon_device_unregister(priv->hwmon_dev);
@ -328,36 +459,36 @@ static void d5next_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
static const struct hid_device_id d5next_table[] = {
{ HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */
{},
static const struct hid_device_id aqc_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) },
{ }
};
MODULE_DEVICE_TABLE(hid, d5next_table);
MODULE_DEVICE_TABLE(hid, aqc_table);
static struct hid_driver d5next_driver = {
static struct hid_driver aqc_driver = {
.name = DRIVER_NAME,
.id_table = d5next_table,
.probe = d5next_probe,
.remove = d5next_remove,
.raw_event = d5next_raw_event,
.id_table = aqc_table,
.probe = aqc_probe,
.remove = aqc_remove,
.raw_event = aqc_raw_event,
};
static int __init d5next_init(void)
static int __init aqc_init(void)
{
return hid_register_driver(&d5next_driver);
return hid_register_driver(&aqc_driver);
}
static void __exit d5next_exit(void)
static void __exit aqc_exit(void)
{
hid_unregister_driver(&d5next_driver);
hid_unregister_driver(&aqc_driver);
}
/* Request to initialize after the HID bus to ensure it's not being loaded before */
late_initcall(d5next_init);
module_exit(d5next_exit);
late_initcall(aqc_init);
module_exit(aqc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump");
MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices");

View File

@ -0,0 +1,716 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HWMON driver for ASUS motherboards that publish some sensor values
* via the embedded controller registers.
*
* Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com>
* EC provides:
* - Chipset temperature
* - CPU temperature
* - Motherboard temperature
* - T_Sensor temperature
* - VRM temperature
* - Water In temperature
* - Water Out temperature
* - CPU Optional fan RPM
* - Chipset fan RPM
* - VRM Heat Sink fan RPM
* - Water Flow fan RPM
* - CPU current
* - CPU core voltage
*/
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/dev_printk.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sort.h>
#include <linux/units.h>
#include <asm/unaligned.h>
static char *mutex_path_override;
/* Writing to this EC register switches EC bank */
#define ASUS_EC_BANK_REGISTER 0xff
#define SENSOR_LABEL_LEN 16
/*
* Arbitrary set max. allowed bank number. Required for sorting banks and
* currently is overkill with just 2 banks used at max, but for the sake
* of alignment let's set it to a higher value.
*/
#define ASUS_EC_MAX_BANK 3
#define ACPI_LOCK_DELAY_MS 500
/* ACPI mutex for locking access to the EC for the firmware */
#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX"
/* There are two variants of the vendor spelling */
#define VENDOR_ASUS_UPPER_CASE "ASUSTeK COMPUTER INC."
typedef union {
u32 value;
struct {
u8 index;
u8 bank;
u8 size;
u8 dummy;
} components;
} sensor_address;
#define MAKE_SENSOR_ADDRESS(size, bank, index) { \
.value = (size << 16) + (bank << 8) + index \
}
static u32 hwmon_attributes[hwmon_max] = {
[hwmon_chip] = HWMON_C_REGISTER_TZ,
[hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL,
[hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
[hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
[hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL,
};
struct ec_sensor_info {
char label[SENSOR_LABEL_LEN];
enum hwmon_sensor_types type;
sensor_address addr;
};
#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \
.label = sensor_label, .type = sensor_type, \
.addr = MAKE_SENSOR_ADDRESS(size, bank, index), \
}
enum ec_sensors {
/* chipset temperature [℃] */
ec_sensor_temp_chipset,
/* CPU temperature [℃] */
ec_sensor_temp_cpu,
/* motherboard temperature [℃] */
ec_sensor_temp_mb,
/* "T_Sensor" temperature sensor reading [℃] */
ec_sensor_temp_t_sensor,
/* VRM temperature [℃] */
ec_sensor_temp_vrm,
/* CPU Core voltage [mV] */
ec_sensor_in_cpu_core,
/* CPU_Opt fan [RPM] */
ec_sensor_fan_cpu_opt,
/* VRM heat sink fan [RPM] */
ec_sensor_fan_vrm_hs,
/* Chipset fan [RPM] */
ec_sensor_fan_chipset,
/* Water flow sensor reading [RPM] */
ec_sensor_fan_water_flow,
/* CPU current [A] */
ec_sensor_curr_cpu,
/* "Water_In" temperature sensor reading [℃] */
ec_sensor_temp_water_in,
/* "Water_Out" temperature sensor reading [℃] */
ec_sensor_temp_water_out,
};
#define SENSOR_TEMP_CHIPSET BIT(ec_sensor_temp_chipset)
#define SENSOR_TEMP_CPU BIT(ec_sensor_temp_cpu)
#define SENSOR_TEMP_MB BIT(ec_sensor_temp_mb)
#define SENSOR_TEMP_T_SENSOR BIT(ec_sensor_temp_t_sensor)
#define SENSOR_TEMP_VRM BIT(ec_sensor_temp_vrm)
#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core)
#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt)
#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs)
#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset)
#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow)
#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu)
#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in)
#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out)
/* All the known sensors for ASUS EC controllers */
static const struct ec_sensor_info known_ec_sensors[] = {
[ec_sensor_temp_chipset] =
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
[ec_sensor_temp_mb] =
EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
[ec_sensor_temp_t_sensor] =
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
[ec_sensor_in_cpu_core] =
EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2),
[ec_sensor_fan_cpu_opt] =
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
[ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
[ec_sensor_fan_chipset] =
EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4),
[ec_sensor_fan_water_flow] =
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc),
[ec_sensor_curr_cpu] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4),
[ec_sensor_temp_water_in] =
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
[ec_sensor_temp_water_out] =
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
};
/* Shortcuts for common combinations */
#define SENSOR_SET_TEMP_CHIPSET_CPU_MB \
(SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB)
#define SENSOR_SET_TEMP_WATER (SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT)
#define DMI_EXACT_MATCH_BOARD(vendor, name, sensors) { \
.matches = { \
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \
}, \
.driver_data = (void *)(sensors), \
}
static const struct dmi_system_id asus_ec_dmi_table[] __initconst = {
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE,
"ROG CROSSHAIR VIII DARK HERO",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE,
"ROG CROSSHAIR VIII FORMULA",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE,
"ROG CROSSHAIR VIII HERO (WI-FI)",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET |
SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE,
"ROG CROSSHAIR VIII IMPACT",
SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING",
SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING",
SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING",
SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR |
SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET |
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING",
SENSOR_SET_TEMP_CHIPSET_CPU_MB |
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET),
DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING",
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS |
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE),
{}
};
struct ec_sensor {
unsigned int info_index;
s32 cached_value;
};
struct ec_sensors_data {
unsigned long board_sensors;
struct ec_sensor *sensors;
/* EC registers to read from */
u16 *registers;
u8 *read_buffer;
/* sorted list of unique register banks */
u8 banks[ASUS_EC_MAX_BANK + 1];
/* in jiffies */
unsigned long last_updated;
acpi_handle aml_mutex;
/* number of board EC sensors */
u8 nr_sensors;
/*
* number of EC registers to read
* (sensor might span more than 1 register)
*/
u8 nr_registers;
/* number of unique register banks */
u8 nr_banks;
};
static u8 register_bank(u16 reg)
{
return reg >> 8;
}
static u8 register_index(u16 reg)
{
return reg & 0x00ff;
}
static bool is_sensor_data_signed(const struct ec_sensor_info *si)
{
/*
* guessed from WMI functions in DSDT code for boards
* of the X470 generation
*/
return si->type == hwmon_temp;
}
static const struct ec_sensor_info *
get_sensor_info(const struct ec_sensors_data *state, int index)
{
return &known_ec_sensors[state->sensors[index].info_index];
}
static int find_ec_sensor_index(const struct ec_sensors_data *ec,
enum hwmon_sensor_types type, int channel)
{
unsigned int i;
for (i = 0; i < ec->nr_sensors; i++) {
if (get_sensor_info(ec, i)->type == type) {
if (channel == 0)
return i;
channel--;
}
}
return -ENOENT;
}
static int __init bank_compare(const void *a, const void *b)
{
return *((const s8 *)a) - *((const s8 *)b);
}
static int __init board_sensors_count(unsigned long sensors)
{
return hweight_long(sensors);
}
static void __init setup_sensor_data(struct ec_sensors_data *ec)
{
struct ec_sensor *s = ec->sensors;
bool bank_found;
int i, j;
u8 bank;
ec->nr_banks = 0;
ec->nr_registers = 0;
for_each_set_bit(i, &ec->board_sensors,
BITS_PER_TYPE(ec->board_sensors)) {
s->info_index = i;
s->cached_value = 0;
ec->nr_registers +=
known_ec_sensors[s->info_index].addr.components.size;
bank_found = false;
bank = known_ec_sensors[s->info_index].addr.components.bank;
for (j = 0; j < ec->nr_banks; j++) {
if (ec->banks[j] == bank) {
bank_found = true;
break;
}
}
if (!bank_found) {
ec->banks[ec->nr_banks++] = bank;
}
s++;
}
sort(ec->banks, ec->nr_banks, 1, bank_compare, NULL);
}
static void __init fill_ec_registers(struct ec_sensors_data *ec)
{
const struct ec_sensor_info *si;
unsigned int i, j, register_idx = 0;
for (i = 0; i < ec->nr_sensors; ++i) {
si = get_sensor_info(ec, i);
for (j = 0; j < si->addr.components.size; ++j, ++register_idx) {
ec->registers[register_idx] =
(si->addr.components.bank << 8) +
si->addr.components.index + j;
}
}
}
static acpi_handle __init asus_hw_access_mutex(struct device *dev)
{
const char *mutex_path;
acpi_handle res;
int status;
mutex_path = mutex_path_override ?
mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX;
status = acpi_get_handle(NULL, (acpi_string)mutex_path, &res);
if (ACPI_FAILURE(status)) {
dev_err(dev,
"Could not get hardware access guard mutex '%s': error %d",
mutex_path, status);
return NULL;
}
return res;
}
static int asus_ec_bank_switch(u8 bank, u8 *old)
{
int status = 0;
if (old) {
status = ec_read(ASUS_EC_BANK_REGISTER, old);
}
if (status || (old && (*old == bank)))
return status;
return ec_write(ASUS_EC_BANK_REGISTER, bank);
}
static int asus_ec_block_read(const struct device *dev,
struct ec_sensors_data *ec)
{
int ireg, ibank, status;
u8 bank, reg_bank, prev_bank;
bank = 0;
status = asus_ec_bank_switch(bank, &prev_bank);
if (status) {
dev_warn(dev, "EC bank switch failed");
return status;
}
if (prev_bank) {
/* oops... somebody else is working with the EC too */
dev_warn(dev,
"Concurrent access to the ACPI EC detected.\nRace condition possible.");
}
/* read registers minimizing bank switches. */
for (ibank = 0; ibank < ec->nr_banks; ibank++) {
if (bank != ec->banks[ibank]) {
bank = ec->banks[ibank];
if (asus_ec_bank_switch(bank, NULL)) {
dev_warn(dev, "EC bank switch to %d failed",
bank);
break;
}
}
for (ireg = 0; ireg < ec->nr_registers; ireg++) {
reg_bank = register_bank(ec->registers[ireg]);
if (reg_bank < bank) {
continue;
}
ec_read(register_index(ec->registers[ireg]),
ec->read_buffer + ireg);
}
}
status = asus_ec_bank_switch(prev_bank, NULL);
return status;
}
static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data)
{
if (is_sensor_data_signed(si)) {
switch (si->addr.components.size) {
case 1:
return (s8)*data;
case 2:
return (s16)get_unaligned_be16(data);
case 4:
return (s32)get_unaligned_be32(data);
default:
return 0;
}
} else {
switch (si->addr.components.size) {
case 1:
return *data;
case 2:
return get_unaligned_be16(data);
case 4:
return get_unaligned_be32(data);
default:
return 0;
}
}
}
static void update_sensor_values(struct ec_sensors_data *ec, u8 *data)
{
const struct ec_sensor_info *si;
struct ec_sensor *s;
for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) {
si = &known_ec_sensors[s->info_index];
s->cached_value = get_sensor_value(si, data);
data += si->addr.components.size;
}
}
static int update_ec_sensors(const struct device *dev,
struct ec_sensors_data *ec)
{
int status;
/*
* ASUS DSDT does not specify that access to the EC has to be guarded,
* but firmware does access it via ACPI
*/
if (ACPI_FAILURE(acpi_acquire_mutex(ec->aml_mutex, NULL,
ACPI_LOCK_DELAY_MS))) {
dev_err(dev, "Failed to acquire AML mutex");
status = -EBUSY;
goto cleanup;
}
status = asus_ec_block_read(dev, ec);
if (!status) {
update_sensor_values(ec, ec->read_buffer);
}
if (ACPI_FAILURE(acpi_release_mutex(ec->aml_mutex, NULL))) {
dev_err(dev, "Failed to release AML mutex");
}
cleanup:
return status;
}
static long scale_sensor_value(s32 value, int data_type)
{
switch (data_type) {
case hwmon_curr:
case hwmon_temp:
return value * MILLI;
default:
return value;
}
}
static int get_cached_value_or_update(const struct device *dev,
int sensor_index,
struct ec_sensors_data *state, s32 *value)
{
if (time_after(jiffies, state->last_updated + HZ)) {
if (update_ec_sensors(dev, state)) {
dev_err(dev, "update_ec_sensors() failure\n");
return -EIO;
}
state->last_updated = jiffies;
}
*value = state->sensors[sensor_index].cached_value;
return 0;
}
/*
* Now follow the functions that implement the hwmon interface
*/
static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
int ret;
s32 value = 0;
struct ec_sensors_data *state = dev_get_drvdata(dev);
int sidx = find_ec_sensor_index(state, type, channel);
if (sidx < 0) {
return sidx;
}
ret = get_cached_value_or_update(dev, sidx, state, &value);
if (!ret) {
*val = scale_sensor_value(value,
get_sensor_info(state, sidx)->type);
}
return ret;
}
static int asus_ec_hwmon_read_string(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
struct ec_sensors_data *state = dev_get_drvdata(dev);
int sensor_index = find_ec_sensor_index(state, type, channel);
*str = get_sensor_info(state, sensor_index)->label;
return 0;
}
static umode_t asus_ec_hwmon_is_visible(const void *drvdata,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
const struct ec_sensors_data *state = drvdata;
return find_ec_sensor_index(state, type, channel) >= 0 ? S_IRUGO : 0;
}
static int __init
asus_ec_hwmon_add_chan_info(struct hwmon_channel_info *asus_ec_hwmon_chan,
struct device *dev, int num,
enum hwmon_sensor_types type, u32 config)
{
int i;
u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return -ENOMEM;
asus_ec_hwmon_chan->type = type;
asus_ec_hwmon_chan->config = cfg;
for (i = 0; i < num; i++, cfg++)
*cfg = config;
return 0;
}
static const struct hwmon_ops asus_ec_hwmon_ops = {
.is_visible = asus_ec_hwmon_is_visible,
.read = asus_ec_hwmon_read,
.read_string = asus_ec_hwmon_read_string,
};
static struct hwmon_chip_info asus_ec_chip_info = {
.ops = &asus_ec_hwmon_ops,
};
static unsigned long __init get_board_sensors(void)
{
const struct dmi_system_id *dmi_entry =
dmi_first_match(asus_ec_dmi_table);
return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0;
}
static int __init asus_ec_probe(struct platform_device *pdev)
{
const struct hwmon_channel_info **ptr_asus_ec_ci;
int nr_count[hwmon_max] = { 0 }, nr_types = 0;
struct hwmon_channel_info *asus_ec_hwmon_chan;
const struct hwmon_chip_info *chip_info;
struct device *dev = &pdev->dev;
struct ec_sensors_data *ec_data;
const struct ec_sensor_info *si;
enum hwmon_sensor_types type;
unsigned long board_sensors;
struct device *hwdev;
unsigned int i;
board_sensors = get_board_sensors();
if (!board_sensors)
return -ENODEV;
ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data),
GFP_KERNEL);
if (!ec_data)
return -ENOMEM;
dev_set_drvdata(dev, ec_data);
ec_data->board_sensors = board_sensors;
ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors);
ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors,
sizeof(struct ec_sensor), GFP_KERNEL);
setup_sensor_data(ec_data);
ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers,
sizeof(u16), GFP_KERNEL);
ec_data->read_buffer = devm_kcalloc(dev, ec_data->nr_registers,
sizeof(u8), GFP_KERNEL);
if (!ec_data->registers || !ec_data->read_buffer)
return -ENOMEM;
fill_ec_registers(ec_data);
ec_data->aml_mutex = asus_hw_access_mutex(dev);
for (i = 0; i < ec_data->nr_sensors; ++i) {
si = get_sensor_info(ec_data, i);
if (!nr_count[si->type])
++nr_types;
++nr_count[si->type];
}
if (nr_count[hwmon_temp])
nr_count[hwmon_chip]++, nr_types++;
asus_ec_hwmon_chan = devm_kcalloc(
dev, nr_types, sizeof(*asus_ec_hwmon_chan), GFP_KERNEL);
if (!asus_ec_hwmon_chan)
return -ENOMEM;
ptr_asus_ec_ci = devm_kcalloc(dev, nr_types + 1,
sizeof(*ptr_asus_ec_ci), GFP_KERNEL);
if (!ptr_asus_ec_ci)
return -ENOMEM;
asus_ec_chip_info.info = ptr_asus_ec_ci;
chip_info = &asus_ec_chip_info;
for (type = 0; type < hwmon_max; ++type) {
if (!nr_count[type])
continue;
asus_ec_hwmon_add_chan_info(asus_ec_hwmon_chan, dev,
nr_count[type], type,
hwmon_attributes[type]);
*ptr_asus_ec_ci++ = asus_ec_hwmon_chan++;
}
dev_info(dev, "board has %d EC sensors that span %d registers",
ec_data->nr_sensors, ec_data->nr_registers);
hwdev = devm_hwmon_device_register_with_info(dev, "asusec",
ec_data, chip_info, NULL);
return PTR_ERR_OR_ZERO(hwdev);
}
static const struct acpi_device_id acpi_ec_ids[] = {
/* Embedded Controller Device */
{ "PNP0C09", 0 },
{}
};
static struct platform_driver asus_ec_sensors_platform_driver = {
.driver = {
.name = "asus-ec-sensors",
.acpi_match_table = acpi_ec_ids,
},
};
MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table);
module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe);
module_param_named(mutex_path, mutex_path_override, charp, 0);
MODULE_PARM_DESC(mutex_path,
"Override ACPI mutex path used to guard access to hardware");
MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>");
MODULE_DESCRIPTION(
"HWMON driver for sensors accessible via ACPI EC in ASUS motherboards");
MODULE_LICENSE("GPL");

View File

@ -112,7 +112,8 @@ struct asus_wmi_data {
/* boards with EC support */
static struct asus_wmi_data sensors_board_PW_X570_P = {
.known_board_sensors = {
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM,
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
SENSOR_FAN_CHIPSET,
SENSOR_MAX
},

View File

@ -77,6 +77,7 @@ static const struct dmi_system_id asus_wmi_dmi_table[] = {
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING II"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"),
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"),

View File

@ -339,7 +339,8 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
ctl->update_tacho_params = true;
} else {
ctl->hw_pwm_req = false;
sysfs_notify(&ctl->hdev->kobj, NULL, "pwm1");
hwmon_notify_event(ctl->hdev, hwmon_pwm,
hwmon_pwm_input, 0);
}
}

View File

@ -21,6 +21,7 @@
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
@ -86,8 +87,8 @@ MODULE_LICENSE("GPL");
MODULE_ALIAS("i8k");
static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported models");
module_param_unsafe(force, bool, 0);
MODULE_PARM_DESC(force, "Force loading without checking for supported models and features");
static bool ignore_dmi;
module_param(ignore_dmi, bool, 0);
@ -250,46 +251,52 @@ static int i8k_smm(struct smm_regs *regs)
/*
* Read the fan status.
*/
static int i8k_get_fan_status(const struct dell_smm_data *data, int fan)
static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan)
{
struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
struct smm_regs regs = {
.eax = I8K_SMM_GET_FAN,
.ebx = fan,
};
if (data->disallow_fan_support)
return -EINVAL;
regs.ebx = fan & 0xff;
return i8k_smm(&regs) ? : regs.eax & 0xff;
}
/*
* Read the fan speed in RPM.
*/
static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan)
static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan)
{
struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
struct smm_regs regs = {
.eax = I8K_SMM_GET_SPEED,
.ebx = fan,
};
if (data->disallow_fan_support)
return -EINVAL;
regs.ebx = fan & 0xff;
return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
}
/*
* Read the fan type.
*/
static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan)
static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan)
{
struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
struct smm_regs regs = {
.eax = I8K_SMM_GET_FAN_TYPE,
.ebx = fan,
};
if (data->disallow_fan_support || data->disallow_fan_type_call)
return -EINVAL;
regs.ebx = fan & 0xff;
return i8k_smm(&regs) ? : regs.eax & 0xff;
}
static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
{
/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
if (data->fan_type[fan] == INT_MIN)
@ -301,14 +308,16 @@ static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
/*
* Read the fan nominal rpm for specific fan speed.
*/
static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed)
static int __init 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, };
struct smm_regs regs = {
.eax = I8K_SMM_GET_NOM_SPEED,
.ebx = fan | (speed << 8),
};
if (data->disallow_fan_support)
return -EINVAL;
regs.ebx = (fan & 0xff) | (speed << 8);
return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
}
@ -329,7 +338,7 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl
/*
* Set the fan speed (off, low, high, ...).
*/
static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
{
struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
@ -337,33 +346,35 @@ static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
return -EINVAL;
speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
regs.ebx = (fan & 0xff) | (speed << 8);
regs.ebx = fan | (speed << 8);
return i8k_smm(&regs);
}
static int __init i8k_get_temp_type(int sensor)
static int __init i8k_get_temp_type(u8 sensor)
{
struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP_TYPE,
.ebx = sensor,
};
regs.ebx = sensor & 0xff;
return i8k_smm(&regs) ? : regs.eax & 0xff;
}
/*
* Read the cpu temperature.
*/
static int _i8k_get_temp(int sensor)
static int _i8k_get_temp(u8 sensor)
{
struct smm_regs regs = {
.eax = I8K_SMM_GET_TEMP,
.ebx = sensor & 0xff,
.ebx = sensor,
};
return i8k_smm(&regs) ? : regs.eax & 0xff;
}
static int i8k_get_temp(int sensor)
static int i8k_get_temp(u8 sensor)
{
int temp = _i8k_get_temp(sensor);
@ -496,6 +507,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (copy_from_user(&val, argp, sizeof(int)))
return -EFAULT;
if (val > U8_MAX || val < 0)
return -EINVAL;
val = i8k_get_fan_speed(data, val);
break;
@ -503,6 +517,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (copy_from_user(&val, argp, sizeof(int)))
return -EFAULT;
if (val > U8_MAX || val < 0)
return -EINVAL;
val = i8k_get_fan_status(data, val);
break;
@ -513,6 +530,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
if (copy_from_user(&val, argp, sizeof(int)))
return -EFAULT;
if (val > U8_MAX || val < 0)
return -EINVAL;
if (copy_from_user(&speed, argp + 1, sizeof(int)))
return -EFAULT;
@ -631,6 +651,11 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types
case hwmon_temp:
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)
return 0444;
break;
case hwmon_temp_label:
if (data->temp_type[channel] >= 0)
return 0444;
@ -920,7 +945,8 @@ static int __init dell_smm_init_hwmon(struct device *dev)
{
struct dell_smm_data *data = dev_get_drvdata(dev);
struct device *dell_smm_hwmon_dev;
int i, state, err;
int state, err;
u8 i;
for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
data->temp_type[i] = i8k_get_temp_type(i);
@ -1131,6 +1157,13 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
},
},
{
.ident = "Dell Inspiron 3505",
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 3505"),
},
},
{ }
};
@ -1236,7 +1269,8 @@ static int __init dell_smm_probe(struct platform_device *pdev)
{
struct dell_smm_data *data;
const struct dmi_system_id *id, *fan_control;
int fan, ret;
int ret;
u8 fan;
data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
if (!data)

View File

@ -18,6 +18,7 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/thermal.h>
@ -30,6 +31,7 @@
struct hwmon_device {
const char *name;
const char *label;
struct device dev;
const struct hwmon_chip_info *chip;
struct list_head tzdata;
@ -71,17 +73,29 @@ name_show(struct device *dev, struct device_attribute *attr, char *buf)
}
static DEVICE_ATTR_RO(name);
static ssize_t
label_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label);
}
static DEVICE_ATTR_RO(label);
static struct attribute *hwmon_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_label.attr,
NULL
};
static umode_t hwmon_dev_name_is_visible(struct kobject *kobj,
static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct hwmon_device *hdev = to_hwmon_device(dev);
if (to_hwmon_device(dev)->name == NULL)
if (attr == &dev_attr_name.attr && hdev->name == NULL)
return 0;
if (attr == &dev_attr_label.attr && hdev->label == NULL)
return 0;
return attr->mode;
@ -89,7 +103,7 @@ static umode_t hwmon_dev_name_is_visible(struct kobject *kobj,
static const struct attribute_group hwmon_dev_attr_group = {
.attrs = hwmon_dev_attrs,
.is_visible = hwmon_dev_name_is_visible,
.is_visible = hwmon_dev_attr_is_visible,
};
static const struct attribute_group *hwmon_dev_attr_groups[] = {
@ -117,6 +131,7 @@ static void hwmon_dev_release(struct device *dev)
if (hwdev->group.attrs)
hwmon_free_attrs(hwdev->group.attrs);
kfree(hwdev->groups);
kfree(hwdev->label);
kfree(hwdev);
}
@ -589,6 +604,7 @@ static const char * const hwmon_pwm_attr_templates[] = {
[hwmon_pwm_enable] = "pwm%d_enable",
[hwmon_pwm_mode] = "pwm%d_mode",
[hwmon_pwm_freq] = "pwm%d_freq",
[hwmon_pwm_auto_channels_temp] = "pwm%d_auto_channels_temp",
};
static const char * const hwmon_intrusion_attr_templates[] = {
@ -625,7 +641,9 @@ static const int __templates_size[] = {
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel)
{
char event[MAX_SYSFS_ATTR_NAME_LENGTH + 5];
char sattr[MAX_SYSFS_ATTR_NAME_LENGTH];
char *envp[] = { event, NULL };
const char * const *templates;
const char *template;
int base;
@ -641,8 +659,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
base = hwmon_attr_base(type);
scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel);
scnprintf(event, sizeof(event), "NAME=%s", sattr);
sysfs_notify(&dev->kobj, NULL, sattr);
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
if (type == hwmon_temp)
hwmon_thermal_notify(dev, channel);
@ -735,6 +754,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
const struct attribute_group **groups)
{
struct hwmon_device *hwdev;
const char *label;
struct device *hdev;
int i, err, id;
@ -790,6 +810,18 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
hdev->groups = groups;
}
if (dev && device_property_present(dev, "label")) {
err = device_property_read_string(dev, "label", &label);
if (err < 0)
goto free_hwmon;
hwdev->label = kstrdup(label, GFP_KERNEL);
if (hwdev->label == NULL) {
err = -ENOMEM;
goto free_hwmon;
}
}
hwdev->name = name;
hdev->class = &hwmon_class;
hdev->parent = dev;

View File

@ -34,6 +34,7 @@
#define LM70_CHIP_LM71 2 /* NS LM71 */
#define LM70_CHIP_LM74 3 /* NS LM74 */
#define LM70_CHIP_TMP122 4 /* TI TMP122/TMP124 */
#define LM70_CHIP_TMP125 5 /* TI TMP125 */
struct lm70 {
struct spi_device *spi;
@ -87,6 +88,12 @@ static ssize_t temp1_input_show(struct device *dev,
* LM71:
* 14 bits of 2's complement data, discard LSB 2 bits,
* resolution 0.0312 degrees celsius.
*
* TMP125:
* MSB/D15 is a leading zero. D14 is the sign-bit. This is
* followed by 9 temperature bits (D13..D5) in 2's complement
* data format with a resolution of 0.25 degrees celsius per unit.
* LSB 5 bits (D4..D0) share the same value as D5 and get discarded.
*/
switch (p_lm70->chip) {
case LM70_CHIP_LM70:
@ -102,6 +109,10 @@ static ssize_t temp1_input_show(struct device *dev,
case LM70_CHIP_LM71:
val = ((int)raw / 4) * 3125 / 100;
break;
case LM70_CHIP_TMP125:
val = (sign_extend32(raw, 14) / 32) * 250;
break;
}
status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */
@ -135,6 +146,10 @@ static const struct of_device_id lm70_of_ids[] = {
.compatible = "ti,tmp122",
.data = (void *) LM70_CHIP_TMP122,
},
{
.compatible = "ti,tmp125",
.data = (void *) LM70_CHIP_TMP125,
},
{
.compatible = "ti,lm71",
.data = (void *) LM70_CHIP_LM71,
@ -184,6 +199,7 @@ static const struct spi_device_id lm70_ids[] = {
{ "lm70", LM70_CHIP_LM70 },
{ "tmp121", LM70_CHIP_TMP121 },
{ "tmp122", LM70_CHIP_TMP122 },
{ "tmp125", LM70_CHIP_TMP125 },
{ "lm71", LM70_CHIP_LM71 },
{ "lm74", LM70_CHIP_LM74 },
{ },

View File

@ -18,15 +18,15 @@
* http://www.national.com/pf/LM/LM82.html
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon-sysfs.h>
#include <linux/hwmon.h>
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
/*
@ -66,35 +66,35 @@ enum chips { lm83, lm82 };
#define LM83_REG_R_TCRIT 0x42
#define LM83_REG_W_TCRIT 0x5A
/*
* Conversions and various macros
* The LM83 uses signed 8-bit values with LSB = 1 degree Celsius.
*/
#define TEMP_FROM_REG(val) ((val) * 1000)
#define TEMP_TO_REG(val) ((val) <= -128000 ? -128 : \
(val) >= 127000 ? 127 : \
(val) < 0 ? ((val) - 500) / 1000 : \
((val) + 500) / 1000)
static const u8 LM83_REG_R_TEMP[] = {
static const u8 LM83_REG_TEMP[] = {
LM83_REG_R_LOCAL_TEMP,
LM83_REG_R_REMOTE1_TEMP,
LM83_REG_R_REMOTE2_TEMP,
LM83_REG_R_REMOTE3_TEMP,
};
static const u8 LM83_REG_MAX[] = {
LM83_REG_R_LOCAL_HIGH,
LM83_REG_R_REMOTE1_HIGH,
LM83_REG_R_REMOTE2_HIGH,
LM83_REG_R_REMOTE3_HIGH,
LM83_REG_R_TCRIT,
};
static const u8 LM83_REG_W_HIGH[] = {
LM83_REG_W_LOCAL_HIGH,
LM83_REG_W_REMOTE1_HIGH,
LM83_REG_W_REMOTE2_HIGH,
LM83_REG_W_REMOTE3_HIGH,
LM83_REG_W_TCRIT,
/* alarm and fault registers and bits, indexed by channel */
static const u8 LM83_ALARM_REG[] = {
LM83_REG_R_STATUS1, LM83_REG_R_STATUS2, LM83_REG_R_STATUS1, LM83_REG_R_STATUS2
};
static const u8 LM83_MAX_ALARM_BIT[] = {
BIT(6), BIT(7), BIT(4), BIT(4)
};
static const u8 LM83_CRIT_ALARM_BIT[] = {
BIT(0), BIT(0), BIT(1), BIT(1)
};
static const u8 LM83_FAULT_BIT[] = {
0, BIT(5), BIT(2), BIT(2)
};
/*
@ -102,180 +102,274 @@ static const u8 LM83_REG_W_HIGH[] = {
*/
struct lm83_data {
struct i2c_client *client;
const struct attribute_group *groups[3];
struct mutex update_lock;
bool valid; /* false until following fields are valid */
unsigned long last_updated; /* in jiffies */
/* registers values */
s8 temp[9]; /* 0..3: input 1-4,
4..7: high limit 1-4,
8 : critical limit */
u16 alarms; /* bitvector, combined */
struct regmap *regmap;
enum chips type;
};
static struct lm83_data *lm83_update_device(struct device *dev)
/* regmap code */
static int lm83_regmap_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct lm83_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
struct i2c_client *client = context;
int ret;
mutex_lock(&data->update_lock);
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
return ret;
if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) {
int nr;
*val = ret;
return 0;
}
dev_dbg(&client->dev, "Updating lm83 data.\n");
for (nr = 0; nr < 9; nr++) {
data->temp[nr] =
i2c_smbus_read_byte_data(client,
LM83_REG_R_TEMP[nr]);
}
data->alarms =
i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1)
+ (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2)
<< 8);
/*
* The regmap write function maps read register addresses to write register
* addresses. This is necessary for regmap register caching to work.
* An alternative would be to clear the regmap cache whenever a register is
* written, but that would be much more expensive.
*/
static int lm83_regmap_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct i2c_client *client = context;
data->last_updated = jiffies;
data->valid = true;
switch (reg) {
case LM83_REG_R_CONFIG:
case LM83_REG_R_LOCAL_HIGH:
case LM83_REG_R_REMOTE2_HIGH:
reg += 0x06;
break;
case LM83_REG_R_REMOTE1_HIGH:
case LM83_REG_R_REMOTE3_HIGH:
case LM83_REG_R_TCRIT:
reg += 0x18;
break;
default:
break;
}
mutex_unlock(&data->update_lock);
return data;
return i2c_smbus_write_byte_data(client, reg, val);
}
/*
* Sysfs stuff
*/
static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
char *buf)
static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm83_data *data = lm83_update_device(dev);
return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]));
switch (reg) {
case LM83_REG_R_LOCAL_TEMP:
case LM83_REG_R_REMOTE1_TEMP:
case LM83_REG_R_REMOTE2_TEMP:
case LM83_REG_R_REMOTE3_TEMP:
case LM83_REG_R_STATUS1:
case LM83_REG_R_STATUS2:
return true;
default:
return false;
}
}
static ssize_t temp_store(struct device *dev,
struct device_attribute *devattr, const char *buf,
size_t count)
static const struct regmap_config lm83_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = lm83_regmap_is_volatile,
.reg_read = lm83_regmap_reg_read,
.reg_write = lm83_regmap_reg_write,
};
/* hwmon API */
static int lm83_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm83_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
long val;
int nr = attr->index;
unsigned int regval;
int err;
err = kstrtol(buf, 10, &val);
if (err < 0)
return err;
mutex_lock(&data->update_lock);
data->temp[nr] = TEMP_TO_REG(val);
i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4],
data->temp[nr]);
mutex_unlock(&data->update_lock);
return count;
switch (attr) {
case hwmon_temp_input:
err = regmap_read(data->regmap, LM83_REG_TEMP[channel], &regval);
if (err < 0)
return err;
*val = (s8)regval * 1000;
break;
case hwmon_temp_max:
err = regmap_read(data->regmap, LM83_REG_MAX[channel], &regval);
if (err < 0)
return err;
*val = (s8)regval * 1000;
break;
case hwmon_temp_crit:
err = regmap_read(data->regmap, LM83_REG_R_TCRIT, &regval);
if (err < 0)
return err;
*val = (s8)regval * 1000;
break;
case hwmon_temp_max_alarm:
err = regmap_read(data->regmap, LM83_ALARM_REG[channel], &regval);
if (err < 0)
return err;
*val = !!(regval & LM83_MAX_ALARM_BIT[channel]);
break;
case hwmon_temp_crit_alarm:
err = regmap_read(data->regmap, LM83_ALARM_REG[channel], &regval);
if (err < 0)
return err;
*val = !!(regval & LM83_CRIT_ALARM_BIT[channel]);
break;
case hwmon_temp_fault:
err = regmap_read(data->regmap, LM83_ALARM_REG[channel], &regval);
if (err < 0)
return err;
*val = !!(regval & LM83_FAULT_BIT[channel]);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy,
char *buf)
static int lm83_temp_write(struct device *dev, u32 attr, int channel, long val)
{
struct lm83_data *data = lm83_update_device(dev);
return sprintf(buf, "%d\n", data->alarms);
struct lm83_data *data = dev_get_drvdata(dev);
unsigned int regval;
int err;
regval = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000);
switch (attr) {
case hwmon_temp_max:
err = regmap_write(data->regmap, LM83_REG_MAX[channel], regval);
if (err < 0)
return err;
break;
case hwmon_temp_crit:
err = regmap_write(data->regmap, LM83_REG_R_TCRIT, regval);
if (err < 0)
return err;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static ssize_t alarm_show(struct device *dev,
struct device_attribute *devattr, char *buf)
static int lm83_chip_read(struct device *dev, u32 attr, int channel, long *val)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm83_data *data = lm83_update_device(dev);
int bitnr = attr->index;
struct lm83_data *data = dev_get_drvdata(dev);
unsigned int regval;
int err;
return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
switch (attr) {
case hwmon_chip_alarms:
err = regmap_read(data->regmap, LM83_REG_R_STATUS1, &regval);
if (err < 0)
return err;
*val = regval;
err = regmap_read(data->regmap, LM83_REG_R_STATUS2, &regval);
if (err < 0)
return err;
*val |= regval << 8;
return 0;
default:
return -EOPNOTSUPP;
}
return 0;
}
static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, 4);
static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, 5);
static SENSOR_DEVICE_ATTR_RW(temp3_max, temp, 6);
static SENSOR_DEVICE_ATTR_RW(temp4_max, temp, 7);
static SENSOR_DEVICE_ATTR_RO(temp1_crit, temp, 8);
static SENSOR_DEVICE_ATTR_RO(temp2_crit, temp, 8);
static SENSOR_DEVICE_ATTR_RW(temp3_crit, temp, 8);
static SENSOR_DEVICE_ATTR_RO(temp4_crit, temp, 8);
static int lm83_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
case hwmon_chip:
return lm83_chip_read(dev, attr, channel, val);
case hwmon_temp:
return lm83_temp_read(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
}
/* Individual alarm files */
static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 0);
static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, 1);
static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, 2);
static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, 4);
static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6);
static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 8);
static SENSOR_DEVICE_ATTR_RO(temp4_crit_alarm, alarm, 9);
static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, 10);
static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, 12);
static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 13);
static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 15);
/* Raw alarm file for compatibility */
static DEVICE_ATTR_RO(alarms);
static int lm83_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
switch (type) {
case hwmon_temp:
return lm83_temp_write(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
}
static struct attribute *lm83_attributes[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp3_input.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr,
&sensor_dev_attr_temp3_max.dev_attr.attr,
&sensor_dev_attr_temp1_crit.dev_attr.attr,
&sensor_dev_attr_temp3_crit.dev_attr.attr,
static umode_t lm83_is_visible(const void *_data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct lm83_data *data = _data;
&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
&sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
&sensor_dev_attr_temp3_fault.dev_attr.attr,
&sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
&dev_attr_alarms.attr,
/*
* LM82 only supports a single external channel, modeled as channel 2.
*/
if (data->type == lm82 && (channel == 1 || channel == 3))
return 0;
switch (type) {
case hwmon_chip:
if (attr == hwmon_chip_alarms)
return 0444;
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_max_alarm:
case hwmon_temp_crit_alarm:
return 0444;
case hwmon_temp_fault:
if (channel)
return 0444;
break;
case hwmon_temp_max:
return 0644;
case hwmon_temp_crit:
if (channel == 2)
return 0644;
return 0444;
default:
break;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *lm83_info[] = {
HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT
),
NULL
};
static const struct attribute_group lm83_group = {
.attrs = lm83_attributes,
static const struct hwmon_ops lm83_hwmon_ops = {
.is_visible = lm83_is_visible,
.read = lm83_read,
.write = lm83_write,
};
static struct attribute *lm83_attributes_opt[] = {
&sensor_dev_attr_temp2_input.dev_attr.attr,
&sensor_dev_attr_temp4_input.dev_attr.attr,
&sensor_dev_attr_temp2_max.dev_attr.attr,
&sensor_dev_attr_temp4_max.dev_attr.attr,
&sensor_dev_attr_temp2_crit.dev_attr.attr,
&sensor_dev_attr_temp4_crit.dev_attr.attr,
&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
&sensor_dev_attr_temp4_crit_alarm.dev_attr.attr,
&sensor_dev_attr_temp4_fault.dev_attr.attr,
&sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp2_fault.dev_attr.attr,
&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
NULL
static const struct hwmon_chip_info lm83_chip_info = {
.ops = &lm83_hwmon_ops,
.info = lm83_info,
};
static const struct attribute_group lm83_group_opt = {
.attrs = lm83_attributes_opt,
};
/*
* Real code
*/
/* Return 0 if detection is successful, -ENODEV otherwise */
static int lm83_detect(struct i2c_client *new_client,
static int lm83_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = new_client->adapter;
struct i2c_adapter *adapter = client->adapter;
const char *name;
u8 man_id, chip_id;
@ -283,22 +377,30 @@ static int lm83_detect(struct i2c_client *new_client,
return -ENODEV;
/* Detection */
if ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) & 0xA8) ||
(i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) & 0x48) ||
(i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) & 0x41)) {
if ((i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) & 0xA8) ||
(i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) & 0x48) ||
(i2c_smbus_read_byte_data(client, LM83_REG_R_CONFIG) & 0x41)) {
dev_dbg(&adapter->dev, "LM83 detection failed at 0x%02x\n",
new_client->addr);
client->addr);
return -ENODEV;
}
/* Identification */
man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID);
man_id = i2c_smbus_read_byte_data(client, LM83_REG_R_MAN_ID);
if (man_id != 0x01) /* National Semiconductor */
return -ENODEV;
chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID);
chip_id = i2c_smbus_read_byte_data(client, LM83_REG_R_CHIP_ID);
switch (chip_id) {
case 0x03:
/*
* According to the LM82 datasheet dated March 2013, recent
* revisions of LM82 have a die revision of 0x03. This was
* confirmed with a real chip. Further details in this revision
* of the LM82 datasheet strongly suggest that LM82 is just a
* repackaged LM83. It is therefore impossible to distinguish
* those chips from LM83, and they will be misdetected as LM83.
*/
name = "lm83";
break;
case 0x01:
@ -306,9 +408,9 @@ static int lm83_detect(struct i2c_client *new_client,
break;
default:
/* identification failed */
dev_info(&adapter->dev,
"Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n",
man_id, chip_id);
dev_dbg(&adapter->dev,
"Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n",
man_id, chip_id);
return -ENODEV;
}
@ -317,41 +419,6 @@ static int lm83_detect(struct i2c_client *new_client,
return 0;
}
static const struct i2c_device_id lm83_id[];
static int lm83_probe(struct i2c_client *new_client)
{
struct device *hwmon_dev;
struct lm83_data *data;
data = devm_kzalloc(&new_client->dev, sizeof(struct lm83_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = new_client;
mutex_init(&data->update_lock);
/*
* Register sysfs hooks
* The LM82 can only monitor one external diode which is
* at the same register as the LM83 temp3 entry - so we
* declare 1 and 3 common, and then 2 and 4 only for the LM83.
*/
data->groups[0] = &lm83_group;
if (i2c_match_id(lm83_id, new_client)->driver_data == lm83)
data->groups[1] = &lm83_group_opt;
hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev,
new_client->name,
data, data->groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
/*
* Driver data (common to all clients)
*/
static const struct i2c_device_id lm83_id[] = {
{ "lm83", lm83 },
{ "lm82", lm82 },
@ -359,6 +426,31 @@ static const struct i2c_device_id lm83_id[] = {
};
MODULE_DEVICE_TABLE(i2c, lm83_id);
static int lm83_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct lm83_data *data;
data = devm_kzalloc(dev, sizeof(struct lm83_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->regmap = devm_regmap_init(dev, NULL, client, &lm83_regmap_config);
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
data->type = i2c_match_id(lm83_id, client)->driver_data;
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data, &lm83_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
/*
* Driver data (common to all clients)
*/
static struct i2c_driver lm83_driver = {
.class = I2C_CLASS_HWMON,
.driver = {

View File

@ -87,6 +87,9 @@ struct max6639_data {
/* Register values initialized only once */
u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */
u8 rpm_range; /* Index in above rpm_ranges table */
/* Optional regulator for FAN supply */
struct regulator *reg;
};
static struct max6639_data *max6639_update_device(struct device *dev)
@ -516,6 +519,11 @@ static int max6639_detect(struct i2c_client *client,
return 0;
}
static void max6639_regulator_disable(void *data)
{
regulator_disable(data);
}
static int max6639_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@ -528,6 +536,28 @@ static int max6639_probe(struct i2c_client *client)
return -ENOMEM;
data->client = client;
data->reg = devm_regulator_get_optional(dev, "fan");
if (IS_ERR(data->reg)) {
if (PTR_ERR(data->reg) != -ENODEV)
return PTR_ERR(data->reg);
data->reg = NULL;
} else {
/* Spin up fans */
err = regulator_enable(data->reg);
if (err) {
dev_err(dev, "Failed to enable fan supply: %d\n", err);
return err;
}
err = devm_add_action_or_reset(dev, max6639_regulator_disable,
data->reg);
if (err) {
dev_err(dev, "Failed to register action: %d\n", err);
return err;
}
}
mutex_init(&data->update_lock);
/* Initialize the max6639 chip */
@ -545,23 +575,39 @@ static int max6639_probe(struct i2c_client *client)
static int max6639_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
if (data < 0)
return data;
struct max6639_data *data = dev_get_drvdata(dev);
int ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
if (ret < 0)
return ret;
if (data->reg)
regulator_disable(data->reg);
return i2c_smbus_write_byte_data(client,
MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY);
MAX6639_REG_GCONFIG, ret | MAX6639_GCONFIG_STANDBY);
}
static int max6639_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
if (data < 0)
return data;
struct max6639_data *data = dev_get_drvdata(dev);
int ret;
if (data->reg) {
ret = regulator_enable(data->reg);
if (ret) {
dev_err(dev, "Failed to enable fan supply: %d\n", ret);
return ret;
}
}
ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG);
if (ret < 0)
return ret;
return i2c_smbus_write_byte_data(client,
MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY);
MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY);
}
#endif /* CONFIG_PM_SLEEP */

View File

@ -18,15 +18,6 @@
#define MLXREG_FAN_MAX_STATE 10
#define MLXREG_FAN_MIN_DUTY 51 /* 20% */
#define MLXREG_FAN_MAX_DUTY 255 /* 100% */
/*
* Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values
* MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for
* setting FAN speed dynamic minimum. For example, if value is set to 14 (40%)
* cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to
* introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100.
*/
#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2)
#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2)
#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */
#define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44
#define MLXREG_FAN_TACHO_DIV_MIN 283
@ -87,13 +78,16 @@ struct mlxreg_fan_tacho {
* @connected: indicates if PWM is connected;
* @reg: register offset;
* @cooling: cooling device levels;
* @last_hwmon_state: last cooling state set by hwmon subsystem;
* @last_thermal_state: last cooling state set by thermal subsystem;
* @cdev: cooling device;
*/
struct mlxreg_fan_pwm {
struct mlxreg_fan *fan;
bool connected;
u32 reg;
u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1];
unsigned long last_hwmon_state;
unsigned long last_thermal_state;
struct thermal_cooling_device *cdev;
};
@ -119,6 +113,9 @@ struct mlxreg_fan {
int divider;
};
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state);
static int
mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
@ -213,6 +210,18 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
val > MLXREG_FAN_MAX_DUTY)
return -EINVAL;
pwm = &fan->pwm[channel];
/* If thermal is configured - handle PWM limit setting. */
if (IS_REACHABLE(CONFIG_THERMAL)) {
pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(val);
/*
* Update PWM only in case requested state is not less than the
* last thermal state.
*/
if (pwm->last_hwmon_state >= pwm->last_thermal_state)
return mlxreg_fan_set_cur_state(pwm->cdev,
pwm->last_hwmon_state);
return 0;
}
return regmap_write(fan->regmap, pwm->reg, val);
default:
return -EOPNOTSUPP;
@ -338,58 +347,22 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
{
struct mlxreg_fan_pwm *pwm = cdev->devdata;
struct mlxreg_fan *fan = pwm->fan;
unsigned long cur_state;
int i, config = 0;
u32 regval;
int err;
/*
* Verify if this request is for changing allowed FAN dynamical
* minimum. If it is - update cooling levels accordingly and update
* state, if current state is below the newly requested minimum state.
* For example, if current state is 5, and minimal state is to be
* changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all
* from 4 to 6. And state 5 (fan->cooling_levels[4]) should be
* overwritten.
*/
if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) {
/*
* This is configuration change, which is only supported through sysfs.
* For configuration non-zero value is to be returned to avoid thermal
* statistics update.
*/
config = 1;
state -= MLXREG_FAN_MAX_STATE;
for (i = 0; i < state; i++)
pwm->cooling_levels[i] = state;
for (i = state; i <= MLXREG_FAN_MAX_STATE; i++)
pwm->cooling_levels[i] = i;
err = regmap_read(fan->regmap, pwm->reg, &regval);
if (err) {
dev_err(fan->dev, "Failed to query PWM duty\n");
return err;
}
cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval);
if (state < cur_state)
return config;
state = cur_state;
}
if (state > MLXREG_FAN_MAX_STATE)
return -EINVAL;
/* Normalize the state to the valid speed range. */
state = pwm->cooling_levels[state];
/* Save thermal state. */
pwm->last_thermal_state = state;
state = max_t(unsigned long, state, pwm->last_hwmon_state);
err = regmap_write(fan->regmap, pwm->reg,
MLXREG_FAN_PWM_STATE2DUTY(state));
if (err) {
dev_err(fan->dev, "Failed to write PWM duty\n");
return err;
}
return config;
return 0;
}
static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
@ -564,7 +537,7 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan,
static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan)
{
int i, j;
int i;
for (i = 0; i < MLXREG_FAN_MAX_PWM; i++) {
struct mlxreg_fan_pwm *pwm = &fan->pwm[i];
@ -579,11 +552,8 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan)
return PTR_ERR(pwm->cdev);
}
/* Init cooling levels per PWM state. */
for (j = 0; j < MLXREG_FAN_SPEED_MIN_LEVEL; j++)
pwm->cooling_levels[j] = MLXREG_FAN_SPEED_MIN_LEVEL;
for (j = MLXREG_FAN_SPEED_MIN_LEVEL; j <= MLXREG_FAN_MAX_STATE; j++)
pwm->cooling_levels[j] = j;
/* Set minimal PWM speed. */
pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY);
}
return 0;

View File

@ -308,6 +308,7 @@ static void superio_exit(struct nct6775_sio_data *sio_data)
#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/
#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */
#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */
#define NUM_REG_ALARM 7 /* Max number of alarm registers */
#define NUM_REG_BEEP 5 /* Max number of beep registers */
@ -498,6 +499,8 @@ static const u16 NCT6775_REG_TEMP_CRIT[32] = {
[11] = 0xa07
};
static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 };
/* NCT6776 specific data */
/* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */
@ -581,6 +584,9 @@ static const u16 NCT6776_REG_TEMP_CRIT[32] = {
[12] = 0x70a,
};
static const u16 NCT6776_REG_TSI_TEMP[] = {
0x409, 0x40b, 0x40d, 0x40f, 0x411, 0x413, 0x415, 0x417 };
/* NCT6779 specific data */
static const u16 NCT6779_REG_IN[] = {
@ -864,6 +870,8 @@ static const char *const nct6796_temp_label[] = {
#define NCT6796_TEMP_MASK 0xbfff0ffe
#define NCT6796_VIRT_TEMP_MASK 0x80000c00
static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b };
static const char *const nct6798_temp_label[] = {
"",
"SYSTIN",
@ -1005,6 +1013,8 @@ static const u16 NCT6106_REG_TEMP_CRIT[32] = {
[12] = 0x205,
};
static const u16 NCT6106_REG_TSI_TEMP[] = { 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65, 0x67 };
/* NCT6112D/NCT6114D/NCT6116D specific data */
static const u16 NCT6116_REG_FAN[] = { 0x20, 0x22, 0x24, 0x26, 0x28 };
@ -1069,6 +1079,8 @@ static const s8 NCT6116_BEEP_BITS[] = {
34, -1 /* intrusion0, intrusion1 */
};
static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b };
static enum pwm_enable reg_to_pwm_enable(int pwm, int mode)
{
if (mode == 0 && pwm == 255)
@ -1169,6 +1181,12 @@ static inline u8 in_to_reg(u32 val, u8 nr)
return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255);
}
/* TSI temperatures are in 8.3 format */
static inline unsigned int tsi_temp_from_reg(unsigned int reg)
{
return (reg >> 5) * 125;
}
/*
* Data structures and manipulation thereof
*/
@ -1179,7 +1197,7 @@ struct nct6775_data {
enum kinds kind;
const char *name;
const struct attribute_group *groups[6];
const struct attribute_group *groups[7];
u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst,
* 3=temp_crit, 4=temp_lcrit
@ -1240,6 +1258,8 @@ struct nct6775_data {
const u16 *REG_ALARM;
const u16 *REG_BEEP;
const u16 *REG_TSI_TEMP;
unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg);
unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg);
@ -1267,6 +1287,7 @@ struct nct6775_data {
s8 temp_offset[NUM_TEMP_FIXED];
s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst,
* 3=temp_crit, 4=temp_lcrit */
s16 tsi_temp[NUM_TSI_TEMP];
u64 alarms;
u64 beeps;
@ -1315,6 +1336,7 @@ struct nct6775_data {
u16 have_temp;
u16 have_temp_fixed;
u16 have_tsi_temp;
u16 have_in;
/* Remember extra register values over suspend/resume */
@ -1464,13 +1486,15 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
switch (data->kind) {
case nct6106:
return reg == 0x20 || reg == 0x22 || reg == 0x24 ||
(reg >= 0x59 && reg < 0x69 && (reg & 1)) ||
reg == 0xe0 || reg == 0xe2 || reg == 0xe4 ||
reg == 0x111 || reg == 0x121 || reg == 0x131;
case nct6116:
return reg == 0x20 || reg == 0x22 || reg == 0x24 ||
reg == 0x26 || reg == 0x28 || reg == 0xe0 || reg == 0xe2 ||
reg == 0xe4 || reg == 0xe6 || reg == 0xe8 || reg == 0x111 ||
reg == 0x121 || reg == 0x131 || reg == 0x191 || reg == 0x1a1;
reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b ||
reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 ||
reg == 0xe8 || reg == 0x111 || reg == 0x121 || reg == 0x131 ||
reg == 0x191 || reg == 0x1a1;
case nct6775:
return (((reg & 0xff00) == 0x100 ||
(reg & 0xff00) == 0x200) &&
@ -1479,7 +1503,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
(reg & 0x00ff) == 0x55)) ||
(reg & 0xfff0) == 0x630 ||
reg == 0x640 || reg == 0x642 ||
reg == 0x662 ||
reg == 0x662 || reg == 0x669 ||
((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) ||
reg == 0x73 || reg == 0x75 || reg == 0x77;
case nct6776:
@ -1490,6 +1514,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
(reg & 0x00ff) == 0x55)) ||
(reg & 0xfff0) == 0x630 ||
reg == 0x402 ||
(reg >= 0x409 && reg < 0x419 && (reg & 1)) ||
reg == 0x640 || reg == 0x642 ||
((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) ||
reg == 0x73 || reg == 0x75 || reg == 0x77;
@ -1504,6 +1529,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
return reg == 0x150 || reg == 0x153 || reg == 0x155 ||
(reg & 0xfff0) == 0x4c0 ||
reg == 0x402 ||
(reg >= 0x409 && reg < 0x419 && (reg & 1)) ||
reg == 0x63a || reg == 0x63c || reg == 0x63e ||
reg == 0x640 || reg == 0x642 || reg == 0x64a ||
reg == 0x64c ||
@ -1987,6 +2013,12 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
data->REG_TEMP_OFFSET[i]);
}
for (i = 0; i < NUM_TSI_TEMP; i++) {
if (!(data->have_tsi_temp & BIT(i)))
continue;
data->tsi_temp[i] = data->read_value(data, data->REG_TSI_TEMP[i]);
}
data->alarms = 0;
for (i = 0; i < NUM_REG_ALARM; i++) {
u8 alarm;
@ -2670,6 +2702,44 @@ static const struct sensor_template_group nct6775_temp_template_group = {
.base = 1,
};
static ssize_t show_tsi_temp(struct device *dev, struct device_attribute *attr, char *buf)
{
struct nct6775_data *data = nct6775_update_device(dev);
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index]));
}
static ssize_t show_tsi_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
return sysfs_emit(buf, "TSI%d_TEMP\n", sattr->index);
}
SENSOR_TEMPLATE(tsi_temp_input, "temp%d_input", 0444, show_tsi_temp, NULL, 0);
SENSOR_TEMPLATE(tsi_temp_label, "temp%d_label", 0444, show_tsi_temp_label, NULL, 0);
static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribute *attr,
int index)
{
struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev);
int temp = index / 2;
return (data->have_tsi_temp & BIT(temp)) ? attr->mode : 0;
}
/*
* The index calculation in nct6775_tsi_temp_is_visible() must be kept in
* sync with the size of this array.
*/
static struct sensor_device_template *nct6775_tsi_temp_template[] = {
&sensor_dev_template_tsi_temp_input,
&sensor_dev_template_tsi_temp_label,
NULL
};
static ssize_t
show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
{
@ -3948,10 +4018,11 @@ static int nct6775_probe(struct platform_device *pdev)
const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config;
const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit;
const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL;
int num_reg_temp, num_reg_temp_mon;
int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp;
u8 cr2a;
struct attribute_group *group;
struct device *hwmon_dev;
struct sensor_template_group tsi_temp_tg;
int num_attr_groups = 0;
if (sio_data->access == access_direct) {
@ -4043,11 +4114,13 @@ static int nct6775_probe(struct platform_device *pdev)
data->ALARM_BITS = NCT6106_ALARM_BITS;
data->REG_BEEP = NCT6106_REG_BEEP;
data->BEEP_BITS = NCT6106_BEEP_BITS;
data->REG_TSI_TEMP = NCT6106_REG_TSI_TEMP;
reg_temp = NCT6106_REG_TEMP;
reg_temp_mon = NCT6106_REG_TEMP_MON;
num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP);
num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6106_REG_TSI_TEMP);
reg_temp_over = NCT6106_REG_TEMP_OVER;
reg_temp_hyst = NCT6106_REG_TEMP_HYST;
reg_temp_config = NCT6106_REG_TEMP_CONFIG;
@ -4116,11 +4189,13 @@ static int nct6775_probe(struct platform_device *pdev)
data->ALARM_BITS = NCT6116_ALARM_BITS;
data->REG_BEEP = NCT6106_REG_BEEP;
data->BEEP_BITS = NCT6116_BEEP_BITS;
data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP;
reg_temp = NCT6106_REG_TEMP;
reg_temp_mon = NCT6106_REG_TEMP_MON;
num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP);
num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP);
reg_temp_over = NCT6106_REG_TEMP_OVER;
reg_temp_hyst = NCT6106_REG_TEMP_HYST;
reg_temp_config = NCT6106_REG_TEMP_CONFIG;
@ -4191,11 +4266,13 @@ static int nct6775_probe(struct platform_device *pdev)
data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE;
data->REG_ALARM = NCT6775_REG_ALARM;
data->REG_BEEP = NCT6775_REG_BEEP;
data->REG_TSI_TEMP = NCT6775_REG_TSI_TEMP;
reg_temp = NCT6775_REG_TEMP;
reg_temp_mon = NCT6775_REG_TEMP_MON;
num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP);
num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6775_REG_TSI_TEMP);
reg_temp_over = NCT6775_REG_TEMP_OVER;
reg_temp_hyst = NCT6775_REG_TEMP_HYST;
reg_temp_config = NCT6775_REG_TEMP_CONFIG;
@ -4264,11 +4341,13 @@ static int nct6775_probe(struct platform_device *pdev)
data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE;
data->REG_ALARM = NCT6775_REG_ALARM;
data->REG_BEEP = NCT6776_REG_BEEP;
data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP;
reg_temp = NCT6775_REG_TEMP;
reg_temp_mon = NCT6775_REG_TEMP_MON;
num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP);
num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP);
reg_temp_over = NCT6775_REG_TEMP_OVER;
reg_temp_hyst = NCT6775_REG_TEMP_HYST;
reg_temp_config = NCT6776_REG_TEMP_CONFIG;
@ -4341,11 +4420,13 @@ static int nct6775_probe(struct platform_device *pdev)
data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE;
data->REG_ALARM = NCT6779_REG_ALARM;
data->REG_BEEP = NCT6776_REG_BEEP;
data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP;
reg_temp = NCT6779_REG_TEMP;
reg_temp_mon = NCT6779_REG_TEMP_MON;
num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP);
num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON);
num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP);
reg_temp_over = NCT6779_REG_TEMP_OVER;
reg_temp_hyst = NCT6779_REG_TEMP_HYST;
reg_temp_config = NCT6779_REG_TEMP_CONFIG;
@ -4460,6 +4541,24 @@ static int nct6775_probe(struct platform_device *pdev)
data->REG_BEEP = NCT6776_REG_BEEP;
else
data->REG_BEEP = NCT6792_REG_BEEP;
switch (data->kind) {
case nct6791:
case nct6792:
case nct6793:
data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP;
num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP);
break;
case nct6795:
case nct6796:
case nct6797:
case nct6798:
data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP;
num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP);
break;
default:
num_reg_tsi_temp = 0;
break;
}
reg_temp = NCT6779_REG_TEMP;
num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP);
@ -4659,6 +4758,12 @@ static int nct6775_probe(struct platform_device *pdev)
}
#endif /* USE_ALTERNATE */
/* Check which TSIx_TEMP registers are active */
for (i = 0; i < num_reg_tsi_temp; i++) {
if (data->read_value(data, data->REG_TSI_TEMP[i]))
data->have_tsi_temp |= BIT(i);
}
/* Initialize the chip */
nct6775_init_device(data);
@ -4766,6 +4871,18 @@ static int nct6775_probe(struct platform_device *pdev)
return PTR_ERR(group);
data->groups[num_attr_groups++] = group;
if (data->have_tsi_temp) {
tsi_temp_tg.templates = nct6775_tsi_temp_template;
tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible;
tsi_temp_tg.base = fls(data->have_temp) + 1;
group = nct6775_create_attr_group(dev, &tsi_temp_tg, fls(data->have_tsi_temp));
if (IS_ERR(group))
return PTR_ERR(group);
data->groups[num_attr_groups++] = group;
}
data->groups[num_attr_groups++] = &nct6775_group_other;
hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name,
@ -4985,9 +5102,14 @@ static struct platform_device *pdev[2];
static const char * const asus_wmi_boards[] = {
"ProArt X570-CREATOR WIFI",
"Pro B550M-C",
"Pro WS X570-ACE",
"PRIME B360-PLUS",
"PRIME B460-PLUS",
"PRIME B550-PLUS",
"PRIME B550M-A",
"PRIME B550M-A (WI-FI)",
"PRIME X570-P",
"PRIME X570-PRO",
"ROG CROSSHAIR VIII DARK HERO",
"ROG CROSSHAIR VIII FORMULA",
@ -4997,10 +5119,22 @@ static const char * const asus_wmi_boards[] = {
"ROG STRIX B550-E GAMING",
"ROG STRIX B550-F GAMING",
"ROG STRIX B550-F GAMING (WI-FI)",
"ROG STRIX B550-F GAMING WIFI II",
"ROG STRIX B550-I GAMING",
"ROG STRIX B550-XE GAMING (WI-FI)",
"ROG STRIX X570-E GAMING",
"ROG STRIX X570-F GAMING",
"ROG STRIX X570-I GAMING",
"ROG STRIX Z390-E GAMING",
"ROG STRIX Z390-F GAMING",
"ROG STRIX Z390-H GAMING",
"ROG STRIX Z390-I GAMING",
"ROG STRIX Z490-A GAMING",
"ROG STRIX Z490-E GAMING",
"ROG STRIX Z490-F GAMING",
"ROG STRIX Z490-G GAMING",
"ROG STRIX Z490-G GAMING (WI-FI)",
"ROG STRIX Z490-H GAMING",
"ROG STRIX Z490-I GAMING",
"TUF GAMING B550M-PLUS",
"TUF GAMING B550M-PLUS (WI-FI)",

View File

@ -674,6 +674,9 @@ static ssize_t occ_show_caps_3(struct device *dev,
case 7:
val = caps->user_source;
break;
case 8:
val = get_unaligned_be16(&caps->soft_min) * 1000000ULL;
break;
default:
return -EINVAL;
}
@ -835,12 +838,13 @@ static int occ_setup_sensor_attrs(struct occ *occ)
case 1:
num_attrs += (sensors->caps.num_sensors * 7);
break;
case 3:
show_caps = occ_show_caps_3;
fallthrough;
case 2:
num_attrs += (sensors->caps.num_sensors * 8);
break;
case 3:
show_caps = occ_show_caps_3;
num_attrs += (sensors->caps.num_sensors * 9);
break;
default:
sensors->caps.num_sensors = 0;
}
@ -1047,6 +1051,15 @@ static int occ_setup_sensor_attrs(struct occ *occ)
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
show_caps, NULL, 7, 0);
attr++;
if (sensors->caps.version > 2) {
snprintf(attr->name, sizeof(attr->name),
"power%d_cap_min_soft", s);
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
show_caps, NULL,
8, 0);
attr++;
}
}
}

View File

@ -119,6 +119,8 @@ struct occ {
u8 prev_stat;
u8 prev_ext_stat;
u8 prev_occs_present;
u8 prev_ips_status;
u8 prev_mode;
};
int occ_setup(struct occ *occ, const char *name);

View File

@ -19,6 +19,8 @@
#define OCC_EXT_STAT_DVFS_POWER BIT(6)
#define OCC_EXT_STAT_MEM_THROTTLE BIT(5)
#define OCC_EXT_STAT_QUICK_DROP BIT(4)
#define OCC_EXT_STAT_DVFS_VDD BIT(3)
#define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0)
static ssize_t occ_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
@ -63,6 +65,18 @@ static ssize_t occ_sysfs_show(struct device *dev,
else
val = 1;
break;
case 8:
val = header->ips_status;
break;
case 9:
val = header->mode;
break;
case 10:
val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD);
break;
case 11:
val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE;
break;
default:
return -EINVAL;
}
@ -88,6 +102,10 @@ static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4);
static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5);
static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6);
static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7);
static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8);
static SENSOR_DEVICE_ATTR(occ_mode, 0444, occ_sysfs_show, NULL, 9);
static SENSOR_DEVICE_ATTR(occ_dvfs_vdd, 0444, occ_sysfs_show, NULL, 10);
static SENSOR_DEVICE_ATTR(occ_gpu_throttle, 0444, occ_sysfs_show, NULL, 11);
static DEVICE_ATTR_RO(occ_error);
static struct attribute *occ_attributes[] = {
@ -99,6 +117,10 @@ static struct attribute *occ_attributes[] = {
&sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr,
&sensor_dev_attr_occ_state.dev_attr.attr,
&sensor_dev_attr_occs_present.dev_attr.attr,
&sensor_dev_attr_occ_ips_status.dev_attr.attr,
&sensor_dev_attr_occ_mode.dev_attr.attr,
&sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr,
&sensor_dev_attr_occ_gpu_throttle.dev_attr.attr,
&dev_attr_occ_error.attr,
NULL
};
@ -156,12 +178,34 @@ void occ_sysfs_poll_done(struct occ *occ)
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if ((header->ext_status & OCC_EXT_STAT_DVFS_VDD) !=
(occ->prev_ext_stat & OCC_EXT_STAT_DVFS_VDD)) {
name = sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if ((header->ext_status & OCC_EXT_STAT_GPU_THROTTLE) !=
(occ->prev_ext_stat & OCC_EXT_STAT_GPU_THROTTLE)) {
name = sensor_dev_attr_occ_gpu_throttle.dev_attr.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if ((header->status & OCC_STAT_MASTER) &&
header->occs_present != occ->prev_occs_present) {
name = sensor_dev_attr_occs_present.dev_attr.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if (header->ips_status != occ->prev_ips_status) {
name = sensor_dev_attr_occ_ips_status.dev_attr.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if (header->mode != occ->prev_mode) {
name = sensor_dev_attr_occ_mode.dev_attr.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
}
if (occ->error && occ->error != occ->prev_error) {
name = dev_attr_occ_error.attr.name;
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
@ -174,6 +218,8 @@ done:
occ->prev_stat = header->status;
occ->prev_ext_stat = header->ext_status;
occ->prev_occs_present = header->occs_present;
occ->prev_ips_status = header->ips_status;
occ->prev_mode = header->mode;
}
int occ_setup_sysfs(struct occ *occ)

View File

@ -174,6 +174,13 @@ config SENSORS_LM25066
This driver can also be built as a module. If so, the module will
be called lm25066.
config SENSORS_LM25066_REGULATOR
bool "Regulator support for LM25066 and compatibles"
depends on SENSORS_LM25066 && REGULATOR
help
If you say yes here you get regulator support for National
Semiconductor LM25066, LM5064, and LM5066.
config SENSORS_LTC2978
tristate "Linear Technologies LTC2978 and compatibles"
help
@ -189,8 +196,8 @@ config SENSORS_LTC2978_REGULATOR
depends on SENSORS_LTC2978 && REGULATOR
help
If you say yes here you get regulator support for Linear Technology
LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880,
LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686,
LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880,
LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686,
and LTM4700.
config SENSORS_LTC3815
@ -310,6 +317,22 @@ config SENSORS_PIM4328
This driver can also be built as a module. If so, the module will
be called pim4328.
config SENSORS_PLI1209BC
tristate "Vicor PLI1209BC"
help
If you say yes here you get hardware monitoring support for Vicor
PLI1209BC Digital Supervisor.
This driver can also be built as a module. If so, the module will
be called pli1209bc.
config SENSORS_PLI1209BC_REGULATOR
bool "Regulator support for PLI1209BC"
depends on SENSORS_PLI1209BC && REGULATOR
help
If you say yes here you get regulator support for Vicor PLI1209BC
Digital Supervisor.
config SENSORS_PM6764TR
tristate "ST PM6764TR"
help
@ -394,6 +417,12 @@ config SENSORS_XDPE122
This driver can also be built as a module. If so, the module will
be called xdpe12284.
config SENSORS_XDPE122_REGULATOR
bool "Regulator support for XDPE122 and compatibles"
depends on SENSORS_XDPE122 && REGULATOR
help
Uses the xdpe12284 or compatible as regulator.
config SENSORS_ZL6100
tristate "Intersil ZL6100 and compatibles"
help

View File

@ -33,6 +33,7 @@ obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
obj-$(CONFIG_SENSORS_MP2888) += mp2888.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o

View File

@ -475,6 +475,7 @@ static int adm1275_probe(struct i2c_client *client)
int vindex = -1, voindex = -1, cindex = -1, pindex = -1;
int tindex = -1;
u32 shunt;
u32 avg;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA
@ -687,7 +688,7 @@ static int adm1275_probe(struct i2c_client *client)
if ((config & (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) !=
(ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) {
config |= ADM1278_VOUT_EN | ADM1278_TEMP1_EN;
ret = i2c_smbus_write_byte_data(client,
ret = i2c_smbus_write_word_data(client,
ADM1275_PMON_CONFIG,
config);
if (ret < 0) {
@ -756,6 +757,43 @@ static int adm1275_probe(struct i2c_client *client)
return -ENODEV;
}
if (data->have_power_sampling &&
of_property_read_u32(client->dev.of_node,
"adi,power-sample-average", &avg) == 0) {
if (!avg || avg > ADM1275_SAMPLES_AVG_MAX ||
BIT(__fls(avg)) != avg) {
dev_err(&client->dev,
"Invalid number of power samples");
return -EINVAL;
}
ret = adm1275_write_pmon_config(data, client, true,
ilog2(avg));
if (ret < 0) {
dev_err(&client->dev,
"Setting power sample averaging failed with error %d",
ret);
return ret;
}
}
if (of_property_read_u32(client->dev.of_node,
"adi,volt-curr-sample-average", &avg) == 0) {
if (!avg || avg > ADM1275_SAMPLES_AVG_MAX ||
BIT(__fls(avg)) != avg) {
dev_err(&client->dev,
"Invalid number of voltage/current samples");
return -EINVAL;
}
ret = adm1275_write_pmon_config(data, client, false,
ilog2(avg));
if (ret < 0) {
dev_err(&client->dev,
"Setting voltage and current sample averaging failed with error %d",
ret);
return ret;
}
}
if (voindex < 0)
voindex = vindex;
if (vindex >= 0) {

View File

@ -435,6 +435,12 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
return ret;
}
#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR)
static const struct regulator_desc lm25066_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
};
#endif
static const struct i2c_device_id lm25066_id[] = {
{"lm25056", lm25056},
{"lm25066", lm25066},
@ -545,6 +551,14 @@ static int lm25066_probe(struct i2c_client *client)
info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000;
info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000;
#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR)
/* LM25056 doesn't support OPERATION */
if (data->id != lm25056) {
info->num_regulators = ARRAY_SIZE(lm25066_reg_desc);
info->reg_desc = lm25066_reg_desc;
}
#endif
return pmbus_do_probe(client, info);
}

View File

@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for Vicor PLI1209BC Digital Supervisor
*
* Copyright (c) 2022 9elements GmbH
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pmbus.h>
#include <linux/regulator/driver.h>
#include "pmbus.h"
/*
* The capability command is only supported at page 0. Probing the device while
* the page register is set to 1 will falsely enable PEC support. Disable
* capability probing accordingly, since the PLI1209BC does not have any
* additional capabilities.
*/
static struct pmbus_platform_data pli1209bc_plat_data = {
.flags = PMBUS_NO_CAPABILITY,
};
static int pli1209bc_read_word_data(struct i2c_client *client, int page,
int phase, int reg)
{
int data;
switch (reg) {
/* PMBUS_READ_POUT uses a direct format with R=0 */
case PMBUS_READ_POUT:
data = pmbus_read_word_data(client, page, phase, reg);
if (data < 0)
return data;
data = sign_extend32(data, 15) * 10;
return clamp_val(data, -32768, 32767) & 0xffff;
/*
* PMBUS_READ_VOUT and PMBUS_READ_TEMPERATURE_1 return invalid data
* when the BCM is turned off. Since it is not possible to return
* ENODATA error, return zero instead.
*/
case PMBUS_READ_VOUT:
case PMBUS_READ_TEMPERATURE_1:
data = pmbus_read_word_data(client, page, phase,
PMBUS_STATUS_WORD);
if (data < 0)
return data;
if (data & PB_STATUS_POWER_GOOD_N)
return 0;
return pmbus_read_word_data(client, page, phase, reg);
default:
return -ENODATA;
}
}
#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR)
static const struct regulator_desc pli1209bc_reg_desc = {
.name = "vout2",
.id = 1,
.of_match = of_match_ptr("vout2"),
.regulators_node = of_match_ptr("regulators"),
.ops = &pmbus_regulator_ops,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
};
#endif
static struct pmbus_driver_info pli1209bc_info = {
.pages = 2,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_VOLTAGE_OUT] = direct,
.format[PSC_CURRENT_IN] = direct,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_POWER] = direct,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_VOLTAGE_IN] = 1,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = 1,
.m[PSC_VOLTAGE_OUT] = 1,
.b[PSC_VOLTAGE_OUT] = 0,
.R[PSC_VOLTAGE_OUT] = 1,
.m[PSC_CURRENT_IN] = 1,
.b[PSC_CURRENT_IN] = 0,
.R[PSC_CURRENT_IN] = 3,
.m[PSC_CURRENT_OUT] = 1,
.b[PSC_CURRENT_OUT] = 0,
.R[PSC_CURRENT_OUT] = 2,
.m[PSC_POWER] = 1,
.b[PSC_POWER] = 0,
.R[PSC_POWER] = 1,
.m[PSC_TEMPERATURE] = 1,
.b[PSC_TEMPERATURE] = 0,
.R[PSC_TEMPERATURE] = 0,
/*
* Page 0 sums up all attributes except voltage readings.
* The pli1209 digital supervisor only contains a single BCM, making
* page 0 redundant.
*/
.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT
| PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT
| PMBUS_HAVE_PIN | PMBUS_HAVE_POUT
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
| PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT,
.read_word_data = pli1209bc_read_word_data,
#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR)
.num_regulators = 1,
.reg_desc = &pli1209bc_reg_desc,
#endif
};
static int pli1209bc_probe(struct i2c_client *client)
{
client->dev.platform_data = &pli1209bc_plat_data;
return pmbus_do_probe(client, &pli1209bc_info);
}
static const struct i2c_device_id pli1209bc_id[] = {
{"pli1209bc", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, pli1209bc_id);
#ifdef CONFIG_OF
static const struct of_device_id pli1209bc_of_match[] = {
{ .compatible = "vicor,pli1209bc" },
{ },
};
MODULE_DEVICE_TABLE(of, pli1209bc_of_match);
#endif
static struct i2c_driver pli1209bc_driver = {
.driver = {
.name = "pli1209bc",
.of_match_table = of_match_ptr(pli1209bc_of_match),
},
.probe_new = pli1209bc_probe,
.id_table = pli1209bc_id,
};
module_i2c_driver(pli1209bc_driver);
MODULE_AUTHOR("Marcello Sylvester Bauer <sylv@sylv.io>");
MODULE_DESCRIPTION("PMBus driver for Vicor PLI1209BC");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(PMBUS);

View File

@ -319,6 +319,7 @@ enum pmbus_fan_mode { percent = 0, rpm };
/*
* STATUS_VOUT, STATUS_INPUT
*/
#define PB_VOLTAGE_VIN_OFF BIT(3)
#define PB_VOLTAGE_UV_FAULT BIT(4)
#define PB_VOLTAGE_UV_WARNING BIT(5)
#define PB_VOLTAGE_OV_WARNING BIT(6)
@ -464,6 +465,7 @@ extern const struct regulator_ops pmbus_regulator_ops;
#define PMBUS_REGULATOR(_name, _id) \
[_id] = { \
.name = (_name # _id), \
.supply_name = "vin", \
.id = (_id), \
.of_match = of_match_ptr(_name # _id), \
.regulators_node = of_match_ptr("regulators"), \

View File

@ -1373,7 +1373,7 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = {
.reg = PMBUS_VIN_UV_FAULT_LIMIT,
.attr = "lcrit",
.alarm = "lcrit_alarm",
.sbit = PB_VOLTAGE_UV_FAULT,
.sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF,
}, {
.reg = PMBUS_VIN_OV_WARN_LIMIT,
.attr = "max",
@ -2391,10 +2391,14 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int ret;
mutex_lock(&data->update_lock);
ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
mutex_unlock(&data->update_lock);
if (ret < 0)
return ret;
@ -2405,11 +2409,17 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable)
{
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int ret;
return pmbus_update_byte_data(client, page, PMBUS_OPERATION,
PB_OPERATION_CONTROL_ON,
enable ? PB_OPERATION_CONTROL_ON : 0);
mutex_lock(&data->update_lock);
ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION,
PB_OPERATION_CONTROL_ON,
enable ? PB_OPERATION_CONTROL_ON : 0);
mutex_unlock(&data->update_lock);
return ret;
}
static int pmbus_regulator_enable(struct regulator_dev *rdev)
@ -2422,10 +2432,124 @@ static int pmbus_regulator_disable(struct regulator_dev *rdev)
return _pmbus_regulator_on_off(rdev, 0);
}
/* A PMBus status flag and the corresponding REGULATOR_ERROR_* flag */
struct pmbus_regulator_status_assoc {
int pflag, rflag;
};
/* PMBus->regulator bit mappings for a PMBus status register */
struct pmbus_regulator_status_category {
int func;
int reg;
const struct pmbus_regulator_status_assoc *bits; /* zero-terminated */
};
static const struct pmbus_regulator_status_category pmbus_regulator_flag_map[] = {
{
.func = PMBUS_HAVE_STATUS_VOUT,
.reg = PMBUS_STATUS_VOUT,
.bits = (const struct pmbus_regulator_status_assoc[]) {
{ PB_VOLTAGE_UV_WARNING, REGULATOR_ERROR_UNDER_VOLTAGE_WARN },
{ PB_VOLTAGE_UV_FAULT, REGULATOR_ERROR_UNDER_VOLTAGE },
{ PB_VOLTAGE_OV_WARNING, REGULATOR_ERROR_OVER_VOLTAGE_WARN },
{ PB_VOLTAGE_OV_FAULT, REGULATOR_ERROR_REGULATION_OUT },
{ },
},
}, {
.func = PMBUS_HAVE_STATUS_IOUT,
.reg = PMBUS_STATUS_IOUT,
.bits = (const struct pmbus_regulator_status_assoc[]) {
{ PB_IOUT_OC_WARNING, REGULATOR_ERROR_OVER_CURRENT_WARN },
{ PB_IOUT_OC_FAULT, REGULATOR_ERROR_OVER_CURRENT },
{ PB_IOUT_OC_LV_FAULT, REGULATOR_ERROR_OVER_CURRENT },
{ },
},
}, {
.func = PMBUS_HAVE_STATUS_TEMP,
.reg = PMBUS_STATUS_TEMPERATURE,
.bits = (const struct pmbus_regulator_status_assoc[]) {
{ PB_TEMP_OT_WARNING, REGULATOR_ERROR_OVER_TEMP_WARN },
{ PB_TEMP_OT_FAULT, REGULATOR_ERROR_OVER_TEMP },
{ },
},
},
};
static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags)
{
int i, status;
const struct pmbus_regulator_status_category *cat;
const struct pmbus_regulator_status_assoc *bit;
struct device *dev = rdev_get_dev(rdev);
struct i2c_client *client = to_i2c_client(dev->parent);
struct pmbus_data *data = i2c_get_clientdata(client);
u8 page = rdev_get_id(rdev);
int func = data->info->func[page];
*flags = 0;
mutex_lock(&data->update_lock);
for (i = 0; i < ARRAY_SIZE(pmbus_regulator_flag_map); i++) {
cat = &pmbus_regulator_flag_map[i];
if (!(func & cat->func))
continue;
status = pmbus_read_byte_data(client, page, cat->reg);
if (status < 0) {
mutex_unlock(&data->update_lock);
return status;
}
for (bit = cat->bits; bit->pflag; bit++) {
if (status & bit->pflag)
*flags |= bit->rflag;
}
}
/*
* Map what bits of STATUS_{WORD,BYTE} we can to REGULATOR_ERROR_*
* bits. Some of the other bits are tempting (especially for cases
* where we don't have the relevant PMBUS_HAVE_STATUS_*
* functionality), but there's an unfortunate ambiguity in that
* they're defined as indicating a fault *or* a warning, so we can't
* easily determine whether to report REGULATOR_ERROR_<foo> or
* REGULATOR_ERROR_<foo>_WARN.
*/
status = pmbus_get_status(client, page, PMBUS_STATUS_WORD);
mutex_unlock(&data->update_lock);
if (status < 0)
return status;
if (pmbus_regulator_is_enabled(rdev) && (status & PB_STATUS_OFF))
*flags |= REGULATOR_ERROR_FAIL;
/*
* Unlike most other status bits, PB_STATUS_{IOUT_OC,VOUT_OV} are
* defined strictly as fault indicators (not warnings).
*/
if (status & PB_STATUS_IOUT_OC)
*flags |= REGULATOR_ERROR_OVER_CURRENT;
if (status & PB_STATUS_VOUT_OV)
*flags |= REGULATOR_ERROR_REGULATION_OUT;
/*
* If we haven't discovered any thermal faults or warnings via
* PMBUS_STATUS_TEMPERATURE, map PB_STATUS_TEMPERATURE to a warning as
* a (conservative) best-effort interpretation.
*/
if (!(*flags & (REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN)) &&
(status & PB_STATUS_TEMPERATURE))
*flags |= REGULATOR_ERROR_OVER_TEMP_WARN;
return 0;
}
const struct regulator_ops pmbus_regulator_ops = {
.enable = pmbus_regulator_enable,
.disable = pmbus_regulator_disable,
.is_enabled = pmbus_regulator_is_enabled,
.get_error_flags = pmbus_regulator_get_error_flags,
};
EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS);

View File

@ -10,6 +10,8 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regulator/driver.h>
#include "pmbus.h"
#define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */
@ -76,7 +78,22 @@ static int xdpe122_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
u8 vout_params;
int i, ret;
int i, ret, vout_mode;
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
if (vout_mode >= 0 && vout_mode != 0xff) {
switch (vout_mode >> 5) {
case 0:
info->format[PSC_VOLTAGE_OUT] = linear;
return 0;
case 1:
info->format[PSC_VOLTAGE_OUT] = vid;
info->read_word_data = xdpe122_read_word_data;
break;
default:
return -ENODEV;
}
}
for (i = 0; i < XDPE122_PAGE_NUM; i++) {
/* Read the register with VOUT scaling value.*/
@ -107,10 +124,14 @@ static int xdpe122_identify(struct i2c_client *client,
return 0;
}
static const struct regulator_desc xdpe122_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR("vout", 1),
};
static struct pmbus_driver_info xdpe122_info = {
.pages = XDPE122_PAGE_NUM,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = vid,
.format[PSC_TEMPERATURE] = linear,
.format[PSC_CURRENT_IN] = linear,
.format[PSC_CURRENT_OUT] = linear,
@ -124,7 +145,10 @@ static struct pmbus_driver_info xdpe122_info = {
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
.identify = xdpe122_identify,
.read_word_data = xdpe122_read_word_data,
#if IS_ENABLED(CONFIG_SENSORS_XDPE122_REGULATOR)
.num_regulators = 2,
.reg_desc = xdpe122_reg_desc,
#endif
};
static int xdpe122_probe(struct i2c_client *client)
@ -140,6 +164,7 @@ static int xdpe122_probe(struct i2c_client *client)
}
static const struct i2c_device_id xdpe122_id[] = {
{"xdpe11280", 0},
{"xdpe12254", 0},
{"xdpe12284", 0},
{}
@ -148,6 +173,7 @@ static const struct i2c_device_id xdpe122_id[] = {
MODULE_DEVICE_TABLE(i2c, xdpe122_id);
static const struct of_device_id __maybe_unused xdpe122_of_match[] = {
{.compatible = "infineon,xdpe11280"},
{.compatible = "infineon,xdpe12254"},
{.compatible = "infineon,xdpe12284"},
{}

View File

@ -22,6 +22,8 @@
#define ADC_STEP_MV 2
#define ADC_MAX_LOW_MEASUREMENT_MV 2000
enum powr1xxx_chips { powr1014, powr1220 };
enum powr1220_regs {
VMON_STATUS0,
VMON_STATUS1,
@ -74,6 +76,7 @@ enum powr1220_adc_values {
struct powr1220_data {
struct i2c_client *client;
struct mutex update_lock;
u8 max_channels;
bool adc_valid[MAX_POWR1220_ADC_VALUES];
/* the next value is in jiffies */
unsigned long adc_last_updated[MAX_POWR1220_ADC_VALUES];
@ -111,7 +114,7 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) ||
!data->adc_valid[ch_num]) {
!data->adc_valid[ch_num]) {
/*
* figure out if we need to use the attenuator for
* high inputs or inputs that we don't yet have a measurement
@ -119,12 +122,12 @@ static int powr1220_read_adc(struct device *dev, int ch_num)
* max reading.
*/
if (data->adc_maxes[ch_num] > ADC_MAX_LOW_MEASUREMENT_MV ||
data->adc_maxes[ch_num] == 0)
data->adc_maxes[ch_num] == 0)
adc_range = 1 << 4;
/* set the attenuator and mux */
result = i2c_smbus_write_byte_data(data->client, ADC_MUX,
adc_range | ch_num);
adc_range | ch_num);
if (result)
goto exit;
@ -167,135 +170,116 @@ exit:
return result;
}
/* Shows the voltage associated with the specified ADC channel */
static ssize_t powr1220_voltage_show(struct device *dev,
struct device_attribute *dev_attr,
char *buf)
static umode_t
powr1220_is_visible(const void *data, enum hwmon_sensor_types type, u32
attr, int channel)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
int adc_val = powr1220_read_adc(dev, attr->index);
struct powr1220_data *chip_data = (struct powr1220_data *)data;
if (adc_val < 0)
return adc_val;
if (channel >= chip_data->max_channels)
return 0;
return sprintf(buf, "%d\n", adc_val);
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_input:
case hwmon_in_highest:
case hwmon_in_label:
return 0444;
default:
break;
}
break;
default:
break;
}
return 0;
}
/* Shows the maximum setting associated with the specified ADC channel */
static ssize_t powr1220_max_show(struct device *dev,
struct device_attribute *dev_attr, char *buf)
static int
powr1220_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_label:
*str = input_names[channel];
return 0;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
static int
powr1220_read(struct device *dev, enum hwmon_sensor_types type, u32
attr, int channel, long *val)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
struct powr1220_data *data = dev_get_drvdata(dev);
int ret;
return sprintf(buf, "%d\n", data->adc_maxes[attr->index]);
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_input:
ret = powr1220_read_adc(dev, channel);
if (ret < 0)
return ret;
*val = ret;
break;
case hwmon_in_highest:
*val = data->adc_maxes[channel];
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
/* Shows the label associated with the specified ADC channel */
static ssize_t powr1220_label_show(struct device *dev,
struct device_attribute *dev_attr,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
return sprintf(buf, "%s\n", input_names[attr->index]);
return 0;
}
static SENSOR_DEVICE_ATTR_RO(in0_input, powr1220_voltage, VMON1);
static SENSOR_DEVICE_ATTR_RO(in1_input, powr1220_voltage, VMON2);
static SENSOR_DEVICE_ATTR_RO(in2_input, powr1220_voltage, VMON3);
static SENSOR_DEVICE_ATTR_RO(in3_input, powr1220_voltage, VMON4);
static SENSOR_DEVICE_ATTR_RO(in4_input, powr1220_voltage, VMON5);
static SENSOR_DEVICE_ATTR_RO(in5_input, powr1220_voltage, VMON6);
static SENSOR_DEVICE_ATTR_RO(in6_input, powr1220_voltage, VMON7);
static SENSOR_DEVICE_ATTR_RO(in7_input, powr1220_voltage, VMON8);
static SENSOR_DEVICE_ATTR_RO(in8_input, powr1220_voltage, VMON9);
static SENSOR_DEVICE_ATTR_RO(in9_input, powr1220_voltage, VMON10);
static SENSOR_DEVICE_ATTR_RO(in10_input, powr1220_voltage, VMON11);
static SENSOR_DEVICE_ATTR_RO(in11_input, powr1220_voltage, VMON12);
static SENSOR_DEVICE_ATTR_RO(in12_input, powr1220_voltage, VCCA);
static SENSOR_DEVICE_ATTR_RO(in13_input, powr1220_voltage, VCCINP);
static SENSOR_DEVICE_ATTR_RO(in0_highest, powr1220_max, VMON1);
static SENSOR_DEVICE_ATTR_RO(in1_highest, powr1220_max, VMON2);
static SENSOR_DEVICE_ATTR_RO(in2_highest, powr1220_max, VMON3);
static SENSOR_DEVICE_ATTR_RO(in3_highest, powr1220_max, VMON4);
static SENSOR_DEVICE_ATTR_RO(in4_highest, powr1220_max, VMON5);
static SENSOR_DEVICE_ATTR_RO(in5_highest, powr1220_max, VMON6);
static SENSOR_DEVICE_ATTR_RO(in6_highest, powr1220_max, VMON7);
static SENSOR_DEVICE_ATTR_RO(in7_highest, powr1220_max, VMON8);
static SENSOR_DEVICE_ATTR_RO(in8_highest, powr1220_max, VMON9);
static SENSOR_DEVICE_ATTR_RO(in9_highest, powr1220_max, VMON10);
static SENSOR_DEVICE_ATTR_RO(in10_highest, powr1220_max, VMON11);
static SENSOR_DEVICE_ATTR_RO(in11_highest, powr1220_max, VMON12);
static SENSOR_DEVICE_ATTR_RO(in12_highest, powr1220_max, VCCA);
static SENSOR_DEVICE_ATTR_RO(in13_highest, powr1220_max, VCCINP);
static SENSOR_DEVICE_ATTR_RO(in0_label, powr1220_label, VMON1);
static SENSOR_DEVICE_ATTR_RO(in1_label, powr1220_label, VMON2);
static SENSOR_DEVICE_ATTR_RO(in2_label, powr1220_label, VMON3);
static SENSOR_DEVICE_ATTR_RO(in3_label, powr1220_label, VMON4);
static SENSOR_DEVICE_ATTR_RO(in4_label, powr1220_label, VMON5);
static SENSOR_DEVICE_ATTR_RO(in5_label, powr1220_label, VMON6);
static SENSOR_DEVICE_ATTR_RO(in6_label, powr1220_label, VMON7);
static SENSOR_DEVICE_ATTR_RO(in7_label, powr1220_label, VMON8);
static SENSOR_DEVICE_ATTR_RO(in8_label, powr1220_label, VMON9);
static SENSOR_DEVICE_ATTR_RO(in9_label, powr1220_label, VMON10);
static SENSOR_DEVICE_ATTR_RO(in10_label, powr1220_label, VMON11);
static SENSOR_DEVICE_ATTR_RO(in11_label, powr1220_label, VMON12);
static SENSOR_DEVICE_ATTR_RO(in12_label, powr1220_label, VCCA);
static SENSOR_DEVICE_ATTR_RO(in13_label, powr1220_label, VCCINP);
static struct attribute *powr1220_attrs[] = {
&sensor_dev_attr_in0_input.dev_attr.attr,
&sensor_dev_attr_in1_input.dev_attr.attr,
&sensor_dev_attr_in2_input.dev_attr.attr,
&sensor_dev_attr_in3_input.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_in5_input.dev_attr.attr,
&sensor_dev_attr_in6_input.dev_attr.attr,
&sensor_dev_attr_in7_input.dev_attr.attr,
&sensor_dev_attr_in8_input.dev_attr.attr,
&sensor_dev_attr_in9_input.dev_attr.attr,
&sensor_dev_attr_in10_input.dev_attr.attr,
&sensor_dev_attr_in11_input.dev_attr.attr,
&sensor_dev_attr_in12_input.dev_attr.attr,
&sensor_dev_attr_in13_input.dev_attr.attr,
&sensor_dev_attr_in0_highest.dev_attr.attr,
&sensor_dev_attr_in1_highest.dev_attr.attr,
&sensor_dev_attr_in2_highest.dev_attr.attr,
&sensor_dev_attr_in3_highest.dev_attr.attr,
&sensor_dev_attr_in4_highest.dev_attr.attr,
&sensor_dev_attr_in5_highest.dev_attr.attr,
&sensor_dev_attr_in6_highest.dev_attr.attr,
&sensor_dev_attr_in7_highest.dev_attr.attr,
&sensor_dev_attr_in8_highest.dev_attr.attr,
&sensor_dev_attr_in9_highest.dev_attr.attr,
&sensor_dev_attr_in10_highest.dev_attr.attr,
&sensor_dev_attr_in11_highest.dev_attr.attr,
&sensor_dev_attr_in12_highest.dev_attr.attr,
&sensor_dev_attr_in13_highest.dev_attr.attr,
&sensor_dev_attr_in0_label.dev_attr.attr,
&sensor_dev_attr_in1_label.dev_attr.attr,
&sensor_dev_attr_in2_label.dev_attr.attr,
&sensor_dev_attr_in3_label.dev_attr.attr,
&sensor_dev_attr_in4_label.dev_attr.attr,
&sensor_dev_attr_in5_label.dev_attr.attr,
&sensor_dev_attr_in6_label.dev_attr.attr,
&sensor_dev_attr_in7_label.dev_attr.attr,
&sensor_dev_attr_in8_label.dev_attr.attr,
&sensor_dev_attr_in9_label.dev_attr.attr,
&sensor_dev_attr_in10_label.dev_attr.attr,
&sensor_dev_attr_in11_label.dev_attr.attr,
&sensor_dev_attr_in12_label.dev_attr.attr,
&sensor_dev_attr_in13_label.dev_attr.attr,
static const struct hwmon_channel_info *powr1220_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL),
NULL
};
ATTRIBUTE_GROUPS(powr1220);
static const struct hwmon_ops powr1220_hwmon_ops = {
.read = powr1220_read,
.read_string = powr1220_read_string,
.is_visible = powr1220_is_visible,
};
static const struct hwmon_chip_info powr1220_chip_info = {
.ops = &powr1220_hwmon_ops,
.info = powr1220_info,
};
static const struct i2c_device_id powr1220_ids[];
static int powr1220_probe(struct i2c_client *client)
{
@ -309,17 +293,30 @@ static int powr1220_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
switch (i2c_match_id(powr1220_ids, client)->driver_data) {
case powr1014:
data->max_channels = 10;
break;
default:
data->max_channels = 12;
break;
}
mutex_init(&data->update_lock);
data->client = client;
hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
client->name, data, powr1220_groups);
hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
client->name,
data,
&powr1220_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id powr1220_ids[] = {
{ "powr1220", 0, },
{ "powr1014", powr1014, },
{ "powr1220", powr1220, },
{ }
};

View File

@ -7,6 +7,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
@ -51,6 +52,9 @@ static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = {
static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = {
0x62, 0x64, 0x66, 0x68 };
static const u16 SCH5627_REG_PWM_MAP[SCH5627_NO_FANS] = {
0xA0, 0xA1, 0xA2, 0xA3 };
static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = {
0x22, 0x23, 0x24, 0x25, 0x189 };
static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = {
@ -222,6 +226,9 @@ static int reg_to_rpm(u16 reg)
static umode_t sch5627_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
int channel)
{
if (type == hwmon_pwm && attr == hwmon_pwm_auto_channels_temp)
return 0644;
return 0444;
}
@ -277,6 +284,23 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at
break;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_auto_channels_temp:
mutex_lock(&data->update_lock);
ret = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel]);
mutex_unlock(&data->update_lock);
if (ret < 0)
return ret;
*val = ret;
return 0;
default:
break;
}
break;
case hwmon_in:
ret = sch5627_update_in(data);
if (ret < 0)
@ -317,10 +341,42 @@ static int sch5627_read_string(struct device *dev, enum hwmon_sensor_types type,
return -EOPNOTSUPP;
}
static int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long val)
{
struct sch5627_data *data = dev_get_drvdata(dev);
int ret;
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_auto_channels_temp:
/* registers are 8 bit wide */
if (val > U8_MAX || val < 0)
return -EINVAL;
mutex_lock(&data->update_lock);
ret = sch56xx_write_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel],
val);
mutex_unlock(&data->update_lock);
return ret;
default:
break;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static const struct hwmon_ops sch5627_ops = {
.is_visible = sch5627_is_visible,
.read = sch5627_read,
.read_string = sch5627_read_string,
.write = sch5627_write,
};
static const struct hwmon_channel_info *sch5627_info[] = {
@ -341,6 +397,12 @@ static const struct hwmon_channel_info *sch5627_info[] = {
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT
),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_AUTO_CHANNELS_TEMP,
HWMON_PWM_AUTO_CHANNELS_TEMP,
HWMON_PWM_AUTO_CHANNELS_TEMP,
HWMON_PWM_AUTO_CHANNELS_TEMP
),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
@ -456,11 +518,20 @@ static int sch5627_probe(struct platform_device *pdev)
return 0;
}
static const struct platform_device_id sch5627_device_id[] = {
{
.name = "sch5627",
},
{ }
};
MODULE_DEVICE_TABLE(platform, sch5627_device_id);
static struct platform_driver sch5627_driver = {
.driver = {
.name = DRVNAME,
},
.probe = sch5627_probe,
.id_table = sch5627_device_id,
};
module_platform_driver(sch5627_driver);

View File

@ -7,6 +7,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
@ -501,12 +502,21 @@ error:
return err;
}
static const struct platform_device_id sch5636_device_id[] = {
{
.name = "sch5636",
},
{ }
};
MODULE_DEVICE_TABLE(platform, sch5636_device_id);
static struct platform_driver sch5636_driver = {
.driver = {
.name = DRVNAME,
},
.probe = sch5636_probe,
.remove = sch5636_remove,
.id_table = sch5636_device_id,
};
module_platform_driver(sch5636_driver);

View File

@ -7,8 +7,10 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/acpi.h>
@ -19,7 +21,10 @@
#include <linux/slab.h>
#include "sch56xx-common.h"
/* Insmod parameters */
static bool ignore_dmi;
module_param(ignore_dmi, bool, 0);
MODULE_PARM_DESC(ignore_dmi, "Omit DMI check for supported devices (default=0)");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
@ -134,7 +139,7 @@ static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v)
/* EM Interface Polling "Algorithm" */
for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
if (i >= max_busy_polls)
msleep(1);
usleep_range(1000, 2000);
/* Read Interrupt source Register */
val = inb(addr + 8);
/* Write Clear the interrupt source bits */
@ -422,7 +427,7 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision,
data->wddev.max_timeout = 255 * 60;
watchdog_set_nowayout(&data->wddev, nowayout);
if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)
set_bit(WDOG_ACTIVE, &data->wddev.status);
set_bit(WDOG_HW_RUNNING, &data->wddev.status);
/* Since the watchdog uses a downcounter there is no register to read
the BIOS set timeout from (if any was set at all) ->
@ -518,11 +523,42 @@ static int __init sch56xx_device_add(int address, const char *name)
return PTR_ERR_OR_ZERO(sch56xx_pdev);
}
/* For autoloading only */
static const struct dmi_system_id sch56xx_dmi_table[] __initconst = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
},
},
{ }
};
MODULE_DEVICE_TABLE(dmi, sch56xx_dmi_table);
static int __init sch56xx_init(void)
{
int address;
const char *name = NULL;
int address;
if (!ignore_dmi) {
if (!dmi_check_system(sch56xx_dmi_table))
return -ENODEV;
/*
* Some machines like the Esprimo P720 and Esprimo C700 have
* onboard devices named " Antiope"/" Theseus" instead of
* "Antiope"/"Theseus", so we need to check for both.
*/
if (!dmi_find_device(DMI_DEV_TYPE_OTHER, "Antiope", NULL) &&
!dmi_find_device(DMI_DEV_TYPE_OTHER, " Antiope", NULL) &&
!dmi_find_device(DMI_DEV_TYPE_OTHER, "Theseus", NULL) &&
!dmi_find_device(DMI_DEV_TYPE_OTHER, " Theseus", NULL))
return -ENODEV;
}
/*
* Some devices like the Esprimo C700 have both onboard devices,
* so we still have to check manually
*/
address = sch56xx_find(0x4e, &name);
if (address < 0)
address = sch56xx_find(0x2e, &name);

View File

@ -141,7 +141,6 @@ static int scpi_hwmon_probe(struct platform_device *pdev)
struct scpi_ops *scpi_ops;
struct device *hwdev, *dev = &pdev->dev;
struct scpi_sensors *scpi_sensors;
const struct of_device_id *of_id;
int idx, ret;
scpi_ops = get_scpi_ops();
@ -171,12 +170,11 @@ static int scpi_hwmon_probe(struct platform_device *pdev)
scpi_sensors->scpi_ops = scpi_ops;
of_id = of_match_device(scpi_of_match, &pdev->dev);
if (!of_id) {
scale = of_device_get_match_data(&pdev->dev);
if (!scale) {
dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n");
return -ENODEV;
}
scale = of_id->data;
for (i = 0, idx = 0; i < nr_sensors; i++) {
struct sensor_data *sensor = &scpi_sensors->data[idx];

View File

@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/util_macros.h>
enum tc654_regs {
@ -379,28 +380,20 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *da,
return sprintf(buf, "%d\n", pwm);
}
static ssize_t pwm_store(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
static int _set_pwm(struct tc654_data *data, unsigned long val)
{
struct tc654_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
unsigned long val;
int ret;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
if (val > 255)
return -EINVAL;
mutex_lock(&data->update_lock);
if (val == 0)
if (val == 0) {
data->config |= TC654_REG_CONFIG_SDM;
else
data->duty_cycle = 0;
} else {
data->config &= ~TC654_REG_CONFIG_SDM;
data->duty_cycle = find_closest(val, tc654_pwm_map,
ARRAY_SIZE(tc654_pwm_map));
data->duty_cycle = val - 1;
}
ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config);
if (ret < 0)
@ -411,6 +404,24 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *da,
out:
mutex_unlock(&data->update_lock);
return ret;
}
static ssize_t pwm_store(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct tc654_data *data = dev_get_drvdata(dev);
unsigned long val;
int ret;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
if (val > 255)
return -EINVAL;
if (val > 0)
val = find_closest(val, tc654_pwm_map, ARRAY_SIZE(tc654_pwm_map)) + 1;
ret = _set_pwm(data, val);
return ret < 0 ? ret : count;
}
@ -442,6 +453,58 @@ static struct attribute *tc654_attrs[] = {
ATTRIBUTE_GROUPS(tc654);
/*
* thermal cooling device functions
*
* Account for the "ShutDown Mode (SDM)" state by offsetting
* the 16 PWM duty cycle states by 1.
*
* State 0 = 0% PWM | Shutdown - Fan(s) are off
* State 1 = 30% PWM | duty_cycle = 0
* State 2 = ~35% PWM | duty_cycle = 1
* [...]
* State 15 = ~95% PWM | duty_cycle = 14
* State 16 = 100% PWM | duty_cycle = 15
*/
#define TC654_MAX_COOLING_STATE 16
static int tc654_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
*state = TC654_MAX_COOLING_STATE;
return 0;
}
static int tc654_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
struct tc654_data *data = tc654_update_client(cdev->devdata);
if (IS_ERR(data))
return PTR_ERR(data);
if (data->config & TC654_REG_CONFIG_SDM)
*state = 0; /* FAN is off */
else
*state = data->duty_cycle + 1; /* offset PWM States by 1 */
return 0;
}
static int tc654_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
struct tc654_data *data = tc654_update_client(cdev->devdata);
if (IS_ERR(data))
return PTR_ERR(data);
return _set_pwm(data, clamp_val(state, 0, TC654_MAX_COOLING_STATE));
}
static const struct thermal_cooling_device_ops tc654_fan_cool_ops = {
.get_max_state = tc654_get_max_state,
.get_cur_state = tc654_get_cur_state,
.set_cur_state = tc654_set_cur_state,
};
/*
* device probe and removal
*/
@ -472,7 +535,18 @@ static int tc654_probe(struct i2c_client *client)
hwmon_dev =
devm_hwmon_device_register_with_groups(dev, client->name, data,
tc654_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
if (IS_ENABLED(CONFIG_THERMAL)) {
struct thermal_cooling_device *cdev;
cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name,
hwmon_dev, &tc654_fan_cool_ops);
return PTR_ERR_OR_ZERO(cdev);
}
return 0;
}
static const struct i2c_device_id tc654_id[] = {

712
drivers/hwmon/tmp464.c Normal file
View File

@ -0,0 +1,712 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Driver for the Texas Instruments TMP464 SMBus temperature sensor IC.
* Supported models: TMP464, TMP468
* Copyright (C) 2022 Agathe Porte <agathe.porte@nokia.com>
* Preliminary support by:
* Lionel Pouliquen <lionel.lp.pouliquen@nokia.com>
*/
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END };
#define TMP464_NUM_CHANNELS 5 /* chan 0 is internal, 1-4 are remote */
#define TMP468_NUM_CHANNELS 9 /* chan 0 is internal, 1-8 are remote */
#define MAX_CHANNELS 9
#define TMP464_TEMP_REG(channel) (channel)
#define TMP464_TEMP_OFFSET_REG(channel) (0x40 + ((channel) - 1) * 8)
#define TMP464_N_FACTOR_REG(channel) (0x41 + ((channel) - 1) * 8)
static const u8 TMP464_THERM_LIMIT[MAX_CHANNELS] = {
0x39, 0x42, 0x4A, 0x52, 0x5A, 0x62, 0x6a, 0x72, 0x7a };
static const u8 TMP464_THERM2_LIMIT[MAX_CHANNELS] = {
0x3A, 0x43, 0x4B, 0x53, 0x5B, 0x63, 0x6b, 0x73, 0x7b };
#define TMP464_THERM_STATUS_REG 0x21
#define TMP464_THERM2_STATUS_REG 0x22
#define TMP464_REMOTE_OPEN_REG 0x23
#define TMP464_CONFIG_REG 0x30
#define TMP464_TEMP_HYST_REG 0x38
#define TMP464_LOCK_REG 0xc4
/* Identification */
#define TMP464_MANUFACTURER_ID_REG 0xFE
#define TMP464_DEVICE_ID_REG 0xFF
/* Flags */
#define TMP464_CONFIG_SHUTDOWN BIT(5)
#define TMP464_CONFIG_RANGE 0x04
#define TMP464_CONFIG_REG_REN(x) (BIT(7 + (x)))
#define TMP464_CONFIG_REG_REN_MASK GENMASK(15, 7)
#define TMP464_CONFIG_CONVERSION_RATE_B0 2
#define TMP464_CONFIG_CONVERSION_RATE_B2 4
#define TMP464_CONFIG_CONVERSION_RATE_MASK GENMASK(TMP464_CONFIG_CONVERSION_RATE_B2, \
TMP464_CONFIG_CONVERSION_RATE_B0)
#define TMP464_UNLOCK_VAL 0xeb19
#define TMP464_LOCK_VAL 0x5ca6
#define TMP464_LOCKED 0x8000
/* Manufacturer / Device ID's */
#define TMP464_MANUFACTURER_ID 0x5449
#define TMP464_DEVICE_ID 0x1468
#define TMP468_DEVICE_ID 0x0468
static const struct i2c_device_id tmp464_id[] = {
{ "tmp464", TMP464_NUM_CHANNELS },
{ "tmp468", TMP468_NUM_CHANNELS },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp464_id);
static const struct of_device_id __maybe_unused tmp464_of_match[] = {
{
.compatible = "ti,tmp464",
.data = (void *)TMP464_NUM_CHANNELS
},
{
.compatible = "ti,tmp468",
.data = (void *)TMP468_NUM_CHANNELS
},
{},
};
MODULE_DEVICE_TABLE(of, tmp464_of_match);
struct tmp464_channel {
const char *label;
bool enabled;
};
struct tmp464_data {
struct regmap *regmap;
struct mutex update_lock;
int channels;
s16 config_orig;
u16 open_reg;
unsigned long last_updated;
bool valid;
int update_interval;
struct tmp464_channel channel[MAX_CHANNELS];
};
static int temp_from_reg(s16 reg)
{
return DIV_ROUND_CLOSEST((reg >> 3) * 625, 10);
}
static s16 temp_to_limit_reg(long temp)
{
return DIV_ROUND_CLOSEST(temp, 500) << 6;
}
static s16 temp_to_offset_reg(long temp)
{
return DIV_ROUND_CLOSEST(temp * 10, 625) << 3;
}
static int tmp464_enable_channels(struct tmp464_data *data)
{
struct regmap *regmap = data->regmap;
u16 enable = 0;
int i;
for (i = 0; i < data->channels; i++)
if (data->channel[i].enabled)
enable |= TMP464_CONFIG_REG_REN(i);
return regmap_update_bits(regmap, TMP464_CONFIG_REG, TMP464_CONFIG_REG_REN_MASK, enable);
}
static int tmp464_chip_read(struct device *dev, u32 attr, int channel, long *val)
{
struct tmp464_data *data = dev_get_drvdata(dev);
switch (attr) {
case hwmon_chip_update_interval:
*val = data->update_interval;
return 0;
default:
return -EOPNOTSUPP;
}
}
static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
struct tmp464_data *data = dev_get_drvdata(dev);
struct regmap *regmap = data->regmap;
unsigned int regval, regval2;
int err = 0;
mutex_lock(&data->update_lock);
switch (attr) {
case hwmon_temp_max_alarm:
err = regmap_read(regmap, TMP464_THERM_STATUS_REG, &regval);
if (err < 0)
break;
*val = !!(regval & BIT(channel + 7));
break;
case hwmon_temp_crit_alarm:
err = regmap_read(regmap, TMP464_THERM2_STATUS_REG, &regval);
if (err < 0)
break;
*val = !!(regval & BIT(channel + 7));
break;
case hwmon_temp_fault:
/*
* The chip clears TMP464_REMOTE_OPEN_REG after it is read
* and only updates it after the next measurement cycle is
* complete. That means we have to cache the value internally
* for one measurement cycle and report the cached value.
*/
if (!data->valid || time_after(jiffies, data->last_updated +
msecs_to_jiffies(data->update_interval))) {
err = regmap_read(regmap, TMP464_REMOTE_OPEN_REG, &regval);
if (err < 0)
break;
data->open_reg = regval;
data->last_updated = jiffies;
data->valid = true;
}
*val = !!(data->open_reg & BIT(channel + 7));
break;
case hwmon_temp_max_hyst:
err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], &regval);
if (err < 0)
break;
err = regmap_read(regmap, TMP464_TEMP_HYST_REG, &regval2);
if (err < 0)
break;
regval -= regval2;
*val = temp_from_reg(regval);
break;
case hwmon_temp_max:
err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], &regval);
if (err < 0)
break;
*val = temp_from_reg(regval);
break;
case hwmon_temp_crit_hyst:
err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], &regval);
if (err < 0)
break;
err = regmap_read(regmap, TMP464_TEMP_HYST_REG, &regval2);
if (err < 0)
break;
regval -= regval2;
*val = temp_from_reg(regval);
break;
case hwmon_temp_crit:
err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], &regval);
if (err < 0)
break;
*val = temp_from_reg(regval);
break;
case hwmon_temp_offset:
err = regmap_read(regmap, TMP464_TEMP_OFFSET_REG(channel), &regval);
if (err < 0)
break;
*val = temp_from_reg(regval);
break;
case hwmon_temp_input:
if (!data->channel[channel].enabled) {
err = -ENODATA;
break;
}
err = regmap_read(regmap, TMP464_TEMP_REG(channel), &regval);
if (err < 0)
break;
*val = temp_from_reg(regval);
break;
case hwmon_temp_enable:
*val = data->channel[channel].enabled;
break;
default:
err = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->update_lock);
return err;
}
static int tmp464_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
case hwmon_chip:
return tmp464_chip_read(dev, attr, channel, val);
case hwmon_temp:
return tmp464_temp_read(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
}
static int tmp464_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
struct tmp464_data *data = dev_get_drvdata(dev);
*str = data->channel[channel].label;
return 0;
}
static int tmp464_set_convrate(struct tmp464_data *data, long interval)
{
int rate;
/*
* For valid rates, interval in milli-seconds can be calculated as
* interval = 125 << (7 - rate);
* or
* interval = (1 << (7 - rate)) * 125;
* The rate is therefore
* rate = 7 - __fls(interval / 125);
* and the rounded rate is
* rate = 7 - __fls(interval * 4 / (125 * 3));
* Use clamp_val() to avoid overflows, and to ensure valid input
* for __fls.
*/
interval = clamp_val(interval, 125, 16000);
rate = 7 - __fls(interval * 4 / (125 * 3));
data->update_interval = 125 << (7 - rate);
return regmap_update_bits(data->regmap, TMP464_CONFIG_REG,
TMP464_CONFIG_CONVERSION_RATE_MASK,
rate << TMP464_CONFIG_CONVERSION_RATE_B0);
}
static int tmp464_chip_write(struct tmp464_data *data, u32 attr, int channel, long val)
{
switch (attr) {
case hwmon_chip_update_interval:
return tmp464_set_convrate(data, val);
default:
return -EOPNOTSUPP;
}
}
static int tmp464_temp_write(struct tmp464_data *data, u32 attr, int channel, long val)
{
struct regmap *regmap = data->regmap;
unsigned int regval;
int err = 0;
switch (attr) {
case hwmon_temp_max_hyst:
err = regmap_read(regmap, TMP464_THERM_LIMIT[0], &regval);
if (err < 0)
break;
val = clamp_val(val, -256000, 256000); /* prevent overflow/underflow */
val = clamp_val(temp_from_reg(regval) - val, 0, 255000);
err = regmap_write(regmap, TMP464_TEMP_HYST_REG,
DIV_ROUND_CLOSEST(val, 1000) << 7);
break;
case hwmon_temp_max:
val = temp_to_limit_reg(clamp_val(val, -255000, 255500));
err = regmap_write(regmap, TMP464_THERM_LIMIT[channel], val);
break;
case hwmon_temp_crit:
val = temp_to_limit_reg(clamp_val(val, -255000, 255500));
err = regmap_write(regmap, TMP464_THERM2_LIMIT[channel], val);
break;
case hwmon_temp_offset:
val = temp_to_offset_reg(clamp_val(val, -128000, 127937));
err = regmap_write(regmap, TMP464_TEMP_OFFSET_REG(channel), val);
break;
case hwmon_temp_enable:
data->channel[channel].enabled = !!val;
err = tmp464_enable_channels(data);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static int tmp464_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct tmp464_data *data = dev_get_drvdata(dev);
int err;
mutex_lock(&data->update_lock);
switch (type) {
case hwmon_chip:
err = tmp464_chip_write(data, attr, channel, val);
break;
case hwmon_temp:
err = tmp464_temp_write(data, attr, channel, val);
break;
default:
err = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->update_lock);
return err;
}
static umode_t tmp464_is_visible(const void *_data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct tmp464_data *data = _data;
if (channel >= data->channels)
return 0;
if (type == hwmon_chip) {
if (attr == hwmon_chip_update_interval)
return 0644;
return 0;
}
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_max_alarm:
case hwmon_temp_crit_alarm:
case hwmon_temp_crit_hyst:
return 0444;
case hwmon_temp_enable:
case hwmon_temp_max:
case hwmon_temp_crit:
return 0644;
case hwmon_temp_max_hyst:
if (!channel)
return 0644;
return 0444;
case hwmon_temp_label:
if (data->channel[channel].label)
return 0444;
return 0;
case hwmon_temp_fault:
if (channel)
return 0444;
return 0;
case hwmon_temp_offset:
if (channel)
return 0644;
return 0;
default:
return 0;
}
}
static void tmp464_restore_lock(void *regmap)
{
regmap_write(regmap, TMP464_LOCK_REG, TMP464_LOCK_VAL);
}
static void tmp464_restore_config(void *_data)
{
struct tmp464_data *data = _data;
regmap_write(data->regmap, TMP464_CONFIG_REG, data->config_orig);
}
static int tmp464_init_client(struct device *dev, struct tmp464_data *data)
{
struct regmap *regmap = data->regmap;
unsigned int regval;
int err;
err = regmap_read(regmap, TMP464_LOCK_REG, &regval);
if (err)
return err;
if (regval == TMP464_LOCKED) {
/* Explicitly unlock chip if it is locked */
err = regmap_write(regmap, TMP464_LOCK_REG, TMP464_UNLOCK_VAL);
if (err)
return err;
/* and lock it again when unloading the driver */
err = devm_add_action_or_reset(dev, tmp464_restore_lock, regmap);
if (err)
return err;
}
err = regmap_read(regmap, TMP464_CONFIG_REG, &regval);
if (err)
return err;
data->config_orig = regval;
err = devm_add_action_or_reset(dev, tmp464_restore_config, data);
if (err)
return err;
/* Default to 500 ms update interval */
err = regmap_update_bits(regmap, TMP464_CONFIG_REG,
TMP464_CONFIG_CONVERSION_RATE_MASK | TMP464_CONFIG_SHUTDOWN,
BIT(TMP464_CONFIG_CONVERSION_RATE_B0) |
BIT(TMP464_CONFIG_CONVERSION_RATE_B2));
if (err)
return err;
data->update_interval = 500;
return tmp464_enable_channels(data);
}
static int tmp464_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
char *name, *chip;
int reg;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
reg = i2c_smbus_read_word_swapped(client, TMP464_MANUFACTURER_ID_REG);
if (reg < 0)
return reg;
if (reg != TMP464_MANUFACTURER_ID)
return -ENODEV;
/* Check for "always return zero" bits */
reg = i2c_smbus_read_word_swapped(client, TMP464_THERM_STATUS_REG);
if (reg < 0)
return reg;
if (reg & 0x1f)
return -ENODEV;
reg = i2c_smbus_read_word_swapped(client, TMP464_THERM2_STATUS_REG);
if (reg < 0)
return reg;
if (reg & 0x1f)
return -ENODEV;
reg = i2c_smbus_read_word_swapped(client, TMP464_DEVICE_ID_REG);
if (reg < 0)
return reg;
switch (reg) {
case TMP464_DEVICE_ID:
name = "tmp464";
chip = "TMP464";
break;
case TMP468_DEVICE_ID:
name = "tmp468";
chip = "TMP468";
break;
default:
return -ENODEV;
}
strscpy(info->type, name, I2C_NAME_SIZE);
dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", chip, client->addr);
return 0;
}
static int tmp464_probe_child_from_dt(struct device *dev,
struct device_node *child,
struct tmp464_data *data)
{
struct regmap *regmap = data->regmap;
u32 channel;
s32 nfactor;
int err;
err = of_property_read_u32(child, "reg", &channel);
if (err) {
dev_err(dev, "missing reg property of %pOFn\n", child);
return err;
}
if (channel >= data->channels) {
dev_err(dev, "invalid reg %d of %pOFn\n", channel, child);
return -EINVAL;
}
of_property_read_string(child, "label", &data->channel[channel].label);
data->channel[channel].enabled = of_device_is_available(child);
err = of_property_read_s32(child, "ti,n-factor", &nfactor);
if (err && err != -EINVAL)
return err;
if (!err) {
if (channel == 0) {
dev_err(dev, "n-factor can't be set for internal channel\n");
return -EINVAL;
}
if (nfactor > 127 || nfactor < -128) {
dev_err(dev, "n-factor for channel %d invalid (%d)\n",
channel, nfactor);
return -EINVAL;
}
err = regmap_write(regmap, TMP464_N_FACTOR_REG(channel),
(nfactor << 8) & 0xff00);
if (err)
return err;
}
return 0;
}
static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data)
{
const struct device_node *np = dev->of_node;
struct device_node *child;
int err;
for_each_child_of_node(np, child) {
if (strcmp(child->name, "channel"))
continue;
err = tmp464_probe_child_from_dt(dev, child, data);
if (err) {
of_node_put(child);
return err;
}
}
return 0;
}
static const struct hwmon_ops tmp464_ops = {
.is_visible = tmp464_is_visible,
.read = tmp464_read,
.read_string = tmp464_read_string,
.write = tmp464_write,
};
static const struct hwmon_channel_info *tmp464_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT |
HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM |
HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE,
HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST |
HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM |
HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE),
NULL
};
static const struct hwmon_chip_info tmp464_chip_info = {
.ops = &tmp464_ops,
.info = tmp464_info,
};
/* regmap */
static bool tmp464_is_volatile_reg(struct device *dev, unsigned int reg)
{
return (reg < TMP464_TEMP_REG(TMP468_NUM_CHANNELS) ||
reg == TMP464_THERM_STATUS_REG ||
reg == TMP464_THERM2_STATUS_REG ||
reg == TMP464_REMOTE_OPEN_REG);
}
static const struct regmap_config tmp464_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = TMP464_DEVICE_ID_REG,
.volatile_reg = tmp464_is_volatile_reg,
.val_format_endian = REGMAP_ENDIAN_BIG,
.cache_type = REGCACHE_RBTREE,
.use_single_read = true,
.use_single_write = true,
};
static int tmp464_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct tmp464_data *data;
int i, err;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(&client->dev, "i2c functionality check failed\n");
return -ENODEV;
}
data = devm_kzalloc(dev, sizeof(struct tmp464_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
mutex_init(&data->update_lock);
if (dev->of_node)
data->channels = (int)(unsigned long)of_device_get_match_data(&client->dev);
else
data->channels = i2c_match_id(tmp464_id, client)->driver_data;
data->regmap = devm_regmap_init_i2c(client, &tmp464_regmap_config);
if (IS_ERR(data->regmap))
return PTR_ERR(data->regmap);
for (i = 0; i < data->channels; i++)
data->channel[i].enabled = true;
err = tmp464_init_client(dev, data);
if (err)
return err;
if (dev->of_node) {
err = tmp464_probe_from_dt(dev, data);
if (err)
return err;
}
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data, &tmp464_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static struct i2c_driver tmp464_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "tmp464",
.of_match_table = of_match_ptr(tmp464_of_match),
},
.probe_new = tmp464_probe,
.id_table = tmp464_id,
.detect = tmp464_detect,
.address_list = normal_i2c,
};
module_i2c_driver(tmp464_driver);
MODULE_AUTHOR("Agathe Porte <agathe.porte@nokia.com>");
MODULE_DESCRIPTION("Texas Instruments TMP464 temperature sensor driver");
MODULE_LICENSE("GPL");

View File

@ -207,7 +207,6 @@ MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match);
static int vexpress_hwmon_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct vexpress_hwmon_data *data;
const struct vexpress_hwmon_type *type;
@ -216,10 +215,9 @@ static int vexpress_hwmon_probe(struct platform_device *pdev)
return -ENOMEM;
platform_set_drvdata(pdev, data);
match = of_match_device(vexpress_hwmon_of_match, &pdev->dev);
if (!match)
type = of_device_get_match_data(&pdev->dev);
if (!type)
return -ENODEV;
type = match->data;
data->reg = devm_regmap_init_vexpress_config(&pdev->dev);
if (IS_ERR(data->reg))

View File

@ -332,12 +332,14 @@ enum hwmon_pwm_attributes {
hwmon_pwm_enable,
hwmon_pwm_mode,
hwmon_pwm_freq,
hwmon_pwm_auto_channels_temp,
};
#define HWMON_PWM_INPUT BIT(hwmon_pwm_input)
#define HWMON_PWM_ENABLE BIT(hwmon_pwm_enable)
#define HWMON_PWM_MODE BIT(hwmon_pwm_mode)
#define HWMON_PWM_FREQ BIT(hwmon_pwm_freq)
#define HWMON_PWM_AUTO_CHANNELS_TEMP BIT(hwmon_pwm_auto_channels_temp)
enum hwmon_intrusion_attributes {
hwmon_intrusion_alarm,