diff mbox series

[V6,2/2] rtc: zynqmp: Add calibration set and get support

Message ID 20220609140833.1784738-2-srinivas.neeli@xilinx.com
State Superseded
Headers show
Series [V6,1/2] dt-bindings: rtc: zynqmp: Add clock information | expand

Commit Message

Srinivas Neeli June 9, 2022, 2:08 p.m. UTC
Zynqmp RTC controller has a calibration feature to compensate
time deviation due to input clock inaccuracy.
Set and get calibration API's are used for setting and getting
calibration value from the controller calibration register.
As per RTC spec default calibration value is 0x7FFF.

Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
---
Changes in V6:
-None
Changes in V5:
-None
Changes in V4:
-Updated MIN and MAX calibration values.
Changes in V3:
-Calculated tick_mult using crystal frequency.
-Calibration register updating based on crystal frequency in probe.
-Supressed MIN an MAX calibration values,Will send separate patch in future.
Changes in V2:
-Removed unused macro.
-Updated code with review comments.
---
 drivers/rtc/rtc-zynqmp.c | 115 ++++++++++++++++++++++++++++++++-------
 1 file changed, 95 insertions(+), 20 deletions(-)

Comments

Peter Korsgaard June 9, 2022, 2:50 p.m. UTC | #1
>>>>> "Srinivas" == Srinivas Neeli <srinivas.neeli@xilinx.com> writes:

 > Zynqmp RTC controller has a calibration feature to compensate
 > time deviation due to input clock inaccuracy.
 > Set and get calibration API's are used for setting and getting
 > calibration value from the controller calibration register.
 > As per RTC spec default calibration value is 0x7FFF.

Where do you see that? ug1085 rev2.2 still lists:

Programming Sequences
init rtc
1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
Alexandre Belloni June 9, 2022, 2:53 p.m. UTC | #2
On 09/06/2022 16:50:03+0200, Peter Korsgaard wrote:
> >>>>> "Srinivas" == Srinivas Neeli <srinivas.neeli@xilinx.com> writes:
> 
>  > Zynqmp RTC controller has a calibration feature to compensate
>  > time deviation due to input clock inaccuracy.
>  > Set and get calibration API's are used for setting and getting
>  > calibration value from the controller calibration register.
>  > As per RTC spec default calibration value is 0x7FFF.
> 
> Where do you see that? ug1085 rev2.2 still lists:
> 
> Programming Sequences
> init rtc
> 1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
> 

This is a very weird value. Last time I had a look, I couldn't find an
explanation for it.
Peter Korsgaard June 9, 2022, 2:59 p.m. UTC | #3
>>>>> "Alexandre" == Alexandre Belloni <alexandre.belloni@bootlin.com> writes:

 > On 09/06/2022 16:50:03+0200, Peter Korsgaard wrote:
 >> >>>>> "Srinivas" == Srinivas Neeli <srinivas.neeli@xilinx.com> writes:
 >> 
 >> > Zynqmp RTC controller has a calibration feature to compensate
 >> > time deviation due to input clock inaccuracy.
 >> > Set and get calibration API's are used for setting and getting
 >> > calibration value from the controller calibration register.
 >> > As per RTC spec default calibration value is 0x7FFF.
 >> 
 >> Where do you see that? ug1085 rev2.2 still lists:
 >> 
 >> Programming Sequences
 >> init rtc
 >> 1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
 >> 

 > This is a very weird value. Last time I had a look, I couldn't find an
 > explanation for it.

Agreed. I am not arguing that it is sensible, I just wondered where this
new value comes from?
Srinivas Neeli June 10, 2022, 10:02 a.m. UTC | #4
[AMD Official Use Only - General]

Hi,

> -----Original Message-----
> From: Peter Korsgaard <peter@korsgaard.com>
> Sent: Thursday, June 9, 2022 8:30 PM
> To: Alexandre Belloni <alexandre.belloni@bootlin.com>
> Cc: Srinivas Neeli <srinivas.neeli@xilinx.com>; a.zummo@towertech.it;
> robh+dt@kernel.org; krzysztof.kozlowski+dt@linaro.org;
> michal.simek@xilinx.com; sgoud@xilinx.com; shubhraj@xilinx.com; Neeli,
> Srinivas <srinivas.neeli@amd.com>; neelisrinivas18@gmail.com;
> devicetree@vger.kernel.org; linux-rtc@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; git@xilinx.com
> Subject: Re: [PATCH V6 2/2] rtc: zynqmp: Add calibration set and get support
>
> >>>>> "Alexandre" == Alexandre Belloni <alexandre.belloni@bootlin.com>
> writes:
>
>  > On 09/06/2022 16:50:03+0200, Peter Korsgaard wrote:
>  >> >>>>> "Srinivas" == Srinivas Neeli <srinivas.neeli@xilinx.com> writes:
>  >>
>  >> > Zynqmp RTC controller has a calibration feature to compensate  >> >
> time deviation due to input clock inaccuracy.
>  >> > Set and get calibration API's are used for setting and getting  >> >
> calibration value from the controller calibration register.
>  >> > As per RTC spec default calibration value is 0x7FFF.
>  >>
>  >> Where do you see that? ug1085 rev2.2 still lists:
>  >>
>  >> Programming Sequences
>  >> init rtc
>  >> 1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
>  >>
>
>  > This is a very weird value. Last time I had a look, I couldn't find an  >
> explanation for it.
>
> Agreed. I am not arguing that it is sensible, I just wondered where this new
> value comes from?
>
This information is missing in our TRM, as per the internal RTC IP design specification document, the calibration value should be crystal clock frequency minus one.
For a crystal oscillator of 32.768KHz(0x8000) frequency, the calibration value will be 0x7FFF.

Will update TRM with the required information.

> --
> Bye, Peter Korsgaard
Peter Korsgaard June 10, 2022, 10:12 a.m. UTC | #5
>>>>> "Neeli," == Neeli, Srinivas <srinivas.neeli@amd.com> writes:

Hi,

 >> >> Where do you see that? ug1085 rev2.2 still lists:
 >> >>
 >> >> Programming Sequences
 >> >> init rtc
 >> >> 1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
 >> >>
 >> 
 >> > This is a very weird value. Last time I had a look, I couldn't find an  >
 >> explanation for it.
 >> 
 >> Agreed. I am not arguing that it is sensible, I just wondered where this new
 >> value comes from?
 >> 
 > This information is missing in our TRM, as per the internal RTC IP
 > design specification document, the calibration value should be crystal
 > clock frequency minus one.
 > For a crystal oscillator of 32.768KHz(0x8000) frequency, the calibration value will be 0x7FFF.

 > Will update TRM with the required information.

Ok, great!

Please put this change in a separate commit from the get/set logic.
Alexandre Belloni June 10, 2022, 10:40 a.m. UTC | #6
On 10/06/2022 12:12:11+0200, Peter Korsgaard wrote:
> >>>>> "Neeli," == Neeli, Srinivas <srinivas.neeli@amd.com> writes:
> 
> Hi,
> 
>  >> >> Where do you see that? ug1085 rev2.2 still lists:
>  >> >>
>  >> >> Programming Sequences
>  >> >> init rtc
>  >> >> 1. Write the value 0019_8231h into the calibration register, CALIB_WRITE.
>  >> >>
>  >> 
>  >> > This is a very weird value. Last time I had a look, I couldn't find an  >
>  >> explanation for it.
>  >> 
>  >> Agreed. I am not arguing that it is sensible, I just wondered where this new
>  >> value comes from?
>  >> 
>  > This information is missing in our TRM, as per the internal RTC IP
>  > design specification document, the calibration value should be crystal
>  > clock frequency minus one.
>  > For a crystal oscillator of 32.768KHz(0x8000) frequency, the calibration value will be 0x7FFF.
> 
>  > Will update TRM with the required information.
> 
> Ok, great!
> 
> Please put this change in a separate commit from the get/set logic.

I'm not sure it is worth it as it is probably being set (to the wrong
value) from your device tree anyway and the driver won't forcefully
change it in that case. The worse that can happen is that your new
boards will have the correct value.
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c
index f440bb52be92..1dd389b891fe 100644
--- a/drivers/rtc/rtc-zynqmp.c
+++ b/drivers/rtc/rtc-zynqmp.c
@@ -6,6 +6,7 @@ 
  *
  */
 
+#include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/io.h>
@@ -36,17 +37,23 @@ 
 #define RTC_OSC_EN		BIT(24)
 #define RTC_BATT_EN		BIT(31)
 
-#define RTC_CALIB_DEF		0x198233
+#define RTC_CALIB_DEF		0x7FFF
 #define RTC_CALIB_MASK		0x1FFFFF
 #define RTC_ALRM_MASK          BIT(1)
 #define RTC_MSEC               1000
+#define RTC_FR_MASK		0xF0000
+#define RTC_FR_MAX_TICKS	16
+#define RTC_PPB			1000000000LL
+#define RTC_MIN_OFFSET		-32768000
+#define RTC_MAX_OFFSET		32767000
 
 struct xlnx_rtc_dev {
 	struct rtc_device	*rtc;
 	void __iomem		*reg_base;
 	int			alarm_irq;
 	int			sec_irq;
-	unsigned int		calibval;
+	struct clk		*rtc_clk;
+	unsigned int		freq;
 };
 
 static int xlnx_rtc_set_time(struct device *dev, struct rtc_time *tm)
@@ -61,13 +68,6 @@  static int xlnx_rtc_set_time(struct device *dev, struct rtc_time *tm)
 	 */
 	new_time = rtc_tm_to_time64(tm) + 1;
 
-	/*
-	 * Writing into calibration register will clear the Tick Counter and
-	 * force the next second to be signaled exactly in 1 second period
-	 */
-	xrtcdev->calibval &= RTC_CALIB_MASK;
-	writel(xrtcdev->calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
-
 	writel(new_time, xrtcdev->reg_base + RTC_SET_TM_WR);
 
 	/*
@@ -173,15 +173,76 @@  static void xlnx_init_rtc(struct xlnx_rtc_dev *xrtcdev)
 	rtc_ctrl = readl(xrtcdev->reg_base + RTC_CTRL);
 	rtc_ctrl |= RTC_BATT_EN;
 	writel(rtc_ctrl, xrtcdev->reg_base + RTC_CTRL);
+}
 
-	/*
-	 * Based on crystal freq of 33.330 KHz
-	 * set the seconds counter and enable, set fractions counter
-	 * to default value suggested as per design spec
-	 * to correct RTC delay in frequency over period of time.
+static int xlnx_rtc_read_offset(struct device *dev, long *offset)
+{
+	struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
+	unsigned long long rtc_ppb = RTC_PPB;
+	unsigned int tick_mult = do_div(rtc_ppb, xrtcdev->freq);
+	unsigned int calibval;
+	long offset_val;
+
+	calibval = readl(xrtcdev->reg_base + RTC_CALIB_RD);
+	/* Offset with seconds ticks */
+	offset_val = calibval & RTC_TICK_MASK;
+	offset_val = offset_val - RTC_CALIB_DEF;
+	offset_val = offset_val * tick_mult;
+
+	/* Offset with fractional ticks */
+	if (calibval & RTC_FR_EN)
+		offset_val += ((calibval & RTC_FR_MASK) >> RTC_FR_DATSHIFT)
+			* (tick_mult / RTC_FR_MAX_TICKS);
+	*offset = offset_val;
+
+	return 0;
+}
+
+static int xlnx_rtc_set_offset(struct device *dev, long offset)
+{
+	struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
+	unsigned long long rtc_ppb = RTC_PPB;
+	unsigned int tick_mult = do_div(rtc_ppb, xrtcdev->freq);
+	unsigned char fract_tick;
+	unsigned int calibval;
+	short int  max_tick;
+	int fract_offset;
+
+	if (offset < RTC_MIN_OFFSET || offset > RTC_MAX_OFFSET)
+		return -ERANGE;
+
+	/* Number ticks for given offset */
+	max_tick = div_s64_rem(offset, tick_mult, &fract_offset);
+
+	/* Number fractional ticks for given offset */
+	if (fract_offset) {
+		if (fract_offset < 0) {
+			fract_offset = fract_offset + tick_mult;
+			max_tick--;
+		}
+		if (fract_offset > (tick_mult / RTC_FR_MAX_TICKS)) {
+			for (fract_tick = 1; fract_tick < 16; fract_tick++) {
+				if (fract_offset <=
+				    (fract_tick *
+				     (tick_mult / RTC_FR_MAX_TICKS)))
+					break;
+			}
+		}
+	}
+
+	/* Zynqmp RTC uses second and fractional tick
+	 * counters for compensation
 	 */
-	xrtcdev->calibval &= RTC_CALIB_MASK;
-	writel(xrtcdev->calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
+	calibval = max_tick + RTC_CALIB_DEF;
+
+	if (fract_tick)
+		calibval |= RTC_FR_EN;
+
+	calibval |= (fract_tick << RTC_FR_DATSHIFT);
+
+	writel(calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
+
+	return 0;
 }
 
 static const struct rtc_class_ops xlnx_rtc_ops = {
@@ -190,6 +251,8 @@  static const struct rtc_class_ops xlnx_rtc_ops = {
 	.read_alarm	  = xlnx_rtc_read_alarm,
 	.set_alarm	  = xlnx_rtc_set_alarm,
 	.alarm_irq_enable = xlnx_rtc_alarm_irq_enable,
+	.read_offset	  = xlnx_rtc_read_offset,
+	.set_offset	  = xlnx_rtc_set_offset,
 };
 
 static irqreturn_t xlnx_rtc_interrupt(int irq, void *id)
@@ -255,10 +318,22 @@  static int xlnx_rtc_probe(struct platform_device *pdev)
 		return ret;
 	}
 
-	ret = of_property_read_u32(pdev->dev.of_node, "calibration",
-				   &xrtcdev->calibval);
-	if (ret)
-		xrtcdev->calibval = RTC_CALIB_DEF;
+	/* Getting the rtc_clk info */
+	xrtcdev->rtc_clk = devm_clk_get_optional(&pdev->dev, "rtc_clk");
+	if (IS_ERR(xrtcdev->rtc_clk)) {
+		if (PTR_ERR(xrtcdev->rtc_clk) != -EPROBE_DEFER)
+			dev_warn(&pdev->dev, "Device clock not found.\n");
+	}
+	xrtcdev->freq = clk_get_rate(xrtcdev->rtc_clk);
+	if (!xrtcdev->freq) {
+		ret = of_property_read_u32(pdev->dev.of_node, "calibration",
+					   &xrtcdev->freq);
+		if (ret)
+			xrtcdev->freq = RTC_CALIB_DEF;
+	}
+	ret = readl(xrtcdev->reg_base + RTC_CALIB_RD);
+	if (!ret)
+		writel(xrtcdev->freq, (xrtcdev->reg_base + RTC_CALIB_WR));
 
 	xlnx_init_rtc(xrtcdev);