aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/rtc
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/rtc
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/rtc')
-rw-r--r--drivers/rtc/Kconfig71
-rw-r--r--drivers/rtc/Makefile5
-rw-r--r--drivers/rtc/alarm-dev.c298
-rw-r--r--drivers/rtc/alarm.c638
-rw-r--r--drivers/rtc/class.c69
-rw-r--r--drivers/rtc/interface.c22
-rw-r--r--drivers/rtc/rtc-lib.c2
-rw-r--r--drivers/rtc/rtc-max77686.c909
-rw-r--r--drivers/rtc/rtc-max8997.c767
-rw-r--r--drivers/rtc/rtc-s3c.c307
-rw-r--r--drivers/rtc/rtc-s5m.c706
11 files changed, 3580 insertions, 214 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index ce2aabf..fdfd85d 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -12,7 +12,7 @@ menuconfig RTC_CLASS
select RTC_LIB
help
Generic RTC class support. If you say yes here, you will
- be allowed to plug one or more RTCs to your system. You will
+ be allowed to plug one or more RTCs to your system. You will
probably want to enable one or more of the interfaces below.
if RTC_CLASS
@@ -106,6 +106,33 @@ config RTC_INTF_DEV_UIE_EMUL
clock several times per second, please enable this option
only if you know that you really need it.
+config RTC_INTF_ALARM
+ bool "Android alarm driver"
+ depends on RTC_CLASS
+ default y
+ help
+ Provides non-wakeup and rtc backed wakeup alarms based on rtc or
+ elapsed realtime, and a non-wakeup alarm on the monotonic clock.
+ Also provides an interface to set the wall time which must be used
+ for elapsed realtime to work.
+
+config RTC_INTF_ALARM_DEV
+ bool "Android alarm device"
+ depends on RTC_INTF_ALARM
+ default y
+ help
+ Exports the alarm interface to user-space.
+
+config RTC_ALARM_BOOT
+ bool "Android alarm boot function"
+ depends on RTC_INTF_ALARM
+ default n
+ help
+ This is for alarm booting function. This feature is only used in
+ Chinese models. When the handset power off after setting alarm
+ time with checking auto power up menu, the handset must be powered
+ on in time automatically.
+
config RTC_DRV_TEST
tristate "Test driver/device"
help
@@ -211,8 +238,38 @@ config RTC_DRV_MAX8998
RTC of Maxim MAX8998 PMIC.
This driver can also be built as a module. If so, the module
+
+config RTC_DRV_MAX8997
+ tristate "Maxim MAX8997"
+ depends on MFD_MAX8997
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX8997 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max8997.
+
+config RTC_DRV_MAX77686
+ tristate "Maxim MAX77686"
+ depends on MFD_MAX77686
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX77686 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max77686.
will be called rtc-max8998.
+config RTC_DRV_MAX77686
+ tristate "Maxim MAX77686"
+ depends on MFD_MAX77686
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX77686 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max77686.
+
config RTC_DRV_RS5C372
tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A"
help
@@ -379,6 +436,16 @@ config RTC_DRV_RV3029C2
This driver can also be built as a module. If so, the module
will be called rtc-rv3029c2.
+config RTC_DRV_S5M
+ tristate "Samsung S5M series"
+ depends on MFD_S5M_CORE
+ help
+ If you say yes here you will get support for the
+ RTC of Samsung S5M series.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-s5m.
+
endif # I2C
comment "SPI RTC drivers"
@@ -789,7 +856,7 @@ config RTC_DRV_SH
Say Y here to enable support for the on-chip RTC found in
most SuperH processors.
- To compile this driver as a module, choose M here: the
+ To compile this driver as a module, choose M here: the
module will be called rtc-sh.
config RTC_DRV_VR41XX
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 0ffefe8..671ef24 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -9,6 +9,8 @@ obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o
obj-$(CONFIG_RTC_CLASS) += rtc-core.o
rtc-core-y := class.o interface.o
+obj-$(CONFIG_RTC_INTF_ALARM) += alarm.o
+obj-$(CONFIG_RTC_INTF_ALARM_DEV) += alarm-dev.o
rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
@@ -63,6 +65,8 @@ obj-$(CONFIG_RTC_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
+obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o
+obj-$(CONFIG_RTC_DRV_MAX77686) += rtc-max77686.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o
obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o
@@ -89,6 +93,7 @@ obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o
obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o
obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o
obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o
+obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o
obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o
diff --git a/drivers/rtc/alarm-dev.c b/drivers/rtc/alarm-dev.c
new file mode 100644
index 0000000..78d80d7
--- /dev/null
+++ b/drivers/rtc/alarm-dev.c
@@ -0,0 +1,298 @@
+/* drivers/rtc/alarm-dev.c
+ *
+ * Copyright (C) 2007-2009 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <asm/mach/time.h>
+#include <linux/android_alarm.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/sysdev.h>
+#include <linux/uaccess.h>
+#include <linux/wakelock.h>
+
+#define ANDROID_ALARM_PRINT_INFO (1U << 0)
+#define ANDROID_ALARM_PRINT_IO (1U << 1)
+#define ANDROID_ALARM_PRINT_INT (1U << 2)
+
+static int debug_mask = ANDROID_ALARM_PRINT_INFO;
+module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define pr_alarm(debug_level_mask, args...) \
+ do { \
+ if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) { \
+ pr_info(args); \
+ } \
+ } while (0)
+
+#define ANDROID_ALARM_WAKEUP_MASK ( \
+ ANDROID_ALARM_RTC_WAKEUP_MASK | \
+ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK)
+
+/* support old usespace code */
+#define ANDROID_ALARM_SET_OLD _IOW('a', 2, time_t) /* set alarm */
+#define ANDROID_ALARM_SET_AND_WAIT_OLD _IOW('a', 3, time_t)
+
+static int alarm_opened;
+static DEFINE_SPINLOCK(alarm_slock);
+static struct wake_lock alarm_wake_lock;
+static DECLARE_WAIT_QUEUE_HEAD(alarm_wait_queue);
+static uint32_t alarm_pending;
+static uint32_t alarm_enabled;
+static uint32_t wait_pending;
+
+static struct alarm alarms[ANDROID_ALARM_TYPE_COUNT];
+
+static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int rv = 0;
+ unsigned long flags;
+ struct timespec new_alarm_time;
+ struct timespec new_rtc_time;
+ struct timespec tmp_time;
+ enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd);
+ uint32_t alarm_type_mask = 1U << alarm_type;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ char bootalarm_data[14];
+#endif
+
+ if (alarm_type >= ANDROID_ALARM_TYPE_COUNT)
+ return -EINVAL;
+
+ if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_GET_TIME(0)) {
+ if ((file->f_flags & O_ACCMODE) == O_RDONLY)
+ return -EPERM;
+ if (file->private_data == NULL &&
+ cmd != ANDROID_ALARM_SET_RTC) {
+ spin_lock_irqsave(&alarm_slock, flags);
+ if (alarm_opened) {
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return -EBUSY;
+ }
+ alarm_opened = 1;
+ file->private_data = (void *)1;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ }
+ }
+
+ switch (ANDROID_ALARM_BASE_CMD(cmd)) {
+ case ANDROID_ALARM_CLEAR(0):
+ spin_lock_irqsave(&alarm_slock, flags);
+ pr_alarm(IO, "alarm %d clear\n", alarm_type);
+ alarm_try_to_cancel(&alarms[alarm_type]);
+ if (alarm_pending) {
+ alarm_pending &= ~alarm_type_mask;
+ if (!alarm_pending && !wait_pending)
+ wake_unlock(&alarm_wake_lock);
+ }
+ alarm_enabled &= ~alarm_type_mask;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ break;
+
+ case ANDROID_ALARM_SET_OLD:
+ case ANDROID_ALARM_SET_AND_WAIT_OLD:
+ if (get_user(new_alarm_time.tv_sec, (int __user *)arg)) {
+ rv = -EFAULT;
+ goto err1;
+ }
+ new_alarm_time.tv_nsec = 0;
+ goto from_old_alarm_set;
+
+ case ANDROID_ALARM_SET_AND_WAIT(0):
+ case ANDROID_ALARM_SET(0):
+ if (copy_from_user(&new_alarm_time, (void __user *)arg,
+ sizeof(new_alarm_time))) {
+ rv = -EFAULT;
+ goto err1;
+ }
+from_old_alarm_set:
+ spin_lock_irqsave(&alarm_slock, flags);
+ pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type,
+ new_alarm_time.tv_sec, new_alarm_time.tv_nsec);
+ alarm_enabled |= alarm_type_mask;
+ alarm_start_range(&alarms[alarm_type],
+ timespec_to_ktime(new_alarm_time),
+ timespec_to_ktime(new_alarm_time));
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0)
+ && cmd != ANDROID_ALARM_SET_AND_WAIT_OLD)
+ break;
+ /* fall though */
+ case ANDROID_ALARM_WAIT:
+ spin_lock_irqsave(&alarm_slock, flags);
+ pr_alarm(IO, "alarm wait\n");
+ if (!alarm_pending && wait_pending) {
+ wake_unlock(&alarm_wake_lock);
+ wait_pending = 0;
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ rv = wait_event_interruptible(alarm_wait_queue, alarm_pending);
+ if (rv)
+ goto err1;
+ spin_lock_irqsave(&alarm_slock, flags);
+ rv = alarm_pending;
+ wait_pending = 1;
+ alarm_pending = 0;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ break;
+ case ANDROID_ALARM_SET_RTC:
+ if (copy_from_user(&new_rtc_time, (void __user *)arg,
+ sizeof(new_rtc_time))) {
+ rv = -EFAULT;
+ goto err1;
+ }
+ rv = alarm_set_rtc(new_rtc_time);
+ spin_lock_irqsave(&alarm_slock, flags);
+ alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK;
+ wake_up(&alarm_wait_queue);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ if (rv < 0)
+ goto err1;
+ break;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ case ANDROID_ALARM_SET_ALARM_BOOT:
+ if (copy_from_user(bootalarm_data, (void __user *)arg, 14)) {
+ rv = -EFAULT;
+ goto err1;
+ }
+ rv = alarm_set_alarm_boot(bootalarm_data);
+ break;
+#endif
+ case ANDROID_ALARM_GET_TIME(0):
+ switch (alarm_type) {
+ case ANDROID_ALARM_RTC_WAKEUP:
+ case ANDROID_ALARM_RTC:
+ getnstimeofday(&tmp_time);
+ break;
+ case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP:
+ case ANDROID_ALARM_ELAPSED_REALTIME:
+ tmp_time =
+ ktime_to_timespec(alarm_get_elapsed_realtime());
+ break;
+ case ANDROID_ALARM_TYPE_COUNT:
+ case ANDROID_ALARM_SYSTEMTIME:
+ ktime_get_ts(&tmp_time);
+ break;
+ }
+ if (copy_to_user((void __user *)arg, &tmp_time,
+ sizeof(tmp_time))) {
+ rv = -EFAULT;
+ goto err1;
+ }
+ break;
+
+ default:
+ rv = -EINVAL;
+ goto err1;
+ }
+err1:
+ return rv;
+}
+
+static int alarm_open(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+ return 0;
+}
+
+static int alarm_release(struct inode *inode, struct file *file)
+{
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ if (file->private_data != 0) {
+ for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) {
+ uint32_t alarm_type_mask = 1U << i;
+ if (alarm_enabled & alarm_type_mask) {
+ pr_alarm(INFO, "alarm_release: clear alarm, "
+ "pending %d\n",
+ !!(alarm_pending & alarm_type_mask));
+ alarm_enabled &= ~alarm_type_mask;
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ alarm_cancel(&alarms[i]);
+ spin_lock_irqsave(&alarm_slock, flags);
+ }
+ if (alarm_pending | wait_pending) {
+ if (alarm_pending)
+ pr_alarm(INFO, "alarm_release: clear "
+ "pending alarms %x\n", alarm_pending);
+ wake_unlock(&alarm_wake_lock);
+ wait_pending = 0;
+ alarm_pending = 0;
+ }
+ alarm_opened = 0;
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return 0;
+}
+
+static void alarm_triggered(struct alarm *alarm)
+{
+ unsigned long flags;
+ uint32_t alarm_type_mask = 1U << alarm->type;
+
+ pr_alarm(INT, "alarm_triggered type %d\n", alarm->type);
+ spin_lock_irqsave(&alarm_slock, flags);
+ if (alarm_enabled & alarm_type_mask) {
+ wake_lock_timeout(&alarm_wake_lock, 5 * HZ);
+ alarm_enabled &= ~alarm_type_mask;
+ alarm_pending |= alarm_type_mask;
+ wake_up(&alarm_wait_queue);
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+}
+
+static const struct file_operations alarm_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = alarm_ioctl,
+ .open = alarm_open,
+ .release = alarm_release,
+};
+
+static struct miscdevice alarm_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "alarm",
+ .fops = &alarm_fops,
+};
+
+static int __init alarm_dev_init(void)
+{
+ int err;
+ int i;
+
+ err = misc_register(&alarm_device);
+ if (err)
+ return err;
+
+ for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++)
+ alarm_init(&alarms[i], i, alarm_triggered);
+ wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm");
+
+ return 0;
+}
+
+static void __exit alarm_dev_exit(void)
+{
+ misc_deregister(&alarm_device);
+ wake_lock_destroy(&alarm_wake_lock);
+}
+
+module_init(alarm_dev_init);
+module_exit(alarm_dev_exit);
+
diff --git a/drivers/rtc/alarm.c b/drivers/rtc/alarm.c
new file mode 100644
index 0000000..94ff21d
--- /dev/null
+++ b/drivers/rtc/alarm.c
@@ -0,0 +1,638 @@
+/* drivers/rtc/alarm.c
+ *
+ * Copyright (C) 2007-2009 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <asm/mach/time.h>
+#include <linux/android_alarm.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/sysdev.h>
+#include <linux/wakelock.h>
+
+#define ANDROID_ALARM_PRINT_ERROR (1U << 0)
+#define ANDROID_ALARM_PRINT_INIT_STATUS (1U << 1)
+#define ANDROID_ALARM_PRINT_TSET (1U << 2)
+#define ANDROID_ALARM_PRINT_CALL (1U << 3)
+#define ANDROID_ALARM_PRINT_SUSPEND (1U << 4)
+#define ANDROID_ALARM_PRINT_INT (1U << 5)
+#define ANDROID_ALARM_PRINT_FLOW (1U << 6)
+
+static int debug_mask = ANDROID_ALARM_PRINT_ERROR | \
+ ANDROID_ALARM_PRINT_INIT_STATUS;
+module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define pr_alarm(debug_level_mask, args...) \
+ do { \
+ if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) { \
+ pr_info(args); \
+ } \
+ } while (0)
+
+#define ANDROID_ALARM_WAKEUP_MASK ( \
+ ANDROID_ALARM_RTC_WAKEUP_MASK | \
+ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK)
+
+/* support old usespace code */
+#define ANDROID_ALARM_SET_OLD _IOW('a', 2, time_t) /* set alarm */
+#define ANDROID_ALARM_SET_AND_WAIT_OLD _IOW('a', 3, time_t)
+
+struct alarm_queue {
+ struct rb_root alarms;
+ struct rb_node *first;
+ struct hrtimer timer;
+ ktime_t delta;
+ bool stopped;
+ ktime_t stopped_time;
+};
+
+static struct rtc_device *alarm_rtc_dev;
+static DEFINE_SPINLOCK(alarm_slock);
+static DEFINE_MUTEX(alarm_setrtc_mutex);
+static struct wake_lock alarm_rtc_wake_lock;
+static struct platform_device *alarm_platform_dev;
+struct alarm_queue alarms[ANDROID_ALARM_TYPE_COUNT];
+static bool suspended;
+
+static void update_timer_locked(struct alarm_queue *base, bool head_removed)
+{
+ struct alarm *alarm;
+ bool is_wakeup = base == &alarms[ANDROID_ALARM_RTC_WAKEUP] ||
+ base == &alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP];
+
+ if (base->stopped) {
+ pr_alarm(FLOW, "changed alarm while setting the wall time\n");
+ return;
+ }
+
+ if (is_wakeup && !suspended && head_removed)
+ wake_unlock(&alarm_rtc_wake_lock);
+
+ if (!base->first)
+ return;
+
+ alarm = container_of(base->first, struct alarm, node);
+
+ pr_alarm(FLOW, "selected alarm, type %d, func %pF at %lld\n",
+ alarm->type, alarm->function, ktime_to_ns(alarm->expires));
+
+ if (is_wakeup && suspended) {
+ pr_alarm(FLOW, "changed alarm while suspened\n");
+ wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ);
+ return;
+ }
+
+ hrtimer_try_to_cancel(&base->timer);
+ base->timer.node.expires = ktime_add(base->delta, alarm->expires);
+ base->timer._softexpires = ktime_add(base->delta, alarm->softexpires);
+ hrtimer_start_expires(&base->timer, HRTIMER_MODE_ABS);
+}
+
+static void alarm_enqueue_locked(struct alarm *alarm)
+{
+ struct alarm_queue *base = &alarms[alarm->type];
+ struct rb_node **link = &base->alarms.rb_node;
+ struct rb_node *parent = NULL;
+ struct alarm *entry;
+ int leftmost = 1;
+ bool was_first = false;
+
+ pr_alarm(FLOW, "added alarm, type %d, func %pF at %lld\n",
+ alarm->type, alarm->function, ktime_to_ns(alarm->expires));
+
+ if (base->first == &alarm->node) {
+ base->first = rb_next(&alarm->node);
+ was_first = true;
+ }
+ if (!RB_EMPTY_NODE(&alarm->node)) {
+ rb_erase(&alarm->node, &base->alarms);
+ RB_CLEAR_NODE(&alarm->node);
+ }
+
+ while (*link) {
+ parent = *link;
+ entry = rb_entry(parent, struct alarm, node);
+ /*
+ * We dont care about collisions. Nodes with
+ * the same expiry time stay together.
+ */
+ if (alarm->expires.tv64 < entry->expires.tv64) {
+ link = &(*link)->rb_left;
+ } else {
+ link = &(*link)->rb_right;
+ leftmost = 0;
+ }
+ }
+ if (leftmost)
+ base->first = &alarm->node;
+ if (leftmost || was_first)
+ update_timer_locked(base, was_first);
+
+ rb_link_node(&alarm->node, parent, link);
+ rb_insert_color(&alarm->node, &base->alarms);
+}
+
+/**
+ * alarm_init - initialize an alarm
+ * @alarm: the alarm to be initialized
+ * @type: the alarm type to be used
+ * @function: alarm callback function
+ */
+void alarm_init(struct alarm *alarm,
+ enum android_alarm_type type, void (*function)(struct alarm *))
+{
+ RB_CLEAR_NODE(&alarm->node);
+ alarm->type = type;
+ alarm->function = function;
+
+ pr_alarm(FLOW, "created alarm, type %d, func %pF\n", type, function);
+}
+
+
+/**
+ * alarm_start_range - (re)start an alarm
+ * @alarm: the alarm to be added
+ * @start: earliest expiry time
+ * @end: expiry time
+ */
+void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ alarm->softexpires = start;
+ alarm->expires = end;
+ alarm_enqueue_locked(alarm);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+}
+
+/**
+ * alarm_try_to_cancel - try to deactivate an alarm
+ * @alarm: alarm to stop
+ *
+ * Returns:
+ * 0 when the alarm was not active
+ * 1 when the alarm was active
+ * -1 when the alarm may currently be excuting the callback function and
+ * cannot be stopped (it may also be inactive)
+ */
+int alarm_try_to_cancel(struct alarm *alarm)
+{
+ struct alarm_queue *base = &alarms[alarm->type];
+ unsigned long flags;
+ bool first = false;
+ int ret = 0;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ if (!RB_EMPTY_NODE(&alarm->node)) {
+ pr_alarm(FLOW, "canceled alarm, type %d, func %pF at %lld\n",
+ alarm->type, alarm->function,
+ ktime_to_ns(alarm->expires));
+ ret = 1;
+ if (base->first == &alarm->node) {
+ base->first = rb_next(&alarm->node);
+ first = true;
+ }
+ rb_erase(&alarm->node, &base->alarms);
+ RB_CLEAR_NODE(&alarm->node);
+ if (first)
+ update_timer_locked(base, true);
+ } else
+ pr_alarm(FLOW, "tried to cancel alarm, type %d, func %pF\n",
+ alarm->type, alarm->function);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ if (!ret && hrtimer_callback_running(&base->timer))
+ ret = -1;
+ return ret;
+}
+
+/**
+ * alarm_cancel - cancel an alarm and wait for the handler to finish.
+ * @alarm: the alarm to be cancelled
+ *
+ * Returns:
+ * 0 when the alarm was not active
+ * 1 when the alarm was active
+ */
+int alarm_cancel(struct alarm *alarm)
+{
+ for (;;) {
+ int ret = alarm_try_to_cancel(alarm);
+ if (ret >= 0)
+ return ret;
+ cpu_relax();
+ }
+}
+
+/**
+ * alarm_set_rtc - set the kernel and rtc walltime
+ * @new_time: timespec value containing the new time
+ */
+int alarm_set_rtc(struct timespec new_time)
+{
+ int i;
+ int ret;
+ unsigned long flags;
+ struct rtc_time rtc_new_rtc_time;
+ struct timespec tmp_time;
+
+ rtc_time_to_tm(new_time.tv_sec, &rtc_new_rtc_time);
+
+ pr_alarm(TSET, "set rtc %ld %ld - rtc %02d:%02d:%02d %02d/%02d/%04d\n",
+ new_time.tv_sec, new_time.tv_nsec,
+ rtc_new_rtc_time.tm_hour, rtc_new_rtc_time.tm_min,
+ rtc_new_rtc_time.tm_sec, rtc_new_rtc_time.tm_mon + 1,
+ rtc_new_rtc_time.tm_mday,
+ rtc_new_rtc_time.tm_year + 1900);
+
+ mutex_lock(&alarm_setrtc_mutex);
+ spin_lock_irqsave(&alarm_slock, flags);
+ wake_lock(&alarm_rtc_wake_lock);
+ getnstimeofday(&tmp_time);
+ for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
+ hrtimer_try_to_cancel(&alarms[i].timer);
+ alarms[i].stopped = true;
+ alarms[i].stopped_time = timespec_to_ktime(tmp_time);
+ }
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].delta =
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta =
+ ktime_sub(alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta,
+ timespec_to_ktime(timespec_sub(tmp_time, new_time)));
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ ret = do_settimeofday(&new_time);
+ spin_lock_irqsave(&alarm_slock, flags);
+ for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
+ alarms[i].stopped = false;
+ update_timer_locked(&alarms[i], false);
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ if (ret < 0) {
+ pr_alarm(ERROR, "alarm_set_rtc: Failed to set time\n");
+ goto err;
+ }
+ if (!alarm_rtc_dev) {
+ pr_alarm(ERROR,
+ "alarm_set_rtc: no RTC, time will be lost on reboot\n");
+ goto err;
+ }
+ ret = rtc_set_time(alarm_rtc_dev, &rtc_new_rtc_time);
+ if (ret < 0)
+ pr_alarm(ERROR, "alarm_set_rtc: "
+ "Failed to set RTC, time will be lost on reboot\n");
+err:
+ wake_unlock(&alarm_rtc_wake_lock);
+ mutex_unlock(&alarm_setrtc_mutex);
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+#define BOOTALM_BIT_EN 0
+#define BOOTALM_BIT_YEAR 1
+#define BOOTALM_BIT_MONTH 5
+#define BOOTALM_BIT_DAY 7
+#define BOOTALM_BIT_HOUR 9
+#define BOOTALM_BIT_MIN 11
+#define BOOTALM_BIT_TOTAL 13
+
+int alarm_set_alarm_boot(char *alarm_data)
+{
+ struct rtc_wkalrm alm;
+ int ret;
+ char buf_ptr[BOOTALM_BIT_TOTAL + 1];
+
+ if (!alarm_rtc_dev) {
+ pr_alarm(ERROR,
+ "alarm_set_alarm_boot: no RTC, time will be lost on reboot\n");
+ return -1;
+ }
+
+ strlcpy(buf_ptr, alarm_data, BOOTALM_BIT_TOTAL + 1);
+
+ alm.time.tm_sec = 0;
+
+ alm.time.tm_min = (buf_ptr[BOOTALM_BIT_MIN] - '0') * 10
+ + (buf_ptr[BOOTALM_BIT_MIN + 1] - '0');
+ alm.time.tm_hour = (buf_ptr[BOOTALM_BIT_HOUR] - '0') * 10
+ + (buf_ptr[BOOTALM_BIT_HOUR + 1] - '0');
+ alm.time.tm_mday = (buf_ptr[BOOTALM_BIT_DAY] - '0') * 10
+ + (buf_ptr[BOOTALM_BIT_DAY + 1] - '0');
+ alm.time.tm_mon = (buf_ptr[BOOTALM_BIT_MONTH] - '0') * 10
+ + (buf_ptr[BOOTALM_BIT_MONTH + 1] - '0');
+ alm.time.tm_year = (buf_ptr[BOOTALM_BIT_YEAR] - '0') * 1000
+ + (buf_ptr[BOOTALM_BIT_YEAR + 1] - '0') * 100
+ + (buf_ptr[BOOTALM_BIT_YEAR + 2] - '0') * 10
+ + (buf_ptr[BOOTALM_BIT_YEAR + 3] - '0');
+ alm.enabled = (*buf_ptr == '1');
+
+ alm.time.tm_mon -= 1;
+ alm.time.tm_year -= 1900;
+
+ ret = rtc_set_alarm_boot(alarm_rtc_dev, &alm);
+
+ return ret;
+}
+#endif
+
+/**
+ * alarm_get_elapsed_realtime - get the elapsed real time in ktime_t format
+ *
+ * returns the time in ktime_t format
+ */
+ktime_t alarm_get_elapsed_realtime(void)
+{
+ ktime_t now;
+ unsigned long flags;
+ struct alarm_queue *base = &alarms[ANDROID_ALARM_ELAPSED_REALTIME];
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ now = base->stopped ? base->stopped_time : ktime_get_real();
+ now = ktime_sub(now, base->delta);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return now;
+}
+
+static enum hrtimer_restart alarm_timer_triggered(struct hrtimer *timer)
+{
+ struct alarm_queue *base;
+ struct alarm *alarm;
+ unsigned long flags;
+ ktime_t now;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+
+ base = container_of(timer, struct alarm_queue, timer);
+ now = base->stopped ? base->stopped_time : hrtimer_cb_get_time(timer);
+ now = ktime_sub(now, base->delta);
+
+ pr_alarm(INT, "alarm_timer_triggered type %d at %lld\n",
+ base - alarms, ktime_to_ns(now));
+
+ while (base->first) {
+ alarm = container_of(base->first, struct alarm, node);
+ if (alarm->softexpires.tv64 > now.tv64) {
+ pr_alarm(FLOW, "don't call alarm, %pF, %lld (s %lld)\n",
+ alarm->function, ktime_to_ns(alarm->expires),
+ ktime_to_ns(alarm->softexpires));
+ break;
+ }
+ base->first = rb_next(&alarm->node);
+ rb_erase(&alarm->node, &base->alarms);
+ RB_CLEAR_NODE(&alarm->node);
+ pr_alarm(CALL, "call alarm, type %d, func %pF, %lld (s %lld)\n",
+ alarm->type, alarm->function,
+ ktime_to_ns(alarm->expires),
+ ktime_to_ns(alarm->softexpires));
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ alarm->function(alarm);
+ spin_lock_irqsave(&alarm_slock, flags);
+ }
+ if (!base->first)
+ pr_alarm(FLOW, "no more alarms of type %d\n", base - alarms);
+ update_timer_locked(base, true);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return HRTIMER_NORESTART;
+}
+
+static void alarm_triggered_func(void *p)
+{
+ struct rtc_device *rtc = alarm_rtc_dev;
+ if (!(rtc->irq_data & RTC_AF))
+ return;
+ pr_alarm(INT, "rtc alarm triggered\n");
+ wake_lock_timeout(&alarm_rtc_wake_lock, 1 * HZ);
+}
+
+static int alarm_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ int err = 0;
+ unsigned long flags;
+ struct rtc_wkalrm rtc_alarm;
+ struct rtc_time rtc_current_rtc_time;
+ unsigned long rtc_current_time;
+ unsigned long rtc_alarm_time;
+ struct timespec rtc_delta;
+ struct timespec wall_time;
+ struct alarm_queue *wakeup_queue = NULL;
+ struct alarm_queue *tmp_queue = NULL;
+
+ pr_alarm(SUSPEND, "alarm_suspend(%p, %d)\n", pdev, state.event);
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ suspended = true;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+
+ hrtimer_cancel(&alarms[ANDROID_ALARM_RTC_WAKEUP].timer);
+ hrtimer_cancel(&alarms[
+ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].timer);
+
+ tmp_queue = &alarms[ANDROID_ALARM_RTC_WAKEUP];
+ if (tmp_queue->first)
+ wakeup_queue = tmp_queue;
+ tmp_queue = &alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP];
+ if (tmp_queue->first && (!wakeup_queue ||
+ hrtimer_get_expires(&tmp_queue->timer).tv64 <
+ hrtimer_get_expires(&wakeup_queue->timer).tv64))
+ wakeup_queue = tmp_queue;
+ if (wakeup_queue) {
+ rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time);
+ getnstimeofday(&wall_time);
+ rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_time);
+ set_normalized_timespec(&rtc_delta,
+ wall_time.tv_sec - rtc_current_time,
+ wall_time.tv_nsec);
+
+ rtc_alarm_time = timespec_sub(ktime_to_timespec(
+ hrtimer_get_expires(&wakeup_queue->timer)),
+ rtc_delta).tv_sec;
+
+ rtc_time_to_tm(rtc_alarm_time, &rtc_alarm.time);
+ rtc_alarm.enabled = 1;
+ rtc_set_alarm(alarm_rtc_dev, &rtc_alarm);
+ rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time);
+ rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_time);
+ pr_alarm(SUSPEND,
+ "rtc alarm set at %ld, now %ld, rtc delta %ld.%09ld\n",
+ rtc_alarm_time, rtc_current_time,
+ rtc_delta.tv_sec, rtc_delta.tv_nsec);
+ if (rtc_current_time + 1 >= rtc_alarm_time) {
+ pr_alarm(SUSPEND, "alarm about to go off\n");
+ memset(&rtc_alarm, 0, sizeof(rtc_alarm));
+ rtc_alarm.enabled = 0;
+ rtc_set_alarm(alarm_rtc_dev, &rtc_alarm);
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ suspended = false;
+ wake_lock_timeout(&alarm_rtc_wake_lock, 2 * HZ);
+ update_timer_locked(&alarms[ANDROID_ALARM_RTC_WAKEUP],
+ false);
+ update_timer_locked(&alarms[
+ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP], false);
+ err = -EBUSY;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ }
+ }
+ return err;
+}
+
+static int alarm_resume(struct platform_device *pdev)
+{
+ struct rtc_wkalrm alarm;
+ unsigned long flags;
+
+ pr_alarm(SUSPEND, "alarm_resume(%p)\n", pdev);
+
+ memset(&alarm, 0, sizeof(alarm));
+ alarm.enabled = 0;
+ rtc_set_alarm(alarm_rtc_dev, &alarm);
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ suspended = false;
+ update_timer_locked(&alarms[ANDROID_ALARM_RTC_WAKEUP], false);
+ update_timer_locked(&alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP],
+ false);
+ spin_unlock_irqrestore(&alarm_slock, flags);
+
+ return 0;
+}
+
+static struct rtc_task alarm_rtc_task = {
+ .func = alarm_triggered_func
+};
+
+static int rtc_alarm_add_device(struct device *dev,
+ struct class_interface *class_intf)
+{
+ int err;
+ struct rtc_device *rtc = to_rtc_device(dev);
+
+ mutex_lock(&alarm_setrtc_mutex);
+
+ if (alarm_rtc_dev) {
+ err = -EBUSY;
+ goto err1;
+ }
+
+ alarm_platform_dev =
+ platform_device_register_simple("alarm", -1, NULL, 0);
+ if (IS_ERR(alarm_platform_dev)) {
+ err = PTR_ERR(alarm_platform_dev);
+ goto err2;
+ }
+ err = rtc_irq_register(rtc, &alarm_rtc_task);
+ if (err)
+ goto err3;
+ alarm_rtc_dev = rtc;
+ pr_alarm(INIT_STATUS, "using rtc device, %s, for alarms", rtc->name);
+ mutex_unlock(&alarm_setrtc_mutex);
+
+ return 0;
+
+err3:
+ platform_device_unregister(alarm_platform_dev);
+err2:
+err1:
+ mutex_unlock(&alarm_setrtc_mutex);
+ return err;
+}
+
+static void rtc_alarm_remove_device(struct device *dev,
+ struct class_interface *class_intf)
+{
+ if (dev == &alarm_rtc_dev->dev) {
+ pr_alarm(INIT_STATUS, "lost rtc device for alarms");
+ rtc_irq_unregister(alarm_rtc_dev, &alarm_rtc_task);
+ platform_device_unregister(alarm_platform_dev);
+ alarm_rtc_dev = NULL;
+ }
+}
+
+static struct class_interface rtc_alarm_interface = {
+ .add_dev = &rtc_alarm_add_device,
+ .remove_dev = &rtc_alarm_remove_device,
+};
+
+static struct platform_driver alarm_driver = {
+ .suspend = alarm_suspend,
+ .resume = alarm_resume,
+ .driver = {
+ .name = "alarm"
+ }
+};
+
+static int __init alarm_late_init(void)
+{
+ unsigned long flags;
+ struct timespec tmp_time, system_time;
+
+ /* this needs to run after the rtc is read at boot */
+ spin_lock_irqsave(&alarm_slock, flags);
+ /* We read the current rtc and system time so we can later calulate
+ * elasped realtime to be (boot_systemtime + rtc - boot_rtc) ==
+ * (rtc - (boot_rtc - boot_systemtime))
+ */
+ getnstimeofday(&tmp_time);
+ ktime_get_ts(&system_time);
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].delta =
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta =
+ timespec_to_ktime(timespec_sub(tmp_time, system_time));
+
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return 0;
+}
+
+static int __init alarm_driver_init(void)
+{
+ int err;
+ int i;
+
+ for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
+ hrtimer_init(&alarms[i].timer,
+ CLOCK_REALTIME, HRTIMER_MODE_ABS);
+ alarms[i].timer.function = alarm_timer_triggered;
+ }
+ hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].timer,
+ CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+ alarms[ANDROID_ALARM_SYSTEMTIME].timer.function = alarm_timer_triggered;
+ err = platform_driver_register(&alarm_driver);
+ if (err < 0)
+ goto err1;
+ wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc");
+ rtc_alarm_interface.class = rtc_class;
+ err = class_interface_register(&rtc_alarm_interface);
+ if (err < 0)
+ goto err2;
+
+ return 0;
+
+err2:
+ wake_lock_destroy(&alarm_rtc_wake_lock);
+ platform_driver_unregister(&alarm_driver);
+err1:
+ return err;
+}
+
+static void __exit alarm_exit(void)
+{
+ class_interface_unregister(&rtc_alarm_interface);
+ wake_lock_destroy(&alarm_rtc_wake_lock);
+ platform_driver_unregister(&alarm_driver);
+}
+
+late_initcall(alarm_late_init);
+module_init(alarm_driver_init);
+module_exit(alarm_exit);
+
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c
index 4194e59..b82a155 100644
--- a/drivers/rtc/class.c
+++ b/drivers/rtc/class.c
@@ -41,20 +41,41 @@ static void rtc_device_release(struct device *dev)
* system's wall clock; restore it on resume().
*/
-static time_t oldtime;
-static struct timespec oldts;
+static struct timespec old_rtc, old_system, old_delta;
+
static int rtc_suspend(struct device *dev, pm_message_t mesg)
{
struct rtc_device *rtc = to_rtc_device(dev);
struct rtc_time tm;
-
+ struct timespec delta, delta_delta;
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
return 0;
+ /* snapshot the current RTC and system time at suspend*/
rtc_read_time(rtc, &tm);
- ktime_get_ts(&oldts);
- rtc_tm_to_time(&tm, &oldtime);
+ getnstimeofday(&old_system);
+ rtc_tm_to_time(&tm, &old_rtc.tv_sec);
+
+
+ /*
+ * To avoid drift caused by repeated suspend/resumes,
+ * which each can add ~1 second drift error,
+ * try to compensate so the difference in system time
+ * and rtc time stays close to constant.
+ */
+ delta = timespec_sub(old_system, old_rtc);
+ delta_delta = timespec_sub(delta, old_delta);
+ if (delta_delta.tv_sec < -2 || delta_delta.tv_sec >= 2) {
+ /*
+ * if delta_delta is too large, assume time correction
+ * has occured and set old_delta to the current delta.
+ */
+ old_delta = delta;
+ } else {
+ /* Otherwise try to adjust old_system to compensate */
+ old_system = timespec_sub(old_system, delta_delta);
+ }
return 0;
}
@@ -63,32 +84,42 @@ static int rtc_resume(struct device *dev)
{
struct rtc_device *rtc = to_rtc_device(dev);
struct rtc_time tm;
- time_t newtime;
- struct timespec time;
- struct timespec newts;
+ struct timespec new_system, new_rtc;
+ struct timespec sleep_time;
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
return 0;
- ktime_get_ts(&newts);
+ /* snapshot the current rtc and system time at resume */
+ getnstimeofday(&new_system);
rtc_read_time(rtc, &tm);
if (rtc_valid_tm(&tm) != 0) {
pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev));
return 0;
}
- rtc_tm_to_time(&tm, &newtime);
- if (newtime <= oldtime) {
- if (newtime < oldtime)
- pr_debug("%s: time travel!\n", dev_name(&rtc->dev));
+ rtc_tm_to_time(&tm, &new_rtc.tv_sec);
+ new_rtc.tv_nsec = 0;
+
+ if (new_rtc.tv_sec < old_rtc.tv_sec) {
+ pr_debug("%s: time travel!\n", dev_name(&rtc->dev));
return 0;
}
- /* calculate the RTC time delta */
- set_normalized_timespec(&time, newtime - oldtime, 0);
-
- /* subtract kernel time between rtc_suspend to rtc_resume */
- time = timespec_sub(time, timespec_sub(newts, oldts));
- timekeeping_inject_sleeptime(&time);
+ /* calculate the RTC time delta (sleep time)*/
+ sleep_time = timespec_sub(new_rtc, old_rtc);
+
+ /*
+ * Since these RTC suspend/resume handlers are not called
+ * at the very end of suspend or the start of resume,
+ * some run-time may pass on either sides of the sleep time
+ * so subtract kernel run-time between rtc_suspend to rtc_resume
+ * to keep things accurate.
+ */
+ sleep_time = timespec_sub(sleep_time,
+ timespec_sub(new_system, old_system));
+
+ if (sleep_time.tv_sec >= 0)
+ timekeeping_inject_sleeptime(&sleep_time);
return 0;
}
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
index bbb6f85..f94efb8 100644
--- a/drivers/rtc/interface.c
+++ b/drivers/rtc/interface.c
@@ -382,6 +382,28 @@ int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
}
EXPORT_SYMBOL_GPL(rtc_set_alarm);
+#if defined(CONFIG_RTC_ALARM_BOOT)
+int rtc_set_alarm_boot(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
+{
+ int err;
+
+ err = mutex_lock_interruptible(&rtc->ops_lock);
+ if (err)
+ return err;
+
+ if (!rtc->ops)
+ err = -ENODEV;
+ else if (!rtc->ops->set_alarm)
+ err = -EINVAL;
+ else
+ err = rtc->ops->set_alarm_boot(rtc->dev.parent, alarm);
+
+ mutex_unlock(&rtc->ops_lock);
+ return err;
+}
+EXPORT_SYMBOL_GPL(rtc_set_alarm_boot);
+#endif
+
/* Called once per device from rtc_device_register */
int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
diff --git a/drivers/rtc/rtc-lib.c b/drivers/rtc/rtc-lib.c
index 075f170..c4cf057 100644
--- a/drivers/rtc/rtc-lib.c
+++ b/drivers/rtc/rtc-lib.c
@@ -85,6 +85,8 @@ void rtc_time_to_tm(unsigned long time, struct rtc_time *tm)
time -= tm->tm_hour * 3600;
tm->tm_min = time / 60;
tm->tm_sec = time - tm->tm_min * 60;
+
+ tm->tm_isdst = 0;
}
EXPORT_SYMBOL(rtc_time_to_tm);
diff --git a/drivers/rtc/rtc-max77686.c b/drivers/rtc/rtc-max77686.c
new file mode 100644
index 0000000..955cc1a
--- /dev/null
+++ b/drivers/rtc/rtc-max77686.c
@@ -0,0 +1,909 @@
+/*
+ * RTC driver for Maxim MAX77686
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ *
+ * based on rtc-max8997.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+#if defined(CONFIG_RTC_ALARM_BOOT)
+#include <linux/reboot.h>
+#endif
+
+/* RTC Control Register */
+#define BCD_EN_SHIFT 0
+#define BCD_EN_MASK (1 << BCD_EN_SHIFT)
+#define MODEL24_SHIFT 1
+#define MODEL24_MASK (1 << MODEL24_SHIFT)
+/* RTC Update Register1 */
+#define RTC_UDR_SHIFT 0
+#define RTC_UDR_MASK (1 << RTC_UDR_SHIFT)
+#define RTC_RBUDR_SHIFT 4
+#define RTC_RBUDR_MASK (1 << RTC_RBUDR_SHIFT)
+/* WTSR and SMPL Register */
+#define WTSRT_SHIFT 0
+#define SMPLT_SHIFT 2
+#define WTSR_EN_SHIFT 6
+#define SMPL_EN_SHIFT 7
+#define WTSRT_MASK (3 << WTSRT_SHIFT)
+#define SMPLT_MASK (3 << SMPLT_SHIFT)
+#define WTSR_EN_MASK (1 << WTSR_EN_SHIFT)
+#define SMPL_EN_MASK (1 << SMPL_EN_SHIFT)
+/* RTC Hour register */
+#define HOUR_PM_SHIFT 6
+#define HOUR_PM_MASK (1 << HOUR_PM_SHIFT)
+/* RTC Alarm Enable */
+#define ALARM_ENABLE_SHIFT 7
+#define ALARM_ENABLE_MASK (1 << ALARM_ENABLE_SHIFT)
+
+#define MAX77686_RTC_UPDATE_DELAY 16
+#define MAX77686_RTC_WTSR_SMPL
+#define MAX77686_RTC_DEBUG
+
+enum {
+ RTC_SEC = 0,
+ RTC_MIN,
+ RTC_HOUR,
+ RTC_WEEKDAY,
+ RTC_MONTH,
+ RTC_YEAR,
+ RTC_DATE,
+ RTC_NR_TIME
+};
+
+struct max77686_rtc_info {
+ struct device *dev;
+ struct max77686_dev *max77686;
+ struct i2c_client *rtc;
+ struct rtc_device *rtc_dev;
+ struct mutex lock;
+ int irq;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ int irq2;
+#endif
+ int rtc_24hr_mode;
+};
+
+enum MAX77686_RTC_OP {
+ MAX77686_RTC_WRITE,
+ MAX77686_RTC_READ,
+};
+
+static inline int max77686_rtc_calculate_wday(u8 shifted)
+{
+ int counter = -1;
+ while (shifted) {
+ shifted >>= 1;
+ counter++;
+ }
+ return counter;
+}
+
+static void max77686_rtc_data_to_tm(u8 *data, struct rtc_time *tm,
+ int rtc_24hr_mode)
+{
+ tm->tm_sec = data[RTC_SEC] & 0x7f;
+ tm->tm_min = data[RTC_MIN] & 0x7f;
+ if (rtc_24hr_mode)
+ tm->tm_hour = data[RTC_HOUR] & 0x1f;
+ else {
+ tm->tm_hour = data[RTC_HOUR] & 0x0f;
+ if (data[RTC_HOUR] & HOUR_PM_MASK)
+ tm->tm_hour += 12;
+ }
+
+ tm->tm_wday = max77686_rtc_calculate_wday(data[RTC_WEEKDAY] & 0x7f);
+ tm->tm_mday = data[RTC_DATE] & 0x1f;
+ tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+ tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100;
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+}
+
+static int max77686_rtc_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = tm->tm_sec;
+ data[RTC_MIN] = tm->tm_min;
+ data[RTC_HOUR] = tm->tm_hour;
+ data[RTC_WEEKDAY] = 1 << tm->tm_wday;
+ data[RTC_DATE] = tm->tm_mday;
+ data[RTC_MONTH] = tm->tm_mon + 1;
+ data[RTC_YEAR] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0 ;
+
+ if (tm->tm_year < 100) {
+ pr_warn("%s: MAX77686 RTC cannot handle the year %d."
+ "Assume it's 2000.\n", __func__, 1900 + tm->tm_year);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static inline int max77686_rtc_update(struct max77686_rtc_info *info,
+ enum MAX77686_RTC_OP op)
+{
+ int ret;
+ u8 data;
+
+ if (!info || !info->rtc) {
+ pr_err("%s: Invalid argument\n", __func__);
+ return -EINVAL;
+ }
+
+ switch (op) {
+ case MAX77686_RTC_WRITE:
+ data = 1 << RTC_UDR_SHIFT;
+ break;
+ case MAX77686_RTC_READ:
+ data = 1 << RTC_RBUDR_SHIFT;
+ break;
+ }
+
+ ret = max77686_update_reg(info->rtc, MAX77686_RTC_UPDATE0, data, data);
+ if (ret < 0)
+ dev_err(info->dev, "%s: fail to write update reg(ret=%d, data=0x%x)\n",
+ __func__, ret, data);
+ else {
+ /* Minimum 16ms delay required before RTC update. */
+ msleep(MAX77686_RTC_UPDATE_DELAY);
+ }
+
+ return ret;
+}
+
+static int max77686_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ mutex_lock(&info->lock);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_RTC_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, tm, info->rtc_24hr_mode);
+
+ ret = rtc_valid_tm(tm);
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+#ifdef MAX77686_RTC_DEBUG
+ struct task_struct *task = current;
+#endif
+
+ ret = max77686_rtc_tm_to_data(tm, data);
+ if (ret < 0)
+ return ret;
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_RTC_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__,
+ ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+
+#ifdef MAX77686_RTC_DEBUG
+ printk(KERN_INFO "%s: task=%s[%d]\n", __func__, task->comm, task->pid);
+#endif
+
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max77686_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ u8 val;
+ int i, ret;
+
+ mutex_lock(&info->lock);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n",
+ __func__, __LINE__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &alrm->time, info->rtc_24hr_mode);
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday,
+ alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec, alrm->time.tm_wday);
+
+ alrm->enabled = 0;
+ for (i = 0; i < RTC_NR_TIME; i++) {
+ if (data[i] & ALARM_ENABLE_MASK) {
+ alrm->enabled = 1;
+ break;
+ }
+ }
+
+ alrm->pending = 0;
+ ret = max77686_read_reg(info->max77686->i2c, MAX77686_REG_STATUS1, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read status1 reg(%d)\n",
+ __func__, __LINE__, ret);
+ goto out;
+ }
+
+ if (val & (1 << 4)) /* RTCA1 */
+ alrm->pending = 1;
+
+out:
+ mutex_unlock(&info->lock);
+ return 0;
+}
+
+static int max77686_rtc_stop_alarm(struct max77686_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret, i;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ for (i = 0; i < RTC_NR_TIME; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max77686_rtc_stop_alarm_boot(struct max77686_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret, i;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM2_SEC,
+ RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ for (i = 0; i < RTC_NR_TIME; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM2_SEC,
+ RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+ return ret;
+}
+
+#endif
+
+static int max77686_rtc_start_alarm(struct max77686_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_WEEKDAY] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT);
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+ return ret;
+}
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max77686_rtc_start_alarm_boot(struct max77686_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM2_SEC,
+ RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_WEEKDAY] |= 0;
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT);
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM2_SEC,
+ RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+ return ret;
+}
+
+
+#endif
+
+static int max77686_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ ret = max77686_rtc_tm_to_data(&alrm->time, data);
+ if (ret < 0)
+ return ret;
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday,
+ alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec, alrm->time.tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = max77686_rtc_stop_alarm(info);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = max77686_rtc_start_alarm(info);
+
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max77686_rtc_set_alarm_boot(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ if (alrm->enabled) {
+ data[RTC_SEC] = alrm->time.tm_sec;
+ data[RTC_MIN] = alrm->time.tm_min;
+ data[RTC_HOUR] = alrm->time.tm_hour;
+ data[RTC_WEEKDAY] = 0;
+ data[RTC_DATE] = alrm->time.tm_mday;
+ data[RTC_MONTH] = alrm->time.tm_mon + 1;
+ data[RTC_YEAR] = alrm->time.tm_year > 100
+ ? (alrm->time.tm_year - 100) : 0;
+ } else {
+ data[RTC_SEC] = 0;
+ data[RTC_MIN] = 0;
+ data[RTC_HOUR] = 0;
+ data[RTC_WEEKDAY] = 0;
+ data[RTC_DATE] = 1;
+ data[RTC_MONTH] = 0;
+ data[RTC_YEAR] = 0;
+ }
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ alrm->time.tm_year, alrm->time.tm_mon, alrm->time.tm_mday,
+ alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec,
+ alrm->time.tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = max77686_rtc_stop_alarm_boot(info);
+ if (ret < 0)
+ goto out;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = max77686_rtc_start_alarm_boot(info);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+#endif
+
+static int max77686_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct max77686_rtc_info *info = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&info->lock);
+ if (enabled)
+ ret = max77686_rtc_start_alarm(info);
+ else
+ ret = max77686_rtc_stop_alarm(info);
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+
+static irqreturn_t max77686_rtc_alarm_irq(int irq, void *data)
+{
+ struct max77686_rtc_info *info = data;
+
+ dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static irqreturn_t max77686_rtc_alarm2_irq(int irq, void *data)
+{
+ struct max77686_rtc_info *info = data;
+ int ret;
+ u8 val;
+
+ dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+#if defined(CONFIG_SLP)
+ if (strstr(saved_command_line, "charger_detect_boot") != 0)
+ kernel_restart(NULL);
+#else
+ if (lpcharge == 1)
+ kernel_restart(NULL);
+#endif
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+#endif
+
+static const struct rtc_class_ops max77686_rtc_ops = {
+ .read_time = max77686_rtc_read_time,
+ .set_time = max77686_rtc_set_time,
+ .read_alarm = max77686_rtc_read_alarm,
+#if defined(CONFIG_RTC_ALARM_BOOT) && defined(CONFIG_SLP)
+ .set_alarm = max77686_rtc_set_alarm_boot,
+#else
+ .set_alarm = max77686_rtc_set_alarm,
+#endif
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ .set_alarm_boot = max77686_rtc_set_alarm_boot,
+#endif
+ .alarm_irq_enable = max77686_rtc_alarm_irq_enable,
+};
+
+#ifdef MAX77686_RTC_WTSR_SMPL
+static void max77686_rtc_enable_wtsr(struct max77686_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = (1 << WTSR_EN_SHIFT) | (3 << WTSRT_SHIFT);
+ else
+ val = 0;
+
+ mask = WTSR_EN_MASK | WTSRT_MASK;
+
+ dev_info(info->dev, "%s: %s WTSR\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = max77686_update_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ max77686_rtc_update(info, MAX77686_RTC_WRITE);
+}
+
+static void max77686_rtc_enable_smpl(struct max77686_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = (1 << SMPL_EN_SHIFT) | (0 << SMPLT_SHIFT);
+ else
+ val = 0;
+
+ mask = SMPL_EN_MASK | SMPLT_MASK;
+
+ dev_info(info->dev, "%s: %s SMPL\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = max77686_update_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ max77686_rtc_update(info, MAX77686_RTC_WRITE);
+
+ val = 0;
+ max77686_read_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val);
+}
+#endif /* MAX77686_RTC_WTSR_SMPL */
+
+static int max77686_rtc_init_reg(struct max77686_rtc_info *info)
+{
+ u8 data[2];
+ u8 buf;
+ int ret = 0;
+ struct rtc_time tm;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ u8 data_alm2[RTC_NR_TIME];
+
+ ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+ if (ret < 0)
+ return ret;
+
+ ret = max77686_bulk_read(info->rtc, MAX77686_ALARM2_SEC,
+ RTC_NR_TIME, data_alm2);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n",
+ __func__, __LINE__, ret);
+ return ret;
+ }
+
+ printk(KERN_INFO "%s:alm2: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ data_alm2[RTC_YEAR], data_alm2[RTC_MONTH], data_alm2[RTC_DATE],
+ data_alm2[RTC_HOUR], data_alm2[RTC_MIN], data_alm2[RTC_SEC],
+ data_alm2[RTC_WEEKDAY]);
+#endif
+
+ ret = max77686_read_reg(info->rtc, MAX77686_RTC_CONTROL, &buf);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read control reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Set RTC control register : Binary mode, 24hour mdoe */
+ data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+ data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+
+ info->rtc_24hr_mode = 1;
+
+ ret = max77686_bulk_write(info->rtc, MAX77686_RTC_CONTROLM, 2, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write controlm reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ max77686_rtc_update(info, MAX77686_RTC_WRITE);
+
+ /* If it's first boot, reset rtc to 1/1/2012 00:00:00(SUN) */
+ if (buf == 0) {
+ dev_info(info->dev, "rtc init\n");
+ tm.tm_sec = 0;
+ tm.tm_min = 0;
+ tm.tm_hour = 0;
+ tm.tm_wday = 0;
+ tm.tm_mday = 1;
+ tm.tm_mon = 0;
+ tm.tm_year = 112;
+ tm.tm_yday = 0;
+ tm.tm_isdst = 0;
+ max77686_rtc_set_time(info->dev, &tm);
+ }
+
+ return ret;
+}
+
+static int __devinit max77686_rtc_probe(struct platform_device *pdev)
+{
+ struct max77686_dev *max77686 = dev_get_drvdata(pdev->dev.parent);
+ struct max77686_rtc_info *info;
+ int ret;
+
+ printk(KERN_INFO "%s\n", __func__);
+
+ info = kzalloc(sizeof(struct max77686_rtc_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ mutex_init(&info->lock);
+ info->dev = &pdev->dev;
+ info->max77686 = max77686;
+ info->rtc = max77686->rtc;
+ info->irq = max77686->irq_base + MAX77686_RTCIRQ_RTCA1;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ info->irq2 = max77686->irq_base + MAX77686_RTCIRQ_RTCA2;
+#endif
+
+ platform_set_drvdata(pdev, info);
+
+ ret = max77686_rtc_init_reg(info);
+
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret);
+ goto err_rtc;
+ }
+
+#ifdef MAX77686_RTC_WTSR_SMPL
+ if (max77686->wtsr_smpl & MAX77686_WTSR_ENABLE)
+ max77686_rtc_enable_wtsr(info, true);
+ if (max77686->wtsr_smpl & MAX77686_SMPL_ENABLE)
+ max77686_rtc_enable_smpl(info, true);
+#endif
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ info->rtc_dev = rtc_device_register("max77686-rtc", &pdev->dev,
+ &max77686_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(info->rtc_dev)) {
+ printk(KERN_INFO "%s: fail\n", __func__);
+
+ ret = PTR_ERR(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ if (ret == 0)
+ ret = -EINVAL;
+ goto err_rtc;
+ }
+
+ ret = request_threaded_irq(info->irq, NULL, max77686_rtc_alarm_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0) {
+ rtc_device_unregister(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+ info->irq, ret);
+ goto err_rtc;
+ }
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ ret = request_threaded_irq(info->irq2, NULL, max77686_rtc_alarm2_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0) {
+ rtc_device_unregister(info->rtc_dev);
+ free_irq(info->irq, info);
+ dev_err(&pdev->dev, "Failed to request alarm2 IRQ: %d: %d\n",
+ info->irq2, ret);
+ goto err_rtc;
+ }
+#endif
+
+ goto out;
+err_rtc:
+ kfree(info);
+ return ret;
+out:
+ return ret;
+}
+
+static int __devexit max77686_rtc_remove(struct platform_device *pdev)
+{
+ struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ free_irq(info->irq, info);
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ free_irq(info->irq2, info);
+#endif
+ rtc_device_unregister(info->rtc_dev);
+ kfree(info);
+ }
+
+ return 0;
+}
+
+static void max77686_rtc_shutdown(struct platform_device *pdev)
+{
+#ifdef MAX77686_RTC_WTSR_SMPL
+ struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+ int i;
+ u8 val = 0;
+
+ for (i = 0; i < 3; i++) {
+ max77686_rtc_enable_wtsr(info, false);
+ max77686_read_reg(info->rtc, MAX77686_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val);
+ if (val & WTSR_EN_MASK)
+ pr_emerg("%s: fail to disable WTSR\n", __func__);
+ else {
+ pr_info("%s: success to disable WTSR\n", __func__);
+ break;
+ }
+ }
+
+ /* Disable SMPL when power off */
+ max77686_rtc_enable_smpl(info, false);
+#endif /* MAX77686_RTC_WTSR_SMPL */
+}
+
+static const struct platform_device_id rtc_id[] = {
+ { "max77686-rtc", 0 },
+ {},
+};
+
+static struct platform_driver max77686_rtc_driver = {
+ .driver = {
+ .name = "max77686-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = max77686_rtc_probe,
+ .remove = __devexit_p(max77686_rtc_remove),
+ .shutdown = max77686_rtc_shutdown,
+ .id_table = rtc_id,
+};
+
+static int __init max77686_rtc_init(void)
+{
+ return platform_driver_register(&max77686_rtc_driver);
+}
+module_init(max77686_rtc_init);
+
+static void __exit max77686_rtc_exit(void)
+{
+ platform_driver_unregister(&max77686_rtc_driver);
+}
+module_exit(max77686_rtc_exit);
+
+MODULE_DESCRIPTION("Maxim MAX77686 RTC driver");
+MODULE_AUTHOR("<woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-max8997.c b/drivers/rtc/rtc-max8997.c
new file mode 100644
index 0000000..6727b0c
--- /dev/null
+++ b/drivers/rtc/rtc-max8997.c
@@ -0,0 +1,767 @@
+/*
+ * RTC driver for Maxim MAX8997
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * based on rtc-max8998.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max8997-private.h>
+#if defined(CONFIG_RTC_ALARM_BOOT)
+#include <linux/io.h>
+#include <linux/reboot.h>
+#include <mach/regs-clock.h>
+#endif
+
+#define MAX8997_RTC_CONTROLM 0x02
+#define MAX8997_RTC_CONTROL 0x03
+#define MAX8997_RTC_UPDATE1 0x04
+#define MAX8997_RTC_UPDATE2 0x05
+#define MAX8997_WTSR_SMPL_CNTL 0x06
+#define MAX8997_RTC_SEC 0x10
+#define MAX8997_RTC_MIN 0x11
+#define MAX8997_RTC_HOUR 0x12
+#define MAX8997_RTC_WEEKDAY 0x13
+#define MAX8997_RTC_MONTH 0x14
+#define MAX8997_RTC_YEAR 0x15
+#define MAX8997_RTC_DATE 0x16
+#define MAX8997_ALARM1_SEC 0x17
+#define MAX8997_ALARM1_MIN 0x18
+#define MAX8997_ALARM1_HOUR 0x19
+#define MAX8997_ALARM1_WEEKDAY 0x1a
+#define MAX8997_ALARM1_MONTH 0x1b
+#define MAX8997_ALARM1_YEAR 0x1c
+#define MAX8997_ALARM1_DATE 0x1d
+#define MAX8997_ALARM2_SEC 0x1e
+#define MAX8997_ALARM2_MIN 0x1f
+#define MAX8997_ALARM2_HOUR 0x20
+#define MAX8997_ALARM2_WEEKDAY 0x21
+#define MAX8997_ALARM2_MONTH 0x22
+#define MAX8997_ALARM2_YEAR 0x23
+#define MAX8997_ALARM2_DATE 0x24
+
+/* RTC Control Register */
+#define BCD_EN_SHIFT 0
+#define BCD_EN_MASK (1 << BCD_EN_SHIFT)
+#define MODEL24_SHIFT 1
+#define MODEL24_MASK (1 << MODEL24_SHIFT)
+/* RTC Update Register1 */
+#define RTC_UDR_SHIFT 0
+#define RTC_UDR_MASK (1 << RTC_UDR_SHIFT)
+/* WTSR and SMPL Register */
+#define WTSRT_SHIFT 0
+#define SMPLT_SHIFT 2
+#define WTSR_EN_SHIFT 6
+#define SMPL_EN_SHIFT 7
+#define WTSRT_MASK (3 << WTSRT_SHIFT)
+#define SMPLT_MASK (3 << SMPLT_SHIFT)
+#define WTSR_EN_MASK (1 << WTSR_EN_SHIFT)
+#define SMPL_EN_MASK (1 << SMPL_EN_SHIFT)
+/* RTC Hour register */
+#define HOUR_PM_SHIFT 6
+#define HOUR_PM_MASK (1 << HOUR_PM_SHIFT)
+/* RTC Alarm Enable */
+#define ALARM_ENABLE_SHIFT 7
+#define ALARM_ENABLE_MASK (1 << ALARM_ENABLE_SHIFT)
+
+enum {
+ RTC_SEC = 0,
+ RTC_MIN,
+ RTC_HOUR,
+ RTC_WEEKDAY,
+ RTC_MONTH,
+ RTC_YEAR,
+ RTC_DATE,
+ RTC_NR_TIME
+};
+
+struct max8997_rtc_info {
+ struct device *dev;
+ struct max8997_dev *max8997;
+ struct i2c_client *rtc;
+ struct rtc_device *rtc_dev;
+ struct mutex lock;
+ int irq;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ int irq2;
+#endif
+ int rtc_24hr_mode;
+};
+
+static inline int max8997_rtc_calculate_wday(u8 shifted)
+{
+ int counter = -1;
+ while (shifted) {
+ shifted >>= 1;
+ counter++;
+ }
+ return counter;
+}
+
+static void max8997_rtc_data_to_tm(u8 *data, struct rtc_time *tm,
+ int rtc_24hr_mode)
+{
+ tm->tm_sec = data[RTC_SEC] & 0x7f;
+ tm->tm_min = data[RTC_MIN] & 0x7f;
+ if (rtc_24hr_mode)
+ tm->tm_hour = data[RTC_HOUR] & 0x1f;
+ else {
+ tm->tm_hour = data[RTC_HOUR] & 0x0f;
+ if (data[RTC_HOUR] & HOUR_PM_MASK)
+ tm->tm_hour += 12;
+ }
+
+ tm->tm_wday = max8997_rtc_calculate_wday(data[RTC_WEEKDAY] & 0x7f);
+ tm->tm_mday = data[RTC_DATE] & 0x1f;
+ tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+ tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100;
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+}
+
+static int max8997_rtc_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = tm->tm_sec;
+ data[RTC_MIN] = tm->tm_min;
+ data[RTC_HOUR] = tm->tm_hour;
+ data[RTC_WEEKDAY] = 1 << tm->tm_wday;
+ data[RTC_DATE] = tm->tm_mday;
+ data[RTC_MONTH] = tm->tm_mon + 1;
+ data[RTC_YEAR] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0 ;
+
+ if (tm->tm_year < 100) {
+ pr_warn("%s: MAX8997 RTC cannot handle the year %d."
+ "Assume it's 2000.\n", __func__, 1900 + tm->tm_year);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static inline int max8997_rtc_set_update_reg(struct max8997_rtc_info *info)
+{
+ int ret;
+ u8 data = 1 << RTC_UDR_SHIFT;
+
+ ret = max8997_write_reg(info->rtc, MAX8997_RTC_UPDATE1, data);
+ if (ret < 0)
+ dev_err(info->dev, "%s: fail to write update reg(%d)\n",
+ __func__, ret);
+ else {
+ /* Minimum 16ms delay required before RTC update.
+ * Otherwise, we may read and update based on out-of-date
+ * value */
+ msleep(20);
+ }
+
+ return ret;
+}
+
+static int max8997_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = max8997_bulk_read(info->rtc, MAX8997_RTC_SEC, RTC_NR_TIME, data);
+ mutex_unlock(&info->lock);
+
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__,
+ ret);
+ return ret;
+ }
+
+ max8997_rtc_data_to_tm(data, tm, info->rtc_24hr_mode);
+
+ return rtc_valid_tm(tm);
+}
+
+static int max8997_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ ret = max8997_rtc_tm_to_data(tm, data);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&info->lock);
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_RTC_SEC, RTC_NR_TIME, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__,
+ ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int max8997_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ u8 val;
+ int i, ret;
+
+ mutex_lock(&info->lock);
+
+ ret = max8997_bulk_read(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n",
+ __func__, __LINE__, ret);
+ goto out;
+ }
+
+ max8997_rtc_data_to_tm(data, &alrm->time, info->rtc_24hr_mode);
+
+ alrm->enabled = 0;
+ for (i = 0; i < RTC_NR_TIME; i++) {
+ if (data[i] & ALARM_ENABLE_MASK) {
+ alrm->enabled = 1;
+ break;
+ }
+ }
+
+ alrm->pending = 0;
+ ret = max8997_read_reg(info->max8997->i2c, MAX8997_REG_STATUS1, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "%s:%d fail to read status1 reg(%d)\n",
+ __func__, __LINE__, ret);
+ goto out;
+ }
+
+ if (val & (1 << 4)) /* RTCA1 */
+ alrm->pending = 1;
+
+out:
+ mutex_unlock(&info->lock);
+ return 0;
+}
+
+static int max8997_rtc_stop_alarm(struct max8997_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret, i;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max8997_bulk_read(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ for (i = 0; i < RTC_NR_TIME; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+out:
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max8997_rtc_stop_alarm_boot(struct max8997_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret, i;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max8997_bulk_read(info->rtc, MAX8997_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ for (i = 0; i < RTC_NR_TIME; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+out:
+ return ret;
+}
+#endif
+
+static int max8997_rtc_start_alarm(struct max8997_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max8997_bulk_read(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT);
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+out:
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max8997_rtc_start_alarm_boot(struct max8997_rtc_info *info)
+{
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = max8997_bulk_read(info->rtc, MAX8997_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT);
+ data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT);
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT);
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+out:
+ return ret;
+}
+#endif
+
+static int max8997_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret;
+
+ ret = max8997_rtc_tm_to_data(&alrm->time, data);
+ if (ret < 0)
+ return ret;
+
+ dev_info(info->dev, "%s: %d-%02d-%02d %02d:%02d:%02d\n", __func__,
+ data[RTC_YEAR] + 2000, data[RTC_MONTH], data[RTC_DATE],
+ data[RTC_HOUR], data[RTC_MIN], data[RTC_SEC]);
+
+ mutex_lock(&info->lock);
+
+ ret = max8997_rtc_stop_alarm(info);
+ if (ret < 0)
+ goto out;
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM1_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = max8997_rtc_start_alarm(info);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static int max8997_rtc_set_alarm_boot(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[RTC_NR_TIME];
+ int ret = 0;
+
+ data[RTC_SEC] = alrm->time.tm_sec;
+ data[RTC_MIN] = alrm->time.tm_min;
+ data[RTC_HOUR] = alrm->time.tm_hour;
+ data[RTC_WEEKDAY] = 1 << alrm->time.tm_wday;
+ data[RTC_DATE] = alrm->time.tm_mday;
+ data[RTC_MONTH] = alrm->time.tm_mon + 1;
+ data[RTC_YEAR] =
+ alrm->time.tm_year > 100 ? (alrm->time.tm_year - 100) : 0;
+
+ mutex_lock(&info->lock);
+
+ ret = max8997_rtc_stop_alarm_boot(info);
+ if (ret < 0)
+ goto out;
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_ALARM2_SEC, RTC_NR_TIME,
+ data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = max8997_rtc_start_alarm_boot(info);
+
+out:
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+#endif
+
+static int max8997_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct max8997_rtc_info *info = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&info->lock);
+ if (enabled)
+ ret = max8997_rtc_start_alarm(info);
+ else
+ ret = max8997_rtc_stop_alarm(info);
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+
+static irqreturn_t max8997_rtc_alarm_irq(int irq, void *data)
+{
+ struct max8997_rtc_info *info = data;
+
+ dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+static irqreturn_t max8997_rtc_alarm2_irq(int irq, void *data)
+{
+ struct max8997_rtc_info *info = data;
+ int ret;
+ u8 val;
+
+ dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+ if (__raw_readl(S5P_INFORM2)) {
+ ret = max8997_read_reg(info->max8997->i2c,
+ MAX8997_REG_STATUS1, &val);
+ if (ret < 0) {
+ dev_err(info->dev,
+ "%s:%d fail to read status1 reg(%d)\n",
+ __func__, __LINE__, ret);
+ }
+ if (val & 0x20)
+ machine_restart(NULL);
+ }
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+#endif
+
+static const struct rtc_class_ops max8997_rtc_ops = {
+ .read_time = max8997_rtc_read_time,
+ .set_time = max8997_rtc_set_time,
+ .read_alarm = max8997_rtc_read_alarm,
+ .set_alarm = max8997_rtc_set_alarm,
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ .set_alarm_boot = max8997_rtc_set_alarm_boot,
+#endif
+ .alarm_irq_enable = max8997_rtc_alarm_irq_enable,
+};
+
+static void max8997_rtc_enable_wtsr(struct max8997_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = (1 << WTSR_EN_SHIFT) | (3 << WTSRT_SHIFT);
+ else
+ val = 0;
+
+ mask = WTSR_EN_MASK | WTSRT_MASK;
+
+ dev_info(info->dev, "%s: %s WTSR\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = max8997_update_reg(info->rtc, MAX8997_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ max8997_rtc_set_update_reg(info);
+}
+
+static void max8997_rtc_enable_smpl(struct max8997_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = (1 << SMPL_EN_SHIFT) | (0 << SMPLT_SHIFT);
+ else
+ val = 0;
+
+ mask = SMPL_EN_MASK | SMPLT_MASK;
+
+ dev_info(info->dev, "%s: %s SMPL\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = max8997_update_reg(info->rtc, MAX8997_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ max8997_rtc_set_update_reg(info);
+
+ val = 0;
+ max8997_read_reg(info->rtc, MAX8997_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val);
+}
+
+static int max8997_rtc_init_reg(struct max8997_rtc_info *info)
+{
+ u8 data[2];
+ int ret;
+
+ /* Set RTC control register : Binary mode, 24hour mdoe */
+ data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+ data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+
+ info->rtc_24hr_mode = 1;
+
+ ret = max8997_bulk_write(info->rtc, MAX8997_RTC_CONTROLM, 2, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write controlm reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = max8997_rtc_set_update_reg(info);
+ return ret;
+}
+
+static int __devinit max8997_rtc_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_rtc_info *info;
+ int ret;
+
+ info = kzalloc(sizeof(struct max8997_rtc_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ mutex_init(&info->lock);
+ info->dev = &pdev->dev;
+ info->max8997 = max8997;
+ info->rtc = max8997->rtc;
+ info->irq = max8997->irq_base + MAX8997_IRQ_RTCA1;
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ info->irq2 = max8997->irq_base + MAX8997_IRQ_RTCA2;
+#endif
+
+ platform_set_drvdata(pdev, info);
+
+ ret = max8997_rtc_init_reg(info);
+
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret);
+ goto err_rtc;
+ }
+
+ max8997_rtc_enable_wtsr(info, true);
+ max8997_rtc_enable_smpl(info, true);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ info->rtc_dev = rtc_device_register("max8997-rtc", &pdev->dev,
+ &max8997_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(info->rtc_dev)) {
+ ret = PTR_ERR(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ if (ret == 0)
+ ret = -EINVAL;
+ goto err_rtc;
+ }
+
+ ret = request_threaded_irq(info->irq, NULL, max8997_rtc_alarm_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+ info->irq, ret);
+ goto err_rtc;
+ }
+
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ ret = request_threaded_irq(info->irq2, NULL, max8997_rtc_alarm2_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request alarm2 IRQ: %d: %d\n",
+ info->irq2, ret);
+ goto err_rtc;
+ }
+#endif
+
+ goto out;
+err_rtc:
+ kfree(info);
+ return ret;
+out:
+ return ret;
+}
+
+static int __devexit max8997_rtc_remove(struct platform_device *pdev)
+{
+ struct max8997_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ free_irq(info->irq, info);
+#if defined(CONFIG_RTC_ALARM_BOOT)
+ free_irq(info->irq2, info);
+#endif
+ rtc_device_unregister(info->rtc_dev);
+ kfree(info);
+ }
+
+ return 0;
+}
+
+static void max8997_rtc_shutdown(struct platform_device *pdev)
+{
+ struct max8997_rtc_info *info = platform_get_drvdata(pdev);
+ int i;
+ u8 val = 0;
+
+ for (i = 0; i < 3; i++) {
+ max8997_rtc_enable_wtsr(info, false);
+ max8997_read_reg(info->rtc, MAX8997_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val);
+ if (val & WTSR_EN_MASK)
+ pr_emerg("%s: fail to disable WTSR\n", __func__);
+ else {
+ pr_info("%s: success to disable WTSR\n", __func__);
+ break;
+ }
+ }
+}
+
+static const struct platform_device_id rtc_id[] = {
+ { "max8997-rtc", 0 },
+ {},
+};
+
+static struct platform_driver max8997_rtc_driver = {
+ .driver = {
+ .name = "max8997-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = max8997_rtc_probe,
+ .remove = __devexit_p(max8997_rtc_remove),
+ .shutdown = max8997_rtc_shutdown,
+ .id_table = rtc_id,
+};
+
+static int __init max8997_rtc_init(void)
+{
+ return platform_driver_register(&max8997_rtc_driver);
+}
+module_init(max8997_rtc_init);
+
+static void __exit max8997_rtc_exit(void)
+{
+ platform_driver_unregister(&max8997_rtc_driver);
+}
+module_exit(max8997_rtc_exit);
+
+MODULE_DESCRIPTION("Maxim MAX8997 RTC driver");
+MODULE_AUTHOR("<ms925.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-s3c.c b/drivers/rtc/rtc-s3c.c
index 16512ec..8f49de7 100644
--- a/drivers/rtc/rtc-s3c.c
+++ b/drivers/rtc/rtc-s3c.c
@@ -27,14 +27,15 @@
#include <linux/slab.h>
#include <mach/hardware.h>
-#include <asm/uaccess.h>
-#include <asm/io.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
#include <asm/irq.h>
#include <plat/regs-rtc.h>
enum s3c_cpu_type {
TYPE_S3C2410,
TYPE_S3C64XX,
+ TYPE_EXYNOS,
};
/* I have yet to find an S3C implementation with more than one
@@ -45,12 +46,9 @@ static struct resource *s3c_rtc_mem;
static struct clk *rtc_clk;
static void __iomem *s3c_rtc_base;
static int s3c_rtc_alarmno = NO_IRQ;
-static int s3c_rtc_tickno = NO_IRQ;
static bool wake_en;
static enum s3c_cpu_type s3c_rtc_cpu_type;
-static DEFINE_SPINLOCK(s3c_rtc_pie_lock);
-
/* IRQ Handlers */
static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
@@ -59,24 +57,12 @@ static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
- if (s3c_rtc_cpu_type == TYPE_S3C64XX)
+ if (s3c_rtc_cpu_type != TYPE_S3C2410)
writeb(S3C2410_INTP_ALM, s3c_rtc_base + S3C2410_INTP);
return IRQ_HANDLED;
}
-static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
-{
- struct rtc_device *rdev = id;
-
- rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);
-
- if (s3c_rtc_cpu_type == TYPE_S3C64XX)
- writeb(S3C2410_INTP_TIC, s3c_rtc_base + S3C2410_INTP);
-
- return IRQ_HANDLED;
-}
-
/* Update control registers */
static int s3c_rtc_setaie(struct device *dev, unsigned int enabled)
{
@@ -94,30 +80,6 @@ static int s3c_rtc_setaie(struct device *dev, unsigned int enabled)
return 0;
}
-static int s3c_rtc_setfreq(struct device *dev, int freq)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
- unsigned int tmp = 0;
-
- if (!is_power_of_2(freq))
- return -EINVAL;
-
- spin_lock_irq(&s3c_rtc_pie_lock);
-
- if (s3c_rtc_cpu_type == TYPE_S3C2410) {
- tmp = readb(s3c_rtc_base + S3C2410_TICNT);
- tmp &= S3C2410_TICNT_ENABLE;
- }
-
- tmp |= (rtc_dev->max_user_freq / freq)-1;
-
- writel(tmp, s3c_rtc_base + S3C2410_TICNT);
- spin_unlock_irq(&s3c_rtc_pie_lock);
-
- return 0;
-}
-
/* Time read/write */
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
@@ -130,7 +92,12 @@ static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
rtc_tm->tm_mon = readb(base + S3C2410_RTCMON);
- rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS)
+ rtc_tm->tm_year = readw(base + S3C2410_RTCYEAR) & 0x0fff;
+ else
+ rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
+
rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC);
/* the only way to work out wether the system was mid-update
@@ -143,18 +110,24 @@ static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
goto retry_get_time;
}
- pr_debug("read time %04d.%02d.%02d %02d:%02d:%02d\n",
- 1900 + rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
- rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
-
rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
- rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS)
+ rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year)
+ + (rtc_tm->tm_year >> 8) * 100;
+ else
+ rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);
rtc_tm->tm_year += 100;
+
+ pr_debug("read time %04d.%02d.%02d %02d:%02d:%02d\n",
+ 1900 + rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
+ rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
+
rtc_tm->tm_mon -= 1;
return rtc_valid_tm(rtc_tm);
@@ -166,14 +139,16 @@ static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
int year = tm->tm_year - 100;
pr_debug("set time %04d.%02d.%02d %02d:%02d:%02d\n",
- 1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
- /* we get around y2k by simply not supporting it */
-
if (year < 0 || year >= 100) {
- dev_err(dev, "rtc only supports 100 years\n");
- return -EINVAL;
+ if (!(s3c_rtc_cpu_type == TYPE_EXYNOS
+ && year >= 100 && year < 1000)) {
+ dev_err(dev, "rtc can't support %04d year\n",
+ year + 2000);
+ return -EINVAL;
+ }
}
writeb(bin2bcd(tm->tm_sec), base + S3C2410_RTCSEC);
@@ -181,7 +156,13 @@ static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR);
writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE);
writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON);
- writeb(bin2bcd(year), base + S3C2410_RTCYEAR);
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS) {
+ year = (((year / 100) << 8) + (((year % 100) / 10) << 4) + year % 10);
+ writew(year, base + S3C2410_RTCYEAR);
+ } else {
+ writeb(bin2bcd(year), base + S3C2410_RTCYEAR);
+ }
return 0;
}
@@ -197,7 +178,13 @@ static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
alm_tm->tm_hour = readb(base + S3C2410_ALMHOUR);
alm_tm->tm_mon = readb(base + S3C2410_ALMMON);
alm_tm->tm_mday = readb(base + S3C2410_ALMDATE);
- alm_tm->tm_year = readb(base + S3C2410_ALMYEAR);
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS)
+ alm_tm->tm_year = readw(base + S3C2410_ALMYEAR) & 0xfff;
+ else
+ alm_tm->tm_year = readb(base + S3C2410_ALMYEAR);
+
+ alm_tm->tm_year += 100;
alm_en = readb(base + S3C2410_RTCALM);
@@ -211,37 +198,27 @@ static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
/* decode the alarm enable field */
- if (alm_en & S3C2410_RTCALM_SECEN)
+ if (alrm->enabled) {
alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec);
- else
- alm_tm->tm_sec = -1;
-
- if (alm_en & S3C2410_RTCALM_MINEN)
alm_tm->tm_min = bcd2bin(alm_tm->tm_min);
- else
- alm_tm->tm_min = -1;
-
- if (alm_en & S3C2410_RTCALM_HOUREN)
alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);
- else
- alm_tm->tm_hour = -1;
-
- if (alm_en & S3C2410_RTCALM_DAYEN)
alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);
- else
- alm_tm->tm_mday = -1;
+ alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon) - 1;
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS)
+ alm_tm->tm_year = bcd2bin(alm_tm->tm_year)
+ + (alm_tm->tm_year >> 8) * 100;
+ else
+ alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
- if (alm_en & S3C2410_RTCALM_MONEN) {
- alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon);
- alm_tm->tm_mon -= 1;
} else {
+ alm_tm->tm_sec = -1;
+ alm_tm->tm_min = -1;
+ alm_tm->tm_hour = -1;
+ alm_tm->tm_mday = -1;
alm_tm->tm_mon = -1;
- }
-
- if (alm_en & S3C2410_RTCALM_YEAREN)
- alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
- else
alm_tm->tm_year = -1;
+ }
return 0;
}
@@ -251,104 +228,56 @@ static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
struct rtc_time *tm = &alrm->time;
void __iomem *base = s3c_rtc_base;
unsigned int alrm_en;
+ int year = tm->tm_year - 100;
pr_debug("s3c_rtc_setalarm: %d, %04d.%02d.%02d %02d:%02d:%02d\n",
alrm->enabled,
- 1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
+ if (year < 0 || year >= 100) {
+ if (!(s3c_rtc_cpu_type == TYPE_EXYNOS
+ && year >= 100 && year < 1000)) {
+ dev_err(dev, "rtc can't support %04d year\n",
+ 2000 + year);
+ return -EINVAL;
+ }
+ }
alrm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
- writeb(0x00, base + S3C2410_RTCALM);
- if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
- alrm_en |= S3C2410_RTCALM_SECEN;
- writeb(bin2bcd(tm->tm_sec), base + S3C2410_ALMSEC);
- }
+ if (alrm->enabled) {
+ writeb(~S3C2410_RTCALM_ALL, base + S3C2410_RTCALM);
- if (tm->tm_min < 60 && tm->tm_min >= 0) {
- alrm_en |= S3C2410_RTCALM_MINEN;
+ writeb(bin2bcd(tm->tm_sec), base + S3C2410_ALMSEC);
writeb(bin2bcd(tm->tm_min), base + S3C2410_ALMMIN);
- }
-
- if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
- alrm_en |= S3C2410_RTCALM_HOUREN;
writeb(bin2bcd(tm->tm_hour), base + S3C2410_ALMHOUR);
- }
-
- pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
-
- writeb(alrm_en, base + S3C2410_RTCALM);
-
- s3c_rtc_setaie(dev, alrm->enabled);
-
- return 0;
-}
-
-static int s3c_rtc_proc(struct device *dev, struct seq_file *seq)
-{
- unsigned int ticnt;
-
- if (s3c_rtc_cpu_type == TYPE_S3C64XX) {
- ticnt = readw(s3c_rtc_base + S3C2410_RTCCON);
- ticnt &= S3C64XX_RTCCON_TICEN;
- } else {
- ticnt = readb(s3c_rtc_base + S3C2410_TICNT);
- ticnt &= S3C2410_TICNT_ENABLE;
- }
-
- seq_printf(seq, "periodic_IRQ\t: %s\n", ticnt ? "yes" : "no");
- return 0;
-}
-
-static int s3c_rtc_open(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
- int ret;
-
- ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,
- IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev);
-
- if (ret) {
- dev_err(dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret);
- return ret;
- }
+ writeb(bin2bcd(tm->tm_mday), base + S3C2410_ALMDATE);
+ writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_ALMMON);
+
+ if (s3c_rtc_cpu_type == TYPE_EXYNOS) {
+ year = (((year / 100) << 8) + (((year % 100) / 10) << 4) + year % 10);
+ writew(year, base + S3C2410_ALMYEAR);
+ } else {
+ writeb(bin2bcd(year), base + S3C2410_ALMYEAR);
+ }
- ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
- IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev);
+ writeb(S3C2410_RTCALM_ALL, base + S3C2410_RTCALM);
- if (ret) {
- dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
- goto tick_err;
+ pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
}
- return ret;
-
- tick_err:
- free_irq(s3c_rtc_alarmno, rtc_dev);
- return ret;
-}
-
-static void s3c_rtc_release(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
-
- /* do not clear AIE here, it may be needed for wake */
+ if (alrm->enabled != alrm_en)
+ s3c_rtc_setaie(dev, alrm->enabled);
- free_irq(s3c_rtc_alarmno, rtc_dev);
- free_irq(s3c_rtc_tickno, rtc_dev);
+ return 0;
}
static const struct rtc_class_ops s3c_rtcops = {
- .open = s3c_rtc_open,
- .release = s3c_rtc_release,
.read_time = s3c_rtc_gettime,
.set_time = s3c_rtc_settime,
.read_alarm = s3c_rtc_getalarm,
.set_alarm = s3c_rtc_setalarm,
- .proc = s3c_rtc_proc,
.alarm_irq_enable = s3c_rtc_setaie,
};
@@ -362,7 +291,7 @@ static void s3c_rtc_enable(struct platform_device *pdev, int en)
if (!en) {
tmp = readw(base + S3C2410_RTCCON);
- if (s3c_rtc_cpu_type == TYPE_S3C64XX)
+ if (s3c_rtc_cpu_type != TYPE_S3C2410)
tmp &= ~S3C64XX_RTCCON_TICEN;
tmp &= ~S3C2410_RTCCON_RTCEN;
writew(tmp, base + S3C2410_RTCCON);
@@ -405,6 +334,8 @@ static int __devexit s3c_rtc_remove(struct platform_device *dev)
{
struct rtc_device *rtc = platform_get_drvdata(dev);
+ free_irq(s3c_rtc_alarmno, rtc);
+
platform_set_drvdata(dev, NULL);
rtc_device_unregister(rtc);
@@ -432,20 +363,13 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
/* find the IRQs */
- s3c_rtc_tickno = platform_get_irq(pdev, 1);
- if (s3c_rtc_tickno < 0) {
- dev_err(&pdev->dev, "no irq for rtc tick\n");
- return -ENOENT;
- }
-
s3c_rtc_alarmno = platform_get_irq(pdev, 0);
if (s3c_rtc_alarmno < 0) {
dev_err(&pdev->dev, "no irq for alarm\n");
return -ENOENT;
}
- pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
- s3c_rtc_tickno, s3c_rtc_alarmno);
+ pr_debug("s3c2410_rtc: alarm irq %d\n", s3c_rtc_alarmno);
/* get the memory region */
@@ -456,7 +380,7 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
}
s3c_rtc_mem = request_mem_region(res->start,
- res->end-res->start+1,
+ resource_size(res),
pdev->name);
if (s3c_rtc_mem == NULL) {
@@ -465,7 +389,7 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
goto err_nores;
}
- s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
+ s3c_rtc_base = ioremap(res->start, resource_size(res));
if (s3c_rtc_base == NULL) {
dev_err(&pdev->dev, "failed ioremap()\n");
ret = -EINVAL;
@@ -491,21 +415,10 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, 1);
- /* register RTC and exit */
-
- rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
- THIS_MODULE);
-
- if (IS_ERR(rtc)) {
- dev_err(&pdev->dev, "cannot attach rtc\n");
- ret = PTR_ERR(rtc);
- goto err_nortc;
- }
+ /* Check RTC Time */
s3c_rtc_cpu_type = platform_get_device_id(pdev)->driver_data;
- /* Check RTC Time */
-
s3c_rtc_gettime(NULL, &rtc_tm);
if (rtc_valid_tm(&rtc_tm)) {
@@ -521,17 +434,36 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "warning: invalid RTC value so initializing it\n");
}
- if (s3c_rtc_cpu_type == TYPE_S3C64XX)
+ /* register RTC and exit */
+
+ rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
+ THIS_MODULE);
+
+ if (IS_ERR(rtc)) {
+ dev_err(&pdev->dev, "cannot attach rtc\n");
+ ret = PTR_ERR(rtc);
+ goto err_nortc;
+ }
+
+ if (s3c_rtc_cpu_type != TYPE_S3C2410)
rtc->max_user_freq = 32768;
else
rtc->max_user_freq = 128;
platform_set_drvdata(pdev, rtc);
- s3c_rtc_setfreq(&pdev->dev, 1);
+ ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,
+ IRQF_DISABLED, "s3c2410-rtc alarm", rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret);
+ goto err_irq;
+ }
return 0;
+ err_irq:
+ rtc_device_unregister(rtc);
+
err_nortc:
s3c_rtc_enable(pdev, 0);
clk_disable(rtc_clk);
@@ -541,6 +473,7 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
iounmap(s3c_rtc_base);
err_nomap:
+ release_mem_region(res->start, resource_size(res));
release_resource(s3c_rtc_mem);
err_nores:
@@ -551,16 +484,8 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev)
/* RTC Power management control */
-static int ticnt_save, ticnt_en_save;
-
static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
- /* save TICNT for anyone using periodic interrupts */
- ticnt_save = readb(s3c_rtc_base + S3C2410_TICNT);
- if (s3c_rtc_cpu_type == TYPE_S3C64XX) {
- ticnt_en_save = readw(s3c_rtc_base + S3C2410_RTCCON);
- ticnt_en_save &= S3C64XX_RTCCON_TICEN;
- }
s3c_rtc_enable(pdev, 0);
if (device_may_wakeup(&pdev->dev) && !wake_en) {
@@ -575,14 +500,7 @@ static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state)
static int s3c_rtc_resume(struct platform_device *pdev)
{
- unsigned int tmp;
-
s3c_rtc_enable(pdev, 1);
- writeb(ticnt_save, s3c_rtc_base + S3C2410_TICNT);
- if (s3c_rtc_cpu_type == TYPE_S3C64XX && ticnt_en_save) {
- tmp = readw(s3c_rtc_base + S3C2410_RTCCON);
- writew(tmp | ticnt_en_save, s3c_rtc_base + S3C2410_RTCCON);
- }
if (device_may_wakeup(&pdev->dev) && wake_en) {
disable_irq_wake(s3c_rtc_alarmno);
@@ -603,6 +521,9 @@ static struct platform_device_id s3c_rtc_driver_ids[] = {
}, {
.name = "s3c64xx-rtc",
.driver_data = TYPE_S3C64XX,
+ }, {
+ .name = "exynos-rtc",
+ .driver_data = TYPE_EXYNOS,
},
{ }
};
diff --git a/drivers/rtc/rtc-s5m.c b/drivers/rtc/rtc-s5m.c
new file mode 100644
index 0000000..22c4ff7
--- /dev/null
+++ b/drivers/rtc/rtc-s5m.c
@@ -0,0 +1,706 @@
+/*
+ * rtc-s5m.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd
+ * http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/s5m87xx/s5m-core.h>
+#include <linux/mfd/s5m87xx/s5m-rtc.h>
+
+struct s5m_rtc_info {
+ struct device *dev;
+ struct s5m87xx_dev *s5m87xx;
+ struct i2c_client *rtc;
+ struct rtc_device *rtc_dev;
+ struct mutex lock;
+ int irq;
+ int device_type;
+ int rtc_24hr_mode;
+ bool wtsr_smpl;
+};
+
+static inline int s5m8767_rtc_calculate_wday(u8 shifted)
+{
+ int counter = -1;
+ while (shifted) {
+ shifted >>= 1;
+ counter++;
+ }
+ return counter;
+}
+
+static void s5m8767_data_to_tm(u8 *data, struct rtc_time *tm,
+ int rtc_24hr_mode)
+{
+ tm->tm_sec = data[RTC_SEC] & 0x7f;
+ tm->tm_min = data[RTC_MIN] & 0x7f;
+ if (rtc_24hr_mode)
+ tm->tm_hour = data[RTC_HOUR] & 0x1f;
+ else {
+ tm->tm_hour = data[RTC_HOUR] & 0x0f;
+ if (data[RTC_HOUR] & HOUR_PM_MASK)
+ tm->tm_hour += 12;
+ }
+
+ tm->tm_wday = s5m8767_rtc_calculate_wday(data[RTC_WEEKDAY] & 0x7f);
+ tm->tm_mday = data[RTC_DATE] & 0x1f;
+ tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+ tm->tm_year = (data[RTC_YEAR1] & 0x7f) + (bcd2bin(data[RTC_YEAR2]) * 100);
+ tm->tm_year -= 1900;
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+}
+
+static void s5m8767_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = tm->tm_sec;
+ data[RTC_MIN] = tm->tm_min;
+
+ if (tm->tm_hour >= 12)
+ data[RTC_HOUR] = tm->tm_hour | HOUR_PM_MASK;
+ else
+ data[RTC_HOUR] = tm->tm_hour & ~HOUR_PM_MASK;
+
+ data[RTC_WEEKDAY] = 1 << tm->tm_wday;
+ data[RTC_DATE] = tm->tm_mday;
+ data[RTC_MONTH] = tm->tm_mon + 1;
+ data[RTC_YEAR1] = tm->tm_year % 100;
+ data[RTC_YEAR2] = bin2bcd((tm->tm_year + 1900) / 100);
+}
+
+static inline int s5m8767_rtc_set_time_reg(struct s5m_rtc_info *info)
+{
+ int ret;
+ u8 data;
+
+ ret = s5m_reg_read(info->rtc, S5M87XX_RTC_UDR_CON, &data);
+ if (ret < 0)
+ return ret;
+
+ data |= RTC_TIME_EN_MASK;
+ data |= RTC_UDR_MASK;
+
+ ret = s5m_reg_write(info->rtc, S5M87XX_RTC_UDR_CON, data);
+ if (ret < 0)
+ dev_err(info->dev, "%s: fail to write update reg(%d)\n",
+ __func__, ret);
+ else
+ msleep(20);
+
+ return ret;
+}
+
+static inline int s5m8767_rtc_set_alarm_reg(struct s5m_rtc_info *info)
+{
+ int ret;
+ u8 data;
+
+ ret = s5m_reg_read(info->rtc, S5M87XX_RTC_UDR_CON, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= ~RTC_TIME_EN_MASK;
+ data |= RTC_UDR_MASK;
+
+ ret = s5m_reg_write(info->rtc, S5M87XX_RTC_UDR_CON, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write update reg(%d)\n",
+ __func__, ret);
+ } else {
+ msleep(20);
+ }
+
+ return ret;
+}
+
+static void s5m8763_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ tm->tm_sec = bcd2bin(data[RTC_SEC]);
+ tm->tm_min = bcd2bin(data[RTC_MIN]);
+
+ if (data[RTC_HOUR] & HOUR_12) {
+ tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x1f);
+ if (data[RTC_HOUR] & HOUR_PM)
+ tm->tm_hour += 12;
+ } else
+ tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f);
+
+ tm->tm_wday = data[RTC_WEEKDAY] & 0x07;
+ tm->tm_mday = bcd2bin(data[RTC_DATE]);
+ tm->tm_mon = bcd2bin(data[RTC_MONTH]);
+ tm->tm_year = bcd2bin(data[RTC_YEAR1]) + bcd2bin(data[RTC_YEAR2]) * 100;
+ tm->tm_year -= 1900;
+}
+
+static void s5m8763_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = bin2bcd(tm->tm_sec);
+ data[RTC_MIN] = bin2bcd(tm->tm_min);
+ data[RTC_HOUR] = bin2bcd(tm->tm_hour);
+ data[RTC_WEEKDAY] = tm->tm_wday;
+ data[RTC_DATE] = bin2bcd(tm->tm_mday);
+ data[RTC_MONTH] = bin2bcd(tm->tm_mon);
+ data[RTC_YEAR1] = bin2bcd(tm->tm_year % 100);
+ data[RTC_YEAR2] = bin2bcd((tm->tm_year + 1900) / 100);
+}
+
+static int s5m_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ mutex_lock(&info->lock);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_RTC_SEC, 8, data);
+ if (ret < 0)
+ goto out;
+
+ switch (info->device_type) {
+ case S5M8763X:
+ s5m8763_data_to_tm(data, tm);
+ break;
+
+ case S5M8767X:
+ s5m8767_data_to_tm(data, tm, info->rtc_24hr_mode);
+ break;
+
+ default:
+ ret = (-EINVAL);
+ goto out;
+ }
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+out:
+ mutex_unlock(&info->lock);
+ if (ret >= 0)
+ ret = rtc_valid_tm(tm);
+ return ret;
+}
+
+static int s5m_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ switch (info->device_type) {
+ case S5M8763X:
+ s5m8763_tm_to_data(tm, data);
+ break;
+ case S5M8767X:
+ s5m8767_tm_to_data(tm, data);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_RTC_SEC, 8, data);
+ if (ret < 0)
+ goto out;
+
+ ret = s5m8767_rtc_set_time_reg(info);
+
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int s5m_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ u8 val;
+ int ret, i;
+
+ mutex_lock(&info->lock);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ goto out;
+
+ switch (info->device_type) {
+ case S5M8763X:
+ s5m8763_data_to_tm(data, &alrm->time);
+ ret = s5m_reg_read(info->rtc, S5M87XX_ALARM0_CONF, &val);
+ if (ret < 0)
+ goto out;
+
+ alrm->enabled = !!val;
+
+ ret = s5m_reg_read(info->rtc, S5M87XX_RTC_STATUS, &val);
+ if (ret < 0)
+ goto out;
+
+ break;
+
+ case S5M8767X:
+ s5m8767_data_to_tm(data, &alrm->time, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + alrm->time.tm_year, 1 + alrm->time.tm_mon,
+ alrm->time.tm_mday, alrm->time.tm_hour,
+ alrm->time.tm_min, alrm->time.tm_sec,
+ alrm->time.tm_wday);
+
+ alrm->enabled = 0;
+ for (i = 0; i < 7; i++) {
+ if (data[i] & ALARM_ENABLE_MASK) {
+ alrm->enabled = 1;
+ break;
+ }
+ }
+
+ alrm->pending = 0;
+ ret = s5m_reg_read(info->rtc, S5M87XX_RTC_STATUS, &val);
+ if (ret < 0)
+ goto out;
+ break;
+
+ default:
+ ret = (-EINVAL);
+ goto out;
+ }
+
+ if (val & ALARM0_STATUS)
+ alrm->pending = 1;
+ else
+ alrm->pending = 0;
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int s5m_rtc_stop_alarm(struct s5m_rtc_info *info)
+{
+ u8 data[8];
+ int ret, i;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ switch (info->device_type) {
+ case S5M8763X:
+ ret = s5m_reg_write(info->rtc, S5M87XX_ALARM0_CONF, 0);
+ break;
+
+ case S5M8767X:
+ for (i = 0; i < 7; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret <0)
+ return ret;
+
+ ret = s5m8767_rtc_set_alarm_reg(info);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int s5m_rtc_start_alarm(struct s5m_rtc_info *info)
+{
+ int ret;
+ u8 data[8];
+ u8 alarm0_conf;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ switch (info->device_type) {
+ case S5M8763X:
+ alarm0_conf = 0x77;
+ ret = s5m_reg_write(info->rtc, S5M87XX_ALARM0_CONF, alarm0_conf);
+ break;
+
+ case S5M8767X:
+ data[RTC_SEC] |= ALARM_ENABLE_MASK;
+ data[RTC_MIN] |= ALARM_ENABLE_MASK;
+ data[RTC_HOUR] |= ALARM_ENABLE_MASK;
+ data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= ALARM_ENABLE_MASK;
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= ALARM_ENABLE_MASK;
+ if (data[RTC_YEAR1] & 0x7f)
+ data[RTC_YEAR1] |= ALARM_ENABLE_MASK;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+ ret = s5m8767_rtc_set_alarm_reg(info);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int s5m_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ switch (info->device_type) {
+ case S5M8763X:
+ s5m8763_tm_to_data(&alrm->time, data);
+ break;
+
+ case S5M8767X:
+ s5m8767_tm_to_data(&alrm->time, data);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + alrm->time.tm_year, 1 + alrm->time.tm_mon,
+ alrm->time.tm_mday, alrm->time.tm_hour, alrm->time.tm_min,
+ alrm->time.tm_sec, alrm->time.tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = s5m_rtc_stop_alarm(info);
+ if (ret < 0)
+ goto out;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
+ if (ret < 0)
+ goto out;
+
+ ret = s5m8767_rtc_set_alarm_reg(info);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = s5m_rtc_start_alarm(info);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int s5m_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&info->lock);
+ if (enabled) {
+ ret = s5m_rtc_start_alarm(info);
+ } else {
+ ret = s5m_rtc_stop_alarm(info);
+ }
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+
+static irqreturn_t s5m_rtc_alarm_irq(int irq, void *data)
+{
+ struct s5m_rtc_info *info = data;
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops s5m_rtc_ops = {
+ .read_time = s5m_rtc_read_time,
+ .set_time = s5m_rtc_set_time,
+ .read_alarm = s5m_rtc_read_alarm,
+ .set_alarm = s5m_rtc_set_alarm,
+ .alarm_irq_enable = s5m_rtc_alarm_irq_enable,
+};
+
+static void s5m_rtc_enable_wtsr(struct s5m_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = WTSR_ENABLE_MASK;
+ else
+ val = 0;
+
+ mask = WTSR_ENABLE_MASK;
+
+ dev_info(info->dev, "%s: %s WTSR\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = s5m_reg_update(info->rtc, S5M87XX_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+}
+
+static void s5m_rtc_enable_smpl(struct s5m_rtc_info *info, bool enable)
+{
+ int ret;
+ u8 val, mask;
+
+ if (enable)
+ val = SMPL_ENABLE_MASK;
+ else
+ val = 0;
+
+ mask = SMPL_ENABLE_MASK;
+
+ dev_info(info->dev, "%s: %s SMPL\n", __func__,
+ enable ? "enable" : "disable");
+
+ ret = s5m_reg_update(info->rtc, S5M87XX_WTSR_SMPL_CNTL, val, mask);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n",
+ __func__, ret);
+ return;
+ }
+
+ val = 0;
+ s5m_reg_read(info->rtc, S5M87XX_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val);
+}
+
+static int s5m8767_rtc_init_reg(struct s5m_rtc_info *info)
+{
+ u8 data[2], tp_read;
+ int ret;
+ struct rtc_time tm;
+
+ ret = s5m_reg_read(info->rtc, S5M87XX_RTC_UDR_CON, &tp_read);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read control reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Set RTC control register : Binary mode, 24hour mdoe */
+ data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+ data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+
+ info->rtc_24hr_mode = 1;
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM0_CONF, 2, data);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to write controlm reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* In first boot time, Set rtc time to 1/1/2012 00:00:00(SUN) */
+ if ((tp_read & RTC_TCON_MASK) == 0) {
+ dev_info(info->dev, "rtc init\n");
+ tm.tm_sec = 0;
+ tm.tm_min = 0;
+ tm.tm_hour = 0;
+ tm.tm_wday = 0;
+ tm.tm_mday = 1;
+ tm.tm_mon = 0;
+ tm.tm_year = 112;
+ tm.tm_yday = 0;
+ tm.tm_isdst = 0;
+ ret = s5m_rtc_set_time(info->dev, &tm);
+ }
+
+ ret = s5m_reg_update(info->rtc, S5M87XX_RTC_UDR_CON,
+ tp_read | RTC_TCON_MASK, RTC_TCON_MASK);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to update TCON reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int __devinit s5m_rtc_probe(struct platform_device *pdev)
+{
+ struct s5m87xx_dev *s5m87xx = dev_get_drvdata(pdev->dev.parent);
+ struct s5m_platform_data *pdata = dev_get_platdata(s5m87xx->dev);
+ struct s5m_rtc_info *info;
+ int ret;
+
+ info = kzalloc(sizeof(struct s5m_rtc_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ mutex_init(&info->lock);
+ info->dev = &pdev->dev;
+ info->s5m87xx = s5m87xx;
+ info->rtc = s5m87xx->rtc;
+ info->device_type = s5m87xx->device_type;
+ info->wtsr_smpl = s5m87xx->wtsr_smpl;
+
+ switch (pdata->device_type) {
+ case S5M8763X:
+ info->irq = s5m87xx->irq_base + S5M8763_IRQ_ALARM0;
+ break;
+
+ case S5M8767X:
+ info->irq = s5m87xx->irq_base + S5M8767_IRQ_RTCA1;
+ break;
+
+ default:
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Unsupported device type: %d\n", ret);
+ goto out_rtc;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ ret = s5m8767_rtc_init_reg(info);
+
+ if (info->wtsr_smpl) {
+ s5m_rtc_enable_wtsr(info, true);
+ s5m_rtc_enable_smpl(info, true);
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ info->rtc_dev = rtc_device_register("s5m-rtc", &pdev->dev,
+ &s5m_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(info->rtc_dev)) {
+ ret = PTR_ERR(info->rtc_dev);
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ goto out_rtc;
+ }
+
+ ret = request_threaded_irq(info->irq, NULL, s5m_rtc_alarm_irq, 0,
+ "rtc-alarm0", info);
+
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+ info->irq, ret);
+
+ dev_info(&pdev->dev, "RTC CHIP NAME: %s\n", pdev->id_entry->name);
+
+ return 0;
+
+out_rtc:
+ platform_set_drvdata(pdev, NULL);
+ kfree(info);
+ return ret;
+}
+
+static int __devexit s5m_rtc_remove(struct platform_device *pdev)
+{
+ struct s5m_rtc_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ free_irq(info->irq, info);
+ rtc_device_unregister(info->rtc_dev);
+ kfree(info);
+ }
+
+ return 0;
+}
+
+static void s5m_rtc_shutdown(struct platform_device *pdev)
+{
+ struct s5m_rtc_info *info = platform_get_drvdata(pdev);
+ int i;
+ u8 val = 0;
+ if (info->wtsr_smpl) {
+ for (i = 0; i < 3; i++) {
+ s5m_rtc_enable_wtsr(info, false);
+ s5m_reg_read(info->rtc, S5M87XX_WTSR_SMPL_CNTL, &val);
+ pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val);
+ if (val & WTSR_ENABLE_MASK)
+ pr_emerg("%s: fail to disable WTSR\n", __func__);
+ else {
+ pr_info("%s: success to disable WTSR\n", __func__);
+ break;
+ }
+ }
+ }
+ /* Disable SMPL when power off */
+ s5m_rtc_enable_smpl(info, false);
+}
+
+static const struct platform_device_id s5m_rtc_id[] = {
+ { "s5m-rtc", 0 },
+};
+
+static struct platform_driver s5m_rtc_driver = {
+ .driver = {
+ .name = "s5m-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = s5m_rtc_probe,
+ .remove = __devexit_p(s5m_rtc_remove),
+ .shutdown = s5m_rtc_shutdown,
+ .id_table = s5m_rtc_id,
+};
+
+static int __init s5m_rtc_init(void)
+{
+ return platform_driver_register(&s5m_rtc_driver);
+}
+module_init(s5m_rtc_init);
+
+static void __exit s5m_rtc_exit(void)
+{
+ platform_driver_unregister(&s5m_rtc_driver);
+}
+module_exit(s5m_rtc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>");
+MODULE_DESCRIPTION("Samsung S5M RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s5m-rtc");