我的第一个linux驱动-s3c6410 gpio

时间:2022-01-26 02:14:40

前段时间学着写了第一个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);
}