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_statetop-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_HZjiffies影响,其执行可能存在较大的延迟(若干毫秒),如果业务应用对这个时间不敏感,则无所谓,如果敏感,则需要另外的修改方法,比如 threaded_irq 处理,但是这样改动相对较大,此处不再展开。