【正点原子Linux连载】第七十三章 Linux PWM驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新
【正点原子Linux连载】第七十三章 Linux PWM驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0_第1张图片
第七十三章 Linux PWM驱动实验

在裸机篇我们已经学习过了如何使用I.MX6ULL的PWM外设来实现LCD的背光调节,其实在Linux的LCD驱动实验我们也提到过I.MX6ULL的PWM背光调节,但是并没有专门的去讲解PWM部分,本章我们就来学习一下Linux下的PWM驱动开发。

73.1 PWM驱动简析
关于PWM原理以及I.MX6ULL的PWM外设已经在裸机篇进行了详细的讲解,这里就不再赘述了,我们重点来看一下NXP原厂提供的Linux内核自带的PWM驱动。
73.1.1 设备树下的PWM控制器节点
I.MX6ULL有8路PWM输出,因此对应8个PWM控制器,所有在设备树下就有8个PWM控制器节点。这8路PWM都属于I.MX6ULL的AIPS-1域,但是在设备树imx6ull.dtsi中分为了两部分,PWM1PWM4在一起,PWM5PWM8在一起,这8路PWM并没有全部放到一起,这一点一定要注意,不要以为imx6ull.dtsi没有写完整。这8路PWM的设备树节点内容都是一样的,除了reg属性不同(毕竟不同的控制器,其地址范围不同)。本章实验我们使用GPIO1_IO04这个引脚来完成PWM实验,而GPIO1_IO04就是PWM3的输出引脚,所以这里我们就以PWM3为例进行讲解,imx6ull.dtsi文件中的pwm3节点信息如下:

示例代码73.1.1.1 pwm3节点内容
1  pwm3: pwm@02088000 {
2   	compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
3   	reg = <0x02088000 0x4000>;
4   	interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
5   	clocks = <&clks IMX6UL_CLK_PWM3>,
6       		<&clks IMX6UL_CLK_PWM3>;
7   	clock-names = "ipg", "per";
8   	#pwm-cells = <2>;
9  };
第2行,compatible属性值有两个“fsl,imx6ul-pwm”和“fsl,imx27-pwm”,所以在整个Linux源码里面搜索这两个字符窜即可找到I.MX6ULL的PWM驱动文件,这个文件就是drivers/pwm/pwm-imx.c。
关于I.MX6ULL的PWM节点更为详细的信息请参考对应的绑定文档:Documentation/devicetree/bindings/pwm/ imx-pwm.txt,这里就不去分析了。

73.1.2 PWM子系统
Linux内核提供了个PWM子系统框架,编写PWM驱动的时候一定要符合这个框架。PWM子系统的核心是pwm_chip结构体,定义在文件include/linux/pwm.h中,定义如下:

示例代码73.1.2.1 pwm_chip结构体
1  struct pwm_chip {
2   	struct device       		*dev;
3   	struct list_head    		list;
4   	const struct pwm_ops    	*ops;
5   	int     					base;
6   	unsigned int        		npwm;
7   	struct pwm_device   		*pwms;
8   	struct pwm_device 		* (*of_xlate)(struct pwm_chip *pc,
9                       				const struct of_phandle_args *args);
10  	unsigned int        		of_pwm_n_cells;
11  	bool            		can_sleep;
12 };
第4行,pwm_ops结构体就是PWM外设的各种操作函数集合,编写PWM外设驱动的时候需要开发人员实现。pwm_ops结构体也定义在pwm.h头文件中,定义如下:

示例代码73.1.2.2 pwm_ops结构体

1  struct pwm_ops {
2   	int		(*request)(struct pwm_chip *chip,   //请求PWM
3               				struct pwm_device *pwm); 
4   	void  	(*free)(struct pwm_chip *chip,  	//释放PWM
5                   		 struct pwm_device *pwm);
6   	int   	(*config)(struct pwm_chip *chip,    //配置PWM周期和占空比
7                     struct pwm_device *pwm,
8                     int duty_ns, int period_ns);
9   	int   	(*set_polarity)(struct pwm_chip *chip,  //设置PWM极性
10                    struct pwm_device *pwm,
11                    enum pwm_polarity polarity);  
12  	int   	(*enable)(struct pwm_chip *chip,    //使能PWM
13                    		struct pwm_device *pwm);
14  	void   	(*disable)(struct pwm_chip *chip,   //关闭PWM
15                     struct pwm_device *pwm);
16  	struct module       *owner;
17 };
pwm_ops中的这些函数不一定全部实现,但是像config、enable和disable这些肯定是需要实现的,否则的话打开/关闭PWM,设置PWM的占空比这些就没操作了。
PWM子系统驱动的核心初始化pwm_chip结构体,然后向内核注册初始化完成以后的pwm_chip。这里就要用到pwmchip_add函数,此函数定义在drivers/pwm/core.c文件中,函数原型如下:

int pwmchip_add(struct pwm_chip *chip)
函数参数和返回值含义如下:
chip:要向内核注册的pwm_chip。
返回值:0 成功;负数 失败。
卸载PWM驱动的时候需要将前面注册的pwm_chip从内核移除掉,这里要用到pwmchip_remove函数,函数原型如下:
int pwmchip_remove(struct pwm_chip *chip)
函数参数和返回值含义如下:
chip:要移除的pwm_chip。
返回值:0 成功;负数 失败。
73.1.3 PWM驱动源码分析
我们简单分析一下Linux内核自带的I.MX6ULL PWM驱动,驱动文件前面都说了,是pwm-imx.c这个文件。打开这个文件,可以看到,这是一个标准的平台设备驱动文件,如下所示:

示例代码73.1.3.1 I.MX6ULL PWM平台驱动
1  static const struct of_device_id imx_pwm_dt_ids[] = {
2   	{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
3   	{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
4   	{ /* sentinel */ }
5  };
6  
7  ......
8  
9  static struct platform_driver imx_pwm_driver = {
10  	.driver     = {
11      	.name   = "imx-pwm",
12      	.of_match_table = imx_pwm_dt_ids,
13  	},
14  	.probe      = imx_pwm_probe,
15 	 	.remove     = imx_pwm_remove,
16 };
17 
18 module_platform_driver(imx_pwm_driver);
第3行,当设备树PWM节点的compatible属性值为“fsl,imx27-pwm”的话就会匹配此驱动,注意后面的.data为imx_pwm_data_v2,这是一个imx_pwm_data类型的结构体变量,内容如下:
示例代码73.1.3.2 imx_pwm_data_v2结构体变量
1 static struct imx_pwm_data imx_pwm_data_v2 = {
2   	.config = imx_pwm_config_v2,
3   	.set_enable = imx_pwm_set_enable_v2,
4 };
imx_pwm_config_v2函数就是最终操作I.MX6ULL的PWM外设寄存器,进行实际配置的函数。imx_pwm_set_enable_v2就是具体使能PWM的函数。
第14行,当设备树节点和驱动匹配以后imx_pwm_probe函数就会执行。
imx_pwm_probe函数如下(有缩减):
示例代码73.1.3.3 imx_pwm_probe函数
1  static int imx_pwm_probe(struct platform_device *pdev)
2  {
3   	const struct of_device_id *of_id =
4           	of_match_device(imx_pwm_dt_ids, &pdev->dev);
5   	const struct imx_pwm_data *data;
6   	struct imx_chip *imx;
7   	struct resource *r;
8   	int ret = 0;
9  
10  	if (!of_id)
11     		return -ENODEV;
12 
13  	imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
14  	if (imx == NULL)
15      	return -ENOMEM;
......
31  	imx->chip.ops = &imx_pwm_ops;
32  	imx->chip.dev = &pdev->dev;
33  	imx->chip.base = -1;
34  	imx->chip.npwm = 1;
35  	imx->chip.can_sleep = true;
36 
37  	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
38  	imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
39  	if (IS_ERR(imx->mmio_base))
40      	return PTR_ERR(imx->mmio_base);
41 
42  	data = of_id->data;
43  	imx->config = data->config;
44  	imx->set_enable = data->set_enable;
45 
46  	ret = pwmchip_add(&imx->chip);
47  	if (ret < 0)
48      	return ret;
49 
50  	platform_set_drvdata(pdev, imx);
51  	return 0;
52 }
第13行,imx是一个imx_chip类型的结构体指针变量,这里为其申请内存。imx_chip结构体有个重要的成员变量chip,chip是pwm_chip类型的。所以这一行就引出了PWM子系统核心部件pwm_chip,稍后的重点就是初始化chip。
第31~35行,初始化imx的chip成员变量,也就是初始化pwm_chip!第31行设置pwm_chip的ops操作集为imx_pwm_ops,imx_pwm_ops定义如下:
示例代码73.1.3.4 imx_pwm_ops操作集合
1 static struct pwm_ops imx_pwm_ops = {
2   	.enable = imx_pwm_enable,
3   	.disable = imx_pwm_disable,
4   	.config = imx_pwm_config,
5   	.owner = THIS_MODULE,
6 };
imx_pwm_enable、imx_pwm_disable和imx_pwm_config这三个函数就是使能、关闭和配置PWM的函数。

继续回到示例代码73.1.3.3中的37和38行,从设备树中获取PWM节点中关于PWM控制器的地址信息,然后在进行内存映射,这样我们就得到了PWM控制器的基地址。
第43和44行,这两行设置imx的config和set_enable这两个成员变量为data->config和data->set_enable,也就是示例代码73.1.3.2中的imx_pwm_config_v2和imx_pwm_set_enable_v2这两个函数。imx_pwm_enable、imx_pwm_disable和imx_pwm_config这三个函数最终调用就是imx_pwm_config_v2和imx_pwm_set_enable_v2。
整个pwm-imx.c文件里面,最终和I.MX6ULL的PWM寄存器打交道的就是imx_pwm_config_v2和imx_pwm_set_enable_v2这两个函数,我们先来看一下imx_pwm_set_enable_v2函数,此函数用于打开或关闭对应的PWM,函数内容如下:

示例代码73.1.3.5 imx_pwm_set_enable_v2函数
1  static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
2  {
3   	struct imx_chip *imx = to_imx_chip(chip);
4   	u32 val;
5  
6   	val = readl(imx->mmio_base + MX3_PWMCR);
7  
8   	if (enable)
9       		val |= MX3_PWMCR_EN;
10  	else
11      	val &= ~MX3_PWMCR_EN;
12 
13  	writel(val, imx->mmio_base + MX3_PWMCR);
14 }
第6行,读取PWMCR寄存器的值。
第9行,如果enable为真,表示使能PWM,将PWMCR寄存器的bit0置1即可,宏MX3_PWMCR_EN为(1<<0)。
第11行,如果enable不为真,表示关闭PWM,将PWMCR寄存器的bit0清0即可。
第13行,将新的val值写入到PWMCR寄存器中。
imx_pwm_config_v2函数用于设置PWM的频率和占空比,相关操作如下:
示例代码73.1.3.6 imx_pwm_config_v2函数
1  static int imx_pwm_config_v2(struct pwm_chip *chip,
2       struct pwm_device *pwm, int duty_ns, int period_ns)
3  {
4   	struct imx_chip *imx = to_imx_chip(chip);
5   	struct device *dev = chip->dev;
6   	unsigned long long c;
7   	unsigned long period_cycles, duty_cycles, prescale;
8   	unsigned int period_ms;
9  	 	bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
10  	int wait_count = 0, fifoav;
11  	u32 cr, sr;
12 
......
42 
43  	c = clk_get_rate(imx->clk_per);
44  	c = c * period_ns;
45  	do_div(c, 1000000000);
46  	period_cycles = c;
47 
48  	prescale = period_cycles / 0x10000 + 1;
49 
50  	period_cycles /= prescale;
51  	c = (unsigned long long)period_cycles * duty_ns;
52  	do_div(c, period_ns);
53  	duty_cycles = c;
54 
55  /*
56   * according to imx pwm RM, the real period value should be
57   * PERIOD value in PWMPR plus 2.
58   */
59  	if (period_cycles > 2)
60      	period_cycles -= 2;
61  	else
62      	period_cycles = 0;
63 
64  	writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
65  	writel(period_cycles, imx->mmio_base + MX3_PWMPR);
66 
67  	cr = MX3_PWMCR_PRESCALER(prescale) |
68     	 	MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
69      	MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
70 
71  	if (enable)
72      	cr |= MX3_PWMCR_EN;
73 
74  	writel(cr, imx->mmio_base + MX3_PWMCR);
75 
76  	return 0;
77 }
第43~62行,根据参数duty_ns和period_ns来计算出应该写入到寄存器里面的值duty_cycles和period_cycles。
第64行,将计算得到的duty_cycles写入到PWMSAR寄存器中,设置PWM的占空比
第65行,将计算得到的period_cycles写入到PWMPR寄存器中,设置PWM的频率。
至此,I.MX6ULL的PWM驱动我们就分析完了。

73.2 PWM驱动编写
73.2.1 修改设备树
PWM驱动就不需要我们再编写了,NXP已经写好了,前面我们也已经详细的分析过这个驱动源码了。我们在实际使用的时候只需要修改设备树即可,ALPHA开发板上的JP2排针引出了GPIO1_IO04这个引脚,如图73.2.1.1所示:
【正点原子Linux连载】第七十三章 Linux PWM驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0_第2张图片

图73.2.1.1 GPIO1_IO4引脚
GPIO1_IO04可以作为PWM3的输出引脚,所以我们需要在设备树里面添加GPIO1_IO04的引脚信息以及PWM3控制器对应的节点信息。
1、添加GPIO1_IO04引脚信息
打开imx6ull-alientek-emmc.dts文件,在iomuxc节点下添加GPIO1_IO04的引脚信息,如下所示:

示例代码73.2.1.1 GPIO1_IO04引脚信息
1 pinctrl_pwm3: pwm3grp {
2   	fsl,pins = <
3       	MX6UL_PAD_GPIO1_IO04__PWM3_OUT   0x110b0
4   	>;
5 };
2、向pwm3节点追加信息
前面已经讲过了,imx6ull.dtsi文件中已经有了“pwm3”节点,但是还不能直接使用,需要在imx6ull-alientek-emmc.dts文件中向pwm3节点追加一些内容,在imx6ull-alientek-emmc.dts文件中加入如下所示内容:
示例代码73.2.1.2 向pwm3添加的内容
1 &pwm3 {
2   	pinctrl-names = "default";
3   	pinctrl-0 = <&pinctrl_pwm3>;
4   	clocks = <&clks IMX6UL_CLK_PWM3>,
5            	  <&clks IMX6UL_CLK_PWM3>;
6   	status = "okay";
7 };
第3行,pinctrl-0属性指定PWM3所使用的输出引脚对应的pinctrl节点,这里设置为示例代码73.2.1中的pinctrl_pwm3。
第4和5行,设置时钟,第4行设置ipg时钟,第5行设置per时钟。有些pwm节点默认时钟源是IMX6UL_CLK_DUMMY,这里我们需要将其改为对应的时钟,比如这里设置为IMX6UL_CLK_PWM3。PWM1~PWM8分别对应IMX6UL_CLK_PWM1~ IMX6UL_CLK_PWM8。
3、屏蔽掉其他复用的IO
检查一下设备树中有没有其他外设用到GPIO1_IO04,如果有的话需要屏蔽掉!注意,不能只屏蔽掉GPIO1_IO04的pinctrl配置信息,也要搜索一下“gpio1 4”,看看有没有哪里用到,用到的话也要屏蔽掉。

设备树修改完成以后重新编译设备树,然后使用新的设备树启动系统。
73.2.2 使能PWM驱动
NXP官方的Linux内核已经默认使能了PWM驱动,所以不需要我们修改,但是为了学习,我们还是需要知道怎么使能。打开Linux内核配置界面,按照如下路径找到配置项:
-> Device Drivers
-> Pulse-Width Modulation (PWM) Support
-> <*> i.MX PWM support
配置如图73.2.2.1所示:
【正点原子Linux连载】第七十三章 Linux PWM驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0_第3张图片

图73.2.2.1 PWM配置项
73.3 PWM驱动测试
使用新的设备树启动系统,然后将开发板JP2排针上的GPIO_4(GPIO1_IO04)引脚连接到示波器上,通过示波器来查看PWM波形图。我们可以直接在用户层来配置PWM,进入目录/sys/class/pwm中,如图73.3.1所示:
在这里插入图片描述

图73.3.1各路PWM
图73.3.1中pwmchip0pwmchip7对应I.MX6ULL的PWM1PWM8,所以我们需要用到pwmchip2。
1、调出pwmchip2的pwm0子目录
首先需要调出pwmchip2下的pwm0目录,否则后续就没法操作了,输入如下命令:
echo 0 > /sys/class/pwm/pwmchip2/export
执行完成会在pwmchip2目录下生成一个名为“pwm0”的子目录,如图73.3.2所示:
在这里插入图片描述

图73.3.2 新生成的pwm0子目录
2、使能PWM3
输入如下命令使能PWM3:
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
3、设置PWM3的频率
注意,这里设置的是周期值,单位为ns,比如20KHz频率的周期就是50000ns,输入如下命令:
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
4、设置PWM3的占空比
这里不能直接设置占空比,而是设置的一个周期的ON时间,也就是高电平时间,比如20KHz频率下20%占空比的ON时间就是10000,输入如下命令:
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
设置完成使用示波器查看波形是否正确,正确的话如图73.3.3所示:
【正点原子Linux连载】第七十三章 Linux PWM驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0_第4张图片

图73.3.3 PWM波形图
从图73.3.3可以看出,此时PWM频率为20KHz,占空比为20%,与我们设置的一致。如果要修改频率或者占空比的话一定要注意这两者时间值,比如20KHz频率的周期值为50000ns,那么你在调整占空比的时候ON时间就不能设置大于50000,否则就会提示你参数无效。
73.4 PWM背光设置
有时候我们需要在某个外设上添加PWM功能,比如,LCD的背光控制就是PWM来完成的,本小节我们就以PWM背光控制为例,学习一下如何在其他外设上添加PWM功能。首先肯定是设备树描述,直接看linux内核里面关于backlight(背光)的绑定文档,路径为Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt,此文档描述了如何创建backlight节点来使用linux内核自带的pwm背光驱动。
必要的属性如下:
compatible:内容必须为“pwm-backlight”,通过这个可以匹配到内核自带的PWM背光驱动,驱动文件为drivers/video/backlight/pwm_bl.c,这里就不去分析驱动源码了。
pwms:此属性指定背光使用哪一路PWM,以及PWM相关的属性。
brightness-levels:背光等级数组,范围0255,对应占空比为0%100%。数组内的值必须从0开始,也就是0%占空比,最后一个值必须是255,也就是100%占空比。数组中间值的个数以及值大小可以自行定义。
default-brightness-level:默认的背光等级,也就是brightness-levels属性中第几个值,注意这里是数索引编号,不是具体的数值!
power-supply:支持的电压,此属性可以不需要。
以正点原子ALPHA开发板为例,看一下PWM背光节点是如何设置的,打开imx6ull-alientek-emmc.dts,找到如下所示节点内容:

示例代码73.4.1.1 pwm背光节点
1 backlight {
2   	compatible = "pwm-backlight";
3   	pwms = <&pwm1 0 5000000>;
4   	brightness-levels = <0 4 8 16 32 64 128 255>;
5   	default-brightness-level = <7>;
6   	status = "okay";
7 };
第2行,compatible属性必须为“pwm-backlight”。
第3行,pwms属性指定背光所使用的pwm通道,第一个参数指定使用pwm1,由于I.MX6ULL的PWM只有一个通道,因此这里为0。最后一个参数是PWM周期,单位为ns,这里PWM周期为5000000ns,频率为200Hz。
第4行,背光等级数组,一共8个等级,索引编号从0到7。
第5行,背光默认处于第7等级,也就是255,为100%占空比。
关于pwm做背光控制就讲解到这里,pwm做其他外设某个功能的时候要具体问题具体分析。

你可能感兴趣的:(LINUX,linux,stm32)