From 495908d2dfc29dc0dbf60979cdec0d7d80ed4e00 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 28 Apr 2026 15:53:29 +0800 Subject: [PATCH 01/13] pwm: atmel-tcb: Remove unneeded semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. This was introduced in commit 68637b68afcc ("pwm: atmel-tcb: Cache clock rates and mark chip as atomic") in Uwe's adaption of Sangyun's original patch. Signed-off-by: Chen Ni Link: https://patch.msgid.link/20260428075329.1234735-1-nichen@iscas.ac.cn Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-atmel-tcb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 3d30aeab507e..a765ef279b51 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -443,7 +443,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) err = clk_prepare_enable(tcbpwmc->slow_clk); if (err) - goto err_disable_clk;; + goto err_disable_clk; err = clk_rate_exclusive_get(tcbpwmc->clk); if (err) From 88c6c956fa310117638e41d4831b20074dfff2ba Mon Sep 17 00:00:00 2001 From: Shiji Yang Date: Tue, 24 Feb 2026 16:51:01 +0800 Subject: [PATCH 02/13] pwm: mediatek: set mt7628 pwm45_fixup flag to false MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the programing guide, mt7628 has generic register layout like most other hardware revisions. We should not set pwm45_fixup flag for it. Signed-off-by: Shiji Yang Link: https://patch.msgid.link/OS7PR01MB13602B3C7E43A2E38275C73AEBC74A@OS7PR01MB13602.jpnprd01.prod.outlook.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-mediatek.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 9d206303404a..dee61e7caf89 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -525,7 +525,7 @@ static const struct pwm_mediatek_of_data mt7623_pwm_data = { static const struct pwm_mediatek_of_data mt7628_pwm_data = { .num_pwms = 4, - .pwm45_fixup = true, + .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, }; From c84291ce0e09db9377d7177e4c82424e42d26c8f Mon Sep 17 00:00:00 2001 From: Shiji Yang Date: Tue, 24 Feb 2026 16:51:02 +0800 Subject: [PATCH 03/13] pwm: mediatek: correct mt7628 clock source setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PWMCON register Bit(3) is used to configure whether to pre divide the clock source. Most revisions clear this bit to disable frequency division. However, mt7628 needs to set this bit. Hence, we introduce a new clksel_fixup flag to correctly configure the clock source for mt7628. Signed-off-by: Shiji Yang Link: https://patch.msgid.link/OS7PR01MB136020DA816E8D601D5BA8BF4BC74A@OS7PR01MB13602.jpnprd01.prod.outlook.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-mediatek.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index dee61e7caf89..992137a27750 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -23,6 +23,8 @@ /* PWM registers and bits definitions */ #define PWMCON 0x00 #define PWMCON_CLKDIV GENMASK(2, 0) +#define PWMCON_CLKSEL BIT(3) +#define PWMCON_OLD_PWM_MODE BIT(15) #define PWMHDUR 0x04 #define PWMLDUR 0x08 #define PWMGDUR 0x0c @@ -38,6 +40,7 @@ struct pwm_mediatek_of_data { unsigned int num_pwms; + bool clksel_fixup; bool pwm45_fixup; u16 pwm_ck_26m_sel_reg; unsigned int chanreg_base; @@ -337,6 +340,7 @@ static int pwm_mediatek_write_waveform(struct pwm_chip *chip, if (wfhw->enable) { u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES; + u32 con_val = PWMCON_OLD_PWM_MODE | wfhw->con; if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) { /* @@ -364,7 +368,11 @@ static int pwm_mediatek_write_waveform(struct pwm_chip *chip, if (pc->soc->pwm_ck_26m_sel_reg) writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg); - pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | wfhw->con); + /* Set BIT(3) to disable clock division */ + if (pc->soc->clksel_fixup) + con_val |= PWMCON_CLKSEL; + + pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, con_val); pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, wfhw->width); pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, wfhw->thres); } else { @@ -496,6 +504,7 @@ static int pwm_mediatek_probe(struct platform_device *pdev) static const struct pwm_mediatek_of_data mt2712_pwm_data = { .num_pwms = 8, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -503,6 +512,7 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = { static const struct pwm_mediatek_of_data mt6795_pwm_data = { .num_pwms = 7, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -510,6 +520,7 @@ static const struct pwm_mediatek_of_data mt6795_pwm_data = { static const struct pwm_mediatek_of_data mt7622_pwm_data = { .num_pwms = 6, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -518,6 +529,7 @@ static const struct pwm_mediatek_of_data mt7622_pwm_data = { static const struct pwm_mediatek_of_data mt7623_pwm_data = { .num_pwms = 5, + .clksel_fixup = false, .pwm45_fixup = true, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -525,6 +537,7 @@ static const struct pwm_mediatek_of_data mt7623_pwm_data = { static const struct pwm_mediatek_of_data mt7628_pwm_data = { .num_pwms = 4, + .clksel_fixup = true, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -532,6 +545,7 @@ static const struct pwm_mediatek_of_data mt7628_pwm_data = { static const struct pwm_mediatek_of_data mt7629_pwm_data = { .num_pwms = 1, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x10, .chanreg_width = 0x40, @@ -539,6 +553,7 @@ static const struct pwm_mediatek_of_data mt7629_pwm_data = { static const struct pwm_mediatek_of_data mt7981_pwm_data = { .num_pwms = 3, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x80, @@ -547,6 +562,7 @@ static const struct pwm_mediatek_of_data mt7981_pwm_data = { static const struct pwm_mediatek_of_data mt7986_pwm_data = { .num_pwms = 2, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -555,6 +571,7 @@ static const struct pwm_mediatek_of_data mt7986_pwm_data = { static const struct pwm_mediatek_of_data mt7988_pwm_data = { .num_pwms = 8, + .clksel_fixup = false, .pwm45_fixup = false, .chanreg_base = 0x80, .chanreg_width = 0x40, @@ -562,6 +579,7 @@ static const struct pwm_mediatek_of_data mt7988_pwm_data = { static const struct pwm_mediatek_of_data mt8183_pwm_data = { .num_pwms = 4, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -570,6 +588,7 @@ static const struct pwm_mediatek_of_data mt8183_pwm_data = { static const struct pwm_mediatek_of_data mt8365_pwm_data = { .num_pwms = 3, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -578,6 +597,7 @@ static const struct pwm_mediatek_of_data mt8365_pwm_data = { static const struct pwm_mediatek_of_data mt8516_pwm_data = { .num_pwms = 5, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL, .chanreg_base = 0x10, @@ -586,6 +606,7 @@ static const struct pwm_mediatek_of_data mt8516_pwm_data = { static const struct pwm_mediatek_of_data mt6991_pwm_data = { .num_pwms = 4, + .clksel_fixup = false, .pwm45_fixup = false, .pwm_ck_26m_sel_reg = PWM_CK_26M_SEL_V3, .chanreg_base = 0x100, From dc9e08fdbcc3eb08a1d2b868b535081c44425e27 Mon Sep 17 00:00:00 2001 From: Ronaldo Nunez Date: Fri, 22 May 2026 16:13:48 -0300 Subject: [PATCH 04/13] pwm: imx27: Fix variable truncation in .apply() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a variable truncation when calculating period in microseconds as part of the solution for the ERR051198 in .apply() callback. Example scenario: - Period of 3us (PWMPR = 196 and prescaler = 1) - Expected value in tmp: 198000000000 (NSEC_PER_SEC * (196 + 2) * 1) - Actual value is 431504384 (truncation to u32) Signed-off-by: Ronaldo Nunez Reviewed-by: Frank Li Link: https://patch.msgid.link/20260522191348.6227-1-rnunez@baylibre.com Fixes: a25351e4c774 ("pwm: imx27: Workaround of the pwm output bug when decrease the duty cycle") Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-imx27.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c index 3d34cdc4a3a5..c8b801fcb525 100644 --- a/drivers/pwm/pwm-imx27.c +++ b/drivers/pwm/pwm-imx27.c @@ -200,7 +200,7 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - unsigned long period_cycles, duty_cycles, prescale, period_us, tmp; + unsigned long period_cycles, duty_cycles, prescale, period_us; struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); unsigned long long c; unsigned long long clkrate; @@ -208,6 +208,7 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, int val; int ret; u32 cr; + u64 tmp; clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk); c = clkrate * state->period; @@ -249,6 +250,11 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, val = readl(imx->mmio_base + MX3_PWMPR); val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; cr = readl(imx->mmio_base + MX3_PWMCR); + + /* + * tmp stores period in nanoseconds. Result fits in u64 since + * val <= 0xfffe and prescaler in [1, 0x1000]. + */ tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr); tmp = DIV_ROUND_UP_ULL(tmp, clkrate); period_us = DIV_ROUND_UP_ULL(tmp, 1000); From c436e3e9c265a1f012008ef5da6fd28863f8025c Mon Sep 17 00:00:00 2001 From: Devi Priya Date: Mon, 6 Apr 2026 22:24:39 +0200 Subject: [PATCH 05/13] pwm: Driver for qualcomm ipq6018 pwm block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on driver from downstream Codeaurora kernel tree. Removed support for older (V1) variants because I have no access to that hardware. Tested on IPQ5018 and IPQ6010 based hardware. Co-developed-by: Baruch Siach Signed-off-by: Baruch Siach Signed-off-by: Devi Priya Reviewed-by: Bjorn Andersson Signed-off-by: George Moussalem Link: https://patch.msgid.link/20260406-ipq-pwm-v21-2-6ed1e868e4c2@outlook.com [ukleinek: Fixed a few nitpicks as agreed on the mailing list] Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-ipq.c | 263 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 drivers/pwm/pwm-ipq.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376..e8886a9b64d9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -347,6 +347,18 @@ config PWM_INTEL_LGM To compile this driver as a module, choose M here: the module will be called pwm-intel-lgm. +config PWM_IPQ + tristate "IPQ PWM support" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAVE_CLK && HAS_IOMEM + help + Generic PWM framework driver for IPQ PWM block which supports + 4 pwm channels. Each of the these channels can be configured + independent of each other. + + To compile this driver as a module, choose M here: the module + will be called pwm-ipq. + config PWM_IQS620A tristate "Azoteq IQS620A PWM support" depends on MFD_IQS62X || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025..5630a521a7cf 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o +obj-$(CONFIG_PWM_IPQ) += pwm-ipq.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c new file mode 100644 index 000000000000..592c26fcc9e6 --- /dev/null +++ b/drivers/pwm/pwm-ipq.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved. + * + * Limitations: + * - The PWM controller has no publicly available datasheet. + * - Each of the four channels is programmed via two 32-bit registers + * (REG0 and REG1 at 8-byte stride). + * - Period and duty-cycle reconfiguration is fully atomic: new divider, + * pre-divider, and high-duration values are latched by setting the + * UPDATE bit (bit 30 in REG1). The hardware applies the new settings + * at the beginning of the next period without disabling the output, + * so the currently running period is always completed. + * - On disable (clearing the ENABLE bit 31 in REG1), the hardware + * finishes the current period before stopping the output. The pin + * is then driven to the inactive (low) level. + * - Upon disabling, the hardware resets the pre-divider (PRE_DIV) and divider + * fields (PWM_DIV) in REG0 and REG1 to 0x0000 and 0x0001 respectively. + * - Only normal polarity is supported. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The frequency range supported is 1 Hz to 100 Mhz (clock rate) */ +#define IPQ_PWM_MAX_PERIOD_NS ((u64)NSEC_PER_SEC) +#define IPQ_PWM_MIN_PERIOD_NS 10 + +/* + * Two 32-bit registers for each PWM: REG0, and REG1. + * Base offset for PWM #i is at 8 * #i. + */ +#define IPQ_PWM_REG0 0 +#define IPQ_PWM_REG0_PWM_DIV GENMASK(15, 0) +#define IPQ_PWM_REG0_HI_DURATION GENMASK(31, 16) + +#define IPQ_PWM_REG1 4 +#define IPQ_PWM_REG1_PRE_DIV GENMASK(15, 0) +/* + * Enable bit is set to enable output toggling in pwm device. + * Update bit is set to trigger the change and is unset automatically + * to reflect the changed divider and high duration values in register. + */ +#define IPQ_PWM_REG1_UPDATE BIT(30) +#define IPQ_PWM_REG1_ENABLE BIT(31) + +/* + * The max value specified for each field is based on the number of bits + * in the pwm control register for that field (16-bit) + */ +#define IPQ_PWM_MAX_DIV FIELD_MAX(IPQ_PWM_REG0_PWM_DIV) + +struct ipq_pwm_chip { + void __iomem *mem; + unsigned long clk_rate; +}; + +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip); + unsigned int off = 8 * pwm->hwpwm + reg; + + return readl(ipq_chip->mem + off); +} + +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg, + unsigned int val) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip); + unsigned int off = 8 * pwm->hwpwm + reg; + + writel(val, ipq_chip->mem + off); +} + +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip); + unsigned int pre_div, pwm_div; + u64 period_ns, duty_ns; + unsigned long val = 0; + unsigned long hi_dur; + + if (!state->enabled) { + /* clear IPQ_PWM_REG1_ENABLE */ + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, IPQ_PWM_REG1_UPDATE); + return 0; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + /* + * Check the upper and lower bounds for the period as per + * hardware limits + */ + if (state->period < IPQ_PWM_MIN_PERIOD_NS) + return -ERANGE; + period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS); + duty_ns = min(state->duty_cycle, period_ns); + + /* + * Pick the maximal value for PWM_DIV that still allows a + * 100% relative duty cycle. This allows a fine grained + * selection of duty cycles. + */ + pwm_div = IPQ_PWM_MAX_DIV - 1; + + /* + * although mul_u64_u64_div_u64 returns a u64, in practice it + * won't overflow due to above constraints. Take the max period + * of 10^9 (NSEC_PER_SEC) and the pwm_div + 1 (IPQ_PWM_MAX_DIV) + * 10^9 * 10^8 + * ------------- => which fits well into a 32-bit unsigned int. + * 10^9 * 65,535 + */ + pre_div = mul_u64_u64_div_u64(period_ns, ipq_chip->clk_rate, + (u64)NSEC_PER_SEC * (pwm_div + 1)); + + if (!pre_div) + return -ERANGE; + + pre_div -= 1; + + if (pre_div > IPQ_PWM_MAX_DIV) + pre_div = IPQ_PWM_MAX_DIV; + + /* pwm duty = HI_DUR * (PRE_DIV + 1) / clk_rate */ + hi_dur = mul_u64_u64_div_u64(duty_ns, ipq_chip->clk_rate, + (u64)NSEC_PER_SEC * (pre_div + 1)); + + val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) | + FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div); + ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val); + + val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div); + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val); + + /* PWM enable toggle needs a separate write to REG1 */ + val |= IPQ_PWM_REG1_UPDATE | IPQ_PWM_REG1_ENABLE; + ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val); + + return 0; +} + +static int ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip); + unsigned int pre_div, pwm_div, hi_dur; + u64 effective_div, hi_div; + u32 reg0, reg1; + + reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1); + state->enabled = reg1 & IPQ_PWM_REG1_ENABLE; + + if (!state->enabled) + return 0; + + reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0); + + state->polarity = PWM_POLARITY_NORMAL; + + pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0); + hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0); + pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1); + + effective_div = (u64)(pwm_div + 1) * (pre_div + 1); + + /* + * effective_div <= 0x100000000, so the multiplication doesn't overflow. + */ + state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, + ipq_chip->clk_rate); + + hi_div = hi_dur * (pre_div + 1); + state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, + ipq_chip->clk_rate); + + /* + * ensure a valid config is passed back to PWM core in case duty_cycle + * is > period (>100%) + */ + state->duty_cycle = min(state->duty_cycle, state->period); + + return 0; +} + +static const struct pwm_ops ipq_pwm_ops = { + .apply = ipq_pwm_apply, + .get_state = ipq_pwm_get_state, +}; + +static int ipq_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ipq_pwm_chip *pwm; + struct pwm_chip *chip; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, 4, sizeof(*pwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + pwm = ipq_pwm_from_chip(chip); + + pwm->mem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pwm->mem)) + return dev_err_probe(dev, PTR_ERR(pwm->mem), + "Failed to acquire resource\n"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to lock clock rate\n"); + + pwm->clk_rate = clk_get_rate(clk); + if (!pwm->clk_rate) + return dev_err_probe(dev, -EINVAL, "Failed due to clock rate being zero\n"); + + chip->ops = &ipq_pwm_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); + + return 0; +} + +static const struct of_device_id pwm_ipq_dt_match[] = { + { .compatible = "qcom,ipq6018-pwm", }, + {} +}; +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match); + +static struct platform_driver ipq_pwm_driver = { + .driver = { + .name = "ipq-pwm", + .of_match_table = pwm_ipq_dt_match, + }, + .probe = ipq_pwm_probe, +}; + +module_platform_driver(ipq_pwm_driver); + +MODULE_LICENSE("GPL"); From 5ac9b13e3fce7fc43bfdbc309dc0871650404023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 4 May 2026 10:55:35 +0200 Subject: [PATCH 06/13] pwm: Consistently define pci_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member in the various struct pci_device_id arrays were initialized by list expressions. This isn't easily readable if you're not into PCI. Using named initializers is more explicit and thus easier to parse. The secret plan is to make struct pci_device_id::driver_data an anonymous union (similar to https://lore.kernel.org/all/cover.1776579304.git.u.kleine-koenig@baylibre.com/) and that requires named initializers. But it's also a nice cleanup on its own. This change doesn't introduce changes to the compiled pci_device_id arrays. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260504085535.1914668-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-dwc.c | 4 ++-- drivers/pwm/pwm-lpss-pci.c | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/pwm/pwm-dwc.c b/drivers/pwm/pwm-dwc.c index 86b72db58741..8ba7ef2c27d0 100644 --- a/drivers/pwm/pwm-dwc.c +++ b/drivers/pwm/pwm-dwc.c @@ -147,8 +147,8 @@ static int dwc_pwm_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume); static const struct pci_device_id dwc_pwm_id_table[] = { - { PCI_VDEVICE(INTEL, 0x4bb7), (kernel_ulong_t)&ehl_pwm_info }, - { } /* Terminating Entry */ + { PCI_VDEVICE(INTEL, 0x4bb7), .driver_data = (kernel_ulong_t)&ehl_pwm_info }, + { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table); diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index ae25d9321d75..3a0fd6593520 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -48,15 +48,15 @@ static void pwm_lpss_remove_pci(struct pci_dev *pdev) } static const struct pci_device_id pwm_lpss_pci_ids[] = { - { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, - { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, - { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_tng_info}, - { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, - { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, - { PCI_VDEVICE(INTEL, 0x31c8), (unsigned long)&pwm_lpss_bxt_info}, - { PCI_VDEVICE(INTEL, 0x5ac8), (unsigned long)&pwm_lpss_bxt_info}, + { PCI_VDEVICE(INTEL, 0x0ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x0f08), .driver_data = (unsigned long)&pwm_lpss_byt_info }, + { PCI_VDEVICE(INTEL, 0x0f09), .driver_data = (unsigned long)&pwm_lpss_byt_info }, + { PCI_VDEVICE(INTEL, 0x11a5), .driver_data = (unsigned long)&pwm_lpss_tng_info }, + { PCI_VDEVICE(INTEL, 0x1ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x2288), .driver_data = (unsigned long)&pwm_lpss_bsw_info }, + { PCI_VDEVICE(INTEL, 0x2289), .driver_data = (unsigned long)&pwm_lpss_bsw_info }, + { PCI_VDEVICE(INTEL, 0x31c8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, + { PCI_VDEVICE(INTEL, 0x5ac8), .driver_data = (unsigned long)&pwm_lpss_bxt_info }, { }, }; MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); From 35ce15049ea6ee5d5a59549039698d6029718efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 15 Apr 2026 16:50:13 +0200 Subject: [PATCH 07/13] pwm: stm32: Make use of mul_u64_u64_div_u64_roundup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the driver was converted to the waveform API the need for this function arised but at that time this function didn't exist yet. In the meantime it's available, so switch to the global function and drop the driver specific implementation. Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/788319f0fff963feca4df3c5fcdd471dcf70ccdf.1776264104.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index 935257a890b0..c708e4a7ad70 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -193,22 +193,6 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, return ret; } -/* - * This should be moved to lib/math/div64.c. Currently there are some changes - * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles. - */ -static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c) -{ - u64 res = mul_u64_u64_div_u64(a, b, c); - /* Those multiplications might overflow but it doesn't matter */ - u64 rem = a * b - c * res; - - if (rem) - res += 1; - - return res; -} - static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, const void *_wfhw, @@ -223,16 +207,15 @@ static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, u64 ccr_ns; /* The result doesn't overflow for rate >= 15259 */ - wf->period_length_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), - NSEC_PER_SEC, rate); + wf->period_length_ns = mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1), + NSEC_PER_SEC, rate); - ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, - NSEC_PER_SEC, rate); + ccr_ns = mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr, NSEC_PER_SEC, rate); if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) { wf->duty_length_ns = - stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), - NSEC_PER_SEC, rate); + mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr), + NSEC_PER_SEC, rate); wf->duty_offset_ns = ccr_ns; } else { From 3c8cec6ddae0297854ad568def98e49084cc9cfb Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 8 May 2026 19:36:09 -0700 Subject: [PATCH 08/13] pwm: ipq: Add missing module description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a MODULE_DESCRIPTION() entry to fix the modpost warning: WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/pwm/pwm-ipq.o Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Fixes: 728796fc4193 ("pwm: Driver for qualcomm ipq6018 pwm block") Link: https://patch.msgid.link/20260509023609.1007698-1-rosenp@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-ipq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c index 592c26fcc9e6..c53373948136 100644 --- a/drivers/pwm/pwm-ipq.c +++ b/drivers/pwm/pwm-ipq.c @@ -260,4 +260,5 @@ static struct platform_driver ipq_pwm_driver = { module_platform_driver(ipq_pwm_driver); +MODULE_DESCRIPTION("Qualcomm IPQ PWM driver"); MODULE_LICENSE("GPL"); From e7abbff5e8b2924a53b583e6d57804c6cad0ab81 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 28 Apr 2026 10:46:50 +0000 Subject: [PATCH 09/13] dt-bindings: pwm: marvell,pxa-pwm: Add SpacemiT K3 PWM support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PWM controller in SpacemiT K3 SoC reuse the same IP as previous K1 generation, while the difference is that one additional bus clock is added. Signed-off-by: Yixun Lan Acked-by: Conor Dooley Link: https://patch.msgid.link/20260428-03-k3-pwm-drv-v2-1-a532bbe45556@kernel.org Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/marvell,pxa-pwm.yaml | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml index 8df327e52810..f1422a401b6b 100644 --- a/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/marvell,pxa-pwm.yaml @@ -15,7 +15,9 @@ allOf: properties: compatible: contains: - const: spacemit,k1-pwm + enum: + - spacemit,k1-pwm + - spacemit,k3-pwm then: properties: "#pwm-cells": @@ -26,6 +28,26 @@ allOf: const: 1 description: | Used for specifying the period length in nanoseconds. + - if: + properties: + compatible: + contains: + enum: + - spacemit,k3-pwm + then: + required: + - clock-names + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + else: + properties: + clocks: + maxItems: 1 + clock-names: + maxItems: 1 properties: compatible: @@ -36,7 +58,9 @@ properties: - marvell,pxa168-pwm - marvell,pxa910-pwm - items: - - const: spacemit,k1-pwm + - enum: + - spacemit,k1-pwm + - spacemit,k3-pwm - const: marvell,pxa910-pwm reg: @@ -47,7 +71,18 @@ properties: description: Number of cells in a pwm specifier. clocks: - maxItems: 1 + minItems: 1 + items: + - description: The function clock + - description: An optional bus clock + + clock-names: + minItems: 1 + maxItems: 2 + oneOf: + - items: + - const: func + - const: bus resets: maxItems: 1 From 74bca0f2d37f5fd9a4bd5378d9f9579e72038556 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 28 Apr 2026 10:46:51 +0000 Subject: [PATCH 10/13] pwm: pxa: Add optional bus clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add one secondary optional bus clock for the PWM PXA driver, also keep it compatible with old single clock. The SpacemiT K3 SoC require a bus clock for PWM controller, acquire and enable it during probe phase. Signed-off-by: Yixun Lan Link: https://patch.msgid.link/20260428-03-k3-pwm-drv-v2-2-a532bbe45556@kernel.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-pxa.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 0f5bdb0e395e..80d2fa10919f 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -161,6 +161,7 @@ static int pwm_probe(struct platform_device *pdev) const struct platform_device_id *id = platform_get_device_id(pdev); struct pwm_chip *chip; struct pxa_pwm_chip *pc; + struct clk *bus_clk; struct device *dev = &pdev->dev; struct reset_control *rst; int ret = 0; @@ -177,7 +178,12 @@ static int pwm_probe(struct platform_device *pdev) return PTR_ERR(chip); pc = to_pxa_pwm_chip(chip); - pc->clk = devm_clk_get(dev, NULL); + bus_clk = devm_clk_get_optional_enabled(dev, "bus"); + if (IS_ERR(bus_clk)) + return dev_err_probe(dev, PTR_ERR(bus_clk), "Failed to get bus clock\n"); + + /* Get named func clk if bus clock is valid */ + pc->clk = devm_clk_get(dev, bus_clk ? "func" : NULL); if (IS_ERR(pc->clk)) return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); From ff558108c0d8cdb4f7d4550f44d53b25c7820a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 18 May 2026 19:23:22 +0200 Subject: [PATCH 11/13] pwm: pca9685: Use named initializers for struct i2c_device_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260518172323.932774-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-pca9685.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 107bebec3546..a02255a64ea8 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -538,7 +538,7 @@ static int __maybe_unused pca9685_pwm_runtime_resume(struct device *dev) } static const struct i2c_device_id pca9685_id[] = { - { "pca9685" }, + { .name = "pca9685" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, pca9685_id); From 5b5e33c44a491d5b5e019f527e028bd567a226fa Mon Sep 17 00:00:00 2001 From: Manish Baing Date: Sat, 23 May 2026 17:32:51 +0000 Subject: [PATCH 12/13] dt-bindings: pwm: stmpe: Drop legacy binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The st,stmpe-pwm binding is already covered by the MFD schema Documentation/devicetree/bindings/mfd/st,stmpe.yaml. Remove the obsolete and redundant text binding file. Signed-off-by: Manish Baing Acked-by: Conor Dooley Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260523173251.72540-3-manishbaing2789@gmail.com Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/st,stmpe-pwm.txt | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt deleted file mode 100644 index f401316e0248..000000000000 --- a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt +++ /dev/null @@ -1,18 +0,0 @@ -== ST STMPE PWM controller == - -This is a PWM block embedded in the ST Microelectronics STMPE -(ST Multi-Purpose Expander) chips. The PWM is registered as a -subdevices of the STMPE MFD device. - -Required properties: -- compatible: should be: - - "st,stmpe-pwm" -- #pwm-cells: should be 2. See pwm.yaml in this directory for a description of - the cells format. - -Example: - -pwm0: pwm { - compatible = "st,stmpe-pwm"; - #pwm-cells = <2>; -}; From 6f9b73071c15001530e6697491b6db1bf639f4c7 Mon Sep 17 00:00:00 2001 From: Maurice Hieronymus Date: Fri, 5 Jun 2026 09:03:59 +0200 Subject: [PATCH 13/13] pwm: th1520: Remove requirement for mul_u64_u64_div_u64_roundup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cycle register is always u32, so cycles_to_ns() can take a u32 instead of a u64. With that narrowing, cycles * NSEC_PER_SEC is at most u32::MAX * 1e9 (~4.3e18), which fits in u64 without overflow. The saturating arithmetic is therefore no longer needed, and the ceiling division can use Rust's u64::div_ceil() directly instead of the open-coded numerator/denominator form. This also drops the TODO referring to a future mul_u64_u64_div_u64_roundup kernel helper, which is no longer required. Reviewed-by: Michal Wilczynski Signed-off-by: Maurice Hieronymus Link: https://patch.msgid.link/20260605-pwm-th1520-fix-v2-1-5921e3a595f7@mailbox.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm_th1520.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index ddd44a5ce497..933c1ec59c2a 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -67,16 +67,10 @@ fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 { ns.saturating_mul(rate_hz) / NSEC_PER_SEC_U64 } -fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 { +fn cycles_to_ns(cycles: u32, rate_hz: u64) -> u64 { const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64; - // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup` - // once available in Rust. - let numerator = cycles - .saturating_mul(NSEC_PER_SEC_U64) - .saturating_add(rate_hz - 1); - - numerator / rate_hz + (u64::from(cycles) * NSEC_PER_SEC_U64).div_ceil(rate_hz) } /// Hardware-specific waveform representation for TH1520. @@ -192,15 +186,15 @@ fn round_waveform_fromhw( return Ok(()); } - wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz); + wf.period_length_ns = cycles_to_ns(wfhw.period_cycles, rate_hz); - let duty_cycles = u64::from(wfhw.duty_cycles); + let duty_cycles = wfhw.duty_cycles; if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 { wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz); wf.duty_offset_ns = 0; } else { - let period_cycles = u64::from(wfhw.period_cycles); + let period_cycles = wfhw.period_cycles; let original_duty_cycles = period_cycles.saturating_sub(duty_cycles); // For an inverted signal, `duty_length_ns` is the high time (period - low_time).