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