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:
Linus Torvalds 2022-05-24 14:23:10 -07:00
commit 076f222a69
62 changed files with 5118 additions and 2354 deletions

View File

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

View File

@ -14,6 +14,7 @@ properties:
compatible:
enum:
- adi,adt75
- atmel,at30ts74
- dallas,ds1775
- dallas,ds75
- dallas,ds7505

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
return i8k_smm(&regs) ? : (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;

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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[] = {
&ltc2992_chip,
&ltc2992_in,
&ltc2992_curr,
&ltc2992_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
};

View File

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

File diff suppressed because it is too large Load Diff

252
drivers/hwmon/nct6775.h Normal file
View 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__ */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@ -737,3 +737,6 @@ config PLDMFW
config ASN1_ENCODER
tristate
config POLYNOMIAL
tristate

View File

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