TQ2440按键,LED驱动
LED:
|
|
/**S3C2410_GPBCON定义在arch/arm/mach-s3c2410/include/mach/regs-gpio.h
*
* LED1 GPB5 S3C2410_GPBCON[11,10]=01 for output
* LED2 GPB6 [13,12]
* LED3 GPB7 [15,14]
* LED4 GPB8 [17,16]
*/
|
key and interrupt:
|
|
/**
* K1 GPF1 EINT1
* K2 GPF4 EINT4
* K3 GPF2 EINT2
* K4 GPF0 EINT0
*/
|
|
LED
|
void led_init(void)
{
int tmp;
#ifdef DEBUG
printk("LED init.\n");
#endif
//LED1 GPB5 GPBCON[11,10]=01
tmp=__raw_readl(S3C2410_GPBCON);
tmp &= (~(0x03 << 10));
tmp |= (0x01 << 10);
__raw_writel(tmp,S3C2410_GPBCON);
//LED2 GPB6 [13,12]
tmp=__raw_readl(S3C2410_GPBCON);
tmp &=~(0x03<<12);
tmp |= (0x01 << 12);
__raw_writel(tmp,S3C2410_GPBCON);
//LED3
tmp=__raw_readl(S3C2410_GPBCON);
tmp &=~(0x03<<14);
tmp |= (0x01 << 14);
__raw_writel(tmp,S3C2410_GPBCON);
//LED4
tmp=__raw_readl(S3C2410_GPBCON);
tmp &=~(0x03<<16);
tmp |= (0x01 << 16);
__raw_writel(tmp,S3C2410_GPBCON);
}
|
初始化GPIOB for LED
|
|
void led_on(int n)
{
int data;
if(n<=0||n>4)
printk("NO LED %d.\n",n);
data=__raw_readl(S3C2410_GPBDAT);
data &=~(LED_ON<<(n+4));//GPB5~GPB8
__raw_writel(data,S3C2410_GPBDAT);
#ifdef DEBUG
printk("LED%d on.\n",n);
#endif
}
|
点亮一个LED,n=1,2,3,4
|
|
void led_off(int n)
{
int data;
if(n<=0||n>4)
printk("NO LED %c.\n",n);
data=__raw_readl(S3C2410_GPBDAT);
data |=LED_ON<<(n+4);
__raw_writel(data,S3C2410_GPBDAT);
#ifdef DEBUG
printk("LED%d off.\n",n);
#endif
}
|
熄灭一个LED,n同上
|
|
KEY and interrupt
|
void tq_key_init(void)
{
int tmp;
#ifdef DEBUG
printk("key init.\n");
#endif
//set gpiof for EINT
//K4
tmp=__raw_readl(S3C2410_GPFCON);
tmp &= ~S3C2410_GPF0_EINT0;
tmp &= ~(S3C2410_GPF0_EINT0>>1);
tmp |= S3C2410_GPF0_EINT0;
__raw_writel(tmp,S3C2410_GPFCON);
//K1
tmp=__raw_readl(S3C2410_GPFCON);
tmp &= ~S3C2410_GPF1_EINT1;
tmp &= ~(S3C2410_GPF1_EINT1>>1);
tmp |= S3C2410_GPF1_EINT1;
__raw_writel(tmp,S3C2410_GPFCON);
//K3
tmp=__raw_readl(S3C2410_GPFCON);
tmp &= ~S3C2410_GPF2_EINT2;
tmp &= ~(S3C2410_GPF2_EINT2>>1);
tmp |= S3C2410_GPF2_EINT2;
__raw_writel(tmp,S3C2410_GPFCON);
//K2
tmp=__raw_readl(S3C2410_GPFCON);
tmp &= ~S3C2410_GPF4_EINT4;
tmp &= ~(S3C2410_GPF4_EINT4>>1);
tmp |= S3C2410_GPF4_EINT4;
__raw_writel(tmp,S3C2410_GPFCON);
}
|
设置GPIOF的相关引脚为扩展功能,即作为外部中断输入引脚。
|
|
void int_init_key(void)
{
//init the interrupt system
//set mask
int tmp;
#ifdef DEBUG
printk("irq init.\n");
#endif
tmp=__raw_readl(S3C2410_INTMSK);
tmp =tmp &(~(0x01)) &(~(0x01<<1)) &(~(0x01<<2) &(~(0x01<<4)));
__raw_writel(tmp,S3C2410_INTMSK);
}
|
设置中断系统,只需设置中断屏蔽寄存器。
|
static int __init TQ2440_chardev_init(void)
{
......
dev_number=MKDEV(major_number,0);//根据主设备号得到完整设备号
printk("before register,dev_number: %d \n",dev_number);
if(major_number)//手工设置设备号的方式
{
result=register_chrdev_region(dev_number,1,"TQ2440_chardev");
......
}
else//动态分配设备号
{
result=alloc_chrdev_region(&dev_number,0,1,"TQ2440_chardev");
major_number=MAJOR(dev_number);
......
}
key_devp=kmalloc(sizeof(*key_devp),GFP_KERNEL);
if(!key_devp)
{
printk("failed kmalloc for key_devp.\n");
}
memset(key_devp,0,sizeof(*key_devp));
......
cdev_init(&key_devp->cdev,&key_fp);//初始化字符设备
key_devp->cdev.ops=&key_fp;
key_devp->cdev.owner = THIS_MODULE;//指定设备的拥有者
#ifdef DEBUG
printk("after register device number:%d \n",dev_number);
#endif
err_number=cdev_add(&key_devp->cdev,dev_number,1);//向系统添加字符设备
if(err_number!=0)
{
printk("add device failed!Error number is%d \n",err_number);
}
tq_key_init();
led_init();
int_init_key();
//申请中断
for(i=0;i<KEY_NUM;i++)
{
int ret=request_irq(key[i].irq_num,key_inthandler,0,"chardev",NULL);第三个参数flags在linux/interrupt.h中定义,已不用SA_SHIFT等。
if(ret)
printk(KERN_ERR "IRQ%d for K%d already in
use.\n",key[i].irq_num,key[i].Kn);
}
return 0;
}
|
中断处理程序:
|
static irqreturn_t key_inthandler(int irq,void *dev_id)
{
int i;
dev_id=dev_id;
led_off_all();
for(i=0;i<KEY_NUM;i++)
{
if(irq==key[i].irq_num)
{
led_on(key[i].Kn);
break;
}
}
printk("K%d pressed.\n",key[i].Kn);
return IRQ_HANDLED;
}
|
关于中断处理程序的函数原型,我用的是2.6.31内核,
只有两个参数。
在include/linux/interrupt.h中有这么一句: typedef irqreturn_t (*irq_handler_t)(int, void *);
这一句的意思是:定义一种函数指针类型irq_handler_t,
这种函数的返回类型为irqreturn_t,参数列表为int, void*
所以,只要把上述程序中的static irqreturn_t myinterrupt(int irq,
void *dev_id, struct pt_regs *regs)以前版本中可能有第三个参数。
去掉第三个参数就可以消除 warning: passing argument 2 of
‘request_irq’ from incompatible pointer type
|
Makefile 很关键的部分(单模块多c文件的编译) |
module_name:=TQ2440chardev
$(module_name)-objs := TQ2440_chardev.o TQ2440_led.o TQ2440_key.o TQ2440_irq.o
obj-m:=$(module_name).o
KERNELDIR ?=/home/lzj/linux-2.6.31
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gcc test.c -o test
install:
cp /home/lzj/work/key_dev/$(module_name).ko /home/lzj/busybox-1.16.0/_install/home
cp /home/lzj/work/key_dev/test /home/lzj/busybox-1.16.0/_install/home
/home/lzj/mkyaffs2image /home/lzj/busybox-1.16.0/_install/ /home/lzj/yaffs2.bin
cp /home/lzj/yaffs2.bin /media/学习/tools
clean:
rm -rf *.o *~ *.ko *.mod.c *.order *.symvers .*.o.cmd .*.ko.cmd
|
|
|
最后,附上所有源文件:TQ2440chardev
注意:交叉编译模板前要先编译内核,并且保留内核编译的中间文件,在此基础上编译模块,否则无法编译模块。当然,交叉编译器要先配置好。
以上源文件中只完成了LED和按键功能,还简单实现了用ioctl函数控制LED的亮和灭。这个接口可以在test.c中调用,用来控制LED。read,write,open等接口都未实现。
如何在test中控制LED:
系统启动之后首先加载驱动程序模块:insmod TQ2440chardev.ko
然后创建设备节点(先用cat /proc/devices查出模块的主设备号N):mknod /dev/TQ2440chardev c N 0
最后运行test。
改进:
加入自动生成/dev/TQ2440chardev0设备节点。
原理:busybox的mdev. 它的作用,就是在系统启动和热插拔
或动态加载驱动程序时,自动产生驱动程序所需的节点文件。
执行mdev -s :以‘-s’为参数调用位于/sbin目录写的mdev(其实是个链接,作用是传递参数给/bin目录下的busybox程序并调用它),mdev扫描 /sys/class 和/sys/block 中所有的类设备目录,如果在目录中含有名为“dev”的文件,且文件中包含的是设备号,则mdev就利用这些信息为这个设备在/dev下创建设备节点文件。一般只在启动时才执行一次 “mdev -s”。(见rcS脚本文件)
热插拔事件:由于启动时运行了命令:echo /sbin/mdev
> /proc/sys/kernel/hotplug
,那么当有热插拔事件产生时,内核就会调用位于/sbin目录的mdev。这时mdev通过环境变量中的 ACTION
和DEVPATH,(这两个变量是系统自带的)来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性
文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。
#include <linux/device.h> /*class_create*/
全局变量:struct class *my_class;
|
关于兼容性问题:
2.6.31版内核有device_create
,device_destroy。取代原来内核中
的class_device_create,
class_device_destroy.
函数申明在linux/device.h中:
extern struct device *device_create(
struct class *cls,
struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
__attribute__((format(printf, 5,
6)));
extern void device_destroy(
struct class *cls,
dev_t devt);
|
|
在TQ2440_chardev_init函数中加入:
my_class = class_create(THIS_MODULE, "gpio_class");
if(IS_ERR(my_class))
{
printk("Err: failed in creating class.\n");
return -1;
}
/* register your own device in sysfs, and this will cause mdev to create corresponding device node */
device_create(my_class,NULL, dev_number, NULL, "TQ2440chardev%d" ,0);
|
|
在TQ2440_chardev_exit中加入:
device_destroy(my_class, dev_number);
class_destroy(my_class);
|
改进版本的源代码:newTQ2440chardev
|