mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-07-08 18:13:59 +02:00
pwm: atmel-tcb: Cache clock rates and mark chip as atomic
[ Upstream commit68637b68af] 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 commit37f7707077("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:37f7707077("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> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
de62cefa31
commit
dd60f26fd8
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user