2019-05-27 14:55:01 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2006-06-25 20:48:27 +08:00
|
|
|
/*
|
|
|
|
* Real Time Clock interface for Linux on Atmel AT91RM9200
|
|
|
|
*
|
|
|
|
* Copyright (C) 2002 Rick Bronson
|
|
|
|
*
|
|
|
|
* Converted to RTC class model by Andrew Victor
|
|
|
|
*
|
|
|
|
* Ported to Linux 2.6 by Steven Scholz
|
|
|
|
* Based on s3c2410-rtc.c Simtec Electronics
|
|
|
|
*
|
|
|
|
* Based on sa1100-rtc.c by Nils Faerber
|
|
|
|
* Based on rtc.c by Paul Gortmaker
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/bcd.h>
|
2015-07-31 17:39:51 +08:00
|
|
|
#include <linux/clk.h>
|
2015-07-29 08:01:33 +08:00
|
|
|
#include <linux/completion.h>
|
2006-06-25 20:48:27 +08:00
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/ioctl.h>
|
2012-07-04 15:45:16 +08:00
|
|
|
#include <linux/io.h>
|
2015-07-29 08:01:33 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
2013-04-30 07:20:15 +08:00
|
|
|
#include <linux/of_device.h>
|
2015-07-29 08:01:33 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/rtc.h>
|
|
|
|
#include <linux/spinlock.h>
|
2015-03-02 17:18:15 +08:00
|
|
|
#include <linux/suspend.h>
|
2015-07-29 08:01:33 +08:00
|
|
|
#include <linux/time.h>
|
2013-07-04 06:05:44 +08:00
|
|
|
#include <linux/uaccess.h>
|
2008-10-16 13:03:08 +08:00
|
|
|
|
2012-10-30 08:03:39 +08:00
|
|
|
#include "rtc-at91rm9200.h"
|
2007-01-06 08:36:25 +08:00
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
#define at91_rtc_read(field) \
|
2015-04-17 03:49:32 +08:00
|
|
|
readl_relaxed(at91_rtc_regs + field)
|
2011-11-14 14:24:53 +08:00
|
|
|
#define at91_rtc_write(field, val) \
|
2015-04-17 03:49:32 +08:00
|
|
|
writel_relaxed((val), at91_rtc_regs + field)
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-06-13 05:04:53 +08:00
|
|
|
struct at91_rtc_config {
|
2013-06-13 05:04:56 +08:00
|
|
|
bool use_shadow_imr;
|
2013-06-13 05:04:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct at91_rtc_config *at91_rtc_config;
|
2006-06-25 20:48:27 +08:00
|
|
|
static DECLARE_COMPLETION(at91_rtc_updated);
|
2014-06-07 05:36:09 +08:00
|
|
|
static DECLARE_COMPLETION(at91_rtc_upd_rdy);
|
2011-11-14 14:24:53 +08:00
|
|
|
static void __iomem *at91_rtc_regs;
|
|
|
|
static int irq;
|
2013-06-13 05:04:56 +08:00
|
|
|
static DEFINE_SPINLOCK(at91_rtc_lock);
|
|
|
|
static u32 at91_rtc_shadow_imr;
|
2015-03-02 17:18:15 +08:00
|
|
|
static bool suspended;
|
|
|
|
static DEFINE_SPINLOCK(suspended_lock);
|
|
|
|
static unsigned long cached_events;
|
|
|
|
static u32 at91_rtc_imr;
|
2015-07-31 17:39:51 +08:00
|
|
|
static struct clk *sclk;
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-06-13 05:04:55 +08:00
|
|
|
static void at91_rtc_write_ier(u32 mask)
|
|
|
|
{
|
2013-06-13 05:04:56 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&at91_rtc_lock, flags);
|
|
|
|
at91_rtc_shadow_imr |= mask;
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write(AT91_RTC_IER, mask);
|
2013-06-13 05:04:56 +08:00
|
|
|
spin_unlock_irqrestore(&at91_rtc_lock, flags);
|
2013-06-13 05:04:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void at91_rtc_write_idr(u32 mask)
|
|
|
|
{
|
2013-06-13 05:04:56 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&at91_rtc_lock, flags);
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write(AT91_RTC_IDR, mask);
|
2013-06-13 05:04:56 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
2013-06-13 05:04:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static u32 at91_rtc_read_imr(void)
|
|
|
|
{
|
2013-06-13 05:04:56 +08:00
|
|
|
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;
|
2013-06-13 05:04:55 +08:00
|
|
|
}
|
|
|
|
|
2006-06-25 20:48:27 +08:00
|
|
|
/*
|
|
|
|
* Decode time/date into rtc_time structure
|
|
|
|
*/
|
2006-06-25 20:48:27 +08:00
|
|
|
static void at91_rtc_decodetime(unsigned int timereg, unsigned int calreg,
|
|
|
|
struct rtc_time *tm)
|
2006-06-25 20:48:27 +08:00
|
|
|
{
|
|
|
|
unsigned int time, date;
|
|
|
|
|
|
|
|
/* must read twice in case it changes */
|
|
|
|
do {
|
2011-11-14 14:24:53 +08:00
|
|
|
time = at91_rtc_read(timereg);
|
|
|
|
date = at91_rtc_read(calreg);
|
|
|
|
} while ((time != at91_rtc_read(timereg)) ||
|
|
|
|
(date != at91_rtc_read(calreg)));
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2008-10-19 11:28:41 +08:00
|
|
|
tm->tm_sec = bcd2bin((time & AT91_RTC_SEC) >> 0);
|
|
|
|
tm->tm_min = bcd2bin((time & AT91_RTC_MIN) >> 8);
|
|
|
|
tm->tm_hour = bcd2bin((time & AT91_RTC_HOUR) >> 16);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The Calendar Alarm register does not have a field for
|
2017-11-10 16:59:31 +08:00
|
|
|
* the year - so these will return an invalid value.
|
2006-06-25 20:48:27 +08:00
|
|
|
*/
|
2008-10-19 11:28:41 +08:00
|
|
|
tm->tm_year = bcd2bin(date & AT91_RTC_CENT) * 100; /* century */
|
|
|
|
tm->tm_year += bcd2bin((date & AT91_RTC_YEAR) >> 8); /* year */
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2008-10-19 11:28:41 +08:00
|
|
|
tm->tm_wday = bcd2bin((date & AT91_RTC_DAY) >> 21) - 1; /* day of the week [0-6], Sunday=0 */
|
|
|
|
tm->tm_mon = bcd2bin((date & AT91_RTC_MONTH) >> 16) - 1;
|
|
|
|
tm->tm_mday = bcd2bin((date & AT91_RTC_DATE) >> 24);
|
2006-06-25 20:48:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read current time and date in RTC
|
|
|
|
*/
|
|
|
|
static int at91_rtc_readtime(struct device *dev, struct rtc_time *tm)
|
|
|
|
{
|
|
|
|
at91_rtc_decodetime(AT91_RTC_TIMR, AT91_RTC_CALR, tm);
|
|
|
|
tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
|
|
|
|
tm->tm_year = tm->tm_year - 1900;
|
|
|
|
|
2018-12-05 05:23:13 +08:00
|
|
|
dev_dbg(dev, "%s(): %ptR\n", __func__, tm);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set current time and date in RTC
|
|
|
|
*/
|
|
|
|
static int at91_rtc_settime(struct device *dev, struct rtc_time *tm)
|
|
|
|
{
|
|
|
|
unsigned long cr;
|
|
|
|
|
2018-12-05 05:23:13 +08:00
|
|
|
dev_dbg(dev, "%s(): %ptR\n", __func__, tm);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2014-06-07 05:36:09 +08:00
|
|
|
wait_for_completion(&at91_rtc_upd_rdy);
|
|
|
|
|
2006-06-25 20:48:27 +08:00
|
|
|
/* Stop Time/Calendar from counting */
|
2011-11-14 14:24:53 +08:00
|
|
|
cr = at91_rtc_read(AT91_RTC_CR);
|
|
|
|
at91_rtc_write(AT91_RTC_CR, cr | AT91_RTC_UPDCAL | AT91_RTC_UPDTIM);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_ier(AT91_RTC_ACKUPD);
|
2006-06-25 20:48:27 +08:00
|
|
|
wait_for_completion(&at91_rtc_updated); /* wait for ACKUPD interrupt */
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(AT91_RTC_ACKUPD);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_TIMR,
|
2008-10-19 11:28:41 +08:00
|
|
|
bin2bcd(tm->tm_sec) << 0
|
|
|
|
| bin2bcd(tm->tm_min) << 8
|
|
|
|
| bin2bcd(tm->tm_hour) << 16);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_CALR,
|
2008-10-19 11:28:41 +08:00
|
|
|
bin2bcd((tm->tm_year + 1900) / 100) /* century */
|
|
|
|
| bin2bcd(tm->tm_year % 100) << 8 /* year */
|
|
|
|
| bin2bcd(tm->tm_mon + 1) << 16 /* tm_mon starts at zero */
|
|
|
|
| bin2bcd(tm->tm_wday + 1) << 21 /* day of the week [0-6], Sunday=0 */
|
|
|
|
| bin2bcd(tm->tm_mday) << 24);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
/* Restart Time/Calendar */
|
2011-11-14 14:24:53 +08:00
|
|
|
cr = at91_rtc_read(AT91_RTC_CR);
|
2014-06-07 05:36:09 +08:00
|
|
|
at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_SECEV);
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_CR, cr & ~(AT91_RTC_UPDCAL | AT91_RTC_UPDTIM));
|
2014-06-07 05:36:09 +08:00
|
|
|
at91_rtc_write_ier(AT91_RTC_SECEV);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read alarm time and date in RTC
|
|
|
|
*/
|
|
|
|
static int at91_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
|
|
{
|
|
|
|
struct rtc_time *tm = &alrm->time;
|
|
|
|
|
|
|
|
at91_rtc_decodetime(AT91_RTC_TIMALR, AT91_RTC_CALALR, tm);
|
2017-11-10 16:59:31 +08:00
|
|
|
tm->tm_year = -1;
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-06-13 05:04:55 +08:00
|
|
|
alrm->enabled = (at91_rtc_read_imr() & AT91_RTC_ALARM)
|
2006-12-13 16:35:08 +08:00
|
|
|
? 1 : 0;
|
|
|
|
|
2018-12-05 05:23:13 +08:00
|
|
|
dev_dbg(dev, "%s(): %ptR %sabled\n", __func__, tm,
|
2017-11-10 16:59:31 +08:00
|
|
|
alrm->enabled ? "en" : "dis");
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set alarm time and date in RTC
|
|
|
|
*/
|
|
|
|
static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
|
|
{
|
|
|
|
struct rtc_time tm;
|
|
|
|
|
|
|
|
at91_rtc_decodetime(AT91_RTC_TIMR, AT91_RTC_CALR, &tm);
|
|
|
|
|
2013-12-13 09:12:23 +08:00
|
|
|
tm.tm_mon = alrm->time.tm_mon;
|
|
|
|
tm.tm_mday = alrm->time.tm_mday;
|
2006-06-25 20:48:27 +08:00
|
|
|
tm.tm_hour = alrm->time.tm_hour;
|
|
|
|
tm.tm_min = alrm->time.tm_min;
|
|
|
|
tm.tm_sec = alrm->time.tm_sec;
|
|
|
|
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(AT91_RTC_ALARM);
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_TIMALR,
|
2008-10-19 11:28:41 +08:00
|
|
|
bin2bcd(tm.tm_sec) << 0
|
|
|
|
| bin2bcd(tm.tm_min) << 8
|
|
|
|
| bin2bcd(tm.tm_hour) << 16
|
2006-06-25 20:48:27 +08:00
|
|
|
| AT91_RTC_HOUREN | AT91_RTC_MINEN | AT91_RTC_SECEN);
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_CALALR,
|
2008-10-19 11:28:41 +08:00
|
|
|
bin2bcd(tm.tm_mon + 1) << 16 /* tm_mon starts at zero */
|
|
|
|
| bin2bcd(tm.tm_mday) << 24
|
2006-06-25 20:48:27 +08:00
|
|
|
| AT91_RTC_DATEEN | AT91_RTC_MTHEN);
|
|
|
|
|
2008-07-24 12:30:46 +08:00
|
|
|
if (alrm->enabled) {
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM);
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_ier(AT91_RTC_ALARM);
|
2008-07-24 12:30:46 +08:00
|
|
|
}
|
2007-02-21 05:58:14 +08:00
|
|
|
|
2018-12-05 05:23:13 +08:00
|
|
|
dev_dbg(dev, "%s(): %ptR\n", __func__, &tm);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-02-03 09:02:41 +08:00
|
|
|
static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
|
|
|
{
|
2013-02-22 08:45:28 +08:00
|
|
|
dev_dbg(dev, "%s(): cmd=%08x\n", __func__, enabled);
|
2011-02-03 09:02:41 +08:00
|
|
|
|
|
|
|
if (enabled) {
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM);
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_ier(AT91_RTC_ALARM);
|
2013-04-06 00:16:34 +08:00
|
|
|
} else
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(AT91_RTC_ALARM);
|
2011-02-03 09:02:41 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2006-06-25 20:48:27 +08:00
|
|
|
/*
|
|
|
|
* Provide additional RTC information in /proc/driver/rtc
|
|
|
|
*/
|
|
|
|
static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
|
|
|
|
{
|
2013-06-13 05:04:55 +08:00
|
|
|
unsigned long imr = at91_rtc_read_imr();
|
2013-04-06 00:16:34 +08:00
|
|
|
|
2006-06-25 20:48:27 +08:00
|
|
|
seq_printf(seq, "update_IRQ\t: %s\n",
|
2013-04-06 00:16:34 +08:00
|
|
|
(imr & AT91_RTC_ACKUPD) ? "yes" : "no");
|
2006-06-25 20:48:27 +08:00
|
|
|
seq_printf(seq, "periodic_IRQ\t: %s\n",
|
2013-04-06 00:16:34 +08:00
|
|
|
(imr & AT91_RTC_SECEV) ? "yes" : "no");
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* IRQ handler for the RTC
|
|
|
|
*/
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
|
2006-06-25 20:48:27 +08:00
|
|
|
{
|
2006-06-25 20:48:27 +08:00
|
|
|
struct platform_device *pdev = dev_id;
|
2006-06-25 20:48:27 +08:00
|
|
|
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
|
|
|
unsigned int rtsr;
|
|
|
|
unsigned long events = 0;
|
2015-03-02 17:18:15 +08:00
|
|
|
int ret = IRQ_NONE;
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2015-03-02 17:18:15 +08:00
|
|
|
spin_lock(&suspended_lock);
|
2013-06-13 05:04:55 +08:00
|
|
|
rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read_imr();
|
2006-06-25 20:48:27 +08:00
|
|
|
if (rtsr) { /* this interrupt is shared! Is it ours? */
|
|
|
|
if (rtsr & AT91_RTC_ALARM)
|
|
|
|
events |= (RTC_AF | RTC_IRQF);
|
2014-06-07 05:36:09 +08:00
|
|
|
if (rtsr & AT91_RTC_SECEV) {
|
|
|
|
complete(&at91_rtc_upd_rdy);
|
|
|
|
at91_rtc_write_idr(AT91_RTC_SECEV);
|
|
|
|
}
|
2006-06-25 20:48:27 +08:00
|
|
|
if (rtsr & AT91_RTC_ACKUPD)
|
|
|
|
complete(&at91_rtc_updated);
|
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_SCCR, rtsr); /* clear status reg */
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2015-03-02 17:18:15 +08:00
|
|
|
if (!suspended) {
|
|
|
|
rtc_update_irq(rtc, 1, events);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2015-03-02 17:18:15 +08:00
|
|
|
dev_dbg(&pdev->dev, "%s(): num=%ld, events=0x%02lx\n",
|
|
|
|
__func__, events >> 8, events & 0x000000FF);
|
|
|
|
} else {
|
|
|
|
cached_events |= events;
|
|
|
|
at91_rtc_write_idr(at91_rtc_imr);
|
|
|
|
pm_system_wakeup();
|
|
|
|
}
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2015-03-02 17:18:15 +08:00
|
|
|
ret = IRQ_HANDLED;
|
2006-06-25 20:48:27 +08:00
|
|
|
}
|
2015-03-17 23:38:10 +08:00
|
|
|
spin_unlock(&suspended_lock);
|
2015-03-02 17:18:15 +08:00
|
|
|
|
|
|
|
return ret;
|
2006-06-25 20:48:27 +08:00
|
|
|
}
|
|
|
|
|
2013-06-13 05:04:53 +08:00
|
|
|
static const struct at91_rtc_config at91rm9200_config = {
|
|
|
|
};
|
|
|
|
|
2013-06-13 05:04:57 +08:00
|
|
|
static const struct at91_rtc_config at91sam9x5_config = {
|
|
|
|
.use_shadow_imr = true,
|
|
|
|
};
|
|
|
|
|
2013-06-13 05:04:53 +08:00
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static const struct of_device_id at91_rtc_dt_ids[] = {
|
|
|
|
{
|
|
|
|
.compatible = "atmel,at91rm9200-rtc",
|
|
|
|
.data = &at91rm9200_config,
|
2013-06-13 05:04:57 +08:00
|
|
|
}, {
|
|
|
|
.compatible = "atmel,at91sam9x5-rtc",
|
|
|
|
.data = &at91sam9x5_config,
|
2013-06-13 05:04:53 +08:00
|
|
|
}, {
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2006-10-01 14:28:17 +08:00
|
|
|
static const struct rtc_class_ops at91_rtc_ops = {
|
2006-06-25 20:48:27 +08:00
|
|
|
.read_time = at91_rtc_readtime,
|
|
|
|
.set_time = at91_rtc_settime,
|
|
|
|
.read_alarm = at91_rtc_readalarm,
|
|
|
|
.set_alarm = at91_rtc_setalarm,
|
|
|
|
.proc = at91_rtc_proc,
|
2011-02-03 09:02:41 +08:00
|
|
|
.alarm_irq_enable = at91_rtc_alarm_irq_enable,
|
2006-06-25 20:48:27 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize and install RTC driver
|
|
|
|
*/
|
|
|
|
static int __init at91_rtc_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct rtc_device *rtc;
|
2011-11-14 14:24:53 +08:00
|
|
|
struct resource *regs;
|
|
|
|
int ret = 0;
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-06-13 05:04:53 +08:00
|
|
|
at91_rtc_config = at91_rtc_get_config(pdev);
|
|
|
|
if (!at91_rtc_config)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (!regs) {
|
|
|
|
dev_err(&pdev->dev, "no mmio resource defined\n");
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
2019-07-31 02:15:39 +08:00
|
|
|
if (irq < 0)
|
2011-11-14 14:24:53 +08:00
|
|
|
return -ENXIO;
|
|
|
|
|
2013-11-13 07:10:29 +08:00
|
|
|
at91_rtc_regs = devm_ioremap(&pdev->dev, regs->start,
|
|
|
|
resource_size(regs));
|
2011-11-14 14:24:53 +08:00
|
|
|
if (!at91_rtc_regs) {
|
|
|
|
dev_err(&pdev->dev, "failed to map registers, aborting.\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2017-07-06 17:42:01 +08:00
|
|
|
rtc = devm_rtc_allocate_device(&pdev->dev);
|
|
|
|
if (IS_ERR(rtc))
|
|
|
|
return PTR_ERR(rtc);
|
|
|
|
platform_set_drvdata(pdev, rtc);
|
|
|
|
|
2015-07-31 17:39:51 +08:00
|
|
|
sclk = devm_clk_get(&pdev->dev, NULL);
|
|
|
|
if (IS_ERR(sclk))
|
|
|
|
return PTR_ERR(sclk);
|
|
|
|
|
|
|
|
ret = clk_prepare_enable(sclk);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev, "Could not enable slow clock\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
at91_rtc_write(AT91_RTC_CR, 0);
|
|
|
|
at91_rtc_write(AT91_RTC_MR, 0); /* 24 hour mode */
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
/* Disable all interrupts */
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |
|
2006-06-25 20:48:27 +08:00
|
|
|
AT91_RTC_SECEV | AT91_RTC_TIMEV |
|
|
|
|
AT91_RTC_CALEV);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2013-11-13 07:10:29 +08:00
|
|
|
ret = devm_request_irq(&pdev->dev, irq, at91_rtc_interrupt,
|
2015-03-02 17:18:15 +08:00
|
|
|
IRQF_SHARED | IRQF_COND_SUSPEND,
|
|
|
|
"at91_rtc", pdev);
|
2006-06-25 20:48:27 +08:00
|
|
|
if (ret) {
|
2013-02-22 08:45:28 +08:00
|
|
|
dev_err(&pdev->dev, "IRQ %d already in use.\n", irq);
|
2015-07-31 17:39:51 +08:00
|
|
|
goto err_clk;
|
2006-06-25 20:48:27 +08:00
|
|
|
}
|
|
|
|
|
2007-02-21 05:58:14 +08:00
|
|
|
/* cpu init code should really have flagged this device as
|
|
|
|
* being wake-capable; if it didn't, do that here.
|
|
|
|
*/
|
|
|
|
if (!device_can_wakeup(&pdev->dev))
|
|
|
|
device_init_wakeup(&pdev->dev, 1);
|
|
|
|
|
2017-07-06 17:42:01 +08:00
|
|
|
rtc->ops = &at91_rtc_ops;
|
2018-05-18 04:17:28 +08:00
|
|
|
rtc->range_min = RTC_TIMESTAMP_BEGIN_1900;
|
|
|
|
rtc->range_max = RTC_TIMESTAMP_END_2099;
|
2017-07-06 17:42:01 +08:00
|
|
|
ret = rtc_register_device(rtc);
|
|
|
|
if (ret)
|
2015-07-31 17:39:51 +08:00
|
|
|
goto err_clk;
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2014-06-07 05:36:09 +08:00
|
|
|
/* enable SECEV interrupt in order to initialize at91_rtc_upd_rdy
|
|
|
|
* completion.
|
|
|
|
*/
|
|
|
|
at91_rtc_write_ier(AT91_RTC_SECEV);
|
|
|
|
|
2013-02-22 08:45:28 +08:00
|
|
|
dev_info(&pdev->dev, "AT91 Real Time Clock driver.\n");
|
2006-06-25 20:48:27 +08:00
|
|
|
return 0;
|
2015-07-31 17:39:51 +08:00
|
|
|
|
|
|
|
err_clk:
|
|
|
|
clk_disable_unprepare(sclk);
|
|
|
|
|
|
|
|
return ret;
|
2006-06-25 20:48:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disable and remove the RTC driver
|
|
|
|
*/
|
2007-02-21 05:58:14 +08:00
|
|
|
static int __exit at91_rtc_remove(struct platform_device *pdev)
|
2006-06-25 20:48:27 +08:00
|
|
|
{
|
|
|
|
/* Disable all interrupts */
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |
|
2006-06-25 20:48:27 +08:00
|
|
|
AT91_RTC_SECEV | AT91_RTC_TIMEV |
|
|
|
|
AT91_RTC_CALEV);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
2015-07-31 17:39:51 +08:00
|
|
|
clk_disable_unprepare(sclk);
|
|
|
|
|
2006-06-25 20:48:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-11-22 06:32:04 +08:00
|
|
|
static void at91_rtc_shutdown(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
/* Disable all interrupts */
|
|
|
|
at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM |
|
|
|
|
AT91_RTC_SECEV | AT91_RTC_TIMEV |
|
|
|
|
AT91_RTC_CALEV);
|
|
|
|
}
|
|
|
|
|
2013-04-30 07:19:56 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
/* AT91RM9200 RTC Power management control */
|
|
|
|
|
2009-09-23 07:46:31 +08:00
|
|
|
static int at91_rtc_suspend(struct device *dev)
|
2006-06-25 20:48:27 +08:00
|
|
|
{
|
2006-10-01 14:28:17 +08:00
|
|
|
/* this IRQ is shared with DBGU and other hardware which isn't
|
|
|
|
* necessarily doing PM like we are...
|
|
|
|
*/
|
2015-10-12 16:39:23 +08:00
|
|
|
at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM);
|
|
|
|
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_imr = at91_rtc_read_imr()
|
2013-04-06 00:16:34 +08:00
|
|
|
& (AT91_RTC_ALARM|AT91_RTC_SECEV);
|
|
|
|
if (at91_rtc_imr) {
|
2015-03-02 17:18:15 +08:00
|
|
|
if (device_may_wakeup(dev)) {
|
|
|
|
unsigned long flags;
|
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
enable_irq_wake(irq);
|
2015-03-02 17:18:15 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&suspended_lock, flags);
|
|
|
|
suspended = true;
|
|
|
|
spin_unlock_irqrestore(&suspended_lock, flags);
|
|
|
|
} else {
|
2013-06-13 05:04:55 +08:00
|
|
|
at91_rtc_write_idr(at91_rtc_imr);
|
2015-03-02 17:18:15 +08:00
|
|
|
}
|
2013-04-06 00:16:34 +08:00
|
|
|
}
|
2006-06-25 20:48:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-09-23 07:46:31 +08:00
|
|
|
static int at91_rtc_resume(struct device *dev)
|
2006-06-25 20:48:27 +08:00
|
|
|
{
|
2015-03-02 17:18:15 +08:00
|
|
|
struct rtc_device *rtc = dev_get_drvdata(dev);
|
|
|
|
|
2013-04-06 00:16:34 +08:00
|
|
|
if (at91_rtc_imr) {
|
2015-03-02 17:18:15 +08:00
|
|
|
if (device_may_wakeup(dev)) {
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&suspended_lock, flags);
|
|
|
|
|
|
|
|
if (cached_events) {
|
|
|
|
rtc_update_irq(rtc, 1, cached_events);
|
|
|
|
cached_events = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
suspended = false;
|
|
|
|
spin_unlock_irqrestore(&suspended_lock, flags);
|
|
|
|
|
2011-11-14 14:24:53 +08:00
|
|
|
disable_irq_wake(irq);
|
2015-03-02 17:18:15 +08:00
|
|
|
}
|
|
|
|
at91_rtc_write_ier(at91_rtc_imr);
|
2006-10-01 14:28:17 +08:00
|
|
|
}
|
2006-06-25 20:48:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-04-30 07:19:56 +08:00
|
|
|
static SIMPLE_DEV_PM_OPS(at91_rtc_pm_ops, at91_rtc_suspend, at91_rtc_resume);
|
|
|
|
|
2006-06-25 20:48:27 +08:00
|
|
|
static struct platform_driver at91_rtc_driver = {
|
2007-02-21 05:58:14 +08:00
|
|
|
.remove = __exit_p(at91_rtc_remove),
|
2013-11-22 06:32:04 +08:00
|
|
|
.shutdown = at91_rtc_shutdown,
|
2006-06-25 20:48:27 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "at91_rtc",
|
2013-04-30 07:19:56 +08:00
|
|
|
.pm = &at91_rtc_pm_ops,
|
2013-04-30 07:20:15 +08:00
|
|
|
.of_match_table = of_match_ptr(at91_rtc_dt_ids),
|
2006-06-25 20:48:27 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2013-04-30 07:18:36 +08:00
|
|
|
module_platform_driver_probe(at91_rtc_driver, at91_rtc_probe);
|
2006-06-25 20:48:27 +08:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Rick Bronson");
|
|
|
|
MODULE_DESCRIPTION("RTC driver for Atmel AT91RM9200");
|
|
|
|
MODULE_LICENSE("GPL");
|
2008-04-11 12:29:25 +08:00
|
|
|
MODULE_ALIAS("platform:at91_rtc");
|