summaryrefslogtreecommitdiffstats
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-07 13:11:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-07 13:11:22 +0000
commitb20732900e4636a467c0183a47f7396700f5f743 (patch)
tree42f079ff82e701ebcb76829974b4caca3e5b6798 /drivers/hwmon
parentAdding upstream version 6.8.12. (diff)
downloadlinux-b20732900e4636a467c0183a47f7396700f5f743.tar.xz
linux-b20732900e4636a467c0183a47f7396700f5f743.zip
Adding upstream version 6.9.7.upstream/6.9.7
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig76
-rw-r--r--drivers/hwmon/Makefile7
-rw-r--r--drivers/hwmon/adm1177.c1
-rw-r--r--drivers/hwmon/adt7310.c2
-rw-r--r--drivers/hwmon/adt7410.c4
-rw-r--r--drivers/hwmon/aspeed-g6-pwm-tach.c549
-rw-r--r--drivers/hwmon/asus_rog_ryujin.c609
-rw-r--r--drivers/hwmon/axi-fan-control.c75
-rw-r--r--drivers/hwmon/chipcap2.c822
-rw-r--r--drivers/hwmon/coretemp.c206
-rw-r--r--drivers/hwmon/dell-smm-hwmon.c16
-rw-r--r--drivers/hwmon/ds1621.c1
-rw-r--r--drivers/hwmon/ds620.c1
-rw-r--r--drivers/hwmon/emc1403.c2
-rw-r--r--drivers/hwmon/emc2305.c5
-rw-r--r--drivers/hwmon/fam15h_power.c2
-rw-r--r--drivers/hwmon/hwmon.c3
-rw-r--r--drivers/hwmon/ina209.c1
-rw-r--r--drivers/hwmon/ina238.c1
-rw-r--r--drivers/hwmon/ina3221.c2
-rw-r--r--drivers/hwmon/intel-m10-bmc-hwmon.c2
-rw-r--r--drivers/hwmon/jc42.c2
-rw-r--r--drivers/hwmon/lm83.c2
-rw-r--r--drivers/hwmon/ltc2992.c4
-rw-r--r--drivers/hwmon/ltc4282.c1782
-rw-r--r--drivers/hwmon/max127.c1
-rw-r--r--drivers/hwmon/max31760.c3
-rw-r--r--drivers/hwmon/max31790.c1
-rw-r--r--drivers/hwmon/max31827.c1
-rw-r--r--drivers/hwmon/max6621.c1
-rw-r--r--drivers/hwmon/max6697.c1
-rw-r--r--drivers/hwmon/nct6683.c3
-rw-r--r--drivers/hwmon/nct7802.c2
-rw-r--r--drivers/hwmon/nzxt-kraken3.c1008
-rw-r--r--drivers/hwmon/occ/p8_i2c.c1
-rw-r--r--drivers/hwmon/oxp-sensors.c10
-rw-r--r--drivers/hwmon/pmbus/Kconfig9
-rw-r--r--drivers/hwmon/pmbus/Makefile1
-rw-r--r--drivers/hwmon/pmbus/ir36021.c1
-rw-r--r--drivers/hwmon/pmbus/ir38064.c2
-rw-r--r--drivers/hwmon/pmbus/lm25066.c2
-rw-r--r--drivers/hwmon/pmbus/mpq8785.c90
-rw-r--r--drivers/hwmon/pmbus/pmbus_core.c2
-rw-r--r--drivers/hwmon/pmbus/tda38640.c2
-rw-r--r--drivers/hwmon/powr1220.c1
-rw-r--r--drivers/hwmon/pt5161l.c667
-rw-r--r--drivers/hwmon/sbrmi.c1
-rw-r--r--drivers/hwmon/sbtsi_temp.c1
-rw-r--r--drivers/hwmon/sch5627.c2
-rw-r--r--drivers/hwmon/sht3x.c66
-rw-r--r--drivers/hwmon/shtc1.c2
-rw-r--r--drivers/hwmon/sis5595.c8
-rw-r--r--drivers/hwmon/surface_fan.c91
-rw-r--r--drivers/hwmon/tmp401.c2
-rw-r--r--drivers/hwmon/ultra45_env.c2
-rw-r--r--drivers/hwmon/w83773g.c1
56 files changed, 5977 insertions, 185 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index a608264da8..83945397b6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -301,6 +301,16 @@ config SENSORS_ASC7621
This driver can also be built as a module. If so, the module
will be called asc7621.
+config SENSORS_ASUS_ROG_RYUJIN
+ tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver"
+ depends on HID
+ help
+ If you say yes here you get support for the fans and sensors of
+ the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler.
+
+ This driver can also be built as a module. If so, the module
+ will be called asus_rog_ryujin.
+
config SENSORS_AXI_FAN_CONTROL
tristate "Analog Devices FAN Control HDL Core driver"
help
@@ -412,6 +422,17 @@ config SENSORS_ASPEED
This driver can also be built as a module. If so, the module
will be called aspeed_pwm_tacho.
+config SENSORS_ASPEED_G6
+ tristate "ASPEED g6 PWM and Fan tach driver"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on PWM
+ help
+ This driver provides support for ASPEED G6 PWM and Fan Tach
+ controllers.
+
+ This driver can also be built as a module. If so, the module
+ will be called aspeed_pwm_tacho.
+
config SENSORS_ATXP1
tristate "Attansic ATXP1 VID controller"
depends on I2C
@@ -452,6 +473,16 @@ config SENSORS_BT1_PVT_ALARMS
the data conversion will be periodically performed and the data will be
saved in the internal driver cache.
+config SENSORS_CHIPCAP2
+ tristate "Amphenol ChipCap 2 relative humidity and temperature sensor"
+ depends on I2C
+ help
+ Say yes here to build support for the Amphenol ChipCap 2
+ relative humidity and temperature sensor.
+
+ To compile this driver as a module, choose M here: the module
+ will be called chipcap2.
+
config SENSORS_CORSAIR_CPRO
tristate "Corsair Commander Pro controller"
depends on HID
@@ -1038,6 +1069,17 @@ config SENSORS_LTC4261
This driver can also be built as a module. If so, the module will
be called ltc4261.
+config SENSORS_LTC4282
+ tristate "Analog Devices LTC4282"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for Analog Devices LTC4282
+ High Current Hot Swap Controller I2C interface.
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc4282.
+
config SENSORS_LTQ_CPUTEMP
bool "Lantiq cpu temperature sensor driver"
depends on SOC_XWAY
@@ -1674,6 +1716,16 @@ config SENSORS_NZXT_KRAKEN2
This driver can also be built as a module. If so, the module
will be called nzxt-kraken2.
+config SENSORS_NZXT_KRAKEN3
+ tristate "NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers"
+ depends on USB_HID
+ help
+ If you say yes here you get support for hardware monitoring for the
+ NZXT Kraken X53/X63/X73, Z53/Z63/Z73 all-in-one CPU liquid coolers.
+
+ This driver can also be built as a module. If so, the module
+ will be called nzxt-kraken3.
+
config SENSORS_NZXT_SMART2
tristate "NZXT RGB & Fan Controller/Smart Device v2"
depends on USB_HID
@@ -1714,6 +1766,16 @@ source "drivers/hwmon/peci/Kconfig"
source "drivers/hwmon/pmbus/Kconfig"
+config SENSORS_PT5161L
+ tristate "Astera Labs PT5161L PCIe retimer hardware monitoring"
+ depends on I2C
+ help
+ If you say yes here you get support for temperature monitoring
+ on the Astera Labs PT5161L PCIe retimer.
+
+ This driver can also be built as a module. If so, the module
+ will be called pt5161l.
+
config SENSORS_PWM_FAN
tristate "PWM fan"
depends on (PWM && OF) || COMPILE_TEST
@@ -1994,6 +2056,20 @@ config SENSORS_SFCTEMP
This driver can also be built as a module. If so, the module
will be called sfctemp.
+config SENSORS_SURFACE_FAN
+ tristate "Surface Fan Driver"
+ depends on SURFACE_AGGREGATOR
+ depends on SURFACE_AGGREGATOR_BUS
+ help
+ Driver that provides monitoring of the fan on Surface Pro devices that
+ have a fan, like the Surface Pro 9.
+
+ This makes the fan's current speed accessible through the hwmon
+ system. It does not provide control over the fan, the firmware is
+ responsible for that, this driver merely provides monitoring.
+
+ Select M or Y here, if you want to be able to read the fan's speed.
+
config SENSORS_ADC128D818
tristate "Texas Instruments ADC128D818"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 47be39af5c..5c31808f63 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -55,9 +55,12 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
+obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
+obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
+obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
@@ -136,6 +139,7 @@ obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
+obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX127) += max127.o
@@ -173,6 +177,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o
obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o
+obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o
obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
@@ -180,6 +185,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
+obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
@@ -201,6 +207,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o
obj-$(CONFIG_SENSORS_STTS751) += stts751.o
+obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o
obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o
obj-$(CONFIG_SENSORS_TC74) += tc74.o
diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c
index 60a893f271..3390102d2d 100644
--- a/drivers/hwmon/adm1177.c
+++ b/drivers/hwmon/adm1177.c
@@ -250,7 +250,6 @@ static const struct of_device_id adm1177_dt_ids[] = {
MODULE_DEVICE_TABLE(of, adm1177_dt_ids);
static struct i2c_driver adm1177_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "adm1177",
.of_match_table = adm1177_dt_ids,
diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c
index 067865f488..25281739aa 100644
--- a/drivers/hwmon/adt7310.c
+++ b/drivers/hwmon/adt7310.c
@@ -124,7 +124,7 @@ static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config adt7310_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7310_regmap_is_volatile,
.reg_read = adt7310_reg_read,
.reg_write = adt7310_reg_write,
diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c
index 9525067793..d15f64d4b6 100644
--- a/drivers/hwmon/adt7410.c
+++ b/drivers/hwmon/adt7410.c
@@ -69,7 +69,7 @@ static const struct regmap_config adt7410_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.max_register = ADT7X10_ID,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = adt7410_regmap_is_volatile,
.reg_read = adt7410_reg_read,
.reg_write = adt7410_reg_write,
@@ -95,14 +95,12 @@ static const struct i2c_device_id adt7410_ids[] = {
MODULE_DEVICE_TABLE(i2c, adt7410_ids);
static struct i2c_driver adt7410_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "adt7410",
.pm = pm_sleep_ptr(&adt7x10_dev_pm_ops),
},
.probe = adt7410_i2c_probe,
.id_table = adt7410_ids,
- .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b),
};
module_i2c_driver(adt7410_driver);
diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c
new file mode 100644
index 0000000000..597b3b019d
--- /dev/null
+++ b/drivers/hwmon/aspeed-g6-pwm-tach.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Aspeed Technology Inc.
+ *
+ * PWM/TACH controller driver for Aspeed ast2600 SoCs.
+ * This drivers doesn't support earlier version of the IP.
+ *
+ * The hardware operates in time quantities of length
+ * Q := (DIV_L + 1) << DIV_H / input-clk
+ * The length of a PWM period is (DUTY_CYCLE_PERIOD + 1) * Q.
+ * The maximal value for DUTY_CYCLE_PERIOD is used here to provide
+ * a fine grained selection for the duty cycle.
+ *
+ * This driver uses DUTY_CYCLE_RISING_POINT = 0, so from the start of a
+ * period the output is active until DUTY_CYCLE_FALLING_POINT * Q. Note
+ * that if DUTY_CYCLE_RISING_POINT = DUTY_CYCLE_FALLING_POINT the output is
+ * always active.
+ *
+ * Register usage:
+ * PIN_ENABLE: When it is unset the pwm controller will emit inactive level to the external.
+ * Use to determine whether the PWM channel is enabled or disabled
+ * CLK_ENABLE: When it is unset the pwm controller will assert the duty counter reset and
+ * emit inactive level to the PIN_ENABLE mux after that the driver can still change the pwm period
+ * and duty and the value will apply when CLK_ENABLE be set again.
+ * Use to determine whether duty_cycle bigger than 0.
+ * PWM_ASPEED_CTRL_INVERSE: When it is toggled the output value will inverse immediately.
+ * PWM_ASPEED_DUTY_CYCLE_FALLING_POINT/PWM_ASPEED_DUTY_CYCLE_RISING_POINT: When these two
+ * values are equal it means the duty cycle = 100%.
+ *
+ * The glitch may generate at:
+ * - Enabled changing when the duty_cycle bigger than 0% and less than 100%.
+ * - Polarity changing when the duty_cycle bigger than 0% and less than 100%.
+ *
+ * Limitations:
+ * - When changing both duty cycle and period, we cannot prevent in
+ * software that the output might produce a period with mixed
+ * settings.
+ * - Disabling the PWM doesn't complete the current period.
+ *
+ * Improvements:
+ * - When only changing one of duty cycle or period, our pwm controller will not
+ * generate the glitch, the configure will change at next cycle of pwm.
+ * This improvement can disable/enable through PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/sysfs.h>
+
+/* The channel number of Aspeed pwm controller */
+#define PWM_ASPEED_NR_PWMS 16
+/* PWM Control Register */
+#define PWM_ASPEED_CTRL(ch) ((ch) * 0x10 + 0x00)
+#define PWM_ASPEED_CTRL_LOAD_SEL_RISING_AS_WDT BIT(19)
+#define PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE BIT(18)
+#define PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE BIT(17)
+#define PWM_ASPEED_CTRL_CLK_ENABLE BIT(16)
+#define PWM_ASPEED_CTRL_LEVEL_OUTPUT BIT(15)
+#define PWM_ASPEED_CTRL_INVERSE BIT(14)
+#define PWM_ASPEED_CTRL_OPEN_DRAIN_ENABLE BIT(13)
+#define PWM_ASPEED_CTRL_PIN_ENABLE BIT(12)
+#define PWM_ASPEED_CTRL_CLK_DIV_H GENMASK(11, 8)
+#define PWM_ASPEED_CTRL_CLK_DIV_L GENMASK(7, 0)
+
+/* PWM Duty Cycle Register */
+#define PWM_ASPEED_DUTY_CYCLE(ch) ((ch) * 0x10 + 0x04)
+#define PWM_ASPEED_DUTY_CYCLE_PERIOD GENMASK(31, 24)
+#define PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT GENMASK(23, 16)
+#define PWM_ASPEED_DUTY_CYCLE_FALLING_POINT GENMASK(15, 8)
+#define PWM_ASPEED_DUTY_CYCLE_RISING_POINT GENMASK(7, 0)
+
+/* PWM fixed value */
+#define PWM_ASPEED_FIXED_PERIOD FIELD_MAX(PWM_ASPEED_DUTY_CYCLE_PERIOD)
+
+/* The channel number of Aspeed tach controller */
+#define TACH_ASPEED_NR_TACHS 16
+/* TACH Control Register */
+#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08)
+#define TACH_ASPEED_IER BIT(31)
+#define TACH_ASPEED_INVERS_LIMIT BIT(30)
+#define TACH_ASPEED_LOOPBACK BIT(29)
+#define TACH_ASPEED_ENABLE BIT(28)
+#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26)
+#define TACH_ASPEED_DEBOUNCE_BIT 26
+#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24)
+#define TACH_ASPEED_IO_EDGE_BIT 24
+#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20)
+#define TACH_ASPEED_CLK_DIV_BIT 20
+#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0)
+/* [27:26] */
+#define DEBOUNCE_3_CLK 0x00
+#define DEBOUNCE_2_CLK 0x01
+#define DEBOUNCE_1_CLK 0x02
+#define DEBOUNCE_0_CLK 0x03
+/* [25:24] */
+#define F2F_EDGES 0x00
+#define R2R_EDGES 0x01
+#define BOTH_EDGES 0x02
+/* [23:20] */
+/* divisor = 4 to the nth power, n = register value */
+#define DEFAULT_TACH_DIV 1024
+#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1)
+
+/* TACH Status Register */
+#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C)
+
+/*PWM_TACH_STS */
+#define TACH_ASPEED_ISR BIT(31)
+#define TACH_ASPEED_PWM_OUT BIT(25)
+#define TACH_ASPEED_PWM_OEN BIT(24)
+#define TACH_ASPEED_DEB_INPUT BIT(23)
+#define TACH_ASPEED_RAW_INPUT BIT(22)
+#define TACH_ASPEED_VALUE_UPDATE BIT(21)
+#define TACH_ASPEED_FULL_MEASUREMENT BIT(20)
+#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0)
+/**********************************************************
+ * Software setting
+ *********************************************************/
+#define DEFAULT_FAN_PULSE_PR 2
+
+struct aspeed_pwm_tach_data {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *reset;
+ unsigned long clk_rate;
+ struct pwm_chip chip;
+ bool tach_present[TACH_ASPEED_NR_TACHS];
+ u32 tach_divisor;
+};
+
+static inline struct aspeed_pwm_tach_data *
+aspeed_pwm_chip_to_data(struct pwm_chip *chip)
+{
+ return container_of(chip, struct aspeed_pwm_tach_data, chip);
+}
+
+static int aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
+ u32 hwpwm = pwm->hwpwm;
+ bool polarity, pin_en, clk_en;
+ u32 duty_pt, val;
+ u64 div_h, div_l, duty_cycle_period, dividend;
+
+ val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
+ polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val);
+ pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val);
+ clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val);
+ div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val);
+ div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val);
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val);
+ duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val);
+ /*
+ * This multiplication doesn't overflow, the upper bound is
+ * 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000
+ */
+ dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1)
+ << div_h;
+ state->period = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
+
+ if (clk_en && duty_pt) {
+ dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt
+ << div_h;
+ state->duty_cycle = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
+ } else {
+ state->duty_cycle = clk_en ? state->period : 0;
+ }
+ state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+ state->enabled = pin_en;
+ return 0;
+}
+
+static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
+ u32 hwpwm = pwm->hwpwm, duty_pt, val;
+ u64 div_h, div_l, divisor, expect_period;
+ bool clk_en;
+
+ expect_period = div64_u64(ULLONG_MAX, (u64)priv->clk_rate);
+ expect_period = min(expect_period, state->period);
+ dev_dbg(chip->dev, "expect period: %lldns, duty_cycle: %lldns",
+ expect_period, state->duty_cycle);
+ /*
+ * Pick the smallest value for div_h so that div_l can be the biggest
+ * which results in a finer resolution near the target period value.
+ */
+ divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) *
+ (FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1);
+ div_h = order_base_2(DIV64_U64_ROUND_UP(priv->clk_rate * expect_period, divisor));
+ if (div_h > 0xf)
+ div_h = 0xf;
+
+ divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h;
+ div_l = div64_u64(priv->clk_rate * expect_period, divisor);
+
+ if (div_l == 0)
+ return -ERANGE;
+
+ div_l -= 1;
+
+ if (div_l > 255)
+ div_l = 255;
+
+ dev_dbg(chip->dev, "clk source: %ld div_h %lld, div_l : %lld\n",
+ priv->clk_rate, div_h, div_l);
+ /* duty_pt = duty_cycle * (PERIOD + 1) / period */
+ duty_pt = div64_u64(state->duty_cycle * priv->clk_rate,
+ (u64)NSEC_PER_SEC * (div_l + 1) << div_h);
+ dev_dbg(chip->dev, "duty_cycle = %lld, duty_pt = %d\n",
+ state->duty_cycle, duty_pt);
+
+ /*
+ * Fixed DUTY_CYCLE_PERIOD to its max value to get a
+ * fine-grained resolution for duty_cycle at the expense of a
+ * coarser period resolution.
+ */
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ val &= ~PWM_ASPEED_DUTY_CYCLE_PERIOD;
+ val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_PERIOD,
+ PWM_ASPEED_FIXED_PERIOD);
+ writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+
+ if (duty_pt == 0) {
+ /* emit inactive level and assert the duty counter reset */
+ clk_en = 0;
+ } else {
+ clk_en = 1;
+ if (duty_pt >= (PWM_ASPEED_FIXED_PERIOD + 1))
+ duty_pt = 0;
+ val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ val &= ~(PWM_ASPEED_DUTY_CYCLE_RISING_POINT |
+ PWM_ASPEED_DUTY_CYCLE_FALLING_POINT);
+ val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, duty_pt);
+ writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
+ }
+
+ val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
+ val &= ~(PWM_ASPEED_CTRL_CLK_DIV_H | PWM_ASPEED_CTRL_CLK_DIV_L |
+ PWM_ASPEED_CTRL_PIN_ENABLE | PWM_ASPEED_CTRL_CLK_ENABLE |
+ PWM_ASPEED_CTRL_INVERSE);
+ val |= FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_H, div_h) |
+ FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_L, div_l) |
+ FIELD_PREP(PWM_ASPEED_CTRL_PIN_ENABLE, state->enabled) |
+ FIELD_PREP(PWM_ASPEED_CTRL_CLK_ENABLE, clk_en) |
+ FIELD_PREP(PWM_ASPEED_CTRL_INVERSE, state->polarity);
+ writel(val, priv->base + PWM_ASPEED_CTRL(hwpwm));
+
+ return 0;
+}
+
+static const struct pwm_ops aspeed_pwm_ops = {
+ .apply = aspeed_pwm_apply,
+ .get_state = aspeed_pwm_get_state,
+};
+
+static void aspeed_tach_ch_enable(struct aspeed_pwm_tach_data *priv, u8 tach_ch,
+ bool enable)
+{
+ if (enable)
+ writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) |
+ TACH_ASPEED_ENABLE,
+ priv->base + TACH_ASPEED_CTRL(tach_ch));
+ else
+ writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) &
+ ~TACH_ASPEED_ENABLE,
+ priv->base + TACH_ASPEED_CTRL(tach_ch));
+}
+
+static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, u32 tach_val)
+{
+ u64 rpm;
+ u32 tach_div;
+
+ tach_div = tach_val * priv->tach_divisor * DEFAULT_FAN_PULSE_PR;
+
+ dev_dbg(priv->dev, "clk %ld, tach_val %d , tach_div %d\n",
+ priv->clk_rate, tach_val, tach_div);
+
+ rpm = (u64)priv->clk_rate * 60;
+ do_div(rpm, tach_div);
+
+ return (int)rpm;
+}
+
+static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tach_data *priv,
+ u8 fan_tach_ch)
+{
+ u32 val;
+
+ val = readl(priv->base + TACH_ASPEED_STS(fan_tach_ch));
+
+ if (!(val & TACH_ASPEED_FULL_MEASUREMENT))
+ return 0;
+ val = FIELD_GET(TACH_ASPEED_VALUE_MASK, val);
+ return aspeed_tach_val_to_rpm(priv, val);
+}
+
+static int aspeed_tach_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
+ u32 reg_val;
+
+ switch (attr) {
+ case hwmon_fan_input:
+ *val = aspeed_get_fan_tach_ch_rpm(priv, channel);
+ break;
+ case hwmon_fan_div:
+ reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
+ reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val);
+ *val = BIT(reg_val << 1);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int aspeed_tach_hwmon_write(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev);
+ u32 reg_val;
+
+ switch (attr) {
+ case hwmon_fan_div:
+ if (!is_power_of_2(val) || (ilog2(val) % 2) ||
+ DIV_TO_REG(val) > 0xb)
+ return -EINVAL;
+ priv->tach_divisor = val;
+ reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel));
+ reg_val &= ~TACH_ASPEED_CLK_DIV_T_MASK;
+ reg_val |= FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
+ DIV_TO_REG(priv->tach_divisor));
+ writel(reg_val, priv->base + TACH_ASPEED_CTRL(channel));
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t aspeed_tach_dev_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct aspeed_pwm_tach_data *priv = drvdata;
+
+ if (!priv->tach_present[channel])
+ return 0;
+ switch (attr) {
+ case hwmon_fan_input:
+ return 0444;
+ case hwmon_fan_div:
+ return 0644;
+ }
+ return 0;
+}
+
+static const struct hwmon_ops aspeed_tach_ops = {
+ .is_visible = aspeed_tach_dev_is_visible,
+ .read = aspeed_tach_hwmon_read,
+ .write = aspeed_tach_hwmon_write,
+};
+
+static const struct hwmon_channel_info *aspeed_tach_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV,
+ HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV),
+ NULL
+};
+
+static const struct hwmon_chip_info aspeed_tach_chip_info = {
+ .ops = &aspeed_tach_ops,
+ .info = aspeed_tach_info,
+};
+
+static void aspeed_present_fan_tach(struct aspeed_pwm_tach_data *priv, u8 *tach_ch, int count)
+{
+ u8 ch, index;
+ u32 val;
+
+ for (index = 0; index < count; index++) {
+ ch = tach_ch[index];
+ priv->tach_present[ch] = true;
+ priv->tach_divisor = DEFAULT_TACH_DIV;
+
+ val = readl(priv->base + TACH_ASPEED_CTRL(ch));
+ val &= ~(TACH_ASPEED_INVERS_LIMIT | TACH_ASPEED_DEBOUNCE_MASK |
+ TACH_ASPEED_IO_EDGE_MASK | TACH_ASPEED_CLK_DIV_T_MASK |
+ TACH_ASPEED_THRESHOLD_MASK);
+ val |= (DEBOUNCE_3_CLK << TACH_ASPEED_DEBOUNCE_BIT) |
+ F2F_EDGES |
+ FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK,
+ DIV_TO_REG(priv->tach_divisor));
+ writel(val, priv->base + TACH_ASPEED_CTRL(ch));
+
+ aspeed_tach_ch_enable(priv, ch, true);
+ }
+}
+
+static int aspeed_create_fan_monitor(struct device *dev,
+ struct device_node *child,
+ struct aspeed_pwm_tach_data *priv)
+{
+ int ret, count;
+ u8 *tach_ch;
+
+ count = of_property_count_u8_elems(child, "tach-ch");
+ if (count < 1)
+ return -EINVAL;
+ tach_ch = devm_kcalloc(dev, count, sizeof(*tach_ch), GFP_KERNEL);
+ if (!tach_ch)
+ return -ENOMEM;
+ ret = of_property_read_u8_array(child, "tach-ch", tach_ch, count);
+ if (ret)
+ return ret;
+
+ aspeed_present_fan_tach(priv, tach_ch, count);
+
+ return 0;
+}
+
+static void aspeed_pwm_tach_reset_assert(void *data)
+{
+ struct reset_control *rst = data;
+
+ reset_control_assert(rst);
+}
+
+static int aspeed_pwm_tach_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev, *hwmon;
+ int ret;
+ struct device_node *child;
+ struct aspeed_pwm_tach_data *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = dev;
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "Couldn't get clock\n");
+ priv->clk_rate = clk_get_rate(priv->clk);
+ priv->reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->reset))
+ return dev_err_probe(dev, PTR_ERR(priv->reset),
+ "Couldn't get reset control\n");
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Couldn't deassert reset control\n");
+ ret = devm_add_action_or_reset(dev, aspeed_pwm_tach_reset_assert,
+ priv->reset);
+ if (ret)
+ return ret;
+
+ priv->chip.dev = dev;
+ priv->chip.ops = &aspeed_pwm_ops;
+ priv->chip.npwm = PWM_ASPEED_NR_PWMS;
+
+ ret = devm_pwmchip_add(dev, &priv->chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ for_each_child_of_node(dev->of_node, child) {
+ ret = aspeed_create_fan_monitor(dev, child, priv);
+ if (ret) {
+ of_node_put(child);
+ dev_warn(dev, "Failed to create fan %d", ret);
+ return 0;
+ }
+ }
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "aspeed_tach", priv,
+ &aspeed_tach_chip_info, NULL);
+ ret = PTR_ERR_OR_ZERO(hwmon);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register hwmon device\n");
+
+ of_platform_populate(dev->of_node, NULL, NULL, dev);
+
+ return 0;
+}
+
+static int aspeed_pwm_tach_remove(struct platform_device *pdev)
+{
+ struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev);
+
+ reset_control_assert(priv->reset);
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_pwm_tach_match[] = {
+ {
+ .compatible = "aspeed,ast2600-pwm-tach",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match);
+
+static struct platform_driver aspeed_pwm_tach_driver = {
+ .probe = aspeed_pwm_tach_probe,
+ .remove = aspeed_pwm_tach_remove,
+ .driver = {
+ .name = "aspeed-g6-pwm-tach",
+ .of_match_table = aspeed_pwm_tach_match,
+ },
+};
+
+module_platform_driver(aspeed_pwm_tach_driver);
+
+MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
+MODULE_DESCRIPTION("Aspeed ast2600 PWM and Fan Tach device driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c
new file mode 100644
index 0000000000..f8b20346a9
--- /dev/null
+++ b/drivers/hwmon/asus_rog_ryujin.c
@@ -0,0 +1,609 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for Asus ROG Ryujin II 360 AIO cooler.
+ *
+ * Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <asm/unaligned.h>
+
+#define DRIVER_NAME "asus_rog_ryujin"
+
+#define USB_VENDOR_ID_ASUS_ROG 0x0b05
+#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
+
+#define STATUS_VALIDITY 1500 /* ms */
+#define MAX_REPORT_LENGTH 65
+
+/* Cooler status report offsets */
+#define RYUJIN_TEMP_SENSOR_1 3
+#define RYUJIN_TEMP_SENSOR_2 4
+#define RYUJIN_PUMP_SPEED 5
+#define RYUJIN_INTERNAL_FAN_SPEED 7
+
+/* Cooler duty report offsets */
+#define RYUJIN_PUMP_DUTY 4
+#define RYUJIN_INTERNAL_FAN_DUTY 5
+
+/* Controller status (speeds) report offsets */
+#define RYUJIN_CONTROLLER_SPEED_1 5
+#define RYUJIN_CONTROLLER_SPEED_2 7
+#define RYUJIN_CONTROLLER_SPEED_3 9
+#define RYUJIN_CONTROLLER_SPEED_4 3
+
+/* Controller duty report offsets */
+#define RYUJIN_CONTROLLER_DUTY 4
+
+/* Control commands and their inner offsets */
+#define RYUJIN_CMD_PREFIX 0xEC
+
+static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 };
+static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A };
+static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 };
+static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 };
+
+#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3
+#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4
+static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };
+
+#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4
+static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };
+
+/* Command lengths */
+#define GET_CMD_LENGTH 2 /* Same length for all get commands */
+#define SET_CMD_LENGTH 5 /* Same length for all set commands */
+
+/* Command response headers */
+#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE 0x19
+#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A
+#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20
+#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21
+
+static const char *const rog_ryujin_temp_label[] = {
+ "Coolant temp"
+};
+
+static const char *const rog_ryujin_speed_label[] = {
+ "Pump speed",
+ "Internal fan speed",
+ "Controller fan 1 speed",
+ "Controller fan 2 speed",
+ "Controller fan 3 speed",
+ "Controller fan 4 speed",
+};
+
+struct rog_ryujin_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ /* For locking access to buffer */
+ struct mutex buffer_lock;
+ /* For queueing multiple readers */
+ struct mutex status_report_request_mutex;
+ /* For reinitializing the completions below */
+ spinlock_t status_report_request_lock;
+ struct completion cooler_status_received;
+ struct completion controller_status_received;
+ struct completion cooler_duty_received;
+ struct completion controller_duty_received;
+ struct completion cooler_duty_set;
+ struct completion controller_duty_set;
+
+ /* Sensor data */
+ s32 temp_input[1];
+ u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */
+ u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */
+
+ u8 *buffer;
+ unsigned long updated; /* jiffies */
+};
+
+static int rog_ryujin_percent_to_pwm(u16 val)
+{
+ return DIV_ROUND_CLOSEST(val * 255, 100);
+}
+
+static int rog_ryujin_pwm_to_percent(long val)
+{
+ return DIV_ROUND_CLOSEST(val * 100, 255);
+}
+
+static umode_t rog_ryujin_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_label:
+ case hwmon_fan_input:
+ return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Writes the command to the device with the rest of the report filled with zeroes */
+static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length)
+{
+ int ret;
+
+ mutex_lock(&priv->buffer_lock);
+
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+
+ mutex_unlock(&priv->buffer_lock);
+ return ret;
+}
+
+/* Assumes priv->status_report_request_mutex is locked */
+static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,
+ struct completion *status_completion)
+{
+ int ret;
+
+ /*
+ * Disable raw event parsing for a moment to safely reinitialize the
+ * completion. Reinit is done because hidraw could have triggered
+ * the raw event parsing and marked the passed in completion as done.
+ */
+ spin_lock_bh(&priv->status_report_request_lock);
+ reinit_completion(status_completion);
+ spin_unlock_bh(&priv->status_report_request_lock);
+
+ /* Send command for getting data */
+ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(status_completion,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
+{
+ int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ goto unlock_and_return;
+ }
+
+ /* Retrieve cooler status */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,
+ &priv->cooler_status_received);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ /* Retrieve controller status (speeds) */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
+ &priv->controller_status_received);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ /* Retrieve cooler duty */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
+ &priv->cooler_duty_received);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ /* Retrieve controller duty */
+ ret =
+ rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
+ &priv->controller_duty_received);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ priv->updated = jiffies;
+
+unlock_and_return:
+ mutex_unlock(&priv->status_report_request_mutex);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct rog_ryujin_data *priv = dev_get_drvdata(dev);
+ int ret = rog_ryujin_get_status(priv);
+
+ if (ret < 0)
+ return ret;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->speed_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ *val = priv->duty_input[channel];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = rog_ryujin_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = rog_ryujin_speed_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP; /* unreachable */
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)
+{
+ u8 set_cmd[SET_CMD_LENGTH];
+ int ret;
+
+ if (channel < 2) {
+ /*
+ * Retrieve cooler duty since both pump and internal fan are set
+ * together, then write back with one of them modified.
+ */
+ ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
+ if (ret < 0)
+ return ret;
+ ret =
+ rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
+ &priv->cooler_duty_received);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
+
+ /* Cooler duties are set as 0-100% */
+ val = rog_ryujin_pwm_to_percent(val);
+
+ if (channel == 0) {
+ /* Cooler pump duty */
+ set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val;
+ set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] =
+ rog_ryujin_pwm_to_percent(priv->duty_input[1]);
+ } else if (channel == 1) {
+ /* Cooler internal fan duty */
+ set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] =
+ rog_ryujin_pwm_to_percent(priv->duty_input[0]);
+ set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
+ }
+
+ ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
+unlock_and_return:
+ mutex_unlock(&priv->status_report_request_mutex);
+ if (ret < 0)
+ return ret;
+ } else {
+ /*
+ * Controller fan duty (channel == 2). No need to retrieve current
+ * duty, so just send the command.
+ */
+ memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH);
+ set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val;
+
+ ret =
+ rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH,
+ &priv->controller_duty_set);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Lock onto this value until next refresh cycle */
+ priv->duty_input[channel] = val;
+
+ return 0;
+}
+
+static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct rog_ryujin_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ ret = rog_ryujin_write_fixed_duty(priv, channel, val);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops rog_ryujin_hwmon_ops = {
+ .is_visible = rog_ryujin_is_visible,
+ .read = rog_ryujin_read,
+ .read_string = rog_ryujin_read_string,
+ .write = rog_ryujin_write
+};
+
+static const struct hwmon_channel_info *rog_ryujin_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info rog_ryujin_chip_info = {
+ .ops = &rog_ryujin_hwmon_ops,
+ .info = rog_ryujin_info,
+};
+
+static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
+ int size)
+{
+ struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
+
+ if (data[0] != RYUJIN_CMD_PREFIX)
+ return 0;
+
+ if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
+ /* Received coolant temp and speeds of pump and internal fan */
+ priv->temp_input[0] =
+ data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;
+ priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);
+ priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);
+
+ if (!completion_done(&priv->cooler_status_received))
+ complete_all(&priv->cooler_status_received);
+ } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {
+ /* Received speeds of four fans attached to the controller */
+ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);
+ priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);
+ priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3);
+ priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4);
+
+ if (!completion_done(&priv->controller_status_received))
+ complete_all(&priv->controller_status_received);
+ } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {
+ /* Received report for pump and internal fan duties (in %) */
+ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {
+ /*
+ * We received a report with zeroes for duty in both places.
+ * The device returns this as a confirmation that setting values
+ * is successful. If we initiated a write, mark it as complete.
+ */
+ if (!completion_done(&priv->cooler_duty_set))
+ complete_all(&priv->cooler_duty_set);
+ else if (!completion_done(&priv->cooler_duty_received))
+ /*
+ * We didn't initiate a write, but received both zeroes.
+ * This means that either both duties are actually zero,
+ * or that we received a success report caused by userspace.
+ * We're expecting a report, so parse it.
+ */
+ goto read_cooler_duty;
+ return 0;
+ }
+read_cooler_duty:
+ priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);
+ priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]);
+
+ if (!completion_done(&priv->cooler_duty_received))
+ complete_all(&priv->cooler_duty_received);
+ } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {
+ /* Received report for controller duty for fans (in PWM) */
+ if (data[RYUJIN_CONTROLLER_DUTY] == 0) {
+ /*
+ * We received a report with a zero for duty. The device returns this as
+ * a confirmation that setting the controller duty value was successful.
+ * If we initiated a write, mark it as complete.
+ */
+ if (!completion_done(&priv->controller_duty_set))
+ complete_all(&priv->controller_duty_set);
+ else if (!completion_done(&priv->controller_duty_received))
+ /*
+ * We didn't initiate a write, but received a zero for duty.
+ * This means that either the duty is actually zero, or that
+ * we received a success report caused by userspace.
+ * We're expecting a report, so parse it.
+ */
+ goto read_controller_duty;
+ return 0;
+ }
+read_controller_duty:
+ priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];
+
+ if (!completion_done(&priv->controller_duty_received))
+ complete_all(&priv->controller_duty_received);
+ }
+
+ return 0;
+}
+
+static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct rog_ryujin_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for rog_ryujin_read() without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /* Enable hidraw so existing user-space tools can continue to work */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ mutex_init(&priv->status_report_request_mutex);
+ mutex_init(&priv->buffer_lock);
+ spin_lock_init(&priv->status_report_request_lock);
+ init_completion(&priv->cooler_status_received);
+ init_completion(&priv->controller_status_received);
+ init_completion(&priv->cooler_duty_received);
+ init_completion(&priv->controller_duty_received);
+ init_completion(&priv->cooler_duty_set);
+ init_completion(&priv->controller_duty_set);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin",
+ priv, &rog_ryujin_chip_info, NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void rog_ryujin_remove(struct hid_device *hdev)
+{
+ struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
+
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id rog_ryujin_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, rog_ryujin_table);
+
+static struct hid_driver rog_ryujin_driver = {
+ .name = "rog_ryujin",
+ .id_table = rog_ryujin_table,
+ .probe = rog_ryujin_probe,
+ .remove = rog_ryujin_remove,
+ .raw_event = rog_ryujin_raw_event,
+};
+
+static int __init rog_ryujin_init(void)
+{
+ return hid_register_driver(&rog_ryujin_driver);
+}
+
+static void __exit rog_ryujin_exit(void)
+{
+ hid_unregister_driver(&rog_ryujin_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(rog_ryujin_init);
+module_exit(rog_ryujin_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");
diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c
index 19b9bf3d75..35c862eb15 100644
--- a/drivers/hwmon/axi-fan-control.c
+++ b/drivers/hwmon/axi-fan-control.c
@@ -13,8 +13,9 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
/* register map */
#define ADI_REG_RSTN 0x0080
@@ -83,7 +84,7 @@ static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute
temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230;
- return sprintf(buf, "%u\n", temp);
+ return sysfs_emit(buf, "%u\n", temp);
}
static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da,
@@ -368,12 +369,12 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
}
static int axi_fan_control_init(struct axi_fan_control_data *ctl,
- const struct device_node *np)
+ const struct device *dev)
{
int ret;
/* get fan pulses per revolution */
- ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr);
+ ret = device_property_read_u32(dev, "pulses-per-revolution", &ctl->ppr);
if (ret)
return ret;
@@ -443,25 +444,16 @@ static struct attribute *axi_fan_control_attrs[] = {
};
ATTRIBUTE_GROUPS(axi_fan_control);
-static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
-
-static const struct of_device_id axi_fan_control_of_match[] = {
- { .compatible = "adi,axi-fan-control-1.00.a",
- .data = (void *)&version_1_0_0},
- {},
-};
-MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
-
static int axi_fan_control_probe(struct platform_device *pdev)
{
struct axi_fan_control_data *ctl;
struct clk *clk;
- const struct of_device_id *id;
+ const unsigned int *id;
const char *name = "axi_fan_control";
u32 version;
int ret;
- id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node);
+ id = device_get_match_data(&pdev->dev);
if (!id)
return -EINVAL;
@@ -474,10 +466,9 @@ static int axi_fan_control_probe(struct platform_device *pdev)
return PTR_ERR(ctl->base);
clk = devm_clk_get_enabled(&pdev->dev, NULL);
- if (IS_ERR(clk)) {
- dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk));
- return PTR_ERR(clk);
- }
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "clk_get failed\n");
ctl->clk_rate = clk_get_rate(clk);
if (!ctl->clk_rate)
@@ -485,22 +476,20 @@ static int axi_fan_control_probe(struct platform_device *pdev)
version = axi_ioread(ADI_AXI_REG_VERSION, ctl);
if (ADI_AXI_PCORE_VER_MAJOR(version) !=
- ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) {
- dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
- ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)),
- ADI_AXI_PCORE_VER_MAJOR(version),
- ADI_AXI_PCORE_VER_MINOR(version),
- ADI_AXI_PCORE_VER_PATCH(version));
- return -ENODEV;
- }
-
- ret = axi_fan_control_init(ctl, pdev->dev.of_node);
- if (ret) {
- dev_err(&pdev->dev, "Failed to initialize device\n");
- return ret;
- }
+ ADI_AXI_PCORE_VER_MAJOR((*id)))
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+ ADI_AXI_PCORE_VER_MAJOR(*id),
+ ADI_AXI_PCORE_VER_MINOR(*id),
+ ADI_AXI_PCORE_VER_PATCH(*id),
+ ADI_AXI_PCORE_VER_MAJOR(version),
+ ADI_AXI_PCORE_VER_MINOR(version),
+ ADI_AXI_PCORE_VER_PATCH(version));
+
+ ret = axi_fan_control_init(ctl, &pdev->dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to initialize device\n");
ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev,
name,
@@ -519,14 +508,22 @@ static int axi_fan_control_probe(struct platform_device *pdev)
axi_fan_control_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
pdev->driver_override, ctl);
- if (ret) {
- dev_err(&pdev->dev, "failed to request an irq, %d", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to request an irq\n");
return 0;
}
+static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
+
+static const struct of_device_id axi_fan_control_of_match[] = {
+ { .compatible = "adi,axi-fan-control-1.00.a",
+ .data = (void *)&version_1_0_0},
+ {},
+};
+MODULE_DEVICE_TABLE(of, axi_fan_control_of_match);
+
static struct platform_driver axi_fan_control_driver = {
.driver = {
.name = "axi_fan_control_driver",
diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c
new file mode 100644
index 0000000000..6ccceae21f
--- /dev/null
+++ b/drivers/hwmon/chipcap2.c
@@ -0,0 +1,822 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor
+ *
+ * Part numbers supported:
+ * CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
+ *
+ * Author: Javier Carrasco <javier.carrasco.cruz@gmail.com>
+ *
+ * Datasheet and application notes:
+ * https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#define CC2_START_CM 0xA0
+#define CC2_START_NOM 0x80
+#define CC2_R_ALARM_H_ON 0x18
+#define CC2_R_ALARM_H_OFF 0x19
+#define CC2_R_ALARM_L_ON 0x1A
+#define CC2_R_ALARM_L_OFF 0x1B
+#define CC2_RW_OFFSET 0x40
+#define CC2_W_ALARM_H_ON (CC2_R_ALARM_H_ON + CC2_RW_OFFSET)
+#define CC2_W_ALARM_H_OFF (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET)
+#define CC2_W_ALARM_L_ON (CC2_R_ALARM_L_ON + CC2_RW_OFFSET)
+#define CC2_W_ALARM_L_OFF (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET)
+
+#define CC2_STATUS_FIELD GENMASK(7, 6)
+#define CC2_STATUS_VALID_DATA 0x00
+#define CC2_STATUS_STALE_DATA 0x01
+#define CC2_STATUS_CMD_MODE 0x02
+
+#define CC2_RESPONSE_FIELD GENMASK(1, 0)
+#define CC2_RESPONSE_BUSY 0x00
+#define CC2_RESPONSE_ACK 0x01
+#define CC2_RESPONSE_NACK 0x02
+
+#define CC2_ERR_CORR_EEPROM BIT(2)
+#define CC2_ERR_UNCORR_EEPROM BIT(3)
+#define CC2_ERR_RAM_PARITY BIT(4)
+#define CC2_ERR_CONFIG_LOAD BIT(5)
+
+#define CC2_EEPROM_SIZE 10
+#define CC2_EEPROM_DATA_LEN 3
+#define CC2_MEASUREMENT_DATA_LEN 4
+
+#define CC2_RH_DATA_FIELD GENMASK(13, 0)
+
+/* ensure clean off -> on transitions */
+#define CC2_POWER_CYCLE_MS 80
+
+#define CC2_STARTUP_TO_DATA_MS 55
+#define CC2_RESP_START_CM_US 100
+#define CC2_RESP_EEPROM_R_US 100
+#define CC2_RESP_EEPROM_W_MS 12
+#define CC2_STARTUP_TIME_US 1250
+
+#define CC2_RH_MAX (100 * 1000U)
+
+#define CC2_CM_RETRIES 5
+
+struct cc2_rh_alarm_info {
+ bool low_alarm;
+ bool high_alarm;
+ bool low_alarm_visible;
+ bool high_alarm_visible;
+};
+
+struct cc2_data {
+ struct cc2_rh_alarm_info rh_alarm;
+ struct completion complete;
+ struct device *hwmon;
+ struct i2c_client *client;
+ struct mutex dev_access_lock; /* device access lock */
+ struct regulator *regulator;
+ const char *name;
+ int irq_ready;
+ int irq_low;
+ int irq_high;
+ bool process_irqs;
+};
+
+enum cc2_chan_addr {
+ CC2_CHAN_TEMP = 0,
+ CC2_CHAN_HUMIDITY,
+};
+
+/* %RH as a per cent mille from a register value */
+static long cc2_rh_convert(u16 data)
+{
+ unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX;
+
+ return tmp / ((1 << 14) - 1);
+}
+
+/* convert %RH to a register value */
+static u16 cc2_rh_to_reg(long data)
+{
+ return data * ((1 << 14) - 1) / CC2_RH_MAX;
+}
+
+/* temperature in milli degrees celsius from a register value */
+static long cc2_temp_convert(u16 data)
+{
+ unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1);
+
+ return tmp - 40 * 1000U;
+}
+
+static int cc2_enable(struct cc2_data *data)
+{
+ int ret;
+
+ /* exclusive regulator, check in case a disable failed */
+ if (regulator_is_enabled(data->regulator))
+ return 0;
+
+ /* clear any pending completion */
+ try_wait_for_completion(&data->complete);
+
+ ret = regulator_enable(data->regulator);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125);
+
+ data->process_irqs = true;
+
+ return 0;
+}
+
+static void cc2_disable(struct cc2_data *data)
+{
+ int err;
+
+ /* ignore alarms triggered by voltage toggling when powering up */
+ data->process_irqs = false;
+
+ /* exclusive regulator, check in case an enable failed */
+ if (regulator_is_enabled(data->regulator)) {
+ err = regulator_disable(data->regulator);
+ if (err)
+ dev_dbg(&data->client->dev, "Failed to disable device");
+ }
+}
+
+static int cc2_cmd_response_diagnostic(struct device *dev, u8 status)
+{
+ int resp;
+
+ if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) {
+ dev_dbg(dev, "Command sent out of command window\n");
+ return -ETIMEDOUT;
+ }
+
+ resp = FIELD_GET(CC2_RESPONSE_FIELD, status);
+ switch (resp) {
+ case CC2_RESPONSE_ACK:
+ return 0;
+ case CC2_RESPONSE_BUSY:
+ return -EBUSY;
+ case CC2_RESPONSE_NACK:
+ if (resp & CC2_ERR_CORR_EEPROM)
+ dev_dbg(dev, "Command failed: corrected EEPROM\n");
+ if (resp & CC2_ERR_UNCORR_EEPROM)
+ dev_dbg(dev, "Command failed: uncorrected EEPROM\n");
+ if (resp & CC2_ERR_RAM_PARITY)
+ dev_dbg(dev, "Command failed: RAM parity\n");
+ if (resp & CC2_ERR_RAM_PARITY)
+ dev_dbg(dev, "Command failed: configuration error\n");
+ return -ENODATA;
+ default:
+ dev_dbg(dev, "Unknown command reply\n");
+ return -EINVAL;
+ }
+}
+
+static int cc2_read_command_status(struct i2c_client *client)
+{
+ u8 status;
+ int ret;
+
+ ret = i2c_master_recv(client, &status, 1);
+ if (ret != 1) {
+ ret = ret < 0 ? ret : -EIO;
+ return ret;
+ }
+
+ return cc2_cmd_response_diagnostic(&client->dev, status);
+}
+
+/*
+ * The command mode is only accessible after sending the START_CM command in the
+ * first 10 ms after power-up. Only in case the command window is missed,
+ * CC2_CM_RETRIES retries are attempted before giving up and returning an error.
+ */
+static int cc2_command_mode_start(struct cc2_data *data)
+{
+ unsigned long timeout;
+ int i, ret;
+
+ for (i = 0; i < CC2_CM_RETRIES; i++) {
+ ret = cc2_enable(data);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0);
+ if (ret < 0)
+ return ret;
+
+ if (data->irq_ready > 0) {
+ timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US);
+ ret = wait_for_completion_timeout(&data->complete,
+ timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+ } else {
+ usleep_range(CC2_RESP_START_CM_US,
+ 2 * CC2_RESP_START_CM_US);
+ }
+ ret = cc2_read_command_status(data->client);
+ if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES)
+ break;
+
+ /* command window missed, prepare for a retry */
+ cc2_disable(data);
+ msleep(CC2_POWER_CYCLE_MS);
+ }
+
+ return ret;
+}
+
+/* Sending a Start_NOM command finishes the command mode immediately with no
+ * reply and the device enters normal operation mode
+ */
+static int cc2_command_mode_finish(struct cc2_data *data)
+{
+ int ret;
+
+ ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val)
+{
+ unsigned long timeout;
+ int ret;
+
+ ret = cc2_command_mode_start(data);
+ if (ret < 0)
+ goto disable;
+
+ cpu_to_be16s(&val);
+ ret = i2c_smbus_write_word_data(data->client, reg, val);
+ if (ret < 0)
+ goto disable;
+
+ if (data->irq_ready > 0) {
+ timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto disable;
+ }
+ } else {
+ msleep(CC2_RESP_EEPROM_W_MS);
+ }
+
+ ret = cc2_read_command_status(data->client);
+
+disable:
+ cc2_disable(data);
+
+ return ret;
+}
+
+static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val)
+{
+ u8 buf[CC2_EEPROM_DATA_LEN];
+ unsigned long timeout;
+ int ret;
+
+ ret = cc2_command_mode_start(data);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_word_data(data->client, reg, 0);
+ if (ret < 0)
+ return ret;
+
+ if (data->irq_ready > 0) {
+ timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+
+ } else {
+ usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10);
+ }
+ ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN);
+ if (ret != CC2_EEPROM_DATA_LEN)
+ return ret < 0 ? ret : -EIO;
+
+ *val = be16_to_cpup((__be16 *)&buf[1]);
+
+ return cc2_read_command_status(data->client);
+}
+
+static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val)
+{
+ u16 reg_val;
+ int ret;
+
+ ret = cc2_read_reg(data, reg, &reg_val);
+ if (!ret)
+ *val = cc2_rh_convert(reg_val);
+
+ cc2_disable(data);
+
+ return ret;
+}
+
+static int cc2_data_fetch(struct i2c_client *client,
+ enum hwmon_sensor_types type, long *val)
+{
+ u8 data[CC2_MEASUREMENT_DATA_LEN];
+ u8 status;
+ int ret;
+
+ ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN);
+ if (ret != CC2_MEASUREMENT_DATA_LEN) {
+ ret = ret < 0 ? ret : -EIO;
+ return ret;
+ }
+ status = FIELD_GET(CC2_STATUS_FIELD, data[0]);
+ if (status == CC2_STATUS_STALE_DATA)
+ return -EBUSY;
+
+ if (status != CC2_STATUS_VALID_DATA)
+ return -EIO;
+
+ switch (type) {
+ case hwmon_humidity:
+ *val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0]));
+ break;
+ case hwmon_temp:
+ *val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2]));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cc2_read_measurement(struct cc2_data *data,
+ enum hwmon_sensor_types type, long *val)
+{
+ unsigned long timeout;
+ int ret;
+
+ if (data->irq_ready > 0) {
+ timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2);
+ ret = wait_for_completion_timeout(&data->complete, timeout);
+ if (!ret)
+ return -ETIMEDOUT;
+
+ } else {
+ msleep(CC2_STARTUP_TO_DATA_MS);
+ }
+
+ ret = cc2_data_fetch(data->client, type, val);
+
+ return ret;
+}
+
+/*
+ * A measurement requires enabling the device, waiting for the automatic
+ * measurement to finish, reading the measurement data and disabling the device
+ * again.
+ */
+static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type,
+ long *val)
+{
+ int ret;
+
+ ret = cc2_enable(data);
+ if (ret)
+ return ret;
+
+ ret = cc2_read_measurement(data, type, val);
+
+ cc2_disable(data);
+
+ return ret;
+}
+
+/*
+ * In order to check alarm status, the corresponding ALARM_OFF (hysteresis)
+ * register must be read and a new measurement must be carried out to trigger
+ * the alarm signals. Given that the device carries out a measurement after
+ * exiting the command mode, there is no need to force two power-up sequences.
+ * Instead, a NOM command is sent and the device is disabled after the
+ * measurement is read.
+ */
+static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg,
+ long *hyst, long *measurement)
+{
+ u16 reg_val;
+ int ret;
+
+ ret = cc2_read_reg(data, reg, &reg_val);
+ if (ret)
+ goto disable;
+
+ *hyst = cc2_rh_convert(reg_val);
+
+ ret = cc2_command_mode_finish(data);
+ if (ret)
+ goto disable;
+
+ ret = cc2_read_measurement(data, hwmon_humidity, measurement);
+
+disable:
+ cc2_disable(data);
+
+ return ret;
+}
+
+static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct cc2_data *cc2 = data;
+
+ switch (type) {
+ case hwmon_humidity:
+ switch (attr) {
+ case hwmon_humidity_input:
+ return 0444;
+ case hwmon_humidity_min_alarm:
+ return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
+ case hwmon_humidity_max_alarm:
+ return cc2->rh_alarm.high_alarm_visible ? 0444 : 0;
+ case hwmon_humidity_min:
+ case hwmon_humidity_min_hyst:
+ return cc2->rh_alarm.low_alarm_visible ? 0644 : 0;
+ case hwmon_humidity_max:
+ case hwmon_humidity_max_hyst:
+ return cc2->rh_alarm.high_alarm_visible ? 0644 : 0;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static irqreturn_t cc2_ready_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs)
+ complete(&cc2->complete);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cc2_low_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs) {
+ hwmon_notify_event(cc2->hwmon, hwmon_humidity,
+ hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
+ cc2->rh_alarm.low_alarm = true;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cc2_high_interrupt(int irq, void *data)
+{
+ struct cc2_data *cc2 = data;
+
+ if (cc2->process_irqs) {
+ hwmon_notify_event(cc2->hwmon, hwmon_humidity,
+ hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
+ cc2->rh_alarm.high_alarm = true;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val)
+{
+ long measurement, min_hyst;
+ int ret;
+
+ ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst,
+ &measurement);
+ if (ret < 0)
+ return ret;
+
+ if (data->rh_alarm.low_alarm) {
+ *val = (measurement < min_hyst) ? 1 : 0;
+ data->rh_alarm.low_alarm = *val;
+ } else {
+ *val = 0;
+ }
+
+ return 0;
+}
+
+static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
+{
+ long measurement, max_hyst;
+ int ret;
+
+ ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst,
+ &measurement);
+ if (ret < 0)
+ return ret;
+
+ if (data->rh_alarm.high_alarm) {
+ *val = (measurement > max_hyst) ? 1 : 0;
+ data->rh_alarm.high_alarm = *val;
+ } else {
+ *val = 0;
+ }
+
+ return 0;
+}
+
+static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct cc2_data *data = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&data->dev_access_lock);
+
+ switch (type) {
+ case hwmon_temp:
+ ret = cc2_measurement(data, type, val);
+ break;
+ case hwmon_humidity:
+ switch (attr) {
+ case hwmon_humidity_input:
+ ret = cc2_measurement(data, type, val);
+ break;
+ case hwmon_humidity_min:
+ ret = cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val);
+ break;
+ case hwmon_humidity_min_hyst:
+ ret = cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val);
+ break;
+ case hwmon_humidity_max:
+ ret = cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val);
+ break;
+ case hwmon_humidity_max_hyst:
+ ret = cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val);
+ break;
+ case hwmon_humidity_min_alarm:
+ ret = cc2_humidity_min_alarm_status(data, val);
+ break;
+ case hwmon_humidity_max_alarm:
+ ret = cc2_humidity_max_alarm_status(data, val);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ }
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+ mutex_unlock(&data->dev_access_lock);
+
+ return ret;
+}
+
+static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct cc2_data *data = dev_get_drvdata(dev);
+ int ret;
+ u16 arg;
+ u8 cmd;
+
+ if (type != hwmon_humidity)
+ return -EOPNOTSUPP;
+
+ if (val < 0 || val > CC2_RH_MAX)
+ return -EINVAL;
+
+ mutex_lock(&data->dev_access_lock);
+
+ switch (attr) {
+ case hwmon_humidity_min:
+ cmd = CC2_W_ALARM_L_ON;
+ arg = cc2_rh_to_reg(val);
+ ret = cc2_write_reg(data, cmd, arg);
+ break;
+
+ case hwmon_humidity_min_hyst:
+ cmd = CC2_W_ALARM_L_OFF;
+ arg = cc2_rh_to_reg(val);
+ ret = cc2_write_reg(data, cmd, arg);
+ break;
+
+ case hwmon_humidity_max:
+ cmd = CC2_W_ALARM_H_ON;
+ arg = cc2_rh_to_reg(val);
+ ret = cc2_write_reg(data, cmd, arg);
+ break;
+
+ case hwmon_humidity_max_hyst:
+ cmd = CC2_W_ALARM_H_OFF;
+ arg = cc2_rh_to_reg(val);
+ ret = cc2_write_reg(data, cmd, arg);
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ mutex_unlock(&data->dev_access_lock);
+
+ return ret;
+}
+
+static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev)
+{
+ int ret = 0;
+
+ data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready");
+ if (data->irq_ready > 0) {
+ init_completion(&data->complete);
+ ret = devm_request_threaded_irq(dev, data->irq_ready, NULL,
+ cc2_ready_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ }
+
+ return ret;
+}
+
+static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
+{
+ int ret = 0;
+
+ data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low");
+ if (data->irq_low > 0) {
+ ret = devm_request_threaded_irq(dev, data->irq_low, NULL,
+ cc2_low_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ data->rh_alarm.low_alarm_visible = true;
+ }
+
+ data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high");
+ if (data->irq_high > 0) {
+ ret = devm_request_threaded_irq(dev, data->irq_high, NULL,
+ cc2_high_interrupt,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ data->rh_alarm.high_alarm_visible = true;
+ }
+
+ return ret;
+}
+
+static const struct hwmon_channel_info *cc2_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
+ HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
+ HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops cc2_hwmon_ops = {
+ .is_visible = cc2_is_visible,
+ .read = cc2_read,
+ .write = cc2_write,
+};
+
+static const struct hwmon_chip_info cc2_chip_info = {
+ .ops = &cc2_hwmon_ops,
+ .info = cc2_info,
+};
+
+static int cc2_probe(struct i2c_client *client)
+{
+ struct cc2_data *data;
+ struct device *dev = &client->dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, data);
+
+ mutex_init(&data->dev_access_lock);
+
+ data->client = client;
+
+ data->regulator = devm_regulator_get_exclusive(dev, "vdd");
+ if (IS_ERR(data->regulator)) {
+ dev_err_probe(dev, PTR_ERR(data->regulator),
+ "Failed to get regulator\n");
+ return PTR_ERR(data->regulator);
+ }
+
+ ret = cc2_request_ready_irq(data, dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to request ready irq\n");
+ return ret;
+ }
+
+ ret = cc2_request_alarm_irqs(data, dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to request alarm irqs\n");
+ goto disable;
+ }
+
+ data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &cc2_chip_info,
+ NULL);
+ if (IS_ERR(data->hwmon)) {
+ dev_err_probe(dev, PTR_ERR(data->hwmon),
+ "Failed to register hwmon device\n");
+ ret = PTR_ERR(data->hwmon);
+ }
+
+disable:
+ cc2_disable(data);
+
+ return ret;
+}
+
+static void cc2_remove(struct i2c_client *client)
+{
+ struct cc2_data *data = i2c_get_clientdata(client);
+
+ cc2_disable(data);
+}
+
+static const struct i2c_device_id cc2_id[] = {
+ { "cc2d23" },
+ { "cc2d23s" },
+ { "cc2d25" },
+ { "cc2d25s" },
+ { "cc2d33" },
+ { "cc2d33s" },
+ { "cc2d35" },
+ { "cc2d35s" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cc2_id);
+
+static const struct of_device_id cc2_of_match[] = {
+ { .compatible = "amphenol,cc2d23" },
+ { .compatible = "amphenol,cc2d23s" },
+ { .compatible = "amphenol,cc2d25" },
+ { .compatible = "amphenol,cc2d25s" },
+ { .compatible = "amphenol,cc2d33" },
+ { .compatible = "amphenol,cc2d33s" },
+ { .compatible = "amphenol,cc2d35" },
+ { .compatible = "amphenol,cc2d35s" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, cc2_of_match);
+
+static struct i2c_driver cc2_driver = {
+ .driver = {
+ .name = "cc2d23",
+ .of_match_table = cc2_of_match,
+ },
+ .probe = cc2_probe,
+ .remove = cc2_remove,
+ .id_table = cc2_id,
+};
+module_i2c_driver(cc2_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gamil.com>");
+MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c
index b8fc8d1ef2..616bd1a5b8 100644
--- a/drivers/hwmon/coretemp.c
+++ b/drivers/hwmon/coretemp.c
@@ -39,13 +39,18 @@ static int force_tjmax;
module_param_named(tjmax, force_tjmax, int, 0444);
MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
-#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */
-#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */
#define NUM_REAL_CORES 512 /* Number of Real cores per cpu */
#define CORETEMP_NAME_LENGTH 28 /* String Length of attrs */
-#define MAX_CORE_ATTRS 4 /* Maximum no of basic attrs */
-#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1)
-#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO)
+
+enum coretemp_attr_index {
+ ATTR_LABEL,
+ ATTR_CRIT_ALARM,
+ ATTR_TEMP,
+ ATTR_TJMAX,
+ ATTR_TTARGET,
+ MAX_CORE_ATTRS = ATTR_TJMAX + 1, /* Maximum no of basic attrs */
+ TOTAL_ATTRS = ATTR_TTARGET + 1 /* Maximum no of possible attrs */
+};
#ifdef CONFIG_SMP
#define for_each_sibling(i, cpu) \
@@ -65,19 +70,17 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius");
* @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS,
* from where the temperature values should be read.
* @attr_size: Total number of pre-core attrs displayed in the sysfs.
- * @is_pkg_data: If this is 1, the temp_data holds pkgtemp data.
- * Otherwise, temp_data holds coretemp data.
*/
struct temp_data {
int temp;
int tjmax;
unsigned long last_updated;
unsigned int cpu;
+ int index;
u32 cpu_core_id;
u32 status_reg;
int attr_size;
- bool is_pkg_data;
- struct sensor_device_attribute sd_attrs[TOTAL_ATTRS];
+ struct device_attribute sd_attrs[TOTAL_ATTRS];
char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH];
struct attribute *attrs[TOTAL_ATTRS + 1];
struct attribute_group attr_group;
@@ -88,10 +91,11 @@ struct temp_data {
struct platform_data {
struct device *hwmon_dev;
u16 pkg_id;
- u16 cpu_map[NUM_REAL_CORES];
+ int nr_cores;
struct ida ida;
struct cpumask cpumask;
- struct temp_data *core_data[MAX_CORE_DATA];
+ struct temp_data *pkg_data;
+ struct temp_data **core_data;
struct device_attribute name_attr;
};
@@ -143,6 +147,11 @@ static const struct tjmax_model tjmax_model_table[] = {
*/
};
+static bool is_pkg_temp_data(struct temp_data *tdata)
+{
+ return tdata->index < 0;
+}
+
static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
{
/* The 100C is default for both mobile and non mobile CPUs */
@@ -332,11 +341,10 @@ static struct platform_device **zone_devices;
static ssize_t show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_LABEL]);
- if (tdata->is_pkg_data)
+ if (is_pkg_temp_data(tdata))
return sprintf(buf, "Package id %u\n", pdata->pkg_id);
return sprintf(buf, "Core %u\n", tdata->cpu_core_id);
@@ -346,9 +354,8 @@ static ssize_t show_crit_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data,
+ sd_attrs[ATTR_CRIT_ALARM]);
mutex_lock(&tdata->update_lock);
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
@@ -360,9 +367,7 @@ static ssize_t show_crit_alarm(struct device *dev,
static ssize_t show_tjmax(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TJMAX]);
int tjmax;
mutex_lock(&tdata->update_lock);
@@ -375,9 +380,7 @@ static ssize_t show_tjmax(struct device *dev,
static ssize_t show_ttarget(struct device *dev,
struct device_attribute *devattr, char *buf)
{
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TTARGET]);
int ttarget;
mutex_lock(&tdata->update_lock);
@@ -393,9 +396,7 @@ static ssize_t show_temp(struct device *dev,
struct device_attribute *devattr, char *buf)
{
u32 eax, edx;
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- struct platform_data *pdata = dev_get_drvdata(dev);
- struct temp_data *tdata = pdata->core_data[attr->index];
+ struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TEMP]);
int tjmax;
mutex_lock(&tdata->update_lock);
@@ -418,8 +419,7 @@ static ssize_t show_temp(struct device *dev,
return sprintf(buf, "%d\n", tdata->temp);
}
-static int create_core_attrs(struct temp_data *tdata, struct device *dev,
- int index)
+static int create_core_attrs(struct temp_data *tdata, struct device *dev)
{
int i;
static ssize_t (*const rd_ptr[TOTAL_ATTRS]) (struct device *dev,
@@ -436,16 +436,15 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev,
* The attr number is always core id + 2
* The Pkgtemp will always show up as temp1_*, if available
*/
- int attr_no = tdata->is_pkg_data ? 1 : tdata->cpu_core_id + 2;
+ int attr_no = is_pkg_temp_data(tdata) ? 1 : tdata->cpu_core_id + 2;
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH,
"temp%d_%s", attr_no, suffixes[i]);
- sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr);
- tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i];
- tdata->sd_attrs[i].dev_attr.attr.mode = 0444;
- tdata->sd_attrs[i].dev_attr.show = rd_ptr[i];
- tdata->sd_attrs[i].index = index;
- tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr;
+ sysfs_attr_init(&tdata->sd_attrs[i].attr);
+ tdata->sd_attrs[i].attr.name = tdata->attr_name[i];
+ tdata->sd_attrs[i].attr.mode = 0444;
+ tdata->sd_attrs[i].show = rd_ptr[i];
+ tdata->attrs[i] = &tdata->sd_attrs[i].attr;
}
tdata->attr_group.attrs = tdata->attrs;
return sysfs_create_group(&dev->kobj, &tdata->attr_group);
@@ -477,17 +476,44 @@ static struct platform_device *coretemp_get_pdev(unsigned int cpu)
return NULL;
}
-static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
+static struct temp_data *
+init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag)
{
struct temp_data *tdata;
+ if (!pdata->core_data) {
+ /*
+ * TODO:
+ * The information of actual possible cores in a package is broken for now.
+ * Will replace hardcoded NUM_REAL_CORES with actual per package core count
+ * when this information becomes available.
+ */
+ pdata->nr_cores = NUM_REAL_CORES;
+ pdata->core_data = kcalloc(pdata->nr_cores, sizeof(struct temp_data *),
+ GFP_KERNEL);
+ if (!pdata->core_data)
+ return NULL;
+ }
+
tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL);
if (!tdata)
return NULL;
+ if (pkg_flag) {
+ pdata->pkg_data = tdata;
+ /* Use tdata->index as indicator of package temp data */
+ tdata->index = -1;
+ } else {
+ tdata->index = ida_alloc_max(&pdata->ida, pdata->nr_cores - 1, GFP_KERNEL);
+ if (tdata->index < 0) {
+ kfree(tdata);
+ return NULL;
+ }
+ pdata->core_data[tdata->index] = tdata;
+ }
+
tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS :
MSR_IA32_THERM_STATUS;
- tdata->is_pkg_data = pkg_flag;
tdata->cpu = cpu;
tdata->cpu_core_id = topology_core_id(cpu);
tdata->attr_size = MAX_CORE_ATTRS;
@@ -495,6 +521,36 @@ static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
return tdata;
}
+static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata)
+{
+ if (is_pkg_temp_data(tdata)) {
+ pdata->pkg_data = NULL;
+ kfree(pdata->core_data);
+ pdata->core_data = NULL;
+ pdata->nr_cores = 0;
+ } else {
+ pdata->core_data[tdata->index] = NULL;
+ ida_free(&pdata->ida, tdata->index);
+ }
+ kfree(tdata);
+}
+
+static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu)
+{
+ int i;
+
+ /* cpu < 0 means get pkg temp_data */
+ if (cpu < 0)
+ return pdata->pkg_data;
+
+ for (i = 0; i < pdata->nr_cores; i++) {
+ if (pdata->core_data[i] &&
+ pdata->core_data[i]->cpu_core_id == topology_core_id(cpu))
+ return pdata->core_data[i];
+ }
+ return NULL;
+}
+
static int create_core_data(struct platform_device *pdev, unsigned int cpu,
int pkg_flag)
{
@@ -502,37 +558,19 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
struct platform_data *pdata = platform_get_drvdata(pdev);
struct cpuinfo_x86 *c = &cpu_data(cpu);
u32 eax, edx;
- int err, index;
+ int err;
if (!housekeeping_cpu(cpu, HK_TYPE_MISC))
return 0;
- /*
- * Get the index of tdata in pdata->core_data[]
- * tdata for package: pdata->core_data[1]
- * tdata for core: pdata->core_data[2] .. pdata->core_data[NUM_REAL_CORES + 1]
- */
- if (pkg_flag) {
- index = PKG_SYSFS_ATTR_NO;
- } else {
- index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL);
- if (index < 0)
- return index;
-
- pdata->cpu_map[index] = topology_core_id(cpu);
- index += BASE_SYSFS_ATTR_NO;
- }
-
- tdata = init_temp_data(cpu, pkg_flag);
- if (!tdata) {
- err = -ENOMEM;
- goto ida_free;
- }
+ tdata = init_temp_data(pdata, cpu, pkg_flag);
+ if (!tdata)
+ return -ENOMEM;
/* Test if we can access the status register */
err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx);
if (err)
- goto exit_free;
+ goto err;
/* Make sure tdata->tjmax is a valid indicator for dynamic/static tjmax */
get_tjmax(tdata, &pdev->dev);
@@ -546,20 +584,15 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu,
if (get_ttarget(tdata, &pdev->dev) >= 0)
tdata->attr_size++;
- pdata->core_data[index] = tdata;
-
/* Create sysfs interfaces */
- err = create_core_attrs(tdata, pdata->hwmon_dev, index);
+ err = create_core_attrs(tdata, pdata->hwmon_dev);
if (err)
- goto exit_free;
+ goto err;
return 0;
-exit_free:
- pdata->core_data[index] = NULL;
- kfree(tdata);
-ida_free:
- if (!pkg_flag)
- ida_free(&pdata->ida, index - BASE_SYSFS_ATTR_NO);
+
+err:
+ destroy_temp_data(pdata, tdata);
return err;
}
@@ -570,10 +603,8 @@ coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag)
dev_err(&pdev->dev, "Adding Core %u failed\n", cpu);
}
-static void coretemp_remove_core(struct platform_data *pdata, int indx)
+static void coretemp_remove_core(struct platform_data *pdata, struct temp_data *tdata)
{
- struct temp_data *tdata = pdata->core_data[indx];
-
/* if we errored on add then this is already gone */
if (!tdata)
return;
@@ -581,11 +612,7 @@ static void coretemp_remove_core(struct platform_data *pdata, int indx)
/* Remove the sysfs attributes */
sysfs_remove_group(&pdata->hwmon_dev->kobj, &tdata->attr_group);
- kfree(pdata->core_data[indx]);
- pdata->core_data[indx] = NULL;
-
- if (indx >= BASE_SYSFS_ATTR_NO)
- ida_free(&pdata->ida, indx - BASE_SYSFS_ATTR_NO);
+ destroy_temp_data(pdata, tdata);
}
static int coretemp_device_add(int zoneid)
@@ -698,7 +725,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
struct platform_device *pdev = coretemp_get_pdev(cpu);
struct platform_data *pd;
struct temp_data *tdata;
- int i, indx = -1, target;
+ int target;
/* No need to tear down any interfaces for suspend */
if (cpuhp_tasks_frozen)
@@ -709,18 +736,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
if (!pd->hwmon_dev)
return 0;
- for (i = 0; i < NUM_REAL_CORES; i++) {
- if (pd->cpu_map[i] == topology_core_id(cpu)) {
- indx = i + BASE_SYSFS_ATTR_NO;
- break;
- }
- }
-
- /* Too many cores and this core is not populated, just return */
- if (indx < 0)
- return 0;
-
- tdata = pd->core_data[indx];
+ tdata = get_temp_data(pd, cpu);
cpumask_clear_cpu(cpu, &pd->cpumask);
@@ -731,7 +747,7 @@ static int coretemp_cpu_offline(unsigned int cpu)
*/
target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu));
if (target >= nr_cpu_ids) {
- coretemp_remove_core(pd, indx);
+ coretemp_remove_core(pd, tdata);
} else if (tdata && tdata->cpu == cpu) {
mutex_lock(&tdata->update_lock);
tdata->cpu = target;
@@ -741,10 +757,10 @@ static int coretemp_cpu_offline(unsigned int cpu)
/*
* If all cores in this pkg are offline, remove the interface.
*/
- tdata = pd->core_data[PKG_SYSFS_ATTR_NO];
+ tdata = get_temp_data(pd, -1);
if (cpumask_empty(&pd->cpumask)) {
if (tdata)
- coretemp_remove_core(pd, PKG_SYSFS_ATTR_NO);
+ coretemp_remove_core(pd, tdata);
hwmon_device_unregister(pd->hwmon_dev);
pd->hwmon_dev = NULL;
return 0;
@@ -782,7 +798,7 @@ static int __init coretemp_init(void)
if (!x86_match_cpu(coretemp_ids))
return -ENODEV;
- max_zones = topology_max_packages() * topology_max_die_per_package();
+ max_zones = topology_max_packages() * topology_max_dies_per_package();
zone_devices = kcalloc(max_zones, sizeof(struct platform_device *),
GFP_KERNEL);
if (!zone_devices)
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 6d8c0f328b..efcf78673e 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -108,7 +108,7 @@ struct dell_smm_cooling_data {
struct dell_smm_data *data;
};
-MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_AUTHOR("Massimo Dal Zotto <dz@debian.org>");
MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
MODULE_LICENSE("GPL");
@@ -1450,10 +1450,15 @@ struct i8k_fan_control_data {
};
enum i8k_fan_controls {
+ I8K_FAN_30A3_31A3,
I8K_FAN_34A3_35A3,
};
static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
+ [I8K_FAN_30A3_31A3] = {
+ .manual_fan = 0x30a3,
+ .auto_fan = 0x31a3,
+ },
[I8K_FAN_34A3_35A3] = {
.manual_fan = 0x34a3,
.auto_fan = 0x35a3,
@@ -1517,6 +1522,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
},
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
},
+ {
+ .ident = "Dell XPS 9315",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 9315"),
+ },
+ .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
+ },
{ }
};
@@ -1587,6 +1600,7 @@ static struct wmi_driver dell_smm_wmi_driver = {
},
.id_table = dell_smm_wmi_id_table,
.probe = dell_smm_wmi_probe,
+ .no_singleton = true,
};
/*
diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c
index 21b6350465..bffbc80401 100644
--- a/drivers/hwmon/ds1621.c
+++ b/drivers/hwmon/ds1621.c
@@ -380,7 +380,6 @@ MODULE_DEVICE_TABLE(i2c, ds1621_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds1621_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ds1621",
},
diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c
index 2b09536630..4fc4df012f 100644
--- a/drivers/hwmon/ds620.c
+++ b/drivers/hwmon/ds620.c
@@ -241,7 +241,6 @@ MODULE_DEVICE_TABLE(i2c, ds620_id);
/* This is the driver that will be inserted */
static struct i2c_driver ds620_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ds620",
},
diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c
index 1332e4ac07..d370efd6f9 100644
--- a/drivers/hwmon/emc1403.c
+++ b/drivers/hwmon/emc1403.c
@@ -385,7 +385,7 @@ static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config emc1403_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = emc1403_regmap_is_volatile,
};
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 29f0e4945f..6ef733c0be 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -12,9 +12,6 @@
#include <linux/platform_data/emc2305.h>
#include <linux/thermal.h>
-static const unsigned short
-emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END };
-
#define EMC2305_REG_DRIVE_FAIL_STATUS 0x27
#define EMC2305_REG_VENDOR 0xfe
#define EMC2305_FAN_MAX 0xff
@@ -611,14 +608,12 @@ static void emc2305_remove(struct i2c_client *client)
}
static struct i2c_driver emc2305_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "emc2305",
},
.probe = emc2305_probe,
.remove = emc2305_remove,
.id_table = emc2305_ids,
- .address_list = emc2305_normal_i2c,
};
module_i2c_driver(emc2305_driver);
diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c
index 6307112c2c..9ed2c4b673 100644
--- a/drivers/hwmon/fam15h_power.c
+++ b/drivers/hwmon/fam15h_power.c
@@ -209,7 +209,7 @@ static ssize_t power1_average_show(struct device *dev,
* With the new x86 topology modelling, x86_max_cores is the
* compute unit number.
*/
- cu_num = boot_cpu_data.x86_max_cores;
+ cu_num = topology_num_cores_per_package();
ret = read_registers(data);
if (ret)
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index c7dd3f5b2b..3b259c425a 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -510,6 +510,7 @@ static const char * const hwmon_in_attr_templates[] = {
[hwmon_in_rated_min] = "in%d_rated_min",
[hwmon_in_rated_max] = "in%d_rated_max",
[hwmon_in_beep] = "in%d_beep",
+ [hwmon_in_fault] = "in%d_fault",
};
static const char * const hwmon_curr_attr_templates[] = {
@@ -586,6 +587,8 @@ static const char * const hwmon_humidity_attr_templates[] = {
[hwmon_humidity_fault] = "humidity%d_fault",
[hwmon_humidity_rated_min] = "humidity%d_rated_min",
[hwmon_humidity_rated_max] = "humidity%d_rated_max",
+ [hwmon_humidity_min_alarm] = "humidity%d_min_alarm",
+ [hwmon_humidity_max_alarm] = "humidity%d_max_alarm",
};
static const char * const hwmon_fan_attr_templates[] = {
diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c
index c558143e52..d9b57a4b3e 100644
--- a/drivers/hwmon/ina209.c
+++ b/drivers/hwmon/ina209.c
@@ -589,7 +589,6 @@ MODULE_DEVICE_TABLE(of, ina209_of_match);
/* This is the driver that will be inserted */
static struct i2c_driver ina209_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ina209",
.of_match_table = of_match_ptr(ina209_of_match),
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index ca9f5d2c81..69289293bc 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -629,7 +629,6 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = {
MODULE_DEVICE_TABLE(of, ina238_of_match);
static struct i2c_driver ina238_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ina238",
.of_match_table = of_match_ptr(ina238_of_match),
diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c
index 5ffdc94db4..2c9530b6f1 100644
--- a/drivers/hwmon/ina3221.c
+++ b/drivers/hwmon/ina3221.c
@@ -762,7 +762,7 @@ static const struct regmap_config ina3221_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_table = &ina3221_volatile_table,
};
diff --git a/drivers/hwmon/intel-m10-bmc-hwmon.c b/drivers/hwmon/intel-m10-bmc-hwmon.c
index 6500ca548f..ca2dff1589 100644
--- a/drivers/hwmon/intel-m10-bmc-hwmon.c
+++ b/drivers/hwmon/intel-m10-bmc-hwmon.c
@@ -429,7 +429,7 @@ static const struct m10bmc_sdata n6000bmc_curr_tbl[] = {
};
static const struct m10bmc_sdata n6000bmc_power_tbl[] = {
- { 0x724, 0x0, 0x0, 0x0, 0x0, 1, "Board Power" },
+ { 0x724, 0x0, 0x0, 0x0, 0x0, 1000, "Board Power" },
};
static const struct hwmon_channel_info * const n6000bmc_hinfo[] = {
diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c
index f958e830b2..75dc25df0f 100644
--- a/drivers/hwmon/jc42.c
+++ b/drivers/hwmon/jc42.c
@@ -497,7 +497,7 @@ static const struct regmap_config jc42_regmap_config = {
.writeable_reg = jc42_writable_reg,
.readable_reg = jc42_readable_reg,
.volatile_reg = jc42_volatile_reg,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
};
static int jc42_probe(struct i2c_client *client)
diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c
index 5befedca6a..b333c9bde4 100644
--- a/drivers/hwmon/lm83.c
+++ b/drivers/hwmon/lm83.c
@@ -165,7 +165,7 @@ static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config lm83_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = lm83_regmap_is_volatile,
.reg_read = lm83_regmap_reg_read,
.reg_write = lm83_regmap_reg_write,
diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c
index 001799bc28..b8548105cd 100644
--- a/drivers/hwmon/ltc2992.c
+++ b/drivers/hwmon/ltc2992.c
@@ -876,9 +876,11 @@ static int ltc2992_parse_dt(struct ltc2992_state *st)
ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val);
if (!ret) {
- if (!val)
+ if (!val) {
+ fwnode_handle_put(child);
return dev_err_probe(&st->client->dev, -EINVAL,
"shunt resistor value cannot be zero\n");
+ }
st->r_sense_uohm[addr] = val;
}
}
diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c
new file mode 100644
index 0000000000..4f608a3790
--- /dev/null
+++ b/drivers/hwmon/ltc4282.c
@@ -0,0 +1,1782 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C
+ *
+ * Copyright 2023 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+#define LTC4282_CTRL_LSB 0x00
+ #define LTC4282_CTRL_OV_RETRY_MASK BIT(0)
+ #define LTC4282_CTRL_UV_RETRY_MASK BIT(1)
+ #define LTC4282_CTRL_OC_RETRY_MASK BIT(2)
+ #define LTC4282_CTRL_ON_ACTIVE_LOW_MASK BIT(5)
+ #define LTC4282_CTRL_ON_DELAY_MASK BIT(6)
+#define LTC4282_CTRL_MSB 0x01
+ #define LTC4282_CTRL_VIN_MODE_MASK GENMASK(1, 0)
+ #define LTC4282_CTRL_OV_MODE_MASK GENMASK(3, 2)
+ #define LTC4282_CTRL_UV_MODE_MASK GENMASK(5, 4)
+#define LTC4282_FAULT_LOG 0x04
+ #define LTC4282_OV_FAULT_MASK BIT(0)
+ #define LTC4282_UV_FAULT_MASK BIT(1)
+ #define LTC4282_VDD_FAULT_MASK \
+ (LTC4282_OV_FAULT_MASK | LTC4282_UV_FAULT_MASK)
+ #define LTC4282_OC_FAULT_MASK BIT(2)
+ #define LTC4282_POWER_BAD_FAULT_MASK BIT(3)
+ #define LTC4282_FET_SHORT_FAULT_MASK BIT(5)
+ #define LTC4282_FET_BAD_FAULT_MASK BIT(6)
+ #define LTC4282_FET_FAILURE_FAULT_MASK \
+ (LTC4282_FET_SHORT_FAULT_MASK | LTC4282_FET_BAD_FAULT_MASK)
+#define LTC4282_ADC_ALERT_LOG 0x05
+ #define LTC4282_GPIO_ALARM_L_MASK BIT(0)
+ #define LTC4282_GPIO_ALARM_H_MASK BIT(1)
+ #define LTC4282_VSOURCE_ALARM_L_MASK BIT(2)
+ #define LTC4282_VSOURCE_ALARM_H_MASK BIT(3)
+ #define LTC4282_VSENSE_ALARM_L_MASK BIT(4)
+ #define LTC4282_VSENSE_ALARM_H_MASK BIT(5)
+ #define LTC4282_POWER_ALARM_L_MASK BIT(6)
+ #define LTC4282_POWER_ALARM_H_MASK BIT(7)
+#define LTC4282_FET_BAD_FAULT_TIMEOUT 0x06
+ #define LTC4282_FET_BAD_MAX_TIMEOUT 255
+#define LTC4282_GPIO_CONFIG 0x07
+ #define LTC4282_GPIO_2_FET_STRESS_MASK BIT(1)
+ #define LTC4282_GPIO_1_CONFIG_MASK GENMASK(5, 4)
+#define LTC4282_VGPIO_MIN 0x08
+#define LTC4282_VGPIO_MAX 0x09
+#define LTC4282_VSOURCE_MIN 0x0a
+#define LTC4282_VSOURCE_MAX 0x0b
+#define LTC4282_VSENSE_MIN 0x0c
+#define LTC4282_VSENSE_MAX 0x0d
+#define LTC4282_POWER_MIN 0x0e
+#define LTC4282_POWER_MAX 0x0f
+#define LTC4282_CLK_DIV 0x10
+ #define LTC4282_CLK_DIV_MASK GENMASK(4, 0)
+ #define LTC4282_CLKOUT_MASK GENMASK(6, 5)
+#define LTC4282_ILIM_ADJUST 0x11
+ #define LTC4282_GPIO_MODE_MASK BIT(1)
+ #define LTC4282_VDD_MONITOR_MASK BIT(2)
+ #define LTC4282_FOLDBACK_MODE_MASK GENMASK(4, 3)
+ #define LTC4282_ILIM_ADJUST_MASK GENMASK(7, 5)
+#define LTC4282_ENERGY 0x12
+#define LTC4282_TIME_COUNTER 0x18
+#define LTC4282_ALERT_CTRL 0x1c
+ #define LTC4282_ALERT_OUT_MASK BIT(6)
+#define LTC4282_ADC_CTRL 0x1d
+ #define LTC4282_FAULT_LOG_EN_MASK BIT(2)
+ #define LTC4282_METER_HALT_MASK BIT(5)
+ #define LTC4282_METER_RESET_MASK BIT(6)
+ #define LTC4282_RESET_MASK BIT(7)
+#define LTC4282_STATUS_LSB 0x1e
+ #define LTC4282_OV_STATUS_MASK BIT(0)
+ #define LTC4282_UV_STATUS_MASK BIT(1)
+ #define LTC4282_VDD_STATUS_MASK \
+ (LTC4282_OV_STATUS_MASK | LTC4282_UV_STATUS_MASK)
+ #define LTC4282_OC_STATUS_MASK BIT(2)
+ #define LTC4282_POWER_GOOD_MASK BIT(3)
+ #define LTC4282_FET_FAILURE_MASK GENMASK(6, 5)
+#define LTC4282_STATUS_MSB 0x1f
+#define LTC4282_RESERVED_1 0x32
+#define LTC4282_RESERVED_2 0x33
+#define LTC4282_VGPIO 0x34
+#define LTC4282_VGPIO_LOWEST 0x36
+#define LTC4282_VGPIO_HIGHEST 0x38
+#define LTC4282_VSOURCE 0x3a
+#define LTC4282_VSOURCE_LOWEST 0x3c
+#define LTC4282_VSOURCE_HIGHEST 0x3e
+#define LTC4282_VSENSE 0x40
+#define LTC4282_VSENSE_LOWEST 0x42
+#define LTC4282_VSENSE_HIGHEST 0x44
+#define LTC4282_POWER 0x46
+#define LTC4282_POWER_LOWEST 0x48
+#define LTC4282_POWER_HIGHEST 0x4a
+#define LTC4282_RESERVED_3 0x50
+
+#define LTC4282_CLKIN_MIN (250 * KILO)
+#define LTC4282_CLKIN_MAX (15500 * KILO)
+#define LTC4282_CLKIN_RANGE (LTC4282_CLKIN_MAX - LTC4282_CLKIN_MIN + 1)
+#define LTC4282_CLKOUT_SYSTEM (250 * KILO)
+#define LTC4282_CLKOUT_CNV 15
+
+enum {
+ LTC4282_CHAN_VSOURCE,
+ LTC4282_CHAN_VDD,
+ LTC4282_CHAN_VGPIO,
+};
+
+struct ltc4282_cache {
+ u32 in_max_raw;
+ u32 in_min_raw;
+ long in_highest;
+ long in_lowest;
+ bool en;
+};
+
+struct ltc4282_state {
+ struct regmap *map;
+ /* Protect against multiple accesses to the device registers */
+ struct mutex lock;
+ struct clk_hw clk_hw;
+ /*
+ * Used to cache values for VDD/VSOURCE depending which will be used
+ * when hwmon is not enabled for that channel. Needed because they share
+ * the same registers.
+ */
+ struct ltc4282_cache in0_1_cache[LTC4282_CHAN_VGPIO];
+ u32 vsense_max;
+ long power_max;
+ u32 rsense;
+ u16 vdd;
+ u16 vfs_out;
+ bool energy_en;
+};
+
+enum {
+ LTC4282_CLKOUT_NONE,
+ LTC4282_CLKOUT_INT,
+ LTC4282_CLKOUT_TICK,
+};
+
+static int ltc4282_set_rate(struct clk_hw *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ struct ltc4282_state *st = container_of(hw, struct ltc4282_state,
+ clk_hw);
+ u32 val = LTC4282_CLKOUT_INT;
+
+ if (rate == LTC4282_CLKOUT_CNV)
+ val = LTC4282_CLKOUT_TICK;
+
+ return regmap_update_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK,
+ FIELD_PREP(LTC4282_CLKOUT_MASK, val));
+}
+
+/*
+ * Note the 15HZ conversion rate assumes 12bit ADC which is what we are
+ * supporting for now.
+ */
+static const unsigned int ltc4282_out_rates[] = {
+ LTC4282_CLKOUT_CNV, LTC4282_CLKOUT_SYSTEM
+};
+
+static long ltc4282_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int idx = find_closest(rate, ltc4282_out_rates,
+ ARRAY_SIZE(ltc4282_out_rates));
+
+ return ltc4282_out_rates[idx];
+}
+
+static unsigned long ltc4282_recalc_rate(struct clk_hw *hw,
+ unsigned long parent)
+{
+ struct ltc4282_state *st = container_of(hw, struct ltc4282_state,
+ clk_hw);
+ u32 clkdiv;
+ int ret;
+
+ ret = regmap_read(st->map, LTC4282_CLK_DIV, &clkdiv);
+ if (ret)
+ return 0;
+
+ clkdiv = FIELD_GET(LTC4282_CLKOUT_MASK, clkdiv);
+ if (!clkdiv)
+ return 0;
+ if (clkdiv == LTC4282_CLKOUT_INT)
+ return LTC4282_CLKOUT_SYSTEM;
+
+ return LTC4282_CLKOUT_CNV;
+}
+
+static void ltc4282_disable(struct clk_hw *clk_hw)
+{
+ struct ltc4282_state *st = container_of(clk_hw, struct ltc4282_state,
+ clk_hw);
+
+ regmap_clear_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK);
+}
+
+static int ltc4282_read_voltage_word(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long *val)
+{
+ __be16 in;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &in, sizeof(in));
+ if (ret)
+ return ret;
+
+ /*
+ * This is also used to calculate current in which case fs comes in
+ * 10 * uV. Hence the ULL usage.
+ */
+ *val = DIV_ROUND_CLOSEST_ULL(be16_to_cpu(in) * (u64)fs, U16_MAX);
+ return 0;
+}
+
+static int ltc4282_read_voltage_byte_cached(const struct ltc4282_state *st,
+ u32 reg, u32 fs, long *val,
+ u32 *cached_raw)
+{
+ int ret;
+ u32 in;
+
+ if (cached_raw) {
+ in = *cached_raw;
+ } else {
+ ret = regmap_read(st->map, reg, &in);
+ if (ret)
+ return ret;
+ }
+
+ *val = DIV_ROUND_CLOSEST(in * fs, U8_MAX);
+ return 0;
+}
+
+static int ltc4282_read_voltage_byte(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long *val)
+{
+ return ltc4282_read_voltage_byte_cached(st, reg, fs, val, NULL);
+}
+
+static int __ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask,
+ long *val)
+{
+ u32 alarm;
+ int ret;
+
+ ret = regmap_read(st->map, reg, &alarm);
+ if (ret)
+ return ret;
+
+ *val = !!(alarm & mask);
+
+ /* if not status/fault logs, clear the alarm after reading it */
+ if (reg != LTC4282_STATUS_LSB && reg != LTC4282_FAULT_LOG)
+ return regmap_clear_bits(st->map, reg, mask);
+
+ return 0;
+}
+
+static int ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask,
+ long *val)
+{
+ guard(mutex)(&st->lock);
+ return __ltc4282_read_alarm(st, reg, mask, val);
+}
+
+static int ltc4282_vdd_source_read_in(struct ltc4282_state *st, u32 channel,
+ long *val)
+{
+ guard(mutex)(&st->lock);
+ if (!st->in0_1_cache[channel].en)
+ return -ENODATA;
+
+ return ltc4282_read_voltage_word(st, LTC4282_VSOURCE, st->vfs_out, val);
+}
+
+static int ltc4282_vdd_source_read_hist(struct ltc4282_state *st, u32 reg,
+ u32 channel, long *cached, long *val)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+ if (!st->in0_1_cache[channel].en) {
+ *val = *cached;
+ return 0;
+ }
+
+ ret = ltc4282_read_voltage_word(st, reg, st->vfs_out, val);
+ if (ret)
+ return ret;
+
+ *cached = *val;
+ return 0;
+}
+
+static int ltc4282_vdd_source_read_lim(struct ltc4282_state *st, u32 reg,
+ u32 channel, u32 *cached, long *val)
+{
+ guard(mutex)(&st->lock);
+ if (!st->in0_1_cache[channel].en)
+ return ltc4282_read_voltage_byte_cached(st, reg, st->vfs_out,
+ val, cached);
+
+ return ltc4282_read_voltage_byte(st, reg, st->vfs_out, val);
+}
+
+static int ltc4282_vdd_source_read_alm(struct ltc4282_state *st, u32 mask,
+ u32 channel, long *val)
+{
+ guard(mutex)(&st->lock);
+ if (!st->in0_1_cache[channel].en) {
+ /*
+ * Do this otherwise alarms can get confused because we clear
+ * them after reading them. So, if someone mistakenly reads
+ * VSOURCE right before VDD (or the other way around), we might
+ * get no alarm just because it was cleared when reading VSOURCE
+ * and had no time for a new conversion and thus having the
+ * alarm again.
+ */
+ *val = 0;
+ return 0;
+ }
+
+ return __ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, mask, val);
+}
+
+static int ltc4282_read_in(struct ltc4282_state *st, u32 attr, long *val,
+ u32 channel)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st, LTC4282_VGPIO,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_in(st, channel, val);
+ case hwmon_in_highest:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st,
+ LTC4282_VGPIO_HIGHEST,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_HIGHEST,
+ channel,
+ &st->in0_1_cache[channel].in_highest, val);
+ case hwmon_in_lowest:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_word(st, LTC4282_VGPIO_LOWEST,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_LOWEST,
+ channel,
+ &st->in0_1_cache[channel].in_lowest, val);
+ case hwmon_in_max_alarm:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_GPIO_ALARM_H_MASK,
+ val);
+
+ return ltc4282_vdd_source_read_alm(st,
+ LTC4282_VSOURCE_ALARM_H_MASK,
+ channel, val);
+ case hwmon_in_min_alarm:
+ if (channel == LTC4282_CHAN_VGPIO)
+ ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_GPIO_ALARM_L_MASK, val);
+
+ return ltc4282_vdd_source_read_alm(st,
+ LTC4282_VSOURCE_ALARM_L_MASK,
+ channel, val);
+ case hwmon_in_crit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_OV_STATUS_MASK, val);
+ case hwmon_in_lcrit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_UV_STATUS_MASK, val);
+ case hwmon_in_max:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MAX,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MAX,
+ channel,
+ &st->in0_1_cache[channel].in_max_raw, val);
+ case hwmon_in_min:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MIN,
+ 1280, val);
+
+ return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MIN,
+ channel,
+ &st->in0_1_cache[channel].in_min_raw, val);
+ case hwmon_in_enable:
+ scoped_guard(mutex, &st->lock) {
+ *val = st->in0_1_cache[channel].en;
+ }
+ return 0;
+ case hwmon_in_fault:
+ /*
+ * We report failure if we detect either a fer_bad or a
+ * fet_short in the status register.
+ */
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_FET_FAILURE_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read_current_word(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ long in;
+ int ret;
+
+ /*
+ * We pass in full scale in 10 * micro (note that 40 is already
+ * millivolt) so we have better approximations to calculate current.
+ */
+ ret = ltc4282_read_voltage_word(st, reg, DECA * 40 * MILLI, &in);
+ if (ret)
+ return ret;
+
+ *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_current_byte(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ long in;
+ int ret;
+
+ ret = ltc4282_read_voltage_byte(st, reg, DECA * 40 * MILLI, &in);
+ if (ret)
+ return ret;
+
+ *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_curr(struct ltc4282_state *st, const u32 attr,
+ long *val)
+{
+ switch (attr) {
+ case hwmon_curr_input:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE, val);
+ case hwmon_curr_highest:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE_HIGHEST,
+ val);
+ case hwmon_curr_lowest:
+ return ltc4282_read_current_word(st, LTC4282_VSENSE_LOWEST,
+ val);
+ case hwmon_curr_max:
+ return ltc4282_read_current_byte(st, LTC4282_VSENSE_MAX, val);
+ case hwmon_curr_min:
+ return ltc4282_read_current_byte(st, LTC4282_VSENSE_MIN, val);
+ case hwmon_curr_max_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_VSENSE_ALARM_H_MASK, val);
+ case hwmon_curr_min_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_VSENSE_ALARM_L_MASK, val);
+ case hwmon_curr_crit_alarm:
+ return ltc4282_read_alarm(st, LTC4282_STATUS_LSB,
+ LTC4282_OC_STATUS_MASK, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read_power_word(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ u64 temp = DECA * 40ULL * st->vfs_out * BIT(16), temp_2;
+ __be16 raw;
+ u16 power;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, reg, &raw, sizeof(raw));
+ if (ret)
+ return ret;
+
+ power = be16_to_cpu(raw);
+ /*
+ * Power is given by:
+ * P = CODE(16b) * 0.040 * Vfs(out) * 2^16 / ((2^16 - 1)^2 * Rsense)
+ */
+ if (check_mul_overflow(power * temp, MICRO, &temp_2)) {
+ temp = DIV_ROUND_CLOSEST_ULL(power * temp, U16_MAX);
+ *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO,
+ U16_MAX * (u64)st->rsense);
+ return 0;
+ }
+
+ *val = DIV64_U64_ROUND_CLOSEST(temp_2,
+ st->rsense * int_pow(U16_MAX, 2));
+
+ return 0;
+}
+
+static int ltc4282_read_power_byte(const struct ltc4282_state *st, u32 reg,
+ long *val)
+{
+ u32 power;
+ u64 temp;
+ int ret;
+
+ ret = regmap_read(st->map, reg, &power);
+ if (ret)
+ return ret;
+
+ temp = power * 40 * DECA * st->vfs_out * BIT_ULL(8);
+ *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO,
+ int_pow(U8_MAX, 2) * st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_energy(const struct ltc4282_state *st, u64 *val)
+{
+ u64 temp, energy;
+ __be64 raw;
+ int ret;
+
+ ret = regmap_bulk_read(st->map, LTC4282_ENERGY, &raw, 6);
+ if (ret)
+ return ret;
+
+ energy = be64_to_cpu(raw) >> 16;
+ /*
+ * The formula for energy is given by:
+ * E = CODE(48b) * 0.040 * Vfs(out) * Tconv * 256 /
+ * ((2^16 - 1)^2 * Rsense)
+ *
+ * Since we only support 12bit ADC, Tconv = 0.065535s. Passing Vfs(out)
+ * and 0.040 to mV and Tconv to us, we can simplify the formula to:
+ * E = CODE(48b) * 40 * Vfs(out) * 256 / (U16_MAX * Rsense)
+ *
+ * As Rsense can have tenths of micro-ohm resolution, we need to
+ * multiply by DECA to get microujoule.
+ */
+ if (check_mul_overflow(DECA * st->vfs_out * 40 * BIT(8), energy, &temp)) {
+ temp = DIV_ROUND_CLOSEST(DECA * st->vfs_out * 40 * BIT(8), U16_MAX);
+ *val = DIV_ROUND_CLOSEST_ULL(temp * energy, st->rsense);
+ return 0;
+ }
+
+ *val = DIV64_U64_ROUND_CLOSEST(temp, U16_MAX * (u64)st->rsense);
+
+ return 0;
+}
+
+static int ltc4282_read_power(struct ltc4282_state *st, const u32 attr,
+ long *val)
+{
+ switch (attr) {
+ case hwmon_power_input:
+ return ltc4282_read_power_word(st, LTC4282_POWER, val);
+ case hwmon_power_input_highest:
+ return ltc4282_read_power_word(st, LTC4282_POWER_HIGHEST, val);
+ case hwmon_power_input_lowest:
+ return ltc4282_read_power_word(st, LTC4282_POWER_LOWEST, val);
+ case hwmon_power_max_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_POWER_ALARM_H_MASK, val);
+ case hwmon_power_min_alarm:
+ return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG,
+ LTC4282_POWER_ALARM_L_MASK, val);
+ case hwmon_power_max:
+ return ltc4282_read_power_byte(st, LTC4282_POWER_MAX, val);
+ case hwmon_power_min:
+ return ltc4282_read_power_byte(st, LTC4282_POWER_MIN, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct ltc4282_state *st = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_in:
+ return ltc4282_read_in(st, attr, val, channel);
+ case hwmon_curr:
+ return ltc4282_read_curr(st, attr, val);
+ case hwmon_power:
+ return ltc4282_read_power(st, attr, val);
+ case hwmon_energy:
+ scoped_guard(mutex, &st->lock) {
+ *val = st->energy_en;
+ }
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_write_power_byte(const struct ltc4282_state *st, u32 reg,
+ long val)
+{
+ u32 power;
+ u64 temp;
+
+ if (val > st->power_max)
+ val = st->power_max;
+
+ temp = val * int_pow(U8_MAX, 2) * st->rsense;
+ power = DIV64_U64_ROUND_CLOSEST(temp,
+ MICRO * DECA * 256ULL * st->vfs_out * 40);
+
+ return regmap_write(st->map, reg, power);
+}
+
+static int ltc4282_write_power_word(const struct ltc4282_state *st, u32 reg,
+ long val)
+{
+ u64 temp = int_pow(U16_MAX, 2) * st->rsense, temp_2;
+ __be16 __raw;
+ u16 code;
+
+ if (check_mul_overflow(temp, val, &temp_2)) {
+ temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MICRO);
+ code = DIV64_U64_ROUND_CLOSEST(temp * val,
+ 40ULL * BIT(16) * st->vfs_out);
+ } else {
+ temp = DECA * MICRO * 40ULL * BIT(16) * st->vfs_out;
+ code = DIV64_U64_ROUND_CLOSEST(temp_2, temp);
+ }
+
+ __raw = cpu_to_be16(code);
+ return regmap_bulk_write(st->map, reg, &__raw, sizeof(__raw));
+}
+
+static int __ltc4282_in_write_history(const struct ltc4282_state *st, u32 reg,
+ long lowest, long highest, u32 fs)
+{
+ __be16 __raw;
+ u16 tmp;
+ int ret;
+
+ tmp = DIV_ROUND_CLOSEST(U16_MAX * lowest, fs);
+
+ __raw = cpu_to_be16(tmp);
+
+ ret = regmap_bulk_write(st->map, reg, &__raw, 2);
+ if (ret)
+ return ret;
+
+ tmp = DIV_ROUND_CLOSEST(U16_MAX * highest, fs);
+
+ __raw = cpu_to_be16(tmp);
+
+ return regmap_bulk_write(st->map, reg + 2, &__raw, 2);
+}
+
+static int ltc4282_in_write_history(struct ltc4282_state *st, u32 reg,
+ long lowest, long highest, u32 fs)
+{
+ guard(mutex)(&st->lock);
+ return __ltc4282_in_write_history(st, reg, lowest, highest, fs);
+}
+
+static int ltc4282_power_reset_hist(struct ltc4282_state *st)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = ltc4282_write_power_word(st, LTC4282_POWER_LOWEST,
+ st->power_max);
+ if (ret)
+ return ret;
+
+ ret = ltc4282_write_power_word(st, LTC4282_POWER_HIGHEST, 0);
+ if (ret)
+ return ret;
+
+ /* now, let's also clear possible power_bad fault logs */
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_POWER_BAD_FAULT_MASK);
+}
+
+static int ltc4282_write_power(struct ltc4282_state *st, u32 attr,
+ long val)
+{
+ switch (attr) {
+ case hwmon_power_max:
+ return ltc4282_write_power_byte(st, LTC4282_POWER_MAX, val);
+ case hwmon_power_min:
+ return ltc4282_write_power_byte(st, LTC4282_POWER_MIN, val);
+ case hwmon_power_reset_history:
+ return ltc4282_power_reset_hist(st);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_write_voltage_byte_cached(const struct ltc4282_state *st,
+ u32 reg, u32 fs, long val,
+ u32 *cache_raw)
+{
+ u32 in;
+
+ val = clamp_val(val, 0, fs);
+ in = DIV_ROUND_CLOSEST(val * U8_MAX, fs);
+
+ if (cache_raw) {
+ *cache_raw = in;
+ return 0;
+ }
+
+ return regmap_write(st->map, reg, in);
+}
+
+static int ltc4282_write_voltage_byte(const struct ltc4282_state *st, u32 reg,
+ u32 fs, long val)
+{
+ return ltc4282_write_voltage_byte_cached(st, reg, fs, val, NULL);
+}
+
+static int ltc4282_cache_history(struct ltc4282_state *st, u32 channel)
+{
+ long val;
+ int ret;
+
+ ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_LOWEST, st->vfs_out,
+ &val);
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].in_lowest = val;
+
+ ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_HIGHEST,
+ st->vfs_out, &val);
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].in_highest = val;
+
+ ret = regmap_read(st->map, LTC4282_VSOURCE_MIN,
+ &st->in0_1_cache[channel].in_min_raw);
+ if (ret)
+ return ret;
+
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[channel].in_max_raw);
+}
+
+static int ltc4282_cache_sync(struct ltc4282_state *st, u32 channel)
+{
+ int ret;
+
+ ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->in0_1_cache[channel].in_lowest,
+ st->in0_1_cache[channel].in_highest,
+ st->vfs_out);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->map, LTC4282_VSOURCE_MIN,
+ st->in0_1_cache[channel].in_min_raw);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->map, LTC4282_VSOURCE_MAX,
+ st->in0_1_cache[channel].in_max_raw);
+}
+
+static int ltc4282_vdd_source_write_lim(struct ltc4282_state *st, u32 reg,
+ int channel, u32 *cache, long val)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+ if (st->in0_1_cache[channel].en)
+ ret = ltc4282_write_voltage_byte(st, reg, st->vfs_out, val);
+ else
+ ret = ltc4282_write_voltage_byte_cached(st, reg, st->vfs_out,
+ val, cache);
+
+ return ret;
+}
+
+static int ltc4282_vdd_source_reset_hist(struct ltc4282_state *st, int channel)
+{
+ long lowest = st->vfs_out;
+ int ret;
+
+ if (channel == LTC4282_CHAN_VDD)
+ lowest = st->vdd;
+
+ guard(mutex)(&st->lock);
+ if (st->in0_1_cache[channel].en) {
+ ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ lowest, 0, st->vfs_out);
+ if (ret)
+ return ret;
+ }
+
+ st->in0_1_cache[channel].in_lowest = lowest;
+ st->in0_1_cache[channel].in_highest = 0;
+
+ /*
+ * We are also clearing possible fault logs in reset_history. Clearing
+ * the logs might be important when the auto retry bits are not enabled
+ * as the chip only enables the output again after having these logs
+ * cleared. As some of these logs are related to limits, it makes sense
+ * to clear them in here. For VDD, we need to clear under/over voltage
+ * events. For VSOURCE, fet_short and fet_bad...
+ */
+ if (channel == LTC4282_CHAN_VSOURCE)
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_FET_FAILURE_FAULT_MASK);
+
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_VDD_FAULT_MASK);
+}
+
+/*
+ * We need to mux between VSOURCE and VDD which means they are mutually
+ * exclusive. Moreover, we can't really disable both VDD and VSOURCE as the ADC
+ * is continuously running (we cannot independently halt it without also
+ * stopping VGPIO). Hence, the logic is that disabling or enabling VDD will
+ * automatically have the reverse effect on VSOURCE and vice-versa.
+ */
+static int ltc4282_vdd_source_enable(struct ltc4282_state *st, int channel,
+ long val)
+{
+ int ret, other_chan = ~channel & 0x1;
+ u8 __val = val;
+
+ guard(mutex)(&st->lock);
+ if (st->in0_1_cache[channel].en == !!val)
+ return 0;
+
+ /* clearing the bit makes the ADC to monitor VDD */
+ if (channel == LTC4282_CHAN_VDD)
+ __val = !__val;
+
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_VDD_MONITOR_MASK,
+ FIELD_PREP(LTC4282_VDD_MONITOR_MASK, !!__val));
+ if (ret)
+ return ret;
+
+ st->in0_1_cache[channel].en = !!val;
+ st->in0_1_cache[other_chan].en = !val;
+
+ if (st->in0_1_cache[channel].en) {
+ /*
+ * Then, we are disabling @other_chan. Let's save it's current
+ * history.
+ */
+ ret = ltc4282_cache_history(st, other_chan);
+ if (ret)
+ return ret;
+
+ return ltc4282_cache_sync(st, channel);
+ }
+ /*
+ * Then, we are enabling @other_chan. We need to do the opposite from
+ * above.
+ */
+ ret = ltc4282_cache_history(st, channel);
+ if (ret)
+ return ret;
+
+ return ltc4282_cache_sync(st, other_chan);
+}
+
+static int ltc4282_write_in(struct ltc4282_state *st, u32 attr, long val,
+ int channel)
+{
+ switch (attr) {
+ case hwmon_in_max:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MAX,
+ 1280, val);
+
+ return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MAX,
+ channel,
+ &st->in0_1_cache[channel].in_max_raw, val);
+ case hwmon_in_min:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MIN,
+ 1280, val);
+
+ return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MIN,
+ channel,
+ &st->in0_1_cache[channel].in_min_raw, val);
+ case hwmon_in_reset_history:
+ if (channel == LTC4282_CHAN_VGPIO)
+ return ltc4282_in_write_history(st,
+ LTC4282_VGPIO_LOWEST,
+ 1280, 0, 1280);
+
+ return ltc4282_vdd_source_reset_hist(st, channel);
+ case hwmon_in_enable:
+ return ltc4282_vdd_source_enable(st, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_curr_reset_hist(struct ltc4282_state *st)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = __ltc4282_in_write_history(st, LTC4282_VSENSE_LOWEST,
+ st->vsense_max, 0, 40 * MILLI);
+ if (ret)
+ return ret;
+
+ /* now, let's also clear possible overcurrent fault logs */
+ return regmap_clear_bits(st->map, LTC4282_FAULT_LOG,
+ LTC4282_OC_FAULT_MASK);
+}
+
+static int ltc4282_write_curr(struct ltc4282_state *st, u32 attr,
+ long val)
+{
+ /* need to pass it in millivolt */
+ u32 in = DIV_ROUND_CLOSEST_ULL((u64)val * st->rsense, DECA * MICRO);
+
+ switch (attr) {
+ case hwmon_curr_max:
+ return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40,
+ in);
+ case hwmon_curr_min:
+ return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MIN, 40,
+ in);
+ case hwmon_curr_reset_history:
+ return ltc4282_curr_reset_hist(st);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ltc4282_energy_enable_set(struct ltc4282_state *st, long val)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+ /* setting the bit halts the meter */
+ ret = regmap_update_bits(st->map, LTC4282_ADC_CTRL,
+ LTC4282_METER_HALT_MASK,
+ FIELD_PREP(LTC4282_METER_HALT_MASK, !val));
+ if (ret)
+ return ret;
+
+ st->energy_en = !!val;
+
+ return 0;
+}
+
+static int ltc4282_write(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct ltc4282_state *st = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_power:
+ return ltc4282_write_power(st, attr, val);
+ case hwmon_in:
+ return ltc4282_write_in(st, attr, val, channel);
+ case hwmon_curr:
+ return ltc4282_write_curr(st, attr, val);
+ case hwmon_energy:
+ return ltc4282_energy_enable_set(st, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t ltc4282_in_is_visible(const struct ltc4282_state *st, u32 attr)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_highest:
+ case hwmon_in_lowest:
+ case hwmon_in_max_alarm:
+ case hwmon_in_min_alarm:
+ case hwmon_in_label:
+ case hwmon_in_lcrit_alarm:
+ case hwmon_in_crit_alarm:
+ case hwmon_in_fault:
+ return 0444;
+ case hwmon_in_max:
+ case hwmon_in_min:
+ case hwmon_in_enable:
+ case hwmon_in_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_curr_is_visible(u32 attr)
+{
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_highest:
+ case hwmon_curr_lowest:
+ case hwmon_curr_max_alarm:
+ case hwmon_curr_min_alarm:
+ case hwmon_curr_crit_alarm:
+ case hwmon_curr_label:
+ return 0444;
+ case hwmon_curr_max:
+ case hwmon_curr_min:
+ case hwmon_curr_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_power_is_visible(u32 attr)
+{
+ switch (attr) {
+ case hwmon_power_input:
+ case hwmon_power_input_highest:
+ case hwmon_power_input_lowest:
+ case hwmon_power_label:
+ case hwmon_power_max_alarm:
+ case hwmon_power_min_alarm:
+ return 0444;
+ case hwmon_power_max:
+ case hwmon_power_min:
+ case hwmon_power_reset_history:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static umode_t ltc4282_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ return ltc4282_in_is_visible(data, attr);
+ case hwmon_curr:
+ return ltc4282_curr_is_visible(attr);
+ case hwmon_power:
+ return ltc4282_power_is_visible(attr);
+ case hwmon_energy:
+ /* hwmon_energy_enable */
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static const char * const ltc4282_in_strs[] = {
+ "VSOURCE", "VDD", "VGPIO"
+};
+
+static int ltc4282_read_labels(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_in:
+ *str = ltc4282_in_strs[channel];
+ return 0;
+ case hwmon_curr:
+ *str = "ISENSE";
+ return 0;
+ case hwmon_power:
+ *str = "Power";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static ssize_t ltc4282_energy_show(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct ltc4282_state *st = dev_get_drvdata(dev);
+ u64 energy;
+ int ret;
+
+ guard(mutex)(&st->lock);
+ if (!st->energy_en)
+ return -ENODATA;
+
+ ret = ltc4282_read_energy(st, &energy);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%llu\n", energy);
+}
+
+static const struct clk_ops ltc4282_ops = {
+ .recalc_rate = ltc4282_recalc_rate,
+ .round_rate = ltc4282_round_rate,
+ .set_rate = ltc4282_set_rate,
+ .disable = ltc4282_disable,
+};
+
+static int ltc428_clk_provider_setup(struct ltc4282_state *st,
+ struct device *dev)
+{
+ struct clk_init_data init;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk",
+ fwnode_get_name(dev_fwnode(dev)));
+ if (!init.name)
+ return -ENOMEM;
+
+ init.ops = &ltc4282_ops;
+ init.flags = CLK_GET_RATE_NOCACHE;
+ st->clk_hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &st->clk_hw);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+ &st->clk_hw);
+}
+
+static int ltc428_clks_setup(struct ltc4282_state *st, struct device *dev)
+{
+ unsigned long rate;
+ struct clk *clkin;
+ u32 val;
+ int ret;
+
+ ret = ltc428_clk_provider_setup(st, dev);
+ if (ret)
+ return ret;
+
+ clkin = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clkin))
+ return dev_err_probe(dev, PTR_ERR(clkin),
+ "Failed to get clkin");
+ if (!clkin)
+ return 0;
+
+ rate = clk_get_rate(clkin);
+ if (!in_range(rate, LTC4282_CLKIN_MIN, LTC4282_CLKIN_RANGE))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid clkin range(%lu) [%lu %lu]\n",
+ rate, LTC4282_CLKIN_MIN,
+ LTC4282_CLKIN_MAX);
+
+ /*
+ * Clocks faster than 250KHZ should be reduced to 250KHZ. The clock
+ * frequency is divided by twice the value in the register.
+ */
+ val = rate / (2 * LTC4282_CLKIN_MIN);
+
+ return regmap_update_bits(st->map, LTC4282_CLK_DIV,
+ LTC4282_CLK_DIV_MASK,
+ FIELD_PREP(LTC4282_CLK_DIV_MASK, val));
+}
+
+static const int ltc4282_curr_lim_uv[] = {
+ 12500, 15625, 18750, 21875, 25000, 28125, 31250, 34375
+};
+
+static int ltc4282_get_defaults(struct ltc4282_state *st, u32 *vin_mode)
+{
+ u32 reg_val, ilm_adjust;
+ int ret;
+
+ ret = regmap_read(st->map, LTC4282_ADC_CTRL, &reg_val);
+ if (ret)
+ return ret;
+
+ st->energy_en = !FIELD_GET(LTC4282_METER_HALT_MASK, reg_val);
+
+ ret = regmap_read(st->map, LTC4282_CTRL_MSB, &reg_val);
+ if (ret)
+ return ret;
+
+ *vin_mode = FIELD_GET(LTC4282_CTRL_VIN_MODE_MASK, reg_val);
+
+ ret = regmap_read(st->map, LTC4282_ILIM_ADJUST, &reg_val);
+ if (ret)
+ return ret;
+
+ ilm_adjust = FIELD_GET(LTC4282_ILIM_ADJUST_MASK, reg_val);
+ st->vsense_max = ltc4282_curr_lim_uv[ilm_adjust];
+
+ st->in0_1_cache[LTC4282_CHAN_VSOURCE].en = FIELD_GET(LTC4282_VDD_MONITOR_MASK,
+ ilm_adjust);
+ if (!st->in0_1_cache[LTC4282_CHAN_VSOURCE].en) {
+ st->in0_1_cache[LTC4282_CHAN_VDD].en = true;
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_max_raw);
+ }
+
+ return regmap_read(st->map, LTC4282_VSOURCE_MAX,
+ &st->in0_1_cache[LTC4282_CHAN_VDD].in_max_raw);
+}
+
+/*
+ * Set max limits for ISENSE and Power as that depends on the max voltage on
+ * rsense that is defined in ILIM_ADJUST. This is specially important for power
+ * because for some rsense and vfsout values, if we allow the default raw 255
+ * value, that would overflow long in 32bit archs when reading back the max
+ * power limit.
+ *
+ * Also set meaningful historic values for VDD and VSOURCE
+ * (0 would not mean much).
+ */
+static int ltc4282_set_max_limits(struct ltc4282_state *st)
+{
+ int ret;
+
+ ret = ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40 * MILLI,
+ st->vsense_max);
+ if (ret)
+ return ret;
+
+ /* Power is given by ISENSE * Vout. */
+ st->power_max = DIV_ROUND_CLOSEST(st->vsense_max * DECA * MILLI, st->rsense) * st->vfs_out;
+ ret = ltc4282_write_power_byte(st, LTC4282_POWER_MAX, st->power_max);
+ if (ret)
+ return ret;
+
+ if (st->in0_1_cache[LTC4282_CHAN_VDD].en) {
+ st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_lowest = st->vfs_out;
+ return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->vdd, 0, st->vfs_out);
+ }
+
+ st->in0_1_cache[LTC4282_CHAN_VDD].in_lowest = st->vdd;
+ return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST,
+ st->vfs_out, 0, st->vfs_out);
+}
+
+static const char * const ltc4282_gpio1_modes[] = {
+ "power_bad", "power_good"
+};
+
+static const char * const ltc4282_gpio2_modes[] = {
+ "adc_input", "stress_fet"
+};
+
+static int ltc4282_gpio_setup(struct ltc4282_state *st, struct device *dev)
+{
+ const char *func = NULL;
+ int ret;
+
+ ret = device_property_read_string(dev, "adi,gpio1-mode", &func);
+ if (!ret) {
+ ret = match_string(ltc4282_gpio1_modes,
+ ARRAY_SIZE(ltc4282_gpio1_modes), func);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Invalid func(%s) for gpio1\n",
+ func);
+
+ ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG,
+ LTC4282_GPIO_1_CONFIG_MASK,
+ FIELD_PREP(LTC4282_GPIO_1_CONFIG_MASK, ret));
+ if (ret)
+ return ret;
+ }
+
+ ret = device_property_read_string(dev, "adi,gpio2-mode", &func);
+ if (!ret) {
+ ret = match_string(ltc4282_gpio2_modes,
+ ARRAY_SIZE(ltc4282_gpio2_modes), func);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Invalid func(%s) for gpio2\n",
+ func);
+ if (!ret) {
+ /* setting the bit to 1 so the ADC to monitors GPIO2 */
+ ret = regmap_set_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_GPIO_MODE_MASK);
+ } else {
+ ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG,
+ LTC4282_GPIO_2_FET_STRESS_MASK,
+ FIELD_PREP(LTC4282_GPIO_2_FET_STRESS_MASK, 1));
+ }
+
+ if (ret)
+ return ret;
+ }
+
+ if (!device_property_read_bool(dev, "adi,gpio3-monitor-enable"))
+ return 0;
+
+ if (func && !strcmp(func, "adc_input"))
+ return dev_err_probe(dev, -EINVAL,
+ "Cannot have both gpio2 and gpio3 muxed into the ADC");
+
+ return regmap_clear_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_GPIO_MODE_MASK);
+}
+
+static const char * const ltc4282_dividers[] = {
+ "external", "vdd_5_percent", "vdd_10_percent", "vdd_15_percent"
+};
+
+/* This maps the Vout full scale for the given Vin mode */
+static const u16 ltc4282_vfs_milli[] = { 5540, 8320, 16640, 33280 };
+
+static const u16 ltc4282_vdd_milli[] = { 3300, 5000, 12000, 24000 };
+
+enum {
+ LTC4282_VIN_3_3V,
+ LTC4282_VIN_5V,
+ LTC4282_VIN_12V,
+ LTC4282_VIN_24V,
+};
+
+static int ltc4282_setup(struct ltc4282_state *st, struct device *dev)
+{
+ const char *divider;
+ u32 val, vin_mode;
+ int ret;
+
+ /* The part has an eeprom so let's get the needed defaults from it */
+ ret = ltc4282_get_defaults(st, &vin_mode);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
+ &st->rsense);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read adi,rsense-nano-ohms\n");
+ if (st->rsense < CENTI)
+ return dev_err_probe(dev, -EINVAL,
+ "adi,rsense-nano-ohms too small (< %lu)\n",
+ CENTI);
+
+ /*
+ * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
+ * means we need nano in the bindings. However, to make things easier to
+ * handle (with respect to overflows) we divide it by 100 as we don't
+ * really need the last two digits.
+ */
+ st->rsense /= CENTI;
+
+ val = vin_mode;
+ ret = device_property_read_u32(dev, "adi,vin-mode-microvolt", &val);
+ if (!ret) {
+ switch (val) {
+ case 3300000:
+ val = LTC4282_VIN_3_3V;
+ break;
+ case 5000000:
+ val = LTC4282_VIN_5V;
+ break;
+ case 12000000:
+ val = LTC4282_VIN_12V;
+ break;
+ case 24000000:
+ val = LTC4282_VIN_24V;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%u) for vin-mode-microvolt\n",
+ val);
+ }
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_VIN_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_VIN_MODE_MASK, val));
+ if (ret)
+ return ret;
+
+ /* Foldback mode should also be set to the input voltage */
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_FOLDBACK_MODE_MASK,
+ FIELD_PREP(LTC4282_FOLDBACK_MODE_MASK, val));
+ if (ret)
+ return ret;
+ }
+
+ st->vfs_out = ltc4282_vfs_milli[val];
+ st->vdd = ltc4282_vdd_milli[val];
+
+ ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
+ &st->vsense_max);
+ if (!ret) {
+ int reg_val;
+
+ switch (val) {
+ case 12500:
+ reg_val = 0;
+ break;
+ case 15625:
+ reg_val = 1;
+ break;
+ case 18750:
+ reg_val = 2;
+ break;
+ case 21875:
+ reg_val = 3;
+ break;
+ case 25000:
+ reg_val = 4;
+ break;
+ case 28125:
+ reg_val = 5;
+ break;
+ case 31250:
+ reg_val = 6;
+ break;
+ case 34375:
+ reg_val = 7;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%u) for adi,current-limit-microvolt\n",
+ st->vsense_max);
+ }
+
+ ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST,
+ LTC4282_ILIM_ADJUST_MASK,
+ FIELD_PREP(LTC4282_ILIM_ADJUST_MASK, reg_val));
+ if (ret)
+ return ret;
+ }
+
+ ret = ltc4282_set_max_limits(st);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_string(dev, "adi,overvoltage-dividers",
+ &divider);
+ if (!ret) {
+ int div = match_string(ltc4282_dividers,
+ ARRAY_SIZE(ltc4282_dividers), divider);
+ if (div < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%s) for adi,overvoltage-divider\n",
+ divider);
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_OV_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_OV_MODE_MASK, div));
+ }
+
+ ret = device_property_read_string(dev, "adi,undervoltage-dividers",
+ &divider);
+ if (!ret) {
+ int div = match_string(ltc4282_dividers,
+ ARRAY_SIZE(ltc4282_dividers), divider);
+ if (div < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid val(%s) for adi,undervoltage-divider\n",
+ divider);
+
+ ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB,
+ LTC4282_CTRL_UV_MODE_MASK,
+ FIELD_PREP(LTC4282_CTRL_UV_MODE_MASK, div));
+ }
+
+ if (device_property_read_bool(dev, "adi,overcurrent-retry")) {
+ ret = regmap_set_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_OC_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
+ ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_OV_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
+ ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB,
+ LTC4282_CTRL_UV_RETRY_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,fault-log-enable")) {
+ ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL,
+ LTC4282_FAULT_LOG_EN_MASK);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_read_bool(dev, "adi,fault-log-enable")) {
+ ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_FAULT_LOG_EN_MASK);
+ if (ret)
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "adi,fet-bad-timeout-ms", &val);
+ if (!ret) {
+ if (val > LTC4282_FET_BAD_MAX_TIMEOUT)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid value(%u) for adi,fet-bad-timeout-ms",
+ val);
+
+ ret = regmap_write(st->map, LTC4282_FET_BAD_FAULT_TIMEOUT, val);
+ if (ret)
+ return ret;
+ }
+
+ return ltc4282_gpio_setup(st, dev);
+}
+
+static bool ltc4282_readable_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2)
+ return false;
+
+ return true;
+}
+
+static bool ltc4282_writable_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == LTC4282_STATUS_LSB || reg == LTC4282_STATUS_MSB)
+ return false;
+ if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2)
+ return false;
+
+ return true;
+}
+
+static const struct regmap_config ltc4282_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = LTC4282_RESERVED_3,
+ .readable_reg = ltc4282_readable_reg,
+ .writeable_reg = ltc4282_writable_reg,
+};
+
+static const struct hwmon_channel_info * const ltc4282_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_MAX_ALARM | HWMON_I_ENABLE |
+ HWMON_I_RESET_HISTORY | HWMON_I_FAULT |
+ HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_MAX_ALARM | HWMON_I_LCRIT_ALARM |
+ HWMON_I_CRIT_ALARM | HWMON_I_ENABLE |
+ HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
+ HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
+ HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
+ HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
+ HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
+ HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
+ HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
+ HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
+ HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
+ HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
+ HWMON_CHANNEL_INFO(energy,
+ HWMON_E_ENABLE),
+ NULL
+};
+
+static const struct hwmon_ops ltc4282_hwmon_ops = {
+ .read = ltc4282_read,
+ .write = ltc4282_write,
+ .is_visible = ltc4282_is_visible,
+ .read_string = ltc4282_read_labels,
+};
+
+static const struct hwmon_chip_info ltc2947_chip_info = {
+ .ops = &ltc4282_hwmon_ops,
+ .info = ltc4282_info,
+};
+
+/* energy attributes are 6bytes wide so we need u64 */
+static SENSOR_DEVICE_ATTR_RO(energy1_input, ltc4282_energy, 0);
+
+static struct attribute *ltc4282_attrs[] = {
+ &sensor_dev_attr_energy1_input.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ltc4282);
+
+static int ltc4282_show_fault_log(void *arg, u64 *val, u32 mask)
+{
+ struct ltc4282_state *st = arg;
+ long alarm;
+ int ret;
+
+ ret = ltc4282_read_alarm(st, LTC4282_FAULT_LOG, mask, &alarm);
+ if (ret)
+ return ret;
+
+ *val = alarm;
+
+ return 0;
+}
+
+static int ltc4282_show_curr1_crit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_OC_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_curr1_crit_fault_log,
+ ltc4282_show_curr1_crit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_in1_lcrit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_UV_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_lcrit_fault_log,
+ ltc4282_show_in1_lcrit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_in1_crit_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_OV_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_crit_fault_log,
+ ltc4282_show_in1_crit_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_fet_bad_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_FET_BAD_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_bad_fault_log,
+ ltc4282_show_fet_bad_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_fet_short_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_FET_SHORT_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_short_fault_log,
+ ltc4282_show_fet_short_fault_log, NULL, "%llu\n");
+
+static int ltc4282_show_power1_bad_fault_log(void *arg, u64 *val)
+{
+ return ltc4282_show_fault_log(arg, val, LTC4282_POWER_BAD_FAULT_MASK);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_power1_bad_fault_log,
+ ltc4282_show_power1_bad_fault_log, NULL, "%llu\n");
+
+static void ltc4282_debugfs_remove(void *dir)
+{
+ debugfs_remove_recursive(dir);
+}
+
+static void ltc4282_debugfs_init(struct ltc4282_state *st,
+ struct i2c_client *i2c,
+ const struct device *hwmon)
+{
+ const char *debugfs_name;
+ struct dentry *dentry;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_DEBUG_FS))
+ return;
+
+ debugfs_name = devm_kasprintf(&i2c->dev, GFP_KERNEL, "ltc4282-%s",
+ dev_name(hwmon));
+ if (!debugfs_name)
+ return;
+
+ dentry = debugfs_create_dir(debugfs_name, NULL);
+ if (IS_ERR(dentry))
+ return;
+
+ ret = devm_add_action_or_reset(&i2c->dev, ltc4282_debugfs_remove,
+ dentry);
+ if (ret)
+ return;
+
+ debugfs_create_file_unsafe("power1_bad_fault_log", 0400, dentry, st,
+ &ltc4282_power1_bad_fault_log);
+ debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, dentry, st,
+ &ltc4282_fet_short_fault_log);
+ debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, dentry, st,
+ &ltc4282_fet_bad_fault_log);
+ debugfs_create_file_unsafe("in1_crit_fault_log", 0400, dentry, st,
+ &ltc4282_in1_crit_fault_log);
+ debugfs_create_file_unsafe("in1_lcrit_fault_log", 0400, dentry, st,
+ &ltc4282_in1_lcrit_fault_log);
+ debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, dentry, st,
+ &ltc4282_curr1_crit_fault_log);
+}
+
+static int ltc4282_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev, *hwmon;
+ struct ltc4282_state *st;
+ int ret;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate memory\n");
+
+ st->map = devm_regmap_init_i2c(i2c, &ltc4282_regmap_config);
+ if (IS_ERR(st->map))
+ return dev_err_probe(dev, PTR_ERR(st->map),
+ "failed regmap init\n");
+
+ /* Soft reset */
+ ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_RESET_MASK);
+ if (ret)
+ return ret;
+
+ /* Yes, it's big but it is as specified in the datasheet */
+ msleep(3200);
+
+ ret = ltc428_clks_setup(st, dev);
+ if (ret)
+ return ret;
+
+ ret = ltc4282_setup(st, dev);
+ if (ret)
+ return ret;
+
+ mutex_init(&st->lock);
+ hwmon = devm_hwmon_device_register_with_info(dev, "ltc4282", st,
+ &ltc2947_chip_info,
+ ltc4282_groups);
+ if (IS_ERR(hwmon))
+ return PTR_ERR(hwmon);
+
+ ltc4282_debugfs_init(st, i2c, hwmon);
+
+ return 0;
+}
+
+static const struct of_device_id ltc4282_of_match[] = {
+ { .compatible = "adi,ltc4282" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ltc4282_of_match);
+
+static struct i2c_driver ltc4282_driver = {
+ .driver = {
+ .name = "ltc4282",
+ .of_match_table = ltc4282_of_match,
+ },
+ .probe = ltc4282_probe,
+};
+module_i2c_driver(ltc4282_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("LTC4282 I2C High Current Hot Swap Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c
index ee5ead06d6..da2289e356 100644
--- a/drivers/hwmon/max127.c
+++ b/drivers/hwmon/max127.c
@@ -335,7 +335,6 @@ static const struct i2c_device_id max127_id[] = {
MODULE_DEVICE_TABLE(i2c, max127_id);
static struct i2c_driver max127_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max127",
},
diff --git a/drivers/hwmon/max31760.c b/drivers/hwmon/max31760.c
index 79945eb466..127e31ca3c 100644
--- a/drivers/hwmon/max31760.c
+++ b/drivers/hwmon/max31760.c
@@ -60,7 +60,7 @@ static const struct regmap_config regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x5B,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = max31760_volatile_reg,
};
@@ -578,7 +578,6 @@ static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend,
max31760_resume);
static struct i2c_driver max31760_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max31760",
.of_match_table = max31760_of_match,
diff --git a/drivers/hwmon/max31790.c b/drivers/hwmon/max31790.c
index 0cd44c1e99..3dc95196b2 100644
--- a/drivers/hwmon/max31790.c
+++ b/drivers/hwmon/max31790.c
@@ -543,7 +543,6 @@ static const struct i2c_device_id max31790_id[] = {
MODULE_DEVICE_TABLE(i2c, max31790_id);
static struct i2c_driver max31790_driver = {
- .class = I2C_CLASS_HWMON,
.probe = max31790_probe,
.driver = {
.name = "max31790",
diff --git a/drivers/hwmon/max31827.c b/drivers/hwmon/max31827.c
index 4a8c3e37c5..f8a13b30f1 100644
--- a/drivers/hwmon/max31827.c
+++ b/drivers/hwmon/max31827.c
@@ -652,7 +652,6 @@ static const struct of_device_id max31827_of_match[] = {
MODULE_DEVICE_TABLE(of, max31827_of_match);
static struct i2c_driver max31827_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max31827",
.of_match_table = max31827_of_match,
diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c
index af7e626858..05426cde0e 100644
--- a/drivers/hwmon/max6621.c
+++ b/drivers/hwmon/max6621.c
@@ -549,7 +549,6 @@ static const struct of_device_id __maybe_unused max6621_of_match[] = {
MODULE_DEVICE_TABLE(of, max6621_of_match);
static struct i2c_driver max6621_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = MAX6621_DRV_NAME,
.of_match_table = of_match_ptr(max6621_of_match),
diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c
index 7d10dd434f..d161ba0e78 100644
--- a/drivers/hwmon/max6697.c
+++ b/drivers/hwmon/max6697.c
@@ -780,7 +780,6 @@ static const struct of_device_id __maybe_unused max6697_of_match[] = {
MODULE_DEVICE_TABLE(of, max6697_of_match);
static struct i2c_driver max6697_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "max6697",
.of_match_table = of_match_ptr(max6697_of_match),
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
index 3f3f7a8841..0d016fedb9 100644
--- a/drivers/hwmon/nct6683.c
+++ b/drivers/hwmon/nct6683.c
@@ -174,6 +174,7 @@ superio_exit(int ioreg)
#define NCT6683_CUSTOMER_ID_MITAC 0xa0e
#define NCT6683_CUSTOMER_ID_MSI 0x201
#define NCT6683_CUSTOMER_ID_MSI2 0x200
+#define NCT6683_CUSTOMER_ID_MSI3 0x207
#define NCT6683_CUSTOMER_ID_ASROCK 0xe2c
#define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b
#define NCT6683_CUSTOMER_ID_ASROCK3 0x1631
@@ -1224,6 +1225,8 @@ static int nct6683_probe(struct platform_device *pdev)
break;
case NCT6683_CUSTOMER_ID_MSI2:
break;
+ case NCT6683_CUSTOMER_ID_MSI3:
+ break;
case NCT6683_CUSTOMER_ID_ASROCK:
break;
case NCT6683_CUSTOMER_ID_ASROCK2:
diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c
index 024cff151c..a0e664d5eb 100644
--- a/drivers/hwmon/nct7802.c
+++ b/drivers/hwmon/nct7802.c
@@ -1051,7 +1051,7 @@ static bool nct7802_regmap_is_volatile(struct device *dev, unsigned int reg)
static const struct regmap_config nct7802_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = nct7802_regmap_is_volatile,
};
diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c
new file mode 100644
index 0000000000..5806a3f32b
--- /dev/null
+++ b/drivers/hwmon/nzxt-kraken3.c
@@ -0,0 +1,1008 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for NZXT Kraken X53/X63/X73 and Z53/Z63/Z73 all in one coolers.
+ * X53 and Z53 in code refer to all models in their respective series (shortened
+ * for brevity).
+ *
+ * Copyright 2021 Jonas Malaco <jonas@protocubo.io>
+ * Copyright 2022 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <asm/unaligned.h>
+
+#define USB_VENDOR_ID_NZXT 0x1e71
+#define USB_PRODUCT_ID_X53 0x2007
+#define USB_PRODUCT_ID_X53_SECOND 0x2014
+#define USB_PRODUCT_ID_Z53 0x3008
+
+enum kinds { X53, Z53 } __packed;
+enum pwm_enable { off, manual, curve } __packed;
+
+static const char *const kraken3_device_names[] = {
+ [X53] = "x53",
+ [Z53] = "z53",
+};
+
+#define DRIVER_NAME "nzxt_kraken3"
+#define STATUS_REPORT_ID 0x75
+#define FIRMWARE_REPORT_ID 0x11
+#define STATUS_VALIDITY 2000 /* In ms, equivalent to period of four status reports */
+#define CUSTOM_CURVE_POINTS 40 /* For temps from 20C to 59C (critical temp) */
+#define PUMP_DUTY_MIN 20 /* In percent */
+
+/* Sensor report offsets for Kraken X53 and Z53 */
+#define TEMP_SENSOR_START_OFFSET 15
+#define TEMP_SENSOR_END_OFFSET 16
+#define PUMP_SPEED_OFFSET 17
+#define PUMP_DUTY_OFFSET 19
+
+/* Firmware version report offset for Kraken X53 and Z53 */
+#define FIRMWARE_VERSION_OFFSET 17
+
+/* Sensor report offsets for Kraken Z53 */
+#define Z53_FAN_SPEED_OFFSET 23
+#define Z53_FAN_DUTY_OFFSET 25
+
+/* Report offsets for control commands for Kraken X53 and Z53 */
+#define SET_DUTY_ID_OFFSET 1
+
+/* Control commands and their lengths for Kraken X53 and Z53 */
+
+/* Last byte sets the report interval at 0.5s */
+static const u8 set_interval_cmd[] = { 0x70, 0x02, 0x01, 0xB8, 1 };
+static const u8 finish_init_cmd[] = { 0x70, 0x01 };
+static const u8 __maybe_unused get_fw_version_cmd[] = { 0x10, 0x01 };
+static const u8 set_pump_duty_cmd_header[] = { 0x72, 0x00, 0x00, 0x00 };
+static const u8 z53_get_status_cmd[] = { 0x74, 0x01 };
+
+#define SET_INTERVAL_CMD_LENGTH 5
+#define FINISH_INIT_CMD_LENGTH 2
+#define GET_FW_VERSION_CMD_LENGTH 2
+#define MAX_REPORT_LENGTH 64
+#define MIN_REPORT_LENGTH 20
+#define SET_CURVE_DUTY_CMD_HEADER_LENGTH 4
+/* 4 byte header and 40 duty offsets */
+#define SET_CURVE_DUTY_CMD_LENGTH (4 + 40)
+#define Z53_GET_STATUS_CMD_LENGTH 2
+
+static const char *const kraken3_temp_label[] = {
+ "Coolant temp",
+};
+
+static const char *const kraken3_fan_label[] = {
+ "Pump speed",
+ "Fan speed"
+};
+
+struct kraken3_channel_info {
+ enum pwm_enable mode;
+
+ /* Both values are PWM */
+ u16 reported_duty;
+ u16 fixed_duty; /* Manually set fixed duty */
+
+ u8 pwm_points[CUSTOM_CURVE_POINTS];
+};
+
+struct kraken3_data {
+ struct hid_device *hdev;
+ struct device *hwmon_dev;
+ struct dentry *debugfs;
+ struct mutex buffer_lock; /* For locking access to buffer */
+ struct mutex z53_status_request_lock;
+ struct completion fw_version_processed;
+ /*
+ * For X53 devices, tracks whether an initial (one) sensor report was received to
+ * make fancontrol not bail outright. For Z53 devices, whether a status report
+ * was processed after requesting one.
+ */
+ struct completion status_report_processed;
+ /* For locking the above completion */
+ spinlock_t status_completion_lock;
+
+ u8 *buffer;
+ struct kraken3_channel_info channel_info[2]; /* Pump and fan */
+ bool is_device_faulty;
+
+ /* Sensor values */
+ s32 temp_input[1];
+ u16 fan_input[2];
+
+ enum kinds kind;
+ u8 firmware_version[3];
+
+ unsigned long updated; /* jiffies */
+};
+
+static umode_t kraken3_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct kraken3_data *priv = data;
+
+ switch (type) {
+ case hwmon_temp:
+ if (channel < 1)
+ return 0444;
+ break;
+ case hwmon_fan:
+ switch (priv->kind) {
+ case X53:
+ /* Just the pump */
+ if (channel < 1)
+ return 0444;
+ break;
+ case Z53:
+ /* Pump and fan */
+ if (channel < 2)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_input:
+ switch (priv->kind) {
+ case X53:
+ /* Just the pump */
+ if (channel < 1)
+ return 0644;
+ break;
+ case Z53:
+ /* Pump and fan */
+ if (channel < 2)
+ return 0644;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Writes the command to the device with the rest of the report (up to 64 bytes) filled
+ * with zeroes.
+ */
+static int kraken3_write_expanded(struct kraken3_data *priv, const u8 *cmd, int cmd_length)
+{
+ int ret;
+
+ mutex_lock(&priv->buffer_lock);
+
+ memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
+ ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
+
+ mutex_unlock(&priv->buffer_lock);
+ return ret;
+}
+
+static int kraken3_percent_to_pwm(long val)
+{
+ return DIV_ROUND_CLOSEST(val * 255, 100);
+}
+
+static int kraken3_pwm_to_percent(long val, int channel)
+{
+ int percent_value;
+
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ percent_value = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ /* Bring up pump duty to min value if needed */
+ if (channel == 0 && percent_value < PUMP_DUTY_MIN)
+ percent_value = PUMP_DUTY_MIN;
+
+ return percent_value;
+}
+
+static int kraken3_read_x53(struct kraken3_data *priv)
+{
+ int ret;
+
+ if (completion_done(&priv->status_report_processed))
+ /*
+ * We're here because data is stale. This means that sensor reports haven't
+ * been received for some time in kraken3_raw_event(). On X-series sensor data
+ * can't be manually requested, so return an error.
+ */
+ return -ENODATA;
+
+ /*
+ * Data needs to be read, but a sensor report wasn't yet received. It's usually
+ * fancontrol that requests data this early and it exits if it reads an error code.
+ * So, wait for the first report to be parsed (but up to STATUS_VALIDITY).
+ * This does not concern the Z series devices, because they send a sensor report
+ * only when requested.
+ */
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ /* The first sensor report was parsed on time and reading can continue */
+ return 0;
+}
+
+static int kraken3_read_z53(struct kraken3_data *priv)
+{
+ int ret = mutex_lock_interruptible(&priv->z53_status_request_lock);
+
+ if (ret < 0)
+ return ret;
+
+ if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ /* Data is up to date */
+ goto unlock_and_return;
+ }
+
+ /*
+ * Disable interrupts for a moment to safely reinit the completion,
+ * as hidraw calls could have allowed one or more readers to complete.
+ */
+ spin_lock_bh(&priv->status_completion_lock);
+ reinit_completion(&priv->status_report_processed);
+ spin_unlock_bh(&priv->status_completion_lock);
+
+ /* Send command for getting status */
+ ret = kraken3_write_expanded(priv, z53_get_status_cmd, Z53_GET_STATUS_CMD_LENGTH);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ /* Wait for completion from kraken3_raw_event() */
+ ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+
+unlock_and_return:
+ mutex_unlock(&priv->z53_status_request_lock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int kraken3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
+ if (priv->kind == X53)
+ ret = kraken3_read_x53(priv);
+ else
+ ret = kraken3_read_z53(priv);
+
+ if (ret < 0)
+ return ret;
+
+ if (priv->is_device_faulty)
+ return -ENODATA;
+ }
+
+ switch (type) {
+ case hwmon_temp:
+ *val = priv->temp_input[channel];
+ break;
+ case hwmon_fan:
+ *val = priv->fan_input[channel];
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ *val = priv->channel_info[channel].mode;
+ break;
+ case hwmon_pwm_input:
+ *val = priv->channel_info[channel].reported_duty;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int kraken3_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = kraken3_temp_label[channel];
+ break;
+ case hwmon_fan:
+ *str = kraken3_fan_label[channel];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/* Writes custom curve to device */
+static int kraken3_write_curve(struct kraken3_data *priv, u8 *curve_array, int channel)
+{
+ u8 fixed_duty_cmd[SET_CURVE_DUTY_CMD_LENGTH];
+ int ret;
+
+ /* Copy command header */
+ memcpy(fixed_duty_cmd, set_pump_duty_cmd_header, SET_CURVE_DUTY_CMD_HEADER_LENGTH);
+
+ /* Set the correct ID for writing pump/fan duty (0x01 or 0x02, respectively) */
+ fixed_duty_cmd[SET_DUTY_ID_OFFSET] = channel + 1;
+
+ /* Copy curve to command */
+ memcpy(fixed_duty_cmd + SET_CURVE_DUTY_CMD_HEADER_LENGTH, curve_array, CUSTOM_CURVE_POINTS);
+
+ ret = kraken3_write_expanded(priv, fixed_duty_cmd, SET_CURVE_DUTY_CMD_LENGTH);
+ return ret;
+}
+
+static int kraken3_write_fixed_duty(struct kraken3_data *priv, long val, int channel)
+{
+ u8 fixed_curve_points[CUSTOM_CURVE_POINTS];
+ int ret, percent_val, i;
+
+ percent_val = kraken3_pwm_to_percent(val, channel);
+ if (percent_val < 0)
+ return percent_val;
+
+ /*
+ * The devices can only control the duty through a curve.
+ * Since we're setting a fixed duty here, fill the whole curve
+ * (ranging from 20C to 59C) with the same duty, except for
+ * the last point, the critical temperature, where it's maxed
+ * out for safety.
+ */
+
+ /* Fill the custom curve with the fixed value we're setting */
+ for (i = 0; i < CUSTOM_CURVE_POINTS - 1; i++)
+ fixed_curve_points[i] = percent_val;
+
+ /* Force duty to 100% at critical temp */
+ fixed_curve_points[CUSTOM_CURVE_POINTS - 1] = 100;
+
+ /* Write the fixed duty curve to the device */
+ ret = kraken3_write_curve(priv, fixed_curve_points, channel);
+ return ret;
+}
+
+static int kraken3_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ /* Remember the last set fixed duty for channel */
+ priv->channel_info[channel].fixed_duty = val;
+
+ if (priv->channel_info[channel].mode == manual) {
+ ret = kraken3_write_fixed_duty(priv, val, channel);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Lock onto this value and report it until next interrupt status
+ * report is received, so userspace tools can continue to work.
+ */
+ priv->channel_info[channel].reported_duty = val;
+ }
+ break;
+ case hwmon_pwm_enable:
+ if (val < 0 || val > 2)
+ return -EINVAL;
+
+ switch (val) {
+ case 0:
+ /* Set channel to 100%, direct duty value */
+ ret = kraken3_write_fixed_duty(priv, 255, channel);
+ if (ret < 0)
+ return ret;
+
+ /* We don't control anything anymore */
+ priv->channel_info[channel].mode = off;
+ break;
+ case 1:
+ /* Apply the last known direct duty value */
+ ret =
+ kraken3_write_fixed_duty(priv,
+ priv->channel_info[channel].fixed_duty,
+ channel);
+ if (ret < 0)
+ return ret;
+
+ priv->channel_info[channel].mode = manual;
+ break;
+ case 2:
+ /* Apply the curve and note as enabled */
+ ret =
+ kraken3_write_curve(priv,
+ priv->channel_info[channel].pwm_points,
+ channel);
+ if (ret < 0)
+ return ret;
+
+ priv->channel_info[channel].mode = curve;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static ssize_t kraken3_fan_curve_pwm_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+ long val;
+ int ret;
+
+ if (kstrtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ val = kraken3_pwm_to_percent(val, dev_attr->nr);
+ if (val < 0)
+ return val;
+
+ priv->channel_info[dev_attr->nr].pwm_points[dev_attr->index] = val;
+
+ if (priv->channel_info[dev_attr->nr].mode == curve) {
+ /* Apply the curve */
+ ret =
+ kraken3_write_curve(priv,
+ priv->channel_info[dev_attr->nr].pwm_points, dev_attr->nr);
+ if (ret < 0)
+ return ret;
+ }
+
+ return count;
+}
+
+static umode_t kraken3_curve_props_are_visible(struct kobject *kobj, struct attribute *attr,
+ int index)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct kraken3_data *priv = dev_get_drvdata(dev);
+
+ /* Only Z53 has the fan curve */
+ if (index >= CUSTOM_CURVE_POINTS && priv->kind != Z53)
+ return 0;
+
+ return attr->mode;
+}
+
+/* Custom pump curve from 20C to 59C (critical temp) */
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point1_pwm, kraken3_fan_curve_pwm, 0, 0);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point2_pwm, kraken3_fan_curve_pwm, 0, 1);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point3_pwm, kraken3_fan_curve_pwm, 0, 2);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point4_pwm, kraken3_fan_curve_pwm, 0, 3);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point5_pwm, kraken3_fan_curve_pwm, 0, 4);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point6_pwm, kraken3_fan_curve_pwm, 0, 5);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point7_pwm, kraken3_fan_curve_pwm, 0, 6);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point8_pwm, kraken3_fan_curve_pwm, 0, 7);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point9_pwm, kraken3_fan_curve_pwm, 0, 8);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point10_pwm, kraken3_fan_curve_pwm, 0, 9);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point11_pwm, kraken3_fan_curve_pwm, 0, 10);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point12_pwm, kraken3_fan_curve_pwm, 0, 11);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point13_pwm, kraken3_fan_curve_pwm, 0, 12);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point14_pwm, kraken3_fan_curve_pwm, 0, 13);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point15_pwm, kraken3_fan_curve_pwm, 0, 14);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point16_pwm, kraken3_fan_curve_pwm, 0, 15);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point17_pwm, kraken3_fan_curve_pwm, 0, 16);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point18_pwm, kraken3_fan_curve_pwm, 0, 17);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point19_pwm, kraken3_fan_curve_pwm, 0, 18);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point20_pwm, kraken3_fan_curve_pwm, 0, 19);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point21_pwm, kraken3_fan_curve_pwm, 0, 20);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point22_pwm, kraken3_fan_curve_pwm, 0, 21);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point23_pwm, kraken3_fan_curve_pwm, 0, 22);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point24_pwm, kraken3_fan_curve_pwm, 0, 23);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point25_pwm, kraken3_fan_curve_pwm, 0, 24);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point26_pwm, kraken3_fan_curve_pwm, 0, 25);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point27_pwm, kraken3_fan_curve_pwm, 0, 26);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point28_pwm, kraken3_fan_curve_pwm, 0, 27);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point29_pwm, kraken3_fan_curve_pwm, 0, 28);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point30_pwm, kraken3_fan_curve_pwm, 0, 29);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point31_pwm, kraken3_fan_curve_pwm, 0, 30);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point32_pwm, kraken3_fan_curve_pwm, 0, 31);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point33_pwm, kraken3_fan_curve_pwm, 0, 32);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point34_pwm, kraken3_fan_curve_pwm, 0, 33);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point35_pwm, kraken3_fan_curve_pwm, 0, 34);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point36_pwm, kraken3_fan_curve_pwm, 0, 35);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point37_pwm, kraken3_fan_curve_pwm, 0, 36);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point38_pwm, kraken3_fan_curve_pwm, 0, 37);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point39_pwm, kraken3_fan_curve_pwm, 0, 38);
+static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point40_pwm, kraken3_fan_curve_pwm, 0, 39);
+
+/* Custom fan curve from 20C to 59C (critical temp) */
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point1_pwm, kraken3_fan_curve_pwm, 1, 0);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point2_pwm, kraken3_fan_curve_pwm, 1, 1);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point3_pwm, kraken3_fan_curve_pwm, 1, 2);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point4_pwm, kraken3_fan_curve_pwm, 1, 3);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point5_pwm, kraken3_fan_curve_pwm, 1, 4);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point6_pwm, kraken3_fan_curve_pwm, 1, 5);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point7_pwm, kraken3_fan_curve_pwm, 1, 6);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point8_pwm, kraken3_fan_curve_pwm, 1, 7);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point9_pwm, kraken3_fan_curve_pwm, 1, 8);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point10_pwm, kraken3_fan_curve_pwm, 1, 9);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point11_pwm, kraken3_fan_curve_pwm, 1, 10);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point12_pwm, kraken3_fan_curve_pwm, 1, 11);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point13_pwm, kraken3_fan_curve_pwm, 1, 12);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point14_pwm, kraken3_fan_curve_pwm, 1, 13);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point15_pwm, kraken3_fan_curve_pwm, 1, 14);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point16_pwm, kraken3_fan_curve_pwm, 1, 15);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point17_pwm, kraken3_fan_curve_pwm, 1, 16);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point18_pwm, kraken3_fan_curve_pwm, 1, 17);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point19_pwm, kraken3_fan_curve_pwm, 1, 18);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point20_pwm, kraken3_fan_curve_pwm, 1, 19);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point21_pwm, kraken3_fan_curve_pwm, 1, 20);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point22_pwm, kraken3_fan_curve_pwm, 1, 21);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point23_pwm, kraken3_fan_curve_pwm, 1, 22);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point24_pwm, kraken3_fan_curve_pwm, 1, 23);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point25_pwm, kraken3_fan_curve_pwm, 1, 24);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point26_pwm, kraken3_fan_curve_pwm, 1, 25);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point27_pwm, kraken3_fan_curve_pwm, 1, 26);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point28_pwm, kraken3_fan_curve_pwm, 1, 27);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point29_pwm, kraken3_fan_curve_pwm, 1, 28);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point30_pwm, kraken3_fan_curve_pwm, 1, 29);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point31_pwm, kraken3_fan_curve_pwm, 1, 30);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point32_pwm, kraken3_fan_curve_pwm, 1, 31);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point33_pwm, kraken3_fan_curve_pwm, 1, 32);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point34_pwm, kraken3_fan_curve_pwm, 1, 33);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point35_pwm, kraken3_fan_curve_pwm, 1, 34);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point36_pwm, kraken3_fan_curve_pwm, 1, 35);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point37_pwm, kraken3_fan_curve_pwm, 1, 36);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point38_pwm, kraken3_fan_curve_pwm, 1, 37);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point39_pwm, kraken3_fan_curve_pwm, 1, 38);
+static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point40_pwm, kraken3_fan_curve_pwm, 1, 39);
+
+static struct attribute *kraken3_curve_attrs[] = {
+ /* Pump control curve */
+ &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point8_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point9_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point10_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point11_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point12_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point13_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point14_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point15_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point16_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point17_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point18_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point19_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point20_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point21_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point22_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point23_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point24_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point25_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point26_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point27_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point28_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point29_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point30_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point31_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point32_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point33_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point34_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point35_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point36_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point37_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point38_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point39_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp1_auto_point40_pwm.dev_attr.attr,
+ /* Fan control curve (Z53 only) */
+ &sensor_dev_attr_temp2_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point8_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point9_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point10_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point11_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point12_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point13_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point14_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point15_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point16_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point17_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point18_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point19_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point20_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point21_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point22_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point23_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point24_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point25_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point26_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point27_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point28_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point29_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point30_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point31_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point32_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point33_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point34_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point35_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point36_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point37_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point38_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point39_pwm.dev_attr.attr,
+ &sensor_dev_attr_temp2_auto_point40_pwm.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group kraken3_curves_group = {
+ .attrs = kraken3_curve_attrs,
+ .is_visible = kraken3_curve_props_are_visible
+};
+
+static const struct attribute_group *kraken3_groups[] = {
+ &kraken3_curves_group,
+ NULL
+};
+
+static const struct hwmon_ops kraken3_hwmon_ops = {
+ .is_visible = kraken3_is_visible,
+ .read = kraken3_read,
+ .read_string = kraken3_read_string,
+ .write = kraken3_write
+};
+
+static const struct hwmon_channel_info *kraken3_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL
+};
+
+static const struct hwmon_chip_info kraken3_chip_info = {
+ .ops = &kraken3_hwmon_ops,
+ .info = kraken3_info,
+};
+
+static int kraken3_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int i;
+
+ if (size < MIN_REPORT_LENGTH)
+ return 0;
+
+ if (report->id == FIRMWARE_REPORT_ID) {
+ /* Read firmware version */
+ for (i = 0; i < 3; i++)
+ priv->firmware_version[i] = data[FIRMWARE_VERSION_OFFSET + i];
+
+ if (!completion_done(&priv->fw_version_processed))
+ complete_all(&priv->fw_version_processed);
+
+ return 0;
+ }
+
+ if (report->id != STATUS_REPORT_ID)
+ return 0;
+
+ if (data[TEMP_SENSOR_START_OFFSET] == 0xff && data[TEMP_SENSOR_END_OFFSET] == 0xff) {
+ hid_err_once(hdev,
+ "firmware or device is possibly damaged (is SATA power connected?), not parsing reports\n");
+
+ /*
+ * Mark first X-series device report as received,
+ * as well as all for Z-series, if faulty.
+ */
+ spin_lock(&priv->status_completion_lock);
+ if (priv->kind != X53 || !completion_done(&priv->status_report_processed)) {
+ priv->is_device_faulty = true;
+ complete_all(&priv->status_report_processed);
+ }
+ spin_unlock(&priv->status_completion_lock);
+
+ return 0;
+ }
+
+ /* Received normal data */
+ priv->is_device_faulty = false;
+
+ /* Temperature and fan sensor readings */
+ priv->temp_input[0] =
+ data[TEMP_SENSOR_START_OFFSET] * 1000 + data[TEMP_SENSOR_END_OFFSET] * 100;
+
+ priv->fan_input[0] = get_unaligned_le16(data + PUMP_SPEED_OFFSET);
+ priv->channel_info[0].reported_duty = kraken3_percent_to_pwm(data[PUMP_DUTY_OFFSET]);
+
+ spin_lock(&priv->status_completion_lock);
+ if (priv->kind == X53 && !completion_done(&priv->status_report_processed)) {
+ /* Mark first X-series device report as received */
+ complete_all(&priv->status_report_processed);
+ } else if (priv->kind == Z53) {
+ /* Additional readings for Z53 */
+ priv->fan_input[1] = get_unaligned_le16(data + Z53_FAN_SPEED_OFFSET);
+ priv->channel_info[1].reported_duty =
+ kraken3_percent_to_pwm(data[Z53_FAN_DUTY_OFFSET]);
+
+ if (!completion_done(&priv->status_report_processed))
+ complete_all(&priv->status_report_processed);
+ }
+ spin_unlock(&priv->status_completion_lock);
+
+ priv->updated = jiffies;
+
+ return 0;
+}
+
+static int kraken3_init_device(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ /* Set the polling interval */
+ ret = kraken3_write_expanded(priv, set_interval_cmd, SET_INTERVAL_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ /* Finalize the init process */
+ ret = kraken3_write_expanded(priv, finish_init_cmd, FINISH_INIT_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int kraken3_get_fw_ver(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = kraken3_write_expanded(priv, get_fw_version_cmd, GET_FW_VERSION_CMD_LENGTH);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed,
+ msecs_to_jiffies(STATUS_VALIDITY));
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int __maybe_unused kraken3_reset_resume(struct hid_device *hdev)
+{
+ int ret;
+
+ ret = kraken3_init_device(hdev);
+ if (ret)
+ hid_err(hdev, "req init (reset_resume) failed with %d\n", ret);
+
+ return ret;
+}
+
+static int firmware_version_show(struct seq_file *seqf, void *unused)
+{
+ struct kraken3_data *priv = seqf->private;
+
+ seq_printf(seqf, "%u.%u.%u\n", priv->firmware_version[0], priv->firmware_version[1],
+ priv->firmware_version[2]);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware_version);
+
+static void kraken3_debugfs_init(struct kraken3_data *priv)
+{
+ char name[64];
+
+ if (!priv->firmware_version[0])
+ return; /* Nothing to display in debugfs */
+
+ scnprintf(name, sizeof(name), "%s_%s-%s", DRIVER_NAME, kraken3_device_names[priv->kind],
+ dev_name(&priv->hdev->dev));
+
+ priv->debugfs = debugfs_create_dir(name, NULL);
+ debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
+}
+
+static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct kraken3_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ /*
+ * Initialize ->updated to STATUS_VALIDITY seconds in the past, making
+ * the initial empty data invalid for kraken3_read without the need for
+ * a special case there.
+ */
+ priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /* Enable hidraw so existing user-space tools can continue to work */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hid hw open failed with %d\n", ret);
+ goto fail_and_stop;
+ }
+
+ switch (hdev->product) {
+ case USB_PRODUCT_ID_X53:
+ case USB_PRODUCT_ID_X53_SECOND:
+ priv->kind = X53;
+ break;
+ case USB_PRODUCT_ID_Z53:
+ priv->kind = Z53;
+ break;
+ default:
+ break;
+ }
+
+ priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
+ if (!priv->buffer) {
+ ret = -ENOMEM;
+ goto fail_and_close;
+ }
+
+ mutex_init(&priv->buffer_lock);
+ mutex_init(&priv->z53_status_request_lock);
+ init_completion(&priv->fw_version_processed);
+ init_completion(&priv->status_report_processed);
+ spin_lock_init(&priv->status_completion_lock);
+
+ hid_device_io_start(hdev);
+ ret = kraken3_init_device(hdev);
+ if (ret < 0) {
+ hid_err(hdev, "device init failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ ret = kraken3_get_fw_ver(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "fw version request failed with %d\n", ret);
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev,
+ kraken3_device_names[priv->kind], priv,
+ &kraken3_chip_info, kraken3_groups);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ hid_err(hdev, "hwmon registration failed with %d\n", ret);
+ goto fail_and_close;
+ }
+
+ kraken3_debugfs_init(priv);
+
+ return 0;
+
+fail_and_close:
+ hid_hw_close(hdev);
+fail_and_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void kraken3_remove(struct hid_device *hdev)
+{
+ struct kraken3_data *priv = hid_get_drvdata(hdev);
+
+ debugfs_remove_recursive(priv->debugfs);
+ hwmon_device_unregister(priv->hwmon_dev);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id kraken3_table[] = {
+ /* NZXT Kraken X53/X63/X73 have two possible product IDs */
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53_SECOND) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, kraken3_table);
+
+static struct hid_driver kraken3_driver = {
+ .name = DRIVER_NAME,
+ .id_table = kraken3_table,
+ .probe = kraken3_probe,
+ .remove = kraken3_remove,
+ .raw_event = kraken3_raw_event,
+#ifdef CONFIG_PM
+ .reset_resume = kraken3_reset_resume,
+#endif
+};
+
+static int __init kraken3_init(void)
+{
+ return hid_register_driver(&kraken3_driver);
+}
+
+static void __exit kraken3_exit(void)
+{
+ hid_unregister_driver(&kraken3_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(kraken3_init);
+module_exit(kraken3_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers");
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 06095975f5..31159606ce 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -241,7 +241,6 @@ static const struct of_device_id p8_i2c_occ_of_match[] = {
MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
static struct i2c_driver p8_i2c_occ_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "occ-hwmon",
.of_match_table = p8_i2c_occ_of_match,
diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c
index ea9602063e..8d3b0f86cc 100644
--- a/drivers/hwmon/oxp-sensors.c
+++ b/drivers/hwmon/oxp-sensors.c
@@ -43,6 +43,7 @@ enum oxp_board {
aok_zoe_a1 = 1,
aya_neo_2,
aya_neo_air,
+ aya_neo_air_plus_mendo,
aya_neo_air_pro,
aya_neo_geek,
oxp_mini_amd,
@@ -101,6 +102,13 @@ static const struct dmi_system_id dmi_table[] = {
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
+ },
+ .driver_data = (void *)aya_neo_air_plus_mendo,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
},
.driver_data = (void *)aya_neo_air_pro,
@@ -332,6 +340,7 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
switch (board) {
case aya_neo_2:
case aya_neo_air:
+ case aya_neo_air_plus_mendo:
case aya_neo_air_pro:
case aya_neo_geek:
case oxp_mini_amd:
@@ -374,6 +383,7 @@ static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
switch (board) {
case aya_neo_2:
case aya_neo_air:
+ case aya_neo_air_plus_mendo:
case aya_neo_air_pro:
case aya_neo_geek:
case oxp_mini_amd:
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 294808f524..557ae0c414 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -377,6 +377,15 @@ config SENSORS_MPQ7932
This driver can also be built as a module. If so, the module will
be called mpq7932.
+config SENSORS_MPQ8785
+ tristate "MPS MPQ8785"
+ help
+ If you say yes here you get hardware monitoring functionality support
+ for power management IC MPS MPQ8785.
+
+ This driver can also be built as a module. If so, the module will
+ be called mpq8785.
+
config SENSORS_PIM4328
tristate "Flex PIM4328 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index cf8a767445..f14ecf03ad 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
obj-$(CONFIG_SENSORS_MP5990) += mp5990.o
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
+obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c
index 382ba6b603..a263afeb8a 100644
--- a/drivers/hwmon/pmbus/ir36021.c
+++ b/drivers/hwmon/pmbus/ir36021.c
@@ -63,7 +63,6 @@ static const struct of_device_id __maybe_unused ir36021_of_id[] = {
MODULE_DEVICE_TABLE(of, ir36021_of_id);
static struct i2c_driver ir36021_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "ir36021",
.of_match_table = of_match_ptr(ir36021_of_id),
diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c
index 04185be3fd..69e18cb468 100644
--- a/drivers/hwmon/pmbus/ir38064.c
+++ b/drivers/hwmon/pmbus/ir38064.c
@@ -22,7 +22,7 @@
#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR)
static const struct regulator_desc ir38064_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE("vout"),
};
#endif /* CONFIG_SENSORS_IR38064_REGULATOR */
diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c
index 3a20df5a43..cfffa4cdc0 100644
--- a/drivers/hwmon/pmbus/lm25066.c
+++ b/drivers/hwmon/pmbus/lm25066.c
@@ -437,7 +437,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR)
static const struct regulator_desc lm25066_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE("vout"),
};
#endif
diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c
new file mode 100644
index 0000000000..4e2549cc81
--- /dev/null
+++ b/drivers/hwmon/pmbus/mpq8785.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MPQ8785 Step-Down Converter
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include "pmbus.h"
+
+static int mpq8785_identify(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ int vout_mode;
+
+ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
+ if (vout_mode < 0 || vout_mode == 0xff)
+ return vout_mode < 0 ? vout_mode : -ENODEV;
+ switch (vout_mode >> 5) {
+ case 0:
+ info->format[PSC_VOLTAGE_OUT] = linear;
+ break;
+ case 1:
+ case 2:
+ info->format[PSC_VOLTAGE_OUT] = direct,
+ info->m[PSC_VOLTAGE_OUT] = 64;
+ info->b[PSC_VOLTAGE_OUT] = 0;
+ info->R[PSC_VOLTAGE_OUT] = 1;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+};
+
+static struct pmbus_driver_info mpq8785_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 4,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] =
+ PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
+ .identify = mpq8785_identify,
+};
+
+static int mpq8785_probe(struct i2c_client *client)
+{
+ return pmbus_do_probe(client, &mpq8785_info);
+};
+
+static const struct i2c_device_id mpq8785_id[] = {
+ { "mpq8785", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, mpq8785_id);
+
+static const struct of_device_id __maybe_unused mpq8785_of_match[] = {
+ { .compatible = "mps,mpq8785" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpq8785_of_match);
+
+static struct i2c_driver mpq8785_driver = {
+ .driver = {
+ .name = "mpq8785",
+ .of_match_table = of_match_ptr(mpq8785_of_match),
+ },
+ .probe = mpq8785_probe,
+ .id_table = mpq8785_id,
+};
+
+module_i2c_driver(mpq8785_driver);
+
+MODULE_AUTHOR("Charles Hsu <ythsu0511@gmail.com>");
+MODULE_DESCRIPTION("PMBus driver for MPS MPQ8785");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 1363d9f891..cb4c65a7f2 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -3188,7 +3188,7 @@ static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, u8 val)
{
- return pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
+ return _pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8));
}
static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
diff --git a/drivers/hwmon/pmbus/tda38640.c b/drivers/hwmon/pmbus/tda38640.c
index 09cd114b17..c31889a036 100644
--- a/drivers/hwmon/pmbus/tda38640.c
+++ b/drivers/hwmon/pmbus/tda38640.c
@@ -15,7 +15,7 @@
#include "pmbus.h"
static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = {
- PMBUS_REGULATOR("vout", 0),
+ PMBUS_REGULATOR_ONE("vout"),
};
struct tda38640_data {
diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c
index 4120cadb00..2388d0565e 100644
--- a/drivers/hwmon/powr1220.c
+++ b/drivers/hwmon/powr1220.c
@@ -323,7 +323,6 @@ static const struct i2c_device_id powr1220_ids[] = {
MODULE_DEVICE_TABLE(i2c, powr1220_ids);
static struct i2c_driver powr1220_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "powr1220",
},
diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c
new file mode 100644
index 0000000000..60361e39c4
--- /dev/null
+++ b/drivers/hwmon/pt5161l.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+/* Aries current average temp ADC code CSR */
+#define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c
+
+/* Device Load check register */
+#define ARIES_CODE_LOAD_REG 0x605
+/* Value indicating FW was loaded properly, [3:1] = 3'b111 */
+#define ARIES_LOAD_CODE 0xe
+
+/* Main Micro Heartbeat register */
+#define ARIES_MM_HEARTBEAT_ADDR 0x923
+
+/* Reg offset to specify Address for MM assisted accesses */
+#define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99
+/* Reg offset to specify Command for MM assisted accesses */
+#define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d
+/* Reg offset to MM SPARE 0 used specify Address[7:0] */
+#define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f
+/* Reg offset to MM SPARE 3 used specify Data Byte 0 */
+#define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2
+/* Wide register reads */
+#define ARIES_MM_RD_WIDE_REG_2B 0x1d
+#define ARIES_MM_RD_WIDE_REG_3B 0x1e
+#define ARIES_MM_RD_WIDE_REG_4B 0x1f
+#define ARIES_MM_RD_WIDE_REG_5B 0x20
+
+/* Time delay between checking MM status of EEPROM write (microseconds) */
+#define ARIES_MM_STATUS_TIME 5000
+
+/* AL Main SRAM DMEM offset (A0) */
+#define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024)
+/* SRAM read command */
+#define AL_TG_RD_LOC_IND_SRAM 0x16
+
+/* Offset for main micro FW info */
+#define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128)
+/* FW Info (Major) offset location in struct */
+#define ARIES_MM_FW_VERSION_MAJOR 0
+/* FW Info (Minor) offset location in struct */
+#define ARIES_MM_FW_VERSION_MINOR 1
+/* FW Info (Build no.) offset location in struct */
+#define ARIES_MM_FW_VERSION_BUILD 2
+
+#define ARIES_TEMP_CAL_CODE_DEFAULT 84
+
+/* Struct defining FW version loaded on an Aries device */
+struct pt5161l_fw_ver {
+ u8 major;
+ u8 minor;
+ u16 build;
+};
+
+/* Each client has this additional data */
+struct pt5161l_data {
+ struct i2c_client *client;
+ struct dentry *debugfs;
+ struct pt5161l_fw_ver fw_ver;
+ struct mutex lock; /* for atomic I2C transactions */
+ bool init_done;
+ bool code_load_okay; /* indicate if code load reg value is expected */
+ bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */
+ bool mm_wide_reg_access; /* MM assisted wide register access */
+};
+
+static struct dentry *pt5161l_debugfs_dir;
+
+/*
+ * Write multiple data bytes to Aries over I2C
+ */
+static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address,
+ u8 len, u8 *val)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+ u8 remain_len = len;
+ u8 xfer_len, curr_len;
+ u8 buf[16];
+ u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
+ u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
+
+ while (remain_len > 0) {
+ if (remain_len > 4) {
+ curr_len = 4;
+ remain_len -= 4;
+ } else {
+ curr_len = remain_len;
+ remain_len = 0;
+ }
+
+ buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1);
+ buf[1] = (address >> 8) & 0xff;
+ buf[2] = address & 0xff;
+ memcpy(&buf[3], val, curr_len);
+
+ xfer_len = 3 + curr_len;
+ ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf);
+ if (ret)
+ return ret;
+
+ val += curr_len;
+ address += curr_len;
+ }
+
+ return 0;
+}
+
+/*
+ * Read multiple data bytes from Aries over I2C
+ */
+static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
+ u8 len, u8 *val)
+{
+ struct i2c_client *client = data->client;
+ int ret, tries;
+ u8 remain_len = len;
+ u8 curr_len;
+ u8 wbuf[16], rbuf[24];
+ u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
+ u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
+
+ while (remain_len > 0) {
+ if (remain_len > 16) {
+ curr_len = 16;
+ remain_len -= 16;
+ } else {
+ curr_len = remain_len;
+ remain_len = 0;
+ }
+
+ wbuf[0] = config | (curr_len - 1) << 1 |
+ ((address >> 16) & 0x1);
+ wbuf[1] = (address >> 8) & 0xff;
+ wbuf[2] = address & 0xff;
+
+ for (tries = 0; tries < 3; tries++) {
+ ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3,
+ wbuf);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_read_block_data(client, (cmd | 0x1),
+ rbuf);
+ if (ret == curr_len)
+ break;
+ }
+ if (tries >= 3)
+ return ret;
+
+ memcpy(val, rbuf, curr_len);
+ val += curr_len;
+ address += curr_len;
+ }
+
+ return 0;
+}
+
+static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address,
+ u8 width, u8 *val)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 status;
+
+ /*
+ * Safely access wide registers using mailbox method to prevent
+ * risking conflict with Aries firmware; otherwise fallback to
+ * legacy, less secure method.
+ */
+ if (data->mm_wide_reg_access) {
+ buf[0] = address & 0xff;
+ buf[1] = (address >> 8) & 0xff;
+ buf[2] = (address >> 16) & 0x1;
+ ret = pt5161l_write_block_data(data,
+ ARIES_MM_ASSIST_SPARE_0_OFFSET,
+ 3, buf);
+ if (ret)
+ return ret;
+
+ /* Set command based on width */
+ switch (width) {
+ case 2:
+ buf[0] = ARIES_MM_RD_WIDE_REG_2B;
+ break;
+ case 3:
+ buf[0] = ARIES_MM_RD_WIDE_REG_3B;
+ break;
+ case 4:
+ buf[0] = ARIES_MM_RD_WIDE_REG_4B;
+ break;
+ case 5:
+ buf[0] = ARIES_MM_RD_WIDE_REG_5B;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET,
+ 1, buf);
+ if (ret)
+ return ret;
+
+ status = 0xff;
+ for (tries = 0; tries < 100; tries++) {
+ ret = pt5161l_read_block_data(data,
+ ARIES_MM_ASSIST_CMD_OFFSET,
+ 1, &status);
+ if (ret)
+ return ret;
+
+ if (status == 0)
+ break;
+
+ usleep_range(ARIES_MM_STATUS_TIME,
+ ARIES_MM_STATUS_TIME + 1000);
+ }
+ if (status != 0)
+ return -ETIMEDOUT;
+
+ ret = pt5161l_read_block_data(data,
+ ARIES_MM_ASSIST_SPARE_3_OFFSET,
+ width, val);
+ if (ret)
+ return ret;
+ } else {
+ return pt5161l_read_block_data(data, address, width, val);
+ }
+
+ return 0;
+}
+
+/*
+ * Read multiple (up to eight) data bytes from micro SRAM over I2C
+ */
+static int
+pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data,
+ u32 address, u8 len, u8 *val)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 i, status;
+ u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET;
+ u32 eeprom_base, eeprom_addr;
+
+ /* No multi-byte indirect support here. Hence read a byte at a time */
+ eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET;
+ for (i = 0; i < len; i++) {
+ eeprom_addr = eeprom_base + i;
+ buf[0] = eeprom_addr & 0xff;
+ buf[1] = (eeprom_addr >> 8) & 0xff;
+ buf[2] = (eeprom_addr >> 16) & 0xff;
+ ret = pt5161l_write_block_data(data, uind_offs, 3, buf);
+ if (ret)
+ return ret;
+
+ buf[0] = AL_TG_RD_LOC_IND_SRAM;
+ ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf);
+ if (ret)
+ return ret;
+
+ status = 0xff;
+ for (tries = 0; tries < 255; tries++) {
+ ret = pt5161l_read_block_data(data, uind_offs + 4, 1,
+ &status);
+ if (ret)
+ return ret;
+
+ if (status == 0)
+ break;
+ }
+ if (status != 0)
+ return -ETIMEDOUT;
+
+ ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf);
+ if (ret)
+ return ret;
+
+ val[i] = buf[0];
+ }
+
+ return 0;
+}
+
+/*
+ * Check firmware load status
+ */
+static int pt5161l_fw_load_check(struct pt5161l_data *data)
+{
+ int ret;
+ u8 buf[8];
+
+ ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf);
+ if (ret)
+ return ret;
+
+ if (buf[0] < ARIES_LOAD_CODE) {
+ dev_dbg(&data->client->dev,
+ "Code Load reg unexpected. Not all modules are loaded %x\n",
+ buf[0]);
+ data->code_load_okay = false;
+ } else {
+ data->code_load_okay = true;
+ }
+
+ return 0;
+}
+
+/*
+ * Check main micro heartbeat
+ */
+static int pt5161l_heartbeat_check(struct pt5161l_data *data)
+{
+ int ret, tries;
+ u8 buf[8];
+ u8 heartbeat;
+ bool hb_changed = false;
+
+ ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf);
+ if (ret)
+ return ret;
+
+ heartbeat = buf[0];
+ for (tries = 0; tries < 100; tries++) {
+ ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1,
+ buf);
+ if (ret)
+ return ret;
+
+ if (buf[0] != heartbeat) {
+ hb_changed = true;
+ break;
+ }
+ }
+ data->mm_heartbeat_okay = hb_changed;
+
+ return 0;
+}
+
+/*
+ * Check the status of firmware
+ */
+static int pt5161l_fwsts_check(struct pt5161l_data *data)
+{
+ int ret;
+ u8 buf[8];
+ u8 major = 0, minor = 0;
+ u16 build = 0;
+
+ ret = pt5161l_fw_load_check(data);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_heartbeat_check(data);
+ if (ret)
+ return ret;
+
+ if (data->code_load_okay && data->mm_heartbeat_okay) {
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_MAJOR,
+ 1, &major);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_MINOR,
+ 1, &minor);
+ if (ret)
+ return ret;
+
+ ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
+ ARIES_MM_FW_VERSION_BUILD,
+ 2, buf);
+ if (ret)
+ return ret;
+ build = buf[1] << 8 | buf[0];
+ }
+ data->fw_ver.major = major;
+ data->fw_ver.minor = minor;
+ data->fw_ver.build = build;
+
+ return 0;
+}
+
+static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor,
+ u16 build)
+{
+ u32 ver = major << 24 | minor << 16 | build;
+ u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 |
+ data->fw_ver.build;
+
+ if (curr_ver >= ver)
+ return true;
+
+ return false;
+}
+
+static int pt5161l_init_dev(struct pt5161l_data *data)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fwsts_check(data);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+
+ /* Firmware 2.2.0 enables safe access to wide registers */
+ if (pt5161l_fw_is_at_least(data, 2, 2, 0))
+ data->mm_wide_reg_access = true;
+
+ data->init_done = true;
+
+ return 0;
+}
+
+static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct pt5161l_data *data = dev_get_drvdata(dev);
+ int ret;
+ u8 buf[8];
+ long adc_code;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ if (!data->init_done) {
+ ret = pt5161l_init_dev(data);
+ if (ret)
+ return ret;
+ }
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_read_wide_reg(data,
+ ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4,
+ buf);
+ mutex_unlock(&data->lock);
+ if (ret) {
+ dev_dbg(dev, "Read adc_code failed %d\n", ret);
+ return ret;
+ }
+
+ adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
+ if (adc_code == 0 || adc_code >= 0x3ff) {
+ dev_dbg(dev, "Invalid adc_code %lx\n", adc_code);
+ return -EIO;
+ }
+
+ *val = 110000 +
+ ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) *
+ -320);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t pt5161l_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *pt5161l_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops pt5161l_hwmon_ops = {
+ .is_visible = pt5161l_is_visible,
+ .read = pt5161l_read,
+};
+
+static const struct hwmon_chip_info pt5161l_chip_info = {
+ .ops = &pt5161l_hwmon_ops,
+ .info = pt5161l_info,
+};
+
+static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ char ver[32];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fwsts_check(data);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+
+ ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major,
+ data->fw_ver.minor, data->fw_ver.build);
+
+ return simple_read_from_buffer(buf, count, ppos, ver, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_fw_ver = {
+ .read = pt5161l_debugfs_read_fw_ver,
+ .open = simple_open,
+};
+
+static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file,
+ char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ bool status = false;
+ char health[16];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_fw_load_check(data);
+ mutex_unlock(&data->lock);
+ if (ret == 0)
+ status = data->code_load_okay;
+
+ ret = snprintf(health, sizeof(health), "%s\n",
+ status ? "normal" : "abnormal");
+
+ return simple_read_from_buffer(buf, count, ppos, health, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = {
+ .read = pt5161l_debugfs_read_fw_load_sts,
+ .open = simple_open,
+};
+
+static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct pt5161l_data *data = file->private_data;
+ int ret;
+ bool status = false;
+ char health[16];
+
+ mutex_lock(&data->lock);
+ ret = pt5161l_heartbeat_check(data);
+ mutex_unlock(&data->lock);
+ if (ret == 0)
+ status = data->mm_heartbeat_okay;
+
+ ret = snprintf(health, sizeof(health), "%s\n",
+ status ? "normal" : "abnormal");
+
+ return simple_read_from_buffer(buf, count, ppos, health, ret);
+}
+
+static const struct file_operations pt5161l_debugfs_ops_hb_sts = {
+ .read = pt5161l_debugfs_read_hb_sts,
+ .open = simple_open,
+};
+
+static int pt5161l_init_debugfs(struct pt5161l_data *data)
+{
+ data->debugfs = debugfs_create_dir(dev_name(&data->client->dev),
+ pt5161l_debugfs_dir);
+
+ debugfs_create_file("fw_ver", 0444, data->debugfs, data,
+ &pt5161l_debugfs_ops_fw_ver);
+
+ debugfs_create_file("fw_load_status", 0444, data->debugfs, data,
+ &pt5161l_debugfs_ops_fw_load_sts);
+
+ debugfs_create_file("heartbeat_status", 0444, data->debugfs, data,
+ &pt5161l_debugfs_ops_hb_sts);
+
+ return 0;
+}
+
+static int pt5161l_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct pt5161l_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ mutex_init(&data->lock);
+ pt5161l_init_dev(data);
+ dev_set_drvdata(dev, data);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data,
+ &pt5161l_chip_info,
+ NULL);
+
+ pt5161l_init_debugfs(data);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static void pt5161l_remove(struct i2c_client *client)
+{
+ struct pt5161l_data *data = i2c_get_clientdata(client);
+
+ debugfs_remove_recursive(data->debugfs);
+}
+
+static const struct of_device_id __maybe_unused pt5161l_of_match[] = {
+ { .compatible = "asteralabs,pt5161l" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pt5161l_of_match);
+
+static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = {
+ { "PT5161L", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match);
+
+static const struct i2c_device_id pt5161l_id[] = {
+ { "pt5161l", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pt5161l_id);
+
+static struct i2c_driver pt5161l_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "pt5161l",
+ .of_match_table = of_match_ptr(pt5161l_of_match),
+ .acpi_match_table = ACPI_PTR(pt5161l_acpi_match),
+ },
+ .probe = pt5161l_probe,
+ .remove = pt5161l_remove,
+ .id_table = pt5161l_id,
+};
+
+static int __init pt5161l_init(void)
+{
+ pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL);
+ return i2c_add_driver(&pt5161l_driver);
+}
+
+static void __exit pt5161l_exit(void)
+{
+ i2c_del_driver(&pt5161l_driver);
+ debugfs_remove_recursive(pt5161l_debugfs_dir);
+}
+
+module_init(pt5161l_init);
+module_exit(pt5161l_exit);
+
+MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>");
+MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sbrmi.c b/drivers/hwmon/sbrmi.c
index 484703f0ea..4318f51211 100644
--- a/drivers/hwmon/sbrmi.c
+++ b/drivers/hwmon/sbrmi.c
@@ -342,7 +342,6 @@ static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
MODULE_DEVICE_TABLE(of, sbrmi_of_match);
static struct i2c_driver sbrmi_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "sbrmi",
.of_match_table = of_match_ptr(sbrmi_of_match),
diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c
index dd85cf89f0..a4181acb1a 100644
--- a/drivers/hwmon/sbtsi_temp.c
+++ b/drivers/hwmon/sbtsi_temp.c
@@ -232,7 +232,6 @@ static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
MODULE_DEVICE_TABLE(of, sbtsi_of_match);
static struct i2c_driver sbtsi_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "sbtsi",
.of_match_table = of_match_ptr(sbtsi_of_match),
diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c
index 1891d4d75a..33e997b5c1 100644
--- a/drivers/hwmon/sch5627.c
+++ b/drivers/hwmon/sch5627.c
@@ -116,7 +116,7 @@ static const struct regmap_config sch5627_regmap_config = {
.val_bits = 8,
.wr_table = &sch5627_tunables_table,
.rd_table = &sch5627_tunables_table,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.use_single_read = true,
.use_single_write = true,
.can_sleep = true,
diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c
index 79657910b7..c0d02fbcdb 100644
--- a/drivers/hwmon/sht3x.c
+++ b/drivers/hwmon/sht3x.c
@@ -10,6 +10,7 @@
#include <asm/page.h>
#include <linux/crc8.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hwmon.h>
@@ -41,6 +42,9 @@ static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 };
/* other commands */
static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d };
static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 };
+static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 };
+
+static struct dentry *debugfs;
/* delays for single-shot mode i2c commands, both in us */
#define SHT3X_SINGLE_WAIT_TIME_HPM 15000
@@ -163,12 +167,14 @@ struct sht3x_data {
enum sht3x_chips chip_id;
struct mutex i2c_lock; /* lock for sending i2c commands */
struct mutex data_lock; /* lock for updating driver data */
+ struct dentry *sensor_dir;
u8 mode;
const unsigned char *command;
u32 wait_time; /* in us*/
unsigned long last_update; /* last update in periodic mode*/
enum sht3x_repeatability repeatability;
+ u32 serial_number;
/*
* cached values for temperature and humidity and limits
@@ -831,6 +837,40 @@ static int sht3x_write(struct device *dev, enum hwmon_sensor_types type,
}
}
+static void sht3x_debugfs_init(struct sht3x_data *data)
+{
+ char name[32];
+
+ snprintf(name, sizeof(name), "i2c%u-%02x",
+ data->client->adapter->nr, data->client->addr);
+ data->sensor_dir = debugfs_create_dir(name, debugfs);
+ debugfs_create_u32("serial_number", 0444,
+ data->sensor_dir, &data->serial_number);
+}
+
+static void sht3x_debugfs_remove(void *sensor_dir)
+{
+ debugfs_remove_recursive(sensor_dir);
+}
+
+static int sht3x_serial_number_read(struct sht3x_data *data)
+{
+ int ret;
+ char buffer[SHT3X_RESPONSE_LENGTH];
+ struct i2c_client *client = data->client;
+
+ ret = sht3x_read_from_command(client, data,
+ sht3x_cmd_read_serial_number,
+ buffer,
+ SHT3X_RESPONSE_LENGTH, 0);
+ if (ret)
+ return ret;
+
+ data->serial_number = (buffer[0] << 24) | (buffer[1] << 16) |
+ (buffer[3] << 8) | buffer[4];
+ return ret;
+}
+
static const struct hwmon_ops sht3x_ops = {
.is_visible = sht3x_is_visible,
.read = sht3x_read,
@@ -899,6 +939,18 @@ static int sht3x_probe(struct i2c_client *client)
if (ret)
return ret;
+ ret = sht3x_serial_number_read(data);
+ if (ret) {
+ dev_dbg(dev, "unable to read serial number\n");
+ } else {
+ sht3x_debugfs_init(data);
+ ret = devm_add_action_or_reset(dev,
+ sht3x_debugfs_remove,
+ data->sensor_dir);
+ if (ret)
+ return ret;
+ }
+
hwmon_dev = devm_hwmon_device_register_with_info(dev,
client->name,
data,
@@ -917,7 +969,19 @@ static struct i2c_driver sht3x_i2c_driver = {
.id_table = sht3x_ids,
};
-module_i2c_driver(sht3x_i2c_driver);
+static int __init sht3x_init(void)
+{
+ debugfs = debugfs_create_dir("sht3x", NULL);
+ return i2c_add_driver(&sht3x_i2c_driver);
+}
+module_init(sht3x_init);
+
+static void __exit sht3x_cleanup(void)
+{
+ debugfs_remove_recursive(debugfs);
+ i2c_del_driver(&sht3x_i2c_driver);
+}
+module_exit(sht3x_cleanup);
MODULE_AUTHOR("David Frey <david.frey@sensirion.com>");
MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
diff --git a/drivers/hwmon/shtc1.c b/drivers/hwmon/shtc1.c
index 1f96e94967..439dd3dba5 100644
--- a/drivers/hwmon/shtc1.c
+++ b/drivers/hwmon/shtc1.c
@@ -238,7 +238,7 @@ static int shtc1_probe(struct i2c_client *client)
if (np) {
data->setup.blocking_io = of_property_read_bool(np, "sensirion,blocking-io");
- data->setup.high_precision = !of_property_read_bool(np, "sensicon,low-precision");
+ data->setup.high_precision = !of_property_read_bool(np, "sensirion,low-precision");
} else {
if (client->dev.platform_data)
data->setup = *(struct shtc1_platform_data *)dev->platform_data;
diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c
index 641be1f7f9..e73b1522f3 100644
--- a/drivers/hwmon/sis5595.c
+++ b/drivers/hwmon/sis5595.c
@@ -153,13 +153,9 @@ static inline s8 TEMP_TO_REG(long val)
}
/*
- * FAN DIV: 1, 2, 4, or 8 (defaults to 2)
- * REG: 0, 1, 2, or 3 (respectively) (defaults to 1)
+ * FAN DIV: 1, 2, 4, or 8
+ * REG: 0, 1, 2, or 3 (respectively)
*/
-static inline u8 DIV_TO_REG(int val)
-{
- return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1;
-}
#define DIV_FROM_REG(val) (1 << (val))
/*
diff --git a/drivers/hwmon/surface_fan.c b/drivers/hwmon/surface_fan.c
new file mode 100644
index 0000000000..de3c5a2409
--- /dev/null
+++ b/drivers/hwmon/surface_fan.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface Fan driver for Surface System Aggregator Module. It provides access
+ * to the fan's rpm through the hwmon system.
+ *
+ * Copyright (C) 2023 Ivor Wanders <ivor@iwanders.net>
+ */
+
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/surface_aggregator/device.h>
+#include <linux/types.h>
+
+// SSAM
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, {
+ .target_category = SSAM_SSH_TC_FAN,
+ .command_id = 0x01,
+});
+
+// hwmon
+static umode_t surface_fan_hwmon_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ return 0444;
+}
+
+static int surface_fan_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ struct ssam_device *sdev = dev_get_drvdata(dev);
+ int ret;
+ __le16 value;
+
+ ret = __ssam_fan_rpm_get(sdev, &value);
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(value);
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *const surface_fan_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops surface_fan_hwmon_ops = {
+ .is_visible = surface_fan_hwmon_is_visible,
+ .read = surface_fan_hwmon_read,
+};
+
+static const struct hwmon_chip_info surface_fan_chip_info = {
+ .ops = &surface_fan_hwmon_ops,
+ .info = surface_fan_info,
+};
+
+static int surface_fan_probe(struct ssam_device *sdev)
+{
+ struct device *hdev;
+
+ hdev = devm_hwmon_device_register_with_info(&sdev->dev,
+ "surface_fan", sdev,
+ &surface_fan_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(hdev);
+}
+
+static const struct ssam_device_id ssam_fan_match[] = {
+ { SSAM_SDEV(FAN, SAM, 0x01, 0x01) },
+ {},
+};
+MODULE_DEVICE_TABLE(ssam, ssam_fan_match);
+
+static struct ssam_device_driver surface_fan = {
+ .probe = surface_fan_probe,
+ .match_table = ssam_fan_match,
+ .driver = {
+ .name = "surface_fan",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_ssam_device_driver(surface_fan);
+
+MODULE_AUTHOR("Ivor Wanders <ivor@iwanders.net>");
+MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c
index 91f2314568..df1b45a62e 100644
--- a/drivers/hwmon/tmp401.c
+++ b/drivers/hwmon/tmp401.c
@@ -256,7 +256,7 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val)
static const struct regmap_config tmp401_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
- .cache_type = REGCACHE_RBTREE,
+ .cache_type = REGCACHE_MAPLE,
.volatile_reg = tmp401_regmap_is_volatile,
.reg_read = tmp401_reg_read,
.reg_write = tmp401_reg_write,
diff --git a/drivers/hwmon/ultra45_env.c b/drivers/hwmon/ultra45_env.c
index 9823afb067..2765d5f1b7 100644
--- a/drivers/hwmon/ultra45_env.c
+++ b/drivers/hwmon/ultra45_env.c
@@ -18,7 +18,7 @@
#define DRV_MODULE_VERSION "0.1"
-MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
+MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
MODULE_DESCRIPTION("Ultra45 environmental monitor driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_MODULE_VERSION);
diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c
index 045eea8378..401a28f55f 100644
--- a/drivers/hwmon/w83773g.c
+++ b/drivers/hwmon/w83773g.c
@@ -290,7 +290,6 @@ static int w83773_probe(struct i2c_client *client)
}
static struct i2c_driver w83773_driver = {
- .class = I2C_CLASS_HWMON,
.driver = {
.name = "w83773g",
.of_match_table = of_match_ptr(w83773_of_match),