Merge tag 'hwmon-for-v6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck: "New drivers: - KEBA fan controller - KEBA battery monitoring controller - MAX77705 Support added to existing drivers: - MAXIMUS VI HERO and ROG MAXIMUS Z90 Formula support (asus-ec-sensors) - SQ52206 support (ina238) - lt3074 support (pmbus/lt3074) - ADPM12160 support (pmbus/max34440) - MPM82504 and for MPM3695 family support (pmbus/mpq8785) - Add the Dell OptiPlex 7050 to the DMI whitelist (dell-smm) - Zen5 Ryzen Desktop support (k10temp) Various other minor fixes and improvements" * tag 'hwmon-for-v6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (48 commits) doc: hwmon: acpi_power_meter: Add information about enabling the power capping feature. hwmon: (isl28022) Fix current reading calculation hwmon: (lm75) Fix I3C transfer buffer pointer for incoming data hwmon: Add KEBA fan controller support hwmon: pmbus: mpq8785: Add support for MPM3695 family hwmon: pmbus: mpq8785: Add support for MPM82504 hwmon: pmbus: mpq8785: Implement VOUT feedback resistor divider ratio configuration hwmon: pmbus: mpq8785: Prepare driver for multiple device support dt-bindings: hwmon: Add bindings for mpq8785 driver hwmon: (ina238) Modify the calculation formula to adapt to different chips hwmon: (ina238) Add support for SQ52206 dt-bindings: Add SQ52206 to ina2xx devicetree bindings hwmon: (ina238) Add ina238_config to save configurations for different chips hwmon: (ausus-ec-sensors) add MAXIMUS VI HERO. hwmon: (isl28022, nct7363) Convert to use maple tree register cache hwmon: (asus-ec-sensors) check sensor index in read_string() hwmon: (asus-ec-sensors) add ROG MAXIMUS Z90 Formula. dt-bindings: hwmon: Add Sophgo SG2044 external hardware monitor support hwmon: (max77705) Add initial support hwmon: (tmp102) add vcc regulator support ...
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/pmbus/adi,lt3074.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices LT3074 voltage regulator
|
||||
|
||||
maintainers:
|
||||
- Cedric Encarnacion <cedricjustine.encarnacion@analog.com>
|
||||
|
||||
description: |
|
||||
The LT3074 is a low voltage, ultra-low noise and ultra-fast transient
|
||||
response linear regulator. It allows telemetry for input/output voltage,
|
||||
output current and temperature through the PMBus serial interface.
|
||||
|
||||
Datasheet:
|
||||
https://www.analog.com/en/products/lt3074.html
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/regulator/regulator.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,lt3074
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
regulator@6d {
|
||||
compatible = "adi,lt3074";
|
||||
reg = <0x6d>;
|
||||
regulator-name = "vout";
|
||||
regulator-max-microvolt = <1250000>;
|
||||
regulator-min-microvolt = <1150000>;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/hwmon/pmbus/mps,mpq8785.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Monolithic Power Systems Multiphase Voltage Regulators with PMBus
|
||||
|
||||
maintainers:
|
||||
- Charles Hsu <ythsu0511@gmail.com>
|
||||
|
||||
description:
|
||||
Monolithic Power Systems digital multiphase voltage regulators with PMBus.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- mps,mpm3695
|
||||
- mps,mpm3695-25
|
||||
- mps,mpm82504
|
||||
- mps,mpq8785
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
mps,vout-fb-divider-ratio-permille:
|
||||
description:
|
||||
The feedback resistor divider ratio, expressed in permille
|
||||
(Vfb / Vout * 1000). This value is written to the PMBUS_VOUT_SCALE_LOOP
|
||||
register and is required for correct output voltage presentation.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 1
|
||||
maximum: 4095
|
||||
default: 706
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- mps,mpm3695
|
||||
- mps,mpm82504
|
||||
then:
|
||||
properties:
|
||||
mps,vout-fb-divider-ratio-permille:
|
||||
maximum: 1023
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
const: mps,mpq8785
|
||||
then:
|
||||
properties:
|
||||
mps,vout-fb-divider-ratio-permille:
|
||||
maximum: 2047
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pmic@30 {
|
||||
compatible = "mps,mpm82504";
|
||||
reg = <0x30>;
|
||||
mps,vout-fb-divider-ratio-permille = <600>;
|
||||
};
|
||||
};
|
||||
@@ -11,7 +11,11 @@ maintainers:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: sophgo,sg2042-hwmon-mcu
|
||||
oneOf:
|
||||
- items:
|
||||
- const: sophgo,sg2044-hwmon-mcu
|
||||
- const: sophgo,sg2042-hwmon-mcu
|
||||
- const: sophgo,sg2042-hwmon-mcu
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
@@ -28,6 +28,17 @@ properties:
|
||||
i2c-mux:
|
||||
type: object
|
||||
|
||||
fan:
|
||||
$ref: fan-common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
"#pwm-cells":
|
||||
const: 2
|
||||
description: |
|
||||
Number of cells in a PWM specifier.
|
||||
- cell 0: PWM period in nanoseconds
|
||||
- cell 1: PWM polarity: 0 or PWM_POLARITY_INVERTED
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@@ -50,9 +61,14 @@ examples:
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fan@18 {
|
||||
fan_controller: fan@18 {
|
||||
compatible = "ti,amc6821";
|
||||
reg = <0x18>;
|
||||
#pwm-cells = <2>;
|
||||
|
||||
fan {
|
||||
pwms = <&fan_controller 40000 0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ description: |
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- silergy,sq52206
|
||||
- silergy,sy24655
|
||||
- ti,ina209
|
||||
- ti,ina219
|
||||
@@ -58,6 +59,9 @@ properties:
|
||||
shunt voltage, and a value of 4 maps to ADCRANGE=0 such that a wider
|
||||
voltage range is used.
|
||||
|
||||
For SQ52206,the shunt-gain value 1 mapps to ADCRANGE=10/11, the value 2
|
||||
mapps to ADCRANGE=01, and the value 4 mapps to ADCRANGE=00.
|
||||
|
||||
The default value is device dependent, and is defined by the reset value
|
||||
of PGA/ADCRANGE in the respective configuration registers.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
@@ -97,6 +101,7 @@ allOf:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- silergy,sq52206
|
||||
- silergy,sy24655
|
||||
- ti,ina209
|
||||
- ti,ina219
|
||||
|
||||
@@ -23,6 +23,9 @@ properties:
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
vcc-supply:
|
||||
description: Power supply for tmp102
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@@ -42,6 +45,7 @@ examples:
|
||||
reg = <0x48>;
|
||||
interrupt-parent = <&gpio7>;
|
||||
interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
|
||||
vcc-supply = <&supply>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -295,8 +295,6 @@ properties:
|
||||
- mps,mp5990
|
||||
# Monolithic Power Systems Inc. digital step-down converter mp9941
|
||||
- mps,mp9941
|
||||
# Monolithic Power Systems Inc. synchronous step-down converter mpq8785
|
||||
- mps,mpq8785
|
||||
# Temperature sensor with integrated fan control
|
||||
- national,lm63
|
||||
# Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
|
||||
|
||||
@@ -37,9 +37,16 @@ arbitrary strings that ACPI provides with the meter. The measures/ directory
|
||||
contains symlinks to the devices that this meter measures.
|
||||
|
||||
Some computers have the ability to enforce a power cap in hardware. If this is
|
||||
the case, the `power[1-*]_cap` and related sysfs files will appear. When the
|
||||
average power consumption exceeds the cap, an ACPI event will be broadcast on
|
||||
the netlink event socket and a poll notification will be sent to the
|
||||
the case, the `power[1-*]_cap` and related sysfs files will appear.
|
||||
For information on enabling the power cap feature, refer to the description
|
||||
of the "force_on_cap" option in the "Module Parameters" chapter.
|
||||
To use the power cap feature properly, you need to set appropriate value
|
||||
(in microWatts) to the `power[1-*]_cap` sysfs files.
|
||||
The value must be within the range between the minimum value at `power[1-]_cap_min`
|
||||
and the maximum value at `power[1-]_cap_max (both in microWatts)`.
|
||||
|
||||
When the average power consumption exceeds the cap, an ACPI event will be
|
||||
broadcast on the netlink event socket and a poll notification will be sent to the
|
||||
appropriate `power[1-*]_alarm` file to indicate that capping has begun, and the
|
||||
hardware has taken action to reduce power consumption. Most likely this will
|
||||
result in reduced performance.
|
||||
@@ -52,3 +59,19 @@ follows:
|
||||
`power[1-*]_cap` will be notified if the firmware changes the power cap.
|
||||
`power[1-*]_interval` will be notified if the firmware changes the averaging
|
||||
interval.
|
||||
|
||||
Module Parameters
|
||||
-----------------
|
||||
|
||||
* force_cap_on: bool
|
||||
Forcefully enable the power capping feature to specify
|
||||
the upper limit of the system's power consumption.
|
||||
|
||||
By default, the driver's power capping feature is only
|
||||
enabled on IBM products.
|
||||
Therefore, on other systems that support power capping,
|
||||
you will need to use the option to enable it.
|
||||
|
||||
Note: power capping is potentially unsafe feature.
|
||||
Please check the platform specifications to make sure
|
||||
that capping is supported before using this option.
|
||||
|
||||
@@ -4,6 +4,7 @@ Kernel driver asus_ec_sensors
|
||||
=================================
|
||||
|
||||
Supported boards:
|
||||
* MAXIMUS VI HERO
|
||||
* PRIME X470-PRO
|
||||
* PRIME X570-PRO
|
||||
* PRIME X670E-PRO WIFI
|
||||
@@ -20,6 +21,7 @@ Supported boards:
|
||||
* ROG CROSSHAIR X670E GENE
|
||||
* ROG MAXIMUS XI HERO
|
||||
* ROG MAXIMUS XI HERO (WI-FI)
|
||||
* ROG MAXIMUS Z690 FORMULA
|
||||
* ROG STRIX B550-E GAMING
|
||||
* ROG STRIX B550-I GAMING
|
||||
* ROG STRIX X570-E GAMING
|
||||
|
||||
@@ -14,6 +14,12 @@ Supported chips:
|
||||
Datasheet:
|
||||
https://www.ti.com/lit/gpn/ina238
|
||||
|
||||
* Silergy SQ52206
|
||||
|
||||
Prefix: 'SQ52206'
|
||||
|
||||
Addresses: I2C 0x40 - 0x4f
|
||||
|
||||
Author: Nathan Rossi <nathan.rossi@digi.com>
|
||||
|
||||
Description
|
||||
@@ -54,3 +60,12 @@ temp1_input Die temperature measurement (mC)
|
||||
temp1_max Maximum die temperature threshold (mC)
|
||||
temp1_max_alarm Maximum die temperature alarm
|
||||
======================= =======================================================
|
||||
|
||||
Additional sysfs entries for sq52206
|
||||
------------------------------------
|
||||
|
||||
======================= =======================================================
|
||||
energy1_input Energy measurement (mJ)
|
||||
|
||||
power1_input_highest Peak Power (uW)
|
||||
======================= =======================================================
|
||||
|
||||
@@ -106,6 +106,8 @@ Hardware Monitoring Kernel Drivers
|
||||
jc42
|
||||
k10temp
|
||||
k8temp
|
||||
kbatt
|
||||
kfan
|
||||
lan966x
|
||||
lineage-pem
|
||||
lm25066
|
||||
@@ -125,6 +127,7 @@ Hardware Monitoring Kernel Drivers
|
||||
lm95234
|
||||
lm95245
|
||||
lochnagar
|
||||
lt3074
|
||||
lt7182s
|
||||
ltc2992
|
||||
ltc2945
|
||||
@@ -161,6 +164,7 @@ Hardware Monitoring Kernel Drivers
|
||||
max6639
|
||||
max6650
|
||||
max6697
|
||||
max77705
|
||||
max8688
|
||||
mc13783-adc
|
||||
mc34vr500
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver kbatt
|
||||
===================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* KEBA battery monitoring controller (IP core in FPGA)
|
||||
|
||||
Prefix: 'kbatt'
|
||||
|
||||
Authors:
|
||||
|
||||
Gerhard Engleder <eg@keba.com>
|
||||
Petar Bojanic <boja@keba.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The KEBA battery monitoring controller is an IP core for FPGAs, which
|
||||
monitors the health of a coin cell battery. The coin cell battery is
|
||||
typically used to supply the RTC during power off to keep the current
|
||||
time. E.g., the CP500 FPGA includes this IP core to monitor the coin cell
|
||||
battery of PLCs and the corresponding cp500 driver creates an auxiliary
|
||||
device for the kbatt driver.
|
||||
|
||||
This driver provides information about the coin cell battery health to
|
||||
user space. Actually the user space shall be informed that the coin cell
|
||||
battery is nearly empty and needs to be replaced.
|
||||
|
||||
The coin cell battery must be tested actively to get to know if its nearly
|
||||
empty or not. Therefore, a load is put on the coin cell battery and the
|
||||
resulting voltage is evaluated. This evaluation is done by some hard wired
|
||||
analog logic, which compares the voltage to a defined limit. If the
|
||||
voltage is above the limit, then the coin cell battery is assumed to be
|
||||
ok. If the voltage is below the limit, then the coin cell battery is
|
||||
nearly empty (or broken, removed, ...) and shall be replaced by a new one.
|
||||
The KEBA battery monitoring controller allows to start the test of the
|
||||
coin cell battery and to get the result if the voltage is above or below
|
||||
the limit. The actual voltage is not available. Only the information if
|
||||
the voltage is below a limit is available.
|
||||
|
||||
The test load, which is put on the coin cell battery for the health check,
|
||||
is similar to the load during power off. Therefore, the lifetime of the
|
||||
coin cell battery is reduced directly by the duration of each test. To
|
||||
limit the negative impact to the lifetime the test is limited to at most
|
||||
once every 10 seconds. The test load is put on the coin cell battery for
|
||||
100ms. Thus, in worst case the coin cell battery lifetime is reduced by
|
||||
1% of the uptime or 3.65 days per year. As the coin cell battery lasts
|
||||
multiple years, this lifetime reduction negligible.
|
||||
|
||||
This driver only provides a single alarm attribute, which is raised when
|
||||
the coin cell battery is nearly empty.
|
||||
|
||||
====================== ==== ===================================================
|
||||
Attribute R/W Contents
|
||||
====================== ==== ===================================================
|
||||
in0_min_alarm R voltage of coin cell battery under load is below
|
||||
limit
|
||||
====================== ==== ===================================================
|
||||
@@ -0,0 +1,39 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver kfan
|
||||
==================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* KEBA fan controller (IP core in FPGA)
|
||||
|
||||
Prefix: 'kfan'
|
||||
|
||||
Authors:
|
||||
|
||||
Gerhard Engleder <eg@keba.com>
|
||||
Petar Bojanic <boja@keba.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The KEBA fan controller is an IP core for FPGAs, which monitors the health
|
||||
and controls the speed of a fan. The fan is typically used to cool the CPU
|
||||
and the whole device. E.g., the CP500 FPGA includes this IP core to monitor
|
||||
and control the fan of PLCs and the corresponding cp500 driver creates an
|
||||
auxiliary device for the kfan driver.
|
||||
|
||||
This driver provides information about the fan health to user space.
|
||||
The user space shall be informed if the fan is removed or blocked.
|
||||
Additionally, the speed in RPM is reported for fans with tacho signal.
|
||||
|
||||
For fan control PWM is supported. For PWM 255 equals 100%. None-regulable
|
||||
fans can be turned on with PWM 255 and turned off with PWM 0.
|
||||
|
||||
====================== ==== ===================================================
|
||||
Attribute R/W Contents
|
||||
====================== ==== ===================================================
|
||||
fan1_fault R Fan fault
|
||||
fan1_input R Fan tachometer input (in RPM)
|
||||
pwm1 RW Fan target duty cycle (0..255)
|
||||
====================== ==== ===================================================
|
||||
@@ -0,0 +1,72 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver lt3074
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Analog Devices LT3074
|
||||
|
||||
Prefix: 'lt3074'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://www.analog.com/en/products/lt3074.html
|
||||
|
||||
Authors: Cedric Encarnacion <cedricjustine.encarnacion@analog.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Analog Devices LT3074 Linear
|
||||
Regulator with PMBus interface.
|
||||
|
||||
The LT3074 is a low voltage, ultra-low noise and ultra-fast transient
|
||||
response linear regulator with PMBus serial interface. PMBus telemetry
|
||||
feature provides information regarding the output voltage and current,
|
||||
input voltage, bias voltage and die temperature.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate
|
||||
the devices explicitly. Please see Documentation/i2c/instantiating-devices.rst
|
||||
for details.
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
======================= =======================================================
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage
|
||||
in1_max Input overvoltage warning limit
|
||||
in1_max_alarm Input overvoltage warning status
|
||||
in1_min Input undervoltage warning limit
|
||||
in1_min_alarm Input undervoltage warning status
|
||||
in2_label "vmon"
|
||||
in2_input Measured bias voltage
|
||||
in2_max Bias overvoltage warning limit
|
||||
in2_min Bias undervoltage warning limit
|
||||
in3_label "vout1"
|
||||
in3_input Measured output voltage
|
||||
in3_max Output overvoltage warning limit
|
||||
in3_max_alarm Output overvoltage warning status
|
||||
in3_min Output undervoltage warning limit
|
||||
in3_min_alarm Output undervoltage warning status
|
||||
curr1_label "iout1"
|
||||
curr1_input Measured output current.
|
||||
curr1_crit Output overcurrent fault limit
|
||||
curr1_crit_alarm Output overcurrent fault status
|
||||
temp1_input Measured temperature
|
||||
temp1_max Maximum temperature limit
|
||||
temp1_max_alarm Overtemperature warning status
|
||||
======================= =======================================================
|
||||
@@ -3,6 +3,14 @@ Kernel driver max34440
|
||||
|
||||
Supported chips:
|
||||
|
||||
* ADI ADPM12160
|
||||
|
||||
Prefixes: 'adpm12160'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: -
|
||||
|
||||
* Maxim MAX34440
|
||||
|
||||
Prefixes: 'max34440'
|
||||
@@ -67,13 +75,14 @@ Author: Guenter Roeck <linux@roeck-us.net>
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Maxim MAX34440 PMBus 6-Channel
|
||||
Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager
|
||||
and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data Logger.
|
||||
It also supports the MAX34451, MAX34460, and MAX34461 PMBus Voltage Monitor &
|
||||
Sequencers. The MAX34451 supports monitoring voltage or current of 12 channels
|
||||
based on GIN pins. The MAX34460 supports 12 voltage channels, and the MAX34461
|
||||
supports 16 voltage channels.
|
||||
This driver supports multiple devices: hardware monitoring for Maxim MAX34440
|
||||
PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply
|
||||
Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data
|
||||
Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34460, and
|
||||
MAX34461; PMBus DC/DC Power Module ADPM12160. The MAX34451 supports monitoring
|
||||
voltage or current of 12 channels based on GIN pins. The MAX34460 supports 12
|
||||
voltage channels, and the MAX34461 supports 16 voltage channels. The ADPM1260
|
||||
also monitors both input and output of voltage and current.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
@@ -128,7 +137,10 @@ in[1-6]_highest Historical maximum voltage.
|
||||
in[1-6]_reset_history Write any value to reset history.
|
||||
======================= =======================================================
|
||||
|
||||
.. note:: MAX34446 only supports in[1-4].
|
||||
.. note::
|
||||
|
||||
- MAX34446 only supports in[1-4].
|
||||
- ADPM12160 only supports in[1-2]. Label is "vin1" and "vout1" respectively.
|
||||
|
||||
Curr
|
||||
~~~~
|
||||
@@ -150,6 +162,7 @@ curr[1-6]_reset_history Write any value to reset history.
|
||||
|
||||
- in6 and curr6 attributes only exist for MAX34440.
|
||||
- MAX34446 only supports curr[1-4].
|
||||
- For ADPM12160, curr[1] is "iin1" and curr[2-6] are "iout[1-5].
|
||||
|
||||
Power
|
||||
~~~~~
|
||||
@@ -185,6 +198,7 @@ temp[1-8]_reset_history Write any value to reset history.
|
||||
.. note::
|
||||
- temp7 and temp8 attributes only exist for MAX34440.
|
||||
- MAX34446 only supports temp[1-3].
|
||||
- ADPM12160 only supports temp[1].
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver max77705
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Maxim Integrated MAX77705
|
||||
|
||||
Prefix: 'max77705'
|
||||
|
||||
Addresses scanned: none
|
||||
|
||||
Datasheet: Not available
|
||||
|
||||
Authors:
|
||||
- Dzmitry Sankouski <dsankouski@gmail.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MAX77705 PMIC provides current and voltage measurements besides fuelgauge:
|
||||
- chip input current
|
||||
- system bus current and voltage
|
||||
- VBYP voltage
|
||||
|
||||
Sysfs Attributes
|
||||
----------------
|
||||
|
||||
================= ========================================
|
||||
in1_label "vbyp"
|
||||
in1_input Measured chip vbyp voltage
|
||||
in2_label "vsys"
|
||||
in2_input Measured chip system bus voltage
|
||||
curr1_label "iin"
|
||||
curr1_input Measured chip input current.
|
||||
curr2_label "isys"
|
||||
curr2_input Measured chip system bus current.
|
||||
================= ========================================
|
||||
@@ -5,6 +5,8 @@ Kernel driver mpq8785
|
||||
|
||||
Supported chips:
|
||||
|
||||
* MPS MPM3695 family
|
||||
* MPS MPM82504
|
||||
* MPS MPQ8785
|
||||
|
||||
Prefix: 'mpq8785'
|
||||
@@ -14,6 +16,22 @@ Author: Charles Hsu <ythsu0511@gmail.com>
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MPM3695 family is a scalable, ultra-thin, fully integrated power module with
|
||||
a PMBus interface. It offers a complete power solution that achieves up to
|
||||
10A (-10 variant), 20A (-25 variant), 25A (-20 variant), 100A (-100 variant)
|
||||
of output current with excellent load and line regulation across a wide input
|
||||
voltage range. It operates at high efficiency over a wide load range, and can
|
||||
be parallled to deliver higher current. Variants -10,-20 and -100 have different
|
||||
voltage scale configuration register range (10 bits) than -25 version (11 bits).
|
||||
|
||||
The MPM82504 is a quad 25A, scalable, fully integrated power module with a PMBus
|
||||
interface. The device offers a complete power solution that achieves up to 25A
|
||||
per output channel. The MPM82504 has four output channels that can be paralleled
|
||||
to provide 50A, 75A, or 100A of output current for flexible configurations.
|
||||
The device can also operate in parallel with the MPM3695-100 and additional
|
||||
MPM82504 devices to provide a higher output current. The MPM82504 operates
|
||||
at high efficiency across a wide load range.
|
||||
|
||||
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
|
||||
@@ -23,19 +41,16 @@ 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)
|
||||
The devices 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
|
||||
The MCOT scheme also allows multiple devices or channels 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.
|
||||
|
||||
+16
@@ -14156,6 +14156,15 @@ L: linux-scsi@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/scsi/sym53c8xx_2/
|
||||
|
||||
LT3074 HARDWARE MONITOR DRIVER
|
||||
M: Cedric Encarnacion <cedricjustine.encarnacion@analog.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Supported
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/hwmon/pmbus/adi,lt3074.yaml
|
||||
F: Documentation/hwmon/lt3074.rst
|
||||
F: drivers/hwmon/pmbus/lt3074.c
|
||||
|
||||
LTC1660 DAC DRIVER
|
||||
M: Marcus Folkesson <marcus.folkesson@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
@@ -18777,6 +18786,13 @@ S: Maintained
|
||||
F: Documentation/hwmon/pc87427.rst
|
||||
F: drivers/hwmon/pc87427.c
|
||||
|
||||
MAX77705 HARDWARE MONITORING DRIVER
|
||||
M: Dzmitry Sankouski <dsankouski@gmail.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/max77705.rst
|
||||
F: drivers/hwmon/max77705-hwmon.c
|
||||
|
||||
PCA9532 LED DRIVER
|
||||
M: Riku Voipio <riku.voipio@iki.fi>
|
||||
S: Maintained
|
||||
|
||||
@@ -335,6 +335,26 @@ config SENSORS_K10TEMP
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called k10temp.
|
||||
|
||||
config SENSORS_KBATT
|
||||
tristate "KEBA battery controller support"
|
||||
depends on KEBA_CP500
|
||||
help
|
||||
This driver supports the battery monitoring controller found in
|
||||
KEBA system FPGA devices.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called kbatt.
|
||||
|
||||
config SENSORS_KFAN
|
||||
tristate "KEBA fan controller support"
|
||||
depends on KEBA_CP500
|
||||
help
|
||||
This driver supports the fan controller found in KEBA system
|
||||
FPGA devices.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called kfan.
|
||||
|
||||
config SENSORS_FAM15H_POWER
|
||||
tristate "AMD Family 15h processor power"
|
||||
depends on X86 && PCI && CPU_SUP_AMD
|
||||
@@ -1308,6 +1328,15 @@ config SENSORS_MAX31790
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max31790.
|
||||
|
||||
config SENSORS_MAX77705
|
||||
tristate "MAX77705 current and voltage sensor"
|
||||
depends on MFD_MAX77705
|
||||
help
|
||||
If you say yes here you get support for MAX77705 sensors connected with I2C.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max77705-hwmon.
|
||||
|
||||
config SENSORS_MC34VR500
|
||||
tristate "NXP MC34VR500 hardware monitoring driver"
|
||||
depends on I2C
|
||||
|
||||
@@ -110,6 +110,8 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o
|
||||
obj-$(CONFIG_SENSORS_JC42) += jc42.o
|
||||
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
|
||||
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
|
||||
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
|
||||
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
|
||||
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
|
||||
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
|
||||
@@ -161,6 +163,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
||||
obj-$(CONFIG_SENSORS_MAX6697) += max6697.o
|
||||
obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
|
||||
obj-$(CONFIG_MAX31827) += max31827.o
|
||||
obj-$(CONFIG_SENSORS_MAX77705) += max77705-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
||||
obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
|
||||
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
|
||||
|
||||
@@ -94,7 +94,7 @@ struct aht10_data {
|
||||
unsigned int meas_size;
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_init() - Initialize an AHT10/AHT20 chip
|
||||
* @data: the data associated with this AHT10/AHT20 chip
|
||||
* Return: 0 if successful, 1 if not
|
||||
@@ -124,7 +124,7 @@ static int aht10_init(struct aht10_data *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_polltime_expired() - check if the minimum poll interval has
|
||||
* expired
|
||||
* @data: the data containing the time to compare
|
||||
@@ -140,7 +140,7 @@ static int aht10_polltime_expired(struct aht10_data *data)
|
||||
|
||||
DECLARE_CRC8_TABLE(crc8_table);
|
||||
|
||||
/**
|
||||
/*
|
||||
* crc8_check() - check crc of the sensor's measurements
|
||||
* @raw_data: data frame received from sensor(including crc as the last byte)
|
||||
* @count: size of the data frame
|
||||
@@ -155,7 +155,7 @@ static int crc8_check(u8 *raw_data, int count)
|
||||
return crc8(crc8_table, raw_data, count, CRC8_INIT_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_read_values() - read and parse the raw data from the AHT10/AHT20
|
||||
* @data: the struct aht10_data to use for the lock
|
||||
* Return: 0 if successful, 1 if not
|
||||
@@ -214,7 +214,7 @@ static int aht10_read_values(struct aht10_data *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_interval_write() - store the given minimum poll interval.
|
||||
* Return: 0 on success, -EINVAL if a value lower than the
|
||||
* AHT10_MIN_POLL_INTERVAL is given
|
||||
@@ -226,7 +226,7 @@ static ssize_t aht10_interval_write(struct aht10_data *data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_interval_read() - read the minimum poll interval
|
||||
* in milliseconds
|
||||
*/
|
||||
@@ -237,7 +237,7 @@ static ssize_t aht10_interval_read(struct aht10_data *data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_temperature1_read() - read the temperature in millidegrees
|
||||
*/
|
||||
static int aht10_temperature1_read(struct aht10_data *data, long *val)
|
||||
@@ -252,7 +252,7 @@ static int aht10_temperature1_read(struct aht10_data *data, long *val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* aht10_humidity1_read() - read the relative humidity in millipercent
|
||||
*/
|
||||
static int aht10_humidity1_read(struct aht10_data *data, long *val)
|
||||
|
||||
+45
-5
@@ -23,9 +23,12 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <dt-bindings/pwm/pwm.h>
|
||||
|
||||
/*
|
||||
* Addresses to scan.
|
||||
*/
|
||||
@@ -37,7 +40,7 @@ static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e,
|
||||
* Insmod parameters
|
||||
*/
|
||||
|
||||
static int pwminv; /*Inverted PWM output. */
|
||||
static int pwminv = -1; /*Inverted PWM output. */
|
||||
module_param(pwminv, int, 0444);
|
||||
|
||||
static int init = 1; /*Power-on initialization.*/
|
||||
@@ -845,9 +848,43 @@ static int amc6821_detect(struct i2c_client *client, struct i2c_board_info *info
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amc6821_init_client(struct amc6821_data *data)
|
||||
static enum pwm_polarity amc6821_pwm_polarity(struct i2c_client *client)
|
||||
{
|
||||
enum pwm_polarity polarity = PWM_POLARITY_NORMAL;
|
||||
struct of_phandle_args args;
|
||||
struct device_node *fan_np;
|
||||
|
||||
/*
|
||||
* For backward compatibility, the pwminv module parameter takes
|
||||
* always the precedence over any other device description
|
||||
*/
|
||||
if (pwminv == 0)
|
||||
return PWM_POLARITY_NORMAL;
|
||||
if (pwminv > 0)
|
||||
return PWM_POLARITY_INVERSED;
|
||||
|
||||
fan_np = of_get_child_by_name(client->dev.of_node, "fan");
|
||||
if (!fan_np)
|
||||
return PWM_POLARITY_NORMAL;
|
||||
|
||||
if (of_parse_phandle_with_args(fan_np, "pwms", "#pwm-cells", 0, &args))
|
||||
goto out;
|
||||
of_node_put(args.np);
|
||||
|
||||
if (args.args_count != 2)
|
||||
goto out;
|
||||
|
||||
if (args.args[1] & PWM_POLARITY_INVERTED)
|
||||
polarity = PWM_POLARITY_INVERSED;
|
||||
out:
|
||||
of_node_put(fan_np);
|
||||
return polarity;
|
||||
}
|
||||
|
||||
static int amc6821_init_client(struct i2c_client *client, struct amc6821_data *data)
|
||||
{
|
||||
struct regmap *regmap = data->regmap;
|
||||
u32 regval;
|
||||
int err;
|
||||
|
||||
if (init) {
|
||||
@@ -864,11 +901,14 @@ static int amc6821_init_client(struct amc6821_data *data)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
regval = AMC6821_CONF1_START;
|
||||
if (amc6821_pwm_polarity(client) == PWM_POLARITY_INVERSED)
|
||||
regval |= AMC6821_CONF1_PWMINV;
|
||||
|
||||
err = regmap_update_bits(regmap, AMC6821_REG_CONF1,
|
||||
AMC6821_CONF1_THERMOVIE | AMC6821_CONF1_FANIE |
|
||||
AMC6821_CONF1_START | AMC6821_CONF1_PWMINV,
|
||||
AMC6821_CONF1_START |
|
||||
(pwminv ? AMC6821_CONF1_PWMINV : 0));
|
||||
regval);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
@@ -916,7 +956,7 @@ static int amc6821_probe(struct i2c_client *client)
|
||||
"Failed to initialize regmap\n");
|
||||
data->regmap = regmap;
|
||||
|
||||
err = amc6821_init_client(data);
|
||||
err = amc6821_init_client(client, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
||||
@@ -169,7 +169,11 @@ enum board_family {
|
||||
family_intel_600_series
|
||||
};
|
||||
|
||||
/* All the known sensors for ASUS EC controllers */
|
||||
/*
|
||||
* All the known sensors for ASUS EC controllers. These arrays have to be sorted
|
||||
* by the full ((bank << 8) + index) register index (see asus_ec_block_read() as
|
||||
* to why).
|
||||
*/
|
||||
static const struct ec_sensor_info sensors_family_amd_400[] = {
|
||||
[ec_sensor_temp_chipset] =
|
||||
EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a),
|
||||
@@ -183,10 +187,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = {
|
||||
EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
|
||||
[ec_sensor_in_cpu_core] =
|
||||
EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
|
||||
[ec_sensor_fan_vrm_hs] =
|
||||
EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc),
|
||||
[ec_sensor_fan_chipset] =
|
||||
/* no chipset fans in this generation */
|
||||
EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00),
|
||||
@@ -194,10 +198,10 @@ static const struct ec_sensor_info sensors_family_amd_400[] = {
|
||||
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4),
|
||||
[ec_sensor_curr_cpu] =
|
||||
EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4),
|
||||
[ec_sensor_temp_water_in] =
|
||||
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d),
|
||||
[ec_sensor_temp_water_out] =
|
||||
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b),
|
||||
[ec_sensor_temp_water_in] =
|
||||
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_amd_500[] = {
|
||||
@@ -239,19 +243,20 @@ static const struct ec_sensor_info sensors_family_amd_500[] = {
|
||||
|
||||
static const struct ec_sensor_info sensors_family_amd_600[] = {
|
||||
[ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x30),
|
||||
[ec_sensor_temp_cpu_package] = EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
|
||||
[ec_sensor_temp_cpu_package] =
|
||||
EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31),
|
||||
[ec_sensor_temp_mb] =
|
||||
EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x32),
|
||||
[ec_sensor_temp_vrm] =
|
||||
EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x36),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
[ec_sensor_temp_water_in] =
|
||||
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
|
||||
[ec_sensor_temp_water_out] =
|
||||
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
|
||||
[ec_sensor_fan_cpu_opt] =
|
||||
EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
|
||||
};
|
||||
|
||||
static const struct ec_sensor_info sensors_family_intel_300[] = {
|
||||
@@ -278,6 +283,14 @@ static const struct ec_sensor_info sensors_family_intel_600[] = {
|
||||
[ec_sensor_temp_t_sensor] =
|
||||
EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d),
|
||||
[ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e),
|
||||
[ec_sensor_fan_water_flow] =
|
||||
EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe),
|
||||
[ec_sensor_temp_water_in] =
|
||||
EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
|
||||
[ec_sensor_temp_water_out] =
|
||||
EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
|
||||
[ec_sensor_temp_water_block_in] =
|
||||
EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02),
|
||||
};
|
||||
|
||||
/* Shortcuts for common combinations */
|
||||
@@ -300,6 +313,15 @@ struct ec_board_info {
|
||||
enum board_family family;
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_vi_hero = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR |
|
||||
SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER |
|
||||
SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH,
|
||||
.family = family_intel_300_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_prime_x470_pro = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
@@ -402,6 +424,13 @@ static const struct ec_board_info board_info_maximus_xi_hero = {
|
||||
.family = family_intel_300_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_maximus_z690_formula = {
|
||||
.sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
|
||||
.mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
|
||||
.family = family_intel_600_series,
|
||||
};
|
||||
|
||||
static const struct ec_board_info board_info_crosshair_viii_impact = {
|
||||
.sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
|
||||
SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
|
||||
@@ -507,6 +536,8 @@ static const struct ec_board_info board_info_tuf_gaming_x670e_plus = {
|
||||
}
|
||||
|
||||
static const struct dmi_system_id dmi_table[] = {
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("MAXIMUS VI HERO",
|
||||
&board_info_maximus_vi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO",
|
||||
&board_info_prime_x470_pro),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO",
|
||||
@@ -537,6 +568,8 @@ static const struct dmi_system_id dmi_table[] = {
|
||||
&board_info_maximus_xi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)",
|
||||
&board_info_maximus_xi_hero),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA",
|
||||
&board_info_maximus_z690_formula),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT",
|
||||
&board_info_crosshair_viii_impact),
|
||||
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
|
||||
@@ -933,6 +966,10 @@ static int asus_ec_hwmon_read_string(struct device *dev,
|
||||
{
|
||||
struct ec_sensors_data *state = dev_get_drvdata(dev);
|
||||
int sensor_index = find_ec_sensor_index(state, type, channel);
|
||||
|
||||
if (sensor_index < 0)
|
||||
return sensor_index;
|
||||
|
||||
*str = get_sensor_info(state, sensor_index)->label;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1273,6 +1273,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7060"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell OptiPlex 7050",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell Precision",
|
||||
.matches = {
|
||||
|
||||
+87
-16
@@ -20,6 +20,9 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
struct gpio_fan_speed {
|
||||
@@ -42,6 +45,7 @@ struct gpio_fan_data {
|
||||
bool pwm_enable;
|
||||
struct gpio_desc *alarm_gpio;
|
||||
struct work_struct alarm_work;
|
||||
struct regulator *supply;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -125,13 +129,32 @@ static int __get_fan_ctrl(struct gpio_fan_data *fan_data)
|
||||
}
|
||||
|
||||
/* Must be called with fan_data->lock held, except during initialization. */
|
||||
static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
|
||||
static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
|
||||
{
|
||||
if (fan_data->speed_index == speed_index)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
if (fan_data->speed_index == 0 && speed_index > 0) {
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(fan_data->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
__set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val);
|
||||
|
||||
if (fan_data->speed_index > 0 && speed_index == 0) {
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_put_sync(fan_data->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
fan_data->speed_index = speed_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_fan_speed_index(struct gpio_fan_data *fan_data)
|
||||
@@ -176,7 +199,7 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr,
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
unsigned long pwm;
|
||||
int speed_index;
|
||||
int ret = count;
|
||||
int ret;
|
||||
|
||||
if (kstrtoul(buf, 10, &pwm) || pwm > 255)
|
||||
return -EINVAL;
|
||||
@@ -189,12 +212,12 @@ static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr,
|
||||
}
|
||||
|
||||
speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255);
|
||||
set_fan_speed(fan_data, speed_index);
|
||||
ret = set_fan_speed(fan_data, speed_index);
|
||||
|
||||
exit_unlock:
|
||||
mutex_unlock(&fan_data->lock);
|
||||
|
||||
return ret;
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t pwm1_enable_show(struct device *dev,
|
||||
@@ -211,6 +234,7 @@ static ssize_t pwm1_enable_store(struct device *dev,
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
unsigned long val;
|
||||
int ret = 0;
|
||||
|
||||
if (kstrtoul(buf, 10, &val) || val > 1)
|
||||
return -EINVAL;
|
||||
@@ -224,11 +248,11 @@ static ssize_t pwm1_enable_store(struct device *dev,
|
||||
|
||||
/* Disable manual control mode: set fan at full speed. */
|
||||
if (val == 0)
|
||||
set_fan_speed(fan_data, fan_data->num_speed - 1);
|
||||
ret = set_fan_speed(fan_data, fan_data->num_speed - 1);
|
||||
|
||||
mutex_unlock(&fan_data->lock);
|
||||
|
||||
return count;
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t pwm1_mode_show(struct device *dev,
|
||||
@@ -279,7 +303,7 @@ static ssize_t set_rpm(struct device *dev, struct device_attribute *attr,
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm));
|
||||
ret = set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm));
|
||||
|
||||
exit_unlock:
|
||||
mutex_unlock(&fan_data->lock);
|
||||
@@ -386,6 +410,7 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = cdev->devdata;
|
||||
int ret;
|
||||
|
||||
if (!fan_data)
|
||||
return -EINVAL;
|
||||
@@ -395,11 +420,11 @@ static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
|
||||
mutex_lock(&fan_data->lock);
|
||||
|
||||
set_fan_speed(fan_data, state);
|
||||
ret = set_fan_speed(fan_data, state);
|
||||
|
||||
mutex_unlock(&fan_data->lock);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_cooling_device_ops gpio_fan_cool_ops = {
|
||||
@@ -499,6 +524,8 @@ static void gpio_fan_stop(void *data)
|
||||
mutex_lock(&fan_data->lock);
|
||||
set_fan_speed(data, 0);
|
||||
mutex_unlock(&fan_data->lock);
|
||||
|
||||
pm_runtime_disable(fan_data->dev);
|
||||
}
|
||||
|
||||
static int gpio_fan_probe(struct platform_device *pdev)
|
||||
@@ -521,6 +548,11 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||
platform_set_drvdata(pdev, fan_data);
|
||||
mutex_init(&fan_data->lock);
|
||||
|
||||
fan_data->supply = devm_regulator_get(dev, "fan");
|
||||
if (IS_ERR(fan_data->supply))
|
||||
return dev_err_probe(dev, PTR_ERR(fan_data->supply),
|
||||
"Failed to get fan-supply");
|
||||
|
||||
/* Configure control GPIOs if available. */
|
||||
if (fan_data->gpios && fan_data->num_gpios > 0) {
|
||||
if (!fan_data->speed || fan_data->num_speed <= 1)
|
||||
@@ -548,6 +580,17 @@ static int gpio_fan_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
/* If current GPIO state is active, mark RPM as active as well */
|
||||
if (fan_data->speed_index > 0) {
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Optional cooling device register for Device tree platforms */
|
||||
fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np,
|
||||
"gpio-fan", fan_data, &gpio_fan_cool_ops);
|
||||
@@ -565,41 +608,69 @@ static void gpio_fan_shutdown(struct platform_device *pdev)
|
||||
set_fan_speed(fan_data, 0);
|
||||
}
|
||||
|
||||
static int gpio_fan_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (fan_data->supply)
|
||||
ret = regulator_disable(fan_data->supply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_fan_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (fan_data->supply)
|
||||
ret = regulator_enable(fan_data->supply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_fan_suspend(struct device *dev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (fan_data->gpios) {
|
||||
fan_data->resume_speed = fan_data->speed_index;
|
||||
mutex_lock(&fan_data->lock);
|
||||
set_fan_speed(fan_data, 0);
|
||||
ret = set_fan_speed(fan_data, 0);
|
||||
mutex_unlock(&fan_data->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_fan_resume(struct device *dev)
|
||||
{
|
||||
struct gpio_fan_data *fan_data = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (fan_data->gpios) {
|
||||
mutex_lock(&fan_data->lock);
|
||||
set_fan_speed(fan_data, fan_data->resume_speed);
|
||||
ret = set_fan_speed(fan_data, fan_data->resume_speed);
|
||||
mutex_unlock(&fan_data->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume);
|
||||
static const struct dev_pm_ops gpio_fan_pm = {
|
||||
RUNTIME_PM_OPS(gpio_fan_runtime_suspend,
|
||||
gpio_fan_runtime_resume, NULL)
|
||||
SYSTEM_SLEEP_PM_OPS(gpio_fan_suspend, gpio_fan_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver gpio_fan_driver = {
|
||||
.probe = gpio_fan_probe,
|
||||
.shutdown = gpio_fan_shutdown,
|
||||
.driver = {
|
||||
.name = "gpio-fan",
|
||||
.pm = pm_sleep_ptr(&gpio_fan_pm),
|
||||
.pm = pm_ptr(&gpio_fan_pm),
|
||||
.of_match_table = of_gpio_fan_match,
|
||||
},
|
||||
};
|
||||
|
||||
+180
-34
@@ -21,11 +21,14 @@
|
||||
#define INA238_CONFIG 0x0
|
||||
#define INA238_ADC_CONFIG 0x1
|
||||
#define INA238_SHUNT_CALIBRATION 0x2
|
||||
#define SQ52206_SHUNT_TEMPCO 0x3
|
||||
#define INA238_SHUNT_VOLTAGE 0x4
|
||||
#define INA238_BUS_VOLTAGE 0x5
|
||||
#define INA238_DIE_TEMP 0x6
|
||||
#define INA238_CURRENT 0x7
|
||||
#define INA238_POWER 0x8
|
||||
#define SQ52206_ENERGY 0x9
|
||||
#define SQ52206_CHARGE 0xa
|
||||
#define INA238_DIAG_ALERT 0xb
|
||||
#define INA238_SHUNT_OVER_VOLTAGE 0xc
|
||||
#define INA238_SHUNT_UNDER_VOLTAGE 0xd
|
||||
@@ -33,9 +36,12 @@
|
||||
#define INA238_BUS_UNDER_VOLTAGE 0xf
|
||||
#define INA238_TEMP_LIMIT 0x10
|
||||
#define INA238_POWER_LIMIT 0x11
|
||||
#define SQ52206_POWER_PEAK 0x20
|
||||
#define INA238_DEVICE_ID 0x3f /* not available on INA237 */
|
||||
|
||||
#define INA238_CONFIG_ADCRANGE BIT(4)
|
||||
#define SQ52206_CONFIG_ADCRANGE_HIGH BIT(4)
|
||||
#define SQ52206_CONFIG_ADCRANGE_LOW BIT(3)
|
||||
|
||||
#define INA238_DIAG_ALERT_TMPOL BIT(7)
|
||||
#define INA238_DIAG_ALERT_SHNTOL BIT(6)
|
||||
@@ -44,12 +50,13 @@
|
||||
#define INA238_DIAG_ALERT_BUSUL BIT(3)
|
||||
#define INA238_DIAG_ALERT_POL BIT(2)
|
||||
|
||||
#define INA238_REGISTERS 0x11
|
||||
#define INA238_REGISTERS 0x20
|
||||
|
||||
#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */
|
||||
|
||||
/* Default configuration of device on reset. */
|
||||
#define INA238_CONFIG_DEFAULT 0
|
||||
#define SQ52206_CONFIG_DEFAULT 0x0005
|
||||
/* 16 sample averaging, 1052us conversion time, continuous mode */
|
||||
#define INA238_ADC_CONFIG_DEFAULT 0xfb6a
|
||||
/* Configure alerts to be based on averaged value (SLOWALERT) */
|
||||
@@ -87,14 +94,19 @@
|
||||
* shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb)
|
||||
*
|
||||
* Current (mA) = register value * 20000 / rshunt / 4 * gain
|
||||
* Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain
|
||||
* Power (mW) = 0.2 * register value * 20000 / rshunt / 4 * gain
|
||||
* (Specific for SQ52206)
|
||||
* Power (mW) = 0.24 * register value * 20000 / rshunt / 4 * gain
|
||||
* Energy (mJ) = 16 * 0.24 * register value * 20000 / rshunt / 4 * gain
|
||||
*/
|
||||
#define INA238_CALIBRATION_VALUE 16384
|
||||
#define INA238_FIXED_SHUNT 20000
|
||||
|
||||
#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */
|
||||
#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */
|
||||
#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */
|
||||
#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */
|
||||
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
|
||||
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
|
||||
|
||||
static const struct regmap_config ina238_regmap_config = {
|
||||
.max_register = INA238_REGISTERS,
|
||||
@@ -102,7 +114,20 @@ static const struct regmap_config ina238_regmap_config = {
|
||||
.val_bits = 16,
|
||||
};
|
||||
|
||||
enum ina238_ids { ina238, ina237, sq52206 };
|
||||
|
||||
struct ina238_config {
|
||||
bool has_power_highest; /* chip detection power peak */
|
||||
bool has_energy; /* chip detection energy */
|
||||
u8 temp_shift; /* fixed parameters for temp calculate */
|
||||
u32 power_calculate_factor; /* fixed parameters for power calculate */
|
||||
u16 config_default; /* Power-on default state */
|
||||
int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */
|
||||
int temp_lsb; /* use for temperature calculate */
|
||||
};
|
||||
|
||||
struct ina238_data {
|
||||
const struct ina238_config *config;
|
||||
struct i2c_client *client;
|
||||
struct mutex config_lock;
|
||||
struct regmap *regmap;
|
||||
@@ -110,6 +135,36 @@ struct ina238_data {
|
||||
int gain;
|
||||
};
|
||||
|
||||
static const struct ina238_config ina238_config[] = {
|
||||
[ina238] = {
|
||||
.has_energy = false,
|
||||
.has_power_highest = false,
|
||||
.temp_shift = 4,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = INA238_DIE_TEMP_LSB,
|
||||
},
|
||||
[ina237] = {
|
||||
.has_energy = false,
|
||||
.has_power_highest = false,
|
||||
.temp_shift = 4,
|
||||
.power_calculate_factor = 20,
|
||||
.config_default = INA238_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = INA238_DIE_TEMP_LSB,
|
||||
},
|
||||
[sq52206] = {
|
||||
.has_energy = true,
|
||||
.has_power_highest = true,
|
||||
.temp_shift = 0,
|
||||
.power_calculate_factor = 24,
|
||||
.config_default = SQ52206_CONFIG_DEFAULT,
|
||||
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
|
||||
.temp_lsb = SQ52206_DIE_TEMP_LSB,
|
||||
},
|
||||
};
|
||||
|
||||
static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
|
||||
{
|
||||
u8 data[3];
|
||||
@@ -126,6 +181,24 @@ static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read_reg40(const struct i2c_client *client, u8 reg, u64 *val)
|
||||
{
|
||||
u8 data[5];
|
||||
u32 low;
|
||||
int err;
|
||||
|
||||
/* 40-bit register read */
|
||||
err = i2c_smbus_read_i2c_block_data(client, reg, 5, data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (err != 5)
|
||||
return -EIO;
|
||||
low = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4];
|
||||
*val = ((long long)data[0] << 32) | low;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
@@ -197,10 +270,10 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
|
||||
regval = (s16)regval;
|
||||
if (channel == 0)
|
||||
/* gain of 1 -> LSB / 4 */
|
||||
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) /
|
||||
(1000 * (4 - data->gain + 1));
|
||||
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) *
|
||||
data->gain / (1000 * 4);
|
||||
else
|
||||
*val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000;
|
||||
*val = (regval * data->config->bus_voltage_lsb) / 1000;
|
||||
break;
|
||||
case hwmon_in_max_alarm:
|
||||
case hwmon_in_min_alarm:
|
||||
@@ -225,8 +298,8 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel,
|
||||
case 0:
|
||||
/* signed value, clamp to max range +/-163 mV */
|
||||
regval = clamp_val(val, -163, 163);
|
||||
regval = (regval * 1000 * (4 - data->gain + 1)) /
|
||||
INA238_SHUNT_VOLTAGE_LSB;
|
||||
regval = (regval * 1000 * 4) /
|
||||
(INA238_SHUNT_VOLTAGE_LSB * data->gain);
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX);
|
||||
|
||||
switch (attr) {
|
||||
@@ -242,7 +315,7 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel,
|
||||
case 1:
|
||||
/* signed value, positive values only. Clamp to max 102.396 V */
|
||||
regval = clamp_val(val, 0, 102396);
|
||||
regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB;
|
||||
regval = (regval * 1000) / data->config->bus_voltage_lsb;
|
||||
regval = clamp_val(regval, 0, S16_MAX);
|
||||
|
||||
switch (attr) {
|
||||
@@ -297,8 +370,19 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
||||
return err;
|
||||
|
||||
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT *
|
||||
data->gain, 20 * data->rshunt);
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
case hwmon_power_input_highest:
|
||||
err = ina238_read_reg24(data->client, SQ52206_POWER_PEAK, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Fixed 1mA lsb, scaled by 1000000 to have result in uW */
|
||||
power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
@@ -311,8 +395,8 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val)
|
||||
* Truncated 24-bit compare register, lower 8-bits are
|
||||
* truncated. Same conversion to/from uW as POWER register.
|
||||
*/
|
||||
power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT *
|
||||
data->gain, 20 * data->rshunt);
|
||||
power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
/* Clamp value to maximum value of long */
|
||||
*val = clamp_val(power, 0, LONG_MAX);
|
||||
break;
|
||||
@@ -344,8 +428,8 @@ static int ina238_write_power(struct device *dev, u32 attr, long val)
|
||||
* register.
|
||||
*/
|
||||
regval = clamp_val(val, 0, LONG_MAX);
|
||||
regval = div_u64(val * 20ULL * data->rshunt,
|
||||
1000ULL * INA238_FIXED_SHUNT * data->gain);
|
||||
regval = div_u64(val * 4 * 100 * data->rshunt, data->config->power_calculate_factor *
|
||||
1000ULL * INA238_FIXED_SHUNT * data->gain);
|
||||
regval = clamp_val(regval >> 8, 0, U16_MAX);
|
||||
|
||||
return regmap_write(data->regmap, INA238_POWER_LIMIT, regval);
|
||||
@@ -362,17 +446,17 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val)
|
||||
err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Signed, bits 15-4 of register, result in mC */
|
||||
*val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
|
||||
/* Signed, result in mC */
|
||||
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
|
||||
(s64)data->config->temp_lsb, 10000);
|
||||
break;
|
||||
case hwmon_temp_max:
|
||||
err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Signed, bits 15-4 of register, result in mC */
|
||||
*val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB;
|
||||
/* Signed, result in mC */
|
||||
*val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) *
|
||||
(s64)data->config->temp_lsb, 10000);
|
||||
break;
|
||||
case hwmon_temp_max_alarm:
|
||||
err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val);
|
||||
@@ -396,13 +480,33 @@ static int ina238_write_temp(struct device *dev, u32 attr, long val)
|
||||
if (attr != hwmon_temp_max)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* Signed, bits 15-4 of register */
|
||||
regval = (val / INA238_DIE_TEMP_LSB) << 4;
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0;
|
||||
/* Signed */
|
||||
regval = clamp_val(val, -40000, 125000);
|
||||
regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift;
|
||||
regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift);
|
||||
|
||||
return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval);
|
||||
}
|
||||
|
||||
static ssize_t energy1_input_show(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
u64 regval;
|
||||
u64 energy;
|
||||
|
||||
ret = ina238_read_reg40(data->client, SQ52206_ENERGY, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* result in mJ */
|
||||
energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 *
|
||||
data->config->power_calculate_factor, 4 * 100 * data->rshunt);
|
||||
|
||||
return sysfs_emit(buf, "%llu\n", energy);
|
||||
}
|
||||
|
||||
static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
@@ -422,7 +526,7 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
}
|
||||
|
||||
static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct ina238_data *data = dev_get_drvdata(dev);
|
||||
int err;
|
||||
@@ -452,6 +556,9 @@ static umode_t ina238_is_visible(const void *drvdata,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct ina238_data *data = drvdata;
|
||||
bool has_power_highest = data->config->has_power_highest;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
@@ -479,6 +586,10 @@ static umode_t ina238_is_visible(const void *drvdata,
|
||||
return 0444;
|
||||
case hwmon_power_max:
|
||||
return 0644;
|
||||
case hwmon_power_input_highest:
|
||||
if (has_power_highest)
|
||||
return 0444;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -512,7 +623,8 @@ static const struct hwmon_channel_info * const ina238_info[] = {
|
||||
HWMON_C_INPUT),
|
||||
HWMON_CHANNEL_INFO(power,
|
||||
/* 0: power */
|
||||
HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM),
|
||||
HWMON_P_INPUT | HWMON_P_MAX |
|
||||
HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
/* 0: die temperature */
|
||||
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
|
||||
@@ -530,20 +642,35 @@ static const struct hwmon_chip_info ina238_chip_info = {
|
||||
.info = ina238_info,
|
||||
};
|
||||
|
||||
/* energy attributes are 5 bytes wide so we need u64 */
|
||||
static DEVICE_ATTR_RO(energy1_input);
|
||||
|
||||
static struct attribute *ina238_attrs[] = {
|
||||
&dev_attr_energy1_input.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ina238);
|
||||
|
||||
static int ina238_probe(struct i2c_client *client)
|
||||
{
|
||||
struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct ina238_data *data;
|
||||
enum ina238_ids chip;
|
||||
int config;
|
||||
int ret;
|
||||
|
||||
chip = (uintptr_t)i2c_get_match_data(client);
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
/* set the device type */
|
||||
data->config = &ina238_config[chip];
|
||||
|
||||
mutex_init(&data->config_lock);
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config);
|
||||
@@ -564,15 +691,21 @@ static int ina238_probe(struct i2c_client *client)
|
||||
/* load shunt gain value */
|
||||
if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0)
|
||||
data->gain = 4; /* Default of ADCRANGE = 0 */
|
||||
if (data->gain != 1 && data->gain != 4) {
|
||||
if (data->gain != 1 && data->gain != 2 && data->gain != 4) {
|
||||
dev_err(dev, "invalid shunt gain value %u\n", data->gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Setup CONFIG register */
|
||||
config = INA238_CONFIG_DEFAULT;
|
||||
if (data->gain == 1)
|
||||
config = data->config->config_default;
|
||||
if (chip == sq52206) {
|
||||
if (data->gain == 1)
|
||||
config |= SQ52206_CONFIG_ADCRANGE_HIGH; /* ADCRANGE = 10/11 is /1 */
|
||||
else if (data->gain == 2)
|
||||
config |= SQ52206_CONFIG_ADCRANGE_LOW; /* ADCRANGE = 01 is /2 */
|
||||
} else if (data->gain == 1) {
|
||||
config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */
|
||||
}
|
||||
ret = regmap_write(data->regmap, INA238_CONFIG, config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
@@ -605,7 +738,8 @@ static int ina238_probe(struct i2c_client *client)
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&ina238_chip_info,
|
||||
NULL);
|
||||
data->config->has_energy ?
|
||||
ina238_groups : NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
@@ -616,15 +750,27 @@ static int ina238_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ina238_id[] = {
|
||||
{ "ina238" },
|
||||
{ "ina237", ina237 },
|
||||
{ "ina238", ina238 },
|
||||
{ "sq52206", sq52206 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ina238_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused ina238_of_match[] = {
|
||||
{ .compatible = "ti,ina237" },
|
||||
{ .compatible = "ti,ina238" },
|
||||
{ },
|
||||
{
|
||||
.compatible = "ti,ina237",
|
||||
.data = (void *)ina237
|
||||
},
|
||||
{
|
||||
.compatible = "ti,ina238",
|
||||
.data = (void *)ina238
|
||||
},
|
||||
{
|
||||
.compatible = "silergy,sq52206",
|
||||
.data = (void *)sq52206
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ina238_of_match);
|
||||
|
||||
|
||||
@@ -959,8 +959,12 @@ static int ina2xx_probe(struct i2c_client *client)
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "vs");
|
||||
if (ret)
|
||||
/*
|
||||
* Regulator core returns -ENODEV if the 'vs' is not available.
|
||||
* Hence the check for -ENODEV return code is necessary.
|
||||
*/
|
||||
ret = devm_regulator_get_enable_optional(dev, "vs");
|
||||
if (ret < 0 && ret != -ENODEV)
|
||||
return dev_err_probe(dev, ret, "failed to enable vs regulator\n");
|
||||
|
||||
ret = ina2xx_init(dev, data);
|
||||
|
||||
@@ -154,6 +154,7 @@ static int isl28022_read_current(struct device *dev, u32 attr, long *val)
|
||||
struct isl28022_data *data = dev_get_drvdata(dev);
|
||||
unsigned int regval;
|
||||
int err;
|
||||
u16 sign_bit;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
@@ -161,8 +162,9 @@ static int isl28022_read_current(struct device *dev, u32 attr, long *val)
|
||||
ISL28022_REG_CURRENT, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = ((long)regval * 1250L * (long)data->gain) /
|
||||
(long)data->shunt;
|
||||
sign_bit = (regval >> 15) & 0x01;
|
||||
*val = (((long)(((u16)regval) & 0x7FFF) - (sign_bit * 32768)) *
|
||||
1250L * (long)data->gain) / (long)data->shunt;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
@@ -301,7 +303,7 @@ static const struct regmap_config isl28022_regmap_config = {
|
||||
.writeable_reg = isl28022_is_writeable_reg,
|
||||
.volatile_reg = isl28022_is_volatile_reg,
|
||||
.val_format_endian = REGMAP_ENDIAN_BIG,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
.use_single_read = true,
|
||||
.use_single_write = true,
|
||||
};
|
||||
|
||||
@@ -503,6 +503,13 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
k10temp_get_ccd_support(data, 12);
|
||||
break;
|
||||
}
|
||||
} else if (boot_cpu_data.x86 == 0x1a) {
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */
|
||||
data->ccd_offset = 0x308;
|
||||
k10temp_get_ccd_support(data, 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025 KEBA Industrial Automation GmbH
|
||||
*
|
||||
* Driver for KEBA battery monitoring controller FPGA IP core
|
||||
*/
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/misc/keba.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#define KBATT "kbatt"
|
||||
|
||||
#define KBATT_CONTROL_REG 0x4
|
||||
#define KBATT_CONTROL_BAT_TEST 0x01
|
||||
|
||||
#define KBATT_STATUS_REG 0x8
|
||||
#define KBATT_STATUS_BAT_OK 0x01
|
||||
|
||||
#define KBATT_MAX_UPD_INTERVAL (10 * HZ)
|
||||
#define KBATT_SETTLE_TIME_US (100 * USEC_PER_MSEC)
|
||||
|
||||
struct kbatt {
|
||||
/* update lock */
|
||||
struct mutex lock;
|
||||
void __iomem *base;
|
||||
|
||||
unsigned long next_update; /* in jiffies */
|
||||
bool alarm;
|
||||
};
|
||||
|
||||
static bool kbatt_alarm(struct kbatt *kbatt)
|
||||
{
|
||||
mutex_lock(&kbatt->lock);
|
||||
|
||||
if (!kbatt->next_update || time_after(jiffies, kbatt->next_update)) {
|
||||
/* switch load on */
|
||||
iowrite8(KBATT_CONTROL_BAT_TEST,
|
||||
kbatt->base + KBATT_CONTROL_REG);
|
||||
|
||||
/* wait some time to let things settle */
|
||||
fsleep(KBATT_SETTLE_TIME_US);
|
||||
|
||||
/* check battery state */
|
||||
if (ioread8(kbatt->base + KBATT_STATUS_REG) &
|
||||
KBATT_STATUS_BAT_OK)
|
||||
kbatt->alarm = false;
|
||||
else
|
||||
kbatt->alarm = true;
|
||||
|
||||
/* switch load off */
|
||||
iowrite8(0, kbatt->base + KBATT_CONTROL_REG);
|
||||
|
||||
kbatt->next_update = jiffies + KBATT_MAX_UPD_INTERVAL;
|
||||
}
|
||||
|
||||
mutex_unlock(&kbatt->lock);
|
||||
|
||||
return kbatt->alarm;
|
||||
}
|
||||
|
||||
static int kbatt_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct kbatt *kbatt = dev_get_drvdata(dev);
|
||||
|
||||
*val = kbatt_alarm(kbatt) ? 1 : 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t kbatt_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
if (channel == 0 && attr == hwmon_in_min_alarm)
|
||||
return 0444;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *kbatt_info[] = {
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
/* 0: input minimum alarm channel */
|
||||
HWMON_I_MIN_ALARM),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops kbatt_hwmon_ops = {
|
||||
.is_visible = kbatt_is_visible,
|
||||
.read = kbatt_read,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info kbatt_chip_info = {
|
||||
.ops = &kbatt_hwmon_ops,
|
||||
.info = kbatt_info,
|
||||
};
|
||||
|
||||
static int kbatt_probe(struct auxiliary_device *auxdev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct keba_batt_auxdev *kbatt_auxdev =
|
||||
container_of(auxdev, struct keba_batt_auxdev, auxdev);
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct kbatt *kbatt;
|
||||
int retval;
|
||||
|
||||
kbatt = devm_kzalloc(dev, sizeof(*kbatt), GFP_KERNEL);
|
||||
if (!kbatt)
|
||||
return -ENOMEM;
|
||||
|
||||
retval = devm_mutex_init(dev, &kbatt->lock);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
kbatt->base = devm_ioremap_resource(dev, &kbatt_auxdev->io);
|
||||
if (IS_ERR(kbatt->base))
|
||||
return PTR_ERR(kbatt->base);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, KBATT, kbatt,
|
||||
&kbatt_chip_info,
|
||||
NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id kbatt_devtype_aux[] = {
|
||||
{ .name = "keba.batt" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, kbatt_devtype_aux);
|
||||
|
||||
static struct auxiliary_driver kbatt_driver_aux = {
|
||||
.name = KBATT,
|
||||
.id_table = kbatt_devtype_aux,
|
||||
.probe = kbatt_probe,
|
||||
};
|
||||
module_auxiliary_driver(kbatt_driver_aux);
|
||||
|
||||
MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
|
||||
MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
|
||||
MODULE_DESCRIPTION("KEBA battery monitoring controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -0,0 +1,246 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025 KEBA Industrial Automation GmbH
|
||||
*
|
||||
* Driver for KEBA fan controller FPGA IP core
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/misc/keba.h>
|
||||
|
||||
#define KFAN "kfan"
|
||||
|
||||
#define KFAN_CONTROL_REG 0x04
|
||||
|
||||
#define KFAN_STATUS_REG 0x08
|
||||
#define KFAN_STATUS_PRESENT 0x01
|
||||
#define KFAN_STATUS_REGULABLE 0x02
|
||||
#define KFAN_STATUS_TACHO 0x04
|
||||
#define KFAN_STATUS_BLOCKED 0x08
|
||||
|
||||
#define KFAN_TACHO_REG 0x0c
|
||||
|
||||
#define KFAN_DEFAULT_DIV 2
|
||||
|
||||
struct kfan {
|
||||
void __iomem *base;
|
||||
bool tacho;
|
||||
bool regulable;
|
||||
|
||||
/* hwmon API configuration */
|
||||
u32 fan_channel_config[2];
|
||||
struct hwmon_channel_info fan_info;
|
||||
u32 pwm_channel_config[2];
|
||||
struct hwmon_channel_info pwm_info;
|
||||
const struct hwmon_channel_info *info[3];
|
||||
struct hwmon_chip_info chip;
|
||||
};
|
||||
|
||||
static bool kfan_get_fault(struct kfan *kfan)
|
||||
{
|
||||
u8 status = ioread8(kfan->base + KFAN_STATUS_REG);
|
||||
|
||||
if (!(status & KFAN_STATUS_PRESENT))
|
||||
return true;
|
||||
|
||||
if (!kfan->tacho && (status & KFAN_STATUS_BLOCKED))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned int kfan_count_to_rpm(u16 count)
|
||||
{
|
||||
if (count == 0 || count == 0xffff)
|
||||
return 0;
|
||||
|
||||
return 5000000UL / (KFAN_DEFAULT_DIV * count);
|
||||
}
|
||||
|
||||
static unsigned int kfan_get_rpm(struct kfan *kfan)
|
||||
{
|
||||
unsigned int rpm;
|
||||
u16 count;
|
||||
|
||||
count = ioread16(kfan->base + KFAN_TACHO_REG);
|
||||
rpm = kfan_count_to_rpm(count);
|
||||
|
||||
return rpm;
|
||||
}
|
||||
|
||||
static unsigned int kfan_get_pwm(struct kfan *kfan)
|
||||
{
|
||||
return ioread8(kfan->base + KFAN_CONTROL_REG);
|
||||
}
|
||||
|
||||
static int kfan_set_pwm(struct kfan *kfan, long val)
|
||||
{
|
||||
if (val < 0 || val > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
/* if none-regulable, then only 0 or 0xff can be written */
|
||||
if (!kfan->regulable && val > 0)
|
||||
val = 0xff;
|
||||
|
||||
iowrite8(val, kfan->base + KFAN_CONTROL_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kfan_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct kfan *kfan = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return kfan_set_pwm(kfan, val);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int kfan_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct kfan *kfan = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_fault:
|
||||
*val = kfan_get_fault(kfan);
|
||||
return 0;
|
||||
case hwmon_fan_input:
|
||||
*val = kfan_get_rpm(kfan);
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
*val = kfan_get_pwm(kfan);
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static umode_t kfan_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
switch (attr) {
|
||||
case hwmon_fan_input:
|
||||
return 0444;
|
||||
case hwmon_fan_fault:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_pwm:
|
||||
switch (attr) {
|
||||
case hwmon_pwm_input:
|
||||
return 0644;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops kfan_hwmon_ops = {
|
||||
.is_visible = kfan_is_visible,
|
||||
.read = kfan_read,
|
||||
.write = kfan_write,
|
||||
};
|
||||
|
||||
static int kfan_probe(struct auxiliary_device *auxdev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct keba_fan_auxdev *kfan_auxdev =
|
||||
container_of(auxdev, struct keba_fan_auxdev, auxdev);
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct kfan *kfan;
|
||||
u8 status;
|
||||
|
||||
kfan = devm_kzalloc(dev, sizeof(*kfan), GFP_KERNEL);
|
||||
if (!kfan)
|
||||
return -ENOMEM;
|
||||
|
||||
kfan->base = devm_ioremap_resource(dev, &kfan_auxdev->io);
|
||||
if (IS_ERR(kfan->base))
|
||||
return PTR_ERR(kfan->base);
|
||||
|
||||
status = ioread8(kfan->base + KFAN_STATUS_REG);
|
||||
if (status & KFAN_STATUS_REGULABLE)
|
||||
kfan->regulable = true;
|
||||
if (status & KFAN_STATUS_TACHO)
|
||||
kfan->tacho = true;
|
||||
|
||||
/* fan */
|
||||
kfan->fan_channel_config[0] = HWMON_F_FAULT;
|
||||
if (kfan->tacho)
|
||||
kfan->fan_channel_config[0] |= HWMON_F_INPUT;
|
||||
kfan->fan_info.type = hwmon_fan;
|
||||
kfan->fan_info.config = kfan->fan_channel_config;
|
||||
kfan->info[0] = &kfan->fan_info;
|
||||
|
||||
/* PWM */
|
||||
kfan->pwm_channel_config[0] = HWMON_PWM_INPUT;
|
||||
kfan->pwm_info.type = hwmon_pwm;
|
||||
kfan->pwm_info.config = kfan->pwm_channel_config;
|
||||
kfan->info[1] = &kfan->pwm_info;
|
||||
|
||||
kfan->chip.ops = &kfan_hwmon_ops;
|
||||
kfan->chip.info = kfan->info;
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, KFAN, kfan,
|
||||
&kfan->chip, NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id kfan_devtype_aux[] = {
|
||||
{ .name = "keba.fan" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, kfan_devtype_aux);
|
||||
|
||||
static struct auxiliary_driver kfan_driver_aux = {
|
||||
.name = KFAN,
|
||||
.id_table = kfan_devtype_aux,
|
||||
.probe = kfan_probe,
|
||||
};
|
||||
module_auxiliary_driver(kfan_driver_aux);
|
||||
|
||||
MODULE_AUTHOR("Petar Bojanic <boja@keba.com>");
|
||||
MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
|
||||
MODULE_DESCRIPTION("KEBA fan controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -622,7 +622,7 @@ static int lm75_i3c_reg_read(void *context, unsigned int reg, unsigned int *val)
|
||||
{
|
||||
.rnw = true,
|
||||
.len = 2,
|
||||
.data.out = data->val_buf,
|
||||
.data.in = data->val_buf,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
@@ -1235,7 +1235,7 @@ static int lm90_update_alarms(struct lm90_data *data, bool force)
|
||||
|
||||
static void lm90_alert_work(struct work_struct *__work)
|
||||
{
|
||||
struct delayed_work *delayed_work = container_of(__work, struct delayed_work, work);
|
||||
struct delayed_work *delayed_work = to_delayed_work(__work);
|
||||
struct lm90_data *data = container_of(delayed_work, struct lm90_data, alert_work);
|
||||
|
||||
/* Nothing to do if alerts are enabled */
|
||||
|
||||
+20
-10
@@ -256,33 +256,38 @@ static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
|
||||
static int ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset,
|
||||
int value)
|
||||
{
|
||||
struct ltc2992_state *st = gpiochip_get_data(chip);
|
||||
unsigned long gpio_ctrl;
|
||||
int reg;
|
||||
int reg, ret;
|
||||
|
||||
mutex_lock(&st->gpio_mutex);
|
||||
reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1);
|
||||
if (reg < 0) {
|
||||
mutex_unlock(&st->gpio_mutex);
|
||||
return;
|
||||
return reg;
|
||||
}
|
||||
|
||||
gpio_ctrl = reg;
|
||||
assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value);
|
||||
|
||||
ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl);
|
||||
ret = ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1,
|
||||
gpio_ctrl);
|
||||
mutex_unlock(&st->gpio_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
static int ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
struct ltc2992_state *st = gpiochip_get_data(chip);
|
||||
unsigned long gpio_ctrl_io = 0;
|
||||
unsigned long gpio_ctrl = 0;
|
||||
unsigned int gpio_nr;
|
||||
int ret;
|
||||
|
||||
for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) {
|
||||
if (gpio_nr < 3)
|
||||
@@ -293,9 +298,14 @@ static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mas
|
||||
}
|
||||
|
||||
mutex_lock(&st->gpio_mutex);
|
||||
ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
|
||||
ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
|
||||
ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl);
|
||||
out:
|
||||
mutex_unlock(&st->gpio_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2992_config_gpio(struct ltc2992_state *st)
|
||||
@@ -329,8 +339,8 @@ static int ltc2992_config_gpio(struct ltc2992_state *st)
|
||||
st->gc.ngpio = ARRAY_SIZE(st->gpio_names);
|
||||
st->gc.get = ltc2992_gpio_get;
|
||||
st->gc.get_multiple = ltc2992_gpio_get_multiple;
|
||||
st->gc.set = ltc2992_gpio_set;
|
||||
st->gc.set_multiple = ltc2992_gpio_set_multiple;
|
||||
st->gc.set_rv = ltc2992_gpio_set;
|
||||
st->gc.set_multiple_rv = ltc2992_gpio_set_multiple;
|
||||
|
||||
ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st);
|
||||
if (ret)
|
||||
|
||||
+14
-2
@@ -80,6 +80,7 @@ struct max6639_data {
|
||||
/* Register values initialized only once */
|
||||
u8 ppr[MAX6639_NUM_CHANNELS]; /* Pulses per rotation 0..3 for 1..4 ppr */
|
||||
u8 rpm_range[MAX6639_NUM_CHANNELS]; /* Index in above rpm_ranges table */
|
||||
u32 target_rpm[MAX6639_NUM_CHANNELS];
|
||||
|
||||
/* Optional regulator for FAN supply */
|
||||
struct regulator *reg;
|
||||
@@ -563,6 +564,10 @@ static int max6639_probe_child_from_dt(struct i2c_client *client,
|
||||
if (!err)
|
||||
data->rpm_range[i] = rpm_range_to_reg(val);
|
||||
|
||||
err = of_property_read_u32(child, "target-rpm", &val);
|
||||
if (!err)
|
||||
data->target_rpm[i] = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -573,6 +578,7 @@ static int max6639_init_client(struct i2c_client *client,
|
||||
const struct device_node *np = dev->of_node;
|
||||
struct device_node *child;
|
||||
int i, err;
|
||||
u8 target_duty;
|
||||
|
||||
/* Reset chip to default values, see below for GCONFIG setup */
|
||||
err = regmap_write(data->regmap, MAX6639_REG_GCONFIG, MAX6639_GCONFIG_POR);
|
||||
@@ -586,6 +592,8 @@ static int max6639_init_client(struct i2c_client *client,
|
||||
/* default: 4000 RPM */
|
||||
data->rpm_range[0] = 1;
|
||||
data->rpm_range[1] = 1;
|
||||
data->target_rpm[0] = 4000;
|
||||
data->target_rpm[1] = 4000;
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
if (strcmp(child->name, "fan"))
|
||||
@@ -639,8 +647,12 @@ static int max6639_init_client(struct i2c_client *client,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* PWM 120/120 (i.e. 100%) */
|
||||
err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), 120);
|
||||
/* Set PWM based on target RPM if specified */
|
||||
if (data->target_rpm[i] > rpm_ranges[data->rpm_range[i]])
|
||||
data->target_rpm[i] = rpm_ranges[data->rpm_range[i]];
|
||||
|
||||
target_duty = 120 * data->target_rpm[i] / rpm_ranges[data->rpm_range[i]];
|
||||
err = regmap_write(data->regmap, MAX6639_REG_TARGTDUTY(i), target_duty);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MAX77705 voltage and current hwmon driver.
|
||||
*
|
||||
* Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/max77705-private.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
struct channel_desc {
|
||||
u8 reg;
|
||||
u8 avg_reg;
|
||||
const char *const label;
|
||||
// register resolution. nano Volts for voltage, nano Amperes for current
|
||||
u32 resolution;
|
||||
};
|
||||
|
||||
static const struct channel_desc current_channel_desc[] = {
|
||||
{
|
||||
.reg = IIN_REG,
|
||||
.label = "IIN_REG",
|
||||
.resolution = 125000
|
||||
},
|
||||
{
|
||||
.reg = ISYS_REG,
|
||||
.avg_reg = AVGISYS_REG,
|
||||
.label = "ISYS_REG",
|
||||
.resolution = 312500
|
||||
}
|
||||
};
|
||||
|
||||
static const struct channel_desc voltage_channel_desc[] = {
|
||||
{
|
||||
.reg = VBYP_REG,
|
||||
.label = "VBYP_REG",
|
||||
.resolution = 427246
|
||||
},
|
||||
{
|
||||
.reg = VSYS_REG,
|
||||
.label = "VSYS_REG",
|
||||
.resolution = 156250
|
||||
}
|
||||
};
|
||||
|
||||
static int max77705_read_and_convert(struct regmap *regmap, u8 reg, u32 res,
|
||||
bool is_signed, long *val)
|
||||
{
|
||||
int ret;
|
||||
u32 regval;
|
||||
|
||||
ret = regmap_read(regmap, reg, ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (is_signed)
|
||||
*val = mult_frac((long)sign_extend32(regval, 15), res, 1000000);
|
||||
else
|
||||
*val = mult_frac((long)regval, res, 1000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t max77705_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_label:
|
||||
return 0444;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
case hwmon_in_label:
|
||||
return 0444;
|
||||
case hwmon_curr_average:
|
||||
if (current_channel_desc[channel].avg_reg)
|
||||
return 0444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77705_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, const char **buf)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_in_label:
|
||||
*buf = current_channel_desc[channel].label;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_label:
|
||||
*buf = voltage_channel_desc[channel].label;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int max77705_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct regmap *regmap = dev_get_drvdata(dev);
|
||||
u8 reg;
|
||||
u32 res;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
reg = current_channel_desc[channel].reg;
|
||||
res = current_channel_desc[channel].resolution;
|
||||
|
||||
return max77705_read_and_convert(regmap, reg, res, true, val);
|
||||
case hwmon_curr_average:
|
||||
reg = current_channel_desc[channel].avg_reg;
|
||||
res = current_channel_desc[channel].resolution;
|
||||
|
||||
return max77705_read_and_convert(regmap, reg, res, true, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
reg = voltage_channel_desc[channel].reg;
|
||||
res = voltage_channel_desc[channel].resolution;
|
||||
|
||||
return max77705_read_and_convert(regmap, reg, res, false, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops max77705_hwmon_ops = {
|
||||
.is_visible = max77705_is_visible,
|
||||
.read = max77705_read,
|
||||
.read_string = max77705_read_string,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *max77705_info[] = {
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL
|
||||
),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_AVERAGE | HWMON_C_LABEL
|
||||
),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info max77705_chip_info = {
|
||||
.ops = &max77705_hwmon_ops,
|
||||
.info = max77705_info,
|
||||
};
|
||||
|
||||
static int max77705_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *hwmon_dev;
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!regmap)
|
||||
return -ENODEV;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "max77705", regmap,
|
||||
&max77705_chip_info, NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev),
|
||||
"Unable to register hwmon device\n");
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static struct platform_driver max77705_hwmon_driver = {
|
||||
.driver = {
|
||||
.name = "max77705-hwmon",
|
||||
},
|
||||
.probe = max77705_hwmon_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(max77705_hwmon_driver);
|
||||
|
||||
MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
|
||||
MODULE_DESCRIPTION("MAX77705 monitor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -391,7 +391,7 @@ static const struct regmap_config nct7363_regmap_config = {
|
||||
.val_bits = 8,
|
||||
.use_single_read = true,
|
||||
.use_single_write = true,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
.volatile_reg = nct7363_regmap_is_volatile,
|
||||
};
|
||||
|
||||
|
||||
@@ -218,6 +218,24 @@ config SENSORS_LM25066_REGULATOR
|
||||
If you say yes here you get regulator support for National
|
||||
Semiconductor LM25066, LM5064, and LM5066.
|
||||
|
||||
config SENSORS_LT3074
|
||||
tristate "Analog Devices LT3074"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices LT3074.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called lt3074.
|
||||
|
||||
config SENSORS_LT3074_REGULATOR
|
||||
tristate "Regulator support for LT3074"
|
||||
depends on SENSORS_LT3074 && REGULATOR
|
||||
help
|
||||
If you say yes here you get regulator support for Analog Devices
|
||||
LT3074. The LT3074 is a low voltage, ultralow noise, high PSRR,
|
||||
dropout linear regulator. The device supplies up to 3A with a
|
||||
typical dropout voltage of 45mV.
|
||||
|
||||
config SENSORS_LT7182S
|
||||
tristate "Analog Devices LT7182S"
|
||||
help
|
||||
|
||||
@@ -23,6 +23,7 @@ obj-$(CONFIG_SENSORS_IR38064) += ir38064.o
|
||||
obj-$(CONFIG_SENSORS_IRPS5401) += irps5401.o
|
||||
obj-$(CONFIG_SENSORS_ISL68137) += isl68137.o
|
||||
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
||||
obj-$(CONFIG_SENSORS_LT3074) += lt3074.o
|
||||
obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o
|
||||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
||||
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
|
||||
|
||||
@@ -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_ONE("vout"),
|
||||
PMBUS_REGULATOR_ONE_NODE("vout"),
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Hardware monitoring driver for Analog Devices LT3074
|
||||
*
|
||||
* Copyright (C) 2025 Analog Devices, Inc.
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
#define LT3074_MFR_READ_VBIAS 0xc6
|
||||
#define LT3074_MFR_BIAS_OV_WARN_LIMIT 0xc7
|
||||
#define LT3074_MFR_BIAS_UV_WARN_LIMIT 0xc8
|
||||
#define LT3074_MFR_SPECIAL_ID 0xe7
|
||||
|
||||
#define LT3074_SPECIAL_ID_VALUE 0x1c1d
|
||||
|
||||
static const struct regulator_desc __maybe_unused lt3074_reg_desc[] = {
|
||||
PMBUS_REGULATOR_ONE("regulator"),
|
||||
};
|
||||
|
||||
static int lt3074_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VMON:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
LT3074_MFR_READ_VBIAS);
|
||||
case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
LT3074_MFR_BIAS_UV_WARN_LIMIT);
|
||||
case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
|
||||
return pmbus_read_word_data(client, page, phase,
|
||||
LT3074_MFR_BIAS_OV_WARN_LIMIT);
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static int lt3074_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
|
||||
return pmbus_write_word_data(client, 0,
|
||||
LT3074_MFR_BIAS_UV_WARN_LIMIT,
|
||||
word);
|
||||
case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
|
||||
return pmbus_write_word_data(client, 0,
|
||||
LT3074_MFR_BIAS_OV_WARN_LIMIT,
|
||||
word);
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info lt3074_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_VMON |
|
||||
PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = lt3074_read_word_data,
|
||||
.write_word_data = lt3074_write_word_data,
|
||||
#if IS_ENABLED(CONFIG_SENSORS_LT3074_REGULATOR)
|
||||
.num_regulators = 1,
|
||||
.reg_desc = lt3074_reg_desc,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int lt3074_probe(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &client->dev;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, LT3074_MFR_SPECIAL_ID);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to read ID\n");
|
||||
|
||||
if (ret != LT3074_SPECIAL_ID_VALUE)
|
||||
return dev_err_probe(dev, -ENODEV, "ID mismatch\n");
|
||||
|
||||
return pmbus_do_probe(client, <3074_info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lt3074_id[] = {
|
||||
{ "lt3074", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lt3074_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused lt3074_of_match[] = {
|
||||
{ .compatible = "adi,lt3074" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lt3074_of_match);
|
||||
|
||||
static struct i2c_driver lt3074_driver = {
|
||||
.driver = {
|
||||
.name = "lt3074",
|
||||
.of_match_table = of_match_ptr(lt3074_of_match),
|
||||
},
|
||||
.probe = lt3074_probe,
|
||||
.id_table = lt3074_id,
|
||||
};
|
||||
module_i2c_driver(lt3074_driver);
|
||||
|
||||
MODULE_AUTHOR("Cedric Encarnacion <cedricjustine.encarnacion@analog.com>");
|
||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices LT3074");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("PMBUS");
|
||||
@@ -12,9 +12,26 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { max34440, max34441, max34446, max34451, max34460, max34461 };
|
||||
enum chips {
|
||||
adpm12160,
|
||||
max34440,
|
||||
max34441,
|
||||
max34446,
|
||||
max34451,
|
||||
max34460,
|
||||
max34461,
|
||||
};
|
||||
|
||||
/*
|
||||
* Firmware is sometimes not ready if we try and read the
|
||||
* data from the page immediately after setting. Maxim
|
||||
* recommends 50us delay due to the chip failing to clock
|
||||
* stretch long enough here.
|
||||
*/
|
||||
#define MAX34440_PAGE_CHANGE_DELAY 50
|
||||
|
||||
#define MAX34440_MFR_VOUT_PEAK 0xd4
|
||||
#define MAX34440_MFR_IOUT_PEAK 0xd5
|
||||
@@ -34,16 +51,21 @@ enum chips { max34440, max34441, max34446, max34451, max34460, max34461 };
|
||||
/*
|
||||
* The whole max344* family have IOUT_OC_WARN_LIMIT and IOUT_OC_FAULT_LIMIT
|
||||
* swapped from the standard pmbus spec addresses.
|
||||
* For max34451, version MAX34451ETNA6+ and later has this issue fixed.
|
||||
*/
|
||||
#define MAX34440_IOUT_OC_WARN_LIMIT 0x46
|
||||
#define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A
|
||||
|
||||
#define MAX34451ETNA6_MFR_REV 0x0012
|
||||
|
||||
#define MAX34451_MFR_CHANNEL_CONFIG 0xe4
|
||||
#define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f
|
||||
|
||||
struct max34440_data {
|
||||
int id;
|
||||
struct pmbus_driver_info info;
|
||||
u8 iout_oc_warn_limit;
|
||||
u8 iout_oc_fault_limit;
|
||||
};
|
||||
|
||||
#define to_max34440_data(x) container_of(x, struct max34440_data, info)
|
||||
@@ -60,11 +82,11 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
MAX34440_IOUT_OC_FAULT_LIMIT);
|
||||
data->iout_oc_fault_limit);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
MAX34440_IOUT_OC_WARN_LIMIT);
|
||||
data->iout_oc_warn_limit);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
@@ -75,7 +97,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page,
|
||||
MAX34440_MFR_VOUT_PEAK);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_IOUT_AVG:
|
||||
if (data->id != max34446 && data->id != max34451)
|
||||
if (data->id != max34446 && data->id != max34451 &&
|
||||
data->id != adpm12160)
|
||||
return -ENXIO;
|
||||
ret = pmbus_read_word_data(client, page, phase,
|
||||
MAX34446_MFR_IOUT_AVG);
|
||||
@@ -133,11 +156,11 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_FAULT_LIMIT,
|
||||
ret = pmbus_write_word_data(client, page, data->iout_oc_fault_limit,
|
||||
word);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||
ret = pmbus_write_word_data(client, page, MAX34440_IOUT_OC_WARN_LIMIT,
|
||||
ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit,
|
||||
word);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_POUT_HISTORY:
|
||||
@@ -159,7 +182,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
ret = pmbus_write_word_data(client, page,
|
||||
MAX34440_MFR_IOUT_PEAK, 0);
|
||||
if (!ret && (data->id == max34446 || data->id == max34451))
|
||||
if (!ret && (data->id == max34446 || data->id == max34451 ||
|
||||
data->id == adpm12160))
|
||||
ret = pmbus_write_word_data(client, page,
|
||||
MAX34446_MFR_IOUT_AVG, 0);
|
||||
|
||||
@@ -235,9 +259,29 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
|
||||
*/
|
||||
|
||||
int page, rv;
|
||||
bool max34451_na6 = false;
|
||||
|
||||
rv = i2c_smbus_read_word_data(client, PMBUS_MFR_REVISION);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
if (rv >= MAX34451ETNA6_MFR_REV) {
|
||||
max34451_na6 = true;
|
||||
data->info.format[PSC_VOLTAGE_IN] = direct;
|
||||
data->info.format[PSC_CURRENT_IN] = direct;
|
||||
data->info.m[PSC_VOLTAGE_IN] = 1;
|
||||
data->info.b[PSC_VOLTAGE_IN] = 0;
|
||||
data->info.R[PSC_VOLTAGE_IN] = 3;
|
||||
data->info.m[PSC_CURRENT_IN] = 1;
|
||||
data->info.b[PSC_CURRENT_IN] = 0;
|
||||
data->info.R[PSC_CURRENT_IN] = 2;
|
||||
data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT;
|
||||
data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT;
|
||||
}
|
||||
|
||||
for (page = 0; page < 16; page++) {
|
||||
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
fsleep(MAX34440_PAGE_CHANGE_DELAY);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
@@ -251,16 +295,30 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
|
||||
case 0x20:
|
||||
data->info.func[page] = PMBUS_HAVE_VOUT |
|
||||
PMBUS_HAVE_STATUS_VOUT;
|
||||
|
||||
if (max34451_na6)
|
||||
data->info.func[page] |= PMBUS_HAVE_VIN |
|
||||
PMBUS_HAVE_STATUS_INPUT;
|
||||
break;
|
||||
case 0x21:
|
||||
data->info.func[page] = PMBUS_HAVE_VOUT;
|
||||
|
||||
if (max34451_na6)
|
||||
data->info.func[page] |= PMBUS_HAVE_VIN;
|
||||
break;
|
||||
case 0x22:
|
||||
data->info.func[page] = PMBUS_HAVE_IOUT |
|
||||
PMBUS_HAVE_STATUS_IOUT;
|
||||
|
||||
if (max34451_na6)
|
||||
data->info.func[page] |= PMBUS_HAVE_IIN |
|
||||
PMBUS_HAVE_STATUS_INPUT;
|
||||
break;
|
||||
case 0x23:
|
||||
data->info.func[page] = PMBUS_HAVE_IOUT;
|
||||
|
||||
if (max34451_na6)
|
||||
data->info.func[page] |= PMBUS_HAVE_IIN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -271,6 +329,41 @@ static int max34451_set_supported_funcs(struct i2c_client *client,
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info max34440_info[] = {
|
||||
[adpm12160] = {
|
||||
.pages = 19,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
.format[PSC_VOLTAGE_OUT] = direct,
|
||||
.format[PSC_CURRENT_IN] = direct,
|
||||
.format[PSC_CURRENT_OUT] = direct,
|
||||
.format[PSC_TEMPERATURE] = direct,
|
||||
.m[PSC_VOLTAGE_IN] = 1,
|
||||
.b[PSC_VOLTAGE_IN] = 0,
|
||||
.R[PSC_VOLTAGE_IN] = 0,
|
||||
.m[PSC_VOLTAGE_OUT] = 1,
|
||||
.b[PSC_VOLTAGE_OUT] = 0,
|
||||
.R[PSC_VOLTAGE_OUT] = 0,
|
||||
.m[PSC_CURRENT_IN] = 1,
|
||||
.b[PSC_CURRENT_IN] = 0,
|
||||
.R[PSC_CURRENT_IN] = 2,
|
||||
.m[PSC_CURRENT_OUT] = 1,
|
||||
.b[PSC_CURRENT_OUT] = 0,
|
||||
.R[PSC_CURRENT_OUT] = 2,
|
||||
.m[PSC_TEMPERATURE] = 1,
|
||||
.b[PSC_TEMPERATURE] = 0,
|
||||
.R[PSC_TEMPERATURE] = 2,
|
||||
/* absent func below [18] are not for monitoring */
|
||||
.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT,
|
||||
.func[4] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
.func[5] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
.func[6] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
.func[7] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
.func[8] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT,
|
||||
.func[9] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
.func[10] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT,
|
||||
.func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
},
|
||||
[max34440] = {
|
||||
.pages = 14,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
@@ -312,6 +405,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.read_byte_data = max34440_read_byte_data,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34441] = {
|
||||
.pages = 12,
|
||||
@@ -355,6 +449,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.read_byte_data = max34440_read_byte_data,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34446] = {
|
||||
.pages = 7,
|
||||
@@ -392,6 +487,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.read_byte_data = max34440_read_byte_data,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34451] = {
|
||||
.pages = 21,
|
||||
@@ -415,6 +511,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34460] = {
|
||||
.pages = 18,
|
||||
@@ -445,6 +542,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
[max34461] = {
|
||||
.pages = 23,
|
||||
@@ -480,6 +578,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
||||
.func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.read_word_data = max34440_read_word_data,
|
||||
.write_word_data = max34440_write_word_data,
|
||||
.page_change_delay = MAX34440_PAGE_CHANGE_DELAY,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -494,17 +593,23 @@ static int max34440_probe(struct i2c_client *client)
|
||||
return -ENOMEM;
|
||||
data->id = i2c_match_id(max34440_id, client)->driver_data;
|
||||
data->info = max34440_info[data->id];
|
||||
data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT;
|
||||
data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT;
|
||||
|
||||
if (data->id == max34451) {
|
||||
rv = max34451_set_supported_funcs(client, data);
|
||||
if (rv)
|
||||
return rv;
|
||||
} else if (data->id == adpm12160) {
|
||||
data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT;
|
||||
data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT;
|
||||
}
|
||||
|
||||
return pmbus_do_probe(client, &data->info);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max34440_id[] = {
|
||||
{"adpm12160", adpm12160},
|
||||
{"max34440", max34440},
|
||||
{"max34441", max34441},
|
||||
{"max34446", max34446},
|
||||
|
||||
@@ -51,8 +51,8 @@ static const struct regulator_desc mpq7932_regulators_desc[] = {
|
||||
};
|
||||
|
||||
static const struct regulator_desc mpq7932_regulators_desc_one[] = {
|
||||
PMBUS_REGULATOR_STEP_ONE("buck", MPQ7932_N_VOLTAGES,
|
||||
MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN),
|
||||
PMBUS_REGULATOR_STEP_ONE_NODE("buck", MPQ7932_N_VOLTAGES,
|
||||
MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN),
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -4,10 +4,23 @@
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/of_device.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define MPM82504_READ_TEMPERATURE_1_SIGN_POS 9
|
||||
|
||||
enum chips { mpm3695, mpm3695_25, mpm82504, mpq8785 };
|
||||
|
||||
static u16 voltage_scale_loop_max_val[] = {
|
||||
[mpm3695] = GENMASK(9, 0),
|
||||
[mpm3695_25] = GENMASK(11, 0),
|
||||
[mpm82504] = GENMASK(9, 0),
|
||||
[mpq8785] = GENMASK(10, 0),
|
||||
};
|
||||
|
||||
static int mpq8785_identify(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
@@ -34,6 +47,20 @@ static int mpq8785_identify(struct i2c_client *client,
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int mpm82504_read_word_data(struct i2c_client *client, int page,
|
||||
int phase, int reg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pmbus_read_word_data(client, page, phase, reg);
|
||||
|
||||
if (ret < 0 || reg != PMBUS_READ_TEMPERATURE_1)
|
||||
return ret;
|
||||
|
||||
/* Fix PMBUS_READ_TEMPERATURE_1 signedness */
|
||||
return sign_extend32(ret, MPM82504_READ_TEMPERATURE_1_SIGN_POS) & 0xffff;
|
||||
}
|
||||
|
||||
static struct pmbus_driver_info mpq8785_info = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = direct,
|
||||
@@ -53,26 +80,74 @@ static struct pmbus_driver_info mpq8785_info = {
|
||||
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" },
|
||||
{ "mpm3695", mpm3695 },
|
||||
{ "mpm3695-25", mpm3695_25 },
|
||||
{ "mpm82504", mpm82504 },
|
||||
{ "mpq8785", mpq8785 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mpq8785_id);
|
||||
|
||||
static const struct of_device_id __maybe_unused mpq8785_of_match[] = {
|
||||
{ .compatible = "mps,mpq8785" },
|
||||
{ .compatible = "mps,mpm3695", .data = (void *)mpm3695 },
|
||||
{ .compatible = "mps,mpm3695-25", .data = (void *)mpm3695_25 },
|
||||
{ .compatible = "mps,mpm82504", .data = (void *)mpm82504 },
|
||||
{ .compatible = "mps,mpq8785", .data = (void *)mpq8785 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mpq8785_of_match);
|
||||
|
||||
static int mpq8785_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct pmbus_driver_info *info;
|
||||
enum chips chip_id;
|
||||
u32 voltage_scale;
|
||||
int ret;
|
||||
|
||||
info = devm_kmemdup(dev, &mpq8785_info, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dev->of_node)
|
||||
chip_id = (kernel_ulong_t)of_device_get_match_data(dev);
|
||||
else
|
||||
chip_id = (kernel_ulong_t)i2c_get_match_data(client);
|
||||
|
||||
switch (chip_id) {
|
||||
case mpm3695:
|
||||
case mpm3695_25:
|
||||
case mpm82504:
|
||||
info->format[PSC_VOLTAGE_OUT] = direct;
|
||||
info->m[PSC_VOLTAGE_OUT] = 8;
|
||||
info->b[PSC_VOLTAGE_OUT] = 0;
|
||||
info->R[PSC_VOLTAGE_OUT] = 2;
|
||||
info->read_word_data = mpm82504_read_word_data;
|
||||
break;
|
||||
case mpq8785:
|
||||
info->identify = mpq8785_identify;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille",
|
||||
&voltage_scale)) {
|
||||
if (voltage_scale > voltage_scale_loop_max_val[chip_id])
|
||||
return -EINVAL;
|
||||
|
||||
ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP,
|
||||
voltage_scale);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return pmbus_do_probe(client, info);
|
||||
};
|
||||
|
||||
static struct i2c_driver mpq8785_driver = {
|
||||
.driver = {
|
||||
.name = "mpq8785",
|
||||
|
||||
@@ -482,6 +482,7 @@ struct pmbus_driver_info {
|
||||
*/
|
||||
int access_delay; /* in microseconds */
|
||||
int write_delay; /* in microseconds */
|
||||
int page_change_delay; /* in microseconds */
|
||||
};
|
||||
|
||||
/* Regulator ops */
|
||||
@@ -508,11 +509,11 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev,
|
||||
|
||||
#define PMBUS_REGULATOR(_name, _id) PMBUS_REGULATOR_STEP(_name, _id, 0, 0, 0)
|
||||
|
||||
#define PMBUS_REGULATOR_STEP_ONE(_name, _voltages, _step, _min_uV) \
|
||||
#define __PMBUS_REGULATOR_STEP_ONE(_name, _node, _voltages, _step, _min_uV) \
|
||||
{ \
|
||||
.name = (_name), \
|
||||
.of_match = of_match_ptr(_name), \
|
||||
.regulators_node = of_match_ptr("regulators"), \
|
||||
.regulators_node = of_match_ptr(_node), \
|
||||
.ops = &pmbus_regulator_ops, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.owner = THIS_MODULE, \
|
||||
@@ -522,7 +523,19 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev,
|
||||
.init_cb = pmbus_regulator_init_cb, \
|
||||
}
|
||||
|
||||
#define PMBUS_REGULATOR_ONE(_name) PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0)
|
||||
/*
|
||||
* _NODE macros are defined for historic reasons and MUST NOT be used in new
|
||||
* drivers.
|
||||
*/
|
||||
#define PMBUS_REGULATOR_STEP_ONE_NODE(_name, _voltages, _step, _min_uV) \
|
||||
__PMBUS_REGULATOR_STEP_ONE(_name, "regulators", _voltages, _step, _min_uV)
|
||||
|
||||
#define PMBUS_REGULATOR_ONE_NODE(_name) PMBUS_REGULATOR_STEP_ONE_NODE(_name, 0, 0, 0)
|
||||
|
||||
#define PMBUS_REGULATOR_STEP_ONE(_name, _voltages, _step, _min_uV) \
|
||||
__PMBUS_REGULATOR_STEP_ONE(_name, NULL, _voltages, _step, _min_uV)
|
||||
|
||||
#define PMBUS_REGULATOR_ONE(_name) PMBUS_REGULATOR_STEP_ONE(_name, 0, 0, 0)
|
||||
|
||||
/* Function declarations */
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
#define PMBUS_ATTR_ALLOC_SIZE 32
|
||||
#define PMBUS_NAME_SIZE 24
|
||||
|
||||
/*
|
||||
* The type of operation used for picking the delay between
|
||||
* successive pmbus operations.
|
||||
*/
|
||||
#define PMBUS_OP_WRITE BIT(0)
|
||||
#define PMBUS_OP_PAGE_CHANGE BIT(1)
|
||||
|
||||
static int wp = -1;
|
||||
module_param(wp, int, 0444);
|
||||
|
||||
@@ -113,8 +120,8 @@ struct pmbus_data {
|
||||
|
||||
int vout_low[PMBUS_PAGES]; /* voltage low margin */
|
||||
int vout_high[PMBUS_PAGES]; /* voltage high margin */
|
||||
ktime_t write_time; /* Last SMBUS write timestamp */
|
||||
ktime_t access_time; /* Last SMBUS access timestamp */
|
||||
|
||||
ktime_t next_access_backoff; /* Wait until at least this time */
|
||||
};
|
||||
|
||||
struct pmbus_debugfs_entry {
|
||||
@@ -169,32 +176,26 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS");
|
||||
static void pmbus_wait(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
const struct pmbus_driver_info *info = data->info;
|
||||
s64 delta;
|
||||
s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get());
|
||||
|
||||
if (info->access_delay) {
|
||||
delta = ktime_us_delta(ktime_get(), data->access_time);
|
||||
|
||||
if (delta < info->access_delay)
|
||||
fsleep(info->access_delay - delta);
|
||||
} else if (info->write_delay) {
|
||||
delta = ktime_us_delta(ktime_get(), data->write_time);
|
||||
|
||||
if (delta < info->write_delay)
|
||||
fsleep(info->write_delay - delta);
|
||||
}
|
||||
if (delay > 0)
|
||||
fsleep(delay);
|
||||
}
|
||||
|
||||
/* Sets the last accessed timestamp for pmbus_wait */
|
||||
static void pmbus_update_ts(struct i2c_client *client, bool write_op)
|
||||
/* Sets the last operation timestamp for pmbus_wait */
|
||||
static void pmbus_update_ts(struct i2c_client *client, int op)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
const struct pmbus_driver_info *info = data->info;
|
||||
int delay = info->access_delay;
|
||||
|
||||
if (info->access_delay)
|
||||
data->access_time = ktime_get();
|
||||
else if (info->write_delay && write_op)
|
||||
data->write_time = ktime_get();
|
||||
if (op & PMBUS_OP_WRITE)
|
||||
delay = max(delay, info->write_delay);
|
||||
if (op & PMBUS_OP_PAGE_CHANGE)
|
||||
delay = max(delay, info->page_change_delay);
|
||||
|
||||
if (delay > 0)
|
||||
data->next_access_backoff = ktime_add_us(ktime_get(), delay);
|
||||
}
|
||||
|
||||
int pmbus_set_page(struct i2c_client *client, int page, int phase)
|
||||
@@ -209,13 +210,13 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
|
||||
data->info->pages > 1 && page != data->currpage) {
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE | PMBUS_OP_PAGE_CHANGE);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
@@ -229,7 +230,7 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE,
|
||||
phase);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE);
|
||||
if (rv)
|
||||
return rv;
|
||||
}
|
||||
@@ -249,7 +250,7 @@ int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_write_byte(client, value);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -284,7 +285,7 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_write_word_data(client, reg, word);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -405,7 +406,7 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg)
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_read_word_data(client, reg);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -468,7 +469,7 @@ int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg)
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_read_byte_data(client, reg);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -484,7 +485,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value)
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_write_byte_data(client, reg, value);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -520,7 +521,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
|
||||
|
||||
pmbus_wait(client);
|
||||
rv = i2c_smbus_read_block_data(client, reg, data_buf);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -2524,7 +2525,7 @@ static int pmbus_read_coefficients(struct i2c_client *client,
|
||||
rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
|
||||
I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS,
|
||||
I2C_SMBUS_BLOCK_PROC_CALL, &data);
|
||||
pmbus_update_ts(client, true);
|
||||
pmbus_update_ts(client, PMBUS_OP_WRITE);
|
||||
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
@@ -2728,7 +2729,7 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
if (!(data->flags & PMBUS_NO_CAPABILITY)) {
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) {
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC))
|
||||
@@ -2744,13 +2745,13 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
data->read_status = pmbus_read_status_word;
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
if (ret < 0 || ret == 0xffff) {
|
||||
data->read_status = pmbus_read_status_byte;
|
||||
pmbus_wait(client);
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE);
|
||||
pmbus_update_ts(client, false);
|
||||
pmbus_update_ts(client, 0);
|
||||
|
||||
if (ret < 0 || ret == 0xff) {
|
||||
dev_err(dev, "PMBus status register not found\n");
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "pmbus.h"
|
||||
|
||||
static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = {
|
||||
PMBUS_REGULATOR_ONE("vout"),
|
||||
PMBUS_REGULATOR_ONE_NODE("vout"),
|
||||
};
|
||||
|
||||
struct tda38640_data {
|
||||
|
||||
@@ -333,7 +333,7 @@ static int tps25990_write_byte_data(struct i2c_client *client,
|
||||
|
||||
#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR)
|
||||
static const struct regulator_desc tps25990_reg_desc[] = {
|
||||
PMBUS_REGULATOR_ONE("vout"),
|
||||
PMBUS_REGULATOR_ONE_NODE("vout"),
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -212,8 +212,8 @@ static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset)
|
||||
return !!(ret & UCD9000_GPIO_CONFIG_STATUS);
|
||||
}
|
||||
|
||||
static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
|
||||
int value)
|
||||
static int ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
|
||||
int value)
|
||||
{
|
||||
struct i2c_client *client = gpiochip_get_data(gc);
|
||||
int ret;
|
||||
@@ -222,17 +222,17 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
|
||||
if (ret < 0) {
|
||||
dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n",
|
||||
offset, ret);
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
if (ret & UCD9000_GPIO_CONFIG_STATUS)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
ret |= UCD9000_GPIO_CONFIG_STATUS;
|
||||
} else {
|
||||
if (!(ret & UCD9000_GPIO_CONFIG_STATUS))
|
||||
return;
|
||||
return 0;
|
||||
|
||||
ret &= ~UCD9000_GPIO_CONFIG_STATUS;
|
||||
}
|
||||
@@ -244,7 +244,7 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
|
||||
if (ret < 0) {
|
||||
dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n",
|
||||
offset, ret);
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret &= ~UCD9000_GPIO_CONFIG_ENABLE;
|
||||
@@ -253,6 +253,8 @@ static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset,
|
||||
if (ret < 0)
|
||||
dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n",
|
||||
offset, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ucd9000_gpio_get_direction(struct gpio_chip *gc,
|
||||
@@ -362,7 +364,7 @@ static void ucd9000_probe_gpio(struct i2c_client *client,
|
||||
data->gpio.direction_input = ucd9000_gpio_direction_input;
|
||||
data->gpio.direction_output = ucd9000_gpio_direction_output;
|
||||
data->gpio.get = ucd9000_gpio_get;
|
||||
data->gpio.set = ucd9000_gpio_set;
|
||||
data->gpio.set_rv = ucd9000_gpio_set;
|
||||
data->gpio.can_sleep = true;
|
||||
data->gpio.base = -1;
|
||||
data->gpio.parent = &client->dev;
|
||||
|
||||
@@ -620,8 +620,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
if (tach->irq == -EPROBE_DEFER)
|
||||
return tach->irq;
|
||||
if (tach->irq > 0) {
|
||||
ret = devm_request_irq(dev, tach->irq, pulse_handler, 0,
|
||||
pdev->name, tach);
|
||||
ret = devm_request_irq(dev, tach->irq, pulse_handler,
|
||||
IRQF_NO_THREAD, pdev->name, tach);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to request interrupt: %d\n",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/mfd/qnap-mcu.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
+220
-139
@@ -66,6 +66,9 @@ static const unsigned short normal_i2c[] = {
|
||||
#define SPD5118_EEPROM_BASE 0x80
|
||||
#define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES)
|
||||
|
||||
#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6)
|
||||
#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1)
|
||||
|
||||
/* Temperature unit in millicelsius */
|
||||
#define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4)
|
||||
/* Representable temperature range in millicelsius */
|
||||
@@ -75,6 +78,7 @@ static const unsigned short normal_i2c[] = {
|
||||
struct spd5118_data {
|
||||
struct regmap *regmap;
|
||||
struct mutex nvmem_lock;
|
||||
bool is_16bit;
|
||||
};
|
||||
|
||||
/* hwmon */
|
||||
@@ -305,51 +309,6 @@ static bool spd5118_vendor_valid(u8 bank, u8 id)
|
||||
return id && id != 0x7f;
|
||||
}
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int regval;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval != 0x5118)
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
|
||||
if (regval < 0 || !spd5118_vendor_valid(regval & 0xff, regval >> 8))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_CAPABILITY);
|
||||
if (regval < 0)
|
||||
return -ENODEV;
|
||||
if (!(regval & SPD5118_CAP_TS_SUPPORT) || (regval & 0xfc))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CLR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_ERROR_CLR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_REVISION);
|
||||
if (regval < 0 || (regval & 0xc1))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CONFIG);
|
||||
if (regval < 0)
|
||||
return -ENODEV;
|
||||
if (regval & ~SPD5118_TS_DISABLE)
|
||||
return -ENODEV;
|
||||
|
||||
strscpy(info->type, "spd5118", I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *spd5118_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
@@ -376,11 +335,12 @@ static const struct hwmon_chip_info spd5118_chip_info = {
|
||||
|
||||
/* nvmem */
|
||||
|
||||
static ssize_t spd5118_nvmem_read_page(struct regmap *regmap, char *buf,
|
||||
static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf,
|
||||
unsigned int offset, size_t count)
|
||||
{
|
||||
int addr = (offset >> SPD5118_PAGE_SHIFT) * 0x100 + SPD5118_EEPROM_BASE;
|
||||
int err;
|
||||
int page = offset >> SPD5118_PAGE_SHIFT;
|
||||
struct regmap *regmap = data->regmap;
|
||||
int err, addr;
|
||||
|
||||
offset &= SPD5118_PAGE_MASK;
|
||||
|
||||
@@ -388,6 +348,12 @@ static ssize_t spd5118_nvmem_read_page(struct regmap *regmap, char *buf,
|
||||
if (offset + count > SPD5118_PAGE_SIZE)
|
||||
count = SPD5118_PAGE_SIZE - offset;
|
||||
|
||||
if (data->is_16bit) {
|
||||
addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) |
|
||||
(PAGE_ADDR1_4(page) << 8);
|
||||
} else {
|
||||
addr = page * 0x100 + SPD5118_EEPROM_BASE;
|
||||
}
|
||||
err = regmap_bulk_read(regmap, addr + offset, buf, count);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -410,7 +376,7 @@ static int spd5118_nvmem_read(void *priv, unsigned int off, void *val, size_t co
|
||||
mutex_lock(&data->nvmem_lock);
|
||||
|
||||
while (count) {
|
||||
ret = spd5118_nvmem_read_page(data->regmap, buf, off, count);
|
||||
ret = spd5118_nvmem_read_page(data, buf, off, count);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&data->nvmem_lock);
|
||||
return ret;
|
||||
@@ -483,7 +449,7 @@ static bool spd5118_volatile_reg(struct device *dev, unsigned int reg)
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_range_cfg spd5118_regmap_range_cfg[] = {
|
||||
static const struct regmap_range_cfg spd5118_i2c_regmap_range_cfg[] = {
|
||||
{
|
||||
.selector_reg = SPD5118_REG_I2C_LEGACY_MODE,
|
||||
.selector_mask = SPD5118_LEGACY_PAGE_MASK,
|
||||
@@ -495,7 +461,7 @@ static const struct regmap_range_cfg spd5118_regmap_range_cfg[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static const struct regmap_config spd5118_regmap_config = {
|
||||
static const struct regmap_config spd5118_regmap8_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x7ff,
|
||||
@@ -503,89 +469,76 @@ static const struct regmap_config spd5118_regmap_config = {
|
||||
.volatile_reg = spd5118_volatile_reg,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
|
||||
.ranges = spd5118_regmap_range_cfg,
|
||||
.num_ranges = ARRAY_SIZE(spd5118_regmap_range_cfg),
|
||||
.ranges = spd5118_i2c_regmap_range_cfg,
|
||||
.num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg),
|
||||
};
|
||||
|
||||
static int spd5118_init(struct i2c_client *client)
|
||||
static const struct regmap_config spd5118_regmap16_config = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x7ff,
|
||||
.writeable_reg = spd5118_writeable_reg,
|
||||
.volatile_reg = spd5118_volatile_reg,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
};
|
||||
|
||||
static int spd5118_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int err, regval, mode;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval < 0 || (regval && regval != 0x5118))
|
||||
return -ENODEV;
|
||||
struct spd5118_data *data = dev_get_drvdata(dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
u32 regval;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* If the device type registers return 0, it is possible that the chip
|
||||
* has a non-zero page selected and takes the specification literally,
|
||||
* i.e. disables access to volatile registers besides the page register
|
||||
* if the page is not 0. Try to identify such chips.
|
||||
* Make sure the configuration register in the regmap cache is current
|
||||
* before bypassing it.
|
||||
*/
|
||||
if (!regval) {
|
||||
/* Vendor ID registers must also be 0 */
|
||||
regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* The selected page in MR11 must not be 0 */
|
||||
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
|
||||
if (mode < 0 || (mode & ~SPD5118_LEGACY_MODE_MASK) ||
|
||||
!(mode & SPD5118_LEGACY_PAGE_MASK))
|
||||
return -ENODEV;
|
||||
regcache_cache_bypass(regmap, true);
|
||||
regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG, SPD5118_TS_DISABLE,
|
||||
SPD5118_TS_DISABLE);
|
||||
regcache_cache_bypass(regmap, false);
|
||||
|
||||
err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE,
|
||||
mode & SPD5118_LEGACY_MODE_ADDR);
|
||||
if (err)
|
||||
return -ENODEV;
|
||||
regcache_cache_only(regmap, true);
|
||||
regcache_mark_dirty(regmap);
|
||||
|
||||
/*
|
||||
* If the device type registers are still bad after selecting
|
||||
* page 0, this is not a SPD5118 device. Restore original
|
||||
* legacy mode register value and abort.
|
||||
*/
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval != 0x5118) {
|
||||
i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/* We are reasonably sure that this is really a SPD5118 hub controller */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spd5118_probe(struct i2c_client *client)
|
||||
static int spd5118_resume(struct device *dev)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
unsigned int regval, revision, vendor, bank;
|
||||
struct spd5118_data *data = dev_get_drvdata(dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
|
||||
regcache_cache_only(regmap, false);
|
||||
return regcache_sync(regmap);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume);
|
||||
|
||||
static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
|
||||
bool is_16bit)
|
||||
{
|
||||
unsigned int capability, revision, vendor, bank;
|
||||
struct spd5118_data *data;
|
||||
struct device *hwmon_dev;
|
||||
struct regmap *regmap;
|
||||
int err;
|
||||
|
||||
err = spd5118_init(client);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &spd5118_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
|
||||
|
||||
err = regmap_read(regmap, SPD5118_REG_CAPABILITY, ®val);
|
||||
err = regmap_read(regmap, SPD5118_REG_CAPABILITY, &capability);
|
||||
if (err)
|
||||
return err;
|
||||
if (!(regval & SPD5118_CAP_TS_SUPPORT))
|
||||
if (!(capability & SPD5118_CAP_TS_SUPPORT))
|
||||
return -ENODEV;
|
||||
|
||||
data->is_16bit = is_16bit;
|
||||
|
||||
err = regmap_read(regmap, SPD5118_REG_REVISION, &revision);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -627,48 +580,176 @@ static int spd5118_probe(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spd5118_suspend(struct device *dev)
|
||||
/* I2C */
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
{
|
||||
struct spd5118_data *data = dev_get_drvdata(dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
u32 regval;
|
||||
int err;
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int regval;
|
||||
|
||||
/*
|
||||
* Make sure the configuration register in the regmap cache is current
|
||||
* before bypassing it.
|
||||
*/
|
||||
err = regmap_read(regmap, SPD5118_REG_TEMP_CONFIG, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
regcache_cache_bypass(regmap, true);
|
||||
regmap_update_bits(regmap, SPD5118_REG_TEMP_CONFIG, SPD5118_TS_DISABLE,
|
||||
SPD5118_TS_DISABLE);
|
||||
regcache_cache_bypass(regmap, false);
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval != 0x5118)
|
||||
return -ENODEV;
|
||||
|
||||
regcache_cache_only(regmap, true);
|
||||
regcache_mark_dirty(regmap);
|
||||
regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
|
||||
if (regval < 0 || !spd5118_vendor_valid(regval & 0xff, regval >> 8))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_CAPABILITY);
|
||||
if (regval < 0)
|
||||
return -ENODEV;
|
||||
if (!(regval & SPD5118_CAP_TS_SUPPORT) || (regval & 0xfc))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CLR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_ERROR_CLR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_REVISION);
|
||||
if (regval < 0 || (regval & 0xc1))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_byte_data(client, SPD5118_REG_TEMP_CONFIG);
|
||||
if (regval < 0)
|
||||
return -ENODEV;
|
||||
if (regval & ~SPD5118_TS_DISABLE)
|
||||
return -ENODEV;
|
||||
|
||||
strscpy(info->type, "spd5118", I2C_NAME_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spd5118_resume(struct device *dev)
|
||||
static int spd5118_i2c_init(struct i2c_client *client)
|
||||
{
|
||||
struct spd5118_data *data = dev_get_drvdata(dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int err, regval, mode;
|
||||
|
||||
regcache_cache_only(regmap, false);
|
||||
return regcache_sync(regmap);
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval < 0 || (regval && regval != 0x5118))
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* If the device type registers return 0, it is possible that the chip
|
||||
* has a non-zero page selected and takes the specification literally,
|
||||
* i.e. disables access to volatile registers besides the page register
|
||||
* if the page is not 0. The Renesas/ITD SPD5118 Hub Controller is known
|
||||
* to show this behavior. Try to identify such chips.
|
||||
*/
|
||||
if (!regval) {
|
||||
/* Vendor ID registers must also be 0 */
|
||||
regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR);
|
||||
if (regval)
|
||||
return -ENODEV;
|
||||
|
||||
/* The selected page in MR11 must not be 0 */
|
||||
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
|
||||
if (mode < 0 || (mode & ~SPD5118_LEGACY_MODE_MASK) ||
|
||||
!(mode & SPD5118_LEGACY_PAGE_MASK))
|
||||
return -ENODEV;
|
||||
|
||||
err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE,
|
||||
mode & SPD5118_LEGACY_MODE_ADDR);
|
||||
if (err)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* If the device type registers are still bad after selecting
|
||||
* page 0, this is not a SPD5118 device. Restore original
|
||||
* legacy mode register value and abort.
|
||||
*/
|
||||
regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE);
|
||||
if (regval != 0x5118) {
|
||||
i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/* We are reasonably sure that this is really a SPD5118 hub controller */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume);
|
||||
/*
|
||||
* 16-bit addressing note:
|
||||
*
|
||||
* If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses
|
||||
* SMBus operations as alternative. To simulate a read operation with a 16-bit
|
||||
* address, it writes the address using i2c_smbus_write_byte_data(), followed
|
||||
* by one or more calls to i2c_smbus_read_byte() to read the data.
|
||||
* Per spd5118 standard, a read operation after writing the address must start
|
||||
* with <Sr> (Repeat Start). However, a SMBus read byte operation starts with
|
||||
* <S> (Start). This resets the register address in the spd5118 chip. As result,
|
||||
* i2c_smbus_read_byte() always returns data from register address 0x00.
|
||||
*
|
||||
* A working alternative to access chips with 16-bit register addresses in the
|
||||
* absence of I2C_FUNC_I2C support is not known.
|
||||
*
|
||||
* For this reason, 16-bit addressing can only be supported with I2C if the
|
||||
* adapter supports I2C_FUNC_I2C.
|
||||
*
|
||||
* For I2C, the addressing mode selected by the BIOS must not be changed.
|
||||
* Experiments show that at least some PC BIOS versions will not change the
|
||||
* addressing mode on a soft reboot and end up in setup, claiming that some
|
||||
* configuration change happened. This will happen again after a power cycle,
|
||||
* which does reset the addressing mode. To prevent this from happening,
|
||||
* detect if 16-bit addressing is enabled and always use the currently
|
||||
* configured addressing mode.
|
||||
*/
|
||||
|
||||
static const struct i2c_device_id spd5118_id[] = {
|
||||
static int spd5118_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
const struct regmap_config *config;
|
||||
struct device *dev = &client->dev;
|
||||
struct regmap *regmap;
|
||||
int err, mode;
|
||||
bool is_16bit;
|
||||
|
||||
err = spd5118_i2c_init(client);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
|
||||
if (mode < 0)
|
||||
return mode;
|
||||
|
||||
is_16bit = mode & SPD5118_LEGACY_MODE_ADDR;
|
||||
if (is_16bit) {
|
||||
/*
|
||||
* See 16-bit addressing note above explaining why it is
|
||||
* necessary to check for I2C_FUNC_I2C support here.
|
||||
*/
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
dev_err(dev, "Adapter does not support 16-bit register addresses\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
config = &spd5118_regmap16_config;
|
||||
} else {
|
||||
config = &spd5118_regmap8_config;
|
||||
}
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
|
||||
|
||||
return spd5118_common_probe(dev, regmap, is_16bit);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id spd5118_i2c_id[] = {
|
||||
{ "spd5118" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, spd5118_id);
|
||||
MODULE_DEVICE_TABLE(i2c, spd5118_i2c_id);
|
||||
|
||||
static const struct of_device_id spd5118_of_ids[] = {
|
||||
{ .compatible = "jedec,spd5118", },
|
||||
@@ -676,20 +757,20 @@ static const struct of_device_id spd5118_of_ids[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spd5118_of_ids);
|
||||
|
||||
static struct i2c_driver spd5118_driver = {
|
||||
static struct i2c_driver spd5118_i2c_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "spd5118",
|
||||
.of_match_table = spd5118_of_ids,
|
||||
.pm = pm_sleep_ptr(&spd5118_pm_ops),
|
||||
},
|
||||
.probe = spd5118_probe,
|
||||
.id_table = spd5118_id,
|
||||
.probe = spd5118_i2c_probe,
|
||||
.id_table = spd5118_i2c_id,
|
||||
.detect = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? spd5118_detect : NULL,
|
||||
.address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL,
|
||||
};
|
||||
|
||||
module_i2c_driver(spd5118_driver);
|
||||
module_i2c_driver(spd5118_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("René Rebe <rene@exactcode.de>");
|
||||
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define DRIVER_NAME "tmp102"
|
||||
@@ -204,6 +205,10 @@ static int tmp102_probe(struct i2c_client *client)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = devm_regulator_get_enable_optional(dev, "vcc");
|
||||
if (err < 0 && err != -ENODEV)
|
||||
return dev_err_probe(dev, err, "Failed to enable regulator\n");
|
||||
|
||||
tmp102 = devm_kzalloc(dev, sizeof(*tmp102), GFP_KERNEL);
|
||||
if (!tmp102)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -103,8 +103,6 @@ struct xgene_hwmon_dev {
|
||||
struct device *hwmon_dev;
|
||||
bool temp_critical_alarm;
|
||||
|
||||
phys_addr_t comm_base_addr;
|
||||
void *pcc_comm_addr;
|
||||
unsigned int usecs_lat;
|
||||
};
|
||||
|
||||
@@ -125,7 +123,8 @@ static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask)
|
||||
|
||||
static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
|
||||
{
|
||||
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
|
||||
struct acpi_pcct_shared_memory __iomem *generic_comm_base =
|
||||
ctx->pcc_chan->shmem;
|
||||
u32 *ptr = (void *)(generic_comm_base + 1);
|
||||
int rc, i;
|
||||
u16 val;
|
||||
@@ -523,7 +522,8 @@ static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg)
|
||||
static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
|
||||
{
|
||||
struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl);
|
||||
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
|
||||
struct acpi_pcct_shared_memory __iomem *generic_comm_base =
|
||||
ctx->pcc_chan->shmem;
|
||||
struct slimpro_resp_msg amsg;
|
||||
|
||||
/*
|
||||
@@ -649,7 +649,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
|
||||
} else {
|
||||
struct pcc_mbox_chan *pcc_chan;
|
||||
const struct acpi_device_id *acpi_id;
|
||||
int version;
|
||||
|
||||
acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table,
|
||||
&pdev->dev);
|
||||
@@ -658,8 +657,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
|
||||
goto out_mbox_free;
|
||||
}
|
||||
|
||||
version = (int)acpi_id->driver_data;
|
||||
|
||||
if (device_property_read_u32(&pdev->dev, "pcc-channel",
|
||||
&ctx->mbox_idx)) {
|
||||
dev_err(&pdev->dev, "no pcc-channel property\n");
|
||||
@@ -685,34 +682,6 @@ static int xgene_hwmon_probe(struct platform_device *pdev)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the shared communication region
|
||||
* for the OS and Platform to communicate over.
|
||||
*/
|
||||
ctx->comm_base_addr = pcc_chan->shmem_base_addr;
|
||||
if (ctx->comm_base_addr) {
|
||||
if (version == XGENE_HWMON_V2)
|
||||
ctx->pcc_comm_addr = (void __force *)devm_ioremap(&pdev->dev,
|
||||
ctx->comm_base_addr,
|
||||
pcc_chan->shmem_size);
|
||||
else
|
||||
ctx->pcc_comm_addr = devm_memremap(&pdev->dev,
|
||||
ctx->comm_base_addr,
|
||||
pcc_chan->shmem_size,
|
||||
MEMREMAP_WB);
|
||||
} else {
|
||||
dev_err(&pdev->dev, "Failed to get PCC comm region\n");
|
||||
rc = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (IS_ERR_OR_NULL(ctx->pcc_comm_addr)) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to ioremap PCC comm region\n");
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* pcc_chan->latency is just a Nominal value. In reality
|
||||
* the remote processor could be much slower to reply.
|
||||
|
||||
Reference in New Issue
Block a user