From 90cf254f88d1d5a5219c8ee122ce677a3dc2f734 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jo=C3=A3o=20Paulo=20Gon=C3=A7alves?= Date: Fri, 13 Jun 2025 10:42:40 -0300 Subject: [PATCH] hwmon: (amc6821) Add cooling device support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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. If this property is present, the driver assumes the fan will operate in open-loop, and the kernel will control it directly. In this case, the driver will change the AMC6821 mode to manual (software DCY) and set the initial PWM duty cycle to the maximum fan cooling state level as defined in the DT. It is worth mentioning that the cooling device is registered on the child fan node, not on the fan controller 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 Link: https://lore.kernel.org/r/20250613-b4-amc6821-cooling-device-support-v4-3-a8fc063c55de@toradex.com [groeck: Reduced line length when registering thermal device] Signed-off-by: Guenter Roeck --- drivers/hwmon/amc6821.c | 113 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index 612895db7d8d4..d5f864b360b03 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -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,65 @@ static const struct hwmon_chip_info amc6821_chip_info = { .info = amc6821_info, }; +static int amc6821_set_sw_dcy(struct amc6821_data *data, u8 duty_cycle) +{ + int ret; + + ret = regmap_write(data->regmap, AMC6821_REG_DCY, duty_cycle); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, AMC6821_REG_CONF1, + AMC6821_CONF1_FDRC0 | AMC6821_CONF1_FDRC1, 0); +} + +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 = amc6821_set_sw_dcy(data, data->fan_cooling_levels[state]); + if (ret) + return ret; + + data->fan_state = state; + + return 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 +940,29 @@ out: 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) @@ -914,6 +995,14 @@ static int amc6821_init_client(struct i2c_client *client, struct amc6821_data *d regval); if (err) return err; + + /* Software DCY-control mode with fan enabled when cooling-levels present */ + if (data->fan_cooling_levels) { + err = amc6821_set_sw_dcy(data, + data->fan_cooling_levels[data->fan_max_state]); + if (err) + return err; + } } return 0; } @@ -961,8 +1050,12 @@ static int amc6821_probe(struct i2c_client *client) data->regmap = regmap; fan_np = of_get_child_by_name(dev->of_node, "fan"); - if (fan_np) - amc6821_of_fan_read_data(client, data, fan_np); + if (fan_np) { + err = amc6821_of_fan_read_data(client, data, 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) @@ -978,7 +1071,15 @@ 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.47.2