#include <linux/module.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/clk.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/rtc.h>
#include <asm/mach/time.h>
#include <asm/arch/regs-rtc.h>
/* need this for the RTC_AF definitions */
#include <linux/mc146818rtc.h> //mc146818 时钟芯片
#undef S3C24XX_VA_RTC
#define S3C24XX_VA_RTC s3c2410_rtc_base
static struct resource *s3c2410_rtc_mem;
static void __iomem *s3c2410_rtc_base;
static int s3c2410_rtc_alarmno = IRQ_RTC;
static int s3c2410_rtc_tickno = IRQ_TICK;//将中断功能加上
static int s3c2410_rtc_freq = 1;
static DEFINE_SPINLOCK(s3c2410_rtc_pie_lock);
/* IRQ Handlers */
//static irqreturn_t s3c2410_rtc_alarmirq(int irq, void *id, struct pt_regs *r)
static irqreturn_t s3c2410_rtc_alarmirq(int irq, void *id)//modify the last
{
rtc_update(1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}
//static irqreturn_t s3c2410_rtc_tickirq(int irq, void *id, struct pt_regs *r)
static irqreturn_t s3c2410_rtc_tickirq(int irq, void *id)
{
rtc_update(1, RTC_PF | RTC_IRQF);
return IRQ_HANDLED;
}
/* Update control registers */
static void s3c2410_rtc_setaie(int to)//设置报警功能
{
unsigned int tmp;
pr_debug("%s: aie=%d\n", __FUNCTION__, to);
tmp = readb(S3C2410_RTCALM);
if (to)
{
enable_irq(s3c2410_rtc_alarmno);//添加使能中断
tmp |= S3C2410_RTCALM_ALMEN;
}
else
{
disable_irq(s3c2410_rtc_alarmno)://添加关闭中断
tmp &= ~S3C2410_RTCALM_ALMEN;
}
writeb(tmp, S3C2410_RTCALM);
}
static void s3c2410_rtc_setpie(int to)//设置TICK interrupt
{
unsigned int tmp;
pr_debug("%s: pie=%d\n", __FUNCTION__, to);
spin_lock_irq(&s3c2410_rtc_pie_lock);
// tmp = readb(S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE; //添加中断功能
tmp = readb(S3C2410_TICNT);
if (to)
{
enable_irq(s3c2410_rtc_tickno);//添加使能中断
tmp |= S3C2410_TICNT_ENABLE;
}
else
{
disable_irq(s3c2410_rtc_tickno);//添加关闭中断
tmp &= ~S3C2410_TICNT_ENABLE;
}
writeb(tmp, S3C2410_TICNT);
spin_unlock_irq(&s3c2410_rtc_pie_lock);
}
static void s3c2410_rtc_setfreq(int freq)
{
unsigned int tmp;
spin_lock_irq(&s3c2410_rtc_pie_lock);//获取自旋锁
tmp = readb(S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
s3c2410_rtc_freq = freq;
tmp |= (128 / freq)-1;
writeb(tmp, S3C2410_TICNT);//Tick time count value为127
spin_unlock_irq(&s3c2410_rtc_pie_lock);//释放自旋锁
}
/* Time read/write */
static int s3c2410_rtc_gettime(struct rtc_time *rtc_tm)
{
unsigned int have_retried = 0;
retry_get_time:
rtc_tm->tm_min = readb(S3C2410_RTCMIN);
rtc_tm->tm_hour = readb(S3C2410_RTCHOUR);
rtc_tm->tm_mday = readb(S3C2410_RTCDATE);
rtc_tm->tm_mon = readb(S3C2410_RTCMON);
rtc_tm->tm_year = readb(S3C2410_RTCYEAR);
rtc_tm->tm_sec = readb(S3C2410_RTCSEC);//将此时时间保存到rtc_tm中
/* the only way to work out wether the system was mid-update
* when we read it is to check the second counter, and if it
* is zero, then we re-try the entire read
*/
/*因为读多个寄存器, 防止 恰好是 一年的最后
一天,最后一分钟,最后一秒的情况, 这个时候, 就应该重新读一下了(最常见的情况还是要重新读一下
BCDMIN) 记住, 重新读一次就好了. 所以后面才有 if (rtc_tm->tm_sec == 0 && !have_retried) 的判断*/
if (rtc_tm->tm_sec == 0 && !have_retried) {
have_retried = 1;
goto retry_get_time;
}
pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
BCD_TO_BIN(rtc_tm->tm_sec);//将相应的BCD码转为二进制
BCD_TO_BIN(rtc_tm->tm_min);
BCD_TO_BIN(rtc_tm->tm_hour);
BCD_TO_BIN(rtc_tm->tm_mday);
BCD_TO_BIN(rtc_tm->tm_mon);
BCD_TO_BIN(rtc_tm->tm_year);
rtc_tm->tm_year += 100;
rtc_tm->tm_mon -= 1;//确保和用户的 api 一致.比如: month-1 , year+100 等等
return 0;
}
static int s3c2410_rtc_settime(struct rtc_time *tm)//设置时间
{
/* the rtc gets round the y2k problem by just not supporting it */
if (tm->tm_year < 100)
return -EINVAL;
writeb(BIN2BCD(tm->tm_sec), S3C2410_RTCSEC);
writeb(BIN2BCD(tm->tm_min), S3C2410_RTCMIN);
writeb(BIN2BCD(tm->tm_hour), S3C2410_RTCHOUR);
writeb(BIN2BCD(tm->tm_mday), S3C2410_RTCDATE);
writeb(BIN2BCD(tm->tm_mon + 1), S3C2410_RTCMON);
writeb(BIN2BCD(tm->tm_year - 100), S3C2410_RTCYEAR);
return 0;
}
static int s3c2410_rtc_getalarm(struct rtc_wkalrm *alrm)//获取报警时间
{
struct rtc_time *alm_tm = &alrm->time;
unsigned int alm_en;
alm_tm->tm_sec = readb(S3C2410_ALMSEC);
alm_tm->tm_min = readb(S3C2410_ALMMIN);
alm_tm->tm_hour = readb(S3C2410_ALMHOUR);
alm_tm->tm_mon = readb(S3C2410_ALMMON);
alm_tm->tm_mday = readb(S3C2410_ALMDATE);
alm_tm->tm_year = readb(S3C2410_ALMYEAR);
alm_en = readb(S3C2410_RTCALM);
pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
alm_en,
alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);
/* decode the alarm enable field */
if (alm_en & S3C2410_RTCALM_SECEN) {
BCD_TO_BIN(alm_tm->tm_sec);
} else {
alm_tm->tm_sec = 0xff;
}
if (alm_en & S3C2410_RTCALM_MINEN) {
BCD_TO_BIN(alm_tm->tm_min);
} else {
alm_tm->tm_min = 0xff;
}
if (alm_en & S3C2410_RTCALM_HOUREN) {
BCD_TO_BIN(alm_tm->tm_hour);
} else {
alm_tm->tm_hour = 0xff;
}
if (alm_en & S3C2410_RTCALM_DAYEN) {
BCD_TO_BIN(alm_tm->tm_mday);
} else {
alm_tm->tm_mday = 0xff;
}
if (alm_en & S3C2410_RTCALM_MONEN) {
BCD_TO_BIN(alm_tm->tm_mon);
alm_tm->tm_mon -= 1;
} else {
alm_tm->tm_mon = 0xff;
}
if (alm_en & S3C2410_RTCALM_YEAREN) {
BCD_TO_BIN(alm_tm->tm_year);
} else {
alm_tm->tm_year = 0xffff;
}
/* todo - set alrm->enabled ? */
return 0;
}
static int s3c2410_rtc_setalarm(struct rtc_wkalrm *alrm)//设置报警时间
{
struct rtc_time *tm = &alrm->time;
unsigned int alrm_en;
pr_debug("s3c2410_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
alrm->enabled,
tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);
if (alrm->enabled || 1) {
alrm_en = readb(S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
writeb(0x00, S3C2410_RTCALM);
if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
alrm_en |= S3C2410_RTCALM_SECEN;
writeb(BIN2BCD(tm->tm_sec), S3C2410_ALMSEC);
}
if (tm->tm_min < 60 && tm->tm_min >= 0) {
alrm_en |= S3C2410_RTCALM_MINEN;
writeb(BIN2BCD(tm->tm_min), S3C2410_ALMMIN);
}
if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
alrm_en |= S3C2410_RTCALM_HOUREN;
writeb(BIN2BCD(tm->tm_hour), S3C2410_ALMHOUR);
}
pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
writeb(alrm_en, S3C2410_RTCALM);
enable_irq_wake(s3c2410_rtc_alarmno);//设置中断唤醒功能
} else {
alrm_en = readb(S3C2410_RTCALM);
alrm_en &= ~S3C2410_RTCALM_ALMEN;
writeb(alrm_en, S3C2410_RTCALM);
disable_irq_wake(s3c2410_rtc_alarmno);//禁止中断唤醒功能
}
return 0;
}
static int s3c2410_rtc_ioctl(unsigned int cmd, unsigned long arg)//命令操作函数
{
switch (cmd) {
case RTC_AIE_OFF:
case RTC_AIE_ON:
s3c2410_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);//打开报警功能
return 0;
case RTC_PIE_OFF:
case RTC_PIE_ON:
s3c2410_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);//打开tick功能
return 0;
case RTC_IRQP_READ:
return put_user(s3c2410_rtc_freq, (unsigned long __user *)arg);
case RTC_IRQP_SET:
if (arg < 1 || arg > 64)
return -EINVAL;
if (!capable(CAP_SYS_RESOURCE))
return -EACCES;
/* check for power of 2 */
if ((arg & (arg-1)) != 0)
return -EINVAL;
pr_debug("s3c2410_rtc: setting frequency %ld\n", arg);
s3c2410_rtc_setfreq(arg);//设置tick计数值
return 0;
case RTC_UIE_ON:
case RTC_UIE_OFF:
return -EINVAL;
}
return -EINVAL;
}
static int s3c2410_rtc_proc(char *buf)//查看配置消息
{
unsigned int rtcalm = readb(S3C2410_RTCALM);
unsigned int ticnt = readb (S3C2410_TICNT);
char *p = buf;
p += sprintf(p, "alarm_IRQ\t: %s\n",
(rtcalm & S3C2410_RTCALM_ALMEN) ? "yes" : "no" );
p += sprintf(p, "periodic_IRQ\t: %s\n",
(ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
p += sprintf(p, "periodic_freq\t: %d\n", s3c2410_rtc_freq);
return p - buf;
}
static int s3c2410_rtc_open(void)
{
int ret;
ret = request_irq(s3c2410_rtc_alarmno, s3c2410_rtc_alarmirq,
IRQF_SHARED, "s3c2410-rtc alarm", NULL);//报警中断申请
if (ret)
printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_alarmno);
ret = request_irq(s3c2410_rtc_tickno, s3c2410_rtc_tickirq,//tick中断申请
IRQF_SHARED, "s3c2410-rtc tick", NULL);
if (ret) {
printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_tickno);
goto tick_err;
}
return ret;
tick_err:
free_irq(s3c2410_rtc_alarmno, NULL);//释放中断源
return ret;
}
static void s3c2410_rtc_release(void)
{
/* do not clear AIE here, it may be needed for wake *///不清除报警功能,可能用于唤醒功能
disable_irq(s3c2410_rtc_alarmno);//添加关闭中断
s3c2410_rtc_setpie(0);//clean tick interrupt
free_irq(s3c2410_rtc_alarmno, NULL);//释放alarm中断源
free_irq(s3c2410_rtc_tickno, NULL);//释放tick中断源
}
static struct rtc_ops s3c2410_rtcops = {//此操作集为最底层的操作集,其只用来操作硬件在内核方面没有任何意义,其是作为file->private_data来供上
.owner = THIS_MODULE, //层file_operations来使用的,有点类是于自定义的设备结构体通过private_data指针被file_operations使用
.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,
};
static void s3c2410_rtc_enable(struct platform_device *pdev, int en)
{
unsigned int tmp;
if (s3c2410_rtc_base == NULL)//没有映射成功
return;
if (!en) {
tmp = readb(S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_RTCEN, S3C2410_RTCCON);//关闭rtc
tmp = readb(S3C2410_TICNT);
writeb(tmp & ~S3C2410_TICNT_ENABLE, S3C2410_TICNT);//关闭TICK
} else {
/* re-enable the device, and check it is ok */
if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
dev_info(&pdev->dev, "rtc disabled, re-enabling\n");
tmp = readb(S3C2410_RTCCON);
writeb(tmp | S3C2410_RTCCON_RTCEN , S3C2410_RTCCON);//使能RTC
}
if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
dev_info(&pdev->dev, "removing S3C2410_RTCCON_CNTSEL\n");
tmp = readb(S3C2410_RTCCON);
writeb(tmp& ~S3C2410_RTCCON_CNTSEL , S3C2410_RTCCON);//Separate BCD counters
}
if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
dev_info(&pdev->dev, "removing S3C2410_RTCCON_CLKRST\n");
tmp = readb(S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_CLKRST, S3C2410_RTCCON);//ROUND RESET FUNCTION ENABLE
}
}
}
static int s3c2410_rtc_remove(struct platform_device *dev)//注销平台设备时调用
{
unregister_rtc(&s3c2410_rtcops);//取消RTC设备
s3c2410_rtc_setpie(0);//clean tick interrupt
s3c2410_rtc_setaie(0);//取消报警功能
if (s3c2410_rtc_mem != NULL) {
pr_debug("s3c2410_rtc: releasing s3c2410_rtc_mem\n");
iounmap(s3c2410_rtc_base);//取消映射虚拟地址
release_resource(s3c2410_rtc_mem);//释放resource
kfree(s3c2410_rtc_mem);//释放使用页面
//以上两函数可以使用release_mem_region(res->start, res->end-res->start+1)来释放
}
return 0;
}
static int s3c2410_rtc_probe(struct platform_device *pdev)//注册平台设备时调用
{
struct resource *res; //struct resource为平台设备描述结构体
int ret;
pr_debug("%s: probe=%p\n", __FUNCTION__, pdev);//驱动中输出 __FUNCTION__打印函数名称
/* find the IRQs */
s3c2410_rtc_tickno = platform_get_irq(pdev, 1);//为tick申请中断号 /drivers/base/platform.c由于有2个IORESOURCE_IRQ类型数组且其为第2个故取1
if (s3c2410_rtc_tickno <= 0) {
dev_err(&pdev->dev, "no irq for rtc tick\n");//含设备输出函数
return -ENOENT; //No such file or directory
}
s3c2410_rtc_alarmno = platform_get_irq(pdev, 0); //为报警申请中断号 drivers/base/platform.c
if (s3c2410_rtc_alarmno <= 0) {
dev_err(&pdev->dev, "no irq for alarm\n");
return -ENOENT;
}
pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
s3c2410_rtc_tickno, s3c2410_rtc_alarmno);
/* get the memory region */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//根据flags类型查找相应的struct resource
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory region resource\n");
return -ENOENT;
}
s3c2410_rtc_mem = request_mem_region(res->start, res->end-res->start+1,
pdev->name); //申请I/O内存,必须释放
if (s3c2410_rtc_mem == NULL) {
dev_err(&pdev->dev, "failed to reserve memory region\n");
ret = -ENOENT;
goto exit_err;
}
s3c2410_rtc_base = ioremap(res->start, res->end - res->start + 1);//映射为虚拟地址,此时为动态映射在初始化过程中已创造了静态映射页表
if (s3c2410_rtc_base == NULL) {
dev_err(&pdev->dev, "failed ioremap()\n");
ret = -EINVAL;//无效参数
goto exit_err;
}
s3c2410_rtc_mem = res;//struct resource为描述物理地址上资源网状结构
pr_debug("s3c2410_rtc_base=%p\n", s3c2410_rtc_base);
pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));//输出RTCCON寄存器值
/* check to see if everything is setup correctly */
s3c2410_rtc_enable(pdev, 1);//使能rtc和Tick time interrupt
pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
s3c2410_rtc_setfreq(s3c2410_rtc_freq);//设置tick interrupt计数值
/* register RTC and exit */
register_rtc(&s3c2410_rtcops);//注册函数位于arch/arm/common/rtctime.c其实质是将RTC作为杂项设备注册
return 0;
exit_err:
dev_err(&pdev->dev, "error %d during initialisation\n", ret);
return ret;
}
#ifdef CONFIG_PM //配置电源管理
/* S3C2410 RTC Power management control */
static struct timespec s3c2410_rtc_delta;
static int ticnt_save;
static int s3c2410_rtc_suspend(struct platform_device *pdev, pm_message_t state)//关机保存现场调用
{
struct rtc_time tm;//传递数据的结构
struct timespec time;
time.tv_nsec = 0;//纳秒
/* save TICNT for anyone using periodic interrupts */
ticnt_save = readb(S3C2410_TICNT);
/* calculate time delta for suspend */
s3c2410_rtc_gettime(&tm);//读取时间
rtc_tm_to_time(&tm, &time.tv_sec);//转换为格林威治时间秒以秒为单位
save_time_delta(&s3c2410_rtc_delta, &time);//保存系统时间和RTC时间的差值到s3c2410_rtc_delta
s3c2410_rtc_enable(pdev, 0);//关闭RTC
return 0;
}
static int s3c2410_rtc_resume(struct platform_device *pdev)//开机恢复现场调用
{
struct rtc_time tm;
struct timespec time;
time.tv_nsec = 0;
s3c2410_rtc_enable(pdev, 1);//使能RTC
s3c2410_rtc_gettime(&tm);//读取时间
rtc_tm_to_time(&tm, &time.tv_sec);//转换为格林威治时间秒以秒为单位
restore_time_delta(&s3c2410_rtc_delta, &time);//加入偏差值恢复
writeb(ticnt_save, S3C2410_TICNT);//恢复tick计数值000
return 0;
}
#else
#define s3c2410_rtc_suspend NULL
#define s3c2410_rtc_resume NULL
#endif
static struct platform_driver s3c2410_rtcdrv = {
.probe = s3c2410_rtc_probe, //探测函数
.remove = s3c2410_rtc_remove, //移除函数
.suspend = s3c2410_rtc_suspend, //挂起函数
.resume = s3c2410_rtc_resume, //恢复函数
.driver = {
.name = "s3c2410-rtc",//此名称一定要与platform中的名称相同
.owner = THIS_MODULE,
},
};
static char __initdata banner[] = "S3C2410 RTC, (c) 2004 Simtec Electronics\n";
static int __init s3c2410_rtc_init(void)
{
printk(banner);
return platform_driver_register(&s3c2410_rtcdrv);//平台设备即SOC内部集成的独立外设单元(iic iis rtc watchdog usb控制器 lcd控制器)
}
static void __exit s3c2410_rtc_exit(void)
{
platform_driver_unregister(&s3c2410_rtcdrv);
}
module_init(s3c2410_rtc_init);//平台设备注册要在模块注册之前才可以
module_exit(s3c2410_rtc_exit);
MODULE_DESCRIPTION("S3C24XX RTC Driver");
MODULE_AUTHOR("Ben Dooks, <
ben@simtec.co.uk>");
MODULE_LICENSE("GPL");
平台设备RTC
rtc设备被分到杂项设备中,但也可以归入平台设备中作为一种虚拟设备.它是通过二层结构来实现的一个驱动,上层是一个arm common的公共层,对应用程序提供标准的通用的RTC操作接口,下层实现针对具体某一中rtc设备实现的一层驱动。所需要的文件和头文件大致如下所示:
arch/arm/mach-s3c2410/devs.c //包含了对各个部件的resource的分配和定义,在这看rtc的资源;
arch/arm/common/rtctime.c //一个arm平台的通用rtc函数层供应用程序调用,在这里并不区分具体的rtc设备
drivers/char/s3c2410-rtc.c //具体的s3c2410上的rtc chip驱动实现,即操作设备的最底层驱动
include/asm-arm/arch-s3c2410/regs-rtc.h //根据datasheet定义S3C2410内部rtc寄存器
include/asm-arm/rtc.h //arm平台rtc操作抽象层rtctime.c对应的头文件
开发流程如下,参照内核源程序先在devs.c 中定义相关的struct platform_device和struct resource,在 s3c2410-rtc.c中的struct platform_device *smdk2410_devices[]添加&s3c_device_rtc后就可以将rtc注册为platform设备了。
在定义平台设备结构和资源结构后,再定义平台驱动结构后,定义平台设备注册和注销函数。在注册平台设备驱动时其结构体中的probe被调用而使得杂项设备被注册,在注销平台设备驱动时其结构体中的remove被调用而使得杂项设备被注销。当应用程序调用驱动时,其所面对的设备是 rtctime.c定义的杂项设备,平台设备作为虚拟设备只是起到将杂项设备间接注册和注销的作用而存在。应用程序对设备的操作是调用杂项设备的file_opertion中的函数,在函数中通过文件结构私有数据指针来指向 s3c2410-rtc.c中的操作函数而完成的。
由于内核配置了devfs格式,在杂项注册和注销函数中会调用devfs_mk_cdev和devfs_remove函数生成.删除设备文件。而字符设备注册和注销函数则需加入两函数。
调试小结:
SA_INTERRUPT的宏定义在从2.6.24后中没有定义而是改用IRQF_SHARED,具体见头文件interrupt.h中的说明
对于request_irq函数中的中断处理函数已由3个参数变为2个。