200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > linux下怎样运行oyrhon 第十章 LINUX驱动程序实验

linux下怎样运行oyrhon 第十章 LINUX驱动程序实验

时间:2019-09-05 19:29:55

相关推荐

linux下怎样运行oyrhon 第十章 LINUX驱动程序实验

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");

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。