i2c总线通信,时钟芯片RX8900CE UB linux下rtc驱动记录
前言
参考资料:
《EPSON RX8900 SA/CE应用手册》
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.7》
参考网站:
30.Linux-RTC驱动分析及使用
Makefile编译驱动失败,找不到头文件
Linux驱动开发: Linux下RTC实时时钟驱动
半导小芯-芯片查询工具
Linux下用C获取当前时间
LinuxC 获取、修改、设置系统软件时间
寄存器表格
手册示例
添加设备树设备
代码如下(示例):
i2c_bus0: i2c@11060000 {
compatible = "vendor,i2c";
reg = <0x11060000 0x1000>;
clocks = <&clock SS626V100_I2C0_CLK>;
clock-rate=<100000000>;
clock-frequency = <100000>;
resets = <&clock 0x4280 0>;
reset-names = "i2c_reset";
#address-cells = <1>;
#size-cells = <0>;
/* dmas = <&edmacv310_0 0 0>, <&edmacv310_0 1 1>; */
/* dma-names = "rx","tx"; */
status = "disabled";
rtc: rtc@32 {
compatible = "xw,rx8900";
reg = <0x32>;
};
#ifdef CONFIG_RTC_DRV_RV8803
rtc: rtc@32 {
compatible = "epson,rx8900";
reg = <0x32>;
epson,vdet-disable;
trickle-diode-disable;
};
#endif
};
设备如何填充,一般在linux内核源码中有厂商给的示例:
linux-5.10.y > Documentation > devicetree > bindings > rtc > epson,rx8900.txt
Real Time Clock driver for:
- Epson RX8900
- Micro Crystal rv8803
Required properties:
- compatible: should be: "microcrystal,rv8803" or "epson,rx8900"
- reg : the I2C address of the device for I2C
Optional properties:
- epson,vdet-disable : boolean, if present will disable voltage detector.
Should be set if no backup battery is used.
- trickle-diode-disable : boolean, if present will disable internal trickle
charger diode
Example:
rtc: rtc@32 {
compatible = "epson,rx8900"
reg = <0x32>;
epson,vdet-disable;
trickle-diode-disable;
};
驱动源码
// rtc_xw_rx8900_driver.c
#include <linux/bcd.h>
#include <linux/bitops.h>
#include <linux/log2.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/rtc.h>
#define RX8900_I2C_TRY_COUNT 4
#define RX8900_SEC 0x00
#define RX8900_MIN 0x01
#define RX8900_HOUR 0x02
#define RX8900_WEEK 0x03
#define RX8900_DAY 0x04
#define RX8900_MONTH 0x05
#define RX8900_YEAR 0x06
#define RX8900_RAM 0x07
#define RX8900_ALARM_MIN 0x08
#define RX8900_ALARM_HOUR 0x09
#define RX8900_ALARM_WEEK_OR_DAY 0x0A
#define RX8900_EXT 0x0D
#define RX8900_FLAG 0x0E
#define RX8900_CTRL 0x0F
#define RX8900_EXT_WADA BIT(6)
#define RX8900_FLAG_V1F BIT(0)
#define RX8900_FLAG_V2F BIT(1)
#define RX8900_FLAG_AF BIT(3)
#define RX8900_FLAG_TF BIT(4)
#define RX8900_FLAG_UF BIT(5)
#define RX8900_CTRL_RESET BIT(0)
#define RX8900_CTRL_EIE BIT(2)
#define RX8900_CTRL_AIE BIT(3)
#define RX8900_CTRL_TIE BIT(4)
#define RX8900_CTRL_UIE BIT(5)
#define RX8900_BACKUP_CTRL 0x18
#define RX8900_FLAG_SWOFF BIT(2)
#define RX8900_FLAG_VDETOFF BIT(3)
struct rtc_xw_rx8900_driver
{
/* data */
struct i2c_client *client;
struct rtc_device *rtc;
struct mutex rx8900_lock;
u8 ctrl;
};
static int rx8900_read_reg(const struct i2c_client *client, u8 reg)
{
// todo
int try = RX8900_I2C_TRY_COUNT;
s32 ret;
/*
* There is a 61µs window during which the RTC does not acknowledge I2C
* transfers. In that case, ensure that there are multiple attempts.
*/
do
ret = i2c_smbus_read_byte_data(client, reg);
while ((ret == -ENXIO || ret == -EIO) && --try);
if (ret < 0)
dev_err(&client->dev, "Unable to read register 0x%02x\n", reg);
return ret;
}
static int rx8900_read_regs(const struct i2c_client *client, u8 reg, u8 count,
u8 *values)
{
int try = RX8900_I2C_TRY_COUNT;
s32 ret;
do
ret = i2c_smbus_read_i2c_block_data(client, reg, count, values);
while ((ret == -ENXIO || ret == -EIO) && --try);
if (ret != count)
{
dev_err(&client->dev,
"Unable to read registers 0x%02x..0x%02x\n", reg,
reg + count - 1);
return ret < 0 ? ret : -EIO;
}
return 0;
}
static int rx8900_write_reg(const struct i2c_client *client, u8 reg, u8 value)
{
// todo
int try = RX8900_I2C_TRY_COUNT;
s32 ret;
do
ret = i2c_smbus_write_byte_data(client, reg, value);
while ((ret == -ENXIO || ret == -EIO) && --try);
if (ret)
dev_err(&client->dev, "Unable to write register 0x%02x\n", reg);
return ret;
}
static int rx8900_write_regs(const struct i2c_client *client, u8 reg, u8 count,
const u8 *values)
{
int try = RX8900_I2C_TRY_COUNT;
s32 ret;
do
ret = i2c_smbus_write_i2c_block_data(client, reg, count,
values);
while ((ret == -ENXIO || ret == -EIO) && --try);
if (ret)
dev_err(&client->dev,
"Unable to write registers 0x%02x..0x%02x\n", reg,
reg + count - 1);
return ret;
}
static int rx8900_get_time(struct device *dev, struct rtc_time *tm)
{
struct rtc_xw_rx8900_driver *rx8900 = dev_get_drvdata(dev);
u8 date1[7];
u8 date2[7];
u8 *date = date1;
int ret, flags;
flags = rx8900_read_reg(rx8900->client, RX8900_FLAG);
if (flags < 0)
return flags;
if (flags & RX8900_FLAG_V2F)
{
dev_warn(dev, "Voltage low, data is invalid.\n");
return -EINVAL;
}
ret = rx8900_read_regs(rx8900->client, RX8900_SEC, 7, date1);
if (ret)
return ret;
if ((date1[RX8900_SEC] & 0x7f) == bin2bcd(59))
{
ret = rx8900_read_regs(rx8900->client, RX8900_SEC, 7, date2);
if (ret)
return ret;
if ((date2[RX8900_SEC] & 0x7f) != bin2bcd(59))
date = date2;
}
tm->tm_sec = bcd2bin(date[RX8900_SEC] & 0x7f);
tm->tm_min = bcd2bin(date[RX8900_MIN] & 0x7f);
tm->tm_hour = bcd2bin(date[RX8900_HOUR] & 0x3f);
tm->tm_wday = ilog2(date[RX8900_WEEK] & 0x7f);
tm->tm_mday = bcd2bin(date[RX8900_DAY] & 0x3f);
tm->tm_mon = bcd2bin(date[RX8900_MONTH] & 0x1f) - 1;
tm->tm_year = bcd2bin(date[RX8900_YEAR]) + 100;
return 0;
}
static int rx8900_set_time(struct device *dev, struct rtc_time *tm)
{
struct rtc_xw_rx8900_driver *rx8900 = dev_get_drvdata(dev);
u8 date[7];
int ctrl, flags, ret;
ctrl = rx8900_read_reg(rx8900->client, RX8900_CTRL);
if (ctrl < 0)
return ctrl;
/* Stop the clock */
ret = rx8900_write_reg(rx8900->client, RX8900_CTRL,
ctrl | RX8900_CTRL_RESET);
if (ret)
return ret;
date[RX8900_SEC] = bin2bcd(tm->tm_sec);
date[RX8900_MIN] = bin2bcd(tm->tm_min);
date[RX8900_HOUR] = bin2bcd(tm->tm_hour);
date[RX8900_WEEK] = 1 << (tm->tm_wday);
date[RX8900_DAY] = bin2bcd(tm->tm_mday);
date[RX8900_MONTH] = bin2bcd(tm->tm_mon + 1);
date[RX8900_YEAR] = bin2bcd(tm->tm_year - 100);
ret = rx8900_write_regs(rx8900->client, RX8900_SEC, 7, date);
if (ret)
return ret;
/* Restart the clock */
ret = rx8900_write_reg(rx8900->client, RX8900_CTRL,
ctrl & ~RX8900_CTRL_RESET);
if (ret)
return ret;
mutex_lock(&rx8900->rx8900_lock);
flags = rx8900_read_reg(rx8900->client, RX8900_FLAG);
if (flags < 0)
{
mutex_unlock(&rx8900->rx8900_lock);
return flags;
}
ret = rx8900_write_reg(rx8900->client, RX8900_FLAG,
flags & ~(RX8900_FLAG_V1F | RX8900_FLAG_V2F));
mutex_unlock(&rx8900->rx8900_lock);
return ret;
}
static struct rtc_class_ops rx8900_rtc_ops = {
.read_time = rx8900_get_time,
.set_time = rx8900_set_time,
};
static int rx8900_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// todo
int flags, err;
struct rtc_xw_rx8900_driver *rx8900;
rx8900 = devm_kzalloc(&client->dev, sizeof(struct rtc_xw_rx8900_driver), GFP_KERNEL);
if (!rx8900)
return -ENOMEM;
mutex_init(&rx8900->rx8900_lock);
rx8900->client = client;
i2c_set_clientdata(client, rx8900);
flags = rx8900_read_reg(client, RX8900_FLAG);
if (flags < 0)
return flags;
if (flags & RX8900_FLAG_V1F)
dev_warn(&client->dev,
"Voltage low, temperature compensation stopped.\n");
if (flags & RX8900_FLAG_V2F)
dev_warn(&client->dev, "Voltage low, data loss detected.\n");
if (flags & RX8900_FLAG_AF)
dev_warn(&client->dev, "An alarm maybe have been missed.\n");
rx8900->rtc = devm_rtc_allocate_device(&client->dev);
if (IS_ERR(rx8900->rtc))
return PTR_ERR(rx8900->rtc);
rx8900->rtc->ops = &rx8900_rtc_ops;
rx8900->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
rx8900->rtc->range_max = RTC_TIMESTAMP_END_2099;
err = rtc_register_device(rx8900->rtc);
if (err)
return err;
rx8900->rtc->max_user_freq = 1;
return 0;
}
static const struct i2c_device_id rx8900_id[] = {
{"rx8900", 0},
{}};
static const struct of_device_id rx8900_of_match[] = {
{.compatible = "xw,rx8900"},
{}};
static struct i2c_driver rx8900_driver = {
.driver = {
.name = "rtc-xw-rx8900",
.of_match_table = of_match_ptr(rx8900_of_match),
},
.probe = rx8900_probe,
.id_table = rx8900_id,
};
module_i2c_driver(rx8900_driver);
MODULE_AUTHOR("Chengdu Xw Co., Ltd");
MODULE_DESCRIPTION("XW RTC RV8900 CUBE Driver");
MODULE_LICENSE("GPL v2");
Makfile
KERNEL_TOPDIR := xxx/open_source/linux/linux-5.10.y
CURRENT_ARCH := arm64
CURRENT_CROSS_COMPILE := aarch64-mix410-linux-
CURRENT_PATH := $(shell pwd)
obj-m := rtc_xw_rx8900_driver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNEL_TOPDIR) M=$(CURRENT_PATH) modules ARCH=$(CURRENT_ARCH) CROSS_COMPILE=$(CURRENT_CROSS_COMPILE)
clean:
$(MAKE) -C $(KERNEL_TOPDIR) M=$(CURRENT_PATH) clean ARCH=$(CURRENT_ARCH) CROSS_COMPILE=$(CURRENT_CROSS_COMPILE)
测试应用程序源码
// rtc_xw_rx8900_app.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
char *get_live_time(void)
{
static char s[32] = {0};
char YMD[16] = {0};
char HMS[16] = {0};
time_t currect_time;
struct tm *now_time;
char *cur_time = (char *)malloc(21 * sizeof(char));
time(&currect_time);
now_time = localtime(&cur_time);
strncat(cur_time, YMD, 11);
strncat(cur_time, HMS, 8);
printf("\nCurrent time %s\r\n", cur_time);
memcpy(s, cur_time, strlen(cur_time) + 1);
free(cur_time);
return s;
}
int main()
{
time_t timer; // time_t就是long int 类型
struct tm *tblock;
timer = time(NULL); // 获取相对于1970到现在的秒数
tblock = localtime(&timer); // 转换成本地日期格式
printf("Local time is: %s\n", asctime(tblock));
char *dt = (char *)malloc(sizeof(char));
// struct tm tm;
struct tm _tm;
struct timeval tv;
time_t timep;
// sscanf(dt, "%d-%d-%d %d:%d:%d", &tm.tm_year,
// &tm.tm_mon, &tm.tm_mday, &tm.tm_hour,
// &tm.tm_min, &tm.tm_sec);
_tm.tm_sec = 6;
_tm.tm_min = 6;
_tm.tm_hour = 6;
_tm.tm_mday = 6;
_tm.tm_mon = 6 - 1;
_tm.tm_year = 2066 - 1900;
timep = mktime(&_tm);
tv.tv_sec = timep;
tv.tv_usec = 0;
if (settimeofday(&tv, (struct timezone *)0) < 0)
{
printf("Set system datatime error!/n");
return -1;
}
free(dt);
return 0;
}