diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 14 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/core.c | 350 | ||||
-rw-r--r-- | drivers/pwm/pwm-adp5585.c | 78 | ||||
-rw-r--r-- | drivers/pwm/pwm-argon-fan-hat.c | 109 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel.c | 12 | ||||
-rw-r--r-- | drivers/pwm/pwm-clps711x.c | 8 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 28 | ||||
-rw-r--r-- | drivers/pwm/pwm-img.c | 2 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpc18xx-sct.c | 14 | ||||
-rw-r--r-- | drivers/pwm/pwm-mc33xs2410.c | 20 | ||||
-rw-r--r-- | drivers/pwm/pwm-mediatek.c | 51 | ||||
-rw-r--r-- | drivers/pwm/pwm-microchip-core.c | 17 | ||||
-rw-r--r-- | drivers/pwm/pwm-pxa.c | 6 | ||||
-rw-r--r-- | drivers/pwm/pwm-rockchip.c | 33 | ||||
-rw-r--r-- | drivers/pwm/pwm-sifive.c | 52 | ||||
-rw-r--r-- | drivers/pwm/pwm-sophgo-sg2042.c | 141 | ||||
-rw-r--r-- | drivers/pwm/pwm-sti.c | 23 | ||||
-rw-r--r-- | drivers/pwm/pwm-stm32.c | 42 | ||||
-rw-r--r-- | drivers/pwm/pwm-sun4i.c | 10 | ||||
-rw-r--r-- | drivers/pwm/pwm-twl-led.c | 49 |
21 files changed, 792 insertions, 268 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d9bcd1e8413e..f00ce973dddf 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -66,6 +66,15 @@ config PWM_APPLE To compile this driver as a module, choose M here: the module will be called pwm-apple. +config PWM_ARGON_FAN_HAT + tristate "Argon40 Fan HAT support" + depends on I2C && OF + help + Generic PWM framework driver for Argon40 Fan HAT. + + To compile this driver as a module, choose M here: the module + will be called pwm-argon-fan-hat. + config PWM_ATMEL tristate "Atmel PWM support" depends on ARCH_AT91 || COMPILE_TEST @@ -427,6 +436,7 @@ config PWM_MC33XS2410 tristate "MC33XS2410 PWM support" depends on OF depends on SPI + select AUXILIARY_BUS help NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four channel high-side switch. The device is operational from 3.0 V @@ -517,7 +527,7 @@ config PWM_PCA9685 config PWM_PXA tristate "PXA PWM support" - depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST + depends on ARCH_PXA || ARCH_MMP || ARCH_SPACEMIT || COMPILE_TEST depends on HAS_IOMEM help Generic PWM framework driver for PXA. @@ -526,7 +536,7 @@ config PWM_PXA will be called pwm-pxa. config PWM_RASPBERRYPI_POE - tristate "Raspberry Pi Firwmware PoE Hat PWM support" + tristate "Raspberry Pi Firmware PoE Hat PWM support" # Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only # happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE. depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 96160f4257fc..ff4f47e5fb7a 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o +obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 4d842c692194..0d66376a83ec 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -23,9 +23,13 @@ #include <dt-bindings/pwm/pwm.h> +#include <uapi/linux/pwm.h> + #define CREATE_TRACE_POINTS #include <trace/events/pwm.h> +#define PWM_MINOR_COUNT 256 + /* protects access to pwm_chips */ static DEFINE_MUTEX(pwm_lock); @@ -206,8 +210,6 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c return ret; } -#define WFHWSIZE 20 - /** * pwm_round_waveform_might_sleep - Query hardware capabilities * Cannot be used in atomic context. @@ -244,10 +246,10 @@ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform * struct pwm_chip *chip = pwm->chip; const struct pwm_ops *ops = chip->ops; struct pwm_waveform wf_req = *wf; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; int ret_tohw, ret_fromhw; - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); if (!pwmchip_supports_waveform(chip)) return -EOPNOTSUPP; @@ -302,10 +304,10 @@ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf { struct pwm_chip *chip = pwm->chip; const struct pwm_ops *ops = chip->ops; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; int err; - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); if (!pwmchip_supports_waveform(chip) || !ops->read_waveform) return -EOPNOTSUPP; @@ -330,11 +332,11 @@ static int __pwm_set_waveform(struct pwm_device *pwm, { struct pwm_chip *chip = pwm->chip; const struct pwm_ops *ops = chip->ops; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; struct pwm_waveform wf_rounded; int err, ret_tohw; - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); if (!pwmchip_supports_waveform(chip)) return -EOPNOTSUPP; @@ -596,7 +598,7 @@ static bool pwm_state_valid(const struct pwm_state *state) * and supposed to be ignored. So also ignore any strange values and * consider the state ok. */ - if (state->enabled) + if (!state->enabled) return true; if (!state->period) @@ -646,9 +648,9 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) if (pwmchip_supports_waveform(chip)) { struct pwm_waveform wf; - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); pwm_state2wf(state, &wf); @@ -805,10 +807,10 @@ int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state) return -ENODEV; if (pwmchip_supports_waveform(chip) && ops->read_waveform) { - char wfhw[WFHWSIZE]; + char wfhw[PWM_WFHWSIZE]; struct pwm_waveform wf; - BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw); ret = __pwm_read_waveform(chip, pwm, &wfhw); if (ret) @@ -1692,8 +1694,8 @@ static bool pwm_ops_check(const struct pwm_chip *chip) !ops->write_waveform) return false; - if (WFHWSIZE < ops->sizeof_wfhw) { - dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw); + if (PWM_WFHWSIZE < ops->sizeof_wfhw) { + dev_warn(pwmchip_parent(chip), "PWM_WFHWSIZE < %zu\n", ops->sizeof_wfhw); return false; } } else { @@ -2007,20 +2009,9 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) } EXPORT_SYMBOL_GPL(pwm_get); -/** - * pwm_put() - release a PWM device - * @pwm: PWM device - */ -void pwm_put(struct pwm_device *pwm) +static void __pwm_put(struct pwm_device *pwm) { - struct pwm_chip *chip; - - if (!pwm) - return; - - chip = pwm->chip; - - guard(mutex)(&pwm_lock); + struct pwm_chip *chip = pwm->chip; /* * Trigger a warning if a consumer called pwm_put() twice. @@ -2041,6 +2032,20 @@ void pwm_put(struct pwm_device *pwm) module_put(chip->owner); } + +/** + * pwm_put() - release a PWM device + * @pwm: PWM device + */ +void pwm_put(struct pwm_device *pwm) +{ + if (!pwm) + return; + + guard(mutex)(&pwm_lock); + + __pwm_put(pwm); +} EXPORT_SYMBOL_GPL(pwm_put); static void devm_pwm_release(void *pwm) @@ -2110,6 +2115,274 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev, } EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); +struct pwm_cdev_data { + struct pwm_chip *chip; + struct pwm_device *pwm[]; +}; + +static int pwm_cdev_open(struct inode *inode, struct file *file) +{ + struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev); + struct pwm_cdev_data *cdata; + + guard(mutex)(&pwm_lock); + + if (!chip->operational) + return -ENXIO; + + cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->chip = chip; + + file->private_data = cdata; + + return nonseekable_open(inode, file); +} + +static int pwm_cdev_release(struct inode *inode, struct file *file) +{ + struct pwm_cdev_data *cdata = file->private_data; + unsigned int i; + + for (i = 0; i < cdata->chip->npwm; ++i) { + struct pwm_device *pwm = cdata->pwm[i]; + + if (pwm) { + const char *label = pwm->label; + + pwm_put(cdata->pwm[i]); + kfree(label); + } + } + kfree(cdata); + + return 0; +} + +static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm) +{ + struct pwm_chip *chip = cdata->chip; + + if (hwpwm >= chip->npwm) + return -EINVAL; + + if (!cdata->pwm[hwpwm]) { + struct pwm_device *pwm = &chip->pwms[hwpwm]; + const char *label; + int ret; + + label = kasprintf(GFP_KERNEL, "pwm-cdev (pid=%d)", current->pid); + if (!label) + return -ENOMEM; + + ret = pwm_device_request(pwm, label); + if (ret < 0) { + kfree(label); + return ret; + } + + cdata->pwm[hwpwm] = pwm; + } + + return 0; +} + +static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm) +{ + struct pwm_chip *chip = cdata->chip; + + if (hwpwm >= chip->npwm) + return -EINVAL; + + if (cdata->pwm[hwpwm]) { + struct pwm_device *pwm = cdata->pwm[hwpwm]; + const char *label = pwm->label; + + __pwm_put(pwm); + + kfree(label); + + cdata->pwm[hwpwm] = NULL; + } + + return 0; +} + +static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata, + u32 hwpwm) +{ + struct pwm_chip *chip = cdata->chip; + + if (hwpwm >= chip->npwm) + return ERR_PTR(-EINVAL); + + if (cdata->pwm[hwpwm]) + return cdata->pwm[hwpwm]; + + return ERR_PTR(-EINVAL); +} + +static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + struct pwm_cdev_data *cdata = file->private_data; + struct pwm_chip *chip = cdata->chip; + + guard(mutex)(&pwm_lock); + + if (!chip->operational) + return -ENODEV; + + switch (cmd) { + case PWM_IOCTL_REQUEST: + { + unsigned int hwpwm = arg; + + return pwm_cdev_request(cdata, hwpwm); + } + + case PWM_IOCTL_FREE: + { + unsigned int hwpwm = arg; + + return pwm_cdev_free(cdata, hwpwm); + } + + case PWM_IOCTL_ROUNDWF: + { + struct pwmchip_waveform cwf; + struct pwm_waveform wf; + struct pwm_device *pwm; + + ret = copy_from_user(&cwf, + (struct pwmchip_waveform __user *)arg, + sizeof(cwf)); + if (ret) + return -EFAULT; + + if (cwf.__pad != 0) + return -EINVAL; + + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + wf = (struct pwm_waveform) { + .period_length_ns = cwf.period_length_ns, + .duty_length_ns = cwf.duty_length_ns, + .duty_offset_ns = cwf.duty_offset_ns, + }; + + ret = pwm_round_waveform_might_sleep(pwm, &wf); + if (ret < 0) + return ret; + + cwf = (struct pwmchip_waveform) { + .hwpwm = cwf.hwpwm, + .period_length_ns = wf.period_length_ns, + .duty_length_ns = wf.duty_length_ns, + .duty_offset_ns = wf.duty_offset_ns, + }; + + return copy_to_user((struct pwmchip_waveform __user *)arg, + &cwf, sizeof(cwf)); + } + + case PWM_IOCTL_GETWF: + { + struct pwmchip_waveform cwf; + struct pwm_waveform wf; + struct pwm_device *pwm; + + ret = copy_from_user(&cwf, + (struct pwmchip_waveform __user *)arg, + sizeof(cwf)); + if (ret) + return -EFAULT; + + if (cwf.__pad != 0) + return -EINVAL; + + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + ret = pwm_get_waveform_might_sleep(pwm, &wf); + if (ret) + return ret; + + cwf = (struct pwmchip_waveform) { + .hwpwm = cwf.hwpwm, + .period_length_ns = wf.period_length_ns, + .duty_length_ns = wf.duty_length_ns, + .duty_offset_ns = wf.duty_offset_ns, + }; + + return copy_to_user((struct pwmchip_waveform __user *)arg, + &cwf, sizeof(cwf)); + } + + case PWM_IOCTL_SETROUNDEDWF: + case PWM_IOCTL_SETEXACTWF: + { + struct pwmchip_waveform cwf; + struct pwm_waveform wf; + struct pwm_device *pwm; + + ret = copy_from_user(&cwf, + (struct pwmchip_waveform __user *)arg, + sizeof(cwf)); + if (ret) + return -EFAULT; + + if (cwf.__pad != 0) + return -EINVAL; + + wf = (struct pwm_waveform){ + .period_length_ns = cwf.period_length_ns, + .duty_length_ns = cwf.duty_length_ns, + .duty_offset_ns = cwf.duty_offset_ns, + }; + + if (!pwm_wf_valid(&wf)) + return -EINVAL; + + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + ret = pwm_set_waveform_might_sleep(pwm, &wf, + cmd == PWM_IOCTL_SETEXACTWF); + + /* + * If userspace cares about rounding deviations it has + * to check the values anyhow, so simplify handling for + * them and don't signal uprounding. This matches the + * behaviour of PWM_IOCTL_ROUNDWF which also returns 0 + * in that case. + */ + if (ret == 1) + ret = 0; + + return ret; + } + + default: + return -ENOTTY; + } +} + +static const struct file_operations pwm_cdev_fileops = { + .open = pwm_cdev_open, + .release = pwm_cdev_release, + .owner = THIS_MODULE, + .unlocked_ioctl = pwm_cdev_ioctl, +}; + +static dev_t pwm_devt; + /** * __pwmchip_add() - register a new PWM chip * @chip: the PWM chip to add @@ -2162,7 +2435,17 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) scoped_guard(pwmchip, chip) chip->operational = true; - ret = device_add(&chip->dev); + if (chip->ops->write_waveform) { + if (chip->id < PWM_MINOR_COUNT) + chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id); + else + dev_warn(&chip->dev, "chip id too high to create a chardev\n"); + } + + cdev_init(&chip->cdev, &pwm_cdev_fileops); + chip->cdev.owner = owner; + + ret = cdev_device_add(&chip->cdev, &chip->dev); if (ret) goto err_device_add; @@ -2213,7 +2496,7 @@ void pwmchip_remove(struct pwm_chip *chip) idr_remove(&pwm_chips, chip->id); } - device_del(&chip->dev); + cdev_device_del(&chip->cdev, &chip->dev); } EXPORT_SYMBOL_GPL(pwmchip_remove); @@ -2357,9 +2640,16 @@ static int __init pwm_init(void) { int ret; + ret = alloc_chrdev_region(&pwm_devt, 0, PWM_MINOR_COUNT, "pwm"); + if (ret) { + pr_err("Failed to initialize chrdev region for PWM usage\n"); + return ret; + } + ret = class_register(&pwm_class); if (ret) { pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret)); + unregister_chrdev_region(pwm_devt, 256); return ret; } diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c index d79106d12181..dc2860979e24 100644 --- a/drivers/pwm/pwm-adp5585.c +++ b/drivers/pwm/pwm-adp5585.c @@ -33,21 +33,33 @@ #define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) #define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) +struct adp5585_pwm_chip { + unsigned int pwm_cfg; + unsigned int pwm_offt_low; + unsigned int pwm_ont_low; +}; + +struct adp5585_pwm { + const struct adp5585_pwm_chip *info; + struct regmap *regmap; + unsigned int ext_cfg; +}; + static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm) { - struct regmap *regmap = pwmchip_get_drvdata(chip); + struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip); /* Configure the R3 pin as PWM output. */ - return regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + return regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg, ADP5585_R3_EXTEND_CFG_MASK, ADP5585_R3_EXTEND_CFG_PWM_OUT); } static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm) { - struct regmap *regmap = pwmchip_get_drvdata(chip); + struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip); - regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + regmap_update_bits(adp5585_pwm->regmap, adp5585_pwm->ext_cfg, ADP5585_R3_EXTEND_CFG_MASK, ADP5585_R3_EXTEND_CFG_GPIO4); } @@ -56,15 +68,16 @@ static int pwm_adp5585_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - struct regmap *regmap = pwmchip_get_drvdata(chip); + struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip); + const struct adp5585_pwm_chip *info = adp5585_pwm->info; + struct regmap *regmap = adp5585_pwm->regmap; u64 period, duty_cycle; u32 on, off; __le16 val; int ret; if (!state->enabled) { - regmap_clear_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); - regmap_clear_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); + regmap_clear_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN); return 0; } @@ -85,45 +98,43 @@ static int pwm_adp5585_apply(struct pwm_chip *chip, off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on; val = cpu_to_le16(off); - ret = regmap_bulk_write(regmap, ADP5585_PWM_OFFT_LOW, &val, 2); + ret = regmap_bulk_write(regmap, info->pwm_offt_low, &val, 2); if (ret) return ret; val = cpu_to_le16(on); - ret = regmap_bulk_write(regmap, ADP5585_PWM_ONT_LOW, &val, 2); + ret = regmap_bulk_write(regmap, info->pwm_ont_low, &val, 2); if (ret) return ret; /* Enable PWM in continuous mode and no external AND'ing. */ - ret = regmap_update_bits(regmap, ADP5585_PWM_CFG, + ret = regmap_update_bits(regmap, info->pwm_cfg, ADP5585_PWM_IN_AND | ADP5585_PWM_MODE | ADP5585_PWM_EN, ADP5585_PWM_EN); if (ret) return ret; - ret = regmap_set_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); - if (ret) - return ret; - - return regmap_set_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); + return regmap_set_bits(regmap, info->pwm_cfg, ADP5585_PWM_EN); } static int pwm_adp5585_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { - struct regmap *regmap = pwmchip_get_drvdata(chip); + struct adp5585_pwm *adp5585_pwm = pwmchip_get_drvdata(chip); + const struct adp5585_pwm_chip *info = adp5585_pwm->info; + struct regmap *regmap = adp5585_pwm->regmap; unsigned int on, off; unsigned int val; __le16 on_off; int ret; - ret = regmap_bulk_read(regmap, ADP5585_PWM_OFFT_LOW, &on_off, 2); + ret = regmap_bulk_read(regmap, info->pwm_offt_low, &on_off, 2); if (ret) return ret; off = le16_to_cpu(on_off); - ret = regmap_bulk_read(regmap, ADP5585_PWM_ONT_LOW, &on_off, 2); + ret = regmap_bulk_read(regmap, info->pwm_ont_low, &on_off, 2); if (ret) return ret; on = le16_to_cpu(on_off); @@ -133,7 +144,7 @@ static int pwm_adp5585_get_state(struct pwm_chip *chip, state->polarity = PWM_POLARITY_NORMAL; - regmap_read(regmap, ADP5585_PWM_CFG, &val); + regmap_read(regmap, info->pwm_cfg, &val); state->enabled = !!(val & ADP5585_PWM_EN); return 0; @@ -148,18 +159,28 @@ static const struct pwm_ops adp5585_pwm_ops = { static int adp5585_pwm_probe(struct platform_device *pdev) { + const struct platform_device_id *id = platform_get_device_id(pdev); struct device *dev = &pdev->dev; struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent); + struct adp5585_pwm *adp5585_pwm; struct pwm_chip *chip; int ret; - chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0); + chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, + sizeof(*adp5585_pwm)); if (IS_ERR(chip)) return PTR_ERR(chip); + adp5585_pwm = pwmchip_get_drvdata(chip); + adp5585_pwm->regmap = adp5585->regmap; + adp5585_pwm->ext_cfg = adp5585->regs->ext_cfg; + + adp5585_pwm->info = (const struct adp5585_pwm_chip *)id->driver_data; + if (!adp5585_pwm->info) + return -ENODEV; + device_set_of_node_from_dev(dev, dev->parent); - pwmchip_set_drvdata(chip, adp5585->regmap); chip->ops = &adp5585_pwm_ops; ret = devm_pwmchip_add(dev, chip); @@ -169,8 +190,21 @@ static int adp5585_pwm_probe(struct platform_device *pdev) return 0; } +static const struct adp5585_pwm_chip adp5589_pwm_chip_info = { + .pwm_cfg = ADP5585_PWM_CFG, + .pwm_offt_low = ADP5585_PWM_OFFT_LOW, + .pwm_ont_low = ADP5585_PWM_ONT_LOW, +}; + +static const struct adp5585_pwm_chip adp5585_pwm_chip_info = { + .pwm_cfg = ADP5589_PWM_CFG, + .pwm_offt_low = ADP5589_PWM_OFFT_LOW, + .pwm_ont_low = ADP5589_PWM_ONT_LOW, +}; + static const struct platform_device_id adp5585_pwm_id_table[] = { - { "adp5585-pwm" }, + { "adp5585-pwm", (kernel_ulong_t)&adp5585_pwm_chip_info }, + { "adp5589-pwm", (kernel_ulong_t)&adp5589_pwm_chip_info }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table); diff --git a/drivers/pwm/pwm-argon-fan-hat.c b/drivers/pwm/pwm-argon-fan-hat.c new file mode 100644 index 000000000000..2c59bd142d40 --- /dev/null +++ b/drivers/pwm/pwm-argon-fan-hat.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Marek Vasut + * + * Limitations: + * - no support for offset/polarity + * - fixed 30 kHz period + * + * Argon Fan HAT https://argon40.com/products/argon-fan-hat + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pwm.h> + +#define ARGON40_FAN_HAT_PERIOD_NS 33333 /* ~30 kHz */ + +#define ARGON40_FAN_HAT_REG_DUTY_CYCLE 0x80 + +static int argon_fan_hat_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + u8 *wfhw = _wfhw; + + if (wf->duty_length_ns > ARGON40_FAN_HAT_PERIOD_NS) + *wfhw = 100; + else + *wfhw = mul_u64_u64_div_u64(wf->duty_length_ns, 100, ARGON40_FAN_HAT_PERIOD_NS); + + return 0; +} + +static int argon_fan_hat_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const u8 *wfhw = _wfhw; + + wf->period_length_ns = ARGON40_FAN_HAT_PERIOD_NS; + wf->duty_length_ns = DIV64_U64_ROUND_UP(wf->period_length_ns * *wfhw, 100); + wf->duty_offset_ns = 0; + + return 0; +} + +static int argon_fan_hat_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct i2c_client *i2c = pwmchip_get_drvdata(chip); + const u8 *wfhw = _wfhw; + + return i2c_smbus_write_byte_data(i2c, ARGON40_FAN_HAT_REG_DUTY_CYCLE, *wfhw); +} + +static const struct pwm_ops argon_fan_hat_pwm_ops = { + .sizeof_wfhw = sizeof(u8), + .round_waveform_fromhw = argon_fan_hat_round_waveform_fromhw, + .round_waveform_tohw = argon_fan_hat_round_waveform_tohw, + .write_waveform = argon_fan_hat_write_waveform, + /* + * The controller does not provide any way to read info back, + * reading from the controller stops the fan, therefore there + * is no .read_waveform here. + */ +}; + +static int argon_fan_hat_i2c_probe(struct i2c_client *i2c) +{ + struct pwm_chip *chip = devm_pwmchip_alloc(&i2c->dev, 1, 0); + int ret; + + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->ops = &argon_fan_hat_pwm_ops; + pwmchip_set_drvdata(chip, i2c); + + ret = devm_pwmchip_add(&i2c->dev, chip); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Could not add PWM chip\n"); + + return 0; +} + +static const struct of_device_id argon_fan_hat_dt_ids[] = { + { .compatible = "argon40,fan-hat" }, + { }, +}; +MODULE_DEVICE_TABLE(of, argon_fan_hat_dt_ids); + +static struct i2c_driver argon_fan_hat_driver = { + .driver = { + .name = "argon-fan-hat", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = argon_fan_hat_dt_ids, + }, + .probe = argon_fan_hat_i2c_probe, +}; + +module_i2c_driver(argon_fan_hat_driver); + +MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@mailbox.org>"); +MODULE_DESCRIPTION("Argon40 Fan HAT"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index b2f0abbbad63..06d22d0f7b26 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -91,9 +91,6 @@ struct atmel_pwm_chip { * hardware. */ u32 update_pending; - - /* Protects .update_pending */ - spinlock_t lock; }; static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip) @@ -145,8 +142,6 @@ static void atmel_pwm_update_pending(struct atmel_pwm_chip *chip) static void atmel_pwm_set_pending(struct atmel_pwm_chip *chip, unsigned int ch) { - spin_lock(&chip->lock); - /* * Clear pending flags in hardware because otherwise there might still * be a stale flag in ISR. @@ -154,16 +149,12 @@ static void atmel_pwm_set_pending(struct atmel_pwm_chip *chip, unsigned int ch) atmel_pwm_update_pending(chip); chip->update_pending |= (1 << ch); - - spin_unlock(&chip->lock); } static int atmel_pwm_test_pending(struct atmel_pwm_chip *chip, unsigned int ch) { int ret = 0; - spin_lock(&chip->lock); - if (chip->update_pending & (1 << ch)) { atmel_pwm_update_pending(chip); @@ -171,8 +162,6 @@ static int atmel_pwm_test_pending(struct atmel_pwm_chip *chip, unsigned int ch) ret = 1; } - spin_unlock(&chip->lock); - return ret; } @@ -509,7 +498,6 @@ static int atmel_pwm_probe(struct platform_device *pdev) atmel_pwm->data = of_device_get_match_data(&pdev->dev); atmel_pwm->update_pending = 0; - spin_lock_init(&atmel_pwm->lock); atmel_pwm->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(atmel_pwm->base)) diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c index 04559a9de718..2c92ce754872 100644 --- a/drivers/pwm/pwm-clps711x.c +++ b/drivers/pwm/pwm-clps711x.c @@ -14,7 +14,6 @@ struct clps711x_chip { void __iomem *pmpcon; struct clk *clk; - spinlock_t lock; }; static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) @@ -42,7 +41,6 @@ static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct clps711x_chip *priv = to_clps711x_chip(chip); /* PWM0 - bits 4..7, PWM1 - bits 8..11 */ u32 shift = (pwm->hwpwm + 1) * 4; - unsigned long flags; u32 pmpcon, val; if (state->polarity != PWM_POLARITY_NORMAL) @@ -56,15 +54,11 @@ static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, else val = 0; - spin_lock_irqsave(&priv->lock, flags); - pmpcon = readl(priv->pmpcon); pmpcon &= ~(0xf << shift); pmpcon |= val << shift; writel(pmpcon, priv->pmpcon); - spin_unlock_irqrestore(&priv->lock, flags); - return 0; } @@ -93,8 +87,6 @@ static int clps711x_pwm_probe(struct platform_device *pdev) chip->ops = &clps711x_pwm_ops; - spin_lock_init(&priv->lock); - return devm_pwmchip_add(&pdev->dev, chip); } diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index c45a5fca4cbb..6683931872fc 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -10,7 +10,6 @@ #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/mutex.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm.h> @@ -40,7 +39,6 @@ struct fsl_pwm_periodcfg { }; struct fsl_pwm_chip { - struct mutex lock; struct regmap *regmap; /* This value is valid iff a pwm is running */ @@ -89,11 +87,8 @@ static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) struct fsl_pwm_chip *fpc = to_fsl_chip(chip); ret = clk_prepare_enable(fpc->ipg_clk); - if (!ret && fpc->soc->has_enable_bits) { - mutex_lock(&fpc->lock); + if (!ret && fpc->soc->has_enable_bits) regmap_set_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16)); - mutex_unlock(&fpc->lock); - } return ret; } @@ -102,11 +97,8 @@ static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - if (fpc->soc->has_enable_bits) { - mutex_lock(&fpc->lock); + if (fpc->soc->has_enable_bits) regmap_clear_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16)); - mutex_unlock(&fpc->lock); - } clk_disable_unprepare(fpc->ipg_clk); } @@ -304,7 +296,7 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); struct pwm_state *oldstate = &pwm->state; - int ret = 0; + int ret; /* * oldstate to newstate : action @@ -315,8 +307,6 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, * disabled to enabled : update settings + enable */ - mutex_lock(&fpc->lock); - if (!newstate->enabled) { if (oldstate->enabled) { regmap_set_bits(fpc->regmap, FTM_OUTMASK, @@ -325,30 +315,28 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); } - goto end_mutex; + return 0; } ret = fsl_pwm_apply_config(chip, pwm, newstate); if (ret) - goto end_mutex; + return ret; /* check if need to enable */ if (!oldstate->enabled) { ret = clk_prepare_enable(fpc->clk[fpc->period.clk_select]); if (ret) - goto end_mutex; + return ret; ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); if (ret) { clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); - goto end_mutex; + return ret; } regmap_clear_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm)); } -end_mutex: - mutex_unlock(&fpc->lock); return ret; } @@ -408,8 +396,6 @@ static int fsl_pwm_probe(struct platform_device *pdev) return PTR_ERR(chip); fpc = to_fsl_chip(chip); - mutex_init(&fpc->lock); - fpc->soc = of_device_get_match_data(&pdev->dev); base = devm_platform_ioremap_resource(pdev, 0); diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c index 71542956feca..91e0b19f0c08 100644 --- a/drivers/pwm/pwm-img.c +++ b/drivers/pwm/pwm-img.c @@ -139,7 +139,6 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, (timebase << PWM_CH_CFG_TMBASE_SHIFT); img_pwm_writel(imgchip, PWM_CH_CFG(pwm->hwpwm), val); - pm_runtime_mark_last_busy(pwmchip_parent(chip)); pm_runtime_put_autosuspend(pwmchip_parent(chip)); return 0; @@ -175,7 +174,6 @@ static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) val &= ~BIT(pwm->hwpwm); img_pwm_writel(imgchip, PWM_CTRL_CFG, val); - pm_runtime_mark_last_busy(pwmchip_parent(chip)); pm_runtime_put_autosuspend(pwmchip_parent(chip)); } diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c index f351baa63453..1e614b2a0227 100644 --- a/drivers/pwm/pwm-lpc18xx-sct.c +++ b/drivers/pwm/pwm-lpc18xx-sct.c @@ -100,8 +100,6 @@ struct lpc18xx_pwm_chip { u64 max_period_ns; unsigned int period_event; unsigned long event_map; - struct mutex res_lock; - struct mutex period_lock; struct lpc18xx_pwm_data channeldata[LPC18XX_NUM_PWMS]; }; @@ -129,8 +127,6 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm, { u32 val; - mutex_lock(&lpc18xx_pwm->res_lock); - /* * Simultaneous set and clear may happen on an output, that is the case * when duty_ns == period_ns. LPC18xx SCT allows to set a conflict @@ -140,8 +136,6 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm, val &= ~LPC18XX_PWM_RES_MASK(pwm->hwpwm); val |= LPC18XX_PWM_RES(pwm->hwpwm, action); lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_RES_BASE, val); - - mutex_unlock(&lpc18xx_pwm->res_lock); } static void lpc18xx_pwm_config_period(struct pwm_chip *chip, u64 period_ns) @@ -200,8 +194,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -ERANGE; } - mutex_lock(&lpc18xx_pwm->period_lock); - requested_events = bitmap_weight(&lpc18xx_pwm->event_map, LPC18XX_PWM_EVENT_MAX); @@ -214,7 +206,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, lpc18xx_pwm->period_ns) { dev_err(pwmchip_parent(chip), "conflicting period requested for PWM %u\n", pwm->hwpwm); - mutex_unlock(&lpc18xx_pwm->period_lock); return -EBUSY; } @@ -224,8 +215,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, lpc18xx_pwm_config_period(chip, period_ns); } - mutex_unlock(&lpc18xx_pwm->period_lock); - lpc18xx_pwm_config_duty(chip, pwm, duty_ns); return 0; @@ -377,9 +366,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev) if (lpc18xx_pwm->clk_rate > NSEC_PER_SEC) return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock to fast\n"); - mutex_init(&lpc18xx_pwm->res_lock); - mutex_init(&lpc18xx_pwm->period_lock); - lpc18xx_pwm->max_period_ns = mul_u64_u64_div_u64(NSEC_PER_SEC, LPC18XX_PWM_TIMER_MAX, lpc18xx_pwm->clk_rate); diff --git a/drivers/pwm/pwm-mc33xs2410.c b/drivers/pwm/pwm-mc33xs2410.c index a1ac3445ccdb..6d99e3ff7239 100644 --- a/drivers/pwm/pwm-mc33xs2410.c +++ b/drivers/pwm/pwm-mc33xs2410.c @@ -17,11 +17,14 @@ * behavior of the output pin that is neither the old nor the new state, * rather something in between. */ +#define DEFAULT_SYMBOL_NAMESPACE "PWM_MC33XS2410" +#include <linux/auxiliary_bus.h> #include <linux/bitfield.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/math64.h> +#include <linux/mc33xs2410.h> #include <linux/minmax.h> #include <linux/module.h> #include <linux/of.h> @@ -120,12 +123,19 @@ static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag return mc33xs2410_read_regs(spi, ®, flag, val, 1); } -static int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val) +int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val) { return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD); } +EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_ctrl); -static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val) +int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val) +{ + return mc33xs2410_read_reg(spi, reg, val, 0); +} +EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_diag); + +int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val) { u16 tmp; int ret; @@ -139,6 +149,7 @@ static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val return mc33xs2410_write_reg(spi, reg, tmp); } +EXPORT_SYMBOL_GPL(mc33xs2410_modify_reg); static u8 mc33xs2410_pwm_get_freq(u64 period) { @@ -314,6 +325,7 @@ static int mc33xs2410_reset(struct device *dev) static int mc33xs2410_probe(struct spi_device *spi) { struct device *dev = &spi->dev; + struct auxiliary_device *adev; struct pwm_chip *chip; int ret; @@ -361,6 +373,10 @@ static int mc33xs2410_probe(struct spi_device *spi) if (ret < 0) return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); + adev = devm_auxiliary_device_create(dev, "hwmon", NULL); + if (!adev) + return dev_err_probe(dev, -ENODEV, "Failed to register hwmon device\n"); + return 0; } diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 7eaab5831499..6777c511622a 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -29,6 +29,7 @@ #define PWM45DWIDTH_FIXUP 0x30 #define PWMTHRES 0x30 #define PWM45THRES_FIXUP 0x34 +#define PWM_CK_26M_SEL_V3 0x74 #define PWM_CK_26M_SEL 0x210 #define PWM_CLK_DIV_MAX 7 @@ -36,7 +37,7 @@ struct pwm_mediatek_of_data { unsigned int num_pwms; bool pwm45_fixup; - bool has_ck_26m_sel; + u16 pwm_ck_26m_sel_reg; const unsigned int *reg_offset; }; @@ -64,6 +65,11 @@ static const unsigned int mtk_pwm_reg_offset_v2[] = { 0x0080, 0x00c0, 0x0100, 0x0140, 0x0180, 0x01c0, 0x0200, 0x0240 }; +/* PWM IP Version 3.0.2 */ +static const unsigned int mtk_pwm_reg_offset_v3[] = { + 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700, 0x0800 +}; + static inline struct pwm_mediatek_chip * to_pwm_mediatek_chip(struct pwm_chip *chip) { @@ -130,12 +136,14 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, return ret; clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]); - if (!clk_rate) - return -EINVAL; + if (!clk_rate) { + ret = -EINVAL; + goto out; + } /* Make sure we use the bus clock and not the 26MHz clock */ - if (pc->soc->has_ck_26m_sel) - writel(0, pc->regs + PWM_CK_26M_SEL); + if (pc->soc->pwm_ck_26m_sel_reg) + writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg); /* Using resolution in picosecond gets accuracy higher */ resolution = (u64)NSEC_PER_SEC * 1000; @@ -150,9 +158,9 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, } if (clkdiv > PWM_CLK_DIV_MAX) { - pwm_mediatek_clk_disable(chip, pwm); dev_err(pwmchip_parent(chip), "period of %d ns not supported\n", period_ns); - return -EINVAL; + ret = -EINVAL; + goto out; } if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) { @@ -169,9 +177,10 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period); pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty); +out: pwm_mediatek_clk_disable(chip, pwm); - return 0; + return ret; } static int pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm) @@ -291,90 +300,92 @@ static int pwm_mediatek_probe(struct platform_device *pdev) static const struct pwm_mediatek_of_data mt2712_pwm_data = { .num_pwms = 8, .pwm45_fixup = false, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt6795_pwm_data = { .num_pwms = 7, .pwm45_fixup = false, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7622_pwm_data = { .num_pwms = 6, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7623_pwm_data = { .num_pwms = 5, .pwm45_fixup = true, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7628_pwm_data = { .num_pwms = 4, .pwm45_fixup = true, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7629_pwm_data = { .num_pwms = 1, .pwm45_fixup = false, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7981_pwm_data = { .num_pwms = 3, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v2, }; static const struct pwm_mediatek_of_data mt7986_pwm_data = { .num_pwms = 2, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt7988_pwm_data = { .num_pwms = 8, .pwm45_fixup = false, - .has_ck_26m_sel = false, .reg_offset = mtk_pwm_reg_offset_v2, }; static const struct pwm_mediatek_of_data mt8183_pwm_data = { .num_pwms = 4, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt8365_pwm_data = { .num_pwms = 3, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v1, }; static const struct pwm_mediatek_of_data mt8516_pwm_data = { .num_pwms = 5, .pwm45_fixup = false, - .has_ck_26m_sel = true, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .reg_offset = mtk_pwm_reg_offset_v1, }; +static const struct pwm_mediatek_of_data mt6991_pwm_data = { + .num_pwms = 4, + .pwm45_fixup = false, + .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL_V3, + .reg_offset = mtk_pwm_reg_offset_v3, +}; + static const struct of_device_id pwm_mediatek_of_match[] = { { .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data }, { .compatible = "mediatek,mt6795-pwm", .data = &mt6795_pwm_data }, + { .compatible = "mediatek,mt6991-pwm", .data = &mt6991_pwm_data }, { .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data }, { .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data }, { .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data }, diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c index 12821b4bbf97..4ff32bb4c205 100644 --- a/drivers/pwm/pwm-microchip-core.c +++ b/drivers/pwm/pwm-microchip-core.c @@ -36,7 +36,6 @@ #include <linux/ktime.h> #include <linux/math.h> #include <linux/module.h> -#include <linux/mutex.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pwm.h> @@ -56,7 +55,6 @@ struct mchp_core_pwm_chip { struct clk *clk; void __iomem *base; - struct mutex lock; /* protects the shared period */ ktime_t update_timestamp; u32 sync_update_mask; u16 channel_enabled; @@ -360,17 +358,10 @@ static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip); - int ret; - - mutex_lock(&mchp_core_pwm->lock); mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); - ret = mchp_core_pwm_apply_locked(chip, pwm, state); - - mutex_unlock(&mchp_core_pwm->lock); - - return ret; + return mchp_core_pwm_apply_locked(chip, pwm, state); } static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, @@ -381,8 +372,6 @@ static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm u16 prescale, period_steps; u8 duty_steps, posedge, negedge; - mutex_lock(&mchp_core_pwm->lock); - mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm); if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm)) @@ -415,8 +404,6 @@ static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm)); negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm)); - mutex_unlock(&mchp_core_pwm->lock); - if (negedge == posedge) { state->duty_cycle = state->period; state->period *= 2; @@ -469,8 +456,6 @@ static int mchp_core_pwm_probe(struct platform_device *pdev) &mchp_core_pwm->sync_update_mask)) mchp_core_pwm->sync_update_mask = 0; - mutex_init(&mchp_core_pwm->lock); - chip->ops = &mchp_core_pwm_ops; mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0)); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 8a4a3d2df30d..0f5bdb0e395e 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -25,6 +25,7 @@ #include <linux/io.h> #include <linux/pwm.h> #include <linux/of.h> +#include <linux/reset.h> #include <asm/div64.h> @@ -161,6 +162,7 @@ static int pwm_probe(struct platform_device *pdev) struct pwm_chip *chip; struct pxa_pwm_chip *pc; struct device *dev = &pdev->dev; + struct reset_control *rst; int ret = 0; if (IS_ENABLED(CONFIG_OF) && id == NULL) @@ -179,6 +181,10 @@ static int pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->clk)) return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); + chip->ops = &pxa_pwm_ops; if (IS_ENABLED(CONFIG_OF)) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index c5f50e5eaf41..67b85bdb491b 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -8,6 +8,8 @@ #include <linux/clk.h> #include <linux/io.h> +#include <linux/limits.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -61,6 +63,7 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip, struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; u32 enable_conf = pc->data->enable_conf; unsigned long clk_rate; u64 tmp; @@ -78,12 +81,12 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip, clk_rate = clk_get_rate(pc->clk); tmp = readl_relaxed(pc->base + pc->data->regs.period); - tmp *= pc->data->prescaler * NSEC_PER_SEC; - state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + tmp *= prescaled_ns; + state->period = DIV_U64_ROUND_UP(tmp, clk_rate); tmp = readl_relaxed(pc->base + pc->data->regs.duty); - tmp *= pc->data->prescaler * NSEC_PER_SEC; - state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + tmp *= prescaled_ns; + state->duty_cycle = DIV_U64_ROUND_UP(tmp, clk_rate); val = readl_relaxed(pc->base + pc->data->regs.ctrl); state->enabled = (val & enable_conf) == enable_conf; @@ -103,8 +106,9 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); - unsigned long period, duty; - u64 clk_rate, div; + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; + u64 clk_rate, tmp; + u32 period_ticks, duty_ticks; u32 ctrl; clk_rate = clk_get_rate(pc->clk); @@ -114,12 +118,15 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * bits, every possible input period can be obtained using the * default prescaler value for all practical clock rate values. */ - div = clk_rate * state->period; - period = DIV_ROUND_CLOSEST_ULL(div, - pc->data->prescaler * NSEC_PER_SEC); + tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns); + if (tmp > U32_MAX) + tmp = U32_MAX; + period_ticks = tmp; - div = clk_rate * state->duty_cycle; - duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); + tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns); + if (tmp > U32_MAX) + tmp = U32_MAX; + duty_ticks = tmp; /* * Lock the period and duty of previous configuration, then @@ -131,8 +138,8 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl); } - writel(period, pc->base + pc->data->regs.period); - writel(duty, pc->base + pc->data->regs.duty); + writel(period_ticks, pc->base + pc->data->regs.period); + writel(duty_ticks, pc->base + pc->data->regs.duty); if (pc->data->supports_polarity) { ctrl &= ~PWM_POLARITY_MASK; diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index d5b647e6be78..4a07315b0744 100644 --- a/drivers/pwm/pwm-sifive.c +++ b/drivers/pwm/pwm-sifive.c @@ -4,11 +4,28 @@ * For SiFive's PWM IP block documentation please refer Chapter 14 of * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf * + * PWM output inversion: According to the SiFive Reference manual + * the output of each comparator is high whenever the value of pwms is + * greater than or equal to the corresponding pwmcmpX[Reference Manual]. + * + * Figure 29 in the same manual shows that the pwmcmpXcenter bit is + * hard-tied to 0 (XNOR), which effectively inverts the comparison so that + * the output goes HIGH when `pwms < pwmcmpX`. + * + * In other words, each pwmcmp register actually defines the **inactive** + * (low) period of the pulse, not the active time exactly opposite to what + * the documentation text implies. + * + * To compensate, this driver always **inverts** the duty value when reading + * or writing pwmcmp registers , so that users interact with a conventional + * **active-high** PWM interface. + * + * * Limitations: * - When changing both duty cycle and period, we cannot prevent in * software that the output might produce a period with mixed * settings (new period length and old duty cycle). - * - The hardware cannot generate a 100% duty cycle. + * - The hardware cannot generate a 0% duty cycle. * - The hardware generates only inverted output. */ #include <linux/clk.h> @@ -101,7 +118,7 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata, /* As scale <= 15 the shift operation cannot overflow. */ num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale); - ddata->real_period = div64_ul(num, rate); + ddata->real_period = DIV_ROUND_UP_ULL(num, rate); dev_dbg(ddata->parent, "New real_period = %u ns\n", ddata->real_period); } @@ -110,9 +127,14 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); - u32 duty, val; + u32 duty, val, inactive; - duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); + inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); + /* + * PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty. + * Here, 'inactive' is the low time and we compute duty as max_count - inactive. + */ + duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive; state->enabled = duty > 0; @@ -121,9 +143,9 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, state->enabled = false; state->period = ddata->real_period; - state->duty_cycle = - (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; - state->polarity = PWM_POLARITY_INVERSED; + state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period, + (1U << PWM_SIFIVE_CMPWIDTH)); + state->polarity = PWM_POLARITY_NORMAL; return 0; } @@ -137,9 +159,10 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long num; bool enabled; int ret = 0; - u32 frac; + u64 frac; + u32 inactive; - if (state->polarity != PWM_POLARITY_INVERSED) + if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; cur_state = pwm->state; @@ -156,9 +179,12 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, * consecutively */ num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); - frac = DIV64_U64_ROUND_CLOSEST(num, state->period); - /* The hardware cannot generate a 100% duty cycle */ - frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + frac = num; + do_div(frac, state->period); + /* The hardware cannot generate a 0% duty cycle */ + frac = min(frac, (u64)(1U << PWM_SIFIVE_CMPWIDTH) - 1); + /* pwmcmp register must be loaded with the inactive(invert the duty) */ + inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac; mutex_lock(&ddata->lock); if (state->period != ddata->approx_period) { @@ -190,7 +216,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, } } - writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); + writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); if (!state->enabled) clk_disable(ddata->clk); diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c index ff4639d849ce..7d07b0ca7d29 100644 --- a/drivers/pwm/pwm-sophgo-sg2042.c +++ b/drivers/pwm/pwm-sophgo-sg2042.c @@ -13,6 +13,7 @@ * the running period. * - When PERIOD and HLPERIOD is set to 0, the PWM wave output will * be stopped and the output is pulled to high. + * - SG2044 supports both polarities, SG2042 only normal polarity. * See the datasheet [1] for more details. * [1]:https://github.com/sophgo/sophgo-doc/tree/main/SG2042/TRM */ @@ -41,6 +42,10 @@ #define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0) #define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4) +#define SG2044_PWM_POLARITY 0x40 +#define SG2044_PWM_PWMSTART 0x44 +#define SG2044_PWM_OE 0xd0 + #define SG2042_PWM_CHANNELNUM 4 /** @@ -53,6 +58,10 @@ struct sg2042_pwm_ddata { unsigned long clk_rate_hz; }; +struct sg2042_chip_data { + const struct pwm_ops ops; +}; + /* * period_ticks: PERIOD * hlperiod_ticks: HLPERIOD @@ -66,21 +75,13 @@ static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan, writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan)); } -static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) +static void pwm_sg2042_set_dutycycle(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) { struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); u32 hlperiod_ticks; u32 period_ticks; - if (state->polarity == PWM_POLARITY_INVERSED) - return -EINVAL; - - if (!state->enabled) { - pwm_sg2042_config(ddata, pwm->hwpwm, 0, 0); - return 0; - } - /* * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk * Duration of One Cycle (period) = PERIOD x Period_of_input_clk @@ -88,10 +89,26 @@ static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm, period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period, NSEC_PER_SEC), U32_MAX); hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle, NSEC_PER_SEC), U32_MAX); - dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n", - pwm->hwpwm, period_ticks, hlperiod_ticks); + dev_dbg(pwmchip_parent(chip), "chan[%u]: ENABLE=%u, PERIOD=%u, HLPERIOD=%u, POLARITY=%u\n", + pwm->hwpwm, state->enabled, period_ticks, hlperiod_ticks, state->polarity); pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks); +} + +static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); + + if (state->polarity == PWM_POLARITY_INVERSED) + return -EINVAL; + + if (!state->enabled) { + pwm_sg2042_config(ddata, pwm->hwpwm, 0, 0); + return 0; + } + + pwm_sg2042_set_dutycycle(chip, pwm, state); return 0; } @@ -123,13 +140,97 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static const struct pwm_ops pwm_sg2042_ops = { - .apply = pwm_sg2042_apply, - .get_state = pwm_sg2042_get_state, +static void pwm_sg2044_set_outputen(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, + bool enabled) +{ + u32 pwmstart; + + pwmstart = readl(ddata->base + SG2044_PWM_PWMSTART); + + if (enabled) + pwmstart |= BIT(pwm->hwpwm); + else + pwmstart &= ~BIT(pwm->hwpwm); + + writel(pwmstart, ddata->base + SG2044_PWM_PWMSTART); +} + +static void pwm_sg2044_set_outputdir(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, + bool enabled) +{ + u32 pwm_oe; + + pwm_oe = readl(ddata->base + SG2044_PWM_OE); + + if (enabled) + pwm_oe |= BIT(pwm->hwpwm); + else + pwm_oe &= ~BIT(pwm->hwpwm); + + writel(pwm_oe, ddata->base + SG2044_PWM_OE); +} + +static void pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, + const struct pwm_state *state) +{ + u32 pwm_polarity; + + pwm_polarity = readl(ddata->base + SG2044_PWM_POLARITY); + + if (state->polarity == PWM_POLARITY_NORMAL) + pwm_polarity &= ~BIT(pwm->hwpwm); + else + pwm_polarity |= BIT(pwm->hwpwm); + + writel(pwm_polarity, ddata->base + SG2044_PWM_POLARITY); +} + +static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); + + pwm_sg2044_set_polarity(ddata, pwm, state); + + pwm_sg2042_set_dutycycle(chip, pwm, state); + + /* + * re-enable PWMSTART to refresh the register period + */ + pwm_sg2044_set_outputen(ddata, pwm, false); + + if (!state->enabled) + return 0; + + pwm_sg2044_set_outputdir(ddata, pwm, true); + pwm_sg2044_set_outputen(ddata, pwm, true); + + return 0; +} + +static const struct sg2042_chip_data sg2042_chip_data = { + .ops = { + .apply = pwm_sg2042_apply, + .get_state = pwm_sg2042_get_state, + }, +}; + +static const struct sg2042_chip_data sg2044_chip_data = { + .ops = { + .apply = pwm_sg2044_apply, + .get_state = pwm_sg2042_get_state, + }, }; static const struct of_device_id sg2042_pwm_ids[] = { - { .compatible = "sophgo,sg2042-pwm" }, + { + .compatible = "sophgo,sg2042-pwm", + .data = &sg2042_chip_data + }, + { + .compatible = "sophgo,sg2044-pwm", + .data = &sg2044_chip_data + }, { } }; MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); @@ -137,12 +238,17 @@ MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); static int pwm_sg2042_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + const struct sg2042_chip_data *chip_data; struct sg2042_pwm_ddata *ddata; struct reset_control *rst; struct pwm_chip *chip; struct clk *clk; int ret; + chip_data = device_get_match_data(dev); + if (!chip_data) + return -ENODEV; + chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata)); if (IS_ERR(chip)) return PTR_ERR(chip); @@ -170,7 +276,7 @@ static int pwm_sg2042_probe(struct platform_device *pdev) if (IS_ERR(rst)) return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n"); - chip->ops = &pwm_sg2042_ops; + chip->ops = &chip_data->ops; chip->atomic = true; ret = devm_pwmchip_add(dev, chip); @@ -190,5 +296,6 @@ static struct platform_driver pwm_sg2042_driver = { module_platform_driver(pwm_sg2042_driver); MODULE_AUTHOR("Chen Wang"); +MODULE_AUTHOR("Longbin Li <looong.bin@gmail.com>"); MODULE_DESCRIPTION("Sophgo SG2042 PWM driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c index 396b52672ce0..3b702b8f0c7f 100644 --- a/drivers/pwm/pwm-sti.c +++ b/drivers/pwm/pwm-sti.c @@ -92,7 +92,6 @@ struct sti_pwm_chip { struct pwm_device *cur; unsigned long configured; unsigned int en_count; - struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ void __iomem *mmio; }; @@ -244,55 +243,46 @@ static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct sti_pwm_chip *pc = to_sti_pwmchip(chip); struct device *dev = pc->dev; - int ret = 0; + int ret; /* * Since we have a common enable for all PWM devices, do not enable if * already enabled. */ - mutex_lock(&pc->sti_pwm_lock); if (!pc->en_count) { ret = clk_enable(pc->pwm_clk); if (ret) - goto out; + return ret; ret = clk_enable(pc->cpt_clk); if (ret) - goto out; + return ret; ret = regmap_field_write(pc->pwm_out_en, 1); if (ret) { dev_err(dev, "failed to enable PWM device %u: %d\n", pwm->hwpwm, ret); - goto out; + return ret; } } pc->en_count++; -out: - mutex_unlock(&pc->sti_pwm_lock); - return ret; + return 0; } static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct sti_pwm_chip *pc = to_sti_pwmchip(chip); - mutex_lock(&pc->sti_pwm_lock); - - if (--pc->en_count) { - mutex_unlock(&pc->sti_pwm_lock); + if (--pc->en_count) return; - } regmap_field_write(pc->pwm_out_en, 0); clk_disable(pc->pwm_clk); clk_disable(pc->cpt_clk); - - mutex_unlock(&pc->sti_pwm_lock); } static void sti_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) @@ -594,7 +584,6 @@ static int sti_pwm_probe(struct platform_device *pdev) pc->dev = dev; pc->en_count = 0; - mutex_init(&pc->sti_pwm_lock); ret = sti_pwm_probe_regmap(pc); if (ret) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index 4b148f0afeb9..2594fb771b04 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -19,6 +19,7 @@ #define CCMR_CHANNEL_SHIFT 8 #define CCMR_CHANNEL_MASK 0xFF #define MAX_BREAKINPUT 2 +#define STM32_MAX_PWM_OUTPUT 4 struct stm32_breakinput { u32 index; @@ -768,10 +769,19 @@ static int stm32_pwm_probe_breakinputs(struct stm32_pwm *priv, return stm32_pwm_apply_breakinputs(priv); } -static void stm32_pwm_detect_complementary(struct stm32_pwm *priv) +static void stm32_pwm_detect_complementary(struct stm32_pwm *priv, struct stm32_timers *ddata) { u32 ccer; + if (ddata->ipidr) { + u32 val; + + /* Simply read from HWCFGR the number of complementary outputs (MP25). */ + regmap_read(priv->regmap, TIM_HWCFGR1, &val); + priv->have_complementary_output = !!FIELD_GET(TIM_HWCFGR1_NB_OF_DT, val); + return; + } + /* * If complementary bit doesn't exist writing 1 will have no * effect so we can detect it. @@ -783,22 +793,39 @@ static void stm32_pwm_detect_complementary(struct stm32_pwm *priv) priv->have_complementary_output = (ccer != 0); } -static unsigned int stm32_pwm_detect_channels(struct regmap *regmap, +static unsigned int stm32_pwm_detect_channels(struct stm32_timers *ddata, unsigned int *num_enabled) { + struct regmap *regmap = ddata->regmap; u32 ccer, ccer_backup; + regmap_read(regmap, TIM_CCER, &ccer_backup); + *num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE); + + if (ddata->ipidr) { + u32 hwcfgr; + unsigned int npwm; + + /* Deduce from HWCFGR the number of outputs (MP25). */ + regmap_read(regmap, TIM_HWCFGR1, &hwcfgr); + + /* + * Timers may have more capture/compare channels than the + * actual number of PWM channel outputs (e.g. TIM_CH[1..4]). + */ + npwm = FIELD_GET(TIM_HWCFGR1_NB_OF_CC, hwcfgr); + + return npwm < STM32_MAX_PWM_OUTPUT ? npwm : STM32_MAX_PWM_OUTPUT; + } + /* * If channels enable bits don't exist writing 1 will have no * effect so we can detect and count them. */ - regmap_read(regmap, TIM_CCER, &ccer_backup); regmap_set_bits(regmap, TIM_CCER, TIM_CCER_CCXE); regmap_read(regmap, TIM_CCER, &ccer); regmap_write(regmap, TIM_CCER, ccer_backup); - *num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE); - return hweight32(ccer & TIM_CCER_CCXE); } @@ -813,7 +840,7 @@ static int stm32_pwm_probe(struct platform_device *pdev) unsigned int i; int ret; - npwm = stm32_pwm_detect_channels(ddata->regmap, &num_enabled); + npwm = stm32_pwm_detect_channels(ddata, &num_enabled); chip = devm_pwmchip_alloc(dev, npwm, sizeof(*priv)); if (IS_ERR(chip)) @@ -834,7 +861,7 @@ static int stm32_pwm_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to configure breakinputs\n"); - stm32_pwm_detect_complementary(priv); + stm32_pwm_detect_complementary(priv, ddata); ret = devm_clk_rate_exclusive_get(dev, priv->clk); if (ret) @@ -907,6 +934,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stm32_pwm_pm_ops, stm32_pwm_suspend, stm32_pwm_r static const struct of_device_id stm32_pwm_of_match[] = { { .compatible = "st,stm32-pwm", }, + { .compatible = "st,stm32mp25-pwm", }, { /* end node */ }, }; MODULE_DEVICE_TABLE(of, stm32_pwm_of_match); diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index e60dc7d6b591..6c5591ca868b 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -21,7 +21,6 @@ #include <linux/pwm.h> #include <linux/reset.h> #include <linux/slab.h> -#include <linux/spinlock.h> #include <linux/time.h> #define PWM_CTRL_REG 0x0 @@ -85,7 +84,6 @@ struct sun4i_pwm_chip { struct clk *clk; struct reset_control *rst; void __iomem *base; - spinlock_t ctrl_lock; const struct sun4i_pwm_data *data; }; @@ -258,7 +256,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return ret; } - spin_lock(&sun4ichip->ctrl_lock); ctrl = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG); if (sun4ichip->data->has_direct_mod_clk_output) { @@ -266,7 +263,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm); /* We can skip other parameter */ sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG); - spin_unlock(&sun4ichip->ctrl_lock); return 0; } @@ -297,8 +293,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG); - spin_unlock(&sun4ichip->ctrl_lock); - if (state->enabled) return 0; @@ -309,12 +303,10 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, else usleep_range(delay_us, delay_us * 2); - spin_lock(&sun4ichip->ctrl_lock); ctrl = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG); ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm); sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG); - spin_unlock(&sun4ichip->ctrl_lock); clk_disable_unprepare(sun4ichip->clk); @@ -456,8 +448,6 @@ static int sun4i_pwm_probe(struct platform_device *pdev) chip->ops = &sun4i_pwm_ops; - spin_lock_init(&sun4ichip->ctrl_lock); - ret = pwmchip_add(chip); if (ret < 0) { dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c index 4b10a8dab312..a555cc3be4b3 100644 --- a/drivers/pwm/pwm-twl-led.c +++ b/drivers/pwm/pwm-twl-led.c @@ -61,10 +61,6 @@ #define TWL6040_LED_MODE_OFF 0x02 #define TWL6040_LED_MODE_MASK 0x03 -struct twl_pwmled_chip { - struct mutex mutex; -}; - static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip) { return pwmchip_get_drvdata(chip); @@ -106,15 +102,13 @@ static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label); - goto out; + return ret; } val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); @@ -123,23 +117,19 @@ static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label); -out: - mutex_unlock(&twl->mutex); return ret; } static void twl4030_pwmled_disable(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label); - goto out; + return; } val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); @@ -147,9 +137,6 @@ static void twl4030_pwmled_disable(struct pwm_chip *chip, ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label); - -out: - mutex_unlock(&twl->mutex); } static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm, @@ -209,16 +196,14 @@ static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n", pwm->label); - goto out; + return ret; } val &= ~TWL6040_LED_MODE_MASK; @@ -228,24 +213,20 @@ static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label); -out: - mutex_unlock(&twl->mutex); return ret; } static void twl6030_pwmled_disable(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n", pwm->label); - goto out; + return; } val &= ~TWL6040_LED_MODE_MASK; @@ -254,9 +235,6 @@ static void twl6030_pwmled_disable(struct pwm_chip *chip, ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label); - -out: - mutex_unlock(&twl->mutex); } static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm, @@ -287,16 +265,14 @@ static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm, static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n", pwm->label); - goto out; + return ret; } val &= ~TWL6040_LED_MODE_MASK; @@ -306,23 +282,19 @@ static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm) if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to request PWM\n", pwm->label); -out: - mutex_unlock(&twl->mutex); return ret; } static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm) { - struct twl_pwmled_chip *twl = to_twl(chip); int ret; u8 val; - mutex_lock(&twl->mutex); ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); if (ret < 0) { dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n", pwm->label); - goto out; + return; } val &= ~TWL6040_LED_MODE_MASK; @@ -331,9 +303,6 @@ static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm) ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); if (ret < 0) dev_err(pwmchip_parent(chip), "%s: Failed to free PWM\n", pwm->label); - -out: - mutex_unlock(&twl->mutex); } static const struct pwm_ops twl6030_pwmled_ops = { @@ -345,7 +314,6 @@ static const struct pwm_ops twl6030_pwmled_ops = { static int twl_pwmled_probe(struct platform_device *pdev) { struct pwm_chip *chip; - struct twl_pwmled_chip *twl; unsigned int npwm; const struct pwm_ops *ops; @@ -357,15 +325,12 @@ static int twl_pwmled_probe(struct platform_device *pdev) npwm = 1; } - chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*twl)); + chip = devm_pwmchip_alloc(&pdev->dev, npwm, 0); if (IS_ERR(chip)) return PTR_ERR(chip); - twl = to_twl(chip); chip->ops = ops; - mutex_init(&twl->mutex); - return devm_pwmchip_add(&pdev->dev, chip); } |