| /* The industrial I/O periodic RTC trigger driver |
| * |
| * Copyright (c) 2008 Jonathan Cameron |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published by |
| * the Free Software Foundation. |
| * |
| * This is a heavily rewritten version of the periodic timer system in |
| * earlier version of industrialio. It supplies the same functionality |
| * but via a trigger rather than a specific periodic timer system. |
| */ |
| |
| #include <linux/platform_device.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/rtc.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/trigger.h> |
| |
| static LIST_HEAD(iio_prtc_trigger_list); |
| static DEFINE_MUTEX(iio_prtc_trigger_list_lock); |
| |
| struct iio_prtc_trigger_info { |
| struct rtc_device *rtc; |
| unsigned int frequency; |
| struct rtc_task task; |
| bool state; |
| }; |
| |
| static int iio_trig_periodic_rtc_set_state(struct iio_trigger *trig, bool state) |
| { |
| struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); |
| int ret; |
| |
| if (trig_info->frequency == 0 && state) |
| return -EINVAL; |
| dev_dbg(&trig_info->rtc->dev, "trigger frequency is %u\n", |
| trig_info->frequency); |
| ret = rtc_irq_set_state(trig_info->rtc, &trig_info->task, state); |
| if (!ret) |
| trig_info->state = state; |
| |
| return ret; |
| } |
| |
| static ssize_t iio_trig_periodic_read_freq(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct iio_trigger *trig = to_iio_trigger(dev); |
| struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); |
| |
| return sprintf(buf, "%u\n", trig_info->frequency); |
| } |
| |
| static ssize_t iio_trig_periodic_write_freq(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t len) |
| { |
| struct iio_trigger *trig = to_iio_trigger(dev); |
| struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); |
| unsigned int val; |
| int ret; |
| |
| ret = kstrtouint(buf, 10, &val); |
| if (ret) |
| goto error_ret; |
| |
| if (val > 0) { |
| ret = rtc_irq_set_freq(trig_info->rtc, &trig_info->task, val); |
| if (ret == 0 && trig_info->state && trig_info->frequency == 0) |
| ret = rtc_irq_set_state(trig_info->rtc, |
| &trig_info->task, 1); |
| } else { |
| ret = rtc_irq_set_state(trig_info->rtc, &trig_info->task, 0); |
| } |
| if (ret) |
| goto error_ret; |
| |
| trig_info->frequency = val; |
| |
| return len; |
| |
| error_ret: |
| return ret; |
| } |
| |
| static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, |
| iio_trig_periodic_read_freq, |
| iio_trig_periodic_write_freq); |
| |
| static struct attribute *iio_trig_prtc_attrs[] = { |
| &dev_attr_frequency.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group iio_trig_prtc_attr_group = { |
| .attrs = iio_trig_prtc_attrs, |
| }; |
| |
| static const struct attribute_group *iio_trig_prtc_attr_groups[] = { |
| &iio_trig_prtc_attr_group, |
| NULL |
| }; |
| |
| static void iio_prtc_trigger_poll(void *private_data) |
| { |
| iio_trigger_poll(private_data); |
| } |
| |
| static const struct iio_trigger_ops iio_prtc_trigger_ops = { |
| .owner = THIS_MODULE, |
| .set_trigger_state = &iio_trig_periodic_rtc_set_state, |
| }; |
| |
| static int iio_trig_periodic_rtc_probe(struct platform_device *dev) |
| { |
| char **pdata = dev->dev.platform_data; |
| struct iio_prtc_trigger_info *trig_info; |
| struct iio_trigger *trig, *trig2; |
| |
| int i, ret; |
| |
| for (i = 0;; i++) { |
| if (!pdata[i]) |
| break; |
| trig = iio_trigger_alloc("periodic%s", pdata[i]); |
| if (!trig) { |
| ret = -ENOMEM; |
| goto error_free_completed_registrations; |
| } |
| list_add(&trig->alloc_list, &iio_prtc_trigger_list); |
| |
| trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); |
| if (!trig_info) { |
| ret = -ENOMEM; |
| goto error_put_trigger_and_remove_from_list; |
| } |
| iio_trigger_set_drvdata(trig, trig_info); |
| trig->ops = &iio_prtc_trigger_ops; |
| /* RTC access */ |
| trig_info->rtc = rtc_class_open(pdata[i]); |
| if (!trig_info->rtc) { |
| ret = -EINVAL; |
| goto error_free_trig_info; |
| } |
| trig_info->task.func = iio_prtc_trigger_poll; |
| trig_info->task.private_data = trig; |
| ret = rtc_irq_register(trig_info->rtc, &trig_info->task); |
| if (ret) |
| goto error_close_rtc; |
| trig->dev.groups = iio_trig_prtc_attr_groups; |
| ret = iio_trigger_register(trig); |
| if (ret) |
| goto error_unregister_rtc_irq; |
| } |
| return 0; |
| error_unregister_rtc_irq: |
| rtc_irq_unregister(trig_info->rtc, &trig_info->task); |
| error_close_rtc: |
| rtc_class_close(trig_info->rtc); |
| error_free_trig_info: |
| kfree(trig_info); |
| error_put_trigger_and_remove_from_list: |
| list_del(&trig->alloc_list); |
| iio_trigger_put(trig); |
| error_free_completed_registrations: |
| list_for_each_entry_safe(trig, |
| trig2, |
| &iio_prtc_trigger_list, |
| alloc_list) { |
| trig_info = iio_trigger_get_drvdata(trig); |
| rtc_irq_unregister(trig_info->rtc, &trig_info->task); |
| rtc_class_close(trig_info->rtc); |
| kfree(trig_info); |
| iio_trigger_unregister(trig); |
| } |
| return ret; |
| } |
| |
| static int iio_trig_periodic_rtc_remove(struct platform_device *dev) |
| { |
| struct iio_trigger *trig, *trig2; |
| struct iio_prtc_trigger_info *trig_info; |
| |
| mutex_lock(&iio_prtc_trigger_list_lock); |
| list_for_each_entry_safe(trig, |
| trig2, |
| &iio_prtc_trigger_list, |
| alloc_list) { |
| trig_info = iio_trigger_get_drvdata(trig); |
| rtc_irq_unregister(trig_info->rtc, &trig_info->task); |
| rtc_class_close(trig_info->rtc); |
| kfree(trig_info); |
| iio_trigger_unregister(trig); |
| } |
| mutex_unlock(&iio_prtc_trigger_list_lock); |
| return 0; |
| } |
| |
| static struct platform_driver iio_trig_periodic_rtc_driver = { |
| .probe = iio_trig_periodic_rtc_probe, |
| .remove = iio_trig_periodic_rtc_remove, |
| .driver = { |
| .name = "iio_prtc_trigger", |
| }, |
| }; |
| |
| module_platform_driver(iio_trig_periodic_rtc_driver); |
| |
| MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); |
| MODULE_DESCRIPTION("Periodic realtime clock trigger for the iio subsystem"); |
| MODULE_LICENSE("GPL v2"); |