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的驅動就寫到這裏,後面寫按鍵驅動


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章