ELADCMSecondEditionChapterFivePartⅦ
来自百问网嵌入式Linux wiki
Zhouyuebiao(讨论 | 贡献)2019年12月16日 (一) 14:42的版本 (Create EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFivePartⅦ page)
__NOTITLE__
目录
具体单板的LED驱动程序
- 我们选用的内核都是4.x版本,操作都是类似的:
rk3399 linux 4.4.154
rk3288 linux 4.4.154
imx6ul linux 4.9.88
am3358 linux 4.9.168
- 录制视频时,我的source insight里总是使用某个版本的内核。这没有关系,驱动程序中调用的内核函数,在这些4.x版本的内核里都是一样的。
怎么写LED驱动程序?
- 详细步骤如下:
- ① 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭LED
- ② 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是?
- ③ 编写驱动:先写框架,再写硬件操作的代码
- 注意:在芯片手册中确定的寄存器地址被称为物理地址,在Linux内核中无法直接使用。
- 需要使用内核提供的ioremap把物理地址映射为虚拟地址,使用虚拟地址。
- ② 它的作用:
- 把物理地址phys_addr开始的一段空间(大小为size),映射为虚拟地址;返回值是该段虚拟地址的首地址。
- ② 它的作用:
virt_addr = ioremap(phys_addr, size);
- 实际上,它是按页(4096字节)进行映射的,是整页整页地映射的。
- 假设phys_addr = 0x10002,size=4,ioremap的内部实现是:
- a. phys_addr按页取整,得到地址0x10000
- b. size按页取整,得到4096
- c. 把起始地址0x10000,大小为4096的这一块物理地址空间,映射到虚拟地址空间,
- 假设得到的虚拟空间起始地址为0xf0010000
- d. 那么phys_addr = 0x10002对应的virt_addr = 0xf0010002
- volatile的使用:
- ① 编译器很聪明,会帮我们做些优化,比如:
int a;
a = 0; // 这句话可以优化掉,不影响a的结果
a = 1;
- ② 有时候编译器会自作聪明,比如:
int *p = ioremap(xxxx, 4); // GPIO寄存器的地址
*p = 0; // 点灯,但是这句话被优化掉了
*p = 1; // 灭灯
- ③ 对于上面的情况,为了避免编译器自动优化,需要加上volatile,告诉它“这是容易出错的,别乱优化”:
volatile int *p = ioremap(xxxx, 4); // GPIO寄存器的地址
*p = 0; // 点灯,这句话不会被优化掉
*p = 1; // 灭灯
AM335X的LED驱动程序
所涉及的寄存器操作
/* set PRCM to enalbe GPIO1
* set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
* val: (1<<18) | 0x2
*/
- b. 设置GPIO1_16的功能,让它工作于GPIO模式
/* set Control Module to set GPIO1_16 (R13) used as GPIO
* conf_gpmc_a0 as mode 7
* addr : 0x44E10000 + 0x840
* val : 7
*/
/* set GPIO1's registers , to set GPIO1_16'S dir (output)
* GPIO_OE
* addr : 0x4804C000 + 0x134
* clear bit 16
*/
/* set GPIO1_16's registers , to output 1
* GPIO_SETDATAOUT
* addr : 0x4804C000 + 0x194
*/
/* set GPIO1_16's registers , to output 0
* GPIO_CLEARDATAOUT
* addr : 0x4804C000 + 0x190
*/
写程序
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\
02_嵌入式Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\am335x_src_bin
- 硬件相关的文件是board_am335x.c,其他文件跟LED框架驱动程序完全一样。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
100 static struct led_operations board_demo_led_opr = {
101 .num = 1,
102 .init = board_demo_led_init,
103 .ctl = board_demo_led_ctl,
104 };
105
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第33~37行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
19 #include "led_opr.h"
20
21 static volatile unsigned int *CM_PER_GPIO1_CLKCTRL;
22 static volatile unsigned int *conf_gpmc_a0;
23 static volatile unsigned int *GPIO1_OE;
24 static volatile unsigned int *GPIO1_CLEARDATAOUT;
25 static volatile unsigned int *GPIO1_SETDATAOUT;
26
27 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
28 {
29 if (which == 0)
30 {
31 if (!CM_PER_GPIO1_CLKCTRL)
32 {
33 CM_PER_GPIO1_CLKCTRL = ioremap(0x44E00000 + 0xAC, 4);
34 conf_gpmc_a0 = ioremap(0x44E10000 + 0x840, 4);
35 GPIO1_OE = ioremap(0x4804C000 + 0x134, 4);
36 GPIO1_CLEARDATAOUT = ioremap(0x4804C000 + 0x190, 4);
37 GPIO1_SETDATAOUT = ioremap(0x4804C000 + 0x194, 4);
38 }
39
40 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
41 /* a. 使能GPIO1
42 * set PRCM to enalbe GPIO1
43 * set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
44 * val: (1<<18) | 0x2
45 */
46 *CM_PER_GPIO1_CLKCTRL = (1<<18) | 0x2;
47
48 /* b. 设置GPIO1_16的功能,让它工作于GPIO模式
49 * set Control Module to set GPIO1_16 (R13) used as GPIO
50 * conf_gpmc_ad0 as mode 7
51 * addr : 0x44E10000 + 0x800
52 * val : 7
53 */
54 *conf_gpmc_a0 = 7;
55
56 /* c. 设置GPIO1_16的方向,让它作为输出引脚
57 * set GPIO1's registers , to set GPIO1_16'S dir (output)
58 * GPIO_OE
59 * addr : 0x4804C000 + 0x134
60 * clear bit 16
61 */
62
63 *GPIO1_OE &= ~(1<<16);
64 }
65
66 return 0;
67 }
68
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
69 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
70 {
71 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
72
73 if (which == 0)
74 {
75 if (status) /* on: output 0 */
76 {
77 /* e. 清除GPIO1_16的数据,让它输出低电平
78 * AM335X芯片支持set-and-clear protocol,设置GPIO_CLEARDATAOUT的bit 16为1即可让引脚输出0:
79 * set GPIO1_16's registers , to output 0
80 * GPIO_CLEARDATAOUT
81 * addr : 0x4804C000 + 0x190
82 */
83 *GPIO1_CLEARDATAOUT = (1<<16);
84 }
85 else
86 {
87 /* d. 设置GPIO1_16的数据,让它输出高电平
88 * AM335X芯片支持set-and-clear protocol,设置GPIO_SETDATAOUT的bit 16为1即可让引脚输出1:
89 * set GPIO1_16's registers , to output 1
90 * GPIO_SETDATAOUT
91 * addr : 0x4804C000 + 0x194
92 */
93 *GPIO1_SETDATAOUT = (1<<16);
94 }
95 }
96
97 return 0;
98 }
99
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
106 struct led_operations *get_board_led_opr(void)
107 {
108 return &board_demo_led_opr;
109 }
110
配置内核去掉原有LED驱动
- 不需要重新配置内核,只需要在开发板上执行以下3条命令关闭内核对LED的使用即可:
# echo none > /sys/class/leds/am335x:green:cpu0/trigger
# echo none > /sys/class/leds/am335x:green:mmc0/trigger
# echo none > /sys/class/leds/am335x:green:nand/trigger
- 然后就可以去安装驱动程序,执行测试程序了,操作过程跟LED框架驱动程序的测试是一样的。
课后作业
- a. 在board_am335x.c里有ioremap,什么时候执行iounmap?请完善程序
- b. 视频里我们只实现了点一个LED,请修改代码实现操作4个LED
RK3288和RK3399的LED驱动程序
原理图
fireflye RK3288的LED原理图
所涉及的寄存器操作
RK3288的GPIO8_A1引脚
/* rk3288 GPIO8_A1 */
/* a. 使能GPIO8
* set CRU to enable GPIO8
* CRU_CLKGATE14_CON 0xFF760000 + 0x198
* (1<<(8+16)) | (0<<8)
*/
/* b. 设置GPIO8_A1用于GPIO
* set PMU/GRF to configure GPIO8_A1 as GPIO
* GRF_GPIO8A_IOMUX 0xFF770000 + 0x0080
* bit[3:2] = 0b00
* (3<<(2+16)) | (0<<2)
*/
- c. 设置GPIO8_A1作为output引脚
- 设置GPIO_SWPORTA_DDR 寄存器b[1]为1,把GPIO8_A1设置为输出引脚。
-
注意:
- GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
- GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
/* c. 设置GPIO8_A1作为output引脚
* set GPIO_SWPORTA_DDR to configure GPIO8_A1 as output
* GPIO_SWPORTA_DDR 0xFF7F0000 + 0x0004
* bit[1] = 0b1
*/
- d. 设置GPIO8_A1输出高电平
- 设置GPIO_SWPORTA_DR 寄存器b[1]为1,让GPIO8_A1输出高电平。
-
注意:
- GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
- GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
/* d. 设置GPIO8_A1输出高电平
* set GPIO_SWPORTA_DR to configure GPIO8_A1 output 1
* GPIO_SWPORTA_DR 0xFF7F0000 + 0x0000
* bit[1] = 0b1
*/
- e. 设置GPIO8_A1输出低电平
- 同样是设置GPIO_SWPORTA_DR 寄存器,把b[1]设为0,让GPIO8_A1输出低电平。
/* e. 设置GPIO8_A1输出低电平
* set GPIO_SWPORTA_DR to configure GPIO8_A1 output 0
* GPIO_SWPORTA_DR 0xFF7F0000 + 0x0000
* bit[1] = 0b0
*/
RK3399的GPIO2_D3引脚
/* rk3399 GPIO2_D3 */
/* a. 使能GPIO2
* set CRU to enable GPIO2
* CRU_CLKGATE_CON31 0xFF760000 + 0x037c
* (1<<(3+16)) | (0<<3)
*/
/* b. 设置GPIO2_D3用于GPIO
* set PMU/GRF to configure GPIO2_D3 as GPIO
* GRF_GPIO2D_IOMUX 0xFF770000 + 0x0e00c
* bit[7:6] = 0b00
* (3<<(6+16)) | (0<<6)
*/
- c. 设置GPIO2_D3作为output引脚
- 设置GPIO_SWPORTA_DDR 寄存器b[27]为1,把GPIO2_D3设置为输出引脚。
-
注意:
- GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
- GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
/* c. 设置GPIO2_D3作为output引脚
* set GPIO_SWPORTA_DDR to configure GPIO2_D3 as output
* GPIO_SWPORTA_DDR 0xFF780000 + 0x0004
* bit[27] = 0b1
*/
- d. 设置GPIO2_D3输出高电平
- 设置GPIO_SWPORTA_DR 寄存器b[27]为1,让GPIO2_D3输出高电平。
-
注意:
- GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
- GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
/* d. 设置GPIO2_D3输出高电平
* set GPIO_SWPORTA_DR to configure GPIO2_D3 output 1
* GPIO_SWPORTA_DR 0xFF780000 + 0x0000
* bit[27] = 0b1
*/
- e. 设置GPIO2_D3输出低电平
- 同样是设置GPIO_SWPORTA_DR 寄存器,把b[27]设为0,让GPIO2_D3输出低电平。
/* e. 设置GPIO2_D3输出低电平
* set GPIO_SWPORTA_DR to configure GPIO2_D3 output 0
* GPIO_SWPORTA_DR 0xFF780000 + 0x0000
* bit[27] = 0b0
*/
写程序
RK3288
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\
02_嵌入式Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\rk3288_src_bin
- 硬件相关的文件是board_rk3288.c,其他文件跟LED框架驱动程序完全一样。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
91 static struct led_operations board_demo_led_opr = {
92 .num = 1,
93 .init = board_demo_led_init,
94 .ctl = board_demo_led_ctl,
95 };
96
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第32~35行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
20 static volatile unsigned int *CRU_CLKGATE14_CON;
21 static volatile unsigned int *GRF_GPIO8A_IOMUX ;
22 static volatile unsigned int *GPIO8_SWPORTA_DDR;
23 static volatile unsigned int *GPIO8_SWPORTA_DR ;
24
25 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
26 {
27 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
28 if (which == 0)
29 {
30 if (!CRU_CLKGATE14_CON)
31 {
32 CRU_CLKGATE14_CON = ioremap(0xFF760000 + 0x0198, 4);
33 GRF_GPIO8A_IOMUX = ioremap(0xFF770000 + 0x0080, 4);
34 GPIO8_SWPORTA_DDR = ioremap(0xFF7F0000 + 0x0004, 4);
35 GPIO8_SWPORTA_DR = ioremap(0xFF7F0000 + 0x0000, 4);
36 }
37
38 /* rk3288 GPIO8_A1 */
39 /* a. 使能GPIO8
40 * set CRU to enable GPIO8
41 * CRU_CLKGATE14_CON 0xFF760000 + 0x198
42 * (1<<(8+16)) | (0<<8)
43 */
44 *CRU_CLKGATE14_CON = (1<<(8+16)) | (0<<8);
45
46 /* b. 设置GPIO8_A1用于GPIO
47 * set PMU/GRF to configure GPIO8_A1 as GPIO
48 * GRF_GPIO8A_IOMUX 0xFF770000 + 0x0080
49 * bit[3:2] = 0b00
50 * (3<<(2+16)) | (0<<2)
51 */
52 *GRF_GPIO8A_IOMUX =(3<<(2+16)) | (0<<2);
53
54 /* c. 设置GPIO8_A1作为output引脚
55 * set GPIO_SWPORTA_DDR to configure GPIO8_A1 as output
56 * GPIO_SWPORTA_DDR 0xFF7F0000 + 0x0004
57 * bit[1] = 0b1
58 */
59 *GPIO8_SWPORTA_DDR |= (1<<1);
60 }
61 return 0;
62 }
63
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
64 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮, 0-灭*/
65 {
66 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
67 if (which == 0)
68 {
69 if (status) /* on: output 0 */
70 {
71 /* e. 设置GPIO8_A1输出低电平
72 * set GPIO_SWPORTA_DR to configure GPIO8_A1 output 0
73 * GPIO_SWPORTA_DR 0xFF7F0000 + 0x0000
74 * bit[1] = 0b0
75 */
76 *GPIO8_SWPORTA_DR &= ~(1<<1);
77 }
78 else /* off: output 1 */
79 {
80 /* d. 设置GPIO8_A1输出高电平
81 * set GPIO_SWPORTA_DR to configure GPIO8_A1 output 1
82 * GPIO_SWPORTA_DR 0xFF7F0000 + 0x0000
83 * bit[1] = 0b1
84 */
85 *GPIO8_SWPORTA_DR |= (1<<1);
86 }
87 }
88 return 0;
89 }
90
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
97 struct led_operations *get_board_led_opr(void)
98 {
99 return &board_demo_led_opr;
100 }
101
RK3399
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\
02_嵌入式Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\rk3399_src_bin
- 硬件相关的文件是board_rk3399.c,其他文件跟LED框架驱动程序完全一样。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
91 static struct led_operations board_demo_led_opr = {
92 .num = 1,
93 .init = board_demo_led_init,
94 .ctl = board_demo_led_ctl,
95 };
96
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第32~35行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
20 static volatile unsigned int *CRU_CLKGATE_CON31;
21 static volatile unsigned int *GRF_GPIO2D_IOMUX ;
22 static volatile unsigned int *GPIO2_SWPORTA_DDR;
23 static volatile unsigned int *GPIO2_SWPORTA_DR ;
24
25 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
26 {
27 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
28 if (which == 0)
29 {
30 if (!CRU_CLKGATE_CON31)
31 {
32 CRU_CLKGATE_CON31 = ioremap(0xFF760000 + 0x037c, 4);
33 GRF_GPIO2D_IOMUX = ioremap(0xFF770000 + 0x0e00c, 4);
34 GPIO2_SWPORTA_DDR = ioremap(0xFF780000 + 0x0004, 4);
35 GPIO2_SWPORTA_DR = ioremap(0xFF780000 + 0x0000, 4);
36 }
37
38 /* rk3399 GPIO2_D3 */
39 /* a. 使能GPIO2
40 * set CRU to enable GPIO2
41 * CRU_CLKGATE_CON31 0xFF760000 + 0x037c
42 * (1<<(3+16)) | (0<<3)
43 */
44 *CRU_CLKGATE_CON31 = (1<<(3+16)) | (0<<3);
45
46 /* b. 设置GPIO2_D3用于GPIO
47 * set PMU/GRF to configure GPIO2_D3 as GPIO
48 * GRF_GPIO2D_IOMUX 0xFF770000 + 0x0e00c
49 * bit[7:6] = 0b00
50 * (3<<(6+16)) | (0<<6)
51 */
52 *GRF_GPIO2D_IOMUX = (3<<(6+16)) | (0<<6);
53
54 /* c. 设置GPIO2_D3作为output引脚
55 * set GPIO_SWPORTA_DDR to configure GPIO2_D3 as output
56 * GPIO_SWPORTA_DDR 0xFF780000 + 0x0004
57 * bit[27] = 0b1
58 */
59 *GPIO2_SWPORTA_DDR |= (1<<27);
60 }
61 return 0;
62 }
63
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
64 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮, 0-灭*/
65 {
66 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
67 if (which == 0)
68 {
69 if (status) /* on: output 1 */
70 {
71 /* d. 设置GPIO2_D3输出高电平
72 * set GPIO_SWPORTA_DR to configure GPIO2_D3 output 1
73 * GPIO_SWPORTA_DR 0xFF780000 + 0x0000
74 * bit[27] = 0b1
75 */
76 *GPIO2_SWPORTA_DR |= (1<<27);
77 }
78 else /* off : output 0 */
79 {
80 /* e. 设置GPIO2_D3输出低电平
81 * set GPIO_SWPORTA_DR to configure GPIO2_D3 output 0
82 * GPIO_SWPORTA_DR 0xFF780000 + 0x0000
83 * bit[27] = 0b0
84 */
85 *GPIO2_SWPORTA_DR &= ~(1<<27);
86 }
87 }
88 return 0;
89 }
90
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
97 struct led_operations *get_board_led_opr(void)
98 {
99 return &board_demo_led_opr;
100 }
101
上机实验
- 首先设置工具链,然后修改驱动程序Makefile指定内核源码路径,就可以编译驱动程序和测试程序了。
- 启动开发板,挂载NFS文件系统,这样就可以访问到Ubuntu中的文件。
- 最后,就可以在开发板上进行下列测试。
RK3288
# insmod 100ask_led.ko
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
RK3399
- 要先禁止内核中原来的LED驱动,把“heatbeat”功能关闭,执行以下命令即可:
# echo none > /sys/class/leds/firefly\:yellow\:heartbeat/trigger
# echo none > /sys/class/leds/firefly\:yellow\:user/trigger
# echo none > /sys/class/leds/firefly\:red\:power/trigger
- 这样就可以使用我们的驱动程序做实验了:
# insmod 100ask_led.ko
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
- 如果想恢复原来的心跳功能,可以执行:
# echo heartbeat > /sys/class/leds/firefly\:yellow\:heartbeat/trigger
# echo heartbeat > /sys/class/leds/firefly\:yellow\:user/trigger
# echo heartbeat > /sys/class/leds/firefly\:red\:power/trigger
课后作业
- a. 在驱动里有ioremap,什么时候执行iounmap?请完善程序
- b. 视频里我们只实现了点一个LED,请修改代码实现操作所有LED
野火/正点原子IMX6ULL的LED驱动程序
- 野火、正点原子用的内核版本是4.1.15,
- 我们用的内核版本是 linux 4.9.88,
- 都是4.x版本,在学习上没有任何差别。
- 你拿到板子后,可以使用他们出厂的系统,
- 也可以根据我们提供的高级用户手册更改为我们的系统。
原理图
所涉及的寄存器操作
野火fire_imx6ull-pro 开发板
- 步骤1:使能GPIO5
/* GPIO5_IO03 */
/* a. 使能GPIO5
* set CCM to enable GPIO5
* CCM_CCGR1[CG15] 0x20C406C
* bit[31:30] = 0b11
*/
/* b. 设置GPIO5_IO03用于GPIO
* set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
* to configure GPIO5_IO03 as GPIO
* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
* bit[3:0] = 0b0101 alt5
*/
- 步骤3:设置GPIO5_IO03为输出引脚,设置其输出电平
/* c. 设置GPIO5_IO03作为output引脚
* set GPIO5_GDIR to configure GPIO5_IO03 as output
* GPIO5_GDIR 0x020AC000 + 0x4
* bit[3] = 0b1
*/
/* d. 设置GPIO5_DR输出低电平
* set GPIO5_DR to configure GPIO5_IO03 output 0
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b0
*/
/* e. 设置GPIO5_IO3输出高电平
* set GPIO5_DR to configure GPIO5_IO03 output 1
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b1
*/
正点原子Atk_imx6ull-alpha开发板
- 步骤1:使能GPIO1
/* GPIO1_IO03 */
/* a. 使能GPIO1
* set CCM to enable GPIO1
* CCM_CCGR1[CG13] 0x20C406C
* bit[27:26] = 0b11
*/
/* b. 设置GPIO1_IO03用于GPIO
* set IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
* to configure GPIO1_IO03 as GPIO
* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20E0068
* bit[3:0] = 0b0101 alt5
*/
/* c. 设置GPIO1_IO03作为output引脚
* set GPIO1_GDIR to configure GPIO1_IO03 as output
* GPIO1_GDIR 0x0209C000 + 0x4
* bit[3] = 0b1
*/
/* d. 设置GPIO1_DR输出低电平
* set GPIO1_DR to configure GPIO1_IO03 output 0
* GPIO1_DR 0x0209C000 + 0
* bit[3] = 0b0
*/
/* e. 设置GPIO1_IO03输出高电平
* set GPIO1_DR to configure GPIO1_IO03 output 1
* GPIO1_DR 0x0209C000 + 0
* bit[3] = 0b1
*/
写程序
野火fire_imx6ull-pro开发板
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\
02_嵌入式Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\fire_imx6ull-pro_src_bin
- 硬件相关的文件是board_fire_imx6ull-pro.c,其他文件跟LED框架驱动程序完全一样。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
100 static struct led_operations board_demo_led_opr = {
101 .num = 1,
102 .init = board_demo_led_init,
103 .ctl = board_demo_led_ctl,
104 };
105
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第35~38行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
21 static volatile unsigned int *CCM_CCGR1 ;
22 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
23 static volatile unsigned int *GPIO5_GDIR ;
24 static volatile unsigned int *GPIO5_DR ;
25
26 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
27 {
28 unsigned int val;
29
30 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
31 if (which == 0)
32 {
33 if (!CCM_CCGR1)
34 {
35 CCM_CCGR1 = ioremap(0x20C406C, 4);
36 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
37 GPIO5_GDIR = ioremap(0x020AC000 + 0x4, 4);
38 GPIO5_DR = ioremap(0x020AC000 + 0, 4);
39 }
40
41 /* GPIO5_IO03 */
42 /* a. 使能GPIO5
43 * set CCM to enable GPIO5
44 * CCM_CCGR1[CG15] 0x20C406C
45 * bit[31:30] = 0b11
46 */
47 *CCM_CCGR1 |= (3<<30);
48
49 /* b. 设置GPIO5_IO03用于GPIO
50 * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
51 * to configure GPIO5_IO03 as GPIO
52 * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
53 * bit[3:0] = 0b0101 alt5
54 */
55 val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
56 val &= ~(0xf);
57 val |= (5);
58 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
59
60
61 /* b. 设置GPIO5_IO03作为output引脚
62 * set GPIO5_GDIR to configure GPIO5_IO03 as output
63 * GPIO5_GDIR 0x020AC000 + 0x4
64 * bit[3] = 0b1
65 */
66 *GPIO5_GDIR |= (1<<3);
67 }
68
69 return 0;
70 }
71
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
72 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
73 {
74 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
75 if (which == 0)
76 {
77 if (status) /* on: output 0*/
78 {
79 /* d. 设置GPIO5_DR输出低电平
80 * set GPIO5_DR to configure GPIO5_IO03 output 0
81 * GPIO5_DR 0x020AC000 + 0
82 * bit[3] = 0b0
83 */
84 *GPIO5_DR &= ~(1<<3);
85 }
86 else /* off: output 1*/
87 {
88 /* e. 设置GPIO5_IO3输出高电平
89 * set GPIO5_DR to configure GPIO5_IO03 output 1
90 * GPIO5_DR 0x020AC000 + 0
91 * bit[3] = 0b1
92 */
93 *GPIO5_DR |= (1<<3);
94 }
95
96 }
97 return 0;
98 }
99
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
106 struct led_operations *get_board_led_opr(void)
107 {
108 return &board_demo_led_opr;
109 }
110
正点原子Atk_imx6ull-alpha开发板
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\
02_嵌入式Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\atk_imx6ull-alpha_src_bin
- 硬件相关的文件是board_atk_imx6ull-alpha.c,其他文件跟LED框架驱动程序完全一样。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
100 static struct led_operations board_demo_led_opr = {
101 .num = 1,
102 .init = board_demo_led_init,
103 .ctl = board_demo_led_ctl,
104 };
105
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第35~38行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
26 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
27 {
28 unsigned int val;
29
30 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
31 if (which == 0)
32 {
33 if (!CCM_CCGR1)
34 {
35 CCM_CCGR1 = ioremap(0x20C406C, 4);
36 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(0x20E0068, 4);
37 GPIO1_GDIR = ioremap(0x0209C000 + 0x4, 4);
38 GPIO1_DR = ioremap(0x0209C000 + 0, 4);
39 }
40
41 /* GPIO1_IO03 */
42 /* a. 使能GPIO1
43 * set CCM to enable GPIO1
44 * CCM_CCGR1[CG13] 0x20C406C
45 * bit[27:26] = 0b11
46 */
47 *CCM_CCGR1 |= (3<<26);
48
49 /* b. 设置GPIO1_IO03用于GPIO
50 * set IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
51 * to configure GPIO1_IO03 as GPIO
52 * IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20E0068
53 * bit[3:0] = 0b0101 alt5
54 */
55 val = *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
56 val &= ~(0xf);
57 val |= (5);
58 *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = val;
59
60
61 /* c. 设置GPIO1_IO03作为output引脚
62 * set GPIO1_GDIR to configure GPIO1_IO03 as output
63 * GPIO1_GDIR 0x0209C000 + 0x4
64 * bit[3] = 0b1
65 */
66 *GPIO1_GDIR |= (1<<3);
67 }
68
69 return 0;
70 }
71
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
72 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
73 {
74 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
75 if (which == 0)
76 {
77 if (status) /* on: output 0*/
78 {
79 /* d. 设置GPIO1_DR输出低电平
80 * set GPIO1_DR to configure GPIO1_IO03 output 0
81 * GPIO1_DR 0x0209C000 + 0
82 * bit[3] = 0b0
83 */
84 *GPIO1_DR &= ~(1<<3);
85 }
86 else /* off: output 1*/
87 {
88 /* e. 设置GPIO1_IO03输出高电平
89 * set GPIO1_DR to configure GPIO1_IO03 output 1
90 * GPIO1_DR 0x0209C000 + 0
91 * bit[3] = 0b1
92 */
93 *GPIO1_DR |= (1<<3);
94 }
95
96 }
97 return 0;
98 }
99
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
06 struct led_operations *get_board_led_opr(void)
07 {
08 return &board_demo_led_opr;
09 }
10
上机实验
- 首先设置工具链,然后修改驱动程序Makefile指定内核源码路径,就可以编译驱动程序和测试程序了。
- 启动开发板,挂载NFS文件系统,这样就可以访问到Ubuntu中的文件。
- 最后,就可以在开发板上进行下列测试。
野火fire_imx6ull-pro 开发板
- 注意:如果要使用板子自带的系统,关闭原有LED驱动的方法是类似的,也是进入开发板/sys/class/leds/目录,对于每一个LED在该目录下都有一个子目录,假设某个子目录名为XXX,则执行如下命令:
# echo none > /sys/class/leds/XXX/trigger
- 使用我们的系统时,按如下操作。
- 要先禁止内核中原来的LED驱动,把“heatbeat”功能关闭,执行以下命令即可:
# echo none > /sys/class/leds/cpu/trigger
- 这样就可以使用我们的驱动程序做实验了:
# insmod 100ask_led.ko
#./ledtest /dev/100ask_led0 on
#./ledtest /dev/100ask_led0 off
- 如果想恢复原来的心跳功能,可以执行:
# echo heartbeat > /sys/class/leds/cpu/trigger
正点原子Atk_imx6ull-alpha开发板
- 注意:如果要使用板子自带的系统,关闭原有LED驱动的方法是类似的,也是进入开发板/sys/class/leds/目录,对于每一个LED在该目录下都有一个子目录,假设某个子目录名为XXX,则执行如下命令:
# echo none > /sys/class/leds/XXX/trigger
- 使用我们的系统时,按如下操作。
- 要先禁止内核中原来的LED驱动,把“heatbeat”功能关闭,执行以下命令即可:
# echo none > /sys/class/leds/sys-led/trigger
- 这样就可以使用我们的驱动程序做实验了:
# insmod 100ask_led.ko
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
- 如果想恢复原来的心跳功能,可以执行:
# echo heartbeat > /sys/class/leds/sys-led/trigger
课后作业
- a. 在驱动里有ioremap,什么时候执行iounmap?请完善程序
- b. 视频里我们只实现了点一个LED,开发板上也只有一个LED,
- 所以,请修改代码操作蜂鸣器。
百问网IMX6ULL-QEMU的LED驱动程序
- 使用QEMU模拟的硬件,它的硬件资源可以随意扩展。
- 在IMX6ULL QEMU 虚拟开发板上,我们为它设计了4个 LED。
看原理图确定引脚及操作方法
写程序
- 使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\04_快速入门(正式开始)\ 02_嵌入式Linux驱动开发基础知识\source\02_led_drv\ 02_led_drv_for_boards\100ask_imx6ull-qemu_src_bin
- 硬件相关的文件是board_100ask_imx6ull-qemu.c,其他文件跟LED框架驱动程序完全一样。
- 涉及的寄存器挺多,一个一个去执行ioremap效率太低。
- 先定义结构体,然后对结构体指针进行ioremap,这些结构体在。
- 对于IOMUXC,可以如下定义:
struct iomux {
volatile unsigned int unnames[23];
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO07;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO09;
};
struct iomux *iomux = ioremap(0x20e0000, sizeof(struct iomux));
- 对于GPIO,可以如下定义:
struct imx6ull_gpio {
volatile unsigned int dr;
volatile unsigned int gdir;
volatile unsigned int psr;
volatile unsigned int icr1;
volatile unsigned int icr2;
volatile unsigned int imr;
volatile unsigned int isr;
volatile unsigned int edge_sel;
};
struct imx6ull_gpio *gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
struct imx6ull_gpio *gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
- 开始详细分析board_100ask_imx6ull-qemu.c。
- 它首先构造了一个led_operations结构体,用来表示LED的硬件操作:
176 static struct led_operations board_demo_led_opr = {
177 .num = 4,
178 .init = board_demo_led_init,
179 .ctl = board_demo_led_ctl,
180 };
181
- led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
- 值得关注的是第61~66行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
57 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
58 {
59 if (!CCM_CCGR1)
60 {
61 CCM_CCGR1 = ioremap(0x20C406C, 4);
62 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
63
64 iomux = ioremap(0x20e0000, sizeof(struct iomux));
65 gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
66 gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
67 }
68
69 if (which == 0)
70 {
71 /* 1. enable GPIO5
72 * CG15, b[31:30] = 0b11
73 */
74 *CCM_CCGR1 |= (3<<30);
75
76 /* 2. set GPIO5_IO03 as GPIO
77 * MUX_MODE, b[3:0] = 0b101
78 */
79 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = 5;
80
81 /* 3. set GPIO5_IO03 as output
82 * GPIO5 GDIR, b[3] = 0b1
83 */
84 gpio5->gdir |= (1<<3);
85 }
86 else if(which == 1)
87 {
88 /* 1. enable GPIO1
89 * CG13, b[27:26] = 0b11
90 */
91 *CCM_CCGR1 |= (3<<26);
92
93 /* 2. set GPIO1_IO03 as GPIO
94 * MUX_MODE, b[3:0] = 0b101
95 */
96 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 5;
97
98 /* 3. set GPIO1_IO03 as output
99 * GPIO1 GDIR, b[3] = 0b1
100 */
101 gpio1->gdir |= (1<<3);
102 }
103 else if(which == 2)
104 {
105 /* 1. enable GPIO1
106 * CG13, b[27:26] = 0b11
107 */
108 *CCM_CCGR1 |= (3<<26);
109
110 /* 2. set GPIO1_IO05 as GPIO
111 * MUX_MODE, b[3:0] = 0b101
112 */
113 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05 = 5;
114
115 /* 3. set GPIO1_IO05 as output
116 * GPIO1 GDIR, b[5] = 0b1
117 */
118 gpio1->gdir |= (1<<5);
119 }
120 else if(which == 3)
121 {
122 /* 1. enable GPIO1
123 * CG13, b[27:26] = 0b11
124 */
125 *CCM_CCGR1 |= (3<<26);
126
127 /* 2. set GPIO1_IO06 as GPIO
128 * MUX_MODE, b[3:0] = 0b101
129 */
130 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06 = 5;
131
132 /* 3. set GPIO1_IO06 as output
133 * GPIO1 GDIR, b[6] = 0b1
134 */
135 gpio1->gdir |= (1<<6);
136 }
137
138 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
139 return 0;
140 }
141
- led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
142 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
143 {
144 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
145 if (which == 0)
146 {
147 if (status) /* on : output 0 */
148 gpio5->dr &= ~(1<<3);
149 else /* on : output 1 */
150 gpio5->dr |= (1<<3);
151 }
152 else if (which == 1)
153 {
154 if (status) /* on : output 0 */
155 gpio1->dr &= ~(1<<3);
156 else /* on : output 1 */
157 gpio1->dr |= (1<<3);
158 }
159 else if (which == 2)
160 {
161 if (status) /* on : output 0 */
162 gpio1->dr &= ~(1<<5);
163 else /* on : output 1 */
164 gpio1->dr |= (1<<5);
165 }
166 else if (which == 3)
167 {
168 if (status) /* on : output 0 */
169 gpio1->dr &= ~(1<<6);
170 else /* on : output 1 */
171 gpio1->dr |= (1<<6);
172 }
173 return 0;
174 }
175
- 下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:
182 struct led_operations *get_board_led_opr(void)
183 {
184 return &board_demo_led_opr;
185 }
186
上机实验
- 先启动IMX6ULL QEMU模拟器,挂载NFS文件系统。
- 运行QEMU时,
- QEMU内部为主机虚拟出一个网卡, IP为 10.0.2.2,
- IMX6ULL有一个网卡, IP为 10.0.2.15,
- 它连接到主机的虚拟网卡。
- 这样IMX6ULL就可以通过10.0.2.2去访问Ubuntu了。
- 然后执行以下命令安装驱动、执行测试程序:
# insmod 100ask_led.ko
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
课后作业
- a. 在驱动里有ioremap,什么时候执行iounmap?请完善程序
- b. 驱动程序中有太多的if判断,请优化程序减少if的使用