Commit 85370d3d authored by Alexandre Belloni's avatar Alexandre Belloni
Browse files

rtc: pcf85063: add offset correction support



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: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
parent fadfd092
Loading
Loading
Loading
Loading
+57 −1
Original line number Diff line number Diff line
@@ -38,6 +38,12 @@
#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,