forked from Minki/linux
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:
commit
fd27687791
10
Documentation/ABI/obsolete/procfs-i8k
Normal file
10
Documentation/ABI/obsolete/procfs-i8k
Normal 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
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
};
|
||||
};
|
||||
|
@ -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>;
|
||||
};
|
||||
};
|
||||
|
114
Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml
Normal file
114
Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
@ -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
|
||||
|
||||
|
@ -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,.*":
|
||||
|
@ -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
|
||||
================ =================================================
|
||||
|
54
Documentation/hwmon/asus_ec_sensors.rst
Normal file
54
Documentation/hwmon/asus_ec_sensors.rst
Normal 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.
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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>
|
||||
|
75
Documentation/hwmon/pli1209bc.rst
Normal file
75
Documentation/hwmon/pli1209bc.rst
Normal 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.
|
||||
======================= ========================================================
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
73
Documentation/hwmon/tmp464.rst
Normal file
73
Documentation/hwmon/tmp464.rst
Normal 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**
|
@ -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.
|
||||
|
16
MAINTAINERS
16
MAINTAINERS
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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], ®val);
|
||||
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, ®val);
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
716
drivers/hwmon/asus-ec-sensors.c
Normal file
716
drivers/hwmon/asus-ec-sensors.c
Normal 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");
|
@ -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
|
||||
},
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(®s) ? : 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(®s) ? : (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(®s) ? : 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(®s) ? : (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(®s);
|
||||
}
|
||||
|
||||
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(®s) ? : 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(®s) ? : 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)
|
||||
|
@ -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;
|
||||
|
@ -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 },
|
||||
{ },
|
||||
|
@ -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], ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = (s8)regval * 1000;
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(data->regmap, LM83_REG_MAX[channel], ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = (s8)regval * 1000;
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
err = regmap_read(data->regmap, LM83_REG_R_TCRIT, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = (s8)regval * 1000;
|
||||
break;
|
||||
case hwmon_temp_max_alarm:
|
||||
err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val);
|
||||
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], ®val);
|
||||
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], ®val);
|
||||
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, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = regval;
|
||||
err = regmap_read(data->regmap, LM83_REG_R_STATUS2, ®val);
|
||||
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 = {
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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, ®val);
|
||||
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;
|
||||
|
@ -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)",
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
146
drivers/hwmon/pmbus/pli1209bc.c
Normal file
146
drivers/hwmon/pmbus/pli1209bc.c
Normal 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);
|
@ -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"), \
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"},
|
||||
{}
|
||||
|
@ -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, },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
|
@ -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
712
drivers/hwmon/tmp464.c
Normal 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, ®val);
|
||||
if (err < 0)
|
||||
break;
|
||||
*val = !!(regval & BIT(channel + 7));
|
||||
break;
|
||||
case hwmon_temp_crit_alarm:
|
||||
err = regmap_read(regmap, TMP464_THERM2_STATUS_REG, ®val);
|
||||
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, ®val);
|
||||
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], ®val);
|
||||
if (err < 0)
|
||||
break;
|
||||
err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2);
|
||||
if (err < 0)
|
||||
break;
|
||||
regval -= regval2;
|
||||
*val = temp_from_reg(regval);
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val);
|
||||
if (err < 0)
|
||||
break;
|
||||
*val = temp_from_reg(regval);
|
||||
break;
|
||||
case hwmon_temp_crit_hyst:
|
||||
err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val);
|
||||
if (err < 0)
|
||||
break;
|
||||
err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2);
|
||||
if (err < 0)
|
||||
break;
|
||||
regval -= regval2;
|
||||
*val = temp_from_reg(regval);
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val);
|
||||
if (err < 0)
|
||||
break;
|
||||
*val = temp_from_reg(regval);
|
||||
break;
|
||||
case hwmon_temp_offset:
|
||||
err = regmap_read(regmap, TMP464_TEMP_OFFSET_REG(channel), ®val);
|
||||
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), ®val);
|
||||
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], ®val);
|
||||
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, ®val);
|
||||
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, ®val);
|
||||
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");
|
@ -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))
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user