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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxx>
> ---
>  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
> 

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux