diff options
Diffstat (limited to 'drivers/rtc/class.c')
| -rw-r--r-- | drivers/rtc/class.c | 306 |
1 files changed, 265 insertions, 41 deletions
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 7a0d8ee2de9..589351ef75d 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c @@ -11,24 +11,138 @@ * published by the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> +#include <linux/of.h> #include <linux/rtc.h> #include <linux/kdev_t.h> #include <linux/idr.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include "rtc-core.h" + -static DEFINE_IDR(rtc_idr); -static DEFINE_MUTEX(idr_lock); +static DEFINE_IDA(rtc_ida); struct class *rtc_class; -static void rtc_device_release(struct class_device *class_dev) +static void rtc_device_release(struct device *dev) { - struct rtc_device *rtc = to_rtc_device(class_dev); - mutex_lock(&idr_lock); - idr_remove(&rtc_idr, rtc->id); - mutex_unlock(&idr_lock); + struct rtc_device *rtc = to_rtc_device(dev); + ida_simple_remove(&rtc_ida, rtc->id); kfree(rtc); } +#ifdef CONFIG_RTC_HCTOSYS_DEVICE +/* Result of the last RTC to system clock attempt. */ +int rtc_hctosys_ret = -ENODEV; +#endif + +#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_RTC_HCTOSYS_DEVICE) +/* + * On suspend(), measure the delta between one RTC and the + * system's wall clock; restore it on resume(). + */ + +static struct timespec old_rtc, old_system, old_delta; + + +static int rtc_suspend(struct device *dev) +{ + struct rtc_device *rtc = to_rtc_device(dev); + struct rtc_time tm; + struct timespec delta, delta_delta; + + if (has_persistent_clock()) + return 0; + + 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); + 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; +} + +static int rtc_resume(struct device *dev) +{ + struct rtc_device *rtc = to_rtc_device(dev); + struct rtc_time tm; + struct timespec new_system, new_rtc; + struct timespec sleep_time; + + if (has_persistent_clock()) + return 0; + + rtc_hctosys_ret = -ENODEV; + if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) + return 0; + + /* 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, &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 (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); + rtc_hctosys_ret = 0; + return 0; +} + +static SIMPLE_DEV_PM_OPS(rtc_class_dev_pm_ops, rtc_suspend, rtc_resume); +#define RTC_CLASS_DEV_PM_OPS (&rtc_class_dev_pm_ops) +#else +#define RTC_CLASS_DEV_PM_OPS NULL +#endif + + /** * rtc_device_register - register w/ RTC class * @dev: the device to register @@ -43,60 +157,93 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev, struct module *owner) { struct rtc_device *rtc; - int id, err; - - if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) { - err = -ENOMEM; - goto exit; - } + struct rtc_wkalrm alrm; + int of_id = -1, id = -1, err; + if (dev->of_node) + of_id = of_alias_get_id(dev->of_node, "rtc"); + else if (dev->parent && dev->parent->of_node) + of_id = of_alias_get_id(dev->parent->of_node, "rtc"); - mutex_lock(&idr_lock); - err = idr_get_new(&rtc_idr, NULL, &id); - mutex_unlock(&idr_lock); - - if (err < 0) - goto exit; + if (of_id >= 0) { + id = ida_simple_get(&rtc_ida, of_id, of_id + 1, + GFP_KERNEL); + if (id < 0) + dev_warn(dev, "/aliases ID %d not available\n", + of_id); + } - id = id & MAX_ID_MASK; + if (id < 0) { + id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + err = id; + goto exit; + } + } rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL); if (rtc == NULL) { err = -ENOMEM; - goto exit_idr; + goto exit_ida; } rtc->id = id; rtc->ops = ops; rtc->owner = owner; + rtc->irq_freq = 1; rtc->max_user_freq = 64; - rtc->class_dev.dev = dev; - rtc->class_dev.class = rtc_class; - rtc->class_dev.release = rtc_device_release; + rtc->dev.parent = dev; + rtc->dev.class = rtc_class; + rtc->dev.release = rtc_device_release; mutex_init(&rtc->ops_lock); spin_lock_init(&rtc->irq_lock); spin_lock_init(&rtc->irq_task_lock); + init_waitqueue_head(&rtc->irq_queue); + + /* Init timerqueue */ + timerqueue_init_head(&rtc->timerqueue); + INIT_WORK(&rtc->irqwork, rtc_timer_do_work); + /* Init aie timer */ + rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc); + /* Init uie timer */ + rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc); + /* Init pie timer */ + hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + rtc->pie_timer.function = rtc_pie_update_irq; + rtc->pie_enabled = 0; + + /* Check to see if there is an ALARM already set in hw */ + err = __rtc_read_alarm(rtc, &alrm); + + if (!err && !rtc_valid_tm(&alrm.time)) + rtc_initialize_alarm(rtc, &alrm); strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); - snprintf(rtc->class_dev.class_id, BUS_ID_SIZE, "rtc%d", id); + dev_set_name(&rtc->dev, "rtc%d", id); + + rtc_dev_prepare(rtc); - err = class_device_register(&rtc->class_dev); - if (err) + err = device_register(&rtc->dev); + if (err) { + put_device(&rtc->dev); goto exit_kfree; + } + + rtc_dev_add_device(rtc); + rtc_sysfs_add_device(rtc); + rtc_proc_add_device(rtc); dev_info(dev, "rtc core: registered %s as %s\n", - rtc->name, rtc->class_dev.class_id); + rtc->name, dev_name(&rtc->dev)); return rtc; exit_kfree: kfree(rtc); -exit_idr: - mutex_lock(&idr_lock); - idr_remove(&rtc_idr, id); - mutex_unlock(&idr_lock); +exit_ida: + ida_simple_remove(&rtc_ida, id); exit: dev_err(dev, "rtc core: unable to register %s, err = %d\n", @@ -113,33 +260,110 @@ EXPORT_SYMBOL_GPL(rtc_device_register); */ void rtc_device_unregister(struct rtc_device *rtc) { - mutex_lock(&rtc->ops_lock); - rtc->ops = NULL; - mutex_unlock(&rtc->ops_lock); - class_device_unregister(&rtc->class_dev); + if (get_device(&rtc->dev) != NULL) { + mutex_lock(&rtc->ops_lock); + /* remove innards of this RTC, then disable it, before + * letting any rtc_class_open() users access it again + */ + rtc_sysfs_del_device(rtc); + rtc_dev_del_device(rtc); + rtc_proc_del_device(rtc); + device_unregister(&rtc->dev); + rtc->ops = NULL; + mutex_unlock(&rtc->ops_lock); + put_device(&rtc->dev); + } } EXPORT_SYMBOL_GPL(rtc_device_unregister); -int rtc_interface_register(struct class_interface *intf) +static void devm_rtc_device_release(struct device *dev, void *res) +{ + struct rtc_device *rtc = *(struct rtc_device **)res; + + rtc_device_unregister(rtc); +} + +static int devm_rtc_device_match(struct device *dev, void *res, void *data) +{ + struct rtc **r = res; + + return *r == data; +} + +/** + * devm_rtc_device_register - resource managed rtc_device_register() + * @dev: the device to register + * @name: the name of the device + * @ops: the rtc operations structure + * @owner: the module owner + * + * @return a struct rtc on success, or an ERR_PTR on error + * + * Managed rtc_device_register(). The rtc_device returned from this function + * are automatically freed on driver detach. See rtc_device_register() + * for more information. + */ + +struct rtc_device *devm_rtc_device_register(struct device *dev, + const char *name, + const struct rtc_class_ops *ops, + struct module *owner) { - intf->class = rtc_class; - return class_interface_register(intf); + struct rtc_device **ptr, *rtc; + + ptr = devres_alloc(devm_rtc_device_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + rtc = rtc_device_register(name, dev, ops, owner); + if (!IS_ERR(rtc)) { + *ptr = rtc; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return rtc; +} +EXPORT_SYMBOL_GPL(devm_rtc_device_register); + +/** + * devm_rtc_device_unregister - resource managed devm_rtc_device_unregister() + * @dev: the device to unregister + * @rtc: the RTC class device to unregister + * + * Deallocated a rtc allocated with devm_rtc_device_register(). Normally this + * function will not need to be called and the resource management code will + * ensure that the resource is freed. + */ +void devm_rtc_device_unregister(struct device *dev, struct rtc_device *rtc) +{ + int rc; + + rc = devres_release(dev, devm_rtc_device_release, + devm_rtc_device_match, rtc); + WARN_ON(rc); } -EXPORT_SYMBOL_GPL(rtc_interface_register); +EXPORT_SYMBOL_GPL(devm_rtc_device_unregister); static int __init rtc_init(void) { rtc_class = class_create(THIS_MODULE, "rtc"); if (IS_ERR(rtc_class)) { - printk(KERN_ERR "%s: couldn't create class\n", __FILE__); + pr_err("couldn't create class\n"); return PTR_ERR(rtc_class); } + rtc_class->pm = RTC_CLASS_DEV_PM_OPS; + rtc_dev_init(); + rtc_sysfs_init(rtc_class); return 0; } static void __exit rtc_exit(void) { + rtc_dev_exit(); class_destroy(rtc_class); + ida_destroy(&rtc_ida); } subsys_initcall(rtc_init); |
