diff --git a/drivers/rtc/rtc-sun6i.c b/drivers/rtc/rtc-sun6i.c index e4fc6e4f2bfbf4..54bd47fb0a5f26 100644 --- a/drivers/rtc/rtc-sun6i.c +++ b/drivers/rtc/rtc-sun6i.c @@ -48,7 +48,8 @@ /* Alarm 0 (counter) */ #define SUN6I_ALRM_COUNTER 0x0020 -#define SUN6I_ALRM_CUR_VAL 0x0024 +/* This holds the remaining alarm seconds on older SoCs (current value) */ +#define SUN6I_ALRM_COUNTER_HMS 0x0024 #define SUN6I_ALRM_EN 0x0028 #define SUN6I_ALRM_EN_CNT_EN BIT(0) #define SUN6I_ALRM_IRQ_EN 0x002c @@ -523,36 +524,55 @@ static int sun6i_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm) struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); struct rtc_time *alrm_tm = &wkalrm->time; struct rtc_time tm_now; - unsigned long time_now = 0; unsigned long time_set = 0; - unsigned long time_gap = 0; + unsigned long counter_val, counter_val_hms; int ret = 0; - ret = sun6i_rtc_gettime(dev, &tm_now); - if (ret < 0) { - dev_err(dev, "Error in getting time\n"); - return -EINVAL; - } - time_set = rtc_tm_to_time64(alrm_tm); - time_now = rtc_tm_to_time64(&tm_now); - if (time_set <= time_now) { - dev_err(dev, "Date to set in the past\n"); - return -EINVAL; - } - - time_gap = time_set - time_now; - if (time_gap > U32_MAX) { - dev_err(dev, "Date too far in the future\n"); - return -EINVAL; + if (chip->flags & RTC_LINEAR_DAY) { + /* + * The alarm registers hold the actual alarm time, encoded + * in the same way (linear day + HMS) as the current time. + */ + counter_val_hms = SUN6I_TIME_SET_SEC_VALUE(alrm_tm->tm_sec) | + SUN6I_TIME_SET_MIN_VALUE(alrm_tm->tm_min) | + SUN6I_TIME_SET_HOUR_VALUE(alrm_tm->tm_hour); + counter_val = mktime64(alrm_tm->tm_year + 1900, alrm_tm->tm_mon, + alrm_tm->tm_mday, 0, 0, 0) / SEC_PER_DAY; + } else { + /* The alarm register holds the number of seconds left. */ + unsigned long time_now; + + ret = sun6i_rtc_gettime(dev, &tm_now); + if (ret < 0) { + dev_err(dev, "Error in getting time\n"); + return -EINVAL; + } + + time_now = rtc_tm_to_time64(&tm_now); + if (time_set <= time_now) { + dev_err(dev, "Date to set in the past\n"); + return -EINVAL; + } + + counter_val = time_set - time_now; + + if (counter_val > U32_MAX) { + dev_err(dev, "Date too far in the future\n"); + return -EINVAL; + } } sun6i_rtc_setaie(0, chip); writel(0, chip->base + SUN6I_ALRM_COUNTER); + if (chip->flags & RTC_LINEAR_DAY) + writel(0, chip->base + SUN6I_ALRM_COUNTER_HMS); usleep_range(100, 300); - writel(time_gap, chip->base + SUN6I_ALRM_COUNTER); + writel(counter_val, chip->base + SUN6I_ALRM_COUNTER); + if (chip->flags & RTC_LINEAR_DAY) + writel(counter_val_hms, chip->base + SUN6I_ALRM_COUNTER_HMS); chip->alarm = time_set; sun6i_rtc_setaie(wkalrm->enabled, chip);