我的板子上有4个led,对应的GPIO口是GPB5,GPB6,GPB8,GPB10
IO映射用的是静态映射的方式,静态映射的内容再arch/arm/mach-s3c2410/mach-smdk2410.c中,如果每记错就是这个路径
linux内核对着个soc支持还是很好的,硬件资源都已头文件的方式写在源码中了,但由于目录纷繁复杂,建议使用vim+ctag浏览代码
回头用空把我对静态映射的理解也写下来,现在就先默认映射都已经做好。
还是用了mach/gpio-fns.h中的s3c2410_gpio_cfgpin等配置gpio的函数,比较好用,很省心
先把代码贴上来再说
1. 头文件部分,没什么好说的,用什么就include进来
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#include<mach/regs-gpio.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/slab.h>
#include<linux/sched.h>
#include<linux/wait.h>
#include<mach/gpio-fns.h>
#define LED_MAJOR 249
#define DEVICE_NAME "myled"
2.一些宏定义
关于S3C2410_GPB(x)这个宏,这个宏定义再mach/gpio-nrs.h中,定义如下
#define S3C2410_GPB(_nr) (S3C2410_GPIO_B_START + (_nr))
在这个之前有个枚举的结构
enum s3c_gpio_number {
S3C2410_GPIO_A_START = 0,
S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A),
S3C2410_GPIO_C_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_B),
S3C2410_GPIO_D_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_C),
S3C2410_GPIO_E_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_D),
S3C2410_GPIO_F_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_E),
S3C2410_GPIO_G_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_F),
S3C2410_GPIO_H_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_G),
S3C2410_GPIO_J_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_H),
S3C2410_GPIO_K_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_J),
S3C2410_GPIO_L_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_K),
S3C2410_GPIO_M_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_L),
};
这里定义了一组START
再往上看
#define S3C2410_GPIO_NEXT(__gpio) \
((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 0)
在#define中,标准定义了#和##两种操作。#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串。
S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A)
这句话就可一翻译成
S3C2410_GPIO_B_START=S3C2410_GPIO_A_START + S3C2410_GPIO_A_NR + CONFIG_S3C_GPIO_SPACES3C2410_GPIO_A_NR定义为#define S3C2410_GPIO_A_NR (32)CONFIG_S3C_GPIO_SPACE 定义为 #define CONFIG_S3C_GPIO_SPACE 0那么最终S3C2410_GPIO_A_START=0S3C2410_GPIO_B_START=32以此类推,这个32实际上就是对应每个GPIO口的地址空间,也就是说GPB0=32,GPB1=33 ...这些都是偏移量,那基地址在哪里呢?这就要看s3c2410_gpio_cfgpin函数中的操作了这个追下去简直是个无底洞,有兴趣的可以试试,这里就不往下追了,继续看代码,关于GPB0~10的定义就清楚了
//***********************代码继续************************************
#define GPB0 (S3C2410_GPB(0))
#define GPB5 (S3C2410_GPB(5))
#define GPB6 (S3C2410_GPB(6))
#define GPB8 (S3C2410_GPB(8))
#define GPB10 (S3C2410_GPB(10))
//************************这里定义一个数组,保存每个LED对应的GPIO口,便于后面调用
static unsigned int led_index[5]={GPB0,GPB5,GPB6,GPB8,GPB10};
static int led_major=LED_MAJOR;
static int led_minor;
//定义led设备结构体,重点是要包含cdev,这个结构体相当于驱动程序挂接再内核驱动框架的接口,其他的参数都是根据自己需要定义的,比如led_status
struct led_dev
{
struct cdev cdev;
unsigned char led_status;
};
struct led_dev *led_devp;
//led的打开函数,驱动模块加载后,mknod相应的节点,open /dev/led就调用到了这个函数
//这里做了几件事情,首先是根据inode节点获得设备号,然后从设备号中分离出次设备号
//因为由4个led,他们用同样的驱动程序,也就是共用4个设备号,但如果想单独操作每个led的话,就要根据led0~led3来区别对待
//这个区别的方法就是用次设备号,这个次设备号一方面再mknod时指定,最重要的要再后面的模块init模块中建立相应的设备结构
//这个后面会看到
//得到了次设备号,就配置对应的GPIO口为输出模式
static int s3c2440_led_open(struct inode *inode,struct file *filp)
{
struct led_dev *dev;
dev=container_of(inode->i_cdev,struct led_dev,cdev);
filp->private_data=dev;
led_minor=MINOR(inode->i_rdev);
printk(KERN_NOTICE "minor=%d\n",led_minor);
//配置输出
s3c2410_gpio_cfgpin(GPB0,S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(GPB5,S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(GPB6,S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(GPB8,S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(GPB10,S3C2410_GPIO_OUTPUT);
//这里原来用的是iowrite这组函数操作GPIO,后来都改用上面的函数了
//iowrite32(GPBCON,S3C2410_GPBCON);
return 0;
}
//release函数,这里没做什么事
static int s3c2440_led_release(struct inode *inode,struct file *filp)
{
return 0;
}
//ioclt函数,这是一个很有用的函数,后面对led的操作的应用程序就是调用的这个函数操作led的
//通过传递的cmd参数,对led进行操作
static int s3c2440_led_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
unsigned int tmp;
switch(cmd)
{
case 0:
//tmp=ioread32(S3C2410_GPBDAT);
//tmp=tmp & ~led_index[led_minor];
//printk(KERN_NOTICE "tmp=%d\n",tmp);
//iowrite32(tmp,S3C2410_GPBDAT);
s3c2410_gpio_setpin(led_index[led_minor],0);
return 0;
case 1:
//tmp=ioread32(S3C2410_GPBDAT);
//tmp=tmp | led_index[led_minor];
//printk(KERN_NOTICE "tmp=%d\n",tmp);
//iowrite32(tmp,S3C2410_GPBDAT);
s3c2410_gpio_setpin(led_index[led_minor],1);
return 0;
}
return 0;
}
//函数挂接
static struct file_operations s3c2440_led_ops=
{
owner:THIS_MODULE,
open:s3c2440_led_open,
release:s3c2440_led_release,
ioctl:s3c2440_led_ioctl,
};
//设定cdev,这是对字符设备的通用做法
static void led_setup_cdev(struct led_dev* dev,int index)
{
int err,devno=MKDEV(led_major,index);
cdev_init(&dev->cdev,&s3c2440_led_ops);
dev->cdev.owner=THIS_MODULE;
dev->cdev.ops=&s3c2440_led_ops;
err=cdev_add(&dev->cdev,devno,1);
if(err)
{
printk(KERN_NOTICE "Error %d adding led%d",err,index);
}
}
//init模块,模块加载的时候会调用这个函数。前面说过,如果要支持多个设备,也就是多个设备号,那么要申请多个设备号,同时申请多个对应的设备结构
static int __init s3c2440_led_init(void)
{
int result;
dev_t devno=MKDEV(led_major,0);
result=register_chrdev_region(devno,5,"testled");
//printk(KERN_NOTICE "devno:%x\n",devno);
led_devp=kmalloc(5*sizeof(struct led_dev),GFP_KERNEL);
if(!led_devp){
result=-ENOMEM;
}
//申请了5个次设备号,对应要申请5个设备结构体
memset(led_devp,0,5*sizeof(struct led_dev));
led_setup_cdev(&led_devp[0],0);
led_setup_cdev(&led_devp[1],1);
led_setup_cdev(&led_devp[2],2);
led_setup_cdev(&led_devp[3],3);
led_setup_cdev(&led_devp[4],4);
printk(KERN_NOTICE "init module, result=%d\n",result);
return result;
}
//卸载函数
static void __exit s3c2440_led_exit(void)
{
cdev_del(&led_devp->cdev);
kfree(led_devp);
unregister_chrdev_region(MKDEV(led_major,0),1);
printk(KERN_NOTICE "exit module\n");
}
MODULE_AUTHOR("weicz");
MODULE_LICENSE("Dual BSD/GPL");
module_init(s3c2440_led_init);
module_exit(s3c2440_led_exit);
至此驱动部分代码就完成了
下面是一个测试程序用来测试这段驱动程序,很简单就不说什么了
#include<stdio.h>
#include<fcntl.h>
int main(int argc,char* argv[])
{
int fd;
char c;
if(argc!=2){
printf("usage: ledapp /dev/led");
return -1;
}
fd=open(argv[1],O_RDWR);
if(fd<0){
printf("open error fd=%d\n",fd);
return -2;
}
while(1){
c=getchar();
if(c=='0')
ioctl(fd,0,0);
else if(c=='1')
ioctl(fd,1,0);
else if(c=='q')
break;
}
}
关于编译,编译模块制定的选项比较多,每次写很麻烦,我写成一个Makefile,,只要在同一目录下make一下就好
Makefilene内容
obj-m :=led.o
KERNEL_DIR := /home/huniu/sources/kernel/linux-2.6.35
PWD :=$(shell pwd)
all:
make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
clean:
rm *.o *.ko *.mod.c
.PHONY:clean
这里的KERNEL_DIR根据自己的源码路径修改
那个应用程序的编译比较简单
arm-linux-gcc ledapp.c -o ledapp
如果文件系统没有移植glibc库的话,要静态编译才能使用stdio.h的函数,也就是编译选项中加入-static选项
arm-linux-gcc ledapp.c -o ledapp -static
led的驱动就写到这里,后面写按键驱动