分享

TQ2440按键,LED驱动

 changqiong0606 2012-12-17

TQ2440按键,LED驱动


  • 硬件表述
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:
key
/**
 * K1    GPF1    EINT1
 * K2    GPF4    EINT4
 * K3    GPF2    EINT2
 * K4    GPF0    EINT0
 */
int

  • 底层硬件设置程序:
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



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多