diff options
Diffstat (limited to 'drivers/staging/iio/trigger')
| -rw-r--r-- | drivers/staging/iio/trigger/Kconfig | 43 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/Makefile | 8 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/iio-trig-bfin-timer.c | 307 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/iio-trig-bfin-timer.h | 24 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/iio-trig-gpio.c | 167 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/iio-trig-periodic-rtc.c | 202 | ||||
| -rw-r--r-- | drivers/staging/iio/trigger/iio-trig-sysfs.c | 227 |
7 files changed, 978 insertions, 0 deletions
diff --git a/drivers/staging/iio/trigger/Kconfig b/drivers/staging/iio/trigger/Kconfig new file mode 100644 index 00000000..1a051da6 --- /dev/null +++ b/drivers/staging/iio/trigger/Kconfig @@ -0,0 +1,43 @@ +# +# Industrial I/O standalone triggers +# +comment "Triggers - standalone" + +if IIO_TRIGGER + +config IIO_PERIODIC_RTC_TRIGGER + tristate "Periodic RTC triggers" + depends on RTC_CLASS + help + Provides support for using periodic capable real time + clocks as IIO triggers. + +config IIO_GPIO_TRIGGER + tristate "GPIO trigger" + depends on GPIOLIB + help + Provides support for using GPIO pins as IIO triggers. + +config IIO_SYSFS_TRIGGER + tristate "SYSFS trigger" + depends on SYSFS + select IRQ_WORK + help + Provides support for using SYSFS entry as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-sysfs. + +config IIO_BFIN_TMR_TRIGGER + tristate "Blackfin TIMER trigger" + depends on BLACKFIN + select BFIN_GPTIMERS + help + Provides support for using a Blackfin timer as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-bfin-timer. + +endif # IIO_TRIGGER diff --git a/drivers/staging/iio/trigger/Makefile b/drivers/staging/iio/trigger/Makefile new file mode 100644 index 00000000..b088b57d --- /dev/null +++ b/drivers/staging/iio/trigger/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for triggers not associated with iio-devices +# + +obj-$(CONFIG_IIO_PERIODIC_RTC_TRIGGER) += iio-trig-periodic-rtc.o +obj-$(CONFIG_IIO_GPIO_TRIGGER) += iio-trig-gpio.o +obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o +obj-$(CONFIG_IIO_BFIN_TMR_TRIGGER) += iio-trig-bfin-timer.o diff --git a/drivers/staging/iio/trigger/iio-trig-bfin-timer.c b/drivers/staging/iio/trigger/iio-trig-bfin-timer.c new file mode 100644 index 00000000..38a158b7 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-bfin-timer.c @@ -0,0 +1,307 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> + +#include <asm/gptimers.h> +#include <asm/portmux.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +#include "iio-trig-bfin-timer.h" + +struct bfin_timer { + unsigned short id, bit; + unsigned long irqbit; + int irq; + int pin; +}; + +/* + * this covers all hardware timer configurations on + * all Blackfin derivatives out there today + */ + +static struct bfin_timer iio_bfin_timer_code[MAX_BLACKFIN_GPTIMERS] = { + {TIMER0_id, TIMER0bit, TIMER_STATUS_TIMIL0, IRQ_TIMER0, P_TMR0}, + {TIMER1_id, TIMER1bit, TIMER_STATUS_TIMIL1, IRQ_TIMER1, P_TMR1}, + {TIMER2_id, TIMER2bit, TIMER_STATUS_TIMIL2, IRQ_TIMER2, P_TMR2}, +#if (MAX_BLACKFIN_GPTIMERS > 3) + {TIMER3_id, TIMER3bit, TIMER_STATUS_TIMIL3, IRQ_TIMER3, P_TMR3}, + {TIMER4_id, TIMER4bit, TIMER_STATUS_TIMIL4, IRQ_TIMER4, P_TMR4}, + {TIMER5_id, TIMER5bit, TIMER_STATUS_TIMIL5, IRQ_TIMER5, P_TMR5}, + {TIMER6_id, TIMER6bit, TIMER_STATUS_TIMIL6, IRQ_TIMER6, P_TMR6}, + {TIMER7_id, TIMER7bit, TIMER_STATUS_TIMIL7, IRQ_TIMER7, P_TMR7}, +#endif +#if (MAX_BLACKFIN_GPTIMERS > 8) + {TIMER8_id, TIMER8bit, TIMER_STATUS_TIMIL8, IRQ_TIMER8, P_TMR8}, + {TIMER9_id, TIMER9bit, TIMER_STATUS_TIMIL9, IRQ_TIMER9, P_TMR9}, + {TIMER10_id, TIMER10bit, TIMER_STATUS_TIMIL10, IRQ_TIMER10, P_TMR10}, +#if (MAX_BLACKFIN_GPTIMERS > 11) + {TIMER11_id, TIMER11bit, TIMER_STATUS_TIMIL11, IRQ_TIMER11, P_TMR11}, +#endif +#endif +}; + +struct bfin_tmr_state { + struct iio_trigger *trig; + struct bfin_timer *t; + unsigned timer_num; + bool output_enable; + unsigned int duty; + int irq; +}; + +static int iio_bfin_tmr_set_state(struct iio_trigger *trig, bool state) +{ + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + + if (get_gptimer_period(st->t->id) == 0) + return -EINVAL; + + if (state) + enable_gptimers(st->t->bit); + else + disable_gptimers(st->t->bit); + + return 0; +} + +static ssize_t iio_bfin_tmr_frequency_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + unsigned long val; + bool enabled; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + if (val > 100000) { + ret = -EINVAL; + goto error_ret; + } + + enabled = get_enabled_gptimers() & st->t->bit; + + if (enabled) + disable_gptimers(st->t->bit); + + if (!val) + goto error_ret; + + val = get_sclk() / val; + if (val <= 4 || val <= st->duty) { + ret = -EINVAL; + goto error_ret; + } + + set_gptimer_period(st->t->id, val); + set_gptimer_pwidth(st->t->id, val - st->duty); + + if (enabled) + enable_gptimers(st->t->bit); + +error_ret: + return ret ? ret : count; +} + +static ssize_t iio_bfin_tmr_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + unsigned int period = get_gptimer_period(st->t->id); + unsigned long val; + + if (period == 0) + val = 0; + else + val = get_sclk() / get_gptimer_period(st->t->id); + + return sprintf(buf, "%lu\n", val); +} + +static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, iio_bfin_tmr_frequency_show, + iio_bfin_tmr_frequency_store); + +static struct attribute *iio_bfin_tmr_trigger_attrs[] = { + &dev_attr_frequency.attr, + NULL, +}; + +static const struct attribute_group iio_bfin_tmr_trigger_attr_group = { + .attrs = iio_bfin_tmr_trigger_attrs, +}; + +static const struct attribute_group *iio_bfin_tmr_trigger_attr_groups[] = { + &iio_bfin_tmr_trigger_attr_group, + NULL +}; + +static irqreturn_t iio_bfin_tmr_trigger_isr(int irq, void *devid) +{ + struct bfin_tmr_state *st = devid; + + clear_gptimer_intr(st->t->id); + iio_trigger_poll(st->trig, 0); + + return IRQ_HANDLED; +} + +static int iio_bfin_tmr_get_number(int irq) +{ + int i; + + for (i = 0; i < MAX_BLACKFIN_GPTIMERS; i++) + if (iio_bfin_timer_code[i].irq == irq) + return i; + + return -ENODEV; +} + +static const struct iio_trigger_ops iio_bfin_tmr_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = iio_bfin_tmr_set_state, +}; + +static int iio_bfin_tmr_trigger_probe(struct platform_device *pdev) +{ + struct iio_bfin_timer_trigger_pdata *pdata = pdev->dev.platform_data; + struct bfin_tmr_state *st; + unsigned int config; + int ret; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto out; + } + + st->irq = platform_get_irq(pdev, 0); + if (!st->irq) { + dev_err(&pdev->dev, "No IRQs specified"); + ret = -ENODEV; + goto out1; + } + + ret = iio_bfin_tmr_get_number(st->irq); + if (ret < 0) + goto out1; + + st->timer_num = ret; + st->t = &iio_bfin_timer_code[st->timer_num]; + + st->trig = iio_trigger_alloc("bfintmr%d", st->timer_num); + if (!st->trig) { + ret = -ENOMEM; + goto out1; + } + + st->trig->ops = &iio_bfin_tmr_trigger_ops; + st->trig->dev.groups = iio_bfin_tmr_trigger_attr_groups; + iio_trigger_set_drvdata(st->trig, st); + ret = iio_trigger_register(st->trig); + if (ret) + goto out2; + + ret = request_irq(st->irq, iio_bfin_tmr_trigger_isr, + 0, st->trig->name, st); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", st->irq); + goto out4; + } + + config = PWM_OUT | PERIOD_CNT | IRQ_ENA; + + if (pdata && pdata->output_enable) { + unsigned long long val; + + st->output_enable = true; + + ret = peripheral_request(st->t->pin, st->trig->name); + if (ret) + goto out_free_irq; + + val = (unsigned long long)get_sclk() * pdata->duty_ns; + do_div(val, NSEC_PER_SEC); + st->duty = val; + + /** + * The interrupt will be generated at the end of the period, + * since we want the interrupt to be generated at end of the + * pulse we invert both polarity and duty cycle, so that the + * pulse will be generated directly before the interrupt. + */ + if (pdata->active_low) + config |= PULSE_HI; + } else { + st->duty = 1; + config |= OUT_DIS; + } + + set_gptimer_config(st->t->id, config); + + dev_info(&pdev->dev, "iio trigger Blackfin TMR%d, IRQ-%d", + st->timer_num, st->irq); + platform_set_drvdata(pdev, st); + + return 0; +out_free_irq: + free_irq(st->irq, st); +out4: + iio_trigger_unregister(st->trig); +out2: + iio_trigger_put(st->trig); +out1: + kfree(st); +out: + return ret; +} + +static int iio_bfin_tmr_trigger_remove(struct platform_device *pdev) +{ + struct bfin_tmr_state *st = platform_get_drvdata(pdev); + + disable_gptimers(st->t->bit); + if (st->output_enable) + peripheral_free(st->t->pin); + free_irq(st->irq, st); + iio_trigger_unregister(st->trig); + iio_trigger_put(st->trig); + kfree(st); + + return 0; +} + +static struct platform_driver iio_bfin_tmr_trigger_driver = { + .driver = { + .name = "iio_bfin_tmr_trigger", + .owner = THIS_MODULE, + }, + .probe = iio_bfin_tmr_trigger_probe, + .remove = iio_bfin_tmr_trigger_remove, +}; + +module_platform_driver(iio_bfin_tmr_trigger_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Blackfin system timer based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-bfin-timer"); diff --git a/drivers/staging/iio/trigger/iio-trig-bfin-timer.h b/drivers/staging/iio/trigger/iio-trig-bfin-timer.h new file mode 100644 index 00000000..c07321f8 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-bfin-timer.h @@ -0,0 +1,24 @@ +#ifndef __IIO_BFIN_TIMER_TRIGGER_H__ +#define __IIO_BFIN_TIMER_TRIGGER_H__ + +/** + * struct iio_bfin_timer_trigger_pdata - timer trigger platform data + * @output_enable: Enable external trigger pulse generation. + * @active_low: Whether the trigger pulse is active low. + * @duty_ns: Length of the trigger pulse in nanoseconds. + * + * This struct is used to configure the output pulse generation of the blackfin + * timer trigger. If output_enable is set to true an external trigger signal + * will generated on the pin corresponding to the timer. This is useful for + * converters which needs an external signal to start conversion. active_low and + * duty_ns are used to configure the type of the trigger pulse. If output_enable + * is set to false no external trigger pulse will be generated and active_low + * and duty_ns are ignored. + **/ +struct iio_bfin_timer_trigger_pdata { + bool output_enable; + bool active_low; + unsigned int duty_ns; +}; + +#endif diff --git a/drivers/staging/iio/trigger/iio-trig-gpio.c b/drivers/staging/iio/trigger/iio-trig-gpio.c new file mode 100644 index 00000000..7c593d18 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-gpio.c @@ -0,0 +1,167 @@ +/* + * Industrial I/O - gpio based trigger support + * + * 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. + * + * Currently this is more of a functioning proof of concept than a full + * fledged trigger driver. + * + * TODO: + * + * Add board config elements to allow specification of startup settings. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +static LIST_HEAD(iio_gpio_trigger_list); +static DEFINE_MUTEX(iio_gpio_trigger_list_lock); + +struct iio_gpio_trigger_info { + struct mutex in_use; + unsigned int irq; +}; +/* + * Need to reference count these triggers and only enable gpio interrupts + * as appropriate. + */ + +/* So what functionality do we want in here?... */ +/* set high / low as interrupt type? */ + +static irqreturn_t iio_gpio_trigger_poll(int irq, void *private) +{ + /* Timestamp not currently provided */ + iio_trigger_poll(private, 0); + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops iio_gpio_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_gpio_trigger_probe(struct platform_device *pdev) +{ + struct iio_gpio_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + unsigned long irqflags; + struct resource *irq_res; + int irq, ret = 0, irq_res_cnt = 0; + + do { + irq_res = platform_get_resource(pdev, + IORESOURCE_IRQ, irq_res_cnt); + + if (irq_res == NULL) { + if (irq_res_cnt == 0) + dev_err(&pdev->dev, "No GPIO IRQs specified"); + break; + } + irqflags = (irq_res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; + + for (irq = irq_res->start; irq <= irq_res->end; irq++) { + + trig = iio_trigger_alloc("irqtrig%d", irq); + if (!trig) { + ret = -ENOMEM; + goto error_free_completed_registrations; + } + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) { + ret = -ENOMEM; + goto error_put_trigger; + } + iio_trigger_set_drvdata(trig, trig_info); + trig_info->irq = irq; + trig->ops = &iio_gpio_trigger_ops; + ret = request_irq(irq, iio_gpio_trigger_poll, + irqflags, trig->name, trig); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", irq); + goto error_free_trig_info; + } + + ret = iio_trigger_register(trig); + if (ret) + goto error_release_irq; + + list_add_tail(&trig->alloc_list, + &iio_gpio_trigger_list); + } + + irq_res_cnt++; + } while (irq_res != NULL); + + + return 0; + +/* First clean up the partly allocated trigger */ +error_release_irq: + free_irq(irq, trig); +error_free_trig_info: + kfree(trig_info); +error_put_trigger: + iio_trigger_put(trig); +error_free_completed_registrations: + /* The rest should have been added to the iio_gpio_trigger_list */ + list_for_each_entry_safe(trig, + trig2, + &iio_gpio_trigger_list, + alloc_list) { + trig_info = iio_trigger_get_drvdata(trig); + free_irq(gpio_to_irq(trig_info->irq), trig); + kfree(trig_info); + iio_trigger_unregister(trig); + } + + return ret; +} + +static int iio_gpio_trigger_remove(struct platform_device *pdev) +{ + struct iio_trigger *trig, *trig2; + struct iio_gpio_trigger_info *trig_info; + + mutex_lock(&iio_gpio_trigger_list_lock); + list_for_each_entry_safe(trig, + trig2, + &iio_gpio_trigger_list, + alloc_list) { + trig_info = iio_trigger_get_drvdata(trig); + iio_trigger_unregister(trig); + free_irq(trig_info->irq, trig); + kfree(trig_info); + iio_trigger_put(trig); + } + mutex_unlock(&iio_gpio_trigger_list_lock); + + return 0; +} + +static struct platform_driver iio_gpio_trigger_driver = { + .probe = iio_gpio_trigger_probe, + .remove = iio_gpio_trigger_remove, + .driver = { + .name = "iio_gpio_trigger", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(iio_gpio_trigger_driver); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Example gpio trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c b/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c new file mode 100644 index 00000000..79695974 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c @@ -0,0 +1,202 @@ +/* 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; + int frequency; + struct rtc_task task; +}; + +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); + if (trig_info->frequency == 0) + return -EINVAL; + printk(KERN_INFO "trigger frequency is %d\n", trig_info->frequency); + return rtc_irq_set_state(trig_info->rtc, &trig_info->task, state); +} + +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 long val; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + ret = rtc_irq_set_freq(trig_info->rtc, &trig_info->task, val); + 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) +{ + /* Timestamp is not provided currently */ + iio_trigger_poll(private_data, 0); +} + +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] == NULL) + 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 == NULL) { + 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", + .owner = THIS_MODULE, + }, +}; + +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"); diff --git a/drivers/staging/iio/trigger/iio-trig-sysfs.c b/drivers/staging/iio/trigger/iio-trig-sysfs.c new file mode 100644 index 00000000..b727bde8 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,227 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/irq_work.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +struct iio_sysfs_trig { + struct iio_trigger *trig; + struct irq_work work; + int id; + struct list_head l; +}; + +static LIST_HEAD(iio_sysfs_trig_list); +static DEFINE_MUTEX(iio_syfs_trig_list_mut); + +static int iio_sysfs_trigger_probe(int id); +static ssize_t iio_sysfs_trig_add(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_probe(input); + if (ret) + return ret; + return len; +} +static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); + +static int iio_sysfs_trigger_remove(int id); +static ssize_t iio_sysfs_trig_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_remove(input); + if (ret) + return ret; + return len; +} + +static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); + +static struct attribute *iio_sysfs_trig_attrs[] = { + &dev_attr_add_trigger.attr, + &dev_attr_remove_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trig_group = { + .attrs = iio_sysfs_trig_attrs, +}; + +static const struct attribute_group *iio_sysfs_trig_groups[] = { + &iio_sysfs_trig_group, + NULL +}; + + +/* Nothing to actually do upon release */ +static void iio_trigger_sysfs_release(struct device *dev) +{ +} + +static struct device iio_sysfs_trig_dev = { + .bus = &iio_bus_type, + .groups = iio_sysfs_trig_groups, + .release = &iio_trigger_sysfs_release, +}; + +static void iio_sysfs_trigger_work(struct irq_work *work) +{ + struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig, + work); + + iio_trigger_poll(trig->trig, 0); +} + +static ssize_t iio_sysfs_trigger_poll(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig); + + irq_work_queue(&sysfs_trig->work); + + return count; +} + +static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); + +static struct attribute *iio_sysfs_trigger_attrs[] = { + &dev_attr_trigger_now.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trigger_attr_group = { + .attrs = iio_sysfs_trigger_attrs, +}; + +static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { + &iio_sysfs_trigger_attr_group, + NULL +}; + +static const struct iio_trigger_ops iio_sysfs_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_sysfs_trigger_probe(int id) +{ + struct iio_sysfs_trig *t; + int ret; + bool foundit = false; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (foundit) { + ret = -EINVAL; + goto out1; + } + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + ret = -ENOMEM; + goto out1; + } + t->id = id; + t->trig = iio_trigger_alloc("sysfstrig%d", id); + if (!t->trig) { + ret = -ENOMEM; + goto free_t; + } + + t->trig->dev.groups = iio_sysfs_trigger_attr_groups; + t->trig->ops = &iio_sysfs_trigger_ops; + t->trig->dev.parent = &iio_sysfs_trig_dev; + iio_trigger_set_drvdata(t->trig, t); + + init_irq_work(&t->work, iio_sysfs_trigger_work); + + ret = iio_trigger_register(t->trig); + if (ret) + goto out2; + list_add(&t->l, &iio_sysfs_trig_list); + __module_get(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; + +out2: + iio_trigger_put(t->trig); +free_t: + kfree(t); +out1: + mutex_unlock(&iio_syfs_trig_list_mut); + return ret; +} + +static int iio_sysfs_trigger_remove(int id) +{ + bool foundit = false; + struct iio_sysfs_trig *t; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (!foundit) { + mutex_unlock(&iio_syfs_trig_list_mut); + return -EINVAL; + } + + iio_trigger_unregister(t->trig); + iio_trigger_free(t->trig); + + list_del(&t->l); + kfree(t); + module_put(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; +} + + +static int __init iio_sysfs_trig_init(void) +{ + device_initialize(&iio_sysfs_trig_dev); + dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); + return device_add(&iio_sysfs_trig_dev); +} +module_init(iio_sysfs_trig_init); + +static void __exit iio_sysfs_trig_exit(void) +{ + device_unregister(&iio_sysfs_trig_dev); +} +module_exit(iio_sysfs_trig_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-sysfs"); |
