10-1ADC驱动程序
10-1-1说明
程序源代码说明:
驱动源代码所在目录
drivers/spi/
驱动程序名称
mcp3201.c
设备号
mcp3201属于杂项设备,设备自动生成
设备名
/dev/mcp3201
测试程序源代码目录
Examples/Drivers/ADC
测试程序名称
test-mcp3201.c
测试程序可执行文件名称
test-mcp3201
要写实际的驱动,就必须了解相关的硬件资源,比如用到的寄存器,物理地址,中断等,在这里,mcp3201是一个很简单的例子,它用到了如下硬件资源。
开发板上所用到的1个ADC的硬件资源:
ADC
对应的IO寄存器名称
对应的CPU引脚
Mcp3201
Spi0
G16
要操作所用到的IO口,就要设置它们所用到的寄存器,我们可以调用一些现成的函数或者宏,在我们的驱动中使用的是mcp3201_open()来进行控制的。
函数mcp3201_open()的定义在我们的驱动程序中,接下来的驱动程序代码将给出示意。
在下面的驱动代码中,你将看到调用mcp3201_open()函数来对设备实施初始化并打开,除此之外,还需要调用一些和设备驱动密切相关的基本函数,如注册设备misc_register,填写驱动函数结构file_operations,以及像Hello,Module中那样的module_init和module_exit函数等。
有些函数并不是必须的,随着你对Linux驱动开发的进一步了解和的代码,你自然明白。下面是我们为mcp3201编写的驱动代码清单。
10-1-2驱动程序清单
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineSB2F_BOARD_SPI0_BASE0xbfe80000
structspi_device*adc;
staticintmcp3201_open(structinode*inode,structfile*file){
//取消片选CS3
writeb(0xff,SB2F_BOARD_SPI0_BASE+REG_SPCSR);
//复位SPI控制寄存器工作
writeb(0x10,SB2F_BOARD_SPI0_BASE+REG_SPCR);
//重置状态寄存器SPSR
writeb(0xc0,SB2F_BOARD_SPI0_BASE+REG_SPSR);
//设置外部寄存器
writeb(0x02,SB2F_BOARD_SPI0_BASE+REG_SPER);
//禁用SPIFLASH读
writeb(0x30,SB2F_BOARD_SPI0_BASE+REG_SPPR);
//配置SPI时序
writeb(0xd3,SB2F_BOARD_SPI0_BASE+REG_SPCR);
//设置片选CS3
writeb(0x7f,SB2F_BOARD_SPI0_BASE+REG_SPCSR);
return0;
}
staticintmcp3201_close(structinode*inode,structfile*file){
//取消对CS3的片选
writeb(0xff,SB2F_BOARD_SPI0_BASE+REG_SPCSR);
return0;
}
staticssize_tmcp3201_read(structfile*file,char__user*buf,size_tcount,loff_t*ptr)
{
ssize_tretval;
unsignedcharval[2]={0x0};
unsignedchartx_buf[1]={0x0};
unsignedcharrx_buf[2]={0};
//发送0个字节的命令到spi从机,并从spi从机中读取2字节的数据到缓冲区
retval=spi_write_then_read(adc,tx_buf,0,rx_buf,2);
if(retval
dev_err(&adc->dev,"error%dreadingSR\n",(int)retval);
returnretval;
}
//对从mcp3201读来的数据,按照其datasheet的要求取出编码
val[0]=rx_buf[1]>>1;
val[0]|=(rx_buf[0]&0x01)<
val[1]=(rx_buf[0]>>1)&0xf;
//将从mcp3201取出的数据合理编码,并传送到用户空间供用户使用
if(copy_to_user(buf,val,2))
{
printk("Copydatatouserspaceerror!\n");
return-EFAULT;
}
return0;
}
staticint__devinitmcp3201_probe(structspi_device*spi)
{
structspi_device*spi0;
spi0=spi;
adc=spi0;
return0;
}
staticint__devexitmcp3201_remove(structspi_device*spi)
{
writeb(0xff,SB2F_BOARD_SPI0_BASE+REG_SPCSR);
return0;
}
staticstructspi_drivermcp3201_driver={
.driver={
.name="mcp3201",
.bus=&spi_bus_type,
.owner=THIS_MODULE,
},
.probe=mcp3201_probe,
.remove=__devexit_p(mcp3201_remove),
};
staticconststructfile_operationsmcp3201_ops={
.owner=THIS_MODULE,
.open=mcp3201_open,
.release=mcp3201_close,
.read=mcp3201_read,
};
staticstructmiscdevicemcp3201_miscdev={
MISC_DYNAMIC_MINOR,
"mcp3201",
&mcp3201_ops,
};
staticintmcp3201_init(void)
{
if(misc_register(&mcp3201_miscdev)){
printk(KERN_WARNING"buzzer:Couldn'tregisterdevice10,%d.\n",255);
return-EBUSY;
}
returnspi_register_driver(&mcp3201_driver);
}
staticvoidmcp3201_exit(void)
{
spi_unregister_driver(&mcp3201_driver);
}
module_init(mcp3201_init);
module_exit(mcp3201_exit);
MODULE_LICENSE("GPL");
10-2外部按键驱动
10-2-1说明
程序源代码说明:
驱动源程序所在目录
driver/input/
驱动程序名
74LV165_button.c
74LV165_button.h
设备名
ls1b_buttons
测试程序源代码目录
测试程序代码名
74LV165_test.c
测试程序可执行文件名
74LV165_test
说明:按键驱动已经被编译到缺省内核中,因此不用使用insmod方式加载
开发板用到的资源:
gpio0
KEY_DATA
gpio1
KEY_EN
gpio2
KEY_SCL
按键驱动在硬件上没有接外部中断,且通过两片并行输入串行输出的移位芯片74LV165连接,因此,按键通过轮询的方式实现。应用程序直接读取/dev/ls1b_buttons设备节点来对按键值进行读取。在启动linux内核后,在根目录下执行当前目录下的可执行文件74LV165_test。
10-2-2驱动程序清单
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"74LV165_button.h"
#defineBUF_MAXSIZE16
#defineTIMER_DELAY5
staticunsignedintBReadBuf=0;
staticintdelay(inttime){
while(--time);
return0;
}
staticvoidprepare_for_read(void){
intreg=0;
/*CP=0*/
LS1B_74LV165_READ(reg,LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP,reg&(~(1<
/*PL=0*/
LS1B_74LV165_READ(reg,LS1B_74LV165_PL);
LS1B_74LV165_WRITE(LS1B_74LV165_PL,reg&~(1<
/*delay100*/
delay(10000);
/*PL=1*/
LS1B_74LV165_READ(reg,LS1B_74LV165_PL);
LS1B_74LV165_WRITE(LS1B_74LV165_PL,reg|(1<
}
staticvoidscanf_keyboard(void){
intreg=0,val=0;
inttime;
delay(10);
LS1B_74LV165_READ(reg,LS1B_74LV165_DATA);
val=((~reg)&(1<
BReadBuf=val>>0;
for(time=1;time
/*delay*/
delay(100);
/*CP=1*/
LS1B_74LV165_READ(reg,LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP,reg|(1<
delay(100);
LS1B_74LV165_READ(reg,LS1B_74LV165_DATA);
val=((~reg)&(1<
BReadBuf|=val<
/*CP=0*/
LS1B_74LV165_READ(reg,LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP,reg&(~(1<
}
}
staticvoidbutton_read(void){
prepare_for_read();
/*scanfkeyboard*/
scanf_keyboard();
if(BReadBuf){
printk("\nBReadBufvalueis0x%08x\n",BReadBuf);
}
}
staticintLS1B_74LV165_button_open(structinode*inode,structfile*filp){
intreg;
printk("Welcometouse74LV165driver\n");
//enalbepin
LS1B_74LV165_EN_GPIO(GPIO_KEY_DATA);
LS1B_74LV165_EN_GPIO(GPIO_KEY_EN);
LS1B_74LV165_EN_GPIO(GPIO_KEY_SCL);
//enableoutput
LS1B_74LV165_OEN_GPIO(GPIO_KEY_SCL);
LS1B_74LV165_OEN_GPIO(GPIO_KEY_EN);
LS1B_74LV165_IEN_GPIO(GPIO_KEY_DATA);
printk("init74LV165isdone\n");
return0;
}
staticssize_tLS1B_74LV165_button_read(structfile*filp,char__user*buf,size_tcount,loff_t*oppos){
button_read();
if(BReadBuf){
copy_to_user(buf,&BReadBuf,count);
BReadBuf=0;
delay(10000000);
returncount;
}
return-EFAULT;
}
staticintLS1B_74LV165_button_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg){
return0;
}
staticstructfile_operationsls1b_74Llv165_button_fops={
.open=LS1B_74LV165_button_open,
.read=LS1B_74LV165_button_read,
.ioctl=LS1B_74LV165_button_ioctl,
};
staticstructmiscdevicels1b_74lv165_button={
.minor=LS1B_BUTTON_MINOR,
.name="ls1b_buttons",
.fops=&ls1b_74Llv165_button_fops,
};
staticint__initLS1B_74LV165_button_init(void){
intret;
printk("======================buttoninit=========================\n");
ret=misc_register(&ls1b_74lv165_button);
if(ret
printk("74LV165_buttoncan'tgetmajornumber!\n");
returnret;
}
#ifdefCONFIG_DEVFS_FS
devfs_button_dir=devfs_mk_dir(NULL,"LS1B_74LV165_button",NULL);
devfs_buttonraw=devfs_register(devfs_kbd_dir,"0raw",DEVFS_FL_DEFAULT,kbdMajor,KBDRAW_MINOR,S_IFCHR|S_IRUSR|S_IWUSR,&LS1B_74LV165_button_fops,NULL);
#endif
return0;
}
staticvoid__exitLS1B_74LV165_button_exit(void){
misc_deregister(&ls1b_74lv165_button);
}
module_init(LS1B_74LV165_button_init);
module_exit(LS1B_74LV165_button_exit);
其中module_init()和module_exit()函数注册了LS1B_74LV165_button_init及LS1B_74LV165_button_exit函数。当模块加载时将会调用这两个函数。
在LS1B_74LV165_button_init()函数中调用misc_register()函数注册按键驱动为misc设备。并将structmiscdevice的数据结构传递给该函数,在该结构中对minor字段及name字段进行赋值,这样该驱动会在/dev目录下创建名为ls1b_buttons的设备文件,其中主设备号为10,次设备号为minor的值,即为143。
在structmiscdevice数据结构中,还有一个fops的字段,该字段为指向structfile_operations数据结构的指针。其中包括open、read、ioctl等字段,当用户程序调用open、write、ioctl函数时,最终调用该驱动的相应函数。
在LS1B_74LV165_button_open()函数中使能芯片74LV165用到的3个gpio口,并将端口设置为输出。当用户调用read函数时,运行驱动程序的LS1B_74LV165_button_read()函数,该函数调用button_read(),读取按键编码。在button_read()函数中调用prepare_for_read()根据芯片74LV165的时序将按键信息写入芯片74LV165的移位寄存器,然后调用scanf_keyboard()函数根据芯片74LV165的时序读写移位寄存器中的按键编码。最后通过LS1B_74LV165_button_read()函数中的copy_to_user()函数将按键编码返回给用户空间。
10-3RTC驱动程序
10-3-1说明
程序源代码说明:
驱动源代码所在目录
/driver/rtc
驱动程序名称
rtc-gs2fsb.c
该驱动的主设备号
RTC设备,设备号将自动生成
设备名
/dev/rtc0
测试程序可执行文件名称
datehwclock
RTC模块寄存器位于0xbfe64000——0xbfe67fff的16KB地址空间内,其基地址为0xbfe64000,所有寄存器位宽均为32位。详细的寄存器描述可参考loongson1B的数据手册。
10-3-2驱动程序清单
#include
#include
#include
#include
#include
#defineRTC_TOYIM0x00
#defineRTC_TOYWLO0x04
#defineRTC_TOYWHI0x08
#defineRTC_TOYRLO0x0c
#defineRTC_TOYRHI0x10
#defineRTC_TOYMH00x14
#defineRTC_TOYMH10x18
#defineRTC_TOYMH20x1c
#defineRTC_CNTL0x20
#defineRTC_RTCIM0x40
#defineRTC_WRITE00x44
#defineRTC_READ00x48
#defineRTC_RTCMH00x4c
#defineRTC_RTCMH10x50
#defineRTC_RTCMH20x54
structrtc_sb2f{
structrtc_device*rtc;
void__iomem*regs;
unsignedlongalarm_time;
unsignedlongirq;
spinlock_tlock;
};
staticintsb2f_rtc_read_register(structrtc_sb2f*rtc,unsignedlongreg)
{
intdata;
data=(*(volatileunsignedint*)(rtc->regs+reg));
returndata;
}
staticintsb2f_rtc_write_register(structrtc_sb2f*rtc,unsignedlongreg,intdata)
{
(*(volatileunsignedint*)(rtc->regs+reg))=data;
}
staticintsb2f_rtc_readtime(structdevice*dev,structrtc_time*tm)
{
structrtc_sb2f*rtc=dev_get_drvdata(dev);
unsignedlongnow,now1;
now=sb2f_rtc_read_register(rtc,RTC_TOYRLO);
tm->tm_sec=((now>>4)&0x3f);
tm->tm_min=((now>>10)&0x3f);
tm->tm_hour=((now>>16)&0x1f);
tm->tm_mday=((now>>21)&0x1f);
tm->tm_mon=((now>>26)&0x3f)-1;
now1=sb2f_rtc_read_register(rtc,RTC_TOYRHI);
tm->tm_year=(now1-1900);
return0;
}
staticintsb2f_rtc_settime(structdevice*dev,structrtc_time*tm)
{
structrtc_sb2f*rtc=dev_get_drvdata(dev);
unsignedlongnow;
intret;
now=((tm->tm_sec
(tm->tm_mday
spin_lock_irq(&rtc->lock);
sb2f_rtc_write_register(rtc,RTC_TOYWLO,now);
//setyear
sb2f_rtc_write_register(rtc,RTC_TOYWHI,(tm->tm_year+1900));
spin_unlock_irq(&rtc->lock);
return0;
}
staticintsb2f_rtc_readalarm(structdevice*dev,structrtc_wkalrm*alrm)
{
structrtc_sb2f*rtc=dev_get_drvdata(dev);
unsignedlongtime;
spin_lock_irq(&rtc->lock);
time=sb2f_rtc_read_register(rtc,RTC_TOYMH0);
spin_unlock_irq(&rtc->lock);
alrm->time.tm_sec=(time&0x3f);
alrm->time.tm_min=((time>>6)&0x3f);
alrm->time.tm_hour=((time>>12)&0x1f);
alrm->time.tm_mday=((time>>17)&0x1f);
alrm->time.tm_mon=((time>>22)&0x1f);
alrm->time.tm_year=((time>>26)&0x3f);
return0;
}
staticintsb2f_rtc_setalarm(structdevice*dev,structrtc_wkalrm*alrm)
{
structrtc_sb2f*rtc=dev_get_drvdata(dev);
inttime;
time=((alrm->time.tm_sec&0x3f)|((alrm->time.tm_min&0x3f)<
|((alrm->time.tm_hour&0x1f)
&0x1f)
((alrm->time.tm_year&0x3f)<
spin_lock_irq(&rtc->lock);
sb2f_rtc_write_register(rtc,RTC_TOYMH0,time);
spin_unlock_irq(&rtc->lock);
return0;
}
staticintsb2f_rtc_ioctl(structdevice*dev,unsignedintcmd,
unsignedlongarg)
{
switch(cmd){
caseRTC_PIE_ON:
break;
caseRTC_PIE_OFF:
break;
caseRTC_UIE_ON:
break;
caseRTC_UIE_OFF:
break;
caseRTC_AIE_ON:
break;
caseRTC_AIE_OFF:
break;
default:
return-ENOIOCTLCMD;
}
return0;
}
staticirqreturn_tsb2f_rtc_interrupt(intirq,void*dev_id)
{
structrtc_sb2f*rtc=(structrtc_sb2f*)dev_id;/*接收申请中断时传递过来的rtc_dev参数*/
unsignedlongevents=0;
spin_lock(&rtc->lock);
events=RTC_AF|RTC_IRQF;
/*节拍时间中断到来的时候,去设定RTC中节拍时间的相关信息,具体设定的方法,RTC核心部分已经在rtc_update_irq接口函数中实现,函数定义实现在interface.c中*/
rtc_update_irq(rtc->rtc,1,events);
spin_unlock(&rtc->lock);
}
staticstructrtc_class_opssb2f_rtc_ops={
.ioctl=sb2f_rtc_ioctl,
.read_time=sb2f_rtc_readtime,
.set_time=sb2f_rtc_settime,
.read_alarm=sb2f_rtc_readalarm,
.set_alarm=sb2f_rtc_setalarm,
};
staticint__initsb2f_rtc_probe(structplatform_device*pdev)
{
structresource*regs;/*定义一个资源,用来保存获取的RTC的资源*/
structrtc_sb2f*rtc;
intirq=-1;
intret;
rtc=kzalloc(sizeof(structrtc_sb2f),GFP_KERNEL);
if(!rtc){
dev_dbg(&pdev->dev,"outofmemory\n");
return-ENOMEM;
}
/*获取RTC平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和RTC平台设备定义中的一致*/
regs=platform_get_resource(pdev,IORESOURCE_MEM,0);
if(!regs){
dev_dbg(&pdev->dev,"nonmioresourcedefined\n");
ret=-ENXIO;
gotoout;
}
printk("theregs->startis0x%x",regs->start);
//在系统定义的RTC平台设备中获取TICK节拍时间中断号
irq=platform_get_irq(pdev,0);
printk("theirqis%d\n",irq);
rtc->irq=irq;
//把I/O资源IORESOURCE_MEM起始地址到结束地址范围的资源映射到虚拟地址ioremap定义在io.h中。
//注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作
rtc->regs=ioremap(regs->start,regs->end-regs->start+1);
if(!rtc->regs){
ret=-ENOMEM;
dev_dbg(&pdev->dev,"couldnotmapi/omemory\n");
gotoout;
}
//初始化自旋锁
spin_lock_init(&rtc->lock);
//nextsetcontrolregister
//sb2f_rtc_write_register(rtc,RTC_CNTL,0x800);
(*(volatileint*)(0xbfe64040))=0x2d00;//TOY和RTC控制寄存器
//0010110100000000
//申请中断(这个可以放到open()函数里)
//res->start中断号(在具体的平台设备platform_device定义时赋值)
//s3c2410wdt_irq中断处理函数IRQF_DISABLED中断处理属性快速慢速共享
//pdev->name这个传递给request_irq的字串用在/proc/interrupts来显示中断的拥有者
//pdev用作共享中断线的指针被驱动用来指向它自己的私有数据区(来标识哪个设备在中断).
ret=request_irq(irq,sb2f_rtc_interrupt,IRQF_SHARED,"rtc",rtc);
if(ret){
dev_dbg(&pdev->dev,"couldnotrequestirq%d",irq);
gotoout_iounmap;
}
//注册RTC设备
/*registerRTCandexit*/
/*将RTC注册为RTC设备类,RTC设备类在RTC驱动核心部分中由系统定义好的,
注意rtcops这个参数是一个结构体,该结构体的作用和里面的接口函数实现在第③步中。
rtc_device_register函数在rtc.h中定义,在drivers/rtc/class.c中实现*/
rtc->rtc=rtc_device_register(pdev->name,&pdev->dev,&sb2f_rtc_ops,THIS_MODULE);
/*里的IS_ERR(),它就是判断kthread_run()返回的指针是否有错,如果指针并不是指向最后一个page,那么没有问题,申请成功了,如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针,这个指针里保存的实际上是一种错误代码.而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码.*/
if(IS_ERR(rtc->rtc)){
dev_dbg(&pdev->dev,"couldnotregisterrtcdevice\n");
//PTR_ERR()只是返回错误代码,也就是提供一个信息给调用者,如果你只需要知道是否出错,而不在乎因为什么而出错
ret=PTR_ERR(rtc->rtc);
gotoout_free_irq;
}
/*将RTC设备类的数据传递给系统平台设备。
platform_set_drvdata是定义在platform_device.h的宏,如下:
#defineplatform_set_drvdata(_dev,data)dev_set_drvdata(&(_dev)->dev,(data))
而dev_set_drvdata又被定义在include/linux/device.h中,如下:
staticinlinevoiddev_set_drvdata(structdevice*dev,void*data){
dev->driver_data=data;
}*/
platform_set_drvdata(pdev,rtc);
/*device_init_wakeup该函数定义在pm_wakeup.h中,定义如下:
staticinlinevoiddevice_init_wakeup(structdevice*dev,intval){
dev->power.can_wakeup=dev->power.should_wakeup=!!val;}
显然这个函数是让驱动支持电源管理的,这里只要知道,can_wakeup为1时表明这个设备可以被唤醒,设备驱动为了支持Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup,而should_wakeup则是在设备的电源状态发生变化的时候被device_may_wakeup()用来测试,测试它该不该变化,因此can_wakeup表明的是一种能力,而should_wakeup表明的是有了这种能力以后去不去做某件事。好了,我们没有必要深入研究电源管理的内容了,要不就扯远了,电源管理以后再讲*/
device_init_wakeup(&pdev->dev,1);
//头文件include/linux/device.h中所提供的宏(包括dev_printk()、dev_dbg()、dev_warn()、dev_info()和dev_err())来决定何种类型的设备访问消息需要被记录。printk()
dev_info(&pdev->dev,"SB2FRTCat%08lxirq%ld\n",(unsignedlong)rtc->regs,rtc->irq);
return0;
out_free_irq:
free_irq(irq,rtc);
out_iounmap:
iounmap(rtc->regs);
out:
kfree(rtc);
returnret;
}
/*注意:这是使用了一个__devexit。
我们还是先来讲讲这个:
在Linux内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,
这些宏在include/linux/init.h头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,
以减少内存占用和提高内核效率。__devinit、__devexit就是这些宏之一,在probe()和remove()函数中
应该使用__devinit和__devexit宏。又当remove()函数使用了__devexit宏时,则在驱动结构体中一定要
使用__devexit_p宏来引用remove(),所以在第①步中就用__devexit_p来引用rtc_remove*/
staticint__exitsb2f_rtc_remove(structplatform_device*pdev)
{
/*从系统平台设备中获取RTC设备类的数据*/
structrtc_sb2f*rtc=platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev,0);
free_irq(rtc->irq,rtc);
iounmap(rtc->regs);/*释放RTC虚拟地址映射空间*/
rtc_device_unregister(rtc->rtc);/*注销RTC设备类*/
kfree(rtc);/*销毁保存RTC平台设备的资源内存空间*/
platform_set_drvdata(pdev,NULL);/*清空平台设备中RTC驱动数据*/
return0;
}
MODULE_ALIAS("platform:sb2f-rtc");
staticstructplatform_driversb2f_rtc_driver={
.remove=__exit_p(sb2f_rtc_remove),
.driver={
.name="sb2f-rtc",
.owner=THIS_MODULE,
},
};
staticint__initsb2f_rtc_init(void)
{
/*将RTC注册成平台设备驱动*/
returnplatform_driver_probe(&sb2f_rtc_driver,sb2f_rtc_probe);
}
module_init(sb2f_rtc_init);
staticvoid__exitsb2f_rtc_exit(void)
{
/*注销RTC平台设备驱动*/
platform_driver_unregister(&sb2f_rtc_driver);
}
module_exit(sb2f_rtc_exit);
MODULE_AUTHOR("");
MODULE_DESCRIPTION("RealTimeclockforloongson2fsb");
MODULE_LICENSE("GPL");