EPSON RX8900 SA/CE Linux驱动开发笔记

i2c总线通信,时钟芯片RX8900CE UB linux下rtc驱动记录


前言

参考资料:

《EPSON RX8900 SA/CE应用手册》
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.7》

参考网站:
30.Linux-RTC驱动分析及使用
Makefile编译驱动失败,找不到头文件
Linux驱动开发: Linux下RTC实时时钟驱动
半导小芯-芯片查询工具
Linux下用C获取当前时间
LinuxC 获取、修改、设置系统软件时间


寄存器表格

基础时间和日历寄存器
寄存器初始化

快速参考

手册示例

初始化
等待VLF清零

设置和读取日期

添加设备树设备

代码如下(示例):

			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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值