[RK3288][Android6.0] RTC模块RX8010SJ驱动添加及改动

来源:互联网 发布:英文缩写查询软件 编辑:程序博客网 时间:2024/05/17 14:29

Platform: Rockchip
OS: Android 6.0
Kernel: 3.10.92

      • 设备部分
      • 驱动部分
      • 改动部分
      • 参考

在网上的其他branch上找到一份RX8010SJ的驱动,但是有点问题,先贴代码,再说明修改部分。

设备部分:

比较简单,确定I2C端口以及设备地址就行。
&i2c1 {
status = “okay”;
rx8010: rx8010@32 {
compatible = “epson,rx8010”;
reg = <0x32>;
status = “okay”;
};
};

驱动部分:

此驱动已经可以读写时间,alarm部分因为没有需求,所以硬件没接,也未做测试。

/* * Driver for the Epson RTC module RX-8010 SJ * * Copyright(C) Timesys Corporation 2015 * Copyright(C) General Electric Company 2015 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */#include <linux/bcd.h>#include <linux/bitops.h>#include <linux/i2c.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/rtc.h>#define RX8010_SEC     0x10#define RX8010_MIN     0x11#define RX8010_HOUR    0x12#define RX8010_WDAY    0x13#define RX8010_MDAY    0x14#define RX8010_MONTH   0x15#define RX8010_YEAR    0x16#define RX8010_YEAR    0x16#define RX8010_RESV17  0x17#define RX8010_ALMIN   0x18#define RX8010_ALHOUR  0x19#define RX8010_ALWDAY  0x1A#define RX8010_TCOUNT0 0x1B#define RX8010_TCOUNT1 0x1C#define RX8010_EXT     0x1D#define RX8010_FLAG    0x1E#define RX8010_CTRL    0x1F/* 0x20 to 0x2F are user registers */#define RX8010_RESV30  0x30#define RX8010_RESV31  0x31#define RX8010_IRQ     0x32#define RX8010_EXT_WADA  BIT(3)#define RX8010_FLAG_VLF  BIT(1)#define RX8010_FLAG_AF   BIT(3)#define RX8010_FLAG_TF   BIT(4)#define RX8010_FLAG_UF   BIT(5)#define RX8010_CTRL_AIE  BIT(3)#define RX8010_CTRL_UIE  BIT(5)#define RX8010_CTRL_STOP BIT(6)#define RX8010_CTRL_TEST BIT(7)#define RX8010_ALARM_AE  BIT(7)static const struct i2c_device_id rx8010_id[] = {    { "rx8010", 0 },    { }};MODULE_DEVICE_TABLE(i2c, rx8010_id);static const struct of_device_id rx8010_of_match[] = {    { .compatible = "epson,rx8010" },    { }};MODULE_DEVICE_TABLE(of, rx8010_of_match);struct rx8010_data {    struct i2c_client *client;    struct rtc_device *rtc;    u8 ctrlreg;};static irqreturn_t rx8010_irq_1_handler(int irq, void *dev_id){    struct i2c_client *client = dev_id;    struct rx8010_data *rx8010 = i2c_get_clientdata(client);    int flagreg;    mutex_lock(&rx8010->rtc->ops_lock);    flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);    if (flagreg <= 0) {        mutex_unlock(&rx8010->rtc->ops_lock);        return IRQ_NONE;    }    if (flagreg & RX8010_FLAG_VLF)        dev_warn(&client->dev, "Frequency stop detected\n");    if (flagreg & RX8010_FLAG_TF) {        flagreg &= ~RX8010_FLAG_TF;        rtc_update_irq(rx8010->rtc, 1, RTC_PF | RTC_IRQF);    }    if (flagreg & RX8010_FLAG_AF) {        flagreg &= ~RX8010_FLAG_AF;        rtc_update_irq(rx8010->rtc, 1, RTC_AF | RTC_IRQF);    }    if (flagreg & RX8010_FLAG_UF) {        flagreg &= ~RX8010_FLAG_UF;        rtc_update_irq(rx8010->rtc, 1, RTC_UF | RTC_IRQF);    }    i2c_smbus_write_byte_data(client, RX8010_FLAG, flagreg);    mutex_unlock(&rx8010->rtc->ops_lock);    return IRQ_HANDLED;}static int rx8010_get_time(struct device *dev, struct rtc_time *dt){    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    u8 date[7];    int flagreg;    int err;    flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);    if (flagreg < 0)        return flagreg;    if (flagreg & RX8010_FLAG_VLF) {        dev_warn(dev, "Frequency stop detected\n");        return -EINVAL;    }    err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_SEC,                        7, date);    if (err != 7)        return err < 0 ? err : -EIO;    dt->tm_sec = bcd2bin(date[RX8010_SEC - RX8010_SEC] & 0x7f);    dt->tm_min = bcd2bin(date[RX8010_MIN - RX8010_SEC] & 0x7f);    dt->tm_hour = bcd2bin(date[RX8010_HOUR - RX8010_SEC] & 0x3f);    dt->tm_mday = bcd2bin(date[RX8010_MDAY - RX8010_SEC] & 0x3f);    dt->tm_mon = bcd2bin(date[RX8010_MONTH - RX8010_SEC] & 0x1f) - 1;    dt->tm_year = bcd2bin(date[RX8010_YEAR - RX8010_SEC]) + 100;    dt->tm_wday = ffs(date[RX8010_WDAY - RX8010_SEC] & 0x7f);    return rtc_valid_tm(dt);}static int rx8010_set_time(struct device *dev, struct rtc_time *dt){    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    u8 date[7];    int ctrl, flagreg;    int ret;    if ((dt->tm_year < 100) || (dt->tm_year > 199))        return -EINVAL;    /* set STOP bit before changing clock/calendar */    ctrl = i2c_smbus_read_byte_data(rx8010->client, RX8010_CTRL);    if (ctrl < 0)        return ctrl;    rx8010->ctrlreg = ctrl | RX8010_CTRL_STOP;    ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,                    rx8010->ctrlreg);    if (ret < 0)        return ret;    date[RX8010_SEC - RX8010_SEC] = bin2bcd(dt->tm_sec);    date[RX8010_MIN - RX8010_SEC] = bin2bcd(dt->tm_min);    date[RX8010_HOUR - RX8010_SEC] = bin2bcd(dt->tm_hour);    date[RX8010_MDAY - RX8010_SEC] = bin2bcd(dt->tm_mday);    date[RX8010_MONTH - RX8010_SEC] = bin2bcd(dt->tm_mon + 1);    date[RX8010_YEAR - RX8010_SEC] = bin2bcd(dt->tm_year - 100);    date[RX8010_WDAY - RX8010_SEC] = bin2bcd(1 << dt->tm_wday);    ret = i2c_smbus_write_i2c_block_data(rx8010->client,                         RX8010_SEC, 7, date);    if (ret < 0)        return ret;    /* clear STOP bit after changing clock/calendar */    ctrl = i2c_smbus_read_byte_data(rx8010->client, RX8010_CTRL);    if (ctrl < 0)        return ctrl;    rx8010->ctrlreg = ctrl & ~RX8010_CTRL_STOP;    ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,                    rx8010->ctrlreg);    if (ret < 0)        return ret;    flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);    if (flagreg < 0) {        return flagreg;    }    if (flagreg & RX8010_FLAG_VLF)        ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG,                        flagreg & ~RX8010_FLAG_VLF);    return 0;}static int rx8010_init_client(struct i2c_client *client){    struct rx8010_data *rx8010 = i2c_get_clientdata(client);    u8 ctrl[2];    int need_clear = 0, err = 0;    u8 flag;    flag = i2c_smbus_read_byte_data(client, RX8010_FLAG);    if (err < 0)        return err;    flag &= ~(RX8010_FLAG_VLF);    err = i2c_smbus_write_byte_data(client, RX8010_FLAG, flag);    /* Initialize reserved registers as specified in datasheet */    err = i2c_smbus_write_byte_data(client, RX8010_RESV17, 0xD8);    if (err < 0)        return err;    err = i2c_smbus_write_byte_data(client, RX8010_RESV30, 0x00);    if (err < 0)        return err;    err = i2c_smbus_write_byte_data(client, RX8010_RESV31, 0x08);    if (err < 0)        return err;    err = i2c_smbus_write_byte_data(client, RX8010_IRQ, 0x00);    if (err < 0)        return err;    err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_FLAG,                        2, ctrl);    if (err != 2)        return err < 0 ? err : -EIO;    if (ctrl[0] & RX8010_FLAG_VLF)        dev_warn(&client->dev, "Frequency stop was detected\n");    if (ctrl[0] & RX8010_FLAG_AF) {        dev_warn(&client->dev, "Alarm was detected\n");        need_clear = 1;    }    if (ctrl[0] & RX8010_FLAG_TF)        need_clear = 1;    if (ctrl[0] & RX8010_FLAG_UF)        need_clear = 1;    if (need_clear) {        ctrl[0] &= ~(RX8010_FLAG_AF | RX8010_FLAG_TF | RX8010_FLAG_UF);        err = i2c_smbus_write_byte_data(client, RX8010_FLAG, ctrl[0]);        if (err < 0)            return err;    }    rx8010->ctrlreg = (ctrl[1] & ~RX8010_CTRL_TEST);    return err;}static int rx8010_read_alarm(struct device *dev, struct rtc_wkalrm *t){    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    struct i2c_client *client = rx8010->client;    u8 alarmvals[3];    int flagreg;    int err;    err = i2c_smbus_read_i2c_block_data(client, RX8010_ALMIN, 3, alarmvals);    if (err != 3)        return err < 0 ? err : -EIO;    flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);    if (flagreg < 0)        return flagreg;    t->time.tm_sec = 0;    t->time.tm_min = bcd2bin(alarmvals[0] & 0x7f);    t->time.tm_hour = bcd2bin(alarmvals[1] & 0x3f);    if (!(alarmvals[2] & RX8010_ALARM_AE))        t->time.tm_mday = bcd2bin(alarmvals[2] & 0x7f);    t->enabled = !!(rx8010->ctrlreg & RX8010_CTRL_AIE);    t->pending = (flagreg & RX8010_FLAG_AF) && t->enabled;    return err;}static int rx8010_set_alarm(struct device *dev, struct rtc_wkalrm *t){    struct i2c_client *client = to_i2c_client(dev);    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    u8 alarmvals[3];    int extreg, flagreg;    int err;    flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);    if (flagreg < 0) {        return flagreg;    }    if (rx8010->ctrlreg & (RX8010_CTRL_AIE | RX8010_CTRL_UIE)) {        rx8010->ctrlreg &= ~(RX8010_CTRL_AIE | RX8010_CTRL_UIE);        err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,                        rx8010->ctrlreg);        if (err < 0) {            return err;        }    }    flagreg &= ~RX8010_FLAG_AF;    err = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG, flagreg);    if (err < 0)        return err;    alarmvals[0] = bin2bcd(t->time.tm_min);    alarmvals[1] = bin2bcd(t->time.tm_hour);    alarmvals[2] = bin2bcd(t->time.tm_mday);    err = i2c_smbus_write_i2c_block_data(rx8010->client, RX8010_ALMIN,                         2, alarmvals);    if (err < 0)        return err;    extreg = i2c_smbus_read_byte_data(client, RX8010_EXT);    if (extreg < 0)        return extreg;    extreg |= RX8010_EXT_WADA;    err = i2c_smbus_write_byte_data(rx8010->client, RX8010_EXT, extreg);    if (err < 0)        return err;    if (alarmvals[2] == 0)        alarmvals[2] |= RX8010_ALARM_AE;    err = i2c_smbus_write_byte_data(rx8010->client, RX8010_ALWDAY,                    alarmvals[2]);    if (err < 0)        return err;    if (t->enabled) {        if (rx8010->rtc->uie_rtctimer.enabled)            rx8010->ctrlreg |= RX8010_CTRL_UIE;        if (rx8010->rtc->aie_timer.enabled)            rx8010->ctrlreg |=                (RX8010_CTRL_AIE | RX8010_CTRL_UIE);        err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,                        rx8010->ctrlreg);        if (err < 0)            return err;    }    return 0;}static int rx8010_alarm_irq_enable(struct device *dev,                   unsigned int enabled){    struct i2c_client *client = to_i2c_client(dev);    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    int flagreg;    u8 ctrl;    int err;    ctrl = rx8010->ctrlreg;    if (enabled) {        if (rx8010->rtc->uie_rtctimer.enabled)            ctrl |= RX8010_CTRL_UIE;        if (rx8010->rtc->aie_timer.enabled)            ctrl |= (RX8010_CTRL_AIE | RX8010_CTRL_UIE);    } else {        if (!rx8010->rtc->uie_rtctimer.enabled)            ctrl &= ~RX8010_CTRL_UIE;        if (!rx8010->rtc->aie_timer.enabled)            ctrl &= ~RX8010_CTRL_AIE;    }    flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);    if (flagreg < 0)        return flagreg;    flagreg &= ~RX8010_FLAG_AF;    err = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG, flagreg);    if (err < 0)        return err;    if (ctrl != rx8010->ctrlreg) {        rx8010->ctrlreg = ctrl;        err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,                        rx8010->ctrlreg);        if (err < 0)            return err;    }    return 0;}static int rx8010_ioctl(struct device *dev, unsigned int cmd, unsigned long arg){    struct i2c_client *client = to_i2c_client(dev);    struct rx8010_data *rx8010 = dev_get_drvdata(dev);    int ret, tmp;    int flagreg;    switch (cmd) {    case RTC_VL_READ:        flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);        if (flagreg < 0)            return flagreg;        tmp = !!(flagreg & RX8010_FLAG_VLF);        if (copy_to_user((void __user *)arg, &tmp, sizeof(int)))            return -EFAULT;        return 0;    case RTC_VL_CLR:        flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);        if (flagreg < 0) {            return flagreg;        }        flagreg &= ~RX8010_FLAG_VLF;        ret = i2c_smbus_write_byte_data(client, RX8010_FLAG, flagreg);        if (ret < 0)            return ret;        return 0;    default:        return -ENOIOCTLCMD;    }}static struct rtc_class_ops rx8010_rtc_ops = {    .read_time = rx8010_get_time,    .set_time = rx8010_set_time,    .ioctl = rx8010_ioctl,};static int rx8010_probe(struct i2c_client *client,            const struct i2c_device_id *id){    struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);    struct rx8010_data *rx8010;    int err = 0;    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA        | I2C_FUNC_SMBUS_I2C_BLOCK)) {        dev_err(&adapter->dev, "doesn't support required functionality\n");        return -EIO;    }    rx8010 = devm_kzalloc(&client->dev, sizeof(struct rx8010_data),                  GFP_KERNEL);    if (!rx8010)        return -ENOMEM;    rx8010->client = client;    i2c_set_clientdata(client, rx8010);    err = rx8010_init_client(client);    if (err)        return err;    if (client->irq > 0) {        dev_info(&client->dev, "IRQ %d supplied\n", client->irq);        err = devm_request_threaded_irq(&client->dev, client->irq, NULL,                        rx8010_irq_1_handler,                        IRQF_TRIGGER_LOW | IRQF_ONESHOT,                        "rx8010", client);        if (err) {            dev_err(&client->dev, "unable to request IRQ\n");            client->irq = 0;        } else {            rx8010_rtc_ops.read_alarm = rx8010_read_alarm;            rx8010_rtc_ops.set_alarm = rx8010_set_alarm;            rx8010_rtc_ops.alarm_irq_enable = rx8010_alarm_irq_enable;        }    }    rx8010_rtc_ops.read_alarm = rx8010_read_alarm;    rx8010_rtc_ops.set_alarm = rx8010_set_alarm;    rx8010_rtc_ops.alarm_irq_enable = rx8010_alarm_irq_enable;    device_init_wakeup(&client->dev, 1);    rx8010->rtc = devm_rtc_device_register(&client->dev, client->name,        &rx8010_rtc_ops, THIS_MODULE);    if (IS_ERR(rx8010->rtc)) {        dev_err(&client->dev, "unable to register the class device\n");        return PTR_ERR(rx8010->rtc);    }    rx8010->rtc->max_user_freq = 1;    return err;}static struct i2c_driver rx8010_driver = {    .driver = {        .name = "rtc-rx8010",        .of_match_table = of_match_ptr(rx8010_of_match),    },    .probe      = rx8010_probe,    .id_table   = rx8010_id,};module_i2c_driver(rx8010_driver);MODULE_AUTHOR("Akshay Bhat <akshay.bhat@timesys.com>");MODULE_DESCRIPTION("Epson RX8010SJ RTC driver");MODULE_LICENSE("GPL v2");

改动部分:

左边是原生驱动,右边是修改部分。
改动一:
这里写图片描述
寄存器地址0x31误写成0x32.

改动二:
这里写图片描述
前面硬件的文章有提过,开机需要检查VLF是否被置位,如果是表示rtc还不能正常工作,需要置成0 ,然后再初始化才能工作,这样也是和数据手册上描述的上电流程是一致的。

改动三:
这里写图片描述
由于驱动没用到irq,那么rx8010_probe()里的rx8010_rtc_ops结构体对应的read_alarm/set_alarm/alarm_irq_enable将不会被赋值,这将导致注册alarm驱动的时候失败。
alarmtimer.c

static int alarmtimer_rtc_add_device(struct device *dev, struct class_interface *class_intf){    unsigned long flags;    struct rtc_device *rtc = to_rtc_device(dev);    if (rtcdev)        return -EBUSY;    /*如果set_alarm是空函数会注册失败*/    if (!rtc->ops->set_alarm)        return -1;    if (!device_may_wakeup(rtc->dev.parent))        return -1;    spin_lock_irqsave(&rtcdev_lock, flags);    if (!rtcdev) {        /*最终得到一个全局的rtcdev*/        rtcdev = rtc;        /* hold a reference so it doesn't go away */        get_device(dev);    }    spin_unlock_irqrestore(&rtcdev_lock, flags);    return 0;}

同样的 device_init_wakeup(&client->dev, 1); 是为了让device_may_wakeup(rtc->dev.parent) 不会返回失败。
这样才设置时间的时候就可以正确地得到rtc device了。
alarm-dev.c

static int alarm_set_rtc(struct timespec *ts){......    rtc_dev = alarmtimer_get_rtcdev();    /*这样得到就不会为NULL了。*/    if (rtc_dev)         rv = rtc_set_time(rtc_dev, &new_rtc_tm);......}

参考:

https://raw.githubusercontent.com/torvalds/linux/master/drivers/rtc/rtc-rx8010.c

原创粉丝点击