ELADCMSecondEditionChapterFivePartⅦ

来自百问网嵌入式Linux wiki

__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把物理地址映射为虚拟地址,使用虚拟地址
ioremap函数的使用:
① 函数原型:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 046.png
使用时,要包含头文件:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 047.png
② 它的作用:
把物理地址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
③ 不再使用该段虚拟地址时,要iounmap(virt_addr):
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 048.png
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驱动程序

原理图

100ask_AM335X开发板结构为:底板+核心板,其中一个LED原理图如下:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 049.png
它使用GPIO1_16这个引脚,当它输出低电平时,LED被点亮;当它输出高电平时,LED被熄灭。

所涉及的寄存器操作

a. 使能GPIO1
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 050.png
	/* set PRCM to enalbe GPIO1 
	 * set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
	 * val: (1<<18) | 0x2
	 */
b. 设置GPIO1_16的功能,让它工作于GPIO模式
根据原理图可以找到GPIO1_16这个引脚接到AM3358的R13引脚,根据下图知道pin name为GPMC_A0,并且知道要设置这个引脚为Mode 7。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 051.png
在芯片手册中搜“conf_gpmc_a0”,可得:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 052.png
	/* set Control Module to set GPIO1_16 (R13) used as GPIO 
	 * conf_gpmc_a0 as mode 7
	 * addr : 0x44E10000 + 0x840
	 * val  : 7
	 */
c. 设置GPIO1_16的方向,让它作为输出引脚
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 053.png
	/* set GPIO1's registers , to set GPIO1_16'S dir (output) 
	 * GPIO_OE 
	 * addr : 0x4804C000 + 0x134
	 * clear bit 16
	 */
d. 设置GPIO1_16的数据,让它输出高电平
AM335X芯片支持set-and-clear protocol,设置GPIO_SETDATAOUT的bit 16为1即可让引脚输出1:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 054.png
	/* set GPIO1_16's registers , to output 1 
	 * GPIO_SETDATAOUT
	 * addr : 0x4804C000 + 0x194
	 */
e. 清除GPIO1_16的数据,让它输出低电平
AM335X芯片支持set-and-clear protocol,设置GPIO_CLEARDATAOUT的bit 16为1即可让引脚输出0:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 055.png
	/* 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开发板上有2个LED,原理图如下,其中的WORK_LED使用引脚GPIO8_A1:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 056.png
这些LED引脚输出低电平时,LED被点亮;输出高电平时,LED被熄灭。
firefly RK3399的LED原理图
RK3399开发板上有3个LED,原理图如下,其中的WORK_LED使用引脚GPIO2_D3:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 057.png
这些LED引脚输出低电平时,LED被点亮;输出高电平时,LED被熄灭。


所涉及的寄存器操作

截图便于对比,后面有文字便于复制:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 058.png
RK3288的GPIO8_A1引脚
a. 使能GPIO8
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 059.png
设置CRU_CLKGATE14_CON的b[8]为0使能GPIO8,要修改b[8]的前提是把b[24]设置为1。
	/* rk3288 GPIO8_A1 */
	/* a. 使能GPIO8
	 * set CRU to enable GPIO8
	 * CRU_CLKGATE14_CON 0xFF760000 + 0x198
	 * (1<<(8+16)) | (0<<8)
	 */
b. 设置GPIO8_A1用于GPIO
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 060.png
设置GRF_GPIO8A_IOMUX的b[3:2]为0b00把GPIO8_A1用作GPIO,要修改b[3:2]的前提是把b[19:18]设置为0b11。
	/* 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引脚
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 061.png
设置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输出高电平
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 062.png
设置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引脚
a. 使能GPIO2
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 063.png
设置CRU_CLKGATE_CON31的b[3]为0使能GPIO2,要修改b[3]的前提是把b[19]设置为1。
	/* rk3399 GPIO2_D3 */
	/* a. 使能GPIO2
	 * set CRU to enable GPIO2
	 * CRU_CLKGATE_CON31 0xFF760000 + 0x037c
	 * (1<<(3+16)) | (0<<3)
	 */
b. 设置GPIO2_D3用于GPIO
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 064.png
设置GRF_GPIO2D_IOMUX的b[7:6]为0b00把GPIO2_D3用作GPIO,要修改b[7:6]的前提是把b[23:22]设置为0b11。
	/* 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引脚
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 065.png
设置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输出高电平
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 066.png
设置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开发板
LED原理图如下,它使用GPIO5_IO03,引脚输出低电平时LED被点亮,输出高电平时LED被熄灭:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 067.png
正点原子Atk_imx6ull-alpha开发板
LED原理图如下,它使用GPIO1_IO03,引脚输出低电平时LED被点亮,输出高电平时LED被熄灭:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 068.png

所涉及的寄存器操作

GPIO模块图如下:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 023.png
代码中对硬件的操作截图如下,截图便于对比,后面有文字便于复制:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 069.png
野火fire_imx6ull-pro 开发板
步骤1:使能GPIO5
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 070.png
设置b[31:30]就可以使能GPIO5,设置为什么值呢?
看下图,设置为0b11:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 024.png
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能
	/* GPIO5_IO03 */
	/* a. 使能GPIO5
	 * set CCM to enable GPIO5
	 * CCM_CCGR1[CG15] 0x20C406C
	 * bit[31:30] = 0b11
	 */
步骤2:设置GPIO5_IO03为GPIO模式
设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 071.png
	/* 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为输出引脚,设置其输出电平
寄存器地址为:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 072.png
设置方向寄存器,把引脚设置为输出引脚:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 073.png
设置数据寄存器,设置引脚的输出电平:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 074.png
	/* 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
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 075.png
设置b[27:26]就可以使能GPIO1,设置为什么值呢?
看下图,设置为0b11:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 024.png
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能
	/* GPIO1_IO03 */
	/* a. 使能GPIO1
	 * set CCM to enable GPIO1
	 * CCM_CCGR1[CG13] 0x20C406C
	 * bit[27:26] = 0b11
	 */
步骤2:设置GPIO1_IO03为GPIO模式
设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 076.png
	/* 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
	 */
步骤3:设置GPIO1_IO03为输出引脚,设置其输出电平
寄存器地址为:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 072.png
设置方向寄存器,把引脚设置为输出引脚:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 073.png
设置数据寄存器,设置引脚的输出电平:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 074.png
	/* 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。

看原理图确定引脚及操作方法

EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 077.png
从上图可知,这4个 LED 用到了GPIO5_3、GPIO1_3、GPIO1_5、GPIO1_6 共4个引脚。
在芯片手册里,这些引脚的名字是:GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06。可以根据名字搜到对应的寄存器。
当这些引脚输出低电平时,对应的LED被点亮;输出高电平时,LED熄灭。

所涉及的寄存器操作

EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 023.png
步骤1:使能GPIO1、GPIO5
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 078.png
设置b[31:30]、b[27:26]就可以使能GPIO5、GPIO1,设置为什么值呢?
看下图,设置为0b11:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 024.png
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能
步骤2:设置GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06为GPIO模式
① 对于GPIO5_IO03,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 071.png
② 对于GPIO1_IO03,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 076.png
③ 对于GPIO1_IO05,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 079.png
④ 对于GPIO1_IO06,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 080.png
步骤3:设置GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06为输出引脚,设置其输出电平
寄存器地址为:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 072.png
设置方向寄存器,把引脚设置为输出引脚:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 073.png
设置数据寄存器,设置引脚的输出电平:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 074.png

写程序

使用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的使用