了解内容
linux系统中不能直接操作物理地址,但是可以操作虚拟地址从而对寄存器进行操作。
使用void *ioremap(unsigned long start, unsigned long len)
输入物理地址和字节长度,可返回虚拟地址指针。
准备内容
以LED2为例,在4412芯片上引脚为GPL2_0。
在三星用户手册中找到GPL2_0有3个主要寄存器:
-
GPL2CON :配置寄存器,配置8个GPIO输入/输出模式,每一个占4bit,32位有效;
Address = Base Address + 0x0100(Base Address: 0x1100_0000)
其中GPL2_0由GPL2CON的低4位配置,写0输入,写1输出。
-
GPL2DAT:数据寄存器,关于8个GPIO的高低电平状态,每一个占1bit,低8位有效;
Address = Base Address + 0x0104(Base Address: 0x1100_0000)
其中GPL2_0状态在GPL2DAT的低1位读取或者写入,写1高电平,写0低电平。
-
GPL2PUD:上下拉控制寄存器,关于8个GPIO的高低电平状态,每一个占2bit,低16位有效;
Address = Base Address + 0x0108(Base Address: 0x1100_0000)
其中GPL2_0上下拉状态由GPL2PUD的低2位控制,写3为上拉。
开始
建立框架:
1
2
3
4
5
6
7
//当模块加载
gpl2_remap(); //实现IO内存的映射,得到3个可控制的寄存器
gpl2_config(); //配置GPL2[0]为输出上拉模式
gpl2_on(); //控制输出寄存器为高
//当模块卸载
gpl2_off(); //控制输出寄存器为低
实现各个函数
led_ioremap_demo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");
volatile static unsigned long virt_addr; //虚拟地址
volatile static unsigned long *GPL2CON, *GPL2DAT, *GPL2PUD;
static void gpl2_remap(void){
//分析知,从0x1100_0100起映射12个字节即可
//一个ulong在32位机器上占用4个字节,寄存器都是ulong
unsigned long phys_addr = 0x11000100; //物理地址
virt_addr = (unsigned long)ioremap(phys_addr,12);//将指针强转为整形
//指定需要操作的寄存器地址
GPL2CON = (unsigned long *)(virt_addr+0x00);
GPL2DAT = (unsigned long *)(virt_addr+0x04);
GPL2PUD = (unsigned long *)(virt_addr+0x08);
}
static void gpl2_config(void){
//配置为输出
*GPL2CON &= 0xfffffff0;
*GPL2CON |= 0x1;
//配置为上拉
*GPL2PUD &= 0xfffc;
*GPL2PUD |= 0x3;
}
static void gpl2_on(void){
*GPL2DAT &= 0xfe;
*GPL2DAT |= 0x1;
}
static void gpl2_off(void){
*GPL2DAT &= 0xfe;
*GPL2DAT |= 0x0;
}
static int demo_init(void){
gpl2_remap(); //实现IO内存的映射,得到3个可控制的寄存器
gpl2_config(); //配置GPL2[0]为输出上拉模式
gpl2_on(); //控制输出寄存器为高
printk(KERN_EMERG "led is on!\n");
return 0;
}
static void demo_exit(void){
gpl2_off(); //控制输出寄存器为低
printk(KERN_EMERG "led is off!\n");
}
module_init(demo_init);
module_exit(demo_exit);
其中为什么不能用指针进行加法运算,非要用整形运算?
因为指针加1偏移可能不是1个字节,比如指针指向一个结构体,加1就会偏移一个结构体size,所以这里必须强转整形进行运算。或者如果知道偏移是4字节(32位机器),使用指针+1也可以。
验证结果
编写简单Makefile:参考这里
编译生成模块后,拷贝到开发板中:
-
加载驱动小灯亮,卸载驱动小灯灭;
关于GPIO的一些库函数
申请和释放GPIO
1
2
ret = gpio_request(EXYNOS4_GPL2(0),"TEST_GPIO"); /*申请GPIO资源*/
gpio_free(EXYNOS4_GPL2(0)); /*释放GPIO资源*/
配置GPIO
1
2
3
4
5
6
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_OUTPUT); /*设置输出模式*/
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_INPUT); /*设置输入模式*/
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_SFN(2)); /*设置特殊功能2模式*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_UP); /*上拉状态*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_DOWN); /*下拉状态*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_NONE); /*无上拉也无下拉*/
输出模式下改变GPIO电平
1
2
gpio_set_value(EXYNOS4_GPL2(0),0); /*置低*/
gpio_set_value(EXYNOS4_GPL2(0),1); /*置高*/
输入模式下获取GPIO电平
1
value = gpio_set_value(EXYNOS4_GPL2(0));
配置输入输出且设置电平
1
2
3
gpio_direction_output(EXYNOS4_GPL2(0),0); //初始化为输出,并输出低电平
gpio_direction_output(EXYNOS4_GPL2(0),1); //初始化为输出,并输出高电平
gpio_direction_input(EXYNOS4_GPL2(0)); //初始化为输入