]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: atmel-tcb: Cache clock rates and mark chip as atomic
authorSangyun Kim <sangyun.kim@snu.ac.kr>
Sun, 19 Apr 2026 08:08:38 +0000 (17:08 +0900)
committerUwe Kleine-König <ukleinek@kernel.org>
Wed, 22 Apr 2026 05:24:33 +0000 (07:24 +0200)
atmel_tcb_pwm_apply() holds tcbpwmc->lock as a spinlock via
guard(spinlock)() and then calls atmel_tcb_pwm_config(), which calls
clk_get_rate() twice. clk_get_rate() acquires clk_prepare_lock (a
mutex), so this is a sleep-in-atomic-context violation.

On CONFIG_DEBUG_ATOMIC_SLEEP kernels every pwm_apply_state() that
enables or reconfigures the PWM triggers a "BUG: sleeping function
called from invalid context" warning.

Acquire exclusive control over the clock rates with
clk_rate_exclusive_get() at probe time and cache the rates in struct
atmel_tcb_pwm_chip, then read the cached rates from
atmel_tcb_pwm_config(). This keeps the spinlock-based mutual exclusion
introduced in commit 37f7707077f5 ("pwm: atmel-tcb: Fix race condition
and convert to guards") and removes the sleeping calls from the atomic
section.

With no sleeping calls left in .apply() and the regmap-mmio bus already
running with fast_io=true, also mark the chip as atomic so consumers
can use pwm_apply_atomic() from atomic context.

Fixes: 37f7707077f5 ("pwm: atmel-tcb: Fix race condition and convert to guards")
Signed-off-by: Sangyun Kim <sangyun.kim@snu.ac.kr>
Link: https://patch.msgid.link/20260419080838.3192357-1-sangyun.kim@snu.ac.kr
[ukleinek: Ensure .clk is enabled before calling clk_get_rate on it.]
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-atmel-tcb.c

index f9ff78ba122d4958c1cc5fe0787c0d418a618abf..3d30aeab507e0ef67c50716dd028cfd8224cef93 100644 (file)
@@ -50,6 +50,8 @@ struct atmel_tcb_pwm_chip {
        spinlock_t lock;
        u8 channel;
        u8 width;
+       unsigned long rate;
+       unsigned long slow_rate;
        struct regmap *regmap;
        struct clk *clk;
        struct clk *gclk;
@@ -266,7 +268,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        int slowclk = 0;
        unsigned period;
        unsigned duty;
-       unsigned rate = clk_get_rate(tcbpwmc->clk);
+       unsigned long rate = tcbpwmc->rate;
        unsigned long long min;
        unsigned long long max;
 
@@ -294,7 +296,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
         */
        if (i == ARRAY_SIZE(atmel_tcb_divisors)) {
                i = slowclk;
-               rate = clk_get_rate(tcbpwmc->slow_clk);
+               rate = tcbpwmc->slow_rate;
                min = div_u64(NSEC_PER_SEC, rate);
                max = min << tcbpwmc->width;
 
@@ -431,24 +433,49 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
        }
 
        chip->ops = &atmel_tcb_pwm_ops;
+       chip->atomic = true;
        tcbpwmc->channel = channel;
        tcbpwmc->width = config->counter_width;
 
-       err = clk_prepare_enable(tcbpwmc->slow_clk);
+       err = clk_prepare_enable(tcbpwmc->clk);
        if (err)
                goto err_gclk;
 
+       err = clk_prepare_enable(tcbpwmc->slow_clk);
+       if (err)
+               goto err_disable_clk;;
+
+       err = clk_rate_exclusive_get(tcbpwmc->clk);
+       if (err)
+               goto err_disable_slow_clk;
+
+       err = clk_rate_exclusive_get(tcbpwmc->slow_clk);
+       if (err)
+               goto err_clk_unlock;
+
+       tcbpwmc->rate = clk_get_rate(tcbpwmc->clk);
+       tcbpwmc->slow_rate = clk_get_rate(tcbpwmc->slow_clk);
+
        spin_lock_init(&tcbpwmc->lock);
 
        err = pwmchip_add(chip);
        if (err < 0)
-               goto err_disable_clk;
+               goto err_slow_clk_unlock;
 
        platform_set_drvdata(pdev, chip);
 
        return 0;
 
+err_slow_clk_unlock:
+       clk_rate_exclusive_put(tcbpwmc->slow_clk);
+
+err_clk_unlock:
+       clk_rate_exclusive_put(tcbpwmc->clk);
+
 err_disable_clk:
+       clk_disable_unprepare(tcbpwmc->clk);
+
+err_disable_slow_clk:
        clk_disable_unprepare(tcbpwmc->slow_clk);
 
 err_gclk:
@@ -470,6 +497,9 @@ static void atmel_tcb_pwm_remove(struct platform_device *pdev)
 
        pwmchip_remove(chip);
 
+       clk_rate_exclusive_put(tcbpwmc->slow_clk);
+       clk_rate_exclusive_put(tcbpwmc->clk);
+       clk_disable_unprepare(tcbpwmc->clk);
        clk_disable_unprepare(tcbpwmc->slow_clk);
        clk_put(tcbpwmc->gclk);
        clk_put(tcbpwmc->clk);