前置条件
T40 + sd2010 外置rtc
首先要确认硬件是否正常链接,设备地址是多少,可以借助i2c-tools
注意:要使用i2c-tools 内核需要打开 CONFIG_I2C_CHARDEV (用户空间支持使用i2c总线),/dev/下有i2c-*说明配置已打开
i2c-tools
这个是专门用来调试i2c的
编译移植:
下载地址:
Index of /pub/software/utils/i2c-tools/
这里用的是i2c-tools-3.1.1
解压之后进入目录修改Makefile(加上静态编译 和 安装路径)
make CC=mips-linux-uclibc-gnu-gcc; make install
编译成功后生成的 _install/sbin/下就是生成的工具
使用方法:
i2cdetect:用于扫描 i2c 总线上的设备,并显示地址 i2cset:向i2c设备某个寄存器写入值 i2cget:读取i2c设备某个寄存器的值 i2cdump:读取某个i2c设备所有寄存器的值 i2ctransfer:一次性读写多个字节
可以看到从机的地址为0x32(i2c总线号问硬件工程师即可)
如果扫描不到,可能是管教复用没打开 gpio 复用i2c
不同厂商的复用方法可能不一样,具体要咨询一下厂商。
T40的复用方法
查原理图确认RTC SDA/SCK 对应的gpio为 PB20 PB21,总线是3.
内核目录 arch/mips/boot/dts/ingenic
这里不知道是怎么个规则, 0、1、2轮流试一下。
读写rtc
查询规格书可知寄存器列表
方法1 用户空间直接操作 i2c设备:
前面已知总线号3,地址0x32
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
typedef struct _RJ_TIME_
{
unsigned char sec; ///< 秒
unsigned char min; ///< 分
unsigned char hour; ///< 时
unsigned char wday; ///< 星期几
unsigned char mday; ///< 某日
unsigned char mon; ///< 某月
unsigned short year; ///< 某年
int msec; ///< 毫秒
}_RJ_TIME_;
static char nChipAddress = (char)0x32;
int readEx(int m_nFd, unsigned char p_Address, char p_RegNo, char* p_Value, int p_valueLen)
{
if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
{
printf("readEx wrong slave address!\n");
return -1;
}
if (write(m_nFd, &p_RegNo, 1) < 0)
{
printf("write i2c error! \n");
return -1;
}
if (read(m_nFd, p_Value, p_valueLen) < 0)
{
printf("read i2c error! \n");
return -1;
}
return 0;
}
int rtc_get_time(_RJ_TIME_ *time)
{
int ret;
unsigned char nValue[7]={0};
int m_nFd = open("/dev/i2c-3", O_RDWR);
if (m_nFd <= 0)
{
printf("error open i2c Device failed!\n");
return -1;
}
ret = readEx(m_nFd, nChipAddress, 0, (char*)nValue,sizeof(nValue));
if (0 != ret)
{
close(m_nFd);
return ret;
}
//printf("%x,%x,%x,%x,%x,%x,%x\n",nValue[0],nValue[1],nValue[2],nValue[3],nValue[4],nValue[5],nValue[6]);
time->sec = (nValue[0]>>4) * 10 + (nValue[0]&0x0f) ;
time->min = (nValue[1]>>4) * 10 + (nValue[1]&0x0f) ;
nValue[2] &= 0x7f;
time->hour = (nValue[2]>>4) * 10 + (nValue[2]&0x0f) ;
time->mday = (nValue[4]>>4) * 10 + (nValue[4]&0x0f) ;
time->mon = (nValue[5]>>4) * 10 + (nValue[5]&0x0f);
time->year = (nValue[6]>>4) * 10 + (nValue[6]&0x0f);
time->wday = (nValue[3]>>4) * 10 + (nValue[3]&0x0f) ;
close(m_nFd);
return 0;
}
int rtc_write(int m_nFd, unsigned char p_Address, char p_RegNo, char p_Value)
{
if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
{
printf("write wrong slave address(0x%x)!\n",p_Address);
return -1;
}
char mbuf[2];
mbuf[0] = p_RegNo;
mbuf[1] = p_Value;
if (write(m_nFd, mbuf, 2) < 0)
{
printf("write i2c error\n");
return -1;
}
return 0;
}
int writeEx(int m_nFd, unsigned char p_Address, char p_RegNo, char* p_Value, int p_valueLen)
{
if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
{
printf("writeEx wrong slave address!\n");
return -1;
}
unsigned char* mbuf = (unsigned char*)calloc(p_valueLen + 1, sizeof(unsigned char));
if ( mbuf == NULL )
{
printf("Error: read buffer allocation\n");
return -1;
}
mbuf[0] = p_RegNo;
for ( int i = 0; i < p_valueLen; i++ )
{
mbuf[i + 1] = (unsigned char)p_Value[i];
}
if (write(m_nFd, mbuf, p_valueLen + 1) < 0)
{
printf("write i2c error !\n");
free(mbuf);
return -1;
}
free(mbuf);
return 0;
}
int rtc_set_time(_RJ_TIME_ *p_tmTime)
{
int ret;
unsigned char nValue[7]={0};
int m_nFd = open("/dev/i2c-3", O_RDWR);
if (m_nFd <= 0)
{
printf("error open i2c Device failed!\n");
return -1;
}
unsigned char nSeconds = p_tmTime->sec;
unsigned char nMinutes = p_tmTime->min;
unsigned char nHours = p_tmTime->hour;
unsigned char nDays = p_tmTime->mday;
unsigned char nMonths = p_tmTime->mon;
unsigned char nYears = p_tmTime->year;
unsigned char nWeekDay = p_tmTime->wday;
nValue[0] = ((nSeconds/10)<<4)|(nSeconds%10);
nValue[1] = ((nMinutes/10)<<4)|(nMinutes%10);
nValue[2] = ((nHours/10)<<4)|(nHours%10);
nValue[2] |= 0x80;//24 小时制
nValue[3] = ((nWeekDay/10)<<4)|(nWeekDay%10);
nValue[4] = ((nDays/10)<<4)|(nDays%10);
nValue[5] = ((nMonths/10)<<4)|(nMonths%10);
nValue[6] = ((nYears/10)<<4)|(nYears%10);
rtc_write(m_nFd, nChipAddress, 0x10,0x80);
rtc_write(m_nFd, nChipAddress, 0x0f,0x84);
printf("nYears[%d]%x, nMonths[%d]%x, nDays[%d]%x, nWeekDay[%d]%x, nHours[%d]%x, nMinutes[%d]%x, nSeconds[%d]%x\n",\
nYears, nValue[6],\
nMonths, nValue[5],\
nDays, nValue[4],\
nWeekDay, nValue[3],\
nHours, nValue[2],\
nMinutes, nValue[1],\
nSeconds, nValue[0]);
writeEx(m_nFd, nChipAddress, 0, (char*)nValue,sizeof(nValue));
rtc_write(m_nFd, nChipAddress, 0x0f,0x0);
rtc_write(m_nFd, nChipAddress, 0x10,0x0);
close(m_nFd);
return 0;
}
void case_get_rtc()
{
_RJ_TIME_ rtc_time;
rtc_get_time(&rtc_time);
printf("%d-%02d-%02d %d:%d:%d\n",rtc_time.year + 2000,rtc_time.mon,rtc_time.mday,
rtc_time.hour,rtc_time.min,rtc_time.sec);
}
void case_set_rtc()
{
time_t timep;
struct tm *p;
time(&timep);
p=gmtime(&timep);
printf("%d",p->tm_year + 1900);/*获取当前年份,从1900开始,所以要加1900*/
printf("-%02d",1+p->tm_mon);/*获取当前月份,范围是0-11,所以要加1*/
printf("-%02d ",p->tm_mday);/*获取当前月份日数,范围是1-31*/
printf("%02d-",p->tm_hour);/*获取当前时,这里获取西方的时间,刚好相差八个小时*/
printf("%02d-",p->tm_min); /*获取当前分*/
printf("%02d ",p->tm_sec); /*获取当前秒*/
printf("星期 %d\n",p->tm_wday); /*获取当前星期*/
_RJ_TIME_ rtc_time;
rtc_time.year = p->tm_year -100; // + 1900 -2000
if (p->tm_year +1900 < 2000)
rtc_time.year = 0x0;
rtc_time.mon = p->tm_mon + 1;
rtc_time.mday = p->tm_mday;
rtc_time.hour = p->tm_hour;
rtc_time.min = p->tm_min;
rtc_time.sec = p->tm_sec;
rtc_time.wday = p->tm_wday;
rtc_set_time(&rtc_time);
}
int main(int argc, char *argv[])
{
int result;
opterr = 0;//使getopt不行stderr输出错误信息
while( (result = getopt(argc, argv, "rwh")) != -1 )
{
switch(result)
{
case 'r':
//printf("[rtc] Get hw time.\n");
case_get_rtc();
break;
case 'w':
printf("[rtc] Set hw time.\n");
case_set_rtc();
break;
case 'h':
printf("[Usage: hwclock [-r|--show][-w|--systohc]\n");
printf("\t-r Show hardware clock time\n");
printf("\t-w Set hardware clock from system time\n");
case '?':
printf("[rtc] Unknow cmd.\n");
break;
default:
printf("[rtc] error.\n");
break;
}
}
return 0;
}
方法2 加载rtc驱动 后台通过hwclock 读写时间:
内核 drivers/rtc目录下有很多厂商已经实现好的驱动, 找一下有没有对应的型号,如果刚好没有,那么我们就自己写驱动。
如果对驱动完全一窍不通,先看下这位博主的系列文章
(一)初识Linux驱动_hanp_linux的博客-CSDN博客 (bing.com)
驱动编译的时候如果有报错 undefine 大概率是内核对应的配置没有打开,此时虽然能生成ko,但是加载的时候是加载不上去的。
我自己遇到了rtc_device_register undefine 。原因是CONFIG_RTC_CLASS没有打开
这篇文章写得已经很详细了
(2条消息) Linux驱动开发: Linux下RTC实时时钟驱动_DS小龙哥的博客-CSDN博客_rtc驱动
补充自己写得针对sd2010的rtc驱动
rtc相关操作接口遇到问题可以先尝试搜一下 rtc 厂商的官网,看有没有一些错误解释 或者demo
device端
rtc_device.c
#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 = "sd2010rtc", /*设备名*/
.id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
.dev = /*驱动卸载时调用*/
{
.release = pdev_release,/*释放资源*/
},
};
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
printk("rtc device init\n");
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");
Makefile
CROSS_COMPILE ?= mips-linux-gnu-
ISVP_ENV_KERNEL_DIR = $(PWD)/../../../../kernel-4.4.94
KDIR := ${ISVP_ENV_KERNEL_DIR}
MODULE_NAME := rtc_device_sd2010
all: modules
.PHONY: modules clean
$(MODULE_NAME)-objs := rtc_device.o
obj-m := $(MODULE_NAME).o
modules:
@$(MAKE) -C $(KDIR) M=$(shell pwd) $@
clean:
@rm -rf *.o *~ .depend .*.cmd *.mod.c .tmp_versions *.ko *.symvers modules.order
drver端 (设置接口未完成)
#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 <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/i2c.h>
#define sd2010_sec_add 0x00 //秒数据地址
#define sd2010_min_add 0x01 //分数据地址
#define sd2010_hr_add 0x02 //时数据地址
#define sd2010_day_add 0x03 //星期数据地址
#define sd2010_date_add 0x04 //日数据地址
#define sd2010_month_add 0x05 //月数据地址
#define sd2010_year_add 0x06 //年数据地址
#define REG_CTRL_1 0x0F
#define REG_CTRL_2 0x10
#define REG_CTRL_3 0x11
#define RTC_I2C_NUM 3
#define RTC_ADDR 0x32
#define I2C_WRITE 0
#define I2C_READ 1
static struct i2c_board_info sd2010_info = {
I2C_BOARD_INFO("sd2010", RTC_ADDR),
};
static struct i2c_client *sd2010_client;
static int sd2010_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
printk("从RTC底层获取时间成功!\n");
//struct i2c_client *client = to_i2c_client(dev);
unsigned char addr = sd2010_sec_add;
unsigned char nValue[7] = {0};
struct i2c_msg msgs[1] = {
[0] = {
.addr = sd2010_client->addr,
.flags = I2C_READ,
.len = 7,
.buf = nValue,
}, /* read time/date */
};
/* read time/date registers */
if ((i2c_transfer(sd2010_client->adapter, &msgs[0], 1)) != 1) {
dev_err(&sd2010_client->dev, "%s: read error\n", __func__);
return -EIO;
}
printk("%x,%x,%x,%x,%x,%x,%x\n",nValue[0],nValue[1],nValue[2],nValue[3],nValue[4],nValue[5],nValue[6]);
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;//秒
rtc_tm->tm_sec = (nValue[0]>>4) * 10 + (nValue[0]&0x0f) ;
rtc_tm->tm_min = (nValue[1]>>4) * 10 + (nValue[1]&0x0f) ;
nValue[2] &= 0x7f;
rtc_tm->tm_hour = (nValue[2]>>4) * 10 + (nValue[2]&0x0f) ;
rtc_tm->tm_mday = (nValue[4]>>4) * 10 + (nValue[4]&0x0f) ;
rtc_tm->tm_mon = (nValue[5]>>4) * 10 + (nValue[5]&0x0f);
rtc_tm->tm_year = (nValue[6]>>4) * 10 + (nValue[6]&0x0f) + 100;
return 0;
}
static int sd2010_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);
//struct i2c_client *client = to_i2c_client(dev);
//printk("-------------1\n");
unsigned char bufdata[8] = {0};
unsigned char nSeconds = 10;
unsigned char nMinutes = 10;
unsigned char nHours = 10;
unsigned char nDays = 10;
unsigned char nMonths = 10;
unsigned char nYears = 10;
bufdata[0] = sd2010_sec_add;
bufdata[1] = ((nSeconds/10)<<4)|(nSeconds%10);
bufdata[2] = ((nMinutes/10)<<4)|(nMinutes%10);
bufdata[3] = ((nHours/10)<<4)|(nHours%10);
bufdata[5] |= 0x80;//24 小时制
//nValue[4] = ((nWeekDay/10)<<4)|(nWeekDay%10);
bufdata[5] = ((nDays/10)<<4)|(nDays%10);
bufdata[6] = ((nMonths/10)<<4)|(nMonths%10);
bufdata[7] = ((nYears/10)<<4)|(nYears%10);
//printk("-------------2\n");
unsigned char bufoff1[2] = {0};
unsigned char bufoff2[2] = {0};
bufoff1[0] = REG_CTRL_1;
bufoff1[1] = 0x0;
bufoff2[0] = REG_CTRL_2;
bufoff2[1] = 0x0;
printk("-------------3\n");
return 0;
}
static int sd2010_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 sd2010_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
printk("RTC闹钟设置成功\n");
return 0;
}
static int sd2010_rtc_proc(struct device *dev, struct seq_file *seq)
{
printk("proc调用成功\n");
return 0;
}
static int sd2010_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
printk("alarm_irq_enable调用成功\n");
return 0;
}
static int sd2010_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
printk("ioctl调用成功\n");
return 0;
}
/*RTC文件操作*/
static const struct rtc_class_ops sd2010_rtcops = {
.read_time = sd2010_rtc_gettime,
.set_time = sd2010_rtc_settime,
.read_alarm = sd2010_rtc_getalarm,
.set_alarm = sd2010_rtc_setalarm,
.proc = sd2010_rtc_proc,
.alarm_irq_enable = sd2010_rtc_alarm_irq_enable,
.ioctl = sd2010_rtc_ioctl,
};
struct rtc_device *rtc;
/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(RTC_I2C_NUM);
sd2010_client = i2c_new_device(i2c_adap, &sd2010_info);
i2c_put_adapter(i2c_adap);
unsigned char buf[2];
struct i2c_msg msg = {
RTC_ADDR, I2C_WRITE, 2, buf,
};
buf[0] = REG_CTRL_1;
buf[1] = 0;
#if 0
if ((i2c_transfer(sd2010_client->adapter, &msg, 1)) != 1) {
dev_err(&sd2010_client->dev, "%s: write error\n", __func__);
printk("i2c_transfer faild!!!\n");
return -EIO;
}
#endif
rtc = rtc_device_register("sd2010rtc",&pdev->dev, &sd2010_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 = "sd2010rtc", /*设备名称,用来与设备端匹配(非常重要)*/
},
};
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
printk("rtc_drver init !!!\n");
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_AUTHOR("SHJ");
MODULE_LICENSE("GPL");
Makefile
CROSS_COMPILE ?= mips-linux-gnu-
ISVP_ENV_KERNEL_DIR = $(PWD)/../../../../kernel-4.4.94
KDIR := ${ISVP_ENV_KERNEL_DIR}
MODULE_NAME := rtc_drver_sd2010
all: modules
.PHONY: modules clean
$(MODULE_NAME)-objs := rtc_drver.o
obj-m := $(MODULE_NAME).o
modules:
@$(MAKE) -C $(KDIR) M=$(shell pwd) $@
clean:
@rm -rf *.o *~ .depend .*.cmd *.mod.c .tmp_versions *.ko *.symvers modules.order