hwmon updates for v6.9

* New drivers for
 
   - Amphenol ChipCap 2
 
   - ASPEED g6 PWM/Fan tach
 
   - Astera Labs PT5161L retimer
 
   - ASUS ROG RYUJIN II 360 AIO cooler
 
   - LTC4282
 
   - Microsoft Surface devices
 
   - MPS MPQ8785 Synchronous Step-Down Converter
 
   - NZXT Kraken X and Z series AIO CPU coolers
 
 * Additional chip support in existing drivers
 
   - Ayaneo Air Plus 7320u (oxp-sensors)
 
   - INA260 (ina2xx)
 
   - XPS 9315 (dell-smm)
 
   - MSI customer ID (nct6683)
 
 * Devicetree bindings updates
 
   - Common schema for hardware monitoring devices
 
   - Common schema for fans
 
   - Update chip descriptions to use common schema
 
   - Document regulator properties in several drivers
 
   - Explicit bindings for infineon buck converters
 
 * Other improvements
 
   - Replaced rbtree with maple tree register cache in several drivers
 
   - Added support for humidity min/max alarm and volatage fault attributes
     to hwmon core
 
   - Dropped non-functional I2C_CLASS_HWMON support for drivers w/o detect()
 
   - Dropped obsolete and redundant entried from MAINTAINERS
 
   - Cleaned up axi-fan-control and coretemp drivers
 
   - Minor fixes and improvements in several other drivers
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAmXvI8QACgkQyx8mb86f
 mYFWTQ/9Hz4QfgIueAEhWGy0XORt7nSCeexkWIR341iyRk4LQA0UjDQg3+Ub5Hi1
 7IBGDNi124S1I/W8fJl1KFgXjbirhuzpHq4DF60Ty+egTZ9IZpu3uySR4mubixYm
 J3el7SIJBvs3SgMFdS/HCtJqeU5HLk+1NjfWDmnq0Z27GHzEy/Nglj2TTO1CjMz/
 tZ/LOWkdG5tMbwI8SZ/mBNMXMpYp/jnUZbrMxgZ/y+07R3jP7i1GWRjq5ZGuWaP6
 SQEs4vfss/y6WUSZZenIIigRIAiAnsNIrjUjrMKPdf0EkjB+0ljn/jLXpAsUU6fL
 07Uy+AwQb89PPWIKHdldn7/MYaR3zU+LwKwPbjULuvpo6Cj87WcIP/x7QqL//Ise
 Ix2Buy/oWoVHKG7Gtf+mF+Ott5MeFgj6pVsCN4IAYYdyai0GPM3RpFAcrIXFCjsE
 i3M5aRC46Yy8Ba6ov3Jmlh83kc9LauJrlCxIxIXTlUJIZiW7a5w083QDSaw3qQdB
 hukwfC8wOzpEsQngkBQyRSpF468lASzc4lp++tPLS/W0zxBrgrnHvgXTHnN8IxvQ
 ocuD5tVMg9gE2xT88t8BHTcw2uv03U5RoXY+nucbxA+Y/aT2t+jZhX9cPbq4+Rhe
 v7XDGMxcBYgtfwx6JT97DKqW9qLc01k8wxonCOrUop6B/+MdRbw=
 =BTB3
 -----END PGP SIGNATURE-----

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

Pull hwmon updates from Guenter Roeck:
 "New drivers:
   - Amphenol ChipCap 2
   - ASPEED g6 PWM/Fan tach
   - Astera Labs PT5161L retimer
   - ASUS ROG RYUJIN II 360 AIO cooler
   - LTC4282
   - Microsoft Surface devices
   - MPS MPQ8785 Synchronous Step-Down Converter
   - NZXT Kraken X and Z series AIO CPU coolers

  Additional chip support in existing drivers:
   - Ayaneo Air Plus 7320u (oxp-sensors)
   - INA260 (ina2xx)
   - XPS 9315 (dell-smm)
   - MSI customer ID (nct6683)

  Devicetree bindings updates:
   - Common schema for hardware monitoring devices
   - Common schema for fans
   - Update chip descriptions to use common schema
   - Document regulator properties in several drivers
   - Explicit bindings for infineon buck converters

  Other improvements:
   - Replaced rbtree with maple tree register cache in several drivers
   - Added support for humidity min/max alarm and volatage fault
     attributes to hwmon core
   - Dropped non-functional I2C_CLASS_HWMON support for drivers w/o
     detect()
   - Dropped obsolete and redundant entried from MAINTAINERS
   - Cleaned up axi-fan-control and coretemp drivers
   - Minor fixes and improvements in several other drivers"

* tag 'hwmon-for-v6.9' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (70 commits)
  hwmon: (dell-smm) Add XPS 9315 to fan control whitelist
  hwmon: (aspeed-g6-pwm-tacho): Support for ASPEED g6 PWM/Fan tach
  dt-bindings: hwmon: Support Aspeed g6 PWM TACH Control
  dt-bindings: hwmon: fan: Add fan binding to schema
  dt-bindings: hwmon: tda38640: Add interrupt & regulator properties
  hwmon: (amc6821) add of_match table
  dt-bindings: hwmon: lm75: use common hwmon schema
  hwmon: (sis5595) drop unused DIV_TO_REG function
  dt-bindings: hwmon: reference common hwmon schema
  dt-bindings: hwmon: lltc,ltc4286: use common hwmon schema
  dt-bindings: hwmon: adi,adm1275: use common hwmon schema
  dt-bindings: hwmon: ti,ina2xx: use common hwmon schema
  dt-bindings: hwmon: add common properties
  hwmon: (pmbus/ir38064) Use PMBUS_REGULATOR_ONE to declare regulator
  hwmon: (pmbus/lm25066) Use PMBUS_REGULATOR_ONE to declare regulator
  hwmon: (pmbus/tda38640) Use PMBUS_REGULATOR_ONE to declare regulator
  regulator: dt-bindings: promote infineon buck converters to their own binding
  dt-bindings: hwmon/pmbus: ti,lm25066: document regulators
  dt-bindings: hwmon: nuvoton,nct6775: Add compatible value for NCT6799
  MAINTAINERS: Drop redundant hwmon entries
  ...
This commit is contained in:
Linus Torvalds 2024-03-13 11:26:58 -07:00
commit 15223fdbdf
89 changed files with 7136 additions and 335 deletions

View File

@ -149,6 +149,15 @@ Description:
RW
What: /sys/class/hwmon/hwmonX/inY_fault
Description:
Reports a voltage hard failure (eg: shorted component)
- 1: Failed
- 0: Ok
RO
What: /sys/class/hwmon/hwmonX/cpuY_vid
Description:
CPU core reference voltage.
@ -968,6 +977,15 @@ Description:
RW
What: /sys/class/hwmon/hwmonX/humidityY_max_alarm
Description:
Maximum humidity detection
- 0: OK
- 1: Maximum humidity detected
RO
What: /sys/class/hwmon/hwmonX/humidityY_max_hyst
Description:
Humidity hysteresis value for max limit.
@ -987,6 +1005,15 @@ Description:
RW
What: /sys/class/hwmon/hwmonX/humidityY_min_alarm
Description:
Minimum humidity detection
- 0: OK
- 1: Minimum humidity detected
RO
What: /sys/class/hwmon/hwmonX/humidityY_min_hyst
Description:
Humidity hysteresis value for min limit.

View File

@ -46,7 +46,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -33,10 +33,6 @@ properties:
reg:
maxItems: 1
shunt-resistor-micro-ohms:
description:
Shunt resistor value in micro-Ohm.
adi,volt-curr-sample-average:
description: |
Number of samples to be used to report voltage and current values.
@ -50,6 +46,7 @@ properties:
enum: [1, 2, 4, 8, 16, 32, 64, 128]
allOf:
- $ref: hwmon-common.yaml#
- if:
properties:
compatible:
@ -107,7 +104,7 @@ required:
- compatible
- reg
additionalProperties: false
unevaluatedProperties: false
examples:
- |

View File

@ -31,7 +31,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -0,0 +1,159 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/adi,ltc4282.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C
maintainers:
- Nuno Sa <nuno.sa@analog.com>
description: |
Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C.
https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4282.pdf
properties:
compatible:
enum:
- adi,ltc4282
reg:
maxItems: 1
vdd-supply: true
clocks:
maxItems: 1
'#clock-cells':
const: 0
adi,rsense-nano-ohms:
description: Value of the sense resistor.
adi,vin-mode-microvolt:
description:
Selects operating range for the Undervoltage, Overvoltage and Foldback
pins. Also for the ADC. Should be set to the nominal input voltage.
enum: [3300000, 5000000, 12000000, 24000000]
default: 12000000
adi,fet-bad-timeout-ms:
description:
From the moment a FET bad conditions is present, this property selects the
wait time/timeout for a FET-bad fault to be signaled. Setting this to 0,
disables FET bad faults to be reported.
default: 255
maximum: 255
adi,overvoltage-dividers:
description: |
Select which dividers to use for VDD Overvoltage detection. Note that
when the internal dividers are used the threshold is referenced to VDD.
The percentages in the datasheet are misleading since the actual values
to look for are in the "Absolute Maximum Ratings" table in the
"Comparator Inputs" section. In there there's a line for each of the 5%,
10% and 15% settings with the actual min, typical and max tolerances.
$ref: /schemas/types.yaml#/definitions/string
enum: [external, vdd_5_percent, vdd_10_percent, vdd_15_percent]
default: external
adi,undervoltage-dividers:
description: |
Select which dividers to use for VDD Overvoltage detection. Note that
when the internal dividers are used the threshold is referenced to VDD.
The percentages in the datasheet are misleading since the actual values
to look for are in the "Absolute Maximum Ratings" table in the
"Comparator Inputs" section. In there there's a line for each of the 5%,
10% and 15% settings with the actual min, typical and max tolerances.
$ref: /schemas/types.yaml#/definitions/string
enum: [external, vdd_5_percent, vdd_10_percent, vdd_15_percent]
default: external
adi,current-limit-sense-microvolt:
description:
The current limit sense voltage of the chip is adjustable between
12.5mV and 34.4mV in 3.1mV steps. This effectively limits the current
on the load.
enum: [12500, 15625, 18750, 21875, 25000, 28125, 31250, 34375]
default: 25000
adi,overcurrent-retry:
description:
If set, enables the chip to auto-retry 256 timer cycles after an
Overcurrent fault.
type: boolean
adi,overvoltage-retry-disable:
description:
If set, disables the chip to auto-retry 50ms after an Overvoltage fault.
It's enabled by default.
type: boolean
adi,undervoltage-retry-disable:
description:
If set, disables the chip to auto-retry 50ms after an Undervoltage fault.
It's enabled by default.
type: boolean
adi,fault-log-enable:
description:
If set, enables the FAULT_LOG and ADC_ALERT_LOG registers to be written
to the EEPROM when a fault bit transitions high and hence, will be
available after a power cycle (the chip loads the contents of
the EE_FAULT_LOG register - the one in EEPROM - into FAULT_LOG at boot).
type: boolean
adi,gpio1-mode:
description: Defines the function of the Pin. It can indicate that power is
good (PULL the pin low when power is not good) or that power is bad (Go
into high-z when power is not good).
$ref: /schemas/types.yaml#/definitions/string
enum: [power_bad, power_good]
default: power_good
adi,gpio2-mode:
description: Defines the function of the Pin. It can be set as the input for
the ADC or indicating that the MOSFET is in stress (dissipating power).
$ref: /schemas/types.yaml#/definitions/string
enum: [adc_input, stress_fet]
default: adc_input
adi,gpio3-monitor-enable:
description: If set, gpio3 is set as input for the ADC instead of gpio2.
type: boolean
allOf:
- if:
required:
- adi,gpio3-monitor-enable
then:
properties:
adi,gpio2-mode:
const: stress_fet
required:
- compatible
- reg
- adi,rsense-nano-ohms
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
hwmon@50 {
compatible = "adi,ltc4282";
reg = <0x50>;
adi,rsense-nano-ohms = <500>;
adi,gpio1-mode = "power_good";
adi,gpio2-mode = "adc_input";
};
};
...

View File

@ -0,0 +1,77 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/amphenol,chipcap2.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ChipCap 2 humidity and temperature iio sensor
maintainers:
- Javier Carrasco <javier.carrasco.cruz@gmail.com>
description: |
Relative humidity and temperature sensor on I2C bus.
Datasheets:
https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
properties:
compatible:
oneOf:
- const: amphenol,cc2d23
- items:
- enum:
- amphenol,cc2d23s
- amphenol,cc2d25
- amphenol,cc2d25s
- amphenol,cc2d33
- amphenol,cc2d33s
- amphenol,cc2d35
- amphenol,cc2d35s
- const: amphenol,cc2d23
reg:
maxItems: 1
interrupts:
items:
- description: measurement ready indicator
- description: low humidity alarm
- description: high humidity alarm
interrupt-names:
items:
- const: ready
- const: low
- const: high
vdd-supply:
description:
Dedicated, controllable supply-regulator to reset the device and
enter in command mode.
required:
- compatible
- reg
- vdd-supply
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
humidity@28 {
compatible = "amphenol,cc2d23s", "amphenol,cc2d23";
reg = <0x28>;
interrupt-parent = <&gpio>;
interrupts = <4 IRQ_TYPE_EDGE_RISING>,
<5 IRQ_TYPE_EDGE_RISING>,
<6 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "ready", "low", "high";
vdd-supply = <&reg_vdd>;
};
};

View File

@ -0,0 +1,71 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) 2023 Aspeed, Inc.
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/aspeed,g6-pwm-tach.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ASPEED G6 PWM and Fan Tach controller
maintainers:
- Billy Tsai <billy_tsai@aspeedtech.com>
description: |
The ASPEED PWM controller can support up to 16 PWM outputs.
The ASPEED Fan Tacho controller can support up to 16 fan tach input.
They are independent hardware blocks, which are different from the
previous version of the ASPEED chip.
properties:
compatible:
enum:
- aspeed,ast2600-pwm-tach
reg:
maxItems: 1
clocks:
maxItems: 1
resets:
maxItems: 1
"#pwm-cells":
const: 3
patternProperties:
"^fan-[0-9]+$":
$ref: fan-common.yaml#
unevaluatedProperties: false
required:
- tach-ch
required:
- reg
- clocks
- resets
- "#pwm-cells"
- compatible
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/aspeed-clock.h>
pwm_tach: pwm-tach-controller@1e610000 {
compatible = "aspeed,ast2600-pwm-tach";
reg = <0x1e610000 0x100>;
clocks = <&syscon ASPEED_CLK_AHB>;
resets = <&syscon ASPEED_RESET_PWM>;
#pwm-cells = <3>;
fan-0 {
tach-ch = /bits/ 8 <0x0>;
pwms = <&pwm_tach 0 40000 0>;
};
fan-1 {
tach-ch = /bits/ 8 <0x1 0x2>;
pwms = <&pwm_tach 1 40000 0>;
};
};

View File

@ -0,0 +1,79 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/fan-common.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Common Fan Properties
maintainers:
- Naresh Solanki <naresh.solanki@9elements.com>
- Billy Tsai <billy_tsai@aspeedtech.com>
properties:
max-rpm:
description:
Max RPM supported by fan.
$ref: /schemas/types.yaml#/definitions/uint32
maximum: 100000
min-rpm:
description:
Min RPM supported by fan.
$ref: /schemas/types.yaml#/definitions/uint32
maximum: 1000
pulses-per-revolution:
description:
The number of pulse from fan sensor per revolution.
$ref: /schemas/types.yaml#/definitions/uint32
maximum: 4
tach-div:
description:
Divisor for the tach sampling clock, which determines the sensitivity of the tach pin.
$ref: /schemas/types.yaml#/definitions/uint32
target-rpm:
description:
The default desired fan speed in RPM.
$ref: /schemas/types.yaml#/definitions/uint32
fan-driving-mode:
description:
Select the driving mode of the fan.(DC, PWM and so on)
$ref: /schemas/types.yaml#/definitions/string
enum: [ dc, pwm ]
pwms:
description:
PWM provider.
maxItems: 1
"#cooling-cells":
const: 2
cooling-levels:
description:
The control value which correspond to thermal cooling states.
$ref: /schemas/types.yaml#/definitions/uint32-array
tach-ch:
description:
The tach channel used for the fan.
$ref: /schemas/types.yaml#/definitions/uint8-array
label:
description:
Optional fan label
fan-supply:
description:
Power supply for fan.
reg:
maxItems: 1
additionalProperties: true
...

View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/hwmon-common.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Hardware Monitoring Devices Common Properties
maintainers:
- Guenter Roeck <linux@roeck-us.net>
properties:
label:
description: A descriptive name for this device.
shunt-resistor-micro-ohms:
description: The value of current sense resistor.
additionalProperties: true

View File

@ -25,7 +25,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -25,15 +25,14 @@ properties:
The default is 102.4 volts.
type: boolean
shunt-resistor-micro-ohms:
description:
Resistor value micro-ohms.
required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -57,6 +57,7 @@ required:
- reg
allOf:
- $ref: hwmon-common.yaml#
- if:
not:
properties:
@ -71,7 +72,7 @@ allOf:
properties:
interrupts: false
additionalProperties: false
unevaluatedProperties: false
examples:
- |

View File

@ -25,6 +25,7 @@ properties:
- nuvoton,nct6796
- nuvoton,nct6797
- nuvoton,nct6798
- nuvoton,nct6799
reg:
maxItems: 1

View File

@ -30,6 +30,23 @@ properties:
unconnected(has internal pull-down).
type: boolean
interrupts:
maxItems: 1
regulators:
type: object
description:
list of regulators provided by this controller.
properties:
vout:
$ref: /schemas/regulator/regulator.yaml#
type: object
unevaluatedProperties: false
additionalProperties: false
required:
- compatible
- reg
@ -38,6 +55,7 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
@ -45,5 +63,15 @@ examples:
tda38640@40 {
compatible = "infineon,tda38640";
reg = <0x40>;
interrupt-parent = <&smb_pex_cpu0_event>;
interrupts = <10 IRQ_TYPE_LEVEL_LOW>;
regulators {
pvnn_main_cpu0: vout {
regulator-name = "pvnn_main_cpu0";
regulator-enable-ramp-delay = <200>;
};
};
};
};

View File

@ -34,11 +34,26 @@ properties:
Shunt (sense) resistor value in micro-Ohms
default: 1000
regulators:
type: object
properties:
vout:
$ref: /schemas/regulator/regulator.yaml#
type: object
unevaluatedProperties: false
additionalProperties: false
required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: /schemas/hwmon/hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -28,10 +28,14 @@ properties:
- ti,ina231
- ti,ina237
- ti,ina238
- ti,ina260
reg:
maxItems: 1
"#io-channel-cells":
const: 1
shunt-resistor:
description:
Shunt resistor value in micro-Ohm.
@ -66,7 +70,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |
@ -77,6 +84,8 @@ examples:
power-sensor@44 {
compatible = "ti,ina220";
reg = <0x44>;
#io-channel-cells = <1>;
label = "vdd_3v0";
shunt-resistor = <1000>;
vs-supply = <&vdd_3v0>;
};

View File

@ -72,7 +72,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -35,7 +35,10 @@ required:
- compatible
- reg
additionalProperties: false
allOf:
- $ref: hwmon-common.yaml#
unevaluatedProperties: false
examples:
- |

View File

@ -0,0 +1,45 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/regulator/infineon,ir38060.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Infineon Buck Regulators with PMBUS interfaces
maintainers:
- Not Me.
allOf:
- $ref: regulator.yaml#
properties:
compatible:
enum:
- infineon,ir38060
- infineon,ir38064
- infineon,ir38164
- infineon,ir38263
reg:
maxItems: 1
required:
- compatible
- reg
unevaluatedProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
regulator@34 {
compatible = "infineon,ir38060";
reg = <0x34>;
regulator-min-microvolt = <437500>;
regulator-max-microvolt = <1387500>;
};
};

View File

@ -47,6 +47,8 @@ properties:
- adi,lt7182s
# AMS iAQ-Core VOC Sensor
- ams,iaq-core
# Temperature monitoring of Astera Labs PT5161L PCIe retimer
- asteralabs,pt5161l
# i2c serial eeprom (24cxx)
- at,24c08
# ATSHA204 - i2c h/w symmetric crypto module
@ -129,6 +131,8 @@ properties:
- mps,mp2975
# Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990
- mps,mp5990
# Monolithic Power Systems Inc. synchronous step-down converter mpq8785
- mps,mpq8785
# Honeywell Humidicon HIH-6130 humidity/temperature sensor
- honeywell,hi6130
# IBM Common Form Factor Power Supply Versions (all versions)
@ -139,14 +143,6 @@ properties:
- ibm,cffps2
# Infineon IR36021 digital POL buck controller
- infineon,ir36021
# Infineon IR38060 Voltage Regulator
- infineon,ir38060
# Infineon IR38064 Voltage Regulator
- infineon,ir38064
# Infineon IR38164 Voltage Regulator
- infineon,ir38164
# Infineon IR38263 Voltage Regulator
- infineon,ir38263
# Infineon IRPS5401 Voltage Regulator (PMIC)
- infineon,irps5401
# Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor

View File

@ -109,6 +109,8 @@ patternProperties:
description: Amlogic, Inc.
"^ampere,.*":
description: Ampere Computing LLC
"^amphenol,.*":
description: Amphenol Advanced Sensors
"^ampire,.*":
description: Ampire Co., Ltd.
"^ams,.*":
@ -161,6 +163,8 @@ patternProperties:
description: ASPEED Technology Inc.
"^asrock,.*":
description: ASRock Inc.
"^asteralabs,.*":
description: Astera Labs, Inc.
"^asus,.*":
description: AsusTek Computer Inc.
"^atheros,.*":

View File

@ -0,0 +1,26 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver aspeed-g6-pwm-tach
=================================
Supported chips:
ASPEED AST2600
Authors:
<billy_tsai@aspeedtech.com>
Description:
------------
This driver implements support for ASPEED AST2600 Fan Tacho controller.
The controller supports up to 16 tachometer inputs.
The driver provides the following sensor accesses in sysfs:
=============== ======= ======================================================
fanX_input ro provide current fan rotation value in RPM as reported
by the fan to the device.
fanX_div rw Fan divisor: Supported value are power of 4 (1, 4, 16
64, ... 4194304)
The larger divisor, the less rpm accuracy and the less
affected by fan signal glitch.
=============== ======= ======================================================

View File

@ -0,0 +1,47 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver asus_rog_ryujin
=============================
Supported devices:
* ASUS ROG RYUJIN II 360
Author: Aleksa Savic
Description
-----------
This driver enables hardware monitoring support for the listed ASUS ROG RYUJIN
all-in-one CPU liquid coolers. Available sensors are pump, internal and external
(controller) fan speed in RPM, their duties in PWM, as well as coolant temperature.
Attaching external fans to the controller is optional and allows them to be
controlled from the device. If not connected, the fan-related sensors will
report zeroes. The controller is a separate hardware unit that comes bundled
with the AIO and connects to it to allow fan control.
The addressable LCD screen is not supported in this driver and should
be controlled through userspace tools.
Usage notes
-----------
As these are USB HIDs, the driver can be loaded automatically by the kernel and
supports hot swapping.
Sysfs entries
-------------
=========== =============================================
fan1_input Pump speed (in rpm)
fan2_input Internal fan speed (in rpm)
fan3_input External (controller) fan 1 speed (in rpm)
fan4_input External (controller) fan 2 speed (in rpm)
fan5_input External (controller) fan 3 speed (in rpm)
fan6_input External (controller) fan 4 speed (in rpm)
temp1_input Coolant temperature (in millidegrees Celsius)
pwm1 Pump duty
pwm2 Internal fan duty
pwm3 External (controller) fan duty
=========== =============================================

View File

@ -0,0 +1,73 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver ChipCap2
======================
Supported chips:
* Amphenol CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
Prefix: 'chipcap2'
Addresses scanned: -
Datasheet: https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
Author:
- Javier Carrasco <javier.carrasco.cruz@gmail.com>
Description
-----------
This driver implements support for the Amphenol ChipCap 2, a humidity and
temperature chip family. Temperature is measured in milli degrees celsius,
relative humidity is expressed as a per cent mille. The measurement ranges
are the following:
- Relative humidity: 0 to 100000 pcm (14-bit resolution)
- Temperature: -40000 to +125000 m°C (14-bit resolution)
The device communicates with the I2C protocol and uses the I2C address 0x28
by default.
Depending on the hardware configuration, up to two humidity alarms to control
minimum and maximum values are provided. Their thresholds and hystersis can be
configured via sysfs.
Thresholds and hysteris must be provided as a per cent mille. These values
might be truncated to match the 14-bit device resolution (6.1 pcm/LSB)
Known Issues
------------
The driver does not support I2C address and command window length modification.
sysfs-Interface
---------------
The following list includes the sysfs attributes that the driver always provides,
their permissions and a short description:
=============================== ======= ========================================
Name Perm Description
=============================== ======= ========================================
temp1_input: RO temperature input
humidity1_input: RO humidity input
=============================== ======= ========================================
The following list includes the sysfs attributes that the driver may provide
depending on the hardware configuration:
=============================== ======= ========================================
Name Perm Description
=============================== ======= ========================================
humidity1_min: RW humidity low limit. Measurements under
this limit trigger a humidity low alarm
humidity1_max: RW humidity high limit. Measurements above
this limit trigger a humidity high alarm
humidity1_min_hyst: RW humidity low hystersis
humidity1_max_hyst: RW humidity high hystersis
humidity1_min_alarm: RO humidity low alarm indicator
humidity1_max_alarm: RO humidity high alarm indicator
=============================== ======= ========================================

View File

@ -6,7 +6,6 @@ Kernel driver emc2305
Supported chips:
Microchip EMC2305, EMC2303, EMC2302, EMC2301
Addresses scanned: I2C 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d
Prefixes: 'emc2305'
Datasheet: Publicly available at the Microchip website :

View File

@ -44,13 +44,16 @@ Hardware Monitoring Kernel Drivers
aquacomputer_d5next
asb100
asc7621
aspeed-g6-pwm-tach
aspeed-pwm-tacho
asus_ec_sensors
asus_rog_ryujin
asus_wmi_sensors
bcm54140
bel-pfe
bpa-rs600
bt1-pvt
chipcap2
coretemp
corsair-cpro
corsair-psu
@ -129,6 +132,7 @@ Hardware Monitoring Kernel Drivers
ltc4245
ltc4260
ltc4261
ltc4282
ltc4286
max127
max15301
@ -163,6 +167,7 @@ Hardware Monitoring Kernel Drivers
mp2975
mp5023
mp5990
mpq8785
nct6683
nct6775
nct7802
@ -171,6 +176,7 @@ Hardware Monitoring Kernel Drivers
nsa320
ntc_thermistor
nzxt-kraken2
nzxt-kraken3
nzxt-smart2
occ
oxp-sensors
@ -185,6 +191,7 @@ Hardware Monitoring Kernel Drivers
pmbus
powerz
powr1220
pt5161l
pxe1610
pwm-fan
q54sj108a2
@ -208,6 +215,7 @@ Hardware Monitoring Kernel Drivers
smsc47m1
sparx5-temp
stpddc60
surface_fan
sy7636a-hwmon
tc654
tc74

View File

@ -0,0 +1,133 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel drivers ltc4282
==========================================
Supported chips:
* Analog Devices LTC4282
Prefix: 'ltc4282'
Addresses scanned: - I2C 0x40 - 0x5A (7-bit)
Addresses scanned: - I2C 0x80 - 0xB4 with a step of 2 (8-bit)
Datasheet:
https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4282.pdf
Author: Nuno Sá <nuno.sa@analog.com>
Description
___________
The LTC4282 hot swap controller allows a board to be safely inserted and removed
from a live backplane. Using one or more external N-channel pass transistors,
board supply voltage and inrush current are ramped up at an adjustable rate. An
I2C interface and onboard ADC allows for monitoring of board current, voltage,
power, energy and fault status. The device features analog foldback current
limiting and supply monitoring for applications from 2.9V to 33V. Dual 12V gate
drive allows high power applications to either share safe operating area across
parallel MOSFETs or support a 2-stage start-up that first charges the load
capacitance followed by enabling a low on-resistance path to the load. The
LTC4282 is well suited to high power applications because the precise monitoring
capability and accurate current limiting reduce the extremes in which both loads
and power supplies must safely operate. Non-volatile configuration allows for
flexibility in the autonomous generation of alerts and response to faults.
Sysfs entries
_____________
The following attributes are supported. Limits are read-write and all the other
attributes are read-only. Note that in0 and in1 are mutually exclusive. Enabling
one disables the other and disabling one enables the other.
======================= ==========================================
in0_input Output voltage (mV).
in0_min Undervoltage threshold
in0_max Overvoltage threshold
in0_lowest Lowest measured voltage
in0_highest Highest measured voltage
in0_reset_history Write 1 to reset in0 history.
Also clears fet bad and short fault logs.
in0_min_alarm Undervoltage alarm
in0_max_alarm Overvoltage alarm
in0_enable Enable/Disable VSOURCE monitoring
in0_fault Failure in the MOSFETs. Either bad or shorted FET.
in0_label Channel label (VSOURCE)
in1_input Input voltage (mV).
in1_min Undervoltage threshold
in1_max Overvoltage threshold
in1_lowest Lowest measured voltage
in1_highest Highest measured voltage
in1_reset_history Write 1 to reset in1 history.
Also clears over/undervoltage fault logs.
in1_min_alarm Undervoltage alarm
in1_max_alarm Overvoltage alarm
in1_lcrit_alarm Critical Undervoltage alarm
in1_crit_alarm Critical Overvoltage alarm
in1_enable Enable/Disable VDD monitoring
in1_label Channel label (VDD)
in2_input GPIO voltage (mV)
in2_min Undervoltage threshold
in2_max Overvoltage threshold
in2_lowest Lowest measured voltage
in2_highest Highest measured voltage
in2_reset_history Write 1 to reset in2 history
in2_min_alarm Undervoltage alarm
in2_max_alarm Overvoltage alarm
in2_label Channel label (VGPIO)
curr1_input Sense current (mA)
curr1_min Undercurrent threshold
curr1_max Overcurrent threshold
curr1_lowest Lowest measured current
curr1_highest Highest measured current
curr1_reset_history Write 1 to reset curr1 history.
Also clears overcurrent fault logs.
curr1_min_alarm Undercurrent alarm
curr1_max_alarm Overcurrent alarm
curr1_crit_alarm Critical Overcurrent alarm
curr1_label Channel label (ISENSE)
power1_input Power (in uW)
power1_min Low power threshold
power1_max High power threshold
power1_input_lowest Historical minimum power use
power1_input_highest Historical maximum power use
power1_reset_history Write 1 to reset power1 history.
Also clears power bad fault logs.
power1_min_alarm Low power alarm
power1_max_alarm High power alarm
power1_label Channel label (Power)
energy1_input Measured energy over time (in microJoule)
energy1_enable Enable/Disable Energy accumulation
======================= ==========================================
DebugFs entries
_______________
The chip also has a fault log register where failures can be logged. Hence,
as these are logging events, we give access to them in debugfs. Note that
even if some failure is detected in these logs, it does necessarily mean
that the failure is still present. As mentioned in the proper Sysfs entries,
these logs can be cleared by writing in the proper reset_history attribute.
.. warning:: The debugfs interface is subject to change without notice
and is only available when the kernel is compiled with
``CONFIG_DEBUG_FS`` defined.
``/sys/kernel/debug/ltc4282-hwmon[X]/``
contains the following attributes:
======================= ==========================================
power1_bad_fault_log Set to 1 by a power1 bad fault occurring.
in0_fet_short_fault_log Set to 1 when the ADC detects a FET-short fault.
in0_fet_bad_fault_log Set to 1 when a FET-BAD fault occurs.
in1_crit_fault_log Set to 1 by a VDD overvoltage fault occurring.
in1_lcrit_fault_log Set to 1 by a VDD undervoltage fault occurring.
curr1_crit_fault_log Set to 1 by an overcurrent fault occurring.
======================= ==========================================

View File

@ -11,7 +11,7 @@ Supported chips:
Addresses scanned: none
Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6620.pdf
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max6620.pdf
Authors:
- L\. Grunenberg <contact@lgrunenberg.de>

View File

@ -0,0 +1,94 @@
.. SPDX-License-Identifier: GPL-2.0-only
Kernel driver mpq8785
=======================
Supported chips:
* MPS MPQ8785
Prefix: 'mpq8785'
Author: Charles Hsu <ythsu0511@gmail.com>
Description
-----------
The MPQ8785 is a fully integrated, PMBus-compatible, high-frequency, synchronous
buck converter. The MPQ8785 offers a very compact solution that achieves up to
40A output current per phase, with excellent load and line regulation over a
wide input supply range. The MPQ8785 operates at high efficiency over a wide
output current load range.
The PMBus interface provides converter configurations and key parameters
monitoring.
The MPQ8785 adopts MPS's proprietary multi-phase digital constant-on-time (MCOT)
control, which provides fast transient response and eases loop stabilization.
The MCOT scheme also allows multiple MPQ8785 devices to be connected in parallel
with excellent current sharing and phase interleaving for high-current
applications.
Fully integrated protection features include over-current protection (OCP),
over-voltage protection (OVP), under-voltage protection (UVP), and
over-temperature protection (OTP).
The MPQ8785 requires a minimal number of readily available, standard external
components, and is available in a TLGA (5mmx6mm) package.
Device compliant with:
- PMBus rev 1.3 interface.
The driver exports the following attributes via the 'sysfs' files
for input voltage:
**in1_input**
**in1_label**
**in1_max**
**in1_max_alarm**
**in1_min**
**in1_min_alarm**
**in1_crit**
**in1_crit_alarm**
The driver provides the following attributes for output voltage:
**in2_input**
**in2_label**
**in2_alarm**
The driver provides the following attributes for output current:
**curr1_input**
**curr1_label**
**curr1_max**
**curr1_max_alarm**
**curr1_crit**
**curr1_crit_alarm**
The driver provides the following attributes for temperature:
**temp1_input**
**temp1_max**
**temp1_max_alarm**
**temp1_crit**
**temp1_crit_alarm**

View File

@ -64,4 +64,5 @@ Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13
ASRock X570 NCT6683D EC firmware version 1.0 build 06/28/19
ASRock X670E NCT6686D EC firmware version 1.0 build 05/19/22
MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20
MSI X670-P NCT6687D EC firmware version 0.0 build 09/27/22
=============== ===============================================

View File

@ -0,0 +1,74 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver nzxt-kraken3
==========================
Supported devices:
* NZXT Kraken X53
* NZXT Kraken X63
* NZXT Kraken X73
* NZXT Kraken Z53
* NZXT Kraken Z63
* NZXT Kraken Z73
Author: Jonas Malaco, Aleksa Savic
Description
-----------
This driver enables hardware monitoring support for NZXT Kraken X53/X63/X73 and
Z53/Z63/Z73 all-in-one CPU liquid coolers. All models expose liquid temperature
and pump speed (in RPM), as well as PWM control (either as a fixed value
or through a temp-PWM curve). The Z-series models additionally expose the speed
and duty of an optionally connected fan, with the same PWM control capabilities.
Pump and fan duty control mode can be set through pwm[1-2]_enable, where 1 is
for the manual control mode and 2 is for the liquid temp to PWM curve mode.
Writing a 0 disables control of the channel through the driver after setting its
duty to 100%.
The temperature of the curves relates to the fixed [20-59] range, correlating to
the detected liquid temperature. Only PWM values (ranging from 0-255) can be set.
If in curve mode, setting point values should be done in moderation - the devices
require complete curves to be sent for each change; they can lock up or discard
the changes if they are too numerous at once. Suggestion is to set them while
in an another mode, and then apply them by switching to curve.
The devices can report if they are faulty. The driver supports that situation
and will issue a warning. This can also happen when the USB cable is connected,
but SATA power is not.
The addressable RGB LEDs and LCD screen (only on Z-series models) are not
supported in this driver, but can be controlled through existing userspace tools,
such as `liquidctl`_.
.. _liquidctl: https://github.com/liquidctl/liquidctl
Usage Notes
-----------
As these are USB HIDs, the driver can be loaded automatically by the kernel and
supports hot swapping.
Possible pwm_enable values are:
====== ==========================================================================
0 Set fan to 100%
1 Direct PWM mode (applies value in corresponding PWM entry)
2 Curve control mode (applies the temp-PWM duty curve based on coolant temp)
====== ==========================================================================
Sysfs entries
-------------
============================== ================================================================
fan1_input Pump speed (in rpm)
fan2_input Fan speed (in rpm)
temp1_input Coolant temperature (in millidegrees Celsius)
pwm1 Pump duty (value between 0-255)
pwm1_enable Pump duty control mode (0: disabled, 1: manual, 2: curve)
pwm2 Fan duty (value between 0-255)
pwm2_enable Fan duty control mode (0: disabled, 1: manual, 2: curve)
temp[1-2]_auto_point[1-40]_pwm Temp-PWM duty curves (for pump and fan), related to coolant temp
============================== ================================================================

View File

@ -33,6 +33,7 @@ Currently the driver supports the following handhelds:
- AOK ZOE A1 PRO
- Aya Neo 2
- Aya Neo AIR
- Aya Neo AIR Plus (Mendocino)
- Aya Neo AIR Pro
- Aya Neo Geek
- OneXPlayer AMD

View File

@ -0,0 +1,42 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver pt5161l
=====================
Supported chips:
* Astera Labs PT5161L
Prefix: 'pt5161l'
Addresses scanned: I2C 0x20 - 0x27
Datasheet: Not publicly available.
Authors: Cosmo Chou <cosmo.chou@quantatw.com>
Description
-----------
This driver implements support for temperature monitoring of Astera Labs
PT5161L series PCIe retimer chips.
This driver implementation originates from the CSDK available at
https://github.com/facebook/openbmc/tree/helium/common/recipes-lib/retimer-v2.14
The communication protocol utilized is based on the I2C/SMBus standard.
Sysfs entries
----------------
================ ==============================================
temp1_input Measured temperature (in millidegrees Celsius)
================ ==============================================
Debugfs entries
----------------
================ ===============================
fw_load_status Firmware load status
fw_ver Firmware version of the retimer
heartbeat_status Heartbeat status
================ ===============================

View File

@ -65,6 +65,10 @@ When the temperature and humidity readings move back between the hysteresis
values, the alert bit is set to 0 and the alert pin on the sensor is set to
low.
The serial number exposed to debugfs allows for unique identification of the
sensors. For sts32, sts33 and sht33, the manufacturer provides calibration
certificates through an API.
sysfs-Interface
---------------
@ -99,3 +103,10 @@ repeatability: write or read repeatability, higher repeatability means
- 1: medium repeatability
- 2: high repeatability
=================== ============================================================
debugfs-Interface
-----------------
=================== ============================================================
serial_number: unique serial number of the sensor in decimal
=================== ============================================================

View File

@ -0,0 +1,25 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver surface_fan
=========================
Supported Devices:
* Microsoft Surface Pro 9
Author: Ivor Wanders <ivor@iwanders.net>
Description
-----------
This provides monitoring of the fan found in some Microsoft Surface Pro devices,
like the Surface Pro 9. The fan is always controlled by the onboard controller.
Sysfs interface
---------------
======================= ======= =========================================
Name Perm Description
======================= ======= =========================================
``fan1_input`` RO Current fan speed in RPM.
======================= ======= =========================================

View File

@ -1104,6 +1104,14 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml
F: drivers/perf/amlogic/
F: include/soc/amlogic/
AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER
M: Javier Carrasco <javier.carrasco.cruz@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml
F: Documentation/hwmon/chipcap2.rst
F: drivers/hwmon/chipcap2.c
AMPHION VPU CODEC V4L2 DRIVER
M: Ming Qian <ming.qian@nxp.com>
M: Zhou Peng <eagle.zhou@nxp.com>
@ -1390,15 +1398,6 @@ F: drivers/iio/amplifiers/hmc425a.c
F: drivers/staging/iio/*/ad*
X: drivers/iio/*/adjd*
ANALOG DEVICES INC MAX31760 DRIVER
M: Ibrahim Tilki <Ibrahim.Tilki@analog.com>
S: Maintained
W: http://wiki.analog.com/
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/hwmon/adi,max31760.yaml
F: Documentation/hwmon/max31760.rst
F: drivers/hwmon/max31760.c
ANALOGBITS PLL LIBRARIES
M: Paul Walmsley <paul.walmsley@sifive.com>
M: Samuel Holland <samuel.holland@sifive.com>
@ -3190,6 +3189,12 @@ S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
F: drivers/platform/x86/asus-tf103c-dock.c
ASUS ROG RYUJIN AIO HARDWARE MONITOR DRIVER
M: Aleksa Savic <savicaleksa83@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: drivers/hwmon/asus_rog_ryujin.c
ASUS WIRELESS RADIO CONTROL DRIVER
M: João Paulo Rechi Vita <jprvita@gmail.com>
L: platform-driver-x86@vger.kernel.org
@ -10531,22 +10536,6 @@ L: linux-fbdev@vger.kernel.org
S: Orphan
F: drivers/video/fbdev/imsttfb.c
INA209 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml
F: Documentation/hwmon/ina209.rst
F: drivers/hwmon/ina209.c
INA2XX HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/ina2xx.rst
F: drivers/hwmon/ina2xx.c
F: include/linux/platform_data/ina2xx.h
INDEX OF FURTHER KERNEL DOCUMENTATION
M: Carlos Bilbao <carlos.bilbao@amd.com>
S: Maintained
@ -11531,14 +11520,6 @@ S: Maintained
F: arch/x86/include/asm/jailhouse_para.h
F: arch/x86/kernel/jailhouse.c
JC42.4 TEMPERATURE SENSOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/jedec,jc42.yaml
F: Documentation/hwmon/jc42.rst
F: drivers/hwmon/jc42.c
JFS FILESYSTEM
M: Dave Kleikamp <shaggy@kernel.org>
L: jfs-discussion@lists.sourceforge.net
@ -12603,13 +12584,6 @@ F: Documentation/hwmon/lm90.rst
F: drivers/hwmon/lm90.c
F: include/dt-bindings/thermal/lm90.h
LM95234 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/lm95234.rst
F: drivers/hwmon/lm95234.c
LME2510 MEDIA DRIVER
M: Malcolm Priestley <tvboxspy@gmail.com>
L: linux-media@vger.kernel.org
@ -12813,12 +12787,13 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
F: drivers/iio/temperature/ltc2983.c
LTC4261 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
LTC4282 HARDWARE MONITOR DRIVER
M: Nuno Sa <nuno.sa@analog.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/ltc4261.rst
F: drivers/hwmon/ltc4261.c
S: Supported
F: Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml
F: Documentation/hwmon/ltc4282.rst
F: drivers/hwmon/ltc4282.c
LTC4286 HARDWARE MONITOR DRIVER
M: Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
@ -13176,13 +13151,6 @@ S: Maintained
F: Documentation/hwmon/max15301.rst
F: drivers/hwmon/pmbus/max15301.c
MAX16065 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/max16065.rst
F: drivers/hwmon/max16065.c
MAX2175 SDR TUNER DRIVER
M: Ramesh Shanmugasundaram <rashanmu@gmail.com>
L: linux-media@vger.kernel.org
@ -13193,15 +13161,6 @@ F: Documentation/userspace-api/media/drivers/max2175.rst
F: drivers/media/i2c/max2175*
F: include/uapi/linux/max2175.h
MAX31827 TEMPERATURE SWITCH DRIVER
M: Daniel Matyas <daniel.matyas@analog.com>
L: linux-hwmon@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/hwmon/adi,max31827.yaml
F: Documentation/hwmon/max31827.rst
F: drivers/hwmon/max31827.c
MAX31335 RTC DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-rtc@vger.kernel.org
@ -13216,15 +13175,6 @@ S: Orphan
F: Documentation/hwmon/max6650.rst
F: drivers/hwmon/max6650.c
MAX6697 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/max6697.txt
F: Documentation/hwmon/max6697.rst
F: drivers/hwmon/max6697.c
F: include/linux/platform_data/max6697.h
MAX9286 QUAD GMSL DESERIALIZER DRIVER
M: Jacopo Mondi <jacopo+renesas@jmondi.org>
M: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
@ -14630,6 +14580,14 @@ F: Documentation/driver-api/surface_aggregator/clients/dtx.rst
F: drivers/platform/surface/surface_dtx.c
F: include/uapi/linux/surface_aggregator/dtx.h
MICROSOFT SURFACE SENSOR FAN DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
M: Ivor Wanders <ivor@iwanders.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/surface_fan.rst
F: drivers/hwmon/surface_fan.c
MICROSOFT SURFACE GPE LID SUPPORT DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
L: platform-driver-x86@vger.kernel.org
@ -15134,15 +15092,6 @@ M: Samuel Mendoza-Jonas <sam@mendozajonas.com>
S: Maintained
F: net/ncsi/
NCT6775 HARDWARE MONITOR DRIVER - CORE & PLATFORM DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/nct6775.rst
F: drivers/hwmon/nct6775-core.c
F: drivers/hwmon/nct6775-platform.c
F: drivers/hwmon/nct6775.h
NCT6775 HARDWARE MONITOR DRIVER - I2C DRIVER
M: Zev Weiss <zev@bewilderbeest.net>
L: linux-hwmon@vger.kernel.org
@ -15898,6 +15847,14 @@ S: Maintained
F: Documentation/hwmon/nzxt-kraken2.rst
F: drivers/hwmon/nzxt-kraken2.c
NZXT-KRAKEN3 HARDWARE MONITORING DRIVER
M: Jonas Malaco <jonas@protocubo.io>
M: Aleksa Savic <savicaleksa83@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/nzxt-kraken3.rst
F: drivers/hwmon/nzxt-kraken3.c
NZXT-SMART2 HARDWARE MONITORING DRIVER
M: Aleksandr Mezin <mezin.alexander@gmail.com>
L: linux-hwmon@vger.kernel.org
@ -17490,35 +17447,6 @@ S: Maintained
F: Documentation/hwmon/pm6764tr.rst
F: drivers/hwmon/pmbus/pm6764tr.c
PMBUS HARDWARE MONITORING DRIVERS
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
W: http://hwmon.wiki.kernel.org/
W: http://www.roeck-us.net/linux/drivers/
T: git git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git
F: Documentation/devicetree/bindings/hwmon/ltc2978.txt
F: Documentation/devicetree/bindings/hwmon/max31785.txt
F: Documentation/hwmon/adm1275.rst
F: Documentation/hwmon/ibm-cffps.rst
F: Documentation/hwmon/ir35221.rst
F: Documentation/hwmon/lm25066.rst
F: Documentation/hwmon/ltc2978.rst
F: Documentation/hwmon/ltc3815.rst
F: Documentation/hwmon/max16064.rst
F: Documentation/hwmon/max20751.rst
F: Documentation/hwmon/max31785.rst
F: Documentation/hwmon/max34440.rst
F: Documentation/hwmon/max8688.rst
F: Documentation/hwmon/pmbus-core.rst
F: Documentation/hwmon/pmbus.rst
F: Documentation/hwmon/tps40422.rst
F: Documentation/hwmon/ucd9000.rst
F: Documentation/hwmon/ucd9200.rst
F: Documentation/hwmon/zl6100.rst
F: drivers/hwmon/pmbus/
F: include/linux/pmbus.h
PMC SIERRA MaxRAID DRIVER
L: linux-scsi@vger.kernel.org
S: Orphan
@ -17740,6 +17668,13 @@ F: fs/pstore/
F: include/linux/pstore*
K: \b(pstore|ramoops)
PT5161L HARDWARE MONITOR DRIVER
M: Cosmo Chou <cosmo.chou@quantatw.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/pt5161l.rst
F: drivers/hwmon/pt5161l.c
PTP HARDWARE CLOCK SUPPORT
M: Richard Cochran <richardcochran@gmail.com>
L: netdev@vger.kernel.org
@ -22227,22 +22162,6 @@ F: drivers/mmc/host/renesas_sdhi*
F: drivers/mmc/host/tmio_mmc*
F: include/linux/mfd/tmio.h
TMP401 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml
F: Documentation/hwmon/tmp401.rst
F: drivers/hwmon/tmp401.c
TMP464 HARDWARE MONITOR DRIVER
M: Guenter Roeck <linux@roeck-us.net>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml
F: Documentation/hwmon/tmp464.rst
F: drivers/hwmon/tmp464.c
TMP513 HARDWARE MONITOR DRIVER
M: Eric Tremblay <etremblay@distech-controls.com>
L: linux-hwmon@vger.kernel.org

View File

@ -301,6 +301,16 @@ config SENSORS_ASC7621
This driver can also be built as a module. If so, the module
will be called asc7621.
config SENSORS_ASUS_ROG_RYUJIN
tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver"
depends on HID
help
If you say yes here you get support for the fans and sensors of
the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler.
This driver can also be built as a module. If so, the module
will be called asus_rog_ryujin.
config SENSORS_AXI_FAN_CONTROL
tristate "Analog Devices FAN Control HDL Core driver"
help
@ -412,6 +422,17 @@ config SENSORS_ASPEED
This driver can also be built as a module. If so, the module
will be called aspeed_pwm_tacho.
config SENSORS_ASPEED_G6
tristate "ASPEED g6 PWM and Fan tach driver"
depends on ARCH_ASPEED || COMPILE_TEST
depends on PWM
help
This driver provides support for ASPEED G6 PWM and Fan Tach
controllers.
This driver can also be built as a module. If so, the module
will be called aspeed_pwm_tacho.
config SENSORS_ATXP1
tristate "Attansic ATXP1 VID controller"
depends on I2C
@ -452,6 +473,16 @@ config SENSORS_BT1_PVT_ALARMS
the data conversion will be periodically performed and the data will be
saved in the internal driver cache.
config SENSORS_CHIPCAP2
tristate "Amphenol ChipCap 2 relative humidity and temperature sensor"
depends on I2C
help
Say yes here to build support for the Amphenol ChipCap 2
relative humidity and temperature sensor.
To compile this driver as a module, choose M here: the module
will be called chipcap2.
config SENSORS_CORSAIR_CPRO
tristate "Corsair Commander Pro controller"
depends on HID
@ -1038,6 +1069,17 @@ config SENSORS_LTC4261
This driver can also be built as a module. If so, the module will
be called ltc4261.
config SENSORS_LTC4282
tristate "Analog Devices LTC4282"
depends on I2C
select REGMAP_I2C
help
If you say yes here you get support for Analog Devices LTC4282
High Current Hot Swap Controller I2C interface.
This driver can also be built as a module. If so, the module will
be called ltc4282.
config SENSORS_LTQ_CPUTEMP
bool "Lantiq cpu temperature sensor driver"
depends on SOC_XWAY
@ -1674,6 +1716,16 @@ config SENSORS_NZXT_KRAKEN2
This driver can also be built as a module. If so, the module
will be called nzxt-kraken2.
config SENSORS_NZXT_KRAKEN3
tristate "NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers"
depends on USB_HID
help
If you say yes here you get support for hardware monitoring for the
NZXT Kraken X53/X63/X73, Z53/Z63/Z73 all-in-one CPU liquid coolers.
This driver can also be built as a module. If so, the module
will be called nzxt-kraken3.
config SENSORS_NZXT_SMART2
tristate "NZXT RGB & Fan Controller/Smart Device v2"
depends on USB_HID
@ -1714,6 +1766,16 @@ source "drivers/hwmon/peci/Kconfig"
source "drivers/hwmon/pmbus/Kconfig"
config SENSORS_PT5161L
tristate "Astera Labs PT5161L PCIe retimer hardware monitoring"
depends on I2C
help
If you say yes here you get support for temperature monitoring
on the Astera Labs PT5161L PCIe retimer.
This driver can also be built as a module. If so, the module
will be called pt5161l.
config SENSORS_PWM_FAN
tristate "PWM fan"
depends on (PWM && OF) || COMPILE_TEST
@ -1994,6 +2056,20 @@ config SENSORS_SFCTEMP
This driver can also be built as a module. If so, the module
will be called sfctemp.
config SENSORS_SURFACE_FAN
tristate "Surface Fan Driver"
depends on SURFACE_AGGREGATOR
depends on SURFACE_AGGREGATOR_BUS
help
Driver that provides monitoring of the fan on Surface Pro devices that
have a fan, like the Surface Pro 9.
This makes the fan's current speed accessible through the hwmon
system. It does not provide control over the fan, the firmware is
responsible for that, this driver merely provides monitoring.
Select M or Y here, if you want to be able to read the fan's speed.
config SENSORS_ADC128D818
tristate "Texas Instruments ADC128D818"
depends on I2C

View File

@ -55,9 +55,12 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
@ -136,6 +139,7 @@ obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX127) += max127.o
@ -173,6 +177,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o
obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o
obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
@ -180,6 +185,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
@ -201,6 +207,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o
obj-$(CONFIG_SENSORS_STTS751) += stts751.o
obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o
obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o
obj-$(CONFIG_SENSORS_TC74) += tc74.o

View File

@ -250,7 +250,6 @@ static const struct of_device_id adm1177_dt_ids[] = {
MODULE_DEVICE_TABLE(of, adm1177_dt_ids);
static struct i2c_driver adm1177_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "adm1177",
.of_match_table = adm1177_dt_ids,

View File

@ -124,7 +124,7 @@ static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config adt7310_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7310_regmap_is_volatile,
.reg_read = adt7310_reg_read,
.reg_write = adt7310_reg_write,

View File

@ -69,7 +69,7 @@ static const struct regmap_config adt7410_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = ADT7X10_ID,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7410_regmap_is_volatile,
.reg_read = adt7410_reg_read,
.reg_write = adt7410_reg_write,
@ -95,14 +95,12 @@ static const struct i2c_device_id adt7410_ids[] = {
MODULE_DEVICE_TABLE(i2c, adt7410_ids);
static struct i2c_driver adt7410_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "adt7410",
.pm = pm_sleep_ptr(&adt7x10_dev_pm_ops),
},
.probe = adt7410_i2c_probe,
.id_table = adt7410_ids,
.address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b),
};
module_i2c_driver(adt7410_driver);

View File

@ -934,10 +934,21 @@ static const struct i2c_device_id amc6821_id[] = {
MODULE_DEVICE_TABLE(i2c, amc6821_id);
static const struct of_device_id __maybe_unused amc6821_of_match[] = {
{
.compatible = "ti,amc6821",
.data = (void *)amc6821,
},
{ }
};
MODULE_DEVICE_TABLE(of, amc6821_of_match);
static struct i2c_driver amc6821_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "amc6821",
.of_match_table = of_match_ptr(amc6821_of_match),
},
.probe = amc6821_probe,
.id_table = amc6821_id,

View File

@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Aspeed Technology Inc.
*
* PWM/TACH controller driver for Aspeed ast2600 SoCs.
* This drivers doesn't support earlier version of the IP.
*
* The hardware operates in time quantities of length
* Q := (DIV_L + 1) << DIV_H / input-clk
* The length of a PWM period is (DUTY_CYCLE_PERIOD + 1) * Q.
* The maximal value for DUTY_CYCLE_PERIOD is used here to provide
* a fine grained selection for the duty cycle.
*
* This driver uses DUTY_CYCLE_RISING_POINT = 0, so from the start of a
* period the output is active until DUTY_CYCLE_FALLING_POINT * Q. Note
* that if DUTY_CYCLE_RISING_POINT = DUTY_CYCLE_FALLING_POINT the output is
* always active.
*
* Register usage:
* PIN_ENABLE: When it is unset the pwm controller will emit inactive level to the external.
* Use to determine whether the PWM channel is enabled or disabled
* CLK_ENABLE: When it is unset the pwm controller will assert the duty counter reset and
* emit inactive level to the PIN_ENABLE mux after that the driver can still change the pwm period
* and duty and the value will apply when CLK_ENABLE be set again.
* Use to determine whether duty_cycle bigger than 0.
* PWM_ASPEED_CTRL_INVERSE: When it is toggled the output value will inverse immediately.
* PWM_ASPEED_DUTY_CYCLE_FALLING_POINT/PWM_ASPEED_DUTY_CYCLE_RISING_POINT: When these two
* values are equal it means the duty cycle = 100%.
*
* The glitch may generate at:
* - Enabled changing when the duty_cycle bigger than 0% and less than 100%.
* - Polarity changing when the duty_cycle bigger than 0% and less than 100%.
*
* Limitations:
* - When changing both duty cycle and period, we cannot prevent in
* software that the output might produce a period with mixed
* settings.
* - Disabling the PWM doesn't complete the current period.
*
* Improvements:
* - When only changing one of duty cycle or period, our pwm controller will not
* generate the glitch, the configure will change at next cycle of pwm.
* This improvement can disable/enable through PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/reset.h>
#include <linux/sysfs.h>
/* The channel number of Aspeed pwm controller */
#define PWM_ASPEED_NR_PWMS 16
/* PWM Control Register */
#define PWM_ASPEED_CTRL(ch) ((ch) * 0x10 + 0x00)
#define PWM_ASPEED_CTRL_LOAD_SEL_RISING_AS_WDT BIT(19)
#define PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE BIT(18)
#define PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE BIT(17)
#define PWM_ASPEED_CTRL_CLK_ENABLE BIT(16)
#define PWM_ASPEED_CTRL_LEVEL_OUTPUT BIT(15)
#define PWM_ASPEED_CTRL_INVERSE BIT(14)
#define PWM_ASPEED_CTRL_OPEN_DRAIN_ENABLE BIT(13)
#define PWM_ASPEED_CTRL_PIN_ENABLE BIT(12)
#define PWM_ASPEED_CTRL_CLK_DIV_H GENMASK(11, 8)
#define PWM_ASPEED_CTRL_CLK_DIV_L GENMASK(7, 0)
/* PWM Duty Cycle Register */
#define PWM_ASPEED_DUTY_CYCLE(ch) ((ch) * 0x10 + 0x04)
#define PWM_ASPEED_DUTY_CYCLE_PERIOD GENMASK(31, 24)
#define PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT GENMASK(23, 16)
#define PWM_ASPEED_DUTY_CYCLE_FALLING_POINT GENMASK(15, 8)
#define PWM_ASPEED_DUTY_CYCLE_RISING_POINT GENMASK(7, 0)
/* PWM fixed value */
#define PWM_ASPEED_FIXED_PERIOD FIELD_MAX(PWM_ASPEED_DUTY_CYCLE_PERIOD)
/* The channel number of Aspeed tach controller */
#define TACH_ASPEED_NR_TACHS 16
/* TACH Control Register */
#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08)
#define TACH_ASPEED_IER BIT(31)
#define TACH_ASPEED_INVERS_LIMIT BIT(30)
#define TACH_ASPEED_LOOPBACK BIT(29)
#define TACH_ASPEED_ENABLE BIT(28)
#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26)
#define TACH_ASPEED_DEBOUNCE_BIT 26
#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24)
#define TACH_ASPEED_IO_EDGE_BIT 24
#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20)
#define TACH_ASPEED_CLK_DIV_BIT 20
#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0)
/* [27:26] */
#define DEBOUNCE_3_CLK 0x00
#define DEBOUNCE_2_CLK 0x01
#define DEBOUNCE_1_CLK 0x02
#define DEBOUNCE_0_CLK 0x03
/* [25:24] */
#define F2F_EDGES 0x00
#define R2R_EDGES 0x01
#define BOTH_EDGES 0x02
/* [23:20] */
/* divisor = 4 to the nth power, n = register value */
#define DEFAULT_TACH_DIV 1024
#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1)
/* TACH Status Register */
#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C)
/*PWM_TACH_STS */
#define TACH_ASPEED_ISR BIT(31)
#define TACH_ASPEED_PWM_OUT BIT(25)
#define TACH_ASPEED_PWM_OEN BIT(24)
#define TACH_ASPEED_DEB_INPUT BIT(23)
#define TACH_ASPEED_RAW_INPUT BIT(22)
#define TACH_ASPEED_VALUE_UPDATE BIT(21)
#define TACH_ASPEED_FULL_MEASUREMENT BIT(20)
#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0)
/**********************************************************
* Software setting
*********************************************************/
#define DEFAULT_FAN_PULSE_PR 2
struct aspeed_pwm_tach_data {
struct device *dev;
void __iomem *base;
struct clk *clk;
struct reset_control *reset;
unsigned long clk_rate;
struct pwm_chip chip;
bool tach_present[TACH_ASPEED_NR_TACHS];
u32 tach_divisor;
};
static inline struct aspeed_pwm_tach_data *
aspeed_pwm_chip_to_data(struct pwm_chip *chip)
{
return container_of(chip, struct aspeed_pwm_tach_data, chip);
}
static int aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
u32 hwpwm = pwm->hwpwm;
bool polarity, pin_en, clk_en;
u32 duty_pt, val;
u64 div_h, div_l, duty_cycle_period, dividend;
val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val);
pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val);
clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val);
div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val);
div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val);
val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val);
duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val);
/*
* This multiplication doesn't overflow, the upper bound is
* 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000
*/
dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1)
<< div_h;
state->period = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
if (clk_en && duty_pt) {
dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt
<< div_h;
state->duty_cycle = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
} else {
state->duty_cycle = clk_en ? state->period : 0;
}
state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
state->enabled = pin_en;
return 0;
}
static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
u32 hwpwm = pwm->hwpwm, duty_pt, val;
u64 div_h, div_l, divisor, expect_period;
bool clk_en;
expect_period = div64_u64(ULLONG_MAX, (u64)priv->clk_rate);
expect_period = min(expect_period, state->period);
dev_dbg(chip->dev, "expect period: %lldns, duty_cycle: %lldns",
expect_period, state->duty_cycle);
/*
* Pick the smallest value for div_h so that div_l can be the biggest
* which results in a finer resolution near the target period value.
*/
divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) *
(FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1);
div_h = order_base_2(DIV64_U64_ROUND_UP(priv->clk_rate * expect_period, divisor));
if (div_h > 0xf)
div_h = 0xf;
divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h;
div_l = div64_u64(priv->clk_rate * expect_period, divisor);
if (div_l == 0)
return -ERANGE;
div_l -= 1;
if (div_l > 255)
div_l = 255;
dev_dbg(chip->dev, "clk source: %ld div_h %lld, div_l : %lld\n",
priv->clk_rate, div_h, div_l);
/* duty_pt = duty_cycle * (PERIOD + 1) / period */
duty_pt = div64_u64(state->duty_cycle * priv->clk_rate,
(u64)NSEC_PER_SEC * (div_l + 1) << div_h);
dev_dbg(chip->dev, "duty_cycle = %lld, duty_pt = %d\n",
state->duty_cycle, duty_pt);
/*
* Fixed DUTY_CYCLE_PERIOD to its max value to get a
* fine-grained resolution for duty_cycle at the expense of a
* coarser period resolution.
*/
val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
val &= ~PWM_ASPEED_DUTY_CYCLE_PERIOD;
val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_PERIOD,
PWM_ASPEED_FIXED_PERIOD);
writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
if (duty_pt == 0) {
/* emit inactive level and assert the duty counter reset */
clk_en = 0;
} else {
clk_en = 1;
if (duty_pt >= (PWM_ASPEED_FIXED_PERIOD + 1))
duty_pt = 0;
val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
val &= ~(PWM_ASPEED_DUTY_CYCLE_RISING_POINT |
PWM_ASPEED_DUTY_CYCLE_FALLING_POINT);
val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, duty_pt);
writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
}
val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
val &= ~(PWM_ASPEED_CTRL_CLK_DIV_H | PWM_ASPEED_CTRL_CLK_DIV_L |
PWM_ASPEED_CTRL_PIN_ENABLE | PWM_ASPEED_CTRL_CLK_ENABLE |
PWM_ASPEED_CTRL_INVERSE);
val |= FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_H, div_h) |
FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_L, div_l) |
FIELD_PREP(PWM_ASPEED_CTRL_PIN_ENABLE, state->enabled) |
FIELD_PREP(PWM_ASPEED_CTRL_CLK_ENABLE, clk_en) |
FIELD_PREP(PWM_ASPEED_CTRL_INVERSE, state->polarity);
writel(val, priv->base + PWM_ASPEED_CTRL(hwpwm));
return 0;
}
static const struct pwm_ops aspeed_pwm_ops = {
.apply = aspeed_pwm_apply,
.get_state = aspeed_pwm_get_state,
};
static void aspeed_tach_ch_enable(struct aspeed_pwm_tach_data *priv, u8 tach_ch,
bool enable)
{
if (enable)
writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) |
TACH_ASPEED_ENABLE,
priv->base + TACH_ASPEED_CTRL(tach_ch));
else
writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) &
~TACH_ASPEED_ENABLE,
priv->base + TACH_ASPEED_CTRL(tach_ch));
}
static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, u32 tach_val)
{
u64 rpm;
u32 tach_div;
tach_div = tach_val * priv->tach_divisor * DEFAULT_FAN_PULSE_PR;
dev_dbg(priv->dev, "clk %ld, tach_val %d , tach_div %d\n",
priv->clk_rate, tach_val, tach_div);
rpm = (u64)priv->clk_rate * 60;
do_div(rpm, tach_div);
return (int)rpm;
}
static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tach_data *priv,
u8 fan_tach_ch)
{
u32 val;
val = readl(priv->base + TACH_ASPEED_STS(fan_tach_ch));
if (!(val & TACH_ASPEED_FULL_MEASUREMENT))
return 0;
val = FIELD_GET(TACH_ASPEED_VALUE_MASK, val);
return aspeed_tach_val_to_rpm(priv, val);
}
static int aspeed_tach_hwmon_read(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
u32 reg_val;
switch (attr) {
case hwmon_fan_input:
*val = aspeed_get_fan_tach_ch_rpm(priv, channel);
break;
case hwmon_fan_div:
reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val);
*val = BIT(reg_val << 1);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int aspeed_tach_hwmon_write(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
u32 reg_val;
switch (attr) {
case hwmon_fan_div:
if (!is_power_of_2(val) || (ilog2(val) % 2) ||
DIV_TO_REG(val) > 0xb)
return -EINVAL;
priv->tach_divisor = val;
reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
reg_val &= ~TACH_ASPEED_CLK_DIV_T_MASK;
reg_val |= FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
DIV_TO_REG(priv->tach_divisor));
writel(reg_val, priv->base + TACH_ASPEED_CTRL(channel));
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static umode_t aspeed_tach_dev_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct aspeed_pwm_tach_data *priv = drvdata;
if (!priv->tach_present[channel])
return 0;
switch (attr) {
case hwmon_fan_input:
return 0444;
case hwmon_fan_div:
return 0644;
}
return 0;
}
static const struct hwmon_ops aspeed_tach_ops = {
.is_visible = aspeed_tach_dev_is_visible,
.read = aspeed_tach_hwmon_read,
.write = aspeed_tach_hwmon_write,
};
static const struct hwmon_channel_info *aspeed_tach_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV),
NULL
};
static const struct hwmon_chip_info aspeed_tach_chip_info = {
.ops = &aspeed_tach_ops,
.info = aspeed_tach_info,
};
static void aspeed_present_fan_tach(struct aspeed_pwm_tach_data *priv, u8 *tach_ch, int count)
{
u8 ch, index;
u32 val;
for (index = 0; index < count; index++) {
ch = tach_ch[index];
priv->tach_present[ch] = true;
priv->tach_divisor = DEFAULT_TACH_DIV;
val = readl(priv->base + TACH_ASPEED_CTRL(ch));
val &= ~(TACH_ASPEED_INVERS_LIMIT | TACH_ASPEED_DEBOUNCE_MASK |
TACH_ASPEED_IO_EDGE_MASK | TACH_ASPEED_CLK_DIV_T_MASK |
TACH_ASPEED_THRESHOLD_MASK);
val |= (DEBOUNCE_3_CLK << TACH_ASPEED_DEBOUNCE_BIT) |
F2F_EDGES |
FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
DIV_TO_REG(priv->tach_divisor));
writel(val, priv->base + TACH_ASPEED_CTRL(ch));
aspeed_tach_ch_enable(priv, ch, true);
}
}
static int aspeed_create_fan_monitor(struct device *dev,
struct device_node *child,
struct aspeed_pwm_tach_data *priv)
{
int ret, count;
u8 *tach_ch;
count = of_property_count_u8_elems(child, "tach-ch");
if (count < 1)
return -EINVAL;
tach_ch = devm_kcalloc(dev, count, sizeof(*tach_ch), GFP_KERNEL);
if (!tach_ch)
return -ENOMEM;
ret = of_property_read_u8_array(child, "tach-ch", tach_ch, count);
if (ret)
return ret;
aspeed_present_fan_tach(priv, tach_ch, count);
return 0;
}
static void aspeed_pwm_tach_reset_assert(void *data)
{
struct reset_control *rst = data;
reset_control_assert(rst);
}
static int aspeed_pwm_tach_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev, *hwmon;
int ret;
struct device_node *child;
struct aspeed_pwm_tach_data *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(priv->clk))
return dev_err_probe(dev, PTR_ERR(priv->clk),
"Couldn't get clock\n");
priv->clk_rate = clk_get_rate(priv->clk);
priv->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(priv->reset))
return dev_err_probe(dev, PTR_ERR(priv->reset),
"Couldn't get reset control\n");
ret = reset_control_deassert(priv->reset);
if (ret)
return dev_err_probe(dev, ret,
"Couldn't deassert reset control\n");
ret = devm_add_action_or_reset(dev, aspeed_pwm_tach_reset_assert,
priv->reset);
if (ret)
return ret;
priv->chip.dev = dev;
priv->chip.ops = &aspeed_pwm_ops;
priv->chip.npwm = PWM_ASPEED_NR_PWMS;
ret = devm_pwmchip_add(dev, &priv->chip);
if (ret)
return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
for_each_child_of_node(dev->of_node, child) {
ret = aspeed_create_fan_monitor(dev, child, priv);
if (ret) {
of_node_put(child);
dev_warn(dev, "Failed to create fan %d", ret);
return 0;
}
}
hwmon = devm_hwmon_device_register_with_info(dev, "aspeed_tach", priv,
&aspeed_tach_chip_info, NULL);
ret = PTR_ERR_OR_ZERO(hwmon);
if (ret)
return dev_err_probe(dev, ret,
"Failed to register hwmon device\n");
of_platform_populate(dev->of_node, NULL, NULL, dev);
return 0;
}
static int aspeed_pwm_tach_remove(struct platform_device *pdev)
{
struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev);
reset_control_assert(priv->reset);
return 0;
}
static const struct of_device_id aspeed_pwm_tach_match[] = {
{
.compatible = "aspeed,ast2600-pwm-tach",
},
{},
};
MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match);
static struct platform_driver aspeed_pwm_tach_driver = {
.probe = aspeed_pwm_tach_probe,
.remove = aspeed_pwm_tach_remove,
.driver = {
.name = "aspeed-g6-pwm-tach",
.of_match_table = aspeed_pwm_tach_match,
},
};
module_platform_driver(aspeed_pwm_tach_driver);
MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
MODULE_DESCRIPTION("Aspeed ast2600 PWM and Fan Tach device driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,609 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* hwmon driver for Asus ROG Ryujin II 360 AIO cooler.
*
* Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com>
*/
#include <linux/debugfs.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <asm/unaligned.h>
#define DRIVER_NAME "asus_rog_ryujin"
#define USB_VENDOR_ID_ASUS_ROG 0x0b05
#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
#define STATUS_VALIDITY 1500 /* ms */
#define MAX_REPORT_LENGTH 65
/* Cooler status report offsets */
#define RYUJIN_TEMP_SENSOR_1 3
#define RYUJIN_TEMP_SENSOR_2 4
#define RYUJIN_PUMP_SPEED 5
#define RYUJIN_INTERNAL_FAN_SPEED 7
/* Cooler duty report offsets */
#define RYUJIN_PUMP_DUTY 4
#define RYUJIN_INTERNAL_FAN_DUTY 5
/* Controller status (speeds) report offsets */
#define RYUJIN_CONTROLLER_SPEED_1 5
#define RYUJIN_CONTROLLER_SPEED_2 7
#define RYUJIN_CONTROLLER_SPEED_3 9
#define RYUJIN_CONTROLLER_SPEED_4 3
/* Controller duty report offsets */
#define RYUJIN_CONTROLLER_DUTY 4
/* Control commands and their inner offsets */
#define RYUJIN_CMD_PREFIX 0xEC
static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 };
static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A };
static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 };
static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 };
#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3
#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4
static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };
#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4
static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };
/* Command lengths */
#define GET_CMD_LENGTH 2 /* Same length for all get commands */
#define SET_CMD_LENGTH 5 /* Same length for all set commands */
/* Command response headers */
#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE 0x19
#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A
#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20
#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21
static const char *const rog_ryujin_temp_label[] = {
"Coolant temp"
};
static const char *const rog_ryujin_speed_label[] = {
"Pump speed",
"Internal fan speed",
"Controller fan 1 speed",
"Controller fan 2 speed",
"Controller fan 3 speed",
"Controller fan 4 speed",
};
struct rog_ryujin_data {
struct hid_device *hdev;
struct device *hwmon_dev;
/* For locking access to buffer */
struct mutex buffer_lock;
/* For queueing multiple readers */
struct mutex status_report_request_mutex;
/* For reinitializing the completions below */
spinlock_t status_report_request_lock;
struct completion cooler_status_received;
struct completion controller_status_received;
struct completion cooler_duty_received;
struct completion controller_duty_received;
struct completion cooler_duty_set;
struct completion controller_duty_set;
/* Sensor data */
s32 temp_input[1];
u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */
u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */
u8 *buffer;
unsigned long updated; /* jiffies */
};
static int rog_ryujin_percent_to_pwm(u16 val)
{
return DIV_ROUND_CLOSEST(val * 255, 100);
}
static int rog_ryujin_pwm_to_percent(long val)
{
return DIV_ROUND_CLOSEST(val * 100, 255);
}
static umode_t rog_ryujin_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr, int channel)
{
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_label:
case hwmon_temp_input:
return 0444;
default:
break;
}
break;
case hwmon_fan:
switch (attr) {
case hwmon_fan_label:
case hwmon_fan_input:
return 0444;
default:
break;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
return 0644;
default:
break;
}
break;
default:
break;
}
return 0;
}
/* Writes the command to the device with the rest of the report filled with zeroes */
static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length)
{
int ret;
mutex_lock(&priv->buffer_lock);
memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
mutex_unlock(&priv->buffer_lock);
return ret;
}
/* Assumes priv->status_report_request_mutex is locked */
static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,
struct completion *status_completion)
{
int ret;
/*
* Disable raw event parsing for a moment to safely reinitialize the
* completion. Reinit is done because hidraw could have triggered
* the raw event parsing and marked the passed in completion as done.
*/
spin_lock_bh(&priv->status_report_request_lock);
reinit_completion(status_completion);
spin_unlock_bh(&priv->status_report_request_lock);
/* Send command for getting data */
ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);
if (ret < 0)
return ret;
ret = wait_for_completion_interruptible_timeout(status_completion,
msecs_to_jiffies(STATUS_VALIDITY));
if (ret == 0)
return -ETIMEDOUT;
else if (ret < 0)
return ret;
return 0;
}
static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
{
int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
if (ret < 0)
return ret;
if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
/* Data is up to date */
goto unlock_and_return;
}
/* Retrieve cooler status */
ret =
rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,
&priv->cooler_status_received);
if (ret < 0)
goto unlock_and_return;
/* Retrieve controller status (speeds) */
ret =
rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
&priv->controller_status_received);
if (ret < 0)
goto unlock_and_return;
/* Retrieve cooler duty */
ret =
rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
&priv->cooler_duty_received);
if (ret < 0)
goto unlock_and_return;
/* Retrieve controller duty */
ret =
rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
&priv->controller_duty_received);
if (ret < 0)
goto unlock_and_return;
priv->updated = jiffies;
unlock_and_return:
mutex_unlock(&priv->status_report_request_mutex);
if (ret < 0)
return ret;
return 0;
}
static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct rog_ryujin_data *priv = dev_get_drvdata(dev);
int ret = rog_ryujin_get_status(priv);
if (ret < 0)
return ret;
switch (type) {
case hwmon_temp:
*val = priv->temp_input[channel];
break;
case hwmon_fan:
*val = priv->speed_input[channel];
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
*val = priv->duty_input[channel];
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP; /* unreachable */
}
return 0;
}
static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
switch (type) {
case hwmon_temp:
*str = rog_ryujin_temp_label[channel];
break;
case hwmon_fan:
*str = rog_ryujin_speed_label[channel];
break;
default:
return -EOPNOTSUPP; /* unreachable */
}
return 0;
}
static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)
{
u8 set_cmd[SET_CMD_LENGTH];
int ret;
if (channel < 2) {
/*
* Retrieve cooler duty since both pump and internal fan are set
* together, then write back with one of them modified.
*/
ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
if (ret < 0)
return ret;
ret =
rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
&priv->cooler_duty_received);
if (ret < 0)
goto unlock_and_return;
memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
/* Cooler duties are set as 0-100% */
val = rog_ryujin_pwm_to_percent(val);
if (channel == 0) {
/* Cooler pump duty */
set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val;
set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] =
rog_ryujin_pwm_to_percent(priv->duty_input[1]);
} else if (channel == 1) {
/* Cooler internal fan duty */
set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] =
rog_ryujin_pwm_to_percent(priv->duty_input[0]);
set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
}
ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
unlock_and_return:
mutex_unlock(&priv->status_report_request_mutex);
if (ret < 0)
return ret;
} else {
/*
* Controller fan duty (channel == 2). No need to retrieve current
* duty, so just send the command.
*/
memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH);
set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val;
ret =
rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH,
&priv->controller_duty_set);
if (ret < 0)
return ret;
}
/* Lock onto this value until next refresh cycle */
priv->duty_input[channel] = val;
return 0;
}
static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long val)
{
struct rog_ryujin_data *priv = dev_get_drvdata(dev);
int ret;
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
if (val < 0 || val > 255)
return -EINVAL;
ret = rog_ryujin_write_fixed_duty(priv, channel, val);
if (ret < 0)
return ret;
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static const struct hwmon_ops rog_ryujin_hwmon_ops = {
.is_visible = rog_ryujin_is_visible,
.read = rog_ryujin_read,
.read_string = rog_ryujin_read_string,
.write = rog_ryujin_write
};
static const struct hwmon_channel_info *rog_ryujin_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT,
HWMON_PWM_INPUT),
NULL
};
static const struct hwmon_chip_info rog_ryujin_chip_info = {
.ops = &rog_ryujin_hwmon_ops,
.info = rog_ryujin_info,
};
static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
int size)
{
struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
if (data[0] != RYUJIN_CMD_PREFIX)
return 0;
if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
/* Received coolant temp and speeds of pump and internal fan */
priv->temp_input[0] =
data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;
priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);
priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);
if (!completion_done(&priv->cooler_status_received))
complete_all(&priv->cooler_status_received);
} else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {
/* Received speeds of four fans attached to the controller */
priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);
priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);
priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3);
priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4);
if (!completion_done(&priv->controller_status_received))
complete_all(&priv->controller_status_received);
} else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {
/* Received report for pump and internal fan duties (in %) */
if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {
/*
* We received a report with zeroes for duty in both places.
* The device returns this as a confirmation that setting values
* is successful. If we initiated a write, mark it as complete.
*/
if (!completion_done(&priv->cooler_duty_set))
complete_all(&priv->cooler_duty_set);
else if (!completion_done(&priv->cooler_duty_received))
/*
* We didn't initiate a write, but received both zeroes.
* This means that either both duties are actually zero,
* or that we received a success report caused by userspace.
* We're expecting a report, so parse it.
*/
goto read_cooler_duty;
return 0;
}
read_cooler_duty:
priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);
priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]);
if (!completion_done(&priv->cooler_duty_received))
complete_all(&priv->cooler_duty_received);
} else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {
/* Received report for controller duty for fans (in PWM) */
if (data[RYUJIN_CONTROLLER_DUTY] == 0) {
/*
* We received a report with a zero for duty. The device returns this as
* a confirmation that setting the controller duty value was successful.
* If we initiated a write, mark it as complete.
*/
if (!completion_done(&priv->controller_duty_set))
complete_all(&priv->controller_duty_set);
else if (!completion_done(&priv->controller_duty_received))
/*
* We didn't initiate a write, but received a zero for duty.
* This means that either the duty is actually zero, or that
* we received a success report caused by userspace.
* We're expecting a report, so parse it.
*/
goto read_controller_duty;
return 0;
}
read_controller_duty:
priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];
if (!completion_done(&priv->controller_duty_received))
complete_all(&priv->controller_duty_received);
}
return 0;
}
static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct rog_ryujin_data *priv;
int ret;
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
/*
* Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
* the initial empty data invalid for rog_ryujin_read() without the need for
* a special case there.
*/
priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "hid parse failed with %d\n", ret);
return ret;
}
/* Enable hidraw so existing user-space tools can continue to work */
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret) {
hid_err(hdev, "hid hw start failed with %d\n", ret);
return ret;
}
ret = hid_hw_open(hdev);
if (ret) {
hid_err(hdev, "hid hw open failed with %d\n", ret);
goto fail_and_stop;
}
priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
if (!priv->buffer) {
ret = -ENOMEM;
goto fail_and_close;
}
mutex_init(&priv->status_report_request_mutex);
mutex_init(&priv->buffer_lock);
spin_lock_init(&priv->status_report_request_lock);
init_completion(&priv->cooler_status_received);
init_completion(&priv->controller_status_received);
init_completion(&priv->cooler_duty_received);
init_completion(&priv->controller_duty_received);
init_completion(&priv->cooler_duty_set);
init_completion(&priv->controller_duty_set);
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin",
priv, &rog_ryujin_chip_info, NULL);
if (IS_ERR(priv->hwmon_dev)) {
ret = PTR_ERR(priv->hwmon_dev);
hid_err(hdev, "hwmon registration failed with %d\n", ret);
goto fail_and_close;
}
return 0;
fail_and_close:
hid_hw_close(hdev);
fail_and_stop:
hid_hw_stop(hdev);
return ret;
}
static void rog_ryujin_remove(struct hid_device *hdev)
{
struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
hwmon_device_unregister(priv->hwmon_dev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
static const struct hid_device_id rog_ryujin_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },
{ }
};
MODULE_DEVICE_TABLE(hid, rog_ryujin_table);
static struct hid_driver rog_ryujin_driver = {
.name = "rog_ryujin",
.id_table = rog_ryujin_table,
.probe = rog_ryujin_probe,
.remove = rog_ryujin_remove,
.raw_event = rog_ryujin_raw_event,
};
static int __init rog_ryujin_init(void)
{
return hid_register_driver(&rog_ryujin_driver);
}
static void __exit rog_ryujin_exit(void)
{
hid_unregister_driver(&rog_ryujin_driver);
}
/* When compiled into the kernel, initialize after the HID bus */
late_initcall(rog_ryujin_init);
module_exit(rog_ryujin_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");

View File

@ -13,8 +13,9 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/property.h>
/* register map */
#define ADI_REG_RSTN 0x0080
@ -83,7 +84,7 @@ static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute
temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230;
return sprintf(buf, "%u\n", temp);
return sysfs_emit(buf, "%u\n", temp);
}
static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da,
@ -368,12 +369,12 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
}
static int axi_fan_control_init(struct axi_fan_control_data *ctl,
const struct device_node *np)
const struct device *dev)
{
int ret;
/* get fan pulses per revolution */
ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr);
ret = device_property_read_u32(dev, "pulses-per-revolution", &ctl->ppr);
if (ret)
return ret;
@ -443,25 +444,16 @@ static struct attribute *axi_fan_control_attrs[] = {
};
ATTRIBUTE_GROUPS(axi_fan_control);
static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
static const struct of_device_id axi_fan_control_of_match[] = {
{ .compatible = "adi,axi-fan-control-1.00.a",
.data = (void *)&version_1_0_0},
{},
};
MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
static int axi_fan_control_probe(struct platform_device *pdev)
{
struct axi_fan_control_data *ctl;
struct clk *clk;
const struct of_device_id *id;
const unsigned int *id;
const char *name = "axi_fan_control";
u32 version;
int ret;
id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node);
id = device_get_match_data(&pdev->dev);
if (!id)
return -EINVAL;
@ -474,10 +466,9 @@ static int axi_fan_control_probe(struct platform_device *pdev)
return PTR_ERR(ctl->base);
clk = devm_clk_get_enabled(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk));
return PTR_ERR(clk);
}
if (IS_ERR(clk))
return dev_err_probe(&pdev->dev, PTR_ERR(clk),
"clk_get failed\n");
ctl->clk_rate = clk_get_rate(clk);
if (!ctl->clk_rate)
@ -485,22 +476,20 @@ static int axi_fan_control_probe(struct platform_device *pdev)
version = axi_ioread(ADI_AXI_REG_VERSION, ctl);
if (ADI_AXI_PCORE_VER_MAJOR(version) !=
ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) {
dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)),
ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)),
ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)),
ADI_AXI_PCORE_VER_MAJOR(version),
ADI_AXI_PCORE_VER_MINOR(version),
ADI_AXI_PCORE_VER_PATCH(version));
return -ENODEV;
}
ADI_AXI_PCORE_VER_MAJOR((*id)))
return dev_err_probe(&pdev->dev, -ENODEV,
"Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
ADI_AXI_PCORE_VER_MAJOR(*id),
ADI_AXI_PCORE_VER_MINOR(*id),
ADI_AXI_PCORE_VER_PATCH(*id),
ADI_AXI_PCORE_VER_MAJOR(version),
ADI_AXI_PCORE_VER_MINOR(version),
ADI_AXI_PCORE_VER_PATCH(version));
ret = axi_fan_control_init(ctl, pdev->dev.of_node);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize device\n");
return ret;
}
ret = axi_fan_control_init(ctl, &pdev->dev);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to initialize device\n");
ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev,
name,
@ -519,14 +508,22 @@ static int axi_fan_control_probe(struct platform_device *pdev)
axi_fan_control_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
pdev->driver_override, ctl);
if (ret) {
dev_err(&pdev->dev, "failed to request an irq, %d", ret);
return ret;
}
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to request an irq\n");
return 0;
}
static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
static const struct of_device_id axi_fan_control_of_match[] = {
{ .compatible = "adi,axi-fan-control-1.00.a",
.data = (void *)&version_1_0_0},
{},
};
MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
static struct platform_driver axi_fan_control_driver = {
.driver = {
.name = "axi_fan_control_driver",

822
drivers/hwmon/chipcap2.c Normal file
View File

@ -0,0 +1,822 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor
*
* Part numbers supported:
* CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
*
* Author: Javier Carrasco <javier.carrasco.cruz@gmail.com>
*
* Datasheet and application notes:
* https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#define CC2_START_CM 0xA0
#define CC2_START_NOM 0x80
#define CC2_R_ALARM_H_ON 0x18
#define CC2_R_ALARM_H_OFF 0x19
#define CC2_R_ALARM_L_ON 0x1A
#define CC2_R_ALARM_L_OFF 0x1B
#define CC2_RW_OFFSET 0x40
#define CC2_W_ALARM_H_ON (CC2_R_ALARM_H_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_H_OFF (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_ON (CC2_R_ALARM_L_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_OFF (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET)
#define CC2_STATUS_FIELD GENMASK(7, 6)
#define CC2_STATUS_VALID_DATA 0x00
#define CC2_STATUS_STALE_DATA 0x01
#define CC2_STATUS_CMD_MODE 0x02
#define CC2_RESPONSE_FIELD GENMASK(1, 0)
#define CC2_RESPONSE_BUSY 0x00
#define CC2_RESPONSE_ACK 0x01
#define CC2_RESPONSE_NACK 0x02
#define CC2_ERR_CORR_EEPROM BIT(2)
#define CC2_ERR_UNCORR_EEPROM BIT(3)
#define CC2_ERR_RAM_PARITY BIT(4)
#define CC2_ERR_CONFIG_LOAD BIT(5)
#define CC2_EEPROM_SIZE 10
#define CC2_EEPROM_DATA_LEN 3
#define CC2_MEASUREMENT_DATA_LEN 4
#define CC2_RH_DATA_FIELD GENMASK(13, 0)
/* ensure clean off -> on transitions */
#define CC2_POWER_CYCLE_MS 80
#define CC2_STARTUP_TO_DATA_MS 55
#define CC2_RESP_START_CM_US 100
#define CC2_RESP_EEPROM_R_US 100
#define CC2_RESP_EEPROM_W_MS 12
#define CC2_STARTUP_TIME_US 1250
#define CC2_RH_MAX (100 * 1000U)
#define CC2_CM_RETRIES 5
struct cc2_rh_alarm_info {
bool low_alarm;
bool high_alarm;
bool low_alarm_visible;
bool high_alarm_visible;
};
struct cc2_data {
struct cc2_rh_alarm_info rh_alarm;
struct completion complete;
struct device *hwmon;
struct i2c_client *client;
struct mutex dev_access_lock; /* device access lock */
struct regulator *regulator;
const char *name;
int irq_ready;
int irq_low;
int irq_high;
bool process_irqs;
};
enum cc2_chan_addr {
CC2_CHAN_TEMP = 0,
CC2_CHAN_HUMIDITY,
};
/* %RH as a per cent mille from a register value */
static long cc2_rh_convert(u16 data)
{
unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX;
return tmp / ((1 << 14) - 1);
}
/* convert %RH to a register value */
static u16 cc2_rh_to_reg(long data)
{
return data * ((1 << 14) - 1) / CC2_RH_MAX;
}
/* temperature in milli degrees celsius from a register value */
static long cc2_temp_convert(u16 data)
{
unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1);
return tmp - 40 * 1000U;
}
static int cc2_enable(struct cc2_data *data)
{
int ret;
/* exclusive regulator, check in case a disable failed */
if (regulator_is_enabled(data->regulator))
return 0;
/* clear any pending completion */
try_wait_for_completion(&data->complete);
ret = regulator_enable(data->regulator);
if (ret < 0)
return ret;
usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125);
data->process_irqs = true;
return 0;
}
static void cc2_disable(struct cc2_data *data)
{
int err;
/* ignore alarms triggered by voltage toggling when powering up */
data->process_irqs = false;
/* exclusive regulator, check in case an enable failed */
if (regulator_is_enabled(data->regulator)) {
err = regulator_disable(data->regulator);
if (err)
dev_dbg(&data->client->dev, "Failed to disable device");
}
}
static int cc2_cmd_response_diagnostic(struct device *dev, u8 status)
{
int resp;
if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) {
dev_dbg(dev, "Command sent out of command window\n");
return -ETIMEDOUT;
}
resp = FIELD_GET(CC2_RESPONSE_FIELD, status);
switch (resp) {
case CC2_RESPONSE_ACK:
return 0;
case CC2_RESPONSE_BUSY:
return -EBUSY;
case CC2_RESPONSE_NACK:
if (resp & CC2_ERR_CORR_EEPROM)
dev_dbg(dev, "Command failed: corrected EEPROM\n");
if (resp & CC2_ERR_UNCORR_EEPROM)
dev_dbg(dev, "Command failed: uncorrected EEPROM\n");
if (resp & CC2_ERR_RAM_PARITY)
dev_dbg(dev, "Command failed: RAM parity\n");
if (resp & CC2_ERR_RAM_PARITY)
dev_dbg(dev, "Command failed: configuration error\n");
return -ENODATA;
default:
dev_dbg(dev, "Unknown command reply\n");
return -EINVAL;
}
}
static int cc2_read_command_status(struct i2c_client *client)
{
u8 status;
int ret;
ret = i2c_master_recv(client, &status, 1);
if (ret != 1) {
ret = ret < 0 ? ret : -EIO;
return ret;
}
return cc2_cmd_response_diagnostic(&client->dev, status);
}
/*
* The command mode is only accessible after sending the START_CM command in the
* first 10 ms after power-up. Only in case the command window is missed,
* CC2_CM_RETRIES retries are attempted before giving up and returning an error.
*/
static int cc2_command_mode_start(struct cc2_data *data)
{
unsigned long timeout;
int i, ret;
for (i = 0; i < CC2_CM_RETRIES; i++) {
ret = cc2_enable(data);
if (ret < 0)
return ret;
ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0);
if (ret < 0)
return ret;
if (data->irq_ready > 0) {
timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US);
ret = wait_for_completion_timeout(&data->complete,
timeout);
if (!ret)
return -ETIMEDOUT;
} else {
usleep_range(CC2_RESP_START_CM_US,
2 * CC2_RESP_START_CM_US);
}
ret = cc2_read_command_status(data->client);
if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES)
break;
/* command window missed, prepare for a retry */
cc2_disable(data);
msleep(CC2_POWER_CYCLE_MS);
}
return ret;
}
/* Sending a Start_NOM command finishes the command mode immediately with no
* reply and the device enters normal operation mode
*/
static int cc2_command_mode_finish(struct cc2_data *data)
{
int ret;
ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0);
if (ret < 0)
return ret;
return 0;
}
static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val)
{
unsigned long timeout;
int ret;
ret = cc2_command_mode_start(data);
if (ret < 0)
goto disable;
cpu_to_be16s(&val);
ret = i2c_smbus_write_word_data(data->client, reg, val);
if (ret < 0)
goto disable;
if (data->irq_ready > 0) {
timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret) {
ret = -ETIMEDOUT;
goto disable;
}
} else {
msleep(CC2_RESP_EEPROM_W_MS);
}
ret = cc2_read_command_status(data->client);
disable:
cc2_disable(data);
return ret;
}
static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val)
{
u8 buf[CC2_EEPROM_DATA_LEN];
unsigned long timeout;
int ret;
ret = cc2_command_mode_start(data);
if (ret < 0)
return ret;
ret = i2c_smbus_write_word_data(data->client, reg, 0);
if (ret < 0)
return ret;
if (data->irq_ready > 0) {
timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret)
return -ETIMEDOUT;
} else {
usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10);
}
ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN);
if (ret != CC2_EEPROM_DATA_LEN)
return ret < 0 ? ret : -EIO;
*val = be16_to_cpup((__be16 *)&buf[1]);
return cc2_read_command_status(data->client);
}
static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val)
{
u16 reg_val;
int ret;
ret = cc2_read_reg(data, reg, &reg_val);
if (!ret)
*val = cc2_rh_convert(reg_val);
cc2_disable(data);
return ret;
}
static int cc2_data_fetch(struct i2c_client *client,
enum hwmon_sensor_types type, long *val)
{
u8 data[CC2_MEASUREMENT_DATA_LEN];
u8 status;
int ret;
ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN);
if (ret != CC2_MEASUREMENT_DATA_LEN) {
ret = ret < 0 ? ret : -EIO;
return ret;
}
status = FIELD_GET(CC2_STATUS_FIELD, data[0]);
if (status == CC2_STATUS_STALE_DATA)
return -EBUSY;
if (status != CC2_STATUS_VALID_DATA)
return -EIO;
switch (type) {
case hwmon_humidity:
*val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0]));
break;
case hwmon_temp:
*val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2]));
break;
default:
return -EINVAL;
}
return 0;
}
static int cc2_read_measurement(struct cc2_data *data,
enum hwmon_sensor_types type, long *val)
{
unsigned long timeout;
int ret;
if (data->irq_ready > 0) {
timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret)
return -ETIMEDOUT;
} else {
msleep(CC2_STARTUP_TO_DATA_MS);
}
ret = cc2_data_fetch(data->client, type, val);
return ret;
}
/*
* A measurement requires enabling the device, waiting for the automatic
* measurement to finish, reading the measurement data and disabling the device
* again.
*/
static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type,
long *val)
{
int ret;
ret = cc2_enable(data);
if (ret)
return ret;
ret = cc2_read_measurement(data, type, val);
cc2_disable(data);
return ret;
}
/*
* In order to check alarm status, the corresponding ALARM_OFF (hysteresis)
* register must be read and a new measurement must be carried out to trigger
* the alarm signals. Given that the device carries out a measurement after
* exiting the command mode, there is no need to force two power-up sequences.
* Instead, a NOM command is sent and the device is disabled after the
* measurement is read.
*/
static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg,
long *hyst, long *measurement)
{
u16 reg_val;
int ret;
ret = cc2_read_reg(data, reg, &reg_val);
if (ret)
goto disable;
*hyst = cc2_rh_convert(reg_val);
ret = cc2_command_mode_finish(data);
if (ret)
goto disable;
ret = cc2_read_measurement(data, hwmon_humidity, measurement);
disable:
cc2_disable(data);
return ret;
}
static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct cc2_data *cc2 = data;
switch (type) {
case hwmon_humidity:
switch (attr) {
case hwmon_humidity_input:
return 0444;
case hwmon_humidity_min_alarm:
return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
case hwmon_humidity_max_alarm:
return cc2->rh_alarm.high_alarm_visible ? 0444 : 0;
case hwmon_humidity_min:
case hwmon_humidity_min_hyst:
return cc2->rh_alarm.low_alarm_visible ? 0644 : 0;
case hwmon_humidity_max:
case hwmon_humidity_max_hyst:
return cc2->rh_alarm.high_alarm_visible ? 0644 : 0;
default:
return 0;
}
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
return 0;
}
default:
break;
}
return 0;
}
static irqreturn_t cc2_ready_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs)
complete(&cc2->complete);
return IRQ_HANDLED;
}
static irqreturn_t cc2_low_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
cc2->rh_alarm.low_alarm = true;
}
return IRQ_HANDLED;
}
static irqreturn_t cc2_high_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
cc2->rh_alarm.high_alarm = true;
}
return IRQ_HANDLED;
}
static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val)
{
long measurement, min_hyst;
int ret;
ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst,
&measurement);
if (ret < 0)
return ret;
if (data->rh_alarm.low_alarm) {
*val = (measurement < min_hyst) ? 1 : 0;
data->rh_alarm.low_alarm = *val;
} else {
*val = 0;
}
return 0;
}
static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
{
long measurement, max_hyst;
int ret;
ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst,
&measurement);
if (ret < 0)
return ret;
if (data->rh_alarm.high_alarm) {
*val = (measurement > max_hyst) ? 1 : 0;
data->rh_alarm.high_alarm = *val;
} else {
*val = 0;
}
return 0;
}
static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct cc2_data *data = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&data->dev_access_lock);
switch (type) {
case hwmon_temp:
ret = cc2_measurement(data, type, val);
break;
case hwmon_humidity:
switch (attr) {
case hwmon_humidity_input:
ret = cc2_measurement(data, type, val);
break;
case hwmon_humidity_min:
ret = cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val);
break;
case hwmon_humidity_min_hyst:
ret = cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val);
break;
case hwmon_humidity_max:
ret = cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val);
break;
case hwmon_humidity_max_hyst:
ret = cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val);
break;
case hwmon_humidity_min_alarm:
ret = cc2_humidity_min_alarm_status(data, val);
break;
case hwmon_humidity_max_alarm:
ret = cc2_humidity_max_alarm_status(data, val);
break;
default:
ret = -EOPNOTSUPP;
}
break;
default:
ret = -EOPNOTSUPP;
}
mutex_unlock(&data->dev_access_lock);
return ret;
}
static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct cc2_data *data = dev_get_drvdata(dev);
int ret;
u16 arg;
u8 cmd;
if (type != hwmon_humidity)
return -EOPNOTSUPP;
if (val < 0 || val > CC2_RH_MAX)
return -EINVAL;
mutex_lock(&data->dev_access_lock);
switch (attr) {
case hwmon_humidity_min:
cmd = CC2_W_ALARM_L_ON;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_min_hyst:
cmd = CC2_W_ALARM_L_OFF;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_max:
cmd = CC2_W_ALARM_H_ON;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_max_hyst:
cmd = CC2_W_ALARM_H_OFF;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
default:
ret = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->dev_access_lock);
return ret;
}
static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev)
{
int ret = 0;
data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready");
if (data->irq_ready > 0) {
init_completion(&data->complete);
ret = devm_request_threaded_irq(dev, data->irq_ready, NULL,
cc2_ready_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
}
return ret;
}
static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
{
int ret = 0;
data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low");
if (data->irq_low > 0) {
ret = devm_request_threaded_irq(dev, data->irq_low, NULL,
cc2_low_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
if (ret)
return ret;
data->rh_alarm.low_alarm_visible = true;
}
data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high");
if (data->irq_high > 0) {
ret = devm_request_threaded_irq(dev, data->irq_high, NULL,
cc2_high_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
if (ret)
return ret;
data->rh_alarm.high_alarm_visible = true;
}
return ret;
}
static const struct hwmon_channel_info *cc2_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
NULL
};
static const struct hwmon_ops cc2_hwmon_ops = {
.is_visible = cc2_is_visible,
.read = cc2_read,
.write = cc2_write,
};
static const struct hwmon_chip_info cc2_chip_info = {
.ops = &cc2_hwmon_ops,
.info = cc2_info,
};
static int cc2_probe(struct i2c_client *client)
{
struct cc2_data *data;
struct device *dev = &client->dev;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
mutex_init(&data->dev_access_lock);
data->client = client;
data->regulator = devm_regulator_get_exclusive(dev, "vdd");
if (IS_ERR(data->regulator)) {
dev_err_probe(dev, PTR_ERR(data->regulator),
"Failed to get regulator\n");
return PTR_ERR(data->regulator);
}
ret = cc2_request_ready_irq(data, dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to request ready irq\n");
return ret;
}
ret = cc2_request_alarm_irqs(data, dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to request alarm irqs\n");
goto disable;
}
data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
data, &cc2_chip_info,
NULL);
if (IS_ERR(data->hwmon)) {
dev_err_probe(dev, PTR_ERR(data->hwmon),
"Failed to register hwmon device\n");
ret = PTR_ERR(data->hwmon);
}
disable:
cc2_disable(data);
return ret;
}
static void cc2_remove(struct i2c_client *client)
{
struct cc2_data *data = i2c_get_clientdata(client);
cc2_disable(data);
}
static const struct i2c_device_id cc2_id[] = {
{ "cc2d23" },
{ "cc2d23s" },
{ "cc2d25" },
{ "cc2d25s" },
{ "cc2d33" },
{ "cc2d33s" },
{ "cc2d35" },
{ "cc2d35s" },
{ }
};
MODULE_DEVICE_TABLE(i2c, cc2_id);
static const struct of_device_id cc2_of_match[] = {
{ .compatible = "amphenol,cc2d23" },
{ .compatible = "amphenol,cc2d23s" },
{ .compatible = "amphenol,cc2d25" },
{ .compatible = "amphenol,cc2d25s" },
{ .compatible = "amphenol,cc2d33" },
{ .compatible = "amphenol,cc2d33s" },
{ .compatible = "amphenol,cc2d35" },
{ .compatible = "amphenol,cc2d35s" },
{ },
};
MODULE_DEVICE_TABLE(of, cc2_of_match);
static struct i2c_driver cc2_driver = {
.driver = {
.name = "cc2d23",
.of_match_table = cc2_of_match,
},
.probe = cc2_probe,
.remove = cc2_remove,
.id_table = cc2_id,
};
module_i2c_driver(cc2_driver);
MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gamil.com>");
MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver");
MODULE_LICENSE("GPL");

View File

@ -39,13 +39,18 @@ static int force_tjmax;
module_param_named(tjmax, force_tjmax, int, 0444);
MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */
#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */
#define NUM_REAL_CORES 512 /* Number of Real cores per cpu */
#define CORETEMP_NAME_LENGTH 28 /* String Length of attrs */
#define MAX_CORE_ATTRS 4 /* Maximum no of basic attrs */
#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1)
#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO)
enum coretemp_attr_index {
ATTR_LABEL,
ATTR_CRIT_ALARM,
ATTR_TEMP,
ATTR_TJMAX,
ATTR_TTARGET,
MAX_CORE_ATTRS = ATTR_TJMAX + 1, /* Maximum no of basic attrs */
TOTAL_ATTRS = ATTR_TTARGET + 1 /* Maximum no of possible attrs */
};
#ifdef CONFIG_SMP
#define for_each_sibling(i, cpu) \
@ -65,19 +70,17 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
* @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS,
* from where the temperature values should be read.
* @attr_size: Total number of pre-core attrs displayed in the sysfs.
* @is_pkg_data: If this is 1, the temp_data holds pkgtemp data.
* Otherwise, temp_data holds coretemp data.
*/
struct temp_data {
int temp;
int tjmax;
unsigned long last_updated;
unsigned int cpu;
int index;
u32 cpu_core_id;
u32 status_reg;
int attr_size;
bool is_pkg_data;
struct sensor_device_attribute sd_attrs[TOTAL_ATTRS];
struct device_attribute sd_attrs[TOTAL_ATTRS];
char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH];
struct attribute *attrs[TOTAL_ATTRS + 1];
struct attribute_group attr_group;
@ -88,10 +91,11 @@ struct temp_data {
struct platform_data {
struct device *hwmon_dev;
u16 pkg_id;
u16 cpu_map[NUM_REAL_CORES];
int nr_cores;
struct ida ida;
struct cpumask cpumask;
struct temp_data *core_data[MAX_CORE_DATA];
struct temp_data *pkg_data;
struct temp_data **core_data;
struct device_attribute name_attr;
};
@ -143,6 +147,11 @@ static const struct tjmax_model tjmax_model_table[] = {
*/
};
static bool is_pkg_temp_data(struct temp_data *tdata)
{
return tdata->index < 0;
}
static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
{
/* The 100C is default for both mobile and non mobile CPUs */
@ -332,11 +341,10 @@ static struct platform_device **zone_devices;
static ssize_t show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
struct temp_data *tdata = pdata->core_data[attr->index];
struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_LABEL]);
if (tdata->is_pkg_data)
if (is_pkg_temp_data(tdata))
return sprintf(buf, "Package id %u\n", pdata->pkg_id);
return sprintf(buf, "Core %u\n", tdata->cpu_core_id);
@ -346,9 +354,8 @@ static ssize_t show_crit_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
struct temp_data *tdata = pdata->core_data[attr->index];
struct temp_data *tdata = container_of(devattr, struct temp_data,
sd_attrs[ATTR_CRIT_ALARM]);
mutex_lock(&tdata->update_lock);
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
@ -360,9 +367,7 @@ static ssize_t show_crit_alarm(struct device *dev,
static ssize_t show_tjmax(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
struct temp_data *tdata = pdata->core_data[attr->index];
struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TJMAX]);
int tjmax;
mutex_lock(&tdata->update_lock);
@ -375,9 +380,7 @@ static ssize_t show_tjmax(struct device *dev,
static ssize_t show_ttarget(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
struct temp_data *tdata = pdata->core_data[attr->index];
struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TTARGET]);
int ttarget;
mutex_lock(&tdata->update_lock);
@ -393,9 +396,7 @@ static ssize_t show_temp(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
struct temp_data *tdata = pdata->core_data[attr->index];
struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TEMP]);
int tjmax;
mutex_lock(&tdata->update_lock);
@ -418,8 +419,7 @@ static ssize_t show_temp(struct device *dev,
return sprintf(buf, "%d\n", tdata->temp);
}
static int create_core_attrs(struct temp_data *tdata, struct device *dev,
int index)
static int create_core_attrs(struct temp_data *tdata, struct device *dev)
{
int i;
static ssize_t (*const rd_ptr[TOTAL_ATTRS]) (struct device *dev,
@ -436,16 +436,15 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev,
* The attr number is always core id + 2
* The Pkgtemp will always show up as temp1_*, if available
*/
int attr_no = tdata->is_pkg_data ? 1 : tdata->cpu_core_id + 2;
int attr_no = is_pkg_temp_data(tdata) ? 1 : tdata->cpu_core_id + 2;
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH,
"temp%d_%s", attr_no, suffixes[i]);
sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr);
tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i];
tdata->sd_attrs[i].dev_attr.attr.mode = 0444;
tdata->sd_attrs[i].dev_attr.show = rd_ptr[i];
tdata->sd_attrs[i].index = index;
tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr;
sysfs_attr_init(&tdata->sd_attrs[i].attr);
tdata->sd_attrs[i].attr.name = tdata->attr_name[i];
tdata->sd_attrs[i].attr.mode = 0444;
tdata->sd_attrs[i].show = rd_ptr[i];
tdata->attrs[i] = &tdata->sd_attrs[i].attr;
}
tdata->attr_group.attrs = tdata->attrs;
return sysfs_create_group(&dev->kobj, &tdata->attr_group);
@ -477,17 +476,44 @@ static struct platform_device *coretemp_get_pdev(unsigned int cpu)
return NULL;
}
static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
static struct temp_data *
init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag)
{
struct temp_data *tdata;
if (!pdata->core_data) {
/*
* TODO:
* The information of actual possible cores in a package is broken for now.
* Will replace hardcoded NUM_REAL_CORES with actual per package core count
* when this information becomes available.
*/
pdata->nr_cores = NUM_REAL_CORES;
pdata->core_data = kcalloc(pdata->nr_cores, sizeof(struct temp_data *),
GFP_KERNEL);
if (!pdata->core_data)
return NULL;
}
tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL);
if (!tdata)
return NULL;
if (pkg_flag) {
pdata->pkg_data = tdata;
/* Use tdata->index as indicator of package temp data */
tdata->index = -1;
} else {
tdata->index = ida_alloc_max(&pdata->ida, pdata->nr_cores - 1, GFP_KERNEL);
if (tdata->index < 0) {
kfree(tdata);
return NULL;
}
pdata->core_data[tdata->index] = tdata;
}
tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS :
MSR_IA32_THERM_STATUS;
tdata->is_pkg_data = pkg_flag;
tdata->cpu = cpu;
tdata->cpu_core_id = topology_core_id(cpu);
tdata->attr_size = MAX_CORE_ATTRS;
@ -495,6 +521,36 @@ static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
return tdata;
}
static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata)
{
if (is_pkg_temp_data(tdata)) {
pdata->pkg_data = NULL;
kfree(pdata->core_data);
pdata->core_data = NULL;
pdata->nr_cores = 0;
} else {
pdata->core_data[tdata->index] = NULL;
ida_free(&pdata->ida, tdata->index);
}
kfree(tdata);
}
static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu)
{
int i;
/* cpu < 0 means get pkg temp_data */
if (cpu < 0)
return pdata->pkg_data;
for (i = 0; i < pdata->nr_cores; i++) {
if (pdata->core_data[i] &&
pdata->core_data[i]->cpu_core_id == topology_core_id(cpu))
return pdata->core_data[i];
}
return NULL;
}
static int create_core_data(struct platform_device *pdev, unsigned int cpu,
int pkg_flag)
{
@ -502,37 +558,19 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
struct platform_data *pdata = platform_get_drvdata(pdev);
struct cpuinfo_x86 *c = &cpu_data(cpu);
u32 eax, edx;
int err, index;
int err;
if (!housekeeping_cpu(cpu, HK_TYPE_MISC))
return 0;
/*
* Get the index of tdata in pdata->core_data[]
* tdata for package: pdata->core_data[1]
* tdata for core: pdata->core_data[2] .. pdata->core_data[NUM_REAL_CORES + 1]
*/
if (pkg_flag) {
index = PKG_SYSFS_ATTR_NO;
} else {
index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL);
if (index < 0)
return index;
pdata->cpu_map[index] = topology_core_id(cpu);
index += BASE_SYSFS_ATTR_NO;
}
tdata = init_temp_data(cpu, pkg_flag);
if (!tdata) {
err = -ENOMEM;
goto ida_free;
}
tdata = init_temp_data(pdata, cpu, pkg_flag);
if (!tdata)
return -ENOMEM;
/* Test if we can access the status register */
err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx);
if (err)
goto exit_free;
goto err;
/* Make sure tdata->tjmax is a valid indicator for dynamic/static tjmax */
get_tjmax(tdata, &pdev->dev);
@ -546,20 +584,15 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
if (get_ttarget(tdata, &pdev->dev) >= 0)
tdata->attr_size++;
pdata->core_data[index] = tdata;
/* Create sysfs interfaces */
err = create_core_attrs(tdata, pdata->hwmon_dev, index);
err = create_core_attrs(tdata, pdata->hwmon_dev);
if (err)
goto exit_free;
goto err;
return 0;
exit_free:
pdata->core_data[index] = NULL;
kfree(tdata);
ida_free:
if (!pkg_flag)
ida_free(&pdata->ida, index - BASE_SYSFS_ATTR_NO);
err:
destroy_temp_data(pdata, tdata);
return err;
}
@ -570,10 +603,8 @@ coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag)
dev_err(&pdev->dev, "Adding Core %u failed\n", cpu);
}
static void coretemp_remove_core(struct platform_data *pdata, int indx)
static void coretemp_remove_core(struct platform_data *pdata, struct temp_data *tdata)
{
struct temp_data *tdata = pdata->core_data[indx];
/* if we errored on add then this is already gone */
if (!tdata)
return;
@ -581,11 +612,7 @@ static void coretemp_remove_core(struct platform_data *pdata, int indx)
/* Remove the sysfs attributes */
sysfs_remove_group(&pdata->hwmon_dev->kobj, &tdata->attr_group);
kfree(pdata->core_data[indx]);
pdata->core_data[indx] = NULL;
if (indx >= BASE_SYSFS_ATTR_NO)
ida_free(&pdata->ida, indx - BASE_SYSFS_ATTR_NO);
destroy_temp_data(pdata, tdata);
}
static int coretemp_device_add(int zoneid)
@ -698,7 +725,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
struct platform_device *pdev = coretemp_get_pdev(cpu);
struct platform_data *pd;
struct temp_data *tdata;
int i, indx = -1, target;
int target;
/* No need to tear down any interfaces for suspend */
if (cpuhp_tasks_frozen)
@ -709,18 +736,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
if (!pd->hwmon_dev)
return 0;
for (i = 0; i < NUM_REAL_CORES; i++) {
if (pd->cpu_map[i] == topology_core_id(cpu)) {
indx = i + BASE_SYSFS_ATTR_NO;
break;
}
}
/* Too many cores and this core is not populated, just return */
if (indx < 0)
return 0;
tdata = pd->core_data[indx];
tdata = get_temp_data(pd, cpu);
cpumask_clear_cpu(cpu, &pd->cpumask);
@ -731,7 +747,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
*/
target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu));
if (target >= nr_cpu_ids) {
coretemp_remove_core(pd, indx);
coretemp_remove_core(pd, tdata);
} else if (tdata && tdata->cpu == cpu) {
mutex_lock(&tdata->update_lock);
tdata->cpu = target;
@ -741,10 +757,10 @@ static int coretemp_cpu_offline(unsigned int cpu)
/*
* If all cores in this pkg are offline, remove the interface.
*/
tdata = pd->core_data[PKG_SYSFS_ATTR_NO];
tdata = get_temp_data(pd, -1);
if (cpumask_empty(&pd->cpumask)) {
if (tdata)
coretemp_remove_core(pd, PKG_SYSFS_ATTR_NO);
coretemp_remove_core(pd, tdata);
hwmon_device_unregister(pd->hwmon_dev);
pd->hwmon_dev = NULL;
return 0;

View File

@ -1450,10 +1450,15 @@ struct i8k_fan_control_data {
};
enum i8k_fan_controls {
I8K_FAN_30A3_31A3,
I8K_FAN_34A3_35A3,
};
static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
[I8K_FAN_30A3_31A3] = {
.manual_fan = 0x30a3,
.auto_fan = 0x31a3,
},
[I8K_FAN_34A3_35A3] = {
.manual_fan = 0x34a3,
.auto_fan = 0x35a3,
@ -1517,6 +1522,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
{
.ident = "Dell XPS 9315",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 9315"),
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
},
{ }
};

View File

@ -380,7 +380,6 @@ MODULE_DEVICE_TABLE(i2c, ds1621_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds1621_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ds1621",
},

View File

@ -241,7 +241,6 @@ MODULE_DEVICE_TABLE(i2c, ds620_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds620_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ds620",
},

View File

@ -385,7 +385,7 @@ static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config emc1403_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = emc1403_regmap_is_volatile,
};

View File

@ -12,9 +12,6 @@
#include <linux/platform_data/emc2305.h>
#include <linux/thermal.h>
static const unsigned short
emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END };
#define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
#define EMC2305_REG_VENDOR 0xfe
#define EMC2305_FAN_MAX 0xff
@ -611,14 +608,12 @@ static void emc2305_remove(struct i2c_client *client)
}
static struct i2c_driver emc2305_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "emc2305",
},
.probe = emc2305_probe,
.remove = emc2305_remove,
.id_table = emc2305_ids,
.address_list = emc2305_normal_i2c,
};
module_i2c_driver(emc2305_driver);

View File

@ -510,6 +510,7 @@ static const char * const hwmon_in_attr_templates[] = {
[hwmon_in_rated_min] = "in%d_rated_min",
[hwmon_in_rated_max] = "in%d_rated_max",
[hwmon_in_beep] = "in%d_beep",
[hwmon_in_fault] = "in%d_fault",
};
static const char * const hwmon_curr_attr_templates[] = {
@ -586,6 +587,8 @@ static const char * const hwmon_humidity_attr_templates[] = {
[hwmon_humidity_fault] = "humidity%d_fault",
[hwmon_humidity_rated_min] = "humidity%d_rated_min",
[hwmon_humidity_rated_max] = "humidity%d_rated_max",
[hwmon_humidity_min_alarm] = "humidity%d_min_alarm",
[hwmon_humidity_max_alarm] = "humidity%d_max_alarm",
};
static const char * const hwmon_fan_attr_templates[] = {

View File

@ -589,7 +589,6 @@ MODULE_DEVICE_TABLE(of, ina209_of_match);
/* This is the driver that will be inserted */
static struct i2c_driver ina209_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ina209",
.of_match_table = of_match_ptr(ina209_of_match),

View File

@ -629,7 +629,6 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = {
MODULE_DEVICE_TABLE(of, ina238_of_match);
static struct i2c_driver ina238_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ina238",
.of_match_table = of_match_ptr(ina238_of_match),

View File

@ -762,7 +762,7 @@ static const struct regmap_config ina3221_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_table = &ina3221_volatile_table,
};

View File

@ -497,7 +497,7 @@ static const struct regmap_config jc42_regmap_config = {
.writeable_reg = jc42_writable_reg,
.readable_reg = jc42_readable_reg,
.volatile_reg = jc42_volatile_reg,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
};
static int jc42_probe(struct i2c_client *client)

View File

@ -165,7 +165,7 @@ static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config lm83_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = lm83_regmap_is_volatile,
.reg_read = lm83_regmap_reg_read,
.reg_write = lm83_regmap_reg_write,

1782
drivers/hwmon/ltc4282.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -335,7 +335,6 @@ static const struct i2c_device_id max127_id[] = {
MODULE_DEVICE_TABLE(i2c, max127_id);
static struct i2c_driver max127_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "max127",
},

View File

@ -60,7 +60,7 @@ static const struct regmap_config regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x5B,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = max31760_volatile_reg,
};
@ -578,7 +578,6 @@ static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend,
max31760_resume);
static struct i2c_driver max31760_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "max31760",
.of_match_table = max31760_of_match,

View File

@ -543,7 +543,6 @@ static const struct i2c_device_id max31790_id[] = {
MODULE_DEVICE_TABLE(i2c, max31790_id);
static struct i2c_driver max31790_driver = {
.class = I2C_CLASS_HWMON,
.probe = max31790_probe,
.driver = {
.name = "max31790",

View File

@ -652,7 +652,6 @@ static const struct of_device_id max31827_of_match[] = {
MODULE_DEVICE_TABLE(of, max31827_of_match);
static struct i2c_driver max31827_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "max31827",
.of_match_table = max31827_of_match,

View File

@ -549,7 +549,6 @@ static const struct of_device_id __maybe_unused max6621_of_match[] = {
MODULE_DEVICE_TABLE(of, max6621_of_match);
static struct i2c_driver max6621_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = MAX6621_DRV_NAME,
.of_match_table = of_match_ptr(max6621_of_match),

View File

@ -780,7 +780,6 @@ static const struct of_device_id __maybe_unused max6697_of_match[] = {
MODULE_DEVICE_TABLE(of, max6697_of_match);
static struct i2c_driver max6697_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "max6697",
.of_match_table = of_match_ptr(max6697_of_match),

View File

@ -174,6 +174,7 @@ superio_exit(int ioreg)
#define NCT6683_CUSTOMER_ID_MITAC 0xa0e
#define NCT6683_CUSTOMER_ID_MSI 0x201
#define NCT6683_CUSTOMER_ID_MSI2 0x200
#define NCT6683_CUSTOMER_ID_MSI3 0x207
#define NCT6683_CUSTOMER_ID_ASROCK 0xe2c
#define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b
#define NCT6683_CUSTOMER_ID_ASROCK3 0x1631
@ -1224,6 +1225,8 @@ static int nct6683_probe(struct platform_device *pdev)
break;
case NCT6683_CUSTOMER_ID_MSI2:
break;
case NCT6683_CUSTOMER_ID_MSI3:
break;
case NCT6683_CUSTOMER_ID_ASROCK:
break;
case NCT6683_CUSTOMER_ID_ASROCK2:

View File

@ -1051,7 +1051,7 @@ static bool nct7802_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config nct7802_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = nct7802_regmap_is_volatile,
};

1008
drivers/hwmon/nzxt-kraken3.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -241,7 +241,6 @@ static const struct of_device_id p8_i2c_occ_of_match[] = {
MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
static struct i2c_driver p8_i2c_occ_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "occ-hwmon",
.of_match_table = p8_i2c_occ_of_match,

View File

@ -43,6 +43,7 @@ enum oxp_board {
aok_zoe_a1 = 1,
aya_neo_2,
aya_neo_air,
aya_neo_air_plus_mendo,
aya_neo_air_pro,
aya_neo_geek,
oxp_mini_amd,
@ -98,6 +99,13 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)aya_neo_air,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
},
.driver_data = (void *)aya_neo_air_plus_mendo,
},
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
@ -332,6 +340,7 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
switch (board) {
case aya_neo_2:
case aya_neo_air:
case aya_neo_air_plus_mendo:
case aya_neo_air_pro:
case aya_neo_geek:
case oxp_mini_amd:
@ -374,6 +383,7 @@ static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
switch (board) {
case aya_neo_2:
case aya_neo_air:
case aya_neo_air_plus_mendo:
case aya_neo_air_pro:
case aya_neo_geek:
case oxp_mini_amd:

View File

@ -377,6 +377,15 @@ config SENSORS_MPQ7932
This driver can also be built as a module. If so, the module will
be called mpq7932.
config SENSORS_MPQ8785
tristate "MPS MPQ8785"
help
If you say yes here you get hardware monitoring functionality support
for power management IC MPS MPQ8785.
This driver can also be built as a module. If so, the module will
be called mpq8785.
config SENSORS_PIM4328
tristate "Flex PIM4328 and compatibles"
help

View File

@ -39,6 +39,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
obj-$(CONFIG_SENSORS_MP5990) += mp5990.o
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o

View File

@ -63,7 +63,6 @@ static const struct of_device_id __maybe_unused ir36021_of_id[] = {
MODULE_DEVICE_TABLE(of, ir36021_of_id);
static struct i2c_driver ir36021_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ir36021",
.of_match_table = of_match_ptr(ir36021_of_id),

View File

@ -22,7 +22,7 @@
#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR)
static const struct regulator_desc ir38064_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR_ONE("vout"),
};
#endif /* CONFIG_SENSORS_IR38064_REGULATOR */

View File

@ -437,7 +437,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR)
static const struct regulator_desc lm25066_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR_ONE("vout"),
};
#endif

View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for MPS MPQ8785 Step-Down Converter
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include "pmbus.h"
static int mpq8785_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
int vout_mode;
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
if (vout_mode < 0 || vout_mode == 0xff)
return vout_mode < 0 ? vout_mode : -ENODEV;
switch (vout_mode >> 5) {
case 0:
info->format[PSC_VOLTAGE_OUT] = linear;
break;
case 1:
case 2:
info->format[PSC_VOLTAGE_OUT] = direct,
info->m[PSC_VOLTAGE_OUT] = 64;
info->b[PSC_VOLTAGE_OUT] = 0;
info->R[PSC_VOLTAGE_OUT] = 1;
break;
default:
return -ENODEV;
}
return 0;
};
static struct pmbus_driver_info mpq8785_info = {
.pages = 1,
.format[PSC_VOLTAGE_IN] = direct,
.format[PSC_CURRENT_OUT] = direct,
.format[PSC_TEMPERATURE] = direct,
.m[PSC_VOLTAGE_IN] = 4,
.b[PSC_VOLTAGE_IN] = 0,
.R[PSC_VOLTAGE_IN] = 1,
.m[PSC_CURRENT_OUT] = 16,
.b[PSC_CURRENT_OUT] = 0,
.R[PSC_CURRENT_OUT] = 0,
.m[PSC_TEMPERATURE] = 1,
.b[PSC_TEMPERATURE] = 0,
.R[PSC_TEMPERATURE] = 0,
.func[0] =
PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
.identify = mpq8785_identify,
};
static int mpq8785_probe(struct i2c_client *client)
{
return pmbus_do_probe(client, &mpq8785_info);
};
static const struct i2c_device_id mpq8785_id[] = {
{ "mpq8785", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, mpq8785_id);
static const struct of_device_id __maybe_unused mpq8785_of_match[] = {
{ .compatible = "mps,mpq8785" },
{}
};
MODULE_DEVICE_TABLE(of, mpq8785_of_match);
static struct i2c_driver mpq8785_driver = {
.driver = {
.name = "mpq8785",
.of_match_table = of_match_ptr(mpq8785_of_match),
},
.probe = mpq8785_probe,
.id_table = mpq8785_id,
};
module_i2c_driver(mpq8785_driver);
MODULE_AUTHOR("Charles Hsu <ythsu0511@gmail.com>");
MODULE_DESCRIPTION("PMBus driver for MPS MPQ8785");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(PMBUS);

View File

@ -3188,7 +3188,7 @@ static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, u8 val)
{
return pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
return _pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
}
static irqreturn_t pmbus_fault_handler(int irq, void *pdata)

View File

@ -15,7 +15,7 @@
#include "pmbus.h"
static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = {
PMBUS_REGULATOR("vout", 0),
PMBUS_REGULATOR_ONE("vout"),
};
struct tda38640_data {

View File

@ -323,7 +323,6 @@ static const struct i2c_device_id powr1220_ids[] = {
MODULE_DEVICE_TABLE(i2c, powr1220_ids);
static struct i2c_driver powr1220_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "powr1220",
},

667
drivers/hwmon/pt5161l.c Normal file
View File

@ -0,0 +1,667 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/mutex.h>
/* Aries current average temp ADC code CSR */
#define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c
/* Device Load check register */
#define ARIES_CODE_LOAD_REG 0x605
/* Value indicating FW was loaded properly, [3:1] = 3'b111 */
#define ARIES_LOAD_CODE 0xe
/* Main Micro Heartbeat register */
#define ARIES_MM_HEARTBEAT_ADDR 0x923
/* Reg offset to specify Address for MM assisted accesses */
#define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99
/* Reg offset to specify Command for MM assisted accesses */
#define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d
/* Reg offset to MM SPARE 0 used specify Address[7:0] */
#define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f
/* Reg offset to MM SPARE 3 used specify Data Byte 0 */
#define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2
/* Wide register reads */
#define ARIES_MM_RD_WIDE_REG_2B 0x1d
#define ARIES_MM_RD_WIDE_REG_3B 0x1e
#define ARIES_MM_RD_WIDE_REG_4B 0x1f
#define ARIES_MM_RD_WIDE_REG_5B 0x20
/* Time delay between checking MM status of EEPROM write (microseconds) */
#define ARIES_MM_STATUS_TIME 5000
/* AL Main SRAM DMEM offset (A0) */
#define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024)
/* SRAM read command */
#define AL_TG_RD_LOC_IND_SRAM 0x16
/* Offset for main micro FW info */
#define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128)
/* FW Info (Major) offset location in struct */
#define ARIES_MM_FW_VERSION_MAJOR 0
/* FW Info (Minor) offset location in struct */
#define ARIES_MM_FW_VERSION_MINOR 1
/* FW Info (Build no.) offset location in struct */
#define ARIES_MM_FW_VERSION_BUILD 2
#define ARIES_TEMP_CAL_CODE_DEFAULT 84
/* Struct defining FW version loaded on an Aries device */
struct pt5161l_fw_ver {
u8 major;
u8 minor;
u16 build;
};
/* Each client has this additional data */
struct pt5161l_data {
struct i2c_client *client;
struct dentry *debugfs;
struct pt5161l_fw_ver fw_ver;
struct mutex lock; /* for atomic I2C transactions */
bool init_done;
bool code_load_okay; /* indicate if code load reg value is expected */
bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */
bool mm_wide_reg_access; /* MM assisted wide register access */
};
static struct dentry *pt5161l_debugfs_dir;
/*
* Write multiple data bytes to Aries over I2C
*/
static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address,
u8 len, u8 *val)
{
struct i2c_client *client = data->client;
int ret;
u8 remain_len = len;
u8 xfer_len, curr_len;
u8 buf[16];
u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
while (remain_len > 0) {
if (remain_len > 4) {
curr_len = 4;
remain_len -= 4;
} else {
curr_len = remain_len;
remain_len = 0;
}
buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1);
buf[1] = (address >> 8) & 0xff;
buf[2] = address & 0xff;
memcpy(&buf[3], val, curr_len);
xfer_len = 3 + curr_len;
ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf);
if (ret)
return ret;
val += curr_len;
address += curr_len;
}
return 0;
}
/*
* Read multiple data bytes from Aries over I2C
*/
static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
u8 len, u8 *val)
{
struct i2c_client *client = data->client;
int ret, tries;
u8 remain_len = len;
u8 curr_len;
u8 wbuf[16], rbuf[24];
u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
while (remain_len > 0) {
if (remain_len > 16) {
curr_len = 16;
remain_len -= 16;
} else {
curr_len = remain_len;
remain_len = 0;
}
wbuf[0] = config | (curr_len - 1) << 1 |
((address >> 16) & 0x1);
wbuf[1] = (address >> 8) & 0xff;
wbuf[2] = address & 0xff;
for (tries = 0; tries < 3; tries++) {
ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3,
wbuf);
if (ret)
return ret;
ret = i2c_smbus_read_block_data(client, (cmd | 0x1),
rbuf);
if (ret == curr_len)
break;
}
if (tries >= 3)
return ret;
memcpy(val, rbuf, curr_len);
val += curr_len;
address += curr_len;
}
return 0;
}
static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address,
u8 width, u8 *val)
{
int ret, tries;
u8 buf[8];
u8 status;
/*
* Safely access wide registers using mailbox method to prevent
* risking conflict with Aries firmware; otherwise fallback to
* legacy, less secure method.
*/
if (data->mm_wide_reg_access) {
buf[0] = address & 0xff;
buf[1] = (address >> 8) & 0xff;
buf[2] = (address >> 16) & 0x1;
ret = pt5161l_write_block_data(data,
ARIES_MM_ASSIST_SPARE_0_OFFSET,
3, buf);
if (ret)
return ret;
/* Set command based on width */
switch (width) {
case 2:
buf[0] = ARIES_MM_RD_WIDE_REG_2B;
break;
case 3:
buf[0] = ARIES_MM_RD_WIDE_REG_3B;
break;
case 4:
buf[0] = ARIES_MM_RD_WIDE_REG_4B;
break;
case 5:
buf[0] = ARIES_MM_RD_WIDE_REG_5B;
break;
default:
return -EINVAL;
}
ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET,
1, buf);
if (ret)
return ret;
status = 0xff;
for (tries = 0; tries < 100; tries++) {
ret = pt5161l_read_block_data(data,
ARIES_MM_ASSIST_CMD_OFFSET,
1, &status);
if (ret)
return ret;
if (status == 0)
break;
usleep_range(ARIES_MM_STATUS_TIME,
ARIES_MM_STATUS_TIME + 1000);
}
if (status != 0)
return -ETIMEDOUT;
ret = pt5161l_read_block_data(data,
ARIES_MM_ASSIST_SPARE_3_OFFSET,
width, val);
if (ret)
return ret;
} else {
return pt5161l_read_block_data(data, address, width, val);
}
return 0;
}
/*
* Read multiple (up to eight) data bytes from micro SRAM over I2C
*/
static int
pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data,
u32 address, u8 len, u8 *val)
{
int ret, tries;
u8 buf[8];
u8 i, status;
u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET;
u32 eeprom_base, eeprom_addr;
/* No multi-byte indirect support here. Hence read a byte at a time */
eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET;
for (i = 0; i < len; i++) {
eeprom_addr = eeprom_base + i;
buf[0] = eeprom_addr & 0xff;
buf[1] = (eeprom_addr >> 8) & 0xff;
buf[2] = (eeprom_addr >> 16) & 0xff;
ret = pt5161l_write_block_data(data, uind_offs, 3, buf);
if (ret)
return ret;
buf[0] = AL_TG_RD_LOC_IND_SRAM;
ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf);
if (ret)
return ret;
status = 0xff;
for (tries = 0; tries < 255; tries++) {
ret = pt5161l_read_block_data(data, uind_offs + 4, 1,
&status);
if (ret)
return ret;
if (status == 0)
break;
}
if (status != 0)
return -ETIMEDOUT;
ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf);
if (ret)
return ret;
val[i] = buf[0];
}
return 0;
}
/*
* Check firmware load status
*/
static int pt5161l_fw_load_check(struct pt5161l_data *data)
{
int ret;
u8 buf[8];
ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf);
if (ret)
return ret;
if (buf[0] < ARIES_LOAD_CODE) {
dev_dbg(&data->client->dev,
"Code Load reg unexpected. Not all modules are loaded %x\n",
buf[0]);
data->code_load_okay = false;
} else {
data->code_load_okay = true;
}
return 0;
}
/*
* Check main micro heartbeat
*/
static int pt5161l_heartbeat_check(struct pt5161l_data *data)
{
int ret, tries;
u8 buf[8];
u8 heartbeat;
bool hb_changed = false;
ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf);
if (ret)
return ret;
heartbeat = buf[0];
for (tries = 0; tries < 100; tries++) {
ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1,
buf);
if (ret)
return ret;
if (buf[0] != heartbeat) {
hb_changed = true;
break;
}
}
data->mm_heartbeat_okay = hb_changed;
return 0;
}
/*
* Check the status of firmware
*/
static int pt5161l_fwsts_check(struct pt5161l_data *data)
{
int ret;
u8 buf[8];
u8 major = 0, minor = 0;
u16 build = 0;
ret = pt5161l_fw_load_check(data);
if (ret)
return ret;
ret = pt5161l_heartbeat_check(data);
if (ret)
return ret;
if (data->code_load_okay && data->mm_heartbeat_okay) {
ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
ARIES_MM_FW_VERSION_MAJOR,
1, &major);
if (ret)
return ret;
ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
ARIES_MM_FW_VERSION_MINOR,
1, &minor);
if (ret)
return ret;
ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
ARIES_MM_FW_VERSION_BUILD,
2, buf);
if (ret)
return ret;
build = buf[1] << 8 | buf[0];
}
data->fw_ver.major = major;
data->fw_ver.minor = minor;
data->fw_ver.build = build;
return 0;
}
static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor,
u16 build)
{
u32 ver = major << 24 | minor << 16 | build;
u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 |
data->fw_ver.build;
if (curr_ver >= ver)
return true;
return false;
}
static int pt5161l_init_dev(struct pt5161l_data *data)
{
int ret;
mutex_lock(&data->lock);
ret = pt5161l_fwsts_check(data);
mutex_unlock(&data->lock);
if (ret)
return ret;
/* Firmware 2.2.0 enables safe access to wide registers */
if (pt5161l_fw_is_at_least(data, 2, 2, 0))
data->mm_wide_reg_access = true;
data->init_done = true;
return 0;
}
static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct pt5161l_data *data = dev_get_drvdata(dev);
int ret;
u8 buf[8];
long adc_code;
switch (attr) {
case hwmon_temp_input:
if (!data->init_done) {
ret = pt5161l_init_dev(data);
if (ret)
return ret;
}
mutex_lock(&data->lock);
ret = pt5161l_read_wide_reg(data,
ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4,
buf);
mutex_unlock(&data->lock);
if (ret) {
dev_dbg(dev, "Read adc_code failed %d\n", ret);
return ret;
}
adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
if (adc_code == 0 || adc_code >= 0x3ff) {
dev_dbg(dev, "Invalid adc_code %lx\n", adc_code);
return -EIO;
}
*val = 110000 +
((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) *
-320);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static umode_t pt5161l_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *pt5161l_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
NULL
};
static const struct hwmon_ops pt5161l_hwmon_ops = {
.is_visible = pt5161l_is_visible,
.read = pt5161l_read,
};
static const struct hwmon_chip_info pt5161l_chip_info = {
.ops = &pt5161l_hwmon_ops,
.info = pt5161l_info,
};
static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct pt5161l_data *data = file->private_data;
int ret;
char ver[32];
mutex_lock(&data->lock);
ret = pt5161l_fwsts_check(data);
mutex_unlock(&data->lock);
if (ret)
return ret;
ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major,
data->fw_ver.minor, data->fw_ver.build);
return simple_read_from_buffer(buf, count, ppos, ver, ret);
}
static const struct file_operations pt5161l_debugfs_ops_fw_ver = {
.read = pt5161l_debugfs_read_fw_ver,
.open = simple_open,
};
static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file,
char __user *buf, size_t count,
loff_t *ppos)
{
struct pt5161l_data *data = file->private_data;
int ret;
bool status = false;
char health[16];
mutex_lock(&data->lock);
ret = pt5161l_fw_load_check(data);
mutex_unlock(&data->lock);
if (ret == 0)
status = data->code_load_okay;
ret = snprintf(health, sizeof(health), "%s\n",
status ? "normal" : "abnormal");
return simple_read_from_buffer(buf, count, ppos, health, ret);
}
static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = {
.read = pt5161l_debugfs_read_fw_load_sts,
.open = simple_open,
};
static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct pt5161l_data *data = file->private_data;
int ret;
bool status = false;
char health[16];
mutex_lock(&data->lock);
ret = pt5161l_heartbeat_check(data);
mutex_unlock(&data->lock);
if (ret == 0)
status = data->mm_heartbeat_okay;
ret = snprintf(health, sizeof(health), "%s\n",
status ? "normal" : "abnormal");
return simple_read_from_buffer(buf, count, ppos, health, ret);
}
static const struct file_operations pt5161l_debugfs_ops_hb_sts = {
.read = pt5161l_debugfs_read_hb_sts,
.open = simple_open,
};
static int pt5161l_init_debugfs(struct pt5161l_data *data)
{
data->debugfs = debugfs_create_dir(dev_name(&data->client->dev),
pt5161l_debugfs_dir);
debugfs_create_file("fw_ver", 0444, data->debugfs, data,
&pt5161l_debugfs_ops_fw_ver);
debugfs_create_file("fw_load_status", 0444, data->debugfs, data,
&pt5161l_debugfs_ops_fw_load_sts);
debugfs_create_file("heartbeat_status", 0444, data->debugfs, data,
&pt5161l_debugfs_ops_hb_sts);
return 0;
}
static int pt5161l_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct pt5161l_data *data;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
pt5161l_init_dev(data);
dev_set_drvdata(dev, data);
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data,
&pt5161l_chip_info,
NULL);
pt5161l_init_debugfs(data);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static void pt5161l_remove(struct i2c_client *client)
{
struct pt5161l_data *data = i2c_get_clientdata(client);
debugfs_remove_recursive(data->debugfs);
}
static const struct of_device_id __maybe_unused pt5161l_of_match[] = {
{ .compatible = "asteralabs,pt5161l" },
{},
};
MODULE_DEVICE_TABLE(of, pt5161l_of_match);
static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = {
{ "PT5161L", 0 },
{},
};
MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match);
static const struct i2c_device_id pt5161l_id[] = {
{ "pt5161l", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, pt5161l_id);
static struct i2c_driver pt5161l_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "pt5161l",
.of_match_table = of_match_ptr(pt5161l_of_match),
.acpi_match_table = ACPI_PTR(pt5161l_acpi_match),
},
.probe = pt5161l_probe,
.remove = pt5161l_remove,
.id_table = pt5161l_id,
};
static int __init pt5161l_init(void)
{
pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL);
return i2c_add_driver(&pt5161l_driver);
}
static void __exit pt5161l_exit(void)
{
i2c_del_driver(&pt5161l_driver);
debugfs_remove_recursive(pt5161l_debugfs_dir);
}
module_init(pt5161l_init);
module_exit(pt5161l_exit);
MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>");
MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer");
MODULE_LICENSE("GPL");

View File

@ -342,7 +342,6 @@ static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
MODULE_DEVICE_TABLE(of, sbrmi_of_match);
static struct i2c_driver sbrmi_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "sbrmi",
.of_match_table = of_match_ptr(sbrmi_of_match),

View File

@ -232,7 +232,6 @@ static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
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),

View File

@ -116,7 +116,7 @@ static const struct regmap_config sch5627_regmap_config = {
.val_bits = 8,
.wr_table = &sch5627_tunables_table,
.rd_table = &sch5627_tunables_table,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.use_single_read = true,
.use_single_write = true,
.can_sleep = true,

View File

@ -10,6 +10,7 @@
#include <asm/page.h>
#include <linux/crc8.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hwmon.h>
@ -41,6 +42,9 @@ static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 };
/* other commands */
static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d };
static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 };
static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 };
static struct dentry *debugfs;
/* delays for single-shot mode i2c commands, both in us */
#define SHT3X_SINGLE_WAIT_TIME_HPM 15000
@ -163,12 +167,14 @@ struct sht3x_data {
enum sht3x_chips chip_id;
struct mutex i2c_lock; /* lock for sending i2c commands */
struct mutex data_lock; /* lock for updating driver data */
struct dentry *sensor_dir;
u8 mode;
const unsigned char *command;
u32 wait_time; /* in us*/
unsigned long last_update; /* last update in periodic mode*/
enum sht3x_repeatability repeatability;
u32 serial_number;
/*
* cached values for temperature and humidity and limits
@ -831,6 +837,40 @@ static int sht3x_write(struct device *dev, enum hwmon_sensor_types type,
}
}
static void sht3x_debugfs_init(struct sht3x_data *data)
{
char name[32];
snprintf(name, sizeof(name), "i2c%u-%02x",
data->client->adapter->nr, data->client->addr);
data->sensor_dir = debugfs_create_dir(name, debugfs);
debugfs_create_u32("serial_number", 0444,
data->sensor_dir, &data->serial_number);
}
static void sht3x_debugfs_remove(void *sensor_dir)
{
debugfs_remove_recursive(sensor_dir);
}
static int sht3x_serial_number_read(struct sht3x_data *data)
{
int ret;
char buffer[SHT3X_RESPONSE_LENGTH];
struct i2c_client *client = data->client;
ret = sht3x_read_from_command(client, data,
sht3x_cmd_read_serial_number,
buffer,
SHT3X_RESPONSE_LENGTH, 0);
if (ret)
return ret;
data->serial_number = (buffer[0] << 24) | (buffer[1] << 16) |
(buffer[3] << 8) | buffer[4];
return ret;
}
static const struct hwmon_ops sht3x_ops = {
.is_visible = sht3x_is_visible,
.read = sht3x_read,
@ -899,6 +939,18 @@ static int sht3x_probe(struct i2c_client *client)
if (ret)
return ret;
ret = sht3x_serial_number_read(data);
if (ret) {
dev_dbg(dev, "unable to read serial number\n");
} else {
sht3x_debugfs_init(data);
ret = devm_add_action_or_reset(dev,
sht3x_debugfs_remove,
data->sensor_dir);
if (ret)
return ret;
}
hwmon_dev = devm_hwmon_device_register_with_info(dev,
client->name,
data,
@ -917,7 +969,19 @@ static struct i2c_driver sht3x_i2c_driver = {
.id_table = sht3x_ids,
};
module_i2c_driver(sht3x_i2c_driver);
static int __init sht3x_init(void)
{
debugfs = debugfs_create_dir("sht3x", NULL);
return i2c_add_driver(&sht3x_i2c_driver);
}
module_init(sht3x_init);
static void __exit sht3x_cleanup(void)
{
debugfs_remove_recursive(debugfs);
i2c_del_driver(&sht3x_i2c_driver);
}
module_exit(sht3x_cleanup);
MODULE_AUTHOR("David Frey <david.frey@sensirion.com>");
MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");

View File

@ -153,13 +153,9 @@ static inline s8 TEMP_TO_REG(long val)
}
/*
* FAN DIV: 1, 2, 4, or 8 (defaults to 2)
* REG: 0, 1, 2, or 3 (respectively) (defaults to 1)
* FAN DIV: 1, 2, 4, or 8
* REG: 0, 1, 2, or 3 (respectively)
*/
static inline u8 DIV_TO_REG(int val)
{
return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1;
}
#define DIV_FROM_REG(val) (1 << (val))
/*

View File

@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface Fan driver for Surface System Aggregator Module. It provides access
* to the fan's rpm through the hwmon system.
*
* Copyright (C) 2023 Ivor Wanders <ivor@iwanders.net>
*/
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/surface_aggregator/device.h>
#include <linux/types.h>
// SSAM
SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, {
.target_category = SSAM_SSH_TC_FAN,
.command_id = 0x01,
});
// hwmon
static umode_t surface_fan_hwmon_is_visible(const void *drvdata,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
return 0444;
}
static int surface_fan_hwmon_read(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct ssam_device *sdev = dev_get_drvdata(dev);
int ret;
__le16 value;
ret = __ssam_fan_rpm_get(sdev, &value);
if (ret)
return ret;
*val = le16_to_cpu(value);
return 0;
}
static const struct hwmon_channel_info *const surface_fan_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
NULL
};
static const struct hwmon_ops surface_fan_hwmon_ops = {
.is_visible = surface_fan_hwmon_is_visible,
.read = surface_fan_hwmon_read,
};
static const struct hwmon_chip_info surface_fan_chip_info = {
.ops = &surface_fan_hwmon_ops,
.info = surface_fan_info,
};
static int surface_fan_probe(struct ssam_device *sdev)
{
struct device *hdev;
hdev = devm_hwmon_device_register_with_info(&sdev->dev,
"surface_fan", sdev,
&surface_fan_chip_info,
NULL);
return PTR_ERR_OR_ZERO(hdev);
}
static const struct ssam_device_id ssam_fan_match[] = {
{ SSAM_SDEV(FAN, SAM, 0x01, 0x01) },
{},
};
MODULE_DEVICE_TABLE(ssam, ssam_fan_match);
static struct ssam_device_driver surface_fan = {
.probe = surface_fan_probe,
.match_table = ssam_fan_match,
.driver = {
.name = "surface_fan",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
module_ssam_device_driver(surface_fan);
MODULE_AUTHOR("Ivor Wanders <ivor@iwanders.net>");
MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module");
MODULE_LICENSE("GPL");

View File

@ -256,7 +256,7 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config tmp401_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.cache_type = REGCACHE_RBTREE,
.cache_type = REGCACHE_MAPLE,
.volatile_reg = tmp401_regmap_is_volatile,
.reg_read = tmp401_reg_read,
.reg_write = tmp401_reg_write,

View File

@ -290,7 +290,6 @@ static int w83773_probe(struct i2c_client *client)
}
static struct i2c_driver w83773_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "w83773g",
.of_match_table = of_match_ptr(w83773_of_match),

View File

@ -141,6 +141,7 @@ enum hwmon_in_attributes {
hwmon_in_rated_min,
hwmon_in_rated_max,
hwmon_in_beep,
hwmon_in_fault,
};
#define HWMON_I_ENABLE BIT(hwmon_in_enable)
@ -162,6 +163,7 @@ enum hwmon_in_attributes {
#define HWMON_I_RATED_MIN BIT(hwmon_in_rated_min)
#define HWMON_I_RATED_MAX BIT(hwmon_in_rated_max)
#define HWMON_I_BEEP BIT(hwmon_in_beep)
#define HWMON_I_FAULT BIT(hwmon_in_fault)
enum hwmon_curr_attributes {
hwmon_curr_enable,
@ -293,6 +295,8 @@ enum hwmon_humidity_attributes {
hwmon_humidity_fault,
hwmon_humidity_rated_min,
hwmon_humidity_rated_max,
hwmon_humidity_min_alarm,
hwmon_humidity_max_alarm,
};
#define HWMON_H_ENABLE BIT(hwmon_humidity_enable)
@ -306,6 +310,8 @@ enum hwmon_humidity_attributes {
#define HWMON_H_FAULT BIT(hwmon_humidity_fault)
#define HWMON_H_RATED_MIN BIT(hwmon_humidity_rated_min)
#define HWMON_H_RATED_MAX BIT(hwmon_humidity_rated_max)
#define HWMON_H_MIN_ALARM BIT(hwmon_humidity_min_alarm)
#define HWMON_H_MAX_ALARM BIT(hwmon_humidity_max_alarm)
enum hwmon_fan_attributes {
hwmon_fan_enable,
@ -425,12 +431,12 @@ struct hwmon_channel_info {
const u32 *config;
};
#define HWMON_CHANNEL_INFO(stype, ...) \
(&(struct hwmon_channel_info) { \
.type = hwmon_##stype, \
.config = (u32 []) { \
__VA_ARGS__, 0 \
} \
#define HWMON_CHANNEL_INFO(stype, ...) \
(&(const struct hwmon_channel_info) { \
.type = hwmon_##stype, \
.config = (const u32 []) { \
__VA_ARGS__, 0 \
} \
})
/**