mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-06 02:50:43 +00:00
hwmon updates for v5.6
core: - Add support for enable attributes to hwmon core - Add intrusion templates pmbus: - Support for Infineon Multi-phase xdpe122 family controllers - Support for Intel IMVP9 and AMD 6.25mV modes - Support for vid mode detection per page bases - Detect if chip is write protected - Support for MAX20730, MAX20734, MAX20743, MAX20796, UCD90320, TPS53688 - Various improvements to ibm-cffps driver k10temp: - Support for additional temperature sensors as well as voltage and current telemetry for Zen CPUs w83627ehf: - Remove support for NCT6775, NCT6776 (they have their own driver) New drivers: - ADM1177 - MAX31730 - Driver for disk and solid state drives with temperature sensors Other: - pwm-fan: stop fan on shutdown -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAl4s++oACgkQyx8mb86f mYFzzhAAjh2FQ2T6TPe+C+JA68ysLGsMCtlfGFWK3GS/pF0CUvr+/nA2FPwQ6ydX Sj+RzjS/CjgrAhabmUvpmRxJZUFFfu96LXCn6p4eaOnIqNMz2UIb+4xIZYMqn+CD kJH3dse7EOlWDk+gHyvkwFtOaye9xV/L9F4C7cKWwKdFYhQad3xA0eARHpNVCaA5 BG33q+PVDx1gc/8C4JwLfqiF/IXExt9RV7akhoHMYHyJEpF4lD8X+/oTPjc/jbo7 EfpIY6/0zBt3+TVMXRd7Kg1IcTDt8iePd7tVOFrL8ZIel+Elv/Fy7HyXm9dFC6zM r3b008XfVWSrk10ezOc7YOYKCv0mODuDzute8rzEA+LJSNIMRSorFzo3BezFex1L GZTZfThpeZXZ2pwu0BMkO8teYP1JpVaxgsE/PDKkgcpfGCloISoNft8DkPIdhB9K lf3iuPT+kX5h6/AMb6XD4PlA1bXb9Uj4FCS9KonbTKqEjwu6N2lE9kCA3Q7nvyaE PbEl/AF3DRGZXMAv/C6yJomsmOlUodolZEOa4tGXsTh1clSvgHOEllGtCLLTPkYS FOYmoReA54oz562e6WdhUtaMaS/O7pZRK9rwufng6qq+81VM1Ta5HcOSoK0kZrW4 fs93B0TfmdP7szMr3HaFH1Xlh6eYGj/dYUabNuAfCKAKMTBdgmI= =rfbE -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "core: - Add support for enable attributes to hwmon core - Add intrusion templates pmbus: - Support for Infineon Multi-phase xdpe122 family controllers - Support for Intel IMVP9 and AMD 6.25mV modes - Support for vid mode detection per page bases - Detect if chip is write protected - Support for MAX20730, MAX20734, MAX20743, MAX20796, UCD90320, TPS53688 - Various improvements to ibm-cffps driver k10temp: - Support for additional temperature sensors as well as voltage and current telemetry for Zen CPUs w83627ehf: - Remove support for NCT6775, NCT6776 (they have their own driver) New drivers: - ADM1177 - MAX31730 - Driver for disk and solid state drives with temperature sensors Other: - pwm-fan: stop fan on shutdown" * tag 'hwmon-for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (35 commits) hwmon: (k10temp) Display up to eight sets of CCD temperatures hwmon: (k10temp) Add debugfs support hwmon: (k10temp) Don't show temperature limits on Ryzen (Zen) CPUs hwmon: (k10temp) Show core and SoC current and voltages on Ryzen CPUs hwmon: (k10temp) Report temperatures per CPU die hmon: (k10temp) Convert to use devm_hwmon_device_register_with_info hwmon: (k10temp) Use bitops hwmon: (pwm-fan) stop fan on shutdown MAINTAINERS: add entry for ADM1177 driver dt-binding: hwmon: Add documentation for ADM1177 hwmon: (adm1177) Add ADM1177 Hot Swap Controller and Digital Power Monitor driver docs: hwmon: Include 'xdpe12284.rst' into docs hwmon: (pmbus) Add support for Infineon Multi-phase xdpe122 family controllers hwmon: (pmbus/tps53679) Extend device list supported by driver hwmon: (pmbus/core) Add support for Intel IMVP9 and AMD 6.25mV modes hwmon: (pmbus/core) Add support for vid mode detection per page bases hwmon: (pmbus/ibm-cffps) Prevent writing on_off_config with bad data hwmon: (w83627ehf) Remove set but not used variable 'fan4min' hwmon: Driver for disk and solid state drives with temperature sensors hwmon: (pmbus/ibm-cffps) Fix the LED behavior when turned off ...
This commit is contained in:
commit
35417d57ef
34 changed files with 3621 additions and 1504 deletions
66
Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml
Normal file
66
Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml
Normal file
|
@ -0,0 +1,66 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/adi,adm1177.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices ADM1177 Hot Swap Controller and Digital Power Monitor
|
||||
|
||||
maintainers:
|
||||
- Michael Hennerich <michael.hennerich@analog.com>
|
||||
- Beniamin Bia <beniamin.bia@analog.com>
|
||||
|
||||
description: |
|
||||
Analog Devices ADM1177 Hot Swap Controller and Digital Power Monitor
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1177.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adm1177
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
avcc-supply:
|
||||
description:
|
||||
Phandle to the Avcc power supply
|
||||
|
||||
shunt-resistor-micro-ohms:
|
||||
description:
|
||||
The value of curent sense resistor in microohms. If not provided,
|
||||
the current reading and overcurrent alert is disabled.
|
||||
|
||||
adi,shutdown-threshold-microamp:
|
||||
description:
|
||||
Specifies the current level at which an over current alert occurs.
|
||||
If not provided, the overcurrent alert is configured to max ADC range
|
||||
based on shunt-resistor-micro-ohms.
|
||||
|
||||
adi,vrange-high-enable:
|
||||
description:
|
||||
Specifies which internal voltage divider to be used. A 1 selects
|
||||
a 7:2 voltage divider while a 0 selects a 14:1 voltage divider.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pwmon@5a {
|
||||
compatible = "adi,adm1177";
|
||||
reg = <0x5a>;
|
||||
shunt-resistor-micro-ohms = <50000>; /* 50 mOhm */
|
||||
adi,shutdown-threshold-microamp = <1059000>; /* 1.059 A */
|
||||
adi,vrange-high-enable;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,45 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
|
||||
$id: http://devicetree.org/schemas/hwmon/pmbus/ti,ucd90320.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: UCD90320 power sequencer
|
||||
|
||||
maintainers:
|
||||
- Jim Wright <wrightj@linux.vnet.ibm.com>
|
||||
|
||||
description: |
|
||||
The UCD90320 is a 32-rail PMBus/I2C addressable power-supply sequencer and
|
||||
monitor. The 24 integrated ADC channels (AMONx) monitor the power supply
|
||||
voltage, current, and temperature. Of the 84 GPIO pins, 8 can be used as
|
||||
digital monitors (DMONx), 32 to enable the power supply (ENx), 24 for
|
||||
margining (MARx), 16 for logical GPO, and 32 GPIs for cascading, and system
|
||||
function.
|
||||
|
||||
http://focus.ti.com/lit/ds/symlink/ucd90320.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,ucd90320
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ucd90320@11 {
|
||||
compatible = "ti,ucd90320";
|
||||
reg = <0x11>;
|
||||
};
|
||||
};
|
36
Documentation/hwmon/adm1177.rst
Normal file
36
Documentation/hwmon/adm1177.rst
Normal file
|
@ -0,0 +1,36 @@
|
|||
Kernel driver adm1177
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* Analog Devices ADM1177
|
||||
Prefix: 'adm1177'
|
||||
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1177.pdf
|
||||
|
||||
Author: Beniamin Bia <beniamin.bia@analog.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Analog Devices ADM1177
|
||||
Hot-Swap Controller and Digital Power Monitors with Soft Start Pin.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices for
|
||||
details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Current maxim attribute
|
||||
is read-write, all other attributes are read-only.
|
||||
|
||||
in0_input Measured voltage in microvolts.
|
||||
|
||||
curr1_input Measured current in microamperes.
|
||||
curr1_max_alarm Overcurrent alarm in microamperes.
|
52
Documentation/hwmon/drivetemp.rst
Normal file
52
Documentation/hwmon/drivetemp.rst
Normal file
|
@ -0,0 +1,52 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver drivetemp
|
||||
=======================
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
ANS T13/1699-D
|
||||
Information technology - AT Attachment 8 - ATA/ATAPI Command Set (ATA8-ACS)
|
||||
|
||||
ANS Project T10/BSR INCITS 513
|
||||
Information technology - SCSI Primary Commands - 4 (SPC-4)
|
||||
|
||||
ANS Project INCITS 557
|
||||
Information technology - SCSI / ATA Translation - 5 (SAT-5)
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports reporting the temperature of disk and solid state
|
||||
drives with temperature sensors.
|
||||
|
||||
If supported, it uses the ATA SCT Command Transport feature to read
|
||||
the current drive temperature and, if available, temperature limits
|
||||
as well as historic minimum and maximum temperatures. If SCT Command
|
||||
Transport is not supported, the driver uses SMART attributes to read
|
||||
the drive temperature.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
Only the temp1_input attribute is always available. Other attributes are
|
||||
available only if reported by the drive. All temperatures are reported in
|
||||
milli-degrees Celsius.
|
||||
|
||||
======================= =====================================================
|
||||
temp1_input Current drive temperature
|
||||
temp1_lcrit Minimum temperature limit. Operating the device below
|
||||
this temperature may cause physical damage to the
|
||||
device.
|
||||
temp1_min Minimum recommended continuous operating limit
|
||||
temp1_max Maximum recommended continuous operating temperature
|
||||
temp1_crit Maximum temperature limit. Operating the device above
|
||||
this temperature may cause physical damage to the
|
||||
device.
|
||||
temp1_lowest Minimum temperature seen this power cycle
|
||||
temp1_highest Maximum temperature seen this power cycle
|
||||
======================= =====================================================
|
|
@ -29,6 +29,7 @@ Hardware Monitoring Kernel Drivers
|
|||
adm1025
|
||||
adm1026
|
||||
adm1031
|
||||
adm1177
|
||||
adm1275
|
||||
adm9240
|
||||
ads7828
|
||||
|
@ -47,6 +48,7 @@ Hardware Monitoring Kernel Drivers
|
|||
da9055
|
||||
dell-smm-hwmon
|
||||
dme1737
|
||||
drivetemp
|
||||
ds1621
|
||||
ds620
|
||||
emc1403
|
||||
|
@ -106,8 +108,10 @@ Hardware Monitoring Kernel Drivers
|
|||
max1619
|
||||
max1668
|
||||
max197
|
||||
max20730
|
||||
max20751
|
||||
max31722
|
||||
max31730
|
||||
max31785
|
||||
max31790
|
||||
max34440
|
||||
|
@ -177,6 +181,7 @@ Hardware Monitoring Kernel Drivers
|
|||
wm831x
|
||||
wm8350
|
||||
xgene-hwmon
|
||||
xdpe12284
|
||||
zl6100
|
||||
|
||||
.. only:: subproject and html
|
||||
|
|
74
Documentation/hwmon/max20730.rst
Normal file
74
Documentation/hwmon/max20730.rst
Normal file
|
@ -0,0 +1,74 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver max20730
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Maxim MAX20730
|
||||
|
||||
Prefix: 'max20730'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX20730.pdf
|
||||
|
||||
* Maxim MAX20734
|
||||
|
||||
Prefix: 'max20734'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX20734.pdf
|
||||
|
||||
* Maxim MAX20743
|
||||
|
||||
Prefix: 'max20743'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX20743.pdf
|
||||
|
||||
Author: Guenter Roeck <linux@roeck-us.net>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Maxim MAX20730, MAX20734, and MAX20743
|
||||
Integrated, Step-Down Switching Regulators with PMBus support.
|
||||
|
||||
The driver is a client driver to the core PMBus driver.
|
||||
Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
|
||||
details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
=================== ===== =======================================================
|
||||
curr1_crit RW/RO Critical output current. Please see datasheet for
|
||||
supported limits. Read-only if the chip is
|
||||
write protected; read-write otherwise.
|
||||
curr1_crit_alarm RO Output current critical alarm
|
||||
curr1_input RO Output current
|
||||
curr1_label RO 'iout1'
|
||||
in1_alarm RO Input voltage alarm
|
||||
in1_input RO Input voltage
|
||||
in1_label RO 'vin'
|
||||
in2_alarm RO Output voltage alarm
|
||||
in2_input RO Output voltage
|
||||
in2_label RO 'vout1'
|
||||
temp1_crit RW/RO Critical temeperature. Supported values are 130 or 150
|
||||
degrees C. Read-only if the chip is write protected;
|
||||
read-write otherwise.
|
||||
temp1_crit_alarm RO Temperature critical alarm
|
||||
temp1_input RO Chip temperature
|
||||
=================== ===== =======================================================
|
44
Documentation/hwmon/max31730.rst
Normal file
44
Documentation/hwmon/max31730.rst
Normal file
|
@ -0,0 +1,44 @@
|
|||
Kernel driver max31790
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Maxim MAX31730
|
||||
|
||||
Prefix: 'max31730'
|
||||
|
||||
Addresses scanned: 0x1c, 0x1d, 0x1e, 0x1f, 0x4c, 0x4d, 0x4e, 0x4f
|
||||
|
||||
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX31730.pdf
|
||||
|
||||
Author: Guenter Roeck <linux@roeck-us.net>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Maxim MAX31730.
|
||||
|
||||
The MAX31730 temperature sensor monitors its own temperature and the
|
||||
temperatures of three external diode-connected transistors. The operating
|
||||
supply voltage is from 3.0V to 3.6V. Resistance cancellation compensates
|
||||
for high series resistance in circuit-board traces and the external thermal
|
||||
diode, while beta compensation corrects for temperature-measurement
|
||||
errors due to low-beta sensing transistors.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
=================== == =======================================================
|
||||
temp[1-4]_enable RW Temperature enable/disable
|
||||
Set to 0 to enable channel, 0 to disable
|
||||
temp[1-4]_input RO Temperature input
|
||||
temp[2-4]_fault RO Fault indicator for remote channels
|
||||
temp[1-4]_max RW Maximum temperature
|
||||
temp[1-4]_max_alarm RW Maximum temperature alarm
|
||||
temp[1-4]_min RW Minimum temperature. Common for all channels.
|
||||
Only temp1_min is writeable.
|
||||
temp[1-4]_min_alarm RO Minimum temperature alarm
|
||||
temp[2-4]_offset RW Temperature offset for remote channels
|
||||
=================== == =======================================================
|
|
@ -63,6 +63,16 @@ Supported chips:
|
|||
|
||||
http://www.ti.com/lit/gpn/tps544c25
|
||||
|
||||
* Maxim MAX20796
|
||||
|
||||
Prefix: 'max20796'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
Not published
|
||||
|
||||
* Generic PMBus devices
|
||||
|
||||
Prefix: 'pmbus'
|
||||
|
|
|
@ -3,9 +3,10 @@ Kernel driver ucd9000
|
|||
|
||||
Supported chips:
|
||||
|
||||
* TI UCD90120, UCD90124, UCD90160, UCD9090, and UCD90910
|
||||
* TI UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, and UCD90910
|
||||
|
||||
Prefixes: 'ucd90120', 'ucd90124', 'ucd90160', 'ucd9090', 'ucd90910'
|
||||
Prefixes: 'ucd90120', 'ucd90124', 'ucd90160', 'ucd90320', 'ucd9090',
|
||||
'ucd90910'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
|
@ -14,6 +15,7 @@ Supported chips:
|
|||
- http://focus.ti.com/lit/ds/symlink/ucd90120.pdf
|
||||
- http://focus.ti.com/lit/ds/symlink/ucd90124.pdf
|
||||
- http://focus.ti.com/lit/ds/symlink/ucd90160.pdf
|
||||
- http://focus.ti.com/lit/ds/symlink/ucd90320.pdf
|
||||
- http://focus.ti.com/lit/ds/symlink/ucd9090.pdf
|
||||
- http://focus.ti.com/lit/ds/symlink/ucd90910.pdf
|
||||
|
||||
|
@ -45,6 +47,12 @@ power-on reset signals, external interrupts, cascading, or other system
|
|||
functions. Twelve of these pins offer PWM functionality. Using these pins, the
|
||||
UCD90160 offers support for margining, and general-purpose PWM functions.
|
||||
|
||||
The UCD90320 is a 32-rail PMBus/I2C addressable power-supply sequencer and
|
||||
monitor. The 24 integrated ADC channels (AMONx) monitor the power supply
|
||||
voltage, current, and temperature. Of the 84 GPIO pins, 8 can be used as
|
||||
digital monitors (DMONx), 32 to enable the power supply (ENx), 24 for margining
|
||||
(MARx), 16 for logical GPO, and 32 GPIs for cascading, and system function.
|
||||
|
||||
The UCD9090 is a 10-rail PMBus/I2C addressable power-supply sequencer and
|
||||
monitor. The device integrates a 12-bit ADC for monitoring up to 10 power-supply
|
||||
voltage inputs. Twenty-three GPIO pins can be used for power supply enables,
|
||||
|
|
101
Documentation/hwmon/xdpe12284.rst
Normal file
101
Documentation/hwmon/xdpe12284.rst
Normal file
|
@ -0,0 +1,101 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver xdpe122
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Infineon XDPE12254
|
||||
|
||||
Prefix: 'xdpe12254'
|
||||
|
||||
* Infineon XDPE12284
|
||||
|
||||
Prefix: 'xdpe12284'
|
||||
|
||||
Authors:
|
||||
|
||||
Vadim Pasternak <vadimp@mellanox.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Infineon Multi-phase XDPE122 family
|
||||
dual loop voltage regulators.
|
||||
The family includes XDPE12284 and XDPE12254 devices.
|
||||
The devices from this family complaint with:
|
||||
- Intel VR13 and VR13HC rev 1.3, IMVP8 rev 1.2 and IMPVP9 rev 1.3 DC-DC
|
||||
converter specification.
|
||||
- Intel SVID rev 1.9. protocol.
|
||||
- PMBus rev 1.3 interface.
|
||||
|
||||
Devices support linear format for reading input voltage, input and output current,
|
||||
input and output power and temperature.
|
||||
Device supports VID format for reading output voltage. The below modes are
|
||||
supported:
|
||||
- VR12.0 mode, 5-mV DAC - 0x01.
|
||||
- VR12.5 mode, 10-mV DAC - 0x02.
|
||||
- IMVP9 mode, 5-mV DAC - 0x03.
|
||||
- AMD mode 6.25mV - 0x10.
|
||||
|
||||
Devices support two pages for telemetry.
|
||||
|
||||
The driver provides for current: input, maximum and critical thresholds
|
||||
and maximum and critical alarms. Critical thresholds and critical alarm are
|
||||
supported only for current output.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "iin" and 3, 4 for "iout":
|
||||
|
||||
**curr[3-4]_crit**
|
||||
|
||||
**curr[3-4]_crit_alarm**
|
||||
|
||||
**curr[1-4]_input**
|
||||
|
||||
**curr[1-4]_label**
|
||||
|
||||
**curr[1-4]_max**
|
||||
|
||||
**curr[1-4]_max_alarm**
|
||||
|
||||
The driver provides for voltage: input, critical and low critical thresholds
|
||||
and critical and low critical alarms.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "vin" and 3, 4 for "vout":
|
||||
|
||||
**in[1-4]_crit**
|
||||
|
||||
**in[1-4_crit_alarm**
|
||||
|
||||
**in[1-4]_input**
|
||||
|
||||
**in[1-4_label**
|
||||
|
||||
**in[1-4]_lcrit**
|
||||
|
||||
**in[1-41_lcrit_alarm**
|
||||
|
||||
The driver provides for power: input and alarms. Power alarm is supported only
|
||||
for power input.
|
||||
The driver exports the following attributes for via the sysfs files, where
|
||||
indexes 1, 2 are for "pin" and 3, 4 for "pout":
|
||||
|
||||
**power[1-2]_alarm**
|
||||
|
||||
**power[1-4]_input**
|
||||
|
||||
**power[1-4]_label**
|
||||
|
||||
The driver provides for temperature: input, maximum and critical thresholds
|
||||
and maximum and critical alarms.
|
||||
The driver exports the following attributes for via the sysfs files:
|
||||
|
||||
**temp[1-2]_crit**
|
||||
|
||||
**temp[1-2]_crit_alarm**
|
||||
|
||||
**temp[1-2]_input**
|
||||
|
||||
**temp[1-2]_max**
|
||||
|
||||
**temp[1-2]_max_alarm**
|
|
@ -977,6 +977,15 @@ W: http://ez.analog.com/community/linux-device-drivers
|
|||
F: drivers/iio/imu/adis16460.c
|
||||
F: Documentation/devicetree/bindings/iio/imu/adi,adis16460.yaml
|
||||
|
||||
ANALOG DEVICES INC ADM1177 DRIVER
|
||||
M: Beniamin Bia <beniamin.bia@analog.com>
|
||||
M: Michael Hennerich <Michael.Hennerich@analog.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
W: http://ez.analog.com/community/linux-device-drivers
|
||||
S: Supported
|
||||
F: drivers/hwmon/adm1177.c
|
||||
F: Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml
|
||||
|
||||
ANALOG DEVICES INC ADP5061 DRIVER
|
||||
M: Stefan Popa <stefan.popa@analog.com>
|
||||
L: linux-pm@vger.kernel.org
|
||||
|
|
|
@ -164,6 +164,16 @@ config SENSORS_ADM1031
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called adm1031.
|
||||
|
||||
config SENSORS_ADM1177
|
||||
tristate "Analog Devices ADM1177 and compatibles"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Analog Devices ADM1177
|
||||
sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called adm1177.
|
||||
|
||||
config SENSORS_ADM9240
|
||||
tristate "Analog Devices ADM9240 and compatibles"
|
||||
depends on I2C
|
||||
|
@ -385,6 +395,16 @@ config SENSORS_ATXP1
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called atxp1.
|
||||
|
||||
config SENSORS_DRIVETEMP
|
||||
tristate "Hard disk drives with temperature sensors"
|
||||
depends on SCSI && ATA
|
||||
help
|
||||
If you say yes you get support for the temperature sensor on
|
||||
hard disk drives.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called satatemp.
|
||||
|
||||
config SENSORS_DS620
|
||||
tristate "Dallas Semiconductor DS620"
|
||||
depends on I2C
|
||||
|
@ -889,7 +909,7 @@ config SENSORS_MAX197
|
|||
will be called max197.
|
||||
|
||||
config SENSORS_MAX31722
|
||||
tristate "MAX31722 temperature sensor"
|
||||
tristate "MAX31722 temperature sensor"
|
||||
depends on SPI
|
||||
help
|
||||
Support for the Maxim Integrated MAX31722/MAX31723 digital
|
||||
|
@ -898,6 +918,16 @@ tristate "MAX31722 temperature sensor"
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called max31722.
|
||||
|
||||
config SENSORS_MAX31730
|
||||
tristate "MAX31730 temperature sensor"
|
||||
depends on I2C
|
||||
help
|
||||
Support for the Maxim Integrated MAX31730 3-Channel Remote
|
||||
Temperature Sensor.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max31730.
|
||||
|
||||
config SENSORS_MAX6621
|
||||
tristate "Maxim MAX6621 sensor chip"
|
||||
depends on I2C
|
||||
|
@ -1905,7 +1935,7 @@ config SENSORS_W83627HF
|
|||
will be called w83627hf.
|
||||
|
||||
config SENSORS_W83627EHF
|
||||
tristate "Winbond W83627EHF/EHG/DHG/UHG, W83667HG, NCT6775F, NCT6776F"
|
||||
tristate "Winbond W83627EHF/EHG/DHG/UHG, W83667HG"
|
||||
depends on !PPC
|
||||
select HWMON_VID
|
||||
help
|
||||
|
@ -1918,8 +1948,7 @@ config SENSORS_W83627EHF
|
|||
the Core 2 Duo. And also the W83627UHG, which is a stripped down
|
||||
version of the W83627DHG (as far as hardware monitoring goes.)
|
||||
|
||||
This driver also supports Nuvoton W83667HG, W83667HG-B, NCT6775F
|
||||
(also known as W83667HG-I), and NCT6776F.
|
||||
This driver also supports Nuvoton W83667HG and W83667HG-B.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called w83627ehf.
|
||||
|
|
|
@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
|
|||
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
||||
obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o
|
||||
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
|
||||
obj-$(CONFIG_SENSORS_ADM1177) += adm1177.o
|
||||
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
||||
obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o
|
||||
obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o
|
||||
|
@ -56,6 +57,7 @@ obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
|
|||
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
|
||||
obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o
|
||||
obj-$(CONFIG_SENSORS_DS620) += ds620.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
|
||||
|
@ -123,6 +125,7 @@ obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
|||
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_MAX6621) += max6621.o
|
||||
obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
|
||||
obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
|
||||
|
|
288
drivers/hwmon/adm1177.c
Normal file
288
drivers/hwmon/adm1177.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ADM1177 Hot Swap Controller and Digital Power Monitor with Soft Start Pin
|
||||
*
|
||||
* Copyright 2015-2019 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
/* Command Byte Operations */
|
||||
#define ADM1177_CMD_V_CONT BIT(0)
|
||||
#define ADM1177_CMD_I_CONT BIT(2)
|
||||
#define ADM1177_CMD_VRANGE BIT(4)
|
||||
|
||||
/* Extended Register */
|
||||
#define ADM1177_REG_ALERT_TH 2
|
||||
|
||||
#define ADM1177_BITS 12
|
||||
|
||||
/**
|
||||
* struct adm1177_state - driver instance specific data
|
||||
* @client pointer to i2c client
|
||||
* @reg regulator info for the the power supply of the device
|
||||
* @r_sense_uohm current sense resistor value
|
||||
* @alert_threshold_ua current limit for shutdown
|
||||
* @vrange_high internal voltage divider
|
||||
*/
|
||||
struct adm1177_state {
|
||||
struct i2c_client *client;
|
||||
struct regulator *reg;
|
||||
u32 r_sense_uohm;
|
||||
u32 alert_threshold_ua;
|
||||
bool vrange_high;
|
||||
};
|
||||
|
||||
static int adm1177_read_raw(struct adm1177_state *st, u8 num, u8 *data)
|
||||
{
|
||||
return i2c_master_recv(st->client, data, num);
|
||||
}
|
||||
|
||||
static int adm1177_write_cmd(struct adm1177_state *st, u8 cmd)
|
||||
{
|
||||
return i2c_smbus_write_byte(st->client, cmd);
|
||||
}
|
||||
|
||||
static int adm1177_write_alert_thr(struct adm1177_state *st,
|
||||
u32 alert_threshold_ua)
|
||||
{
|
||||
u64 val;
|
||||
int ret;
|
||||
|
||||
val = 0xFFULL * alert_threshold_ua * st->r_sense_uohm;
|
||||
val = div_u64(val, 105840000U);
|
||||
val = div_u64(val, 1000U);
|
||||
if (val > 0xFF)
|
||||
val = 0xFF;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(st->client, ADM1177_REG_ALERT_TH,
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->alert_threshold_ua = alert_threshold_ua;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adm1177_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct adm1177_state *st = dev_get_drvdata(dev);
|
||||
u8 data[3];
|
||||
long dummy;
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
ret = adm1177_read_raw(st, 3, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dummy = (data[1] << 4) | (data[2] & 0xF);
|
||||
/*
|
||||
* convert to milliamperes
|
||||
* ((105.84mV / 4096) x raw) / senseResistor(ohm)
|
||||
*/
|
||||
*val = div_u64((105840000ull * dummy),
|
||||
4096 * st->r_sense_uohm);
|
||||
return 0;
|
||||
case hwmon_curr_max_alarm:
|
||||
*val = st->alert_threshold_ua;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
case hwmon_in:
|
||||
ret = adm1177_read_raw(st, 3, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dummy = (data[0] << 4) | (data[2] >> 4);
|
||||
/*
|
||||
* convert to millivolts based on resistor devision
|
||||
* (V_fullscale / 4096) * raw
|
||||
*/
|
||||
if (st->vrange_high)
|
||||
dummy *= 26350;
|
||||
else
|
||||
dummy *= 6650;
|
||||
|
||||
*val = DIV_ROUND_CLOSEST(dummy, 4096);
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int adm1177_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct adm1177_state *st = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_max_alarm:
|
||||
adm1177_write_alert_thr(st, val);
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t adm1177_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct adm1177_state *st = data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
return 0444;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
if (st->r_sense_uohm)
|
||||
return 0444;
|
||||
return 0;
|
||||
case hwmon_curr_max_alarm:
|
||||
if (st->r_sense_uohm)
|
||||
return 0644;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *adm1177_info[] = {
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_MAX_ALARM),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops adm1177_hwmon_ops = {
|
||||
.is_visible = adm1177_is_visible,
|
||||
.read = adm1177_read,
|
||||
.write = adm1177_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info adm1177_chip_info = {
|
||||
.ops = &adm1177_hwmon_ops,
|
||||
.info = adm1177_info,
|
||||
};
|
||||
|
||||
static void adm1177_remove(void *data)
|
||||
{
|
||||
struct adm1177_state *st = data;
|
||||
|
||||
regulator_disable(st->reg);
|
||||
}
|
||||
|
||||
static int adm1177_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct adm1177_state *st;
|
||||
u32 alert_threshold_ua;
|
||||
int ret;
|
||||
|
||||
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return -ENOMEM;
|
||||
|
||||
st->client = client;
|
||||
|
||||
st->reg = devm_regulator_get_optional(&client->dev, "vref");
|
||||
if (IS_ERR(st->reg)) {
|
||||
if (PTR_ERR(st->reg) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
st->reg = NULL;
|
||||
} else {
|
||||
ret = regulator_enable(st->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_add_action_or_reset(&client->dev, adm1177_remove,
|
||||
st);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (device_property_read_u32(dev, "shunt-resistor-micro-ohms",
|
||||
&st->r_sense_uohm))
|
||||
st->r_sense_uohm = 0;
|
||||
if (device_property_read_u32(dev, "adi,shutdown-threshold-microamp",
|
||||
&alert_threshold_ua)) {
|
||||
if (st->r_sense_uohm)
|
||||
/*
|
||||
* set maximum default value from datasheet based on
|
||||
* shunt-resistor
|
||||
*/
|
||||
alert_threshold_ua = div_u64(105840000000,
|
||||
st->r_sense_uohm);
|
||||
else
|
||||
alert_threshold_ua = 0;
|
||||
}
|
||||
st->vrange_high = device_property_read_bool(dev,
|
||||
"adi,vrange-high-enable");
|
||||
if (alert_threshold_ua && st->r_sense_uohm)
|
||||
adm1177_write_alert_thr(st, alert_threshold_ua);
|
||||
|
||||
ret = adm1177_write_cmd(st, ADM1177_CMD_V_CONT |
|
||||
ADM1177_CMD_I_CONT |
|
||||
(st->vrange_high ? 0 : ADM1177_CMD_VRANGE));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hwmon_dev =
|
||||
devm_hwmon_device_register_with_info(dev, client->name, st,
|
||||
&adm1177_chip_info, NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adm1177_id[] = {
|
||||
{"adm1177", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adm1177_id);
|
||||
|
||||
static const struct of_device_id adm1177_dt_ids[] = {
|
||||
{ .compatible = "adi,adm1177" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adm1177_dt_ids);
|
||||
|
||||
static struct i2c_driver adm1177_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "adm1177",
|
||||
.of_match_table = adm1177_dt_ids,
|
||||
},
|
||||
.probe = adm1177_probe,
|
||||
.id_table = adm1177_id,
|
||||
};
|
||||
module_i2c_driver(adm1177_driver);
|
||||
|
||||
MODULE_AUTHOR("Beniamin Bia <beniamin.bia@analog.com>");
|
||||
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices ADM1177 ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
574
drivers/hwmon/drivetemp.c
Normal file
574
drivers/hwmon/drivetemp.c
Normal file
|
@ -0,0 +1,574 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Hwmon client for disk and solid state drives with temperature sensors
|
||||
* Copyright (C) 2019 Zodiac Inflight Innovations
|
||||
*
|
||||
* With input from:
|
||||
* Hwmon client for S.M.A.R.T. hard disk drives with temperature sensors.
|
||||
* (C) 2018 Linus Walleij
|
||||
*
|
||||
* hwmon: Driver for SCSI/ATA temperature sensors
|
||||
* by Constantin Baranov <const@mimas.ru>, submitted September 2009
|
||||
*
|
||||
* This drive supports reporting the temperatire of SATA drives. It can be
|
||||
* easily extended to report the temperature of SCSI drives.
|
||||
*
|
||||
* The primary means to read drive temperatures and temperature limits
|
||||
* for ATA drives is the SCT Command Transport feature set as specified in
|
||||
* ATA8-ACS.
|
||||
* It can be used to read the current drive temperature, temperature limits,
|
||||
* and historic minimum and maximum temperatures. The SCT Command Transport
|
||||
* feature set is documented in "AT Attachment 8 - ATA/ATAPI Command Set
|
||||
* (ATA8-ACS)".
|
||||
*
|
||||
* If the SCT Command Transport feature set is not available, drive temperatures
|
||||
* may be readable through SMART attributes. Since SMART attributes are not well
|
||||
* defined, this method is only used as fallback mechanism.
|
||||
*
|
||||
* There are three SMART attributes which may report drive temperatures.
|
||||
* Those are defined as follows (from
|
||||
* http://www.cropel.com/library/smart-attribute-list.aspx).
|
||||
*
|
||||
* 190 Temperature Temperature, monitored by a sensor somewhere inside
|
||||
* the drive. Raw value typicaly holds the actual
|
||||
* temperature (hexadecimal) in its rightmost two digits.
|
||||
*
|
||||
* 194 Temperature Temperature, monitored by a sensor somewhere inside
|
||||
* the drive. Raw value typicaly holds the actual
|
||||
* temperature (hexadecimal) in its rightmost two digits.
|
||||
*
|
||||
* 231 Temperature Temperature, monitored by a sensor somewhere inside
|
||||
* the drive. Raw value typicaly holds the actual
|
||||
* temperature (hexadecimal) in its rightmost two digits.
|
||||
*
|
||||
* Wikipedia defines attributes a bit differently.
|
||||
*
|
||||
* 190 Temperature Value is equal to (100-temp. °C), allowing manufacturer
|
||||
* Difference or to set a minimum threshold which corresponds to a
|
||||
* Airflow maximum temperature. This also follows the convention of
|
||||
* Temperature 100 being a best-case value and lower values being
|
||||
* undesirable. However, some older drives may instead
|
||||
* report raw Temperature (identical to 0xC2) or
|
||||
* Temperature minus 50 here.
|
||||
* 194 Temperature or Indicates the device temperature, if the appropriate
|
||||
* Temperature sensor is fitted. Lowest byte of the raw value contains
|
||||
* Celsius the exact temperature value (Celsius degrees).
|
||||
* 231 Life Left Indicates the approximate SSD life left, in terms of
|
||||
* (SSDs) or program/erase cycles or available reserved blocks.
|
||||
* Temperature A normalized value of 100 represents a new drive, with
|
||||
* a threshold value at 10 indicating a need for
|
||||
* replacement. A value of 0 may mean that the drive is
|
||||
* operating in read-only mode to allow data recovery.
|
||||
* Previously (pre-2010) occasionally used for Drive
|
||||
* Temperature (more typically reported at 0xC2).
|
||||
*
|
||||
* Common denominator is that the first raw byte reports the temperature
|
||||
* in degrees C on almost all drives. Some drives may report a fractional
|
||||
* temperature in the second raw byte.
|
||||
*
|
||||
* Known exceptions (from libatasmart):
|
||||
* - SAMSUNG SV0412H and SAMSUNG SV1204H) report the temperature in 10th
|
||||
* degrees C in the first two raw bytes.
|
||||
* - A few Maxtor drives report an unknown or bad value in attribute 194.
|
||||
* - Certain Apple SSD drives report an unknown value in attribute 190.
|
||||
* Only certain firmware versions are affected.
|
||||
*
|
||||
* Those exceptions affect older ATA drives and are currently ignored.
|
||||
* Also, the second raw byte (possibly reporting the fractional temperature)
|
||||
* is currently ignored.
|
||||
*
|
||||
* Many drives also report temperature limits in additional SMART data raw
|
||||
* bytes. The format of those is not well defined and varies widely.
|
||||
* The driver does not currently attempt to report those limits.
|
||||
*
|
||||
* According to data in smartmontools, attribute 231 is rarely used to report
|
||||
* drive temperatures. At the same time, several drives report SSD life left
|
||||
* in attribute 231, but do not support temperature sensors. For this reason,
|
||||
* attribute 231 is currently ignored.
|
||||
*
|
||||
* Following above definitions, temperatures are reported as follows.
|
||||
* If SCT Command Transport is supported, it is used to read the
|
||||
* temperature and, if available, temperature limits.
|
||||
* - Otherwise, if SMART attribute 194 is supported, it is used to read
|
||||
* the temperature.
|
||||
* - Otherwise, if SMART attribute 190 is supported, it is used to read
|
||||
* the temperature.
|
||||
*/
|
||||
|
||||
#include <linux/ata.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_driver.h>
|
||||
#include <scsi/scsi_proto.h>
|
||||
|
||||
struct drivetemp_data {
|
||||
struct list_head list; /* list of instantiated devices */
|
||||
struct mutex lock; /* protect data buffer accesses */
|
||||
struct scsi_device *sdev; /* SCSI device */
|
||||
struct device *dev; /* instantiating device */
|
||||
struct device *hwdev; /* hardware monitoring device */
|
||||
u8 smartdata[ATA_SECT_SIZE]; /* local buffer */
|
||||
int (*get_temp)(struct drivetemp_data *st, u32 attr, long *val);
|
||||
bool have_temp_lowest; /* lowest temp in SCT status */
|
||||
bool have_temp_highest; /* highest temp in SCT status */
|
||||
bool have_temp_min; /* have min temp */
|
||||
bool have_temp_max; /* have max temp */
|
||||
bool have_temp_lcrit; /* have lower critical limit */
|
||||
bool have_temp_crit; /* have critical limit */
|
||||
int temp_min; /* min temp */
|
||||
int temp_max; /* max temp */
|
||||
int temp_lcrit; /* lower critical limit */
|
||||
int temp_crit; /* critical limit */
|
||||
};
|
||||
|
||||
static LIST_HEAD(drivetemp_devlist);
|
||||
|
||||
#define ATA_MAX_SMART_ATTRS 30
|
||||
#define SMART_TEMP_PROP_190 190
|
||||
#define SMART_TEMP_PROP_194 194
|
||||
|
||||
#define SCT_STATUS_REQ_ADDR 0xe0
|
||||
#define SCT_STATUS_VERSION_LOW 0 /* log byte offsets */
|
||||
#define SCT_STATUS_VERSION_HIGH 1
|
||||
#define SCT_STATUS_TEMP 200
|
||||
#define SCT_STATUS_TEMP_LOWEST 201
|
||||
#define SCT_STATUS_TEMP_HIGHEST 202
|
||||
#define SCT_READ_LOG_ADDR 0xe1
|
||||
#define SMART_READ_LOG 0xd5
|
||||
#define SMART_WRITE_LOG 0xd6
|
||||
|
||||
#define INVALID_TEMP 0x80
|
||||
|
||||
#define temp_is_valid(temp) ((temp) != INVALID_TEMP)
|
||||
#define temp_from_sct(temp) (((s8)(temp)) * 1000)
|
||||
|
||||
static inline bool ata_id_smart_supported(u16 *id)
|
||||
{
|
||||
return id[ATA_ID_COMMAND_SET_1] & BIT(0);
|
||||
}
|
||||
|
||||
static inline bool ata_id_smart_enabled(u16 *id)
|
||||
{
|
||||
return id[ATA_ID_CFS_ENABLE_1] & BIT(0);
|
||||
}
|
||||
|
||||
static int drivetemp_scsi_command(struct drivetemp_data *st,
|
||||
u8 ata_command, u8 feature,
|
||||
u8 lba_low, u8 lba_mid, u8 lba_high)
|
||||
{
|
||||
u8 scsi_cmd[MAX_COMMAND_SIZE];
|
||||
int data_dir;
|
||||
|
||||
memset(scsi_cmd, 0, sizeof(scsi_cmd));
|
||||
scsi_cmd[0] = ATA_16;
|
||||
if (ata_command == ATA_CMD_SMART && feature == SMART_WRITE_LOG) {
|
||||
scsi_cmd[1] = (5 << 1); /* PIO Data-out */
|
||||
/*
|
||||
* No off.line or cc, write to dev, block count in sector count
|
||||
* field.
|
||||
*/
|
||||
scsi_cmd[2] = 0x06;
|
||||
data_dir = DMA_TO_DEVICE;
|
||||
} else {
|
||||
scsi_cmd[1] = (4 << 1); /* PIO Data-in */
|
||||
/*
|
||||
* No off.line or cc, read from dev, block count in sector count
|
||||
* field.
|
||||
*/
|
||||
scsi_cmd[2] = 0x0e;
|
||||
data_dir = DMA_FROM_DEVICE;
|
||||
}
|
||||
scsi_cmd[4] = feature;
|
||||
scsi_cmd[6] = 1; /* 1 sector */
|
||||
scsi_cmd[8] = lba_low;
|
||||
scsi_cmd[10] = lba_mid;
|
||||
scsi_cmd[12] = lba_high;
|
||||
scsi_cmd[14] = ata_command;
|
||||
|
||||
return scsi_execute_req(st->sdev, scsi_cmd, data_dir,
|
||||
st->smartdata, ATA_SECT_SIZE, NULL, HZ, 5,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static int drivetemp_ata_command(struct drivetemp_data *st, u8 feature,
|
||||
u8 select)
|
||||
{
|
||||
return drivetemp_scsi_command(st, ATA_CMD_SMART, feature, select,
|
||||
ATA_SMART_LBAM_PASS, ATA_SMART_LBAH_PASS);
|
||||
}
|
||||
|
||||
static int drivetemp_get_smarttemp(struct drivetemp_data *st, u32 attr,
|
||||
long *temp)
|
||||
{
|
||||
u8 *buf = st->smartdata;
|
||||
bool have_temp = false;
|
||||
u8 temp_raw;
|
||||
u8 csum;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
err = drivetemp_ata_command(st, ATA_SMART_READ_VALUES, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Checksum the read value table */
|
||||
csum = 0;
|
||||
for (i = 0; i < ATA_SECT_SIZE; i++)
|
||||
csum += buf[i];
|
||||
if (csum) {
|
||||
dev_dbg(&st->sdev->sdev_gendev,
|
||||
"checksum error reading SMART values\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
for (i = 0; i < ATA_MAX_SMART_ATTRS; i++) {
|
||||
u8 *attr = buf + i * 12;
|
||||
int id = attr[2];
|
||||
|
||||
if (!id)
|
||||
continue;
|
||||
|
||||
if (id == SMART_TEMP_PROP_190) {
|
||||
temp_raw = attr[7];
|
||||
have_temp = true;
|
||||
}
|
||||
if (id == SMART_TEMP_PROP_194) {
|
||||
temp_raw = attr[7];
|
||||
have_temp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_temp) {
|
||||
*temp = temp_raw * 1000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int drivetemp_get_scttemp(struct drivetemp_data *st, u32 attr, long *val)
|
||||
{
|
||||
u8 *buf = st->smartdata;
|
||||
int err;
|
||||
|
||||
err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_STATUS_REQ_ADDR);
|
||||
if (err)
|
||||
return err;
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
*val = temp_from_sct(buf[SCT_STATUS_TEMP]);
|
||||
break;
|
||||
case hwmon_temp_lowest:
|
||||
*val = temp_from_sct(buf[SCT_STATUS_TEMP_LOWEST]);
|
||||
break;
|
||||
case hwmon_temp_highest:
|
||||
*val = temp_from_sct(buf[SCT_STATUS_TEMP_HIGHEST]);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int drivetemp_identify_sata(struct drivetemp_data *st)
|
||||
{
|
||||
struct scsi_device *sdev = st->sdev;
|
||||
u8 *buf = st->smartdata;
|
||||
struct scsi_vpd *vpd;
|
||||
bool is_ata, is_sata;
|
||||
bool have_sct_data_table;
|
||||
bool have_sct_temp;
|
||||
bool have_smart;
|
||||
bool have_sct;
|
||||
u16 *ata_id;
|
||||
u16 version;
|
||||
long temp;
|
||||
int err;
|
||||
|
||||
/* SCSI-ATA Translation present? */
|
||||
rcu_read_lock();
|
||||
vpd = rcu_dereference(sdev->vpd_pg89);
|
||||
|
||||
/*
|
||||
* Verify that ATA IDENTIFY DEVICE data is included in ATA Information
|
||||
* VPD and that the drive implements the SATA protocol.
|
||||
*/
|
||||
if (!vpd || vpd->len < 572 || vpd->data[56] != ATA_CMD_ID_ATA ||
|
||||
vpd->data[36] != 0x34) {
|
||||
rcu_read_unlock();
|
||||
return -ENODEV;
|
||||
}
|
||||
ata_id = (u16 *)&vpd->data[60];
|
||||
is_ata = ata_id_is_ata(ata_id);
|
||||
is_sata = ata_id_is_sata(ata_id);
|
||||
have_sct = ata_id_sct_supported(ata_id);
|
||||
have_sct_data_table = ata_id_sct_data_tables(ata_id);
|
||||
have_smart = ata_id_smart_supported(ata_id) &&
|
||||
ata_id_smart_enabled(ata_id);
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
/* bail out if this is not a SATA device */
|
||||
if (!is_ata || !is_sata)
|
||||
return -ENODEV;
|
||||
if (!have_sct)
|
||||
goto skip_sct;
|
||||
|
||||
err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_STATUS_REQ_ADDR);
|
||||
if (err)
|
||||
goto skip_sct;
|
||||
|
||||
version = (buf[SCT_STATUS_VERSION_HIGH] << 8) |
|
||||
buf[SCT_STATUS_VERSION_LOW];
|
||||
if (version != 2 && version != 3)
|
||||
goto skip_sct;
|
||||
|
||||
have_sct_temp = temp_is_valid(buf[SCT_STATUS_TEMP]);
|
||||
if (!have_sct_temp)
|
||||
goto skip_sct;
|
||||
|
||||
st->have_temp_lowest = temp_is_valid(buf[SCT_STATUS_TEMP_LOWEST]);
|
||||
st->have_temp_highest = temp_is_valid(buf[SCT_STATUS_TEMP_HIGHEST]);
|
||||
|
||||
if (!have_sct_data_table)
|
||||
goto skip_sct;
|
||||
|
||||
/* Request and read temperature history table */
|
||||
memset(buf, '\0', sizeof(st->smartdata));
|
||||
buf[0] = 5; /* data table command */
|
||||
buf[2] = 1; /* read table */
|
||||
buf[4] = 2; /* temperature history table */
|
||||
|
||||
err = drivetemp_ata_command(st, SMART_WRITE_LOG, SCT_STATUS_REQ_ADDR);
|
||||
if (err)
|
||||
goto skip_sct_data;
|
||||
|
||||
err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_READ_LOG_ADDR);
|
||||
if (err)
|
||||
goto skip_sct_data;
|
||||
|
||||
/*
|
||||
* Temperature limits per AT Attachment 8 -
|
||||
* ATA/ATAPI Command Set (ATA8-ACS)
|
||||
*/
|
||||
st->have_temp_max = temp_is_valid(buf[6]);
|
||||
st->have_temp_crit = temp_is_valid(buf[7]);
|
||||
st->have_temp_min = temp_is_valid(buf[8]);
|
||||
st->have_temp_lcrit = temp_is_valid(buf[9]);
|
||||
|
||||
st->temp_max = temp_from_sct(buf[6]);
|
||||
st->temp_crit = temp_from_sct(buf[7]);
|
||||
st->temp_min = temp_from_sct(buf[8]);
|
||||
st->temp_lcrit = temp_from_sct(buf[9]);
|
||||
|
||||
skip_sct_data:
|
||||
if (have_sct_temp) {
|
||||
st->get_temp = drivetemp_get_scttemp;
|
||||
return 0;
|
||||
}
|
||||
skip_sct:
|
||||
if (!have_smart)
|
||||
return -ENODEV;
|
||||
st->get_temp = drivetemp_get_smarttemp;
|
||||
return drivetemp_get_smarttemp(st, hwmon_temp_input, &temp);
|
||||
}
|
||||
|
||||
static int drivetemp_identify(struct drivetemp_data *st)
|
||||
{
|
||||
struct scsi_device *sdev = st->sdev;
|
||||
|
||||
/* Bail out immediately if there is no inquiry data */
|
||||
if (!sdev->inquiry || sdev->inquiry_len < 16)
|
||||
return -ENODEV;
|
||||
|
||||
/* Disk device? */
|
||||
if (sdev->type != TYPE_DISK && sdev->type != TYPE_ZBC)
|
||||
return -ENODEV;
|
||||
|
||||
return drivetemp_identify_sata(st);
|
||||
}
|
||||
|
||||
static int drivetemp_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct drivetemp_data *st = dev_get_drvdata(dev);
|
||||
int err = 0;
|
||||
|
||||
if (type != hwmon_temp)
|
||||
return -EINVAL;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_lowest:
|
||||
case hwmon_temp_highest:
|
||||
mutex_lock(&st->lock);
|
||||
err = st->get_temp(st, attr, val);
|
||||
mutex_unlock(&st->lock);
|
||||
break;
|
||||
case hwmon_temp_lcrit:
|
||||
*val = st->temp_lcrit;
|
||||
break;
|
||||
case hwmon_temp_min:
|
||||
*val = st->temp_min;
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
*val = st->temp_max;
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
*val = st->temp_crit;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static umode_t drivetemp_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct drivetemp_data *st = data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
return 0444;
|
||||
case hwmon_temp_lowest:
|
||||
if (st->have_temp_lowest)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_temp_highest:
|
||||
if (st->have_temp_highest)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_temp_min:
|
||||
if (st->have_temp_min)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
if (st->have_temp_max)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_temp_lcrit:
|
||||
if (st->have_temp_lcrit)
|
||||
return 0444;
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
if (st->have_temp_crit)
|
||||
return 0444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *drivetemp_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT |
|
||||
HWMON_T_LOWEST | HWMON_T_HIGHEST |
|
||||
HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_LCRIT | HWMON_T_CRIT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops drivetemp_ops = {
|
||||
.is_visible = drivetemp_is_visible,
|
||||
.read = drivetemp_read,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info drivetemp_chip_info = {
|
||||
.ops = &drivetemp_ops,
|
||||
.info = drivetemp_info,
|
||||
};
|
||||
|
||||
/*
|
||||
* The device argument points to sdev->sdev_dev. Its parent is
|
||||
* sdev->sdev_gendev, which we can use to get the scsi_device pointer.
|
||||
*/
|
||||
static int drivetemp_add(struct device *dev, struct class_interface *intf)
|
||||
{
|
||||
struct scsi_device *sdev = to_scsi_device(dev->parent);
|
||||
struct drivetemp_data *st;
|
||||
int err;
|
||||
|
||||
st = kzalloc(sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return -ENOMEM;
|
||||
|
||||
st->sdev = sdev;
|
||||
st->dev = dev;
|
||||
mutex_init(&st->lock);
|
||||
|
||||
if (drivetemp_identify(st)) {
|
||||
err = -ENODEV;
|
||||
goto abort;
|
||||
}
|
||||
|
||||
st->hwdev = hwmon_device_register_with_info(dev->parent, "drivetemp",
|
||||
st, &drivetemp_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(st->hwdev)) {
|
||||
err = PTR_ERR(st->hwdev);
|
||||
goto abort;
|
||||
}
|
||||
|
||||
list_add(&st->list, &drivetemp_devlist);
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
kfree(st);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void drivetemp_remove(struct device *dev, struct class_interface *intf)
|
||||
{
|
||||
struct drivetemp_data *st, *tmp;
|
||||
|
||||
list_for_each_entry_safe(st, tmp, &drivetemp_devlist, list) {
|
||||
if (st->dev == dev) {
|
||||
list_del(&st->list);
|
||||
hwmon_device_unregister(st->hwdev);
|
||||
kfree(st);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct class_interface drivetemp_interface = {
|
||||
.add_dev = drivetemp_add,
|
||||
.remove_dev = drivetemp_remove,
|
||||
};
|
||||
|
||||
static int __init drivetemp_init(void)
|
||||
{
|
||||
return scsi_register_interface(&drivetemp_interface);
|
||||
}
|
||||
|
||||
static void __exit drivetemp_exit(void)
|
||||
{
|
||||
scsi_unregister_interface(&drivetemp_interface);
|
||||
}
|
||||
|
||||
module_init(drivetemp_init);
|
||||
module_exit(drivetemp_exit);
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck <linus@roeck-us.net>");
|
||||
MODULE_DESCRIPTION("Hard drive temperature monitor");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -188,7 +188,7 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
|||
|
||||
static int hwmon_attr_base(enum hwmon_sensor_types type)
|
||||
{
|
||||
if (type == hwmon_in)
|
||||
if (type == hwmon_in || type == hwmon_intrusion)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
@ -343,6 +343,7 @@ static const char * const hwmon_chip_attrs[] = {
|
|||
};
|
||||
|
||||
static const char * const hwmon_temp_attr_templates[] = {
|
||||
[hwmon_temp_enable] = "temp%d_enable",
|
||||
[hwmon_temp_input] = "temp%d_input",
|
||||
[hwmon_temp_type] = "temp%d_type",
|
||||
[hwmon_temp_lcrit] = "temp%d_lcrit",
|
||||
|
@ -370,6 +371,7 @@ static const char * const hwmon_temp_attr_templates[] = {
|
|||
};
|
||||
|
||||
static const char * const hwmon_in_attr_templates[] = {
|
||||
[hwmon_in_enable] = "in%d_enable",
|
||||
[hwmon_in_input] = "in%d_input",
|
||||
[hwmon_in_min] = "in%d_min",
|
||||
[hwmon_in_max] = "in%d_max",
|
||||
|
@ -385,10 +387,10 @@ static const char * const hwmon_in_attr_templates[] = {
|
|||
[hwmon_in_max_alarm] = "in%d_max_alarm",
|
||||
[hwmon_in_lcrit_alarm] = "in%d_lcrit_alarm",
|
||||
[hwmon_in_crit_alarm] = "in%d_crit_alarm",
|
||||
[hwmon_in_enable] = "in%d_enable",
|
||||
};
|
||||
|
||||
static const char * const hwmon_curr_attr_templates[] = {
|
||||
[hwmon_curr_enable] = "curr%d_enable",
|
||||
[hwmon_curr_input] = "curr%d_input",
|
||||
[hwmon_curr_min] = "curr%d_min",
|
||||
[hwmon_curr_max] = "curr%d_max",
|
||||
|
@ -407,6 +409,7 @@ static const char * const hwmon_curr_attr_templates[] = {
|
|||
};
|
||||
|
||||
static const char * const hwmon_power_attr_templates[] = {
|
||||
[hwmon_power_enable] = "power%d_enable",
|
||||
[hwmon_power_average] = "power%d_average",
|
||||
[hwmon_power_average_interval] = "power%d_average_interval",
|
||||
[hwmon_power_average_interval_max] = "power%d_interval_max",
|
||||
|
@ -438,11 +441,13 @@ static const char * const hwmon_power_attr_templates[] = {
|
|||
};
|
||||
|
||||
static const char * const hwmon_energy_attr_templates[] = {
|
||||
[hwmon_energy_enable] = "energy%d_enable",
|
||||
[hwmon_energy_input] = "energy%d_input",
|
||||
[hwmon_energy_label] = "energy%d_label",
|
||||
};
|
||||
|
||||
static const char * const hwmon_humidity_attr_templates[] = {
|
||||
[hwmon_humidity_enable] = "humidity%d_enable",
|
||||
[hwmon_humidity_input] = "humidity%d_input",
|
||||
[hwmon_humidity_label] = "humidity%d_label",
|
||||
[hwmon_humidity_min] = "humidity%d_min",
|
||||
|
@ -454,6 +459,7 @@ static const char * const hwmon_humidity_attr_templates[] = {
|
|||
};
|
||||
|
||||
static const char * const hwmon_fan_attr_templates[] = {
|
||||
[hwmon_fan_enable] = "fan%d_enable",
|
||||
[hwmon_fan_input] = "fan%d_input",
|
||||
[hwmon_fan_label] = "fan%d_label",
|
||||
[hwmon_fan_min] = "fan%d_min",
|
||||
|
@ -474,6 +480,11 @@ static const char * const hwmon_pwm_attr_templates[] = {
|
|||
[hwmon_pwm_freq] = "pwm%d_freq",
|
||||
};
|
||||
|
||||
static const char * const hwmon_intrusion_attr_templates[] = {
|
||||
[hwmon_intrusion_alarm] = "intrusion%d_alarm",
|
||||
[hwmon_intrusion_beep] = "intrusion%d_beep",
|
||||
};
|
||||
|
||||
static const char * const *__templates[] = {
|
||||
[hwmon_chip] = hwmon_chip_attrs,
|
||||
[hwmon_temp] = hwmon_temp_attr_templates,
|
||||
|
@ -484,6 +495,7 @@ static const char * const *__templates[] = {
|
|||
[hwmon_humidity] = hwmon_humidity_attr_templates,
|
||||
[hwmon_fan] = hwmon_fan_attr_templates,
|
||||
[hwmon_pwm] = hwmon_pwm_attr_templates,
|
||||
[hwmon_intrusion] = hwmon_intrusion_attr_templates,
|
||||
};
|
||||
|
||||
static const int __templates_size[] = {
|
||||
|
@ -496,6 +508,7 @@ static const int __templates_size[] = {
|
|||
[hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates),
|
||||
[hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates),
|
||||
[hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates),
|
||||
[hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates),
|
||||
};
|
||||
|
||||
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* k10temp.c - AMD Family 10h/11h/12h/14h/15h/16h processor hardware monitoring
|
||||
* k10temp.c - AMD Family 10h/11h/12h/14h/15h/16h/17h
|
||||
* processor hardware monitoring
|
||||
*
|
||||
* Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2020 Guenter Roeck <linux@roeck-us.net>
|
||||
*
|
||||
* Implementation notes:
|
||||
* - CCD register address information as well as the calculation to
|
||||
* convert raw register values is from https://github.com/ocerman/zenpower.
|
||||
* The information is not confirmed from chip datasheets, but experiments
|
||||
* suggest that it provides reasonable temperature values.
|
||||
* - Register addresses to read chip voltage and current are also from
|
||||
* https://github.com/ocerman/zenpower, and not confirmed from chip
|
||||
* datasheets. Current calibration is board specific and not typically
|
||||
* shared by board vendors. For this reason, current values are
|
||||
* normalized to report 1A/LSB for core current and and 0.25A/LSB for SoC
|
||||
* current. Reported values can be adjusted using the sensors configuration
|
||||
* file.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
|
@ -31,22 +47,22 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
|
|||
#endif
|
||||
|
||||
/* CPUID function 0x80000001, ebx */
|
||||
#define CPUID_PKGTYPE_MASK 0xf0000000
|
||||
#define CPUID_PKGTYPE_MASK GENMASK(31, 28)
|
||||
#define CPUID_PKGTYPE_F 0x00000000
|
||||
#define CPUID_PKGTYPE_AM2R2_AM3 0x10000000
|
||||
|
||||
/* DRAM controller (PCI function 2) */
|
||||
#define REG_DCT0_CONFIG_HIGH 0x094
|
||||
#define DDR3_MODE 0x00000100
|
||||
#define DDR3_MODE BIT(8)
|
||||
|
||||
/* miscellaneous (PCI function 3) */
|
||||
#define REG_HARDWARE_THERMAL_CONTROL 0x64
|
||||
#define HTC_ENABLE 0x00000001
|
||||
#define HTC_ENABLE BIT(0)
|
||||
|
||||
#define REG_REPORTED_TEMPERATURE 0xa4
|
||||
|
||||
#define REG_NORTHBRIDGE_CAPABILITIES 0xe8
|
||||
#define NB_CAP_HTC 0x00000400
|
||||
#define NB_CAP_HTC BIT(10)
|
||||
|
||||
/*
|
||||
* For F15h M60h and M70h, REG_HARDWARE_THERMAL_CONTROL
|
||||
|
@ -60,6 +76,20 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
|
|||
/* F17h M01h Access througn SMN */
|
||||
#define F17H_M01H_REPORTED_TEMP_CTRL_OFFSET 0x00059800
|
||||
|
||||
#define F17H_M70H_CCD_TEMP(x) (0x00059954 + ((x) * 4))
|
||||
#define F17H_M70H_CCD_TEMP_VALID BIT(11)
|
||||
#define F17H_M70H_CCD_TEMP_MASK GENMASK(10, 0)
|
||||
|
||||
#define F17H_M01H_SVI 0x0005A000
|
||||
#define F17H_M01H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0xc)
|
||||
#define F17H_M01H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0x10)
|
||||
|
||||
#define CUR_TEMP_SHIFT 21
|
||||
#define CUR_TEMP_RANGE_SEL_MASK BIT(19)
|
||||
|
||||
#define CFACTOR_ICORE 1000000 /* 1A / LSB */
|
||||
#define CFACTOR_ISOC 250000 /* 0.25A / LSB */
|
||||
|
||||
struct k10temp_data {
|
||||
struct pci_dev *pdev;
|
||||
void (*read_htcreg)(struct pci_dev *pdev, u32 *regval);
|
||||
|
@ -67,6 +97,10 @@ struct k10temp_data {
|
|||
int temp_offset;
|
||||
u32 temp_adjust_mask;
|
||||
bool show_tdie;
|
||||
u32 show_tccd;
|
||||
u32 svi_addr[2];
|
||||
bool show_current;
|
||||
int cfactor[2];
|
||||
};
|
||||
|
||||
struct tctl_offset {
|
||||
|
@ -84,6 +118,16 @@ static const struct tctl_offset tctl_offset_table[] = {
|
|||
{ 0x17, "AMD Ryzen Threadripper 29", 27000 }, /* 29{20,50,70,90}[W]X */
|
||||
};
|
||||
|
||||
static bool is_threadripper(void)
|
||||
{
|
||||
return strstr(boot_cpu_data.x86_model_id, "Threadripper");
|
||||
}
|
||||
|
||||
static bool is_epyc(void)
|
||||
{
|
||||
return strstr(boot_cpu_data.x86_model_id, "EPYC");
|
||||
}
|
||||
|
||||
static void read_htcreg_pci(struct pci_dev *pdev, u32 *regval)
|
||||
{
|
||||
pci_read_config_dword(pdev, REG_HARDWARE_THERMAL_CONTROL, regval);
|
||||
|
@ -123,130 +167,237 @@ static void read_tempreg_nb_f17(struct pci_dev *pdev, u32 *regval)
|
|||
F17H_M01H_REPORTED_TEMP_CTRL_OFFSET, regval);
|
||||
}
|
||||
|
||||
static unsigned int get_raw_temp(struct k10temp_data *data)
|
||||
static long get_raw_temp(struct k10temp_data *data)
|
||||
{
|
||||
unsigned int temp;
|
||||
u32 regval;
|
||||
long temp;
|
||||
|
||||
data->read_tempreg(data->pdev, ®val);
|
||||
temp = (regval >> 21) * 125;
|
||||
temp = (regval >> CUR_TEMP_SHIFT) * 125;
|
||||
if (regval & data->temp_adjust_mask)
|
||||
temp -= 49000;
|
||||
return temp;
|
||||
}
|
||||
|
||||
static ssize_t temp1_input_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
const char *k10temp_temp_label[] = {
|
||||
"Tdie",
|
||||
"Tctl",
|
||||
"Tccd1",
|
||||
"Tccd2",
|
||||
"Tccd3",
|
||||
"Tccd4",
|
||||
"Tccd5",
|
||||
"Tccd6",
|
||||
"Tccd7",
|
||||
"Tccd8",
|
||||
};
|
||||
|
||||
const char *k10temp_in_label[] = {
|
||||
"Vcore",
|
||||
"Vsoc",
|
||||
};
|
||||
|
||||
const char *k10temp_curr_label[] = {
|
||||
"Icore",
|
||||
"Isoc",
|
||||
};
|
||||
|
||||
static int k10temp_read_labels(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
*str = k10temp_temp_label[channel];
|
||||
break;
|
||||
case hwmon_in:
|
||||
*str = k10temp_in_label[channel];
|
||||
break;
|
||||
case hwmon_curr:
|
||||
*str = k10temp_curr_label[channel];
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k10temp_read_curr(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct k10temp_data *data = dev_get_drvdata(dev);
|
||||
unsigned int temp = get_raw_temp(data);
|
||||
|
||||
if (temp > data->temp_offset)
|
||||
temp -= data->temp_offset;
|
||||
else
|
||||
temp = 0;
|
||||
|
||||
return sprintf(buf, "%u\n", temp);
|
||||
}
|
||||
|
||||
static ssize_t temp2_input_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct k10temp_data *data = dev_get_drvdata(dev);
|
||||
unsigned int temp = get_raw_temp(data);
|
||||
|
||||
return sprintf(buf, "%u\n", temp);
|
||||
}
|
||||
|
||||
static ssize_t temp_label_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%s\n", attr->index ? "Tctl" : "Tdie");
|
||||
}
|
||||
|
||||
static ssize_t temp1_max_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", 70 * 1000);
|
||||
}
|
||||
|
||||
static ssize_t temp_crit_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct k10temp_data *data = dev_get_drvdata(dev);
|
||||
int show_hyst = attr->index;
|
||||
u32 regval;
|
||||
int value;
|
||||
|
||||
data->read_htcreg(data->pdev, ®val);
|
||||
value = ((regval >> 16) & 0x7f) * 500 + 52000;
|
||||
if (show_hyst)
|
||||
value -= ((regval >> 24) & 0xf) * 500;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
|
||||
data->svi_addr[channel], ®val);
|
||||
*val = DIV_ROUND_CLOSEST(data->cfactor[channel] *
|
||||
(regval & 0xff),
|
||||
1000);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(temp1_input);
|
||||
static DEVICE_ATTR_RO(temp1_max);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_crit, temp_crit, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_crit_hyst, temp_crit, 1);
|
||||
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_label, temp_label, 0);
|
||||
static DEVICE_ATTR_RO(temp2_input);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_label, temp_label, 1);
|
||||
|
||||
static umode_t k10temp_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int index)
|
||||
static int k10temp_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct k10temp_data *data = dev_get_drvdata(dev);
|
||||
u32 regval;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
|
||||
data->svi_addr[channel], ®val);
|
||||
regval = (regval >> 16) & 0xff;
|
||||
*val = DIV_ROUND_CLOSEST(155000 - regval * 625, 100);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct k10temp_data *data = dev_get_drvdata(dev);
|
||||
u32 regval;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
switch (channel) {
|
||||
case 0: /* Tdie */
|
||||
*val = get_raw_temp(data) - data->temp_offset;
|
||||
if (*val < 0)
|
||||
*val = 0;
|
||||
break;
|
||||
case 1: /* Tctl */
|
||||
*val = get_raw_temp(data);
|
||||
if (*val < 0)
|
||||
*val = 0;
|
||||
break;
|
||||
case 2 ... 9: /* Tccd{1-8} */
|
||||
amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
|
||||
F17H_M70H_CCD_TEMP(channel - 2), ®val);
|
||||
*val = (regval & F17H_M70H_CCD_TEMP_MASK) * 125 - 49000;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
*val = 70 * 1000;
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
data->read_htcreg(data->pdev, ®val);
|
||||
*val = ((regval >> 16) & 0x7f) * 500 + 52000;
|
||||
break;
|
||||
case hwmon_temp_crit_hyst:
|
||||
data->read_htcreg(data->pdev, ®val);
|
||||
*val = (((regval >> 16) & 0x7f)
|
||||
- ((regval >> 24) & 0xf)) * 500 + 52000;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k10temp_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return k10temp_read_temp(dev, attr, channel, val);
|
||||
case hwmon_in:
|
||||
return k10temp_read_in(dev, attr, channel, val);
|
||||
case hwmon_curr:
|
||||
return k10temp_read_curr(dev, attr, channel, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static umode_t k10temp_is_visible(const void *_data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct k10temp_data *data = _data;
|
||||
struct pci_dev *pdev = data->pdev;
|
||||
u32 reg;
|
||||
|
||||
switch (index) {
|
||||
case 0 ... 1: /* temp1_input, temp1_max */
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
switch (channel) {
|
||||
case 0: /* Tdie, or Tctl if we don't show it */
|
||||
break;
|
||||
case 1: /* Tctl */
|
||||
if (!data->show_tdie)
|
||||
return 0;
|
||||
break;
|
||||
case 2 ... 9: /* Tccd{1-8} */
|
||||
if (!(data->show_tccd & BIT(channel - 2)))
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
if (channel || data->show_tdie)
|
||||
return 0;
|
||||
break;
|
||||
case hwmon_temp_crit:
|
||||
case hwmon_temp_crit_hyst:
|
||||
if (channel || !data->read_htcreg)
|
||||
return 0;
|
||||
|
||||
pci_read_config_dword(pdev,
|
||||
REG_NORTHBRIDGE_CAPABILITIES,
|
||||
®);
|
||||
if (!(reg & NB_CAP_HTC))
|
||||
return 0;
|
||||
|
||||
data->read_htcreg(data->pdev, ®);
|
||||
if (!(reg & HTC_ENABLE))
|
||||
return 0;
|
||||
break;
|
||||
case hwmon_temp_label:
|
||||
/* No labels if we don't show the die temperature */
|
||||
if (!data->show_tdie)
|
||||
return 0;
|
||||
switch (channel) {
|
||||
case 0: /* Tdie */
|
||||
case 1: /* Tctl */
|
||||
break;
|
||||
case 2 ... 9: /* Tccd{1-8} */
|
||||
if (!(data->show_tccd & BIT(channel - 2)))
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
case hwmon_curr:
|
||||
if (!data->show_current)
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 2 ... 3: /* temp1_crit, temp1_crit_hyst */
|
||||
if (!data->read_htcreg)
|
||||
return 0;
|
||||
|
||||
pci_read_config_dword(pdev, REG_NORTHBRIDGE_CAPABILITIES,
|
||||
®);
|
||||
if (!(reg & NB_CAP_HTC))
|
||||
return 0;
|
||||
|
||||
data->read_htcreg(data->pdev, ®);
|
||||
if (!(reg & HTC_ENABLE))
|
||||
return 0;
|
||||
break;
|
||||
case 4 ... 6: /* temp1_label, temp2_input, temp2_label */
|
||||
if (!data->show_tdie)
|
||||
return 0;
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
return attr->mode;
|
||||
return 0444;
|
||||
}
|
||||
|
||||
static struct attribute *k10temp_attrs[] = {
|
||||
&dev_attr_temp1_input.attr,
|
||||
&dev_attr_temp1_max.attr,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
&dev_attr_temp2_input.attr,
|
||||
&sensor_dev_attr_temp2_label.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group k10temp_group = {
|
||||
.attrs = k10temp_attrs,
|
||||
.is_visible = k10temp_is_visible,
|
||||
};
|
||||
__ATTRIBUTE_GROUPS(k10temp);
|
||||
|
||||
static bool has_erratum_319(struct pci_dev *pdev)
|
||||
{
|
||||
u32 pkg_type, reg_dram_cfg;
|
||||
|
@ -281,8 +432,125 @@ static bool has_erratum_319(struct pci_dev *pdev)
|
|||
(boot_cpu_data.x86_model == 4 && boot_cpu_data.x86_stepping <= 2);
|
||||
}
|
||||
|
||||
static int k10temp_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
static void k10temp_smn_regs_show(struct seq_file *s, struct pci_dev *pdev,
|
||||
u32 addr, int count)
|
||||
{
|
||||
u32 reg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!(i & 3))
|
||||
seq_printf(s, "0x%06x: ", addr + i * 4);
|
||||
amd_smn_read(amd_pci_dev_to_node_id(pdev), addr + i * 4, ®);
|
||||
seq_printf(s, "%08x ", reg);
|
||||
if ((i & 3) == 3)
|
||||
seq_puts(s, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int svi_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct k10temp_data *data = s->private;
|
||||
|
||||
k10temp_smn_regs_show(s, data->pdev, F17H_M01H_SVI, 32);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(svi);
|
||||
|
||||
static int thm_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct k10temp_data *data = s->private;
|
||||
|
||||
k10temp_smn_regs_show(s, data->pdev,
|
||||
F17H_M01H_REPORTED_TEMP_CTRL_OFFSET, 256);
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(thm);
|
||||
|
||||
static void k10temp_debugfs_cleanup(void *ddir)
|
||||
{
|
||||
debugfs_remove_recursive(ddir);
|
||||
}
|
||||
|
||||
static void k10temp_init_debugfs(struct k10temp_data *data)
|
||||
{
|
||||
struct dentry *debugfs;
|
||||
char name[32];
|
||||
|
||||
/* Only show debugfs data for Family 17h/18h CPUs */
|
||||
if (!data->show_tdie)
|
||||
return;
|
||||
|
||||
scnprintf(name, sizeof(name), "k10temp-%s", pci_name(data->pdev));
|
||||
|
||||
debugfs = debugfs_create_dir(name, NULL);
|
||||
if (debugfs) {
|
||||
debugfs_create_file("svi", 0444, debugfs, data, &svi_fops);
|
||||
debugfs_create_file("thm", 0444, debugfs, data, &thm_fops);
|
||||
devm_add_action_or_reset(&data->pdev->dev,
|
||||
k10temp_debugfs_cleanup, debugfs);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void k10temp_init_debugfs(struct k10temp_data *data)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static const struct hwmon_channel_info *k10temp_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_CRIT | HWMON_T_CRIT_HYST |
|
||||
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_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops k10temp_hwmon_ops = {
|
||||
.is_visible = k10temp_is_visible,
|
||||
.read = k10temp_read,
|
||||
.read_string = k10temp_read_labels,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info k10temp_chip_info = {
|
||||
.ops = &k10temp_hwmon_ops,
|
||||
.info = k10temp_info,
|
||||
};
|
||||
|
||||
static void k10temp_get_ccd_support(struct pci_dev *pdev,
|
||||
struct k10temp_data *data, int limit)
|
||||
{
|
||||
u32 regval;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < limit; i++) {
|
||||
amd_smn_read(amd_pci_dev_to_node_id(pdev),
|
||||
F17H_M70H_CCD_TEMP(i), ®val);
|
||||
if (regval & F17H_M70H_CCD_TEMP_VALID)
|
||||
data->show_tccd |= BIT(i);
|
||||
}
|
||||
}
|
||||
|
||||
static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
{
|
||||
int unreliable = has_erratum_319(pdev);
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -312,9 +580,32 @@ static int k10temp_probe(struct pci_dev *pdev,
|
|||
data->read_htcreg = read_htcreg_nb_f15;
|
||||
data->read_tempreg = read_tempreg_nb_f15;
|
||||
} else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
|
||||
data->temp_adjust_mask = 0x80000;
|
||||
data->temp_adjust_mask = CUR_TEMP_RANGE_SEL_MASK;
|
||||
data->read_tempreg = read_tempreg_nb_f17;
|
||||
data->show_tdie = true;
|
||||
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x1: /* Zen */
|
||||
case 0x8: /* Zen+ */
|
||||
case 0x11: /* Zen APU */
|
||||
case 0x18: /* Zen+ APU */
|
||||
data->show_current = !is_threadripper() && !is_epyc();
|
||||
data->svi_addr[0] = F17H_M01H_SVI_TEL_PLANE0;
|
||||
data->svi_addr[1] = F17H_M01H_SVI_TEL_PLANE1;
|
||||
data->cfactor[0] = CFACTOR_ICORE;
|
||||
data->cfactor[1] = CFACTOR_ISOC;
|
||||
k10temp_get_ccd_support(pdev, data, 4);
|
||||
break;
|
||||
case 0x31: /* Zen2 Threadripper */
|
||||
case 0x71: /* Zen2 */
|
||||
data->show_current = !is_threadripper() && !is_epyc();
|
||||
data->cfactor[0] = CFACTOR_ICORE;
|
||||
data->cfactor[1] = CFACTOR_ISOC;
|
||||
data->svi_addr[0] = F17H_M01H_SVI_TEL_PLANE1;
|
||||
data->svi_addr[1] = F17H_M01H_SVI_TEL_PLANE0;
|
||||
k10temp_get_ccd_support(pdev, data, 8);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
data->read_htcreg = read_htcreg_pci;
|
||||
data->read_tempreg = read_tempreg_pci;
|
||||
|
@ -330,9 +621,15 @@ static int k10temp_probe(struct pci_dev *pdev,
|
|||
}
|
||||
}
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, "k10temp", data,
|
||||
k10temp_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, "k10temp", data,
|
||||
&k10temp_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
k10temp_init_debugfs(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_device_id k10temp_id_table[] = {
|
||||
|
|
440
drivers/hwmon/max31730.c
Normal file
440
drivers/hwmon/max31730.c
Normal file
|
@ -0,0 +1,440 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Driver for MAX31730 3-Channel Remote Temperature Sensor
|
||||
*
|
||||
* Copyright (c) 2019 Guenter Roeck <linux@roeck-us.net>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Addresses scanned */
|
||||
static const unsigned short normal_i2c[] = { 0x1c, 0x1d, 0x1e, 0x1f, 0x4c,
|
||||
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||
|
||||
/* The MAX31730 registers */
|
||||
#define MAX31730_REG_TEMP 0x00
|
||||
#define MAX31730_REG_CONF 0x13
|
||||
#define MAX31730_STOP BIT(7)
|
||||
#define MAX31730_EXTRANGE BIT(1)
|
||||
#define MAX31730_REG_TEMP_OFFSET 0x16
|
||||
#define MAX31730_TEMP_OFFSET_BASELINE 0x77
|
||||
#define MAX31730_REG_OFFSET_ENABLE 0x17
|
||||
#define MAX31730_REG_TEMP_MAX 0x20
|
||||
#define MAX31730_REG_TEMP_MIN 0x30
|
||||
#define MAX31730_REG_STATUS_HIGH 0x32
|
||||
#define MAX31730_REG_STATUS_LOW 0x33
|
||||
#define MAX31730_REG_CHANNEL_ENABLE 0x35
|
||||
#define MAX31730_REG_TEMP_FAULT 0x36
|
||||
|
||||
#define MAX31730_REG_MFG_ID 0x50
|
||||
#define MAX31730_MFG_ID 0x4d
|
||||
#define MAX31730_REG_MFG_REV 0x51
|
||||
#define MAX31730_MFG_REV 0x01
|
||||
|
||||
#define MAX31730_TEMP_MIN (-128000)
|
||||
#define MAX31730_TEMP_MAX 127937
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct max31730_data {
|
||||
struct i2c_client *client;
|
||||
u8 orig_conf;
|
||||
u8 current_conf;
|
||||
u8 offset_enable;
|
||||
u8 channel_enable;
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static inline long max31730_reg_to_mc(s16 temp)
|
||||
{
|
||||
return DIV_ROUND_CLOSEST((temp >> 4) * 1000, 16);
|
||||
}
|
||||
|
||||
static int max31730_write_config(struct max31730_data *data, u8 set_mask,
|
||||
u8 clr_mask)
|
||||
{
|
||||
u8 value;
|
||||
|
||||
clr_mask |= MAX31730_EXTRANGE;
|
||||
value = data->current_conf & ~clr_mask;
|
||||
value |= set_mask;
|
||||
|
||||
if (data->current_conf != value) {
|
||||
s32 err;
|
||||
|
||||
err = i2c_smbus_write_byte_data(data->client, MAX31730_REG_CONF,
|
||||
value);
|
||||
if (err)
|
||||
return err;
|
||||
data->current_conf = value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max31730_set_enable(struct i2c_client *client, int reg,
|
||||
u8 *confdata, int channel, bool enable)
|
||||
{
|
||||
u8 regval = *confdata;
|
||||
int err;
|
||||
|
||||
if (enable)
|
||||
regval |= BIT(channel);
|
||||
else
|
||||
regval &= ~BIT(channel);
|
||||
|
||||
if (regval != *confdata) {
|
||||
err = i2c_smbus_write_byte_data(client, reg, regval);
|
||||
if (err)
|
||||
return err;
|
||||
*confdata = regval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max31730_set_offset_enable(struct max31730_data *data, int channel,
|
||||
bool enable)
|
||||
{
|
||||
return max31730_set_enable(data->client, MAX31730_REG_OFFSET_ENABLE,
|
||||
&data->offset_enable, channel, enable);
|
||||
}
|
||||
|
||||
static int max31730_set_channel_enable(struct max31730_data *data, int channel,
|
||||
bool enable)
|
||||
{
|
||||
return max31730_set_enable(data->client, MAX31730_REG_CHANNEL_ENABLE,
|
||||
&data->channel_enable, channel, enable);
|
||||
}
|
||||
|
||||
static int max31730_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct max31730_data *data = dev_get_drvdata(dev);
|
||||
int regval, reg, offset;
|
||||
|
||||
if (type != hwmon_temp)
|
||||
return -EINVAL;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
if (!(data->channel_enable & BIT(channel)))
|
||||
return -ENODATA;
|
||||
reg = MAX31730_REG_TEMP + (channel * 2);
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
reg = MAX31730_REG_TEMP_MAX + (channel * 2);
|
||||
break;
|
||||
case hwmon_temp_min:
|
||||
reg = MAX31730_REG_TEMP_MIN;
|
||||
break;
|
||||
case hwmon_temp_enable:
|
||||
*val = !!(data->channel_enable & BIT(channel));
|
||||
return 0;
|
||||
case hwmon_temp_offset:
|
||||
if (!channel)
|
||||
return -EINVAL;
|
||||
if (!(data->offset_enable & BIT(channel))) {
|
||||
*val = 0;
|
||||
return 0;
|
||||
}
|
||||
offset = i2c_smbus_read_byte_data(data->client,
|
||||
MAX31730_REG_TEMP_OFFSET);
|
||||
if (offset < 0)
|
||||
return offset;
|
||||
*val = (offset - MAX31730_TEMP_OFFSET_BASELINE) * 125;
|
||||
return 0;
|
||||
case hwmon_temp_fault:
|
||||
regval = i2c_smbus_read_byte_data(data->client,
|
||||
MAX31730_REG_TEMP_FAULT);
|
||||
if (regval < 0)
|
||||
return regval;
|
||||
*val = !!(regval & BIT(channel));
|
||||
return 0;
|
||||
case hwmon_temp_min_alarm:
|
||||
regval = i2c_smbus_read_byte_data(data->client,
|
||||
MAX31730_REG_STATUS_LOW);
|
||||
if (regval < 0)
|
||||
return regval;
|
||||
*val = !!(regval & BIT(channel));
|
||||
return 0;
|
||||
case hwmon_temp_max_alarm:
|
||||
regval = i2c_smbus_read_byte_data(data->client,
|
||||
MAX31730_REG_STATUS_HIGH);
|
||||
if (regval < 0)
|
||||
return regval;
|
||||
*val = !!(regval & BIT(channel));
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
regval = i2c_smbus_read_word_swapped(data->client, reg);
|
||||
if (regval < 0)
|
||||
return regval;
|
||||
|
||||
*val = max31730_reg_to_mc(regval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max31730_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct max31730_data *data = dev_get_drvdata(dev);
|
||||
int reg, err;
|
||||
|
||||
if (type != hwmon_temp)
|
||||
return -EINVAL;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_max:
|
||||
reg = MAX31730_REG_TEMP_MAX + channel * 2;
|
||||
break;
|
||||
case hwmon_temp_min:
|
||||
reg = MAX31730_REG_TEMP_MIN;
|
||||
break;
|
||||
case hwmon_temp_enable:
|
||||
if (val != 0 && val != 1)
|
||||
return -EINVAL;
|
||||
return max31730_set_channel_enable(data, channel, val);
|
||||
case hwmon_temp_offset:
|
||||
val = clamp_val(val, -14875, 17000) + 14875;
|
||||
val = DIV_ROUND_CLOSEST(val, 125);
|
||||
err = max31730_set_offset_enable(data, channel,
|
||||
val != MAX31730_TEMP_OFFSET_BASELINE);
|
||||
if (err)
|
||||
return err;
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
MAX31730_REG_TEMP_OFFSET, val);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = clamp_val(val, MAX31730_TEMP_MIN, MAX31730_TEMP_MAX);
|
||||
val = DIV_ROUND_CLOSEST(val << 4, 1000) << 4;
|
||||
|
||||
return i2c_smbus_write_word_swapped(data->client, reg, (u16)val);
|
||||
}
|
||||
|
||||
static umode_t max31730_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_min_alarm:
|
||||
case hwmon_temp_max_alarm:
|
||||
case hwmon_temp_fault:
|
||||
return 0444;
|
||||
case hwmon_temp_min:
|
||||
return channel ? 0444 : 0644;
|
||||
case hwmon_temp_offset:
|
||||
case hwmon_temp_enable:
|
||||
case hwmon_temp_max:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *max31730_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_ENABLE |
|
||||
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_OFFSET | HWMON_T_ENABLE |
|
||||
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
|
||||
HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_OFFSET | HWMON_T_ENABLE |
|
||||
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
|
||||
HWMON_T_FAULT,
|
||||
HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
|
||||
HWMON_T_OFFSET | HWMON_T_ENABLE |
|
||||
HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM |
|
||||
HWMON_T_FAULT
|
||||
),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops max31730_hwmon_ops = {
|
||||
.is_visible = max31730_is_visible,
|
||||
.read = max31730_read,
|
||||
.write = max31730_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info max31730_chip_info = {
|
||||
.ops = &max31730_hwmon_ops,
|
||||
.info = max31730_info,
|
||||
};
|
||||
|
||||
static void max31730_remove(void *data)
|
||||
{
|
||||
struct max31730_data *max31730 = data;
|
||||
struct i2c_client *client = max31730->client;
|
||||
|
||||
i2c_smbus_write_byte_data(client, MAX31730_REG_CONF,
|
||||
max31730->orig_conf);
|
||||
}
|
||||
|
||||
static int
|
||||
max31730_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct max31730_data *data;
|
||||
int status, err;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -EIO;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct max31730_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
|
||||
/* Cache original configuration and enable status */
|
||||
status = i2c_smbus_read_byte_data(client, MAX31730_REG_CHANNEL_ENABLE);
|
||||
if (status < 0)
|
||||
return status;
|
||||
data->channel_enable = status;
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, MAX31730_REG_OFFSET_ENABLE);
|
||||
if (status < 0)
|
||||
return status;
|
||||
data->offset_enable = status;
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, MAX31730_REG_CONF);
|
||||
if (status < 0)
|
||||
return status;
|
||||
data->orig_conf = status;
|
||||
data->current_conf = status;
|
||||
|
||||
err = max31730_write_config(data,
|
||||
data->channel_enable ? 0 : MAX31730_STOP,
|
||||
data->channel_enable ? MAX31730_STOP : 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dev_set_drvdata(dev, data);
|
||||
|
||||
err = devm_add_action_or_reset(dev, max31730_remove, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
data,
|
||||
&max31730_chip_info,
|
||||
NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max31730_ids[] = {
|
||||
{ "max31730", 0, },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max31730_ids);
|
||||
|
||||
static const struct of_device_id __maybe_unused max31730_of_match[] = {
|
||||
{
|
||||
.compatible = "maxim,max31730",
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max31730_of_match);
|
||||
|
||||
static bool max31730_check_reg_temp(struct i2c_client *client,
|
||||
int reg)
|
||||
{
|
||||
int regval;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, reg + 1);
|
||||
return regval < 0 || (regval & 0x0f);
|
||||
}
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int max31730_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int regval;
|
||||
int i;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, MAX31730_REG_MFG_ID);
|
||||
if (regval != MAX31730_MFG_ID)
|
||||
return -ENODEV;
|
||||
regval = i2c_smbus_read_byte_data(client, MAX31730_REG_MFG_REV);
|
||||
if (regval != MAX31730_MFG_REV)
|
||||
return -ENODEV;
|
||||
|
||||
/* lower 4 bit of temperature and limit registers must be 0 */
|
||||
if (max31730_check_reg_temp(client, MAX31730_REG_TEMP_MIN))
|
||||
return -ENODEV;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (max31730_check_reg_temp(client, MAX31730_REG_TEMP + i * 2))
|
||||
return -ENODEV;
|
||||
if (max31730_check_reg_temp(client,
|
||||
MAX31730_REG_TEMP_MAX + i * 2))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
strlcpy(info->type, "max31730", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused 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)
|
||||
{
|
||||
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 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,
|
||||
},
|
||||
.probe = max31730_probe,
|
||||
.id_table = max31730_ids,
|
||||
.detect = max31730_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
module_i2c_driver(max31730_driver);
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
||||
MODULE_DESCRIPTION("MAX31730 driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -20,8 +20,8 @@ config SENSORS_PMBUS
|
|||
help
|
||||
If you say yes here you get hardware monitoring support for generic
|
||||
PMBus devices, including but not limited to ADP4000, BMR453, BMR454,
|
||||
MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, TPS40400, TPS544B20,
|
||||
TPS544B25, TPS544C20, TPS544C25, and UDT020.
|
||||
MAX20796, MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, TPS40400,
|
||||
TPS544B20, TPS544B25, TPS544C20, TPS544C25, and UDT020.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called pmbus.
|
||||
|
@ -145,6 +145,15 @@ config SENSORS_MAX16064
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called max16064.
|
||||
|
||||
config SENSORS_MAX20730
|
||||
tristate "Maxim MAX20730, MAX20734, MAX20743"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Maxim
|
||||
MAX20730, MAX20734, and MAX20743.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called max20730.
|
||||
|
||||
config SENSORS_MAX20751
|
||||
tristate "Maxim MAX20751"
|
||||
help
|
||||
|
@ -200,20 +209,20 @@ config SENSORS_TPS40422
|
|||
be called tps40422.
|
||||
|
||||
config SENSORS_TPS53679
|
||||
tristate "TI TPS53679"
|
||||
tristate "TI TPS53679, TPS53688"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for TI
|
||||
TPS53679.
|
||||
TPS53679, TPS53688
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called tps53679.
|
||||
|
||||
config SENSORS_UCD9000
|
||||
tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910"
|
||||
tristate "TI UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for TI
|
||||
UCD90120, UCD90124, UCD90160, UCD9090, UCD90910, Sequencer and System
|
||||
Health Controllers.
|
||||
UCD90120, UCD90124, UCD90160, UCD90320, UCD9090, UCD90910, Sequencer
|
||||
and System Health Controllers.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9000.
|
||||
|
@ -228,6 +237,15 @@ config SENSORS_UCD9200
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9200.
|
||||
|
||||
config SENSORS_XDPE122
|
||||
tristate "Infineon XDPE122 family"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Infineon
|
||||
XDPE12254, XDPE12284, device.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called xdpe12284.
|
||||
|
||||
config SENSORS_ZL6100
|
||||
tristate "Intersil ZL6100 and compatibles"
|
||||
help
|
||||
|
|
|
@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
|||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
||||
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
|
||||
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
|
||||
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
|
||||
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
|
||||
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
||||
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
|
||||
|
@ -26,4 +27,5 @@ obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
|
|||
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
|
||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
|
||||
obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o
|
||||
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
|
||||
|
|
|
@ -20,12 +20,15 @@
|
|||
|
||||
#define CFFPS_FRU_CMD 0x9A
|
||||
#define CFFPS_PN_CMD 0x9B
|
||||
#define CFFPS_HEADER_CMD 0x9C
|
||||
#define CFFPS_SN_CMD 0x9E
|
||||
#define CFFPS_MAX_POWER_OUT_CMD 0xA7
|
||||
#define CFFPS_CCIN_CMD 0xBD
|
||||
#define CFFPS_FW_CMD 0xFA
|
||||
#define CFFPS1_FW_NUM_BYTES 4
|
||||
#define CFFPS2_FW_NUM_WORDS 3
|
||||
#define CFFPS_SYS_CONFIG_CMD 0xDA
|
||||
#define CFFPS_12VCS_VOUT_CMD 0xDE
|
||||
|
||||
#define CFFPS_INPUT_HISTORY_CMD 0xD6
|
||||
#define CFFPS_INPUT_HISTORY_SIZE 100
|
||||
|
@ -44,22 +47,21 @@
|
|||
#define CFFPS_MFR_VAUX_FAULT BIT(6)
|
||||
#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7)
|
||||
|
||||
/*
|
||||
* LED off state actually relinquishes LED control to PSU firmware, so it can
|
||||
* turn on the LED for faults.
|
||||
*/
|
||||
#define CFFPS_LED_OFF 0
|
||||
#define CFFPS_LED_BLINK BIT(0)
|
||||
#define CFFPS_LED_ON BIT(1)
|
||||
#define CFFPS_LED_OFF BIT(2)
|
||||
#define CFFPS_BLINK_RATE_MS 250
|
||||
|
||||
enum {
|
||||
CFFPS_DEBUGFS_INPUT_HISTORY = 0,
|
||||
CFFPS_DEBUGFS_FRU,
|
||||
CFFPS_DEBUGFS_PN,
|
||||
CFFPS_DEBUGFS_HEADER,
|
||||
CFFPS_DEBUGFS_SN,
|
||||
CFFPS_DEBUGFS_MAX_POWER_OUT,
|
||||
CFFPS_DEBUGFS_CCIN,
|
||||
CFFPS_DEBUGFS_FW,
|
||||
CFFPS_DEBUGFS_ON_OFF_CONFIG,
|
||||
CFFPS_DEBUGFS_NUM_ENTRIES
|
||||
};
|
||||
|
||||
|
@ -136,15 +138,15 @@ static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu,
|
|||
psu->input_history.byte_count);
|
||||
}
|
||||
|
||||
static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
static ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
u8 cmd;
|
||||
int i, rc;
|
||||
int *idxp = file->private_data;
|
||||
int idx = *idxp;
|
||||
struct ibm_cffps *psu = to_psu(idxp, idx);
|
||||
char data[I2C_SMBUS_BLOCK_MAX] = { 0 };
|
||||
char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
|
||||
|
||||
pmbus_set_page(psu->client, 0);
|
||||
|
||||
|
@ -157,9 +159,20 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf,
|
|||
case CFFPS_DEBUGFS_PN:
|
||||
cmd = CFFPS_PN_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_HEADER:
|
||||
cmd = CFFPS_HEADER_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_SN:
|
||||
cmd = CFFPS_SN_CMD;
|
||||
break;
|
||||
case CFFPS_DEBUGFS_MAX_POWER_OUT:
|
||||
rc = i2c_smbus_read_word_swapped(psu->client,
|
||||
CFFPS_MAX_POWER_OUT_CMD);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = snprintf(data, I2C_SMBUS_BLOCK_MAX, "%d", rc);
|
||||
goto done;
|
||||
case CFFPS_DEBUGFS_CCIN:
|
||||
rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD);
|
||||
if (rc < 0)
|
||||
|
@ -199,6 +212,14 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf,
|
|||
return -EOPNOTSUPP;
|
||||
}
|
||||
goto done;
|
||||
case CFFPS_DEBUGFS_ON_OFF_CONFIG:
|
||||
rc = i2c_smbus_read_byte_data(psu->client,
|
||||
PMBUS_ON_OFF_CONFIG);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = snprintf(data, 3, "%02x", rc);
|
||||
goto done;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -214,9 +235,42 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf,
|
|||
return simple_read_from_buffer(buf, count, ppos, data, rc);
|
||||
}
|
||||
|
||||
static ssize_t ibm_cffps_debugfs_write(struct file *file,
|
||||
const char __user *buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
u8 data;
|
||||
ssize_t rc;
|
||||
int *idxp = file->private_data;
|
||||
int idx = *idxp;
|
||||
struct ibm_cffps *psu = to_psu(idxp, idx);
|
||||
|
||||
switch (idx) {
|
||||
case CFFPS_DEBUGFS_ON_OFF_CONFIG:
|
||||
pmbus_set_page(psu->client, 0);
|
||||
|
||||
rc = simple_write_to_buffer(&data, 1, ppos, buf, count);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
rc = i2c_smbus_write_byte_data(psu->client,
|
||||
PMBUS_ON_OFF_CONFIG, data);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = 1;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct file_operations ibm_cffps_fops = {
|
||||
.llseek = noop_llseek,
|
||||
.read = ibm_cffps_debugfs_op,
|
||||
.read = ibm_cffps_debugfs_read,
|
||||
.write = ibm_cffps_debugfs_write,
|
||||
.open = simple_open,
|
||||
};
|
||||
|
||||
|
@ -293,6 +347,9 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page,
|
|||
if (mfr & CFFPS_MFR_PS_KILL)
|
||||
rc |= PB_STATUS_OFF;
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VMON:
|
||||
rc = pmbus_read_word_data(client, page, CFFPS_12VCS_VOUT_CMD);
|
||||
break;
|
||||
default:
|
||||
rc = -ENODATA;
|
||||
break;
|
||||
|
@ -375,6 +432,9 @@ static void ibm_cffps_create_led_class(struct ibm_cffps *psu)
|
|||
rc = devm_led_classdev_register(dev, &psu->led);
|
||||
if (rc)
|
||||
dev_warn(dev, "failed to register led class: %d\n", rc);
|
||||
else
|
||||
i2c_smbus_write_byte_data(client, CFFPS_SYS_CONFIG_CMD,
|
||||
CFFPS_LED_OFF);
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info ibm_cffps_info[] = {
|
||||
|
@ -396,7 +456,7 @@ static struct pmbus_driver_info ibm_cffps_info[] = {
|
|||
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
|
||||
PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_STATUS_FAN12,
|
||||
PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_VMON,
|
||||
.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
|
||||
PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
|
@ -486,15 +546,24 @@ static int ibm_cffps_probe(struct i2c_client *client,
|
|||
debugfs_create_file("part_number", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_PN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("header", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_HEADER],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("serial_number", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_SN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("max_power_out", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_MAX_POWER_OUT],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("ccin", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_CCIN],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("fw_version", 0444, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_FW],
|
||||
&ibm_cffps_fops);
|
||||
debugfs_create_file("on_off_config", 0644, ibm_cffps_dir,
|
||||
&psu->debugfs_entries[CFFPS_DEBUGFS_ON_OFF_CONFIG],
|
||||
&ibm_cffps_fops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
372
drivers/hwmon/pmbus/max20730.c
Normal file
372
drivers/hwmon/pmbus/max20730.c
Normal file
|
@ -0,0 +1,372 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Driver for MAX20730, MAX20734, and MAX20743 Integrated, Step-Down
|
||||
* Switching Regulators
|
||||
*
|
||||
* Copyright 2019 Google LLC.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pmbus.h>
|
||||
#include <linux/util_macros.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips {
|
||||
max20730,
|
||||
max20734,
|
||||
max20743
|
||||
};
|
||||
|
||||
struct max20730_data {
|
||||
enum chips id;
|
||||
struct pmbus_driver_info info;
|
||||
struct mutex lock; /* Used to protect against parallel writes */
|
||||
u16 mfr_devset1;
|
||||
};
|
||||
|
||||
#define to_max20730_data(x) container_of(x, struct max20730_data, info)
|
||||
|
||||
#define MAX20730_MFR_DEVSET1 0xd2
|
||||
|
||||
/*
|
||||
* Convert discreet value to direct data format. Strictly speaking, all passed
|
||||
* values are constants, so we could do that calculation manually. On the
|
||||
* downside, that would make the driver more difficult to maintain, so lets
|
||||
* use this approach.
|
||||
*/
|
||||
static u16 val_to_direct(int v, enum pmbus_sensor_classes class,
|
||||
const struct pmbus_driver_info *info)
|
||||
{
|
||||
int R = info->R[class] - 3; /* take milli-units into account */
|
||||
int b = info->b[class] * 1000;
|
||||
long d;
|
||||
|
||||
d = v * info->m[class] + b;
|
||||
/*
|
||||
* R < 0 is true for all callers, so we don't need to bother
|
||||
* about the R > 0 case.
|
||||
*/
|
||||
while (R < 0) {
|
||||
d = DIV_ROUND_CLOSEST(d, 10);
|
||||
R++;
|
||||
}
|
||||
return (u16)d;
|
||||
}
|
||||
|
||||
static long direct_to_val(u16 w, enum pmbus_sensor_classes class,
|
||||
const struct pmbus_driver_info *info)
|
||||
{
|
||||
int R = info->R[class] - 3;
|
||||
int b = info->b[class] * 1000;
|
||||
int m = info->m[class];
|
||||
long d = (s16)w;
|
||||
|
||||
if (m == 0)
|
||||
return 0;
|
||||
|
||||
while (R < 0) {
|
||||
d *= 10;
|
||||
R++;
|
||||
}
|
||||
d = (d - b) / m;
|
||||
return d;
|
||||
}
|
||||
|
||||
static u32 max_current[][5] = {
|
||||
[max20730] = { 13000, 16600, 20100, 23600 },
|
||||
[max20734] = { 21000, 27000, 32000, 38000 },
|
||||
[max20743] = { 18900, 24100, 29200, 34100 },
|
||||
};
|
||||
|
||||
static int max20730_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct max20730_data *data = to_max20730_data(info);
|
||||
int ret = 0;
|
||||
u32 max_c;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
switch ((data->mfr_devset1 >> 11) & 0x3) {
|
||||
case 0x0:
|
||||
ret = val_to_direct(150000, PSC_TEMPERATURE, info);
|
||||
break;
|
||||
case 0x1:
|
||||
ret = val_to_direct(130000, PSC_TEMPERATURE, info);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
max_c = max_current[data->id][(data->mfr_devset1 >> 5) & 0x3];
|
||||
ret = val_to_direct(max_c, PSC_CURRENT_OUT, info);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max20730_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
struct max20730_data *data;
|
||||
u16 devset1;
|
||||
int ret = 0;
|
||||
int idx;
|
||||
|
||||
info = (struct pmbus_driver_info *)pmbus_get_driver_info(client);
|
||||
data = to_max20730_data(info);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
devset1 = data->mfr_devset1;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_OT_FAULT_LIMIT:
|
||||
devset1 &= ~(BIT(11) | BIT(12));
|
||||
if (direct_to_val(word, PSC_TEMPERATURE, info) < 140000)
|
||||
devset1 |= BIT(11);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
devset1 &= ~(BIT(5) | BIT(6));
|
||||
|
||||
idx = find_closest(direct_to_val(word, PSC_CURRENT_OUT, info),
|
||||
max_current[data->id], 4);
|
||||
devset1 |= (idx << 5);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret && devset1 != data->mfr_devset1) {
|
||||
ret = i2c_smbus_write_word_data(client, MAX20730_MFR_DEVSET1,
|
||||
devset1);
|
||||
if (!ret) {
|
||||
data->mfr_devset1 = devset1;
|
||||
pmbus_clear_cache(client);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pmbus_driver_info max20730_info[] = {
|
||||
[max20730] = {
|
||||
.pages = 1,
|
||||
.read_word_data = max20730_read_word_data,
|
||||
.write_word_data = max20730_write_word_data,
|
||||
|
||||
/* Source : Maxim AN6042 */
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_TEMPERATURE] = 21,
|
||||
.b[PSC_TEMPERATURE] = 5887,
|
||||
.R[PSC_TEMPERATURE] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 3609,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = -2,
|
||||
|
||||
/*
|
||||
* Values in the datasheet are adjusted for temperature and
|
||||
* for the relationship between Vin and Vout.
|
||||
* Unfortunately, the data sheet suggests that Vout measurement
|
||||
* may be scaled with a resistor array. This is indeed the case
|
||||
* at least on the evaulation boards. As a result, any in-driver
|
||||
* adjustments would either be wrong or require elaborate means
|
||||
* to configure the scaling. Instead of doing that, just report
|
||||
* raw values and let userspace handle adjustments.
|
||||
*/
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.m[PSC_CURRENT_OUT] = 153,
|
||||
.b[PSC_CURRENT_OUT] = 4976,
|
||||
.R[PSC_CURRENT_OUT] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
|
||||
.func[0] = PMBUS_HAVE_VIN |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
},
|
||||
[max20734] = {
|
||||
.pages = 1,
|
||||
.read_word_data = max20730_read_word_data,
|
||||
.write_word_data = max20730_write_word_data,
|
||||
|
||||
/* Source : Maxim AN6209 */
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_TEMPERATURE] = 21,
|
||||
.b[PSC_TEMPERATURE] = 5887,
|
||||
.R[PSC_TEMPERATURE] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 3592,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = -2,
|
||||
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.m[PSC_CURRENT_OUT] = 111,
|
||||
.b[PSC_CURRENT_OUT] = 3461,
|
||||
.R[PSC_CURRENT_OUT] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
|
||||
.func[0] = PMBUS_HAVE_VIN |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
},
|
||||
[max20743] = {
|
||||
.pages = 1,
|
||||
.read_word_data = max20730_read_word_data,
|
||||
.write_word_data = max20730_write_word_data,
|
||||
|
||||
/* Source : Maxim AN6042 */
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_TEMPERATURE] = 21,
|
||||
.b[PSC_TEMPERATURE] = 5887,
|
||||
.R[PSC_TEMPERATURE] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 3597,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = -2,
|
||||
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.m[PSC_CURRENT_OUT] = 95,
|
||||
.b[PSC_CURRENT_OUT] = 5014,
|
||||
.R[PSC_CURRENT_OUT] = -1,
|
||||
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
|
||||
.func[0] = PMBUS_HAVE_VIN |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
},
|
||||
};
|
||||
|
||||
static int max20730_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
struct max20730_data *data;
|
||||
enum chips chip_id;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_READ_WORD_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read Manufacturer ID\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != 5 || strncmp(buf, "MAXIM", 5)) {
|
||||
buf[ret] = '\0';
|
||||
dev_err(dev, "Unsupported Manufacturer ID '%s'\n", buf);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* The chips support reading PMBUS_MFR_MODEL. On both MAX20730
|
||||
* and MAX20734, reading it returns M20743. Presumably that is
|
||||
* the reason why the command is not documented. Unfortunately,
|
||||
* that means that there is no reliable means to detect the chip.
|
||||
* However, we can at least detect the chip series. Compare
|
||||
* the returned value against 'M20743' and bail out if there is
|
||||
* a mismatch. If that doesn't work for all chips, we may have
|
||||
* to remove this check.
|
||||
*/
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read Manufacturer Model\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != 6 || strncmp(buf, "M20743", 6)) {
|
||||
buf[ret] = '\0';
|
||||
dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read Manufacturer Revision\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != 1 || buf[0] != 'F') {
|
||||
buf[ret] = '\0';
|
||||
dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (client->dev.of_node)
|
||||
chip_id = (enum chips)of_device_get_match_data(dev);
|
||||
else
|
||||
chip_id = id->driver_data;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
data->id = chip_id;
|
||||
mutex_init(&data->lock);
|
||||
memcpy(&data->info, &max20730_info[chip_id], sizeof(data->info));
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, MAX20730_MFR_DEVSET1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->mfr_devset1 = ret;
|
||||
|
||||
return pmbus_do_probe(client, id, &data->info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max20730_id[] = {
|
||||
{ "max20730", max20730 },
|
||||
{ "max20734", max20734 },
|
||||
{ "max20743", max20743 },
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, max20730_id);
|
||||
|
||||
static const struct of_device_id max20730_of_match[] = {
|
||||
{ .compatible = "maxim,max20730", .data = (void *)max20730 },
|
||||
{ .compatible = "maxim,max20734", .data = (void *)max20734 },
|
||||
{ .compatible = "maxim,max20743", .data = (void *)max20743 },
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, max20730_of_match);
|
||||
|
||||
static struct i2c_driver max20730_driver = {
|
||||
.driver = {
|
||||
.name = "max20730",
|
||||
.of_match_table = max20730_of_match,
|
||||
},
|
||||
.probe = max20730_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max20730_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(max20730_driver);
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
||||
MODULE_DESCRIPTION("PMBus driver for Maxim MAX20730 / MAX20734 / MAX20743");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -16,7 +16,7 @@ static struct pmbus_driver_info max20751_info = {
|
|||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = vid,
|
||||
.vrm_version = vr12,
|
||||
.vrm_version[0] = vr12,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_POWER] = linear,
|
||||
|
|
|
@ -115,7 +115,7 @@ static int pmbus_identify(struct i2c_client *client,
|
|||
}
|
||||
|
||||
if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
|
||||
int vout_mode;
|
||||
int vout_mode, i;
|
||||
|
||||
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
if (vout_mode >= 0 && vout_mode != 0xff) {
|
||||
|
@ -124,7 +124,8 @@ static int pmbus_identify(struct i2c_client *client,
|
|||
break;
|
||||
case 1:
|
||||
info->format[PSC_VOLTAGE_OUT] = vid;
|
||||
info->vrm_version = vr11;
|
||||
for (i = 0; i < info->pages; i++)
|
||||
info->vrm_version[i] = vr11;
|
||||
break;
|
||||
case 2:
|
||||
info->format[PSC_VOLTAGE_OUT] = direct;
|
||||
|
@ -210,6 +211,7 @@ static const struct i2c_device_id pmbus_id[] = {
|
|||
{"dps460", (kernel_ulong_t)&pmbus_info_one_skip},
|
||||
{"dps650ab", (kernel_ulong_t)&pmbus_info_one_skip},
|
||||
{"dps800", (kernel_ulong_t)&pmbus_info_one_skip},
|
||||
{"max20796", (kernel_ulong_t)&pmbus_info_one},
|
||||
{"mdt040", (kernel_ulong_t)&pmbus_info_one},
|
||||
{"ncp4200", (kernel_ulong_t)&pmbus_info_one},
|
||||
{"ncp4208", (kernel_ulong_t)&pmbus_info_one},
|
||||
|
|
|
@ -22,6 +22,8 @@ enum pmbus_regs {
|
|||
PMBUS_CLEAR_FAULTS = 0x03,
|
||||
PMBUS_PHASE = 0x04,
|
||||
|
||||
PMBUS_WRITE_PROTECT = 0x10,
|
||||
|
||||
PMBUS_CAPABILITY = 0x19,
|
||||
PMBUS_QUERY = 0x1A,
|
||||
|
||||
|
@ -225,6 +227,15 @@ enum pmbus_regs {
|
|||
*/
|
||||
#define PB_OPERATION_CONTROL_ON BIT(7)
|
||||
|
||||
/*
|
||||
* WRITE_PROTECT
|
||||
*/
|
||||
#define PB_WP_ALL BIT(7) /* all but WRITE_PROTECT */
|
||||
#define PB_WP_OP BIT(6) /* all but WP, OPERATION, PAGE */
|
||||
#define PB_WP_VOUT BIT(5) /* all but WP, OPERATION, PAGE, VOUT, ON_OFF */
|
||||
|
||||
#define PB_WP_ANY (PB_WP_ALL | PB_WP_OP | PB_WP_VOUT)
|
||||
|
||||
/*
|
||||
* CAPABILITY
|
||||
*/
|
||||
|
@ -377,12 +388,12 @@ enum pmbus_sensor_classes {
|
|||
#define PMBUS_PAGE_VIRTUAL BIT(31)
|
||||
|
||||
enum pmbus_data_format { linear = 0, direct, vid };
|
||||
enum vrm_version { vr11 = 0, vr12, vr13 };
|
||||
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
|
||||
|
||||
struct pmbus_driver_info {
|
||||
int pages; /* Total number of pages */
|
||||
enum pmbus_data_format format[PSC_NUM_CLASSES];
|
||||
enum vrm_version vrm_version;
|
||||
enum vrm_version vrm_version[PMBUS_PAGES]; /* vrm version per page */
|
||||
/*
|
||||
* Support one set of coefficients for each sensor type
|
||||
* Used for chips providing data in direct mode.
|
||||
|
|
|
@ -696,7 +696,7 @@ static long pmbus_reg2data_vid(struct pmbus_data *data,
|
|||
long val = sensor->data;
|
||||
long rv = 0;
|
||||
|
||||
switch (data->info->vrm_version) {
|
||||
switch (data->info->vrm_version[sensor->page]) {
|
||||
case vr11:
|
||||
if (val >= 0x02 && val <= 0xb2)
|
||||
rv = DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100);
|
||||
|
@ -709,6 +709,14 @@ static long pmbus_reg2data_vid(struct pmbus_data *data,
|
|||
if (val >= 0x01)
|
||||
rv = 500 + (val - 1) * 10;
|
||||
break;
|
||||
case imvp9:
|
||||
if (val >= 0x01)
|
||||
rv = 200 + (val - 1) * 10;
|
||||
break;
|
||||
case amd625mv:
|
||||
if (val >= 0x0 && val <= 0xd8)
|
||||
rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100);
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
@ -1088,6 +1096,9 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,
|
|||
snprintf(sensor->name, sizeof(sensor->name), "%s%d",
|
||||
name, seq);
|
||||
|
||||
if (data->flags & PMBUS_WRITE_PROTECTED)
|
||||
readonly = true;
|
||||
|
||||
sensor->page = page;
|
||||
sensor->reg = reg;
|
||||
sensor->class = class;
|
||||
|
@ -2141,6 +2152,15 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
|||
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK))
|
||||
client->flags |= I2C_CLIENT_PEC;
|
||||
|
||||
/*
|
||||
* Check if the chip is write protected. If it is, we can not clear
|
||||
* faults, and we should not try it. Also, in that case, writes into
|
||||
* limit registers need to be disabled.
|
||||
*/
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT);
|
||||
if (ret > 0 && (ret & PB_WP_ANY))
|
||||
data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK;
|
||||
|
||||
if (data->info->pages)
|
||||
pmbus_clear_faults(client);
|
||||
else
|
||||
|
|
|
@ -19,26 +19,30 @@
|
|||
static int pxe1610_identify(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) {
|
||||
u8 vout_mode;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/* Read the register with VOUT scaling value.*/
|
||||
ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
for (i = 0; i < PXE1610_NUM_PAGES; i++) {
|
||||
if (pmbus_check_byte_register(client, i, PMBUS_VOUT_MODE)) {
|
||||
u8 vout_mode;
|
||||
int ret;
|
||||
|
||||
vout_mode = ret & GENMASK(4, 0);
|
||||
/* Read the register with VOUT scaling value.*/
|
||||
ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (vout_mode) {
|
||||
case 1:
|
||||
info->vrm_version = vr12;
|
||||
break;
|
||||
case 2:
|
||||
info->vrm_version = vr13;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
vout_mode = ret & GENMASK(4, 0);
|
||||
|
||||
switch (vout_mode) {
|
||||
case 1:
|
||||
info->vrm_version[i] = vr12;
|
||||
break;
|
||||
case 2:
|
||||
info->vrm_version[i] = vr13;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,27 +24,29 @@ static int tps53679_identify(struct i2c_client *client,
|
|||
struct pmbus_driver_info *info)
|
||||
{
|
||||
u8 vout_params;
|
||||
int ret;
|
||||
int i, ret;
|
||||
|
||||
/* Read the register with VOUT scaling value.*/
|
||||
ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
for (i = 0; i < TPS53679_PAGE_NUM; i++) {
|
||||
/* Read the register with VOUT scaling value.*/
|
||||
ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
vout_params = ret & GENMASK(4, 0);
|
||||
vout_params = ret & GENMASK(4, 0);
|
||||
|
||||
switch (vout_params) {
|
||||
case TPS53679_PROT_VR13_10MV:
|
||||
case TPS53679_PROT_VR12_5_10MV:
|
||||
info->vrm_version = vr13;
|
||||
break;
|
||||
case TPS53679_PROT_VR13_5MV:
|
||||
case TPS53679_PROT_VR12_5MV:
|
||||
case TPS53679_PROT_IMVP8_5MV:
|
||||
info->vrm_version = vr12;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
switch (vout_params) {
|
||||
case TPS53679_PROT_VR13_10MV:
|
||||
case TPS53679_PROT_VR12_5_10MV:
|
||||
info->vrm_version[i] = vr13;
|
||||
break;
|
||||
case TPS53679_PROT_VR13_5MV:
|
||||
case TPS53679_PROT_VR12_5MV:
|
||||
case TPS53679_PROT_IMVP8_5MV:
|
||||
info->vrm_version[i] = vr12;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -83,6 +85,7 @@ static int tps53679_probe(struct i2c_client *client,
|
|||
|
||||
static const struct i2c_device_id tps53679_id[] = {
|
||||
{"tps53679", 0},
|
||||
{"tps53688", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -90,6 +93,7 @@ MODULE_DEVICE_TABLE(i2c, tps53679_id);
|
|||
|
||||
static const struct of_device_id __maybe_unused tps53679_of_match[] = {
|
||||
{.compatible = "ti,tps53679"},
|
||||
{.compatible = "ti,tps53688"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tps53679_of_match);
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
#include <linux/gpio/driver.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 };
|
||||
enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd90320, ucd9090,
|
||||
ucd90910 };
|
||||
|
||||
#define UCD9000_MONITOR_CONFIG 0xd5
|
||||
#define UCD9000_NUM_PAGES 0xd6
|
||||
|
@ -38,7 +39,7 @@ enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 };
|
|||
#define UCD9000_GPIO_OUTPUT 1
|
||||
|
||||
#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07)
|
||||
#define UCD9000_MON_PAGE(x) ((x) & 0x0f)
|
||||
#define UCD9000_MON_PAGE(x) ((x) & 0x1f)
|
||||
|
||||
#define UCD9000_MON_VOLTAGE 1
|
||||
#define UCD9000_MON_TEMPERATURE 2
|
||||
|
@ -50,10 +51,12 @@ enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 };
|
|||
#define UCD9000_GPIO_NAME_LEN 16
|
||||
#define UCD9090_NUM_GPIOS 23
|
||||
#define UCD901XX_NUM_GPIOS 26
|
||||
#define UCD90320_NUM_GPIOS 84
|
||||
#define UCD90910_NUM_GPIOS 26
|
||||
|
||||
#define UCD9000_DEBUGFS_NAME_LEN 24
|
||||
#define UCD9000_GPI_COUNT 8
|
||||
#define UCD90320_GPI_COUNT 32
|
||||
|
||||
struct ucd9000_data {
|
||||
u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX];
|
||||
|
@ -131,6 +134,7 @@ static const struct i2c_device_id ucd9000_id[] = {
|
|||
{"ucd90120", ucd90120},
|
||||
{"ucd90124", ucd90124},
|
||||
{"ucd90160", ucd90160},
|
||||
{"ucd90320", ucd90320},
|
||||
{"ucd9090", ucd9090},
|
||||
{"ucd90910", ucd90910},
|
||||
{}
|
||||
|
@ -154,6 +158,10 @@ static const struct of_device_id __maybe_unused ucd9000_of_match[] = {
|
|||
.compatible = "ti,ucd90160",
|
||||
.data = (void *)ucd90160
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ucd90320",
|
||||
.data = (void *)ucd90320
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ucd9090",
|
||||
.data = (void *)ucd9090
|
||||
|
@ -322,6 +330,9 @@ static void ucd9000_probe_gpio(struct i2c_client *client,
|
|||
case ucd90160:
|
||||
data->gpio.ngpio = UCD901XX_NUM_GPIOS;
|
||||
break;
|
||||
case ucd90320:
|
||||
data->gpio.ngpio = UCD90320_NUM_GPIOS;
|
||||
break;
|
||||
case ucd90910:
|
||||
data->gpio.ngpio = UCD90910_NUM_GPIOS;
|
||||
break;
|
||||
|
@ -372,17 +383,18 @@ static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val)
|
|||
struct ucd9000_debugfs_entry *entry = data;
|
||||
struct i2c_client *client = entry->client;
|
||||
u8 buffer[I2C_SMBUS_BLOCK_MAX];
|
||||
int ret;
|
||||
int ret, i;
|
||||
|
||||
ret = ucd9000_get_mfr_status(client, buffer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Attribute only created for devices with gpi fault bits at bits
|
||||
* 16-23, which is the second byte of the response.
|
||||
* GPI fault bits are in sets of 8, two bytes from end of response.
|
||||
*/
|
||||
*val = !!(buffer[1] & BIT(entry->index));
|
||||
i = ret - 3 - entry->index / 8;
|
||||
if (i >= 0)
|
||||
*val = !!(buffer[i] & BIT(entry->index % 8));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -422,7 +434,7 @@ static int ucd9000_init_debugfs(struct i2c_client *client,
|
|||
{
|
||||
struct dentry *debugfs;
|
||||
struct ucd9000_debugfs_entry *entries;
|
||||
int i;
|
||||
int i, gpi_count;
|
||||
char name[UCD9000_DEBUGFS_NAME_LEN];
|
||||
|
||||
debugfs = pmbus_get_debugfs_dir(client);
|
||||
|
@ -435,18 +447,21 @@ static int ucd9000_init_debugfs(struct i2c_client *client,
|
|||
|
||||
/*
|
||||
* Of the chips this driver supports, only the UCD9090, UCD90160,
|
||||
* and UCD90910 report GPI faults in their MFR_STATUS register, so only
|
||||
* create the GPI fault debugfs attributes for those chips.
|
||||
* UCD90320, and UCD90910 report GPI faults in their MFR_STATUS
|
||||
* register, so only create the GPI fault debugfs attributes for those
|
||||
* chips.
|
||||
*/
|
||||
if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 ||
|
||||
mid->driver_data == ucd90910) {
|
||||
mid->driver_data == ucd90320 || mid->driver_data == ucd90910) {
|
||||
gpi_count = mid->driver_data == ucd90320 ? UCD90320_GPI_COUNT
|
||||
: UCD9000_GPI_COUNT;
|
||||
entries = devm_kcalloc(&client->dev,
|
||||
UCD9000_GPI_COUNT, sizeof(*entries),
|
||||
gpi_count, sizeof(*entries),
|
||||
GFP_KERNEL);
|
||||
if (!entries)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < UCD9000_GPI_COUNT; i++) {
|
||||
for (i = 0; i < gpi_count; i++) {
|
||||
entries[i].client = client;
|
||||
entries[i].index = i;
|
||||
scnprintf(name, UCD9000_DEBUGFS_NAME_LEN,
|
||||
|
|
117
drivers/hwmon/pmbus/xdpe12284.c
Normal file
117
drivers/hwmon/pmbus/xdpe12284.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers
|
||||
*
|
||||
* Copyright (c) 2020 Mellanox Technologies. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */
|
||||
#define XDPE122_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */
|
||||
#define XDPE122_PROT_IMVP9_10MV 0x03 /* IMVP9 mode, 10-mV DAC */
|
||||
#define XDPE122_AMD_625MV 0x10 /* AMD mode 6.25mV */
|
||||
#define XDPE122_PAGE_NUM 2
|
||||
|
||||
static int xdpe122_identify(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
u8 vout_params;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < XDPE122_PAGE_NUM; i++) {
|
||||
/* Read the register with VOUT scaling value.*/
|
||||
ret = pmbus_read_byte_data(client, i, PMBUS_VOUT_MODE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
vout_params = ret & GENMASK(4, 0);
|
||||
|
||||
switch (vout_params) {
|
||||
case XDPE122_PROT_VR12_5_10MV:
|
||||
info->vrm_version[i] = vr13;
|
||||
break;
|
||||
case XDPE122_PROT_VR12_5MV:
|
||||
info->vrm_version[i] = vr12;
|
||||
break;
|
||||
case XDPE122_PROT_IMVP9_10MV:
|
||||
info->vrm_version[i] = imvp9;
|
||||
break;
|
||||
case XDPE122_AMD_625MV:
|
||||
info->vrm_version[i] = amd625mv;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info xdpe122_info = {
|
||||
.pages = XDPE122_PAGE_NUM,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = vid,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_CURRENT_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_POWER] = linear,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
.identify = xdpe122_identify,
|
||||
};
|
||||
|
||||
static int xdpe122_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct pmbus_driver_info *info;
|
||||
|
||||
info = devm_kmemdup(&client->dev, &xdpe122_info, sizeof(*info),
|
||||
GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
return pmbus_do_probe(client, id, info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id xdpe122_id[] = {
|
||||
{"xdpe12254", 0},
|
||||
{"xdpe12284", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, xdpe122_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused xdpe122_of_match[] = {
|
||||
{.compatible = "infineon, xdpe12254"},
|
||||
{.compatible = "infineon, xdpe12284"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xdpe122_of_match);
|
||||
|
||||
static struct i2c_driver xdpe122_driver = {
|
||||
.driver = {
|
||||
.name = "xdpe12284",
|
||||
.of_match_table = of_match_ptr(xdpe122_of_match),
|
||||
},
|
||||
.probe = xdpe122_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = xdpe122_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(xdpe122_driver);
|
||||
|
||||
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
|
||||
MODULE_DESCRIPTION("PMBus driver for Infineon XDPE122 family");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -390,8 +390,7 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pwm_fan_suspend(struct device *dev)
|
||||
static int pwm_fan_disable(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
struct pwm_args args;
|
||||
|
@ -418,6 +417,17 @@ static int pwm_fan_suspend(struct device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void pwm_fan_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
pwm_fan_disable(&pdev->dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pwm_fan_suspend(struct device *dev)
|
||||
{
|
||||
return pwm_fan_disable(dev);
|
||||
}
|
||||
|
||||
static int pwm_fan_resume(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
|
@ -455,6 +465,7 @@ MODULE_DEVICE_TABLE(of, of_pwm_fan_match);
|
|||
|
||||
static struct platform_driver pwm_fan_driver = {
|
||||
.probe = pwm_fan_probe,
|
||||
.shutdown = pwm_fan_shutdown,
|
||||
.driver = {
|
||||
.name = "pwm-fan",
|
||||
.pm = &pwm_fan_pm,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,7 @@ enum hwmon_sensor_types {
|
|||
hwmon_humidity,
|
||||
hwmon_fan,
|
||||
hwmon_pwm,
|
||||
hwmon_intrusion,
|
||||
hwmon_max,
|
||||
};
|
||||
|
||||
|
@ -59,7 +60,8 @@ enum hwmon_chip_attributes {
|
|||
#define HWMON_C_TEMP_SAMPLES BIT(hwmon_chip_temp_samples)
|
||||
|
||||
enum hwmon_temp_attributes {
|
||||
hwmon_temp_input = 0,
|
||||
hwmon_temp_enable,
|
||||
hwmon_temp_input,
|
||||
hwmon_temp_type,
|
||||
hwmon_temp_lcrit,
|
||||
hwmon_temp_lcrit_hyst,
|
||||
|
@ -85,6 +87,7 @@ enum hwmon_temp_attributes {
|
|||
hwmon_temp_reset_history,
|
||||
};
|
||||
|
||||
#define HWMON_T_ENABLE BIT(hwmon_temp_enable)
|
||||
#define HWMON_T_INPUT BIT(hwmon_temp_input)
|
||||
#define HWMON_T_TYPE BIT(hwmon_temp_type)
|
||||
#define HWMON_T_LCRIT BIT(hwmon_temp_lcrit)
|
||||
|
@ -111,6 +114,7 @@ enum hwmon_temp_attributes {
|
|||
#define HWMON_T_RESET_HISTORY BIT(hwmon_temp_reset_history)
|
||||
|
||||
enum hwmon_in_attributes {
|
||||
hwmon_in_enable,
|
||||
hwmon_in_input,
|
||||
hwmon_in_min,
|
||||
hwmon_in_max,
|
||||
|
@ -126,9 +130,9 @@ enum hwmon_in_attributes {
|
|||
hwmon_in_max_alarm,
|
||||
hwmon_in_lcrit_alarm,
|
||||
hwmon_in_crit_alarm,
|
||||
hwmon_in_enable,
|
||||
};
|
||||
|
||||
#define HWMON_I_ENABLE BIT(hwmon_in_enable)
|
||||
#define HWMON_I_INPUT BIT(hwmon_in_input)
|
||||
#define HWMON_I_MIN BIT(hwmon_in_min)
|
||||
#define HWMON_I_MAX BIT(hwmon_in_max)
|
||||
|
@ -144,9 +148,9 @@ enum hwmon_in_attributes {
|
|||
#define HWMON_I_MAX_ALARM BIT(hwmon_in_max_alarm)
|
||||
#define HWMON_I_LCRIT_ALARM BIT(hwmon_in_lcrit_alarm)
|
||||
#define HWMON_I_CRIT_ALARM BIT(hwmon_in_crit_alarm)
|
||||
#define HWMON_I_ENABLE BIT(hwmon_in_enable)
|
||||
|
||||
enum hwmon_curr_attributes {
|
||||
hwmon_curr_enable,
|
||||
hwmon_curr_input,
|
||||
hwmon_curr_min,
|
||||
hwmon_curr_max,
|
||||
|
@ -164,6 +168,7 @@ enum hwmon_curr_attributes {
|
|||
hwmon_curr_crit_alarm,
|
||||
};
|
||||
|
||||
#define HWMON_C_ENABLE BIT(hwmon_curr_enable)
|
||||
#define HWMON_C_INPUT BIT(hwmon_curr_input)
|
||||
#define HWMON_C_MIN BIT(hwmon_curr_min)
|
||||
#define HWMON_C_MAX BIT(hwmon_curr_max)
|
||||
|
@ -181,6 +186,7 @@ enum hwmon_curr_attributes {
|
|||
#define HWMON_C_CRIT_ALARM BIT(hwmon_curr_crit_alarm)
|
||||
|
||||
enum hwmon_power_attributes {
|
||||
hwmon_power_enable,
|
||||
hwmon_power_average,
|
||||
hwmon_power_average_interval,
|
||||
hwmon_power_average_interval_max,
|
||||
|
@ -211,6 +217,7 @@ enum hwmon_power_attributes {
|
|||
hwmon_power_crit_alarm,
|
||||
};
|
||||
|
||||
#define HWMON_P_ENABLE BIT(hwmon_power_enable)
|
||||
#define HWMON_P_AVERAGE BIT(hwmon_power_average)
|
||||
#define HWMON_P_AVERAGE_INTERVAL BIT(hwmon_power_average_interval)
|
||||
#define HWMON_P_AVERAGE_INTERVAL_MAX BIT(hwmon_power_average_interval_max)
|
||||
|
@ -241,14 +248,17 @@ enum hwmon_power_attributes {
|
|||
#define HWMON_P_CRIT_ALARM BIT(hwmon_power_crit_alarm)
|
||||
|
||||
enum hwmon_energy_attributes {
|
||||
hwmon_energy_enable,
|
||||
hwmon_energy_input,
|
||||
hwmon_energy_label,
|
||||
};
|
||||
|
||||
#define HWMON_E_ENABLE BIT(hwmon_energy_enable)
|
||||
#define HWMON_E_INPUT BIT(hwmon_energy_input)
|
||||
#define HWMON_E_LABEL BIT(hwmon_energy_label)
|
||||
|
||||
enum hwmon_humidity_attributes {
|
||||
hwmon_humidity_enable,
|
||||
hwmon_humidity_input,
|
||||
hwmon_humidity_label,
|
||||
hwmon_humidity_min,
|
||||
|
@ -259,6 +269,7 @@ enum hwmon_humidity_attributes {
|
|||
hwmon_humidity_fault,
|
||||
};
|
||||
|
||||
#define HWMON_H_ENABLE BIT(hwmon_humidity_enable)
|
||||
#define HWMON_H_INPUT BIT(hwmon_humidity_input)
|
||||
#define HWMON_H_LABEL BIT(hwmon_humidity_label)
|
||||
#define HWMON_H_MIN BIT(hwmon_humidity_min)
|
||||
|
@ -269,6 +280,7 @@ enum hwmon_humidity_attributes {
|
|||
#define HWMON_H_FAULT BIT(hwmon_humidity_fault)
|
||||
|
||||
enum hwmon_fan_attributes {
|
||||
hwmon_fan_enable,
|
||||
hwmon_fan_input,
|
||||
hwmon_fan_label,
|
||||
hwmon_fan_min,
|
||||
|
@ -282,6 +294,7 @@ enum hwmon_fan_attributes {
|
|||
hwmon_fan_fault,
|
||||
};
|
||||
|
||||
#define HWMON_F_ENABLE BIT(hwmon_fan_enable)
|
||||
#define HWMON_F_INPUT BIT(hwmon_fan_input)
|
||||
#define HWMON_F_LABEL BIT(hwmon_fan_label)
|
||||
#define HWMON_F_MIN BIT(hwmon_fan_min)
|
||||
|
@ -306,6 +319,13 @@ enum hwmon_pwm_attributes {
|
|||
#define HWMON_PWM_MODE BIT(hwmon_pwm_mode)
|
||||
#define HWMON_PWM_FREQ BIT(hwmon_pwm_freq)
|
||||
|
||||
enum hwmon_intrusion_attributes {
|
||||
hwmon_intrusion_alarm,
|
||||
hwmon_intrusion_beep,
|
||||
};
|
||||
#define HWMON_INTRUSION_ALARM BIT(hwmon_intrusion_alarm)
|
||||
#define HWMON_INTRUSION_BEEP BIT(hwmon_intrusion_beep)
|
||||
|
||||
/**
|
||||
* struct hwmon_ops - hwmon device operations
|
||||
* @is_visible: Callback to return attribute visibility. Mandatory.
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#ifndef _PMBUS_H_
|
||||
#define _PMBUS_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
/* flags */
|
||||
|
||||
/*
|
||||
|
@ -23,7 +25,14 @@
|
|||
* communication errors for no explicable reason. For such chips, checking
|
||||
* the status register must be disabled.
|
||||
*/
|
||||
#define PMBUS_SKIP_STATUS_CHECK (1 << 0)
|
||||
#define PMBUS_SKIP_STATUS_CHECK BIT(0)
|
||||
|
||||
/*
|
||||
* PMBUS_WRITE_PROTECTED
|
||||
* Set if the chip is write protected and write protection is not determined
|
||||
* by the standard WRITE_PROTECT command.
|
||||
*/
|
||||
#define PMBUS_WRITE_PROTECTED BIT(1)
|
||||
|
||||
struct pmbus_platform_data {
|
||||
u32 flags; /* Device specific flags */
|
||||
|
|
Loading…
Reference in a new issue