⑬tiny4412 Linux驱动开发之RTC子系统驱动程序

本次来说一下Linux的RTC子系统. (Real Time Clock).

在说之前,先说一下STM32上的一些事儿,在移植UCOS的时候,总是想着用timer为系统提供定时中断服务,然后这次搞RTC驱动,忽然,就想到了实际上还可以使用RTC来为UCOS提供系统时钟中断服务,STM32上是有集成RTC的,其实UCOS的创始人出的书上就有说,能提供定时中断的对象有很对,甚至可以用交流电来提供,这样一判断,发现STM32上能提供类似定时中断的器件还是不少呢,比如timer, watchdog, RTC等等都可以.

好了回到主题,本次说一下Linux上的RTC子系统驱动程序如何编写,并在这之前,先来认识一下RTC这个器件.

RTC实时时钟

RTC一般成为RTC实时时钟. 实时时钟(RTC)单元可以在系统电源关闭的情况下依靠备用电池工作,一般主板上都有一个纽扣电池作为实时时钟的电源.RTC可以通过使用STRB/LDDRB这两个ARM指令向CPU传递8为数据(BCD码).数据包括秒, 分, 小时, 日期, 天, 月, 年.RTC实时时钟依靠一个外部的32.768kHz的石英晶体,产生周期性的脉冲信号.每一个脉冲信号到来时,计数器就加1,通过这种方式,完成计时功能.
RTC实时时钟有如下特性:
1).BCD数据:这些数据包括秒,分,小时,日期,星期几,月和年.
2).闰年产生器.
3).闹钟功能:闹钟中断或者从掉电模式唤醒.
4).滴答功能:支持滴答中断或者滴答唤醒.
5).独立电源引脚RTCVDD.
6).支持ms中断作为RTOS内核时钟.

 

RTC实时时钟的功能

如下图所示,是RTC实时时钟的框架图.XrtcXTI和XercXTO产生脉冲信号.XRTCCLKO则是把这个产生的信号输出外部,这个可以通过配置RTCCON寄存器使能或者关闭,传给2^15 的之时钟分频器,得到一个128Hz的频率,这个频率用来产生滴答计数.当TICNT计数为0时,产生一个TIME TICK中断信号.RTCCON寄存器用来控制RTC实时时钟的功能.RTCRST是重置寄存器,用来重置SEC和MIN寄存器.Leap Year Generator是一个闰年发生器,用来产生闰年逻辑.RTCALM用来控制是否产生报警信号.下面对这些功能分别介绍:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第1张图片

 

1,闰年发生器(Leap Year Generator)

闰年发生器可以基于BCDDATE, BCDMON, BCDYEAR决定每月最后一天的日期是28,29,30 还是31.一个8位计数器只能表示两位BCD码,每一位BCD码由4位表示.因此不能决定00年是否是闰年,例如它不能区别1900年还是2000年.RTC模块通过硬件逻辑支持2000年为闰年(注意1900年不是闰年,2000年才是闰年).因此这两位"00"指的是2000年,而不是1900年.

2,读写寄存器

要写BCD寄存器时必须要将RTCCON寄存器的0位置1;要显示秒,分,小时,日期,星期几,月和年等时间,必须单独读取BCDSEC,BCDMIN,BCDHOUR,BCDDAY,BCDDATE,BCDMON,BCDYEAR寄存器的值.但是这中间可能存在1秒钟的偏差,因为要读多个寄存器.例如,用户读取到的结果是2017年12月31日23点59分,如果读取BCDSEC寄存器的值是1~59是没有问题的,但是如果是0,由于存在1秒的偏差,时间将变成2018年1月1日0时0分.这种情况下,应该重新读取BCDYEAR~BCDSEC寄存器的值,否则读出的时间仍然是2017年12月31日23点59分.

3,后备电池

即使系统电源关闭,RTC模块可以由后备电池通过RTCVDD引脚供电.当系统电源关闭时,CPU和RTC的接口应该被阻塞,后备电池应该只驱动晶振电路和BCD计数器,以消耗最少的电量.

 

4,报警功能

在正常模式和掉电模式下,RTC在指定的时刻会产生一个报警信号.正常模式下,报警中断ALMINT有效,对应INT_RTC引脚.掉电模式下,报警中断ALMINT有效外还产生一个唤醒信号PMWKUP,对用PMWKUP引脚.RTC报警寄存器RTCALM决定是否使能报警状态和设置报警条件.

 

5,时钟脉冲中断与时钟唤醒

RTC时钟脉冲用于中断请求,如上图中的TICNT寄存器有一个中断/唤醒使能位和一个相关的计数器值.最高位是中断/唤醒使能位,低7位是计数位,每产生一个时钟脉冲,计数值减1.如果当时钟脉冲发生时,计数器值到达0,那么会发生一个TIME TICK中断.上图方框Time Tick Generator下有一个128Hz的时钟频率,表示1秒钟产生128次时钟滴答.可以给TICNT的低7位赋值,取值范围为0~127,用n表示.则产生中断请求的周期为如下公式所示:
Period = (n + 1) / 128 second

其中,n表示产生中断前需要滴答的次数(1~127).

 

6,后循环测试功能

注意:在Exynos 4412的datasheet上处理上面那个图里边有写RTCRST之外,我没有找到这个寄存器,可能是没有了,但是还是写出来,万一有呢,哈哈, 后循环测试功能由RTCRST寄存器执行.秒执行发生器的循环边界可以选择为30秒,40秒或50秒,第二个值变为0.例如,如果当前时间是23:37:45,循环测试时间为40秒,则循环测试将当前时间设为23:38:00.注意,所有RTC寄存器都必须使用STRB和LDRB指令或者字符型指针操作.

RTC实时时钟的工作原理

RTC实时时钟的工作由多个寄存器来控制.Exynos 4412都有如下寄存器:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第2张图片

从上面的表格看,好像是没有RTCRST寄存器,下面对其中一些寄存器进行一个大概地讲解,详细的请直接参考datasheet.

1.INTP寄存器

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第3张图片

INTP寄存器主要控制ALARM和TICK是否产生中断,从上面的表格来看,只有到了低两位.

2,RTCCON寄存器

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第4张图片

配置寄存器则是一个整体功能的控制,从0位开始包括RTC使能,时钟选择,计数器选择,时钟复位滴答频率选择,滴答使能,以及是否向外部提供RTC时钟.

3,RTC报警控制寄存器RTCALM

RTCALM控制报警使能和报警时间,注意,注意,RTCALM在掉电模式下产生ALMINT和PMWKUP报警信号,而在正常模式下,只产生ALMINT信号.RTCALM寄存器的各位表示的内容如下:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第5张图片

4,RTC报警秒数据寄存器ALMSEC

5,RTC报警分钟数据寄存器ALMMIN

6,RTC报警小时数据寄存器ALMHOUR

7,RTC报警日期数据寄存器ALMDATE

8,RTC报警月数据寄存器ALMMON

9,RTC报警年数据寄存器ALMYEAR

10,RTC秒数据寄存器BCDSEC

11,RTC分数据寄存器BCDMIN

12,RTC小时数据寄存器BCDHOUR

由于代码里把关键的地方都进行了注释,所以,代码就直接贴出来了:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 


enum exynos4_cpu_type {
    TYPE_S3C2410,
    TYPE_S3C64XX,
};

struct exynos4_rtc_drv_data {
    int cpu_type;
};

struct exynos4_rtc_object {
    struct resource *exynos4_rtc_mem;
    void __iomem *exynos4_rtc_base;
    struct clk  *rtc_clk;
    int exynos4_rtc_alarmno;
    int exynos4_rtc_tickno;
    bool wake_en;
    enum exynos4_cpu_type exynos4_rtc_cpu_type;
};

struct exynos4_rtc_object *rtc_obj = NULL;
static DEFINE_SPINLOCK(exynos4_rtc_pie_lock);

static void exynos4_rtc_alarm_clk_enable(bool enable)
{
    static DEFINE_SPINLOCK(exynos4_rtc_alarm_clk_lock);
    static bool alarm_clk_enabled;
    unsigned long irq_flags;

    spin_lock_irqsave(&exynos4_rtc_alarm_clk_lock, irq_flags);

    if(enable){
        if(!alarm_clk_enabled){
            clk_enable(rtc_obj->rtc_clk);
            alarm_clk_enabled = true;
        }
    }
    else{
        if(alarm_clk_enabled){
            clk_disable(rtc_obj->rtc_clk);
            alarm_clk_enabled = false;
        }
    }

    spin_unlock_irqrestore(&exynos4_rtc_alarm_clk_lock, irq_flags);
}

irqreturn_t 
alarm_irq_handler(int irqno, void *devid)
{
    struct rtc_device *rdev = devid;

    clk_enable(rtc_obj->rtc_clk);

    rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);

    if(TYPE_S3C2410 == rtc_obj->exynos4_rtc_cpu_type)
        writeb(S3C2410_INTP_ALM, rtc_obj->exynos4_rtc_base + S3C2410_INTP);

    clk_disable(rtc_obj->rtc_clk);

    exynos4_rtc_alarm_clk_enable(false);

    return IRQ_HANDLED;
}

irqreturn_t 
tick_irq_handler(int irqno, void *devid)
{
    struct rtc_device *rdev = devid;

    clk_enable(rtc_obj->rtc_clk);

    rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);

    if(TYPE_S3C64XX == rtc_obj->exynos4_rtc_cpu_type)
        writeb(S3C2410_INTP_TIC, rtc_obj->exynos4_rtc_base + S3C2410_INTP);

    clk_disable(rtc_obj->rtc_clk);

    return IRQ_HANDLED;
}

static int exynos4_rtc_setfreq(struct device *dev, int freq)
{
    struct paltform_device *pdev = to_platform_device(dev);
    struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
    unsigned int temp = 0;
    int val;

    if(!is_power_of_2(freq))
        return -EINVAL;

    clk_enable(rtc_obj->rtc_clk);
    spin_lock_irq(&exynos4_rtc_pie_lock);

    if(TYPE_S3C64XX != rtc_obj->exynos4_rtc_cpu_type){
        temp = readb(rtc_obj->exynos4_rtc_base + S3C2410_TICNT);
        temp &= S3C2410_TICNT_ENABLE;
    }

    val = (rtc_dev->max_user_freq / freq) - 1;

    temp |= val; 

    writel(temp, rtc_obj->exynos4_rtc_base + S3C2410_TICNT);

    spin_unlock_irq(&exynos4_rtc_pie_lock);
    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

int 
exynos4_rtc_open(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct rtc_device *rtc = platform_get_drvdata(pdev);
    int ret = -1;

    // IRQF_DISABLED代表快速中断,使用datasheet上默认的中断触发方式
    ret = request_irq(rtc_obj->exynos4_rtc_alarmno, alarm_irq_handler, IRQF_DISABLED, "exynos4 alarm_irq", rtc);
    if(ret){
        printk("request alarm irq failed !\n");
        return ret;
    }

    ret = request_irq(rtc_obj->exynos4_rtc_tickno, tick_irq_handler, IRQF_DISABLED, "exynos4 tick_irq", rtc);
    if(ret){
        printk("request tick irq failed !\n");
	free_irq(rtc_obj->exynos4_rtc_alarmno, rtc);
        return ret;
    }

    return 0;
}

int 
exynos4_rtc_setaie(struct device *dev, unsigned int enabled)
{
    unsigned int temp;

    clk_enable(rtc_obj->rtc_clk);

    temp = readb(rtc_obj->exynos4_rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;

    if(enabled)
        temp |= S3C2410_RTCALM_ALMEN;

    writeb(temp, rtc_obj->exynos4_rtc_base + S3C2410_RTCALM);

    clk_disable(rtc_obj->rtc_clk);

    exynos4_rtc_alarm_clk_enable(enabled);

    return 0;
}

int 
exynos4_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
    unsigned int have_retried = 0;
    void __iomem *base = rtc_obj->exynos4_rtc_base;

    printk("-----%s-----\n", __func__);    // 用户测试程序里会调用这个函数,但时候会打印这个函数名

    clk_enable(rtc_obj->rtc_clk);

retry_read_time:
    tm->tm_min  = readb(base + S3C2410_RTCMIN);
    tm->tm_hour = readb(base + S3C2410_RTCHOUR);
    tm->tm_mday = readb(base + S3C2410_RTCDATE);
    tm->tm_mon  = readb(base + S3C2410_RTCMON);
    tm->tm_year = readb(base + S3C2410_RTCYEAR);
    tm->tm_sec  = readb(base + S3C2410_RTCSEC);

    // 判断读到的秒数是不是0,如果是0的话,说明已经更新了分钟寄存器了,重读
    if((0 == tm->tm_sec) && !have_retried){
        have_retried = 1;
        goto retry_read_time;
    }

    tm->tm_sec  = bcd2bin(tm->tm_sec);
    tm->tm_min  = bcd2bin(tm->tm_min);
    tm->tm_hour = bcd2bin(tm->tm_hour);
    tm->tm_mday = bcd2bin(tm->tm_mday);
    tm->tm_mon  = bcd2bin(tm->tm_mon);
    tm->tm_year = bcd2bin(tm->tm_year);

    tm->tm_year += 100;
    tm->tm_mon  -= 1;

    clk_disable(rtc_obj->rtc_clk);

    return rtc_valid_tm(tm);;
}

int 
exynos4_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
    void __iomem *base = rtc_obj->exynos4_rtc_base;
    int year = tm->tm_year - 100;

    if(year < 0 || year >= 100){
        printk("set time year more than 100 !\n");
        return -EINVAL;
    }

    clk_enable(rtc_obj->rtc_clk);

    writeb(bin2bcd(tm->tm_sec), base + S3C2410_RTCSEC);
    writeb(bin2bcd(tm->tm_min), base + S3C2410_RTCMIN);
    writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR);
    writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE);
    writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON);
    writeb(bin2bcd(year), base + S3C2410_RTCYEAR);

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

int 
exynos4_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
    struct rtc_time *alm_tm = &alm->time;
    void __iomem *base = rtc_obj->exynos4_rtc_base;
    unsigned int alm_en;

    clk_enable(rtc_obj->rtc_clk);

    alm_tm->tm_sec  = readb(base + S3C2410_ALMSEC);
    alm_tm->tm_min  = readb(base + S3C2410_ALMMIN);
    alm_tm->tm_hour = readb(base + S3C2410_ALMHOUR);
    alm_tm->tm_mday = readb(base + S3C2410_ALMDATE);
    alm_tm->tm_mon  = readb(base + S3C2410_ALMMON);
    alm_tm->tm_year = readb(base + S3C2410_ALMYEAR);

    alm_en = readb(base + S3C2410_RTCALM);

    alm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;

    if(alm_en & S3C2410_RTCALM_SECEN)
        alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec);
    else
        alm_tm->tm_sec = -1;

    if(alm_en & S3C2410_RTCALM_MINEN)
        alm_tm->tm_min = bcd2bin(alm_tm->tm_min);
    else
        alm_tm->tm_min = -1;
    
    if(alm_en & S3C2410_RTCALM_HOUREN)
        alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);
    else
        alm_tm->tm_hour = -1;
    
    if(alm_en & S3C2410_RTCALM_DAYEN)
        alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);
    else
        alm_tm->tm_mday = -1;
    
    if(alm_en & S3C2410_RTCALM_MONEN)
        alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon);
    else
        alm_tm->tm_mon = -1;
    
    if(alm_en & S3C2410_RTCALM_YEAREN)
        alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
    else
        alm_tm->tm_year = -1;

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

int 
exynos4_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
    struct rtc_time *tm = &alm->time;
    void __iomem *base = rtc_obj->exynos4_rtc_base;
    unsigned int alm_en;

    clk_enable(rtc_obj->rtc_clk);

    alm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
    writeb(0x00, base + S3C2410_RTCALM);

    if(tm->tm_sec < 60 && tm->tm_sec >= 0){
        alm_en |= S3C2410_RTCALM_SECEN;
        writeb(bin2bcd(tm->tm_sec), base + S3C2410_ALMSEC);
    }
    
    if(tm->tm_min < 60 && tm->tm_min >= 0){
        alm_en |= S3C2410_RTCALM_MINEN;
        writeb(bin2bcd(tm->tm_min), base + S3C2410_ALMMIN);
    }

    if(tm->tm_hour < 24 && tm->tm_hour >= 0){
        alm_en |= S3C2410_RTCALM_HOUREN;
        writeb(bin2bcd(tm->tm_hour), base + S3C2410_ALMHOUR);
    }

    writeb(alm_en, base + S3C2410_RTCALM);

    exynos4_rtc_setaie(dev, alm->enabled);

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

int 
exynos4_rtc_proc(struct device *dev, struct seq_file *seq)
{
    unsigned int ticnt;

    clk_enable(rtc_obj->rtc_clk);

    if(TYPE_S3C64XX == rtc_obj->exynos4_rtc_cpu_type){
        ticnt = readw(rtc_obj->exynos4_rtc_base + S3C2410_RTCCON);
        ticnt &= S3C64XX_RTCCON_TICEN;
    }
    else{
        ticnt = readb(rtc_obj->exynos4_rtc_base + S3C2410_TICNT);
        ticnt &= S3C2410_TICNT_ENABLE;
    }

    seq_printf(seq, "periodic_IRQ\t: %s\n", ticnt ? "yes" : "no");

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

void 
exynos4_rtc_release(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct rtc_device *rtc = platform_get_drvdata(pdev);

    exynos4_rtc_setaie(dev, 0);
    free_irq(rtc_obj->exynos4_rtc_tickno, rtc);
    free_irq(rtc_obj->exynos4_rtc_alarmno, rtc);
}

static const struct rtc_class_ops exynos4_rtcops = {
    .open       = exynos4_rtc_open,
    .release    = exynos4_rtc_release,
    .read_time  = exynos4_rtc_read_time,
    .set_time   = exynos4_rtc_set_time,
    .read_alarm = exynos4_rtc_read_alarm,
    .set_alarm  = exynos4_rtc_set_alarm,
    .proc       = exynos4_rtc_proc,
    .alarm_irq_enable = exynos4_rtc_setaie,
};

static void exynos4_rtc_enable(struct platform_device *pdev, int en)
{
    void __iomem *base = rtc_obj->exynos4_rtc_base;
    unsigned int temp;

    if(NULL == rtc_obj->exynos4_rtc_base)
        return;

    clk_enable(rtc_obj->rtc_clk);

    if(!en){
        temp = readw(base + S3C2410_RTCCON);
        if(TYPE_S3C64XX == rtc_obj->exynos4_rtc_cpu_type)
            temp &= ~S3C64XX_RTCCON_TICEN;
        temp &= ~S3C2410_RTCCON_RTCEN;
        writew(temp, base + S3C2410_RTCCON);

        if(TYPE_S3C64XX != rtc_obj->exynos4_rtc_cpu_type){
            temp = readb(base + S3C2410_TICNT);
            temp &= ~S3C2410_TICNT_ENABLE;
            writeb(temp, base + S3C2410_TICNT);
        }
    }
    else{
        if(0 == (readw(base + S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN)){
            temp = readw(base + S3C2410_RTCCON);
            writew(temp | S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);
        }

        if((readw(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
            temp = readw(base + S3C2410_RTCCON);
            writew(temp & ~S3C2410_RTCCON_CNTSEL, base + S3C2410_RTCCON);
        }

        if(readw(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST){
            temp = readw(base + S3C2410_RTCCON);
            writew(temp & ~S3C2410_RTCCON_CLKRST, base + S3C2410_RTCCON);
        }
    }

    clk_disable(rtc_obj->rtc_clk);
}

static const struct of_device_id exynos4_rtc_dt_match[];

static inline int exynos4_rtc_get_driver_data(struct platform_device *pdev)
{
#ifdef CONFIG_OF
    struct exynos4_rtc_drv_data *data;
    if(pdev->dev.of_node){
        const struct of_device_id *match;
        match = of_match_node(exynos4_rtc_dt_match, pdev->dev.of_node);
        data = (struct exynos4_rtc_drv_data *)mach->data;
        return data->cpu_type;
    }
#endif
    return platform_get_device_id(pdev)->driver_data;
}

int 
exynos4_rtc_probe(struct platform_device *pdev)
{
    struct rtc_device *rtc;
    struct rtc_time rtc_tm;
    struct resource *res;
    int ret = -1;

    printk("Exynos RTC start (c) 2018...\n");

    // 1,申请设备对象
    rtc_obj = kzalloc(sizeof(struct exynos4_rtc_object), GFP_KERNEL);
    if(NULL == rtc_obj){
        printk("kzalloc failed !\n");
        return -ENOMEM;
    }

    // 2,获取平台自定义数据
    // 2.1,获取tick中断
    rtc_obj->exynos4_rtc_tickno = platform_get_irq(pdev, 1);
    if(rtc_obj->exynos4_rtc_tickno < 0){
        printk("platform get tick irq failed !\n");
        goto err1;
    }

    // 2.2,获取alarm寄存器中断
    rtc_obj->exynos4_rtc_alarmno = platform_get_irq(pdev, 0);
    if(rtc_obj->exynos4_rtc_alarmno < 0){
        printk("paltform get alarm irq failed !\n");
        goto err2;
    }

    // 2.3,获取内存资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(NULL == res){
        printk("platform get io resource failed !\n");
        goto err3;
    }

    // 3,申请内存资源空间
    rtc_obj->exynos4_rtc_mem = request_mem_region(res->start, resource_size(res), pdev->name);
    if(NULL == rtc_obj->exynos4_rtc_mem){
        printk("request mem failed !\n");
        goto err3;
    }

    // 4,映射内存到虚拟空间
    rtc_obj->exynos4_rtc_base = ioremap(res->start, resource_size(res));
    if(NULL == rtc_obj->exynos4_rtc_base){
        printk("ioremap failed !\n");
        goto err4;
    }

    // 5,获取RTC时钟频率
    rtc_obj->rtc_clk = clk_get(&pdev->dev, "rtc");
    if(IS_ERR(rtc_obj->rtc_clk)){
        printk("clk get failed !\n");
        goto err5;    
    }

    // 6,使能RTC时钟
    clk_enable(rtc_obj->rtc_clk);

    // 7,配置RTC寄存器,使能
    exynos4_rtc_enable(pdev, 1);

    // 8,设置设备是否可以唤醒
    device_init_wakeup(&pdev->dev, 1);

    // 9,注册RTC驱动程序
    rtc = rtc_device_register("s3c", &pdev->dev, &exynos4_rtcops, THIS_MODULE);
    if(IS_ERR(rtc)){
        printk("rtc device register failed !\n");
        goto err6;
    }

    // 10,获取driver的数据
    rtc_obj->exynos4_rtc_cpu_type = exynos4_rtc_get_driver_data(pdev);

    // 11,检查RTC是否正常工作
    exynos4_rtc_read_time(NULL, &rtc_tm);
    if(rtc_valid_tm(&rtc_tm)){  // 如果数据无效,则设置默认数据
        rtc_tm.tm_year  = 118;
        rtc_tm.tm_mon   = 0;
        rtc_tm.tm_mday  = 1;
        rtc_tm.tm_hour  = 12;
        rtc_tm.tm_min   = 0;
        rtc_tm.tm_sec   = 0;

        exynos4_rtc_set_time(NULL, &rtc_tm);
    }

    // 12,设置用户自定义的最大频率值
    if(TYPE_S3C2410 == rtc_obj->exynos4_rtc_cpu_type)
        rtc->max_user_freq = 128;
    else
        rtc->max_user_freq = 32768;
    
    // 13,把RTC的数据赋给pdev
    platform_set_drvdata(pdev, rtc);

    // 14,设置时钟中断频率
    exynos4_rtc_setfreq(&pdev->dev, 1);

    // 15,关闭rtc时钟
    clk_disable(rtc_obj->rtc_clk);

    return 0;

err6:
    exynos4_rtc_enable(pdev, 0);
    clk_disable(rtc_obj->rtc_clk);
    clk_put(rtc_obj->rtc_clk);
err5:
    iounmap(rtc_obj->exynos4_rtc_base);
err4:
    release_resource(rtc_obj->exynos4_rtc_mem);
err3:
    rtc_obj->exynos4_rtc_alarmno = -1;
err2:
    rtc_obj->exynos4_rtc_tickno = -1;
err1:
    kfree(rtc_obj);
    return ret;
}

int 
exynos4_rtc_remove(struct platform_device *pdev)
{
    struct rtc_device *rtc = platform_get_drvdata(pdev);

    platform_set_drvdata(pdev, NULL);

    rtc_device_unregister(rtc);

    exynos4_rtc_setaie(&pdev->dev, 0);

    exynos4_rtc_enable(pdev, 0);

    clk_disable(rtc_obj->rtc_clk);
    clk_put(rtc_obj->rtc_clk);
    rtc_obj->rtc_clk = NULL;

    iounmap(rtc_obj->exynos4_rtc_base);

    release_resource(rtc_obj->exynos4_rtc_mem);

    rtc_obj->exynos4_rtc_alarmno = -1;
    rtc_obj->exynos4_rtc_tickno = -1;

    kfree(rtc_obj);

    return 0;
}

#if defined CONFIG_PM
static int ticnt_save, ticnt_en_save;

int 
exynos4_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
    clk_enable(rtc_obj->rtc_clk);

    ticnt_save = readb(rtc_obj->exynos4_rtc_base + S3C2410_TICNT);
    if(TYPE_S3C64XX == rtc_obj->exynos4_rtc_cpu_type){
        ticnt_en_save = readw(rtc_obj->exynos4_rtc_base + S3C2410_RTCCON);
        ticnt_en_save &= S3C64XX_RTCCON_TICEN;
    }
    exynos4_rtc_enable(pdev, 0);

    if(device_may_wakeup(&pdev->dev) && !rtc_obj->wake_en){
        if(0 == enable_irq_wake(rtc_obj->exynos4_rtc_alarmno))
            rtc_obj->wake_en = true;
        else
            printk("enable_irq_wake failed !\n");
    }

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}

int 
exynos4_rtc_resume(struct platform_device *pdev)
{
    unsigned int temp;

    clk_enable(rtc_obj->rtc_clk);

    exynos4_rtc_enable(pdev, 1);
    writeb(ticnt_save, rtc_obj->exynos4_rtc_base + S3C2410_TICNT);
    if(TYPE_S3C64XX == rtc_obj->exynos4_rtc_cpu_type && ticnt_en_save){
        temp = readw(rtc_obj->exynos4_rtc_base + S3C2410_RTCCON);
        writew(temp | ticnt_en_save, rtc_obj->exynos4_rtc_base + S3C2410_RTCCON);
    }

    if(device_may_wakeup(&pdev->dev) && rtc_obj->wake_en){
        disable_irq_wake(rtc_obj->exynos4_rtc_alarmno);
        rtc_obj->wake_en = false;
    }

    clk_disable(rtc_obj->rtc_clk);

    return 0;
}
#else
#define exynos4_rtc_suspend     NULL
#define exynos4_rtc_resume      NULL
#endif

#ifdef CONFIG_OF
static struct exynos4_rtc_drv_data exynos4_rtc_drv_data_array[] = {
    [TYPE_S3C2410] = { TYPE_S3C2410 },
    [TYPE_S3C64XX] = { TYPE_S3C64XX },
};

static const struct of_device_id exynos4_rtc_dt_match[] = {
    {
        .compatible = "samsung,s3c2410-rtc",
        .data       = &exynos4_rtc_drv_data_array[TYPE_S3C2410],
    },
    {
        .compatible = "samsung,s3c64xx-rtc",
        .data       = &exynos4_rtc_drv_data_array[TYPE_S3C64XX],
    },
};
MODULE_DEVICE_TABLE(of, exynos4_rtc_dt_match);
#else
#define exynos4_rtc_dt_match    NULL
#endif

const struct platform_device_id exynos4_rtc_id_table[] = {
    {
        .name       = "s3c2410-rtc",
        .driver_data= TYPE_S3C2410,
    },
    {
        .name       = "s3c64xx-rtc",
        .driver_data= TYPE_S3C64XX,
    },
};

static struct platform_driver exynos4_rtc_driver = {
    .driver = {
        .name   = "s3c-rtc",
        .owner  = THIS_MODULE,
        .of_match_table = exynos4_rtc_dt_match,
    },
    .probe  = exynos4_rtc_probe,
    .remove = __devexit_p(exynos4_rtc_remove),
    .suspend= exynos4_rtc_suspend,
    .resume = exynos4_rtc_resume,
    .id_table = exynos4_rtc_id_table,
};

module_platform_driver(exynos4_rtc_driver);

MODULE_LICENSE("GPL");

由于Linux开机的时候,需要读取RTC时间,所以,这个加载要开机即加载,所以,我们把它加入到Linux开机启动的行列中,位置是:\drivers\rtc目录下面,我们这个驱动的名字叫rtc-exynos4.c,我们来修改一下\drivers\rtc目录下的Kconfig和Makefile文件,修改如下:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第6张图片

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第7张图片

然后在menuconfig里配置如下:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第8张图片

接下来是测试代码:

#include 
#include 

#include 
#include 
#include 
#include 
#include 

#include 

int main(void)
{
    int fd = -1,
        ret= -1;
    struct rtc_time rtc_tm;

    fd = open("/dev/rtc0", O_RDWR);
    if(fd < 0){
        perror("open failed");
        exit(1);
    }

    ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
    if(ret < 0){
        perror("ioctl failed");
        exit(1);
    }

    printf("%d-%d-%d, %d:%d:%d\n", rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday,
                rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);

    if(close(fd) < 0){
        perror("close");
        exit(1);
    }

    return 0;
}

然后是测试效果:

⑬tiny4412 Linux驱动开发之RTC子系统驱动程序_第9张图片

你可能感兴趣的:(Linux)