ELADCMSecondEditionChapterFivePartXV

来自百问网嵌入式Linux wiki

__NOTITLE__

具体单板的按键驱动程序(查询方式)

GPIO操作回顾

参考第4章《普适的GPIO引脚操作方法》、第5章《具体单板的GPIO操作方法》。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 118.png

AM335X的按键驱动程序(查询方式)

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

AM335X是底板+核心板的结构,打开底板原理图100ask_am335x_v12_原理图.pdf,它有4个按键,本视频只操作一个按键,原理图如下:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 119.png
平时按键电平为高,按下按键后电平为低。
按键引脚为GPIO1_25。

再看芯片手册确定寄存器及操作方法

EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 120.png
步骤1:使能GPIO1模块
设置CM_PER_GPIO1_CLKCTRL寄存器的bit[18]为1,bit[1:0]为0x2,该寄存器地址为0x44E00000+0xAC。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 121.png
步骤2:把GPIO1_25对应的引脚设置为GPIO模式
要用哪一个寄存器来把GPIO1_25对应的引脚设置为GPIO模式?
① 在核心板原理图ET-som335X原理图.pdf里搜“GPIO1_25”,可以看到下图,确定pin number为U16:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 122.png
② 在芯片手册AM335x Sitara™ Processors.pdf里搜“U16”,可得下图,引脚名为GPMC_A9,用作GPIO时要设置为mode 7:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 123.png
③ 在芯片手册AM335x_datasheet_spruh73p.pdf中搜gpmc_a9,
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 124.png
所以,要把GPIO1_25对应的引脚设置为GPIO模式,要设置conf_gpmc_a9寄存器的bit[5]为1,bit[2:0]为7,这个寄存器的地址是 0x44E10000+0x864。
步骤3:设置GPIO1内部寄存器,把GPIO1_25设置为输入引脚,读数据寄存器
GPIO_OE寄存器:地址为0x4804C000+0x134,bit[25]设置为1。
GPIO_DATAIN寄存器:地址为0x4804C000+0x138,读其bit[25]。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 125.png
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 126.png

编程

程序框架
使用GIT下载所有源码后,本节源码位于如下目录:
		01_all_series_quickstart\04_快速入门(正式开始)\
			02_嵌入式Linux驱动开发基础知识\source\
				04_button_drv\02_button_drv_for_boards\01_button_drv_for_am335x
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 127.png
硬件相关的代码
主要看board_am335x.c,先看它的入口函数,代码如下。
第84行向上层驱动注册一个button_operations结构体,该结构体在第76~80行定义。
		76 static struct button_operations my_buttons_ops = {
		77     .count = 1,
		78     .init = board_am335x_button_init,
		79     .read = board_am335x_button_read,
		80 };
		81
		82 int board_am335x_button_drv_init(void)
		83 {
		84     register_button_operations(&my_buttons_ops);
		85     return 0;
		86 }
		87
button_operations结构体中有init函数指针,它指向board_am335x_button_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。代码如下。
值得关注的是第32~35行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
		21 static volatile unsigned int *CM_PER_GPIO1_CLKCTRL;
		22 static volatile unsigned int *conf_gpmc_a9;
		23 static volatile unsigned int *GPIO1_OE;
		24 static volatile unsigned int *GPIO1_DATAIN;
		25
		26 static void board_am335x_button_init (int which) /* 初始化button, which-哪个button */
		27 {
		28     if (which == 0)
		29     {
		30         if (!CM_PER_GPIO1_CLKCTRL)
		31         {
		32             CM_PER_GPIO1_CLKCTRL = ioremap(0x44E00000 + 0xAC, 4);
		33             conf_gpmc_a9 = ioremap(0x44E10000 + 0x864, 4);
		34             GPIO1_OE = ioremap(0x4804C000 + 0x134, 4);
		35             GPIO1_DATAIN = ioremap(0x4804C000 + 0x138, 4);
		36         }
		37
		38         //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
		39         /* a. 使能GPIO1
		40          * set PRCM to enalbe GPIO1
		41          * set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
		42          * val: (1<<18) | 0x2
		43          */
		44         *CM_PER_GPIO1_CLKCTRL = (1<<18) | 0x2;
		45
		46         /* b. 设置GPIO1_25的功能,让它工作于GPIO模式
		47          * set Control Module to set GPIO1_25 (U16) used as GPIO
		48          * conf_gpmc_a9 as mode 7
		49          * addr : 0x44E10000 + 0x864
		50          * bit[5]   : 1, Input enable value for the PAD
		51          * bit[2:0] : mode 7
		52          */
		53         *conf_gpmc_a9 = (1<<5) | 7;
		54
		55         /* c. 设置GPIO1_25的方向,让它作为输入引脚
		56          * set GPIO1's registers , to set 设置GPIO1_25的方向'S dir (input)
		57          * GPIO_OE
		58          * addr : 0x4804C000 + 0x134
		59          * set bit 25
		60          */
		61
		62         *GPIO1_OE |= (1<<25);
		63     }
		64
		65 }
		66
button_operations结构体中还有有read函数指针,它指向board_am335x_button_read函数,在里面将会读取并返回按键引脚的电平。代码如下。
		67 static int board_am335x_button_read (int which) /* 读button, which-哪个 */
		68 {
		69     printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
		70     if (which == 0)
		71         return (*GPIO1_DATAIN & (1<<25)) ? 1 : 0;
		72     else
		73         return 0;
		74 }
		75

测试

安装驱动程序之后执行测试程序,观察它的返回值(执行测试程序的同时操作按键):
		# insmod button_drv.ko
		# insmod board_am335x.ko
		# ./button_test /dev/100ask_button0

课后作业

① 修改board_am335x.c,增加更多按键
② 修改button_test.c,使用按键来点灯

RK3288的按键驱动程序(查询方式)

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

Firefly的RK3288开发板上没有按键,我们为它制作的扩展板上有1个按键。在扩展板原理图rk3288_extend_v12_0715.pdf中可以看到按键,如下:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 128.png
平时按键电平为高,按下按键后电平为低。
按键引脚为GPIO7_B1。


再看芯片手册确定寄存器及操作方法

芯片手册为Rockchip_RK3288_TRM_V1.2_Part1-20170321.pdf,不过我们总结如下。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 129.png
步骤1:使能GPIO7模块
设置CRU_CLKGATE14_CON寄存器的bit[7]为0。
要设置bit7,必须同时设置bit23为1。
该寄存器地址为0xFF760000+0x198。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 130.png
步骤2:把GPIO7_B1对应的引脚设置为GPIO模式
设置GRF_GPIO7B_IOMUX寄存器的bit[3:2]为0b00。
要设置bit[3:2],必须同时设置bit[19:18]为0b11。
该寄存器地址为0xFF770000+0x0070。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 131.png
步骤3:设置GPIO7内部寄存器,把GPIO7_B1设置为输入引脚,读数据寄存器
GPIO_SWPORTA_DDR方向寄存器:地址为0xFF7E0000+ 0x0004,bit[9]设置为0。
GPIO_EXT_PORTA外部端口寄存器:地址为0xFF7E0000+ 0x0050,读其bit[9]。
注意:
GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 132.png

编程

程序框架
使用GIT下载所有源码后,本节源码位于如下目录:
		01_all_series_quickstart\04_快速入门(正式开始)\
			02_嵌入式Linux驱动开发基础知识\source\
				04_button_drv\02_button_drv_for_boards\02_button_drv_for_rk3288
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 133.png
硬件相关的代码
主要看board_rk3288.c,先看它的入口函数,代码如下。
第 81 行向上层驱动注册一个button_operations结构体,该结构体在第73~77行定义。
		73 static struct button_operations my_buttons_ops = {
		74     .count = 1,
		75     .init = board_rk3288_button_init,
		76     .read = board_rk3288_button_read,
		77 };
		78
		79 int board_rk3288_button_drv_init(void)
		80 {
		81     register_button_operations(&my_buttons_ops);
		82     return 0;
		83 }
button_operations结构体中有init函数指针,它指向board_rk3288_button_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。代码如下。
值得关注的是第32~35行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
		21 static volatile unsigned int *CRU_CLKGATE14_CON;
		22 static volatile unsigned int *GRF_GPIO7B_IOMUX ;
		23 static volatile unsigned int *GPIO7_SWPORTA_DDR;
		24 static volatile unsigned int *GPIO7_EXT_PORTA ;
		25
		26 static void board_rk3288_button_init (int which) /* 初始化button, which-哪个button */
		27 {
		28     if (which == 0)
		29     {
		30         if (!CRU_CLKGATE14_CON)
		31         {
		32             CRU_CLKGATE14_CON = ioremap(0xFF760000 + 0x0198, 4);
		33             GRF_GPIO7B_IOMUX  = ioremap(0xFF770000 + 0x0070, 4);
		34             GPIO7_SWPORTA_DDR = ioremap(0xFF7E0000 + 0x0004, 4);
		35             GPIO7_EXT_PORTA   = ioremap(0xFF7E0000 + 0x0050, 4);
		36         }
		37
		38         /* rk3288 GPIO7_B1 */
		39         /* a. 使能GPIO7
		40          * set CRU to enable GPIO7
		41          * CRU_CLKGATE14_CON 0xFF760000 + 0x198
		42          * (1<<(7+16)) | (0<<7)
		43          */
		44         *CRU_CLKGATE14_CON = (1<<(7+16)) | (0<<7);
		45
		46         /* b. 设置GPIO7_B1用于GPIO
		47          * set PMU/GRF to configure GPIO7_B1 as GPIO
		48          * GRF_GPIO7B_IOMUX 0xFF770000 + 0x0070
		49          * bit[3:2] = 0b00
		50          * (3<<(2+16)) | (0<<2)
		51          */
		52         *GRF_GPIO7B_IOMUX =(3<<(2+16)) | (0<<2);
		53
		54         /* c. 设置GPIO7_B1作为input引脚
		55          * set GPIO_SWPORTA_DDR to configure GPIO7_B1 as input
		56          * GPIO_SWPORTA_DDR 0xFF7E0000 + 0x0004
		57          * bit[9] = 0b0
		58          */
		59         *GPIO7_SWPORTA_DDR &= ~(1<<9);
		60     }
		61
		62 }
button_operations结构体中还有有read函数指针,它指向board_rk3288_button_read函数,在里面将会读取并返回按键引脚的电平。代码如下。
		64 static int board_rk3288_button_read (int which) /* 读button, which-哪个 */
		65 {
		66     //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
		67     if (which == 0)
		68         return (*GPIO7_EXT_PORTA & (1<<9)) ? 1 : 0;
		69     else
		70         return 0;
		71 }

测试

安装驱动程序之后执行测试程序,观察它的返回值(执行测试程序的同时操作按键):
		# insmod button_drv.ko
		# insmod board_rk3288.ko
		# ./button_test /dev/100ask_button0

课后作业

① 修改button_test.c,使用按键来点灯


RK3399的按键驱动程序(查询方式)

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

Firefly的RK3399开发板上没有按键,我们为它制作的扩展板上有3个按键。在扩展板原理图rk3399_extend_v12_0709final.pdf中可以看到按键,如下:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 134.png
平时按键电平为高,按下按键后电平为低。
按键引脚为GPIO0_B1、GPIO0_B2、GPIO0_B4。
本视频中,只操作一个按键:GPIO0_B1。


再看芯片手册确定寄存器及操作方法

芯片手册为Rockchip RK3399TRM V1.3 Part1.pdf和Rockchip RK3399TRM V1.3 Part2.pdf,不过我们总结如下。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 135.png
步骤1:使能GPIO0模块
设置PMUCRU_CLKGATE_CON1寄存器的bit[3]为0。
要设置bit3,必须同时设置bit19为1。
该寄存器地址为0xFF760000+ 0x0104。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 136.png
步骤2:把GPIO0_B1对应的引脚设置为GPIO模式
设置PMUGRF_GPIO0B_IOMUX寄存器的bit[3:2]为0b00。
要设置bit[3:2],必须同时设置bit[19:18]为0b11。
该寄存器地址为0xFF310000+0x0004。
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 137.png
步骤3:设置GPIO0内部寄存器,把GPIO0_B1设置为输入引脚,读数据寄存器
这些寄存器的介绍在芯片手册Rockchip RK3399TRM V1.3 Part2.pdf中。
GPIO_SWPORTA_DDR方向寄存器:地址为0xFF720000+ 0x0004,bit[9]设置为0。
GPIO_EXT_PORTA外部端口寄存器:地址为0xFF720000+ 0x0050,读其bit[9]。
注意:
GPIO_A0~A7 对应bit0~bit7;GPIO_B0~B7 对应bit8~bit15;
GPIO_C0~C7 对应bit16~bit23;GPIO_D0~D7 对应bit24~bit31
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 138.png

编程

程序框架
使用GIT下载所有源码后,本节源码位于如下目录:
		01_all_series_quickstart\04_快速入门(正式开始)\
			02_嵌入式Linux驱动开发基础知识\source\
				04_button_drv\02_button_drv_for_boards\03_button_drv_for_rk3399
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 139.png
硬件相关的代码
主要看board_rk3399.c,先看它的入口函数,代码如下。
第81行向上层驱动注册一个button_operations结构体,该结构体在第73~77行定义。
		73 static struct button_operations my_buttons_ops = {
		74     .count = 1,
		75     .init = board_rk3399_button_init,
		76     .read = board_rk3399_button_read,
		77 };
		78
		79 int board_rk3399_button_drv_init(void)
		80 {
		81     register_button_operations(&my_buttons_ops);
		82     return 0;
		83 }
button_operations结构体中有init函数指针,它指向board_rk3399_button_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。代码如下。
值得关注的是第32~35行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
		21 static volatile unsigned int *PMUCRU_CLKGATE_CON1;
		22 static volatile unsigned int *GRF_GPIO0B_IOMUX ;
		23 static volatile unsigned int *GPIO0_SWPORTA_DDR;
		24 static volatile unsigned int *GPIO0_EXT_PORTA ;
		25
		26 static void board_rk3399_button_init (int which) /* 初始化button, which-哪个button */
		27 {
		28     if (which == 0)
		29     {
		30         if (!PMUCRU_CLKGATE_CON1)
		31         {
		32             PMUCRU_CLKGATE_CON1 = ioremap(0xFF760000+ 0x0104, 4);
		33             GRF_GPIO0B_IOMUX  = ioremap(0xFF310000+0x0004, 4);
		34             GPIO0_SWPORTA_DDR = ioremap(0xFF720000 + 0x0004, 4);
		35             GPIO0_EXT_PORTA   = ioremap(0xFF720000 + 0x0050, 4);
		36         }
		37
		38         /* rk3399 GPIO0_B1 */
		39         /* a. 使能GPIO0
		40          * set CRU to enable GPIO0
		41          * PMUCRU_CLKGATE_CON1 0xFF760000+ 0x0104
		42          * (1<<(3+16)) | (0<<3)
		43          */
		44         *PMUCRU_CLKGATE_CON1 = (1<<(3+16)) | (0<<3);
		45
		46         /* b. 设置GPIO0_B1用于GPIO
		47          * set PMU/GRF to configure GPIO0_B1 as GPIO
		48          * GRF_GPIO0B_IOMUX 0xFF310000+0x0004
		49          * bit[3:2] = 0b00
		50          * (3<<(2+16)) | (0<<2)
		51          */
		52         *GRF_GPIO0B_IOMUX =(3<<(2+16)) | (0<<2);
		53
		54         /* c. 设置GPIO0_B1作为input引脚
		55          * set GPIO_SWPORTA_DDR to configure GPIO0_B1 as input
		56          * GPIO_SWPORTA_DDR 0xFF720000 + 0x0004
		57          * bit[9] = 0b0
		58          */
		59         *GPIO0_SWPORTA_DDR &= ~(1<<9);
		60     }
		61
		62 }
button_operations结构体中还有有read函数指针,它指向board_rk3399_button_read函数,在里面将会读取并返回按键引脚的电平。代码如下。
		64 static int board_rk3399_button_read (int which) /* 读button, which-哪个 */
		65 {
		66    //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
		67     if (which == 0)
		68         return (*GPIO0_EXT_PORTA & (1<<9)) ? 1 : 0;
		69     else
		70         return 0;
		71 }

测试

安装驱动程序之后执行测试程序,观察它的返回值(执行测试程序的同时操作按键):
		# insmod button_drv.ko
		# insmod board_rk3399.ko
		# ./button_test /dev/100ask_button0

课后作业

① 修改board_rk3399.c,增加更多按键
② 修改button_test.c,使用按键来点灯

百问网IMX6ULL-QEMU的按键驱动程序(查询方式)

使用QEMU模拟的硬件,它的硬件资源可以随意扩展。
在IMX6ULL QEMU 虚拟开发板上,我们为它设计了2个 按键。在QEMU的GUI上有4个按键,右边的2个留待以后用于电源管理。

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

EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 140.png
平时按键电平为低,按下按键后电平为高。
按键引脚为GPIO5_IO01、GPIO1_IO18。

再看芯片手册确定寄存器及操作方法

EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 023.png
步骤1:使能GPIO1、GPIO5
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 078.png
设置b[31:30]、b[27:26]就可以使能GPIO5、GPIO1,设置为什么值呢?
看下图,设置为0b11:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 141.png
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能
步骤2:设置GPIO5_IO01、GPIO1_IO18为GPIO模式
① 对于GPIO5_IO01,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 142.png
② 对于GPIO1_IO18,设置如下寄存器:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 001.png
步骤3:设置GPIO5_IO01、GPIO1_IO18为输入引脚,读取引脚电平
寄存器地址为:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 072.png
设置方向寄存器,把引脚设置为输出引脚:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 073.png
读取引脚状态寄存器,得到引脚电平:
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 143.png

编程

程序框架
使用GIT下载所有源码后,本节源码位于如下目录:
	 01_all_series_quickstart\04_快速入门(正式开始)\
	 		02_嵌入式Linux驱动开发基础知识\source\
	 			04_button_drv\02_button_drv_for_boards\04_button_drv_for_100ask_imx6ull-qemu
EmbeddedLinuxApplicationDevelopmentCompleteManualSecondEditionChapterFive 144.png
硬件相关的代码
主要看board_100ask_imx6ull-qemu.c。
涉及的寄存器挺多,一个一个去执行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;
			volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA;
			volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA;
			volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
		};
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));
看一个驱动程序,先看它的入口函数,代码如下。
第127行向上层驱动注册一个button_operations结构体,该结构体在第119~123行定义。
		119 static struct button_operations my_buttons_ops = {
		120     .count = 2,
		121     .init = board_imx6ull_button_init,
		122     .read = board_imx6ull_button_read,
		123 };
		124
		125 int board_imx6ull_button_drv_init(void)
		126 {
		127     register_button_operations(&my_buttons_ops);
		128     return 0;
		129 }
button_operations结构体中有init函数指针,它指向board_imx6ull_button_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。代码如下。
值得关注的是第65~70行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
		50 /* enable GPIO1,GPIO5 */
		51 static volatile unsigned int *CCM_CCGR1;
		52
		53 /* set GPIO5_IO03 as GPIO */
		54 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;
		55
		56 static struct iomux *iomux;
		57
		58 static struct imx6ull_gpio *gpio1;
		59 static struct imx6ull_gpio *gpio5;
		60
		61 static void board_imx6ull_button_init (int which) /* 初始化button, which-哪个button */
		62 {
		63     if (!CCM_CCGR1)
		64     {
		65         CCM_CCGR1 = ioremap(0x20C406C, 4);
		66         IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);
		67
		68         iomux = ioremap(0x20e0000, sizeof(struct iomux));
		69         gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
		70         gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
		71     }
		72
		73     if (which == 0)
		74     {
		75         /* 1. enable GPIO5
		76          * CG15, b[31:30] = 0b11
		77          */
		78         *CCM_CCGR1 |= (3<<30);
		79
		80         /* 2. set GPIO5_IO01 as GPIO
		81          * MUX_MODE, b[3:0] = 0b101
		82          */
		83         *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = 5;
		84
		85         /* 3. set GPIO5_IO01 as input
		86          * GPIO5 GDIR, b[1] = 0b0
		87          */
		88         gpio5->gdir &= ~(1<<1);
		89     }
		90     else if(which == 1)
		91     {
		92         /* 1. enable GPIO1
		93          * CG13, b[27:26] = 0b11
		94          */
		95         *CCM_CCGR1 |= (3<<26);
		96
		97         /* 2. set GPIO1_IO18 as GPIO
		98          * MUX_MODE, b[3:0] = 0b101
		99          */
		100         iomux->IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B = 5;
		101
		102         /* 3. set GPIO1_IO18 as input
		103          * GPIO1 GDIR, b[18] = 0b0
		104          */
		105         gpio1->gdir &= ~(1<<18);
		106     }
		107
		108 }
button_operations结构体中还有有read函数指针,它指向board_imx6ull_button_read函数,在里面将会读取并返回按键引脚的电平。代码如下。
		110 static int board_imx6ull_button_read (int which) /* 读button, which-哪个 */
		111 {
		112     //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
		113     if (which == 0)
		114         return (gpio5->psr & (1<<1)) ? 1 : 0;
		115     else
		116         return (gpio1->psr & (1<<18)) ? 1 : 0;
		117 }

测试

先启动IMX6ULL QEMU模拟器,挂载NFS文件系统。
运行QEMU时,
QEMU内部为主机虚拟出一个网卡, IP为 10.0.2.2,
IMX6ULL有一个网卡, IP为 10.0.2.15,
它连接到主机的虚拟网卡。
这样IMX6ULL就可以通过10.0.2.2去访问Ubuntu了。
安装驱动程序之后执行测试程序,观察它的返回值(执行测试程序的同时操作按键):
	# insmod button_drv.ko
	# insmod board_drv.ko
	# insmod board_100ask_imx6ull-qemu.ko
	# ./button_test  /dev/100ask_button0
	# ./button_test  /dev/100ask_button1

课后作业

① 修改button_test.c,使用按键来点灯