From 9c06f26ba5f5da14bcac405c7a652dcf578a785d Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Wed, 30 Apr 2025 13:56:01 +0200 Subject: pwm: Add support for pwmchip devices for faster and easier userspace access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this change each pwmchip defining the new-style waveform callbacks can be accessed from userspace via a character device. Compared to the sysfs-API this is faster and allows to pass the whole configuration in a single ioctl allowing atomic application and thus reducing glitches. On an STM32MP13 I see: root@DistroKit:~ time pwmtestperf real 0m 1.27s user 0m 0.02s sys 0m 1.21s root@DistroKit:~ rm /dev/pwmchip0 root@DistroKit:~ time pwmtestperf real 0m 3.61s user 0m 0.27s sys 0m 3.26s pwmtestperf does essentially: for i in 0 .. 50000: pwm_set_waveform(duty_length_ns=i, period_length_ns=50000, duty_offset_ns=0) and in the presence of /dev/pwmchip0 is uses the ioctls introduced here, without that device it uses /sys/class/pwm/pwmchip0. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/ad4a4e49ae3f8ea81e23cac1ac12b338c3bf5c5b.1746010245.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 322 ++++++++++++++++++++++++++++++++++++++++++++--- include/linux/pwm.h | 3 + include/uapi/linux/pwm.h | 53 ++++++++ 3 files changed, 363 insertions(+), 15 deletions(-) create mode 100644 include/uapi/linux/pwm.h diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index edf776b8ad53..50aa0528a265 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -23,9 +23,13 @@ #include +#include + #define CREATE_TRACE_POINTS #include +#define PWM_MINOR_COUNT 256 + /* protects access to pwm_chips */ static DEFINE_MUTEX(pwm_lock); @@ -2007,20 +2011,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 +2034,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 +2117,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 +2437,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 +2498,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 +2642,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/include/linux/pwm.h b/include/linux/pwm.h index 63a17d2b4ec8..2492c91452f9 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -2,6 +2,7 @@ #ifndef __LINUX_PWM_H #define __LINUX_PWM_H +#include #include #include #include @@ -311,6 +312,7 @@ struct pwm_ops { /** * struct pwm_chip - abstract a PWM controller * @dev: device providing the PWMs + * @cdev: &struct cdev for this device * @ops: callbacks for this PWM controller * @owner: module providing this chip * @id: unique number of this PWM chip @@ -325,6 +327,7 @@ struct pwm_ops { */ struct pwm_chip { struct device dev; + struct cdev cdev; const struct pwm_ops *ops; struct module *owner; unsigned int id; diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h new file mode 100644 index 000000000000..182d59cc07ee --- /dev/null +++ b/include/uapi/linux/pwm.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ + +#ifndef _UAPI_PWM_H_ +#define _UAPI_PWM_H_ + +#include +#include + +/** + * struct pwmchip_waveform - Describe a PWM waveform for a pwm_chip's PWM channel + * @hwpwm: per-chip relative index of the PWM device + * @__pad: padding, must be zero + * @period_length_ns: duration of the repeating period. + * A value of 0 represents a disabled PWM. + * @duty_length_ns: duration of the active part in each period + * @duty_offset_ns: offset of the rising edge from a period's start + */ +struct pwmchip_waveform { + __u32 hwpwm; + __u32 __pad; + __u64 period_length_ns; + __u64 duty_length_ns; + __u64 duty_offset_ns; +}; + +/* Reserves the passed hwpwm for exclusive control. */ +#define PWM_IOCTL_REQUEST _IO(0x75, 1) + +/* counter part to PWM_IOCTL_REQUEST */ +#define PWM_IOCTL_FREE _IO(0x75, 2) + +/* + * Modifies the passed wf according to hardware constraints. All parameters are + * rounded down to the next possible value, unless there is no such value, then + * values are rounded up. Note that zero isn't considered for rounding down + * period_length_ns. + */ +#define PWM_IOCTL_ROUNDWF _IOWR(0x75, 3, struct pwmchip_waveform) + +/* Get the currently implemented waveform */ +#define PWM_IOCTL_GETWF _IOWR(0x75, 4, struct pwmchip_waveform) + +/* Like PWM_IOCTL_ROUNDWF + PWM_IOCTL_SETEXACTWF in one go. */ +#define PWM_IOCTL_SETROUNDEDWF _IOW(0x75, 5, struct pwmchip_waveform) + +/* + * Program the PWM to emit exactly the passed waveform, subject only to rounding + * down each value less than 1 ns. Returns 0 on success, -EDOM if the waveform + * cannot be implemented exactly, or other negative error codes. + */ +#define PWM_IOCTL_SETEXACTWF _IOW(0x75, 6, struct pwmchip_waveform) + +#endif /* _UAPI_PWM_H_ */ -- cgit v1.2.3 From 08e0b981231fd467204764f162087d6d9d1359af Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Tue, 29 Apr 2025 16:50:43 +0800 Subject: dt-bindings: pwm: marvell,pxa-pwm: Add SpacemiT K1 PWM support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SpacemiT K1 SoC reuses the Marvell PXA910-compatible PWM controller with one notable difference: the addition of a resets property. To make the device tree pass schema validation (make dtbs_check W=3), this patch updates the binding to accept spacemit,k1-pwm as a compatible string, when used in conjunction with the fallback marvell,pxa910-pwm. Support for the optional resets property is also added, as it is required by the K1 integration but was not present in the original Marvell bindings. Since the PWM reset line may be deasserted during the early bootloader stage, making the resets property optional avoids potential double-deassertion, which could otherwise cause flickering on displays that use PWM for backlight control. Additionally, this patch adjusts the required value of the #pwm-cells property for the new compatible string: - For "spacemit,k1-pwm", #pwm-cells must be set to 3. - For existing Marvell compatibles, #pwm-cells remains 1. Background of #pwm-cells change is by an ongoing community discussion about increasing the #pwm-cells value from 1 to 3 for all Marvell PXA PWM devices. These devices are currently the only ones whose bindings do not pass the line index as the first argument. See [1] for further details. [1] https://lore.kernel.org/all/cover.1738842938.git.u.kleine-koenig@baylibre.com/ Reviewed-by: Rob Herring (Arm) # v2 Signed-off-by: Guodong Xu Link: https://lore.kernel.org/r/20250429085048.1310409-2-guodong@riscstar.com Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/marvell,pxa-pwm.yaml | 35 +++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml index 9ee1946dc2e1..8df327e52810 100644 --- a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml @@ -11,26 +11,47 @@ maintainers: allOf: - $ref: pwm.yaml# + - if: + properties: + compatible: + contains: + const: spacemit,k1-pwm + then: + properties: + "#pwm-cells": + const: 3 + else: + properties: + "#pwm-cells": + const: 1 + description: | + Used for specifying the period length in nanoseconds. properties: compatible: - enum: - - marvell,pxa250-pwm - - marvell,pxa270-pwm - - marvell,pxa168-pwm - - marvell,pxa910-pwm + oneOf: + - enum: + - marvell,pxa250-pwm + - marvell,pxa270-pwm + - marvell,pxa168-pwm + - marvell,pxa910-pwm + - items: + - const: spacemit,k1-pwm + - const: marvell,pxa910-pwm reg: # Length should be 0x10 maxItems: 1 "#pwm-cells": - # Used for specifying the period length in nanoseconds - const: 1 + description: Number of cells in a pwm specifier. clocks: maxItems: 1 + resets: + maxItems: 1 + required: - compatible - reg -- cgit v1.2.3 From 52d2d14d9e49b8c33ff718d2036084d0c92f23f1 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Tue, 29 Apr 2025 16:50:44 +0800 Subject: pwm: pxa: Add optional reset control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support optional reset control for the PWM PXA driver. During probe, it acquires the reset controller using devm_reset_control_get_optional_exclusive_deasserted() to get and deassert the reset controller to enable the PWM channel. Signed-off-by: Guodong Xu Link: https://lore.kernel.org/r/20250429085048.1310409-3-guodong@riscstar.com [ukleinek: Fix conflict with commit df08fff8add2 ("pwm: pxa: Improve using dev_err_probe()")] Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-pxa.c | 6 ++++++ 1 file changed, 6 insertions(+) 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 #include #include +#include #include @@ -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)) -- cgit v1.2.3 From 27b5dfe4b4eaf490df6fa9d5d0bf95cd51abf563 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Tue, 29 Apr 2025 16:50:47 +0800 Subject: pwm: pxa: Allow to enable for SpacemiT K1 SoC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SpacemiT K1 SoC uses devices similar to the ones on PXA SoCs. Add ARCH_SPACEMIT as one of the possible architectures this driver can be enabled for. Signed-off-by: Guodong Xu Link: https://lore.kernel.org/r/20250429085048.1310409-6-guodong@riscstar.com [ukleinek: reword commit log] Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d9bcd1e8413e..6e113f8b4baf 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -517,7 +517,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. -- cgit v1.2.3 From f4bcf818e5d6474c981ef16153827458a2b57181 Mon Sep 17 00:00:00 2001 From: Nylon Chen Date: Thu, 29 May 2025 11:53:39 +0800 Subject: riscv: dts: sifive: unleashed/unmatched: Remove PWM controlled LED's active-low properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes the active-low properties of the PWM-controlled LEDs in the HiFive Unmatched device tree. The reference is hifive-unleashed-a00.pdf[0] and hifive-unmatched-schematics-v3.pdf[1]. Link: https://sifive.cdn.prismic.io/sifive/c52a8e32-05ce-4aaf-95c8-7bf8453f8698_hifive-unleashed-a00-schematics-1.pdf [0] Link: https://sifive.cdn.prismic.io/sifive/6a06d6c0-6e66-49b5-8e9e-e68ce76f4192_hifive-unmatched-schematics-v3.pdf [1] Acked-by: Conor Dooley Reviewed-by: Conor Dooley Signed-off-by: Vincent Chen Signed-off-by: Nylon Chen Link: https://lore.kernel.org/r/20250529035341.51736-2-nylon.chen@sifive.com Signed-off-by: Uwe Kleine-König --- arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts | 12 ++++-------- arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts b/arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts index 900a50526d77..06731b8c7bc3 100644 --- a/arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts +++ b/arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dts @@ -49,32 +49,28 @@ compatible = "pwm-leds"; led-d1 { - pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 0 7812500 0>; color = ; max-brightness = <255>; label = "d1"; }; led-d2 { - pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 1 7812500 0>; color = ; max-brightness = <255>; label = "d2"; }; led-d3 { - pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 2 7812500 0>; color = ; max-brightness = <255>; label = "d3"; }; led-d4 { - pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 3 7812500 0>; color = ; max-brightness = <255>; label = "d4"; diff --git a/arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts b/arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts index 72b87b08ab44..03ce2cee4e97 100644 --- a/arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts +++ b/arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dts @@ -51,8 +51,7 @@ compatible = "pwm-leds"; led-d12 { - pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 0 7812500 0>; color = ; max-brightness = <255>; label = "d12"; @@ -68,20 +67,17 @@ label = "d2"; led-red { - pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 2 7812500 0>; color = ; }; led-green { - pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 1 7812500 0>; color = ; }; led-blue { - pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>; - active-low; + pwms = <&pwm0 3 7812500 0>; color = ; }; }; -- cgit v1.2.3 From 7dbc4432ea6bf9d709391eb57f1e9fb44e99845a Mon Sep 17 00:00:00 2001 From: Nylon Chen Date: Thu, 29 May 2025 11:53:40 +0800 Subject: pwm: sifive: Fix PWM algorithm and clarify inverted compare behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `frac` variable represents the pulse inactive time, and the result of this algorithm is the pulse active time. Therefore, we must reverse the result. Although the SiFive Reference Manual states "pwms >= pwmcmpX -> HIGH", the hardware behavior is inverted due to a fixed XNOR with 0. As a result, the pwmcmp register actually defines the low (inactive) portion of the pulse. The reference is SiFive FU740-C000 Manual[0] Link: https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf [0] Co-developed-by: Zong Li Signed-off-by: Zong Li Co-developed-by: Vincent Chen Signed-off-by: Vincent Chen Signed-off-by: Nylon Chen Link: https://lore.kernel.org/r/20250529035341.51736-3-nylon.chen@sifive.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sifive.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index d5b647e6be78..f3694801d3ee 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 @@ -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; @@ -123,7 +145,7 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, state->period = ddata->real_period; state->duty_cycle = (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; - state->polarity = PWM_POLARITY_INVERSED; + state->polarity = PWM_POLARITY_NORMAL; return 0; } @@ -137,9 +159,9 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long num; bool enabled; int ret = 0; - u32 frac; + u32 frac, inactive; - if (state->polarity != PWM_POLARITY_INVERSED) + if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; cur_state = pwm->state; @@ -157,8 +179,9 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, */ num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); frac = DIV64_U64_ROUND_CLOSEST(num, state->period); - /* The hardware cannot generate a 100% duty cycle */ + /* The hardware cannot generate a 0% duty cycle */ frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac; mutex_lock(&ddata->lock); if (state->period != ddata->approx_period) { @@ -190,7 +213,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); -- cgit v1.2.3 From 6df3aac763fa995260ab0545c1e54f0c21b2feb8 Mon Sep 17 00:00:00 2001 From: Nylon Chen Date: Thu, 29 May 2025 11:53:41 +0800 Subject: pwm: sifive: Fix rounding and idempotency issues in apply and get_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fix ensures consistent rounding and avoids mismatches between applied and reported PWM values that could trigger false idempotency failures in debug checks This change ensures: - real_period is now calculated using DIV_ROUND_UP_ULL() to avoid underestimation. - duty_cycle is rounded up to match the fractional computation in apply() - apply() truncates the result to compensate for get_state's rounding up logic These fixes resolve issues like: .apply is supposed to round down duty_cycle (requested: 360/504000, applied: 361/504124) .apply is not idempotent (ena=1 pol=0 1739692/4032985) -> (ena=1 pol=0 1739630/4032985) Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202505080303.dBfU5YMS-lkp@intel.com/ Co-developed-by: Zong Li Signed-off-by: Zong Li Signed-off-by: Nylon Chen Link: https://lore.kernel.org/r/20250529035341.51736-4-nylon.chen@sifive.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sifive.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index f3694801d3ee..4a07315b0744 100644 --- a/drivers/pwm/pwm-sifive.c +++ b/drivers/pwm/pwm-sifive.c @@ -118,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); } @@ -143,8 +143,8 @@ 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->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period, + (1U << PWM_SIFIVE_CMPWIDTH)); state->polarity = PWM_POLARITY_NORMAL; return 0; @@ -159,7 +159,8 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long num; bool enabled; int ret = 0; - u32 frac, inactive; + u64 frac; + u32 inactive; if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; @@ -178,9 +179,11 @@ 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); + frac = num; + do_div(frac, state->period); /* The hardware cannot generate a 0% duty cycle */ - frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + 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); -- cgit v1.2.3 From 2b66b67530b828ce939c050d7d3366a80ef22d7a Mon Sep 17 00:00:00 2001 From: Longbin Li Date: Wed, 28 May 2025 18:11:36 +0800 Subject: dt-bindings: pwm: sophgo: Add pwm controller for SG2044 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add compatible string for PWM controller on SG2044. Tested-by: Chen Wang Reviewed-by: Chen Wang Acked-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Signed-off-by: Longbin Li Link: https://lore.kernel.org/r/20250528101139.28702-2-looong.bin@gmail.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml b/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml index bbb6326d47d7..e0e91aa237ec 100644 --- a/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml @@ -17,7 +17,9 @@ allOf: properties: compatible: - const: sophgo,sg2042-pwm + enum: + - sophgo,sg2042-pwm + - sophgo,sg2044-pwm reg: maxItems: 1 -- cgit v1.2.3 From 8c805dfafd9b9d82a4bdb2e804d85f3be0435aeb Mon Sep 17 00:00:00 2001 From: Longbin Li Date: Wed, 28 May 2025 18:11:37 +0800 Subject: pwm: sophgo-sg2042: Reorganize the code structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the driver logic can be used in both SG2042 and SG2044, it will be better to reorganize the code structure. Reviewed-by: Chen Wang Tested-by: Chen Wang Signed-off-by: Longbin Li Link: https://lore.kernel.org/r/20250528101139.28702-3-looong.bin@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sophgo-sg2042.c | 52 +++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c index ff4639d849ce..f3956563037f 100644 --- a/drivers/pwm/pwm-sophgo-sg2042.c +++ b/drivers/pwm/pwm-sophgo-sg2042.c @@ -53,6 +53,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 +70,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 @@ -92,6 +88,22 @@ static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm, pwm->hwpwm, period_ticks, hlperiod_ticks); 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 +135,18 @@ 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 const struct sg2042_chip_data sg2042_chip_data = { + .ops = { + .apply = pwm_sg2042_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 + }, { } }; MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); @@ -137,12 +154,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 +192,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); -- cgit v1.2.3 From 21d5daad93547c36732b6568dfb8ad88004b2d68 Mon Sep 17 00:00:00 2001 From: Longbin Li Date: Wed, 28 May 2025 18:11:38 +0800 Subject: pwm: sophgo-sg2042: Add support for SG2044 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PWM controller for SG2044 on base of SG2042. Reviewed-by: Chen Wang Tested-by: Chen Wang Signed-off-by: Longbin Li Link: https://lore.kernel.org/r/20250528101139.28702-4-looong.bin@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sophgo-sg2042.c | 89 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c index f3956563037f..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 /** @@ -84,8 +89,8 @@ static void pwm_sg2042_set_dutycycle(struct pwm_chip *chip, struct pwm_device *p 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); } @@ -135,6 +140,74 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +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, @@ -142,11 +215,22 @@ static const struct sg2042_chip_data sg2042_chip_data = { }, }; +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", .data = &sg2042_chip_data }, + { + .compatible = "sophgo,sg2044-pwm", + .data = &sg2044_chip_data + }, { } }; MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); @@ -212,5 +296,6 @@ static struct platform_driver pwm_sg2042_driver = { module_platform_driver(pwm_sg2042_driver); MODULE_AUTHOR("Chen Wang"); +MODULE_AUTHOR("Longbin Li "); MODULE_DESCRIPTION("Sophgo SG2042 PWM driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 076a2f3d54a95330709a39c95bde7f19660673da Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 29 May 2025 11:53:18 -0500 Subject: dt-bindings: pwm: adi,axi-pwmgen: Update documentation link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the documentation link to point to the location with the most up-to-date information. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20250529-pwm-axi-pwmgen-add-external-clock-v3-1-5d8809a7da91@baylibre.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml b/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml index 5575c58357d6..e4c2d5186ded 100644 --- a/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml +++ b/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml @@ -14,7 +14,7 @@ description: The Analog Devices AXI PWM generator can generate PWM signals with variable pulse width and period. - https://wiki.analog.com/resources/fpga/docs/axi_pwm_gen + https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html allOf: - $ref: pwm.yaml# -- cgit v1.2.3 From fd0b06972a8f92d57358e62267f5925721c73c6e Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Fri, 10 Jan 2025 10:19:18 +0100 Subject: pwm: stm32: add support for stm32mp25 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for STM32MP25 SoC. Use newly introduced compatible to handle new features along with registers and bits diversity. The MFD part of the driver fills in ipidr, so it is used to check the hardware configuration register, when available to gather the number of PWM channels and complementary outputs. Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20250110091922.980627-5-fabrice.gasnier@foss.st.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) 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); -- cgit v1.2.3 From 0b4d1abe5ca568c5b7f667345ec2b5ad0fb2e54b Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 16 Jun 2025 17:14:17 +0200 Subject: pwm: rockchip: Round period/duty down on apply, up on get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_PWM_DEBUG=y, the rockchip PWM driver produces warnings like this: rockchip-pwm fd8b0010.pwm: .apply is supposed to round down duty_cycle (requested: 23529/50000, applied: 23542/50000) This is because the driver chooses ROUND_CLOSEST for purported idempotency reasons. However, it's possible to keep idempotency while always rounding down in .apply(). Do this by making .get_state() always round up, and making .apply() always round down. This is done with u64 maths, and setting both period and duty to U32_MAX (the biggest the hardware can support) if they would exceed their 32 bits confines. Fixes: 12f9ce4a5198 ("pwm: rockchip: Fix period and duty cycle approximation") Fixes: 1ebb74cf3537 ("pwm: rockchip: Add support for hardware readout") Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20250616-rockchip-pwm-rounding-fix-v2-1-a9c65acad7b6@collabora.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-rockchip.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) 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 #include +#include +#include #include #include #include @@ -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; -- cgit v1.2.3 From 56ad79b848d4a1e9ae270687f6f41835911eba87 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 16 Jun 2025 15:04:34 -0400 Subject: dt-bindings: pwm: convert lpc1850-sct-pwm.txt to yaml format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert lpc1850-sct-pwm.txt to yaml format. Additional changes: - add ref pwm.yaml. - add resets property to match existed dts. Reviewed-by: Rob Herring (Arm) Reviewed-by: Vladimir Zapolskiy Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250616190435.1998078-1-Frank.Li@nxp.com Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/lpc1850-sct-pwm.txt | 20 -------- .../bindings/pwm/nxp,lpc1850-sct-pwm.yaml | 54 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) delete mode 100644 Documentation/devicetree/bindings/pwm/lpc1850-sct-pwm.txt create mode 100644 Documentation/devicetree/bindings/pwm/nxp,lpc1850-sct-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/lpc1850-sct-pwm.txt b/Documentation/devicetree/bindings/pwm/lpc1850-sct-pwm.txt deleted file mode 100644 index 43d9f4f08a2e..000000000000 --- a/Documentation/devicetree/bindings/pwm/lpc1850-sct-pwm.txt +++ /dev/null @@ -1,20 +0,0 @@ -* NXP LPC18xx State Configurable Timer - Pulse Width Modulator driver - -Required properties: - - compatible: Should be "nxp,lpc1850-sct-pwm" - - reg: Should contain physical base address and length of pwm registers. - - clocks: Must contain an entry for each entry in clock-names. - See ../clock/clock-bindings.txt for details. - - clock-names: Must include the following entries. - - pwm: PWM operating clock. - - #pwm-cells: Should be 3. See pwm.yaml in this directory for the description - of the cells format. - -Example: - pwm: pwm@40000000 { - compatible = "nxp,lpc1850-sct-pwm"; - reg = <0x40000000 0x1000>; - clocks =<&ccu1 CLK_CPU_SCT>; - clock-names = "pwm"; - #pwm-cells = <3>; - }; diff --git a/Documentation/devicetree/bindings/pwm/nxp,lpc1850-sct-pwm.yaml b/Documentation/devicetree/bindings/pwm/nxp,lpc1850-sct-pwm.yaml new file mode 100644 index 000000000000..ffda0123878e --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/nxp,lpc1850-sct-pwm.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/nxp,lpc1850-sct-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP LPC18xx State Configurable Timer + +maintainers: + - Frank Li + +properties: + compatible: + const: nxp,lpc1850-sct-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: pwm + + '#pwm-cells': + const: 3 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - '#pwm-cells' + +allOf: + - $ref: pwm.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + pwm@40000000 { + compatible = "nxp,lpc1850-sct-pwm"; + reg = <0x40000000 0x1000>; + clocks =<&ccu1 CLK_CPU_SCT>; + clock-names = "pwm"; + #pwm-cells = <3>; + }; -- cgit v1.2.3 From 3bb9948921784b6c9cc2f5ed8df7bdf8ab4ffaf3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 23 Jun 2025 14:01:16 +0200 Subject: dt-bindings: pwm: mediatek,mt2712-pwm: Add support for MT6991/MT8196 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add compatible strings for the MediaTek Dimensity 9400 MT6991 and for the MT8196 Chromebook SoC, having the same PWM IP v3.0.2. Signed-off-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20250623120118.109170-2-angelogioacchino.delregno@collabora.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/mediatek,mt2712-pwm.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/mediatek,mt2712-pwm.yaml b/Documentation/devicetree/bindings/pwm/mediatek,mt2712-pwm.yaml index d515c09e1021..fc31758a40b0 100644 --- a/Documentation/devicetree/bindings/pwm/mediatek,mt2712-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/mediatek,mt2712-pwm.yaml @@ -18,6 +18,7 @@ properties: - enum: - mediatek,mt2712-pwm - mediatek,mt6795-pwm + - mediatek,mt6991-pwm - mediatek,mt7622-pwm - mediatek,mt7623-pwm - mediatek,mt7628-pwm @@ -32,6 +33,10 @@ properties: - enum: - mediatek,mt8195-pwm - const: mediatek,mt8183-pwm + - items: + - enum: + - mediatek,mt8196-pwm + - const: mediatek,mt6991-pwm reg: maxItems: 1 -- cgit v1.2.3 From d4f1e7a2fe029ec7ca2c32ec10b58a84b56d719d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 23 Jun 2025 14:01:17 +0200 Subject: pwm: pwm-mediatek: Pass PWM_CK_26M_SEL from platform data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for adding support for new SoCs, remove variable has_ck_26m_sel from pwm_mediatek_of_data and replace it with a u16 pwm_ck_26m_sel_reg, meant to hold the register offset for PWM_CK_26M_SEL. Also, since the reg offset is guaranteed to never be zero, the logic to check for "has_ck_26m_sel" is changed to check if the register offset in pwm_ck_26m_sel_reg is more than zero. Analogously, when writing, use the register offset from platform data instead of using the PWM_CK_26M_SEL definition. Signed-off-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20250623120118.109170-3-angelogioacchino.delregno@collabora.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-mediatek.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 33d3554b9197..680ab2346c71 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -36,7 +36,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; }; @@ -136,8 +136,8 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, } /* 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; @@ -294,84 +294,78 @@ 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, }; -- cgit v1.2.3 From e47026facf73a8431a4cdb90f11918c84af98597 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 23 Jun 2025 14:01:18 +0200 Subject: pwm: pwm-mediatek: Add support for PWM IP V3.0.2 in MT6991/MT8196 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the PWM IP version 3.0.2, found in MediaTek's Dimensity 9400 MT6991 and in the MT8196 Chromebook SoC: this needs a new register offset array and also a different offset for the PWM_CK_26M_SEL register. Signed-off-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20250623120118.109170-4-angelogioacchino.delregno@collabora.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-mediatek.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 680ab2346c71..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 @@ -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) { @@ -369,9 +375,17 @@ static const struct pwm_mediatek_of_data mt8516_pwm_data = { .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 }, -- cgit v1.2.3 From 6fdd4d8c84f36c4814ec7d499e9fb88b170669c5 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 30 Jun 2025 00:07:18 +0200 Subject: dt-bindings: vendor-prefixes: Document Argon40 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Argon 40 Technologies Limited is a SBC expansion board vendor. Document the prefix. For details see https://argon40.com . Acked-by: Conor Dooley Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20250629220757.936212-1-marek.vasut+renesas@mailbox.org Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 5d2a7a8d3ac6..e5e5bd34136a 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -149,6 +149,8 @@ patternProperties: description: Arctic Sand "^arcx,.*": description: arcx Inc. / Archronix Inc. + "^argon40,.*": + description: Argon 40 Technologies Limited "^ariaboard,.*": description: Shanghai Novotech Co., Ltd. (Ariaboard) "^aries,.*": -- cgit v1.2.3 From f6bd99a2d24ecccb560771dde13eff2d0808d897 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 30 Jun 2025 00:07:19 +0200 Subject: dt-bindings: pwm: argon40,fan-hat: Document Argon40 Fan HAT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document trivial PWM on Argon40 Fan HAT, which is a RaspberryPi blower fan hat which can be controlled over I2C. Reviewed-by: Rob Herring (Arm) Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20250629220757.936212-2-marek.vasut+renesas@mailbox.org Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/argon40,fan-hat.yaml | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml diff --git a/Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml b/Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml new file mode 100644 index 000000000000..7dbc7c2cd802 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/argon40,fan-hat.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Argon40 Fan HAT PWM controller + +maintainers: + - Marek Vasut + +description: + The trivial PWM on Argon40 Fan HAT, which is a RaspberryPi blower fan + hat which can be controlled over I2C, generates a fixed 30 kHz period + PWM signal with configurable 0..100% duty cycle to control the fan + speed. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + const: argon40,fan-hat + + reg: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pwm@1a { + compatible = "argon40,fan-hat"; + reg = <0x1a>; + #pwm-cells = <3>; + }; + }; -- cgit v1.2.3 From 0191c80e8a288d38f3f5a17dd0bb6a3b08c6e464 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 30 Jun 2025 00:07:20 +0200 Subject: pwm: argon-fan-hat: Add Argon40 Fan HAT support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add trivial PWM driver for Argon40 Fan HAT, which is a RaspberryPi blower fan hat which can be controlled over I2C. Model this device as a PWM, so the pwm-fan can be attached to it and handle thermal zones and RPM management in a generic manner. Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20250629220757.936212-3-marek.vasut+renesas@mailbox.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 9 ++++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-argon-fan-hat.c | 109 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 drivers/pwm/pwm-argon-fan-hat.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6e113f8b4baf..3ef1757502eb 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 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/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 +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Argon40 Fan HAT"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 62df49917eb4aa378c21cb2f6a7093749870da96 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:37 +0200 Subject: pwm: atmel: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two functions making use of the lock are only called transitively from .apply(). Calls to .apply() are already serialized by the pwm core so the lock in the driver has no effect and can safely be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/5ad3417aecd4dc6eca9699e21691e3725ea0bb87.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-atmel.c | 12 ------------ 1 file changed, 12 deletions(-) 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)) -- cgit v1.2.3 From f0d91b16dcb3d3daa013572caf76720fffcd7bab Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:38 +0200 Subject: pwm: clps711x: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core serializes calls to .apply(), so the spinlock adds no additional protection. Disabling the irq is also irrelevant as the driver isn't an atomic one and so the callbacks cannot be called from atomic context. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/d4931dc0c0d657d80722cfe7d97cb4fb4ccec90e.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-clps711x.c | 8 -------- 1 file changed, 8 deletions(-) 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); } -- cgit v1.2.3 From 7c1a529a240b1c84204b9f45cc05f0db0fb17402 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:39 +0200 Subject: pwm: fsl-ftm: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core serializes calls to .apply(), so the driver local lock isn't needed for that. It only has the effect to serialize .apply() with .request() and .free() for a different PWM, and .request() and .free against each other. But given that .request and .free() only do a single regmap operation under the lock and regmap itself serializes register accesses, it might happen without the lock that the calls are interleaved now, but affecting different PWMs, so nothing bad can happen. So the mutex has no effect and can be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/6b72104e5e1823170c7c9664189cc0f2ca5c2347.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-fsl-ftm.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) 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 #include #include -#include #include #include #include @@ -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); -- cgit v1.2.3 From 33d73bde06e9ab7862e8e8482ad1a9c4fa1a57f2 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:40 +0200 Subject: pwm: lpc18xx-sct: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both mutexes are only used in one function each. These functions are only called by the .apply() callback. As the .apply() calls are serialized by the core since commit 1cc2e1faafb3 ("pwm: Add more locking") the mutexes have no effect apart from runtime overhead. Drop them. Signed-off-by: Uwe Kleine-König Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/4f7a2da37adbfe4743564245119045826d86eca6.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-lpc18xx-sct.c | 14 -------------- 1 file changed, 14 deletions(-) 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); -- cgit v1.2.3 From 9470e7d11fe2b6c21a35327175d51a06afd3fca9 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:41 +0200 Subject: pwm: microchip-core: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core already serializes .apply() and .get_state(), so the driver local lock is always free and adds no protection. Drop it. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/6d6ef0376ea0058b040eec3b257e324493a083f1.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-microchip-core.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) 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 #include #include -#include #include #include #include @@ -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)); -- cgit v1.2.3 From d2c8bdc72fa906d78cac4f87ad9d75692fe47a9f Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:42 +0200 Subject: pwm: sti: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core already serializes calls to .apply(), so the driver local mutex adds no protection and can be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/7ad150e40b45d6cb16fee633dcd6390a49a327a1.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sti.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) 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) -- cgit v1.2.3 From dce0df8ac14fd2b4d00c17bc893fe3f3b06c853c Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:43 +0200 Subject: pwm: sun4i: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core serializes calls to .apply(), so the driver lock doesn't add any protection and can safely be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/87b71c46b82b787959f0cea314d3010f16a50a29.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-sun4i.c | 10 ---------- 1 file changed, 10 deletions(-) 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 #include #include -#include #include #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); -- cgit v1.2.3 From 2c06a2178926993f77e52f77e6b0c540e3d771ce Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 20:15:44 +0200 Subject: pwm: twl-led: Drop driver local locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pwm core already serializes .apply(). twl6030's .request() and .free() are also already serialized against .apply() because there is only a single PWM. So the mutex doesn't add any additional protection and can be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/c1c7f646190f7cb2fe43b10959aa8dade80cb79e.1750788649.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-twl-led.c | 49 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) 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); } -- cgit v1.2.3 From 10e9b32d9a14edbbed902dd5447a1ca3cd487935 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 24 Jun 2025 12:05:00 +0200 Subject: docs: pwm: Adapt Locking paragraph to reality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have the distinction between pwm_apply_atomic() and pwm_apply_might_sleep() since commit c748a6d77c06 (pwm: Rename pwm_apply_state() to pwm_apply_might_sleep()) contained in v6.8-rc1. Locking in the core was introduced in commit 1cc2e1faafb3 ("pwm: Add more locking", contained in v6.13-rc1) to serialize per-chip callbacks and device removal. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20250624100500.1429163-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- Documentation/driver-api/pwm.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst index b41b1c56477f..0d27a40f5818 100644 --- a/Documentation/driver-api/pwm.rst +++ b/Documentation/driver-api/pwm.rst @@ -173,10 +173,15 @@ Locking ------- The PWM core list manipulations are protected by a mutex, so pwm_get() -and pwm_put() may not be called from an atomic context. Currently the -PWM core does not enforce any locking to pwm_enable(), pwm_disable() and -pwm_config(), so the calling context is currently driver specific. This -is an issue derived from the former barebone API and should be fixed soon. +and pwm_put() may not be called from an atomic context. +Most functions in the PWM consumer API might sleep and so must not be called +from atomic context. The notable exception is pwm_apply_atomic() which has the +same semantics as pwm_apply_might_sleep() but can be called from atomic context. +(The price for that is that it doesn't work for all PWM devices, use +pwm_might_sleep() to check if a given PWM supports atomic operation. + +Locking in the PWM core ensures that callbacks related to a single chip are +serialized. Helpers ------- -- cgit v1.2.3 From 4cd2f417a0acdced4335fa19dc75b3a80941d60d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 25 Jun 2025 12:19:08 -0400 Subject: dt-bindings: pwm: Convert lpc32xx-pwm.txt to yaml format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert pc32xx-pwm.txt to yaml format. Additional changes: - add ref to pwm.yaml - add clocks - restrict #pwm-cells to 3 Signed-off-by: Frank Li Reviewed-by: Vladimir Zapolskiy Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250625161909.2541315-1-Frank.Li@nxp.com Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/lpc32xx-pwm.txt | 17 --------- .../devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml | 44 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 17 deletions(-) delete mode 100644 Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt create mode 100644 Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt deleted file mode 100644 index 74b5bc5dd19a..000000000000 --- a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt +++ /dev/null @@ -1,17 +0,0 @@ -LPC32XX PWM controller - -Required properties: -- compatible: should be "nxp,lpc3220-pwm" -- reg: physical base address and length of the controller's registers - -Examples: - -pwm@4005c000 { - compatible = "nxp,lpc3220-pwm"; - reg = <0x4005c000 0x4>; -}; - -pwm@4005c004 { - compatible = "nxp,lpc3220-pwm"; - reg = <0x4005c004 0x4>; -}; diff --git a/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml b/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml new file mode 100644 index 000000000000..d8ebb0735c96 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/nxp,lpc3220-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP LPC32XX PWM controller + +maintainers: + - Frank Li + +properties: + compatible: + enum: + - nxp,lpc3220-pwm + - nxp,lpc3220-motor-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + '#pwm-cells': + const: 3 + +required: + - compatible + - reg + - '#pwm-cells' + +allOf: + - $ref: pwm.yaml# + +unevaluatedProperties: false + +examples: + - | + pwm@4005c000 { + compatible = "nxp,lpc3220-pwm"; + reg = <0x4005c000 0x4>; + #pwm-cells = <3>; + }; + -- cgit v1.2.3 From edd3bcb1801e1bb98f4f81485140e18c86406ced Mon Sep 17 00:00:00 2001 From: Michal Wilczynski Date: Wed, 2 Jul 2025 15:45:29 +0200 Subject: pwm: Expose PWM_WFHWSIZE in public header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WFHWSIZE constant defines the maximum size for the hardware-specific waveform representation buffer. It is currently local to drivers/pwm/core.c, which makes it inaccessible to external tools like bindgen. Move the constant to include/linux/pwm.h to make it part of the public API. As part of this change, rename it to PWM_WFHWSIZE to follow standard kernel conventions for namespacing macros in public headers. This allows bindgen to automatically generate a corresponding constant for the Rust PWM abstractions, ensuring the value remains synchronized between the C core and Rust code and preventing future maintenance issues. Signed-off-by: Michal Wilczynski Link: https://lore.kernel.org/r/20250702-rust-next-pwm-working-fan-for-sending-v7-1-67ef39ff1d29@samsung.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 26 ++++++++++++-------------- include/linux/pwm.h | 2 ++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 50aa0528a265..0d66376a83ec 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -210,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. @@ -248,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; @@ -306,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; @@ -334,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; @@ -650,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); @@ -809,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) @@ -1696,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 { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 2492c91452f9..8cafc483db53 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -274,6 +274,8 @@ struct pwm_capture { unsigned int duty_cycle; }; +#define PWM_WFHWSIZE 20 + /** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM -- cgit v1.2.3 From a582469541a3f39bed452c50c5d2744620b6db02 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 4 Jul 2025 10:54:43 +0300 Subject: pwm: img: Remove redundant pm_runtime_mark_last_busy() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pm_runtime_put_autosuspend(), pm_runtime_put_sync_autosuspend(), pm_runtime_autosuspend() and pm_request_autosuspend() now include a call to pm_runtime_mark_last_busy(). Remove the now-reduntant explicit call to pm_runtime_mark_last_busy(). Signed-off-by: Sakari Ailus Link: https://lore.kernel.org/r/20250704075443.3221370-1-sakari.ailus@linux.intel.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-img.c | 2 -- 1 file changed, 2 deletions(-) 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)); } -- cgit v1.2.3 From 28517c8b6275a9cd25a4974d0e4d58eaba465a67 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Wed, 23 Jul 2025 19:34:56 +0200 Subject: pwm: mc33xs2410: add hwmon support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support for hwmon is provided by a separate driver residing in hwmon subsystem which is implemented as auxiliary device. Add handling of this device. Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20250723-mc33xs2410-hwmon-v5-1-f62aab71cd59@liebherr.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 1 + drivers/pwm/pwm-mc33xs2410.c | 20 ++++++++++++++++++-- include/linux/mc33xs2410.h | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 include/linux/mc33xs2410.h diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 3ef1757502eb..64f1c86340fd 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -436,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 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 #include #include #include #include +#include #include #include #include @@ -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/include/linux/mc33xs2410.h b/include/linux/mc33xs2410.h new file mode 100644 index 000000000000..31c0edf10dd7 --- /dev/null +++ b/include/linux/mc33xs2410.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + */ +#ifndef _MC33XS2410_H +#define _MC33XS2410_H + +#include + +MODULE_IMPORT_NS("PWM_MC33XS2410"); + +int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val); +int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val); +int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val); + +#endif /* _MC33XS2410_H */ -- cgit v1.2.3 From a02b105fe9f2b82cbd13b13a98c2b9ffae4a7c27 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Wed, 23 Jul 2025 19:34:57 +0200 Subject: hwmon: add support for MC33XS2410 hardware monitoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device is able to monitor temperature, voltage and current of each of the four outputs. Add basic support for monitoring the temperature of the four outputs and the die temperature. Signed-off-by: Dimitri Fedrau Acked-by: Guenter Roeck Link: https://lore.kernel.org/r/20250723-mc33xs2410-hwmon-v5-2-f62aab71cd59@liebherr.com Signed-off-by: Uwe Kleine-König --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mc33xs2410_hwmon.rst | 34 ++++++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/mc33xs2410_hwmon.c | 178 +++++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 Documentation/hwmon/mc33xs2410_hwmon.rst create mode 100644 drivers/hwmon/mc33xs2410_hwmon.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b45bfb4ebf30..d292a86ac5da 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -167,6 +167,7 @@ Hardware Monitoring Kernel Drivers max77705 max8688 mc13783-adc + mc33xs2410_hwmon mc34vr500 mcp3021 menf21bmc diff --git a/Documentation/hwmon/mc33xs2410_hwmon.rst b/Documentation/hwmon/mc33xs2410_hwmon.rst new file mode 100644 index 000000000000..8a2136ef9139 --- /dev/null +++ b/Documentation/hwmon/mc33xs2410_hwmon.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mc33xs2410_hwmon +============================== + +Supported devices: + + * NXPs MC33XS2410 + + Datasheet: https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf + +Authors: + + Dimitri Fedrau + +Description +----------- + +The MC33XS2410 is a four channel self-protected high-side switch featuring +hardware monitoring functions such as temperature, current and voltages for each +of the four channels. + +Sysfs entries +------------- + +======================= ====================================================== +temp1_label "Central die temperature" +temp1_input Measured temperature of central die + +temp[2-5]_label "Channel [1-4] temperature" +temp[2-5]_input Measured temperature of a single channel +temp[2-5]_alarm Temperature alarm +temp[2-5]_max Maximal temperature +======================= ====================================================== diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 079620dd4286..9d28fcf7cd2a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -700,6 +700,16 @@ config SENSORS_MC13783_ADC help Support for the A/D converter on MC13783 and MC13892 PMIC. +config SENSORS_MC33XS2410 + tristate "MC33XS2410 HWMON support" + depends on PWM_MC33XS2410 + help + If you say yes here you get hardware monitoring support for + MC33XS2410. + + This driver can also be built as a module. If so, the module + will be called mc33xs2410_hwmon. + config SENSORS_FSCHMD tristate "Fujitsu Siemens Computers sensor chips" depends on (X86 || COMPILE_TEST) && I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 48e5866c0c9a..cd8bc4752b4d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -165,6 +165,7 @@ obj-$(CONFIG_SENSORS_MAX31790) += max31790.o obj-$(CONFIG_MAX31827) += max31827.o obj-$(CONFIG_SENSORS_MAX77705) += max77705-hwmon.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o +obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_TC654) += tc654.o diff --git a/drivers/hwmon/mc33xs2410_hwmon.c b/drivers/hwmon/mc33xs2410_hwmon.c new file mode 100644 index 000000000000..23eb90e33709 --- /dev/null +++ b/drivers/hwmon/mc33xs2410_hwmon.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Liebherr-Electronics and Drives GmbH + */ + +#include +#include +#include +#include +#include +#include + +/* ctrl registers */ + +#define MC33XS2410_TEMP_WT 0x29 +#define MC33XS2410_TEMP_WT_MASK GENMASK(7, 0) + +/* diag registers */ + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_OUT_STA(chan) (0x02 + (chan) - 1) +#define MC33XS2410_OUT_STA_OTW BIT(8) + +#define MC33XS2410_TS_TEMP_DIE 0x26 +#define MC33XS2410_TS_TEMP_MASK GENMASK(9, 0) + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_TS_TEMP(chan) (0x2f + (chan) - 1) + +static const struct hwmon_channel_info * const mc33xs2410_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_LABEL | HWMON_T_INPUT, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_ALARM, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_ALARM, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_ALARM, + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_ALARM), + NULL, +}; + +static umode_t mc33xs2410_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_alarm: + case hwmon_temp_label: + return 0444; + case hwmon_temp_max: + return 0644; + default: + return 0; + } +} + +static int mc33xs2410_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct spi_device *spi = dev_get_drvdata(dev); + u16 reg_val; + int ret; + u8 reg; + + switch (attr) { + case hwmon_temp_input: + reg = (channel == 0) ? MC33XS2410_TS_TEMP_DIE : + MC33XS2410_TS_TEMP(channel); + ret = mc33xs2410_read_reg_diag(spi, reg, ®_val); + if (ret < 0) + return ret; + + /* LSB is 0.25 degree celsius */ + *val = FIELD_GET(MC33XS2410_TS_TEMP_MASK, reg_val) * 250 - 40000; + return 0; + case hwmon_temp_alarm: + ret = mc33xs2410_read_reg_diag(spi, MC33XS2410_OUT_STA(channel), + ®_val); + if (ret < 0) + return ret; + + *val = FIELD_GET(MC33XS2410_OUT_STA_OTW, reg_val); + return 0; + case hwmon_temp_max: + ret = mc33xs2410_read_reg_ctrl(spi, MC33XS2410_TEMP_WT, ®_val); + if (ret < 0) + return ret; + + /* LSB is 1 degree celsius */ + *val = FIELD_GET(MC33XS2410_TEMP_WT_MASK, reg_val) * 1000 - 40000; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int mc33xs2410_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct spi_device *spi = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_temp_max: + val = clamp_val(val, -40000, 215000); + + /* LSB is 1 degree celsius */ + val = (val / 1000) + 40; + return mc33xs2410_modify_reg(spi, MC33XS2410_TEMP_WT, + MC33XS2410_TEMP_WT_MASK, val); + default: + return -EOPNOTSUPP; + } +} + +static const char *const mc33xs2410_temp_label[] = { + "Central die temperature", + "Channel 1 temperature", + "Channel 2 temperature", + "Channel 3 temperature", + "Channel 4 temperature", +}; + +static int mc33xs2410_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + *str = mc33xs2410_temp_label[channel]; + + return 0; +} + +static const struct hwmon_ops mc33xs2410_hwmon_hwmon_ops = { + .is_visible = mc33xs2410_hwmon_is_visible, + .read = mc33xs2410_hwmon_read, + .read_string = mc33xs2410_read_string, + .write = mc33xs2410_hwmon_write, +}; + +static const struct hwmon_chip_info mc33xs2410_hwmon_chip_info = { + .ops = &mc33xs2410_hwmon_hwmon_ops, + .info = mc33xs2410_hwmon_info, +}; + +static int mc33xs2410_hwmon_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &adev->dev; + struct spi_device *spi = container_of(dev->parent, struct spi_device, dev); + struct device *hwmon; + + hwmon = devm_hwmon_device_register_with_info(dev, NULL, spi, + &mc33xs2410_hwmon_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon); +} + +static const struct auxiliary_device_id mc33xs2410_hwmon_ids[] = { + { + .name = "pwm_mc33xs2410.hwmon", + }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, mc33xs2410_hwmon_ids); + +static struct auxiliary_driver mc33xs2410_hwmon_driver = { + .probe = mc33xs2410_hwmon_probe, + .id_table = mc33xs2410_hwmon_ids, +}; +module_auxiliary_driver(mc33xs2410_hwmon_driver); + +MODULE_DESCRIPTION("NXP MC33XS2410 hwmon driver"); +MODULE_AUTHOR("Dimitri Fedrau "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 68b9272ca7ac948b71aba482ef8244dee8032f46 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 24 Jul 2025 11:41:48 +0100 Subject: pwm: raspberrypi-poe: Fix spelling mistake "Firwmware" -> "Firmware" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a spelling mistake in the PWM_RASPBERRYPI_POE Kconfig, fix it. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20250724104148.139559-1-colin.i.king@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 64f1c86340fd..f00ce973dddf 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -536,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) -- cgit v1.2.3