diff options
Diffstat (limited to 'drivers/hwmon/emc2305.c')
-rw-r--r-- | drivers/hwmon/emc2305.c | 181 |
1 files changed, 157 insertions, 24 deletions
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 234c54956a4b..60809289f816 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -11,6 +11,9 @@ #include <linux/module.h> #include <linux/platform_data/emc2305.h> #include <linux/thermal.h> +#include <linux/pwm.h> +#include <linux/of_device.h> +#include <linux/util_macros.h> #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27 #define EMC2305_REG_VENDOR 0xfe @@ -23,6 +26,12 @@ #define EMC2305_TACH_REGS_UNUSE_BITS 3 #define EMC2305_TACH_CNT_MULTIPLIER 0x02 #define EMC2305_TACH_RANGE_MIN 480 +#define EMC2305_DEFAULT_OUTPUT 0x0 +#define EMC2305_DEFAULT_POLARITY 0x0 +#define EMC2305_REG_POLARITY 0x2a +#define EMC2305_REG_DRIVE_PWM_OUT 0x2b +#define EMC2305_OPEN_DRAIN 0x0 +#define EMC2305_PUSH_PULL 0x1 #define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \ DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max)) @@ -39,6 +48,9 @@ #define EMC2305_REG_FAN_MIN_DRIVE(n) (0x38 + 0x10 * (n)) #define EMC2305_REG_FAN_TACH(n) (0x3e + 0x10 * (n)) +/* Supported base PWM frequencies */ +static const unsigned int base_freq_table[] = { 2441, 4882, 19530, 26000 }; + enum emc230x_product_id { EMC2305 = 0x34, EMC2303 = 0x35, @@ -89,8 +101,11 @@ struct emc2305_cdev_data { * @hwmon_dev: hwmon device * @max_state: maximum cooling state of the cooling device * @pwm_num: number of PWM channels + * @pwm_output_mask: PWM output mask + * @pwm_polarity_mask: PWM polarity mask * @pwm_separate: separate PWM settings for every channel * @pwm_min: array of minimum PWM per channel + * @pwm_freq: array of PWM frequency per channel * @cdev_data: array of cooling devices data */ struct emc2305_data { @@ -98,8 +113,11 @@ struct emc2305_data { struct device *hwmon_dev; u8 max_state; u8 pwm_num; + u8 pwm_output_mask; + u8 pwm_polarity_mask; bool pwm_separate; u8 pwm_min[EMC2305_PWM_MAX]; + u16 pwm_freq[EMC2305_PWM_MAX]; struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX]; }; @@ -281,7 +299,7 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel) return 0; } -static int emc2305_set_single_tz(struct device *dev, int idx) +static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_node, int idx) { struct emc2305_data *data = dev_get_drvdata(dev); long pwm; @@ -291,7 +309,7 @@ static int emc2305_set_single_tz(struct device *dev, int idx) pwm = data->pwm_min[cdev_idx]; data->cdev_data[cdev_idx].cdev = - devm_thermal_of_cooling_device_register(dev, dev->of_node, + devm_thermal_of_cooling_device_register(dev, fan_node, emc2305_fan_name[idx], data, &emc2305_cooling_ops); @@ -299,6 +317,12 @@ static int emc2305_set_single_tz(struct device *dev, int idx) dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]); return PTR_ERR(data->cdev_data[cdev_idx].cdev); } + + if (data->cdev_data[cdev_idx].cur_state > 0) + /* Update pwm when temperature is above trips */ + pwm = EMC2305_PWM_STATE2DUTY(data->cdev_data[cdev_idx].cur_state, + data->max_state, EMC2305_FAN_MAX); + /* Set minimal PWM speed. */ if (data->pwm_separate) { ret = emc2305_set_pwm(dev, pwm, cdev_idx); @@ -312,10 +336,10 @@ static int emc2305_set_single_tz(struct device *dev, int idx) } } data->cdev_data[cdev_idx].cur_state = - EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state, + EMC2305_PWM_DUTY2STATE(pwm, data->max_state, EMC2305_FAN_MAX); data->cdev_data[cdev_idx].last_hwmon_state = - EMC2305_PWM_DUTY2STATE(data->pwm_min[cdev_idx], data->max_state, + EMC2305_PWM_DUTY2STATE(pwm, data->max_state, EMC2305_FAN_MAX); return 0; } @@ -326,10 +350,10 @@ static int emc2305_set_tz(struct device *dev) int i, ret; if (!data->pwm_separate) - return emc2305_set_single_tz(dev, 0); + return emc2305_set_single_tz(dev, dev->of_node, 0); for (i = 0; i < data->pwm_num; i++) { - ret = emc2305_set_single_tz(dev, i + 1); + ret = emc2305_set_single_tz(dev, dev->of_node, i + 1); if (ret) return ret; } @@ -511,15 +535,85 @@ static int emc2305_identify(struct device *dev) return 0; } +static int emc2305_of_parse_pwm_child(struct device *dev, + struct device_node *child, + struct emc2305_data *data) +{ u32 ch; + int ret; + struct of_phandle_args args; + + ret = of_property_read_u32(child, "reg", &ch); + if (ret) { + dev_err(dev, "missing reg property of %pOFn\n", child); + return ret; + } + + ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells", 0, &args); + + if (ret) + return ret; + + if (args.args_count > 0) { + data->pwm_freq[ch] = find_closest(args.args[0], base_freq_table, + ARRAY_SIZE(base_freq_table)); + } else { + data->pwm_freq[ch] = base_freq_table[3]; + } + + if (args.args_count > 1) { + if (args.args[1] == PWM_POLARITY_NORMAL || args.args[1] == PWM_POLARITY_INVERSED) + data->pwm_polarity_mask |= args.args[1] << ch; + else + dev_err(dev, "Wrong PWM polarity config provided: %d\n", args.args[0]); + } else { + data->pwm_polarity_mask |= PWM_POLARITY_NORMAL << ch; + } + + if (args.args_count > 2) { + if (args.args[2] == EMC2305_PUSH_PULL || args.args[2] <= EMC2305_OPEN_DRAIN) + data->pwm_output_mask |= args.args[2] << ch; + else + dev_err(dev, "Wrong PWM output config provided: %d\n", args.args[1]); + } else { + data->pwm_output_mask |= EMC2305_OPEN_DRAIN << ch; + } + + return 0; +} + +static int emc2305_probe_childs_from_dt(struct device *dev) +{ + struct emc2305_data *data = dev_get_drvdata(dev); + struct device_node *child; + int ret, count = 0; + + data->pwm_output_mask = 0x0; + data->pwm_polarity_mask = 0x0; + + for_each_child_of_node(dev->of_node, child) { + if (of_property_present(child, "reg")) { + ret = emc2305_of_parse_pwm_child(dev, child, data); + if (ret) { + of_node_put(child); + continue; + } + count++; + } + } + return count; +} + static int emc2305_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; struct device *dev = &client->dev; + struct device_node *child; struct emc2305_data *data; struct emc2305_platform_data *pdata; int vendor; int ret; int i; + int pwm_childs; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) return -ENODEV; @@ -539,22 +633,40 @@ static int emc2305_probe(struct i2c_client *client) if (ret) return ret; + pwm_childs = emc2305_probe_childs_from_dt(dev); + pdata = dev_get_platdata(&client->dev); - if (pdata) { - if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE) - return -EINVAL; - data->max_state = pdata->max_state; - /* - * Validate a number of active PWM channels. Note that - * configured number can be less than the actual maximum - * supported by the device. - */ - if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX) - return -EINVAL; - data->pwm_num = pdata->pwm_num; - data->pwm_separate = pdata->pwm_separate; - for (i = 0; i < EMC2305_PWM_MAX; i++) - data->pwm_min[i] = pdata->pwm_min[i]; + + if (!pwm_childs) { + if (pdata) { + if (!pdata->max_state || pdata->max_state > EMC2305_FAN_MAX_STATE) + return -EINVAL; + data->max_state = pdata->max_state; + /* + * Validate a number of active PWM channels. Note that + * configured number can be less than the actual maximum + * supported by the device. + */ + if (!pdata->pwm_num || pdata->pwm_num > EMC2305_PWM_MAX) + return -EINVAL; + data->pwm_num = pdata->pwm_num; + data->pwm_output_mask = pdata->pwm_output_mask; + data->pwm_polarity_mask = pdata->pwm_polarity_mask; + data->pwm_separate = pdata->pwm_separate; + for (i = 0; i < EMC2305_PWM_MAX; i++) { + data->pwm_min[i] = pdata->pwm_min[i]; + data->pwm_freq[i] = pdata->pwm_freq[i]; + } + } else { + data->max_state = EMC2305_FAN_MAX_STATE; + data->pwm_separate = false; + data->pwm_output_mask = EMC2305_DEFAULT_OUTPUT; + data->pwm_polarity_mask = EMC2305_DEFAULT_POLARITY; + for (i = 0; i < EMC2305_PWM_MAX; i++) { + data->pwm_min[i] = EMC2305_FAN_MIN; + data->pwm_freq[i] = base_freq_table[3]; + } + } } else { data->max_state = EMC2305_FAN_MAX_STATE; data->pwm_separate = false; @@ -568,11 +680,32 @@ static int emc2305_probe(struct i2c_client *client) return PTR_ERR(data->hwmon_dev); if (IS_REACHABLE(CONFIG_THERMAL)) { - ret = emc2305_set_tz(dev); - if (ret != 0) - return ret; + /* Parse and check for the available PWM child nodes */ + if (pwm_childs > 0) { + i = 0; + for_each_child_of_node(dev->of_node, child) { + ret = emc2305_set_single_tz(dev, child, i); + if (ret != 0) + return ret; + i++; + } + } else { + ret = emc2305_set_tz(dev); + if (ret != 0) + return ret; + } } + ret = i2c_smbus_write_byte_data(client, EMC2305_REG_DRIVE_PWM_OUT, + data->pwm_output_mask); + if (ret < 0) + dev_err(dev, "Failed to configure pwm output, using default\n"); + + ret = i2c_smbus_write_byte_data(client, EMC2305_REG_POLARITY, + data->pwm_polarity_mask); + if (ret < 0) + dev_err(dev, "Failed to configure pwm polarity, using default\n"); + for (i = 0; i < data->pwm_num; i++) { ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_MIN_DRIVE(i), data->pwm_min[i]); |