Rockchip RK3506 芯片 的 PWM 控制器支持 oneshot 模式,使用时,可以通过驱动模块、sysfs接口来配置周期、占空比和需要发送的数量(个数和重复次数),激活后硬件自动发送指定数量波形并结束。
这个功能非常适合用来变频控制外部设备,比如步进电机。
但是在实际使用时,通过 sysfs 调试,如果设置的周期过大(样例文档的 10us 没有问题),比如 100us 以上,当设置 enable 为 1 后,波形发送结束时,会出现驱动崩溃及内核panic(驱动=y构建),提示检测到 scheduling while atomic.
通过实际驱动 drivers/pwm/rockchip-pwm.c 中可以看 irq 函数,这里主要是针对 PWMv4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
static irqreturn_t rockchip_pwm_irq_v4(int irq, void *data)
{
struct rockchip_pwm_chip *pc = data;
int val;
irqreturn_t ret = IRQ_NONE;
val = readl_relaxed(pc->base + INTSTS);
#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT
if (val & ONESHOT_END_INT) {
struct pwm_state state;
writel_relaxed(ONESHOT_END_INT, pc->base + INTSTS);
/*
* Set pwm state to disabled when the oneshot mode finished.
*/
pwm_get_state(&pc->chip.pwms[0], &state);
state.enabled = false;
state.oneshot_count = 0;
state.oneshot_repeat = 0;
pwm_apply_state(&pc->chip.pwms[0], &state);
rockchip_pwm_oneshot_callback(&pc->chip.pwms[0], &state);
ret = IRQ_HANDLED;
}
#endif
if (val & CAP_LPR_INT) {
|
重点看 pwm_apply_state 这部分代码,这里是要清除已经设置的 oneshot 模式参数,保证结束时内部状态一致,但是 pwm_apply_state 这个函数内部明确标记 might_sleep() ,在 irq 中调用可能阻塞的函数会导致死锁等问题,而这个函数最终内部会调用rockhip_pwm及底层pinctrl一系列函数。
要解决这个崩溃问题,我们需要将 pwm_apply_state 从 top-half 移出去,丢给可以阻塞的上下文处理。驱动中最简单的修改办法,就是利用驱动结构体提供但未使用的 pwm_work 来实现。
具体就是删除这5行代码,新建一个处理函数:
1
2
3
4
5
6
7
8
9
10
11
12
|
+static void rockchip_pwm_work(struct work_struct *work)
+{
+ struct rockchip_pwm_chip *pc = container_of(work, struct rockchip_pwm_chip, pwm_work.work);
+ struct pwm_device *pwm = &pc->chip.pwms[0];
+ struct pwm_state state;
+ pwm_get_state(pwm, &state);
+ state.enabled = false;
+ state.oneshot_count = 0;
+ state.oneshot_repeat = 0;
+ pwm_apply_state(pwm, &state);
+}
+
|
然后,在 irq 函数中,调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if (val & ONESHOT_END_INT) {
struct pwm_state state;
writel_relaxed(ONESHOT_END_INT, pc->base + INTSTS);
/*
* Set pwm state to disabled when the oneshot mode finished.
*/
+ schedule_delayed_work(&pc->pwm_work, 0);
rockchip_pwm_oneshot_callback(&pc->chip.pwms[0], &state);
ret = IRQ_HANDLED;
}
|
并在 probe 函数中初始化:
1
2
3
4
5
6
7
8
|
static int rockchip_pwm_probe(struct platform_device *pdev)
goto err_pclk;
}
+ INIT_DELAYED_WORK(&pc->pwm_work, rockchip_pwm_work);
rockchip_pwm_debugfs_init(pc);
/* Keep the PWM clk enabled if the PWM appears to be up and running. */
|
以及 remove 函数中移除:
1
2
3
4
5
6
7
8
|
static int rockchip_pwm_remove(struct platform_device *pdev)
u32 val;
rockchip_pwm_debugfs_deinit(pc);
+ cancel_delayed_work_sync(&pc->pwm_work);
/*
* For oneshot mode, it is needed to wait for bit PWM_ENABLE
|
需要注意的时,delayed_work 运行在进程上下文中,其调度受 CONFIG_HZ 的 jiffies影响,其执行可能存在较大的延迟(若干毫秒),如果业务应用对这个时间不敏感,则无所谓,如果敏感,则需要另外的修改方法,比如 threaded_irq 处理,但是这样改动相对较大,此处不再展开。