Linux驱动开发: Linux下RTC实时时钟驱动

简介: Linux驱动开发: Linux下RTC实时时钟驱动

Linux内核版本: 3.5


1.1 Linux下RTC时间的读写分析

1.1.1 系统时间与RTC实时时钟时间

Linux系统下包含两个时间:系统时间和RTC时间。


系统时间:是由主芯片的定时器进行维护的时间,一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是掉电后不保存。


RTC时间:是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行。


每次Linux系统启动后在启动过程中会检测和挂载RTC驱动,在挂载后会自动从RTC芯片中读取时间并设置到系统时间中去。此后如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。


linux命令中的date和time等命令都是用来设置系统时间的,而hwclock命令是用来设置和读写RTC时间的。


1.1.2  Linux内核RTC实时时钟配置查看与选择:

进入到内核根目录下,输入: make menuconfig 进入到内核配置菜单:

image.png

根据选项进入到RTC实时驱动菜单:

image.png

根据内核的配置得知3个信息(红色选中的配置选项):


1. 系统时间默认从RTC0里获取时间进行设置。


(rtc0表示/dev下第一个rtc驱动,如果安装了第二个RTC驱动,就以rtc1表示,依次类推)


2. 使用proc查看RTC信息,默认只能从rtc0节点里获取(系统里的第一个rtc驱动)

image.png

image.png

3. 内核默认选择CPU本身自带的RTC作为系统实时时钟。


驱动源码\linux-3.5\drivers\rtc\ rtc-s3c.c是三星公司编写的RTC驱动。


1.1.3 date命令使用介绍

date是用来显示或设定系统的日期与时间的命令。


命令使用格式: date [参数]... [+格式]


命令可以的参数如下:

image.png

系统时间的方式

image.png

格式示例

image.png

1.1.4 系统RTC实时时钟时间的获取与设置

1. 将RTC时间同步到系统时间

image.png

为了在启动时自动执行RTC时间同步到系统时间,可以把hwclock -s命令加入到profile或者rcS文件中。

2. 获取显示RTC时间

image.png

3. 将系统时间同步到RTC,用于设置时间

image.png

4. 查看RTC的信息

image.png

1.2 Linux内核RTC子系统结构

1.2.1 RTC框架相关的核心文件

1. /drivers/rtc/class.c      这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口


2. /drivers/rtc/rtc-dev.c    这个文件定义了基本的设备文件操作函数,如:open,read等


3. /drivers/rtc/interface.c  顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数


4. /drivers/rtc/rtc-sysfs.c    与sysfs有关


5. /drivers/rtc/rtc-proc.c    与proc文件系统有关


6. /include/linux/rtc.h      定义了与RTC有关的数据结构


Linux内核源码自带的RTC驱动代码存放位置:

image.png

1.2.2 内核提供的rtc底层注册与注销函数

1. RTC框架注册函数

image.png

使用示例: rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);


使用rtc_device_register函数注册成功之后,在/dev/下可以看到rtcx的设备节点(x是rtc的顺序编号)。


2. RTC框架注销函数

image.png

经过RTC注册函数形参分析,RTC子系统的注册需要通过平台设备框架完成,在平台设备的驱动端的probe函数里进行rtc注册,remove函数里进行注销,在rtc设备端向驱动端传递RTC硬件需要的一些信息。


1.2.3 文件操作集合接口

rtc_class_ops 这个结构是RTC驱动程序要实现的基本操作函数。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。这个驱动接口与应用层的hwclock命令关联在一起,可以通过hwclock命令调用底层RTC这些函数。

image.png

RTC子系统里驱动一般只需要实现设置时间和获取时间的函数接口即可,用户可以在应用层通过ioctl函数传入对应的命令调用驱动层的接口,实现时间获取与设置。

常用的两个命令:

image.png

1.2.4 RTC时间结构

rtc_time代表了RTC记录的时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中。

image.png

1.2.5 闹钟结构

image.png

1.3 编写RTC驱动代码

1.3.1 准备工作

要测试自己的编写的RTC驱动,提前需要将内核自带的RTC驱动先去除掉,再重新编译烧写内核,再安装测试。

以tiny4412开发板为例,去除掉自带的rtc驱动。

1. 进入到内核配置菜单: make menuconfig

  Device Drivers  --->
       [*] Real Time Clock  --->

image.png

2. 重新编译内核,再重新烧写内核到SD或者EMMC:

image.png

默认没有RTC驱动的情况下,获取系统时间是从1970年开始的:

image.png

1.3.2 RTC驱动代码编写—框架示例

以下代码只是演示了RTC驱动的注册框架。

1. RTC设备端代码:

#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
 * device  设备端
 */
//释放平台总线
static void pdev_release(struct device *dev)
{
  printk("rtc_pdev:the rtc_pdev is close!!!\n");
}
/*设备端结构体*/
struct platform_device  rtc_pdev= /*设备结构体,设备名字很重要!*/
{
  .name = "tiny4412rtc",  /*设备名*/
  .id = -1,         /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
  .dev =            /*驱动卸载时调用*/
  {
    .release = pdev_release,/*释放资源*/
  },
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
  platform_device_register(&rtc_pdev);/*注册平台设备端*/
  return 0;
}
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
  platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");

2. RTC驱动端代码

#include <linux/module.h>             /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h>                 /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>          /*中断相关头文件*/
#include <linux/irq.h>                /*中断相关头文件*/
#include <linux/gpio.h>               /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>              
#include <linux/sched.h>
#include <linux/timer.h>              /*内核定时器*/
#include <asm-generic/poll.h>         
#include <linux/poll.h>               /* poll机制*/
#include <linux/platform_device.h>    /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
  printk("获取时间成功\n");
  return 0;
}
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
  printk("设置时间成功\n");
  return 0; 
}
static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
  printk("getalarm调用成功\n");
  return 0; 
}
static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
  printk("getalarm调用成功\n");
  return 0; 
}
static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq)
{
  printk("proc调用成功\n");
  return 0; 
}
static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
  printk("alarm_irq_enable调用成功\n");
  return 0; 
}
static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
  printk("ioctl调用成功\n");
  return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops tiny4412_rtcops = {
  .read_time  = tiny4412_rtc_gettime,
  .set_time = tiny4412_rtc_settime,
  .read_alarm = tiny4412_rtc_getalarm,
  .set_alarm  = tiny4412_rtc_setalarm,
  .proc   = tiny4412_rtc_proc,
  .alarm_irq_enable = tiny4412_rtc_alarm_irq_enable,
  .ioctl    = tiny4412_rtc_ioctl,
};
struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{ 
  rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
  if(rtc==NULL)
  printk("RTC驱动注册失败\n");
  else
    {
      printk("RTC驱动注册成功\n");
    }         
  return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
  rtc_device_unregister(rtc);
  printk("RTC驱动卸载成功\n");
  return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver  drv= 
{
  .probe = drv_probe,    /*需要创建一个probe函数,这个函数是对设备进行操作*/
  .remove = drv_remove,  /*创建一个remove函数,用于设备退出*/
  .driver = 
  {
    .name = "tiny4412rtc",    /*设备名称,用来与设备端匹配(非常重要)*/
  },
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
  platform_driver_register(&drv);/*注册平台驱动*/ 
  return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
  platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init);  /*驱动模块的入口*/
module_exit(plat_drv_exit);  /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/

3. 安装RTC驱动

image.png

4. 查看生成的RTC的设备节点

image.png

5. 查看rtc信息

image.png

查看/proc/driver/rtc文件时,底层驱动函数接口也相继被调用,只不过刚才写的RTC驱动没有完善,所以获取的信息不正确,是默认值。

6. 设置RTC时间相关的命令测试

image.png

通过命令测试,设置时间和获取时间都调用了底层的RTC函数接口,剩下的工作就是完善驱动代码了。


1.3.3 完善RTC驱动

上一步完成了RTC驱动代码框架编写,这一步就先不添加RTC硬件代码,使用软件方式模拟时间传递给应用层。


注意:  内核里RTC时间换算的时间是从:  1900年开始计算的,月份是从0开始的。


在给rtc结构赋值时,在正常的年份上需要减去1900,月份再减去1


赋值示例:

//此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
  rtc_tm->tm_year=2018-1900;   //年 
  rtc_tm->tm_mon=8-1;         //月 
  rtc_tm->tm_mday=18;         //日 
  rtc_tm->tm_hour=18;      //时 
  rtc_tm->tm_min=18;    //分 
  rtc_tm->tm_sec=18;//秒
  printk("从RTC底层获取时间成功!\n");
  return 0;
}

应用层获取的时间如下:

image.png

完善过后的RTC设备驱动端代码

#include <linux/module.h>             /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h>                 /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>          /*中断相关头文件*/
#include <linux/irq.h>                /*中断相关头文件*/
#include <linux/gpio.h>               /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>              
#include <linux/sched.h>
#include <linux/timer.h>              /*内核定时器*/
#include <asm-generic/poll.h>         
#include <linux/poll.h>               /* poll机制*/
#include <linux/platform_device.h>    /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
//此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
  rtc_tm->tm_year=2018-1900;   //年 
  rtc_tm->tm_mon=8-1;     //月 
  rtc_tm->tm_mday=18;   //日 
  rtc_tm->tm_hour=18;   //时 
  rtc_tm->tm_min=18;    //分 
  rtc_tm->tm_sec=18;//秒
  printk("从RTC底层获取时间成功!\n");
  return 0;
}
//此函数可以通过应用层的ioctl的RTC_SET_TIME命令进行调用
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
    printk("RTC收到的时间为:%d-%d-%d %d-%d-%d\n",1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
     tm->tm_hour, tm->tm_min, tm->tm_sec);
  return 0; 
}
//获取闹钟时间
static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
  alrm->enabled=0;  //默认闹钟处于关闭状态
    alrm->time.tm_year=2018-1900;  //年 
  alrm->time.tm_mon=8-1;    //月 
  alrm->time.tm_mday=18;  //日 
  alrm->time.tm_hour=18;  //时 
  alrm->time.tm_min=18; //分 
  alrm->time.tm_sec=18; //秒
  printk("从RTC底层获取闹钟时间成功!\n");
  return 0; 
}
//设置闹钟时间
static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
  printk("RTC闹钟设置成功\n");
  return 0; 
}
//proc接口调用
static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq)
{
  printk("proc调用成功\n");
  return 0; 
}
//闹钟中断使能
static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
  printk("alarm_irq_enable调用成功\n");
  return 0; 
}
//可以实现用户自定义的命令
static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
  printk("ioctl调用成功\n");
  return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops tiny4412_rtcops = {
  .read_time  = tiny4412_rtc_gettime,
  .set_time = tiny4412_rtc_settime,
  .read_alarm = tiny4412_rtc_getalarm,
  .set_alarm  = tiny4412_rtc_setalarm,
  .proc   = tiny4412_rtc_proc,
  .alarm_irq_enable = tiny4412_rtc_alarm_irq_enable,
  .ioctl    = tiny4412_rtc_ioctl,
};
struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{ 
  rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);
  if(rtc==NULL)
  printk("RTC驱动注册失败\n");
  else
    {
      printk("RTC驱动注册成功\n");
    }         
  return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
  rtc_device_unregister(rtc);
  printk("RTC驱动卸载成功\n");
  return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver  drv= 
{
  .probe = drv_probe,    /*需要创建一个probe函数,这个函数是对设备进行操作*/
  .remove = drv_remove,  /*创建一个remove函数,用于设备退出*/
  .driver = 
  {
    .name = "tiny4412rtc",    /*设备名称,用来与设备端匹配(非常重要)*/
  },
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
  platform_driver_register(&drv);/*注册平台驱动*/ 
  return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
  platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init);  /*驱动模块的入口*/
module_exit(plat_drv_exit);  /*驱动模块的出口*/
MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/

安装测试结果:

[root@XiaoLong /code]# date
Thu Jan  1 00:00:13 UTC 1970
[root@XiaoLong /code]# insmod plat_rtc_device.ko 
[root@XiaoLong /code]# insmod plat_rtc_drver.ko 
[   24.350000] 从RTC底层获取时间成功!
[   24.350000] 从RTC底层获取闹钟时间成功!
[   24.350000] 从RTC底层获取时间成功!
[   24.350000] tiny4412rtc tiny4412rtc: rtc core: registered tiny4412_rtc as rtc0
[   24.350000] RTC驱动注册成功
[root@XiaoLong /code]# cat /proc/driver/rtc 
[   37.085000] 从RTC底层获取时间成功!
[   37.085000] proc调用成功
rtc_time        : 18:18:18
rtc_date        : 2018-08-18
alrm_time       : 18:18:18
alrm_date       : 2018-08-18
alarm_IRQ       : no
alrm_pending    : no
update IRQ enabled      : no
periodic IRQ enabled    : no
periodic IRQ frequency  : 1
max user IRQ frequency  : 64
24hr            : yes
[root@XiaoLong /code]# 
[root@XiaoLong /code]# hwclock -s
[   58.600000] 从RTC底层获取时间成功!
[root@XiaoLong /code]# hwclock -r
[   61.020000] 从RTC底层获取时间成功!
Sat Aug 18 18:18:18 2018  0.000000 seconds
[root@XiaoLong /code]# hwclock -w
[   62.920000] RTC收到的时间为:2018-7-18 18-18-22
[   62.920000] 从RTC底层获取时间成功!
[   62.920000] alarm_irq_enable调用成功
[root@XiaoLong /code]# date
Sat Aug 18 18:18:24 UTC 2018

1.3.4 RTC应用层代码

应用层想要与RTC驱动交互,可以使用ioctl函数特定的一些命令进行。

RTC子系统常用的ioctl命令如下:

//支持的全部命令 在interface.c文件中有使用范例。 这些命令在用户自己写应用层代码时可以用到
RTC_ALM_READ                     rtc_read_alarm        读取闹钟时间
RTC_ALM_SET                      rtc_set_alarm          设置闹钟时间
RTC_RD_TIME                      rtc_read_time          读取时间与日期
RTC_SET_TIME                     rtc_set_time            设置时间与日期
RTC_PIE_ON RTC_PIE_OFF           rtc_irq_set_state         开关RTC全局中断的函数
RTC_AIE_ON RTC_AIE_OFF           rtc_alarm_irq_enable     使能禁止RTC闹钟中断
RTC_UIE_OFF RTC_UIE_ON           rtc_update_irq_enable    使能禁止RTC更新中断
RTC_IRQP_SET                       rtc_irq_set_freq           设置中断的频率

示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
struct rtc_time time; //保存时间值
int main(int argc,char **argv)
{
  if(argc!=2)
  {
    printf("传参格式:/dev/rtc\r\n");
    return;
  }
  int fd=open(argv[1],O_RDWR); // 2==O_RDWR
  if(fd<0)
  {
    printf("驱动设备文件打开失败!\r\n");
    return 0;
  }
  time.tm_year=2017;
  time.tm_mon=10;
  time.tm_mday=13;
  time.tm_hour=21;
  time.tm_min=10;
  time.tm_sec=10;
  //注意:年月日必须填写正常,否则会导致底层函数无法调用成功
    ioctl(fd,RTC_SET_TIME,&time);   //底层自己实现了ioctl函数,设置RTC时间
  while(1)
  {
    ioctl(fd,RTC_RD_TIME,&time);
    printf("%d-%d-%d %d:%d:%d\r\n",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec);
    sleep(1);
  }
}

1.3.5 标准时间到秒单位时间转换函数

硬件上有些RTC实时时钟设置只计算秒数,不提供年月日时分秒格式的时间设置,这时候就需要自己对标准时间进行转换。

将标准时间转为秒单位时间:

/*
 * 自01-01-1970就是将公历日期转换为秒。
 */
int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time)
{
  *time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
      tm->tm_hour, tm->tm_min, tm->tm_sec);
  return 0;
}

1.3.6 DS1302时钟芯片驱动编写示例

上面代码都是模拟时钟,学习RTC框架的用法,下面的的代码就加入了实际的RTC硬件,实现完整的RTC计时。

DS1302驱动端代码:

#include <linux/module.h>             /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h>                 /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>          /*中断相关头文件*/
#include <linux/irq.h>                /*中断相关头文件*/
#include <linux/gpio.h>               /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>              
#include <linux/sched.h>
#include <linux/timer.h>              /*内核定时器*/
#include <asm-generic/poll.h>         
#include <linux/poll.h>               /* poll机制*/
#include <linux/platform_device.h>    /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
/*--------------------------------DS1302相关操作代码---------------------------------------------*/
static unsigned char RTC_bin2bcd(unsigned val)
{
  return ((val/10)<<4)+val%10;
}
static unsigned RTC_bcd2bin(unsigned char val)
{
  return (val&0x0f)+(val>>4)*10;
}
/*
函数功能:DS1302初始化
Tiny4412硬件连接:
  CLK :GPB_4
  DAT :GPB_5
  RST :GPB_6
*/
void DS1302IO_Init(void)
{
  /*1. 注册GPIO*/
  gpio_request(EXYNOS4_GPB(4), "DS1302_CLK");
  gpio_request(EXYNOS4_GPB(5), "DS1302_DAT");
  gpio_request(EXYNOS4_GPB(6), "DS1302_RST");
  /*2. 配置GPIO口模式*/
  s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT);  //时钟
  s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT);  //数据
//  s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_INPUT);   //输入模式
  s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_OUTPUT);  //复位
  /*3. 上拉GPIO口*/
  gpio_set_value(EXYNOS4_GPB(4), 1); //CLK
  gpio_set_value(EXYNOS4_GPB(5), 1); //DAT
  gpio_set_value(EXYNOS4_GPB(6), 1); //RST
  gpio_set_value(EXYNOS4_GPB(6), 0);      //RST脚置低
  gpio_set_value(EXYNOS4_GPB(4), 0);      //SCK脚置低
}
//#define RTC_CMD_READ  0x81    /* Read command */
//#define RTC_CMD_WRITE 0x80    /* Write command */
//#define RTC_ADDR_RAM0 0x20      /* Address of RAM0 */
//#define RTC_ADDR_TCR  0x08      /* Address of trickle charge register */
//#define RTC_ADDR_YEAR 0x06    /* Address of year register */
//#define RTC_ADDR_DAY  0x05    /* Address of day of week register */
//#define RTC_ADDR_MON  0x04    /* Address of month register */
//#define RTC_ADDR_DATE 0x03    /* Address of day of month register */
//#define RTC_ADDR_HOUR 0x02    /* Address of hour register */
//#define RTC_ADDR_MIN  0x01    /* Address of minute register */
//#define RTC_ADDR_SEC  0x00    /* Address of second register */
//DS1302地址定义
#define ds1302_sec_add      0x80    //秒数据地址
#define ds1302_min_add      0x82    //分数据地址
#define ds1302_hr_add     0x84    //时数据地址
#define ds1302_date_add     0x86    //日数据地址
#define ds1302_month_add    0x88    //月数据地址
#define ds1302_day_add      0x8a    //星期数据地址
#define ds1302_year_add     0x8c    //年数据地址
#define ds1302_control_add    0x8e    //控制数据地址
#define ds1302_charger_add    0x90           
#define ds1302_clkburst_add   0xbe
//初始时间定义
static unsigned char time_buf[8] = {0x20,0x10,0x06,0x01,0x23,0x59,0x55,0x02};//初始时间2010年6月1号23点59分55秒 星期二
static unsigned char readtime[14];//当前时间
static unsigned char sec_buf=0;   //秒缓存
static unsigned char sec_flag=0;  //秒标志位
//向DS1302写入一字节数据
static void ds1302_write_byte(unsigned char addr, unsigned char d) 
{
  unsigned char i;
  gpio_set_value(EXYNOS4_GPB(6), 1);          //启动DS1302总线  
  //写入目标地址:addr
  addr = addr & 0xFE;   //最低位置零,寄存器0位为0时写,为1时读
  for(i=0;i<8;i++)
  {
    if(addr&0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
    else{gpio_set_value(EXYNOS4_GPB(5), 0);}
    gpio_set_value(EXYNOS4_GPB(4), 1);      //产生时钟
    gpio_set_value(EXYNOS4_GPB(4), 0);
    addr=addr >> 1;
  }
  //写入数据:d
  for(i=0;i<8;i++)
  {
    if(d & 0x01) {gpio_set_value(EXYNOS4_GPB(5), 1);}
    else {gpio_set_value(EXYNOS4_GPB(5), 0);}
    gpio_set_value(EXYNOS4_GPB(4), 1);    //产生时钟
    gpio_set_value(EXYNOS4_GPB(4), 0);
    d = d >> 1;
  }
  gpio_set_value(EXYNOS4_GPB(6), 0);    //停止DS1302总线
}
//从DS1302读出一字节数据
static unsigned char ds1302_read_byte(unsigned char addr)
{
  unsigned char i,temp; 
  gpio_set_value(EXYNOS4_GPB(6), 1);//启动DS1302总线
  //写入目标地址:addr
  addr=addr | 0x01;    //最低位置高,寄存器0位为0时写,为1时读
  for(i=0; i<8; i++)
  {
    if(addr & 0x01){gpio_set_value(EXYNOS4_GPB(5), 1);}
    else {gpio_set_value(EXYNOS4_GPB(5), 0);}
    gpio_set_value(EXYNOS4_GPB(4), 1);
    gpio_set_value(EXYNOS4_GPB(4), 0);
    addr=addr >> 1;
  }
  s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_INPUT);   //输入模式
  //输出数据:temp
  for(i=0; i<8; i++)
  {
    temp=temp>>1;
    if(gpio_get_value(EXYNOS4_GPB(5))){temp |= 0x80;}
    else{temp&=0x7F;}
    gpio_set_value(EXYNOS4_GPB(4), 1);
    gpio_set_value(EXYNOS4_GPB(4), 0);
  }
  s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT);  //输出模式
  gpio_set_value(EXYNOS4_GPB(6), 0);          //停止DS1302总线
  return temp;
}
//向DS302写入时钟数据
static void ds1302_write_time(struct rtc_time *time) 
{
  ds1302_write_byte(ds1302_control_add,0x00);       //关闭写保护 
  ds1302_write_byte(ds1302_sec_add,0x80);         //暂停时钟 
  //ds1302_write_byte(ds1302_charger_add,0xa9);       //涓流充电
  /*设置RTC时间*/
  //因为DS1302的年份只能设置后两位,所有需要使用正常的年份减去2000,得到实际的后两位
  ds1302_write_byte(ds1302_year_add,RTC_bin2bcd(time->tm_year-2000));   //年 
  ds1302_write_byte(ds1302_month_add,RTC_bin2bcd(time->tm_mon));    //月 
  ds1302_write_byte(ds1302_date_add,RTC_bin2bcd(time->tm_mday));    //日 
  ds1302_write_byte(ds1302_hr_add,RTC_bin2bcd(time->tm_hour));    //时 
  ds1302_write_byte(ds1302_min_add,RTC_bin2bcd(time->tm_min));    //分
  ds1302_write_byte(ds1302_sec_add,RTC_bin2bcd(time->tm_sec));    //秒
  //ds1302_write_byte(ds1302_day_add,RTC_bin2bcd(time->tm_wday));   //周  time->tm_wday一周中的某一天
  ds1302_write_byte(ds1302_control_add,0x80);        //打开写保护     
}
static int DS1302_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
  /*设置RTC时间*/
  struct rtc_time time;
  copy_from_user(&time,(const void __user *)arg,sizeof(struct rtc_time));
  ds1302_write_time(&time);
  return 0;
}
//此函数通过应用层的ioctl的RTC_RD_TIME命令进行调用
static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
  rtc_tm->tm_year=RTC_bcd2bin(ds1302_read_byte(ds1302_year_add))+2000;   //年 
  rtc_tm->tm_mon=RTC_bcd2bin(ds1302_read_byte(ds1302_month_add));   //月 
  rtc_tm->tm_mday=RTC_bcd2bin(ds1302_read_byte(ds1302_date_add));   //日 
  rtc_tm->tm_hour=RTC_bcd2bin(ds1302_read_byte(ds1302_hr_add));   //时 
  rtc_tm->tm_min=RTC_bcd2bin(ds1302_read_byte(ds1302_min_add));   //分 
  rtc_tm->tm_sec=RTC_bcd2bin((ds1302_read_byte(ds1302_sec_add))&0x7f);//秒,屏蔽秒的第7位,避免超出59
  //time_buf[7]=ds1302_read_byte(ds1302_day_add);   //周 
  return 0;
}
//此函数通过应用层的ioctl的RTC_SET_TIME命令进行调用
static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm)
{
  ds1302_write_time(tm); 
  return 0; 
}
/*RTC文件操作*/
static const struct rtc_class_ops DS1302_rtcops = {
  .ioctl=DS1302_rtc_ioctl,
  .read_time  = tiny4412_rtc_gettime,
  .set_time = tiny4412_rtc_settime
};
static struct rtc_device *rtc=NULL;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{ 
  rtc = rtc_device_register("DS1302RTC",&pdev->dev, &DS1302_rtcops,THIS_MODULE);
  if(rtc==NULL)
  printk("RTC驱动注册失败1\n");
  else
    {
      printk("RTC驱动注册成功1\n");
    }
  /*1. 初始化GPIO口*/
  DS1302IO_Init();
  msleep(10); 
  return 0;
}
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
  /*释放GPIO口*/
  gpio_free(EXYNOS4_GPB(4));
  gpio_free(EXYNOS4_GPB(5));
  gpio_free(EXYNOS4_GPB(6));
  rtc_device_unregister(rtc);
  printk("RTC驱动卸载成功\n");
  return 0;
}
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver  drv= 
{
  .probe = drv_probe,    /*需要创建一个probe函数,这个函数是对设备进行操作*/
  .remove = drv_remove,  /*创建一个remove函数,用于设备退出*/
  .driver = 
  {
    .name = "DS1302rtc",    /*设备名称,用来与设备端匹配(非常重要)*/
  },
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
  platform_driver_register(&drv);/*注册平台驱动*/ 
  return 0;
}
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
  platform_driver_unregister(&drv);/*释放平台驱动*/
}
module_init(plat_drv_init);  /*驱动模块的入口*/
module_exit(plat_drv_exit);  /*驱动模块的出口*/
MODULE_LICENSE("GPL");       /*驱动的许可证-声明*/

DS1320设备端代码

#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
 * device  设备端
 */
//释放平台总线
static void pdev_release(struct device *dev)
{
  printk("rtc_pdev:the rtc_pdev is close!!!\n");
}
/*设备端结构体*/
struct platform_device  rtc_pdev= /*设备结构体,设备名字很重要!*/
{
  .name = "DS1302rtc",  /*设备名*/
  .id = -1,         /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
  .dev =            /*驱动卸载时调用*/
  {
    .release = pdev_release,/*释放资源*/
  },
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
  platform_device_register(&rtc_pdev);/*注册平台设备端*/
  return 0;
}
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
  platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");
目录
相关文章
|
1月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
2月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
29 6
|
2月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
29 5
|
1月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
Linux 调度 芯片
|
3天前
|
Linux Python Perl
Linux命令删除文件里的字符串
Linux命令删除文件里的字符串
14 7
|
4天前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
5天前
|
Linux Perl
Linux之sed命令
Linux之sed命令
|
5天前
|
Linux
深入理解Linux中的cp命令:文件与目录的复制利器
深入理解Linux中的cp命令:文件与目录的复制利器
下一篇
无影云桌面