linux设备驱动(一)---字符设备之led驱动

我的板子上有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_SPACE
S3C2410_GPIO_A_NR定义为#define S3C2410_GPIO_A_NR   (32)
CONFIG_S3C_GPIO_SPACE 定义为 #define CONFIG_S3C_GPIO_SPACE 0 
那么最终
S3C2410_GPIO_A_START=0
S3C2410_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的驱动就写到这里,后面写按键驱动


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值