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:
Linus Torvalds 2020-01-27 09:14:11 -08:00
commit 35417d57ef
34 changed files with 3621 additions and 1504 deletions

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

View file

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

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

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

View file

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

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

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &regval);
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, &regval);
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], &regval);
*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], &regval);
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), &regval);
*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, &regval);
*val = ((regval >> 16) & 0x7f) * 500 + 52000;
break;
case hwmon_temp_crit_hyst:
data->read_htcreg(data->pdev, &regval);
*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,
&reg);
if (!(reg & NB_CAP_HTC))
return 0;
data->read_htcreg(data->pdev, &reg);
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,
&reg);
if (!(reg & NB_CAP_HTC))
return 0;
data->read_htcreg(data->pdev, &reg);
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, &reg);
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), &regval);
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
View 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");

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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