Skip to content

Commit dd93849

Browse files
Camel Guoalexandrebelloni
authored andcommitted
rtc: rs5c372: add offset correction support
In order for linux userspace application to be able to adjust offset to keep rtc precision as high as possible, this commit adds support of offset correction by adjusting the time trimming register on rs5c372[a|b] and oscilluation adjustment register on r2025x, r222[1|3]x, rv5c38[6|7]a. Signed-off-by: Camel Guo <[email protected]> Signed-off-by: Alexandre Belloni <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent cd17420 commit dd93849

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

drivers/rtc/rtc-rs5c372.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#define RS5C372_REG_TRIM 7
3131
# define RS5C372_TRIM_XSL 0x80
3232
# define RS5C372_TRIM_MASK 0x7F
33+
# define R2221TL_TRIM_DEV (1 << 7) /* only if R2221TL */
34+
# define RS5C372_TRIM_DECR (1 << 6)
3335

3436
#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
3537
#define RS5C_REG_ALARM_A_HOURS 9
@@ -539,6 +541,122 @@ static int rs5c372_ioctl(struct device *dev, unsigned int cmd, unsigned long arg
539541
#define rs5c372_ioctl NULL
540542
#endif
541543

544+
static int rs5c372_read_offset(struct device *dev, long *offset)
545+
{
546+
struct rs5c372 *rs5c = i2c_get_clientdata(to_i2c_client(dev));
547+
u8 val = rs5c->regs[RS5C372_REG_TRIM];
548+
long ppb_per_step = 0;
549+
bool decr = val & RS5C372_TRIM_DECR;
550+
551+
switch (rs5c->type) {
552+
case rtc_r2221tl:
553+
ppb_per_step = val & R2221TL_TRIM_DEV ? 1017 : 3051;
554+
break;
555+
case rtc_rs5c372a:
556+
case rtc_rs5c372b:
557+
ppb_per_step = val & RS5C372_TRIM_XSL ? 3125 : 3051;
558+
break;
559+
default:
560+
ppb_per_step = 3051;
561+
break;
562+
}
563+
564+
/* Only bits[0:5] repsents the time counts */
565+
val &= 0x3F;
566+
567+
/* If bits[1:5] are all 0, it means no increment or decrement */
568+
if (!(val & 0x3E)) {
569+
*offset = 0;
570+
} else {
571+
if (decr)
572+
*offset = -(((~val) & 0x3F) + 1) * ppb_per_step;
573+
else
574+
*offset = (val - 1) * ppb_per_step;
575+
}
576+
577+
return 0;
578+
}
579+
580+
static int rs5c372_set_offset(struct device *dev, long offset)
581+
{
582+
struct rs5c372 *rs5c = i2c_get_clientdata(to_i2c_client(dev));
583+
int addr = RS5C_ADDR(RS5C372_REG_TRIM);
584+
u8 val = 0;
585+
u8 tmp = 0;
586+
long ppb_per_step = 3051;
587+
long steps = LONG_MIN;
588+
589+
switch (rs5c->type) {
590+
case rtc_rs5c372a:
591+
case rtc_rs5c372b:
592+
tmp = rs5c->regs[RS5C372_REG_TRIM];
593+
if (tmp & RS5C372_TRIM_XSL) {
594+
ppb_per_step = 3125;
595+
val |= RS5C372_TRIM_XSL;
596+
}
597+
break;
598+
case rtc_r2221tl:
599+
/*
600+
* Check if it is possible to use high resolution mode (DEV=1).
601+
* In this mode, the minimum resolution is 2 / (32768 * 20 * 3),
602+
* which is about 1017 ppb.
603+
*/
604+
steps = DIV_ROUND_CLOSEST(offset, 1017);
605+
if (steps >= -0x3E && steps <= 0x3E) {
606+
ppb_per_step = 1017;
607+
val |= R2221TL_TRIM_DEV;
608+
} else {
609+
/*
610+
* offset is out of the range of high resolution mode.
611+
* Try to use low resolution mode (DEV=0). In this mode,
612+
* the minimum resolution is 2 / (32768 * 20), which is
613+
* about 3051 ppb.
614+
*/
615+
steps = LONG_MIN;
616+
}
617+
break;
618+
default:
619+
break;
620+
}
621+
622+
if (steps == LONG_MIN) {
623+
steps = DIV_ROUND_CLOSEST(offset, ppb_per_step);
624+
if (steps > 0x3E || steps < -0x3E)
625+
return -ERANGE;
626+
}
627+
628+
if (steps > 0) {
629+
val |= steps + 1;
630+
} else {
631+
val |= RS5C372_TRIM_DECR;
632+
val |= (~(-steps - 1)) & 0x3F;
633+
}
634+
635+
if (!steps || !(val & 0x3E)) {
636+
/*
637+
* if offset is too small, set oscillation adjustment register
638+
* or time trimming register with its default value whic means
639+
* no increment or decrement. But for rs5c372[a|b], the XSL bit
640+
* should be kept unchanged.
641+
*/
642+
if (rs5c->type == rtc_rs5c372a || rs5c->type == rtc_rs5c372b)
643+
val &= RS5C372_TRIM_XSL;
644+
else
645+
val = 0;
646+
}
647+
648+
dev_dbg(&rs5c->client->dev, "write 0x%x for offset %ld\n", val, offset);
649+
650+
if (i2c_smbus_write_byte_data(rs5c->client, addr, val) < 0) {
651+
dev_err(&rs5c->client->dev, "failed to write 0x%x to reg %d\n", val, addr);
652+
return -EIO;
653+
}
654+
655+
rs5c->regs[RS5C372_REG_TRIM] = val;
656+
657+
return 0;
658+
}
659+
542660
static const struct rtc_class_ops rs5c372_rtc_ops = {
543661
.proc = rs5c372_rtc_proc,
544662
.read_time = rs5c372_rtc_read_time,
@@ -547,6 +665,8 @@ static const struct rtc_class_ops rs5c372_rtc_ops = {
547665
.set_alarm = rs5c_set_alarm,
548666
.alarm_irq_enable = rs5c_rtc_alarm_irq_enable,
549667
.ioctl = rs5c372_ioctl,
668+
.read_offset = rs5c372_read_offset,
669+
.set_offset = rs5c372_set_offset,
550670
};
551671

552672
#if IS_ENABLED(CONFIG_RTC_INTF_SYSFS)

0 commit comments

Comments
 (0)