前段时间学着写了第一个linux下的驱动,很简单,基于友善之臂的tiny6410,通过控制GPIOK4-7输入输出来控制板上的4个led。led的驱动友善已经提供,不过我自己写的有些不一样,是按照标准的char驱动来写的,下面是全过程。
注意:此代码基于友善之臂提供的已经移植好的linux2.6.36核心
第一步编写驱动代码
//tiny6410_gpio.c
#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/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.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 <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio-bank-e.h>
#include <mach/gpio-bank-k.h>
#define DEVICE_NAME "tiny6410_gpio" //驱动名字
#define DEVICE_MAJOR 100 //主设备号
static int tiny6410_gpio_major=DEVICE_MAJOR;
//自己定义的描述gpio的结构体
struct tiny6410_gpio_dev
{
struct cdev cdev;//描述字符型设备的cdev结构体
unsigned *conf0;
unsigned *conf1;
unsigned *data;
unsigned *pud;
};
//初始化dev内的寄存器地址
struct tiny6410_gpio_dev dev=
{
.conf0=S3C64XX_GPKCON,
.conf1=S3C64XX_GPKCON1,
.data=S3C64XX_GPKDAT,
.pud=S3C64XX_GPKPUD,
};
//这个驱动仅实现read和write函数,一个字节代表一个灯的状态
ssize_t tiny6410_gpio_read (struct file *filp, char __user *buf_user, size_t size, loff_t *f_pos)
{
unsigned val,i;
val=size;
i=*f_pos;
printk("start read start:%x size:%d/r/n",i,val);//输出调试信息。很奇怪,直接输出*f_pos和size数值回不对,只能中转一下
if(*f_pos>=4 || size==0)
{
put_user(0,buf_user);
return 0;
}
val=readl(dev.data);
printk("raw val:%x/r/n",val);
val=(val>>(4+*f_pos))&0xf;
for(i=0;i<((size+*f_pos)>4?4-*f_pos:size);i++)
put_user((val>>i)&0x01,buf_user+i);
printk("get val:%x max index:%d/r/n",val,i);
return 0;
}
//ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t tiny6410_gpio_write (struct file *filp, const char __user *buf_user, size_t size, loff_t *f_pos)
{
unsigned char writesize,i,data[4];
unsigned raw;
printk("start write start:%x,size:%d",*f_pos,size);
if(*f_pos>=4)
{
return size;
}
writesize=4-copy_from_user(data,buf_user,size);
if(writesize==0)
{
printk("write size 0!/r/n");
return size;
}
raw=readl(dev.data);
printk("source val:%x,write size:%d/r/n",raw,writesize);
for(i=0;i<((writesize+*f_pos)>4?4-*f_pos:writesize);i++)
{
raw&=~(1<<(i+*f_pos+4));
raw|=((data[i]?1:0)<<(i+4+*f_pos));
}
writel(raw,dev.data);
printk("write end! dest val:%x max index:/r/n",raw,i);
return 4-writesize;
}
//这个结构体内的函数是驱动编写主要修改的地方
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
//.unlocked_ioctl = sbc2440_leds_ioctl,
.read = tiny6410_gpio_read,
.write = tiny6410_gpio_write,
};
static int tiny6410_gpio_setup_cdev()
{
int devno=MKDEV(tiny6410_gpio_major,0);
cdev_init(&dev.cdev,&dev_fops);
dev.cdev.owner=THIS_MODULE;
return cdev_add(&dev.cdev,devno,1);//添加一个cdev结构体
}
//驱动初始化部分
static int __init dev_init(void)
{
int ret;
unsigned int val;
printk("start init");
dev_t devno=MKDEV(tiny6410_gpio_major,0);
//第一步:注册一个cdev设备
if(tiny6410_gpio_major)
//原型:register_chrdev_region(dev_t from,unsigned count,const char *name)
ret=register_chrdev_region(devno,1,DEVICE_NAME);
else
{
//原型:alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
ret=alloc_chrdev_region(&devno,0,1,DEVICE_NAME);
tiny6410_gpio_major=MAJOR(devno);
}
if (ret<0)
return ret;
//第二步:初始化cdev结构体,并注册cdev
ret=tiny6410_gpio_setup_cdev();
if(ret!=0)
return ret;
//开始用户初始化阶段,这里设置端口类型为输出,并拉高端口
val = readl(dev.conf0);
val = (val & ~(0xffffU<<16))|(0x1111U<<16);
writel(val,dev.conf0);
val = readl(dev.data);
val |= (0xF << 4);
writel(val,dev.data);
return 0;
}
static void __exit dev_exit(void)
{
cdev_del(&dev.cdev);//删除cdev结构
unregister_chrdev_region(MKDEV(tiny6410_gpio_major,0),1);//注销
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("quqw");
第二步,修改源代码同级目录下的Kconfig文件,这样编译内核配置文件的时候就能看到这个驱动的选项了。
在适当的位置添加如下命令,仅当CPU_S3C6410被定义的情况下此选项才可选。
config TINY6410_GPIO
tristate "GPIO CTRL Support for Mini6410 "
depends on CPU_S3C6410
default y
help
This option enables support for GPIOK4-7 read and write
on Tiny6410 boards.
第三步,修改源代码同级目录下的Makefile,添加如下行,Makefile和config文件是配合使用的,当.config中TINY6410_GPIO
被定义的情况下就会对tiny6410_gpio.o进行编译
obj-$(CONFIG_TINY6410_GPIO) +=tiny6410_gpio.o
这样整个驱动就写好了,到源代码根目录下运行make xconfig,就能找到TINY6410_GPIO选项了。选中后保存退出,接着运行make指令编译内核。
新内核加载后,先使用命令mknod /dev/gpio c 100 0建立驱动节点,这样对gpio的操作就只要像操作文件一样对/dev/gpio读写就可以了。我用QT写了一个小程序来验证驱动正确性,以下是代码片段:
void MainWindow::on_btn_led3_clicked()
{
int fd,i;
char buf[]={0,0,0,0};
fd=::open("/dev/gpio",O_RDWR);
if(fd<0)
{
QMessageBox::warning(this,tr("Warning"),tr("Can't open file"),QMessageBox::Yes);
return;
}
::read(fd,buf,4);
if(buf[3])
buf[3]=0;
else
buf[3]=1;
::write(fd,buf,4);
::close(fd);
}