rtc: pcf85063: add offset correction support
authorAlexandre Belloni <alexandre.belloni@bootlin.com>
Mon, 1 Apr 2019 16:08:15 +0000 (18:08 +0200)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Thu, 4 Apr 2019 08:07:11 +0000 (10:07 +0200)
The PCF850363 has an offset correction with two modes:

With mode 0, the correction is triggered once every two hours and then
correction pulses are applied once per minute until the programmed
correction values have been implemented. This gives a step of 4.34 ppm.

With mode 1, the correction is triggered once every four minutes and then
correction pulses are applied once per second up to a maximum of 60 pulses.
When correction values greater than 60 pulses are used, additional
correction pulses are made in the 59 th second. This gives a step of 4.069
ppm.

Use the correction closest to the requested value.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/rtc/rtc-pcf85063.c

index 0a03a60bc31fbc5b26cb83c1bdbb70ee4e522fb9..ed7f171253655eabc9fbcc3d4663b868f0c36404 100644 (file)
 #define PCF85063_CTRL2_AF              BIT(6)
 #define PCF85063_CTRL2_AIE             BIT(7)
 
+#define PCF85063_REG_OFFSET            0x02
+#define PCF85063_OFFSET_SIGN_BIT       6       /* 2's complement sign bit */
+#define PCF85063_OFFSET_MODE           BIT(7)
+#define PCF85063_OFFSET_STEP0          4340
+#define PCF85063_OFFSET_STEP1          4069
+
 #define PCF85063_REG_RAM               0x03
 
 #define PCF85063_REG_SC                        0x04 /* datetime */
@@ -225,14 +231,64 @@ static irqreturn_t pcf85063_rtc_handle_irq(int irq, void *dev_id)
        return IRQ_NONE;
 }
 
+static int pcf85063_read_offset(struct device *dev, long *offset)
+{
+       struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
+       long val;
+       u32 reg;
+       int ret;
+
+       ret = regmap_read(pcf85063->regmap, PCF85063_REG_OFFSET, &reg);
+       if (ret < 0)
+               return ret;
+
+       val = sign_extend32(reg & ~PCF85063_OFFSET_MODE,
+                           PCF85063_OFFSET_SIGN_BIT);
+
+       if (reg & PCF85063_OFFSET_MODE)
+               *offset = val * PCF85063_OFFSET_STEP1;
+       else
+               *offset = val * PCF85063_OFFSET_STEP0;
+
+       return 0;
+}
+
+static int pcf85063_set_offset(struct device *dev, long offset)
+{
+       struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
+       s8 mode0, mode1, reg;
+       unsigned int error0, error1;
+
+       if (offset > PCF85063_OFFSET_STEP0 * 63)
+               return -ERANGE;
+       if (offset < PCF85063_OFFSET_STEP0 * -64)
+               return -ERANGE;
+
+       mode0 = DIV_ROUND_CLOSEST(offset, PCF85063_OFFSET_STEP0);
+       mode1 = DIV_ROUND_CLOSEST(offset, PCF85063_OFFSET_STEP1);
+
+       error0 = abs(offset - (mode0 * PCF85063_OFFSET_STEP0));
+       error1 = abs(offset - (mode1 * PCF85063_OFFSET_STEP1));
+       if (mode1 > 63 || mode1 < -64 || error0 < error1)
+               reg = mode0 & ~PCF85063_OFFSET_MODE;
+       else
+               reg = mode1 | PCF85063_OFFSET_MODE;
+
+       return regmap_write(pcf85063->regmap, PCF85063_REG_OFFSET, reg);
+}
+
 static const struct rtc_class_ops pcf85063_rtc_ops = {
        .read_time      = pcf85063_rtc_read_time,
-       .set_time       = pcf85063_rtc_set_time
+       .set_time       = pcf85063_rtc_set_time,
+       .read_offset    = pcf85063_read_offset,
+       .set_offset     = pcf85063_set_offset,
 };
 
 static const struct rtc_class_ops pcf85063_rtc_ops_alarm = {
        .read_time      = pcf85063_rtc_read_time,
        .set_time       = pcf85063_rtc_set_time,
+       .read_offset    = pcf85063_read_offset,
+       .set_offset     = pcf85063_set_offset,
        .read_alarm     = pcf85063_rtc_read_alarm,
        .set_alarm      = pcf85063_rtc_set_alarm,
        .alarm_irq_enable = pcf85063_rtc_alarm_irq_enable,