diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e51269fd9de3..e60bfc31bbad 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -224,6 +224,7 @@ config SENSORS_ADT7462 config SENSORS_ADT7470 tristate "Analog Devices ADT7470" depends on I2C + depends on PWM || PWM=n select REGMAP_I2C help If you say yes here you get support for the Analog Devices diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 87ddd7b8ddae..eeec3db0da88 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; @@ -164,6 +167,7 @@ struct adt7470_data { char limits_valid; unsigned long sensors_last_updated; /* In jiffies */ unsigned long limits_last_updated; /* In jiffies */ + bool use_pwm_framework; int num_temp_sensors; /* -1 = probe */ int temperatures_probed; @@ -887,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val return err; } +struct adt7470_pwm_wfhw { + u8 val; +}; + +static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + u64 actual_period; + + if (wf->duty_length_ns == 0) { + wfhw->val = 0; + return 0; + } + + /* + * The PWM frequency (period) is a single chip-wide setting shared by + * all 4 channels, so it cannot be changed on a per-pwm_device basis + * through this API. The duty cycle is rounded against the currently + * configured hardware period rather than the period requested in + * @wf; round_waveform_fromhw() reports the actual resulting + * waveform back so the core/consumer can detect a mismatch. + */ + actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + + if (actual_period > wf->period_length_ns) + /* period too short */ + return 1; + + if (wf->duty_length_ns >= actual_period) { + wfhw->val = ADT7470_PWM_MAX; + } else { + wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, + ADT7470_PWM_MAX, + actual_period); + } + + return 0; +} + +static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + + wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + wf->duty_offset_ns = 0; + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, + ADT7470_PWM_MAX); + return 0; +} + +static int adt7470_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct device *dev = regmap_get_device(data->regmap); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + + data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * No lock needed: like the other hwmon_ops read callbacks in this + * driver (e.g. adt7470_pwm_read()), this only does a single byte + * read from the cache populated by adt7470_update_device(). + */ + wfhw->val = data->pwm[pwm->hwpwm]; + + return 0; +} + +static int adt7470_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + unsigned int pwm_auto_reg_mask; + int err; + + if (pwm->hwpwm % 2) + pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; + else + pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; + + guard(mutex)(&data->lock); + + if (data->pwm[pwm->hwpwm] == wfhw->val && + data->pwm_automatic[pwm->hwpwm] == 0) + return 0; + + /* Put the PWM channel in manual mode before updating it. */ + err = regmap_update_bits(data->regmap, + ADT7470_REG_PWM_CFG(pwm->hwpwm), + pwm_auto_reg_mask, 0); + if (err < 0) + return err; + + data->pwm_automatic[pwm->hwpwm] = 0; + + err = regmap_write(data->regmap, + ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); + if (err < 0) + return err; + + data->pwm[pwm->hwpwm] = wfhw->val; + + return 0; +} + +static const struct pwm_ops adt7470_pwm_ops = { + .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), + .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, + .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, + .read_waveform = adt7470_pwm_read_waveform, + .write_waveform = adt7470_pwm_write_waveform, +}; + static ssize_t pwm_max_show(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -1107,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { &dev_attr_alarm_mask.attr, &dev_attr_num_temp_sensors.attr, &dev_attr_auto_update_interval.attr, + NULL +}; + +static struct attribute *adt7470_pwm_attrs[] = { &sensor_dev_attr_force_pwm_max.dev_attr.attr, &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, @@ -1131,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { NULL }; -ATTRIBUTE_GROUPS(adt7470); +static const struct attribute_group adt7470_group = { + .attrs = adt7470_attrs, +}; + +static umode_t adt7470_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct adt7470_data *data = dev_get_drvdata(dev); + + if (data->use_pwm_framework) + return 0; + + return attr->mode; +} + +static const struct attribute_group adt7470_pwm_group = { + .attrs = adt7470_pwm_attrs, + .is_visible = adt7470_pwm_is_visible, +}; + +static const struct attribute_group *adt7470_groups[] = { + &adt7470_group, + &adt7470_pwm_group, + NULL, +}; static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) @@ -1166,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct adt7470_data *data = _data; umode_t mode = 0; switch (type) { @@ -1198,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ } break; case hwmon_pwm: + /* Hide all pwm attributes if this device is exposed to the PWM + * framework + */ + if (data->use_pwm_framework) { + mode = 0; + break; + } + switch (attr) { case hwmon_pwm_input: case hwmon_pwm_enable: @@ -1328,6 +1497,29 @@ static int adt7470_probe(struct i2c_client *client) data->pwm_freq = (u32)freq_val; + data->use_pwm_framework = false; + + if (device_property_present(dev, "#pwm-cells")) { + if (IS_REACHABLE(CONFIG_PWM)) { + struct pwm_chip *chip; + + chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->ops = &adt7470_pwm_ops; + pwmchip_set_drvdata(chip, data); + + err = devm_pwmchip_add(dev, chip); + if (err) + return dev_err_probe(dev, err, "failed to register PWM chip\n"); + + data->use_pwm_framework = true; + } else { + dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); + } + } + /* Register sysfs hooks */ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &adt7470_chip_info,