X-Git-Url: http://pilppa.org/gitweb/?a=blobdiff_plain;f=drivers%2Frtc%2Finterface.c;h=de0da545c7a107c0c967e35b9a4eb3daa4cf1c5b;hb=6f37ac793d6ba7b35d338f791974166f67fdd9ba;hp=6f11f6dfdd9dccc699b5a1a958132d55dec5daa6;hpb=463e7c7cf9aaf95dd05e97e1a47854fdf5454cdc;p=linux-2.6-omap-h63xx.git diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c index 6f11f6dfdd9..de0da545c7a 100644 --- a/drivers/rtc/interface.c +++ b/drivers/rtc/interface.c @@ -12,11 +12,11 @@ */ #include +#include -int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm) +int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm) { int err; - struct rtc_device *rtc = to_rtc_device(class_dev); err = mutex_lock_interruptible(&rtc->ops_lock); if (err) @@ -28,7 +28,7 @@ int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm) err = -EINVAL; else { memset(tm, 0, sizeof(struct rtc_time)); - err = rtc->ops->read_time(class_dev->dev, tm); + err = rtc->ops->read_time(rtc->dev.parent, tm); } mutex_unlock(&rtc->ops_lock); @@ -36,10 +36,9 @@ int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm) } EXPORT_SYMBOL_GPL(rtc_read_time); -int rtc_set_time(struct class_device *class_dev, struct rtc_time *tm) +int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm) { int err; - struct rtc_device *rtc = to_rtc_device(class_dev); err = rtc_valid_tm(tm); if (err != 0) @@ -54,17 +53,16 @@ int rtc_set_time(struct class_device *class_dev, struct rtc_time *tm) else if (!rtc->ops->set_time) err = -EINVAL; else - err = rtc->ops->set_time(class_dev->dev, tm); + err = rtc->ops->set_time(rtc->dev.parent, tm); mutex_unlock(&rtc->ops_lock); return err; } EXPORT_SYMBOL_GPL(rtc_set_time); -int rtc_set_mmss(struct class_device *class_dev, unsigned long secs) +int rtc_set_mmss(struct rtc_device *rtc, unsigned long secs) { int err; - struct rtc_device *rtc = to_rtc_device(class_dev); err = mutex_lock_interruptible(&rtc->ops_lock); if (err) @@ -73,11 +71,11 @@ int rtc_set_mmss(struct class_device *class_dev, unsigned long secs) if (!rtc->ops) err = -ENODEV; else if (rtc->ops->set_mmss) - err = rtc->ops->set_mmss(class_dev->dev, secs); + err = rtc->ops->set_mmss(rtc->dev.parent, secs); else if (rtc->ops->read_time && rtc->ops->set_time) { struct rtc_time new, old; - err = rtc->ops->read_time(class_dev->dev, &old); + err = rtc->ops->read_time(rtc->dev.parent, &old); if (err == 0) { rtc_time_to_tm(secs, &new); @@ -89,7 +87,8 @@ int rtc_set_mmss(struct class_device *class_dev, unsigned long secs) */ if (!((old.tm_hour == 23 && old.tm_min == 59) || (new.tm_hour == 23 && new.tm_min == 59))) - err = rtc->ops->set_time(class_dev->dev, &new); + err = rtc->ops->set_time(rtc->dev.parent, + &new); } } else @@ -101,10 +100,9 @@ int rtc_set_mmss(struct class_device *class_dev, unsigned long secs) } EXPORT_SYMBOL_GPL(rtc_set_mmss); -int rtc_read_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) +static int rtc_read_alarm_internal(struct rtc_device *rtc, struct rtc_wkalrm *alarm) { int err; - struct rtc_device *rtc = to_rtc_device(class_dev); err = mutex_lock_interruptible(&rtc->ops_lock); if (err) @@ -116,18 +114,102 @@ int rtc_read_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) err = -EINVAL; else { memset(alarm, 0, sizeof(struct rtc_wkalrm)); - err = rtc->ops->read_alarm(class_dev->dev, alarm); + err = rtc->ops->read_alarm(rtc->dev.parent, alarm); } mutex_unlock(&rtc->ops_lock); return err; } + +int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) +{ + int err; + struct rtc_time before, now; + int first_time = 1; + + /* The lower level RTC driver may not be capable of filling + * in all fields of the rtc_time struct (eg. rtc-cmos), + * and so might instead return -1 in some fields. + * We deal with that here by grabbing a current RTC timestamp + * and using values from that for any missing (-1) values. + * + * But this can be racey, because some fields of the RTC timestamp + * may have wrapped in the interval since we read the RTC alarm, + * which would lead to us inserting inconsistent values in place + * of the -1 fields. + * + * Reading the alarm and timestamp in the reverse sequence + * would have the same race condition, and not solve the issue. + * + * So, we must first read the RTC timestamp, + * then read the RTC alarm value, + * and then read a second RTC timestamp. + * + * If any fields of the second timestamp have changed + * when compared with the first timestamp, then we know + * our timestamp may be inconsistent with that used by + * the low-level rtc_read_alarm_internal() function. + * + * So, when the two timestamps disagree, we just loop and do + * the process again to get a fully consistent set of values. + * + * This could all instead be done in the lower level driver, + * but since more than one lower level RTC implementation needs it, + * then it's probably best best to do it here instead of there.. + */ + + /* Get the "before" timestamp */ + err = rtc_read_time(rtc, &before); + if (err < 0) + return err; + do { + if (!first_time) + memcpy(&before, &now, sizeof(struct rtc_time)); + first_time = 0; + + /* get the RTC alarm values, which may be incomplete */ + err = rtc_read_alarm_internal(rtc, alarm); + if (err) + return err; + if (!alarm->enabled) + return 0; + + /* get the "after" timestamp, to detect wrapped fields */ + err = rtc_read_time(rtc, &now); + if (err < 0) + return err; + + /* note that tm_sec is a "don't care" value here: */ + } while ( before.tm_min != now.tm_min + || before.tm_hour != now.tm_hour + || before.tm_mon != now.tm_mon + || before.tm_year != now.tm_year + || before.tm_isdst != now.tm_isdst); + + /* Fill in any missing alarm fields using the timestamp */ + if (alarm->time.tm_sec == -1) + alarm->time.tm_sec = now.tm_sec; + if (alarm->time.tm_min == -1) + alarm->time.tm_min = now.tm_min; + if (alarm->time.tm_hour == -1) + alarm->time.tm_hour = now.tm_hour; + if (alarm->time.tm_mday == -1) + alarm->time.tm_mday = now.tm_mday; + if (alarm->time.tm_mon == -1) + alarm->time.tm_mon = now.tm_mon; + if (alarm->time.tm_year == -1) + alarm->time.tm_year = now.tm_year; + return 0; +} EXPORT_SYMBOL_GPL(rtc_read_alarm); -int rtc_set_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) +int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) { int err; - struct rtc_device *rtc = to_rtc_device(class_dev); + + err = rtc_valid_tm(&alarm->time); + if (err != 0) + return err; err = mutex_lock_interruptible(&rtc->ops_lock); if (err) @@ -138,7 +220,7 @@ int rtc_set_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) else if (!rtc->ops->set_alarm) err = -EINVAL; else - err = rtc->ops->set_alarm(class_dev->dev, alarm); + err = rtc->ops->set_alarm(rtc->dev.parent, alarm); mutex_unlock(&rtc->ops_lock); return err; @@ -147,16 +229,14 @@ EXPORT_SYMBOL_GPL(rtc_set_alarm); /** * rtc_update_irq - report RTC periodic, alarm, and/or update irqs - * @class_dev: the rtc's class device + * @rtc: the rtc device * @num: how many irqs are being reported (usually one) * @events: mask of RTC_IRQF with one or more of RTC_PF, RTC_AF, RTC_UF * Context: in_interrupt(), irqs blocked */ -void rtc_update_irq(struct class_device *class_dev, +void rtc_update_irq(struct rtc_device *rtc, unsigned long num, unsigned long events) { - struct rtc_device *rtc = to_rtc_device(class_dev); - spin_lock(&rtc->irq_lock); rtc->irq_data = (rtc->irq_data + (num << 8)) | events; spin_unlock(&rtc->irq_lock); @@ -171,43 +251,51 @@ void rtc_update_irq(struct class_device *class_dev, } EXPORT_SYMBOL_GPL(rtc_update_irq); -struct class_device *rtc_class_open(char *name) +struct rtc_device *rtc_class_open(char *name) { - struct class_device *class_dev = NULL, - *class_dev_tmp; + struct device *dev; + struct rtc_device *rtc = NULL; down(&rtc_class->sem); - list_for_each_entry(class_dev_tmp, &rtc_class->children, node) { - if (strncmp(class_dev_tmp->class_id, name, BUS_ID_SIZE) == 0) { - class_dev = class_dev_tmp; + list_for_each_entry(dev, &rtc_class->devices, node) { + if (strncmp(dev->bus_id, name, BUS_ID_SIZE) == 0) { + dev = get_device(dev); + if (dev) + rtc = to_rtc_device(dev); break; } } - if (class_dev) { - if (!try_module_get(to_rtc_device(class_dev)->owner)) - class_dev = NULL; + if (rtc) { + if (!try_module_get(rtc->owner)) { + put_device(dev); + rtc = NULL; + } } up(&rtc_class->sem); - return class_dev; + return rtc; } EXPORT_SYMBOL_GPL(rtc_class_open); -void rtc_class_close(struct class_device *class_dev) +void rtc_class_close(struct rtc_device *rtc) { - module_put(to_rtc_device(class_dev)->owner); + module_put(rtc->owner); + put_device(&rtc->dev); } EXPORT_SYMBOL_GPL(rtc_class_close); -int rtc_irq_register(struct class_device *class_dev, struct rtc_task *task) +int rtc_irq_register(struct rtc_device *rtc, struct rtc_task *task) { int retval = -EBUSY; - struct rtc_device *rtc = to_rtc_device(class_dev); if (task == NULL || task->func == NULL) return -EINVAL; + /* Cannot register while the char dev is in use */ + if (!(mutex_trylock(&rtc->char_lock))) + return -EBUSY; + spin_lock_irq(&rtc->irq_task_lock); if (rtc->irq_task == NULL) { rtc->irq_task = task; @@ -215,14 +303,14 @@ int rtc_irq_register(struct class_device *class_dev, struct rtc_task *task) } spin_unlock_irq(&rtc->irq_task_lock); + mutex_unlock(&rtc->char_lock); + return retval; } EXPORT_SYMBOL_GPL(rtc_irq_register); -void rtc_irq_unregister(struct class_device *class_dev, struct rtc_task *task) +void rtc_irq_unregister(struct rtc_device *rtc, struct rtc_task *task) { - struct rtc_device *rtc = to_rtc_device(class_dev); - spin_lock_irq(&rtc->irq_task_lock); if (rtc->irq_task == task) rtc->irq_task = NULL; @@ -230,43 +318,68 @@ void rtc_irq_unregister(struct class_device *class_dev, struct rtc_task *task) } EXPORT_SYMBOL_GPL(rtc_irq_unregister); -int rtc_irq_set_state(struct class_device *class_dev, struct rtc_task *task, int enabled) +/** + * rtc_irq_set_state - enable/disable 2^N Hz periodic IRQs + * @rtc: the rtc device + * @task: currently registered with rtc_irq_register() + * @enabled: true to enable periodic IRQs + * Context: any + * + * Note that rtc_irq_set_freq() should previously have been used to + * specify the desired frequency of periodic IRQ task->func() callbacks. + */ +int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled) { int err = 0; unsigned long flags; - struct rtc_device *rtc = to_rtc_device(class_dev); if (rtc->ops->irq_set_state == NULL) return -ENXIO; spin_lock_irqsave(&rtc->irq_task_lock, flags); + if (rtc->irq_task != NULL && task == NULL) + err = -EBUSY; if (rtc->irq_task != task) - err = -ENXIO; + err = -EACCES; spin_unlock_irqrestore(&rtc->irq_task_lock, flags); if (err == 0) - err = rtc->ops->irq_set_state(class_dev->dev, enabled); + err = rtc->ops->irq_set_state(rtc->dev.parent, enabled); return err; } EXPORT_SYMBOL_GPL(rtc_irq_set_state); -int rtc_irq_set_freq(struct class_device *class_dev, struct rtc_task *task, int freq) +/** + * rtc_irq_set_freq - set 2^N Hz periodic IRQ frequency for IRQ + * @rtc: the rtc device + * @task: currently registered with rtc_irq_register() + * @freq: positive frequency with which task->func() will be called + * Context: any + * + * Note that rtc_irq_set_state() is used to enable or disable the + * periodic IRQs. + */ +int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq) { int err = 0; unsigned long flags; - struct rtc_device *rtc = to_rtc_device(class_dev); if (rtc->ops->irq_set_freq == NULL) return -ENXIO; + if (!is_power_of_2(freq)) + return -EINVAL; + spin_lock_irqsave(&rtc->irq_task_lock, flags); + if (rtc->irq_task != NULL && task == NULL) + err = -EBUSY; if (rtc->irq_task != task) - err = -ENXIO; + err = -EACCES; spin_unlock_irqrestore(&rtc->irq_task_lock, flags); if (err == 0) { - err = rtc->ops->irq_set_freq(class_dev->dev, freq); + err = rtc->ops->irq_set_freq(rtc->dev.parent, freq); if (err == 0) rtc->irq_freq = freq; }