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:
commit
0f97458173
|
@ -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:
|
||||
|
||||
|
|
|
@ -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>;
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -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>;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -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>;
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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>;
|
||||
};
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
======================= ========================================================
|
|
@ -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
|
||||
|
|
|
@ -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 supply’s 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.
|
|
@ -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
|
||||
============== ==============================================================
|
|
@ -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
|
||||
=============== ===============================================
|
||||
|
|
|
@ -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 Intel’s 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.
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
===================== ===== ==================================================
|
|
@ -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.
|
16
MAINTAINERS
16
MAINTAINERS
|
@ -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
|
||||
|
|
|
@ -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
|
||||
},
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[] = {
|
||||
<c2992_chip,
|
||||
<c2992_in,
|
||||
<c2992_curr,
|
||||
<c2992_power,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info ltc2992_chip_info = {
|
||||
.ops = <c2992_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, <c2992_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,
|
||||
<c2992_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");
|
|
@ -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 two’s 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");
|
|
@ -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);
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -797,7 +797,6 @@ static struct i2c_driver adm1275_driver = {
|
|||
.name = "adm1275",
|
||||
},
|
||||
.probe_new = adm1275_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = adm1275_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -137,7 +137,6 @@ static struct i2c_driver ir35221_driver = {
|
|||
.name = "ir35221",
|
||||
},
|
||||
.probe_new = ir35221_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = ir35221_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ static struct i2c_driver ir38064_driver = {
|
|||
.name = "ir38064",
|
||||
},
|
||||
.probe_new = ir38064_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = ir38064_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ static struct i2c_driver irps5401_driver = {
|
|||
.name = "irps5401",
|
||||
},
|
||||
.probe_new = irps5401_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = irps5401_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -508,7 +508,6 @@ static struct i2c_driver lm25066_driver = {
|
|||
.name = "lm25066",
|
||||
},
|
||||
.probe_new = lm25066_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = lm25066_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -200,7 +200,6 @@ static struct i2c_driver ltc3815_driver = {
|
|||
.name = "ltc3815",
|
||||
},
|
||||
.probe_new = ltc3815_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = ltc3815_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -103,7 +103,6 @@ static struct i2c_driver max16064_driver = {
|
|||
.name = "max16064",
|
||||
},
|
||||
.probe_new = max16064_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max16064_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -302,7 +302,6 @@ static struct i2c_driver max16601_driver = {
|
|||
.name = "max16601",
|
||||
},
|
||||
.probe_new = max16601_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max16601_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ static struct i2c_driver max20751_driver = {
|
|||
.name = "max20751",
|
||||
},
|
||||
.probe_new = max20751_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max20751_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -521,7 +521,6 @@ static struct i2c_driver max34440_driver = {
|
|||
.name = "max34440",
|
||||
},
|
||||
.probe_new = max34440_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max34440_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -183,7 +183,6 @@ static struct i2c_driver max8688_driver = {
|
|||
.name = "max8688",
|
||||
},
|
||||
.probe_new = max8688_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = max8688_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -238,7 +238,6 @@ static struct i2c_driver pmbus_driver = {
|
|||
.name = "pmbus",
|
||||
},
|
||||
.probe_new = pmbus_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = pmbus_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -131,7 +131,6 @@ static struct i2c_driver pxe1610_driver = {
|
|||
.name = "pxe1610",
|
||||
},
|
||||
.probe_new = pxe1610_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = pxe1610_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -43,7 +43,6 @@ static struct i2c_driver tps40422_driver = {
|
|||
.name = "tps40422",
|
||||
},
|
||||
.probe_new = tps40422_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = tps40422_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -396,7 +396,6 @@ static struct i2c_driver zl6100_driver = {
|
|||
.name = "zl6100",
|
||||
},
|
||||
.probe_new = zl6100_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = zl6100_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue