接口设备树之PWM研读及变更为编码器输入接口

设备树是对接口的文本描述,smartIOmux能生成设备树文件,一些开发板上没有的接口,或是更高层次的驱动,仍可能涉及到设备树的微调修改。

下面以LVDS背光驱动为例,来分析设备树的相关要素和定义方法。

通常,编译出镜像后,设备树位于:tmp/work/ccmp25_dvk-dey-linux/linux-dey/6.1-r0/git/arch/arm64/boot/dts目录下,一般入口是板级的设备树,即:ccmp25-dvk.dts

设备树可相互嵌套,所以常见的做法是板级的dts会include芯片和模块级的dtsi。大部分的接口或接口基础定义是在dtsi完成。对于特定的一个接口,我们通常需要从pin脚定义开始,如果没在dtsi中定义,或是需要给个关联性强的名称来覆盖定义,可以在板级的@pinctrl节点引用处定义它。

以LVDS的背光驱动模块为例,它是一个基于PWM驱动的接口,顶层片段位于:

	panel_lvds_pwm_backlight: panel-lvds-pwm-backlight {
		compatible = "pwm-backlight";
		/* node TIM20_CH4 period (ns) */
		pwms = <&pwm_lvds_bckl 3 500000 PWM_POLARITY_INVERTED>;
		brightness-levels = <0 16 22 30 40 55 75 102 138 188 255>;
		default-brightness-level = <8>;
		power-supply = <&reg_5v_board>;
		status = "disabled";
	};

其中,&pwm_lvds_bckl:这是对 PWM 控制器的引用,它定义timers20的子节点中:

&timers20 {
	/delete-property/dmas;
	/delete-property/dma-names;
	status = "okay";
	pwm_lvds_bckl: pwm {
		pinctrl-0 = <&ccmp25_pwm20_pins>;
		pinctrl-1 = <&ccmp25_pwm20_sleep_pins>;
		pinctrl-names = "default", "sleep";
		status = "okay";
	};
	timer@19 {
		status = "okay";
	};
};

从名称上可以看出0和1分别对应的子节点,查到这两个定义,发现是同一引脚的两种模式:

/*正常模式*/
	ccmp25_pwm20_pins: ccmp25-pwm20-0 {
		pins {
			pinmux = <STM32_PINMUX('B', 0, AF8)>; /* TIM20_CH4N */
			bias-disable;
			drive-push-pull;
			slew-rate = <0>;
		};
	};
/*低功耗或称休眠模式*/
	ccmp25_pwm20_sleep_pins: ccmp25-pwm20-sleep-0 {
		pins {
			pinmux = <STM32_PINMUX('B', 0, ANALOG)>; /* TIM20_CH4N */
		};
	};

结合名称来分析,这pinctrl-0是正常模式,而pinctrl-1是休眠模式,对应ccmp25_pwm20_pins 用于正常操作模式,而 ccmp25_pwm20_sleep_pins 用于睡眠低功耗模式。大概是高电平时引脚功耗小。 这背光驱动应该是PWM输出,这里对应的AF8和ANALOG所具备的功能,可以从STMP257手册查得,事实上,smartIOMUX已经默认将其设置为timer对应的功能。

以MP257的PLC参考设计为例,当前的smartIOMUX接口只能选择pwm接口功能而非编码器接口,所以它生成的设备树是这样的结构,分两个定时器节点和&pinctrl的子节点两个部分,注意两个PWM才构成一个编码器的接口,我们先以pwm10为例来解读PWM的设备树:

&timers1 {
    pwm10: pwm {
      	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&ccmp25_timers1_ch3_pwm_pins>;
	pinctrl-1 = <&ccmp25_timers1_ch3_pwm_sleep_pins>;
	status = "okay";

    }
}

而&pinctrl处的子节点默认是这配置

       ccmp25_timers1_ch3_pwm_pins: ccmp25-timers1-ch3-pwm {
		pins {
			pinmux = <STM32_PINMUX('D',  9,  AF8)>; /* TIM1_CH3_PWM */
			drive-push-pull;
			bias-disable;
			slew-rate = <0>;
		};
	};

	ccmp25_timers1_ch3_pwm_sleep_pins: ccmp25-timers1-ch3-pwm-sleep {
		pins {
			pinmux = <STM32_PINMUX('D',  9, ANALOG)>; /* TIM1_CH3_PWM */
		};
	};

对于encoder的设备树示例可参考文档:https://wiki.stmicroelectronics.cn/stm32mpu/wiki/LPTIM_device_tree_configuration#LPTIM2_configured_as_counter_and_quadrature_encoder

注意两个pwm通道构成一个counter,也就是一个编码器接口,所以命名上保留PWM意义不大。并且我们应该用用原理图上的名称,比如DI_1_2以方便和原理图对应上,两个pwm接口对应一个counter,在smartIOMux中,我们可以查看到pwm对应的片段,相应地pwm10,pwm11就转为下面这个:

因此编码器的设备树可对应改为:

&timers1 {
    counter_DI_1_2:  {                                          /* remove pwm10,pwm11 and add this */
      	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&ccmp25_timers1_ch3_4_pins>;
	pinctrl-1 = <&ccmp25_timers1_ch3_4_sleep_pins>;
	status = "okay";

    }
}

ccmp25_timers1_ch3_4_pins: ccmp25-timers1-ch3_4 {
	pins {
		pinmux = <STM32_PINMUX('D',  9,  AF8)>, /* TIM1_CH3_PWM */
          		 <STM32_PINMUX('D',  8,  AF8)>; /* TIM1_CH4_PWM */
		bias-disable;
	};
};
ccmp25_timers1_ch3_4_sleep_pins: ccmp25-timers1-ch3_4_sleep {
	pins {
		pinmux = <STM32_PINMUX('D',  9, ANALOG)>, /* TIM1_CH3_PWM */
          		 <STM32_PINMUX('D',  8, ANALOG)>; /* TIM1_CH4_PWM */
	};
};

</code>

总体上来说,SmartIOMux生成的设备树片段,AF的功能块一般是对的,所以我们只要找出PLC参考设计中的编码器定时器接口,两个一组,可批量参考上面修改。

在smartIOMUX中,pwm名称后有DI的序号,相邻的两个DI为一组,因此通过smartIOMUX可以很快查到对应编码器接口所对应的定时器和引脚,同原理图匹配:

在smartIOMUX上把pwm和对应的编码器接口整理列表如下,以方便设备树修改:

编码器 原pwm 定时器 MPU引脚 SOM引脚
counter_DI_1_2 pwm10,pwm11 timers1_ch3_ch4 PD9,PD8 AA11,AA12
counter_DI_3_4 pwm14,pwm15 timers2_ch3_ch4 PG5,PF11 V1,AC17
counter_DI_5_6 pwm18,pwm19 timers3_ch3_ch4 PF13,PF3
counter_DI_7_8 pwm22,pwm23 timers4_ch3_ch4 PD6,PF4
counter_DI_9_10 pwm26,pwm27 timers5_ch3_ch4 PG2,PG1
counter_DI_11_12 pwm30,pwm4 timers8_ch3,lptimers3_ch1 PI4,PZ2

按上面表格和之前DI_1_2的编码器接口格式,以此类推,很快就可以改好所有编码器的设备树片段。

为了方便版本管理,可以用git的方式来管理设备树源码,并以链接文件的形式通过deyaio工具来编译,请进一步参考:自定义设备树的实现

一般地,为了让编码器的输入使用同一个定时器的通道,我们在设计时应先添加DI,把PWM输出放在后面,最后添加GPIO。如果您使用Digi开发板作为参考,保留了原先GPIO和PWM等接口,在添加较多的定时器输入捕获资源时,有时会碰到无法把两个通道放在同一个定时器的情况,这时可以手动修改。 首先,在SmartIOMux里,把不同定时器的其中一个通道lock取消,这时可以看到,有中一个定时器通道可以选择,如下所示: 但在添加时会告诉你无法解决冲突,我们这时可以去Pads处,查看可选的pad,本例是AC_7(但显示为黄色,表示有冲突可解决),

这样我们再到Table View里查找AC_7,发现是某个GPIO占用了,先记录一下这个GPIO的相关定义和功能,把这个GPIO先暂时删除,添加好PWM对应的DI后,再把这个GPIO添加回来,让系统自动选择可用的GPIO即可。

疑问:作为编码器的输入,是否要使用lowpower的定时器,因为官方spec在这些定时器的功能中有专门的encoder mode, 但如果只要普通的input capture,只需普通的定时器即可。此外,还有TIM1和TIM8是高级控制功能的定时器,是否有专用的功能需要保留不用它们?