diff mbox series

rtc: zynqmp: Add calibration set and get support

Message ID 1582191106-30431-1-git-send-email-srinivas.neeli@xilinx.com
State Changes Requested
Headers show
Series rtc: zynqmp: Add calibration set and get support | expand

Commit Message

Srinivas Neeli Feb. 20, 2020, 9:31 a.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.

Signed-off-by: Srinivas Goud <srinivas.goud@xilinx.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
---
 drivers/rtc/rtc-zynqmp.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)

Comments

Michal Simek Feb. 24, 2020, 11:16 a.m. UTC | #1
On 20. 02. 20 10:31, Srinivas Neeli wrote:
> 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.
> 
> Signed-off-by: Srinivas Goud <srinivas.goud@xilinx.com>
> Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com>
> ---
>  drivers/rtc/rtc-zynqmp.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 78 insertions(+)
> 
> diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c
> index 4b1077e2f826..b4118e9e4fcc 100644
> --- a/drivers/rtc/rtc-zynqmp.c
> +++ b/drivers/rtc/rtc-zynqmp.c
> @@ -40,6 +40,12 @@
>  #define RTC_CALIB_MASK		0x1FFFFF
>  #define RTC_ALRM_MASK          BIT(1)
>  #define RTC_MSEC               1000
> +#define RTC_FR_MASK             0xF0000
> +#define RTC_SEC_MAX_VAL         0xFFFFFFFF
> +#define RTC_FR_MAX_TICKS        16
> +#define RTC_OFFSET_MAX          150000
> +#define RTC_OFFSET_MIN          -150000
> +#define RTC_PPB                 1000000000LL
>  

please use tabs here.

>  struct xlnx_rtc_dev {
>  	struct rtc_device	*rtc;
> @@ -184,12 +190,84 @@ static void xlnx_init_rtc(struct xlnx_rtc_dev *xrtcdev)
>  	writel(xrtcdev->calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
>  }
>  
> +static int xlnx_rtc_read_offset(struct device *dev, long *offset)
> +{
> +	struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
> +	long offset_val;
> +	unsigned int reg;
> +	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
> +
> +	reg = readl(xrtcdev->reg_base + RTC_CALIB_RD);
> +
> +	/* Offset with seconds ticks */
> +	offset_val = reg & RTC_TICK_MASK;
> +	offset_val = offset_val - xrtcdev->calibval;
> +	offset_val = offset_val * tick_mult;
> +
> +	/* Offset with fractional ticks */
> +	if (reg & RTC_FR_EN)
> +		offset_val += ((reg & 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);
> +	short int  max_tick;
> +	unsigned char fract_tick = 0;
> +	unsigned int  calibval;

double space.

> +	int fract_offset;
> +	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
> +
> +	/* Make sure offset value is within supported range */
> +	if (offset < RTC_OFFSET_MIN || offset > RTC_OFFSET_MAX)
> +		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
> +	 */
> +	calibval = max_tick + xrtcdev->calibval;
> +
> +	if (fract_tick)
> +		calibval |= RTC_FR_EN;
> +
> +	calibval |= (fract_tick <<  RTC_FR_DATSHIFT);

here is double space.

> +
> +	writel(calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
> +
> +	return 0;
> +}
> +
>  static const struct rtc_class_ops xlnx_rtc_ops = {
>  	.set_time	  = xlnx_rtc_set_time,
>  	.read_time	  = xlnx_rtc_read_time,
>  	.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,

use tabs as is done above.

>  };
>  
>  static irqreturn_t xlnx_rtc_interrupt(int irq, void *id)
> 

The rest looks good.

Thanks,
Michal
kernel test robot Feb. 25, 2020, 1:19 a.m. UTC | #2
Hi Srinivas,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on abelloni/rtc-next]
[also build test ERROR on next-20200224]
[cannot apply to v5.6-rc3]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Srinivas-Neeli/rtc-zynqmp-Add-calibration-set-and-get-support/20200222-053755
base:   https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git rtc-next
config: c6x-randconfig-a001-20200225 (attached as .config)
compiler: c6x-elf-gcc (GCC) 7.5.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=7.5.0 make.cross ARCH=c6x 

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/rtc/rtc-zynqmp.o: In function `xlnx_rtc_read_offset':
>> drivers/rtc/rtc-zynqmp.c:198: undefined reference to `__c6xabi_divlli'
>> drivers/rtc/rtc-zynqmp.c:198: undefined reference to `__c6xabi_divlli'
   drivers/rtc/rtc-zynqmp.o: In function `xlnx_rtc_set_offset':
   drivers/rtc/rtc-zynqmp.c:223: undefined reference to `__c6xabi_divlli'
   drivers/rtc/rtc-zynqmp.c:223: undefined reference to `__c6xabi_divlli'

vim +198 drivers/rtc/rtc-zynqmp.c

   192	
   193	static int xlnx_rtc_read_offset(struct device *dev, long *offset)
   194	{
   195		struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
   196		long offset_val;
   197		unsigned int reg;
 > 198		unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
   199	
   200		reg = readl(xrtcdev->reg_base + RTC_CALIB_RD);
   201	
   202		/* Offset with seconds ticks */
   203		offset_val = reg & RTC_TICK_MASK;
   204		offset_val = offset_val - xrtcdev->calibval;
   205		offset_val = offset_val * tick_mult;
   206	
   207		/* Offset with fractional ticks */
   208		if (reg & RTC_FR_EN)
   209			offset_val += ((reg & RTC_FR_MASK) >> RTC_FR_DATSHIFT)
   210				* (tick_mult / RTC_FR_MAX_TICKS);
   211		*offset = offset_val;
   212	
   213		return 0;
   214	}
   215	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Alexandre Belloni Feb. 27, 2020, 11:45 a.m. UTC | #3
Hi,

On 20/02/2020 15:01:46+0530, Srinivas Neeli wrote:
> diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c
> index 4b1077e2f826..b4118e9e4fcc 100644
> --- a/drivers/rtc/rtc-zynqmp.c
> +++ b/drivers/rtc/rtc-zynqmp.c
> @@ -40,6 +40,12 @@
>  #define RTC_CALIB_MASK		0x1FFFFF
>  #define RTC_ALRM_MASK          BIT(1)
>  #define RTC_MSEC               1000
> +#define RTC_FR_MASK             0xF0000
> +#define RTC_SEC_MAX_VAL         0xFFFFFFFF

This value is not used

> +#define RTC_FR_MAX_TICKS        16
> +#define RTC_OFFSET_MAX          150000
> +#define RTC_OFFSET_MIN          -150000
> +#define RTC_PPB                 1000000000LL
>  
>  struct xlnx_rtc_dev {
>  	struct rtc_device	*rtc;
> @@ -184,12 +190,84 @@ static void xlnx_init_rtc(struct xlnx_rtc_dev *xrtcdev)
>  	writel(xrtcdev->calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
>  }
>  
> +static int xlnx_rtc_read_offset(struct device *dev, long *offset)
> +{
> +	struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
> +	long offset_val;
> +	unsigned int reg;
> +	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
> +

I don't get why you are not simply reusing xrtcdev->calibval. Using
.set_offset has to take precedence on any value that would have been set
using DT. Ideally, the DT binding should be removed too.

Currently, the calibration value is overwritten using the DT value
every time .set_time is called because xrtcdev->calibval is never
updated.

> +	reg = readl(xrtcdev->reg_base + RTC_CALIB_RD);
> +
> +	/* Offset with seconds ticks */
> +	offset_val = reg & RTC_TICK_MASK;
> +	offset_val = offset_val - xrtcdev->calibval;
> +	offset_val = offset_val * tick_mult;
> +
> +	/* Offset with fractional ticks */
> +	if (reg & RTC_FR_EN)
> +		offset_val += ((reg & 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);
> +	short int  max_tick;
> +	unsigned char fract_tick = 0;
> +	unsigned int  calibval;
> +	int fract_offset;
> +	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
> +
> +	/* Make sure offset value is within supported range */
> +	if (offset < RTC_OFFSET_MIN || offset > RTC_OFFSET_MAX)
> +		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
> +	 */
> +	calibval = max_tick + xrtcdev->calibval;
> +
> +	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 = {
>  	.set_time	  = xlnx_rtc_set_time,
>  	.read_time	  = xlnx_rtc_read_time,
>  	.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)
> -- 
> 2.7.4
>
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c
index 4b1077e2f826..b4118e9e4fcc 100644
--- a/drivers/rtc/rtc-zynqmp.c
+++ b/drivers/rtc/rtc-zynqmp.c
@@ -40,6 +40,12 @@ 
 #define RTC_CALIB_MASK		0x1FFFFF
 #define RTC_ALRM_MASK          BIT(1)
 #define RTC_MSEC               1000
+#define RTC_FR_MASK             0xF0000
+#define RTC_SEC_MAX_VAL         0xFFFFFFFF
+#define RTC_FR_MAX_TICKS        16
+#define RTC_OFFSET_MAX          150000
+#define RTC_OFFSET_MIN          -150000
+#define RTC_PPB                 1000000000LL
 
 struct xlnx_rtc_dev {
 	struct rtc_device	*rtc;
@@ -184,12 +190,84 @@  static void xlnx_init_rtc(struct xlnx_rtc_dev *xrtcdev)
 	writel(xrtcdev->calibval, (xrtcdev->reg_base + RTC_CALIB_WR));
 }
 
+static int xlnx_rtc_read_offset(struct device *dev, long *offset)
+{
+	struct xlnx_rtc_dev *xrtcdev = dev_get_drvdata(dev);
+	long offset_val;
+	unsigned int reg;
+	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
+
+	reg = readl(xrtcdev->reg_base + RTC_CALIB_RD);
+
+	/* Offset with seconds ticks */
+	offset_val = reg & RTC_TICK_MASK;
+	offset_val = offset_val - xrtcdev->calibval;
+	offset_val = offset_val * tick_mult;
+
+	/* Offset with fractional ticks */
+	if (reg & RTC_FR_EN)
+		offset_val += ((reg & 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);
+	short int  max_tick;
+	unsigned char fract_tick = 0;
+	unsigned int  calibval;
+	int fract_offset;
+	unsigned int tick_mult = RTC_PPB / xrtcdev->calibval;
+
+	/* Make sure offset value is within supported range */
+	if (offset < RTC_OFFSET_MIN || offset > RTC_OFFSET_MAX)
+		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
+	 */
+	calibval = max_tick + xrtcdev->calibval;
+
+	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 = {
 	.set_time	  = xlnx_rtc_set_time,
 	.read_time	  = xlnx_rtc_read_time,
 	.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)