diff options
| author | Miquel Raynal <miquel.raynal@bootlin.com> | 2022-05-16 10:25:02 +0200 | 
|---|---|---|
| committer | Alexandre Belloni <alexandre.belloni@bootlin.com> | 2022-05-17 23:11:44 +0200 | 
| commit | b5ad1bf00d2c4bf96bf9318f44a929f0b22dd29c (patch) | |
| tree | 3525b693918ead8e79139600b60fdcb7cc73be1b | |
| parent | deeb4b5393e106b990607df06261fba0ebb7ebde (diff) | |
rtc: rzn1: Add alarm support
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
| -rw-r--r-- | drivers/rtc/rtc-rzn1.c | 106 | 
1 files changed, 105 insertions, 1 deletions
| diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 6b2139a09ae2..c1b082e3c8a7 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -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; | 
