mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
hwmon updates for v6.1
* New drivers - Driver for MAX31760 fan speed controller - Driver for TEXAS TPS546D24 Buck Converter - Driver for EMC2301/2/3/5 RPM-based PWM Fan Speed Controller * Removed drivers - Drop obsolete asus_wmi_ec_sensors driver * Cleanups, affecting various drivers - Use DEFINE_SIMPLE_DEV_PM_OPS where appropriate - Remove forward declarations - Move from strlcpy with unused retval to strscpy - Make use of devm_clk_get_enabled() - Drop devm_of_pwm_get() * Other notable cleanup and improvements - Support for additional USB devide ID and support for reporting of rail mode via debugfs added to corsair-psu driver - Support for aditional USB ID in nzxt-smart2 driver - Support for Aquacomputer High Flow Next in aquacomputer_d5next driver - Major cleanup of pwm-fan driver - Major cleanup of mr75203 driver, and added support for new device revision * Various other minor fixes and cleanups -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmM6CSsACgkQyx8mb86f mYEwFw//SvJ5vW/bS0kynaH2TICzwHDHjH7nBjXyIlqN3ILrrTKyFQgkfqIlqYT9 OYlXaKB6DqRBOZjLCYKNhDy13gdvOh0XBIsX/emqzFDmhGZ6yJtm0u+6CMp7UFGK 3KDf4LSkEJ3QhgOOiROsz1u6CSXdZI8THnIBRCrzve3nlamRaR6n8tvvf6Fqspsa WQTDg1g2dOjpP6bi1EKre/KiBfjxrzy2/r9WGQr1J7LZb598/F7CPGztJU6P27TE gHltO4wcAgBgnL3ODBmLPDHyAa91fTmfHCvKRUfaAlZll8ucfXJnlfZVV8YW9ptC Fm00Wmy8aFX57ziXFlJ7LOFdjdCKyTRVp5ferPHCEuzmztWLdWauwiVV7GkeqzIB /seHcC59kX7yGKgP2sYV9SlD0GWa2Ax2rJT8gh78ozUgXMUlKkVPSly5ooOaKkEV nNRLJB40tAK43wwSWNVSw0wPXK0fWfxmyF1AMowNEoC12zDLO0JAlw/sVXj4GneG sq0ZN6vyAaDdcX3RKan6jTaus2AdHf3vZHqy/xW59Jo+JqoxF7zo1MLRDP2EekWn x0HbXT5ghWrmntN7mgRPvN1sgebK8L+yGttuJ/guFuYTTy8FrXOKtjhBCcvZHg7C tiMUAMTl7s6FujcuMEY84ZwwIDczLGpIwxXammIKJiHGBSeHsdk= =8u0w -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v6.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "New drivers: - Driver for MAX31760 fan speed controller - Driver for TEXAS TPS546D24 Buck Converter - Driver for EMC2301/2/3/5 RPM-based PWM Fan Speed Controller Removed drivers: - Drop obsolete asus_wmi_ec_sensors driver Cleanups, affecting various drivers: - Use DEFINE_SIMPLE_DEV_PM_OPS where appropriate - Remove forward declarations - Move from strlcpy with unused retval to strscpy - Make use of devm_clk_get_enabled() - Drop devm_of_pwm_get() Other notable cleanup and improvements: - Support for additional USB devide ID and support for reporting of rail mode via debugfs added to corsair-psu driver - Support for aditional USB ID in nzxt-smart2 driver - Support for Aquacomputer High Flow Next in aquacomputer_d5next driver - Major cleanup of pwm-fan driver - Major cleanup of mr75203 driver, and added support for new device revision And various other minor fixes and cleanups" * tag 'hwmon-for-v6.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (86 commits) hwmon: (corsair-psu) add USB id of new revision of the HX1000i psu hwmon: (pmbus/mp2888) Fix sensors readouts for MPS Multi-phase mp2888 controller dt-bindings: hwmon: sensirion,shtc1: Clean up spelling mistakes and grammar hwmon: (nct6683) remove unused variable in nct6683_create_attr_group hwmon: w83627hf: Reorder symbols to get rid of a few forward declarations hwmon: (ina3221) Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() hwmon: (w83627ehf) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (tmp108) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (tmp103) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (tmp102) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (pwm-fan) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (nct6775) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (max6639) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (max31730) witch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (max31722) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (ltc2947) Switch to EXPORT_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (lm90) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (it87) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (gpio-fan) Switch to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() hwmon: (adt7x10) Switch to EXPORT_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr() ...
This commit is contained in:
commit
0baf6dcc02
42
Documentation/devicetree/bindings/hwmon/adi,max31760.yaml
Normal file
42
Documentation/devicetree/bindings/hwmon/adi,max31760.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/adi,max31760.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices MAX31760 Fan-Speed Controller
|
||||
|
||||
maintainers:
|
||||
- Ibrahim Tilki <Ibrahim.Tilki@analog.com>
|
||||
|
||||
description: |
|
||||
Analog Devices MAX31760 Fan-Speed Controller
|
||||
https://datasheets.maximintegrated.com/en/ds/MAX31760.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,max31760
|
||||
|
||||
reg:
|
||||
description: I2C address of slave device.
|
||||
minimum: 0x50
|
||||
maximum: 0x57
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fan-controller@50 {
|
||||
reg = <0x50>;
|
||||
compatible = "adi,max31760";
|
||||
};
|
||||
};
|
@ -22,7 +22,7 @@ properties:
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: AHB reference clock
|
||||
- description: System reference clock
|
||||
|
||||
'#thermal-sensor-cells':
|
||||
const: 0
|
||||
@ -40,5 +40,5 @@ examples:
|
||||
compatible = "microchip,sparx5-temp";
|
||||
reg = <0x10508110 0xc>;
|
||||
#thermal-sensor-cells = <0>;
|
||||
clocks = <&ahb_clk>;
|
||||
clocks = <&sys_clk>;
|
||||
};
|
||||
|
@ -9,6 +9,32 @@ title: Moortec Semiconductor MR75203 PVT Controller bindings
|
||||
maintainers:
|
||||
- Rahul Tanwar <rtanwar@maxlinear.com>
|
||||
|
||||
description: |
|
||||
A Moortec PVT (Process, Voltage, Temperature) monitoring logic design can
|
||||
include many different units.
|
||||
Such a design will usually consists of several Moortec's embedded analog IPs,
|
||||
and a single Moortec controller (mr75203) to configure and control the IPs.
|
||||
|
||||
Some of the Moortec's analog hard IPs that can be used in a design:
|
||||
*) Temperature Sensor (TS) - used to monitor core temperature (e.g. mr74137).
|
||||
*) Voltage Monitor (VM) - used to monitor voltage levels (e.g. mr74138).
|
||||
*) Process Detector (PD) - used to assess silicon speed (e.g. mr74139).
|
||||
*) Delay Chain - ring oscillator connected to the PD, used to measure IO
|
||||
based transistors (e.g. mr76008 ring oscillator at 1.1V, mr76007 ring
|
||||
oscillator at 1.8V).
|
||||
*) Pre Scaler - provides divide-by-X scaling of input voltage, which can then
|
||||
be presented for VM for measurement within its range (e.g. mr76006 -
|
||||
divide by 2 pre-scaler).
|
||||
|
||||
TS, VM & PD also include a digital interface, which consists of configuration
|
||||
inputs and measurement outputs.
|
||||
|
||||
Some of the units have number of series, each series can have slightly
|
||||
different characteristics.
|
||||
|
||||
The mr75203 binding describes configuration for the controller unit, but also
|
||||
for some of the analog IPs.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: moortec,mr75203
|
||||
@ -44,12 +70,76 @@ properties:
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
moortec,vm-active-channels:
|
||||
description:
|
||||
Defines the number of channels per VM that are actually used and are
|
||||
connected to some input source.
|
||||
Maximum number of items - number of VMs.
|
||||
Maximum value of each item - number of channels.
|
||||
Minimum value of each item - 0 (which means entire VM sensor is not used).
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
|
||||
moortec,vm-pre-scaler-x2:
|
||||
description:
|
||||
Defines the channels that use a mr76006 pre-scaler to divide the input
|
||||
source by 2.
|
||||
The pre-scaler is used for input sources that exceed the VM input range.
|
||||
The driver uses this information to present to the user with the actual
|
||||
value of the voltage source.
|
||||
For channels that are not listed, no pre-scaler is assumed.
|
||||
Maximum number of items - total number of channels in all VMs.
|
||||
Each channel should not appear more than once.
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
|
||||
moortec,ts-series:
|
||||
description:
|
||||
Definition of the temperature equation and coefficients that shall be
|
||||
used to convert the digital output to value in milli-Celsius.
|
||||
minimum: 5
|
||||
maximum: 6
|
||||
default: 5
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
moortec,ts-coeff-g:
|
||||
description:
|
||||
G coefficient for temperature equation.
|
||||
Default for series 5 = 60000
|
||||
Default for series 6 = 57400
|
||||
multipleOf: 1000
|
||||
minimum: 1000
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
moortec,ts-coeff-h:
|
||||
description:
|
||||
H coefficient for temperature equation.
|
||||
Default for series 5 = 200000
|
||||
Default for series 6 = 249400
|
||||
multipleOf: 1000
|
||||
minimum: 1000
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
moortec,ts-coeff-cal5:
|
||||
description:
|
||||
cal5 coefficient for temperature equation.
|
||||
Default for series 5 = 4094
|
||||
Default for series 6 = 4096
|
||||
minimum: 1
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
moortec,ts-coeff-j:
|
||||
description:
|
||||
J coefficient for temperature equation.
|
||||
Default for series 5 = -100
|
||||
Default for series 6 = 0
|
||||
multipleOf: 1000
|
||||
maximum: 0
|
||||
$ref: /schemas/types.yaml#/definitions/int32
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- clocks
|
||||
- resets
|
||||
- "#thermal-sensor-cells"
|
||||
|
||||
additionalProperties: false
|
||||
@ -66,5 +156,9 @@ examples:
|
||||
intel,vm-map = [03 01 04 ff ff];
|
||||
clocks = <&osc0>;
|
||||
resets = <&rcu0 0x40 7>;
|
||||
moortec,vm-active-channels = /bits/ 8 <0x10 0x05>;
|
||||
moortec,vm-pre-scaler-x2 = /bits/ 8 <5 6 20>;
|
||||
moortec,ts-coeff-g = <61400>;
|
||||
moortec,ts-coeff-h = <253700>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ maintainers:
|
||||
- Christopher Ruehl chris.ruehl@gtsys.com.hk
|
||||
|
||||
description: |
|
||||
The SHTC1, SHTW1 and SHTC3 are digital humidity and temperature sensor
|
||||
The SHTC1, SHTW1 and SHTC3 are digital humidity and temperature sensors
|
||||
designed especially for battery-driven high-volume consumer electronics
|
||||
applications.
|
||||
For further information refere to Documentation/hwmon/shtc1.rst
|
||||
@ -31,13 +31,13 @@ properties:
|
||||
sensirion,blocking-io:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
If set, the driver hold the i2c bus until measurement is finished.
|
||||
If set, the driver holds the i2c bus until the measurement is finished.
|
||||
|
||||
sensirion,low-precision:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
If set, the sensor aquire data with low precision (not recommended).
|
||||
The driver aquire data with high precision by default.
|
||||
If set, the sensor acquires data with low precision (not recommended).
|
||||
The driver acquires data with high precision by default.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
|
@ -819,6 +819,8 @@ patternProperties:
|
||||
description: MNT Research GmbH
|
||||
"^modtronix,.*":
|
||||
description: Modtronix Engineering
|
||||
"^moortec,.*":
|
||||
description: Moortec Semiconductor Ltd.
|
||||
"^mosaixtech,.*":
|
||||
description: Mosaix Technologies, Inc.
|
||||
"^motorola,.*":
|
||||
|
@ -404,7 +404,6 @@ POWER
|
||||
|
||||
PWM
|
||||
devm_pwm_get()
|
||||
devm_of_pwm_get()
|
||||
devm_fwnode_pwm_get()
|
||||
|
||||
REGULATOR
|
||||
|
@ -40,8 +40,7 @@ after usage with pwm_free().
|
||||
|
||||
New users should use the pwm_get() function and pass to it the consumer
|
||||
device or a consumer name. pwm_put() is used to free the PWM device. Managed
|
||||
variants of the getter, devm_pwm_get(), devm_of_pwm_get(),
|
||||
devm_fwnode_pwm_get(), also exist.
|
||||
variants of the getter, devm_pwm_get() and devm_fwnode_pwm_get(), also exist.
|
||||
|
||||
After being requested, a PWM has to be configured using::
|
||||
|
||||
|
@ -10,6 +10,7 @@ Supported devices:
|
||||
* Aquacomputer Farbwerk 360 RGB controller
|
||||
* Aquacomputer Octo fan controller
|
||||
* Aquacomputer Quadro fan controller
|
||||
* Aquacomputer High Flow Next sensor
|
||||
|
||||
Author: Aleksa Savic
|
||||
|
||||
@ -20,10 +21,11 @@ This driver exposes hardware sensors of listed Aquacomputer devices, which
|
||||
communicate through proprietary USB HID protocols.
|
||||
|
||||
For the D5 Next pump, available sensors are pump and fan speed, power, voltage
|
||||
and current, as well as coolant temperature. Also available through debugfs are
|
||||
the serial number, firmware version and power-on count. Attaching a fan to it is
|
||||
optional and allows it to be controlled using temperature curves directly from the
|
||||
pump. If it's not connected, the fan-related sensors will report zeroes.
|
||||
and current, as well as coolant temperature and eight virtual temp sensors. Also
|
||||
available through debugfs are the serial number, firmware version and power-on
|
||||
count. Attaching a fan to it is optional and allows it to be controlled using
|
||||
temperature curves directly from the pump. If it's not connected, the fan-related
|
||||
sensors will report zeroes.
|
||||
|
||||
The pump can be configured either through software or via its physical
|
||||
interface. Configuring the pump through this driver is not implemented, as it
|
||||
@ -31,14 +33,23 @@ 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 Octo exposes four temperature sensors and eight PWM controllable fans, along
|
||||
with their speed (in RPM), power, voltage and current.
|
||||
The Octo exposes four physical and sixteen virtual temperature sensors, as well as
|
||||
eight PWM controllable fans, along with their speed (in RPM), power, voltage and
|
||||
current.
|
||||
|
||||
The Quadro exposes four temperature sensors, a flow sensor and four PWM controllable
|
||||
fans, along with their speed (in RPM), power, voltage and current.
|
||||
The Quadro exposes four physical and sixteen virtual temperature sensors, a flow
|
||||
sensor and four 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.
|
||||
The Farbwerk and Farbwerk 360 expose four temperature sensors. Additionally,
|
||||
sixteen virtual temperature sensors of the Farbwerk 360 are exposed.
|
||||
|
||||
The High Flow Next exposes +5V voltages, water quality, conductivity and flow readings.
|
||||
A temperature sensor can be connected to it, in which case it provides its reading
|
||||
and an estimation of the dissipated/absorbed power in the liquid cooling loop.
|
||||
|
||||
Depending on the device, not all sysfs and debugfs entries will be available.
|
||||
Writing to virtual temperature sensors is not currently supported.
|
||||
|
||||
Usage notes
|
||||
-----------
|
||||
@ -49,14 +60,14 @@ the kernel and supports hotswapping.
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
================ ==============================================
|
||||
temp[1-4]_input Temperature sensors (in millidegrees Celsius)
|
||||
================ ==============================================================
|
||||
temp[1-20]_input Physical/virtual temperature sensors (in millidegrees Celsius)
|
||||
fan[1-8]_input Pump/fan speed (in RPM) / Flow speed (in dL/h)
|
||||
power[1-8]_input Pump/fan power (in micro Watts)
|
||||
in[0-7]_input Pump/fan voltage (in milli Volts)
|
||||
curr[1-8]_input Pump/fan current (in milli Amperes)
|
||||
pwm[1-8] Fan PWM (0 - 255)
|
||||
================ ==============================================
|
||||
================ ==============================================================
|
||||
|
||||
Debugfs entries
|
||||
---------------
|
||||
|
@ -1,38 +0,0 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver asus_wmi_ec_sensors
|
||||
=================================
|
||||
|
||||
Supported boards:
|
||||
* PRIME X570-PRO,
|
||||
* Pro WS X570-ACE,
|
||||
* ROG CROSSHAIR VIII DARK HERO,
|
||||
* ROG CROSSHAIR VIII FORMULA,
|
||||
* ROG CROSSHAIR VIII HERO,
|
||||
* ROG STRIX B550-E GAMING,
|
||||
* ROG STRIX B550-I GAMING,
|
||||
* ROG STRIX X570-E GAMING.
|
||||
|
||||
Authors:
|
||||
- Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
|
||||
Description:
|
||||
------------
|
||||
ASUS mainboards publish hardware monitoring information via Super I/O
|
||||
chip and the ACPI embedded controller (EC) registers. Some of the sensors
|
||||
are only available via the EC.
|
||||
|
||||
ASUS WMI interface provides a method (BREC) to read data from EC registers,
|
||||
which is utilized by this driver to publish those sensor readings to the
|
||||
HWMON system. The driver is aware of and reads the following sensors:
|
||||
|
||||
1. Chipset (PCH) temperature
|
||||
2. CPU package temperature
|
||||
3. Motherboard temperature
|
||||
4. Readings from the T_Sensor header
|
||||
5. VRM temperature
|
||||
6. CPU_Opt fan RPM
|
||||
7. Chipset fan RPM
|
||||
8. Readings from the "Water flow meter" header (RPM)
|
||||
9. Readings from the "Water In" and "Water Out" temperature headers
|
||||
10. CPU current
|
@ -15,7 +15,7 @@ Supported devices:
|
||||
|
||||
Corsair HX850i
|
||||
|
||||
Corsair HX1000i
|
||||
Corsair HX1000i (revision 1 and 2)
|
||||
|
||||
Corsair HX1200i
|
||||
|
||||
@ -86,8 +86,9 @@ Debugfs entries
|
||||
---------------
|
||||
|
||||
======================= ========================================================
|
||||
uptime Current uptime of the psu
|
||||
ocpmode Single or multi rail mode of the PCIe power connectors
|
||||
product Product name of the psu
|
||||
uptime Session uptime of the psu
|
||||
uptime_total Total uptime of the psu
|
||||
vendor Vendor name of the psu
|
||||
product Product name of the psu
|
||||
======================= ========================================================
|
||||
|
37
Documentation/hwmon/emc2305.rst
Normal file
37
Documentation/hwmon/emc2305.rst
Normal file
@ -0,0 +1,37 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver emc2305
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
Microchip EMC2305, EMC2303, EMC2302, EMC2301
|
||||
|
||||
Addresses scanned: I2C 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d
|
||||
Prefixes: 'emc2305'
|
||||
|
||||
Datasheet: Publicly available at the Microchip website :
|
||||
https://www.microchip.com/en-us/product/EMC2305
|
||||
|
||||
Description:
|
||||
------------
|
||||
This driver implements support for Microchip EMC2301/2/3/5 RPM-based PWM Fan Controller.
|
||||
The EMC2305 Fan Controller supports up to 5 independently controlled PWM fan drives.
|
||||
Fan rotation speeds are reported in RPM.
|
||||
The driver supports the RPM-based PWM control to keep a fan at the desired speed.
|
||||
The driver provides the possibility to have one common PWM interface for all FANs
|
||||
or up to the maximum available or configured independent PWMs.
|
||||
|
||||
The driver provides the following sysfs interfaces in hwmon subsystem:
|
||||
|
||||
================= == ===================================================
|
||||
fan[1-5]_fault RO files for tachometers TACH1-TACH5 fault indication
|
||||
fan[1-5]_input RO files for tachometers TACH1-TACH5 input (in RPM)
|
||||
pwm[1-5] RW file for fan[1-5] target duty cycle (0..255)
|
||||
================= == ===================================================
|
||||
|
||||
sysfs interfaces in thermal subsystem:
|
||||
|
||||
================= == ========================================================================
|
||||
cur_state RW file for the current cooling state of the cooling device (0..max_state)
|
||||
max_state RO file for the maximum cooling state of the cooling device
|
||||
================= == ========================================================================
|
@ -44,7 +44,6 @@ Hardware Monitoring Kernel Drivers
|
||||
asc7621
|
||||
aspeed-pwm-tacho
|
||||
asus_ec_sensors
|
||||
asus_wmi_ec_sensors
|
||||
asus_wmi_sensors
|
||||
bcm54140
|
||||
bel-pfe
|
||||
@ -63,6 +62,7 @@ Hardware Monitoring Kernel Drivers
|
||||
ds620
|
||||
emc1403
|
||||
emc2103
|
||||
emc2305
|
||||
emc6w201
|
||||
f71805f
|
||||
f71882fg
|
||||
@ -133,6 +133,7 @@ Hardware Monitoring Kernel Drivers
|
||||
max20751
|
||||
max31722
|
||||
max31730
|
||||
max31760
|
||||
max31785
|
||||
max31790
|
||||
max34440
|
||||
@ -205,6 +206,7 @@ Hardware Monitoring Kernel Drivers
|
||||
tps23861
|
||||
tps40422
|
||||
tps53679
|
||||
tps546d24
|
||||
twl4030-madc-hwmon
|
||||
ucd9000
|
||||
ucd9200
|
||||
|
77
Documentation/hwmon/max31760.rst
Normal file
77
Documentation/hwmon/max31760.rst
Normal file
@ -0,0 +1,77 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver max31760
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
* Analog Devices MAX31760
|
||||
|
||||
Prefix: 'max31760'
|
||||
|
||||
Addresses scanned: none
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31760.pdf
|
||||
|
||||
|
||||
Author: Ibrahim Tilki <Ibrahim.Tilki@analog.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MAX31760 integrates temperature sensing along with precision PWM fan
|
||||
control. It accurately measures its local die temperature and the remote
|
||||
temperature of a discrete diode-connected transistor, such as a 2N3906,
|
||||
or a thermal diode commonly found on CPUs, graphics processor units (GPUs),
|
||||
and other ASICs. Multiple temperature thresholds, such as local
|
||||
high/overtemperature (OT) and remote high/overtemperature, can be set by an
|
||||
I2C-compatible interface. Fan speed is controlled based on the temperature
|
||||
reading as an index to a 48-byte lookup table (LUT) containing
|
||||
user-programmed PWM values. The flexible LUT-based architecture enables
|
||||
the user to program a smooth nonlinear fan speed vs. temperature transfer
|
||||
function to minimize acoustic fan noise. Two tachometer inputs allow
|
||||
measuring the speeds of two fans independently. When the local or remote
|
||||
OT threshold is exceeded, the SHDN pin is asserted low and can be used to
|
||||
shut down the system. A dedicated ALERT pin reports that either a local or
|
||||
remote high-temperature threshold has been exceeded.
|
||||
|
||||
Temperature measurement range: from -55°C to 125°C
|
||||
|
||||
Temperature Resolution: 11 Bits, ±0.125°C
|
||||
|
||||
Please refer how to instantiate this driver: Documentation/i2c/instantiating-devices.rst
|
||||
|
||||
Lookup table for auto fan control
|
||||
---------------------------------
|
||||
|
||||
========= =================================
|
||||
LUT Index Name
|
||||
========= =================================
|
||||
1 PWM value for T < +18°C
|
||||
2 PWM value for +18°C ≤ T < +20°C
|
||||
3 PWM value for +20°C ≤ T < +22°C
|
||||
... ...
|
||||
47 PWM value for +108°C ≤ T < +110°C
|
||||
48 PWM value for T ≥ +110°C
|
||||
========= =================================
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
=============================== =================================================================================
|
||||
fan[1-2]_input Fan speed (in RPM)
|
||||
fan[1-2]_enable Enable fan readings and fan fault alarms
|
||||
fan[1-2]_fault Fan fault status
|
||||
temp[1-2]_label "Remote" and "Local" temperature channel labels
|
||||
temp[1-2]_input Temperature sensor readings (in millidegrees Celsius)
|
||||
temp1_fault Remote temperature sensor fault status
|
||||
temp[1-2]_max Temperature max value. Asserts "ALERT" pin when exceeded
|
||||
temp[1-2]_max_alarm Temperature max alarm status
|
||||
temp[1-2]_crit Temperature critical value. Asserts "SHDN" pin when exceeded
|
||||
temp[1-2]_crit_alarm Temperature critical alarm status
|
||||
pwm1 PWM value for direct fan control
|
||||
pwm1_enable 1: direct fan control, 2: temperature based auto fan control
|
||||
pwm1_freq PWM frequency in hertz
|
||||
pwm1_auto_channels_temp Temperature source for auto fan control. 1: temp1, 2: temp2, 3: max(temp1, temp2)
|
||||
pwm1_auto_point[1-48]_pwm PWM value for LUT point
|
||||
pwm1_auto_point_temp_hyst Temperature hysteresis for auto fan control. Can be either 2000mC or 4000mC
|
||||
=============================== =================================================================================
|
@ -38,6 +38,7 @@ Sysfs entries
|
||||
fan[1-12]_input RO fan tachometer speed in RPM
|
||||
fan[1-12]_fault RO fan experienced fault
|
||||
fan[1-6]_target RW desired fan speed in RPM
|
||||
fan[1-6]_enable RW enable or disable the tachometer input
|
||||
pwm[1-6]_enable RW regulator mode, 0=disabled (duty cycle=0%), 1=manual mode, 2=rpm mode
|
||||
pwm[1-6] RW read: current pwm duty cycle,
|
||||
write: target pwm duty cycle (0-255)
|
||||
|
@ -18,3 +18,15 @@ the hwmon's sysfs interface.
|
||||
|
||||
The fan rotation speed returned via the optional 'fan1_input' is extrapolated
|
||||
from the sampled interrupts from the tachometer signal within 1 second.
|
||||
|
||||
The driver provides the following sensor accesses in sysfs:
|
||||
|
||||
=============== ======= =======================================================
|
||||
fan1_input ro fan tachometer speed in RPM
|
||||
pwm1_enable rw keep enable mode, defines behaviour when pwm1=0
|
||||
0 -> disable pwm and regulator
|
||||
1 -> enable pwm; if pwm==0, disable pwm, keep regulator enabled
|
||||
2 -> enable pwm; if pwm==0, keep pwm and regulator enabled
|
||||
3 -> enable pwm; if pwm==0, disable pwm and regulator
|
||||
pwm1 rw relative speed (0-255), 255=max. speed.
|
||||
=============== ======= =======================================================
|
||||
|
35
Documentation/hwmon/tps546d24.rst
Normal file
35
Documentation/hwmon/tps546d24.rst
Normal file
@ -0,0 +1,35 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
Kernel driver tps546d24
|
||||
=======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* TI TPS546D24
|
||||
|
||||
Prefix: 'tps546d24'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://www.ti.com/lit/gpn/tps546d24
|
||||
|
||||
Author: Duke Du <dukedu83@gmail.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The TPS546D24A is a highly integrated, non-isolated DC/DC converter capable
|
||||
of high frequency operation and 40-A current output from a 7-mm x 5-mm
|
||||
package.
|
||||
|
||||
Two, three, and four TPS546D24A devices can be interconnected
|
||||
to provide up to 160 A on a single output. The device has an option to
|
||||
overdrive the internal 5-V LDO with an external 5-V supply via the VDD5
|
||||
pin to improve efficiency and reduce power dissipation of the converter.
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data.
|
27
MAINTAINERS
27
MAINTAINERS
@ -1339,6 +1339,15 @@ F: drivers/iio/amplifiers/hmc425a.c
|
||||
F: drivers/staging/iio/*/ad*
|
||||
X: drivers/iio/*/adjd*
|
||||
|
||||
ANALOG DEVICES INC MAX31760 DRIVER
|
||||
M: Ibrahim Tilki <Ibrahim.Tilki@analog.com>
|
||||
S: Maintained
|
||||
W: http://wiki.analog.com/
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/hwmon/adi,max31760.yaml
|
||||
F: Documentation/hwmon/max31760.rst
|
||||
F: drivers/hwmon/max31760.c
|
||||
|
||||
ANALOGBITS PLL LIBRARIES
|
||||
M: Paul Walmsley <paul.walmsley@sifive.com>
|
||||
S: Supported
|
||||
@ -3238,13 +3247,6 @@ L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hwmon/asus_wmi_sensors.c
|
||||
|
||||
ASUS WMI EC HARDWARE MONITOR DRIVER
|
||||
M: Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
M: Denis Pauk <pauk.denis@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hwmon/asus_wmi_ec_sensors.c
|
||||
|
||||
ASUS EC HARDWARE MONITOR DRIVER
|
||||
M: Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
@ -6181,7 +6183,7 @@ F: Documentation/devicetree/bindings/memory-controllers/samsung,exynos5422-dmc.y
|
||||
F: drivers/memory/samsung/exynos5422-dmc.c
|
||||
|
||||
DME1737 HARDWARE MONITOR DRIVER
|
||||
M: Juerg Haefliger <juergh@gmail.com>
|
||||
M: Juerg Haefliger <juergh@proton.me>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/dme1737.rst
|
||||
@ -20649,6 +20651,13 @@ Q: https://patchwork.kernel.org/project/linux-integrity/list/
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git
|
||||
F: drivers/char/tpm/
|
||||
|
||||
TPS546D24 DRIVER
|
||||
M: Duke Du <dukedu83@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/tps546d24.rst
|
||||
F: drivers/hwmon/pmbus/tps546d24.c
|
||||
|
||||
TRACING
|
||||
M: Steven Rostedt <rostedt@goodmis.org>
|
||||
M: Ingo Molnar <mingo@redhat.com>
|
||||
@ -21852,7 +21861,7 @@ F: lib/test_scanf.c
|
||||
F: lib/vsprintf.c
|
||||
|
||||
VT1211 HARDWARE MONITOR DRIVER
|
||||
M: Juerg Haefliger <juergh@gmail.com>
|
||||
M: Juerg Haefliger <juergh@proton.me>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/vt1211.rst
|
||||
|
@ -257,14 +257,14 @@ config SENSORS_AHT10
|
||||
will be called aht10.
|
||||
|
||||
config SENSORS_AQUACOMPUTER_D5NEXT
|
||||
tristate "Aquacomputer D5 Next, Octo, Quadro, Farbwerk, and Farbwerk 360"
|
||||
tristate "Aquacomputer D5 Next, Octo, Quadro, Farbwerk, Farbwerk 360, High Flow Next"
|
||||
depends on USB_HID
|
||||
select CRC16
|
||||
help
|
||||
If you say yes here you get support for sensors and fans of
|
||||
the Aquacomputer D5 Next watercooling pump, Octo and Quadro fan
|
||||
controllers, Farbwerk and Farbwerk 360 RGB controllers, where
|
||||
available.
|
||||
controllers, Farbwerk and Farbwerk 360 RGB controllers, High Flow
|
||||
Next sensor, where available.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called aquacomputer_d5next.
|
||||
@ -393,6 +393,7 @@ config SENSORS_ASB100
|
||||
|
||||
config SENSORS_ASPEED
|
||||
tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on THERMAL || THERMAL=n
|
||||
select REGMAP
|
||||
help
|
||||
@ -1066,6 +1067,18 @@ config SENSORS_MAX31730
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max31730.
|
||||
|
||||
config SENSORS_MAX31760
|
||||
tristate "MAX31760 fan speed controller"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Support for the Analog Devices MAX31760 Precision Fan-Speed
|
||||
Controller. MAX31760 integrates temperature sensing along with
|
||||
precision PWM fan control.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max31760.
|
||||
|
||||
config SENSORS_MAX6620
|
||||
tristate "Maxim MAX6620 fan controller"
|
||||
depends on I2C
|
||||
@ -1785,6 +1798,19 @@ config SENSORS_EMC2103
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called emc2103.
|
||||
|
||||
config SENSORS_EMC2305
|
||||
tristate "Microchip EMC2305 and compatible EMC2301/2/3"
|
||||
depends on I2C
|
||||
imply THERMAL
|
||||
help
|
||||
If you say yes here you get support for the Microchip EMC2305
|
||||
fan controller chips.
|
||||
The Microchip EMC2305 is a fan controller for up to 5 fans.
|
||||
Fan rotation speeds are reported in RPM.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called emc2305.
|
||||
|
||||
config SENSORS_EMC6W201
|
||||
tristate "SMSC EMC6W201"
|
||||
depends on I2C
|
||||
@ -2341,21 +2367,6 @@ config SENSORS_ASUS_WMI
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called asus_wmi_sensors.
|
||||
|
||||
config SENSORS_ASUS_WMI_EC
|
||||
tristate "ASUS WMI B550/X570"
|
||||
depends on ACPI_WMI && SENSORS_ASUS_EC=n
|
||||
help
|
||||
If you say yes here you get support for the ACPI embedded controller
|
||||
hardware monitoring interface found in B550/X570 ASUS motherboards.
|
||||
This driver will provide readings of fans, voltages and temperatures
|
||||
through the system firmware.
|
||||
|
||||
This driver is deprecated in favor of the ASUS EC Sensors driver
|
||||
which provides fully compatible output.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called asus_wmi_sensors_ec.
|
||||
|
||||
config SENSORS_ASUS_EC
|
||||
tristate "ASUS EC Sensors"
|
||||
depends on X86
|
||||
|
@ -11,7 +11,6 @@ obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o
|
||||
obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o
|
||||
obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o
|
||||
obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o
|
||||
obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o
|
||||
|
||||
# Native drivers
|
||||
# asb100, then w83781d go first, as they can override other drivers' addresses.
|
||||
@ -70,6 +69,7 @@ obj-$(CONFIG_SENSORS_DS620) += ds620.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
|
||||
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
|
||||
obj-$(CONFIG_SENSORS_EMC2305) += emc2305.o
|
||||
obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
|
||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||
obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
|
||||
@ -140,6 +140,7 @@ obj-$(CONFIG_SENSORS_MAX1668) += max1668.o
|
||||
obj-$(CONFIG_SENSORS_MAX197) += max197.o
|
||||
obj-$(CONFIG_SENSORS_MAX31722) += max31722.o
|
||||
obj-$(CONFIG_SENSORS_MAX31730) += max31730.o
|
||||
obj-$(CONFIG_SENSORS_MAX31760) += max31760.o
|
||||
obj-$(CONFIG_SENSORS_MAX6620) += max6620.o
|
||||
obj-$(CONFIG_SENSORS_MAX6621) += max6621.o
|
||||
obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
|
||||
|
@ -1504,7 +1504,6 @@ LEAVE_UPDATE:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int abituguru_suspend(struct device *dev)
|
||||
{
|
||||
struct abituguru_data *data = dev_get_drvdata(dev);
|
||||
@ -1526,16 +1525,12 @@ static int abituguru_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(abituguru_pm, abituguru_suspend, abituguru_resume);
|
||||
#define ABIT_UGURU_PM (&abituguru_pm)
|
||||
#else
|
||||
#define ABIT_UGURU_PM NULL
|
||||
#endif /* CONFIG_PM */
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(abituguru_pm, abituguru_suspend, abituguru_resume);
|
||||
|
||||
static struct platform_driver abituguru_driver = {
|
||||
.driver = {
|
||||
.name = ABIT_UGURU_NAME,
|
||||
.pm = ABIT_UGURU_PM,
|
||||
.pm = pm_sleep_ptr(&abituguru_pm),
|
||||
},
|
||||
.probe = abituguru_probe,
|
||||
.remove = abituguru_remove,
|
||||
|
@ -1127,7 +1127,6 @@ LEAVE_UPDATE:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int abituguru3_suspend(struct device *dev)
|
||||
{
|
||||
struct abituguru3_data *data = dev_get_drvdata(dev);
|
||||
@ -1146,16 +1145,12 @@ static int abituguru3_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(abituguru3_pm, abituguru3_suspend, abituguru3_resume);
|
||||
#define ABIT_UGURU3_PM (&abituguru3_pm)
|
||||
#else
|
||||
#define ABIT_UGURU3_PM NULL
|
||||
#endif /* CONFIG_PM */
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(abituguru3_pm, abituguru3_suspend, abituguru3_resume);
|
||||
|
||||
static struct platform_driver abituguru3_driver = {
|
||||
.driver = {
|
||||
.name = ABIT_UGURU3_NAME,
|
||||
.pm = ABIT_UGURU3_PM
|
||||
.pm = pm_sleep_ptr(&abituguru3_pm),
|
||||
},
|
||||
.probe = abituguru3_probe,
|
||||
.remove = abituguru3_remove,
|
||||
|
@ -927,8 +927,6 @@ static int acpi_power_meter_remove(struct acpi_device *device)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int acpi_power_meter_resume(struct device *dev)
|
||||
{
|
||||
struct acpi_power_meter_resource *resource;
|
||||
@ -946,9 +944,8 @@ static int acpi_power_meter_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL,
|
||||
acpi_power_meter_resume);
|
||||
|
||||
static struct acpi_driver acpi_power_meter_driver = {
|
||||
.name = "power_meter",
|
||||
@ -959,7 +956,7 @@ static struct acpi_driver acpi_power_meter_driver = {
|
||||
.remove = acpi_power_meter_remove,
|
||||
.notify = acpi_power_meter_notify,
|
||||
},
|
||||
.drv.pm = &acpi_power_meter_pm,
|
||||
.drv.pm = pm_sleep_ptr(&acpi_power_meter_pm),
|
||||
};
|
||||
|
||||
/* Module init/exit routines */
|
||||
|
@ -384,7 +384,7 @@ static int adc128_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
if (i2c_smbus_read_byte_data(client, ADC128_REG_BUSY_STATUS) & 0xfc)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "adc128d818", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "adc128d818", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ static int adm1021_detect(struct i2c_client *client,
|
||||
|
||||
pr_debug("Detected chip %s at adapter %d, address 0x%02x.\n",
|
||||
type_name, i2c_adapter_id(adapter), client->addr);
|
||||
strlcpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -470,7 +470,7 @@ static int adm1025_detect(struct i2c_client *client,
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1610,7 +1610,7 @@ static int adm1026_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "adm1026", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "adm1026", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ static int adm1029_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "adm1029", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "adm1029", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -985,7 +985,7 @@ static int adm1031_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
name = (id == 0x30) ? "adm1030" : "adm1031";
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -501,17 +501,23 @@ static int adm9240_fan_read(struct device *dev, u32 attr, int channel, long *val
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
mutex_lock(&data->update_lock);
|
||||
err = regmap_read(data->regmap, ADM9240_REG_FAN(channel), ®val);
|
||||
if (err < 0)
|
||||
if (err < 0) {
|
||||
mutex_unlock(&data->update_lock);
|
||||
return err;
|
||||
}
|
||||
if (regval == 255 && data->fan_div[channel] < 3) {
|
||||
/* adjust fan clock divider on overflow */
|
||||
err = adm9240_write_fan_div(data, channel,
|
||||
++data->fan_div[channel]);
|
||||
if (err)
|
||||
if (err) {
|
||||
mutex_unlock(&data->update_lock);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
*val = FAN_FROM_REG(regval, BIT(data->fan_div[channel]));
|
||||
mutex_unlock(&data->update_lock);
|
||||
break;
|
||||
case hwmon_fan_div:
|
||||
*val = BIT(data->fan_div[channel]);
|
||||
|
@ -152,7 +152,7 @@ MODULE_DEVICE_TABLE(spi, adt7310_id);
|
||||
static struct spi_driver adt7310_driver = {
|
||||
.driver = {
|
||||
.name = "adt7310",
|
||||
.pm = ADT7X10_DEV_PM_OPS,
|
||||
.pm = pm_sleep_ptr(&adt7x10_dev_pm_ops),
|
||||
},
|
||||
.probe = adt7310_spi_probe,
|
||||
.id_table = adt7310_id,
|
||||
|
@ -98,7 +98,7 @@ static struct i2c_driver adt7410_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "adt7410",
|
||||
.pm = ADT7X10_DEV_PM_OPS,
|
||||
.pm = pm_sleep_ptr(&adt7x10_dev_pm_ops),
|
||||
},
|
||||
.probe_new = adt7410_i2c_probe,
|
||||
.id_table = adt7410_ids,
|
||||
|
@ -590,7 +590,7 @@ static int adt7411_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "adt7411", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "adt7411", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1782,7 +1782,7 @@ static int adt7462_detect(struct i2c_client *client,
|
||||
if (revision != ADT7462_REVISION)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "adt7462", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "adt7462", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1342,7 +1342,7 @@ static int adt7475_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -397,8 +397,6 @@ int adt7x10_probe(struct device *dev, const char *name, int irq,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(adt7x10_probe);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int adt7x10_suspend(struct device *dev)
|
||||
{
|
||||
struct adt7x10_data *data = dev_get_drvdata(dev);
|
||||
@ -414,10 +412,7 @@ static int adt7x10_resume(struct device *dev)
|
||||
return regmap_write(data->regmap, ADT7X10_CONFIG, data->config);
|
||||
}
|
||||
|
||||
SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume);
|
||||
EXPORT_SYMBOL_GPL(adt7x10_dev_pm_ops);
|
||||
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
EXPORT_SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume);
|
||||
|
||||
MODULE_AUTHOR("Hartmut Knaack");
|
||||
MODULE_DESCRIPTION("ADT7410/ADT7420, ADT7310/ADT7320 common code");
|
||||
|
@ -20,11 +20,6 @@ struct device;
|
||||
int adt7x10_probe(struct device *dev, const char *name, int irq,
|
||||
struct regmap *regmap);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
extern const struct dev_pm_ops adt7x10_dev_pm_ops;
|
||||
#define ADT7X10_DEV_PM_OPS (&adt7x10_dev_pm_ops)
|
||||
#else
|
||||
#define ADT7X10_DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -809,7 +809,7 @@ static int amc6821_detect(
|
||||
}
|
||||
|
||||
dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address);
|
||||
strlcpy(info->type, "amc6821", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "amc6821", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* hwmon driver for Aquacomputer devices (D5 Next, Farbwerk, Farbwerk 360, Octo,
|
||||
* Quadro)
|
||||
* Quadro, High Flow Next)
|
||||
*
|
||||
* Aquacomputer devices send HID reports (with ID 0x01) every second to report
|
||||
* sensor values.
|
||||
@ -26,15 +26,17 @@
|
||||
#define USB_PRODUCT_ID_D5NEXT 0xf00e
|
||||
#define USB_PRODUCT_ID_FARBWERK360 0xf010
|
||||
#define USB_PRODUCT_ID_OCTO 0xf011
|
||||
#define USB_PRODUCT_ID_HIGHFLOWNEXT 0xf012
|
||||
|
||||
enum kinds { d5next, farbwerk, farbwerk360, octo, quadro };
|
||||
enum kinds { d5next, farbwerk, farbwerk360, octo, quadro, highflownext };
|
||||
|
||||
static const char *const aqc_device_names[] = {
|
||||
[d5next] = "d5next",
|
||||
[farbwerk] = "farbwerk",
|
||||
[farbwerk360] = "farbwerk360",
|
||||
[octo] = "octo",
|
||||
[quadro] = "quadro"
|
||||
[quadro] = "quadro",
|
||||
[highflownext] = "highflownext"
|
||||
};
|
||||
|
||||
#define DRIVER_NAME "aquacomputer_d5next"
|
||||
@ -71,6 +73,8 @@ static u8 secondary_ctrl_report[] = {
|
||||
#define D5NEXT_COOLANT_TEMP 0x57
|
||||
#define D5NEXT_NUM_FANS 2
|
||||
#define D5NEXT_NUM_SENSORS 1
|
||||
#define D5NEXT_NUM_VIRTUAL_SENSORS 8
|
||||
#define D5NEXT_VIRTUAL_SENSORS_START 0x3f
|
||||
#define D5NEXT_PUMP_OFFSET 0x6c
|
||||
#define D5NEXT_FAN_OFFSET 0x5f
|
||||
#define D5NEXT_5V_VOLTAGE 0x39
|
||||
@ -86,14 +90,18 @@ static u16 d5next_ctrl_fan_offsets[] = { 0x97, 0x42 };
|
||||
#define FARBWERK_SENSOR_START 0x2f
|
||||
|
||||
/* Register offsets for the Farbwerk 360 RGB controller */
|
||||
#define FARBWERK360_NUM_SENSORS 4
|
||||
#define FARBWERK360_SENSOR_START 0x32
|
||||
#define FARBWERK360_NUM_SENSORS 4
|
||||
#define FARBWERK360_SENSOR_START 0x32
|
||||
#define FARBWERK360_NUM_VIRTUAL_SENSORS 16
|
||||
#define FARBWERK360_VIRTUAL_SENSORS_START 0x3a
|
||||
|
||||
/* Register offsets for the Octo fan controller */
|
||||
#define OCTO_POWER_CYCLES 0x18
|
||||
#define OCTO_NUM_FANS 8
|
||||
#define OCTO_NUM_SENSORS 4
|
||||
#define OCTO_SENSOR_START 0x3D
|
||||
#define OCTO_NUM_VIRTUAL_SENSORS 16
|
||||
#define OCTO_VIRTUAL_SENSORS_START 0x45
|
||||
#define OCTO_CTRL_REPORT_SIZE 0x65F
|
||||
static u8 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 };
|
||||
|
||||
@ -105,12 +113,24 @@ static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0
|
||||
#define QUADRO_NUM_FANS 4
|
||||
#define QUADRO_NUM_SENSORS 4
|
||||
#define QUADRO_SENSOR_START 0x34
|
||||
#define QUADRO_NUM_VIRTUAL_SENSORS 16
|
||||
#define QUADRO_VIRTUAL_SENSORS_START 0x3c
|
||||
#define QUADRO_CTRL_REPORT_SIZE 0x3c1
|
||||
#define QUADRO_FLOW_SENSOR_OFFSET 0x6e
|
||||
static u8 quadro_sensor_fan_offsets[] = { 0x70, 0x7D, 0x8A, 0x97 };
|
||||
|
||||
/* Fan speed registers in Quadro control report (from 0-100%) */
|
||||
static u16 quadro_ctrl_fan_offsets[] = { 0x36, 0x8b, 0xe0, 0x135 };
|
||||
static u16 quadro_ctrl_fan_offsets[] = { 0x37, 0x8c, 0xe1, 0x136 };
|
||||
|
||||
/* Register offsets for the High Flow Next */
|
||||
#define HIGHFLOWNEXT_NUM_SENSORS 2
|
||||
#define HIGHFLOWNEXT_SENSOR_START 85
|
||||
#define HIGHFLOWNEXT_FLOW 81
|
||||
#define HIGHFLOWNEXT_WATER_QUALITY 89
|
||||
#define HIGHFLOWNEXT_POWER 91
|
||||
#define HIGHFLOWNEXT_CONDUCTIVITY 95
|
||||
#define HIGHFLOWNEXT_5V_VOLTAGE 97
|
||||
#define HIGHFLOWNEXT_5V_VOLTAGE_USB 99
|
||||
|
||||
/* Labels for D5 Next */
|
||||
static const char *const label_d5next_temp[] = {
|
||||
@ -147,6 +167,25 @@ static const char *const label_temp_sensors[] = {
|
||||
"Sensor 4"
|
||||
};
|
||||
|
||||
static const char *const label_virtual_temp_sensors[] = {
|
||||
"Virtual sensor 1",
|
||||
"Virtual sensor 2",
|
||||
"Virtual sensor 3",
|
||||
"Virtual sensor 4",
|
||||
"Virtual sensor 5",
|
||||
"Virtual sensor 6",
|
||||
"Virtual sensor 7",
|
||||
"Virtual sensor 8",
|
||||
"Virtual sensor 9",
|
||||
"Virtual sensor 10",
|
||||
"Virtual sensor 11",
|
||||
"Virtual sensor 12",
|
||||
"Virtual sensor 13",
|
||||
"Virtual sensor 14",
|
||||
"Virtual sensor 15",
|
||||
"Virtual sensor 16",
|
||||
};
|
||||
|
||||
/* Labels for Octo and Quadro (except speed) */
|
||||
static const char *const label_fan_speed[] = {
|
||||
"Fan 1 speed",
|
||||
@ -201,6 +240,27 @@ static const char *const label_quadro_speeds[] = {
|
||||
"Flow speed [dL/h]"
|
||||
};
|
||||
|
||||
/* Labels for High Flow Next */
|
||||
static const char *const label_highflownext_temp_sensors[] = {
|
||||
"Coolant temp",
|
||||
"External sensor"
|
||||
};
|
||||
|
||||
static const char *const label_highflownext_fan_speed[] = {
|
||||
"Flow [dL/h]",
|
||||
"Water quality [%]",
|
||||
"Conductivity [nS/cm]",
|
||||
};
|
||||
|
||||
static const char *const label_highflownext_power[] = {
|
||||
"Dissipated power",
|
||||
};
|
||||
|
||||
static const char *const label_highflownext_voltage[] = {
|
||||
"+5V voltage",
|
||||
"+5V USB voltage"
|
||||
};
|
||||
|
||||
struct aqc_data {
|
||||
struct hid_device *hdev;
|
||||
struct device *hwmon_dev;
|
||||
@ -220,6 +280,8 @@ struct aqc_data {
|
||||
u16 *fan_ctrl_offsets;
|
||||
int num_temp_sensors;
|
||||
int temp_sensor_start_offset;
|
||||
int num_virtual_temp_sensors;
|
||||
int virtual_temp_sensor_start_offset;
|
||||
u16 power_cycle_count_offset;
|
||||
u8 flow_sensor_offset;
|
||||
|
||||
@ -231,7 +293,7 @@ struct aqc_data {
|
||||
u32 power_cycles;
|
||||
|
||||
/* Sensor values */
|
||||
s32 temp_input[4];
|
||||
s32 temp_input[20]; /* Max 4 physical and 16 virtual */
|
||||
u16 speed_input[8];
|
||||
u32 power_input[8];
|
||||
u16 voltage_input[8];
|
||||
@ -239,6 +301,7 @@ struct aqc_data {
|
||||
|
||||
/* Label values */
|
||||
const char *const *temp_label;
|
||||
const char *const *virtual_temp_label;
|
||||
const char *const *speed_label;
|
||||
const char *const *power_label;
|
||||
const char *const *voltage_label;
|
||||
@ -345,7 +408,7 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (channel < priv->num_temp_sensors)
|
||||
if (channel < priv->num_temp_sensors + priv->num_virtual_temp_sensors)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
@ -360,6 +423,11 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
break;
|
||||
case hwmon_fan:
|
||||
switch (priv->kind) {
|
||||
case highflownext:
|
||||
/* Special case to support flow sensor, water quality and conductivity */
|
||||
if (channel < 3)
|
||||
return 0444;
|
||||
break;
|
||||
case quadro:
|
||||
/* Special case to support flow sensor */
|
||||
if (channel < priv->num_fans + 1)
|
||||
@ -372,6 +440,18 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
}
|
||||
break;
|
||||
case hwmon_power:
|
||||
switch (priv->kind) {
|
||||
case highflownext:
|
||||
/* Special case to support one power sensor */
|
||||
if (channel == 0)
|
||||
return 0444;
|
||||
break;
|
||||
default:
|
||||
if (channel < priv->num_fans)
|
||||
return 0444;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
if (channel < priv->num_fans)
|
||||
return 0444;
|
||||
@ -383,6 +463,11 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
|
||||
if (channel < priv->num_fans + 2)
|
||||
return 0444;
|
||||
break;
|
||||
case highflownext:
|
||||
/* Special case to support two voltage sensors */
|
||||
if (channel < 2)
|
||||
return 0444;
|
||||
break;
|
||||
default:
|
||||
if (channel < priv->num_fans)
|
||||
return 0444;
|
||||
@ -447,7 +532,10 @@ static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
*str = priv->temp_label[channel];
|
||||
if (channel < priv->num_temp_sensors)
|
||||
*str = priv->temp_label[channel];
|
||||
else
|
||||
*str = priv->virtual_temp_label[channel - priv->num_temp_sensors];
|
||||
break;
|
||||
case hwmon_fan:
|
||||
*str = priv->speed_label[channel];
|
||||
@ -509,6 +597,22 @@ static const struct hwmon_ops aqc_hwmon_ops = {
|
||||
|
||||
static const struct hwmon_channel_info *aqc_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
@ -568,7 +672,7 @@ static const struct hwmon_chip_info aqc_chip_info = {
|
||||
|
||||
static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
int i, sensor_value;
|
||||
int i, j, sensor_value;
|
||||
struct aqc_data *priv;
|
||||
|
||||
if (report->id != STATUS_REPORT_ID)
|
||||
@ -581,7 +685,7 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
|
||||
priv->serial_number[1] = get_unaligned_be16(data + SERIAL_SECOND_PART);
|
||||
priv->firmware_version = get_unaligned_be16(data + FIRMWARE_VERSION);
|
||||
|
||||
/* Temperature sensor readings */
|
||||
/* Physical temperature sensor readings */
|
||||
for (i = 0; i < priv->num_temp_sensors; i++) {
|
||||
sensor_value = get_unaligned_be16(data +
|
||||
priv->temp_sensor_start_offset +
|
||||
@ -592,6 +696,18 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
|
||||
priv->temp_input[i] = sensor_value * 10;
|
||||
}
|
||||
|
||||
/* Virtual temperature sensor readings */
|
||||
for (j = 0; j < priv->num_virtual_temp_sensors; j++) {
|
||||
sensor_value = get_unaligned_be16(data +
|
||||
priv->virtual_temp_sensor_start_offset +
|
||||
j * AQC_TEMP_SENSOR_SIZE);
|
||||
if (sensor_value == AQC_TEMP_SENSOR_DISCONNECTED)
|
||||
priv->temp_input[i] = -ENODATA;
|
||||
else
|
||||
priv->temp_input[i] = sensor_value * 10;
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Fan speed and related readings */
|
||||
for (i = 0; i < priv->num_fans; i++) {
|
||||
priv->speed_input[i] =
|
||||
@ -618,6 +734,22 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8
|
||||
case quadro:
|
||||
priv->speed_input[4] = get_unaligned_be16(data + priv->flow_sensor_offset);
|
||||
break;
|
||||
case highflownext:
|
||||
/* If external temp sensor is not connected, its power reading is also N/A */
|
||||
if (priv->temp_input[1] == -ENODATA)
|
||||
priv->power_input[0] = -ENODATA;
|
||||
else
|
||||
priv->power_input[0] =
|
||||
get_unaligned_be16(data + HIGHFLOWNEXT_POWER) * 1000000;
|
||||
|
||||
priv->voltage_input[0] = get_unaligned_be16(data + HIGHFLOWNEXT_5V_VOLTAGE) * 10;
|
||||
priv->voltage_input[1] =
|
||||
get_unaligned_be16(data + HIGHFLOWNEXT_5V_VOLTAGE_USB) * 10;
|
||||
|
||||
priv->speed_input[0] = get_unaligned_be16(data + HIGHFLOWNEXT_FLOW);
|
||||
priv->speed_input[1] = get_unaligned_be16(data + HIGHFLOWNEXT_WATER_QUALITY);
|
||||
priv->speed_input[2] = get_unaligned_be16(data + HIGHFLOWNEXT_CONDUCTIVITY);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -717,10 +849,13 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
priv->fan_ctrl_offsets = d5next_ctrl_fan_offsets;
|
||||
priv->num_temp_sensors = D5NEXT_NUM_SENSORS;
|
||||
priv->temp_sensor_start_offset = D5NEXT_COOLANT_TEMP;
|
||||
priv->num_virtual_temp_sensors = D5NEXT_NUM_VIRTUAL_SENSORS;
|
||||
priv->virtual_temp_sensor_start_offset = D5NEXT_VIRTUAL_SENSORS_START;
|
||||
priv->power_cycle_count_offset = D5NEXT_POWER_CYCLES;
|
||||
priv->buffer_size = D5NEXT_CTRL_REPORT_SIZE;
|
||||
|
||||
priv->temp_label = label_d5next_temp;
|
||||
priv->virtual_temp_label = label_virtual_temp_sensors;
|
||||
priv->speed_label = label_d5next_speeds;
|
||||
priv->power_label = label_d5next_power;
|
||||
priv->voltage_label = label_d5next_voltages;
|
||||
@ -740,7 +875,11 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
priv->num_fans = 0;
|
||||
priv->num_temp_sensors = FARBWERK360_NUM_SENSORS;
|
||||
priv->temp_sensor_start_offset = FARBWERK360_SENSOR_START;
|
||||
priv->num_virtual_temp_sensors = FARBWERK360_NUM_VIRTUAL_SENSORS;
|
||||
priv->virtual_temp_sensor_start_offset = FARBWERK360_VIRTUAL_SENSORS_START;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
priv->virtual_temp_label = label_virtual_temp_sensors;
|
||||
break;
|
||||
case USB_PRODUCT_ID_OCTO:
|
||||
priv->kind = octo;
|
||||
@ -750,10 +889,13 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
priv->fan_ctrl_offsets = octo_ctrl_fan_offsets;
|
||||
priv->num_temp_sensors = OCTO_NUM_SENSORS;
|
||||
priv->temp_sensor_start_offset = OCTO_SENSOR_START;
|
||||
priv->num_virtual_temp_sensors = OCTO_NUM_VIRTUAL_SENSORS;
|
||||
priv->virtual_temp_sensor_start_offset = OCTO_VIRTUAL_SENSORS_START;
|
||||
priv->power_cycle_count_offset = OCTO_POWER_CYCLES;
|
||||
priv->buffer_size = OCTO_CTRL_REPORT_SIZE;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
priv->virtual_temp_label = label_virtual_temp_sensors;
|
||||
priv->speed_label = label_fan_speed;
|
||||
priv->power_label = label_fan_power;
|
||||
priv->voltage_label = label_fan_voltage;
|
||||
@ -767,16 +909,32 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
priv->fan_ctrl_offsets = quadro_ctrl_fan_offsets;
|
||||
priv->num_temp_sensors = QUADRO_NUM_SENSORS;
|
||||
priv->temp_sensor_start_offset = QUADRO_SENSOR_START;
|
||||
priv->num_virtual_temp_sensors = QUADRO_NUM_VIRTUAL_SENSORS;
|
||||
priv->virtual_temp_sensor_start_offset = QUADRO_VIRTUAL_SENSORS_START;
|
||||
priv->power_cycle_count_offset = QUADRO_POWER_CYCLES;
|
||||
priv->buffer_size = QUADRO_CTRL_REPORT_SIZE;
|
||||
priv->flow_sensor_offset = QUADRO_FLOW_SENSOR_OFFSET;
|
||||
|
||||
priv->temp_label = label_temp_sensors;
|
||||
priv->virtual_temp_label = label_virtual_temp_sensors;
|
||||
priv->speed_label = label_quadro_speeds;
|
||||
priv->power_label = label_fan_power;
|
||||
priv->voltage_label = label_fan_voltage;
|
||||
priv->current_label = label_fan_current;
|
||||
break;
|
||||
case USB_PRODUCT_ID_HIGHFLOWNEXT:
|
||||
priv->kind = highflownext;
|
||||
|
||||
priv->num_fans = 0;
|
||||
priv->num_temp_sensors = HIGHFLOWNEXT_NUM_SENSORS;
|
||||
priv->temp_sensor_start_offset = HIGHFLOWNEXT_SENSOR_START;
|
||||
priv->power_cycle_count_offset = QUADRO_POWER_CYCLES;
|
||||
|
||||
priv->temp_label = label_highflownext_temp_sensors;
|
||||
priv->speed_label = label_highflownext_fan_speed;
|
||||
priv->power_label = label_highflownext_power;
|
||||
priv->voltage_label = label_highflownext_voltage;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -833,6 +991,7 @@ static const struct hid_device_id aqc_table[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_QUADRO) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_HIGHFLOWNEXT) },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -769,7 +769,7 @@ static int asb100_detect(struct i2c_client *client,
|
||||
if (val1 != 0x31 || val2 != 0x06)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "asb100", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "asb100", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1153,7 +1153,7 @@ static int asc7621_detect(struct i2c_client *client,
|
||||
|
||||
if (company == asc7621_chips[chip_index].company_id &&
|
||||
verstep == asc7621_chips[chip_index].verstep_id) {
|
||||
strlcpy(info->type, asc7621_chips[chip_index].name,
|
||||
strscpy(info->type, asc7621_chips[chip_index].name,
|
||||
I2C_NAME_SIZE);
|
||||
|
||||
dev_info(&adapter->dev, "Matched %s at 0x%02x\n",
|
||||
|
@ -1,622 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HWMON driver for ASUS B550/X570 motherboards that publish sensor
|
||||
* values via the embedded controller registers.
|
||||
*
|
||||
* Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
* Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org>
|
||||
*
|
||||
* EC provides:
|
||||
* - Chipset temperature
|
||||
* - CPU temperature
|
||||
* - Motherboard temperature
|
||||
* - T_Sensor temperature
|
||||
* - VRM temperature
|
||||
* - Water In temperature
|
||||
* - Water Out temperature
|
||||
* - CPU Optional Fan RPM
|
||||
* - Chipset Fan RPM
|
||||
* - Water Flow Fan RPM
|
||||
* - CPU current
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/units.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66"
|
||||
#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */
|
||||
/* From the ASUS DSDT source */
|
||||
#define ASUSWMI_BREC_REGISTERS_MAX 16
|
||||
#define ASUSWMI_MAX_BUF_LEN 128
|
||||
#define SENSOR_LABEL_LEN 16
|
||||
|
||||
static u32 hwmon_attributes[hwmon_max] = {
|
||||
[hwmon_chip] = HWMON_C_REGISTER_TZ,
|
||||
[hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
[hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
[hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
[hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL,
|
||||
};
|
||||
|
||||
struct asus_wmi_ec_sensor_address {
|
||||
u8 index;
|
||||
u8 bank;
|
||||
u8 size;
|
||||
};
|
||||
|
||||
#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) { \
|
||||
.size = size_i, \
|
||||
.bank = bank_i, \
|
||||
.index = index_i, \
|
||||
}
|
||||
|
||||
struct ec_sensor_info {
|
||||
struct asus_wmi_ec_sensor_address addr;
|
||||
char label[SENSOR_LABEL_LEN];
|
||||
enum hwmon_sensor_types type;
|
||||
};
|
||||
|
||||
#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \
|
||||
.addr = MAKE_SENSOR_ADDRESS(size, bank, index), \
|
||||
.label = sensor_label, \
|
||||
.type = sensor_type, \
|
||||
}
|
||||
|
||||
enum known_ec_sensor {
|
||||
SENSOR_TEMP_CHIPSET,
|
||||
SENSOR_TEMP_CPU,
|
||||
SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR,
|
||||
SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
SENSOR_FAN_CHIPSET,
|
||||
SENSOR_FAN_VRM_HS,
|
||||
SENSOR_FAN_WATER_FLOW,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_TEMP_WATER_IN,
|
||||
SENSOR_TEMP_WATER_OUT,
|
||||
SENSOR_MAX
|
||||
};
|
||||
|
||||
/* All known sensors for ASUS EC controllers */
|
||||
static const struct ec_sensor_info known_ec_sensors[] = {
|
||||
[SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
|
||||
[SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b),
|
||||
[SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c),
|
||||
[SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
|
||||
[SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
|
||||
[SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
[SENSOR_FAN_VRM_HS] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
|
||||
[SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4),
|
||||
[SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc),
|
||||
[SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4),
|
||||
[SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
|
||||
[SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
|
||||
};
|
||||
|
||||
struct asus_wmi_data {
|
||||
const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1];
|
||||
};
|
||||
|
||||
/* boards with EC support */
|
||||
static struct asus_wmi_data sensors_board_PW_X570_P = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CHIPSET,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
static struct asus_wmi_data sensors_board_PW_X570_A = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CHIPSET,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
static struct asus_wmi_data sensors_board_R_C8H = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT,
|
||||
SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
/* Same as Hero but without chipset fan */
|
||||
static struct asus_wmi_data sensors_board_R_C8DH = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT,
|
||||
SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
/* Same as Hero but without water */
|
||||
static struct asus_wmi_data sensors_board_R_C8F = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
static struct asus_wmi_data sensors_board_RS_B550_E_G = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CPU_OPT,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
static struct asus_wmi_data sensors_board_RS_B550_I_G = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_VRM_HS,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
static struct asus_wmi_data sensors_board_RS_X570_E_G = {
|
||||
.known_board_sensors = {
|
||||
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB,
|
||||
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM,
|
||||
SENSOR_FAN_CHIPSET,
|
||||
SENSOR_CURR_CPU,
|
||||
SENSOR_MAX
|
||||
},
|
||||
};
|
||||
|
||||
#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) { \
|
||||
.matches = { \
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \
|
||||
}, \
|
||||
.driver_data = sensors, \
|
||||
}
|
||||
|
||||
static const struct dmi_system_id asus_wmi_ec_dmi_table[] = {
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &sensors_board_RS_B550_I_G),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G),
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table);
|
||||
|
||||
struct ec_sensor {
|
||||
enum known_ec_sensor info_index;
|
||||
long cached_value;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct asus_wmi_ec_info - sensor info.
|
||||
* @sensors: list of sensors.
|
||||
* @read_arg: UTF-16LE string to pass to BRxx() WMI function.
|
||||
* @read_buffer: decoded output from WMI result.
|
||||
* @nr_sensors: number of board EC sensors.
|
||||
* @nr_registers: number of EC registers to read (sensor might span more than 1 register).
|
||||
* @last_updated: in jiffies.
|
||||
*/
|
||||
struct asus_wmi_ec_info {
|
||||
struct ec_sensor sensors[SENSOR_MAX];
|
||||
char read_arg[(ASUSWMI_BREC_REGISTERS_MAX * 4 + 1) * 2];
|
||||
u8 read_buffer[ASUSWMI_BREC_REGISTERS_MAX];
|
||||
unsigned int nr_sensors;
|
||||
unsigned int nr_registers;
|
||||
unsigned long last_updated;
|
||||
};
|
||||
|
||||
struct asus_wmi_sensors {
|
||||
struct asus_wmi_ec_info ec;
|
||||
/* lock access to internal cache */
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec,
|
||||
const enum known_ec_sensor *bsi)
|
||||
{
|
||||
struct ec_sensor *s = ec->sensors;
|
||||
int i;
|
||||
|
||||
ec->nr_sensors = 0;
|
||||
ec->nr_registers = 0;
|
||||
|
||||
for (i = 0; bsi[i] != SENSOR_MAX; i++) {
|
||||
s[i].info_index = bsi[i];
|
||||
ec->nr_sensors++;
|
||||
ec->nr_registers += known_ec_sensors[bsi[i]].addr.size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The next four functions convert to or from BRxx string argument format.
|
||||
* The format of the string is as follows:
|
||||
* - The string consists of two-byte UTF-16LE characters.
|
||||
* - The value of the very first byte in the string is equal to the total
|
||||
* length of the next string in bytes, thus excluding the first two-byte
|
||||
* character.
|
||||
* - The rest of the string encodes the pairs of (bank, index) pairs, where
|
||||
* both values are byte-long (0x00 to 0xFF).
|
||||
* - Numbers are encoded as UTF-16LE hex values.
|
||||
*/
|
||||
static int asus_wmi_ec_decode_reply_buffer(const u8 *in, u32 length, u8 *out)
|
||||
{
|
||||
char buffer[ASUSWMI_MAX_BUF_LEN * 2];
|
||||
u32 len = min_t(u32, get_unaligned_le16(in), length - 2);
|
||||
|
||||
utf16s_to_utf8s((wchar_t *)(in + 2), len / 2, UTF16_LITTLE_ENDIAN, buffer, sizeof(buffer));
|
||||
|
||||
return hex2bin(out, buffer, len / 4);
|
||||
}
|
||||
|
||||
static void asus_wmi_ec_encode_registers(const u8 *in, u32 len, char *out)
|
||||
{
|
||||
char buffer[ASUSWMI_MAX_BUF_LEN * 2];
|
||||
|
||||
bin2hex(buffer, in, len);
|
||||
|
||||
utf8s_to_utf16s(buffer, len * 2, UTF16_LITTLE_ENDIAN, (wchar_t *)(out + 2), len * 2);
|
||||
|
||||
put_unaligned_le16(len * 4, out);
|
||||
}
|
||||
|
||||
static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec)
|
||||
{
|
||||
u8 registers[ASUSWMI_BREC_REGISTERS_MAX * 2];
|
||||
const struct ec_sensor_info *si;
|
||||
int i, j, offset;
|
||||
|
||||
offset = 0;
|
||||
for (i = 0; i < ec->nr_sensors; i++) {
|
||||
si = &known_ec_sensors[ec->sensors[i].info_index];
|
||||
for (j = 0; j < si->addr.size; j++) {
|
||||
registers[offset++] = si->addr.bank;
|
||||
registers[offset++] = si->addr.index + j;
|
||||
}
|
||||
}
|
||||
|
||||
asus_wmi_ec_encode_registers(registers, offset, ec->read_arg);
|
||||
}
|
||||
|
||||
static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out)
|
||||
{
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct acpi_buffer input;
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
/* The first byte of the BRxx() argument string has to be the string size. */
|
||||
input.length = query[0] + 2;
|
||||
input.pointer = query;
|
||||
status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj)
|
||||
return -EIO;
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) {
|
||||
ret = -EIO;
|
||||
goto out_free_obj;
|
||||
}
|
||||
|
||||
ret = asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, obj->buffer.length, out);
|
||||
|
||||
out_free_obj:
|
||||
ACPI_FREE(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long get_sensor_value(const struct ec_sensor_info *si, u8 *data)
|
||||
{
|
||||
switch (si->addr.size) {
|
||||
case 1:
|
||||
return *data;
|
||||
case 2:
|
||||
return get_unaligned_be16(data);
|
||||
case 4:
|
||||
return get_unaligned_be32(data);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec)
|
||||
{
|
||||
const struct ec_sensor_info *si;
|
||||
struct ec_sensor *s;
|
||||
u8 i_sensor;
|
||||
u8 *data;
|
||||
|
||||
data = ec->read_buffer;
|
||||
for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) {
|
||||
s = &ec->sensors[i_sensor];
|
||||
si = &known_ec_sensors[s->info_index];
|
||||
s->cached_value = get_sensor_value(si, data);
|
||||
data += si->addr.size;
|
||||
}
|
||||
}
|
||||
|
||||
static long asus_wmi_ec_scale_sensor_value(long value, int data_type)
|
||||
{
|
||||
switch (data_type) {
|
||||
case hwmon_curr:
|
||||
case hwmon_temp:
|
||||
case hwmon_in:
|
||||
return value * MILLI;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec,
|
||||
enum hwmon_sensor_types type, int channel)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ec->nr_sensors; i++) {
|
||||
if (known_ec_sensors[ec->sensors[i].info_index].type == type) {
|
||||
if (channel == 0)
|
||||
return i;
|
||||
|
||||
channel--;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int asus_wmi_ec_get_cached_value_or_update(struct asus_wmi_sensors *sensor_data,
|
||||
int sensor_index,
|
||||
long *value)
|
||||
{
|
||||
struct asus_wmi_ec_info *ec = &sensor_data->ec;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&sensor_data->lock);
|
||||
|
||||
if (time_after(jiffies, ec->last_updated + HZ)) {
|
||||
ret = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC,
|
||||
ec->read_arg, ec->read_buffer);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
asus_wmi_ec_update_ec_sensors(ec);
|
||||
ec->last_updated = jiffies;
|
||||
}
|
||||
|
||||
*value = ec->sensors[sensor_index].cached_value;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&sensor_data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Now follow the functions that implement the hwmon interface */
|
||||
|
||||
static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev);
|
||||
struct asus_wmi_ec_info *ec = &sensor_data->ec;
|
||||
int ret, sidx, info_index;
|
||||
long value = 0;
|
||||
|
||||
sidx = asus_wmi_ec_find_sensor_index(ec, type, channel);
|
||||
if (sidx < 0)
|
||||
return sidx;
|
||||
|
||||
ret = asus_wmi_ec_get_cached_value_or_update(sensor_data, sidx, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
info_index = ec->sensors[sidx].info_index;
|
||||
*val = asus_wmi_ec_scale_sensor_value(value, known_ec_sensors[info_index].type);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int asus_wmi_ec_hwmon_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, const char **str)
|
||||
{
|
||||
struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev);
|
||||
struct asus_wmi_ec_info *ec = &sensor_data->ec;
|
||||
int sensor_index;
|
||||
|
||||
sensor_index = asus_wmi_ec_find_sensor_index(ec, type, channel);
|
||||
*str = known_ec_sensors[ec->sensors[sensor_index].info_index].label;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
{
|
||||
const struct asus_wmi_sensors *sensor_data = drvdata;
|
||||
const struct asus_wmi_ec_info *ec = &sensor_data->ec;
|
||||
int index;
|
||||
|
||||
index = asus_wmi_ec_find_sensor_index(ec, type, channel);
|
||||
|
||||
return index < 0 ? 0 : 0444;
|
||||
}
|
||||
|
||||
static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan,
|
||||
struct device *dev, int num,
|
||||
enum hwmon_sensor_types type, u32 config)
|
||||
{
|
||||
u32 *cfg;
|
||||
|
||||
cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);
|
||||
if (!cfg)
|
||||
return -ENOMEM;
|
||||
|
||||
asus_wmi_hwmon_chan->type = type;
|
||||
asus_wmi_hwmon_chan->config = cfg;
|
||||
memset32(cfg, config, num);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops asus_wmi_ec_hwmon_ops = {
|
||||
.is_visible = asus_wmi_ec_hwmon_is_visible,
|
||||
.read = asus_wmi_ec_hwmon_read,
|
||||
.read_string = asus_wmi_ec_hwmon_read_string,
|
||||
};
|
||||
|
||||
static struct hwmon_chip_info asus_wmi_ec_chip_info = {
|
||||
.ops = &asus_wmi_ec_hwmon_ops,
|
||||
};
|
||||
|
||||
static int asus_wmi_ec_configure_sensor_setup(struct device *dev,
|
||||
const enum known_ec_sensor *bsi)
|
||||
{
|
||||
struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev);
|
||||
struct asus_wmi_ec_info *ec = &sensor_data->ec;
|
||||
struct hwmon_channel_info *asus_wmi_hwmon_chan;
|
||||
const struct hwmon_channel_info **asus_wmi_ci;
|
||||
int nr_count[hwmon_max] = {}, nr_types = 0;
|
||||
const struct hwmon_chip_info *chip_info;
|
||||
const struct ec_sensor_info *si;
|
||||
enum hwmon_sensor_types type;
|
||||
struct device *hwdev;
|
||||
int i, ret;
|
||||
|
||||
ret = asus_wmi_ec_fill_board_sensors(ec, bsi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!sensor_data->ec.nr_sensors)
|
||||
return -ENODEV;
|
||||
|
||||
for (i = 0; i < ec->nr_sensors; i++) {
|
||||
si = &known_ec_sensors[ec->sensors[i].info_index];
|
||||
if (!nr_count[si->type])
|
||||
nr_types++;
|
||||
nr_count[si->type]++;
|
||||
}
|
||||
|
||||
if (nr_count[hwmon_temp]) {
|
||||
nr_count[hwmon_chip]++;
|
||||
nr_types++;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we can get values for all the registers in a single query,
|
||||
* the query will not change from call to call.
|
||||
*/
|
||||
asus_wmi_ec_make_block_read_query(ec);
|
||||
|
||||
asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*asus_wmi_hwmon_chan),
|
||||
GFP_KERNEL);
|
||||
if (!asus_wmi_hwmon_chan)
|
||||
return -ENOMEM;
|
||||
|
||||
asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*asus_wmi_ci), GFP_KERNEL);
|
||||
if (!asus_wmi_ci)
|
||||
return -ENOMEM;
|
||||
|
||||
asus_wmi_ec_chip_info.info = asus_wmi_ci;
|
||||
chip_info = &asus_wmi_ec_chip_info;
|
||||
|
||||
for (type = 0; type < hwmon_max; type++) {
|
||||
if (!nr_count[type])
|
||||
continue;
|
||||
|
||||
ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev,
|
||||
nr_count[type], type,
|
||||
hwmon_attributes[type]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*asus_wmi_ci++ = asus_wmi_hwmon_chan++;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "board has %d EC sensors that span %d registers",
|
||||
ec->nr_sensors, ec->nr_registers);
|
||||
|
||||
hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_ec_sensors",
|
||||
sensor_data, chip_info, NULL);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwdev);
|
||||
}
|
||||
|
||||
static int asus_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct asus_wmi_sensors *sensor_data;
|
||||
struct asus_wmi_data *board_sensors;
|
||||
const struct dmi_system_id *dmi_id;
|
||||
const enum known_ec_sensor *bsi;
|
||||
struct device *dev = &wdev->dev;
|
||||
|
||||
dmi_id = dmi_first_match(asus_wmi_ec_dmi_table);
|
||||
if (!dmi_id)
|
||||
return -ENODEV;
|
||||
|
||||
board_sensors = dmi_id->driver_data;
|
||||
bsi = board_sensors->known_board_sensors;
|
||||
|
||||
sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL);
|
||||
if (!sensor_data)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&sensor_data->lock);
|
||||
|
||||
dev_set_drvdata(dev, sensor_data);
|
||||
|
||||
return asus_wmi_ec_configure_sensor_setup(dev, bsi);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id asus_ec_wmi_id_table[] = {
|
||||
{ ASUSWMI_MONITORING_GUID, NULL },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct wmi_driver asus_sensors_wmi_driver = {
|
||||
.driver = {
|
||||
.name = "asus_wmi_ec_sensors",
|
||||
},
|
||||
.id_table = asus_ec_wmi_id_table,
|
||||
.probe = asus_wmi_probe,
|
||||
};
|
||||
module_wmi_driver(asus_sensors_wmi_driver);
|
||||
|
||||
MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>");
|
||||
MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>");
|
||||
MODULE_DESCRIPTION("Asus WMI Sensors Driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -394,11 +394,6 @@ static int axi_fan_control_init(struct axi_fan_control_data *ctl,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void axi_fan_control_clk_disable(void *clk)
|
||||
{
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *axi_fan_control_info[] = {
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL),
|
||||
@ -478,20 +473,12 @@ static int axi_fan_control_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(ctl->base))
|
||||
return PTR_ERR(ctl->base);
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, NULL);
|
||||
clk = devm_clk_get_enabled(&pdev->dev, NULL);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&pdev->dev, axi_fan_control_clk_disable, clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctl->clk_rate = clk_get_rate(clk);
|
||||
if (!ctl->clk_rate)
|
||||
return -EINVAL;
|
||||
|
@ -55,6 +55,7 @@
|
||||
#define SECONDS_PER_DAY (SECONDS_PER_HOUR * 24)
|
||||
#define RAIL_COUNT 3 /* 3v3 + 5v + 12v */
|
||||
#define TEMP_COUNT 2
|
||||
#define OCP_MULTI_RAIL 0x02
|
||||
|
||||
#define PSU_CMD_SELECT_RAIL 0x00 /* expects length 2 */
|
||||
#define PSU_CMD_RAIL_VOLTS_HCRIT 0x40 /* the rest of the commands expect length 3 */
|
||||
@ -71,9 +72,10 @@
|
||||
#define PSU_CMD_RAIL_WATTS 0x96
|
||||
#define PSU_CMD_VEND_STR 0x99
|
||||
#define PSU_CMD_PROD_STR 0x9A
|
||||
#define PSU_CMD_TOTAL_WATTS 0xEE
|
||||
#define PSU_CMD_TOTAL_UPTIME 0xD1
|
||||
#define PSU_CMD_UPTIME 0xD2
|
||||
#define PSU_CMD_OCPMODE 0xD8
|
||||
#define PSU_CMD_TOTAL_WATTS 0xEE
|
||||
#define PSU_CMD_INIT 0xFE
|
||||
|
||||
#define L_IN_VOLTS "v_in"
|
||||
@ -268,6 +270,7 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l
|
||||
break;
|
||||
case PSU_CMD_TOTAL_UPTIME:
|
||||
case PSU_CMD_UPTIME:
|
||||
case PSU_CMD_OCPMODE:
|
||||
*val = tmp;
|
||||
break;
|
||||
default:
|
||||
@ -660,6 +663,29 @@ static int product_show(struct seq_file *seqf, void *unused)
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(product);
|
||||
|
||||
static int ocpmode_show(struct seq_file *seqf, void *unused)
|
||||
{
|
||||
struct corsairpsu_data *priv = seqf->private;
|
||||
long val;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The rail mode is switchable on the fly. The RAW interface can be used for this. But it
|
||||
* will not be included here, because I consider it somewhat dangerous for the health of the
|
||||
* PSU. The returned value can be a bogus one, if the PSU is in the process of switching and
|
||||
* getting of the value itself can also fail during this. Because of this every other value
|
||||
* than OCP_MULTI_RAIL can be considered as "single rail".
|
||||
*/
|
||||
ret = corsairpsu_get_value(priv, PSU_CMD_OCPMODE, 0, &val);
|
||||
if (ret < 0)
|
||||
seq_puts(seqf, "N/A\n");
|
||||
else
|
||||
seq_printf(seqf, "%s\n", (val == OCP_MULTI_RAIL) ? "multi rail" : "single rail");
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(ocpmode);
|
||||
|
||||
static void corsairpsu_debugfs_init(struct corsairpsu_data *priv)
|
||||
{
|
||||
char name[32];
|
||||
@ -671,6 +697,7 @@ static void corsairpsu_debugfs_init(struct corsairpsu_data *priv)
|
||||
debugfs_create_file("uptime_total", 0444, priv->debugfs, priv, &uptime_total_fops);
|
||||
debugfs_create_file("vendor", 0444, priv->debugfs, priv, &vendor_fops);
|
||||
debugfs_create_file("product", 0444, priv->debugfs, priv, &product_fops);
|
||||
debugfs_create_file("ocpmode", 0444, priv->debugfs, priv, &ocpmode_fops);
|
||||
}
|
||||
|
||||
#else
|
||||
@ -786,13 +813,14 @@ static const struct hid_device_id corsairpsu_idtable[] = {
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c04) }, /* Corsair HX650i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c05) }, /* Corsair HX750i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c06) }, /* Corsair HX850i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i revision 1 */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c08) }, /* Corsair HX1200i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c09) }, /* Corsair RM550i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0a) }, /* Corsair RM650i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0b) }, /* Corsair RM750i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */
|
||||
{ HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsaur HX1000i revision 2 */
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, corsairpsu_idtable);
|
||||
|
@ -1355,15 +1355,21 @@ static int __init dell_smm_probe(struct platform_device *pdev)
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
|
||||
dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
|
||||
if (!force)
|
||||
if (!force) {
|
||||
dev_notice(&pdev->dev, "Disabling fan support due to BIOS bugs\n");
|
||||
data->disallow_fan_support = true;
|
||||
} else {
|
||||
dev_warn(&pdev->dev, "Enabling fan support despite BIOS bugs\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
|
||||
dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
|
||||
if (!force)
|
||||
if (!force) {
|
||||
dev_notice(&pdev->dev, "Disabling fan type call due to BIOS bugs\n");
|
||||
data->disallow_fan_type_call = true;
|
||||
} else {
|
||||
dev_warn(&pdev->dev, "Enabling fan type call despite BIOS bugs\n");
|
||||
}
|
||||
}
|
||||
|
||||
strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
|
||||
|
@ -2456,7 +2456,7 @@ static int dme1737_i2c_detect(struct i2c_client *client,
|
||||
dev_info(dev, "Found a %s chip at 0x%02x (rev 0x%02x).\n",
|
||||
verstep == SCH5027_VERSTEP ? "SCH5027" : "DME1737",
|
||||
client->addr, verstep);
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -329,22 +329,22 @@ static int emc1403_detect(struct i2c_client *client,
|
||||
id = i2c_smbus_read_byte_data(client, THERMAL_PID_REG);
|
||||
switch (id) {
|
||||
case 0x20:
|
||||
strlcpy(info->type, "emc1402", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1402", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x21:
|
||||
strlcpy(info->type, "emc1403", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1403", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x22:
|
||||
strlcpy(info->type, "emc1422", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1422", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x23:
|
||||
strlcpy(info->type, "emc1423", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1423", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x25:
|
||||
strlcpy(info->type, "emc1404", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1404", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x27:
|
||||
strlcpy(info->type, "emc1424", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc1424", I2C_NAME_SIZE);
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
|
@ -643,7 +643,7 @@ emc2103_detect(struct i2c_client *new_client, struct i2c_board_info *info)
|
||||
if ((product != 0x24) && (product != 0x26))
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "emc2103", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc2103", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
620
drivers/hwmon/emc2305.c
Normal file
620
drivers/hwmon/emc2305.c
Normal file
@ -0,0 +1,620 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Hardware monitoring driver for EMC2305 fan controller
|
||||
*
|
||||
* Copyright (C) 2022 Nvidia Technologies Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/emc2305.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
static const unsigned short
|
||||
emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END };
|
||||
|
||||
#define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
|
||||
#define EMC2305_REG_DEVICE 0xfd
|
||||
#define EMC2305_REG_VENDOR 0xfe
|
||||
#define EMC2305_FAN_MAX 0xff
|
||||
#define EMC2305_FAN_MIN 0x00
|
||||
#define EMC2305_FAN_MAX_STATE 10
|
||||
#define EMC2305_DEVICE 0x34
|
||||
#define EMC2305_VENDOR 0x5d
|
||||
#define EMC2305_REG_PRODUCT_ID 0xfd
|
||||
#define EMC2305_TACH_REGS_UNUSE_BITS 3
|
||||
#define EMC2305_TACH_CNT_MULTIPLIER 0x02
|
||||
#define EMC2305_TACH_RANGE_MIN 480
|
||||
|
||||
#define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
|
||||
DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max))
|
||||
#define EMC2305_PWM_STATE2DUTY(state, max_state, pwm_max) \
|
||||
DIV_ROUND_CLOSEST((state) * (pwm_max), (max_state))
|
||||
|
||||
/*
|
||||
* Factor by equations [2] and [3] from data sheet; valid for fans where the number of edges
|
||||
* equal (poles * 2 + 1).
|
||||
*/
|
||||
#define EMC2305_RPM_FACTOR 3932160
|
||||
|
||||
#define EMC2305_REG_FAN_DRIVE(n) (0x30 + 0x10 * (n))
|
||||
#define EMC2305_REG_FAN_MIN_DRIVE(n) (0x38 + 0x10 * (n))
|
||||
#define EMC2305_REG_FAN_TACH(n) (0x3e + 0x10 * (n))
|
||||
|
||||
enum emc230x_product_id {
|
||||
EMC2305 = 0x34,
|
||||
EMC2303 = 0x35,
|
||||
EMC2302 = 0x36,
|
||||
EMC2301 = 0x37,
|
||||
};
|
||||
|
||||
static const struct i2c_device_id emc2305_ids[] = {
|
||||
{ "emc2305", 0 },
|
||||
{ "emc2303", 0 },
|
||||
{ "emc2302", 0 },
|
||||
{ "emc2301", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, emc2305_ids);
|
||||
|
||||
/**
|
||||
* @cdev: cooling device;
|
||||
* @curr_state: cooling current state;
|
||||
* @last_hwmon_state: last cooling state updated by hwmon subsystem;
|
||||
* @last_thermal_state: last cooling state updated by thermal subsystem;
|
||||
*
|
||||
* The 'last_hwmon_state' and 'last_thermal_state' fields are provided to support fan low limit
|
||||
* speed feature. The purpose of this feature is to provides ability to limit fan speed
|
||||
* according to some system wise considerations, like absence of some replaceable units (PSU or
|
||||
* line cards), high system ambient temperature, unreliable transceivers temperature sensing or
|
||||
* some other factors which indirectly impacts system's airflow
|
||||
* Fan low limit feature is supported through 'hwmon' interface: 'hwmon' 'pwm' attribute is
|
||||
* used for setting low limit for fan speed in case 'thermal' subsystem is configured in
|
||||
* kernel. In this case setting fan speed through 'hwmon' will never let the 'thermal'
|
||||
* subsystem to select a lower duty cycle than the duty cycle selected with the 'pwm'
|
||||
* attribute.
|
||||
* From other side, fan speed is to be updated in hardware through 'pwm' only in case the
|
||||
* requested fan speed is above last speed set by 'thermal' subsystem, otherwise requested fan
|
||||
* speed will be just stored with no PWM update.
|
||||
*/
|
||||
struct emc2305_cdev_data {
|
||||
struct thermal_cooling_device *cdev;
|
||||
unsigned int cur_state;
|
||||
unsigned long last_hwmon_state;
|
||||
unsigned long last_thermal_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* @client: i2c client;
|
||||
* @hwmon_dev: hwmon device;
|
||||
* @max_state: maximum cooling state of the cooling device;
|
||||
* @pwm_num: number of PWM channels;
|
||||
* @pwm_separate: separate PWM settings for every channel;
|
||||
* @pwm_min: array of minimum PWM per channel;
|
||||
* @cdev_data: array of cooling devices data;
|
||||
*/
|
||||
struct emc2305_data {
|
||||
struct i2c_client *client;
|
||||
struct device *hwmon_dev;
|
||||
u8 max_state;
|
||||
u8 pwm_num;
|
||||
bool pwm_separate;
|
||||
u8 pwm_min[EMC2305_PWM_MAX];
|
||||
struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
|
||||
};
|
||||
|
||||
static char *emc2305_fan_name[] = {
|
||||
"emc2305_fan",
|
||||
"emc2305_fan1",
|
||||
"emc2305_fan2",
|
||||
"emc2305_fan3",
|
||||
"emc2305_fan4",
|
||||
"emc2305_fan5",
|
||||
};
|
||||
|
||||
static void emc2305_unset_tz(struct device *dev);
|
||||
|
||||
static int emc2305_get_max_channel(const struct emc2305_data *data)
|
||||
{
|
||||
return data->pwm_num;
|
||||
}
|
||||
|
||||
static int emc2305_get_cdev_idx(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct emc2305_data *data = cdev->devdata;
|
||||
size_t len = strlen(cdev->type);
|
||||
int ret;
|
||||
|
||||
if (len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Returns index of cooling device 0..4 in case of separate PWM setting.
|
||||
* Zero index is used in case of one common PWM setting.
|
||||
* If the mode is not set as pwm_separate, all PWMs are to be bound
|
||||
* to the common thermal zone and should work at the same speed
|
||||
* to perform cooling for the same thermal junction.
|
||||
* Otherwise, return specific channel that will be used in bound
|
||||
* related PWM to the thermal zone.
|
||||
*/
|
||||
if (!data->pwm_separate)
|
||||
return 0;
|
||||
|
||||
ret = cdev->type[len - 1];
|
||||
switch (ret) {
|
||||
case '1' ... '5':
|
||||
return ret - '1';
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int emc2305_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
|
||||
{
|
||||
int cdev_idx;
|
||||
struct emc2305_data *data = cdev->devdata;
|
||||
|
||||
cdev_idx = emc2305_get_cdev_idx(cdev);
|
||||
if (cdev_idx < 0)
|
||||
return cdev_idx;
|
||||
|
||||
*state = data->cdev_data[cdev_idx].cur_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emc2305_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
|
||||
{
|
||||
struct emc2305_data *data = cdev->devdata;
|
||||
*state = data->max_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emc2305_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
|
||||
{
|
||||
int cdev_idx, ret;
|
||||
struct emc2305_data *data = cdev->devdata;
|
||||
struct i2c_client *client = data->client;
|
||||
u8 val, i;
|
||||
|
||||
if (state > data->max_state)
|
||||
return -EINVAL;
|
||||
|
||||
cdev_idx = emc2305_get_cdev_idx(cdev);
|
||||
if (cdev_idx < 0)
|
||||
return cdev_idx;
|
||||
|
||||
/* Save thermal state. */
|
||||
data->cdev_data[cdev_idx].last_thermal_state = state;
|
||||
state = max_t(unsigned long, state, data->cdev_data[cdev_idx].last_hwmon_state);
|
||||
|
||||
val = EMC2305_PWM_STATE2DUTY(state, data->max_state, EMC2305_FAN_MAX);
|
||||
|
||||
data->cdev_data[cdev_idx].cur_state = state;
|
||||
if (data->pwm_separate) {
|
||||
ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(cdev_idx), val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
/*
|
||||
* Set the same PWM value in all channels
|
||||
* if common PWM channel is used.
|
||||
*/
|
||||
for (i = 0; i < data->pwm_num; i++) {
|
||||
ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(i), val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops emc2305_cooling_ops = {
|
||||
.get_max_state = emc2305_get_max_state,
|
||||
.get_cur_state = emc2305_get_cur_state,
|
||||
.set_cur_state = emc2305_set_cur_state,
|
||||
};
|
||||
|
||||
static int emc2305_show_fault(struct device *dev, int channel)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int status_reg;
|
||||
|
||||
status_reg = i2c_smbus_read_byte_data(client, EMC2305_REG_DRIVE_FAIL_STATUS);
|
||||
if (status_reg < 0)
|
||||
return status_reg;
|
||||
|
||||
return status_reg & (1 << channel) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int emc2305_show_fan(struct device *dev, int channel)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_word_swapped(client, EMC2305_REG_FAN_TACH(channel));
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
|
||||
ret = ret >> EMC2305_TACH_REGS_UNUSE_BITS;
|
||||
ret = EMC2305_RPM_FACTOR / ret;
|
||||
if (ret <= EMC2305_TACH_RANGE_MIN)
|
||||
return 0;
|
||||
|
||||
return ret * EMC2305_TACH_CNT_MULTIPLIER;
|
||||
}
|
||||
|
||||
static int emc2305_show_pwm(struct device *dev, int channel)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
return i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_DRIVE(channel));
|
||||
}
|
||||
|
||||
static int emc2305_set_pwm(struct device *dev, long val, int channel)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
if (val < data->pwm_min[channel] || val > EMC2305_FAN_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(channel), val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->cdev_data[channel].cur_state = EMC2305_PWM_DUTY2STATE(val, data->max_state,
|
||||
EMC2305_FAN_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emc2305_set_single_tz(struct device *dev, int idx)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
long pwm;
|
||||
int i, cdev_idx, ret;
|
||||
|
||||
cdev_idx = (idx) ? idx - 1 : 0;
|
||||
pwm = data->pwm_min[cdev_idx];
|
||||
|
||||
data->cdev_data[cdev_idx].cdev =
|
||||
thermal_cooling_device_register(emc2305_fan_name[idx], data,
|
||||
&emc2305_cooling_ops);
|
||||
|
||||
if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
|
||||
dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
|
||||
return PTR_ERR(data->cdev_data[cdev_idx].cdev);
|
||||
}
|
||||
/* Set minimal PWM speed. */
|
||||
if (data->pwm_separate) {
|
||||
ret = emc2305_set_pwm(dev, pwm, cdev_idx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
for (i = 0; i < data->pwm_num; i++) {
|
||||
ret = emc2305_set_pwm(dev, pwm, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
data->cdev_data[cdev_idx].cur_state =
|
||||
EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
|
||||
EMC2305_FAN_MAX);
|
||||
data->cdev_data[cdev_idx].last_hwmon_state =
|
||||
EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state,
|
||||
EMC2305_FAN_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emc2305_set_tz(struct device *dev)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
|
||||
if (!data->pwm_separate)
|
||||
return emc2305_set_single_tz(dev, 0);
|
||||
|
||||
for (i = 0; i < data->pwm_num; i++) {
|
||||
ret = emc2305_set_single_tz(dev, i + 1);
|
||||
if (ret)
|
||||
goto thermal_cooling_device_register_fail;
|
||||
}
|
||||
return 0;
|
||||
|
||||
thermal_cooling_device_register_fail:
|
||||
emc2305_unset_tz(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void emc2305_unset_tz(struct device *dev)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
/* Unregister cooling device. */
|
||||
for (i = 0; i < EMC2305_PWM_MAX; i++)
|
||||
if (data->cdev_data[i].cdev)
|
||||
thermal_cooling_device_unregister(data->cdev_data[i].cdev);
|
||||
}
|
||||
|
||||
static umode_t
|
||||
emc2305_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel)
|
||||
{
|
||||
int max_channel = emc2305_get_max_channel(data);
|
||||
|
||||
/* Don't show channels which are not physically connected. */
|
||||
if (channel >= max_channel)
|
||||
return 0;
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
return 0444;
|
||||
case hwmon_fan_fault:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return 0644;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int
|
||||
emc2305_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val)
|
||||
{
|
||||
struct emc2305_data *data = dev_get_drvdata(dev);
|
||||
int cdev_idx;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
/* If thermal is configured - handle PWM limit setting. */
|
||||
if (IS_REACHABLE(CONFIG_THERMAL)) {
|
||||
if (data->pwm_separate)
|
||||
cdev_idx = channel;
|
||||
else
|
||||
cdev_idx = 0;
|
||||
data->cdev_data[cdev_idx].last_hwmon_state =
|
||||
EMC2305_PWM_DUTY2STATE(val, data->max_state,
|
||||
EMC2305_FAN_MAX);
|
||||
/*
|
||||
* Update PWM only in case requested state is not less than the
|
||||
* last thermal state.
|
||||
*/
|
||||
if (data->cdev_data[cdev_idx].last_hwmon_state >=
|
||||
data->cdev_data[cdev_idx].last_thermal_state)
|
||||
return emc2305_set_cur_state(data->cdev_data[cdev_idx].cdev,
|
||||
data->cdev_data[cdev_idx].last_hwmon_state);
|
||||
return 0;
|
||||
}
|
||||
return emc2305_set_pwm(dev, val, channel);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
};
|
||||
|
||||
static int
|
||||
emc2305_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
ret = emc2305_show_fan(dev, channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return 0;
|
||||
case hwmon_fan_fault:
|
||||
ret = emc2305_show_fault(dev, channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
ret = emc2305_show_pwm(dev, channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
};
|
||||
|
||||
static const struct hwmon_ops emc2305_ops = {
|
||||
.is_visible = emc2305_is_visible,
|
||||
.read = emc2305_read,
|
||||
.write = emc2305_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *emc2305_info[] = {
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT,
|
||||
HWMON_PWM_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info emc2305_chip_info = {
|
||||
.ops = &emc2305_ops,
|
||||
.info = emc2305_info,
|
||||
};
|
||||
|
||||
static int emc2305_identify(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, EMC2305_REG_PRODUCT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret) {
|
||||
case EMC2305:
|
||||
data->pwm_num = 5;
|
||||
break;
|
||||
case EMC2303:
|
||||
data->pwm_num = 3;
|
||||
break;
|
||||
case EMC2302:
|
||||
data->pwm_num = 2;
|
||||
break;
|
||||
case EMC2301:
|
||||
data->pwm_num = 1;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emc2305_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct device *dev = &client->dev;
|
||||
struct emc2305_data *data;
|
||||
struct emc2305_platform_data *pdata;
|
||||
int vendor, device;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
vendor = i2c_smbus_read_byte_data(client, EMC2305_REG_VENDOR);
|
||||
if (vendor != EMC2305_VENDOR)
|
||||
return -ENODEV;
|
||||
|
||||
device = i2c_smbus_read_byte_data(client, EMC2305_REG_DEVICE);
|
||||
if (device != EMC2305_DEVICE)
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
data->client = client;
|
||||
|
||||
ret = emc2305_identify(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pdata = dev_get_platdata(&client->dev);
|
||||
if (pdata) {
|
||||
if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE)
|
||||
return -EINVAL;
|
||||
data->max_state = pdata->max_state;
|
||||
/*
|
||||
* Validate a number of active PWM channels. Note that
|
||||
* configured number can be less than the actual maximum
|
||||
* supported by the device.
|
||||
*/
|
||||
if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX)
|
||||
return -EINVAL;
|
||||
data->pwm_num = pdata->pwm_num;
|
||||
data->pwm_separate = pdata->pwm_separate;
|
||||
for (i = 0; i < EMC2305_PWM_MAX; i++)
|
||||
data->pwm_min[i] = pdata->pwm_min[i];
|
||||
} else {
|
||||
data->max_state = EMC2305_FAN_MAX_STATE;
|
||||
data->pwm_separate = false;
|
||||
for (i = 0; i < EMC2305_PWM_MAX; i++)
|
||||
data->pwm_min[i] = EMC2305_FAN_MIN;
|
||||
}
|
||||
|
||||
data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "emc2305", data,
|
||||
&emc2305_chip_info, NULL);
|
||||
if (IS_ERR(data->hwmon_dev))
|
||||
return PTR_ERR(data->hwmon_dev);
|
||||
|
||||
if (IS_REACHABLE(CONFIG_THERMAL)) {
|
||||
ret = emc2305_set_tz(dev);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < data->pwm_num; i++) {
|
||||
ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_MIN_DRIVE(i),
|
||||
data->pwm_min[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void emc2305_remove(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
if (IS_REACHABLE(CONFIG_THERMAL))
|
||||
emc2305_unset_tz(dev);
|
||||
}
|
||||
|
||||
static struct i2c_driver emc2305_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "emc2305",
|
||||
},
|
||||
.probe = emc2305_probe,
|
||||
.remove = emc2305_remove,
|
||||
.id_table = emc2305_ids,
|
||||
.address_list = emc2305_normal_i2c,
|
||||
};
|
||||
|
||||
module_i2c_driver(emc2305_driver);
|
||||
|
||||
MODULE_AUTHOR("Nvidia");
|
||||
MODULE_DESCRIPTION("Microchip EMC2305 fan controller driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -439,7 +439,7 @@ static int emc6w201_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "emc6w201", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "emc6w201", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -896,7 +896,7 @@ static int f75375_detect(struct i2c_client *client,
|
||||
|
||||
version = f75375_read8(client, F75375_REG_VERSION);
|
||||
dev_info(&adapter->dev, "found %s version: %02X\n", name, version);
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1075,7 +1075,7 @@ static int fschmd_detect(struct i2c_client *client,
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, fschmd_id[kind].name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, fschmd_id[kind].name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -739,7 +739,7 @@ static int fts_detect(struct i2c_client *client,
|
||||
if (val != 0x11)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, fts_id[0].name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, fts_id[0].name, I2C_NAME_SIZE);
|
||||
info->flags = 0;
|
||||
return 0;
|
||||
}
|
||||
|
@ -586,7 +586,7 @@ static int gl518_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
if (rev != 0x00 && rev != 0x80)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "gl518sm", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "gl518sm", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -811,7 +811,7 @@ static int gl520_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "gl520sm", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "gl520sm", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -37,9 +37,7 @@ struct gpio_fan_data {
|
||||
int num_speed;
|
||||
struct gpio_fan_speed *speed;
|
||||
int speed_index;
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
int resume_speed;
|
||||
#endif
|
||||
bool pwm_enable;
|
||||
struct gpio_desc *alarm_gpio;
|
||||
struct work_struct alarm_work;
|
||||
@ -557,7 +555,6 @@ static void gpio_fan_shutdown(struct platform_device *pdev)
|
||||
set_fan_speed(fan_data, 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int gpio_fan_suspend(struct device *dev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
@ -580,18 +577,14 @@ static int gpio_fan_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume);
|
||||
#define GPIO_FAN_PM (&gpio_fan_pm)
|
||||
#else
|
||||
#define GPIO_FAN_PM NULL
|
||||
#endif
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume);
|
||||
|
||||
static struct platform_driver gpio_fan_driver = {
|
||||
.probe = gpio_fan_probe,
|
||||
.shutdown = gpio_fan_shutdown,
|
||||
.driver = {
|
||||
.name = "gpio-fan",
|
||||
.pm = GPIO_FAN_PM,
|
||||
.pm = pm_sleep_ptr(&gpio_fan_pm),
|
||||
.of_match_table = of_match_ptr(of_gpio_fan_match),
|
||||
},
|
||||
};
|
||||
|
@ -267,6 +267,7 @@ gsc_hwmon_get_devtree_pdata(struct device *dev)
|
||||
pdata->nchannels = nchannels;
|
||||
|
||||
/* fan controller base address */
|
||||
of_node_get(dev->parent->of_node);
|
||||
fan = of_find_compatible_node(dev->parent->of_node, NULL, "gw,gsc-fan");
|
||||
if (fan && of_property_read_u32(fan, "reg", &pdata->fan_base)) {
|
||||
of_node_put(fan);
|
||||
|
@ -6,11 +6,13 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/iio/types.h>
|
||||
@ -149,8 +151,8 @@ static int iio_hwmon_probe(struct platform_device *pdev)
|
||||
st->attr_group.attrs = st->attrs;
|
||||
st->groups[0] = &st->attr_group;
|
||||
|
||||
if (dev->of_node) {
|
||||
sname = devm_kasprintf(dev, GFP_KERNEL, "%pOFn", dev->of_node);
|
||||
if (dev_fwnode(dev)) {
|
||||
sname = devm_kasprintf(dev, GFP_KERNEL, "%pfwP", dev_fwnode(dev));
|
||||
if (!sname)
|
||||
return -ENOMEM;
|
||||
strreplace(sname, '-', '_');
|
||||
|
@ -928,7 +928,7 @@ static void ina3221_remove(struct i2c_client *client)
|
||||
mutex_destroy(&ina->lock);
|
||||
}
|
||||
|
||||
static int __maybe_unused ina3221_suspend(struct device *dev)
|
||||
static int ina3221_suspend(struct device *dev)
|
||||
{
|
||||
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
@ -951,7 +951,7 @@ static int __maybe_unused ina3221_suspend(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ina3221_resume(struct device *dev)
|
||||
static int ina3221_resume(struct device *dev)
|
||||
{
|
||||
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
@ -994,11 +994,8 @@ static int __maybe_unused ina3221_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ina3221_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(ina3221_suspend, ina3221_resume, NULL)
|
||||
};
|
||||
static DEFINE_RUNTIME_DEV_PM_OPS(ina3221_pm, ina3221_suspend, ina3221_resume,
|
||||
NULL);
|
||||
|
||||
static const struct of_device_id ina3221_of_match_table[] = {
|
||||
{ .compatible = "ti,ina3221", },
|
||||
@ -1018,7 +1015,7 @@ static struct i2c_driver ina3221_i2c_driver = {
|
||||
.driver = {
|
||||
.name = INA3221_DRIVER_NAME,
|
||||
.of_match_table = ina3221_of_match_table,
|
||||
.pm = &ina3221_pm,
|
||||
.pm = pm_ptr(&ina3221_pm),
|
||||
},
|
||||
.id_table = ina3221_ids,
|
||||
};
|
||||
|
@ -3179,7 +3179,7 @@ static int it87_probe(struct platform_device *pdev)
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static void __maybe_unused it87_resume_sio(struct platform_device *pdev)
|
||||
static void it87_resume_sio(struct platform_device *pdev)
|
||||
{
|
||||
struct it87_data *data = dev_get_drvdata(&pdev->dev);
|
||||
int err;
|
||||
@ -3211,7 +3211,7 @@ static void __maybe_unused it87_resume_sio(struct platform_device *pdev)
|
||||
superio_exit(data->sioaddr);
|
||||
}
|
||||
|
||||
static int __maybe_unused it87_resume(struct device *dev)
|
||||
static int it87_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct it87_data *data = dev_get_drvdata(dev);
|
||||
@ -3238,12 +3238,12 @@ static int __maybe_unused it87_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(it87_dev_pm_ops, NULL, it87_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(it87_dev_pm_ops, NULL, it87_resume);
|
||||
|
||||
static struct platform_driver it87_driver = {
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.pm = &it87_dev_pm_ops,
|
||||
.pm = pm_sleep_ptr(&it87_dev_pm_ops),
|
||||
},
|
||||
.probe = it87_probe,
|
||||
};
|
||||
|
@ -441,7 +441,7 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
struct jc42_chips *chip = &jc42_chips[i];
|
||||
if (manid == chip->manid &&
|
||||
(devid & chip->devid_mask) == chip->devid) {
|
||||
strlcpy(info->type, "jc42", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "jc42", I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -996,11 +996,11 @@ static int lm63_detect(struct i2c_client *client,
|
||||
}
|
||||
|
||||
if (chip_id == 0x41 && address == 0x4c)
|
||||
strlcpy(info->type, "lm63", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm63", I2C_NAME_SIZE);
|
||||
else if (chip_id == 0x51 && (address == 0x18 || address == 0x4e))
|
||||
strlcpy(info->type, "lm64", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm64", I2C_NAME_SIZE);
|
||||
else if (chip_id == 0x49 && address == 0x4c)
|
||||
strlcpy(info->type, "lm96163", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm96163", I2C_NAME_SIZE);
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
|
@ -257,7 +257,7 @@ static int lm73_detect(struct i2c_client *new_client,
|
||||
if (id < 0 || id != LM73_ID)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "lm73", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm73", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -893,7 +893,7 @@ static int lm75_detect(struct i2c_client *new_client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE);
|
||||
strscpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ static int lm77_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
|| i2c_smbus_read_word_data(client, 7) != min)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "lm77", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm77", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -617,7 +617,7 @@ static int lm78_i2c_detect(struct i2c_client *client,
|
||||
if (isa)
|
||||
mutex_unlock(&isa->update_lock);
|
||||
|
||||
strlcpy(info->type, client_name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, client_name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -586,7 +586,7 @@ static int lm80_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
name = "lm80";
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -412,7 +412,7 @@ static int lm83_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1539,7 +1539,7 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
if (!type_name)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -833,7 +833,7 @@ static int lm87_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2547,7 +2547,7 @@ static int lm90_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -2956,7 +2956,7 @@ static void lm90_alert(struct i2c_client *client, enum i2c_alert_protocol type,
|
||||
}
|
||||
}
|
||||
|
||||
static int __maybe_unused lm90_suspend(struct device *dev)
|
||||
static int lm90_suspend(struct device *dev)
|
||||
{
|
||||
struct lm90_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
@ -2967,7 +2967,7 @@ static int __maybe_unused lm90_suspend(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused lm90_resume(struct device *dev)
|
||||
static int lm90_resume(struct device *dev)
|
||||
{
|
||||
struct lm90_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
@ -2978,14 +2978,14 @@ static int __maybe_unused lm90_resume(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(lm90_pm_ops, lm90_suspend, lm90_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(lm90_pm_ops, lm90_suspend, lm90_resume);
|
||||
|
||||
static struct i2c_driver lm90_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "lm90",
|
||||
.of_match_table = of_match_ptr(lm90_of_match),
|
||||
.pm = &lm90_pm_ops,
|
||||
.pm = pm_sleep_ptr(&lm90_pm_ops),
|
||||
},
|
||||
.probe_new = lm90_probe,
|
||||
.alert = lm90_alert,
|
||||
|
@ -287,7 +287,7 @@ static int lm92_detect(struct i2c_client *new_client,
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "lm92", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "lm92", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2575,7 +2575,7 @@ static int lm93_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
dev_dbg(&adapter->dev, "loading %s at %d, 0x%02x\n",
|
||||
client->name, i2c_adapter_id(client->adapter),
|
||||
client->addr);
|
||||
|
@ -644,7 +644,7 @@ static int lm95234_detect(struct i2c_client *client,
|
||||
if (val & model_mask)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -389,7 +389,7 @@ static int lm95241_detect(struct i2c_client *new_client,
|
||||
}
|
||||
|
||||
/* Fill the i2c board info */
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -461,7 +461,7 @@ static int lm95245_detect(struct i2c_client *new_client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, name, I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -956,13 +956,6 @@ static struct attribute *ltc2947_attrs[] = {
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ltc2947);
|
||||
|
||||
static void ltc2947_clk_disable(void *data)
|
||||
{
|
||||
struct clk *extclk = data;
|
||||
|
||||
clk_disable_unprepare(extclk);
|
||||
}
|
||||
|
||||
static int ltc2947_setup(struct ltc2947_data *st)
|
||||
{
|
||||
int ret;
|
||||
@ -989,7 +982,7 @@ static int ltc2947_setup(struct ltc2947_data *st)
|
||||
return ret;
|
||||
|
||||
/* check external clock presence */
|
||||
extclk = devm_clk_get_optional(st->dev, NULL);
|
||||
extclk = devm_clk_get_optional_enabled(st->dev, NULL);
|
||||
if (IS_ERR(extclk))
|
||||
return dev_err_probe(st->dev, PTR_ERR(extclk),
|
||||
"Failed to get external clock\n");
|
||||
@ -1007,14 +1000,6 @@ static int ltc2947_setup(struct ltc2947_data *st)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(extclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(st->dev, ltc2947_clk_disable,
|
||||
extclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* as in table 1 of the datasheet */
|
||||
if (rate_hz >= LTC2947_CLK_MIN && rate_hz <= 1000000)
|
||||
pre = 0;
|
||||
@ -1135,7 +1120,7 @@ int ltc2947_core_probe(struct regmap *map, const char *name)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ltc2947_core_probe);
|
||||
|
||||
static int __maybe_unused ltc2947_resume(struct device *dev)
|
||||
static int ltc2947_resume(struct device *dev)
|
||||
{
|
||||
struct ltc2947_data *st = dev_get_drvdata(dev);
|
||||
u32 ctrl = 0;
|
||||
@ -1164,7 +1149,7 @@ static int __maybe_unused ltc2947_resume(struct device *dev)
|
||||
LTC2947_CONT_MODE_MASK, LTC2947_CONT_MODE(1));
|
||||
}
|
||||
|
||||
static int __maybe_unused ltc2947_suspend(struct device *dev)
|
||||
static int ltc2947_suspend(struct device *dev)
|
||||
{
|
||||
struct ltc2947_data *st = dev_get_drvdata(dev);
|
||||
|
||||
@ -1172,8 +1157,7 @@ static int __maybe_unused ltc2947_suspend(struct device *dev)
|
||||
LTC2947_SHUTDOWN_MASK, 1);
|
||||
}
|
||||
|
||||
SIMPLE_DEV_PM_OPS(ltc2947_pm_ops, ltc2947_suspend, ltc2947_resume);
|
||||
EXPORT_SYMBOL_GPL(ltc2947_pm_ops);
|
||||
EXPORT_SIMPLE_DEV_PM_OPS(ltc2947_pm_ops, ltc2947_suspend, ltc2947_resume);
|
||||
|
||||
const struct of_device_id ltc2947_of_match[] = {
|
||||
{ .compatible = "adi,ltc2947" },
|
||||
|
@ -36,7 +36,7 @@ static struct i2c_driver ltc2947_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2947",
|
||||
.of_match_table = ltc2947_of_match,
|
||||
.pm = <c2947_pm_ops,
|
||||
.pm = pm_sleep_ptr(<c2947_pm_ops),
|
||||
},
|
||||
.probe_new = ltc2947_probe,
|
||||
.id_table = ltc2947_id,
|
||||
|
@ -38,7 +38,7 @@ static struct spi_driver ltc2947_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2947",
|
||||
.of_match_table = ltc2947_of_match,
|
||||
.pm = <c2947_pm_ops,
|
||||
.pm = pm_sleep_ptr(<c2947_pm_ops),
|
||||
},
|
||||
.probe = ltc2947_probe,
|
||||
.id_table = ltc2947_id,
|
||||
|
@ -241,7 +241,7 @@ static int max1619_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "max1619", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "max1619", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ static int max1668_detect(struct i2c_client *client,
|
||||
if (!type_name)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
strscpy(info->type, type_name, I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ static void max31722_remove(struct spi_device *spi)
|
||||
dev_warn(&spi->dev, "Failed to put device in stand-by mode\n");
|
||||
}
|
||||
|
||||
static int __maybe_unused max31722_suspend(struct device *dev)
|
||||
static int max31722_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_device *spi_device = to_spi_device(dev);
|
||||
struct max31722_data *data = spi_get_drvdata(spi_device);
|
||||
@ -121,7 +121,7 @@ static int __maybe_unused max31722_suspend(struct device *dev)
|
||||
return max31722_set_mode(data, MAX31722_MODE_STANDBY);
|
||||
}
|
||||
|
||||
static int __maybe_unused max31722_resume(struct device *dev)
|
||||
static int max31722_resume(struct device *dev)
|
||||
{
|
||||
struct spi_device *spi_device = to_spi_device(dev);
|
||||
struct max31722_data *data = spi_get_drvdata(spi_device);
|
||||
@ -129,7 +129,7 @@ static int __maybe_unused max31722_resume(struct device *dev)
|
||||
return max31722_set_mode(data, MAX31722_MODE_CONTINUOUS);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(max31722_pm_ops, max31722_suspend, max31722_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(max31722_pm_ops, max31722_suspend, max31722_resume);
|
||||
|
||||
static const struct spi_device_id max31722_spi_id[] = {
|
||||
{"max31722", 0},
|
||||
@ -141,7 +141,7 @@ MODULE_DEVICE_TABLE(spi, max31722_spi_id);
|
||||
static struct spi_driver max31722_driver = {
|
||||
.driver = {
|
||||
.name = "max31722",
|
||||
.pm = &max31722_pm_ops,
|
||||
.pm = pm_sleep_ptr(&max31722_pm_ops),
|
||||
},
|
||||
.probe = max31722_probe,
|
||||
.remove = max31722_remove,
|
||||
|
@ -399,33 +399,33 @@ static int max31730_detect(struct i2c_client *client,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "max31730", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "max31730", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused max31730_suspend(struct device *dev)
|
||||
static int max31730_suspend(struct device *dev)
|
||||
{
|
||||
struct max31730_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return max31730_write_config(data, MAX31730_STOP, 0);
|
||||
}
|
||||
|
||||
static int __maybe_unused max31730_resume(struct device *dev)
|
||||
static int max31730_resume(struct device *dev)
|
||||
{
|
||||
struct max31730_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return max31730_write_config(data, 0, MAX31730_STOP);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(max31730_pm_ops, max31730_suspend, max31730_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(max31730_pm_ops, max31730_suspend, max31730_resume);
|
||||
|
||||
static struct i2c_driver max31730_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "max31730",
|
||||
.of_match_table = of_match_ptr(max31730_of_match),
|
||||
.pm = &max31730_pm_ops,
|
||||
.pm = pm_sleep_ptr(&max31730_pm_ops),
|
||||
},
|
||||
.probe_new = max31730_probe,
|
||||
.id_table = max31730_ids,
|
||||
|
596
drivers/hwmon/max31760.c
Normal file
596
drivers/hwmon/max31760.c
Normal file
@ -0,0 +1,596 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
#define REG_CR1 0x00
|
||||
#define CR1_HYST BIT(5)
|
||||
#define CR1_DRV GENMASK(4, 3)
|
||||
#define CR1_TEMP_SRC GENMASK(1, 0)
|
||||
#define REG_CR2 0x01
|
||||
#define CR2_STBY BIT(7)
|
||||
#define CR2_ALERTS BIT(6)
|
||||
#define CR2_DFC BIT(0)
|
||||
#define REG_CR3 0x02
|
||||
#define REG_PWMR 0x50
|
||||
#define REG_PWMV 0x51
|
||||
#define REG_STATUS 0x5A
|
||||
#define STATUS_ALARM_CRIT(ch) BIT(2 + 2 * (ch))
|
||||
#define STATUS_ALARM_MAX(ch) BIT(3 + 2 * (ch))
|
||||
#define STATUS_RDFA BIT(6)
|
||||
|
||||
#define REG_TACH(ch) (0x52 + (ch) * 2)
|
||||
#define REG_TEMP_INPUT(ch) (0x56 + (ch) * 2)
|
||||
#define REG_TEMP_MAX(ch) (0x06 + (ch) * 2)
|
||||
#define REG_TEMP_CRIT(ch) (0x0A + (ch) * 2)
|
||||
|
||||
#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125)
|
||||
#define TEMP11_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \
|
||||
127875), 125) * 32)
|
||||
|
||||
#define LUT_SIZE 48
|
||||
|
||||
#define REG_LUT(index) (0x20 + (index))
|
||||
|
||||
struct max31760_state {
|
||||
struct regmap *regmap;
|
||||
|
||||
struct lut_attribute {
|
||||
char name[24];
|
||||
struct sensor_device_attribute sda;
|
||||
} lut[LUT_SIZE];
|
||||
|
||||
struct attribute *attrs[LUT_SIZE + 2];
|
||||
struct attribute_group group;
|
||||
const struct attribute_group *groups[2];
|
||||
};
|
||||
|
||||
static bool max31760_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
return reg > 0x50;
|
||||
}
|
||||
|
||||
static const struct regmap_config regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x5B,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.volatile_reg = max31760_volatile_reg,
|
||||
};
|
||||
|
||||
static const int max31760_pwm_freq[] = {33, 150, 1500, 25000};
|
||||
|
||||
static int tach_to_rpm(u16 tach)
|
||||
{
|
||||
if (tach == 0)
|
||||
tach = 1;
|
||||
|
||||
return 60 * 100000 / tach / 2;
|
||||
}
|
||||
|
||||
static int max31760_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
unsigned int regval;
|
||||
unsigned int reg_temp;
|
||||
s16 temp;
|
||||
u8 reg[2];
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_fault:
|
||||
ret = regmap_read(state->regmap, REG_STATUS, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = FIELD_GET(STATUS_RDFA, regval);
|
||||
|
||||
return 0;
|
||||
case hwmon_temp_max_alarm:
|
||||
ret = regmap_read(state->regmap, REG_STATUS, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (channel)
|
||||
*val = FIELD_GET(STATUS_ALARM_MAX(1), regval);
|
||||
else
|
||||
*val = FIELD_GET(STATUS_ALARM_MAX(0), regval);
|
||||
|
||||
return 0;
|
||||
case hwmon_temp_crit_alarm:
|
||||
ret = regmap_read(state->regmap, REG_STATUS, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (channel)
|
||||
*val = FIELD_GET(STATUS_ALARM_CRIT(1), regval);
|
||||
else
|
||||
*val = FIELD_GET(STATUS_ALARM_CRIT(0), regval);
|
||||
|
||||
return 0;
|
||||
case hwmon_temp_input:
|
||||
reg_temp = REG_TEMP_INPUT(channel);
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
reg_temp = REG_TEMP_MAX(channel);
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
reg_temp = REG_TEMP_CRIT(channel);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = regmap_bulk_read(state->regmap, reg_temp, reg, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
temp = (reg[0] << 8) | reg[1];
|
||||
|
||||
*val = TEMP11_FROM_REG(temp);
|
||||
|
||||
return 0;
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
ret = regmap_bulk_read(state->regmap, REG_TACH(channel), reg, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = tach_to_rpm(reg[0] * 256 + reg[1]);
|
||||
|
||||
return 0;
|
||||
case hwmon_fan_fault:
|
||||
ret = regmap_read(state->regmap, REG_STATUS, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (channel)
|
||||
*val = FIELD_GET(BIT(1), regval);
|
||||
else
|
||||
*val = FIELD_GET(BIT(0), regval);
|
||||
|
||||
return 0;
|
||||
case hwmon_fan_enable:
|
||||
ret = regmap_read(state->regmap, REG_CR3, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (channel)
|
||||
*val = FIELD_GET(BIT(1), regval);
|
||||
else
|
||||
*val = FIELD_GET(BIT(0), regval);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
ret = regmap_read(state->regmap, REG_PWMV, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = regval;
|
||||
|
||||
return 0;
|
||||
case hwmon_pwm_freq:
|
||||
ret = regmap_read(state->regmap, REG_CR1, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regval = FIELD_GET(CR1_DRV, regval);
|
||||
if (regval >= ARRAY_SIZE(max31760_pwm_freq))
|
||||
return -EINVAL;
|
||||
|
||||
*val = max31760_pwm_freq[regval];
|
||||
|
||||
return 0;
|
||||
case hwmon_pwm_enable:
|
||||
ret = regmap_read(state->regmap, REG_CR2, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = 2 - FIELD_GET(CR2_DFC, regval);
|
||||
|
||||
return 0;
|
||||
case hwmon_pwm_auto_channels_temp:
|
||||
ret = regmap_read(state->regmap, REG_CR1, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (FIELD_GET(CR1_TEMP_SRC, regval)) {
|
||||
case 0:
|
||||
*val = 2;
|
||||
break;
|
||||
case 1:
|
||||
*val = 1;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
*val = 3;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int max31760_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
unsigned int pwm_index;
|
||||
unsigned int reg_temp;
|
||||
int temp;
|
||||
u8 reg_val[2];
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_max:
|
||||
reg_temp = REG_TEMP_MAX(channel);
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
reg_temp = REG_TEMP_CRIT(channel);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
temp = TEMP11_TO_REG(val);
|
||||
reg_val[0] = temp >> 8;
|
||||
reg_val[1] = temp & 0xFF;
|
||||
|
||||
return regmap_bulk_write(state->regmap, reg_temp, reg_val, 2);
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_enable:
|
||||
if (val == 0)
|
||||
return regmap_clear_bits(state->regmap, REG_CR3, BIT(channel));
|
||||
|
||||
if (val == 1)
|
||||
return regmap_set_bits(state->regmap, REG_CR3, BIT(channel));
|
||||
|
||||
return -EINVAL;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
if (val < 0 || val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_write(state->regmap, REG_PWMR, val);
|
||||
case hwmon_pwm_enable:
|
||||
if (val == 1)
|
||||
return regmap_set_bits(state->regmap, REG_CR2, CR2_DFC);
|
||||
|
||||
if (val == 2)
|
||||
return regmap_clear_bits(state->regmap, REG_CR2, CR2_DFC);
|
||||
|
||||
return -EINVAL;
|
||||
case hwmon_pwm_freq:
|
||||
pwm_index = find_closest(val, max31760_pwm_freq,
|
||||
ARRAY_SIZE(max31760_pwm_freq));
|
||||
|
||||
return regmap_update_bits(state->regmap,
|
||||
REG_CR1, CR1_DRV,
|
||||
FIELD_PREP(CR1_DRV, pwm_index));
|
||||
case hwmon_pwm_auto_channels_temp:
|
||||
switch (val) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
val = 0;
|
||||
break;
|
||||
case 3:
|
||||
val = 2;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return regmap_update_bits(state->regmap, REG_CR1, CR1_TEMP_SRC, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *max31760_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_ENABLE),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_FAULT |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
|
||||
HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(pwm,
|
||||
HWMON_PWM_ENABLE | HWMON_PWM_FREQ | HWMON_PWM_INPUT |
|
||||
HWMON_PWM_AUTO_CHANNELS_TEMP),
|
||||
NULL
|
||||
};
|
||||
|
||||
static umode_t max31760_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_max_alarm:
|
||||
case hwmon_temp_crit_alarm:
|
||||
case hwmon_temp_fault:
|
||||
case hwmon_temp_label:
|
||||
return 0444;
|
||||
case hwmon_temp_max:
|
||||
case hwmon_temp_crit:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
case hwmon_fan_fault:
|
||||
return 0444;
|
||||
case hwmon_fan_enable:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_enable:
|
||||
case hwmon_pwm_input:
|
||||
case hwmon_pwm_freq:
|
||||
case hwmon_pwm_auto_channels_temp:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int max31760_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (attr != hwmon_temp_label)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
*str = channel ? "local" : "remote";
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_ops max31760_hwmon_ops = {
|
||||
.is_visible = max31760_is_visible,
|
||||
.read = max31760_read,
|
||||
.write = max31760_write,
|
||||
.read_string = max31760_read_string
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info max31760_chip_info = {
|
||||
.ops = &max31760_hwmon_ops,
|
||||
.info = max31760_info,
|
||||
};
|
||||
|
||||
static ssize_t lut_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(devattr);
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
unsigned int regval;
|
||||
|
||||
ret = regmap_read(state->regmap, REG_LUT(sda->index), ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", regval);
|
||||
}
|
||||
|
||||
static ssize_t lut_store(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(devattr);
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
u8 pwm;
|
||||
|
||||
ret = kstrtou8(buf, 10, &pwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(state->regmap, REG_LUT(sda->index), pwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pwm1_auto_point_temp_hyst_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(state->regmap, REG_CR1, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", (1 + (int)FIELD_GET(CR1_HYST, regval)) * 2000);
|
||||
}
|
||||
|
||||
static ssize_t pwm1_auto_point_temp_hyst_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
unsigned int hyst;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou32(buf, 10, &hyst);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hyst < 3000)
|
||||
ret = regmap_clear_bits(state->regmap, REG_CR1, CR1_HYST);
|
||||
else
|
||||
ret = regmap_set_bits(state->regmap, REG_CR1, CR1_HYST);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(pwm1_auto_point_temp_hyst);
|
||||
|
||||
static void max31760_create_lut_nodes(struct max31760_state *state)
|
||||
{
|
||||
int i;
|
||||
struct sensor_device_attribute *sda;
|
||||
struct lut_attribute *lut;
|
||||
|
||||
for (i = 0; i < LUT_SIZE; ++i) {
|
||||
lut = &state->lut[i];
|
||||
sda = &lut->sda;
|
||||
|
||||
snprintf(lut->name, sizeof(lut->name),
|
||||
"pwm1_auto_point%d_pwm", i + 1);
|
||||
|
||||
sda->dev_attr.attr.mode = 0644;
|
||||
sda->index = i;
|
||||
sda->dev_attr.show = lut_show;
|
||||
sda->dev_attr.store = lut_store;
|
||||
sda->dev_attr.attr.name = lut->name;
|
||||
|
||||
sysfs_attr_init(&sda->dev_attr.attr);
|
||||
|
||||
state->attrs[i] = &sda->dev_attr.attr;
|
||||
}
|
||||
|
||||
state->attrs[i] = &dev_attr_pwm1_auto_point_temp_hyst.attr;
|
||||
|
||||
state->group.attrs = state->attrs;
|
||||
state->groups[0] = &state->group;
|
||||
}
|
||||
|
||||
static int max31760_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct max31760_state *state;
|
||||
struct device *hwmon_dev;
|
||||
int ret;
|
||||
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
state->regmap = devm_regmap_init_i2c(client, ®map_config);
|
||||
if (IS_ERR(state->regmap))
|
||||
return dev_err_probe(dev,
|
||||
PTR_ERR(state->regmap),
|
||||
"regmap initialization failed\n");
|
||||
|
||||
dev_set_drvdata(dev, state);
|
||||
|
||||
/* Set alert output to comparator mode */
|
||||
ret = regmap_set_bits(state->regmap, REG_CR2, CR2_ALERTS);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "cannot write register\n");
|
||||
|
||||
max31760_create_lut_nodes(state);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
state,
|
||||
&max31760_chip_info,
|
||||
state->groups);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id max31760_of_match[] = {
|
||||
{.compatible = "adi,max31760"},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max31760_of_match);
|
||||
|
||||
static const struct i2c_device_id max31760_id[] = {
|
||||
{"max31760"},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max31760_id);
|
||||
|
||||
static int max31760_suspend(struct device *dev)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
|
||||
return regmap_set_bits(state->regmap, REG_CR2, CR2_STBY);
|
||||
}
|
||||
|
||||
static int max31760_resume(struct device *dev)
|
||||
{
|
||||
struct max31760_state *state = dev_get_drvdata(dev);
|
||||
|
||||
return regmap_clear_bits(state->regmap, REG_CR2, CR2_STBY);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend,
|
||||
max31760_resume);
|
||||
|
||||
static struct i2c_driver max31760_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "max31760",
|
||||
.of_match_table = max31760_of_match,
|
||||
.pm = pm_ptr(&max31760_pm_ops)
|
||||
},
|
||||
.probe_new = max31760_probe,
|
||||
.id_table = max31760_id
|
||||
};
|
||||
module_i2c_driver(max31760_driver);
|
||||
|
||||
MODULE_AUTHOR("Ibrahim Tilki <Ibrahim.Tilki@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices MAX31760 Fan Speed Controller");
|
||||
MODULE_SOFTDEP("pre: regmap_i2c");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_LICENSE("GPL");
|
@ -202,6 +202,9 @@ static int max31790_read_fan(struct device *dev, u32 attr, int channel,
|
||||
}
|
||||
mutex_unlock(&data->update_lock);
|
||||
return 0;
|
||||
case hwmon_fan_enable:
|
||||
*val = !!(data->fan_config[channel] & MAX31790_FAN_CFG_TACH_INPUT_EN);
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
@ -214,7 +217,7 @@ static int max31790_write_fan(struct device *dev, u32 attr, int channel,
|
||||
struct i2c_client *client = data->client;
|
||||
int target_count;
|
||||
int err = 0;
|
||||
u8 bits;
|
||||
u8 bits, fan_config;
|
||||
int sr;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
@ -243,6 +246,23 @@ static int max31790_write_fan(struct device *dev, u32 attr, int channel,
|
||||
MAX31790_REG_TARGET_COUNT(channel),
|
||||
data->target_count[channel]);
|
||||
break;
|
||||
case hwmon_fan_enable:
|
||||
fan_config = data->fan_config[channel];
|
||||
if (val == 0) {
|
||||
fan_config &= ~MAX31790_FAN_CFG_TACH_INPUT_EN;
|
||||
} else if (val == 1) {
|
||||
fan_config |= MAX31790_FAN_CFG_TACH_INPUT_EN;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (fan_config != data->fan_config[channel]) {
|
||||
err = i2c_smbus_write_byte_data(client, MAX31790_REG_FAN_CONFIG(channel),
|
||||
fan_config);
|
||||
if (!err)
|
||||
data->fan_config[channel] = fan_config;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
err = -EOPNOTSUPP;
|
||||
break;
|
||||
@ -270,6 +290,10 @@ static umode_t max31790_fan_is_visible(const void *_data, u32 attr, int channel)
|
||||
!(fan_config & MAX31790_FAN_CFG_TACH_INPUT))
|
||||
return 0644;
|
||||
return 0;
|
||||
case hwmon_fan_enable:
|
||||
if (channel < NR_CHANNEL)
|
||||
return 0644;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -423,12 +447,12 @@ static umode_t max31790_is_visible(const void *data,
|
||||
|
||||
static const struct hwmon_channel_info *max31790_info[] = {
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT | HWMON_F_ENABLE,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
|
@ -514,7 +514,7 @@ static int max6639_detect(struct i2c_client *client,
|
||||
if (dev_id != 0x58 || manu_id != 0x4D)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "max6639", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "max6639", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -571,7 +571,6 @@ static int max6639_probe(struct i2c_client *client)
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int max6639_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
@ -609,7 +608,6 @@ static int max6639_resume(struct device *dev)
|
||||
return i2c_smbus_write_byte_data(client,
|
||||
MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY);
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
static const struct i2c_device_id max6639_id[] = {
|
||||
{"max6639", 0},
|
||||
@ -618,13 +616,13 @@ static const struct i2c_device_id max6639_id[] = {
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, max6639_id);
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume);
|
||||
|
||||
static struct i2c_driver max6639_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "max6639",
|
||||
.pm = &max6639_pm_ops,
|
||||
.pm = pm_sleep_ptr(&max6639_pm_ops),
|
||||
},
|
||||
.probe_new = max6639_probe,
|
||||
.id_table = max6639_id,
|
||||
|
@ -148,7 +148,7 @@ static int max6642_detect(struct i2c_client *client,
|
||||
if ((reg_status & 0x2b) != 0x00)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "max6642", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "max6642", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
@ -17,6 +18,7 @@
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
/* PVT Common register */
|
||||
@ -30,6 +32,8 @@
|
||||
#define CH_NUM_MSK GENMASK(31, 24)
|
||||
#define CH_NUM_SFT 24
|
||||
|
||||
#define VM_NUM_MAX (VM_NUM_MSK >> VM_NUM_SFT)
|
||||
|
||||
/* Macro Common Register */
|
||||
#define CLK_SYNTH 0x00
|
||||
#define CLK_SYNTH_LO_SFT 0
|
||||
@ -99,13 +103,67 @@
|
||||
|
||||
#define PVT_POLL_DELAY_US 20
|
||||
#define PVT_POLL_TIMEOUT_US 20000
|
||||
#define PVT_H_CONST 100000
|
||||
#define PVT_CAL5_CONST 2047
|
||||
#define PVT_G_CONST 40000
|
||||
#define PVT_CONV_BITS 10
|
||||
#define PVT_N_CONST 90
|
||||
#define PVT_R_CONST 245805
|
||||
|
||||
#define PVT_TEMP_MIN_mC -40000
|
||||
#define PVT_TEMP_MAX_mC 125000
|
||||
|
||||
/* Temperature coefficients for series 5 */
|
||||
#define PVT_SERIES5_H_CONST 200000
|
||||
#define PVT_SERIES5_G_CONST 60000
|
||||
#define PVT_SERIES5_J_CONST -100
|
||||
#define PVT_SERIES5_CAL5_CONST 4094
|
||||
|
||||
/* Temperature coefficients for series 6 */
|
||||
#define PVT_SERIES6_H_CONST 249400
|
||||
#define PVT_SERIES6_G_CONST 57400
|
||||
#define PVT_SERIES6_J_CONST 0
|
||||
#define PVT_SERIES6_CAL5_CONST 4096
|
||||
|
||||
#define TEMPERATURE_SENSOR_SERIES_5 5
|
||||
#define TEMPERATURE_SENSOR_SERIES_6 6
|
||||
|
||||
#define PRE_SCALER_X1 1
|
||||
#define PRE_SCALER_X2 2
|
||||
|
||||
/**
|
||||
* struct voltage_device - VM single input parameters.
|
||||
* @vm_map: Map channel number to VM index.
|
||||
* @ch_map: Map channel number to channel index.
|
||||
* @pre_scaler: Pre scaler value (1 or 2) used to normalize the voltage output
|
||||
* result.
|
||||
*
|
||||
* The structure provides mapping between channel-number (0..N-1) to VM-index
|
||||
* (0..num_vm-1) and channel-index (0..ch_num-1) where N = num_vm * ch_num.
|
||||
* It also provides normalization factor for the VM equation.
|
||||
*/
|
||||
struct voltage_device {
|
||||
u32 vm_map;
|
||||
u32 ch_map;
|
||||
u32 pre_scaler;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct voltage_channels - VM channel count.
|
||||
* @total: Total number of channels in all VMs.
|
||||
* @max: Maximum number of channels among all VMs.
|
||||
*
|
||||
* The structure provides channel count information across all VMs.
|
||||
*/
|
||||
struct voltage_channels {
|
||||
u32 total;
|
||||
u8 max;
|
||||
};
|
||||
|
||||
struct temp_coeff {
|
||||
u32 h;
|
||||
u32 g;
|
||||
u32 cal5;
|
||||
s32 j;
|
||||
};
|
||||
|
||||
struct pvt_device {
|
||||
struct regmap *c_map;
|
||||
struct regmap *t_map;
|
||||
@ -113,14 +171,74 @@ struct pvt_device {
|
||||
struct regmap *v_map;
|
||||
struct clk *clk;
|
||||
struct reset_control *rst;
|
||||
struct dentry *dbgfs_dir;
|
||||
struct voltage_device *vd;
|
||||
struct voltage_channels vm_channels;
|
||||
struct temp_coeff ts_coeff;
|
||||
u32 t_num;
|
||||
u32 p_num;
|
||||
u32 v_num;
|
||||
u32 c_num;
|
||||
u32 ip_freq;
|
||||
u8 *vm_idx;
|
||||
};
|
||||
|
||||
static ssize_t pvt_ts_coeff_j_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct pvt_device *pvt = file->private_data;
|
||||
unsigned int len;
|
||||
char buf[13];
|
||||
|
||||
len = scnprintf(buf, sizeof(buf), "%d\n", pvt->ts_coeff.j);
|
||||
|
||||
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
||||
}
|
||||
|
||||
static ssize_t pvt_ts_coeff_j_write(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct pvt_device *pvt = file->private_data;
|
||||
int ret;
|
||||
|
||||
ret = kstrtos32_from_user(user_buf, count, 0, &pvt->ts_coeff.j);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations pvt_ts_coeff_j_fops = {
|
||||
.read = pvt_ts_coeff_j_read,
|
||||
.write = pvt_ts_coeff_j_write,
|
||||
.open = simple_open,
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static void devm_pvt_ts_dbgfs_remove(void *data)
|
||||
{
|
||||
struct pvt_device *pvt = (struct pvt_device *)data;
|
||||
|
||||
debugfs_remove_recursive(pvt->dbgfs_dir);
|
||||
pvt->dbgfs_dir = NULL;
|
||||
}
|
||||
|
||||
static int pvt_ts_dbgfs_create(struct pvt_device *pvt, struct device *dev)
|
||||
{
|
||||
pvt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL);
|
||||
|
||||
debugfs_create_u32("ts_coeff_h", 0644, pvt->dbgfs_dir,
|
||||
&pvt->ts_coeff.h);
|
||||
debugfs_create_u32("ts_coeff_g", 0644, pvt->dbgfs_dir,
|
||||
&pvt->ts_coeff.g);
|
||||
debugfs_create_u32("ts_coeff_cal5", 0644, pvt->dbgfs_dir,
|
||||
&pvt->ts_coeff.cal5);
|
||||
debugfs_create_file("ts_coeff_j", 0644, pvt->dbgfs_dir, pvt,
|
||||
&pvt_ts_coeff_j_fops);
|
||||
|
||||
return devm_add_action_or_reset(dev, devm_pvt_ts_dbgfs_remove, pvt);
|
||||
}
|
||||
|
||||
static umode_t pvt_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
@ -139,13 +257,28 @@ static umode_t pvt_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long pvt_calc_temp(struct pvt_device *pvt, u32 nbs)
|
||||
{
|
||||
/*
|
||||
* Convert the register value to degrees centigrade temperature:
|
||||
* T = G + H * (n / cal5 - 0.5) + J * F
|
||||
*/
|
||||
struct temp_coeff *ts_coeff = &pvt->ts_coeff;
|
||||
|
||||
s64 tmp = ts_coeff->g +
|
||||
div_s64(ts_coeff->h * (s64)nbs, ts_coeff->cal5) -
|
||||
ts_coeff->h / 2 +
|
||||
div_s64(ts_coeff->j * (s64)pvt->ip_freq, HZ_PER_MHZ);
|
||||
|
||||
return clamp_val(tmp, PVT_TEMP_MIN_mC, PVT_TEMP_MAX_mC);
|
||||
}
|
||||
|
||||
static int pvt_read_temp(struct device *dev, u32 attr, int channel, long *val)
|
||||
{
|
||||
struct pvt_device *pvt = dev_get_drvdata(dev);
|
||||
struct regmap *t_map = pvt->t_map;
|
||||
u32 stat, nbs;
|
||||
int ret;
|
||||
u64 tmp;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
@ -157,7 +290,7 @@ static int pvt_read_temp(struct device *dev, u32 attr, int channel, long *val)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(t_map, SDIF_DATA(channel), &nbs);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
nbs &= SAMPLE_DATA_MSK;
|
||||
@ -166,9 +299,7 @@ static int pvt_read_temp(struct device *dev, u32 attr, int channel, long *val)
|
||||
* Convert the register value to
|
||||
* degrees centigrade temperature
|
||||
*/
|
||||
tmp = nbs * PVT_H_CONST;
|
||||
do_div(tmp, PVT_CAL5_CONST);
|
||||
*val = tmp - PVT_G_CONST - pvt->ip_freq;
|
||||
*val = pvt_calc_temp(pvt, nbs);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
@ -180,15 +311,15 @@ static int pvt_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
{
|
||||
struct pvt_device *pvt = dev_get_drvdata(dev);
|
||||
struct regmap *v_map = pvt->v_map;
|
||||
u32 n, stat, pre_scaler;
|
||||
u8 vm_idx, ch_idx;
|
||||
u32 n, stat;
|
||||
int ret;
|
||||
|
||||
if (channel >= pvt->v_num * pvt->c_num)
|
||||
if (channel >= pvt->vm_channels.total)
|
||||
return -EINVAL;
|
||||
|
||||
vm_idx = pvt->vm_idx[channel / pvt->c_num];
|
||||
ch_idx = channel % pvt->c_num;
|
||||
vm_idx = pvt->vd[channel].vm_map;
|
||||
ch_idx = pvt->vd[channel].ch_map;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
@ -200,10 +331,11 @@ static int pvt_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(v_map, VM_SDIF_DATA(vm_idx, ch_idx), &n);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
n &= SAMPLE_DATA_MSK;
|
||||
pre_scaler = pvt->vd[channel].pre_scaler;
|
||||
/*
|
||||
* Convert the N bitstream count into voltage.
|
||||
* To support negative voltage calculation for 64bit machines
|
||||
@ -215,7 +347,8 @@ static int pvt_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
* BIT(x) may not be used instead of (1 << x) because it's
|
||||
* unsigned.
|
||||
*/
|
||||
*val = (PVT_N_CONST * (long)n - PVT_R_CONST) / (1 << PVT_CONV_BITS);
|
||||
*val = pre_scaler * (PVT_N_CONST * (long)n - PVT_R_CONST) /
|
||||
(1 << PVT_CONV_BITS);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
@ -290,23 +423,23 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
(key >> 1) << CLK_SYNTH_HI_SFT |
|
||||
(key >> 1) << CLK_SYNTH_HOLD_SFT | CLK_SYNTH_EN;
|
||||
|
||||
pvt->ip_freq = sys_freq * 100 / (key + 2);
|
||||
pvt->ip_freq = clk_get_rate(pvt->clk) / (key + 2);
|
||||
|
||||
if (t_num) {
|
||||
ret = regmap_write(t_map, SDIF_SMPL_CTRL, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(t_map, SDIF_HALT, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(t_map, CLK_SYNTH, clk_synth);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(t_map, SDIF_DISABLE, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(t_map, SDIF_STAT,
|
||||
@ -319,7 +452,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
val = CFG0_MODE_2 | CFG0_PARALLEL_OUT | CFG0_12_BIT |
|
||||
IP_CFG << SDIF_ADDR_SFT | SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(t_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(t_map, SDIF_STAT,
|
||||
@ -332,7 +465,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
val = POWER_DELAY_CYCLE_256 | IP_TMR << SDIF_ADDR_SFT |
|
||||
SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(t_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(t_map, SDIF_STAT,
|
||||
@ -346,39 +479,39 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
IP_CTRL << SDIF_ADDR_SFT |
|
||||
SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(t_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (p_num) {
|
||||
ret = regmap_write(p_map, SDIF_HALT, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(p_map, SDIF_DISABLE, BIT(p_num) - 1);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(p_map, CLK_SYNTH, clk_synth);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (v_num) {
|
||||
ret = regmap_write(v_map, SDIF_SMPL_CTRL, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(v_map, SDIF_HALT, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(v_map, CLK_SYNTH, clk_synth);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(v_map, SDIF_DISABLE, 0x0);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(v_map, SDIF_STAT,
|
||||
@ -388,7 +521,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = (BIT(pvt->c_num) - 1) | VM_CH_INIT |
|
||||
val = (BIT(pvt->vm_channels.max) - 1) | VM_CH_INIT |
|
||||
IP_POLL << SDIF_ADDR_SFT | SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(v_map, SDIF_W, val);
|
||||
if (ret < 0)
|
||||
@ -405,7 +538,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
CFG1_14_BIT | IP_CFG << SDIF_ADDR_SFT |
|
||||
SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(v_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(v_map, SDIF_STAT,
|
||||
@ -418,7 +551,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
val = POWER_DELAY_CYCLE_64 | IP_TMR << SDIF_ADDR_SFT |
|
||||
SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(v_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(v_map, SDIF_STAT,
|
||||
@ -432,7 +565,7 @@ static int pvt_init(struct pvt_device *pvt)
|
||||
IP_CTRL << SDIF_ADDR_SFT |
|
||||
SDIF_WRN_W | SDIF_PROG;
|
||||
ret = regmap_write(v_map, SDIF_W, val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -477,24 +610,6 @@ static int pvt_get_regmap(struct platform_device *pdev, char *reg_name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pvt_clk_disable(void *data)
|
||||
{
|
||||
struct pvt_device *pvt = data;
|
||||
|
||||
clk_disable_unprepare(pvt->clk);
|
||||
}
|
||||
|
||||
static int pvt_clk_enable(struct device *dev, struct pvt_device *pvt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(pvt->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, pvt_clk_disable, pvt);
|
||||
}
|
||||
|
||||
static void pvt_reset_control_assert(void *data)
|
||||
{
|
||||
struct pvt_device *pvt = data;
|
||||
@ -513,6 +628,141 @@ static int pvt_reset_control_deassert(struct device *dev, struct pvt_device *pvt
|
||||
return devm_add_action_or_reset(dev, pvt_reset_control_assert, pvt);
|
||||
}
|
||||
|
||||
static int pvt_get_active_channel(struct device *dev, struct pvt_device *pvt,
|
||||
u32 vm_num, u32 ch_num, u8 *vm_idx)
|
||||
{
|
||||
u8 vm_active_ch[VM_NUM_MAX];
|
||||
int ret, i, j, k;
|
||||
|
||||
ret = device_property_read_u8_array(dev, "moortec,vm-active-channels",
|
||||
vm_active_ch, vm_num);
|
||||
if (ret) {
|
||||
/*
|
||||
* Incase "moortec,vm-active-channels" property is not defined,
|
||||
* we assume each VM sensor has all of its channels active.
|
||||
*/
|
||||
memset(vm_active_ch, ch_num, vm_num);
|
||||
pvt->vm_channels.max = ch_num;
|
||||
pvt->vm_channels.total = ch_num * vm_num;
|
||||
} else {
|
||||
for (i = 0; i < vm_num; i++) {
|
||||
if (vm_active_ch[i] > ch_num) {
|
||||
dev_err(dev, "invalid active channels: %u\n",
|
||||
vm_active_ch[i]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pvt->vm_channels.total += vm_active_ch[i];
|
||||
|
||||
if (vm_active_ch[i] > pvt->vm_channels.max)
|
||||
pvt->vm_channels.max = vm_active_ch[i];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Map between the channel-number to VM-index and channel-index.
|
||||
* Example - 3 VMs, "moortec,vm_active_ch" = <5 2 4>:
|
||||
* vm_map = [0 0 0 0 0 1 1 2 2 2 2]
|
||||
* ch_map = [0 1 2 3 4 0 1 0 1 2 3]
|
||||
*/
|
||||
pvt->vd = devm_kcalloc(dev, pvt->vm_channels.total, sizeof(*pvt->vd),
|
||||
GFP_KERNEL);
|
||||
if (!pvt->vd)
|
||||
return -ENOMEM;
|
||||
|
||||
k = 0;
|
||||
for (i = 0; i < vm_num; i++) {
|
||||
for (j = 0; j < vm_active_ch[i]; j++) {
|
||||
pvt->vd[k].vm_map = vm_idx[i];
|
||||
pvt->vd[k].ch_map = j;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pvt_get_pre_scaler(struct device *dev, struct pvt_device *pvt)
|
||||
{
|
||||
u8 *pre_scaler_ch_list;
|
||||
int i, ret, num_ch;
|
||||
u32 channel;
|
||||
|
||||
/* Set default pre-scaler value to be 1. */
|
||||
for (i = 0; i < pvt->vm_channels.total; i++)
|
||||
pvt->vd[i].pre_scaler = PRE_SCALER_X1;
|
||||
|
||||
/* Get number of channels configured in "moortec,vm-pre-scaler-x2". */
|
||||
num_ch = device_property_count_u8(dev, "moortec,vm-pre-scaler-x2");
|
||||
if (num_ch <= 0)
|
||||
return 0;
|
||||
|
||||
pre_scaler_ch_list = kcalloc(num_ch, sizeof(*pre_scaler_ch_list),
|
||||
GFP_KERNEL);
|
||||
if (!pre_scaler_ch_list)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Get list of all channels that have pre-scaler of 2. */
|
||||
ret = device_property_read_u8_array(dev, "moortec,vm-pre-scaler-x2",
|
||||
pre_scaler_ch_list, num_ch);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < num_ch; i++) {
|
||||
channel = pre_scaler_ch_list[i];
|
||||
pvt->vd[channel].pre_scaler = PRE_SCALER_X2;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(pre_scaler_ch_list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pvt_set_temp_coeff(struct device *dev, struct pvt_device *pvt)
|
||||
{
|
||||
struct temp_coeff *ts_coeff = &pvt->ts_coeff;
|
||||
u32 series;
|
||||
int ret;
|
||||
|
||||
/* Incase ts-series property is not defined, use default 5. */
|
||||
ret = device_property_read_u32(dev, "moortec,ts-series", &series);
|
||||
if (ret)
|
||||
series = TEMPERATURE_SENSOR_SERIES_5;
|
||||
|
||||
switch (series) {
|
||||
case TEMPERATURE_SENSOR_SERIES_5:
|
||||
ts_coeff->h = PVT_SERIES5_H_CONST;
|
||||
ts_coeff->g = PVT_SERIES5_G_CONST;
|
||||
ts_coeff->j = PVT_SERIES5_J_CONST;
|
||||
ts_coeff->cal5 = PVT_SERIES5_CAL5_CONST;
|
||||
break;
|
||||
case TEMPERATURE_SENSOR_SERIES_6:
|
||||
ts_coeff->h = PVT_SERIES6_H_CONST;
|
||||
ts_coeff->g = PVT_SERIES6_G_CONST;
|
||||
ts_coeff->j = PVT_SERIES6_J_CONST;
|
||||
ts_coeff->cal5 = PVT_SERIES6_CAL5_CONST;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "invalid temperature sensor series (%u)\n",
|
||||
series);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "temperature sensor series = %u\n", series);
|
||||
|
||||
/* Override ts-coeff-h/g/j/cal5 if they are defined. */
|
||||
device_property_read_u32(dev, "moortec,ts-coeff-h", &ts_coeff->h);
|
||||
device_property_read_u32(dev, "moortec,ts-coeff-g", &ts_coeff->g);
|
||||
device_property_read_u32(dev, "moortec,ts-coeff-j", &ts_coeff->j);
|
||||
device_property_read_u32(dev, "moortec,ts-coeff-cal5", &ts_coeff->cal5);
|
||||
|
||||
dev_dbg(dev, "ts-coeff: h = %u, g = %u, j = %d, cal5 = %u\n",
|
||||
ts_coeff->h, ts_coeff->g, ts_coeff->j, ts_coeff->cal5);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mr75203_probe(struct platform_device *pdev)
|
||||
{
|
||||
u32 ts_num, vm_num, pd_num, ch_num, val, index, i;
|
||||
@ -531,27 +781,24 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pvt->clk = devm_clk_get(dev, NULL);
|
||||
pvt->clk = devm_clk_get_enabled(dev, NULL);
|
||||
if (IS_ERR(pvt->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(pvt->clk), "failed to get clock\n");
|
||||
|
||||
ret = pvt_clk_enable(dev, pvt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pvt->rst = devm_reset_control_get_exclusive(dev, NULL);
|
||||
pvt->rst = devm_reset_control_get_optional_exclusive(dev, NULL);
|
||||
if (IS_ERR(pvt->rst))
|
||||
return dev_err_probe(dev, PTR_ERR(pvt->rst),
|
||||
"failed to get reset control\n");
|
||||
|
||||
ret = pvt_reset_control_deassert(dev, pvt);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "cannot deassert reset control\n");
|
||||
if (pvt->rst) {
|
||||
ret = pvt_reset_control_deassert(dev, pvt);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"cannot deassert reset control\n");
|
||||
}
|
||||
|
||||
ret = regmap_read(pvt->c_map, PVT_IP_CONFIG, &val);
|
||||
if(ret < 0)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ts_num = (val & TS_NUM_MSK) >> TS_NUM_SFT;
|
||||
@ -561,7 +808,6 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
pvt->t_num = ts_num;
|
||||
pvt->p_num = pd_num;
|
||||
pvt->v_num = vm_num;
|
||||
pvt->c_num = ch_num;
|
||||
val = 0;
|
||||
if (ts_num)
|
||||
val++;
|
||||
@ -581,6 +827,10 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pvt_set_temp_coeff(dev, pvt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
temp_config = devm_kcalloc(dev, ts_num + 1,
|
||||
sizeof(*temp_config), GFP_KERNEL);
|
||||
if (!temp_config)
|
||||
@ -589,6 +839,8 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
memset32(temp_config, HWMON_T_INPUT, ts_num);
|
||||
pvt_temp.config = temp_config;
|
||||
pvt_info[index++] = &pvt_temp;
|
||||
|
||||
pvt_ts_dbgfs_create(pvt, dev);
|
||||
}
|
||||
|
||||
if (pd_num) {
|
||||
@ -598,44 +850,45 @@ static int mr75203_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
if (vm_num) {
|
||||
u32 total_ch;
|
||||
u8 vm_idx[VM_NUM_MAX];
|
||||
|
||||
ret = pvt_get_regmap(pdev, "vm", pvt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pvt->vm_idx = devm_kcalloc(dev, vm_num, sizeof(*pvt->vm_idx),
|
||||
GFP_KERNEL);
|
||||
if (!pvt->vm_idx)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = device_property_read_u8_array(dev, "intel,vm-map",
|
||||
pvt->vm_idx, vm_num);
|
||||
ret = device_property_read_u8_array(dev, "intel,vm-map", vm_idx,
|
||||
vm_num);
|
||||
if (ret) {
|
||||
/*
|
||||
* Incase intel,vm-map property is not defined, we
|
||||
* assume incremental channel numbers.
|
||||
*/
|
||||
for (i = 0; i < vm_num; i++)
|
||||
pvt->vm_idx[i] = i;
|
||||
vm_idx[i] = i;
|
||||
} else {
|
||||
for (i = 0; i < vm_num; i++)
|
||||
if (pvt->vm_idx[i] >= vm_num ||
|
||||
pvt->vm_idx[i] == 0xff) {
|
||||
if (vm_idx[i] >= vm_num || vm_idx[i] == 0xff) {
|
||||
pvt->v_num = i;
|
||||
vm_num = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
total_ch = ch_num * vm_num;
|
||||
in_config = devm_kcalloc(dev, total_ch + 1,
|
||||
ret = pvt_get_active_channel(dev, pvt, vm_num, ch_num, vm_idx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pvt_get_pre_scaler(dev, pvt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
in_config = devm_kcalloc(dev, pvt->vm_channels.total + 1,
|
||||
sizeof(*in_config), GFP_KERNEL);
|
||||
if (!in_config)
|
||||
return -ENOMEM;
|
||||
|
||||
memset32(in_config, HWMON_I_INPUT, total_ch);
|
||||
in_config[total_ch] = 0;
|
||||
memset32(in_config, HWMON_I_INPUT, pvt->vm_channels.total);
|
||||
in_config[pvt->vm_channels.total] = 0;
|
||||
pvt_in.config = in_config;
|
||||
|
||||
pvt_info[index++] = &pvt_in;
|
||||
|
@ -412,7 +412,7 @@ nct6683_create_attr_group(struct device *dev,
|
||||
struct sensor_device_attr_u *su;
|
||||
struct attribute_group *group;
|
||||
struct attribute **attrs;
|
||||
int i, j, count;
|
||||
int i, count;
|
||||
|
||||
if (repeat <= 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
@ -443,7 +443,7 @@ nct6683_create_attr_group(struct device *dev,
|
||||
|
||||
for (i = 0; i < repeat; i++) {
|
||||
t = tg->templates;
|
||||
for (j = 0; *t != NULL; j++) {
|
||||
while (*t) {
|
||||
snprintf(su->name, sizeof(su->name),
|
||||
(*t)->dev_attr.attr.name, tg->base + i);
|
||||
if ((*t)->s2) {
|
||||
|
@ -355,7 +355,7 @@ static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data)
|
||||
}
|
||||
}
|
||||
|
||||
static int __maybe_unused nct6775_suspend(struct device *dev)
|
||||
static int nct6775_suspend(struct device *dev)
|
||||
{
|
||||
int err;
|
||||
u16 tmp;
|
||||
@ -386,7 +386,7 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __maybe_unused nct6775_resume(struct device *dev)
|
||||
static int nct6775_resume(struct device *dev)
|
||||
{
|
||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||
struct nct6775_sio_data *sio_data = dev_get_platdata(dev);
|
||||
@ -467,7 +467,7 @@ abort:
|
||||
return err;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume);
|
||||
|
||||
static void
|
||||
nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data)
|
||||
@ -934,7 +934,7 @@ static int nct6775_platform_probe(struct platform_device *pdev)
|
||||
static struct platform_driver nct6775_driver = {
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.pm = &nct6775_dev_pm_ops,
|
||||
.pm = pm_sleep_ptr(&nct6775_dev_pm_ops),
|
||||
},
|
||||
.probe = nct6775_platform_probe,
|
||||
};
|
||||
|
@ -1038,7 +1038,7 @@ static int nct7802_detect(struct i2c_client *client,
|
||||
if (reg < 0 || (reg & 0x3f))
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "nct7802", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "nct7802", I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -798,7 +798,7 @@ static int nct7904_detect(struct i2c_client *client,
|
||||
(i2c_smbus_read_byte_data(client, BANK_SEL_REG) & 0xf8) != 0x00)
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "nct7904", I2C_NAME_SIZE);
|
||||
strscpy(info->type, "nct7904", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -787,6 +787,7 @@ static void nzxt_smart2_hid_remove(struct hid_device *hdev)
|
||||
static const struct hid_device_id nzxt_smart2_hid_id_table[] = {
|
||||
{ HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */
|
||||
{ HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */
|
||||
{ HID_USB_DEVICE(0x1e71, 0x200f) }, /* NZXT Smart Device V2 */
|
||||
{ HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */
|
||||
{ HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */
|
||||
{ HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -397,6 +397,15 @@ config SENSORS_TPS53679
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called tps53679.
|
||||
|
||||
config SENSORS_TPS546D24
|
||||
tristate "TPS546D24"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for TEXAS
|
||||
TPS546D24.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called tps546d24
|
||||
|
||||
config SENSORS_UCD9000
|
||||
tristate "TI UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910"
|
||||
help
|
||||
|
@ -41,6 +41,7 @@ obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o
|
||||
obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o
|
||||
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
|
||||
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
||||
obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o
|
||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
|
||||
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
|
||||
|
@ -34,7 +34,7 @@ struct mp2888_data {
|
||||
int curr_sense_gain;
|
||||
};
|
||||
|
||||
#define to_mp2888_data(x) container_of(x, struct mp2888_data, info)
|
||||
#define to_mp2888_data(x) container_of(x, struct mp2888_data, info)
|
||||
|
||||
static int mp2888_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
@ -109,7 +109,7 @@ mp2888_read_phase(struct i2c_client *client, struct mp2888_data *data, int page,
|
||||
* - Kcs is the DrMOS current sense gain of power stage, which is obtained from the
|
||||
* register MP2888_MFR_VR_CONFIG1, bits 13-12 with the following selection of DrMOS
|
||||
* (data->curr_sense_gain):
|
||||
* 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A.
|
||||
* 00b - 8.5µA/A, 01b - 9.7µA/A, 1b - 10µA/A, 11b - 5µA/A.
|
||||
* - Rcs is the internal phase current sense resistor. This parameter depends on hardware
|
||||
* assembly. By default it is set to 1kΩ. In case of different assembly, user should
|
||||
* scale this parameter by dividing it by Rcs.
|
||||
@ -118,10 +118,9 @@ mp2888_read_phase(struct i2c_client *client, struct mp2888_data *data, int page,
|
||||
* because sampling of current occurrence of bit weight has a big deviation, especially for
|
||||
* light load.
|
||||
*/
|
||||
ret = DIV_ROUND_CLOSEST(ret * 100 - 9800, data->curr_sense_gain);
|
||||
ret = (data->phase_curr_resolution) ? ret * 2 : ret;
|
||||
ret = DIV_ROUND_CLOSEST(ret * 200 - 19600, data->curr_sense_gain);
|
||||
/* Scale according to total current resolution. */
|
||||
ret = (data->total_curr_resolution) ? ret * 8 : ret * 4;
|
||||
ret = (data->total_curr_resolution) ? ret * 2 : ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -212,7 +211,7 @@ static int mp2888_read_word_data(struct i2c_client *client, int page, int phase,
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = data->total_curr_resolution ? ret * 2 : ret;
|
||||
ret = data->total_curr_resolution ? ret : DIV_ROUND_CLOSEST(ret, 2);
|
||||
break;
|
||||
case PMBUS_POUT_OP_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
@ -223,7 +222,7 @@ static int mp2888_read_word_data(struct i2c_client *client, int page, int phase,
|
||||
* set 1. Actual power is reported with 0.5W or 1W respectively resolution. Scaling
|
||||
* is needed to match both.
|
||||
*/
|
||||
ret = data->total_curr_resolution ? ret * 4 : ret * 2;
|
||||
ret = data->total_curr_resolution ? ret * 2 : ret;
|
||||
break;
|
||||
/*
|
||||
* The below registers are not implemented by device or implemented not according to the
|
||||
|
71
drivers/hwmon/pmbus/tps546d24.c
Normal file
71
drivers/hwmon/pmbus/tps546d24.c
Normal file
@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Hardware monitoring driver for TEXAS TPS546D24 buck converter
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pmbus.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
static struct pmbus_driver_info tps546d24_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
|
||||
| PMBUS_HAVE_IOUT | PMBUS_HAVE_VOUT
|
||||
| PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_VOUT
|
||||
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
};
|
||||
|
||||
static int tps546d24_probe(struct i2c_client *client)
|
||||
{
|
||||
int reg;
|
||||
|
||||
reg = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
if (reg & 0x80) {
|
||||
int err;
|
||||
|
||||
err = i2c_smbus_write_byte_data(client, PMBUS_VOUT_MODE, reg & 0x7f);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
return pmbus_do_probe(client, &tps546d24_info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tps546d24_id[] = {
|
||||
{"tps546d24", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tps546d24_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused tps546d24_of_match[] = {
|
||||
{.compatible = "ti,tps546d24"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tps546d24_of_match);
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver tps546d24_driver = {
|
||||
.driver = {
|
||||
.name = "tps546d24",
|
||||
.of_match_table = of_match_ptr(tps546d24_of_match),
|
||||
},
|
||||
.probe_new = tps546d24_probe,
|
||||
.id_table = tps546d24_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tps546d24_driver);
|
||||
|
||||
MODULE_AUTHOR("Duke Du <dukedu83@gmail.com>");
|
||||
MODULE_DESCRIPTION("PMBus driver for TI tps546d24");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS(PMBUS);
|
@ -28,11 +28,23 @@ struct pwm_fan_tach {
|
||||
u8 pulses_per_revolution;
|
||||
};
|
||||
|
||||
enum pwm_fan_enable_mode {
|
||||
pwm_off_reg_off,
|
||||
pwm_disable_reg_enable,
|
||||
pwm_enable_reg_enable,
|
||||
pwm_disable_reg_disable,
|
||||
};
|
||||
|
||||
struct pwm_fan_ctx {
|
||||
struct device *dev;
|
||||
|
||||
struct mutex lock;
|
||||
struct pwm_device *pwm;
|
||||
struct pwm_state pwm_state;
|
||||
struct regulator *reg_en;
|
||||
enum pwm_fan_enable_mode enable_mode;
|
||||
bool regulator_enabled;
|
||||
bool enabled;
|
||||
|
||||
int tach_count;
|
||||
struct pwm_fan_tach *tachs;
|
||||
@ -82,25 +94,140 @@ static void sample_timer(struct timer_list *t)
|
||||
mod_timer(&ctx->rpm_timer, jiffies + HZ);
|
||||
}
|
||||
|
||||
static void pwm_fan_enable_mode_2_state(int enable_mode,
|
||||
struct pwm_state *state,
|
||||
bool *enable_regulator)
|
||||
{
|
||||
switch (enable_mode) {
|
||||
case pwm_disable_reg_enable:
|
||||
/* disable pwm, keep regulator enabled */
|
||||
state->enabled = false;
|
||||
*enable_regulator = true;
|
||||
break;
|
||||
case pwm_enable_reg_enable:
|
||||
/* keep pwm and regulator enabled */
|
||||
state->enabled = true;
|
||||
*enable_regulator = true;
|
||||
break;
|
||||
case pwm_off_reg_off:
|
||||
case pwm_disable_reg_disable:
|
||||
/* disable pwm and regulator */
|
||||
state->enabled = false;
|
||||
*enable_regulator = false;
|
||||
}
|
||||
}
|
||||
|
||||
static int pwm_fan_switch_power(struct pwm_fan_ctx *ctx, bool on)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!ctx->reg_en)
|
||||
return ret;
|
||||
|
||||
if (!ctx->regulator_enabled && on) {
|
||||
ret = regulator_enable(ctx->reg_en);
|
||||
if (ret == 0)
|
||||
ctx->regulator_enabled = true;
|
||||
} else if (ctx->regulator_enabled && !on) {
|
||||
ret = regulator_disable(ctx->reg_en);
|
||||
if (ret == 0)
|
||||
ctx->regulator_enabled = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pwm_fan_power_on(struct pwm_fan_ctx *ctx)
|
||||
{
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
int ret;
|
||||
|
||||
if (ctx->enabled)
|
||||
return 0;
|
||||
|
||||
ret = pwm_fan_switch_power(ctx, true);
|
||||
if (ret < 0) {
|
||||
dev_err(ctx->dev, "failed to enable power supply\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
state->enabled = true;
|
||||
ret = pwm_apply_state(ctx->pwm, state);
|
||||
if (ret) {
|
||||
dev_err(ctx->dev, "failed to enable PWM\n");
|
||||
goto disable_regulator;
|
||||
}
|
||||
|
||||
ctx->enabled = true;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_regulator:
|
||||
pwm_fan_switch_power(ctx, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pwm_fan_power_off(struct pwm_fan_ctx *ctx)
|
||||
{
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
bool enable_regulator = false;
|
||||
int ret;
|
||||
|
||||
if (!ctx->enabled)
|
||||
return 0;
|
||||
|
||||
pwm_fan_enable_mode_2_state(ctx->enable_mode,
|
||||
state,
|
||||
&enable_regulator);
|
||||
|
||||
state->enabled = false;
|
||||
state->duty_cycle = 0;
|
||||
ret = pwm_apply_state(ctx->pwm, state);
|
||||
if (ret) {
|
||||
dev_err(ctx->dev, "failed to disable PWM\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pwm_fan_switch_power(ctx, enable_regulator);
|
||||
|
||||
ctx->enabled = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
{
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
unsigned long period;
|
||||
int ret = 0;
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
if (ctx->pwm_value == pwm)
|
||||
goto exit_set_pwm_err;
|
||||
if (pwm > 0) {
|
||||
if (ctx->enable_mode == pwm_off_reg_off)
|
||||
/* pwm-fan hard disabled */
|
||||
return 0;
|
||||
|
||||
period = state->period;
|
||||
state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
|
||||
state->enabled = pwm ? true : false;
|
||||
|
||||
ret = pwm_apply_state(ctx->pwm, state);
|
||||
period = state->period;
|
||||
state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
|
||||
ret = pwm_apply_state(ctx->pwm, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = pwm_fan_power_on(ctx);
|
||||
} else {
|
||||
ret = pwm_fan_power_off(ctx);
|
||||
}
|
||||
if (!ret)
|
||||
ctx->pwm_value = pwm;
|
||||
exit_set_pwm_err:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
ret = __set_pwm(ctx, pwm);
|
||||
mutex_unlock(&ctx->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -115,20 +242,76 @@ static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
ctx->pwm_fan_state = i;
|
||||
}
|
||||
|
||||
static int pwm_fan_update_enable(struct pwm_fan_ctx *ctx, long val)
|
||||
{
|
||||
int ret = 0;
|
||||
int old_val;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
if (ctx->enable_mode == val)
|
||||
goto out;
|
||||
|
||||
old_val = ctx->enable_mode;
|
||||
ctx->enable_mode = val;
|
||||
|
||||
if (val == 0) {
|
||||
/* Disable pwm-fan unconditionally */
|
||||
ret = __set_pwm(ctx, 0);
|
||||
if (ret)
|
||||
ctx->enable_mode = old_val;
|
||||
pwm_fan_update_state(ctx, 0);
|
||||
} else {
|
||||
/*
|
||||
* Change PWM and/or regulator state if currently disabled
|
||||
* Nothing to do if currently enabled
|
||||
*/
|
||||
if (!ctx->enabled) {
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
bool enable_regulator = false;
|
||||
|
||||
state->duty_cycle = 0;
|
||||
pwm_fan_enable_mode_2_state(val,
|
||||
state,
|
||||
&enable_regulator);
|
||||
|
||||
pwm_apply_state(ctx->pwm, state);
|
||||
pwm_fan_switch_power(ctx, enable_regulator);
|
||||
pwm_fan_update_state(ctx, 0);
|
||||
}
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&ctx->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pwm_fan_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (val < 0 || val > MAX_PWM)
|
||||
return -EINVAL;
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
if (val < 0 || val > MAX_PWM)
|
||||
return -EINVAL;
|
||||
ret = set_pwm(ctx, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
pwm_fan_update_state(ctx, val);
|
||||
break;
|
||||
case hwmon_pwm_enable:
|
||||
if (val < 0 || val > 3)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = pwm_fan_update_enable(ctx, val);
|
||||
|
||||
ret = __set_pwm(ctx, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
pwm_fan_update_state(ctx, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -139,9 +322,15 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
*val = ctx->pwm_value;
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
*val = ctx->pwm_value;
|
||||
return 0;
|
||||
case hwmon_pwm_enable:
|
||||
*val = ctx->enable_mode;
|
||||
return 0;
|
||||
}
|
||||
return -EOPNOTSUPP;
|
||||
case hwmon_fan:
|
||||
*val = ctx->tachs[channel].rpm;
|
||||
return 0;
|
||||
@ -212,7 +401,7 @@ pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
|
||||
if (state == ctx->pwm_fan_state)
|
||||
return 0;
|
||||
|
||||
ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
|
||||
ret = set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
|
||||
if (ret) {
|
||||
dev_err(&cdev->device, "Cannot set pwm!\n");
|
||||
return ret;
|
||||
@ -270,18 +459,14 @@ static int pwm_fan_of_get_cooling_data(struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pwm_fan_regulator_disable(void *data)
|
||||
{
|
||||
regulator_disable(data);
|
||||
}
|
||||
|
||||
static void pwm_fan_pwm_disable(void *__ctx)
|
||||
static void pwm_fan_cleanup(void *__ctx)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = __ctx;
|
||||
|
||||
ctx->pwm_state.enabled = false;
|
||||
pwm_apply_state(ctx->pwm, &ctx->pwm_state);
|
||||
del_timer_sync(&ctx->rpm_timer);
|
||||
/* Switch off everything */
|
||||
ctx->enable_mode = pwm_disable_reg_disable;
|
||||
pwm_fan_power_off(ctx);
|
||||
}
|
||||
|
||||
static int pwm_fan_probe(struct platform_device *pdev)
|
||||
@ -302,7 +487,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
|
||||
mutex_init(&ctx->lock);
|
||||
|
||||
ctx->pwm = devm_of_pwm_get(dev, dev->of_node, NULL);
|
||||
ctx->dev = &pdev->dev;
|
||||
ctx->pwm = devm_pwm_get(dev, NULL);
|
||||
if (IS_ERR(ctx->pwm))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->pwm), "Could not get PWM\n");
|
||||
|
||||
@ -314,22 +500,12 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(ctx->reg_en);
|
||||
|
||||
ctx->reg_en = NULL;
|
||||
} else {
|
||||
ret = regulator_enable(ctx->reg_en);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable fan supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = devm_add_action_or_reset(dev, pwm_fan_regulator_disable,
|
||||
ctx->reg_en);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
pwm_init_state(ctx->pwm, &ctx->pwm_state);
|
||||
|
||||
/*
|
||||
* __set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned
|
||||
* set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned
|
||||
* long. Check this here to prevent the fan running at a too low
|
||||
* frequency.
|
||||
*/
|
||||
@ -338,14 +514,19 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set duty cycle to maximum allowed and enable PWM output */
|
||||
ret = __set_pwm(ctx, MAX_PWM);
|
||||
ctx->enable_mode = pwm_disable_reg_enable;
|
||||
|
||||
/*
|
||||
* Set duty cycle to maximum allowed and enable PWM output as well as
|
||||
* the regulator. In case of error nothing is changed
|
||||
*/
|
||||
ret = set_pwm(ctx, MAX_PWM);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to configure PWM: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
timer_setup(&ctx->rpm_timer, sample_timer, 0);
|
||||
ret = devm_add_action_or_reset(dev, pwm_fan_pwm_disable, ctx);
|
||||
ret = devm_add_action_or_reset(dev, pwm_fan_cleanup, ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -377,7 +558,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
if (!channels)
|
||||
return -ENOMEM;
|
||||
|
||||
channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT);
|
||||
channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE);
|
||||
|
||||
for (i = 0; i < ctx->tach_count; i++) {
|
||||
struct pwm_fan_tach *tach = &ctx->tachs[i];
|
||||
@ -451,65 +632,28 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_fan_disable(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (ctx->pwm_value) {
|
||||
/* keep ctx->pwm_state unmodified for pwm_fan_resume() */
|
||||
struct pwm_state state = ctx->pwm_state;
|
||||
|
||||
state.duty_cycle = 0;
|
||||
state.enabled = false;
|
||||
ret = pwm_apply_state(ctx->pwm, &state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ctx->reg_en) {
|
||||
ret = regulator_disable(ctx->reg_en);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to disable fan supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pwm_fan_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
pwm_fan_disable(&pdev->dev);
|
||||
struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
|
||||
|
||||
pwm_fan_cleanup(ctx);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pwm_fan_suspend(struct device *dev)
|
||||
{
|
||||
return pwm_fan_disable(dev);
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
|
||||
return pwm_fan_power_off(ctx);
|
||||
}
|
||||
|
||||
static int pwm_fan_resume(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (ctx->reg_en) {
|
||||
ret = regulator_enable(ctx->reg_en);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable fan supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->pwm_value == 0)
|
||||
return 0;
|
||||
|
||||
return pwm_apply_state(ctx->pwm, &ctx->pwm_state);
|
||||
return set_pwm(ctx, ctx->pwm_value);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume);
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume);
|
||||
|
||||
static const struct of_device_id of_pwm_fan_match[] = {
|
||||
{ .compatible = "pwm-fan", },
|
||||
@ -522,7 +666,7 @@ static struct platform_driver pwm_fan_driver = {
|
||||
.shutdown = pwm_fan_shutdown,
|
||||
.driver = {
|
||||
.name = "pwm-fan",
|
||||
.pm = &pwm_fan_pm,
|
||||
.pm = pm_sleep_ptr(&pwm_fan_pm),
|
||||
.of_match_table = of_pwm_fan_match,
|
||||
},
|
||||
};
|
||||
|
@ -129,7 +129,7 @@ unlock:
|
||||
|
||||
static ssize_t sht4x_interval_write(struct sht4x_data *data, long val)
|
||||
{
|
||||
data->update_interval = clamp_val(val, SHT4X_MIN_POLL_INTERVAL, UINT_MAX);
|
||||
data->update_interval = clamp_val(val, SHT4X_MIN_POLL_INTERVAL, INT_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user