diff options
Diffstat (limited to 'drivers/rtc/rtc-at91rm9200.c')
| -rw-r--r-- | drivers/rtc/rtc-at91rm9200.c | 131 | 
1 files changed, 111 insertions, 20 deletions
diff --git a/drivers/rtc/rtc-at91rm9200.c b/drivers/rtc/rtc-at91rm9200.c index 0eab77b22340..f296f3f7db9b 100644 --- a/drivers/rtc/rtc-at91rm9200.c +++ b/drivers/rtc/rtc-at91rm9200.c @@ -25,6 +25,7 @@  #include <linux/rtc.h>  #include <linux/bcd.h>  #include <linux/interrupt.h> +#include <linux/spinlock.h>  #include <linux/ioctl.h>  #include <linux/completion.h>  #include <linux/io.h> @@ -42,10 +43,65 @@  #define AT91_RTC_EPOCH		1900UL	/* just like arch/arm/common/rtctime.c */ +struct at91_rtc_config { +	bool use_shadow_imr; +}; + +static const struct at91_rtc_config *at91_rtc_config;  static DECLARE_COMPLETION(at91_rtc_updated);  static unsigned int at91_alarm_year = AT91_RTC_EPOCH;  static void __iomem *at91_rtc_regs;  static int irq; +static DEFINE_SPINLOCK(at91_rtc_lock); +static u32 at91_rtc_shadow_imr; + +static void at91_rtc_write_ier(u32 mask) +{ +	unsigned long flags; + +	spin_lock_irqsave(&at91_rtc_lock, flags); +	at91_rtc_shadow_imr |= mask; +	at91_rtc_write(AT91_RTC_IER, mask); +	spin_unlock_irqrestore(&at91_rtc_lock, flags); +} + +static void at91_rtc_write_idr(u32 mask) +{ +	unsigned long flags; + +	spin_lock_irqsave(&at91_rtc_lock, flags); +	at91_rtc_write(AT91_RTC_IDR, mask); +	/* +	 * Register read back (of any RTC-register) needed to make sure +	 * IDR-register write has reached the peripheral before updating +	 * shadow mask. +	 * +	 * Note that there is still a possibility that the mask is updated +	 * before interrupts have actually been disabled in hardware. The only +	 * way to be certain would be to poll the IMR-register, which is is +	 * the very register we are trying to emulate. The register read back +	 * is a reasonable heuristic. +	 */ +	at91_rtc_read(AT91_RTC_SR); +	at91_rtc_shadow_imr &= ~mask; +	spin_unlock_irqrestore(&at91_rtc_lock, flags); +} + +static u32 at91_rtc_read_imr(void) +{ +	unsigned long flags; +	u32 mask; + +	if (at91_rtc_config->use_shadow_imr) { +		spin_lock_irqsave(&at91_rtc_lock, flags); +		mask = at91_rtc_shadow_imr; +		spin_unlock_irqrestore(&at91_rtc_lock, flags); +	} else { +		mask = at91_rtc_read(AT91_RTC_IMR); +	} + +	return mask; +}  /*   * Decode time/date into rtc_time structure @@ -110,9 +166,9 @@ static int at91_rtc_settime(struct device *dev, struct rtc_time *tm)  	cr = at91_rtc_read(AT91_RTC_CR);  	at91_rtc_write(AT91_RTC_CR, cr | AT91_RTC_UPDCAL | AT91_RTC_UPDTIM); -	at91_rtc_write(AT91_RTC_IER, AT91_RTC_ACKUPD); +	at91_rtc_write_ier(AT91_RTC_ACKUPD);  	wait_for_completion(&at91_rtc_updated);	/* wait for ACKUPD interrupt */ -	at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD); +	at91_rtc_write_idr(AT91_RTC_ACKUPD);  	at91_rtc_write(AT91_RTC_TIMR,  			  bin2bcd(tm->tm_sec) << 0 @@ -144,7 +200,7 @@ static int at91_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)  	tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);  	tm->tm_year = at91_alarm_year - 1900; -	alrm->enabled = (at91_rtc_read(AT91_RTC_IMR) & AT91_RTC_ALARM) +	alrm->enabled = (at91_rtc_read_imr() & AT91_RTC_ALARM)  			? 1 : 0;  	dev_dbg(dev, "%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __func__, @@ -169,7 +225,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)  	tm.tm_min = alrm->time.tm_min;  	tm.tm_sec = alrm->time.tm_sec; -	at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM); +	at91_rtc_write_idr(AT91_RTC_ALARM);  	at91_rtc_write(AT91_RTC_TIMALR,  		  bin2bcd(tm.tm_sec) << 0  		| bin2bcd(tm.tm_min) << 8 @@ -182,7 +238,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)  	if (alrm->enabled) {  		at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM); -		at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM); +		at91_rtc_write_ier(AT91_RTC_ALARM);  	}  	dev_dbg(dev, "%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __func__, @@ -198,9 +254,9 @@ static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)  	if (enabled) {  		at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM); -		at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM); +		at91_rtc_write_ier(AT91_RTC_ALARM);  	} else -		at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM); +		at91_rtc_write_idr(AT91_RTC_ALARM);  	return 0;  } @@ -209,7 +265,7 @@ static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)   */  static int at91_rtc_proc(struct device *dev, struct seq_file *seq)  { -	unsigned long imr = at91_rtc_read(AT91_RTC_IMR); +	unsigned long imr = at91_rtc_read_imr();  	seq_printf(seq, "update_IRQ\t: %s\n",  			(imr & AT91_RTC_ACKUPD) ? "yes" : "no"); @@ -229,7 +285,7 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)  	unsigned int rtsr;  	unsigned long events = 0; -	rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read(AT91_RTC_IMR); +	rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read_imr();  	if (rtsr) {		/* this interrupt is shared!  Is it ours? */  		if (rtsr & AT91_RTC_ALARM)  			events |= (RTC_AF | RTC_IRQF); @@ -250,6 +306,43 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)  	return IRQ_NONE;		/* not handled */  } +static const struct at91_rtc_config at91rm9200_config = { +}; + +static const struct at91_rtc_config at91sam9x5_config = { +	.use_shadow_imr	= true, +}; + +#ifdef CONFIG_OF +static const struct of_device_id at91_rtc_dt_ids[] = { +	{ +		.compatible = "atmel,at91rm9200-rtc", +		.data = &at91rm9200_config, +	}, { +		.compatible = "atmel,at91sam9x5-rtc", +		.data = &at91sam9x5_config, +	}, { +		/* sentinel */ +	} +}; +MODULE_DEVICE_TABLE(of, at91_rtc_dt_ids); +#endif + +static const struct at91_rtc_config * +at91_rtc_get_config(struct platform_device *pdev) +{ +	const struct of_device_id *match; + +	if (pdev->dev.of_node) { +		match = of_match_node(at91_rtc_dt_ids, pdev->dev.of_node); +		if (!match) +			return NULL; +		return (const struct at91_rtc_config *)match->data; +	} + +	return &at91rm9200_config; +} +  static const struct rtc_class_ops at91_rtc_ops = {  	.read_time	= at91_rtc_readtime,  	.set_time	= at91_rtc_settime, @@ -268,6 +361,10 @@ static int __init at91_rtc_probe(struct platform_device *pdev)  	struct resource *regs;  	int ret = 0; +	at91_rtc_config = at91_rtc_get_config(pdev); +	if (!at91_rtc_config) +		return -ENODEV; +  	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);  	if (!regs) {  		dev_err(&pdev->dev, "no mmio resource defined\n"); @@ -290,7 +387,7 @@ static int __init at91_rtc_probe(struct platform_device *pdev)  	at91_rtc_write(AT91_RTC_MR, 0);		/* 24 hour mode */  	/* Disable all interrupts */ -	at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM | +	at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |  					AT91_RTC_SECEV | AT91_RTC_TIMEV |  					AT91_RTC_CALEV); @@ -335,7 +432,7 @@ static int __exit at91_rtc_remove(struct platform_device *pdev)  	struct rtc_device *rtc = platform_get_drvdata(pdev);  	/* Disable all interrupts */ -	at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM | +	at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |  					AT91_RTC_SECEV | AT91_RTC_TIMEV |  					AT91_RTC_CALEV);  	free_irq(irq, pdev); @@ -358,13 +455,13 @@ static int at91_rtc_suspend(struct device *dev)  	/* this IRQ is shared with DBGU and other hardware which isn't  	 * necessarily doing PM like we are...  	 */ -	at91_rtc_imr = at91_rtc_read(AT91_RTC_IMR) +	at91_rtc_imr = at91_rtc_read_imr()  			& (AT91_RTC_ALARM|AT91_RTC_SECEV);  	if (at91_rtc_imr) {  		if (device_may_wakeup(dev))  			enable_irq_wake(irq);  		else -			at91_rtc_write(AT91_RTC_IDR, at91_rtc_imr); +			at91_rtc_write_idr(at91_rtc_imr);  	}  	return 0;  } @@ -375,7 +472,7 @@ static int at91_rtc_resume(struct device *dev)  		if (device_may_wakeup(dev))  			disable_irq_wake(irq);  		else -			at91_rtc_write(AT91_RTC_IER, at91_rtc_imr); +			at91_rtc_write_ier(at91_rtc_imr);  	}  	return 0;  } @@ -383,12 +480,6 @@ static int at91_rtc_resume(struct device *dev)  static SIMPLE_DEV_PM_OPS(at91_rtc_pm_ops, at91_rtc_suspend, at91_rtc_resume); -static const struct of_device_id at91_rtc_dt_ids[] = { -	{ .compatible = "atmel,at91rm9200-rtc" }, -	{ /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, at91_rtc_dt_ids); -  static struct platform_driver at91_rtc_driver = {  	.remove		= __exit_p(at91_rtc_remove),  	.driver		= {  | 
