diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/rtc | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_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/Kconfig | 71 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 5 | ||||
-rw-r--r-- | drivers/rtc/alarm-dev.c | 298 | ||||
-rw-r--r-- | drivers/rtc/alarm.c | 638 | ||||
-rw-r--r-- | drivers/rtc/class.c | 69 | ||||
-rw-r--r-- | drivers/rtc/interface.c | 22 | ||||
-rw-r--r-- | drivers/rtc/rtc-lib.c | 2 | ||||
-rw-r--r-- | drivers/rtc/rtc-max77686.c | 909 | ||||
-rw-r--r-- | drivers/rtc/rtc-max8997.c | 767 | ||||
-rw-r--r-- | drivers/rtc/rtc-s3c.c | 307 | ||||
-rw-r--r-- | drivers/rtc/rtc-s5m.c | 706 |
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"); |