diff mbox

[v2,1/2] drivers: rtc: rtc-tps65910: Add RTC calibration support

Message ID 1481406644-1614-2-git-send-email-vesa.jaaskelainen@vaisala.com
State Changes Requested
Headers show

Commit Message

Vesa Jääskeläinen Dec. 10, 2016, 9:50 p.m. UTC
Texas Instrument's TPS65910 has support for compensating RTC crystal
inaccuracies. When enabled every hour RTC counter value will be compensated
with two's complement value.

Signed-off-by: Vesa Jääskeläinen <vesa.jaaskelainen@vaisala.com>
---
 drivers/rtc/rtc-tps65910.c   | 132 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/tps65910.h |   1 +
 2 files changed, 133 insertions(+)

Comments

Lee Jones Dec. 12, 2016, 7:44 a.m. UTC | #1
On Sat, 10 Dec 2016, Vesa Jääskeläinen wrote:

> Texas Instrument's TPS65910 has support for compensating RTC crystal
> inaccuracies. When enabled every hour RTC counter value will be compensated
> with two's complement value.
> 
> Signed-off-by: Vesa Jääskeläinen <vesa.jaaskelainen@vaisala.com>
> ---
>  drivers/rtc/rtc-tps65910.c   | 132 +++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/tps65910.h |   1 +

For the MFD change:

Acked-by: Lee Jones <lee.jones@linaro.org>

>  2 files changed, 133 insertions(+)
> 
> diff --git a/drivers/rtc/rtc-tps65910.c b/drivers/rtc/rtc-tps65910.c
> index 5a3d53c..aafa716 100644
> --- a/drivers/rtc/rtc-tps65910.c
> +++ b/drivers/rtc/rtc-tps65910.c
> @@ -21,6 +21,7 @@
>  #include <linux/types.h>
>  #include <linux/rtc.h>
>  #include <linux/bcd.h>
> +#include <linux/math64.h>
>  #include <linux/platform_device.h>
>  #include <linux/interrupt.h>
>  #include <linux/mfd/tps65910.h>
> @@ -33,6 +34,19 @@ struct tps65910_rtc {
>  /* Total number of RTC registers needed to set time*/
>  #define NUM_TIME_REGS	(TPS65910_YEARS - TPS65910_SECONDS + 1)
>  
> +/* Total number of RTC registers needed to set compensation registers */
> +#define NUM_COMP_REGS	(TPS65910_RTC_COMP_MSB - TPS65910_RTC_COMP_LSB + 1)
> +
> +/* Min and max values supported with 'offset' interface (swapped sign) */
> +#define MIN_OFFSET	(-277761)
> +#define MAX_OFFSET	(277778)
> +
> +/* Number of ticks per hour */
> +#define TICKS_PER_HOUR	(32768 * 3600)
> +
> +/* Multiplier for ppb conversions */
> +#define PPB_MULT	(1000000000LL)
> +
>  static int tps65910_rtc_alarm_irq_enable(struct device *dev, unsigned enabled)
>  {
>  	struct tps65910 *tps = dev_get_drvdata(dev->parent);
> @@ -187,6 +201,122 @@ static int tps65910_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
>  	return ret;
>  }
>  
> +static int tps65910_rtc_set_calibration(struct device *dev, int calibration)
> +{
> +	unsigned char comp_data[NUM_COMP_REGS];
> +	struct tps65910 *tps = dev_get_drvdata(dev->parent);
> +	s16 value;
> +	int ret;
> +
> +	/*
> +	 * TPS65910 uses two's complement 16 bit value for compensation for RTC
> +	 * crystal inaccuracies. One time every hour when seconds counter
> +	 * increments from 0 to 1 compensation value will be added to internal
> +	 * RTC counter value.
> +	 *
> +	 * Compensation value 0x7FFF is prohibited value.
> +	 *
> +	 * Valid range for compensation value: [-32768 .. 32766]
> +	 */
> +	if ((calibration < -32768) || (calibration > 32766)) {
> +		dev_err(dev, "RTC calibration value out of range: %d\n",
> +			calibration);
> +		return -EINVAL;
> +	}
> +
> +	value = (s16)calibration;
> +
> +	comp_data[0] = (u16)value & 0xFF;
> +	comp_data[1] = ((u16)value >> 8) & 0xFF;
> +
> +	/* Update all the compensation registers in one shot */
> +	ret = regmap_bulk_write(tps->regmap, TPS65910_RTC_COMP_LSB,
> +		comp_data, NUM_COMP_REGS);
> +	if (ret < 0) {
> +		dev_err(dev, "rtc_set_calibration error: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Enable automatic compensation */
> +	ret = regmap_update_bits(tps->regmap, TPS65910_RTC_CTRL,
> +		TPS65910_RTC_CTRL_AUTO_COMP, TPS65910_RTC_CTRL_AUTO_COMP);
> +	if (ret < 0)
> +		dev_err(dev, "auto_comp enable failed with error: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int tps65910_rtc_get_calibration(struct device *dev, int *calibration)
> +{
> +	unsigned char comp_data[NUM_COMP_REGS];
> +	struct tps65910 *tps = dev_get_drvdata(dev->parent);
> +	u16 value;
> +	int ret;
> +
> +	ret = regmap_bulk_read(tps->regmap, TPS65910_RTC_COMP_LSB, comp_data,
> +		NUM_COMP_REGS);
> +	if (ret < 0) {
> +		dev_err(dev, "rtc_get_calibration error: %d\n", ret);
> +		return ret;
> +	}
> +
> +	value = (u16)comp_data[0] | ((u16)comp_data[1] << 8);
> +
> +	*calibration = (s16)value;
> +
> +	return 0;
> +}
> +
> +static int tps65910_read_offset(struct device *dev, long *offset)
> +{
> +	int calibration;
> +	s64 tmp;
> +	int ret;
> +
> +	ret = tps65910_rtc_get_calibration(dev, &calibration);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Convert from RTC calibration register format to ppb format */
> +	tmp = calibration * (s64)PPB_MULT;
> +	if (tmp < 0)
> +		tmp -= TICKS_PER_HOUR / 2LL;
> +	else
> +		tmp += TICKS_PER_HOUR / 2LL;
> +	tmp = div_s64(tmp, TICKS_PER_HOUR);
> +
> +	/* Offset value operates in negative way, so swap sign */
> +	*offset = (long)-tmp;
> +
> +	return 0;
> +}
> +
> +static int tps65910_set_offset(struct device *dev, long offset)
> +{
> +	int calibration;
> +	s64 tmp;
> +	int ret;
> +
> +	/* Make sure offset value is within supported range */
> +	if (offset < MIN_OFFSET || offset > MAX_OFFSET)
> +		return -ERANGE;
> +
> +	/* Convert from ppb format to RTC calibration register format */
> +	tmp = offset * (s64)TICKS_PER_HOUR;
> +	if (tmp < 0)
> +		tmp -= PPB_MULT / 2LL;
> +	else
> +		tmp += PPB_MULT / 2LL;
> +	tmp = div_s64(tmp, PPB_MULT);
> +
> +	/* Offset value operates in negative way, so swap sign */
> +	calibration = (int)-tmp;
> +
> +	ret = tps65910_rtc_set_calibration(dev, calibration);
> +
> +	return ret;
> +}
> +
>  static irqreturn_t tps65910_rtc_interrupt(int irq, void *rtc)
>  {
>  	struct device *dev = rtc;
> @@ -219,6 +349,8 @@ static const struct rtc_class_ops tps65910_rtc_ops = {
>  	.read_alarm	= tps65910_rtc_read_alarm,
>  	.set_alarm	= tps65910_rtc_set_alarm,
>  	.alarm_irq_enable = tps65910_rtc_alarm_irq_enable,
> +	.read_offset	= tps65910_read_offset,
> +	.set_offset	= tps65910_set_offset,
>  };
>  
>  static int tps65910_rtc_probe(struct platform_device *pdev)
> diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h
> index 6483a6f..ffb21e7 100644
> --- a/include/linux/mfd/tps65910.h
> +++ b/include/linux/mfd/tps65910.h
> @@ -134,6 +134,7 @@
>  
>  /* RTC_CTRL_REG bitfields */
>  #define TPS65910_RTC_CTRL_STOP_RTC			0x01 /*0=stop, 1=run */
> +#define TPS65910_RTC_CTRL_AUTO_COMP			0x04
>  #define TPS65910_RTC_CTRL_GET_TIME			0x40
>  
>  /* RTC_STATUS_REG bitfields */
Vesa Jääskeläinen Dec. 23, 2016, 11:17 a.m. UTC | #2
On 2016-12-12 09:44, Lee Jones wrote:
> On Sat, 10 Dec 2016, Vesa Jääskeläinen wrote:
>
>> Texas Instrument's TPS65910 has support for compensating RTC crystal
>> inaccuracies. When enabled every hour RTC counter value will be compensated
>> with two's complement value.
>>
>> Signed-off-by: Vesa Jääskeläinen <vesa.jaaskelainen@vaisala.com>
>> ---
>>   drivers/rtc/rtc-tps65910.c   | 132 +++++++++++++++++++++++++++++++++++++++++++
>>   include/linux/mfd/tps65910.h |   1 +
> For the MFD change:
>
> Acked-by: Lee Jones <lee.jones@linaro.org>

Thanks for the review. Added Acked-by to v3.

Thanks,
Vesa Jääskeläinen
diff mbox

Patch

diff --git a/drivers/rtc/rtc-tps65910.c b/drivers/rtc/rtc-tps65910.c
index 5a3d53c..aafa716 100644
--- a/drivers/rtc/rtc-tps65910.c
+++ b/drivers/rtc/rtc-tps65910.c
@@ -21,6 +21,7 @@ 
 #include <linux/types.h>
 #include <linux/rtc.h>
 #include <linux/bcd.h>
+#include <linux/math64.h>
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
 #include <linux/mfd/tps65910.h>
@@ -33,6 +34,19 @@  struct tps65910_rtc {
 /* Total number of RTC registers needed to set time*/
 #define NUM_TIME_REGS	(TPS65910_YEARS - TPS65910_SECONDS + 1)
 
+/* Total number of RTC registers needed to set compensation registers */
+#define NUM_COMP_REGS	(TPS65910_RTC_COMP_MSB - TPS65910_RTC_COMP_LSB + 1)
+
+/* Min and max values supported with 'offset' interface (swapped sign) */
+#define MIN_OFFSET	(-277761)
+#define MAX_OFFSET	(277778)
+
+/* Number of ticks per hour */
+#define TICKS_PER_HOUR	(32768 * 3600)
+
+/* Multiplier for ppb conversions */
+#define PPB_MULT	(1000000000LL)
+
 static int tps65910_rtc_alarm_irq_enable(struct device *dev, unsigned enabled)
 {
 	struct tps65910 *tps = dev_get_drvdata(dev->parent);
@@ -187,6 +201,122 @@  static int tps65910_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
 	return ret;
 }
 
+static int tps65910_rtc_set_calibration(struct device *dev, int calibration)
+{
+	unsigned char comp_data[NUM_COMP_REGS];
+	struct tps65910 *tps = dev_get_drvdata(dev->parent);
+	s16 value;
+	int ret;
+
+	/*
+	 * TPS65910 uses two's complement 16 bit value for compensation for RTC
+	 * crystal inaccuracies. One time every hour when seconds counter
+	 * increments from 0 to 1 compensation value will be added to internal
+	 * RTC counter value.
+	 *
+	 * Compensation value 0x7FFF is prohibited value.
+	 *
+	 * Valid range for compensation value: [-32768 .. 32766]
+	 */
+	if ((calibration < -32768) || (calibration > 32766)) {
+		dev_err(dev, "RTC calibration value out of range: %d\n",
+			calibration);
+		return -EINVAL;
+	}
+
+	value = (s16)calibration;
+
+	comp_data[0] = (u16)value & 0xFF;
+	comp_data[1] = ((u16)value >> 8) & 0xFF;
+
+	/* Update all the compensation registers in one shot */
+	ret = regmap_bulk_write(tps->regmap, TPS65910_RTC_COMP_LSB,
+		comp_data, NUM_COMP_REGS);
+	if (ret < 0) {
+		dev_err(dev, "rtc_set_calibration error: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable automatic compensation */
+	ret = regmap_update_bits(tps->regmap, TPS65910_RTC_CTRL,
+		TPS65910_RTC_CTRL_AUTO_COMP, TPS65910_RTC_CTRL_AUTO_COMP);
+	if (ret < 0)
+		dev_err(dev, "auto_comp enable failed with error: %d\n", ret);
+
+	return ret;
+}
+
+static int tps65910_rtc_get_calibration(struct device *dev, int *calibration)
+{
+	unsigned char comp_data[NUM_COMP_REGS];
+	struct tps65910 *tps = dev_get_drvdata(dev->parent);
+	u16 value;
+	int ret;
+
+	ret = regmap_bulk_read(tps->regmap, TPS65910_RTC_COMP_LSB, comp_data,
+		NUM_COMP_REGS);
+	if (ret < 0) {
+		dev_err(dev, "rtc_get_calibration error: %d\n", ret);
+		return ret;
+	}
+
+	value = (u16)comp_data[0] | ((u16)comp_data[1] << 8);
+
+	*calibration = (s16)value;
+
+	return 0;
+}
+
+static int tps65910_read_offset(struct device *dev, long *offset)
+{
+	int calibration;
+	s64 tmp;
+	int ret;
+
+	ret = tps65910_rtc_get_calibration(dev, &calibration);
+	if (ret < 0)
+		return ret;
+
+	/* Convert from RTC calibration register format to ppb format */
+	tmp = calibration * (s64)PPB_MULT;
+	if (tmp < 0)
+		tmp -= TICKS_PER_HOUR / 2LL;
+	else
+		tmp += TICKS_PER_HOUR / 2LL;
+	tmp = div_s64(tmp, TICKS_PER_HOUR);
+
+	/* Offset value operates in negative way, so swap sign */
+	*offset = (long)-tmp;
+
+	return 0;
+}
+
+static int tps65910_set_offset(struct device *dev, long offset)
+{
+	int calibration;
+	s64 tmp;
+	int ret;
+
+	/* Make sure offset value is within supported range */
+	if (offset < MIN_OFFSET || offset > MAX_OFFSET)
+		return -ERANGE;
+
+	/* Convert from ppb format to RTC calibration register format */
+	tmp = offset * (s64)TICKS_PER_HOUR;
+	if (tmp < 0)
+		tmp -= PPB_MULT / 2LL;
+	else
+		tmp += PPB_MULT / 2LL;
+	tmp = div_s64(tmp, PPB_MULT);
+
+	/* Offset value operates in negative way, so swap sign */
+	calibration = (int)-tmp;
+
+	ret = tps65910_rtc_set_calibration(dev, calibration);
+
+	return ret;
+}
+
 static irqreturn_t tps65910_rtc_interrupt(int irq, void *rtc)
 {
 	struct device *dev = rtc;
@@ -219,6 +349,8 @@  static const struct rtc_class_ops tps65910_rtc_ops = {
 	.read_alarm	= tps65910_rtc_read_alarm,
 	.set_alarm	= tps65910_rtc_set_alarm,
 	.alarm_irq_enable = tps65910_rtc_alarm_irq_enable,
+	.read_offset	= tps65910_read_offset,
+	.set_offset	= tps65910_set_offset,
 };
 
 static int tps65910_rtc_probe(struct platform_device *pdev)
diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h
index 6483a6f..ffb21e7 100644
--- a/include/linux/mfd/tps65910.h
+++ b/include/linux/mfd/tps65910.h
@@ -134,6 +134,7 @@ 
 
 /* RTC_CTRL_REG bitfields */
 #define TPS65910_RTC_CTRL_STOP_RTC			0x01 /*0=stop, 1=run */
+#define TPS65910_RTC_CTRL_AUTO_COMP			0x04
 #define TPS65910_RTC_CTRL_GET_TIME			0x40
 
 /* RTC_STATUS_REG bitfields */