Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

rtc: armada38x: add support for trimming the RTC

Add support for trimming the RTC using the offset mechanism. This RTC
supports two modes: low update mode and high update mode. Low update
mode has finer precision than high update mode, so we use the low mode
where possible.

Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>

authored by

Russell King and committed by
Alexandre Belloni
f94ffbc2 8a25c8f6

+101
+101
drivers/rtc/rtc-armada38x.c
··· 28 28 #define RTC_IRQ_AL_EN BIT(0) 29 29 #define RTC_IRQ_FREQ_EN BIT(1) 30 30 #define RTC_IRQ_FREQ_1HZ BIT(2) 31 + #define RTC_CCR 0x18 32 + #define RTC_CCR_MODE BIT(15) 31 33 32 34 #define RTC_TIME 0xC 33 35 #define RTC_ALARM1 0x10 ··· 345 343 return IRQ_HANDLED; 346 344 } 347 345 346 + /* 347 + * The information given in the Armada 388 functional spec is complex. 348 + * They give two different formulas for calculating the offset value, 349 + * but when considering "Offset" as an 8-bit signed integer, they both 350 + * reduce down to (we shall rename "Offset" as "val" here): 351 + * 352 + * val = (f_ideal / f_measured - 1) / resolution where f_ideal = 32768 353 + * 354 + * Converting to time, f = 1/t: 355 + * val = (t_measured / t_ideal - 1) / resolution where t_ideal = 1/32768 356 + * 357 + * => t_measured / t_ideal = val * resolution + 1 358 + * 359 + * "offset" in the RTC interface is defined as: 360 + * t = t0 * (1 + offset * 1e-9) 361 + * where t is the desired period, t0 is the measured period with a zero 362 + * offset, which is t_measured above. With t0 = t_measured and t = t_ideal, 363 + * offset = (t_ideal / t_measured - 1) / 1e-9 364 + * 365 + * => t_ideal / t_measured = offset * 1e-9 + 1 366 + * 367 + * so: 368 + * 369 + * offset * 1e-9 + 1 = 1 / (val * resolution + 1) 370 + * 371 + * We want "resolution" to be an integer, so resolution = R * 1e-9, giving 372 + * offset = 1e18 / (val * R + 1e9) - 1e9 373 + * val = (1e18 / (offset + 1e9) - 1e9) / R 374 + * with a common transformation: 375 + * f(x) = 1e18 / (x + 1e9) - 1e9 376 + * offset = f(val * R) 377 + * val = f(offset) / R 378 + * 379 + * Armada 38x supports two modes, fine mode (954ppb) and coarse mode (3815ppb). 380 + */ 381 + static long armada38x_ppb_convert(long ppb) 382 + { 383 + long div = ppb + 1000000000L; 384 + 385 + return div_s64(1000000000000000000LL + div / 2, div) - 1000000000L; 386 + } 387 + 388 + static int armada38x_rtc_read_offset(struct device *dev, long *offset) 389 + { 390 + struct armada38x_rtc *rtc = dev_get_drvdata(dev); 391 + unsigned long ccr, flags; 392 + long ppb_cor; 393 + 394 + spin_lock_irqsave(&rtc->lock, flags); 395 + ccr = rtc->data->read_rtc_reg(rtc, RTC_CCR); 396 + spin_unlock_irqrestore(&rtc->lock, flags); 397 + 398 + ppb_cor = (ccr & RTC_CCR_MODE ? 3815 : 954) * (s8)ccr; 399 + /* ppb_cor + 1000000000L can never be zero */ 400 + *offset = armada38x_ppb_convert(ppb_cor); 401 + 402 + return 0; 403 + } 404 + 405 + static int armada38x_rtc_set_offset(struct device *dev, long offset) 406 + { 407 + struct armada38x_rtc *rtc = dev_get_drvdata(dev); 408 + unsigned long ccr = 0; 409 + long ppb_cor, off; 410 + 411 + /* 412 + * The maximum ppb_cor is -128 * 3815 .. 127 * 3815, but we 413 + * need to clamp the input. This equates to -484270 .. 488558. 414 + * Not only is this to stop out of range "off" but also to 415 + * avoid the division by zero in armada38x_ppb_convert(). 416 + */ 417 + offset = clamp(offset, -484270L, 488558L); 418 + 419 + ppb_cor = armada38x_ppb_convert(offset); 420 + 421 + /* 422 + * Use low update mode where possible, which gives a better 423 + * resolution of correction. 424 + */ 425 + off = DIV_ROUND_CLOSEST(ppb_cor, 954); 426 + if (off > 127 || off < -128) { 427 + ccr = RTC_CCR_MODE; 428 + off = DIV_ROUND_CLOSEST(ppb_cor, 3815); 429 + } 430 + 431 + /* 432 + * Armada 388 requires a bit pattern in bits 14..8 depending on 433 + * the sign bit: { 0, ~S, S, S, S, S, S } 434 + */ 435 + ccr |= (off & 0x3fff) ^ 0x2000; 436 + rtc_delayed_write(ccr, rtc, RTC_CCR); 437 + 438 + return 0; 439 + } 440 + 348 441 static const struct rtc_class_ops armada38x_rtc_ops = { 349 442 .read_time = armada38x_rtc_read_time, 350 443 .set_time = armada38x_rtc_set_time, 351 444 .read_alarm = armada38x_rtc_read_alarm, 352 445 .set_alarm = armada38x_rtc_set_alarm, 353 446 .alarm_irq_enable = armada38x_rtc_alarm_irq_enable, 447 + .read_offset = armada38x_rtc_read_offset, 448 + .set_offset = armada38x_rtc_set_offset, 354 449 }; 355 450 356 451 static const struct rtc_class_ops armada38x_rtc_ops_noirq = { 357 452 .read_time = armada38x_rtc_read_time, 358 453 .set_time = armada38x_rtc_set_time, 359 454 .read_alarm = armada38x_rtc_read_alarm, 455 + .read_offset = armada38x_rtc_read_offset, 456 + .set_offset = armada38x_rtc_set_offset, 360 457 }; 361 458 362 459 static const struct armada38x_rtc_data armada38x_data = {