diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-12-15 16:06:14 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-12-15 16:06:14 -0800 |
commit | 0f97458173a23c8f218f6041767d0a145a13abe6 (patch) | |
tree | b91aa532e5a8c16b9d03b383a1a8ae9c718a684f | |
parent | ce51c2b7ceb23a23eb0dc523c80879d8f35e4f38 (diff) | |
parent | 1a033769a4fe9a86ee791fd553b6a996dd76e026 (diff) | |
download | linux-0f97458173a23c8f218f6041767d0a145a13abe6.tar.gz linux-0f97458173a23c8f218f6041767d0a145a13abe6.tar.bz2 linux-0f97458173a23c8f218f6041767d0a145a13abe6.zip |
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.
...
81 files changed, 3875 insertions, 316 deletions
diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt index 99ca9862a586..e73358075a90 100644 --- a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt @@ -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: diff --git a/Documentation/devicetree/bindings/hwmon/ad741x.txt b/Documentation/devicetree/bindings/hwmon/ad741x.txt deleted file mode 100644 index 9102152c8410..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ad741x.txt +++ /dev/null @@ -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>; -}; diff --git a/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml b/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml new file mode 100644 index 000000000000..ce7f8ce9da0a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml @@ -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>; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml new file mode 100644 index 000000000000..223393d7cafd --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -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>; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml new file mode 100644 index 000000000000..64a8fcb7bc46 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml @@ -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>; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/hwmon/adm1275.txt b/Documentation/devicetree/bindings/hwmon/adm1275.txt deleted file mode 100644 index 1ecd03f3da4d..000000000000 --- a/Documentation/devicetree/bindings/hwmon/adm1275.txt +++ /dev/null @@ -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>; -}; diff --git a/Documentation/devicetree/bindings/hwmon/ads7828.txt b/Documentation/devicetree/bindings/hwmon/ads7828.txt deleted file mode 100644 index fe0cc4ad7ea9..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ads7828.txt +++ /dev/null @@ -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; - }; diff --git a/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml b/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml new file mode 100644 index 000000000000..446b09f1ce94 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml @@ -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>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/hwmon/ina2xx.txt b/Documentation/devicetree/bindings/hwmon/ina2xx.txt deleted file mode 100644 index 02af0d94e921..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ina2xx.txt +++ /dev/null @@ -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>; -}; diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt index 41b76762953a..4509e688623a 100644 --- a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt +++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt @@ -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>; + }; diff --git a/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml b/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml new file mode 100644 index 000000000000..33ee575bb09d --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml @@ -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; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml new file mode 100644 index 000000000000..6f0443322a36 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -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>; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 52a7f793737e..750671394988 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -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 diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst index ce6528f90e4a..804590eeabdc 100644 --- a/Documentation/hwmon/adm1275.rst +++ b/Documentation/hwmon/adm1275.rst @@ -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 diff --git a/Documentation/hwmon/amd_energy.rst b/Documentation/hwmon/amd_energy.rst index 86e4ebc5cbc2..9d58cd5ee3da 100644 --- a/Documentation/hwmon/amd_energy.rst +++ b/Documentation/hwmon/amd_energy.rst @@ -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. diff --git a/Documentation/hwmon/corsair-psu.rst b/Documentation/hwmon/corsair-psu.rst new file mode 100644 index 000000000000..396b95c9a76a --- /dev/null +++ b/Documentation/hwmon/corsair-psu.rst @@ -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 +======================= ======================================================== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b797db738225..fcb870ce6286 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -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 diff --git a/Documentation/hwmon/ltc2992.rst b/Documentation/hwmon/ltc2992.rst new file mode 100644 index 000000000000..46aa1aa84a1a --- /dev/null +++ b/Documentation/hwmon/ltc2992.rst @@ -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. diff --git a/Documentation/hwmon/max127.rst b/Documentation/hwmon/max127.rst new file mode 100644 index 000000000000..dc192dd9c37c --- /dev/null +++ b/Documentation/hwmon/max127.rst @@ -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 + ============== ============================================================== diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst index efbf7e9703ec..8646ad519fcd 100644 --- a/Documentation/hwmon/nct6683.rst +++ b/Documentation/hwmon/nct6683.rst @@ -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 =============== =============================================== diff --git a/Documentation/hwmon/pm6764tr.rst b/Documentation/hwmon/pm6764tr.rst new file mode 100644 index 000000000000..a1fb8fea2326 --- /dev/null +++ b/Documentation/hwmon/pm6764tr.rst @@ -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. diff --git a/Documentation/hwmon/pmbus-core.rst b/Documentation/hwmon/pmbus-core.rst index e22c4f6808bc..73e23ab42cc3 100644 --- a/Documentation/hwmon/pmbus-core.rst +++ b/Documentation/hwmon/pmbus-core.rst @@ -279,12 +279,6 @@ function. :: - void pmbus_do_remove(struct i2c_client *client); - -Execute driver remove function. Similar to standard driver remove function. - -:: - const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client); diff --git a/Documentation/hwmon/pmbus.rst b/Documentation/hwmon/pmbus.rst index fb3ad67dedc1..c44f14115413 100644 --- a/Documentation/hwmon/pmbus.rst +++ b/Documentation/hwmon/pmbus.rst @@ -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, }; diff --git a/Documentation/hwmon/q54sj108a2.rst b/Documentation/hwmon/q54sj108a2.rst new file mode 100644 index 000000000000..f95d81382a9f --- /dev/null +++ b/Documentation/hwmon/q54sj108a2.rst @@ -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 +===================== ===== ================================================== diff --git a/Documentation/hwmon/sbtsi_temp.rst b/Documentation/hwmon/sbtsi_temp.rst new file mode 100644 index 000000000000..922b3c8db666 --- /dev/null +++ b/Documentation/hwmon/sbtsi_temp.rst @@ -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. diff --git a/MAINTAINERS b/MAINTAINERS index fdb7003bc1f9..b94944e175d7 100644 --- a/MAINTAINERS +++ b/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 diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index 9eeb856c8905..10ca2e290655 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -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 */ + 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; + } + + buf[0] = cpu_to_be32(cmd_len); /* 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; + 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, 7, buf, &resp_len); + 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 + }, { }, }; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a850e4f0e0bd..1ecf697d8d99 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9db2903b61e5..09a86c5e1d29 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -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 diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index 50e67cdd8e5e..4b9648819836 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -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) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index a270b975e90b..848718ab7312 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -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; diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c index 6e8bb661894b..0c5dbc5e33b4 100644 --- a/drivers/hwmon/adm1177.c +++ b/drivers/hwmon/adm1177.c @@ -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; diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 740f39a54ab0..2e8feacccf84 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -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 diff --git a/drivers/hwmon/amd_energy.c b/drivers/hwmon/amd_energy.c index 3197cda7bcd9..9b306448b7a0 100644 --- a/drivers/hwmon/amd_energy.c +++ b/drivers/hwmon/amd_energy.c @@ -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); diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 79b498f816fe..89207af81c48 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -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 } }; diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c new file mode 100644 index 000000000000..99494056f4bd --- /dev/null +++ b/drivers/hwmon/corsair-psu.c @@ -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"); diff --git a/drivers/hwmon/drivetemp.c b/drivers/hwmon/drivetemp.c index 72c760373957..1eb37106a220 100644 --- a/drivers/hwmon/drivetemp.c +++ b/drivers/hwmon/drivetemp.c @@ -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 diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c index a750647e66a4..8e3724728cce 100644 --- a/drivers/hwmon/ibmpowernv.c +++ b/drivers/hwmon/ibmpowernv.c @@ -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; } diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c index b85a125dd86f..580a7d125b88 100644 --- a/drivers/hwmon/iio_hwmon.c +++ b/drivers/hwmon/iio_hwmon.c @@ -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, diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index 41fb17e0d641..d80bd3efcd6d 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -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; diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c new file mode 100644 index 000000000000..4382105bf142 --- /dev/null +++ b/drivers/hwmon/ltc2992.c @@ -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"); diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c new file mode 100644 index 000000000000..402ffdc2f425 --- /dev/null +++ b/drivers/hwmon/max127.c @@ -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"); diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 2d299149f4d2..7f7e30f0de7b 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -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); diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index a71777990d49..7a5e539b567b 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -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++; + } } } diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a25faf69fce3..03606d4298a4 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -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 diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 4c97ad0bd791..6a4ba0fdc1db 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -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 diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index c7b373ba92f2..4d2e4ddcfbfd 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index e7997f37b266..38a6515b0763 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -797,7 +797,6 @@ static struct i2c_driver adm1275_driver = { .name = "adm1275", }, .probe_new = adm1275_probe, - .remove = pmbus_do_remove, .id_table = adm1275_id, }; diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c index 2c5b853d6c7f..aed7542d7ce5 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2fb7540ee952..d6bbbb223871 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/inspur-ipsps.c b/drivers/hwmon/pmbus/inspur-ipsps.c index be493182174d..88c5865c4d6f 100644 --- a/drivers/hwmon/pmbus/inspur-ipsps.c +++ b/drivers/hwmon/pmbus/inspur-ipsps.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c index 5fadb1def49f..3aebeb1443fd 100644 --- a/drivers/hwmon/pmbus/ir35221.c +++ b/drivers/hwmon/pmbus/ir35221.c @@ -137,7 +137,6 @@ static struct i2c_driver ir35221_driver = { .name = "ir35221", }, .probe_new = ir35221_probe, - .remove = pmbus_do_remove, .id_table = ir35221_id, }; diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 9ac563ce7dd8..46f17c4b4873 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -53,7 +53,6 @@ static struct i2c_driver ir38064_driver = { .name = "ir38064", }, .probe_new = ir38064_probe, - .remove = pmbus_do_remove, .id_table = ir38064_id, }; diff --git a/drivers/hwmon/pmbus/irps5401.c b/drivers/hwmon/pmbus/irps5401.c index 44aeafcbd56c..93ef6d64a33a 100644 --- a/drivers/hwmon/pmbus/irps5401.c +++ b/drivers/hwmon/pmbus/irps5401.c @@ -55,7 +55,6 @@ static struct i2c_driver irps5401_driver = { .name = "irps5401", }, .probe_new = irps5401_probe, - .remove = pmbus_do_remove, .id_table = irps5401_id, }; diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 7cad76e07f70..2bee930d3900 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 429172a42902..c75a6bf39641 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -508,7 +508,6 @@ static struct i2c_driver lm25066_driver = { .name = "lm25066", }, .probe_new = lm25066_probe, - .remove = pmbus_do_remove, .id_table = lm25066_id, }; diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 9a024cf70145..7e53fa95b92d 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/ltc3815.c b/drivers/hwmon/pmbus/ltc3815.c index 8328fb367ad6..e45e14d26c9a 100644 --- a/drivers/hwmon/pmbus/ltc3815.c +++ b/drivers/hwmon/pmbus/ltc3815.c @@ -200,7 +200,6 @@ static struct i2c_driver ltc3815_driver = { .name = "ltc3815", }, .probe_new = ltc3815_probe, - .remove = pmbus_do_remove, .id_table = ltc3815_id, }; diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c index 26e7f5ef9d7f..d79add99083e 100644 --- a/drivers/hwmon/pmbus/max16064.c +++ b/drivers/hwmon/pmbus/max16064.c @@ -103,7 +103,6 @@ static struct i2c_driver max16064_driver = { .name = "max16064", }, .probe_new = max16064_probe, - .remove = pmbus_do_remove, .id_table = max16064_id, }; diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index 71bb74e27a5c..a960b86e72d2 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -302,7 +302,6 @@ static struct i2c_driver max16601_driver = { .name = "max16601", }, .probe_new = max16601_probe, - .remove = pmbus_do_remove, .id_table = max16601_id, }; diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index be83b98411c7..9dd3dd79bc18 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c index 921e92d82aec..9d42f82fdd99 100644 --- a/drivers/hwmon/pmbus/max20751.c +++ b/drivers/hwmon/pmbus/max20751.c @@ -43,7 +43,6 @@ static struct i2c_driver max20751_driver = { .name = "max20751", }, .probe_new = max20751_probe, - .remove = pmbus_do_remove, .id_table = max20751_id, }; diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 839b957bc03e..e5a9f4019cd5 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index f4cb196aaaf3..dad66b3c0116 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -521,7 +521,6 @@ static struct i2c_driver max34440_driver = { .name = "max34440", }, .probe_new = max34440_probe, - .remove = pmbus_do_remove, .id_table = max34440_id, }; diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c index 4b2239a6afd3..329dc851fc59 100644 --- a/drivers/hwmon/pmbus/max8688.c +++ b/drivers/hwmon/pmbus/max8688.c @@ -183,7 +183,6 @@ static struct i2c_driver max8688_driver = { .name = "max8688", }, .probe_new = max8688_probe, - .remove = pmbus_do_remove, .id_table = max8688_id, }; diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c index 1c3e2a9453b1..60fbdb371332 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/pm6764tr.c b/drivers/hwmon/pmbus/pm6764tr.c new file mode 100644 index 000000000000..d97cb6d6c87f --- /dev/null +++ b/drivers/hwmon/pmbus/pm6764tr.c @@ -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"); diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 20f1af9165c2..a1b4260e75b2 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -238,7 +238,6 @@ static struct i2c_driver pmbus_driver = { .name = "pmbus", }, .probe_new = pmbus_probe, - .remove = pmbus_do_remove, .id_table = pmbus_id, }; diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 88a5df2633fb..4c30ec89f5bf 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -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, diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index b0e2820a2d57..192442b3b7a2 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -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); diff --git a/drivers/hwmon/pmbus/pxe1610.c b/drivers/hwmon/pmbus/pxe1610.c index fa5c5dd29b7a..da27ce34ee3f 100644 --- a/drivers/hwmon/pmbus/pxe1610.c +++ b/drivers/hwmon/pmbus/pxe1610.c @@ -131,7 +131,6 @@ static struct i2c_driver pxe1610_driver = { .name = "pxe1610", }, .probe_new = pxe1610_probe, - .remove = pmbus_do_remove, .id_table = pxe1610_id, }; diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c new file mode 100644 index 000000000000..aec512766c31 --- /dev/null +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -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"); diff --git a/drivers/hwmon/pmbus/tps40422.c b/drivers/hwmon/pmbus/tps40422.c index edbdfa809d51..f7f00ab6f46c 100644 --- a/drivers/hwmon/pmbus/tps40422.c +++ b/drivers/hwmon/pmbus/tps40422.c @@ -43,7 +43,6 @@ static struct i2c_driver tps40422_driver = { .name = "tps40422", }, .probe_new = tps40422_probe, - .remove = pmbus_do_remove, .id_table = tps40422_id, }; diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index db2bdf2a1f02..ba838fa311c3 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index f8017993e2b4..a15e6fe3e425 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c index e111e25e1619..47cc7ca9d329 100644 --- a/drivers/hwmon/pmbus/ucd9200.c +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index c95ac934fde4..f8bc0f41cd5f 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -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, }; diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index e8bda340482b..69120ca7aaa8 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -396,7 +396,6 @@ static struct i2c_driver zl6100_driver = { .name = "zl6100", }, .probe_new = zl6100_probe, - .remove = pmbus_do_remove, .id_table = zl6100_id, }; diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 1f63807c0399..777439f48c14 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -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); -} + switch (type) { + case hwmon_pwm: + *val = ctx->pwm_value; + return 0; -static ssize_t rpm_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + case hwmon_fan: + *val = ctx->rpm; + return 0; - return sprintf(buf, "%u\n", ctx->rpm); + default: + return -ENOTSUPP; + } } -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) +static umode_t pwm_fan_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) { - struct device *dev = container_of(kobj, struct device, kobj); - struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + switch (type) { + case hwmon_pwm: + return 0644; - /* Hide fan_input in case no interrupt is available */ - if (n == 1 && ctx->irq <= 0) - return 0; + case hwmon_fan: + return 0444; - return a->mode; + default: + return 0; + } } -static const struct attribute_group pwm_fan_group = { - .attrs = pwm_fan_attrs, - .is_visible = pwm_fan_attrs_visible, -}; - -static const struct attribute_group *pwm_fan_groups[] = { - &pwm_fan_group, - NULL, +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); diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c new file mode 100644 index 000000000000..e35357c48b8e --- /dev/null +++ b/drivers/hwmon/sbtsi_temp.c @@ -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"); diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index f2a5af239c95..1489e83cb0c4 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -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 = { |