首页 > 代码库 > SylixOS中RTC设备驱动

SylixOS中RTC设备驱动

1、概述

本文档基于SylixOS-EVB-i.MX6Q验证平台,介绍SylixOSRTC设备驱动实现过程,可作为在SylixOS集成开发环境下进行字符设备驱动开发的参考。

 

2RTC设备驱动

2.1硬件原理

实时时钟(RTC)的主要功能是在系统掉电的情况下,利用备用电源使时钟继续运行,保证不会丢失时间信息。

i.MX6Q验证平台上使用的是外置实时时钟集成电路ISL1208。硬件接线如图 2.1所示。

技术分享

2.1 RTC硬件接线

    图中,X1X2为内部反向放大器的输入和输出引脚,要求外置一个32.768kHz的晶体振荡器以提供振荡源;VBAT为备用电源提供端,当VDD电源失效时,VBAT端的电源立即工作,保证在外部供电中断的情况下,内部的时钟信号产生电路依旧正常工作;SDASCL为连接到I2C总线的两个引脚,用于传输串行数据信号和时钟信号,最高传输速率达到400kHznRQ/FOUT是一个多功能引脚,可以将其配置为中断输出或固定频率输出端;VDDGND分别为电源端和地,ISL1208的工作电压为2.0~5.5V

2.2驱动实现

2.2.1安装RTC设备驱动程序

参照上一章节字符设备驱动模型,调用API_RtcDrvInstall内核函数,注册RTC设备驱动程序,包括RTC设备的创建、删除、打开、关闭、读写及IO控制等驱动函数,注册的过称即是在内核维护的驱动程序表中,找到空闲位置,然后将驱动函数填入该空闲驱动程序表。注册RTC设备驱动流程如程序清单 2.1所示,函数返回驱动函数索引号_G_iRtcDrvNum——驱动函数在驱动程序表中的位置。

程序清单 2.1安装RTC设备驱动程序

LW_API  INT  API_RtcDrvInstall (VOID)
{
    if (_G_iRtcDrvNum > 0) {
        return  (ERROR_NONE);
    }
    
    _G_iRtcDrvNum = iosDrvInstall(__rtcOpen, 
                                  (FUNCPTR)LW_NULL, 
                                  __rtcOpen,
                                  __rtcClose,
                                  LW_NULL,
                                  LW_NULL,
                                  __rtcIoctl);
    DRIVER_LICENSE(_G_iRtcDrvNum,     "GPL->Ver 2.0");
    DRIVER_AUTHOR(_G_iRtcDrvNum,      "Han.hui");
    DRIVER_DESCRIPTION(_G_iRtcDrvNum, "hardware rtc.");

    return  ((_G_iRtcDrvNum > 0) ? (ERROR_NONE) : (PX_ERROR));
}



2.2.2创建设备

1. ISL1208 RTC驱动程序

ISL1208驱动程序主要包括RTC初始化、时间设置、时间获取。如程序清单 2.2所示。

程序清单 2.2 ISL1208 RTC驱动程序集

static  LW_RTC_FUNCS     _G_isl1208RtcFuncs = {
        isl1208RtcInit,
        isl1208RtcSetTime,
        isl1208RtcGetTime,
        LW_NULL
};


1isl1208RtcInit——RTC初始化

由于ISL1208 RTC通过I2C总线进行数据传输,所以需要在RTC初始化函数中调用API_I2cDeviceCreate内核函数创建RTC设备,将其作为从设备挂接在I2C总线上(前提是已实现验证平台上的I2C驱动并创建I2C适配器),这样,RTC进行数据传输时即可根据适配器名查找到对应的I2C适配器,并调用I2C适配器的操作函数。具体实现如程序清单 2.3所示。

程序清单 2.3 RTC初始化

static VOID  isl1208RtcInit (VOID)
{
    __PISL1208_RTC_CONTROLER  pRtcControler = &_G_isl1208RtcControlers[0];

    if (!pRtcControler->RTCC_pI2cDevice) {
        pRtcControler->RTCC_pI2cDevice = API_I2cDeviceCreate(pRtcControler->RTCC_pcI2cBusName,
                                                             "/dev/rtc",
                                                             0x6F,      /* ISL 芯片的设备地址          */
                                                             0);
        if (pRtcControler->RTCC_pI2cDevice) {
        	isl1208RtcHwInit();
        }
    }
}


2isl1208RtcSetTime——RTC时间设置

RTC的时间设置过程:

  1. 将待设置时间进行转码并填入数据buf

  2. 封装I2C总线传输控制消息,消息中包含RTC从设备地址、消息长度、传输控制标志(读/写)、数据buf

  3. 发送I2C控制消息,控制RTC设备写使能;

  4. 发送I2C控制消息,填充RTC对应时间设置寄存器;

  5. 发送I2C控制消息,控制RTC设备写失能。

RTC时间设置过程如程序清单 2.4所示,其中isl1208ReadRegisl1208SetRegs即为封装I2C总线传输控制消息并调用总线传输控制函数进行消息传输的过程,这里不做过多描述。

程序清单 2.4 RTC时间设置

static INT  isl1208RtcSetTime (PLW_RTC_FUNCS  pRtcFuncs, time_t  *pTimeNow)
{
    __PISL1208_RTC_CONTROLER  pRtcControler = &_G_isl1208RtcControlers[0];
    struct tm                 tmNow;
    UINT8                     aucBuffer[ISL1208_RTC_SECTION_LEN] = { 0, };
    UINT8                     ucCtlDat[1], ucRegDat[1];

    gmtime_r(pTimeNow, &tmNow);                                         /*  转换成 tm 时间格式          */

    /*
     * 将待设置时间进行转码并填入数据buf
     */
    aucBuffer[ISL1208_REG_YR] = rtcBinToBcd(tmNow.tm_year % 100);
    aucBuffer[ISL1208_REG_MO] = rtcBinToBcd(tmNow.tm_mon + 1);
    aucBuffer[ISL1208_REG_DT] = rtcBinToBcd(tmNow.tm_mday);
    aucBuffer[ISL1208_REG_DW] = rtcBinToBcd(tmNow.tm_wday & 7);
    aucBuffer[ISL1208_REG_HR] = rtcBinToBcd(tmNow.tm_hour);
    aucBuffer[ISL1208_REG_MN] = rtcBinToBcd(tmNow.tm_min);
    aucBuffer[ISL1208_REG_SC] = rtcBinToBcd(tmNow.tm_sec);

    /*
     * 读取 RTC 的状态值并设置 WRTC, 控制 RTC 写使能
     */
    isl1208ReadReg(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucRegDat, 1);
    ucCtlDat[0] = ucRegDat[0] | ISL1208_REG_SR_WRTC;
    isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucCtlDat, 1);

    /*
     * 写入待设置时间
     */
    isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, 0, aucBuffer, ISL1208_RTC_SECTION_LEN);

    /*
     * 设置 WRTC, 控制 RTC 写失能
     */
    ucCtlDat[0] = ucRegDat[0] & ~ISL1208_REG_SR_WRTC;
    isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucCtlDat, 1);

    return  (ERROR_NONE);
}


3isl1208RtcGetTime——RTC时间获取

RTC时间获取就是调用isl1208ReadReg函数,读取RTC设备存放时间信息的寄存器,然后将获取的时间进行转码,获得当前年、月、日、时、分、秒等信息,填入相应结构体。具体实现过程如程序清单 2.5所示。

程序清单 2.5 RTC时间获取

static INT  isl1208RtcGetTime (PLW_RTC_FUNCS  pRtcFuncs, time_t  *pTimeNow)
{
    __PISL1208_RTC_CONTROLER  pRtcControler = &_G_isl1208RtcControlers[0];
    struct tm                 tmNow;
    UINT8                     ucValue;
    UINT8                     aucBuffer[ISL1208_RTC_SECTION_LEN] = { 0, };

    /*
     * 读取RTC设备存放时间信息的寄存器
     */
    isl1208ReadReg(pRtcControler->RTCC_pI2cDevice, 0, aucBuffer, ISL1208_RTC_SECTION_LEN);

    /*
     * 对获取的时间信息进行转码并填入相应的结构体
     */
    tmNow.tm_sec  = rtcBcdToBin(aucBuffer[ISL1208_REG_SC]);
    tmNow.tm_min  = rtcBcdToBin(aucBuffer[ISL1208_REG_MN]);

    ucValue = aucBuffer[ISL1208_REG_HR];
    if (ucValue & ISL1208_REG_HR_MIL) {                                 /*  24h 格式                    */
        tmNow.tm_hour = rtcBcdToBin(ucValue & 0x3F);
    } else {                                                            /*  12h 格式                    */
        if (ucValue & ISL1208_REG_HR_PM) {                              /*  PM 标志设置                 */
            tmNow.tm_hour = 12 + rtcBcdToBin(ucValue & 0x1F);
        } else {
            tmNow.tm_hour = rtcBcdToBin(ucValue & 0x1F);
        }
    }

    tmNow.tm_wday  = rtcBcdToBin(aucBuffer[ISL1208_REG_DW]);
    tmNow.tm_mday  = rtcBcdToBin(aucBuffer[ISL1208_REG_DT]);
    tmNow.tm_mon   = rtcBcdToBin(aucBuffer[ISL1208_REG_MO]) - 1;
    tmNow.tm_year  = rtcBcdToBin(aucBuffer[ISL1208_REG_YR]) + 100;
    tmNow.tm_yday  = 0;
    tmNow.tm_isdst = 0;

    if (pTimeNow) {
        *pTimeNow = timegm(&tmNow);
    }

    return  (ERROR_NONE);
}


2. 创建RTC设备

调用API_RtcDevCreate创建RTC设备,设备结构体prtcdev中保存的操作函数集就是函数入参——ISL1208 RTC驱动程序_G_isl1208RtcFuncs。然后再调用API_IosDevAddEx内核函数,向系统中添加一个设备,该函数中主要就是填充设备结构体中的另一个成员——设备头,设备头中保存着设备名称、设备驱动索引号、设备类型、打开次数等。最后,将该设备头添加入设备头管理链表进行管理。RTC设备的创建过程如程序清单 2.6所示。

程序清单 2.6 RTC设备创建

LW_API  INT  API_RtcDevCreate (PLW_RTC_FUNCS    prtcfuncs)
{
    PLW_RTC_DEV     prtcdev;
    
    if (prtcfuncs == LW_NULL) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    
    if (_G_iRtcDrvNum <= 0) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "no driver.\r\n");
        _ErrorHandle(ERROR_IO_NO_DRIVER);
        return  (PX_ERROR);
    }

    /*
     * 为设备结构体分配空间
     */
    prtcdev = (PLW_RTC_DEV)__SHEAP_ALLOC(sizeof(LW_RTC_DEV));
    if (prtcdev == LW_NULL) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "system low memory.\r\n");
        _ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);
        return  (PX_ERROR);
    }
    lib_bzero(prtcdev, sizeof(LW_RTC_DEV));
    
    /*
     * 保存RTC设备驱动程序
     */
    prtcdev->RTCDEV_prtcfuncs = prtcfuncs;

    /*
     * 向系统中添加一个设备,将设备头链入链表进行管理
     */
    if (iosDevAddEx(&prtcdev->RTCDEV_devhdr, __LW_RTC_DEV_NAME, _G_iRtcDrvNum, DT_CHR) != ERROR_NONE) {
        __SHEAP_FREE((PVOID)prtcdev);
        return  (PX_ERROR);
    }
    
    /*
     * 初始化硬件
     */
    if (prtcfuncs->RTC_pfuncInit) {
        prtcfuncs->RTC_pfuncInit();
    }

    return  (ERROR_NONE);
}


3RTC时间同步

RTC设备创建完成并安装完设备驱动程序后,即可调用API_RtcToSys内核函数进行时间同步。该函数调用API_RtcGet函数,打开已安装的RTC设备,通过ioctl命令控制,获取当前RTC时间,最终调用lib_clock_settime,将获取的时间设置为系统时间。具体实现过程如程序清单 3.1所示。

程序清单 3.1系统时间同步

LW_API  INT  API_RtcToSys (VOID)
{
    struct timespec   tv;
    
    /*
     * 获取当前RTC时间
     */
    if (API_RtcGet(&tv.tv_sec) < 0) {
        return  (PX_ERROR);
    }
    tv.tv_nsec = 0;

    /*
     * 将获取的时间设置为系统时间
     */
    return  (lib_clock_settime(CLOCK_REALTIME, &tv));
}



SylixOS中RTC设备驱动