mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-10 15:58:47 +00:00
hwmon updates for v5.19-rc1
- New drivers - Driver for the Microchip LAN966x SoC - PMBus driver for Infineon Digital Multi-phase xdp152 family controllers - Chip support added to existing drivers - asus-ec-sensors - Support for ROG STRIX X570-E GAMING WIFI II, PRIME X470-PRO, and ProArt X570 Creator WIFI - External temperature sensor support for ASUS WS X570-ACE - nct6775 - Support for I2C driver - Support for ASUS PRO H410T / PRIME H410M-R / ROG X570-E GAMING WIFI II - lm75 - Support for - Atmel AT30TS74 - pmbus/max16601 - Support for MAX16602 - aquacomputer_d5next - Support for Aquacomputer Farbwerk - Support for Aquacomputer Octo - jc42 - Support for S-34TS04A - Kernel API changes / clarifications - The chip parameter of with_info API is now mandatory - New hwmon_device_register_for_thermal API call for use by the thermal subsystem - Improvements - PMBus and JC42 drivers now register with thermal subsystem - PMBus drivers now support get_voltage/set_voltage power operations - The adt7475 driver now supports pin configuration - The lm90 driver now supports setting extended range temperatures configuration with a devicetree property - The dell-smm driver now registers as cooling device - The OCC driver delays hwmon registration until requested by userspace - Various other minor fixes and improvements -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmKKpskACgkQyx8mb86f mYHmkw/+IsOgkaSwA0PMBSQvPdncDcywchhtJ20UP3aKogy9Lp4HZ9NBRPZKeL7Y r89LSi3OT27yn+NQ7JXGIA7VLnqftoHREkyq3khJDwqRCMv0/bTxEYuO04Hdte1n 4QrLth4yMfG5domgQn/M1KyS40jsMLPLMg0ui/Zwbm6O9J4D/Jj+P8KiT+Txgdmh Zm/a2WQEkqueXENv1XEOgZ4DvKxq236pqn9kLVBQSiI74GAtg08pB5K+HyDIcTph 1nnbW/hJclWX96/Dbw87QNV7tu5xTAfno9xN4rbTYNgafx6gtoJoXWXukA9memi4 NzkFiaOdf+47Pr+EEi7SczVf+P+EwisVt4IMahMLIXZMaStHEJFcodR3PjsVPWt/ 8R6z6r+byNFjfGJDpvGwUm9zJcaiCs/zrylyrOx2UXdzMrD3A6zngsPtWoli37h0 X5vV5MYEVKSE1m4ZEt0rq8O2gc2Jrb2FyVxhEzaDoM5IwviXSNEGIiav6uPaFI/R ehmsWV/qbqRp3lfcvwyei4frITHhpgZQC5eaEiN+LFu1XbBxy7TlSp3UAqL0jHj+ qBZxpFgAz9MmEH1NgfSc8hHdz1cKIo9eR8IdteFg3WexcJ9evFwKiVK8yvlMOlVS CnOhGOTOFHZVASnNQS45Vi9Ofr6Ou2YSss2McyB1eMOYUMC0cxU= =LA2x -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "New drivers: - Driver for the Microchip LAN966x SoC - PMBus driver for Infineon Digital Multi-phase xdp152 family controllers Chip support added to existing drivers: - asus-ec-sensors: - Support for ROG STRIX X570-E GAMING WIFI II, PRIME X470-PRO, and ProArt X570 Creator WIFI - External temperature sensor support for ASUS WS X570-ACE - nct6775: - Support for I2C driver - Support for ASUS PRO H410T / PRIME H410M-R / ROG X570-E GAMING WIFI II - lm75: - Support for - Atmel AT30TS74 - pmbus/max16601: - Support for MAX16602 - aquacomputer_d5next: - Support for Aquacomputer Farbwerk - Support for Aquacomputer Octo - jc42: - Support for S-34TS04A Kernel API changes / clarifications: - The chip parameter of with_info API is now mandatory - New hwmon_device_register_for_thermal API call for use by the thermal subsystem Improvements: - PMBus and JC42 drivers now register with thermal subsystem - PMBus drivers now support get_voltage/set_voltage power operations - The adt7475 driver now supports pin configuration - The lm90 driver now supports setting extended range temperatures configuration with a devicetree property - The dell-smm driver now registers as cooling device - The OCC driver delays hwmon registration until requested by userspace ... and various other minor fixes and improvements" * tag 'hwmon-for-v5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (71 commits) hwmon: (aquacomputer_d5next) Fix an error handling path in aqc_probe() hwmon: (sl28cpld) Fix typo in comment hwmon: (pmbus) Check PEC support before reading other registers hwmon: (dimmtemp) Fix bitmap handling hwmon: (lm90) enable extended range according to DTS node dt-bindings: hwmon: lm90: add ti,extended-range-enable property dt-bindings: hwmon: lm90: add missing ti,tmp461 hwmon: (ibmaem) Directly use ida_alloc()/free() hwmon: Directly use ida_alloc()/free() hwmon: (asus-ec-sensors) fix Formula VIII definition dt-bindings: trivial-devices: Add xdp152 hwmon: (sl28cpld-hwmon) Use HWMON_CHANNEL_INFO macro hwmon: (pwm-fan) Use HWMON_CHANNEL_INFO macro hwmon: (peci/dimmtemp) Use HWMON_CHANNEL_INFO macro hwmon: (peci/cputemp) Use HWMON_CHANNEL_INFO macro hwmon: (mr75203) Use HWMON_CHANNEL_INFO macro hwmon: (ltc2992) Use HWMON_CHANNEL_INFO macro hwmon: (as370-hwmon) Use HWMON_CHANNEL_INFO macro hwmon: Make chip parameter for with_info API mandatory thermal/drivers/thermal_hwmon: Use hwmon_device_register_for_thermal() ...
This commit is contained in:
commit
076f222a69
@ -61,6 +61,26 @@ patternProperties:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [0, 1]
|
||||
|
||||
"adi,pin(5|10)-function":
|
||||
description: |
|
||||
Configures the function for pin 5 on the adi,adt7473 and adi,adt7475. Or
|
||||
pin 10 on the adi,adt7476 and adi,adt7490.
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum:
|
||||
- pwm2
|
||||
- smbalert#
|
||||
|
||||
"adi,pin(9|14)-function":
|
||||
description: |
|
||||
Configures the function for pin 9 on the adi,adt7473 and adi,adt7475. Or
|
||||
pin 14 on the adi,adt7476 and adi,adt7490
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum:
|
||||
- tach4
|
||||
- therm#
|
||||
- smbalert#
|
||||
- gpio
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@ -79,6 +99,8 @@ examples:
|
||||
adi,bypass-attenuator-in0 = <1>;
|
||||
adi,bypass-attenuator-in1 = <0>;
|
||||
adi,pwm-active-state = <1 0 1>;
|
||||
adi,pin10-function = "smbalert#";
|
||||
adi,pin14-function = "tach4";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@ properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adt75
|
||||
- atmel,at30ts74
|
||||
- dallas,ds1775
|
||||
- dallas,ds75
|
||||
- dallas,ds7505
|
||||
|
@ -0,0 +1,53 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/microchip,lan966x.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Microchip LAN966x Hardware Monitor
|
||||
|
||||
maintainers:
|
||||
- Michael Walle <michael@walle.cc>
|
||||
|
||||
description: |
|
||||
Microchip LAN966x temperature monitor and fan controller
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- microchip,lan9668-hwmon
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: PVT registers
|
||||
- description: FAN registers
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: pvt
|
||||
- const: fan
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
'#thermal-sensor-cells':
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- clocks
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
hwmon: hwmon@e2010180 {
|
||||
compatible = "microchip,lan9668-hwmon";
|
||||
reg = <0xe2010180 0xc>,
|
||||
<0xe20042a8 0xc>;
|
||||
reg-names = "pvt", "fan";
|
||||
clocks = <&sys_clk>;
|
||||
#thermal-sensor-cells = <0>;
|
||||
};
|
@ -34,6 +34,7 @@ properties:
|
||||
- nxp,sa56004
|
||||
- onnn,nct1008
|
||||
- ti,tmp451
|
||||
- ti,tmp461
|
||||
- winbond,w83l771
|
||||
|
||||
|
||||
@ -52,10 +53,29 @@ properties:
|
||||
vcc-supply:
|
||||
description: phandle to the regulator that provides the +VCC supply
|
||||
|
||||
ti,extended-range-enable:
|
||||
description: Set to enable extended range temperature.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
not:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- adi,adt7461
|
||||
- adi,adt7461a
|
||||
- ti,tmp451
|
||||
- ti,tmp461
|
||||
then:
|
||||
properties:
|
||||
ti,extended-range-enable: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
|
57
Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml
Normal file
57
Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml
Normal file
@ -0,0 +1,57 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
|
||||
$id: http://devicetree.org/schemas/hwmon/nuvoton,nct6775.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Nuvoton NCT6775 and compatible Super I/O chips
|
||||
|
||||
maintainers:
|
||||
- Zev Weiss <zev@bewilderbeest.net>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- nuvoton,nct6106
|
||||
- nuvoton,nct6116
|
||||
- nuvoton,nct6775
|
||||
- nuvoton,nct6776
|
||||
- nuvoton,nct6779
|
||||
- nuvoton,nct6791
|
||||
- nuvoton,nct6792
|
||||
- nuvoton,nct6793
|
||||
- nuvoton,nct6795
|
||||
- nuvoton,nct6796
|
||||
- nuvoton,nct6797
|
||||
- nuvoton,nct6798
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
nuvoton,tsi-channel-mask:
|
||||
description:
|
||||
Bitmask indicating which TSI temperature sensor channels are
|
||||
active. LSB is TSI0, bit 1 is TSI1, etc.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
maximum: 0xff
|
||||
default: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
superio@4d {
|
||||
compatible = "nuvoton,nct6779";
|
||||
reg = <0x4d>;
|
||||
nuvoton,tsi-channel-mask = <0x03>;
|
||||
};
|
||||
};
|
105
Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml
Normal file
105
Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml
Normal file
@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/ti,tmp401.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TMP401, TPM411 and TMP43x temperature sensor
|
||||
|
||||
maintainers:
|
||||
- Guenter Roeck <linux@roeck-us.net>
|
||||
|
||||
description: |
|
||||
±1°C Remote and Local temperature sensor
|
||||
|
||||
Datasheets:
|
||||
https://www.ti.com/lit/ds/symlink/tmp401.pdf
|
||||
https://www.ti.com/lit/ds/symlink/tmp411.pdf
|
||||
https://www.ti.com/lit/ds/symlink/tmp431.pdf
|
||||
https://www.ti.com/lit/ds/symlink/tmp435.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,tmp401
|
||||
- ti,tmp411
|
||||
- ti,tmp431
|
||||
- ti,tmp432
|
||||
- ti,tmp435
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
ti,extended-range-enable:
|
||||
description:
|
||||
When set, this sensor measures over extended temperature range.
|
||||
type: boolean
|
||||
|
||||
ti,n-factor:
|
||||
description:
|
||||
value to be used for converting remote channel measurements to
|
||||
temperature.
|
||||
$ref: /schemas/types.yaml#/definitions/int32
|
||||
items:
|
||||
minimum: -128
|
||||
maximum: 127
|
||||
|
||||
ti,beta-compensation:
|
||||
description:
|
||||
value to select beta correction range.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 0
|
||||
maximum: 15
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- ti,tmp401
|
||||
then:
|
||||
properties:
|
||||
ti,n-factor: false
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- ti,tmp401
|
||||
- ti,tmp411
|
||||
then:
|
||||
properties:
|
||||
ti,beta-compensation: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
sensor@4c {
|
||||
compatible = "ti,tmp401";
|
||||
reg = <0x4c>;
|
||||
};
|
||||
};
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
sensor@4c {
|
||||
compatible = "ti,tmp431";
|
||||
reg = <0x4c>;
|
||||
ti,extended-range-enable;
|
||||
ti,n-factor = <0x3b>;
|
||||
ti,beta-compensation = <0x7>;
|
||||
};
|
||||
};
|
@ -143,6 +143,10 @@ properties:
|
||||
- infineon,xdpe12254
|
||||
# Infineon Multi-phase Digital VR Controller xdpe12284
|
||||
- infineon,xdpe12284
|
||||
# Infineon Multi-phase Digital VR Controller xdpe15284
|
||||
- infineon,xdpe15284
|
||||
# Infineon Multi-phase Digital VR Controller xdpe152c4
|
||||
- infineon,xdpe152c4
|
||||
# Injoinic IP5108 2.0A Power Bank IC with I2C
|
||||
- injoinic,ip5108
|
||||
# Injoinic IP5109 2.1A Power Bank IC with I2C
|
||||
|
@ -6,7 +6,9 @@ Kernel driver aquacomputer-d5next
|
||||
Supported devices:
|
||||
|
||||
* Aquacomputer D5 Next watercooling pump
|
||||
* Aquacomputer Farbwerk RGB controller
|
||||
* Aquacomputer Farbwerk 360 RGB controller
|
||||
* Aquacomputer Octo fan controller
|
||||
|
||||
Author: Aleksa Savic
|
||||
|
||||
@ -28,7 +30,10 @@ 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,
|
||||
The Octo exposes four temperature sensors and eight PWM controllable fans, along
|
||||
with their speed (in RPM), power, voltage and current.
|
||||
|
||||
The Farbwerk and Farbwerk 360 expose four temperature sensors. Depending on the device,
|
||||
not all sysfs and debugfs entries will be available.
|
||||
|
||||
Usage notes
|
||||
|
@ -4,17 +4,20 @@ Kernel driver asus_ec_sensors
|
||||
=================================
|
||||
|
||||
Supported boards:
|
||||
* PRIME X570-PRO,
|
||||
* Pro WS X570-ACE,
|
||||
* ROG CROSSHAIR VIII DARK HERO,
|
||||
* PRIME X470-PRO
|
||||
* PRIME X570-PRO
|
||||
* Pro WS X570-ACE
|
||||
* ProArt X570-CREATOR WIFI
|
||||
* 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 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-E GAMING WIFI II
|
||||
* ROG STRIX X570-F GAMING
|
||||
* ROG STRIX X570-I GAMING
|
||||
|
||||
Authors:
|
||||
@ -52,3 +55,5 @@ Module Parameters
|
||||
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.
|
||||
A special string ":GLOBAL_LOCK" can be passed to use the ACPI
|
||||
global lock instead of a dedicated mutex.
|
||||
|
@ -86,6 +86,13 @@ probe the BIOS on your machine and discover the appropriate codes.
|
||||
|
||||
Again, when you find new codes, we'd be happy to have your patches!
|
||||
|
||||
``thermal`` interface
|
||||
---------------------------
|
||||
|
||||
The driver also exports the fans as thermal cooling devices with
|
||||
``type`` set to ``dell-smm-fan[1-3]``. This allows for easy fan control
|
||||
using one of the thermal governors.
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
@ -324,6 +331,8 @@ Reading of fan types causes erratic fan behaviour. Studio XPS 8000
|
||||
|
||||
Inspiron 580
|
||||
|
||||
Inspiron 3505
|
||||
|
||||
Fan-related SMM calls take too long (about 500ms). Inspiron 7720
|
||||
|
||||
Vostro 3360
|
||||
|
@ -50,6 +50,10 @@ register/unregister functions::
|
||||
|
||||
void devm_hwmon_device_unregister(struct device *dev);
|
||||
|
||||
char *hwmon_sanitize_name(const char *name);
|
||||
|
||||
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
|
||||
|
||||
hwmon_device_register_with_groups registers a hardware monitoring device.
|
||||
The first parameter of this function is a pointer to the parent device.
|
||||
The name parameter is a pointer to the hwmon device name. The registration
|
||||
@ -72,7 +76,7 @@ hwmon_device_register_with_info is the most comprehensive and preferred means
|
||||
to register a hardware monitoring device. It creates the standard sysfs
|
||||
attributes in the hardware monitoring core, letting the driver focus on reading
|
||||
from and writing to the chip instead of having to bother with sysfs attributes.
|
||||
The parent device parameter cannot be NULL with non-NULL chip info. Its
|
||||
The parent device parameter as well as the chip parameter must not be NULL. Its
|
||||
parameters are described in more detail below.
|
||||
|
||||
devm_hwmon_device_register_with_info is similar to
|
||||
@ -95,6 +99,18 @@ All supported hwmon device registration functions only accept valid device
|
||||
names. Device names including invalid characters (whitespace, '*', or '-')
|
||||
will be rejected. The 'name' parameter is mandatory.
|
||||
|
||||
If the driver doesn't use a static device name (for example it uses
|
||||
dev_name()), and therefore cannot make sure the name only contains valid
|
||||
characters, hwmon_sanitize_name can be used. This convenience function
|
||||
will duplicate the string and replace any invalid characters with an
|
||||
underscore. It will allocate memory for the new string and it is the
|
||||
responsibility of the caller to release the memory when the device is
|
||||
removed.
|
||||
|
||||
devm_hwmon_sanitize_name is the resource managed version of
|
||||
hwmon_sanitize_name; the memory will be freed automatically on device
|
||||
removal.
|
||||
|
||||
Using devm_hwmon_device_register_with_info()
|
||||
--------------------------------------------
|
||||
|
||||
|
@ -90,6 +90,7 @@ Hardware Monitoring Kernel Drivers
|
||||
jc42
|
||||
k10temp
|
||||
k8temp
|
||||
lan966x
|
||||
lineage-pem
|
||||
lm25066
|
||||
lm63
|
||||
@ -223,6 +224,7 @@ Hardware Monitoring Kernel Drivers
|
||||
wm8350
|
||||
xgene-hwmon
|
||||
xdpe12284
|
||||
xdpe152c4
|
||||
zl6100
|
||||
|
||||
.. only:: subproject and html
|
||||
|
40
Documentation/hwmon/lan966x.rst
Normal file
40
Documentation/hwmon/lan966x.rst
Normal file
@ -0,0 +1,40 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver lan966x-hwmon
|
||||
===========================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Microchip LAN9668 (sensor in SoC)
|
||||
|
||||
Prefix: 'lan9668-hwmon'
|
||||
|
||||
Datasheet: https://microchip-ung.github.io/lan9668_reginfo
|
||||
|
||||
Authors:
|
||||
|
||||
Michael Walle <michael@walle.cc>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for the Microchip LAN9668 on-chip
|
||||
temperature sensor as well as its fan controller. It provides one
|
||||
temperature sensor and one fan controller. The temperature range
|
||||
of the sensor is specified from -40 to +125 degrees Celsius and
|
||||
its accuracy is +/- 5 degrees Celsius. The fan controller has a
|
||||
tacho input and a PWM output with a customizable PWM output
|
||||
frequency ranging from ~20Hz to ~650kHz.
|
||||
|
||||
No alarms are supported by the SoC.
|
||||
|
||||
The driver exports temperature values, fan tacho input and PWM
|
||||
settings via the following sysfs files:
|
||||
|
||||
**temp1_input**
|
||||
|
||||
**fan1_input**
|
||||
|
||||
**pwm1**
|
||||
|
||||
**pwm1_freq**
|
@ -21,6 +21,14 @@ Supported chips:
|
||||
|
||||
Datasheet: Not published
|
||||
|
||||
* Maxim MAX16602
|
||||
|
||||
Prefix: 'max16602'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX16602.pdf
|
||||
|
||||
Author: Guenter Roeck <linux@roeck-us.net>
|
||||
|
||||
|
||||
|
118
Documentation/hwmon/xdpe152c4.rst
Normal file
118
Documentation/hwmon/xdpe152c4.rst
Normal file
@ -0,0 +1,118 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver xdpe152
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Infineon XDPE152C4
|
||||
|
||||
Prefix: 'xdpe152c4'
|
||||
|
||||
* Infineon XDPE15284
|
||||
|
||||
Prefix: 'xdpe15284'
|
||||
|
||||
Authors:
|
||||
|
||||
Greg Schwendimann <greg.schwendimann@infineon.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Infineon Digital Multi-phase Controller
|
||||
XDPE152C4 and XDPE15284 dual loop voltage regulators.
|
||||
The devices are compliant with:
|
||||
|
||||
- Intel VR13, VR13HC and VR14 rev 1.86
|
||||
converter specification.
|
||||
- Intel SVID rev 1.93. protocol.
|
||||
- PMBus rev 1.3.1 interface.
|
||||
|
||||
Devices support linear format for reading input and output voltage, input
|
||||
and output current, input and output power and temperature.
|
||||
|
||||
Devices support two pages for telemetry.
|
||||
|
||||
The driver provides for current: input, maximum and critical thresholds
|
||||
and maximum and critical alarms. Low Critical thresholds and Low critical alarm are
|
||||
supported only for current output.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "iin" and 3, 4 for "iout":
|
||||
|
||||
**curr[1-4]_crit**
|
||||
|
||||
**curr[1-4]_crit_alarm**
|
||||
|
||||
**curr[1-4]_input**
|
||||
|
||||
**curr[1-4]_label**
|
||||
|
||||
**curr[1-4]_max**
|
||||
|
||||
**curr[1-4]_max_alarm**
|
||||
|
||||
**curr[3-4]_lcrit**
|
||||
|
||||
**curr[3-4]_lcrit_alarm**
|
||||
|
||||
**curr[3-4]_rated_max**
|
||||
|
||||
The driver provides for voltage: input, critical and low critical thresholds
|
||||
and critical and low critical alarms.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "vin" and 3, 4 for "vout":
|
||||
|
||||
**in[1-4]_min**
|
||||
|
||||
**in[1-4]_crit**
|
||||
|
||||
**in[1-4_crit_alarm**
|
||||
|
||||
**in[1-4]_input**
|
||||
|
||||
**in[1-4]_label**
|
||||
|
||||
**in[1-4]_max**
|
||||
|
||||
**in[1-4]_max_alarm**
|
||||
|
||||
**in[1-4]_min**
|
||||
|
||||
**in[1-4]_min_alarm**
|
||||
|
||||
**in[3-4]_lcrit**
|
||||
|
||||
**in[3-4]_lcrit_alarm**
|
||||
|
||||
**in[3-4]_rated_max**
|
||||
|
||||
**in[3-4]_rated_min**
|
||||
|
||||
The driver provides for power: input and alarms.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "pin" and 3, 4 for "pout":
|
||||
|
||||
**power[1-2]_alarm**
|
||||
|
||||
**power[1-4]_input**
|
||||
|
||||
**power[1-4]_label**
|
||||
|
||||
**power[1-4]_max**
|
||||
|
||||
**power[1-4]_rated_max**
|
||||
|
||||
The driver provides for temperature: input, maximum and critical thresholds
|
||||
and maximum and critical alarms.
|
||||
The driver exports the following attributes for via the sysfs files:
|
||||
|
||||
**temp[1-2]_crit**
|
||||
|
||||
**temp[1-2]_crit_alarm**
|
||||
|
||||
**temp[1-2]_input**
|
||||
|
||||
**temp[1-2]_max**
|
||||
|
||||
**temp[1-2]_max_alarm**
|
15
MAINTAINERS
15
MAINTAINERS
@ -1447,6 +1447,7 @@ F: drivers/media/i2c/aptina-pll.*
|
||||
|
||||
AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER
|
||||
M: Aleksa Savic <savicaleksa83@gmail.com>
|
||||
M: Jack Doan <me@jackdoan.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/aquacomputer_d5next.rst
|
||||
@ -13554,12 +13555,21 @@ M: Samuel Mendoza-Jonas <sam@mendozajonas.com>
|
||||
S: Maintained
|
||||
F: net/ncsi/
|
||||
|
||||
NCT6775 HARDWARE MONITOR DRIVER
|
||||
NCT6775 HARDWARE MONITOR DRIVER - CORE & PLATFORM DRIVER
|
||||
M: Guenter Roeck <linux@roeck-us.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/nct6775.rst
|
||||
F: drivers/hwmon/nct6775.c
|
||||
F: drivers/hwmon/nct6775-core.c
|
||||
F: drivers/hwmon/nct6775-platform.c
|
||||
F: drivers/hwmon/nct6775.h
|
||||
|
||||
NCT6775 HARDWARE MONITOR DRIVER - I2C DRIVER
|
||||
M: Zev Weiss <zev@bewilderbeest.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml
|
||||
F: drivers/hwmon/nct6775-i2c.c
|
||||
|
||||
NETDEVSIM
|
||||
M: Jakub Kicinski <kuba@kernel.org>
|
||||
@ -19870,6 +19880,7 @@ TMP401 HARDWARE MONITOR DRIVER
|
||||
M: Guenter Roeck <linux@roeck-us.net>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml
|
||||
F: Documentation/hwmon/tmp401.rst
|
||||
F: drivers/hwmon/tmp401.c
|
||||
|
||||
|
@ -256,11 +256,14 @@ config SENSORS_AHT10
|
||||
will be called aht10.
|
||||
|
||||
config SENSORS_AQUACOMPUTER_D5NEXT
|
||||
tristate "Aquacomputer D5 Next watercooling pump"
|
||||
tristate "Aquacomputer D5 Next, Octo, Farbwerk, and Farbwerk 360"
|
||||
depends on USB_HID
|
||||
select CRC16
|
||||
help
|
||||
If you say yes here you get support for the Aquacomputer D5 Next
|
||||
watercooling pump sensors.
|
||||
If you say yes here you get support for sensors and fans of
|
||||
the Aquacomputer D5 Next watercooling pump, Octo fan
|
||||
controller, Farbwerk and Farbwerk 360 RGB controllers, where
|
||||
available.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called aquacomputer_d5next.
|
||||
@ -415,6 +418,7 @@ config SENSORS_ATXP1
|
||||
config SENSORS_BT1_PVT
|
||||
tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
|
||||
depends on MIPS_BAIKAL_T1 || COMPILE_TEST
|
||||
select POLYNOMIAL
|
||||
help
|
||||
If you say yes here you get support for Baikal-T1 PVT sensor
|
||||
embedded into the SoC.
|
||||
@ -498,6 +502,7 @@ config SENSORS_DS1621
|
||||
config SENSORS_DELL_SMM
|
||||
tristate "Dell laptop SMM BIOS hwmon driver"
|
||||
depends on X86
|
||||
imply THERMAL
|
||||
help
|
||||
This hwmon driver adds support for reporting temperature of different
|
||||
sensors and controls the fans on Dell laptops via System Management
|
||||
@ -814,6 +819,18 @@ config SENSORS_POWR1220
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called powr1220.
|
||||
|
||||
config SENSORS_LAN966X
|
||||
tristate "Microchip LAN966x Hardware Monitoring"
|
||||
depends on SOC_LAN966 || COMPILE_TEST
|
||||
select REGMAP
|
||||
select POLYNOMIAL
|
||||
help
|
||||
If you say yes here you get support for temperature monitoring
|
||||
on the Microchip LAN966x SoC.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called lan966x-hwmon.
|
||||
|
||||
config SENSORS_LINEAGE
|
||||
tristate "Lineage Compact Power Line Power Entry Module"
|
||||
depends on I2C
|
||||
@ -1248,6 +1265,7 @@ config SENSORS_LM75
|
||||
temperature sensor chip, with models including:
|
||||
|
||||
- Analog Devices ADT75
|
||||
- Atmel (now Microchip) AT30TS74
|
||||
- Dallas Semiconductor DS75, DS1775 and DS7505
|
||||
- Global Mixed-mode Technology (GMT) G751
|
||||
- Maxim MAX6625 and MAX6626
|
||||
@ -1457,11 +1475,23 @@ config SENSORS_NCT6683
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nct6683.
|
||||
|
||||
config SENSORS_NCT6775_CORE
|
||||
tristate
|
||||
select REGMAP
|
||||
help
|
||||
This module contains common code shared by the platform and
|
||||
i2c versions of the nct6775 driver; it is not useful on its
|
||||
own.
|
||||
|
||||
If built as a module, the module will be called
|
||||
nct6775-core.
|
||||
|
||||
config SENSORS_NCT6775
|
||||
tristate "Nuvoton NCT6775F and compatibles"
|
||||
tristate "Platform driver for Nuvoton NCT6775F and compatibles"
|
||||
depends on !PPC
|
||||
depends on ACPI_WMI || ACPI_WMI=n
|
||||
select HWMON_VID
|
||||
select SENSORS_NCT6775_CORE
|
||||
help
|
||||
If you say yes here you get support for the hardware monitoring
|
||||
functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D,
|
||||
@ -1472,6 +1502,23 @@ config SENSORS_NCT6775
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nct6775.
|
||||
|
||||
config SENSORS_NCT6775_I2C
|
||||
tristate "I2C driver for Nuvoton NCT6775F and compatibles"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select SENSORS_NCT6775_CORE
|
||||
help
|
||||
If you say yes here you get support for the hardware monitoring
|
||||
functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D,
|
||||
NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, and compatible
|
||||
Super-I/O chips via their I2C interface.
|
||||
|
||||
If you're not building a kernel for a BMC, this is probably
|
||||
not the driver you want (see CONFIG_SENSORS_NCT6775).
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called nct6775-i2c.
|
||||
|
||||
config SENSORS_NCT7802
|
||||
tristate "Nuvoton NCT7802Y"
|
||||
depends on I2C
|
||||
|
@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o
|
||||
obj-$(CONFIG_SENSORS_JC42) += jc42.o
|
||||
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
|
||||
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
|
||||
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
|
||||
obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_LM63) += lm63.o
|
||||
@ -154,7 +155,10 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
|
||||
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
|
||||
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
|
||||
obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
|
||||
nct6775-objs := nct6775-platform.o
|
||||
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
|
||||
obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o
|
||||
obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o
|
||||
obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o
|
||||
obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o
|
||||
|
@ -481,7 +481,7 @@ static struct sensor_template meter_attrs[] = {
|
||||
RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1),
|
||||
RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5),
|
||||
RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval,
|
||||
set_avg_interval, 0),
|
||||
set_avg_interval, 0),
|
||||
{},
|
||||
};
|
||||
|
||||
@ -530,6 +530,7 @@ static void remove_domain_devices(struct acpi_power_meter_resource *resource)
|
||||
|
||||
for (i = 0; i < resource->num_domain_devices; i++) {
|
||||
struct acpi_device *obj = resource->domain_devices[i];
|
||||
|
||||
if (!obj)
|
||||
continue;
|
||||
|
||||
@ -580,7 +581,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource)
|
||||
}
|
||||
|
||||
resource->holders_dir = kobject_create_and_add("measures",
|
||||
&resource->acpi_dev->dev.kobj);
|
||||
&resource->acpi_dev->dev.kobj);
|
||||
if (!resource->holders_dir) {
|
||||
res = -ENOMEM;
|
||||
goto exit_free;
|
||||
@ -590,7 +591,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource)
|
||||
|
||||
for (i = 0; i < pss->package.count; i++) {
|
||||
struct acpi_device *obj;
|
||||
union acpi_object *element = &(pss->package.elements[i]);
|
||||
union acpi_object *element = &pss->package.elements[i];
|
||||
|
||||
/* Refuse non-references */
|
||||
if (element->type != ACPI_TYPE_LOCAL_REFERENCE)
|
||||
@ -603,7 +604,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource)
|
||||
continue;
|
||||
|
||||
res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj,
|
||||
kobject_name(&obj->dev.kobj));
|
||||
kobject_name(&obj->dev.kobj));
|
||||
if (res) {
|
||||
acpi_dev_put(obj);
|
||||
resource->domain_devices[i] = NULL;
|
||||
@ -788,7 +789,7 @@ static int read_capabilities(struct acpi_power_meter_resource *resource)
|
||||
str = &resource->model_number;
|
||||
|
||||
for (i = 11; i < 14; i++) {
|
||||
union acpi_object *element = &(pss->package.elements[i]);
|
||||
union acpi_object *element = &pss->package.elements[i];
|
||||
|
||||
if (element->type != ACPI_TYPE_STRING) {
|
||||
res = -EINVAL;
|
||||
@ -868,8 +869,7 @@ static int acpi_power_meter_add(struct acpi_device *device)
|
||||
if (!device)
|
||||
return -EINVAL;
|
||||
|
||||
resource = kzalloc(sizeof(struct acpi_power_meter_resource),
|
||||
GFP_KERNEL);
|
||||
resource = kzalloc(sizeof(*resource), GFP_KERNEL);
|
||||
if (!resource)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -884,7 +884,8 @@ static int acpi_power_meter_add(struct acpi_device *device)
|
||||
if (res)
|
||||
goto exit_free;
|
||||
|
||||
resource->trip[0] = resource->trip[1] = -1;
|
||||
resource->trip[0] = -1;
|
||||
resource->trip[1] = -1;
|
||||
|
||||
res = setup_attrs(resource);
|
||||
if (res)
|
||||
|
@ -112,6 +112,8 @@
|
||||
#define CONFIG3_THERM 0x02
|
||||
|
||||
#define CONFIG4_PINFUNC 0x03
|
||||
#define CONFIG4_THERM 0x01
|
||||
#define CONFIG4_SMBALERT 0x02
|
||||
#define CONFIG4_MAXDUTY 0x08
|
||||
#define CONFIG4_ATTN_IN10 0x30
|
||||
#define CONFIG4_ATTN_IN43 0xC0
|
||||
@ -1460,6 +1462,96 @@ static int adt7475_update_limits(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_config3(const struct i2c_client *client, const char *propname)
|
||||
{
|
||||
const char *function;
|
||||
u8 config3;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string(client->dev.of_node, propname, &function);
|
||||
if (!ret) {
|
||||
ret = adt7475_read(REG_CONFIG3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
config3 = ret & ~CONFIG3_SMBALERT;
|
||||
if (!strcmp("pwm2", function))
|
||||
;
|
||||
else if (!strcmp("smbalert#", function))
|
||||
config3 |= CONFIG3_SMBALERT;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, REG_CONFIG3, config3);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_config4(const struct i2c_client *client, const char *propname)
|
||||
{
|
||||
const char *function;
|
||||
u8 config4;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string(client->dev.of_node, propname, &function);
|
||||
if (!ret) {
|
||||
ret = adt7475_read(REG_CONFIG4);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
config4 = ret & ~CONFIG4_PINFUNC;
|
||||
|
||||
if (!strcmp("tach4", function))
|
||||
;
|
||||
else if (!strcmp("therm#", function))
|
||||
config4 |= CONFIG4_THERM;
|
||||
else if (!strcmp("smbalert#", function))
|
||||
config4 |= CONFIG4_SMBALERT;
|
||||
else if (!strcmp("gpio", function))
|
||||
config4 |= CONFIG4_PINFUNC;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, REG_CONFIG4, config4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_config(const struct i2c_client *client, enum chips chip)
|
||||
{
|
||||
int err;
|
||||
const char *prop1, *prop2;
|
||||
|
||||
switch (chip) {
|
||||
case adt7473:
|
||||
case adt7475:
|
||||
prop1 = "adi,pin5-function";
|
||||
prop2 = "adi,pin9-function";
|
||||
break;
|
||||
case adt7476:
|
||||
case adt7490:
|
||||
prop1 = "adi,pin10-function";
|
||||
prop2 = "adi,pin14-function";
|
||||
break;
|
||||
}
|
||||
|
||||
err = load_config3(client, prop1);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "failed to configure %s\n", prop1);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = load_config4(client, prop2);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "failed to configure %s\n", prop2);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_property_bit(const struct i2c_client *client, char *property,
|
||||
u8 *config, u8 bit_index)
|
||||
{
|
||||
@ -1477,12 +1569,12 @@ static int set_property_bit(const struct i2c_client *client, char *property,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int load_attenuators(const struct i2c_client *client, int chip,
|
||||
static int load_attenuators(const struct i2c_client *client, enum chips chip,
|
||||
struct adt7475_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (chip == adt7476 || chip == adt7490) {
|
||||
switch (chip) {
|
||||
case adt7476:
|
||||
case adt7490:
|
||||
set_property_bit(client, "adi,bypass-attenuator-in0",
|
||||
&data->config4, 4);
|
||||
set_property_bit(client, "adi,bypass-attenuator-in1",
|
||||
@ -1492,18 +1584,15 @@ static int load_attenuators(const struct i2c_client *client, int chip,
|
||||
set_property_bit(client, "adi,bypass-attenuator-in4",
|
||||
&data->config4, 7);
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, REG_CONFIG4,
|
||||
data->config4);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else if (chip == adt7473 || chip == adt7475) {
|
||||
return i2c_smbus_write_byte_data(client, REG_CONFIG4,
|
||||
data->config4);
|
||||
case adt7473:
|
||||
case adt7475:
|
||||
set_property_bit(client, "adi,bypass-attenuator-in1",
|
||||
&data->config2, 5);
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, REG_CONFIG2,
|
||||
data->config2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return i2c_smbus_write_byte_data(client, REG_CONFIG2,
|
||||
data->config2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1585,6 +1674,10 @@ static int adt7475_probe(struct i2c_client *client)
|
||||
revision = adt7475_read(REG_DEVID2) & 0x07;
|
||||
}
|
||||
|
||||
ret = load_config(client, chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
config3 = adt7475_read(REG_CONFIG3);
|
||||
/* Pin PWM2 may alternatively be used for ALERT output */
|
||||
if (!(config3 & CONFIG3_SMBALERT))
|
||||
|
@ -1,30 +1,37 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360)
|
||||
* hwmon driver for Aquacomputer devices (D5 Next, Farbwerk, Farbwerk 360, Octo)
|
||||
*
|
||||
* Aquacomputer devices send HID reports (with ID 0x01) every second to report
|
||||
* sensor values.
|
||||
*
|
||||
* Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
|
||||
* Copyright 2022 Jack Doan <me@jackdoan.com>
|
||||
*/
|
||||
|
||||
#include <linux/crc16.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#define USB_VENDOR_ID_AQUACOMPUTER 0x0c70
|
||||
#define USB_PRODUCT_ID_FARBWERK 0xf00a
|
||||
#define USB_PRODUCT_ID_D5NEXT 0xf00e
|
||||
#define USB_PRODUCT_ID_FARBWERK360 0xf010
|
||||
#define USB_PRODUCT_ID_OCTO 0xf011
|
||||
|
||||
enum kinds { d5next, farbwerk360 };
|
||||
enum kinds { d5next, farbwerk, farbwerk360, octo };
|
||||
|
||||
static const char *const aqc_device_names[] = {
|
||||
[d5next] = "d5next",
|
||||
[farbwerk360] = "farbwerk360"
|
||||
[farbwerk] = "farbwerk",
|
||||
[farbwerk360] = "farbwerk360",
|
||||
[octo] = "octo"
|
||||
};
|
||||
|
||||
#define DRIVER_NAME "aquacomputer_d5next"
|
||||
@ -35,6 +42,18 @@ static const char *const aqc_device_names[] = {
|
||||
#define SERIAL_SECOND_PART 5
|
||||
#define FIRMWARE_VERSION 13
|
||||
|
||||
#define CTRL_REPORT_ID 0x03
|
||||
|
||||
/* The HID report that the official software always sends
|
||||
* after writing values, currently same for all devices
|
||||
*/
|
||||
#define SECONDARY_CTRL_REPORT_ID 0x02
|
||||
#define SECONDARY_CTRL_REPORT_SIZE 0x0B
|
||||
|
||||
static u8 secondary_ctrl_report[] = {
|
||||
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x34, 0xC6
|
||||
};
|
||||
|
||||
/* Register offsets for the D5 Next pump */
|
||||
#define D5NEXT_POWER_CYCLES 24
|
||||
|
||||
@ -53,14 +72,46 @@ static const char *const aqc_device_names[] = {
|
||||
#define D5NEXT_PUMP_CURRENT 112
|
||||
#define D5NEXT_FAN_CURRENT 99
|
||||
|
||||
/* Register offsets for the Farbwerk RGB controller */
|
||||
#define FARBWERK_NUM_SENSORS 4
|
||||
#define FARBWERK_SENSOR_START 0x2f
|
||||
#define FARBWERK_SENSOR_SIZE 0x02
|
||||
#define FARBWERK_SENSOR_DISCONNECTED 0x7FFF
|
||||
|
||||
/* Register offsets for the Farbwerk 360 RGB controller */
|
||||
#define FARBWERK360_NUM_SENSORS 4
|
||||
#define FARBWERK360_SENSOR_START 0x32
|
||||
#define FARBWERK360_SENSOR_START 0x32
|
||||
#define FARBWERK360_SENSOR_SIZE 0x02
|
||||
#define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF
|
||||
|
||||
/* Register offsets for the Octo fan controller */
|
||||
#define OCTO_POWER_CYCLES 0x18
|
||||
#define OCTO_NUM_FANS 8
|
||||
#define OCTO_FAN_PERCENT_OFFSET 0x00
|
||||
#define OCTO_FAN_VOLTAGE_OFFSET 0x02
|
||||
#define OCTO_FAN_CURRENT_OFFSET 0x04
|
||||
#define OCTO_FAN_POWER_OFFSET 0x06
|
||||
#define OCTO_FAN_SPEED_OFFSET 0x08
|
||||
|
||||
static u8 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 };
|
||||
|
||||
#define OCTO_NUM_SENSORS 4
|
||||
#define OCTO_SENSOR_START 0x3D
|
||||
#define OCTO_SENSOR_SIZE 0x02
|
||||
#define OCTO_SENSOR_DISCONNECTED 0x7FFF
|
||||
|
||||
#define OCTO_CTRL_REPORT_SIZE 0x65F
|
||||
#define OCTO_CTRL_REPORT_CHECKSUM_OFFSET 0x65D
|
||||
#define OCTO_CTRL_REPORT_CHECKSUM_START 0x01
|
||||
#define OCTO_CTRL_REPORT_CHECKSUM_LENGTH 0x65C
|
||||
|
||||
/* Fan speed registers in Octo control report (from 0-100%) */
|
||||
static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0x259, 0x2AE };
|
||||
|
||||
/* Labels for D5 Next */
|
||||
#define L_D5NEXT_COOLANT_TEMP "Coolant temp"
|
||||
static const char *const label_d5next_temp[] = {
|
||||
"Coolant temp"
|
||||
};
|
||||
|
||||
static const char *const label_d5next_speeds[] = {
|
||||
"Pump speed",
|
||||
@ -83,7 +134,7 @@ static const char *const label_d5next_current[] = {
|
||||
"Fan current"
|
||||
};
|
||||
|
||||
/* Labels for Farbwerk 360 temperature sensors */
|
||||
/* Labels for Farbwerk, Farbwerk 360 and Octo temperature sensors */
|
||||
static const char *const label_temp_sensors[] = {
|
||||
"Sensor 1",
|
||||
"Sensor 2",
|
||||
@ -91,32 +142,182 @@ static const char *const label_temp_sensors[] = {
|
||||
"Sensor 4"
|
||||
};
|
||||
|
||||
/* Labels for Octo */
|
||||
static const char *const label_fan_speed[] = {
|
||||
"Fan 1 speed",
|
||||
"Fan 2 speed",
|
||||
"Fan 3 speed",
|
||||
"Fan 4 speed",
|
||||
"Fan 5 speed",
|
||||
"Fan 6 speed",
|
||||
"Fan 7 speed",
|
||||
"Fan 8 speed"
|
||||
};
|
||||
|
||||
static const char *const label_fan_power[] = {
|
||||
"Fan 1 power",
|
||||
"Fan 2 power",
|
||||
"Fan 3 power",
|
||||
"Fan 4 power",
|
||||
"Fan 5 power",
|
||||
"Fan 6 power",
|
||||
"Fan 7 power",
|
||||
"Fan 8 power"
|
||||
};
|
||||
|
||||
static const char *const label_fan_voltage[] = {
|
||||
"Fan 1 voltage",
|
||||
"Fan 2 voltage",
|
||||
"Fan 3 voltage",
|
||||
"Fan 4 voltage",
|
||||
"Fan 5 voltage",
|
||||
"Fan 6 voltage",
|
||||
"Fan 7 voltage",
|
||||
"Fan 8 voltage"
|
||||
};
|
||||
|
||||
static const char *const label_fan_current[] = {
|
||||
"Fan 1 current",
|
||||
"Fan 2 current",
|
||||
"Fan 3 current",
|
||||
"Fan 4 current",
|
||||
"Fan 5 current",
|
||||
"Fan 6 current",
|
||||
"Fan 7 current",
|
||||
"Fan 8 current"
|
||||
};
|
||||
|
||||
struct aqc_data {
|
||||
struct hid_device *hdev;
|
||||
struct device *hwmon_dev;
|
||||
struct dentry *debugfs;
|
||||
struct mutex mutex; /* Used for locking access when reading and writing PWM values */
|
||||
enum kinds kind;
|
||||
const char *name;
|
||||
|
||||
int buffer_size;
|
||||
u8 *buffer;
|
||||
int checksum_start;
|
||||
int checksum_length;
|
||||
int checksum_offset;
|
||||
|
||||
/* General info, same across all devices */
|
||||
u32 serial_number[2];
|
||||
u16 firmware_version;
|
||||
|
||||
/* D5 Next specific - how many times the device was powered on */
|
||||
/* 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];
|
||||
u16 speed_input[8];
|
||||
u32 power_input[8];
|
||||
u16 voltage_input[8];
|
||||
u16 current_input[8];
|
||||
|
||||
/* Label values */
|
||||
const char *const *temp_label;
|
||||
const char *const *speed_label;
|
||||
const char *const *power_label;
|
||||
const char *const *voltage_label;
|
||||
const char *const *current_label;
|
||||
|
||||
unsigned long updated;
|
||||
};
|
||||
|
||||
static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
/* Converts from centi-percent */
|
||||
static int aqc_percent_to_pwm(u16 val)
|
||||
{
|
||||
return DIV_ROUND_CLOSEST(val * 255, 100 * 100);
|
||||
}
|
||||
|
||||
/* Converts to centi-percent */
|
||||
static int aqc_pwm_to_percent(long val)
|
||||
{
|
||||
if (val < 0 || val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
return DIV_ROUND_CLOSEST(val * 100 * 100, 255);
|
||||
}
|
||||
|
||||
/* Expects the mutex to be locked */
|
||||
static int aqc_get_ctrl_data(struct aqc_data *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
memset(priv->buffer, 0x00, priv->buffer_size);
|
||||
ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size,
|
||||
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
||||
if (ret < 0)
|
||||
ret = -ENODATA;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Expects the mutex to be locked */
|
||||
static int aqc_send_ctrl_data(struct aqc_data *priv)
|
||||
{
|
||||
int ret;
|
||||
u16 checksum;
|
||||
|
||||
/* Init and xorout value for CRC-16/USB is 0xffff */
|
||||
checksum = crc16(0xffff, priv->buffer + priv->checksum_start, priv->checksum_length);
|
||||
checksum ^= 0xffff;
|
||||
|
||||
/* Place the new checksum at the end of the report */
|
||||
put_unaligned_be16(checksum, priv->buffer + priv->checksum_offset);
|
||||
|
||||
/* Send the patched up report back to the device */
|
||||
ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size,
|
||||
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* The official software sends this report after every change, so do it here as well */
|
||||
ret = hid_hw_raw_request(priv->hdev, SECONDARY_CTRL_REPORT_ID, secondary_ctrl_report,
|
||||
SECONDARY_CTRL_REPORT_SIZE, HID_FEATURE_REPORT,
|
||||
HID_REQ_SET_REPORT);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Refreshes the control buffer and returns value at offset */
|
||||
static int aqc_get_ctrl_val(struct aqc_data *priv, int offset)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
|
||||
ret = aqc_get_ctrl_data(priv);
|
||||
if (ret < 0)
|
||||
goto unlock_and_return;
|
||||
|
||||
ret = get_unaligned_be16(priv->buffer + offset);
|
||||
|
||||
unlock_and_return:
|
||||
mutex_unlock(&priv->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aqc_set_ctrl_val(struct aqc_data *priv, int offset, long val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->mutex);
|
||||
|
||||
ret = aqc_get_ctrl_data(priv);
|
||||
if (ret < 0)
|
||||
goto unlock_and_return;
|
||||
|
||||
put_unaligned_be16((u16)val, priv->buffer + offset);
|
||||
|
||||
ret = aqc_send_ctrl_data(priv);
|
||||
|
||||
unlock_and_return:
|
||||
mutex_unlock(&priv->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel)
|
||||
{
|
||||
const struct aqc_data *priv = data;
|
||||
|
||||
@ -127,18 +328,49 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
if (channel == 0)
|
||||
return 0444;
|
||||
break;
|
||||
case farbwerk:
|
||||
case farbwerk360:
|
||||
case octo:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (priv->kind) {
|
||||
case octo:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return 0644;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_fan:
|
||||
case hwmon_power:
|
||||
case hwmon_in:
|
||||
case hwmon_curr:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
if (channel < 2)
|
||||
return 0444;
|
||||
break;
|
||||
case octo:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
if (channel < 3)
|
||||
return 0444;
|
||||
break;
|
||||
case octo:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
@ -154,6 +386,7 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
struct aqc_data *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL))
|
||||
@ -172,6 +405,19 @@ static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
case hwmon_power:
|
||||
*val = priv->power_input[channel];
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (priv->kind) {
|
||||
case octo:
|
||||
ret = aqc_get_ctrl_val(priv, octo_ctrl_fan_offsets[channel]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = aqc_percent_to_pwm(ret);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
*val = priv->voltage_input[channel];
|
||||
break;
|
||||
@ -192,48 +438,51 @@ static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
*str = L_D5NEXT_COOLANT_TEMP;
|
||||
break;
|
||||
case farbwerk360:
|
||||
*str = label_temp_sensors[channel];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*str = priv->temp_label[channel];
|
||||
break;
|
||||
case hwmon_fan:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
*str = label_d5next_speeds[channel];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*str = priv->speed_label[channel];
|
||||
break;
|
||||
case hwmon_power:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
*str = label_d5next_power[channel];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*str = priv->power_label[channel];
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
*str = label_d5next_voltages[channel];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*str = priv->voltage_label[channel];
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
*str = label_d5next_current[channel];
|
||||
*str = priv->current_label[channel];
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aqc_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
|
||||
long val)
|
||||
{
|
||||
int ret, pwm_value;
|
||||
struct aqc_data *priv = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
switch (priv->kind) {
|
||||
case octo:
|
||||
pwm_value = aqc_pwm_to_percent(val);
|
||||
if (pwm_value < 0)
|
||||
return pwm_value;
|
||||
|
||||
ret = aqc_set_ctrl_val(priv, octo_ctrl_fan_offsets[channel],
|
||||
pwm_value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -250,6 +499,7 @@ static const struct hwmon_ops aqc_hwmon_ops = {
|
||||
.is_visible = aqc_is_visible,
|
||||
.read = aqc_read,
|
||||
.read_string = aqc_read_string,
|
||||
.write = aqc_write
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *aqc_info[] = {
|
||||
@ -259,16 +509,48 @@ static const struct hwmon_channel_info *aqc_info[] = {
|
||||
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_F_INPUT | HWMON_F_LABEL,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL,
|
||||
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_P_INPUT | HWMON_P_LABEL,
|
||||
HWMON_P_INPUT | HWMON_P_LABEL,
|
||||
HWMON_P_INPUT | HWMON_P_LABEL,
|
||||
HWMON_P_INPUT | HWMON_P_LABEL,
|
||||
HWMON_P_INPUT | HWMON_P_LABEL,
|
||||
HWMON_P_INPUT | HWMON_P_LABEL),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
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_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL),
|
||||
NULL
|
||||
@ -279,8 +561,7 @@ static const struct hwmon_chip_info aqc_chip_info = {
|
||||
.info = aqc_info,
|
||||
};
|
||||
|
||||
static int aqc_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)
|
||||
{
|
||||
int i, sensor_value;
|
||||
struct aqc_data *priv;
|
||||
@ -315,6 +596,17 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
|
||||
priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
|
||||
priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
|
||||
break;
|
||||
case farbwerk:
|
||||
/* Temperature sensor readings */
|
||||
for (i = 0; i < FARBWERK_NUM_SENSORS; i++) {
|
||||
sensor_value = get_unaligned_be16(data + FARBWERK_SENSOR_START +
|
||||
i * FARBWERK_SENSOR_SIZE);
|
||||
if (sensor_value == FARBWERK_SENSOR_DISCONNECTED)
|
||||
priv->temp_input[i] = -ENODATA;
|
||||
else
|
||||
priv->temp_input[i] = sensor_value * 10;
|
||||
}
|
||||
break;
|
||||
case farbwerk360:
|
||||
/* Temperature sensor readings */
|
||||
for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) {
|
||||
@ -326,6 +618,35 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
|
||||
priv->temp_input[i] = sensor_value * 10;
|
||||
}
|
||||
break;
|
||||
case octo:
|
||||
priv->power_cycles = get_unaligned_be32(data + OCTO_POWER_CYCLES);
|
||||
|
||||
/* Fan speed and related readings */
|
||||
for (i = 0; i < OCTO_NUM_FANS; i++) {
|
||||
priv->speed_input[i] =
|
||||
get_unaligned_be16(data + octo_sensor_fan_offsets[i] +
|
||||
OCTO_FAN_SPEED_OFFSET);
|
||||
priv->power_input[i] =
|
||||
get_unaligned_be16(data + octo_sensor_fan_offsets[i] +
|
||||
OCTO_FAN_POWER_OFFSET) * 10000;
|
||||
priv->voltage_input[i] =
|
||||
get_unaligned_be16(data + octo_sensor_fan_offsets[i] +
|
||||
OCTO_FAN_VOLTAGE_OFFSET) * 10;
|
||||
priv->current_input[i] =
|
||||
get_unaligned_be16(data + octo_sensor_fan_offsets[i] +
|
||||
OCTO_FAN_CURRENT_OFFSET);
|
||||
}
|
||||
|
||||
/* Temperature sensor readings */
|
||||
for (i = 0; i < OCTO_NUM_SENSORS; i++) {
|
||||
sensor_value = get_unaligned_be16(data + OCTO_SENSOR_START +
|
||||
i * OCTO_SENSOR_SIZE);
|
||||
if (sensor_value == OCTO_SENSOR_DISCONNECTED)
|
||||
priv->temp_input[i] = -ENODATA;
|
||||
else
|
||||
priv->temp_input[i] = sensor_value * 10;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -378,8 +699,14 @@ static void aqc_debugfs_init(struct aqc_data *priv)
|
||||
debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
|
||||
debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
|
||||
|
||||
if (priv->kind == d5next)
|
||||
switch (priv->kind) {
|
||||
case d5next:
|
||||
case octo:
|
||||
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
@ -419,9 +746,35 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
switch (hdev->product) {
|
||||
case USB_PRODUCT_ID_D5NEXT:
|
||||
priv->kind = d5next;
|
||||
|
||||
priv->temp_label = label_d5next_temp;
|
||||
priv->speed_label = label_d5next_speeds;
|
||||
priv->power_label = label_d5next_power;
|
||||
priv->voltage_label = label_d5next_voltages;
|
||||
priv->current_label = label_d5next_current;
|
||||
break;
|
||||
case USB_PRODUCT_ID_FARBWERK:
|
||||
priv->kind = farbwerk;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
break;
|
||||
case USB_PRODUCT_ID_FARBWERK360:
|
||||
priv->kind = farbwerk360;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
break;
|
||||
case USB_PRODUCT_ID_OCTO:
|
||||
priv->kind = octo;
|
||||
priv->buffer_size = OCTO_CTRL_REPORT_SIZE;
|
||||
priv->checksum_start = OCTO_CTRL_REPORT_CHECKSUM_START;
|
||||
priv->checksum_length = OCTO_CTRL_REPORT_CHECKSUM_LENGTH;
|
||||
priv->checksum_offset = OCTO_CTRL_REPORT_CHECKSUM_OFFSET;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
priv->speed_label = label_fan_speed;
|
||||
priv->power_label = label_fan_power;
|
||||
priv->voltage_label = label_fan_voltage;
|
||||
priv->current_label = label_fan_current;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -429,6 +782,14 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
|
||||
priv->name = aqc_device_names[priv->kind];
|
||||
|
||||
priv->buffer = devm_kzalloc(&hdev->dev, priv->buffer_size, GFP_KERNEL);
|
||||
if (!priv->buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_and_close;
|
||||
}
|
||||
|
||||
mutex_init(&priv->mutex);
|
||||
|
||||
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv,
|
||||
&aqc_chip_info, NULL);
|
||||
|
||||
@ -461,7 +822,9 @@ static void aqc_remove(struct hid_device *hdev)
|
||||
|
||||
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_FARBWERK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) },
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -491,4 +854,5 @@ module_exit(aqc_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
|
||||
MODULE_AUTHOR("Jack Doan <me@jackdoan.com>");
|
||||
MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices");
|
||||
|
@ -76,18 +76,8 @@ as370_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
}
|
||||
}
|
||||
|
||||
static const u32 as370_hwmon_temp_config[] = {
|
||||
HWMON_T_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info as370_hwmon_temp = {
|
||||
.type = hwmon_temp,
|
||||
.config = as370_hwmon_temp_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *as370_hwmon_info[] = {
|
||||
&as370_hwmon_temp,
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -54,8 +54,10 @@ static char *mutex_path_override;
|
||||
/* 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."
|
||||
#define MAX_IDENTICAL_BOARD_VARIATIONS 3
|
||||
|
||||
/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */
|
||||
#define ACPI_GLOBAL_LOCK_PSEUDO_PATH ":GLOBAL_LOCK"
|
||||
|
||||
typedef union {
|
||||
u32 value;
|
||||
@ -133,8 +135,44 @@ enum ec_sensors {
|
||||
#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in)
|
||||
#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out)
|
||||
|
||||
enum board_family {
|
||||
family_unknown,
|
||||
family_amd_400_series,
|
||||
family_amd_500_series,
|
||||
};
|
||||
|
||||
/* All the known sensors for ASUS EC controllers */
|
||||
static const struct ec_sensor_info known_ec_sensors[] = {
|
||||
static const struct ec_sensor_info sensors_family_amd_400[] = {
|
||||
[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, 0xbc),
|
||||
[ec_sensor_fan_vrm_hs] =
|
||||
EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
|
||||
[ec_sensor_fan_chipset] =
|
||||
/* no chipset fans in this generation */
|
||||
EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00),
|
||||
[ec_sensor_fan_water_flow] =
|
||||
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4),
|
||||
[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, 0x0d),
|
||||
[ec_sensor_temp_water_out] =
|
||||
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_amd_500[] = {
|
||||
[ec_sensor_temp_chipset] =
|
||||
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
|
||||
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
|
||||
@ -164,68 +202,134 @@ static const struct ec_sensor_info known_ec_sensors[] = {
|
||||
(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), \
|
||||
}
|
||||
struct ec_board_info {
|
||||
const char *board_names[MAX_IDENTICAL_BOARD_VARIATIONS];
|
||||
unsigned long sensors;
|
||||
/*
|
||||
* Defines which mutex to use for guarding access to the state and the
|
||||
* hardware. Can be either a full path to an AML mutex or the
|
||||
* pseudo-path ACPI_GLOBAL_LOCK_PSEUDO_PATH to use the global ACPI lock,
|
||||
* or left empty to use a regular mutex object, in which case access to
|
||||
* the hardware is not guarded.
|
||||
*/
|
||||
const char *mutex_path;
|
||||
enum board_family family;
|
||||
};
|
||||
|
||||
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),
|
||||
static const struct ec_board_info board_info[] = {
|
||||
{
|
||||
.board_names = {"PRIME X470-PRO"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CPU_OPT |
|
||||
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_amd_400_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"PRIME X570-PRO"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ProArt X570-CREATOR WIFI"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT |
|
||||
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
|
||||
},
|
||||
{
|
||||
.board_names = {"Pro WS X570-ACE"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET |
|
||||
SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG CROSSHAIR VIII DARK HERO"},
|
||||
.sensors = 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,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {
|
||||
"ROG CROSSHAIR VIII FORMULA"
|
||||
"ROG CROSSHAIR VIII HERO",
|
||||
"ROG CROSSHAIR VIII HERO (WI-FI)",
|
||||
},
|
||||
.sensors = 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,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG CROSSHAIR VIII IMPACT"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX B550-E GAMING"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX B550-I GAMING"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX X570-E GAMING"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX X570-E GAMING WIFI II"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX X570-F GAMING"},
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{
|
||||
.board_names = {"ROG STRIX X570-I GAMING"},
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS |
|
||||
SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU |
|
||||
SENSOR_IN_CPU_CORE,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX,
|
||||
.family = family_amd_500_series,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
@ -234,8 +338,49 @@ struct ec_sensor {
|
||||
s32 cached_value;
|
||||
};
|
||||
|
||||
struct lock_data {
|
||||
union {
|
||||
acpi_handle aml;
|
||||
/* global lock handle */
|
||||
u32 glk;
|
||||
} mutex;
|
||||
bool (*lock)(struct lock_data *data);
|
||||
bool (*unlock)(struct lock_data *data);
|
||||
};
|
||||
|
||||
/*
|
||||
* The next function pairs implement options for locking access to the
|
||||
* state and the EC
|
||||
*/
|
||||
static bool lock_via_acpi_mutex(struct lock_data *data)
|
||||
{
|
||||
/*
|
||||
* ASUS DSDT does not specify that access to the EC has to be guarded,
|
||||
* but firmware does access it via ACPI
|
||||
*/
|
||||
return ACPI_SUCCESS(acpi_acquire_mutex(data->mutex.aml,
|
||||
NULL, ACPI_LOCK_DELAY_MS));
|
||||
}
|
||||
|
||||
static bool unlock_acpi_mutex(struct lock_data *data)
|
||||
{
|
||||
return ACPI_SUCCESS(acpi_release_mutex(data->mutex.aml, NULL));
|
||||
}
|
||||
|
||||
static bool lock_via_global_acpi_lock(struct lock_data *data)
|
||||
{
|
||||
return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
|
||||
&data->mutex.glk));
|
||||
}
|
||||
|
||||
static bool unlock_global_acpi_lock(struct lock_data *data)
|
||||
{
|
||||
return ACPI_SUCCESS(acpi_release_global_lock(data->mutex.glk));
|
||||
}
|
||||
|
||||
struct ec_sensors_data {
|
||||
unsigned long board_sensors;
|
||||
const struct ec_board_info *board_info;
|
||||
const struct ec_sensor_info *sensors_info;
|
||||
struct ec_sensor *sensors;
|
||||
/* EC registers to read from */
|
||||
u16 *registers;
|
||||
@ -244,7 +389,7 @@ struct ec_sensors_data {
|
||||
u8 banks[ASUS_EC_MAX_BANK + 1];
|
||||
/* in jiffies */
|
||||
unsigned long last_updated;
|
||||
acpi_handle aml_mutex;
|
||||
struct lock_data lock_data;
|
||||
/* number of board EC sensors */
|
||||
u8 nr_sensors;
|
||||
/*
|
||||
@ -278,7 +423,7 @@ static bool is_sensor_data_signed(const struct ec_sensor_info *si)
|
||||
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];
|
||||
return state->sensors_info + state->sensors[index].info_index;
|
||||
}
|
||||
|
||||
static int find_ec_sensor_index(const struct ec_sensors_data *ec,
|
||||
@ -301,11 +446,6 @@ 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;
|
||||
@ -316,14 +456,14 @@ static void __init setup_sensor_data(struct ec_sensors_data *ec)
|
||||
ec->nr_banks = 0;
|
||||
ec->nr_registers = 0;
|
||||
|
||||
for_each_set_bit(i, &ec->board_sensors,
|
||||
BITS_PER_TYPE(ec->board_sensors)) {
|
||||
for_each_set_bit(i, &ec->board_info->sensors,
|
||||
BITS_PER_TYPE(ec->board_info->sensors)) {
|
||||
s->info_index = i;
|
||||
s->cached_value = 0;
|
||||
ec->nr_registers +=
|
||||
known_ec_sensors[s->info_index].addr.components.size;
|
||||
ec->sensors_info[s->info_index].addr.components.size;
|
||||
bank_found = false;
|
||||
bank = known_ec_sensors[s->info_index].addr.components.bank;
|
||||
bank = ec->sensors_info[s->info_index].addr.components.bank;
|
||||
for (j = 0; j < ec->nr_banks; j++) {
|
||||
if (ec->banks[j] == bank) {
|
||||
bank_found = true;
|
||||
@ -353,23 +493,36 @@ static void __init fill_ec_registers(struct ec_sensors_data *ec)
|
||||
}
|
||||
}
|
||||
|
||||
static acpi_handle __init asus_hw_access_mutex(struct device *dev)
|
||||
static int __init setup_lock_data(struct device *dev)
|
||||
{
|
||||
const char *mutex_path;
|
||||
acpi_handle res;
|
||||
int status;
|
||||
struct ec_sensors_data *state = dev_get_drvdata(dev);
|
||||
|
||||
mutex_path = mutex_path_override ?
|
||||
mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX;
|
||||
mutex_path_override : state->board_info->mutex_path;
|
||||
|
||||
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;
|
||||
if (!mutex_path || !strlen(mutex_path)) {
|
||||
dev_err(dev, "Hardware access guard mutex name is empty");
|
||||
return -EINVAL;
|
||||
}
|
||||
return res;
|
||||
if (!strcmp(mutex_path, ACPI_GLOBAL_LOCK_PSEUDO_PATH)) {
|
||||
state->lock_data.mutex.glk = 0;
|
||||
state->lock_data.lock = lock_via_global_acpi_lock;
|
||||
state->lock_data.unlock = unlock_global_acpi_lock;
|
||||
} else {
|
||||
status = acpi_get_handle(NULL, (acpi_string)mutex_path,
|
||||
&state->lock_data.mutex.aml);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev,
|
||||
"Failed to get hardware access guard AML mutex '%s': error %d",
|
||||
mutex_path, status);
|
||||
return -ENOENT;
|
||||
}
|
||||
state->lock_data.lock = lock_via_acpi_mutex;
|
||||
state->lock_data.unlock = unlock_acpi_mutex;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_bank_switch(u8 bank, u8 *old)
|
||||
@ -457,10 +610,11 @@ static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data)
|
||||
static void update_sensor_values(struct ec_sensors_data *ec, u8 *data)
|
||||
{
|
||||
const struct ec_sensor_info *si;
|
||||
struct ec_sensor *s;
|
||||
struct ec_sensor *s, *sensor_end;
|
||||
|
||||
for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) {
|
||||
si = &known_ec_sensors[s->info_index];
|
||||
sensor_end = ec->sensors + ec->nr_sensors;
|
||||
for (s = ec->sensors; s != sensor_end; s++) {
|
||||
si = ec->sensors_info + s->info_index;
|
||||
s->cached_value = get_sensor_value(si, data);
|
||||
data += si->addr.components.size;
|
||||
}
|
||||
@ -471,15 +625,9 @@ static int update_ec_sensors(const struct device *dev,
|
||||
{
|
||||
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;
|
||||
if (!ec->lock_data.lock(&ec->lock_data)) {
|
||||
dev_warn(dev, "Failed to acquire mutex");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
status = asus_ec_block_read(dev, ec);
|
||||
@ -487,10 +635,10 @@ static int update_ec_sensors(const struct device *dev,
|
||||
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:
|
||||
|
||||
if (!ec->lock_data.unlock(&ec->lock_data))
|
||||
dev_err(dev, "Failed to release mutex");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -597,12 +745,24 @@ static struct hwmon_chip_info asus_ec_chip_info = {
|
||||
.ops = &asus_ec_hwmon_ops,
|
||||
};
|
||||
|
||||
static unsigned long __init get_board_sensors(void)
|
||||
static const struct ec_board_info * __init get_board_info(void)
|
||||
{
|
||||
const struct dmi_system_id *dmi_entry =
|
||||
dmi_first_match(asus_ec_dmi_table);
|
||||
const char *dmi_board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
|
||||
const char *dmi_board_name = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
const struct ec_board_info *board;
|
||||
|
||||
return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0;
|
||||
if (!dmi_board_vendor || !dmi_board_name ||
|
||||
strcasecmp(dmi_board_vendor, "ASUSTeK COMPUTER INC."))
|
||||
return NULL;
|
||||
|
||||
for (board = board_info; board->sensors; board++) {
|
||||
if (match_string(board->board_names,
|
||||
MAX_IDENTICAL_BOARD_VARIATIONS,
|
||||
dmi_board_name) >= 0)
|
||||
return board;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int __init asus_ec_probe(struct platform_device *pdev)
|
||||
@ -610,17 +770,18 @@ 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 ec_board_info *pboard_info;
|
||||
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;
|
||||
int status;
|
||||
|
||||
board_sensors = get_board_sensors();
|
||||
if (!board_sensors)
|
||||
pboard_info = get_board_info();
|
||||
if (!pboard_info)
|
||||
return -ENODEV;
|
||||
|
||||
ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data),
|
||||
@ -629,11 +790,31 @@ static int __init asus_ec_probe(struct platform_device *pdev)
|
||||
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->board_info = pboard_info;
|
||||
|
||||
switch (ec_data->board_info->family) {
|
||||
case family_amd_400_series:
|
||||
ec_data->sensors_info = sensors_family_amd_400;
|
||||
break;
|
||||
case family_amd_500_series:
|
||||
ec_data->sensors_info = sensors_family_amd_500;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Unknown board family: %d",
|
||||
ec_data->board_info->family);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ec_data->nr_sensors = hweight_long(ec_data->board_info->sensors);
|
||||
ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors,
|
||||
sizeof(struct ec_sensor), GFP_KERNEL);
|
||||
|
||||
status = setup_lock_data(dev);
|
||||
if (status) {
|
||||
dev_err(dev, "Failed to setup state/EC locking: %d", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
setup_sensor_data(ec_data);
|
||||
ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers,
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
@ -645,8 +826,6 @@ static int __init asus_ec_probe(struct platform_device *pdev)
|
||||
|
||||
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])
|
||||
@ -703,7 +882,14 @@ static struct platform_driver asus_ec_sensors_platform_driver = {
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table);
|
||||
MODULE_DEVICE_TABLE(acpi, acpi_ec_ids);
|
||||
/*
|
||||
* we use module_platform_driver_probe() rather than module_platform_driver()
|
||||
* because the probe function (and its dependants) are marked with __init, which
|
||||
* means we can't put it into the .probe member of the platform_driver struct
|
||||
* above, and we can't mark the asus_ec_sensors_platform_driver object as __init
|
||||
* because the object is referenced from the module exit code.
|
||||
*/
|
||||
module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe);
|
||||
|
||||
module_param_named(mutex_path, mutex_path_override, charp, 0);
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/polynomial.h>
|
||||
#include <linux/seqlock.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
@ -65,7 +66,7 @@ static const struct pvt_sensor_info pvt_info[] = {
|
||||
* 48380,
|
||||
* where T = [-48380, 147438] mC and N = [0, 1023].
|
||||
*/
|
||||
static const struct pvt_poly __maybe_unused poly_temp_to_N = {
|
||||
static const struct polynomial __maybe_unused poly_temp_to_N = {
|
||||
.total_divider = 10000,
|
||||
.terms = {
|
||||
{4, 18322, 10000, 10000},
|
||||
@ -76,7 +77,7 @@ static const struct pvt_poly __maybe_unused poly_temp_to_N = {
|
||||
}
|
||||
};
|
||||
|
||||
static const struct pvt_poly poly_N_to_temp = {
|
||||
static const struct polynomial poly_N_to_temp = {
|
||||
.total_divider = 1,
|
||||
.terms = {
|
||||
{4, -16743, 1000, 1},
|
||||
@ -97,7 +98,7 @@ static const struct pvt_poly poly_N_to_temp = {
|
||||
* N = (18658e-3*V - 11572) / 10,
|
||||
* V = N * 10^5 / 18658 + 11572 * 10^4 / 18658.
|
||||
*/
|
||||
static const struct pvt_poly __maybe_unused poly_volt_to_N = {
|
||||
static const struct polynomial __maybe_unused poly_volt_to_N = {
|
||||
.total_divider = 10,
|
||||
.terms = {
|
||||
{1, 18658, 1000, 1},
|
||||
@ -105,7 +106,7 @@ static const struct pvt_poly __maybe_unused poly_volt_to_N = {
|
||||
}
|
||||
};
|
||||
|
||||
static const struct pvt_poly poly_N_to_volt = {
|
||||
static const struct polynomial poly_N_to_volt = {
|
||||
.total_divider = 10,
|
||||
.terms = {
|
||||
{1, 100000, 18658, 1},
|
||||
@ -113,31 +114,6 @@ static const struct pvt_poly poly_N_to_volt = {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Here is the polynomial calculation function, which performs the
|
||||
* redistributed terms calculations. It's pretty straightforward. We walk
|
||||
* over each degree term up to the free one, and perform the redistributed
|
||||
* multiplication of the term coefficient, its divider (as for the rationale
|
||||
* fraction representation), data power and the rational fraction divider
|
||||
* leftover. Then all of this is collected in a total sum variable, which
|
||||
* value is normalized by the total divider before being returned.
|
||||
*/
|
||||
static long pvt_calc_poly(const struct pvt_poly *poly, long data)
|
||||
{
|
||||
const struct pvt_poly_term *term = poly->terms;
|
||||
long tmp, ret = 0;
|
||||
int deg;
|
||||
|
||||
do {
|
||||
tmp = term->coef;
|
||||
for (deg = 0; deg < term->deg; ++deg)
|
||||
tmp = mult_frac(tmp, data, term->divider);
|
||||
ret += tmp / term->divider_leftover;
|
||||
} while ((term++)->deg);
|
||||
|
||||
return ret / poly->total_divider;
|
||||
}
|
||||
|
||||
static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data)
|
||||
{
|
||||
u32 old;
|
||||
@ -324,9 +300,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
|
||||
} while (read_seqretry(&cache->data_seqlock, seq));
|
||||
|
||||
if (type == PVT_TEMP)
|
||||
*val = pvt_calc_poly(&poly_N_to_temp, data);
|
||||
*val = polynomial_calc(&poly_N_to_temp, data);
|
||||
else
|
||||
*val = pvt_calc_poly(&poly_N_to_volt, data);
|
||||
*val = polynomial_calc(&poly_N_to_volt, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -345,9 +321,9 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
|
||||
data = FIELD_GET(PVT_THRES_HI_MASK, data);
|
||||
|
||||
if (type == PVT_TEMP)
|
||||
*val = pvt_calc_poly(&poly_N_to_temp, data);
|
||||
*val = polynomial_calc(&poly_N_to_temp, data);
|
||||
else
|
||||
*val = pvt_calc_poly(&poly_N_to_volt, data);
|
||||
*val = polynomial_calc(&poly_N_to_volt, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -360,10 +336,10 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
|
||||
|
||||
if (type == PVT_TEMP) {
|
||||
val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX);
|
||||
data = pvt_calc_poly(&poly_temp_to_N, val);
|
||||
data = polynomial_calc(&poly_temp_to_N, val);
|
||||
} else {
|
||||
val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX);
|
||||
data = pvt_calc_poly(&poly_volt_to_N, val);
|
||||
data = polynomial_calc(&poly_volt_to_N, val);
|
||||
}
|
||||
|
||||
/* Serialize limit update, since a part of the register is changed. */
|
||||
@ -522,9 +498,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
|
||||
return -ETIMEDOUT;
|
||||
|
||||
if (type == PVT_TEMP)
|
||||
*val = pvt_calc_poly(&poly_N_to_temp, data);
|
||||
*val = polynomial_calc(&poly_N_to_temp, data);
|
||||
else
|
||||
*val = pvt_calc_poly(&poly_N_to_volt, data);
|
||||
*val = polynomial_calc(&poly_N_to_volt, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -21,14 +21,17 @@
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kconfig.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
@ -46,8 +49,11 @@
|
||||
#define I8K_SMM_GET_DELL_SIG1 0xfea3
|
||||
#define I8K_SMM_GET_DELL_SIG2 0xffa3
|
||||
|
||||
/* in usecs */
|
||||
#define DELL_SMM_MAX_DURATION 250000
|
||||
|
||||
#define I8K_FAN_MULT 30
|
||||
#define I8K_FAN_MAX_RPM 30000
|
||||
#define I8K_FAN_RPM_THRESHOLD 1000
|
||||
#define I8K_MAX_TEMP 127
|
||||
|
||||
#define I8K_FN_NONE 0x00
|
||||
@ -80,6 +86,11 @@ struct dell_smm_data {
|
||||
int *fan_nominal_speed[DELL_SMM_NO_FANS];
|
||||
};
|
||||
|
||||
struct dell_smm_cooling_data {
|
||||
u8 fan_num;
|
||||
struct dell_smm_data *data;
|
||||
};
|
||||
|
||||
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
|
||||
MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
|
||||
MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
|
||||
@ -231,6 +242,9 @@ static int i8k_smm_func(void *par)
|
||||
pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lld usecs)\n", eax, ebx,
|
||||
(rc ? 0xffff : regs->eax & 0xffff), duration);
|
||||
|
||||
if (duration > DELL_SMM_MAX_DURATION)
|
||||
pr_warn_once("SMM call took %lld usecs!\n", duration);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -318,7 +332,7 @@ static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8
|
||||
if (data->disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
|
||||
return i8k_smm(®s) ? : (regs.eax & 0xffff);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -638,9 +652,50 @@ static void __init i8k_init_procfs(struct device *dev)
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Hwmon interface
|
||||
*/
|
||||
static int dell_smm_get_max_state(struct thermal_cooling_device *dev, unsigned long *state)
|
||||
{
|
||||
struct dell_smm_cooling_data *cdata = dev->devdata;
|
||||
|
||||
*state = cdata->data->i8k_fan_max;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned long *state)
|
||||
{
|
||||
struct dell_smm_cooling_data *cdata = dev->devdata;
|
||||
int ret;
|
||||
|
||||
ret = i8k_get_fan_status(cdata->data, cdata->fan_num);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*state = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dell_smm_set_cur_state(struct thermal_cooling_device *dev, unsigned long state)
|
||||
{
|
||||
struct dell_smm_cooling_data *cdata = dev->devdata;
|
||||
struct dell_smm_data *data = cdata->data;
|
||||
int ret;
|
||||
|
||||
if (state > data->i8k_fan_max)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->i8k_mutex);
|
||||
ret = i8k_set_fan(data, cdata->fan_num, (int)state);
|
||||
mutex_unlock(&data->i8k_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops dell_smm_cooling_ops = {
|
||||
.get_max_state = dell_smm_get_max_state,
|
||||
.get_cur_state = dell_smm_get_cur_state,
|
||||
.set_cur_state = dell_smm_set_cur_state,
|
||||
};
|
||||
|
||||
static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
@ -727,6 +782,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
|
||||
long *val)
|
||||
{
|
||||
struct dell_smm_data *data = dev_get_drvdata(dev);
|
||||
int mult = data->i8k_fan_mult;
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
@ -755,11 +811,11 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
|
||||
|
||||
return 0;
|
||||
case hwmon_fan_min:
|
||||
*val = data->fan_nominal_speed[channel][0];
|
||||
*val = data->fan_nominal_speed[channel][0] * mult;
|
||||
|
||||
return 0;
|
||||
case hwmon_fan_max:
|
||||
*val = data->fan_nominal_speed[channel][data->i8k_fan_max];
|
||||
*val = data->fan_nominal_speed[channel][data->i8k_fan_max] * mult;
|
||||
|
||||
return 0;
|
||||
case hwmon_fan_target:
|
||||
@ -770,7 +826,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a
|
||||
if (ret > data->i8k_fan_max)
|
||||
ret = data->i8k_fan_max;
|
||||
|
||||
*val = data->fan_nominal_speed[channel][ret];
|
||||
*val = data->fan_nominal_speed[channel][ret] * mult;
|
||||
|
||||
return 0;
|
||||
default:
|
||||
@ -941,6 +997,37 @@ static const struct hwmon_chip_info dell_smm_chip_info = {
|
||||
.info = dell_smm_info,
|
||||
};
|
||||
|
||||
static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num)
|
||||
{
|
||||
struct dell_smm_data *data = dev_get_drvdata(dev);
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct dell_smm_cooling_data *cdata;
|
||||
int ret = 0;
|
||||
char *name;
|
||||
|
||||
name = kasprintf(GFP_KERNEL, "dell-smm-fan%u", fan_num + 1);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
cdata = devm_kmalloc(dev, sizeof(*cdata), GFP_KERNEL);
|
||||
if (cdata) {
|
||||
cdata->fan_num = fan_num;
|
||||
cdata->data = data;
|
||||
cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata,
|
||||
&dell_smm_cooling_ops);
|
||||
if (IS_ERR(cdev)) {
|
||||
devm_kfree(dev, cdata);
|
||||
ret = PTR_ERR(cdev);
|
||||
}
|
||||
} else {
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
|
||||
kfree(name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init dell_smm_init_hwmon(struct device *dev)
|
||||
{
|
||||
struct dell_smm_data *data = dev_get_drvdata(dev);
|
||||
@ -967,6 +1054,15 @@ static int __init dell_smm_init_hwmon(struct device *dev)
|
||||
continue;
|
||||
|
||||
data->fan[i] = true;
|
||||
|
||||
/* the cooling device is not critical, ignore failures */
|
||||
if (IS_REACHABLE(CONFIG_THERMAL)) {
|
||||
err = dell_smm_init_cdev(dev, i);
|
||||
if (err < 0)
|
||||
dev_warn(dev, "Failed to register cooling device for fan %u\n",
|
||||
i + 1);
|
||||
}
|
||||
|
||||
data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1,
|
||||
sizeof(*data->fan_nominal_speed[i]),
|
||||
GFP_KERNEL);
|
||||
@ -982,6 +1078,13 @@ static int __init dell_smm_init_hwmon(struct device *dev)
|
||||
break;
|
||||
}
|
||||
data->fan_nominal_speed[i][state] = err;
|
||||
/*
|
||||
* Autodetect fan multiplier based on nominal rpm if multiplier
|
||||
* was not specified as module param or in DMI. If fan reports
|
||||
* rpm value too high then set multiplier to 1.
|
||||
*/
|
||||
if (!fan_mult && err > I8K_FAN_RPM_THRESHOLD)
|
||||
data->i8k_fan_mult = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1270,15 +1373,12 @@ static int __init dell_smm_probe(struct platform_device *pdev)
|
||||
struct dell_smm_data *data;
|
||||
const struct dmi_system_id *id, *fan_control;
|
||||
int ret;
|
||||
u8 fan;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&data->i8k_mutex);
|
||||
data->i8k_fan_mult = I8K_FAN_MULT;
|
||||
data->i8k_fan_max = I8K_FAN_HIGH;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
|
||||
@ -1313,7 +1413,9 @@ static int __init dell_smm_probe(struct platform_device *pdev)
|
||||
fan_max = conf->fan_max;
|
||||
}
|
||||
|
||||
data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */
|
||||
/* All options must not be 0 */
|
||||
data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
|
||||
data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
|
||||
data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
|
||||
|
||||
fan_control = dmi_first_match(i8k_whitelist_fan_control);
|
||||
@ -1325,25 +1427,6 @@ static int __init dell_smm_probe(struct platform_device *pdev)
|
||||
dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
|
||||
}
|
||||
|
||||
if (!fan_mult) {
|
||||
/*
|
||||
* Autodetect fan multiplier based on nominal rpm
|
||||
* If fan reports rpm value too high then set multiplier to 1
|
||||
*/
|
||||
for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) {
|
||||
ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
if (ret > I8K_FAN_MAX_RPM)
|
||||
data->i8k_fan_mult = 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Fan multiplier was specified in module param or in dmi */
|
||||
data->i8k_fan_mult = fan_mult;
|
||||
}
|
||||
|
||||
ret = dell_smm_init_hwmon(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -764,7 +764,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||
"hwmon: '%s' is not a valid name attribute, please fix\n",
|
||||
name);
|
||||
|
||||
id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL);
|
||||
id = ida_alloc(&hwmon_ida, GFP_KERNEL);
|
||||
if (id < 0)
|
||||
return ERR_PTR(id);
|
||||
|
||||
@ -856,7 +856,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
||||
free_hwmon:
|
||||
hwmon_dev_release(hdev);
|
||||
ida_remove:
|
||||
ida_simple_remove(&hwmon_ida, id);
|
||||
ida_free(&hwmon_ida, id);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
@ -886,11 +886,12 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups);
|
||||
|
||||
/**
|
||||
* hwmon_device_register_with_info - register w/ hwmon
|
||||
* @dev: the parent device
|
||||
* @name: hwmon name attribute
|
||||
* @drvdata: driver data to attach to created device
|
||||
* @chip: pointer to hwmon chip information
|
||||
* @dev: the parent device (mandatory)
|
||||
* @name: hwmon name attribute (mandatory)
|
||||
* @drvdata: driver data to attach to created device (optional)
|
||||
* @chip: pointer to hwmon chip information (mandatory)
|
||||
* @extra_groups: pointer to list of additional non-standard attribute groups
|
||||
* (optional)
|
||||
*
|
||||
* hwmon_device_unregister() must be called when the device is no
|
||||
* longer needed.
|
||||
@ -903,19 +904,41 @@ hwmon_device_register_with_info(struct device *dev, const char *name,
|
||||
const struct hwmon_chip_info *chip,
|
||||
const struct attribute_group **extra_groups)
|
||||
{
|
||||
if (!name)
|
||||
if (!dev || !name || !chip)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (chip && !dev)
|
||||
if (!chip->ops || !chip->ops->is_visible || !chip->info)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __hwmon_device_register(dev, name, drvdata, chip, extra_groups);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
|
||||
|
||||
/**
|
||||
* hwmon_device_register_for_thermal - register hwmon device for thermal subsystem
|
||||
* @dev: the parent device
|
||||
* @name: hwmon name attribute
|
||||
* @drvdata: driver data to attach to created device
|
||||
*
|
||||
* The use of this function is restricted. It is provided for legacy reasons
|
||||
* and must only be called from the thermal subsystem.
|
||||
*
|
||||
* hwmon_device_unregister() must be called when the device is no
|
||||
* longer needed.
|
||||
*
|
||||
* Returns the pointer to the new device.
|
||||
*/
|
||||
struct device *
|
||||
hwmon_device_register_for_thermal(struct device *dev, const char *name,
|
||||
void *drvdata)
|
||||
{
|
||||
if (!name || !dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __hwmon_device_register(dev, name, drvdata, NULL, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, HWMON_THERMAL);
|
||||
|
||||
/**
|
||||
* hwmon_device_register - register w/ hwmon
|
||||
* @dev: the device to register
|
||||
@ -945,7 +968,7 @@ void hwmon_device_unregister(struct device *dev)
|
||||
|
||||
if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) {
|
||||
device_unregister(dev);
|
||||
ida_simple_remove(&hwmon_ida, id);
|
||||
ida_free(&hwmon_ida, id);
|
||||
} else
|
||||
dev_dbg(dev->parent,
|
||||
"hwmon_device_unregister() failed: bad class ID!\n");
|
||||
@ -1057,6 +1080,59 @@ void devm_hwmon_device_unregister(struct device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister);
|
||||
|
||||
static char *__hwmon_sanitize_name(struct device *dev, const char *old_name)
|
||||
{
|
||||
char *name, *p;
|
||||
|
||||
if (dev)
|
||||
name = devm_kstrdup(dev, old_name, GFP_KERNEL);
|
||||
else
|
||||
name = kstrdup(old_name, GFP_KERNEL);
|
||||
if (!name)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (p = name; *p; p++)
|
||||
if (hwmon_is_bad_char(*p))
|
||||
*p = '_';
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* hwmon_sanitize_name - Replaces invalid characters in a hwmon name
|
||||
* @name: NUL-terminated name
|
||||
*
|
||||
* Allocates a new string where any invalid characters will be replaced
|
||||
* by an underscore. It is the responsibility of the caller to release
|
||||
* the memory.
|
||||
*
|
||||
* Returns newly allocated name, or ERR_PTR on error.
|
||||
*/
|
||||
char *hwmon_sanitize_name(const char *name)
|
||||
{
|
||||
return __hwmon_sanitize_name(NULL, name);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwmon_sanitize_name);
|
||||
|
||||
/**
|
||||
* devm_hwmon_sanitize_name - resource managed hwmon_sanitize_name()
|
||||
* @dev: device to allocate memory for
|
||||
* @name: NUL-terminated name
|
||||
*
|
||||
* Allocates a new string where any invalid characters will be replaced
|
||||
* by an underscore.
|
||||
*
|
||||
* Returns newly allocated name, or ERR_PTR on error.
|
||||
*/
|
||||
char *devm_hwmon_sanitize_name(struct device *dev, const char *name)
|
||||
{
|
||||
if (!dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return __hwmon_sanitize_name(dev, name);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_hwmon_sanitize_name);
|
||||
|
||||
static void __init hwmon_pci_quirks(void)
|
||||
{
|
||||
#if defined CONFIG_X86 && defined CONFIG_PCI
|
||||
|
@ -482,7 +482,7 @@ static void aem_delete(struct aem_data *data)
|
||||
ipmi_destroy_user(data->ipmi.user);
|
||||
platform_set_drvdata(data->pdev, NULL);
|
||||
platform_device_unregister(data->pdev);
|
||||
ida_simple_remove(&aem_ida, data->id);
|
||||
ida_free(&aem_ida, data->id);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
@ -539,7 +539,7 @@ static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle)
|
||||
data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
|
||||
|
||||
/* Create sub-device for this fw instance */
|
||||
data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL);
|
||||
data->id = ida_alloc(&aem_ida, GFP_KERNEL);
|
||||
if (data->id < 0)
|
||||
goto id_err;
|
||||
|
||||
@ -600,7 +600,7 @@ ipmi_err:
|
||||
platform_set_drvdata(data->pdev, NULL);
|
||||
platform_device_unregister(data->pdev);
|
||||
dev_err:
|
||||
ida_simple_remove(&aem_ida, data->id);
|
||||
ida_free(&aem_ida, data->id);
|
||||
id_err:
|
||||
kfree(data);
|
||||
|
||||
@ -679,7 +679,7 @@ static int aem_init_aem2_inst(struct aem_ipmi_data *probe,
|
||||
data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
|
||||
|
||||
/* Create sub-device for this fw instance */
|
||||
data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL);
|
||||
data->id = ida_alloc(&aem_ida, GFP_KERNEL);
|
||||
if (data->id < 0)
|
||||
goto id_err;
|
||||
|
||||
@ -740,7 +740,7 @@ ipmi_err:
|
||||
platform_set_drvdata(data->pdev, NULL);
|
||||
platform_device_unregister(data->pdev);
|
||||
dev_err:
|
||||
ida_simple_remove(&aem_ida, data->id);
|
||||
ida_free(&aem_ida, data->id);
|
||||
id_err:
|
||||
kfree(data);
|
||||
|
||||
|
@ -515,7 +515,6 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev)
|
||||
struct intel_m10bmc *m10bmc = dev_get_drvdata(pdev->dev.parent);
|
||||
struct device *hwmon_dev, *dev = &pdev->dev;
|
||||
struct m10bmc_hwmon *hw;
|
||||
int i;
|
||||
|
||||
hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
|
||||
if (!hw)
|
||||
@ -528,13 +527,9 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev)
|
||||
hw->chip.info = hw->bdata->hinfo;
|
||||
hw->chip.ops = &m10bmc_hwmon_ops;
|
||||
|
||||
hw->hw_name = devm_kstrdup(dev, id->name, GFP_KERNEL);
|
||||
if (!hw->hw_name)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; hw->hw_name[i]; i++)
|
||||
if (hwmon_is_bad_char(hw->hw_name[i]))
|
||||
hw->hw_name[i] = '_';
|
||||
hw->hw_name = devm_hwmon_sanitize_name(dev, id->name);
|
||||
if (IS_ERR(hw->hw_name))
|
||||
return PTR_ERR(hw->hw_name);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, hw->hw_name,
|
||||
hw, &hw->chip, NULL);
|
||||
|
@ -63,6 +63,7 @@ static const unsigned short normal_i2c[] = {
|
||||
#define STM_MANID 0x104a /* ST Microelectronics */
|
||||
#define GT_MANID 0x1c68 /* Giantec */
|
||||
#define GT_MANID2 0x132d /* Giantec, 2nd mfg ID */
|
||||
#define SI_MANID 0x1c85 /* Seiko Instruments */
|
||||
|
||||
/* SMBUS register */
|
||||
#define SMBUS_STMOUT BIT(7) /* SMBus time-out, active low */
|
||||
@ -156,6 +157,10 @@ static const unsigned short normal_i2c[] = {
|
||||
#define STTS3000_DEVID 0x0200
|
||||
#define STTS3000_DEVID_MASK 0xffff
|
||||
|
||||
/* Seiko Instruments */
|
||||
#define S34TS04A_DEVID 0x2221
|
||||
#define S34TS04A_DEVID_MASK 0xffff
|
||||
|
||||
static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 };
|
||||
|
||||
struct jc42_chips {
|
||||
@ -186,6 +191,7 @@ static struct jc42_chips jc42_chips[] = {
|
||||
{ ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK },
|
||||
{ ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK },
|
||||
{ NXP_MANID, SE98_DEVID, SE98_DEVID_MASK },
|
||||
{ SI_MANID, S34TS04A_DEVID, S34TS04A_DEVID_MASK },
|
||||
{ STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK },
|
||||
{ STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK },
|
||||
{ STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK },
|
||||
@ -443,6 +449,8 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *jc42_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_MAX_HYST |
|
||||
|
418
drivers/hwmon/lan966x-hwmon.c
Normal file
418
drivers/hwmon/lan966x-hwmon.c
Normal file
@ -0,0 +1,418 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/polynomial.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/*
|
||||
* The original translation formulae of the temperature (in degrees of Celsius)
|
||||
* are as follows:
|
||||
*
|
||||
* T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) +
|
||||
* 3.0604e-1*(N^1) + -5.6197e1
|
||||
*
|
||||
* where [-56.197, 136.402]C and N = [0, 1023].
|
||||
*
|
||||
* They must be accordingly altered to be suitable for the integer arithmetics.
|
||||
* The technique is called 'factor redistribution', which just makes sure the
|
||||
* multiplications and divisions are made so to have a result of the operations
|
||||
* within the integer numbers limit. In addition we need to translate the
|
||||
* formulae to accept millidegrees of Celsius. Here what it looks like after
|
||||
* the alterations:
|
||||
*
|
||||
* T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) +
|
||||
* 306040e-3*(N^1) + -56197
|
||||
*
|
||||
* where T = [-56197, 136402]mC and N = [0, 1023].
|
||||
*/
|
||||
|
||||
static const struct polynomial poly_N_to_temp = {
|
||||
.terms = {
|
||||
{4, -34627, 1000, 1},
|
||||
{3, 110230, 1000, 1},
|
||||
{2, -191650, 1000, 1},
|
||||
{1, 306040, 1000, 1},
|
||||
{0, -56197, 1, 1}
|
||||
}
|
||||
};
|
||||
|
||||
#define PVT_SENSOR_CTRL 0x0 /* unused */
|
||||
#define PVT_SENSOR_CFG 0x4
|
||||
#define SENSOR_CFG_CLK_CFG GENMASK(27, 20)
|
||||
#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9)
|
||||
#define SENSOR_CFG_SAMPLE_ENA BIT(8)
|
||||
#define SENSOR_CFG_START_CAPTURE BIT(7)
|
||||
#define SENSOR_CFG_CONTINIOUS_MODE BIT(6)
|
||||
#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0)
|
||||
#define PVT_SENSOR_STAT 0x8
|
||||
#define SENSOR_STAT_DATA_VALID BIT(10)
|
||||
#define SENSOR_STAT_DATA GENMASK(9, 0)
|
||||
|
||||
#define FAN_CFG 0x0
|
||||
#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16)
|
||||
#define INV_POL BIT(3)
|
||||
#define GATE_ENA BIT(2)
|
||||
#define PWM_OPEN_COL_ENA BIT(1)
|
||||
#define FAN_STAT_CFG BIT(0)
|
||||
#define FAN_PWM_FREQ 0x4
|
||||
#define FAN_PWM_CYC_10US GENMASK(25, 15)
|
||||
#define FAN_PWM_FREQ_FREQ GENMASK(14, 0)
|
||||
#define FAN_CNT 0xc
|
||||
#define FAN_CNT_DATA GENMASK(15, 0)
|
||||
|
||||
#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */
|
||||
|
||||
struct lan966x_hwmon {
|
||||
struct regmap *regmap_pvt;
|
||||
struct regmap *regmap_fan;
|
||||
struct clk *clk;
|
||||
unsigned long clk_rate;
|
||||
};
|
||||
|
||||
static int lan966x_hwmon_read_temp(struct device *dev, long *val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
unsigned int data;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!(data & SENSOR_STAT_DATA_VALID))
|
||||
return -ENODATA;
|
||||
|
||||
*val = polynomial_calc(&poly_N_to_temp,
|
||||
FIELD_GET(SENSOR_STAT_DATA, data));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_read_fan(struct device *dev, long *val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
unsigned int data;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Data is given in pulses per second. Assume two pulses
|
||||
* per revolution.
|
||||
*/
|
||||
*val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_read_pwm(struct device *dev, long *val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
unsigned int data;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
unsigned long tmp;
|
||||
unsigned int data;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Datasheet says it is sys_clk / 256 / pwm_freq. But in reality
|
||||
* it is sys_clk / 256 / (pwm_freq + 1).
|
||||
*/
|
||||
data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1;
|
||||
tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256);
|
||||
*val = DIV_ROUND_CLOSEST(tmp, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return lan966x_hwmon_read_temp(dev, val);
|
||||
case hwmon_fan:
|
||||
return lan966x_hwmon_read_fan(dev, val);
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return lan966x_hwmon_read_pwm(dev, val);
|
||||
case hwmon_pwm_freq:
|
||||
return lan966x_hwmon_read_pwm_freq(dev, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_write_pwm(struct device *dev, long val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
|
||||
if (val < 0 || val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(hwmon->regmap_fan, FAN_CFG,
|
||||
FAN_CFG_DUTY_CYCLE,
|
||||
FIELD_PREP(FAN_CFG_DUTY_CYCLE, val));
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
|
||||
if (val <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val);
|
||||
val = DIV_ROUND_CLOSEST(val, 256) - 1;
|
||||
val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ);
|
||||
|
||||
return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ,
|
||||
FAN_PWM_FREQ_FREQ,
|
||||
FIELD_PREP(FAN_PWM_FREQ_FREQ, val));
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return lan966x_hwmon_write_pwm(dev, val);
|
||||
case hwmon_pwm_freq:
|
||||
return lan966x_hwmon_write_pwm_freq(dev, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t lan966x_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
umode_t mode = 0;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
mode = 0444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
mode = 0444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
case hwmon_pwm_freq:
|
||||
mode = 0644;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *lan966x_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops lan966x_hwmon_ops = {
|
||||
.is_visible = lan966x_hwmon_is_visible,
|
||||
.read = lan966x_hwmon_read,
|
||||
.write = lan966x_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info lan966x_hwmon_chip_info = {
|
||||
.ops = &lan966x_hwmon_ops,
|
||||
.info = lan966x_hwmon_info,
|
||||
};
|
||||
|
||||
static void lan966x_hwmon_disable(void *data)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = data;
|
||||
|
||||
regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG,
|
||||
SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE,
|
||||
0);
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_enable(struct device *dev,
|
||||
struct lan966x_hwmon *hwmon)
|
||||
{
|
||||
unsigned int mask = SENSOR_CFG_CLK_CFG |
|
||||
SENSOR_CFG_SAMPLE_ENA |
|
||||
SENSOR_CFG_START_CAPTURE |
|
||||
SENSOR_CFG_CONTINIOUS_MODE |
|
||||
SENSOR_CFG_PSAMPLE_ENA;
|
||||
unsigned int val;
|
||||
unsigned int div;
|
||||
int ret;
|
||||
|
||||
/* enable continuous mode */
|
||||
val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE;
|
||||
|
||||
/* set PVT clock to be between 1.15 and 1.25 MHz */
|
||||
div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK);
|
||||
val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div);
|
||||
|
||||
ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG,
|
||||
mask, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon);
|
||||
}
|
||||
|
||||
static struct regmap *lan966x_init_regmap(struct platform_device *pdev,
|
||||
const char *name)
|
||||
{
|
||||
struct regmap_config regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
};
|
||||
void __iomem *base;
|
||||
|
||||
base = devm_platform_ioremap_resource_byname(pdev, name);
|
||||
if (IS_ERR(base))
|
||||
return ERR_CAST(base);
|
||||
|
||||
regmap_config.name = name;
|
||||
|
||||
return devm_regmap_init_mmio(&pdev->dev, base, ®map_config);
|
||||
}
|
||||
|
||||
static void lan966x_clk_disable(void *data)
|
||||
{
|
||||
struct lan966x_hwmon *hwmon = data;
|
||||
|
||||
clk_disable_unprepare(hwmon->clk);
|
||||
}
|
||||
|
||||
static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(hwmon->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon);
|
||||
}
|
||||
|
||||
static int lan966x_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct lan966x_hwmon *hwmon;
|
||||
struct device *hwmon_dev;
|
||||
int ret;
|
||||
|
||||
hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
|
||||
if (!hwmon)
|
||||
return -ENOMEM;
|
||||
|
||||
hwmon->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(hwmon->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(hwmon->clk),
|
||||
"failed to get clock\n");
|
||||
|
||||
ret = lan966x_clk_enable(dev, hwmon);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to enable clock\n");
|
||||
|
||||
hwmon->clk_rate = clk_get_rate(hwmon->clk);
|
||||
|
||||
hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt");
|
||||
if (IS_ERR(hwmon->regmap_pvt))
|
||||
return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt),
|
||||
"failed to get regmap for PVT registers\n");
|
||||
|
||||
hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan");
|
||||
if (IS_ERR(hwmon->regmap_fan))
|
||||
return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan),
|
||||
"failed to get regmap for fan registers\n");
|
||||
|
||||
ret = lan966x_hwmon_enable(dev, hwmon);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to enable sensor\n");
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
|
||||
"lan966x_hwmon", hwmon,
|
||||
&lan966x_hwmon_chip_info, NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return dev_err_probe(dev, PTR_ERR(hwmon_dev),
|
||||
"failed to register hwmon device\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id lan966x_hwmon_of_match[] = {
|
||||
{ .compatible = "microchip,lan9668-hwmon" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match);
|
||||
|
||||
static struct platform_driver lan966x_hwmon_driver = {
|
||||
.probe = lan966x_hwmon_probe,
|
||||
.driver = {
|
||||
.name = "lan966x-hwmon",
|
||||
.of_match_table = lan966x_hwmon_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(lan966x_hwmon_driver);
|
||||
|
||||
MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver");
|
||||
MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
|
||||
MODULE_LICENSE("GPL");
|
@ -26,6 +26,7 @@
|
||||
|
||||
enum lm75_type { /* keep sorted in alphabetical order */
|
||||
adt75,
|
||||
at30ts74,
|
||||
ds1775,
|
||||
ds75,
|
||||
ds7505,
|
||||
@ -128,6 +129,14 @@ static const struct lm75_params device_params[] = {
|
||||
.default_resolution = 12,
|
||||
.default_sample_time = MSEC_PER_SEC / 10,
|
||||
},
|
||||
[at30ts74] = {
|
||||
.set_mask = 3 << 5, /* 12-bit mode*/
|
||||
.default_resolution = 12,
|
||||
.default_sample_time = 200,
|
||||
.num_sample_times = 4,
|
||||
.sample_times = (unsigned int []){ 25, 50, 100, 200 },
|
||||
.resolutions = (u8 []) {9, 10, 11, 12 },
|
||||
},
|
||||
[ds1775] = {
|
||||
.clr_mask = 3 << 5,
|
||||
.set_mask = 2 << 5, /* 11-bit mode */
|
||||
@ -645,6 +654,7 @@ static int lm75_probe(struct i2c_client *client)
|
||||
|
||||
static const struct i2c_device_id lm75_ids[] = {
|
||||
{ "adt75", adt75, },
|
||||
{ "at30ts74", at30ts74, },
|
||||
{ "ds1775", ds1775, },
|
||||
{ "ds75", ds75, },
|
||||
{ "ds7505", ds7505, },
|
||||
@ -680,6 +690,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = {
|
||||
.compatible = "adi,adt75",
|
||||
.data = (void *)adt75
|
||||
},
|
||||
{
|
||||
.compatible = "atmel,at30ts74",
|
||||
.data = (void *)at30ts74
|
||||
},
|
||||
{
|
||||
.compatible = "dallas,ds1775",
|
||||
.data = (void *)ds1775
|
||||
|
@ -24,10 +24,8 @@
|
||||
#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>
|
||||
|
||||
/*
|
||||
* Addresses to scan
|
||||
|
@ -1707,6 +1707,7 @@ static void lm90_restore_conf(void *_data)
|
||||
|
||||
static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||
{
|
||||
struct device_node *np = client->dev.of_node;
|
||||
int config, convrate;
|
||||
|
||||
convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE);
|
||||
@ -1727,6 +1728,9 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||
|
||||
/* Check Temperature Range Select */
|
||||
if (data->flags & LM90_HAVE_EXTENDED_TEMP) {
|
||||
if (of_property_read_bool(np, "ti,extended-range-enable"))
|
||||
config |= 0x04;
|
||||
|
||||
if (config & 0x04)
|
||||
data->flags |= LM90_FLAG_ADT7461_EXT;
|
||||
}
|
||||
|
@ -811,68 +811,32 @@ static const struct hwmon_ops ltc2992_hwmon_ops = {
|
||||
.write = ltc2992_write,
|
||||
};
|
||||
|
||||
static const u32 ltc2992_chip_config[] = {
|
||||
HWMON_C_IN_RESET_HISTORY,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info ltc2992_chip = {
|
||||
.type = hwmon_chip,
|
||||
.config = ltc2992_chip_config,
|
||||
};
|
||||
|
||||
static const u32 ltc2992_in_config[] = {
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
|
||||
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info ltc2992_in = {
|
||||
.type = hwmon_in,
|
||||
.config = ltc2992_in_config,
|
||||
};
|
||||
|
||||
static const u32 ltc2992_curr_config[] = {
|
||||
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
|
||||
HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
|
||||
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
|
||||
HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info ltc2992_curr = {
|
||||
.type = hwmon_curr,
|
||||
.config = ltc2992_curr_config,
|
||||
};
|
||||
|
||||
static const u32 ltc2992_power_config[] = {
|
||||
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
|
||||
HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
|
||||
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
|
||||
HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info ltc2992_power = {
|
||||
.type = hwmon_power,
|
||||
.config = ltc2992_power_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *ltc2992_info[] = {
|
||||
<c2992_chip,
|
||||
<c2992_in,
|
||||
<c2992_curr,
|
||||
<c2992_power,
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_IN_RESET_HISTORY),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
|
||||
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN |
|
||||
HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN |
|
||||
HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
|
||||
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN |
|
||||
HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM),
|
||||
HWMON_CHANNEL_INFO(power,
|
||||
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST |
|
||||
HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
|
||||
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST |
|
||||
HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -223,16 +223,6 @@ static int pvt_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
}
|
||||
}
|
||||
|
||||
static const u32 pvt_chip_config[] = {
|
||||
HWMON_C_REGISTER_TZ,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info pvt_chip = {
|
||||
.type = hwmon_chip,
|
||||
.config = pvt_chip_config,
|
||||
};
|
||||
|
||||
static struct hwmon_channel_info pvt_temp = {
|
||||
.type = hwmon_temp,
|
||||
};
|
||||
@ -555,7 +545,7 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
pvt_info = devm_kcalloc(dev, val + 2, sizeof(*pvt_info), GFP_KERNEL);
|
||||
if (!pvt_info)
|
||||
return -ENOMEM;
|
||||
pvt_info[0] = &pvt_chip;
|
||||
pvt_info[0] = HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ);
|
||||
index = 1;
|
||||
|
||||
if (ts_num) {
|
||||
|
File diff suppressed because it is too large
Load Diff
195
drivers/hwmon/nct6775-i2c.c
Normal file
195
drivers/hwmon/nct6775-i2c.c
Normal file
@ -0,0 +1,195 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* nct6775-i2c - I2C driver for the hardware monitoring functionality of
|
||||
* Nuvoton NCT677x Super-I/O chips
|
||||
*
|
||||
* Copyright (C) 2022 Zev Weiss <zev@bewilderbeest.net>
|
||||
*
|
||||
* This driver interacts with the chip via it's "back door" i2c interface, as
|
||||
* is often exposed to a BMC. Because the host may still be operating the
|
||||
* chip via the ("front door") LPC interface, this driver cannot assume that
|
||||
* it actually has full control of the chip, and in particular must avoid
|
||||
* making any changes that could confuse the host's LPC usage of it. It thus
|
||||
* operates in a strictly read-only fashion, with the only exception being the
|
||||
* bank-select register (which seems, thankfully, to be replicated for the i2c
|
||||
* interface so it doesn't affect the LPC interface).
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include "nct6775.h"
|
||||
|
||||
static int nct6775_i2c_read(void *ctx, unsigned int reg, unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
u32 tmp;
|
||||
u8 bank = reg >> 8;
|
||||
struct nct6775_data *data = ctx;
|
||||
struct i2c_client *client = data->driver_data;
|
||||
|
||||
if (bank != data->bank) {
|
||||
ret = i2c_smbus_write_byte_data(client, NCT6775_REG_BANK, bank);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->bank = bank;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, reg & 0xff);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
tmp = ret;
|
||||
|
||||
if (nct6775_reg_is_word_sized(data, reg)) {
|
||||
ret = i2c_smbus_read_byte_data(client, (reg & 0xff) + 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
tmp = (tmp << 8) | ret;
|
||||
}
|
||||
|
||||
*val = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The write operation is a dummy so as not to disturb anything being done
|
||||
* with the chip via LPC.
|
||||
*/
|
||||
static int nct6775_i2c_write(void *ctx, unsigned int reg, unsigned int value)
|
||||
{
|
||||
struct nct6775_data *data = ctx;
|
||||
struct i2c_client *client = data->driver_data;
|
||||
|
||||
dev_dbg(&client->dev, "skipping attempted write: %02x -> %03x\n", value, reg);
|
||||
|
||||
/*
|
||||
* This is a lie, but writing anything but the bank-select register is
|
||||
* something this driver shouldn't be doing.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id __maybe_unused nct6775_i2c_of_match[] = {
|
||||
{ .compatible = "nuvoton,nct6106", .data = (void *)nct6106, },
|
||||
{ .compatible = "nuvoton,nct6116", .data = (void *)nct6116, },
|
||||
{ .compatible = "nuvoton,nct6775", .data = (void *)nct6775, },
|
||||
{ .compatible = "nuvoton,nct6776", .data = (void *)nct6776, },
|
||||
{ .compatible = "nuvoton,nct6779", .data = (void *)nct6779, },
|
||||
{ .compatible = "nuvoton,nct6791", .data = (void *)nct6791, },
|
||||
{ .compatible = "nuvoton,nct6792", .data = (void *)nct6792, },
|
||||
{ .compatible = "nuvoton,nct6793", .data = (void *)nct6793, },
|
||||
{ .compatible = "nuvoton,nct6795", .data = (void *)nct6795, },
|
||||
{ .compatible = "nuvoton,nct6796", .data = (void *)nct6796, },
|
||||
{ .compatible = "nuvoton,nct6797", .data = (void *)nct6797, },
|
||||
{ .compatible = "nuvoton,nct6798", .data = (void *)nct6798, },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, nct6775_i2c_of_match);
|
||||
|
||||
static const struct i2c_device_id nct6775_i2c_id[] = {
|
||||
{ "nct6106", nct6106 },
|
||||
{ "nct6116", nct6116 },
|
||||
{ "nct6775", nct6775 },
|
||||
{ "nct6776", nct6776 },
|
||||
{ "nct6779", nct6779 },
|
||||
{ "nct6791", nct6791 },
|
||||
{ "nct6792", nct6792 },
|
||||
{ "nct6793", nct6793 },
|
||||
{ "nct6795", nct6795 },
|
||||
{ "nct6796", nct6796 },
|
||||
{ "nct6797", nct6797 },
|
||||
{ "nct6798", nct6798 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, nct6775_i2c_id);
|
||||
|
||||
static int nct6775_i2c_probe_init(struct nct6775_data *data)
|
||||
{
|
||||
u32 tsi_channel_mask;
|
||||
struct i2c_client *client = data->driver_data;
|
||||
|
||||
/*
|
||||
* The i2c interface doesn't provide access to the control registers
|
||||
* needed to determine the presence of other fans, but fans 1 and 2
|
||||
* are (in principle) always there.
|
||||
*
|
||||
* In practice this is perhaps a little silly, because the system
|
||||
* using this driver is mostly likely a BMC, and hence probably has
|
||||
* totally separate fan tachs & pwms of its own that are actually
|
||||
* controlling/monitoring the fans -- these are thus unlikely to be
|
||||
* doing anything actually useful.
|
||||
*/
|
||||
data->has_fan = 0x03;
|
||||
data->has_fan_min = 0x03;
|
||||
data->has_pwm = 0x03;
|
||||
|
||||
/*
|
||||
* Because on a BMC this driver may be bound very shortly after power
|
||||
* is first applied to the device, the automatic TSI channel detection
|
||||
* in nct6775_probe() (which has already been run at this point) may
|
||||
* not find anything if a channel hasn't yet produced a temperature
|
||||
* reading. Augment whatever was found via autodetection (if
|
||||
* anything) with the channels DT says should be active.
|
||||
*/
|
||||
if (!of_property_read_u32(client->dev.of_node, "nuvoton,tsi-channel-mask",
|
||||
&tsi_channel_mask))
|
||||
data->have_tsi_temp |= tsi_channel_mask & GENMASK(NUM_TSI_TEMP - 1, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_config nct6775_i2c_regmap_config = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 16,
|
||||
.reg_read = nct6775_i2c_read,
|
||||
.reg_write = nct6775_i2c_write,
|
||||
};
|
||||
|
||||
static int nct6775_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct nct6775_data *data;
|
||||
const struct of_device_id *of_id;
|
||||
const struct i2c_device_id *i2c_id;
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
of_id = of_match_device(nct6775_i2c_of_match, dev);
|
||||
i2c_id = i2c_match_id(nct6775_i2c_id, client);
|
||||
|
||||
if (of_id && (unsigned long)of_id->data != i2c_id->driver_data)
|
||||
dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n",
|
||||
of_id->name, i2c_id->name);
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->kind = i2c_id->driver_data;
|
||||
|
||||
data->read_only = true;
|
||||
data->driver_data = client;
|
||||
data->driver_init = nct6775_i2c_probe_init;
|
||||
|
||||
return nct6775_probe(dev, data, &nct6775_i2c_regmap_config);
|
||||
}
|
||||
|
||||
static struct i2c_driver nct6775_i2c_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "nct6775-i2c",
|
||||
.of_match_table = of_match_ptr(nct6775_i2c_of_match),
|
||||
},
|
||||
.probe_new = nct6775_i2c_probe,
|
||||
.id_table = nct6775_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(nct6775_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Zev Weiss <zev@bewilderbeest.net>");
|
||||
MODULE_DESCRIPTION("I2C driver for NCT6775F and compatible chips");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS(HWMON_NCT6775);
|
1229
drivers/hwmon/nct6775-platform.c
Normal file
1229
drivers/hwmon/nct6775-platform.c
Normal file
File diff suppressed because it is too large
Load Diff
252
drivers/hwmon/nct6775.h
Normal file
252
drivers/hwmon/nct6775.h
Normal file
@ -0,0 +1,252 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#ifndef __HWMON_NCT6775_H__
|
||||
#define __HWMON_NCT6775_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792,
|
||||
nct6793, nct6795, nct6796, nct6797, nct6798 };
|
||||
enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 };
|
||||
|
||||
#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 */
|
||||
|
||||
#define NUM_FAN 7
|
||||
|
||||
struct nct6775_data {
|
||||
int addr; /* IO base of hw monitor block */
|
||||
int sioreg; /* SIO register address */
|
||||
enum kinds kind;
|
||||
const char *name;
|
||||
|
||||
const struct attribute_group *groups[7];
|
||||
u8 num_groups;
|
||||
|
||||
u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst,
|
||||
* 3=temp_crit, 4=temp_lcrit
|
||||
*/
|
||||
u8 temp_src[NUM_TEMP];
|
||||
u16 reg_temp_config[NUM_TEMP];
|
||||
const char * const *temp_label;
|
||||
u32 temp_mask;
|
||||
u32 virt_temp_mask;
|
||||
|
||||
u16 REG_CONFIG;
|
||||
u16 REG_VBAT;
|
||||
u16 REG_DIODE;
|
||||
u8 DIODE_MASK;
|
||||
|
||||
const s8 *ALARM_BITS;
|
||||
const s8 *BEEP_BITS;
|
||||
|
||||
const u16 *REG_VIN;
|
||||
const u16 *REG_IN_MINMAX[2];
|
||||
|
||||
const u16 *REG_TARGET;
|
||||
const u16 *REG_FAN;
|
||||
const u16 *REG_FAN_MODE;
|
||||
const u16 *REG_FAN_MIN;
|
||||
const u16 *REG_FAN_PULSES;
|
||||
const u16 *FAN_PULSE_SHIFT;
|
||||
const u16 *REG_FAN_TIME[3];
|
||||
|
||||
const u16 *REG_TOLERANCE_H;
|
||||
|
||||
const u8 *REG_PWM_MODE;
|
||||
const u8 *PWM_MODE_MASK;
|
||||
|
||||
const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor,
|
||||
* [3]=pwm_max, [4]=pwm_step,
|
||||
* [5]=weight_duty_step, [6]=weight_duty_base
|
||||
*/
|
||||
const u16 *REG_PWM_READ;
|
||||
|
||||
const u16 *REG_CRITICAL_PWM_ENABLE;
|
||||
u8 CRITICAL_PWM_ENABLE_MASK;
|
||||
const u16 *REG_CRITICAL_PWM;
|
||||
|
||||
const u16 *REG_AUTO_TEMP;
|
||||
const u16 *REG_AUTO_PWM;
|
||||
|
||||
const u16 *REG_CRITICAL_TEMP;
|
||||
const u16 *REG_CRITICAL_TEMP_TOLERANCE;
|
||||
|
||||
const u16 *REG_TEMP_SOURCE; /* temp register sources */
|
||||
const u16 *REG_TEMP_SEL;
|
||||
const u16 *REG_WEIGHT_TEMP_SEL;
|
||||
const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */
|
||||
|
||||
const u16 *REG_TEMP_OFFSET;
|
||||
|
||||
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);
|
||||
|
||||
struct mutex update_lock;
|
||||
bool valid; /* true if following fields are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
|
||||
/* Register values */
|
||||
u8 bank; /* current register bank */
|
||||
u8 in_num; /* number of in inputs we have */
|
||||
u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */
|
||||
unsigned int rpm[NUM_FAN];
|
||||
u16 fan_min[NUM_FAN];
|
||||
u8 fan_pulses[NUM_FAN];
|
||||
u8 fan_div[NUM_FAN];
|
||||
u8 has_pwm;
|
||||
u8 has_fan; /* some fan inputs can be disabled */
|
||||
u8 has_fan_min; /* some fans don't have min register */
|
||||
bool has_fan_div;
|
||||
|
||||
u8 num_temp_alarms; /* 2, 3, or 6 */
|
||||
u8 num_temp_beeps; /* 2, 3, or 6 */
|
||||
u8 temp_fixed_num; /* 3 or 6 */
|
||||
u8 temp_type[NUM_TEMP_FIXED];
|
||||
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;
|
||||
|
||||
u8 pwm_num; /* number of pwm */
|
||||
u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage,
|
||||
* 1->PWM variable duty cycle
|
||||
*/
|
||||
enum pwm_enable pwm_enable[NUM_FAN];
|
||||
/* 0->off
|
||||
* 1->manual
|
||||
* 2->thermal cruise mode (also called SmartFan I)
|
||||
* 3->fan speed cruise mode
|
||||
* 4->SmartFan III
|
||||
* 5->enhanced variable thermal cruise (SmartFan IV)
|
||||
*/
|
||||
u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor,
|
||||
* [3]=pwm_max, [4]=pwm_step,
|
||||
* [5]=weight_duty_step, [6]=weight_duty_base
|
||||
*/
|
||||
|
||||
u8 target_temp[NUM_FAN];
|
||||
u8 target_temp_mask;
|
||||
u32 target_speed[NUM_FAN];
|
||||
u32 target_speed_tolerance[NUM_FAN];
|
||||
u8 speed_tolerance_limit;
|
||||
|
||||
u8 temp_tolerance[2][NUM_FAN];
|
||||
u8 tolerance_mask;
|
||||
|
||||
u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */
|
||||
|
||||
/* Automatic fan speed control registers */
|
||||
int auto_pwm_num;
|
||||
u8 auto_pwm[NUM_FAN][7];
|
||||
u8 auto_temp[NUM_FAN][7];
|
||||
u8 pwm_temp_sel[NUM_FAN];
|
||||
u8 pwm_weight_temp_sel[NUM_FAN];
|
||||
u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol,
|
||||
* 2->temp_base
|
||||
*/
|
||||
|
||||
u8 vid;
|
||||
u8 vrm;
|
||||
|
||||
bool have_vid;
|
||||
|
||||
u16 have_temp;
|
||||
u16 have_temp_fixed;
|
||||
u16 have_tsi_temp;
|
||||
u16 have_in;
|
||||
|
||||
/* Remember extra register values over suspend/resume */
|
||||
u8 vbat;
|
||||
u8 fandiv1;
|
||||
u8 fandiv2;
|
||||
u8 sio_reg_enable;
|
||||
|
||||
struct regmap *regmap;
|
||||
bool read_only;
|
||||
|
||||
/* driver-specific (platform, i2c) initialization hook and data */
|
||||
int (*driver_init)(struct nct6775_data *data);
|
||||
void *driver_data;
|
||||
};
|
||||
|
||||
static inline int nct6775_read_value(struct nct6775_data *data, u16 reg, u16 *value)
|
||||
{
|
||||
unsigned int tmp;
|
||||
int ret = regmap_read(data->regmap, reg, &tmp);
|
||||
|
||||
if (!ret)
|
||||
*value = tmp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value)
|
||||
{
|
||||
return regmap_write(data->regmap, reg, value);
|
||||
}
|
||||
|
||||
bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg);
|
||||
int nct6775_probe(struct device *dev, struct nct6775_data *data,
|
||||
const struct regmap_config *regmapcfg);
|
||||
|
||||
ssize_t nct6775_show_alarm(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
ssize_t nct6775_show_beep(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
ssize_t nct6775_store_beep(struct device *dev, struct device_attribute *attr, const char *buf,
|
||||
size_t count);
|
||||
|
||||
static inline int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value)
|
||||
{
|
||||
if (!nct6775_reg_is_word_sized(data, reg))
|
||||
value >>= 8;
|
||||
return nct6775_write_value(data, reg, value);
|
||||
}
|
||||
|
||||
static inline umode_t nct6775_attr_mode(struct nct6775_data *data, struct attribute *attr)
|
||||
{
|
||||
return data->read_only ? (attr->mode & ~0222) : attr->mode;
|
||||
}
|
||||
|
||||
static inline int
|
||||
nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group)
|
||||
{
|
||||
/* Need to leave a NULL terminator at the end of data->groups */
|
||||
if (data->num_groups == ARRAY_SIZE(data->groups) - 1)
|
||||
return -ENOBUFS;
|
||||
|
||||
data->groups[data->num_groups++] = group;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NCT6775_REG_BANK 0x4E
|
||||
#define NCT6775_REG_CONFIG 0x40
|
||||
|
||||
#define NCT6775_REG_FANDIV1 0x506
|
||||
#define NCT6775_REG_FANDIV2 0x507
|
||||
|
||||
#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28
|
||||
|
||||
#define FAN_ALARM_BASE 16
|
||||
#define TEMP_ALARM_BASE 24
|
||||
#define INTRUSION_ALARM_BASE 30
|
||||
#define BEEP_ENABLE_BASE 15
|
||||
|
||||
/*
|
||||
* Not currently used:
|
||||
* REG_MAN_ID has the value 0x5ca3 for all supported chips.
|
||||
* REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model.
|
||||
* REG_MAN_ID is at port 0x4f
|
||||
* REG_CHIP_ID is at port 0x58
|
||||
*/
|
||||
|
||||
#endif /* __HWMON_NCT6775_H__ */
|
@ -1149,44 +1149,75 @@ static void occ_parse_poll_response(struct occ *occ)
|
||||
sizeof(*header), size + sizeof(*header));
|
||||
}
|
||||
|
||||
int occ_setup(struct occ *occ, const char *name)
|
||||
int occ_active(struct occ *occ, bool active)
|
||||
{
|
||||
int rc = mutex_lock_interruptible(&occ->lock);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (active) {
|
||||
if (occ->active) {
|
||||
rc = -EALREADY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
occ->error_count = 0;
|
||||
occ->last_safe = 0;
|
||||
|
||||
rc = occ_poll(occ);
|
||||
if (rc < 0) {
|
||||
dev_err(occ->bus_dev,
|
||||
"failed to get OCC poll response=%02x: %d\n",
|
||||
occ->resp.return_status, rc);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
occ->active = true;
|
||||
occ->next_update = jiffies + OCC_UPDATE_FREQUENCY;
|
||||
occ_parse_poll_response(occ);
|
||||
|
||||
rc = occ_setup_sensor_attrs(occ);
|
||||
if (rc) {
|
||||
dev_err(occ->bus_dev,
|
||||
"failed to setup sensor attrs: %d\n", rc);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
occ->hwmon = hwmon_device_register_with_groups(occ->bus_dev,
|
||||
"occ", occ,
|
||||
occ->groups);
|
||||
if (IS_ERR(occ->hwmon)) {
|
||||
rc = PTR_ERR(occ->hwmon);
|
||||
occ->hwmon = NULL;
|
||||
dev_err(occ->bus_dev,
|
||||
"failed to register hwmon device: %d\n", rc);
|
||||
goto unlock;
|
||||
}
|
||||
} else {
|
||||
if (!occ->active) {
|
||||
rc = -EALREADY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (occ->hwmon)
|
||||
hwmon_device_unregister(occ->hwmon);
|
||||
occ->active = false;
|
||||
occ->hwmon = NULL;
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&occ->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int occ_setup(struct occ *occ)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_init(&occ->lock);
|
||||
occ->groups[0] = &occ->group;
|
||||
|
||||
/* no need to lock */
|
||||
rc = occ_poll(occ);
|
||||
if (rc == -ESHUTDOWN) {
|
||||
dev_info(occ->bus_dev, "host is not ready\n");
|
||||
return rc;
|
||||
} else if (rc < 0) {
|
||||
dev_err(occ->bus_dev,
|
||||
"failed to get OCC poll response=%02x: %d\n",
|
||||
occ->resp.return_status, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
occ->next_update = jiffies + OCC_UPDATE_FREQUENCY;
|
||||
occ_parse_poll_response(occ);
|
||||
|
||||
rc = occ_setup_sensor_attrs(occ);
|
||||
if (rc) {
|
||||
dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name,
|
||||
occ, occ->groups);
|
||||
if (IS_ERR(occ->hwmon)) {
|
||||
rc = PTR_ERR(occ->hwmon);
|
||||
dev_err(occ->bus_dev, "failed to register hwmon device: %d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = occ_setup_sysfs(occ);
|
||||
if (rc)
|
||||
dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc);
|
||||
@ -1195,6 +1226,15 @@ int occ_setup(struct occ *occ, const char *name)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(occ_setup);
|
||||
|
||||
void occ_shutdown(struct occ *occ)
|
||||
{
|
||||
occ_shutdown_sysfs(occ);
|
||||
|
||||
if (occ->hwmon)
|
||||
hwmon_device_unregister(occ->hwmon);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(occ_shutdown);
|
||||
|
||||
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
|
||||
MODULE_DESCRIPTION("Common OCC hwmon code");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -106,6 +106,7 @@ struct occ {
|
||||
struct attribute_group group;
|
||||
const struct attribute_group *groups[2];
|
||||
|
||||
bool active;
|
||||
int error; /* final transfer error after retry */
|
||||
int last_error; /* latest transfer error */
|
||||
unsigned int error_count; /* number of xfr errors observed */
|
||||
@ -123,9 +124,11 @@ struct occ {
|
||||
u8 prev_mode;
|
||||
};
|
||||
|
||||
int occ_setup(struct occ *occ, const char *name);
|
||||
int occ_active(struct occ *occ, bool active);
|
||||
int occ_setup(struct occ *occ);
|
||||
int occ_setup_sysfs(struct occ *occ);
|
||||
void occ_shutdown(struct occ *occ);
|
||||
void occ_shutdown_sysfs(struct occ *occ);
|
||||
void occ_sysfs_poll_done(struct occ *occ);
|
||||
int occ_update_response(struct occ *occ);
|
||||
|
||||
|
@ -223,7 +223,7 @@ static int p8_i2c_occ_probe(struct i2c_client *client)
|
||||
occ->poll_cmd_data = 0x10; /* P8 OCC poll data */
|
||||
occ->send_cmd = p8_i2c_occ_send_cmd;
|
||||
|
||||
return occ_setup(occ, "p8_occ");
|
||||
return occ_setup(occ);
|
||||
}
|
||||
|
||||
static int p8_i2c_occ_remove(struct i2c_client *client)
|
||||
|
@ -145,7 +145,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
|
||||
occ->poll_cmd_data = 0x20; /* P9 OCC poll data */
|
||||
occ->send_cmd = p9_sbe_occ_send_cmd;
|
||||
|
||||
rc = occ_setup(occ, "p9_occ");
|
||||
rc = occ_setup(occ);
|
||||
if (rc == -ESHUTDOWN)
|
||||
rc = -ENODEV; /* Host is shutdown, don't spew errors */
|
||||
|
||||
|
@ -6,13 +6,13 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/* OCC status register */
|
||||
#define OCC_STAT_MASTER BIT(7)
|
||||
#define OCC_STAT_ACTIVE BIT(0)
|
||||
|
||||
/* OCC extended status register */
|
||||
#define OCC_EXT_STAT_DVFS_OT BIT(7)
|
||||
@ -22,6 +22,25 @@
|
||||
#define OCC_EXT_STAT_DVFS_VDD BIT(3)
|
||||
#define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0)
|
||||
|
||||
static ssize_t occ_active_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
bool active;
|
||||
struct occ *occ = dev_get_drvdata(dev);
|
||||
|
||||
rc = kstrtobool(buf, &active);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = occ_active(occ, active);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t occ_sysfs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
@ -31,54 +50,64 @@ static ssize_t occ_sysfs_show(struct device *dev,
|
||||
struct occ_poll_response_header *header;
|
||||
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
|
||||
|
||||
rc = occ_update_response(occ);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (occ->active) {
|
||||
rc = occ_update_response(occ);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
header = (struct occ_poll_response_header *)occ->resp.data;
|
||||
header = (struct occ_poll_response_header *)occ->resp.data;
|
||||
|
||||
switch (sattr->index) {
|
||||
case 0:
|
||||
val = !!(header->status & OCC_STAT_MASTER);
|
||||
break;
|
||||
case 1:
|
||||
val = !!(header->status & OCC_STAT_ACTIVE);
|
||||
break;
|
||||
case 2:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT);
|
||||
break;
|
||||
case 3:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER);
|
||||
break;
|
||||
case 4:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE);
|
||||
break;
|
||||
case 5:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP);
|
||||
break;
|
||||
case 6:
|
||||
val = header->occ_state;
|
||||
break;
|
||||
case 7:
|
||||
if (header->status & OCC_STAT_MASTER)
|
||||
val = hweight8(header->occs_present);
|
||||
else
|
||||
switch (sattr->index) {
|
||||
case 0:
|
||||
val = !!(header->status & OCC_STAT_MASTER);
|
||||
break;
|
||||
case 1:
|
||||
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;
|
||||
break;
|
||||
case 2:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT);
|
||||
break;
|
||||
case 3:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER);
|
||||
break;
|
||||
case 4:
|
||||
val = !!(header->ext_status &
|
||||
OCC_EXT_STAT_MEM_THROTTLE);
|
||||
break;
|
||||
case 5:
|
||||
val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP);
|
||||
break;
|
||||
case 6:
|
||||
val = header->occ_state;
|
||||
break;
|
||||
case 7:
|
||||
if (header->status & OCC_STAT_MASTER)
|
||||
val = hweight8(header->occs_present);
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (sattr->index == 1)
|
||||
val = 0;
|
||||
else if (sattr->index <= 11)
|
||||
val = -ENODATA;
|
||||
else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "%d\n", val);
|
||||
@ -95,7 +124,8 @@ static ssize_t occ_error_show(struct device *dev,
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(occ_active, 0644, occ_sysfs_show, occ_active_store,
|
||||
1);
|
||||
static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4);
|
||||
@ -139,7 +169,7 @@ void occ_sysfs_poll_done(struct occ *occ)
|
||||
* On the first poll response, we haven't yet created the sysfs
|
||||
* attributes, so don't make any notify calls.
|
||||
*/
|
||||
if (!occ->hwmon)
|
||||
if (!occ->active)
|
||||
goto done;
|
||||
|
||||
if ((header->status & OCC_STAT_MASTER) !=
|
||||
@ -148,12 +178,6 @@ void occ_sysfs_poll_done(struct occ *occ)
|
||||
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
|
||||
}
|
||||
|
||||
if ((header->status & OCC_STAT_ACTIVE) !=
|
||||
(occ->prev_stat & OCC_STAT_ACTIVE)) {
|
||||
name = sensor_dev_attr_occ_active.dev_attr.attr.name;
|
||||
sysfs_notify(&occ->bus_dev->kobj, NULL, name);
|
||||
}
|
||||
|
||||
if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) !=
|
||||
(occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) {
|
||||
name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name;
|
||||
@ -227,8 +251,7 @@ int occ_setup_sysfs(struct occ *occ)
|
||||
return sysfs_create_group(&occ->bus_dev->kobj, &occ_sysfs);
|
||||
}
|
||||
|
||||
void occ_shutdown(struct occ *occ)
|
||||
void occ_shutdown_sysfs(struct occ *occ)
|
||||
{
|
||||
sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(occ_shutdown);
|
||||
|
@ -447,29 +447,23 @@ static const struct hwmon_ops peci_cputemp_ops = {
|
||||
.read = cputemp_read,
|
||||
};
|
||||
|
||||
static const u32 peci_cputemp_temp_channel_config[] = {
|
||||
/* Die temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST,
|
||||
/* DTS margin */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST,
|
||||
/* Tcontrol temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT,
|
||||
/* Tthrottle temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
/* Tjmax temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
/* Core temperature - for all core channels */
|
||||
[channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info peci_cputemp_temp_channel = {
|
||||
.type = hwmon_temp,
|
||||
.config = peci_cputemp_temp_channel_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *peci_cputemp_info[] = {
|
||||
&peci_cputemp_temp_channel,
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
/* Die temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_CRIT_HYST,
|
||||
/* DTS margin */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_CRIT_HYST,
|
||||
/* Tcontrol temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT,
|
||||
/* Tthrottle temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
/* Tjmax temperature */
|
||||
HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
/* Core temperature - for all core channels */
|
||||
[channel_core ... CPUTEMP_CHANNEL_NUMS - 1] =
|
||||
HWMON_T_LABEL | HWMON_T_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
@ -219,7 +220,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv)
|
||||
int chan_rank_max = priv->gen_info->chan_rank_max;
|
||||
int dimm_idx_max = priv->gen_info->dimm_idx_max;
|
||||
u32 chan_rank_empty = 0;
|
||||
u64 dimm_mask = 0;
|
||||
u32 dimm_mask = 0;
|
||||
int chan_rank, dimm_idx, ret;
|
||||
u32 pcs;
|
||||
|
||||
@ -278,9 +279,9 @@ static int check_populated_dimms(struct peci_dimmtemp *priv)
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
dev_dbg(priv->dev, "Scanned populated DIMMs: %#llx\n", dimm_mask);
|
||||
dev_dbg(priv->dev, "Scanned populated DIMMs: %#x\n", dimm_mask);
|
||||
|
||||
bitmap_from_u64(priv->dimm_mask, dimm_mask);
|
||||
bitmap_from_arr32(priv->dimm_mask, &dimm_mask, DIMM_NUMS_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -299,18 +300,10 @@ static int create_dimm_temp_label(struct peci_dimmtemp *priv, int chan)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u32 peci_dimmtemp_temp_channel_config[] = {
|
||||
[0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info peci_dimmtemp_temp_channel = {
|
||||
.type = hwmon_temp,
|
||||
.config = peci_dimmtemp_temp_channel_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *peci_dimmtemp_temp_info[] = {
|
||||
&peci_dimmtemp_temp_channel,
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
[0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL |
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT),
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -378,13 +371,6 @@ static void create_dimm_temp_info_delayed(struct work_struct *work)
|
||||
dev_err(priv->dev, "Failed to populate DIMM temp info\n");
|
||||
}
|
||||
|
||||
static void remove_delayed_work(void *_priv)
|
||||
{
|
||||
struct peci_dimmtemp *priv = _priv;
|
||||
|
||||
cancel_delayed_work_sync(&priv->detect_work);
|
||||
}
|
||||
|
||||
static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct device *dev = &adev->dev;
|
||||
@ -415,9 +401,8 @@ static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxil
|
||||
"Unexpected PECI revision %#x, some features may be unavailable\n",
|
||||
peci_dev->info.peci_revision);
|
||||
|
||||
INIT_DELAYED_WORK(&priv->detect_work, create_dimm_temp_info_delayed);
|
||||
|
||||
ret = devm_add_action_or_reset(priv->dev, remove_delayed_work, priv);
|
||||
ret = devm_delayed_work_autocancel(priv->dev, &priv->detect_work,
|
||||
create_dimm_temp_info_delayed);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -228,10 +228,10 @@ config SENSORS_MAX16064
|
||||
be called max16064.
|
||||
|
||||
config SENSORS_MAX16601
|
||||
tristate "Maxim MAX16508, MAX16601"
|
||||
tristate "Maxim MAX16508, MAX16601, MAX16602"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Maxim
|
||||
MAX16508 and MAX16601.
|
||||
MAX16508, MAX16601 and MAX16602.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called max16601.
|
||||
@ -408,6 +408,15 @@ config SENSORS_UCD9200
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9200.
|
||||
|
||||
config SENSORS_XDPE152
|
||||
tristate "Infineon XDPE152 family"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Infineon
|
||||
XDPE15284, XDPE152C4, device.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called xdpe152c4.
|
||||
|
||||
config SENSORS_XDPE122
|
||||
tristate "Infineon XDPE122 family"
|
||||
help
|
||||
|
@ -43,5 +43,6 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
|
||||
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
|
||||
obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o
|
||||
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
|
||||
obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o
|
||||
|
@ -196,6 +196,17 @@ static int ltc_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
return pmbus_read_byte_data(client, page, reg);
|
||||
}
|
||||
|
||||
static int ltc_write_byte_data(struct i2c_client *client, int page, int reg, u8 value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ltc_wait_ready(client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return pmbus_write_byte_data(client, page, reg, value);
|
||||
}
|
||||
|
||||
static int ltc_write_byte(struct i2c_client *client, int page, u8 byte)
|
||||
{
|
||||
int ret;
|
||||
@ -681,6 +692,7 @@ static int ltc2978_probe(struct i2c_client *client)
|
||||
info = &data->info;
|
||||
info->write_word_data = ltc2978_write_word_data;
|
||||
info->write_byte = ltc_write_byte;
|
||||
info->write_byte_data = ltc_write_byte_data;
|
||||
info->read_word_data = ltc_read_word_data;
|
||||
info->read_byte_data = ltc_read_byte_data;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Hardware monitoring driver for Maxim MAX16508 and MAX16601.
|
||||
* Hardware monitoring driver for Maxim MAX16508, MAX16601 and MAX16602.
|
||||
*
|
||||
* Implementation notes:
|
||||
*
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { max16508, max16601 };
|
||||
enum chips { max16508, max16601, max16602 };
|
||||
|
||||
#define REG_DEFAULT_NUM_POP 0xc4
|
||||
#define REG_SETPT_DVID 0xd1
|
||||
@ -202,7 +202,7 @@ static int max16601_identify(struct i2c_client *client,
|
||||
else
|
||||
info->vrm_version[0] = vr12;
|
||||
|
||||
if (data->id != max16601)
|
||||
if (data->id != max16601 && data->id != max16602)
|
||||
return 0;
|
||||
|
||||
reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP);
|
||||
@ -264,6 +264,7 @@ static void max16601_remove(void *_data)
|
||||
static const struct i2c_device_id max16601_id[] = {
|
||||
{"max16508", max16508},
|
||||
{"max16601", max16601},
|
||||
{"max16602", max16602},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max16601_id);
|
||||
@ -280,13 +281,15 @@ static int max16601_get_id(struct i2c_client *client)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx"
|
||||
* or "MAX16500y.xx".
|
||||
* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" or
|
||||
* MAX16602y.xx or "MAX16500y.xx".cdxxcccccccccc
|
||||
*/
|
||||
if (!strncmp(buf, "MAX16500", 8)) {
|
||||
id = max16508;
|
||||
} else if (!strncmp(buf, "MAX16601", 8)) {
|
||||
id = max16601;
|
||||
} else if (!strncmp(buf, "MAX16602", 8)) {
|
||||
id = max16602;
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
dev_err(dev, "Unsupported chip '%s'\n", buf);
|
||||
|
@ -438,6 +438,8 @@ struct pmbus_driver_info {
|
||||
int (*read_byte_data)(struct i2c_client *client, int page, int reg);
|
||||
int (*read_word_data)(struct i2c_client *client, int page, int phase,
|
||||
int reg);
|
||||
int (*write_byte_data)(struct i2c_client *client, int page, int reg,
|
||||
u8 byte);
|
||||
int (*write_word_data)(struct i2c_client *client, int page, int reg,
|
||||
u16 word);
|
||||
int (*write_byte)(struct i2c_client *client, int page, u8 value);
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include <linux/pmbus.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/thermal.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
/*
|
||||
@ -276,6 +278,42 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
return pmbus_write_word_data(client, page, reg, word);
|
||||
}
|
||||
|
||||
/*
|
||||
* _pmbus_write_byte_data() is similar to pmbus_write_byte_data(), but checks if
|
||||
* a device specific mapping function exists and calls it if necessary.
|
||||
*/
|
||||
static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, u8 value)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
const struct pmbus_driver_info *info = data->info;
|
||||
int status;
|
||||
|
||||
if (info->write_byte_data) {
|
||||
status = info->write_byte_data(client, page, reg, value);
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
return pmbus_write_byte_data(client, page, reg, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if
|
||||
* a device specific mapping function exists and calls it if necessary.
|
||||
*/
|
||||
static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
const struct pmbus_driver_info *info = data->info;
|
||||
int status;
|
||||
|
||||
if (info->read_byte_data) {
|
||||
status = info->read_byte_data(client, page, reg);
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
return pmbus_read_byte_data(client, page, reg);
|
||||
}
|
||||
|
||||
int pmbus_update_fan(struct i2c_client *client, int page, int id,
|
||||
u8 config, u8 mask, u16 command)
|
||||
{
|
||||
@ -283,14 +321,14 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id,
|
||||
int rv;
|
||||
u8 to;
|
||||
|
||||
from = pmbus_read_byte_data(client, page,
|
||||
from = _pmbus_read_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id]);
|
||||
if (from < 0)
|
||||
return from;
|
||||
|
||||
to = (from & ~mask) | (config & mask);
|
||||
if (to != from) {
|
||||
rv = pmbus_write_byte_data(client, page,
|
||||
rv = _pmbus_write_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id], to);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
@ -390,37 +428,19 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
|
||||
unsigned int tmp;
|
||||
int rv;
|
||||
|
||||
rv = pmbus_read_byte_data(client, page, reg);
|
||||
rv = _pmbus_read_byte_data(client, page, reg);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
tmp = (rv & ~mask) | (value & mask);
|
||||
|
||||
if (tmp != rv)
|
||||
rv = pmbus_write_byte_data(client, page, reg, tmp);
|
||||
rv = _pmbus_write_byte_data(client, page, reg, tmp);
|
||||
|
||||
return rv;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS);
|
||||
|
||||
/*
|
||||
* _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if
|
||||
* a device specific mapping function exists and calls it if necessary.
|
||||
*/
|
||||
static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
const struct pmbus_driver_info *info = data->info;
|
||||
int status;
|
||||
|
||||
if (info->read_byte_data) {
|
||||
status = info->read_byte_data(client, page, reg);
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
return pmbus_read_byte_data(client, page, reg);
|
||||
}
|
||||
|
||||
static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page,
|
||||
int reg)
|
||||
{
|
||||
@ -455,7 +475,7 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id,
|
||||
return s->data;
|
||||
}
|
||||
|
||||
config = pmbus_read_byte_data(client, page,
|
||||
config = _pmbus_read_byte_data(client, page,
|
||||
pmbus_fan_config_registers[id]);
|
||||
if (config < 0)
|
||||
return config;
|
||||
@ -912,7 +932,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b,
|
||||
|
||||
regval = status & mask;
|
||||
if (regval) {
|
||||
ret = pmbus_write_byte_data(client, page, reg, regval);
|
||||
ret = _pmbus_write_byte_data(client, page, reg, regval);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
@ -1083,6 +1103,68 @@ static int pmbus_add_boolean(struct pmbus_data *data,
|
||||
return pmbus_add_attribute(data, &a->dev_attr.attr);
|
||||
}
|
||||
|
||||
/* of thermal for pmbus temperature sensors */
|
||||
struct pmbus_thermal_data {
|
||||
struct pmbus_data *pmbus_data;
|
||||
struct pmbus_sensor *sensor;
|
||||
};
|
||||
|
||||
static int pmbus_thermal_get_temp(void *data, int *temp)
|
||||
{
|
||||
struct pmbus_thermal_data *tdata = data;
|
||||
struct pmbus_sensor *sensor = tdata->sensor;
|
||||
struct pmbus_data *pmbus_data = tdata->pmbus_data;
|
||||
struct i2c_client *client = to_i2c_client(pmbus_data->dev);
|
||||
struct device *dev = pmbus_data->hwmon_dev;
|
||||
int ret = 0;
|
||||
|
||||
if (!dev) {
|
||||
/* May not even get to hwmon yet */
|
||||
*temp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&pmbus_data->update_lock);
|
||||
pmbus_update_sensor_data(client, sensor);
|
||||
if (sensor->data < 0)
|
||||
ret = sensor->data;
|
||||
else
|
||||
*temp = (int)pmbus_reg2data(pmbus_data, sensor);
|
||||
mutex_unlock(&pmbus_data->update_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_of_device_ops pmbus_thermal_ops = {
|
||||
.get_temp = pmbus_thermal_get_temp,
|
||||
};
|
||||
|
||||
static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data,
|
||||
struct pmbus_sensor *sensor, int index)
|
||||
{
|
||||
struct device *dev = pmbus_data->dev;
|
||||
struct pmbus_thermal_data *tdata;
|
||||
struct thermal_zone_device *tzd;
|
||||
|
||||
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
|
||||
if (!tdata)
|
||||
return -ENOMEM;
|
||||
|
||||
tdata->sensor = sensor;
|
||||
tdata->pmbus_data = pmbus_data;
|
||||
|
||||
tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata,
|
||||
&pmbus_thermal_ops);
|
||||
/*
|
||||
* If CONFIG_THERMAL_OF is disabled, this returns -ENODEV,
|
||||
* so ignore that error but forward any other error.
|
||||
*/
|
||||
if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
|
||||
return PTR_ERR(tzd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
||||
const char *name, const char *type,
|
||||
int seq, int page, int phase,
|
||||
@ -1126,6 +1208,10 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
||||
sensor->next = data->sensors;
|
||||
data->sensors = sensor;
|
||||
|
||||
/* temperature sensors with _input values are registered with thermal */
|
||||
if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0)
|
||||
pmbus_thermal_add_sensor(data, sensor, seq);
|
||||
|
||||
return sensor;
|
||||
}
|
||||
|
||||
@ -2308,6 +2394,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
struct device *dev = &client->dev;
|
||||
int page, ret;
|
||||
|
||||
/*
|
||||
* Figure out if PEC is enabled before accessing any other register.
|
||||
* Make sure PEC is disabled, will be enabled later if needed.
|
||||
*/
|
||||
client->flags &= ~I2C_CLIENT_PEC;
|
||||
|
||||
/* Enable PEC if the controller and bus supports it */
|
||||
if (!(data->flags & PMBUS_NO_CAPABILITY)) {
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
|
||||
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC))
|
||||
client->flags |= I2C_CLIENT_PEC;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Some PMBus chips don't support PMBUS_STATUS_WORD, so try
|
||||
* to use PMBUS_STATUS_BYTE instead if that is the case.
|
||||
@ -2326,19 +2427,6 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
data->has_status_word = true;
|
||||
}
|
||||
|
||||
/* Make sure PEC is disabled, will be enabled later if needed */
|
||||
client->flags &= ~I2C_CLIENT_PEC;
|
||||
|
||||
/* Enable PEC if the controller and bus supports it */
|
||||
if (!(data->flags & PMBUS_NO_CAPABILITY)) {
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
|
||||
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
|
||||
client->flags |= I2C_CLIENT_PEC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the chip is write protected. If it is, we can not clear
|
||||
* faults, and we should not try it. Also, in that case, writes into
|
||||
@ -2399,7 +2487,7 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev)
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION);
|
||||
ret = _pmbus_read_byte_data(client, page, PMBUS_OPERATION);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
if (ret < 0)
|
||||
@ -2498,7 +2586,7 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned
|
||||
if (!(func & cat->func))
|
||||
continue;
|
||||
|
||||
status = pmbus_read_byte_data(client, page, cat->reg);
|
||||
status = _pmbus_read_byte_data(client, page, cat->reg);
|
||||
if (status < 0) {
|
||||
mutex_unlock(&data->update_lock);
|
||||
return status;
|
||||
@ -2548,11 +2636,78 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmbus_regulator_get_voltage(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);
|
||||
struct pmbus_sensor s = {
|
||||
.page = rdev_get_id(rdev),
|
||||
.class = PSC_VOLTAGE_OUT,
|
||||
.convert = true,
|
||||
};
|
||||
|
||||
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
|
||||
if (s.data < 0)
|
||||
return s.data;
|
||||
|
||||
return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
|
||||
}
|
||||
|
||||
static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
|
||||
int max_uv, unsigned int *selector)
|
||||
{
|
||||
struct device *dev = rdev_get_dev(rdev);
|
||||
struct i2c_client *client = to_i2c_client(dev->parent);
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
struct pmbus_sensor s = {
|
||||
.page = rdev_get_id(rdev),
|
||||
.class = PSC_VOLTAGE_OUT,
|
||||
.convert = true,
|
||||
.data = -1,
|
||||
};
|
||||
int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
|
||||
int low, high;
|
||||
|
||||
*selector = 0;
|
||||
|
||||
if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MIN))
|
||||
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MIN);
|
||||
if (s.data < 0) {
|
||||
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_LOW);
|
||||
if (s.data < 0)
|
||||
return s.data;
|
||||
}
|
||||
low = pmbus_reg2data(data, &s);
|
||||
|
||||
s.data = -1;
|
||||
if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MAX))
|
||||
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MAX);
|
||||
if (s.data < 0) {
|
||||
s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_HIGH);
|
||||
if (s.data < 0)
|
||||
return s.data;
|
||||
}
|
||||
high = pmbus_reg2data(data, &s);
|
||||
|
||||
/* Make sure we are within margins */
|
||||
if (low > val)
|
||||
val = low;
|
||||
if (high < val)
|
||||
val = high;
|
||||
|
||||
val = pmbus_data2reg(data, &s, val);
|
||||
|
||||
return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
|
||||
}
|
||||
|
||||
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,
|
||||
.get_voltage = pmbus_regulator_get_voltage,
|
||||
.set_voltage = pmbus_regulator_set_voltage,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS);
|
||||
|
||||
|
75
drivers/hwmon/pmbus/xdpe152c4.c
Normal file
75
drivers/hwmon/pmbus/xdpe152c4.c
Normal file
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers
|
||||
*
|
||||
* Copyright (c) 2022 Infineon Technologies. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define XDPE152_PAGE_NUM 2
|
||||
|
||||
static struct pmbus_driver_info xdpe152_info = {
|
||||
.pages = XDPE152_PAGE_NUM,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_CURRENT_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_POWER] = linear,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
};
|
||||
|
||||
static int xdpe152_probe(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
|
||||
info = devm_kmemdup(&client->dev, &xdpe152_info, sizeof(*info),
|
||||
GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id xdpe152_id[] = {
|
||||
{"xdpe152c4", 0},
|
||||
{"xdpe15284", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, xdpe152_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused xdpe152_of_match[] = {
|
||||
{.compatible = "infineon,xdpe152c4"},
|
||||
{.compatible = "infineon,xdpe15284"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xdpe152_of_match);
|
||||
|
||||
static struct i2c_driver xdpe152_driver = {
|
||||
.driver = {
|
||||
.name = "xdpe152c4",
|
||||
.of_match_table = of_match_ptr(xdpe152_of_match),
|
||||
},
|
||||
.probe_new = xdpe152_probe,
|
||||
.id_table = xdpe152_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(xdpe152_driver);
|
||||
|
||||
MODULE_AUTHOR("Greg Schwendimann <greg.schwendimann@infineon.com>");
|
||||
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE152 family");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS(PMBUS);
|
@ -49,16 +49,6 @@ struct pwm_fan_ctx {
|
||||
struct hwmon_channel_info fan_channel;
|
||||
};
|
||||
|
||||
static const u32 pwm_fan_channel_config_pwm[] = {
|
||||
HWMON_PWM_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info pwm_fan_channel_pwm = {
|
||||
.type = hwmon_pwm,
|
||||
.config = pwm_fan_channel_config_pwm,
|
||||
};
|
||||
|
||||
/* This handler assumes self resetting edge triggered interrupt. */
|
||||
static irqreturn_t pulse_handler(int irq, void *dev_id)
|
||||
{
|
||||
@ -387,7 +377,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
if (!channels)
|
||||
return -ENOMEM;
|
||||
|
||||
channels[0] = &pwm_fan_channel_pwm;
|
||||
channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT);
|
||||
|
||||
for (i = 0; i < ctx->tach_count; i++) {
|
||||
struct pwm_fan_tach *tach = &ctx->tachs[i];
|
||||
|
@ -54,7 +54,7 @@ static int sl28cpld_hwmon_read(struct device *dev,
|
||||
|
||||
/*
|
||||
* The counter period is 1000ms and the sysfs specification
|
||||
* says we should asssume 2 pulses per revolution.
|
||||
* says we should assume 2 pulses per revolution.
|
||||
*/
|
||||
value *= 60 / 2;
|
||||
|
||||
@ -67,18 +67,8 @@ static int sl28cpld_hwmon_read(struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u32 sl28cpld_hwmon_fan_config[] = {
|
||||
HWMON_F_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info sl28cpld_hwmon_fan = {
|
||||
.type = hwmon_fan,
|
||||
.config = sl28cpld_hwmon_fan_config,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = {
|
||||
&sl28cpld_hwmon_fan,
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -41,6 +41,8 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 };
|
||||
#define TMP401_STATUS 0x02
|
||||
#define TMP401_CONFIG 0x03
|
||||
#define TMP401_CONVERSION_RATE 0x04
|
||||
#define TMP4XX_N_FACTOR_REG 0x18
|
||||
#define TMP43X_BETA_RANGE 0x25
|
||||
#define TMP401_TEMP_CRIT_HYST 0x21
|
||||
#define TMP401_MANUFACTURER_ID_REG 0xFE
|
||||
#define TMP401_DEVICE_ID_REG 0xFF
|
||||
@ -543,6 +545,8 @@ static int tmp401_init_client(struct tmp401_data *data)
|
||||
struct regmap *regmap = data->regmap;
|
||||
u32 config, config_orig;
|
||||
int ret;
|
||||
u32 val = 0;
|
||||
s32 nfactor = 0;
|
||||
|
||||
/* Set conversion rate to 2 Hz */
|
||||
ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5);
|
||||
@ -557,12 +561,50 @@ static int tmp401_init_client(struct tmp401_data *data)
|
||||
config_orig = config;
|
||||
config &= ~TMP401_CONFIG_SHUTDOWN;
|
||||
|
||||
if (of_property_read_bool(data->client->dev.of_node, "ti,extended-range-enable")) {
|
||||
/* Enable measurement over extended temperature range */
|
||||
config |= TMP401_CONFIG_RANGE;
|
||||
}
|
||||
|
||||
data->extended_range = !!(config & TMP401_CONFIG_RANGE);
|
||||
|
||||
if (config != config_orig)
|
||||
if (config != config_orig) {
|
||||
ret = regmap_write(regmap, TMP401_CONFIG, config);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
ret = of_property_read_u32(data->client->dev.of_node, "ti,n-factor", &nfactor);
|
||||
if (!ret) {
|
||||
if (data->kind == tmp401) {
|
||||
dev_err(&data->client->dev, "ti,tmp401 does not support n-factor correction\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (nfactor < -128 || nfactor > 127) {
|
||||
dev_err(&data->client->dev, "n-factor is invalid (%d)\n", nfactor);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = regmap_write(regmap, TMP4XX_N_FACTOR_REG, (unsigned int)nfactor);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(data->client->dev.of_node, "ti,beta-compensation", &val);
|
||||
if (!ret) {
|
||||
if (data->kind == tmp401 || data->kind == tmp411) {
|
||||
dev_err(&data->client->dev, "ti,tmp401 or ti,tmp411 does not support beta compensation\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (val > 15) {
|
||||
dev_err(&data->client->dev, "beta-compensation is invalid (%u)\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = regmap_write(regmap, TMP43X_BETA_RANGE, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp401_detect(struct i2c_client *client,
|
||||
|
@ -149,8 +149,8 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
INIT_LIST_HEAD(&hwmon->tz_list);
|
||||
strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
|
||||
strreplace(hwmon->type, '-', '_');
|
||||
hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
|
||||
hwmon, NULL, NULL);
|
||||
hwmon->device = hwmon_device_register_for_thermal(&tz->device,
|
||||
hwmon->type, hwmon);
|
||||
if (IS_ERR(hwmon->device)) {
|
||||
result = PTR_ERR(hwmon->device);
|
||||
goto free_mem;
|
||||
@ -277,3 +277,5 @@ int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
|
||||
|
||||
MODULE_IMPORT_NS(HWMON_THERMAL);
|
||||
|
@ -450,6 +450,9 @@ hwmon_device_register_with_info(struct device *dev,
|
||||
const struct hwmon_chip_info *info,
|
||||
const struct attribute_group **extra_groups);
|
||||
struct device *
|
||||
hwmon_device_register_for_thermal(struct device *dev, const char *name,
|
||||
void *drvdata);
|
||||
struct device *
|
||||
devm_hwmon_device_register_with_info(struct device *dev,
|
||||
const char *name, void *drvdata,
|
||||
const struct hwmon_chip_info *info,
|
||||
@ -461,6 +464,9 @@ void devm_hwmon_device_unregister(struct device *dev);
|
||||
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel);
|
||||
|
||||
char *hwmon_sanitize_name(const char *name);
|
||||
char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
|
||||
|
||||
/**
|
||||
* hwmon_is_bad_char - Is the char invalid in a hwmon name
|
||||
* @ch: the char to be considered
|
||||
|
35
include/linux/polynomial.h
Normal file
35
include/linux/polynomial.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
|
||||
*/
|
||||
|
||||
#ifndef _POLYNOMIAL_H
|
||||
#define _POLYNOMIAL_H
|
||||
|
||||
/*
|
||||
* struct polynomial_term - one term descriptor of a polynomial
|
||||
* @deg: degree of the term.
|
||||
* @coef: multiplication factor of the term.
|
||||
* @divider: distributed divider per each degree.
|
||||
* @divider_leftover: divider leftover, which couldn't be redistributed.
|
||||
*/
|
||||
struct polynomial_term {
|
||||
unsigned int deg;
|
||||
long coef;
|
||||
long divider;
|
||||
long divider_leftover;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct polynomial - a polynomial descriptor
|
||||
* @total_divider: total data divider.
|
||||
* @terms: polynomial terms, last term must have degree of 0
|
||||
*/
|
||||
struct polynomial {
|
||||
long total_divider;
|
||||
struct polynomial_term terms[];
|
||||
};
|
||||
|
||||
long polynomial_calc(const struct polynomial *poly, long data);
|
||||
|
||||
#endif
|
@ -737,3 +737,6 @@ config PLDMFW
|
||||
|
||||
config ASN1_ENCODER
|
||||
tristate
|
||||
|
||||
config POLYNOMIAL
|
||||
tristate
|
||||
|
@ -263,6 +263,8 @@ obj-$(CONFIG_MEMREGION) += memregion.o
|
||||
obj-$(CONFIG_STMP_DEVICE) += stmp_device.o
|
||||
obj-$(CONFIG_IRQ_POLL) += irq_poll.o
|
||||
|
||||
obj-$(CONFIG_POLYNOMIAL) += polynomial.o
|
||||
|
||||
# stackdepot.c should not be instrumented or call instrumented functions.
|
||||
# Prevent the compiler from calling builtins like memcmp() or bcmp() from this
|
||||
# file.
|
||||
|
108
lib/polynomial.c
Normal file
108
lib/polynomial.c
Normal file
@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Generic polynomial calculation using integer coefficients.
|
||||
*
|
||||
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
|
||||
*
|
||||
* Authors:
|
||||
* Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
|
||||
* Serge Semin <Sergey.Semin@baikalelectronics.ru>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/polynomial.h>
|
||||
|
||||
/*
|
||||
* Originally this was part of drivers/hwmon/bt1-pvt.c.
|
||||
* There the following conversion is used and should serve as an example here:
|
||||
*
|
||||
* The original translation formulae of the temperature (in degrees of Celsius)
|
||||
* to PVT data and vice-versa are following:
|
||||
*
|
||||
* N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) +
|
||||
* 1.7204e2
|
||||
* T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) +
|
||||
* 3.1020e-1*(N^1) - 4.838e1
|
||||
*
|
||||
* where T = [-48.380, 147.438]C and N = [0, 1023].
|
||||
*
|
||||
* They must be accordingly altered to be suitable for the integer arithmetics.
|
||||
* The technique is called 'factor redistribution', which just makes sure the
|
||||
* multiplications and divisions are made so to have a result of the operations
|
||||
* within the integer numbers limit. In addition we need to translate the
|
||||
* formulae to accept millidegrees of Celsius. Here what they look like after
|
||||
* the alterations:
|
||||
*
|
||||
* N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T +
|
||||
* 17204e2) / 1e4
|
||||
* T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D -
|
||||
* 48380
|
||||
* where T = [-48380, 147438] mC and N = [0, 1023].
|
||||
*
|
||||
* static const struct polynomial poly_temp_to_N = {
|
||||
* .total_divider = 10000,
|
||||
* .terms = {
|
||||
* {4, 18322, 10000, 10000},
|
||||
* {3, 2343, 10000, 10},
|
||||
* {2, 87018, 10000, 10},
|
||||
* {1, 39269, 1000, 1},
|
||||
* {0, 1720400, 1, 1}
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* static const struct polynomial poly_N_to_temp = {
|
||||
* .total_divider = 1,
|
||||
* .terms = {
|
||||
* {4, -16743, 1000, 1},
|
||||
* {3, 81542, 1000, 1},
|
||||
* {2, -182010, 1000, 1},
|
||||
* {1, 310200, 1000, 1},
|
||||
* {0, -48380, 1, 1}
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
|
||||
/**
|
||||
* polynomial_calc - calculate a polynomial using integer arithmetic
|
||||
*
|
||||
* @poly: pointer to the descriptor of the polynomial
|
||||
* @data: input value of the polynimal
|
||||
*
|
||||
* Calculate the result of a polynomial using only integer arithmetic. For
|
||||
* this to work without too much loss of precision the coefficients has to
|
||||
* be altered. This is called factor redistribution.
|
||||
*
|
||||
* Returns the result of the polynomial calculation.
|
||||
*/
|
||||
long polynomial_calc(const struct polynomial *poly, long data)
|
||||
{
|
||||
const struct polynomial_term *term = poly->terms;
|
||||
long total_divider = poly->total_divider ?: 1;
|
||||
long tmp, ret = 0;
|
||||
int deg;
|
||||
|
||||
/*
|
||||
* Here is the polynomial calculation function, which performs the
|
||||
* redistributed terms calculations. It's pretty straightforward.
|
||||
* We walk over each degree term up to the free one, and perform
|
||||
* the redistributed multiplication of the term coefficient, its
|
||||
* divider (as for the rationale fraction representation), data
|
||||
* power and the rational fraction divider leftover. Then all of
|
||||
* this is collected in a total sum variable, which value is
|
||||
* normalized by the total divider before being returned.
|
||||
*/
|
||||
do {
|
||||
tmp = term->coef;
|
||||
for (deg = 0; deg < term->deg; ++deg)
|
||||
tmp = mult_frac(tmp, data, term->divider);
|
||||
ret += tmp / term->divider_leftover;
|
||||
} while ((term++)->deg);
|
||||
|
||||
return ret / total_divider;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(polynomial_calc);
|
||||
|
||||
MODULE_DESCRIPTION("Generic polynomial calculations");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
x
Reference in New Issue
Block a user