[PATCH v2 3/3] hwmon: (amc6821) Add cooling device support

From: João Paulo Gonçalves
Date: Tue Jun 03 2025 - 07:47:14 EST


From: João Paulo Gonçalves <joao.goncalves@xxxxxxxxxxx>

Add support for using the AMC6821 as a cooling device. The AMC6821
registers with the thermal framework only if the `cooling-levels`
property is present in the fan device tree child node. Existing behavior
is unchanged, so the AMC6821 can still be used without the thermal
framework (hwmon only).

Signed-off-by: João Paulo Gonçalves <joao.goncalves@xxxxxxxxxxx>
---
v2: Unchanged
v1: https://lore.kernel.org/lkml/20250530-b4-v1-amc6821-cooling-device-support-b4-v1-0-7bb98496c969@xxxxxxxxxxx/
---
drivers/hwmon/amc6821.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 92 insertions(+), 5 deletions(-)

diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
index 850a42510649279fba23ed504826384e1d8e335c..218efa0d405b87e838874c273cff7f2872a02448 100644
--- a/drivers/hwmon/amc6821.c
+++ b/drivers/hwmon/amc6821.c
@@ -26,6 +26,7 @@
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/thermal.h>

#include <dt-bindings/pwm/pwm.h>

@@ -126,6 +127,9 @@ module_param(init, int, 0444);
struct amc6821_data {
struct regmap *regmap;
struct mutex update_lock;
+ unsigned long fan_state;
+ unsigned long fan_max_state;
+ unsigned int *fan_cooling_levels;
enum pwm_polarity pwm_polarity;
};

@@ -805,6 +809,56 @@ static const struct hwmon_chip_info amc6821_chip_info = {
.info = amc6821_info,
};

+static int amc6821_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct amc6821_data *data = cdev->devdata;
+
+ if (!data)
+ return -EINVAL;
+
+ *state = data->fan_max_state;
+
+ return 0;
+}
+
+static int amc6821_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct amc6821_data *data = cdev->devdata;
+
+ if (!data)
+ return -EINVAL;
+
+ *state = data->fan_state;
+
+ return 0;
+}
+
+static int amc6821_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct amc6821_data *data = cdev->devdata;
+ int ret;
+
+ if (!data || state > data->fan_max_state)
+ return -EINVAL;
+
+ ret = regmap_write(data->regmap, AMC6821_REG_DCY,
+ data->fan_cooling_levels[state]);
+ if (ret)
+ return ret;
+
+ data->fan_state = state;
+
+ /* Change to manual mode (software DCY) */
+ return regmap_update_bits(data->regmap, AMC6821_REG_CONF1,
+ AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1, 0);
+}
+
+static const struct thermal_cooling_device_ops amc6821_cooling_ops = {
+ .get_max_state = amc6821_get_max_state,
+ .get_cur_state = amc6821_get_cur_state,
+ .set_cur_state = amc6821_set_cur_state,
+};
+
/* Return 0 if detection is successful, -ENODEV otherwise */
static int amc6821_detect(struct i2c_client *client, struct i2c_board_info *info)
{
@@ -877,11 +931,29 @@ static enum pwm_polarity amc6821_pwm_polarity(struct i2c_client *client,
return polarity;
}

-static void amc6821_of_fan_read_data(struct i2c_client *client,
- struct amc6821_data *data,
- struct device_node *fan_np)
+static int amc6821_of_fan_read_data(struct i2c_client *client,
+ struct amc6821_data *data,
+ struct device_node *fan_np)
{
+ int num;
+
data->pwm_polarity = amc6821_pwm_polarity(client, fan_np);
+
+ num = of_property_count_u32_elems(fan_np, "cooling-levels");
+ if (num <= 0)
+ return 0;
+
+ data->fan_max_state = num - 1;
+
+ data->fan_cooling_levels = devm_kcalloc(&client->dev, num,
+ sizeof(u32),
+ GFP_KERNEL);
+
+ if (!data->fan_cooling_levels)
+ return -ENOMEM;
+
+ return of_property_read_u32_array(fan_np, "cooling-levels",
+ data->fan_cooling_levels, num);
}

static int amc6821_init_client(struct i2c_client *client, struct amc6821_data *data)
@@ -962,10 +1034,14 @@ static int amc6821_probe(struct i2c_client *client)

fan_np = of_get_child_by_name(dev->of_node, "fan");
if (fan_np) {
- amc6821_of_fan_read_data(client, data, fan_np);
+ err = amc6821_of_fan_read_data(client, data, fan_np);
of_node_put(fan_np);
}

+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to read fan device tree data\n");
+
err = amc6821_init_client(client, data);
if (err)
return err;
@@ -980,7 +1056,18 @@ static int amc6821_probe(struct i2c_client *client)
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
data, &amc6821_chip_info,
amc6821_groups);
- return PTR_ERR_OR_ZERO(hwmon_dev);
+ if (IS_ERR(hwmon_dev))
+ return dev_err_probe(dev, PTR_ERR(hwmon_dev),
+ "Failed to initialize hwmon\n");
+
+ if (IS_ENABLED(CONFIG_THERMAL) && fan_np && data->fan_cooling_levels)
+ return PTR_ERR_OR_ZERO(devm_thermal_of_cooling_device_register(dev,
+ fan_np,
+ client->name,
+ data,
+ &amc6821_cooling_ops));
+
+ return 0;
}

static const struct i2c_device_id amc6821_id[] = {

--
2.43.0