rtc: rzn1: Add alarm support
authorMiquel Raynal <miquel.raynal@bootlin.com>
Mon, 16 May 2022 08:25:02 +0000 (10:25 +0200)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Tue, 17 May 2022 21:11:44 +0000 (23:11 +0200)
The RZN1 RTC can trigger an interrupt when reaching a particular date up
to 7 days ahead. Bring support for this alarm.

One drawback though, the granularity is about a minute.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Link: https://lore.kernel.org/r/20220516082504.33913-4-miquel.raynal@bootlin.com
drivers/rtc/rtc-rzn1.c

index 6b2139a..c1b082e 100644 (file)
@@ -156,14 +156,107 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm)
        return 0;
 }
 
+static irqreturn_t rzn1_rtc_alarm_irq(int irq, void *dev_id)
+{
+       struct rzn1_rtc *rtc = dev_id;
+
+       rtc_update_irq(rtc->rtcdev, 1, RTC_AF | RTC_IRQF);
+
+       return IRQ_HANDLED;
+}
+
+static int rzn1_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       u32 ctl1 = readl(rtc->base + RZN1_RTC_CTL1);
+
+       if (enable)
+               ctl1 |= RZN1_RTC_CTL1_ALME;
+       else
+               ctl1 &= ~RZN1_RTC_CTL1_ALME;
+
+       writel(ctl1, rtc->base + RZN1_RTC_CTL1);
+
+       return 0;
+}
+
+static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       struct rtc_time *tm = &alrm->time;
+       unsigned int min, hour, wday, delta_days;
+       time64_t alarm;
+       u32 ctl1;
+       int ret;
+
+       ret = rzn1_rtc_read_time(dev, tm);
+       if (ret)
+               return ret;
+
+       min = readl(rtc->base + RZN1_RTC_ALM);
+       hour = readl(rtc->base + RZN1_RTC_ALH);
+       wday = readl(rtc->base + RZN1_RTC_ALW);
+
+       tm->tm_sec = 0;
+       tm->tm_min = bcd2bin(min);
+       tm->tm_hour = bcd2bin(hour);
+       delta_days = ((fls(wday) - 1) - tm->tm_wday + 7) % 7;
+       tm->tm_wday = fls(wday) - 1;
+
+       if (delta_days) {
+               alarm = rtc_tm_to_time64(tm) + (delta_days * 86400);
+               rtc_time64_to_tm(alarm, tm);
+       }
+
+       ctl1 = readl(rtc->base + RZN1_RTC_CTL1);
+       alrm->enabled = !!(ctl1 & RZN1_RTC_CTL1_ALME);
+
+       return 0;
+}
+
+static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+       struct rzn1_rtc *rtc = dev_get_drvdata(dev);
+       struct rtc_time *tm = &alrm->time, tm_now;
+       unsigned long alarm, farest;
+       unsigned int days_ahead, wday;
+       int ret;
+
+       ret = rzn1_rtc_read_time(dev, &tm_now);
+       if (ret)
+               return ret;
+
+       /* We cannot set alarms more than one week ahead */
+       farest = rtc_tm_to_time64(&tm_now) + (7 * 86400);
+       alarm = rtc_tm_to_time64(tm);
+       if (time_after(alarm, farest))
+               return -ERANGE;
+
+       /* Convert alarm day into week day */
+       days_ahead = tm->tm_mday - tm_now.tm_mday;
+       wday = (tm_now.tm_wday + days_ahead) % 7;
+
+       writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM);
+       writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH);
+       writel(BIT(wday), rtc->base + RZN1_RTC_ALW);
+
+       rzn1_rtc_alarm_irq_enable(dev, alrm->enabled);
+
+       return 0;
+}
+
 static const struct rtc_class_ops rzn1_rtc_ops = {
        .read_time = rzn1_rtc_read_time,
        .set_time = rzn1_rtc_set_time,
+       .read_alarm = rzn1_rtc_read_alarm,
+       .set_alarm = rzn1_rtc_set_alarm,
+       .alarm_irq_enable = rzn1_rtc_alarm_irq_enable,
 };
 
 static int rzn1_rtc_probe(struct platform_device *pdev)
 {
        struct rzn1_rtc *rtc;
+       int alarm_irq;
        int ret;
 
        rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
@@ -176,6 +269,10 @@ static int rzn1_rtc_probe(struct platform_device *pdev)
        if (IS_ERR(rtc->base))
                return dev_err_probe(&pdev->dev, PTR_ERR(rtc->base), "Missing reg\n");
 
+       alarm_irq = platform_get_irq(pdev, 0);
+       if (alarm_irq < 0)
+               return alarm_irq;
+
        rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev);
        if (IS_ERR(rtc->rtcdev))
                return PTR_ERR(rtc);
@@ -183,7 +280,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev)
        rtc->rtcdev->range_min = RTC_TIMESTAMP_BEGIN_2000;
        rtc->rtcdev->range_max = RTC_TIMESTAMP_END_2099;
        rtc->rtcdev->ops = &rzn1_rtc_ops;
-       clear_bit(RTC_FEATURE_ALARM, rtc->rtcdev->features);
+       set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features);
        clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features);
 
        devm_pm_runtime_enable(&pdev->dev);
@@ -201,6 +298,13 @@ static int rzn1_rtc_probe(struct platform_device *pdev)
        /* Disable all interrupts */
        writel(0, rtc->base + RZN1_RTC_CTL1);
 
+       ret = devm_request_irq(&pdev->dev, alarm_irq, rzn1_rtc_alarm_irq, 0,
+                              dev_name(&pdev->dev), rtc);
+       if (ret) {
+               dev_err(&pdev->dev, "RTC timer interrupt not available\n");
+               goto dis_runtime_pm;
+       }
+
        ret = devm_rtc_register_device(rtc->rtcdev);
        if (ret)
                goto dis_runtime_pm;