hwmon patches for v5.11

- Driver for SB-TSI sensors
 - Add support for P10 to fsi/occ
 - Driver for LTC2992
 - Driver for Delta power supplies Q54SJ108A2
 - Support for NCT6687D added to nct6883 driver
 - Support for Intel-based Xserves added to applesmc driver
 - Driver for  Maxim MAX127
 - Support for  AMD family 19h model 01h added to amd_energy driver
 - Driver to support Corsair PSU
 - Driver for STMicroelectronics PM6764 Voltage Regulator
 - Various minor bug fixes and improvements
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAl/X2TsACgkQyx8mb86f
 mYHZNxAAh3GROFcPxWGANUoksNMgnoiq/Wg7OE4MSVqWP3lDuMd9fNpXBSl/6p8F
 zm9SpPpgplWsoFkymYFokCNIrxE4YDajsJq4k6obOpxpqyPXTtw6g8CX0ZXA16VR
 9VH8woUpGQVUSTCeQxo7HoIx3ntwt7NyHaZt5qAXjz2Mjkd56ZJsI0MS4vwsfssO
 nqrgCOR/We/QBJ7UiV5RGSnGOU1n1GD2EQxagQ38SWd9fvkYS5LteX88kcVGl/7b
 o+ZuInhMtoyb0aN25yLCKZOKcU4W/HWCm4Mdto2wNFhCFrS902kp2/kA4gHLJ1nv
 WU4pTZsC7nagVyOTFQ+3MKMbRP9v1wVG+HZf4OTf2dEx5SdWM9BFjGLK44+5KZdS
 CvK7ckjTbkrcmxng3tCAHavlTAPfr09r45Wdh9tUvHb61anN8ZiwPdsjNJN2IKzm
 8zt+ZNLcBYHWnQkAqU2r4SrmxvcAxBbM8Vc7JDIPSExzjEtsPyizljPzkQk8WmaB
 Dk+9bPOHCtQowYgaGlu0vQsFyKHfoxekOWq1aUacuDLF94G4OIYTzhRVzhwIvBI+
 uDilaSF09/wuPqLRc0srk3QPwrLHLT0O+XD20vPWeJu09x2ysaMW1lmerd3DToV7
 BWf53c+QlhoG3hcHLsNrJxF3wtNUniw+fqdxc9s+2RK1+hCguT8=
 =YomY
 -----END PGP SIGNATURE-----

Merge tag 'hwmon-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:
 "New drivers:
   - SB-TSI sensors
   - Lineat Technology LTC2992
   - Delta power supplies Q54SJ108A2
   - Maxim MAX127
   - Corsair PSU
   - STMicroelectronics PM6764 Voltage Regulator

  New chip support:
   - P10 added to fsi/occ driver
   - NCT6687D added to nct6883 driver
   - Intel-based Xserves added to applesmc driver
   - AMD family 19h model 01h added to amd_energy driver

  And various minor bug fixes and improvements"

* tag 'hwmon-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (41 commits)
  dt-bindings: (hwmon/sbtsi_temp) Add SB-TSI hwmon driver bindings
  hwmon: (sbtsi) Add documentation
  hwmon: (sbtsi) Add basic support for SB-TSI sensors
  hwmon: (iio_hwmon) Drop bogus __refdata annotation
  hwmon: (xgene) Drop bogus __refdata annotation
  dt-bindings: hwmon: convert AD ADM1275 bindings to dt-schema
  hwmon: (occ) Add new temperature sensor type
  fsi: occ: Add support for P10
  dt-bindings: fsi: Add P10 OCC device documentation
  dt-bindings: hwmon: convert TI ADS7828 bindings to dt-schema
  dt-bindings: hwmon: convert AD AD741x bindings to dt-schema
  dt-bindings: hwmon: convert TI INA2xx bindings to dt-schema
  hwmon: (ltc2992) Fix less than zero comparisons with an unsigned integer
  hwmon: (pmbus/q54sj108a2) Correct title underline length
  dt-bindings: hwmon: Add documentation for ltc2992
  hwmon: (ltc2992) Add support for GPIOs.
  hwmon: (ltc2992) Add support
  hwmon: (pmbus) Driver for Delta power supplies Q54SJ108A2
  hwmon: Add driver for STMicroelectronics PM6764 Voltage Regulator
  hwmon: (nct6683) Support NCT6687D.
  ...
This commit is contained in:
Linus Torvalds 2020-12-15 16:06:14 -08:00
commit 0f97458173
81 changed files with 3880 additions and 321 deletions

View File

@ -1,13 +1,13 @@
Device-tree bindings for FSI-attached POWER9 On-Chip Controller (OCC)
---------------------------------------------------------------------
Device-tree bindings for FSI-attached POWER9/POWER10 On-Chip Controller (OCC)
-----------------------------------------------------------------------------
This is the binding for the P9 On-Chip Controller accessed over FSI from a
service processor. See fsi.txt for details on bindings for FSI slave and CFAM
This is the binding for the P9 or P10 On-Chip Controller accessed over FSI from
a service processor. See fsi.txt for details on bindings for FSI slave and CFAM
nodes. The OCC is not an FSI slave device itself, rather it is accessed
through the SBE fifo.
through the SBE FIFO.
Required properties:
- compatible = "ibm,p9-occ"
- compatible = "ibm,p9-occ" or "ibm,p10-occ"
Examples:

View File

@ -1,15 +0,0 @@
* AD7416/AD7417/AD7418 Temperature Sensor Device Tree Bindings
Required properties:
- compatible: one of
"adi,ad7416"
"adi,ad7417"
"adi,ad7418"
- reg: I2C address
Example:
hwmon@28 {
compatible = "adi,ad7418";
reg = <0x28>;
};

View File

@ -0,0 +1,39 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/adi,ad741x.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Analog Devices AD7416/AD7417/AD7418 temperature sensors
maintainers:
- Krzysztof Kozlowski <krzk@kernel.org>
properties:
compatible:
enum:
- adi,ad7416
- adi,ad7417
- adi,ad7418
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@28 {
compatible = "adi,ad7418";
reg = <0x28>;
};
};

View File

@ -0,0 +1,57 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/adi,adm1275.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Analog Devices ADM1075/ADM127x/ADM129x digital power monitors
maintainers:
- Krzysztof Kozlowski <krzk@kernel.org>
description: |
The ADM1293 and ADM1294 are high accuracy integrated digital power monitors
that offer digital current, voltage, and power monitoring using an on-chip,
12-bit analog-to-digital converter (ADC), communicated through a PMBus
compliant I2C interface.
Datasheets:
https://www.analog.com/en/products/adm1294.html
properties:
compatible:
enum:
- adi,adm1075
- adi,adm1272
- adi,adm1275
- adi,adm1276
- adi,adm1278
- adi,adm1293
- adi,adm1294
reg:
maxItems: 1
shunt-resistor-micro-ohms:
description:
Shunt resistor value in micro-Ohm.
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
power-sensor@10 {
compatible = "adi,adm1272";
reg = <0x10>;
shunt-resistor-micro-ohms = <500>;
};
};

View File

@ -0,0 +1,80 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/adi,ltc2992.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Linear Technology 2992 Power Monitor
maintainers:
- Alexandru Tachici <alexandru.tachici@analog.com>
description: |
Linear Technology 2992 Dual Wide Range Power Monitor
https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
properties:
compatible:
enum:
- adi,ltc2992
reg:
maxItems: 1
'#address-cells':
const: 1
'#size-cells':
const: 0
avcc-supply: true
patternProperties:
"^channel@([0-1])$":
type: object
description: |
Represents the two supplies to be monitored.
properties:
reg:
description: |
The channel number. LTC2992 can monitor two supplies.
items:
minimum: 0
maximum: 1
shunt-resistor-micro-ohms:
description:
The value of curent sense resistor in microohms.
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c1 {
#address-cells = <1>;
#size-cells = <0>;
ltc2992@6F {
#address-cells = <1>;
#size-cells = <0>;
compatible = "adi,ltc2992";
reg = <0x6F>;
channel@0 {
reg = <0x0>;
shunt-resistor-micro-ohms = <10000>;
};
channel@1 {
reg = <0x1>;
shunt-resistor-micro-ohms = <10000>;
};
};
};
...

View File

@ -1,25 +0,0 @@
adm1275 properties
Required properties:
- compatible: Must be one of the supported compatible strings:
- "adi,adm1075" for adm1075
- "adi,adm1272" for adm1272
- "adi,adm1275" for adm1275
- "adi,adm1276" for adm1276
- "adi,adm1278" for adm1278
- "adi,adm1293" for adm1293
- "adi,adm1294" for adm1294
- reg: I2C address
Optional properties:
- shunt-resistor-micro-ohms
Shunt resistor value in micro-Ohm
Example:
adm1272@10 {
compatible = "adi,adm1272";
reg = <0x10>;
shunt-resistor-micro-ohms = <500>;
};

View File

@ -1,25 +0,0 @@
ads7828 properties
Required properties:
- compatible: Should be one of
ti,ads7828
ti,ads7830
- reg: I2C address
Optional properties:
- ti,differential-input
Set to use the device in differential mode.
- vref-supply
The external reference on the device is set to this regulators output. If it
does not exists the internal reference will be used and output by the ads78xx
on the "external vref" pin.
Example ADS7828 node:
ads7828: ads@48 {
comatible = "ti,ads7828";
reg = <0x48>;
vref-supply = <&vref>;
ti,differential-input;
};

View File

@ -0,0 +1,54 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/amd,sbtsi.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: >
Sideband interface Temperature Sensor Interface (SB-TSI) compliant
AMD SoC temperature device
maintainers:
- Kun Yi <kunyi@google.com>
- Supreeth Venkatesh <supreeth.venkatesh@amd.com>
description: |
SB Temperature Sensor Interface (SB-TSI) is an SMBus compatible
interface that reports AMD SoC's Ttcl (normalized temperature),
and resembles a typical 8-pin remote temperature sensor's I2C interface
to BMC. The emulated thermal sensor can report temperatures in increments
of 0.125 degrees, ranging from 0 to 255.875.
properties:
compatible:
enum:
- amd,sbtsi
reg:
maxItems: 1
description: |
I2C bus address of the device as specified in Section 6.3.1 of the
SoC register reference. The SB-TSI address is normally 98h for socket
0 and 90h for socket 1, but it could vary based on hardware address
select pins.
\[open source SoC register reference\]
https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
sbtsi@4c {
compatible = "amd,sbtsi";
reg = <0x4c>;
};
};
...

View File

@ -1,24 +0,0 @@
ina2xx properties
Required properties:
- compatible: Must be one of the following:
- "ti,ina209" for ina209
- "ti,ina219" for ina219
- "ti,ina220" for ina220
- "ti,ina226" for ina226
- "ti,ina230" for ina230
- "ti,ina231" for ina231
- reg: I2C address
Optional properties:
- shunt-resistor
Shunt resistor value in micro-Ohm
Example:
ina220@44 {
compatible = "ti,ina220";
reg = <0x44>;
shunt-resistor = <1000>;
};

View File

@ -8,15 +8,16 @@ Required properties:
Optional properties:
- fan-supply : phandle to the regulator that provides power to the fan
- interrupts : This contains a single interrupt specifier which
describes the tachometer output of the fan as an
interrupt source. The output signal must generate a
defined number of interrupts per fan revolution, which
require that it must be self resetting edge interrupts.
See interrupt-controller/interrupts.txt for the format.
- pulses-per-revolution : define the tachometer pulses per fan revolution as
an integer (default is 2 interrupts per revolution).
The value must be greater than zero.
- interrupts : This contains an interrupt specifier for each fan
tachometer output connected to an interrupt source.
The output signal must generate a defined number of
interrupts per fan revolution, which require that
it must be self resetting edge interrupts. See
interrupt-controller/interrupts.txt for the format.
- pulses-per-revolution : define the number of pulses per fan revolution for
each tachometer input as an integer (default is 2
interrupts per revolution). The value must be
greater than zero.
Example:
fan0: pwm-fan {
@ -55,3 +56,12 @@ Example 2:
interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
pulses-per-revolution = <2>;
};
Example 3:
fan0: pwm-fan {
compatible = "pwm-fan";
pwms = <&pwm1 0 25000 0>;
interrupts-extended = <&gpio1 1 IRQ_TYPE_EDGE_FALLING>,
<&gpio2 5 IRQ_TYPE_EDGE_FALLING>;
pulses-per-revolution = <2>, <1>;
};

View File

@ -0,0 +1,57 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/ti,ads7828.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments ADS7828/ADS7830 Analog to Digital Converter (ADC)
maintainers:
- Krzysztof Kozlowski <krzk@kernel.org>
description: |
The ADS7828 is 12-Bit, 8-Channel Sampling Analog to Digital Converter (ADC)
with an I2C interface.
Datasheets:
https://www.ti.com/product/ADS7828
properties:
compatible:
enum:
- ti,ads7828
- ti,ads7830
reg:
maxItems: 1
ti,differential-input:
description:
Set to use the device in differential mode.
type: boolean
vref-supply:
description:
The regulator to use as an external reference. If it does not exists the
internal reference will be used.
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
adc@48 {
comatible = "ti,ads7828";
reg = <0x48>;
vref-supply = <&vref>;
ti,differential-input;
};
};

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/ti,ina2xx.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments INA209 family of power/voltage monitors
maintainers:
- Krzysztof Kozlowski <krzk@kernel.org>
description: |
The INA209 is a high-side current shunt and power monitor with
an I2C interface.
Datasheets:
https://www.ti.com/product/INA209
properties:
compatible:
enum:
- ti,ina209
- ti,ina219
- ti,ina220
- ti,ina226
- ti,ina230
- ti,ina231
reg:
maxItems: 1
shunt-resistor:
description:
Shunt resistor value in micro-Ohm.
$ref: /schemas/types.yaml#/definitions/uint32
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
power-sensor@44 {
compatible = "ti,ina220";
reg = <0x44>;
shunt-resistor = <1000>;
};
};

View File

@ -254,10 +254,6 @@ properties:
- st,24c256
# Ambient Light Sensor with SMBUS/Two Wire Serial Interface
- taos,tsl2550
# 8-Channels, 12-bit ADC
- ti,ads7828
# 8-Channels, 8-bit ADC
- ti,ads7830
# Temperature Monitoring and Fan Control
- ti,amc6821
# Temperature and humidity sensor with i2c interface

View File

@ -83,7 +83,7 @@ or current scaling. Reported voltages, currents, and power are raw measurements,
and will typically have to be scaled.
The shunt value in micro-ohms can be set via device tree at compile-time. Please
refer to the Documentation/devicetree/bindings/hwmon/adm1275.txt for bindings
refer to the Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml for bindings
if the device tree is used.
Platform data support

View File

@ -5,7 +5,9 @@ Kernel driver amd_energy
Supported chips:
* AMD Family 17h Processors
* AMD Family 17h Processors: Model 30h
* AMD Family 19h Processors: Model 01h
Prefix: 'amd_energy'
@ -112,3 +114,6 @@ energy[N]_input EcoreX Core Energy X = [0] to [nr_cpus - 1]
energy[N]_input EsocketX Socket Energy X = [0] to [nr_socks -1]
Measured input socket energy
=============== ======== ======================================
Note: To address CVE-2020-12912, the visibility of the energy[N]_input
attributes is restricted to owner and groups only.

View File

@ -0,0 +1,82 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver corsair-psu
=========================
Supported devices:
* Corsair Power Supplies
Corsair HX550i
Corsair HX650i
Corsair HX750i
Corsair HX850i
Corsair HX1000i
Corsair HX1200i
Corsair RM550i
Corsair RM650i
Corsair RM750i
Corsair RM850i
Corsair RM1000i
Author: Wilken Gottwalt
Description
-----------
This driver implements the sysfs interface for the Corsair PSUs with a HID protocol
interface of the HXi and RMi series.
These power supplies provide access to a micro-controller with 2 attached
temperature sensors, 1 fan rpm sensor, 4 sensors for volt levels, 4 sensors for
power usage and 4 sensors for current levels and addtional non-sensor information
like uptimes.
Sysfs entries
-------------
======================= ========================================================
curr1_input Total current usage
curr2_input Current on the 12v psu rail
curr3_input Current on the 5v psu rail
curr4_input Current on the 3.3v psu rail
fan1_input RPM of psu fan
in0_input Voltage of the psu ac input
in1_input Voltage of the 12v psu rail
in2_input Voltage of the 5v psu rail
in3_input Voltage of the 3.3 psu rail
power1_input Total power usage
power2_input Power usage of the 12v psu rail
power3_input Power usage of the 5v psu rail
power4_input Power usage of the 3.3v psu rail
temp1_input Temperature of the psu vrm component
temp2_input Temperature of the psu case
======================= ========================================================
Usage Notes
-----------
It is an USB HID device, so it is auto-detected and supports hot-swapping.
Flickering values in the rail voltage levels can be an indicator for a failing
PSU. The driver also provides some additional useful values via debugfs, which
do not fit into the hwmon class.
Debugfs entries
---------------
======================= ========================================================
uptime Current uptime of the psu
uptime_total Total uptime of the psu
vendor Vendor name of the psu
product Product name of the psu
======================= ========================================================

View File

@ -49,6 +49,7 @@ Hardware Monitoring Kernel Drivers
bt1-pvt
coretemp
corsair-cpro
corsair-psu
da9052
da9055
dell-smm-hwmon
@ -100,6 +101,7 @@ Hardware Monitoring Kernel Drivers
lm95234
lm95245
lochnagar
ltc2992
ltc2945
ltc2947
ltc2978
@ -110,6 +112,7 @@ Hardware Monitoring Kernel Drivers
ltc4245
ltc4260
ltc4261
max127
max16064
max16065
max1619
@ -144,11 +147,14 @@ Hardware Monitoring Kernel Drivers
pc87360
pc87427
pcf8591
pm6764tr
pmbus
powr1220
pxe1610
pwm-fan
q54sj108a2
raspberrypi-hwmon
sbtsi_temp
sch5627
sch5636
scpi-hwmon

View File

@ -0,0 +1,56 @@
.. SPDX-License-Identifier: GPL-2.0
Kernel driver ltc2992
=====================
Supported chips:
* Linear Technology LTC2992
Prefix: 'ltc2992'
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf
Author: Alexandru Tachici <alexandru.tachici@analog.com>
Description
-----------
This driver supports hardware monitoring for Linear Technology LTC2992 power monitor.
LTC2992 is a rail-to-rail system monitor that measures current,
voltage, and power of two supplies.
Two ADCs simultaneously measure each supplys current. A third ADC monitors
the input voltages and four auxiliary external voltages.
Sysfs entries
-------------
The following attributes are supported. Limits are read-write,
all other attributes are read-only.
in_reset_history Reset all highest/lowest values.
inX_input Measured voltage.
inX_lowest Minimum measured voltage.
inX_highest Maximum measured voltage.
inX_min Minimum voltage allowed.
inX_max Maximum voltage allowed.
inX_min_alarm An undervoltage occurred. Cleared on read.
inX_max_alarm An overvoltage occurred. Cleared on read.
currX_input Measured current.
currX_lowest Minimum measured current.
currX_highest Maximum measured current.
currX_min Minimum current allowed.
currX_max Maximum current allowed.
currX_min_alarm An undercurrent occurred. Cleared on read.
currX_max_alarm An overcurrent occurred. Cleared on read.
powerX_input Measured power.
powerX_input_lowest Minimum measured voltage.
powerX_input_highest Maximum measured voltage.
powerX_min Minimum power.
powerX_max Maximum power.
powerX_min_alarm An underpower occurred. Cleared on read.
powerX_max_alarm An overpower occurred. Cleared on read.

View File

@ -0,0 +1,45 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver max127
====================
Author:
* Tao Ren <rentao.bupt@gmail.com>
Supported chips:
* Maxim MAX127
Prefix: 'max127'
Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX127-MAX128.pdf
Description
-----------
The MAX127 is a multirange, 12-bit data acquisition system (DAS) providing
8 analog input channels that are independently software programmable for
a variety of ranges. The available ranges are {0,5V}, {0,10V}, {-5,5V}
and {-10,10V}.
The MAX127 features a 2-wire, I2C-compatible serial interface that allows
communication among multiple devices using SDA and SCL lines.
Sysfs interface
---------------
============== ==============================================================
in[0-7]_input The input voltage (in mV) of the corresponding channel.
RO
in[0-7]_min The lower input limit (in mV) for the corresponding channel.
ADC range and LSB will be updated when the limit is changed.
For the MAX127, it will be adjusted to -10000, -5000, or 0.
RW
in[0-7]_max The higher input limit (in mV) for the corresponding channel.
ADC range and LSB will be updated when the limit is changed.
For the MAX127, it will be adjusted to 0, 5000, or 10000.
RW
============== ==============================================================

View File

@ -3,7 +3,7 @@ Kernel driver nct6683
Supported chips:
* Nuvoton NCT6683D
* Nuvoton NCT6683D/NCT6687D
Prefix: 'nct6683'
@ -61,4 +61,5 @@ Board Firmware version
Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13
Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13
Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13
MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20
=============== ===============================================

View File

@ -0,0 +1,32 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel driver pm6764tr
======================
Supported chips:
* ST PM6764TR
Prefix: 'pm6764tr'
Addresses scanned: -
Datasheet: http://www.st.com/resource/en/data_brief/pm6764.pdf
Authors:
<hsu.yungteng@gmail.com>
Description:
------------
This driver supports the STMicroelectronics PM6764TR chip. The PM6764TR is a high
performance digital controller designed to power Intels VR12.5 processors and memories.
The device utilizes digital technology to implement all control and power management
functions to provide maximum flexibility and performance. The NVM is embedded to store
custom configurations. The PM6764TR device features up to 4-phase programmable operation.
The PM6764TR supports power state transitions featuring VFDE, and programmable DPM
maintaining the best efficiency over all loading conditions without compromising transient
response. The device assures fast and independent protection against load overcurrent,
under/overvoltage and feedback disconnections.

View File

@ -277,12 +277,6 @@ with the pointer to struct pmbus_driver_info as additional argument. Calls
identify function if supported. Must only be called from device probe
function.
::
void pmbus_do_remove(struct i2c_client *client);
Execute driver remove function. Similar to standard driver remove function.
::
const struct pmbus_driver_info

View File

@ -148,11 +148,6 @@ Emerson DS1200 power modules might look as follows::
return pmbus_do_probe(client, &ds1200_info);
}
static int ds1200_remove(struct i2c_client *client)
{
return pmbus_do_remove(client);
}
static const struct i2c_device_id ds1200_id[] = {
{"ds1200", 0},
{}
@ -166,7 +161,6 @@ Emerson DS1200 power modules might look as follows::
.name = "ds1200",
},
.probe_new = ds1200_probe,
.remove = ds1200_remove,
.id_table = ds1200_id,
};

View File

@ -0,0 +1,54 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver q54sj108a2
========================
Supported chips:
* DELTA Q54SJ108A2NCAH, Q54SJ108A2NCDH, Q54SJ108A2NCPG, Q54SJ108A2NCPH
Prefix: 'q54sj108a2'
Addresses scanned: -
Datasheet: https://filecenter.delta-china.com.cn/products/download/01/0102/datasheet/DS_Q54SJ108A2.pdf
Authors:
Xiao.ma <xiao.mx.ma@deltaww.com>
Description
-----------
This driver implements support for DELTA Q54SJ108A2NCAH, Q54SJ108A2NCDH,
Q54SJ108A2NCPG, and Q54SJ108A2NCPH 1/4 Brick DC/DC Regulated Power Module
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_alarm RO Output current 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_alarm RO Temperature alarm
temp1_input RO Chip temperature
===================== ===== ==================================================

View File

@ -0,0 +1,42 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver sbtsi_temp
==================
Supported hardware:
* Sideband interface (SBI) Temperature Sensor Interface (SB-TSI)
compliant AMD SoC temperature device.
Prefix: 'sbtsi_temp'
Addresses scanned: This driver doesn't support address scanning.
To instantiate this driver on an AMD CPU with SB-TSI
support, the i2c bus number would be the bus connected from the board
management controller (BMC) to the CPU. The i2c address is specified in
Section 6.3.1 of the SoC register reference: The SB-TSI address is normally
98h for socket 0 and 90h for socket 1, but it could vary based on hardware
address select pins.
Datasheet: The SB-TSI interface and protocol is available as part of
the open source SoC register reference at:
https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf
The Advanced Platform Management Link (APML) Specification is
available at:
http://developer.amd.com/wordpress/media/2012/10/41918.pdf
Author: Kun Yi <kunyi@google.com>
Description
-----------
The SBI temperature sensor interface (SB-TSI) is an emulation of the software
and physical interface of a typical 8-pin remote temperature sensor (RTS) on
AMD SoCs. It implements one temperature sensor with readings and limit
registers encode the temperature in increments of 0.125 from 0 to 255.875.
Limits can be set through the writable thresholds, and if reached will trigger
corresponding alert signals.

View File

@ -4513,6 +4513,13 @@ L: linux-hwmon@vger.kernel.org
S: Maintained
F: drivers/hwmon/corsair-cpro.c
CORSAIR-PSU HARDWARE MONITOR DRIVER
M: Wilken Gottwalt <wilken.gottwalt@posteo.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/corsair-psu.rst
F: drivers/hwmon/corsair-psu.c
COSA/SRP SYNC SERIAL DRIVER
M: Jan "Yenya" Kasprzak <kas@fi.muni.cz>
S: Maintained
@ -8675,7 +8682,7 @@ INA209 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/ina2xx.txt
F: Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml
F: Documentation/hwmon/ina209.rst
F: drivers/hwmon/ina209.c
@ -14035,6 +14042,13 @@ M: Logan Gunthorpe <logang@deltatee.com>
S: Maintained
F: drivers/dma/plx_dma.c
PM6764TR DRIVER
M: Charles Hsu <hsu.yungteng@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/pm6764tr.rst
F: drivers/hwmon/pmbus/pm6764tr.c
PM-GRAPH UTILITY
M: "Todd E Brandt" <todd.e.brandt@linux.intel.com>
L: linux-pm@vger.kernel.org

View File

@ -14,6 +14,7 @@
#include <linux/mutex.h>
#include <linux/fsi-occ.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
@ -24,8 +25,13 @@
#define OCC_CMD_DATA_BYTES 4090
#define OCC_RESP_DATA_BYTES 4089
#define OCC_SRAM_CMD_ADDR 0xFFFBE000
#define OCC_SRAM_RSP_ADDR 0xFFFBF000
#define OCC_P9_SRAM_CMD_ADDR 0xFFFBE000
#define OCC_P9_SRAM_RSP_ADDR 0xFFFBF000
#define OCC_P10_SRAM_CMD_ADDR 0xFFFFD000
#define OCC_P10_SRAM_RSP_ADDR 0xFFFFE000
#define OCC_P10_SRAM_MODE 0x58 /* Normal mode, OCB channel 2 */
/*
* Assume we don't have much FFDC, if we do we'll overflow and
@ -37,11 +43,14 @@
#define OCC_TIMEOUT_MS 1000
#define OCC_CMD_IN_PRG_WAIT_MS 50
enum versions { occ_p9, occ_p10 };
struct occ {
struct device *dev;
struct device *sbefifo;
char name[32];
int idx;
enum versions version;
struct miscdevice mdev;
struct mutex occ_lock;
};
@ -235,29 +244,43 @@ static int occ_verify_checksum(struct occ_response *resp, u16 data_length)
return 0;
}
static int occ_getsram(struct occ *occ, u32 address, void *data, ssize_t len)
static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
{
u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
size_t resp_len, resp_data_len;
__be32 *resp, cmd[5];
int rc;
size_t cmd_len, resp_len, resp_data_len;
__be32 *resp, cmd[6];
int idx = 0, rc;
/*
* Magic sequence to do SBE getsram command. SBE will fetch data from
* specified SRAM address.
*/
cmd[0] = cpu_to_be32(0x5);
switch (occ->version) {
default:
case occ_p9:
cmd_len = 5;
cmd[2] = cpu_to_be32(1); /* Normal mode */
cmd[3] = cpu_to_be32(OCC_P9_SRAM_RSP_ADDR + offset);
break;
case occ_p10:
idx = 1;
cmd_len = 6;
cmd[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
cmd[3] = 0;
cmd[4] = cpu_to_be32(OCC_P10_SRAM_RSP_ADDR + offset);
break;
}
cmd[0] = cpu_to_be32(cmd_len);
cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM);
cmd[2] = cpu_to_be32(1);
cmd[3] = cpu_to_be32(address);
cmd[4] = cpu_to_be32(data_len);
cmd[4 + idx] = cpu_to_be32(data_len);
resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS;
resp = kzalloc(resp_len << 2, GFP_KERNEL);
if (!resp)
return -ENOMEM;
rc = sbefifo_submit(occ->sbefifo, cmd, 5, resp, &resp_len);
rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len);
if (rc)
goto free;
@ -287,20 +310,21 @@ free:
return rc;
}
static int occ_putsram(struct occ *occ, u32 address, const void *data,
ssize_t len)
static int occ_putsram(struct occ *occ, const void *data, ssize_t len)
{
size_t cmd_len, buf_len, resp_len, resp_data_len;
u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
__be32 *buf;
int rc;
int idx = 0, rc;
cmd_len = (occ->version == occ_p10) ? 6 : 5;
/*
* We use the same buffer for command and response, make
* sure it's big enough
*/
resp_len = OCC_SBE_STATUS_WORDS;
cmd_len = (data_len >> 2) + 5;
cmd_len += data_len >> 2;
buf_len = max(cmd_len, resp_len);
buf = kzalloc(buf_len << 2, GFP_KERNEL);
if (!buf)
@ -312,11 +336,23 @@ static int occ_putsram(struct occ *occ, u32 address, const void *data,
*/
buf[0] = cpu_to_be32(cmd_len);
buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
buf[2] = cpu_to_be32(1);
buf[3] = cpu_to_be32(address);
buf[4] = cpu_to_be32(data_len);
memcpy(&buf[5], data, len);
switch (occ->version) {
default:
case occ_p9:
buf[2] = cpu_to_be32(1); /* Normal mode */
buf[3] = cpu_to_be32(OCC_P9_SRAM_CMD_ADDR);
break;
case occ_p10:
idx = 1;
buf[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
buf[3] = 0;
buf[4] = cpu_to_be32(OCC_P10_SRAM_CMD_ADDR);
break;
}
buf[4 + idx] = cpu_to_be32(data_len);
memcpy(&buf[5 + idx], data, len);
rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
if (rc)
@ -356,21 +392,35 @@ free:
static int occ_trigger_attn(struct occ *occ)
{
__be32 buf[OCC_SBE_STATUS_WORDS];
size_t resp_len, resp_data_len;
int rc;
size_t cmd_len, resp_len, resp_data_len;
int idx = 0, rc;
BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 7);
BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 8);
resp_len = OCC_SBE_STATUS_WORDS;
buf[0] = cpu_to_be32(0x5 + 0x2); /* Chip-op length in words */
buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
buf[2] = cpu_to_be32(0x3); /* Mode: Circular */
buf[3] = cpu_to_be32(0x0); /* Address: ignore in mode 3 */
buf[4] = cpu_to_be32(0x8); /* Data length in bytes */
buf[5] = cpu_to_be32(0x20010000); /* Trigger OCC attention */
buf[6] = 0;
switch (occ->version) {
default:
case occ_p9:
cmd_len = 7;
buf[2] = cpu_to_be32(3); /* Circular mode */
buf[3] = 0;
break;
case occ_p10:
idx = 1;
cmd_len = 8;
buf[2] = cpu_to_be32(0xd0); /* Circular mode, OCB Channel 1 */
buf[3] = 0;
buf[4] = 0;
break;
}
rc = sbefifo_submit(occ->sbefifo, buf, 7, buf, &resp_len);
buf[0] = cpu_to_be32(cmd_len); /* Chip-op length in words */
buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
buf[4 + idx] = cpu_to_be32(8); /* Data length in bytes */
buf[5 + idx] = cpu_to_be32(0x20010000); /* Trigger OCC attention */
buf[6 + idx] = 0;
rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
if (rc)
goto error;
@ -429,7 +479,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
/* Extract the seq_no from the command (first byte) */
seq_no = *(const u8 *)request;
rc = occ_putsram(occ, OCC_SRAM_CMD_ADDR, request, req_len);
rc = occ_putsram(occ, request, req_len);
if (rc)
goto done;
@ -440,7 +490,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
/* Read occ response header */
start = jiffies;
do {
rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR, resp, 8);
rc = occ_getsram(occ, 0, resp, 8);
if (rc)
goto done;
@ -476,8 +526,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
/* Grab the rest */
if (resp_data_length > 1) {
/* already got 3 bytes resp, also need 2 bytes checksum */
rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR + 8,
&resp->data[3], resp_data_length - 1);
rc = occ_getsram(occ, 8, &resp->data[3], resp_data_length - 1);
if (rc)
goto done;
}
@ -517,6 +566,7 @@ static int occ_probe(struct platform_device *pdev)
if (!occ)
return -ENOMEM;
occ->version = (uintptr_t)of_device_get_match_data(dev);
occ->dev = dev;
occ->sbefifo = dev->parent;
mutex_init(&occ->occ_lock);
@ -575,7 +625,14 @@ static int occ_remove(struct platform_device *pdev)
}
static const struct of_device_id occ_match[] = {
{ .compatible = "ibm,p9-occ" },
{
.compatible = "ibm,p9-occ",
.data = (void *)occ_p9
},
{
.compatible = "ibm,p10-occ",
.data = (void *)occ_p10
},
{ },
};

View File

@ -449,6 +449,19 @@ config SENSORS_CORSAIR_CPRO
This driver can also be built as a module. If so, the module
will be called corsair-cpro.
config SENSORS_CORSAIR_PSU
tristate "Corsair PSU HID controller"
depends on HID
help
If you say yes here you get support for Corsair PSUs with a HID
interface.
Currently this driver supports the (RM/HX)550i, (RM/HX)650i,
(RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies
by Corsair.
This driver can also be built as a module. If so, the module
will be called corsair-psu.
config SENSORS_DRIVETEMP
tristate "Hard disk drives with temperature sensors"
depends on SCSI && ATA
@ -858,6 +871,18 @@ config SENSORS_LTC2990
This driver can also be built as a module. If so, the module will
be called ltc2990.
config SENSORS_LTC2992
tristate "Linear Technology LTC2992"
depends on I2C
depends on GPIOLIB
help
If you say yes here you get support for Linear Technology LTC2992
I2C System Monitor. The LTC2992 measures current, voltage, and
power of two supplies.
This driver can also be built as a module. If so, the module will
be called ltc2992.
config SENSORS_LTC4151
tristate "Linear Technology LTC4151"
depends on I2C
@ -937,6 +962,15 @@ config SENSORS_MAX1111
This driver can also be built as a module. If so, the module
will be called max1111.
config SENSORS_MAX127
tristate "Maxim MAX127 12-bit 8-channel Data Acquisition System"
depends on I2C
help
Say y here to support Maxim's MAX127 DAS chips.
This driver can also be built as a module. If so, the module
will be called max127.
config SENSORS_MAX16065
tristate "Maxim MAX16065 System Manager and compatibles"
depends on I2C
@ -1499,6 +1533,16 @@ config SENSORS_SL28CPLD
This driver can also be built as a module. If so, the module
will be called sl28cpld-hwmon.
config SENSORS_SBTSI
tristate "Emulated SB-TSI temperature sensor"
depends on I2C
help
If you say yes here you get support for emulated temperature
sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
This driver can also be built as a module. If so, the module will
be called sbtsi_temp.
config SENSORS_SHT15
tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
depends on GPIOLIB || COMPILE_TEST

View File

@ -57,6 +57,7 @@ obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
@ -118,6 +119,7 @@ obj-$(CONFIG_SENSORS_LTC2947) += ltc2947-core.o
obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o
obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o
obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o
obj-$(CONFIG_SENSORS_LTC2992) += ltc2992.o
obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o
@ -126,6 +128,7 @@ obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX127) += max127.o
obj-$(CONFIG_SENSORS_MAX16065) += max16065.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
obj-$(CONFIG_SENSORS_MAX1668) += max1668.o
@ -158,6 +161,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o

View File

@ -263,7 +263,7 @@ static ssize_t max_alarm_show(struct device *dev,
static umode_t abx500_attrs_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct abx500_temp *data = dev_get_drvdata(dev);
if (data->ops.is_visible)

View File

@ -725,8 +725,10 @@ static void free_capabilities(struct acpi_power_meter_resource *resource)
int i;
str = &resource->model_number;
for (i = 0; i < 3; i++, str++)
for (i = 0; i < 3; i++, str++) {
kfree(*str);
*str = NULL;
}
}
static int read_capabilities(struct acpi_power_meter_resource *resource)
@ -801,9 +803,7 @@ static int read_capabilities(struct acpi_power_meter_resource *resource)
dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
goto end;
error:
str = &resource->model_number;
for (i = 0; i < 3; i++, str++)
kfree(*str);
free_capabilities(resource);
end:
kfree(buffer.pointer);
return res;
@ -874,7 +874,6 @@ static int acpi_power_meter_add(struct acpi_device *device)
strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
device->driver_data = resource;
free_capabilities(resource);
res = read_capabilities(resource);
if (res)
goto exit_free;

View File

@ -25,11 +25,11 @@
/**
* 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
* @client: pointer to i2c client
* @reg: regulator info for 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;

View File

@ -270,37 +270,11 @@ static int adt7470_update_thread(void *p)
return 0;
}
static struct adt7470_data *adt7470_update_device(struct device *dev)
static int adt7470_update_sensors(struct adt7470_data *data)
{
struct adt7470_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
unsigned long local_jiffies = jiffies;
u8 cfg;
int i;
int need_sensors = 1;
int need_limits = 1;
/*
* Figure out if we need to update the shadow registers.
* Lockless means that we may occasionally report out of
* date data.
*/
if (time_before(local_jiffies, data->sensors_last_updated +
SENSOR_REFRESH_INTERVAL) &&
data->sensors_valid)
need_sensors = 0;
if (time_before(local_jiffies, data->limits_last_updated +
LIMIT_REFRESH_INTERVAL) &&
data->limits_valid)
need_limits = 0;
if (!need_sensors && !need_limits)
return data;
mutex_lock(&data->lock);
if (!need_sensors)
goto no_sensor_update;
if (!data->temperatures_probed)
adt7470_read_temperatures(client, data);
@ -352,12 +326,13 @@ static struct adt7470_data *adt7470_update_device(struct device *dev)
data->alarms_mask = adt7470_read_word_data(client,
ADT7470_REG_ALARM1_MASK);
data->sensors_last_updated = local_jiffies;
data->sensors_valid = 1;
return 0;
}
no_sensor_update:
if (!need_limits)
goto out;
static int adt7470_update_limits(struct adt7470_data *data)
{
struct i2c_client *client = data->client;
int i;
for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
data->temp_min[i] = i2c_smbus_read_byte_data(client,
@ -382,12 +357,55 @@ no_sensor_update:
ADT7470_REG_PWM_TMIN(i));
}
data->limits_last_updated = local_jiffies;
data->limits_valid = 1;
return 0;
}
static struct adt7470_data *adt7470_update_device(struct device *dev)
{
struct adt7470_data *data = dev_get_drvdata(dev);
unsigned long local_jiffies = jiffies;
int need_sensors = 1;
int need_limits = 1;
int err;
/*
* Figure out if we need to update the shadow registers.
* Lockless means that we may occasionally report out of
* date data.
*/
if (time_before(local_jiffies, data->sensors_last_updated +
SENSOR_REFRESH_INTERVAL) &&
data->sensors_valid)
need_sensors = 0;
if (time_before(local_jiffies, data->limits_last_updated +
LIMIT_REFRESH_INTERVAL) &&
data->limits_valid)
need_limits = 0;
if (!need_sensors && !need_limits)
return data;
mutex_lock(&data->lock);
if (need_sensors) {
err = adt7470_update_sensors(data);
if (err < 0)
goto out;
data->sensors_last_updated = local_jiffies;
data->sensors_valid = 1;
}
if (need_limits) {
err = adt7470_update_limits(data);
if (err < 0)
goto out;
data->limits_last_updated = local_jiffies;
data->limits_valid = 1;
}
out:
mutex_unlock(&data->lock);
return data;
return err < 0 ? ERR_PTR(err) : data;
}
static ssize_t auto_update_interval_show(struct device *dev,
@ -395,6 +413,10 @@ static ssize_t auto_update_interval_show(struct device *dev,
char *buf)
{
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->auto_update_interval);
}
@ -422,6 +444,10 @@ static ssize_t num_temp_sensors_show(struct device *dev,
char *buf)
{
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->num_temp_sensors);
}
@ -451,6 +477,10 @@ static ssize_t temp_min_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]);
}
@ -483,6 +513,10 @@ static ssize_t temp_max_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]);
}
@ -515,6 +549,10 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]);
}
@ -524,6 +562,9 @@ static ssize_t alarm_mask_show(struct device *dev,
{
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%x\n", data->alarms_mask);
}
@ -554,6 +595,9 @@ static ssize_t fan_max_show(struct device *dev,
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
if (FAN_DATA_VALID(data->fan_max[attr->index]))
return sprintf(buf, "%d\n",
FAN_PERIOD_TO_RPM(data->fan_max[attr->index]));
@ -590,6 +634,9 @@ static ssize_t fan_min_show(struct device *dev,
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
if (FAN_DATA_VALID(data->fan_min[attr->index]))
return sprintf(buf, "%d\n",
FAN_PERIOD_TO_RPM(data->fan_min[attr->index]));
@ -626,6 +673,9 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
if (FAN_DATA_VALID(data->fan[attr->index]))
return sprintf(buf, "%d\n",
FAN_PERIOD_TO_RPM(data->fan[attr->index]));
@ -637,6 +687,10 @@ static ssize_t force_pwm_max_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->force_pwm_max);
}
@ -670,6 +724,10 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->pwm[attr->index]);
}
@ -763,6 +821,10 @@ static ssize_t pwm_max_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->pwm_max[attr->index]);
}
@ -794,6 +856,10 @@ static ssize_t pwm_min_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", data->pwm_min[attr->index]);
}
@ -825,6 +891,10 @@ static ssize_t pwm_tmax_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
/* the datasheet says that tmax = tmin + 20C */
return sprintf(buf, "%d\n", 1000 * (20 + data->pwm_tmin[attr->index]));
}
@ -834,6 +904,10 @@ static ssize_t pwm_tmin_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", 1000 * data->pwm_tmin[attr->index]);
}
@ -866,6 +940,10 @@ static ssize_t pwm_auto_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]);
}
@ -911,8 +989,12 @@ static ssize_t pwm_auto_temp_show(struct device *dev,
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct adt7470_data *data = adt7470_update_device(dev);
u8 ctrl = data->pwm_auto_temp[attr->index];
u8 ctrl;
if (IS_ERR(data))
return PTR_ERR(data);
ctrl = data->pwm_auto_temp[attr->index];
if (ctrl)
return sprintf(buf, "%d\n", 1 << (ctrl - 1));
else

View File

@ -331,6 +331,7 @@ static struct platform_device *amd_energy_platdev;
static const struct x86_cpu_id cpu_ids[] __initconst = {
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL),
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, cpu_ids);

View File

@ -1299,6 +1299,10 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = {
DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
DMI_MATCH(DMI_PRODUCT_NAME, "iMac") },
},
{ applesmc_dmi_match, "Apple Xserve", {
DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
DMI_MATCH(DMI_PRODUCT_NAME, "Xserve") },
},
{ .ident = NULL }
};

600
drivers/hwmon/corsair-psu.c Normal file
View File

@ -0,0 +1,600 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* corsair-psu.c - Linux driver for Corsair power supplies with HID sensors interface
* Copyright (C) 2020 Wilken Gottwalt <wilken.gottwalt@posteo.net>
*/
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/errno.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/types.h>
/*
* Corsair protocol for PSUs
*
* message size = 64 bytes (request and response, little endian)
* request:
* [length][command][param0][param1][paramX]...
* reply:
* [echo of length][echo of command][data0][data1][dataX]...
*
* - commands are byte sized opcodes
* - length is the sum of all bytes of the commands/params
* - the micro-controller of most of these PSUs support concatenation in the request and reply,
* but it is better to not rely on this (it is also hard to parse)
* - the driver uses raw events to be accessible from userspace (though this is not really
* supported, it is just there for convenience, may be removed in the future)
* - a reply always start with the length and command in the same order the request used it
* - length of the reply data is specific to the command used
* - some of the commands work on a rail and can be switched to a specific rail (0 = 12v,
* 1 = 5v, 2 = 3.3v)
* - the format of the init command 0xFE is swapped length/command bytes
* - parameter bytes amount and values are specific to the command (rail setting is the only
* for now that uses non-zero values)
* - there are much more commands, especially for configuring the device, but they are not
* supported because a wrong command/length can lockup the micro-controller
* - the driver supports debugfs for values not fitting into the hwmon class
* - not every device class (HXi, RMi or AXi) supports all commands
* - it is a pure sensors reading driver (will not support configuring)
*/
#define DRIVER_NAME "corsair-psu"
#define REPLY_SIZE 16 /* max length of a reply to a single command */
#define CMD_BUFFER_SIZE 64
#define CMD_TIMEOUT_MS 250
#define SECONDS_PER_HOUR (60 * 60)
#define SECONDS_PER_DAY (SECONDS_PER_HOUR * 24)
#define PSU_CMD_SELECT_RAIL 0x00 /* expects length 2 */
#define PSU_CMD_IN_VOLTS 0x88 /* the rest of the commands expect length 3 */
#define PSU_CMD_IN_AMPS 0x89
#define PSU_CMD_RAIL_OUT_VOLTS 0x8B
#define PSU_CMD_RAIL_AMPS 0x8C
#define PSU_CMD_TEMP0 0x8D
#define PSU_CMD_TEMP1 0x8E
#define PSU_CMD_FAN 0x90
#define PSU_CMD_RAIL_WATTS 0x96
#define PSU_CMD_VEND_STR 0x99
#define PSU_CMD_PROD_STR 0x9A
#define PSU_CMD_TOTAL_WATTS 0xEE
#define PSU_CMD_TOTAL_UPTIME 0xD1
#define PSU_CMD_UPTIME 0xD2
#define PSU_CMD_INIT 0xFE
#define L_IN_VOLTS "v_in"
#define L_OUT_VOLTS_12V "v_out +12v"
#define L_OUT_VOLTS_5V "v_out +5v"
#define L_OUT_VOLTS_3_3V "v_out +3.3v"
#define L_IN_AMPS "curr in"
#define L_AMPS_12V "curr +12v"
#define L_AMPS_5V "curr +5v"
#define L_AMPS_3_3V "curr +3.3v"
#define L_FAN "psu fan"
#define L_TEMP0 "vrm temp"
#define L_TEMP1 "case temp"
#define L_WATTS "power total"
#define L_WATTS_12V "power +12v"
#define L_WATTS_5V "power +5v"
#define L_WATTS_3_3V "power +3.3v"
static const char *const label_watts[] = {
L_WATTS,
L_WATTS_12V,
L_WATTS_5V,
L_WATTS_3_3V
};
static const char *const label_volts[] = {
L_IN_VOLTS,
L_OUT_VOLTS_12V,
L_OUT_VOLTS_5V,
L_OUT_VOLTS_3_3V
};
static const char *const label_amps[] = {
L_IN_AMPS,
L_AMPS_12V,
L_AMPS_5V,
L_AMPS_3_3V
};
struct corsairpsu_data {
struct hid_device *hdev;
struct device *hwmon_dev;
struct dentry *debugfs;
struct completion wait_completion;
struct mutex lock; /* for locking access to cmd_buffer */
u8 *cmd_buffer;
char vendor[REPLY_SIZE];
char product[REPLY_SIZE];
};
/* some values are SMBus LINEAR11 data which need a conversion */
static int corsairpsu_linear11_to_int(const int val)
{
int exp = (val & 0xFFFF) >> 0x0B;
int mant = val & 0x7FF;
int i;
if (exp > 0x0F)
exp -= 0x20;
if (mant > 0x3FF)
mant -= 0x800;
if ((mant & 0x01) == 1)
++mant;
if (exp < 0) {
for (i = 0; i < -exp; ++i)
mant /= 2;
} else {
for (i = 0; i < exp; ++i)
mant *= 2;
}
return mant;
}
static int corsairpsu_usb_cmd(struct corsairpsu_data *priv, u8 p0, u8 p1, u8 p2, void *data)
{
unsigned long time;
int ret;
memset(priv->cmd_buffer, 0, CMD_BUFFER_SIZE);
priv->cmd_buffer[0] = p0;
priv->cmd_buffer[1] = p1;
priv->cmd_buffer[2] = p2;
reinit_completion(&priv->wait_completion);
ret = hid_hw_output_report(priv->hdev, priv->cmd_buffer, CMD_BUFFER_SIZE);
if (ret < 0)
return ret;
time = wait_for_completion_timeout(&priv->wait_completion,
msecs_to_jiffies(CMD_TIMEOUT_MS));
if (!time)
return -ETIMEDOUT;
/*
* at the start of the reply is an echo of the send command/length in the same order it
* was send, not every command is supported on every device class, if a command is not
* supported, the length value in the reply is okay, but the command value is set to 0
*/
if (p0 != priv->cmd_buffer[0] || p1 != priv->cmd_buffer[1])
return -EOPNOTSUPP;
if (data)
memcpy(data, priv->cmd_buffer + 2, REPLY_SIZE);
return 0;
}
static int corsairpsu_init(struct corsairpsu_data *priv)
{
/*
* PSU_CMD_INIT uses swapped length/command and expects 2 parameter bytes, this command
* actually generates a reply, but we don't need it
*/
return corsairpsu_usb_cmd(priv, PSU_CMD_INIT, 3, 0, NULL);
}
static int corsairpsu_fwinfo(struct corsairpsu_data *priv)
{
int ret;
ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_VEND_STR, 0, priv->vendor);
if (ret < 0)
return ret;
ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_PROD_STR, 0, priv->product);
if (ret < 0)
return ret;
return 0;
}
static int corsairpsu_request(struct corsairpsu_data *priv, u8 cmd, u8 rail, void *data)
{
int ret;
mutex_lock(&priv->lock);
switch (cmd) {
case PSU_CMD_RAIL_OUT_VOLTS:
case PSU_CMD_RAIL_AMPS:
case PSU_CMD_RAIL_WATTS:
ret = corsairpsu_usb_cmd(priv, 2, PSU_CMD_SELECT_RAIL, rail, NULL);
if (ret < 0)
goto cmd_fail;
break;
default:
break;
}
ret = corsairpsu_usb_cmd(priv, 3, cmd, 0, data);
cmd_fail:
mutex_unlock(&priv->lock);
return ret;
}
static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, long *val)
{
u8 data[REPLY_SIZE];
long tmp;
int ret;
ret = corsairpsu_request(priv, cmd, rail, data);
if (ret < 0)
return ret;
/*
* the biggest value here comes from the uptime command and to exceed MAXINT total uptime
* needs to be about 68 years, the rest are u16 values and the biggest value coming out of
* the LINEAR11 conversion are the watts values which are about 1200 for the strongest psu
* supported (HX1200i)
*/
tmp = ((long)data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0];
switch (cmd) {
case PSU_CMD_IN_VOLTS:
case PSU_CMD_IN_AMPS:
case PSU_CMD_RAIL_OUT_VOLTS:
case PSU_CMD_RAIL_AMPS:
case PSU_CMD_TEMP0:
case PSU_CMD_TEMP1:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000;
break;
case PSU_CMD_FAN:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF);
break;
case PSU_CMD_RAIL_WATTS:
case PSU_CMD_TOTAL_WATTS:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000000;
break;
case PSU_CMD_TOTAL_UPTIME:
case PSU_CMD_UPTIME:
*val = tmp;
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static umode_t corsairpsu_hwmon_ops_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_temp && (attr == hwmon_temp_input || attr == hwmon_temp_label))
return 0444;
else if (type == hwmon_fan && (attr == hwmon_fan_input || attr == hwmon_fan_label))
return 0444;
else if (type == hwmon_power && (attr == hwmon_power_input || attr == hwmon_power_label))
return 0444;
else if (type == hwmon_in && (attr == hwmon_in_input || attr == hwmon_in_label))
return 0444;
else if (type == hwmon_curr && (attr == hwmon_curr_input || attr == hwmon_curr_label))
return 0444;
return 0;
}
static int corsairpsu_hwmon_ops_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct corsairpsu_data *priv = dev_get_drvdata(dev);
int ret;
if (type == hwmon_temp && attr == hwmon_temp_input && channel < 2) {
ret = corsairpsu_get_value(priv, channel ? PSU_CMD_TEMP1 : PSU_CMD_TEMP0, channel,
val);
} else if (type == hwmon_fan && attr == hwmon_fan_input) {
ret = corsairpsu_get_value(priv, PSU_CMD_FAN, 0, val);
} else if (type == hwmon_power && attr == hwmon_power_input) {
switch (channel) {
case 0:
ret = corsairpsu_get_value(priv, PSU_CMD_TOTAL_WATTS, 0, val);
break;
case 1 ... 3:
ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_WATTS, channel - 1, val);
break;
default:
return -EOPNOTSUPP;
}
} else if (type == hwmon_in && attr == hwmon_in_input) {
switch (channel) {
case 0:
ret = corsairpsu_get_value(priv, PSU_CMD_IN_VOLTS, 0, val);
break;
case 1 ... 3:
ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_OUT_VOLTS, channel - 1, val);
break;
default:
return -EOPNOTSUPP;
}
} else if (type == hwmon_curr && attr == hwmon_curr_input) {
switch (channel) {
case 0:
ret = corsairpsu_get_value(priv, PSU_CMD_IN_AMPS, 0, val);
break;
case 1 ... 3:
ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_AMPS, channel - 1, val);
break;
default:
return -EOPNOTSUPP;
}
} else {
return -EOPNOTSUPP;
}
if (ret < 0)
return ret;
return 0;
}
static int corsairpsu_hwmon_ops_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
if (type == hwmon_temp && attr == hwmon_temp_label) {
*str = channel ? L_TEMP1 : L_TEMP0;
return 0;
} else if (type == hwmon_fan && attr == hwmon_fan_label) {
*str = L_FAN;
return 0;
} else if (type == hwmon_power && attr == hwmon_power_label && channel < 4) {
*str = label_watts[channel];
return 0;
} else if (type == hwmon_in && attr == hwmon_in_label && channel < 4) {
*str = label_volts[channel];
return 0;
} else if (type == hwmon_curr && attr == hwmon_curr_label && channel < 4) {
*str = label_amps[channel];
return 0;
}
return -EOPNOTSUPP;
}
static const struct hwmon_ops corsairpsu_hwmon_ops = {
.is_visible = corsairpsu_hwmon_ops_is_visible,
.read = corsairpsu_hwmon_ops_read,
.read_string = corsairpsu_hwmon_ops_read_string,
};
static const struct hwmon_channel_info *corsairpsu_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_LABEL,
HWMON_P_INPUT | HWMON_P_LABEL,
HWMON_P_INPUT | HWMON_P_LABEL,
HWMON_P_INPUT | HWMON_P_LABEL),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL),
NULL
};
static const struct hwmon_chip_info corsairpsu_chip_info = {
.ops = &corsairpsu_hwmon_ops,
.info = corsairpsu_info,
};
#ifdef CONFIG_DEBUG_FS
static void print_uptime(struct seq_file *seqf, u8 cmd)
{
struct corsairpsu_data *priv = seqf->private;
long val;
int ret;
ret = corsairpsu_get_value(priv, cmd, 0, &val);
if (ret < 0) {
seq_puts(seqf, "N/A\n");
return;
}
if (val > SECONDS_PER_DAY) {
seq_printf(seqf, "%ld day(s), %02ld:%02ld:%02ld\n", val / SECONDS_PER_DAY,
val % SECONDS_PER_DAY / SECONDS_PER_HOUR, val % SECONDS_PER_HOUR / 60,
val % 60);
return;
}
seq_printf(seqf, "%02ld:%02ld:%02ld\n", val % SECONDS_PER_DAY / SECONDS_PER_HOUR,
val % SECONDS_PER_HOUR / 60, val % 60);
}
static int uptime_show(struct seq_file *seqf, void *unused)
{
print_uptime(seqf, PSU_CMD_UPTIME);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(uptime);
static int uptime_total_show(struct seq_file *seqf, void *unused)
{
print_uptime(seqf, PSU_CMD_TOTAL_UPTIME);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(uptime_total);
static int vendor_show(struct seq_file *seqf, void *unused)
{
struct corsairpsu_data *priv = seqf->private;
seq_printf(seqf, "%s\n", priv->vendor);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(vendor);
static int product_show(struct seq_file *seqf, void *unused)
{
struct corsairpsu_data *priv = seqf->private;
seq_printf(seqf, "%s\n", priv->product);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(product);
static void corsairpsu_debugfs_init(struct corsairpsu_data *priv)
{
char name[32];
scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
priv->debugfs = debugfs_create_dir(name, NULL);
debugfs_create_file("uptime", 0444, priv->debugfs, priv, &uptime_fops);
debugfs_create_file("uptime_total", 0444, priv->debugfs, priv, &uptime_total_fops);
debugfs_create_file("vendor", 0444, priv->debugfs, priv, &vendor_fops);
debugfs_create_file("product", 0444, priv->debugfs, priv, &product_fops);
}
#else
static void corsairpsu_debugfs_init(struct corsairpsu_data *priv)
{
}
#endif
static int corsairpsu_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct corsairpsu_data *priv;
int ret;
priv = devm_kzalloc(&hdev->dev, sizeof(struct corsairpsu_data), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->cmd_buffer = devm_kmalloc(&hdev->dev, CMD_BUFFER_SIZE, GFP_KERNEL);
if (!priv->cmd_buffer)
return -ENOMEM;
ret = hid_parse(hdev);
if (ret)
return ret;
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret)
return ret;
ret = hid_hw_open(hdev);
if (ret)
goto fail_and_stop;
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
mutex_init(&priv->lock);
init_completion(&priv->wait_completion);
hid_device_io_start(hdev);
ret = corsairpsu_init(priv);
if (ret < 0) {
dev_err(&hdev->dev, "unable to initialize device (%d)\n", ret);
goto fail_and_stop;
}
ret = corsairpsu_fwinfo(priv);
if (ret < 0) {
dev_err(&hdev->dev, "unable to query firmware (%d)\n", ret);
goto fail_and_stop;
}
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsairpsu", priv,
&corsairpsu_chip_info, 0);
if (IS_ERR(priv->hwmon_dev)) {
ret = PTR_ERR(priv->hwmon_dev);
goto fail_and_close;
}
corsairpsu_debugfs_init(priv);
return 0;
fail_and_close:
hid_hw_close(hdev);
fail_and_stop:
hid_hw_stop(hdev);
return ret;
}
static void corsairpsu_remove(struct hid_device *hdev)
{
struct corsairpsu_data *priv = hid_get_drvdata(hdev);
debugfs_remove_recursive(priv->debugfs);
hwmon_device_unregister(priv->hwmon_dev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
static int corsairpsu_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
int size)
{
struct corsairpsu_data *priv = hid_get_drvdata(hdev);
if (completion_done(&priv->wait_completion))
return 0;
memcpy(priv->cmd_buffer, data, min(CMD_BUFFER_SIZE, size));
complete(&priv->wait_completion);
return 0;
}
static const struct hid_device_id corsairpsu_idtable[] = {
{ HID_USB_DEVICE(0x1b1c, 0x1c03) }, /* Corsair HX550i */
{ HID_USB_DEVICE(0x1b1c, 0x1c04) }, /* Corsair HX650i */
{ HID_USB_DEVICE(0x1b1c, 0x1c05) }, /* Corsair HX750i */
{ HID_USB_DEVICE(0x1b1c, 0x1c06) }, /* Corsair HX850i */
{ HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i */
{ HID_USB_DEVICE(0x1b1c, 0x1c08) }, /* Corsair HX1200i */
{ HID_USB_DEVICE(0x1b1c, 0x1c09) }, /* Corsair RM550i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0a) }, /* Corsair RM650i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0b) }, /* Corsair RM750i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */
{ HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */
{ },
};
MODULE_DEVICE_TABLE(hid, corsairpsu_idtable);
static struct hid_driver corsairpsu_driver = {
.name = DRIVER_NAME,
.id_table = corsairpsu_idtable,
.probe = corsairpsu_probe,
.remove = corsairpsu_remove,
.raw_event = corsairpsu_raw_event,
};
module_hid_driver(corsairpsu_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wilken Gottwalt <wilken.gottwalt@posteo.net>");
MODULE_DESCRIPTION("Linux driver for Corsair power supplies with HID sensors interface");

View File

@ -10,7 +10,7 @@
* 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
* This drive supports reporting the temperature 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

View File

@ -240,7 +240,7 @@ static int get_sensor_index_attr(const char *name, u32 *index, char *attr)
if (err)
return err;
strncpy(attr, dash_pos + 1, MAX_ATTR_LEN);
strscpy(attr, dash_pos + 1, MAX_ATTR_LEN);
return 0;
}

View File

@ -169,7 +169,7 @@ static const struct of_device_id iio_hwmon_of_match[] = {
};
MODULE_DEVICE_TABLE(of, iio_hwmon_of_match);
static struct platform_driver __refdata iio_hwmon_driver = {
static struct platform_driver iio_hwmon_driver = {
.driver = {
.name = "iio_hwmon",
.of_match_table = iio_hwmon_of_match,

View File

@ -139,7 +139,7 @@ static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel)
(ina->reg_config & INA3221_CONFIG_CHx_EN(channel));
}
/**
/*
* Helper function to return the resistor value for current summation.
*
* There is a condition to calculate current summation -- all the shunt
@ -489,7 +489,7 @@ static int ina3221_write_enable(struct device *dev, int channel, bool enable)
/* For enabling routine, increase refcount and resume() at first */
if (enable) {
ret = pm_runtime_get_sync(ina->pm_dev);
ret = pm_runtime_resume_and_get(ina->pm_dev);
if (ret < 0) {
dev_err(dev, "Failed to get PM runtime\n");
return ret;

971
drivers/hwmon/ltc2992.c Normal file
View File

@ -0,0 +1,971 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/*
* LTC2992 - Dual Wide Range Power Monitor
*
* Copyright 2020 Analog Devices Inc.
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/gpio/driver.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
#define LTC2992_CTRLB 0x01
#define LTC2992_FAULT1 0x03
#define LTC2992_POWER1 0x05
#define LTC2992_POWER1_MAX 0x08
#define LTC2992_POWER1_MIN 0x0B
#define LTC2992_POWER1_MAX_THRESH 0x0E
#define LTC2992_POWER1_MIN_THRESH 0x11
#define LTC2992_DSENSE1 0x14
#define LTC2992_DSENSE1_MAX 0x16
#define LTC2992_DSENSE1_MIN 0x18
#define LTC2992_DSENSE1_MAX_THRESH 0x1A
#define LTC2992_DSENSE1_MIN_THRESH 0x1C
#define LTC2992_SENSE1 0x1E
#define LTC2992_SENSE1_MAX 0x20
#define LTC2992_SENSE1_MIN 0x22
#define LTC2992_SENSE1_MAX_THRESH 0x24
#define LTC2992_SENSE1_MIN_THRESH 0x26
#define LTC2992_G1 0x28
#define LTC2992_G1_MAX 0x2A
#define LTC2992_G1_MIN 0x2C
#define LTC2992_G1_MAX_THRESH 0x2E
#define LTC2992_G1_MIN_THRESH 0x30
#define LTC2992_FAULT2 0x35
#define LTC2992_G2 0x5A
#define LTC2992_G2_MAX 0x5C
#define LTC2992_G2_MIN 0x5E
#define LTC2992_G2_MAX_THRESH 0x60
#define LTC2992_G2_MIN_THRESH 0x62
#define LTC2992_G3 0x64
#define LTC2992_G3_MAX 0x66
#define LTC2992_G3_MIN 0x68
#define LTC2992_G3_MAX_THRESH 0x6A
#define LTC2992_G3_MIN_THRESH 0x6C
#define LTC2992_G4 0x6E
#define LTC2992_G4_MAX 0x70
#define LTC2992_G4_MIN 0x72
#define LTC2992_G4_MAX_THRESH 0x74
#define LTC2992_G4_MIN_THRESH 0x76
#define LTC2992_FAULT3 0x92
#define LTC2992_GPIO_STATUS 0x95
#define LTC2992_GPIO_IO_CTRL 0x96
#define LTC2992_GPIO_CTRL 0x97
#define LTC2992_POWER(x) (LTC2992_POWER1 + ((x) * 0x32))
#define LTC2992_POWER_MAX(x) (LTC2992_POWER1_MAX + ((x) * 0x32))
#define LTC2992_POWER_MIN(x) (LTC2992_POWER1_MIN + ((x) * 0x32))
#define LTC2992_POWER_MAX_THRESH(x) (LTC2992_POWER1_MAX_THRESH + ((x) * 0x32))
#define LTC2992_POWER_MIN_THRESH(x) (LTC2992_POWER1_MIN_THRESH + ((x) * 0x32))
#define LTC2992_DSENSE(x) (LTC2992_DSENSE1 + ((x) * 0x32))
#define LTC2992_DSENSE_MAX(x) (LTC2992_DSENSE1_MAX + ((x) * 0x32))
#define LTC2992_DSENSE_MIN(x) (LTC2992_DSENSE1_MIN + ((x) * 0x32))
#define LTC2992_DSENSE_MAX_THRESH(x) (LTC2992_DSENSE1_MAX_THRESH + ((x) * 0x32))
#define LTC2992_DSENSE_MIN_THRESH(x) (LTC2992_DSENSE1_MIN_THRESH + ((x) * 0x32))
#define LTC2992_SENSE(x) (LTC2992_SENSE1 + ((x) * 0x32))
#define LTC2992_SENSE_MAX(x) (LTC2992_SENSE1_MAX + ((x) * 0x32))
#define LTC2992_SENSE_MIN(x) (LTC2992_SENSE1_MIN + ((x) * 0x32))
#define LTC2992_SENSE_MAX_THRESH(x) (LTC2992_SENSE1_MAX_THRESH + ((x) * 0x32))
#define LTC2992_SENSE_MIN_THRESH(x) (LTC2992_SENSE1_MIN_THRESH + ((x) * 0x32))
#define LTC2992_POWER_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32))
#define LTC2992_SENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32))
#define LTC2992_DSENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32))
/* CTRLB register bitfields */
#define LTC2992_RESET_HISTORY BIT(3)
/* FAULT1 FAULT2 registers common bitfields */
#define LTC2992_POWER_FAULT_MSK(x) (BIT(6) << (x))
#define LTC2992_DSENSE_FAULT_MSK(x) (BIT(4) << (x))
#define LTC2992_SENSE_FAULT_MSK(x) (BIT(2) << (x))
/* FAULT1 bitfields */
#define LTC2992_GPIO1_FAULT_MSK(x) (BIT(0) << (x))
/* FAULT2 bitfields */
#define LTC2992_GPIO2_FAULT_MSK(x) (BIT(0) << (x))
/* FAULT3 bitfields */
#define LTC2992_GPIO3_FAULT_MSK(x) (BIT(6) << (x))
#define LTC2992_GPIO4_FAULT_MSK(x) (BIT(4) << (x))
#define LTC2992_IADC_NANOV_LSB 12500
#define LTC2992_VADC_UV_LSB 25000
#define LTC2992_VADC_GPIO_UV_LSB 500
#define LTC2992_GPIO_NR 4
#define LTC2992_GPIO1_BIT 7
#define LTC2992_GPIO2_BIT 6
#define LTC2992_GPIO3_BIT 0
#define LTC2992_GPIO4_BIT 6
#define LTC2992_GPIO_BIT(x) (LTC2992_GPIO_NR - (x) - 1)
struct ltc2992_state {
struct i2c_client *client;
struct gpio_chip gc;
struct mutex gpio_mutex; /* lock for gpio access */
const char *gpio_names[LTC2992_GPIO_NR];
struct regmap *regmap;
u32 r_sense_uohm[2];
};
struct ltc2992_gpio_regs {
u8 data;
u8 max;
u8 min;
u8 max_thresh;
u8 min_thresh;
u8 alarm;
u8 min_alarm_msk;
u8 max_alarm_msk;
u8 ctrl;
u8 ctrl_bit;
};
static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = {
{
.data = LTC2992_G1,
.max = LTC2992_G1_MAX,
.min = LTC2992_G1_MIN,
.max_thresh = LTC2992_G1_MAX_THRESH,
.min_thresh = LTC2992_G1_MIN_THRESH,
.alarm = LTC2992_FAULT1,
.min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0),
.max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1),
.ctrl = LTC2992_GPIO_IO_CTRL,
.ctrl_bit = LTC2992_GPIO1_BIT,
},
{
.data = LTC2992_G2,
.max = LTC2992_G2_MAX,
.min = LTC2992_G2_MIN,
.max_thresh = LTC2992_G2_MAX_THRESH,
.min_thresh = LTC2992_G2_MIN_THRESH,
.alarm = LTC2992_FAULT2,
.min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0),
.max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1),
.ctrl = LTC2992_GPIO_IO_CTRL,
.ctrl_bit = LTC2992_GPIO2_BIT,
},
{
.data = LTC2992_G3,
.max = LTC2992_G3_MAX,
.min = LTC2992_G3_MIN,
.max_thresh = LTC2992_G3_MAX_THRESH,
.min_thresh = LTC2992_G3_MIN_THRESH,
.alarm = LTC2992_FAULT3,
.min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0),
.max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1),
.ctrl = LTC2992_GPIO_IO_CTRL,
.ctrl_bit = LTC2992_GPIO3_BIT,
},
{
.data = LTC2992_G4,
.max = LTC2992_G4_MAX,
.min = LTC2992_G4_MIN,
.max_thresh = LTC2992_G4_MAX_THRESH,
.min_thresh = LTC2992_G4_MIN_THRESH,
.alarm = LTC2992_FAULT3,
.min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0),
.max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1),
.ctrl = LTC2992_GPIO_CTRL,
.ctrl_bit = LTC2992_GPIO4_BIT,
},
};
static const char *ltc2992_gpio_names[LTC2992_GPIO_NR] = {
"GPIO1", "GPIO2", "GPIO3", "GPIO4",
};
static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len)
{
u8 regvals[4];
int val;
int ret;
int i;
ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len);
if (ret < 0)
return ret;
val = 0;
for (i = 0; i < reg_len; i++)
val |= regvals[reg_len - i - 1] << (i * 8);
return val;
}
static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len, u32 val)
{
u8 regvals[4];
int i;
for (i = 0; i < reg_len; i++)
regvals[reg_len - i - 1] = (val >> (i * 8)) & 0xFF;
return regmap_bulk_write(st->regmap, addr, regvals, reg_len);
}
static int ltc2992_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_status;
int reg;
mutex_lock(&st->gpio_mutex);
reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
mutex_unlock(&st->gpio_mutex);
if (reg < 0)
return reg;
gpio_status = reg;
return !test_bit(LTC2992_GPIO_BIT(offset), &gpio_status);
}
static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_status;
unsigned int gpio_nr;
int reg;
mutex_lock(&st->gpio_mutex);
reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1);
mutex_unlock(&st->gpio_mutex);
if (reg < 0)
return reg;
gpio_status = reg;
gpio_nr = 0;
for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) {
if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status))
set_bit(gpio_nr, bits);
}
return 0;
}
static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_ctrl;
int reg;
mutex_lock(&st->gpio_mutex);
reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1);
if (reg < 0) {
mutex_unlock(&st->gpio_mutex);
return;
}
gpio_ctrl = reg;
assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value);
ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl);
mutex_unlock(&st->gpio_mutex);
}
static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
struct ltc2992_state *st = gpiochip_get_data(chip);
unsigned long gpio_ctrl_io = 0;
unsigned long gpio_ctrl = 0;
unsigned int gpio_nr;
for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) {
if (gpio_nr < 3)
assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl_io, true);
if (gpio_nr == 3)
assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl, true);
}
mutex_lock(&st->gpio_mutex);
ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
mutex_unlock(&st->gpio_mutex);
}
static int ltc2992_config_gpio(struct ltc2992_state *st)
{
const char *name = dev_name(&st->client->dev);
char *gpio_name;
int ret;
int i;
ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, 0);
if (ret < 0)
return ret;
mutex_init(&st->gpio_mutex);
for (i = 0; i < ARRAY_SIZE(st->gpio_names); i++) {
gpio_name = devm_kasprintf(&st->client->dev, GFP_KERNEL, "ltc2992-%x-%s",
st->client->addr, ltc2992_gpio_names[i]);
if (!gpio_name)
return -ENOMEM;
st->gpio_names[i] = gpio_name;
}
st->gc.label = name;
st->gc.parent = &st->client->dev;
st->gc.owner = THIS_MODULE;
st->gc.base = -1;
st->gc.names = st->gpio_names;
st->gc.ngpio = ARRAY_SIZE(st->gpio_names);
st->gc.get = ltc2992_gpio_get;
st->gc.get_multiple = ltc2992_gpio_get_multiple;
st->gc.set = ltc2992_gpio_set;
st->gc.set_multiple = ltc2992_gpio_set_multiple;
ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st);
if (ret)
dev_err(&st->client->dev, "GPIO registering failed (%d)\n", ret);
return ret;
}
static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
const struct ltc2992_state *st = data;
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_in_reset_history:
return 0200;
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_input:
case hwmon_in_lowest:
case hwmon_in_highest:
case hwmon_in_min_alarm:
case hwmon_in_max_alarm:
return 0444;
case hwmon_in_min:
case hwmon_in_max:
return 0644;
}
break;
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
case hwmon_curr_lowest:
case hwmon_curr_highest:
case hwmon_curr_min_alarm:
case hwmon_curr_max_alarm:
if (st->r_sense_uohm[channel])
return 0444;
break;
case hwmon_curr_min:
case hwmon_curr_max:
if (st->r_sense_uohm[channel])
return 0644;
break;
}
break;
case hwmon_power:
switch (attr) {
case hwmon_power_input:
case hwmon_power_input_lowest:
case hwmon_power_input_highest:
case hwmon_power_min_alarm:
case hwmon_power_max_alarm:
if (st->r_sense_uohm[channel])
return 0444;
break;
case hwmon_power_min:
case hwmon_power_max:
if (st->r_sense_uohm[channel])
return 0644;
break;
}
break;
default:
break;
}
return 0;
}
static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long *val)
{
int reg_val;
reg_val = ltc2992_read_reg(st, reg, 2);
if (reg_val < 0)
return reg_val;
reg_val = reg_val >> 4;
*val = DIV_ROUND_CLOSEST(reg_val * scale, 1000);
return 0;
}
static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val)
{
val = DIV_ROUND_CLOSEST(val * 1000, scale);
val = val << 4;
return ltc2992_write_reg(st, reg, 2, val);
}
static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val)
{
int reg_val;
u32 mask;
if (attr == hwmon_in_max_alarm)
mask = ltc2992_gpio_addr_map[nr_gpio].max_alarm_msk;
else
mask = ltc2992_gpio_addr_map[nr_gpio].min_alarm_msk;
reg_val = ltc2992_read_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1);
if (reg_val < 0)
return reg_val;
*val = !!(reg_val & mask);
reg_val &= ~mask;
return ltc2992_write_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1, reg_val);
}
static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long *val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_in_input:
reg = ltc2992_gpio_addr_map[nr_gpio].data;
break;
case hwmon_in_lowest:
reg = ltc2992_gpio_addr_map[nr_gpio].min;
break;
case hwmon_in_highest:
reg = ltc2992_gpio_addr_map[nr_gpio].max;
break;
case hwmon_in_min:
reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
break;
case hwmon_in_max:
reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
break;
case hwmon_in_min_alarm:
case hwmon_in_max_alarm:
return ltc2992_read_gpio_alarm(st, nr_gpio, attr, val);
default:
return -EOPNOTSUPP;
}
return ltc2992_get_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
}
static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
{
int reg_val;
u32 mask;
if (attr == hwmon_in_max_alarm)
mask = LTC2992_SENSE_FAULT_MSK(1);
else
mask = LTC2992_SENSE_FAULT_MSK(0);
reg_val = ltc2992_read_reg(st, LTC2992_SENSE_FAULT(channel), 1);
if (reg_val < 0)
return reg_val;
*val = !!(reg_val & mask);
reg_val &= ~mask;
return ltc2992_write_reg(st, LTC2992_SENSE_FAULT(channel), 1, reg_val);
}
static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
if (channel > 1)
return ltc2992_read_gpios_in(dev, attr, channel - 2, val);
switch (attr) {
case hwmon_in_input:
reg = LTC2992_SENSE(channel);
break;
case hwmon_in_lowest:
reg = LTC2992_SENSE_MIN(channel);
break;
case hwmon_in_highest:
reg = LTC2992_SENSE_MAX(channel);
break;
case hwmon_in_min:
reg = LTC2992_SENSE_MIN_THRESH(channel);
break;
case hwmon_in_max:
reg = LTC2992_SENSE_MAX_THRESH(channel);
break;
case hwmon_in_min_alarm:
case hwmon_in_max_alarm:
return ltc2992_read_in_alarm(st, channel, val, attr);
default:
return -EOPNOTSUPP;
}
return ltc2992_get_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
}
static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
{
int reg_val;
reg_val = ltc2992_read_reg(st, reg, 2);
if (reg_val < 0)
return reg_val;
reg_val = reg_val >> 4;
*val = DIV_ROUND_CLOSEST(reg_val * LTC2992_IADC_NANOV_LSB, st->r_sense_uohm[channel]);
return 0;
}
static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val)
{
u32 reg_val;
reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB);
reg_val = reg_val << 4;
return ltc2992_write_reg(st, reg, 2, reg_val);
}
static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
{
int reg_val;
u32 mask;
if (attr == hwmon_curr_max_alarm)
mask = LTC2992_DSENSE_FAULT_MSK(1);
else
mask = LTC2992_DSENSE_FAULT_MSK(0);
reg_val = ltc2992_read_reg(st, LTC2992_DSENSE_FAULT(channel), 1);
if (reg_val < 0)
return reg_val;
*val = !!(reg_val & mask);
reg_val &= ~mask;
return ltc2992_write_reg(st, LTC2992_DSENSE_FAULT(channel), 1, reg_val);
}
static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_curr_input:
reg = LTC2992_DSENSE(channel);
break;
case hwmon_curr_lowest:
reg = LTC2992_DSENSE_MIN(channel);
break;
case hwmon_curr_highest:
reg = LTC2992_DSENSE_MAX(channel);
break;
case hwmon_curr_min:
reg = LTC2992_DSENSE_MIN_THRESH(channel);
break;
case hwmon_curr_max:
reg = LTC2992_DSENSE_MAX_THRESH(channel);
break;
case hwmon_curr_min_alarm:
case hwmon_curr_max_alarm:
return ltc2992_read_curr_alarm(st, channel, val, attr);
default:
return -EOPNOTSUPP;
}
return ltc2992_get_current(st, reg, channel, val);
}
static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val)
{
int reg_val;
reg_val = ltc2992_read_reg(st, reg, 3);
if (reg_val < 0)
return reg_val;
*val = mul_u64_u32_div(reg_val, LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB,
st->r_sense_uohm[channel] * 1000);
return 0;
}
static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val)
{
u32 reg_val;
reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000,
LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB);
return ltc2992_write_reg(st, reg, 3, reg_val);
}
static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr)
{
int reg_val;
u32 mask;
if (attr == hwmon_power_max_alarm)
mask = LTC2992_POWER_FAULT_MSK(1);
else
mask = LTC2992_POWER_FAULT_MSK(0);
reg_val = ltc2992_read_reg(st, LTC2992_POWER_FAULT(channel), 1);
if (reg_val < 0)
return reg_val;
*val = !!(reg_val & mask);
reg_val &= ~mask;
return ltc2992_write_reg(st, LTC2992_POWER_FAULT(channel), 1, reg_val);
}
static int ltc2992_read_power(struct device *dev, u32 attr, int channel, long *val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_power_input:
reg = LTC2992_POWER(channel);
break;
case hwmon_power_input_lowest:
reg = LTC2992_POWER_MIN(channel);
break;
case hwmon_power_input_highest:
reg = LTC2992_POWER_MAX(channel);
break;
case hwmon_power_min:
reg = LTC2992_POWER_MIN_THRESH(channel);
break;
case hwmon_power_max:
reg = LTC2992_POWER_MAX_THRESH(channel);
break;
case hwmon_power_min_alarm:
case hwmon_power_max_alarm:
return ltc2992_read_power_alarm(st, channel, val, attr);
default:
return -EOPNOTSUPP;
}
return ltc2992_get_power(st, reg, channel, val);
}
static int ltc2992_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
{
switch (type) {
case hwmon_in:
return ltc2992_read_in(dev, attr, channel, val);
case hwmon_curr:
return ltc2992_read_curr(dev, attr, channel, val);
case hwmon_power:
return ltc2992_read_power(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
}
static int ltc2992_write_curr(struct device *dev, u32 attr, int channel, long val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_curr_min:
reg = LTC2992_DSENSE_MIN_THRESH(channel);
break;
case hwmon_curr_max:
reg = LTC2992_DSENSE_MAX_THRESH(channel);
break;
default:
return -EOPNOTSUPP;
}
return ltc2992_set_current(st, reg, channel, val);
}
static int ltc2992_write_gpios_in(struct device *dev, u32 attr, int nr_gpio, long val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_in_min:
reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh;
break;
case hwmon_in_max:
reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh;
break;
default:
return -EOPNOTSUPP;
}
return ltc2992_set_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val);
}
static int ltc2992_write_in(struct device *dev, u32 attr, int channel, long val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
if (channel > 1)
return ltc2992_write_gpios_in(dev, attr, channel - 2, val);
switch (attr) {
case hwmon_in_min:
reg = LTC2992_SENSE_MIN_THRESH(channel);
break;
case hwmon_in_max:
reg = LTC2992_SENSE_MAX_THRESH(channel);
break;
default:
return -EOPNOTSUPP;
}
return ltc2992_set_voltage(st, reg, LTC2992_VADC_UV_LSB, val);
}
static int ltc2992_write_power(struct device *dev, u32 attr, int channel, long val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
u32 reg;
switch (attr) {
case hwmon_power_min:
reg = LTC2992_POWER_MIN_THRESH(channel);
break;
case hwmon_power_max:
reg = LTC2992_POWER_MAX_THRESH(channel);
break;
default:
return -EOPNOTSUPP;
}
return ltc2992_set_power(st, reg, channel, val);
}
static int ltc2992_write_chip(struct device *dev, u32 attr, int channel, long val)
{
struct ltc2992_state *st = dev_get_drvdata(dev);
switch (attr) {
case hwmon_chip_in_reset_history:
return regmap_update_bits(st->regmap, LTC2992_CTRLB, LTC2992_RESET_HISTORY,
LTC2992_RESET_HISTORY);
default:
return -EOPNOTSUPP;
}
}
static int ltc2992_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long val)
{
switch (type) {
case hwmon_chip:
return ltc2992_write_chip(dev, attr, channel, val);
case hwmon_in:
return ltc2992_write_in(dev, attr, channel, val);
case hwmon_curr:
return ltc2992_write_curr(dev, attr, channel, val);
case hwmon_power:
return ltc2992_write_power(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
}
static const struct hwmon_ops ltc2992_hwmon_ops = {
.is_visible = ltc2992_is_visible,
.read = ltc2992_read,
.write = ltc2992_write,
};
static const u32 ltc2992_chip_config[] = {
HWMON_C_IN_RESET_HISTORY,
0
};
static const struct hwmon_channel_info ltc2992_chip = {
.type = hwmon_chip,
.config = ltc2992_chip_config,
};
static const u32 ltc2992_in_config[] = {
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX |
HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
0
};
static const struct hwmon_channel_info ltc2992_in = {
.type = hwmon_in,
.config = ltc2992_in_config,
};
static const u32 ltc2992_curr_config[] = {
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX |
HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM,
0
};
static const struct hwmon_channel_info ltc2992_curr = {
.type = hwmon_curr,
.config = ltc2992_curr_config,
};
static const u32 ltc2992_power_config[] = {
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX |
HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM,
0
};
static const struct hwmon_channel_info ltc2992_power = {
.type = hwmon_power,
.config = ltc2992_power_config,
};
static const struct hwmon_channel_info *ltc2992_info[] = {
&ltc2992_chip,
&ltc2992_in,
&ltc2992_curr,
&ltc2992_power,
NULL
};
static const struct hwmon_chip_info ltc2992_chip_info = {
.ops = &ltc2992_hwmon_ops,
.info = ltc2992_info,
};
static const struct regmap_config ltc2992_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xE8,
};
static int ltc2992_parse_dt(struct ltc2992_state *st)
{
struct fwnode_handle *fwnode;
struct fwnode_handle *child;
u32 addr;
u32 val;
int ret;
fwnode = dev_fwnode(&st->client->dev);
fwnode_for_each_available_child_node(fwnode, child) {
ret = fwnode_property_read_u32(child, "reg", &addr);
if (ret < 0)
return ret;
if (addr > 1)
return -EINVAL;
ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val);
if (!ret)
st->r_sense_uohm[addr] = val;
}
return 0;
}
static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *hwmon_dev;
struct ltc2992_state *st;
int ret;
st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
st->client = client;
st->regmap = devm_regmap_init_i2c(client, &ltc2992_regmap_config);
if (IS_ERR(st->regmap))
return PTR_ERR(st->regmap);
ret = ltc2992_parse_dt(st);
if (ret < 0)
return ret;
ret = ltc2992_config_gpio(st);
if (ret < 0)
return ret;
hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st,
&ltc2992_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct of_device_id ltc2992_of_match[] = {
{ .compatible = "adi,ltc2992" },
{ }
};
MODULE_DEVICE_TABLE(of, ltc2992_of_match);
static const struct i2c_device_id ltc2992_i2c_id[] = {
{"ltc2992", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id);
static struct i2c_driver ltc2992_i2c_driver = {
.driver = {
.name = "ltc2992",
.of_match_table = ltc2992_of_match,
},
.probe = ltc2992_i2c_probe,
.id_table = ltc2992_i2c_id,
};
module_i2c_driver(ltc2992_i2c_driver);
MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>");
MODULE_DESCRIPTION("Hwmon driver for Linear Technology 2992");
MODULE_LICENSE("Dual BSD/GPL");

352
drivers/hwmon/max127.c Normal file
View File

@ -0,0 +1,352 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for MAX127.
*
* Copyright (c) 2020 Facebook Inc.
*/
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
/*
* MAX127 Control Byte. Refer to MAX127 datasheet, Table 1 "Control-Byte
* Format" for details.
*/
#define MAX127_CTRL_START BIT(7)
#define MAX127_CTRL_SEL_SHIFT 4
#define MAX127_CTRL_RNG BIT(3)
#define MAX127_CTRL_BIP BIT(2)
#define MAX127_CTRL_PD1 BIT(1)
#define MAX127_CTRL_PD0 BIT(0)
#define MAX127_NUM_CHANNELS 8
#define MAX127_SET_CHANNEL(ch) (((ch) & 7) << MAX127_CTRL_SEL_SHIFT)
/*
* MAX127 channel input ranges. Refer to MAX127 datasheet, Table 3 "Range
* and Polarity Selection" for details.
*/
#define MAX127_FULL_RANGE 10000 /* 10V */
#define MAX127_HALF_RANGE 5000 /* 5V */
/*
* MAX127 returns 2 bytes at read:
* - the first byte contains data[11:4].
* - the second byte contains data[3:0] (MSB) and 4 dummy 0s (LSB).
* Refer to MAX127 datasheet, "Read a Conversion (Read Cycle)" section
* for details.
*/
#define MAX127_DATA_LEN 2
#define MAX127_DATA_SHIFT 4
#define MAX127_SIGN_BIT BIT(11)
struct max127_data {
struct mutex lock;
struct i2c_client *client;
u8 ctrl_byte[MAX127_NUM_CHANNELS];
};
static int max127_select_channel(struct i2c_client *client, u8 ctrl_byte)
{
int status;
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = sizeof(ctrl_byte),
.buf = &ctrl_byte,
};
status = i2c_transfer(client->adapter, &msg, 1);
if (status < 0)
return status;
if (status != 1)
return -EIO;
return 0;
}
static int max127_read_channel(struct i2c_client *client, long *val)
{
int status;
u8 i2c_data[MAX127_DATA_LEN];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.len = sizeof(i2c_data),
.buf = i2c_data,
};
status = i2c_transfer(client->adapter, &msg, 1);
if (status < 0)
return status;
if (status != 1)
return -EIO;
*val = (i2c_data[1] >> MAX127_DATA_SHIFT) |
((u16)i2c_data[0] << MAX127_DATA_SHIFT);
return 0;
}
static long max127_process_raw(u8 ctrl_byte, long raw)
{
long scale, weight;
/*
* MAX127's data coding is binary in unipolar mode with 1 LSB =
* (Full-Scale/4096) and twos complement binary in bipolar mode
* with 1 LSB = [(2 x |FS|)/4096].
* Refer to MAX127 datasheet, "Transfer Function" section for
* details.
*/
scale = (ctrl_byte & MAX127_CTRL_RNG) ? MAX127_FULL_RANGE :
MAX127_HALF_RANGE;
if (ctrl_byte & MAX127_CTRL_BIP) {
weight = (raw & MAX127_SIGN_BIT);
raw &= ~MAX127_SIGN_BIT;
raw -= weight;
raw *= 2;
}
return raw * scale / 4096;
}
static int max127_read_input(struct max127_data *data, int channel, long *val)
{
long raw;
int status;
struct i2c_client *client = data->client;
u8 ctrl_byte = data->ctrl_byte[channel];
mutex_lock(&data->lock);
status = max127_select_channel(client, ctrl_byte);
if (status)
goto exit;
status = max127_read_channel(client, &raw);
if (status)
goto exit;
*val = max127_process_raw(ctrl_byte, raw);
exit:
mutex_unlock(&data->lock);
return status;
}
static int max127_read_min(struct max127_data *data, int channel, long *val)
{
u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3;
static const int min_input_map[4] = {
0, /* RNG=0, BIP=0 */
-MAX127_HALF_RANGE, /* RNG=0, BIP=1 */
0, /* RNG=1, BIP=0 */
-MAX127_FULL_RANGE, /* RNG=1, BIP=1 */
};
*val = min_input_map[rng_bip];
return 0;
}
static int max127_read_max(struct max127_data *data, int channel, long *val)
{
u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3;
static const int max_input_map[4] = {
MAX127_HALF_RANGE, /* RNG=0, BIP=0 */
MAX127_HALF_RANGE, /* RNG=0, BIP=1 */
MAX127_FULL_RANGE, /* RNG=1, BIP=0 */
MAX127_FULL_RANGE, /* RNG=1, BIP=1 */
};
*val = max_input_map[rng_bip];
return 0;
}
static int max127_write_min(struct max127_data *data, int channel, long val)
{
u8 ctrl;
mutex_lock(&data->lock);
ctrl = data->ctrl_byte[channel];
if (val <= -MAX127_FULL_RANGE) {
ctrl |= (MAX127_CTRL_RNG | MAX127_CTRL_BIP);
} else if (val < 0) {
ctrl |= MAX127_CTRL_BIP;
ctrl &= ~MAX127_CTRL_RNG;
} else {
ctrl &= ~MAX127_CTRL_BIP;
}
data->ctrl_byte[channel] = ctrl;
mutex_unlock(&data->lock);
return 0;
}
static int max127_write_max(struct max127_data *data, int channel, long val)
{
mutex_lock(&data->lock);
if (val >= MAX127_FULL_RANGE)
data->ctrl_byte[channel] |= MAX127_CTRL_RNG;
else
data->ctrl_byte[channel] &= ~MAX127_CTRL_RNG;
mutex_unlock(&data->lock);
return 0;
}
static umode_t max127_is_visible(const void *_data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_in) {
switch (attr) {
case hwmon_in_input:
return 0444;
case hwmon_in_min:
case hwmon_in_max:
return 0644;
default:
break;
}
}
return 0;
}
static int max127_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
int status;
struct max127_data *data = dev_get_drvdata(dev);
if (type != hwmon_in)
return -EOPNOTSUPP;
switch (attr) {
case hwmon_in_input:
status = max127_read_input(data, channel, val);
break;
case hwmon_in_min:
status = max127_read_min(data, channel, val);
break;
case hwmon_in_max:
status = max127_read_max(data, channel, val);
break;
default:
status = -EOPNOTSUPP;
break;
}
return status;
}
static int max127_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
int status;
struct max127_data *data = dev_get_drvdata(dev);
if (type != hwmon_in)
return -EOPNOTSUPP;
switch (attr) {
case hwmon_in_min:
status = max127_write_min(data, channel, val);
break;
case hwmon_in_max:
status = max127_write_max(data, channel, val);
break;
default:
status = -EOPNOTSUPP;
break;
}
return status;
}
static const struct hwmon_ops max127_hwmon_ops = {
.is_visible = max127_is_visible,
.read = max127_read,
.write = max127_write,
};
static const struct hwmon_channel_info *max127_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX,
HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX),
NULL,
};
static const struct hwmon_chip_info max127_chip_info = {
.ops = &max127_hwmon_ops,
.info = max127_info,
};
static int max127_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int i;
struct device *hwmon_dev;
struct max127_data *data;
struct device *dev = &client->dev;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
for (i = 0; i < ARRAY_SIZE(data->ctrl_byte); i++)
data->ctrl_byte[i] = (MAX127_CTRL_START |
MAX127_SET_CHANNEL(i));
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data,
&max127_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id max127_id[] = {
{ "max127", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max127_id);
static struct i2c_driver max127_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "max127",
},
.probe = max127_probe,
.id_table = max127_id,
};
module_i2c_driver(max127_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Choi <mikechoi@fb.com>");
MODULE_AUTHOR("Tao Ren <rentao.bupt@gmail.com>");
MODULE_DESCRIPTION("MAX127 Hardware Monitoring driver");

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* nct6683 - Driver for the hardware monitoring functionality of
* Nuvoton NCT6683D eSIO
* Nuvoton NCT6683D/NCT6687D eSIO
*
* Copyright (C) 2013 Guenter Roeck <linux@roeck-us.net>
*
@ -12,6 +12,7 @@
*
* Chip #vin #fan #pwm #temp chip ID
* nct6683d 21(1) 16 8 32(1) 0xc730
* nct6687d 21(1) 16 8 32(1) 0xd590
*
* Notes:
* (1) Total number of vin and temp inputs is 32.
@ -32,7 +33,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
enum kinds { nct6683 };
enum kinds { nct6683, nct6687 };
static bool force;
module_param(force, bool, 0);
@ -40,10 +41,12 @@ MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors");
static const char * const nct6683_device_names[] = {
"nct6683",
"nct6687",
};
static const char * const nct6683_chip_names[] = {
"NCT6683D",
"NCT6687D",
};
#define DRVNAME "nct6683"
@ -63,6 +66,7 @@ static const char * const nct6683_chip_names[] = {
#define SIO_NCT6681_ID 0xb270 /* for later */
#define SIO_NCT6683_ID 0xc730
#define SIO_NCT6687_ID 0xd590
#define SIO_ID_MASK 0xFFF0
static inline void
@ -164,6 +168,7 @@ superio_exit(int ioreg)
#define NCT6683_REG_CUSTOMER_ID 0x602
#define NCT6683_CUSTOMER_ID_INTEL 0x805
#define NCT6683_CUSTOMER_ID_MITAC 0xa0e
#define NCT6683_CUSTOMER_ID_MSI 0x201
#define NCT6683_REG_BUILD_YEAR 0x604
#define NCT6683_REG_BUILD_MONTH 0x605
@ -1218,6 +1223,8 @@ static int nct6683_probe(struct platform_device *pdev)
break;
case NCT6683_CUSTOMER_ID_MITAC:
break;
case NCT6683_CUSTOMER_ID_MSI:
break;
default:
if (!force)
return -ENODEV;
@ -1352,6 +1359,9 @@ static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data)
case SIO_NCT6683_ID:
sio_data->kind = nct6683;
break;
case SIO_NCT6687_ID:
sio_data->kind = nct6687;
break;
default:
if (val != 0xffff)
pr_debug("unsupported chip ID: 0x%04x\n", val);

View File

@ -41,6 +41,14 @@ struct temp_sensor_2 {
u8 value;
} __packed;
struct temp_sensor_10 {
u32 sensor_id;
u8 fru_type;
u8 value;
u8 throttle;
u8 reserved;
} __packed;
struct freq_sensor_1 {
u16 sensor_id;
u16 value;
@ -307,6 +315,60 @@ static ssize_t occ_show_temp_2(struct device *dev,
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
}
static ssize_t occ_show_temp_10(struct device *dev,
struct device_attribute *attr, char *buf)
{
int rc;
u32 val = 0;
struct temp_sensor_10 *temp;
struct occ *occ = dev_get_drvdata(dev);
struct occ_sensors *sensors = &occ->sensors;
struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
rc = occ_update_response(occ);
if (rc)
return rc;
temp = ((struct temp_sensor_10 *)sensors->temp.data) + sattr->index;
switch (sattr->nr) {
case 0:
val = get_unaligned_be32(&temp->sensor_id);
break;
case 1:
val = temp->value;
if (val == OCC_TEMP_SENSOR_FAULT)
return -EREMOTEIO;
/*
* VRM doesn't return temperature, only alarm bit. This
* attribute maps to tempX_alarm instead of tempX_input for
* VRM
*/
if (temp->fru_type != OCC_FRU_TYPE_VRM) {
/* sensor not ready */
if (val == 0)
return -EAGAIN;
val *= 1000;
}
break;
case 2:
val = temp->fru_type;
break;
case 3:
val = temp->value == OCC_TEMP_SENSOR_FAULT;
break;
case 4:
val = temp->throttle * 1000;
break;
default:
return -EINVAL;
}
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
}
static ssize_t occ_show_freq_1(struct device *dev,
struct device_attribute *attr, char *buf)
{
@ -745,6 +807,10 @@ static int occ_setup_sensor_attrs(struct occ *occ)
num_attrs += (sensors->temp.num_sensors * 4);
show_temp = occ_show_temp_2;
break;
case 0x10:
num_attrs += (sensors->temp.num_sensors * 5);
show_temp = occ_show_temp_10;
break;
default:
sensors->temp.num_sensors = 0;
}
@ -844,6 +910,15 @@ static int occ_setup_sensor_attrs(struct occ *occ)
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
show_temp, NULL, 3, i);
attr++;
if (sensors->temp.version == 0x10) {
snprintf(attr->name, sizeof(attr->name),
"temp%d_max", s);
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
show_temp, NULL,
4, i);
attr++;
}
}
}

View File

@ -220,6 +220,15 @@ config SENSORS_MP2975
This driver can also be built as a module. If so, the module will
be called mp2975.
config SENSORS_PM6764TR
tristate "ST PM6764TR"
help
If you say yes here you get hardware monitoring support for ST
PM6764TR.
This driver can also be built as a module. If so, the module will
be called pm6764tr.
config SENSORS_PXE1610
tristate "Infineon PXE1610"
help
@ -229,6 +238,15 @@ config SENSORS_PXE1610
This driver can also be built as a module. If so, the module will
be called pxe1610.
config SENSORS_Q54SJ108A2
tristate "Delta Power Supplies Q54SJ108A2"
help
If you say yes here you get hardware monitoring support for Delta
Q54SJ108A2 series Power Supplies.
This driver can also be built as a module. If so, the module will
be called q54sj108a2.
config SENSORS_TPS40422
tristate "TI TPS40422"
help

View File

@ -25,7 +25,9 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o

View File

@ -502,7 +502,6 @@ static struct i2c_driver adm1266_driver = {
.of_match_table = adm1266_of_match,
},
.probe_new = adm1266_probe,
.remove = pmbus_do_remove,
.id_table = adm1266_id,
};

View File

@ -797,7 +797,6 @@ static struct i2c_driver adm1275_driver = {
.name = "adm1275",
},
.probe_new = adm1275_probe,
.remove = pmbus_do_remove,
.id_table = adm1275_id,
};

View File

@ -121,7 +121,6 @@ static struct i2c_driver pfe_pmbus_driver = {
.name = "bel-pfe",
},
.probe_new = pfe_pmbus_probe,
.remove = pmbus_do_remove,
.id_table = pfe_device_id,
};

View File

@ -617,7 +617,6 @@ static struct i2c_driver ibm_cffps_driver = {
.of_match_table = ibm_cffps_of_match,
},
.probe_new = ibm_cffps_probe,
.remove = pmbus_do_remove,
.id_table = ibm_cffps_id,
};

View File

@ -216,7 +216,6 @@ static struct i2c_driver ipsps_driver = {
.of_match_table = of_match_ptr(ipsps_of_match),
},
.probe_new = ipsps_probe,
.remove = pmbus_do_remove,
.id_table = ipsps_id,
};

View File

@ -137,7 +137,6 @@ static struct i2c_driver ir35221_driver = {
.name = "ir35221",
},
.probe_new = ir35221_probe,
.remove = pmbus_do_remove,
.id_table = ir35221_id,
};

View File

@ -53,7 +53,6 @@ static struct i2c_driver ir38064_driver = {
.name = "ir38064",
},
.probe_new = ir38064_probe,
.remove = pmbus_do_remove,
.id_table = ir38064_id,
};

View File

@ -55,7 +55,6 @@ static struct i2c_driver irps5401_driver = {
.name = "irps5401",
},
.probe_new = irps5401_probe,
.remove = pmbus_do_remove,
.id_table = irps5401_id,
};

View File

@ -324,7 +324,6 @@ static struct i2c_driver isl68137_driver = {
.name = "isl68137",
},
.probe_new = isl68137_probe,
.remove = pmbus_do_remove,
.id_table = raa_dmpvr_id,
};

View File

@ -508,7 +508,6 @@ static struct i2c_driver lm25066_driver = {
.name = "lm25066",
},
.probe_new = lm25066_probe,
.remove = pmbus_do_remove,
.id_table = lm25066_id,
};

View File

@ -875,7 +875,6 @@ static struct i2c_driver ltc2978_driver = {
.of_match_table = of_match_ptr(ltc2978_of_match),
},
.probe_new = ltc2978_probe,
.remove = pmbus_do_remove,
.id_table = ltc2978_id,
};

View File

@ -200,7 +200,6 @@ static struct i2c_driver ltc3815_driver = {
.name = "ltc3815",
},
.probe_new = ltc3815_probe,
.remove = pmbus_do_remove,
.id_table = ltc3815_id,
};

View File

@ -103,7 +103,6 @@ static struct i2c_driver max16064_driver = {
.name = "max16064",
},
.probe_new = max16064_probe,
.remove = pmbus_do_remove,
.id_table = max16064_id,
};

View File

@ -302,7 +302,6 @@ static struct i2c_driver max16601_driver = {
.name = "max16601",
},
.probe_new = max16601_probe,
.remove = pmbus_do_remove,
.id_table = max16601_id,
};

View File

@ -328,8 +328,6 @@ static int max20730_init_debugfs(struct i2c_client *client,
return -ENOENT;
max20730_dir = debugfs_create_dir(client->name, debugfs);
if (!max20730_dir)
return -ENOENT;
for (i = 0; i < MAX20730_DEBUGFS_NUM_ENTRIES; ++i)
psu->debugfs_entries[i] = i;
@ -779,7 +777,6 @@ static struct i2c_driver max20730_driver = {
.of_match_table = max20730_of_match,
},
.probe_new = max20730_probe,
.remove = pmbus_do_remove,
.id_table = max20730_id,
};

View File

@ -43,7 +43,6 @@ static struct i2c_driver max20751_driver = {
.name = "max20751",
},
.probe_new = max20751_probe,
.remove = pmbus_do_remove,
.id_table = max20751_id,
};

View File

@ -390,7 +390,6 @@ static struct i2c_driver max31785_driver = {
.of_match_table = max31785_of_match,
},
.probe_new = max31785_probe,
.remove = pmbus_do_remove,
.id_table = max31785_id,
};

View File

@ -521,7 +521,6 @@ static struct i2c_driver max34440_driver = {
.name = "max34440",
},
.probe_new = max34440_probe,
.remove = pmbus_do_remove,
.id_table = max34440_id,
};

View File

@ -183,7 +183,6 @@ static struct i2c_driver max8688_driver = {
.name = "max8688",
},
.probe_new = max8688_probe,
.remove = pmbus_do_remove,
.id_table = max8688_id,
};

View File

@ -758,7 +758,6 @@ static struct i2c_driver mp2975_driver = {
.of_match_table = of_match_ptr(mp2975_of_match),
},
.probe_new = mp2975_probe,
.remove = pmbus_do_remove,
.id_table = mp2975_id,
};

View File

@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Hardware monitoring driver for STMicroelectronics digital controller PM6764TR
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pmbus.h>
#include "pmbus.h"
#define PM6764TR_PMBUS_READ_VOUT 0xD4
static int pm6764tr_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
int ret;
switch (reg) {
case PMBUS_VIRT_READ_VMON:
ret = pmbus_read_word_data(client, page, phase, PM6764TR_PMBUS_READ_VOUT);
break;
default:
ret = -ENODATA;
break;
}
return ret;
}
static struct pmbus_driver_info pm6764tr_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = vid,
.format[PSC_TEMPERATURE] = linear,
.format[PSC_CURRENT_OUT] = linear,
.format[PSC_POWER] = linear,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_VMON |
PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
.read_word_data = pm6764tr_read_word_data,
};
static int pm6764tr_probe(struct i2c_client *client)
{
return pmbus_do_probe(client, &pm6764tr_info);
}
static const struct i2c_device_id pm6764tr_id[] = {
{"pm6764tr", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, pm6764tr_id);
static const struct of_device_id __maybe_unused pm6764tr_of_match[] = {
{.compatible = "st,pm6764tr"},
{}
};
/* This is the driver that will be inserted */
static struct i2c_driver pm6764tr_driver = {
.driver = {
.name = "pm6764tr",
.of_match_table = of_match_ptr(pm6764tr_of_match),
},
.probe_new = pm6764tr_probe,
.id_table = pm6764tr_id,
};
module_i2c_driver(pm6764tr_driver);
MODULE_AUTHOR("Charles Hsu");
MODULE_DESCRIPTION("PMBus driver for ST PM6764TR");
MODULE_LICENSE("GPL");

View File

@ -238,7 +238,6 @@ static struct i2c_driver pmbus_driver = {
.name = "pmbus",
},
.probe_new = pmbus_probe,
.remove = pmbus_do_remove,
.id_table = pmbus_id,
};

View File

@ -490,7 +490,6 @@ void pmbus_clear_faults(struct i2c_client *client);
bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info);
int pmbus_do_remove(struct i2c_client *client);
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client
*client);
int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id,

View File

@ -2395,6 +2395,13 @@ static int pmbus_debugfs_set_pec(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_pec, pmbus_debugfs_get_pec,
pmbus_debugfs_set_pec, "%llu\n");
static void pmbus_remove_debugfs(void *data)
{
struct dentry *entry = data;
debugfs_remove_recursive(entry);
}
static int pmbus_init_debugfs(struct i2c_client *client,
struct pmbus_data *data)
{
@ -2530,7 +2537,8 @@ static int pmbus_init_debugfs(struct i2c_client *client,
}
}
return 0;
return devm_add_action_or_reset(data->dev,
pmbus_remove_debugfs, data->debugfs);
}
#else
static int pmbus_init_debugfs(struct i2c_client *client,
@ -2617,16 +2625,6 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info)
}
EXPORT_SYMBOL_GPL(pmbus_do_probe);
int pmbus_do_remove(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);
debugfs_remove_recursive(data->debugfs);
return 0;
}
EXPORT_SYMBOL_GPL(pmbus_do_remove);
struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client)
{
struct pmbus_data *data = i2c_get_clientdata(client);

View File

@ -131,7 +131,6 @@ static struct i2c_driver pxe1610_driver = {
.name = "pxe1610",
},
.probe_new = pxe1610_probe,
.remove = pmbus_do_remove,
.id_table = pxe1610_id,
};

View File

@ -0,0 +1,422 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Delta modules, Q54SJ108A2 series 1/4 Brick DC/DC
* Regulated Power Module
*
* Copyright 2020 Delta LLC.
*/
#include <linux/debugfs.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include "pmbus.h"
#define STORE_DEFAULT_ALL 0x11
#define ERASE_BLACKBOX_DATA 0xD1
#define READ_HISTORY_EVENT_NUMBER 0xD2
#define READ_HISTORY_EVENTS 0xE0
#define SET_HISTORY_EVENT_OFFSET 0xE1
#define PMBUS_FLASH_KEY_WRITE 0xEC
enum chips {
q54sj108a2
};
enum {
Q54SJ108A2_DEBUGFS_OPERATION = 0,
Q54SJ108A2_DEBUGFS_CLEARFAULT,
Q54SJ108A2_DEBUGFS_WRITEPROTECT,
Q54SJ108A2_DEBUGFS_STOREDEFAULT,
Q54SJ108A2_DEBUGFS_VOOV_RESPONSE,
Q54SJ108A2_DEBUGFS_IOOC_RESPONSE,
Q54SJ108A2_DEBUGFS_PMBUS_VERSION,
Q54SJ108A2_DEBUGFS_MFR_ID,
Q54SJ108A2_DEBUGFS_MFR_MODEL,
Q54SJ108A2_DEBUGFS_MFR_REVISION,
Q54SJ108A2_DEBUGFS_MFR_LOCATION,
Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE,
Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET,
Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET,
Q54SJ108A2_DEBUGFS_BLACKBOX_READ,
Q54SJ108A2_DEBUGFS_FLASH_KEY,
Q54SJ108A2_DEBUGFS_NUM_ENTRIES
};
struct q54sj108a2_data {
enum chips chip;
struct i2c_client *client;
int debugfs_entries[Q54SJ108A2_DEBUGFS_NUM_ENTRIES];
};
#define to_psu(x, y) container_of((x), struct q54sj108a2_data, debugfs_entries[(y)])
static struct pmbus_driver_info q54sj108a2_info[] = {
[q54sj108a2] = {
.pages = 1,
/* Source : Delta Q54SJ108A2 */
.format[PSC_TEMPERATURE] = linear,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_CURRENT_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 |
PMBUS_HAVE_STATUS_INPUT,
},
};
static ssize_t q54sj108a2_debugfs_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int rc;
int *idxp = file->private_data;
int idx = *idxp;
struct q54sj108a2_data *psu = to_psu(idxp, idx);
char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
char data_char[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
char *res;
switch (idx) {
case Q54SJ108A2_DEBUGFS_OPERATION:
rc = i2c_smbus_read_byte_data(psu->client, PMBUS_OPERATION);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_WRITEPROTECT:
rc = i2c_smbus_read_byte_data(psu->client, PMBUS_WRITE_PROTECT);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE:
rc = i2c_smbus_read_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE:
rc = i2c_smbus_read_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_PMBUS_VERSION:
rc = i2c_smbus_read_byte_data(psu->client, PMBUS_REVISION);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_MFR_ID:
rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_ID, data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_MFR_MODEL:
rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_MODEL, data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_MFR_REVISION:
rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_REVISION, data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_MFR_LOCATION:
rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_LOCATION, data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET:
rc = i2c_smbus_read_byte_data(psu->client, READ_HISTORY_EVENT_NUMBER);
if (rc < 0)
return rc;
rc = snprintf(data, 3, "%02x", rc);
break;
case Q54SJ108A2_DEBUGFS_BLACKBOX_READ:
rc = i2c_smbus_read_block_data(psu->client, READ_HISTORY_EVENTS, data);
if (rc < 0)
return rc;
res = bin2hex(data, data_char, 32);
rc = res - data;
break;
case Q54SJ108A2_DEBUGFS_FLASH_KEY:
rc = i2c_smbus_read_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, data);
if (rc < 0)
return rc;
res = bin2hex(data, data_char, 4);
rc = res - data;
break;
default:
return -EINVAL;
}
data[rc] = '\n';
rc += 2;
return simple_read_from_buffer(buf, count, ppos, data, rc);
}
static ssize_t q54sj108a2_debugfs_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
u8 flash_key[4];
u8 dst_data;
ssize_t rc;
int *idxp = file->private_data;
int idx = *idxp;
struct q54sj108a2_data *psu = to_psu(idxp, idx);
rc = i2c_smbus_write_byte_data(psu->client, PMBUS_WRITE_PROTECT, 0);
if (rc)
return rc;
switch (idx) {
case Q54SJ108A2_DEBUGFS_OPERATION:
rc = kstrtou8_from_user(buf, count, 0, &dst_data);
if (rc < 0)
return rc;
rc = i2c_smbus_write_byte_data(psu->client, PMBUS_OPERATION, dst_data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_CLEARFAULT:
rc = i2c_smbus_write_byte(psu->client, PMBUS_CLEAR_FAULTS);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_STOREDEFAULT:
flash_key[0] = 0x7E;
flash_key[1] = 0x15;
flash_key[2] = 0xDC;
flash_key[3] = 0x42;
rc = i2c_smbus_write_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, 4, flash_key);
if (rc < 0)
return rc;
rc = i2c_smbus_write_byte(psu->client, STORE_DEFAULT_ALL);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE:
rc = kstrtou8_from_user(buf, count, 0, &dst_data);
if (rc < 0)
return rc;
rc = i2c_smbus_write_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE, dst_data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE:
rc = kstrtou8_from_user(buf, count, 0, &dst_data);
if (rc < 0)
return rc;
rc = i2c_smbus_write_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE, dst_data);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE:
rc = i2c_smbus_write_byte(psu->client, ERASE_BLACKBOX_DATA);
if (rc < 0)
return rc;
break;
case Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET:
rc = kstrtou8_from_user(buf, count, 0, &dst_data);
if (rc < 0)
return rc;
rc = i2c_smbus_write_byte_data(psu->client, SET_HISTORY_EVENT_OFFSET, dst_data);
if (rc < 0)
return rc;
break;
default:
return -EINVAL;
}
return count;
}
static const struct file_operations q54sj108a2_fops = {
.llseek = noop_llseek,
.read = q54sj108a2_debugfs_read,
.write = q54sj108a2_debugfs_write,
.open = simple_open,
};
static const struct i2c_device_id q54sj108a2_id[] = {
{ "q54sj108a2", q54sj108a2 },
{ },
};
MODULE_DEVICE_TABLE(i2c, q54sj108a2_id);
static int q54sj108a2_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
enum chips chip_id;
int ret, i;
struct dentry *debugfs;
struct dentry *q54sj108a2_dir;
struct q54sj108a2_data *psu;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA))
return -ENODEV;
if (client->dev.of_node)
chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev);
else
chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data;
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, "DELTA", 5)) {
buf[ret] = '\0';
dev_err(dev, "Unsupported Manufacturer ID '%s'\n", buf);
return -ENODEV;
}
/*
* The chips support reading PMBUS_MFR_MODEL.
*/
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 != 14 || strncmp(buf, "Q54SJ108A2", 10)) {
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 != 4 || buf[0] != 'S') {
buf[ret] = '\0';
dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf);
return -ENODEV;
}
ret = pmbus_do_probe(client, &q54sj108a2_info[chip_id]);
if (ret)
return ret;
psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL);
if (!psu)
return 0;
psu->client = client;
debugfs = pmbus_get_debugfs_dir(client);
q54sj108a2_dir = debugfs_create_dir(client->name, debugfs);
for (i = 0; i < Q54SJ108A2_DEBUGFS_NUM_ENTRIES; ++i)
psu->debugfs_entries[i] = i;
debugfs_create_file("operation", 0644, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_OPERATION],
&q54sj108a2_fops);
debugfs_create_file("clear_fault", 0200, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_CLEARFAULT],
&q54sj108a2_fops);
debugfs_create_file("write_protect", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_WRITEPROTECT],
&q54sj108a2_fops);
debugfs_create_file("store_default", 0200, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_STOREDEFAULT],
&q54sj108a2_fops);
debugfs_create_file("vo_ov_response", 0644, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_VOOV_RESPONSE],
&q54sj108a2_fops);
debugfs_create_file("io_oc_response", 0644, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_IOOC_RESPONSE],
&q54sj108a2_fops);
debugfs_create_file("pmbus_revision", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_PMBUS_VERSION],
&q54sj108a2_fops);
debugfs_create_file("mfr_id", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_ID],
&q54sj108a2_fops);
debugfs_create_file("mfr_model", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_MODEL],
&q54sj108a2_fops);
debugfs_create_file("mfr_revision", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_REVISION],
&q54sj108a2_fops);
debugfs_create_file("mfr_location", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_LOCATION],
&q54sj108a2_fops);
debugfs_create_file("blackbox_erase", 0200, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE],
&q54sj108a2_fops);
debugfs_create_file("blackbox_read_offset", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET],
&q54sj108a2_fops);
debugfs_create_file("blackbox_set_offset", 0200, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET],
&q54sj108a2_fops);
debugfs_create_file("blackbox_read", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ],
&q54sj108a2_fops);
debugfs_create_file("flash_key", 0444, q54sj108a2_dir,
&psu->debugfs_entries[Q54SJ108A2_DEBUGFS_FLASH_KEY],
&q54sj108a2_fops);
return 0;
}
static const struct of_device_id q54sj108a2_of_match[] = {
{ .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 },
{ },
};
MODULE_DEVICE_TABLE(of, q54sj108a2_of_match);
static struct i2c_driver q54sj108a2_driver = {
.driver = {
.name = "q54sj108a2",
.of_match_table = q54sj108a2_of_match,
},
.probe_new = q54sj108a2_probe,
.id_table = q54sj108a2_id,
};
module_i2c_driver(q54sj108a2_driver);
MODULE_AUTHOR("Xiao.Ma <xiao.mx.ma@deltaww.com>");
MODULE_DESCRIPTION("PMBus driver for Delta Q54SJ108A2 series modules");
MODULE_LICENSE("GPL");

View File

@ -43,7 +43,6 @@ static struct i2c_driver tps40422_driver = {
.name = "tps40422",
},
.probe_new = tps40422_probe,
.remove = pmbus_do_remove,
.id_table = tps40422_id,
};

View File

@ -251,7 +251,6 @@ static struct i2c_driver tps53679_driver = {
.of_match_table = of_match_ptr(tps53679_of_match),
},
.probe_new = tps53679_probe,
.remove = pmbus_do_remove,
.id_table = tps53679_id,
};

View File

@ -621,7 +621,6 @@ static struct i2c_driver ucd9000_driver = {
.of_match_table = of_match_ptr(ucd9000_of_match),
},
.probe_new = ucd9000_probe,
.remove = pmbus_do_remove,
.id_table = ucd9000_id,
};

View File

@ -201,7 +201,6 @@ static struct i2c_driver ucd9200_driver = {
.of_match_table = of_match_ptr(ucd9200_of_match),
},
.probe_new = ucd9200_probe,
.remove = pmbus_do_remove,
.id_table = ucd9200_id,
};

View File

@ -160,7 +160,6 @@ static struct i2c_driver xdpe122_driver = {
.of_match_table = of_match_ptr(xdpe122_of_match),
},
.probe_new = xdpe122_probe,
.remove = pmbus_do_remove,
.id_table = xdpe122_id,
};

View File

@ -396,7 +396,6 @@ static struct i2c_driver zl6100_driver = {
.name = "zl6100",
},
.probe_new = zl6100_probe,
.remove = pmbus_do_remove,
.id_table = zl6100_id,
};

View File

@ -8,7 +8,6 @@
*/
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
@ -39,6 +38,28 @@ struct pwm_fan_ctx {
unsigned int pwm_fan_max_state;
unsigned int *pwm_fan_cooling_levels;
struct thermal_cooling_device *cdev;
struct hwmon_chip_info info;
};
static const u32 pwm_fan_channel_config_pwm[] = {
HWMON_PWM_INPUT,
0
};
static const struct hwmon_channel_info pwm_fan_channel_pwm = {
.type = hwmon_pwm,
.config = pwm_fan_channel_config_pwm,
};
static const u32 pwm_fan_channel_config_fan[] = {
HWMON_F_INPUT,
0
};
static const struct hwmon_channel_info pwm_fan_channel_fan = {
.type = hwmon_fan,
.config = pwm_fan_channel_config_fan,
};
/* This handler assumes self resetting edge triggered interrupt. */
@ -103,70 +124,62 @@ static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm)
ctx->pwm_fan_state = i;
}
static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
static int pwm_fan_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
unsigned long pwm;
int ret;
if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
if (val < 0 || val > MAX_PWM)
return -EINVAL;
ret = __set_pwm(ctx, pwm);
ret = __set_pwm(ctx, val);
if (ret)
return ret;
pwm_fan_update_state(ctx, pwm);
return count;
pwm_fan_update_state(ctx, val);
return 0;
}
static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
char *buf)
static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ctx->pwm_value);
}
static ssize_t rpm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ctx->rpm);
}
static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0);
static SENSOR_DEVICE_ATTR_RO(fan1_input, rpm, 0);
static struct attribute *pwm_fan_attrs[] = {
&sensor_dev_attr_pwm1.dev_attr.attr,
&sensor_dev_attr_fan1_input.dev_attr.attr,
NULL,
};
static umode_t pwm_fan_attrs_visible(struct kobject *kobj, struct attribute *a,
int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
/* Hide fan_input in case no interrupt is available */
if (n == 1 && ctx->irq <= 0)
switch (type) {
case hwmon_pwm:
*val = ctx->pwm_value;
return 0;
return a->mode;
case hwmon_fan:
*val = ctx->rpm;
return 0;
default:
return -ENOTSUPP;
}
}
static const struct attribute_group pwm_fan_group = {
.attrs = pwm_fan_attrs,
.is_visible = pwm_fan_attrs_visible,
};
static umode_t pwm_fan_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_pwm:
return 0644;
static const struct attribute_group *pwm_fan_groups[] = {
&pwm_fan_group,
NULL,
case hwmon_fan:
return 0444;
default:
return 0;
}
}
static const struct hwmon_ops pwm_fan_hwmon_ops = {
.is_visible = pwm_fan_is_visible,
.read = pwm_fan_read,
.write = pwm_fan_write,
};
/* thermal cooling device callbacks */
@ -286,7 +299,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
struct device *hwmon;
int ret;
struct pwm_state state = { };
u32 ppr = 2;
int tach_count;
const struct hwmon_channel_info **channels;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
@ -300,10 +314,6 @@ static int pwm_fan_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, ctx);
ctx->irq = platform_get_irq_optional(pdev, 0);
if (ctx->irq == -EPROBE_DEFER)
return ctx->irq;
ctx->reg_en = devm_regulator_get_optional(dev, "fan");
if (IS_ERR(ctx->reg_en)) {
if (PTR_ERR(ctx->reg_en) != -ENODEV)
@ -339,26 +349,58 @@ static int pwm_fan_probe(struct platform_device *pdev)
if (ret)
return ret;
of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr);
ctx->pulses_per_revolution = ppr;
if (!ctx->pulses_per_revolution) {
dev_err(dev, "pulses-per-revolution can't be zero.\n");
return -EINVAL;
}
tach_count = platform_irq_count(pdev);
if (tach_count < 0)
return dev_err_probe(dev, tach_count,
"Could not get number of fan tachometer inputs\n");
if (ctx->irq > 0) {
ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
pdev->name, ctx);
if (ret) {
dev_err(dev, "Failed to request interrupt: %d\n", ret);
return ret;
channels = devm_kcalloc(dev, tach_count + 2,
sizeof(struct hwmon_channel_info *), GFP_KERNEL);
if (!channels)
return -ENOMEM;
channels[0] = &pwm_fan_channel_pwm;
if (tach_count > 0) {
u32 ppr = 2;
ctx->irq = platform_get_irq(pdev, 0);
if (ctx->irq == -EPROBE_DEFER)
return ctx->irq;
if (ctx->irq > 0) {
ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
pdev->name, ctx);
if (ret) {
dev_err(dev,
"Failed to request interrupt: %d\n",
ret);
return ret;
}
}
of_property_read_u32(dev->of_node,
"pulses-per-revolution",
&ppr);
ctx->pulses_per_revolution = ppr;
if (!ctx->pulses_per_revolution) {
dev_err(dev, "pulses-per-revolution can't be zero.\n");
return -EINVAL;
}
dev_dbg(dev, "tach: irq=%d, pulses_per_revolution=%d\n",
ctx->irq, ctx->pulses_per_revolution);
ctx->sample_start = ktime_get();
mod_timer(&ctx->rpm_timer, jiffies + HZ);
channels[1] = &pwm_fan_channel_fan;
}
hwmon = devm_hwmon_device_register_with_groups(dev, "pwmfan",
ctx, pwm_fan_groups);
ctx->info.ops = &pwm_fan_hwmon_ops;
ctx->info.info = channels;
hwmon = devm_hwmon_device_register_with_info(dev, "pwmfan",
ctx, &ctx->info, NULL);
if (IS_ERR(hwmon)) {
dev_err(dev, "Failed to register hwmon device\n");
return PTR_ERR(hwmon);

250
drivers/hwmon/sbtsi_temp.c Normal file
View File

@ -0,0 +1,250 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* sbtsi_temp.c - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI)
* compliant AMD SoC temperature device.
*
* Copyright (c) 2020, Google Inc.
* Copyright (c) 2020, Kun Yi <kunyi@google.com>
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/of.h>
/*
* SB-TSI registers only support SMBus byte data access. "_INT" registers are
* the integer part of a temperature value or limit, and "_DEC" registers are
* corresponding decimal parts.
*/
#define SBTSI_REG_TEMP_INT 0x01 /* RO */
#define SBTSI_REG_STATUS 0x02 /* RO */
#define SBTSI_REG_CONFIG 0x03 /* RO */
#define SBTSI_REG_TEMP_HIGH_INT 0x07 /* RW */
#define SBTSI_REG_TEMP_LOW_INT 0x08 /* RW */
#define SBTSI_REG_TEMP_DEC 0x10 /* RW */
#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
#define SBTSI_TEMP_MIN 0
#define SBTSI_TEMP_MAX 255875
/* Each client has this additional data */
struct sbtsi_data {
struct i2c_client *client;
struct mutex lock;
};
/*
* From SB-TSI spec: CPU temperature readings and limit registers encode the
* temperature in increments of 0.125 from 0 to 255.875. The "high byte"
* register encodes the base-2 of the integer portion, and the upper 3 bits of
* the "low byte" encode in base-2 the decimal portion.
*
* e.g. INT=0x19, DEC=0x20 represents 25.125 degrees Celsius
*
* Therefore temperature in millidegree Celsius =
* (INT + DEC / 256) * 1000 = (INT * 8 + DEC / 32) * 125
*/
static inline int sbtsi_reg_to_mc(s32 integer, s32 decimal)
{
return ((integer << 3) + (decimal >> 5)) * 125;
}
/*
* Inversely, given temperature in millidegree Celsius
* INT = (TEMP / 125) / 8
* DEC = ((TEMP / 125) % 8) * 32
* Caller have to make sure temp doesn't exceed 255875, the max valid value.
*/
static inline void sbtsi_mc_to_reg(s32 temp, u8 *integer, u8 *decimal)
{
temp /= 125;
*integer = temp >> 3;
*decimal = (temp & 0x7) << 5;
}
static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
int err;
switch (attr) {
case hwmon_temp_input:
/*
* ReadOrder bit specifies the reading order of integer and
* decimal part of CPU temp for atomic reads. If bit == 0,
* reading integer part triggers latching of the decimal part,
* so integer part should be read first. If bit == 1, read
* order should be reversed.
*/
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
return err;
mutex_lock(&data->lock);
if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) {
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
} else {
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
}
mutex_unlock(&data->lock);
break;
case hwmon_temp_max:
mutex_lock(&data->lock);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC);
mutex_unlock(&data->lock);
break;
case hwmon_temp_min:
mutex_lock(&data->lock);
temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT);
temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC);
mutex_unlock(&data->lock);
break;
default:
return -EINVAL;
}
if (temp_int < 0)
return temp_int;
if (temp_dec < 0)
return temp_dec;
*val = sbtsi_reg_to_mc(temp_int, temp_dec);
return 0;
}
static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct sbtsi_data *data = dev_get_drvdata(dev);
int reg_int, reg_dec, err;
u8 temp_int, temp_dec;
switch (attr) {
case hwmon_temp_max:
reg_int = SBTSI_REG_TEMP_HIGH_INT;
reg_dec = SBTSI_REG_TEMP_HIGH_DEC;
break;
case hwmon_temp_min:
reg_int = SBTSI_REG_TEMP_LOW_INT;
reg_dec = SBTSI_REG_TEMP_LOW_DEC;
break;
default:
return -EINVAL;
}
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
mutex_lock(&data->lock);
err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int);
if (err)
goto exit;
err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
exit:
mutex_unlock(&data->lock);
return err;
}
static umode_t sbtsi_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:
return 0444;
case hwmon_temp_min:
return 0644;
case hwmon_temp_max:
return 0644;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *sbtsi_info[] = {
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX),
NULL
};
static const struct hwmon_ops sbtsi_hwmon_ops = {
.is_visible = sbtsi_is_visible,
.read = sbtsi_read,
.write = sbtsi_write,
};
static const struct hwmon_chip_info sbtsi_chip_info = {
.ops = &sbtsi_hwmon_ops,
.info = sbtsi_info,
};
static int sbtsi_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct sbtsi_data *data;
data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id sbtsi_id[] = {
{"sbtsi", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, sbtsi_id);
static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
{
.compatible = "amd,sbtsi",
},
{ },
};
MODULE_DEVICE_TABLE(of, sbtsi_of_match);
static struct i2c_driver sbtsi_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "sbtsi",
.of_match_table = of_match_ptr(sbtsi_of_match),
},
.probe = sbtsi_probe,
.id_table = sbtsi_id,
};
module_i2c_driver(sbtsi_driver);
MODULE_AUTHOR("Kun Yi <kunyi@google.com>");
MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor");
MODULE_LICENSE("GPL");

View File

@ -784,7 +784,7 @@ static const struct of_device_id xgene_hwmon_of_match[] = {
};
MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match);
static struct platform_driver xgene_hwmon_driver __refdata = {
static struct platform_driver xgene_hwmon_driver = {
.probe = xgene_hwmon_probe,
.remove = xgene_hwmon_remove,
.driver = {