diff options
Diffstat (limited to 'drivers/hwmon/amc6821.c')
-rw-r--r-- | drivers/hwmon/amc6821.c | 161 |
1 files changed, 155 insertions, 6 deletions
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index 1e3c6acd8974..d5f864b360b0 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -23,8 +23,12 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of_platform.h> +#include <linux/pwm.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/thermal.h> + +#include <dt-bindings/pwm/pwm.h> /* * Addresses to scan. @@ -37,7 +41,7 @@ static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, * Insmod parameters */ -static int pwminv; /*Inverted PWM output. */ +static int pwminv = -1; /*Inverted PWM output. */ module_param(pwminv, int, 0444); static int init = 1; /*Power-on initialization.*/ @@ -123,6 +127,10 @@ 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; }; /* @@ -801,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) { @@ -845,9 +912,63 @@ static int amc6821_detect(struct i2c_client *client, struct i2c_board_info *info return 0; } -static int amc6821_init_client(struct amc6821_data *data) +static enum pwm_polarity amc6821_pwm_polarity(struct i2c_client *client, + struct device_node *fan_np) +{ + enum pwm_polarity polarity = PWM_POLARITY_NORMAL; + struct of_phandle_args args; + + /* + * For backward compatibility, the pwminv module parameter takes + * always the precedence over any other device description + */ + if (pwminv == 0) + return PWM_POLARITY_NORMAL; + if (pwminv > 0) + return PWM_POLARITY_INVERSED; + + if (of_parse_phandle_with_args(fan_np, "pwms", "#pwm-cells", 0, &args)) + goto out; + of_node_put(args.np); + + if (args.args_count != 2) + goto out; + + if (args.args[1] & PWM_POLARITY_INVERTED) + polarity = PWM_POLARITY_INVERSED; +out: + return polarity; +} + +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) { struct regmap *regmap = data->regmap; + u32 regval; int err; if (init) { @@ -864,13 +985,24 @@ static int amc6821_init_client(struct amc6821_data *data) if (err) return err; + regval = AMC6821_CONF1_START; + if (data->pwm_polarity == PWM_POLARITY_INVERSED) + regval |= AMC6821_CONF1_PWMINV; + err = regmap_update_bits(regmap, AMC6821_REG_CONF1, AMC6821_CONF1_THERMOVIE | AMC6821_CONF1_FANIE | AMC6821_CONF1_START | AMC6821_CONF1_PWMINV, - AMC6821_CONF1_START | - (pwminv ? AMC6821_CONF1_PWMINV : 0)); + regval); if (err) return err; + + /* 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; } @@ -904,6 +1036,7 @@ static int amc6821_probe(struct i2c_client *client) struct amc6821_data *data; struct device *hwmon_dev; struct regmap *regmap; + struct device_node *fan_np __free(device_node) = NULL; int err; data = devm_kzalloc(dev, sizeof(struct amc6821_data), GFP_KERNEL); @@ -916,7 +1049,15 @@ static int amc6821_probe(struct i2c_client *client) "Failed to initialize regmap\n"); data->regmap = regmap; - err = amc6821_init_client(data); + fan_np = of_get_child_by_name(dev->of_node, "fan"); + 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) return err; @@ -930,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[] = { |