Linux中控制GPIO点亮LED的方法有好几种。一种是使用内核提供的专门用来控制GPIO的函数来点亮LED,如:
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 0);
一种是通过IO内存获取硬件地址从而控制GPIO来点亮LED,如:
void *ioremap(unsigned long phys_addr, unsigned long size);
void ioumap(void *addr);
这里主要介绍第二种点亮LED方式。
1. 理论支撑
1.1 IO内存分配和映射
1.1.1在使用IO内存之前首先分配IO内存区域。分配和撤销IO内存区域使用的函数如下:
#include <linux/ioport.h>
struct resource *request_mem_region(unsigned long start,unsigned long len,char *name);
参数说明:start 分配内存起始地址
len 分配内存长度
返回 成功非NULL,否则返回NULL。
相应的释放函数:
void release_mem_region(unsigned long start,unsigned long len);
1.1.2在linux中不能使用实际的物理地址,要对指定的物理地址进行操作必须先将物理地址映射到虚拟地址中。下面的函数就是实现物理地址到虚拟地址的映射:
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
参数说明:phys_addr 需要访问的物理内存(寄存器)的首地址
size 内存区域大小
返回与该段物理地址对应的虚拟地址
相应的撤销映射关系的函数是:
void ioumap(void *addr);
1.1.3使用IO内存时,request_mem_region函数并不是必须的,该函数只是在内核中标志该内存区域已经分配出去,不能再进行分配出去。但是,这不不是说别的驱动不能再使用该IO内存。至于能不能使用,分下面2种情况:
1. 两个驱动都用request_mem_region分配相同的IO内存,则两个驱动只能有一个驱动可以使用。
2. 两个驱动只有一个使用了request_mem_region函数,那么这两个驱动可以同时使用,并可以同时访问该IO内存。
1.2 访问IO内存
ioremap函数的返回值可以直接当做指针(指向对应的物理内存(寄存器)地址)使用,但是这种使用方式不具有可移植性。使用下面的访问IO内存的专用函数符合可移植性要求。
从 I/O 内存读取, 使用下列函数之一:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
参数:addr是从 ioremap 获得的地址(可能包含一个整数偏移量)
返回值: 从给定 I/O 内存读取的到的值
相应的有一系列函数来写 I/O 内存:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
参数:value 要写入IO内存值
addr 所要写入的IO内存地址
2. 具体操作与实现
2.1只使用ioremap映射GPIO寄存器实现LED点亮
内核模块代码(基于mini2440开发板的4个LED) 如下:
/*************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址
volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址
void led_device_init(void)
{
// 0x56000000 + 0xd0 包揽全所有的IO引脚寄存器地址
phys = 0x56000000; // 0x56000000=GPACON
//在虚拟地址空间中申请一块长度为0xd0的连续空间
//这样,物理地址phys到phys+0xd0对应虚拟地址virt到virt+0xd0
virt =(unsigned long)ioremap(phys, 0xd0);
GPBCON = (unsigned long *)(virt + 0x10);//指定需要操作的三个寄存器的地址
GPBDAT = (unsigned long *)(virt + 0x14);
GPBUP = (unsigned long *)(virt + 0x18);
}
//led配置函数,这种是裸板控制寄存器的方式
void led_configure(void)
{
*GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule
*GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
*GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8); //禁止上拉电阻
}
void led_on(void) //点亮led
{
*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);
}
void led_off(void) //灭掉led
{
*GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
}
static int __init test_init(void) //模块初始化函数
{
led_device_init(); //实现IO内存的映射
led_configure(); //配置GPB5-8为输出
led_on();
printk("hello led!\n");
return 0;
}
static void __exit test_exit(void) //模块卸载函数
{
led_off();
iounmap((void *)virt); //撤销映射关系
printk("bye\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hrz.2ml");
MODULE_VERSION("v0.1");
/*************************************************************************/
该模块在安装时点亮所有的LED灯,模块卸载时关闭所有LED灯。这个模块只使用了ioremap一个函数,把寄存器地址映射到虚拟地址,其余的操作都是基于裸板控制GPIO寄存器的方式。
2.2使用ioremap映射GPIO寄存器,内核函数读写寄存器实现LED点亮
上面模块的实现方式像我们在裸板上点亮LED一样,对于从裸机转到操作系统的同学,这样的控制方式很容易理解。但是,这样的方式不具有可移植性,我们应该尽量使用内核提供的IO内存访问接口,即我们上面介绍的函数:
unsigned int ioread32(void *addr);
void iowrite32(u32 value, void *addr);
接着修改一下函数(蓝色字体为添加代码),以内核接口实现LED点亮:
/*************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址
volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址
unsigned long reg;
void led_device_init(void)
{
phys = 0x56000000; //指定物理地址 0x56000000=GPACON
virt =(unsigned long)ioremap(phys, 0xd0);
GPBCON = (unsigned long *)(virt + 0x10);
GPBDAT = (unsigned long *)(virt + 0x14);
GPBUP = (unsigned long *)(virt + 0x18);
}
void led_configure(void) //led配置函数
{
//*GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule
//*GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
reg=ioread32(GPBCON);//读取GPBCON值到reg中
reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);
reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
iowrite32(reg,GPBCON);//把修改后的reg值写回GPBCON
//*GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8); //禁止上拉电阻
reg=ioread32(GPBUP);
reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8); //禁止上拉电阻
iowrite32(reg,GPBUP);
}
void led_on(void) //点亮led
{
//*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);
reg=ioread32(GPBDAT);
reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);
iowrite32(reg,GPBDAT);
}
void led_off(void) //灭掉led
{
//*GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
reg=ioread32(GPBDAT);
reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
iowrite32(reg,GPBDAT);
}
static int __init test_init(void) //模块初始化函数
{
led_device_init();
led_configure();
led_on();
printk("hello led!\n");
return 0;
}
static void __exit test_exit(void) //模块卸载函数
{
led_off();
iounmap((void *)virt);
printk("bye\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hrz.2ml");
MODULE_VERSION("v0.2");
/***********************************************************************/
这里只是把访问内存的方式改了,由原来的一行代码变成三行。把模块安装到内核中,结果一样。
2.3添加内存分配函数实现LED点亮
在这个模块中添加request_mem_region()进行内存的分配。而调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
模块代码如下:
/*************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
#include <linux/ioport.h>
volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址
volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址
unsigned long reg;
struct resource *led_resource;
void led_device_init(void)
{
phys = 0x56000000;
virt =(unsigned long)ioremap(phys, 0xd0);
GPBCON = (unsigned long *)(virt + 0x10);
GPBDAT = (unsigned long *)(virt + 0x14);
GPBUP = (unsigned long *)(virt + 0x18);
}
void led_configure(void) //led配置函数
{
//*GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule
//*GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
reg=ioread32(GPBCON);
reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);
reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
iowrite32(reg,GPBCON);
//*GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8); //禁止上拉电阻
reg=ioread32(GPBUP);
reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8); //禁止上拉电阻
iowrite32(reg,GPBUP);
}
void led_on(void) //点亮led
{
//*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);
reg=ioread32(GPBDAT);
reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);
iowrite32(reg,GPBDAT);
}
void led_off(void) //灭掉led
{
//*GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
reg=ioread32(GPBDAT);
reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
iowrite32(reg,GPBDAT);
}
static int __init test_init(void) //模块初始化函数
{
led_device_init();
led_resource=request_mem_region(phys,0xd0,"LED_MEM");
if(led_resource==NULL){
printk("request mem for led error!\n");
return -ENOMEM;
}
led_configure();
led_on();
printk("hello led!\n");
return 0;
}
static void __exit test_exit(void) //模块卸载函数
{
if(led_resource!=NULL){
led_off();
iounmap((void *)virt);
release_mem_region(phys,0xd0);
}
printk("bye\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hrz.2ml");
MODULE_VERSION("v0.3");
/*************************************************************************/
结果还是一样。安装后执行:
# cat /proc/iomem
就会看到:
56000000-560000cf : LED_MEM
2.4最终的LED驱动程序
/*********************************************************************/
#include <asm/io.h>
#include <asm/sizes.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <asm/atomic.h>
volatile unsigned long virt, phys;
volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;
unsigned long reg;
struct resource *led_resource;
dev_t my_major=0;//主设备号
#define MY_DEVNAME " leds" //设备名称
//设备结构体
struct my_led {
struct cdev my_cdev;
};
struct my_led *my_ledp;
struct class *my_class;
void led_device_init(void)
{
//0x56000000=GPACON 0x56000000 + 0xd0 包揽所有的IO引脚寄存器地址
phys = 0x56000000;
//在虚拟地址空间中申请一块长度为0xd0的连续空间
//这样,物理地址phys到phys+0xd0对应虚拟地址virt到virt+0xd0
virt =(unsigned long)ioremap(phys, 0xd0);
GPBCON = (unsigned long *)(virt + 0x10);
GPBDAT = (unsigned long *)(virt + 0x14);
GPBUP = (unsigned long *)(virt + 0x18);
}
void led_configure(void)
{
reg=ioread32(GPBCON);
reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);
reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output
iowrite32(reg,GPBCON);
reg=ioread32(GPBUP);
reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);
iowrite32(reg,GPBUP);
}
void led_on(void)
{
//*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);
reg=ioread32(GPBDAT);
reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);
iowrite32(reg,GPBDAT);
}
static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case 0:
case 1:
if (arg > 4)
{
return -EINVAL;
}
reg=ioread32(GPBDAT);
if(cmd==1)
reg &= ~(1 << (arg+5));
if(cmd==0)
reg |= (1 << (arg+5));
iowrite32(reg,GPBDAT);
return 0;
default:
return -EINVAL;
}
}
void led_off(void)
{
reg=ioread32(GPBDAT);
reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);
iowrite32(reg,GPBDAT);
}
static const struct file_operations my_led_fops={
.owner=THIS_MODULE,
.ioctl=leds_ioctl,
};
static int __init test_init(void)
{
dev_t devno=MKDEV(my_major,0);
int result;
led_device_init();
led_resource=request_mem_region(phys,0xd0,"LED_MEM");
if(led_resource==NULL){
printk("request mem for led error!\n");
return -ENOMEM;
}
led_configure();
led_on();
//设备号申请
if(my_major) {
result=register_chrdev_region(devno,1,MY_DEVNAME);
}
else {
result=alloc_chrdev_region(&devno,0,1,MY_DEVNAME);
my_major=MAJOR(devno);
}
if(result<0)
return result;
//设备结构体内存申请并初始化
my_ledp=kmalloc(sizeof(struct my_led),GFP_KERNEL);
if(!my_ledp) {
result=-ENOMEM;
goto fail_malloc;
}
memset(my_ledp,0,sizeof(struct my_led));
//添加led的cdev
cdev_init(&my_ledp->my_cdev,&my_led_fops);
my_ledp->my_cdev.owner=THIS_MODULE;
cdev_add(&my_ledp->my_cdev,devno,1);
//创建设备文件
my_class=class_create(THIS_MODULE,"my_class");
device_create(my_class,NULL,MKDEV(my_major,0),NULL,"my_led""%d",0);
printk("hello led!\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
return 0;
}
static void __exit test_exit(void)
{
if(led_resource!=NULL){
led_off();
iounmap((void *)virt);
release_mem_region(phys,0xd0);
}
cdev_del(&my_ledp->my_cdev);
kfree(my_ledp);
unregister_chrdev_region(MKDEV(my_major,0),1);
device_destroy(my_class,MKDEV(my_major,0));
class_destroy(my_class);
printk("bye\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hrz.2ml");
MODULE_VERSION("v0.4");
/*********************************************************************/
3. 总结
IO内存的分配以及使用如下图所示:
其中第一步和最后一步可以省去。如果没有省去,那么所分配IO内存就会在内核中标志已经被使用,其他驱动再去分配该IO内存就会失败。但是如果驱动程序不使用request_mem_region()分配IO内存而是映射后直接操作该IO内存是允许的。