diff mbox series

[4/7] rtc: rzn1: Add alarm support

Message ID 20220405184716.1578385-5-miquel.raynal@bootlin.com (mailing list archive)
State Superseded
Delegated to: Geert Uytterhoeven
Headers show
Series RZN1 RTC support | expand

Commit Message

Miquel Raynal April 5, 2022, 6:47 p.m. UTC
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>
---
 drivers/rtc/rtc-rzn1.c | 108 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 108 insertions(+)

Comments

Alexandre Belloni April 6, 2022, 9:10 a.m. UTC | #1
On 05/04/2022 20:47:13+0200, Miquel Raynal wrote:
> 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>
> ---
>  drivers/rtc/rtc-rzn1.c | 108 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 108 insertions(+)
> 
> diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c
> index 15c533333930..85c5a68944a0 100644
> --- a/drivers/rtc/rtc-rzn1.c
> +++ b/drivers/rtc/rtc-rzn1.c
> @@ -154,14 +154,110 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm)
>  	return ret;
>  }
>  
> +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;
> +	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;
> +	tm->tm_mday += delta_days;
> +	if (delta_days > rtc_month_days(tm->tm_mon, tm->tm_year)) {
> +		tm->tm_mday %= rtc_month_days(tm->tm_mon, tm->tm_year);
> +		tm->tm_mon++;
> +	}
> +	if (tm->tm_mon > 12) {
> +		tm->tm_mon %= 12;
> +		tm->tm_year++;
> +	}

I guess you could avoid having to handle rollover by making the
calculations on a time64_t and then convert back to a tm. I don't think
this would be much worse in terms of processing.

> +
> +	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 -EOPNOTSUPP;
> +

I would 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);
> @@ -178,12 +274,17 @@ 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 dev_err_probe(&pdev->dev, alarm_irq, "Missing timer IRQ\n");
> +
>  	rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev);
>  	if (IS_ERR(rtc->rtcdev))
>  		return PTR_ERR(rtc);
>  
>  	rtc->rtcdev->range_max = 3178591199UL; /* 100 years */
>  	rtc->rtcdev->ops = &rzn1_rtc_ops;
> +	set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features);

You should probably clear RTC_FEATURE_UPDATE_INTERRUPT too.

>  
>  	ret = r9a06g032_sysctrl_enable_rtc(true);
>  	if (ret)
> @@ -206,6 +307,13 @@ static int rzn1_rtc_probe(struct platform_device *pdev)
>  	/* Enable counter operation */
>  	writel(0, rtc->base + RZN1_RTC_CTL2);
>  
> +	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 disable_clk;
> +	}
> +
>  	ret = devm_rtc_register_device(rtc->rtcdev);
>  	if (ret) {
>  		dev_err(&pdev->dev, "Failed to register RTC\n");
> -- 
> 2.27.0
>
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c
index 15c533333930..85c5a68944a0 100644
--- a/drivers/rtc/rtc-rzn1.c
+++ b/drivers/rtc/rtc-rzn1.c
@@ -154,14 +154,110 @@  static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm)
 	return ret;
 }
 
+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;
+	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;
+	tm->tm_mday += delta_days;
+	if (delta_days > rtc_month_days(tm->tm_mon, tm->tm_year)) {
+		tm->tm_mday %= rtc_month_days(tm->tm_mon, tm->tm_year);
+		tm->tm_mon++;
+	}
+	if (tm->tm_mon > 12) {
+		tm->tm_mon %= 12;
+		tm->tm_year++;
+	}
+
+	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 -EOPNOTSUPP;
+
+	/* 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);
@@ -178,12 +274,17 @@  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 dev_err_probe(&pdev->dev, alarm_irq, "Missing timer IRQ\n");
+
 	rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev);
 	if (IS_ERR(rtc->rtcdev))
 		return PTR_ERR(rtc);
 
 	rtc->rtcdev->range_max = 3178591199UL; /* 100 years */
 	rtc->rtcdev->ops = &rzn1_rtc_ops;
+	set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features);
 
 	ret = r9a06g032_sysctrl_enable_rtc(true);
 	if (ret)
@@ -206,6 +307,13 @@  static int rzn1_rtc_probe(struct platform_device *pdev)
 	/* Enable counter operation */
 	writel(0, rtc->base + RZN1_RTC_CTL2);
 
+	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 disable_clk;
+	}
+
 	ret = devm_rtc_register_device(rtc->rtcdev);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to register RTC\n");