rtc: pcf2123: implement read_offset and set_offset
authorJoshua Clayton <stillcompiling@gmail.com>
Fri, 5 Feb 2016 20:41:13 +0000 (12:41 -0800)
committerAlexandre Belloni <alexandre.belloni@free-electrons.com>
Mon, 14 Mar 2016 16:08:16 +0000 (17:08 +0100)
pcf2123 has an offset register, which can be used to make minor
adjustments to the clock rate to compensate for temperature or
a crystal that is not exactly right.

Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
drivers/rtc/rtc-pcf2123.c

index d9a44ad1239b8b8486f3ac498a34bff2d5419cc8..da27738b12428eaac52771cfc62261995d1aa58e 100644 (file)
 /* PCF2123_REG_OFFSET BITS */
 #define OFFSET_SIGN_BIT                BIT(6)  /* 2's complement sign bit */
 #define OFFSET_COARSE          BIT(7)  /* Coarse mode offset */
+#define OFFSET_STEP            (2170)  /* Offset step in parts per billion */
 
 /* READ/WRITE ADDRESS BITS */
 #define PCF2123_WRITE          BIT(4)
@@ -206,6 +207,59 @@ static ssize_t pcf2123_store(struct device *dev, struct device_attribute *attr,
        return count;
 }
 
+static int pcf2123_read_offset(struct device *dev, long *offset)
+{
+       int ret;
+       s8 reg;
+
+       ret = pcf2123_read(dev, PCF2123_REG_OFFSET, &reg, 1);
+       if (ret < 0)
+               return ret;
+
+       if (reg & OFFSET_COARSE)
+               reg <<= 1; /* multiply by 2 and sign extend */
+       else
+               reg |= (reg & OFFSET_SIGN_BIT) << 1; /* sign extend only */
+
+       *offset = ((long)reg) * OFFSET_STEP;
+
+       return 0;
+}
+
+/*
+ * The offset register is a 7 bit signed value with a coarse bit in bit 7.
+ * The main difference between the two is normal offset adjusts the first
+ * second of n minutes every other hour, with 61, 62 and 63 being shoved
+ * into the 60th minute.
+ * The coarse adjustment does the same, but every hour.
+ * the two overlap, with every even normal offset value corresponding
+ * to a coarse offset. Based on this algorithm, it seems that despite the
+ * name, coarse offset is a better fit for overlapping values.
+ */
+static int pcf2123_set_offset(struct device *dev, long offset)
+{
+       s8 reg;
+
+       if (offset > OFFSET_STEP * 127)
+               reg = 127;
+       else if (offset < OFFSET_STEP * -128)
+               reg = -128;
+       else
+               reg = (s8)((offset + (OFFSET_STEP >> 1)) / OFFSET_STEP);
+
+       /* choose fine offset only for odd values in the normal range */
+       if (reg & 1 && reg <= 63 && reg >= -64) {
+               /* Normal offset. Clear the coarse bit */
+               reg &= ~OFFSET_COARSE;
+       } else {
+               /* Coarse offset. Divide by 2 and set the coarse bit */
+               reg >>= 1;
+               reg |= OFFSET_COARSE;
+       }
+
+       return pcf2123_write_reg(dev, PCF2123_REG_OFFSET, reg);
+}
+
 static int pcf2123_rtc_read_time(struct device *dev, struct rtc_time *tm)
 {
        u8 rxbuf[7];
@@ -314,6 +368,9 @@ static int pcf2123_reset(struct device *dev)
 static const struct rtc_class_ops pcf2123_rtc_ops = {
        .read_time      = pcf2123_rtc_read_time,
        .set_time       = pcf2123_rtc_set_time,
+       .read_offset    = pcf2123_read_offset,
+       .set_offset     = pcf2123_set_offset,
+
 };
 
 static int pcf2123_probe(struct spi_device *spi)