s3c2410 RTC驅(qū)動框架linux內(nèi)核源碼分析
/*********************************************************************************************************
*@Description:s3c2410的rtc驅(qū)動的實現(xiàn),rtc(realtimeclock)實時時鐘的驅(qū)動是個很好的
*理解如果編寫驅(qū)動的硬件,它包括了最基本的硬中斷,軟中斷的底層機制;
*s3c2410的RTC驅(qū)動的實現(xiàn)個人認為更是對linux設(shè)備驅(qū)動一個很好的例子,他是通過二層結(jié)構(gòu)來
*實現(xiàn)的一個驅(qū)動,上層是一個armcommon的公共層,對上提供標準的通用的RTC操作接口,下層由
*我們來實現(xiàn)針對自己的chip和自己要提供的功能來實現(xiàn)的一層驅(qū)動;
*
*@FileTree:
**********************************************************************************************************
linux-2.6.14.6
|
|--arch
||
||--arm
|||--mach-s3c2410
||||-devs.c//包含了對各個部件的resource的分配和定義,在這看rtc的資源;
|||--common
||||-rtctime.c//一個arm平臺的通用rtc函數(shù)層,它對上隱藏了各種soc的rtcdriver的區(qū)別;
|||--kernel
||||-time.c//內(nèi)核的初始化例程time_init()會調(diào)用的xxx_cmos_xxx函數(shù)的實現(xiàn);定義了全局自旋鎖rtc_lock用來串行化所有CPU對RTC的操作
|
|--drivers
||--char
|||-s3c2410-rtc.c//具體的s3c2410上的rtcchip的驅(qū)動實現(xiàn),如果需要在arm平臺的
||板子上實現(xiàn)一個驅(qū)動,改寫它就ok了。
|
|--include
||--asm-arm
|||--arch-s3c2410
||||-regs-rtc.h//S3C2410InternalRTCregisterdefinitionrefertodatasheet;
|||-rtc.h//arm平臺rtc操作抽象層rtctime.c對應(yīng)的.h
||--linux
|||-time.h//mktime的實現(xiàn);
|||-rtc.h//公用RTC.h
*
*[小結(jié)]
*1) 提供給user的接口,在arch/arm/common/rtctime.c,include/asm-arm/rtc.h中實現(xiàn),調(diào)用操作硬件驅(qū)動在 drivers/char/s3c2410-rtc.c,include/asm-arm/arch-s3c2410/regs-rtc.h實現(xiàn);
*2) 提供給kernel的接口,在arch/arm/kernel/time.c,include/linux/time.h中實現(xiàn),調(diào)用操作硬件驅(qū)動在 drivers/char/s3c2410-rtc.c,include/asm-arm/arch-s3c2410/regs-rtc.h實現(xiàn);
**********************************************************************************************************
*
*@Author:liyangth@gmail.com
*
*@FunctionList:
*
*
*
*
*@Changelog:
*2007-06-24LiYangFirstversion
*
*@FQA:
*[50%]Q1.在驅(qū)動中要將設(shè)備注冊到總線,必須將設(shè)備封裝成structdevice_driver;調(diào)查這個結(jié)構(gòu)體中的每個成員.
*[0%]Q2.在板子(什么類型)上什么樣的設(shè)備要用總線(什么類型)注冊?
*[90%]Q3.
*structdevice--總線設(shè)備
*structdevice_driver--總線設(shè)備驅(qū)動
*structplatform_device--平臺設(shè)備
*structresource--平臺資源
*
*[!0%]Q4.初始化rtcregister的函數(shù)的后面的flag具體控制什么?(在s2s65a里是否可以用它控制是softResetorhardwareReset)
*
*[0%]Q5.什么時候調(diào)用suspend,resume?
**********************************************************************************************************/
/*****************************************************************************
*Structures&Unions&Enums(#typedef)
*/
/*[include/linux/device.h]
*總線設(shè)備驅(qū)動結(jié)構(gòu)體,將它注冊到板子的總線上
*/
staticstructdevice_drivers3c2410_rtcdrv={
.name="s3c2410-rtc",
.owner=THIS_MODULE,
.bus=&platform_bus_type,//總線類型,貌似不用管
.probe=s3c2410_rtc_probe,//自檢->初始化REG->注冊到上一層
.remove=s3c2410_rtc_remove,//注銷
.suspend=s3c2410_rtc_suspend,//[掛起???]
.resume=s3c2410_rtc_resume,//[重起???]
};
/*[/include/asm-arm/rtc.h]
*底層特別操作集,將他注冊到上層的armcommon操作層
*/
staticstructrtc_opss3c2410_rtcops={
.owner=THIS_MODULE,
.open=s3c2410_rtc_open,
.release=s3c2410_rtc_release,
.ioctl=s3c2410_rtc_ioctl,
.read_time=s3c2410_rtc_gettime,
.set_time=s3c2410_rtc_settime,
.read_alarm=s3c2410_rtc_getalarm,
.set_alarm=s3c2410_rtc_setalarm,
.proc=s3c2410_rtc_proc,
};
/*****************************************************************************
*GlobalVariables
*/
s3c2410-rtc.c
|
|/*IRQHandlers*/
|-s3c2410_rtc_alarmirq(intirq,void*id,structpt_regs*r)
||
||-rtc_update(1,RTC_AF|RTC_IRQF);//獲得中斷標志,和喚醒read阻塞,異步通知;
|
|-s3c2410_rtc_tickirq(intirq,void*id,structpt_regs*r)
|
|/*Updatecontrolregisters,與硬件實現(xiàn)有關(guān),refertodatasheet*/
|-s3c2410_rtc_setaie(intto)
|-s3c2410_rtc_setpie(intto)
|-s3c2410_rtc_setfreq(intfreq)
|
|/*實現(xiàn)了要插到上層armcommon層的具體的硬件操作,來填充structrtc_ops,這個具體與硬件相關(guān)的操作集會用
|register_rtc注冊到上層的*/
|
|-s3c2410_rtc_gettime(structrtc_time*rtc_tm)
|
|-s3c2410_rtc_settime(structrtc_time*tm)
|
|-s3c2410_rtc_getalarm(structrtc_wkalrm*alrm)
|
|-s3c2410_rtc_setalarm(structrtc_wkalrm*alrm)
|
|/*
|*插入到上層ioctl中的ioctl,上層中已經(jīng)通過這個driver中的gettime,settime在其ioctl中實現(xiàn)了取得和設(shè)置時間
|*,和一些共同的ioctl操作了
|*所以我們在這只要實現(xiàn)與硬件不同部分的ioctl操作*/
|-s3c2410_rtc_ioctl(unsignedintcmd,unsignedlongarg)
|
|-s3c2410_rtc_proc(char*buf)
|-s3c2410_rtc_open(void)
||
||1.注冊申請鬧鐘中斷ISQ--s3c2410_rtc_alarmirq
||2.周期中斷ISQ--s3c2410_rtc_tickirq
|
|-s3c2410_rtc_release(void)
|
|/*InitializeRTCRegs*/
|-s3c2410_rtc_enable(structdevice*dev,inten)
||
||-if(!en)
||/*beforepoweroff,theRTCENbitshouldbeclearedto
||0topreventinadvertentwritingintoRTCregisters.*/
||1.將控制R的RTCEN位清0;
||
||2.disableinterrupt.
||
||-else/*re-enablethedevice,andcheckitisok*/
||
||1.將控制R的RTCEN位致1。
||
||2.BCDcountselect.--0=MergeBCDcounters
||
||3.RTCclockcountreset.--0=noreset
||
|
|
|
|-s3c2410_rtc_probe(structdevice*dev)
||[??]structplatform_device*pdev=to_platform_device(dev);//通過這個設(shè)備找到它宿主平臺的大設(shè)備;
||structresource*res;
||
||/*findtheIRQs,RTC有2中中斷,周期中斷和鬧鐘中斷*/
||-s3c2410_rtc_tickno=platform_get_irq(pdev,1);//從平臺上取得一個IRQ號給這個設(shè)備;
||
||/*getthememoryregion*/
||-res=platform_get_resource(pdev,IORESOURCE_MEM,0);
||
||/*向內(nèi)核申請資源空間*/
||-s3c2410_rtc_mem=request_mem_region(res->start,res->end-res->start+1,
pdev->name);//res->start這些資源的分配和在哪個段下,可以看../mach-s3c2410/devs.c
||
||/*然后將物理地址映射到虛擬地址,這樣驅(qū)動和內(nèi)核就可以看到設(shè)備的I/Oregs了*/
||-s3c2410_rtc_base=ioremap(res->start,res->end-res->start+1);
||
||/*初始化設(shè)備regs*/
||-s3c2410_rtc_enable(dev,1);//可以用后面這個1(這個flag在s2s65a中可以用ram0-7來保存,掉電不清的)來控制是softreset還是hardwarereset.
||
||-s3c2410_rtc_setfreq(s3c2410_rtc_freq);//設(shè)定RTC周期頻率;
||
||/*最關(guān)鍵的一步,將與具體不同的底層硬件相關(guān)的設(shè)備驅(qū)動注冊給arm通用操作層common/rtctime.c*/
||-register_rtc(&s3c2410_rtcops);
|
|-s3c2410_rtc_remove(structdevice*dev)
||-unregister_rtc(&s3c2410_rtcops);//從上一層將s3c2410的rtc的devicedirveroperationset拔下來。
||
||-s3c2410_rtc_setpie(0);//disable周期中斷
||
||-s3c2410_rtc_setaie(0);//disablealarminterrupt
||
||-iounmap(s3c2410_rtc_base);
||
||-release_resource(s3c2410_rtc_mem);
||
||-kfree(s3c2410_rtc_mem);//[MQA]哪塊kmalloc了呢,為什么這要free?
|
|
|#ifdefCONFIG_PM//如果電源控制開關(guān)打開
|
|-s3c2410_rtc_suspend(structdevice*dev,pm_message_tstate,u32level)
||
||if(level==SUSPEND_POWER_DOWN)//SUSPEND_POWER_DOWN在include/linux/device.h(generic,centralizeddrivermodel)中定義,這個里面是否是對設(shè)備的一些公用的行為的操作宏的定義呢?
|||1.保存周期中斷寄存器的值;
|||
|||2.從RTC中讀出時間
|||-s3c2410_rtc_gettime(&tm);//[localvariable]structrtc_timetm;
|||
|||3.將從RTC取出的時間ConvertGregoriandatetosecondssince01-01-197000:00:00.
|||-rtc_tm_to_time(&tm,&time.tv_sec);//[arch/arm/common/rtctime.c]
|||
|||4.將系統(tǒng)時間和RTC時間的差值保存到s3c2410_rtc_delta里;
|||-save_time_delta(&s3c2410_rtc_delta,&time);
|||
|||5.啟動RTC,注意這次啟動后面的falg為0了;
|||-s3c2410_rtc_enable(dev,0);
|
|
|-s3c2410_rtc_resume(structdevice*dev,u32level)
||
||1.啟動RTC,注意這次啟動后面的falg為1了;
||-s3c2410_rtc_enable(dev,1);
||
||2.從RTC中讀出時間
||-s3c2410_rtc_gettime(&tm);
||
||3.轉(zhuǎn)換
||-rtc_tm_to_time(&tm,&time.tv_sec);
||
||4.利用在suspend中保存的delta來恢復(fù)系統(tǒng)時間
||-restore_time_delta(&s3c2410_rtc_delta,&time);
||
||5.恢復(fù)周期中斷寄存器的值;
|
|[LiY]
|suspend(暫停,掛起)和resume(恢復(fù),再開始)有點象關(guān)機前保存現(xiàn)場和開機后再恢復(fù)現(xiàn)場一樣;
|
|#else
|#defines3c2410_rtc_suspendNULL
|#defines3c2410_rtc_resumeNULL
|#endif
|
|
|module_init(s3c2410_rtc_init)
|-s3c2410_rtc_init
||-driver_register(&s3c2410_rtcdrv);
|||[purpose]registerdriverwithbus;
|
|module_exit(s3c2410_rtc_exit)
|-s3c2410_rtc_exit(void)
||-driver_unregister(&s3c2410_rtcdrv)
/*****************************************************************************
*ExternFunctionDetails
*/
/**[drivers/base/driver.c]
*driver_register-registerdriverwithbus
*@drv:drivertoregister
*
*Wepassoffmostoftheworktothebus_add_driver()call,
*sincemostofthethingswehavetododealwiththebus
*structures.
*
*Theoneinterestingaspectisthatwesetup@drv->unloaded
*asacompletionthatgetscompletewhenthedriverreference
*countreaches0.
*/
intdriver_register(structdevice_driver*drv)
{
klist_init(&drv->klist_devices,klist_devices_get,klist_devices_put);
init_completion(&drv->unloaded);
returnbus_add_driver(drv);
}
/**[drivers/base/platform.c]
*platform_get_irq-getanIRQforadevice
*@dev:platformdevice
*@num:IRQnumberindex
*/
intplatform_get_irq(structplatform_device*dev,unsignedintnum)
{
structresource*r=platform_get_resource(dev,IORESOURCE_IRQ,num);
returnr?r->start:0;
}
/**[drivers/base/platform.c]
*platform_get_resource-getaresourceforadevice
*@dev:platformdevice
*@type:resourcetype
*@num:resourceindex
*/
structresource*
platform_get_resource(structplatform_device*dev,unsignedinttype,
unsignedintnum)
{
inti;
for(i=0;i
structresource*r=&dev->resource[i];
if((r->flags&(IORESOURCE_IO|IORESOURCE_MEM|
IORESOURCE_IRQ|IORESOURCE_DMA))
==type)
if(num--==0)
returnr;
}
returnNULL;
}
/*[../include/linux/ioport.h]
*Resourcesaretree-like,allowing
*nestingetc..
*/
structresource{
constchar*name;
unsignedlongstart,end;
unsignedlongflags;
structresource*parent,*sibling,*child;
};
/**[arch/arm/kernel/time.c]
*save_time_delta-SavetheoffsetbetweensystemtimeandRTCtime
*@delta:pointertotimespectostoredelta
*@rtc:pointertotimespecforcurrentRTCtime
*
*ReturnadeltabetweenthesystemtimeandtheRTCtime,such
*thatsystemtimecanberestoredlaterwithrestore_time_delta()
*
*[LiY]返回一個系統(tǒng)時間和RTC時間的差值,在重起或系統(tǒng)去讀RTC后,可以再用這個差值(delta)來恢復(fù)系統(tǒng)時間(系統(tǒng)時間不一定非要和RTC時間相同的);
*/
voidsave_time_delta(structtimespec*delta,structtimespec*rtc)
{
set_normalized_timespec(delta,
xtime.tv_sec-rtc->tv_sec,
xtime.tv_nsec-rtc->tv_nsec);
}
/***[arch/arm/kernel/time.c]
*restore_time_delta-Restorethecurrentsystemtime
*@delta:deltareturnedbysave_time_delta()
*@rtc:pointertotimespecforcurrentRTCtime
*/
voidrestore_time_delta(structtimespec*delta,structtimespec*rtc)
{
structtimespects;
set_normalized_timespec(&ts,
delta->tv_sec+rtc->tv_sec,
delta->tv_nsec+rtc->tv_nsec);
do_settimeofday(&ts);
}
/*
*[arch/arm/common/rtctime.c]
*/
voidrtc_update(unsignedlongnum,unsignedlongevents)
{
spin_lock(&rtc_lock);
rtc_irq_data=(rtc_irq_data+(num<<8))|events;
spin_unlock(&rtc_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue,SIGIO,POLL_IN);
}