* [PATCH 00/14] IIO V4. Industrial I/O Subsystem @ 2009-07-02 13:47 Jonathan Cameron 2009-07-02 13:47 ` [PATCH 01/14] IIO: core support for device registration and management Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Major changes since last version: * Allocation of most internal structures now handled in a much simpler fashion - very close to how input does it. * Moved to cdev based dynamic allocation of all chrdevs. * Cleaner and more modular code. * Now works fine with udev * Patch series reordered in order to aid review by adding devices as soon as there is sufficient support. * Software ring buffer code thoroughly reworked. Now much more reliable.now reasonably happy with the core design so all comments on the contents of the first patch especially welcome. * Numerous minor fixes, clarifications etc As is readily apparent from the patches, some are still very much at the proof of concept stage. The periodic rtc trigger is a tempoary stop gap until a more suitable timer interface is available. The gpio trigger is very minimal. Finally the documenation clearly still needs considerable refinement. Thanks to everyone who has helped with comments on the previous versions and to the users for bug reports etc. All comments on any aspect so design or indeed specific elements of the patches would be most welcome! More formal reviews also welcome if anyone has time. Suggestions relating to moving towards merging this also particuarly welcome. For now I'll still be reworking this patch set in response to reviews, so I wouldn't suggest using the tree as the basis of development just yet. git tree available at: (based on Linus' tree as of about 2 days ago) git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio_v4.git http://www.kernel.org/pub/scm/linux/kernel/git/jic23/iio_v4.git Updated white paper describing the subsystem to appear in the next few days. Note that I'm still not all that convinced by the name. Other suggestions would be most welcome. Jonathan Cameron (14): IIO: core support for device registration and management IIO: max1363 ADC driver IIO: tsl2561 digital light sensor core support IIO: lis3l02dq accelerometer core support IIO: kxsd9 accelerometer minimal support IIO: Add generic ring buffer support to the IIO core IIO: VTI sca3000 series accelerometer driver (spi) IIO: Trigger support added to core. IIO: Ring buffer: Initial pass at rarely locked ring buffer IIO: lis3l02dq ring buffer and data ready trigger support IIO: max1363 add software ring buffer support using ring_sw IIO: Periodic timer based trigger IIO: Proof of concept gpio trigger IIO: Initial documentation Documentation/iio/device.txt | 49 + Documentation/iio/iio_utils.h | 163 +++ Documentation/iio/lis3l02dqbuffersimple.c | 172 +++ Documentation/iio/overview.txt | 62 ++ Documentation/iio/ring.txt | 62 ++ Documentation/iio/trigger.txt | 38 + Documentation/iio/userspace.txt | 60 ++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/iio/Kconfig | 42 + drivers/iio/Makefile | 16 + drivers/iio/accel/Kconfig | 28 + drivers/iio/accel/Makefile | 11 + drivers/iio/accel/kxsd9.c | 395 +++++++ drivers/iio/accel/lis3l02dq.h | 232 ++++ drivers/iio/accel/lis3l02dq_core.c | 925 ++++++++++++++++ drivers/iio/accel/lis3l02dq_ring.c | 599 +++++++++++ drivers/iio/accel/sca3000.h | 273 +++++ drivers/iio/accel/sca3000_core.c | 1509 +++++++++++++++++++++++++++ drivers/iio/accel/sca3000_ring.c | 334 ++++++ drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 8 + drivers/iio/adc/max1363.h | 269 +++++ drivers/iio/adc/max1363_core.c | 622 +++++++++++ drivers/iio/adc/max1363_ring.c | 235 +++++ drivers/iio/industrialio-core.c | 847 +++++++++++++++ drivers/iio/industrialio-ring.c | 566 ++++++++++ drivers/iio/industrialio-trigger.c | 400 +++++++ drivers/iio/light/Kconfig | 14 + drivers/iio/light/Makefile | 5 + drivers/iio/light/tsl2561.c | 275 +++++ drivers/iio/ring_sw.c | 431 ++++++++ drivers/iio/trigger/Kconfig | 18 + drivers/iio/trigger/Makefile | 5 + drivers/iio/trigger/iio-trig-gpio.c | 203 ++++ drivers/iio/trigger/iio-trig-periodic-rtc.c | 230 ++++ include/linux/iio/accel.h | 167 +++ include/linux/iio/adc.h | 13 + include/linux/iio/chrdev.h | 118 +++ include/linux/iio/gpio_trigger.h | 6 + include/linux/iio/iio.h | 411 ++++++++ include/linux/iio/light.h | 12 + include/linux/iio/prtc_trigger.h | 7 + include/linux/iio/ring_generic.h | 283 +++++ include/linux/iio/ring_hw.h | 22 + include/linux/iio/ring_sw.h | 189 ++++ include/linux/iio/sca3000.h | 37 + include/linux/iio/sysfs.h | 293 ++++++ include/linux/iio/trigger.h | 148 +++ include/linux/iio/trigger_consumer.h | 45 + 50 files changed, 10866 insertions(+), 0 deletions(-) create mode 100644 Documentation/iio/device.txt create mode 100644 Documentation/iio/iio_utils.h create mode 100644 Documentation/iio/lis3l02dqbuffersimple.c create mode 100644 Documentation/iio/overview.txt create mode 100644 Documentation/iio/ring.txt create mode 100644 Documentation/iio/trigger.txt create mode 100644 Documentation/iio/userspace.txt create mode 100644 drivers/iio/Kconfig create mode 100644 drivers/iio/Makefile create mode 100644 drivers/iio/accel/Kconfig create mode 100644 drivers/iio/accel/Makefile create mode 100644 drivers/iio/accel/kxsd9.c create mode 100644 drivers/iio/accel/lis3l02dq.h create mode 100644 drivers/iio/accel/lis3l02dq_core.c create mode 100644 drivers/iio/accel/lis3l02dq_ring.c create mode 100644 drivers/iio/accel/sca3000.h create mode 100644 drivers/iio/accel/sca3000_core.c create mode 100644 drivers/iio/accel/sca3000_ring.c create mode 100644 drivers/iio/adc/Kconfig create mode 100644 drivers/iio/adc/Makefile create mode 100644 drivers/iio/adc/max1363.h create mode 100644 drivers/iio/adc/max1363_core.c create mode 100644 drivers/iio/adc/max1363_ring.c create mode 100644 drivers/iio/industrialio-core.c create mode 100644 drivers/iio/industrialio-ring.c create mode 100644 drivers/iio/industrialio-trigger.c create mode 100644 drivers/iio/light/Kconfig create mode 100644 drivers/iio/light/Makefile create mode 100644 drivers/iio/light/tsl2561.c create mode 100644 drivers/iio/ring_sw.c create mode 100644 drivers/iio/trigger/Kconfig create mode 100644 drivers/iio/trigger/Makefile create mode 100644 drivers/iio/trigger/iio-trig-gpio.c create mode 100644 drivers/iio/trigger/iio-trig-periodic-rtc.c create mode 100644 include/linux/iio/accel.h create mode 100644 include/linux/iio/adc.h create mode 100644 include/linux/iio/chrdev.h create mode 100644 include/linux/iio/gpio_trigger.h create mode 100644 include/linux/iio/iio.h create mode 100644 include/linux/iio/light.h create mode 100644 include/linux/iio/prtc_trigger.h create mode 100644 include/linux/iio/ring_generic.h create mode 100644 include/linux/iio/ring_hw.h create mode 100644 include/linux/iio/ring_sw.h create mode 100644 include/linux/iio/sca3000.h create mode 100644 include/linux/iio/sysfs.h create mode 100644 include/linux/iio/trigger.h create mode 100644 include/linux/iio/trigger_consumer.h ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 01/14] IIO: core support for device registration and management 2009-07-02 13:47 [PATCH 00/14] IIO V4. Industrial I/O Subsystem Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 02/14] IIO: max1363 ADC driver Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> --- Changes since V2. * New structure, much closer to that on input subsystem. An IIO device now has an associated struct device which has the underlying hardware device (i2c, spi etc) as its parent. This in turn has struct devices as children for event interfaces. * Chrdevs now handled via cdev structures with dynamically allocated major number. * Ring buffer and trigger functionality made more modular (and hence hardly show up in the core at all). This results in device drivers having to call a couple more functions but results in a much cleaner core. Also trigger consumer head separated from main trigger header. * Scan mode selection etc changed to being via access functions to allow for simple change of implementation as apprpriate as device requirements become apparent. * Numerous clean ups and comment fixes. --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/iio/Kconfig | 11 + drivers/iio/Makefile | 6 + drivers/iio/industrialio-core.c | 847 ++++++++++++++++++++++++++++++++++ include/linux/iio/chrdev.h | 118 +++++ include/linux/iio/iio.h | 411 ++++++++++++++++ include/linux/iio/sysfs.h | 293 ++++++++++++ include/linux/iio/trigger_consumer.h | 26 + 9 files changed, 1715 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/Kconfig create mode 100644 drivers/iio/Makefile create mode 100644 drivers/iio/industrialio-core.c create mode 100644 include/linux/iio/chrdev.h create mode 100644 include/linux/iio/iio.h create mode 100644 include/linux/iio/sysfs.h create mode 100644 include/linux/iio/trigger_consumer.h diff --git a/drivers/Kconfig b/drivers/Kconfig index 48bbdbe..cbd7978 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -110,6 +110,8 @@ source "drivers/vlynq/Kconfig" source "drivers/xen/Kconfig" +source "drivers/iio/Kconfig" + source "drivers/staging/Kconfig" source "drivers/platform/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index bc4205d..89d0722 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -107,6 +107,7 @@ obj-$(CONFIG_OF) += of/ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VIRTIO) += virtio/ obj-$(CONFIG_VLYNQ) += vlynq/ +obj-$(CONFIG_IIO) += iio/ obj-$(CONFIG_STAGING) += staging/ obj-y += platform/ obj-y += ieee802154/ diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig new file mode 100644 index 0000000..09e4101 --- /dev/null +++ b/drivers/iio/Kconfig @@ -0,0 +1,11 @@ +# +# Industrial I/O subsytem configuration +# + +menuconfig IIO + tristate "Industrial I/O support" + ---help--- + The industrial I/O subsystem provides a unified framework for + drivers for many different types of embedded sensors using a + number of different physical interfaces (i2c, spi etc). See + Documentation/industrialio for more information. diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile new file mode 100644 index 0000000..92b462a --- /dev/null +++ b/drivers/iio/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_IIO) += industrialio.o +industrialio-y := industrialio-core.o diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c new file mode 100644 index 0000000..e80800d --- /dev/null +++ b/drivers/iio/industrialio-core.c @@ -0,0 +1,847 @@ +/* The industrial I/O core + * + * 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. + * + * Based on elements of hwmon and input subsystems. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/idr.h> +#include <linux/kdev_t.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> + +#define IIO_ID_PREFIX "device" +#define IIO_ID_FORMAT IIO_ID_PREFIX "%d" + +/* IDR to assign each registered device a unique id*/ +static DEFINE_IDR(iio_idr); + +/* IDR for general event identifiers */ +static DEFINE_IDR(iio_event_idr); +/* IDR to allocate character device minor numbers */ +static DEFINE_IDR(iio_chrdev_idr); +/* Lock used to protect both of the above */ +static DEFINE_SPINLOCK(iio_idr_lock); + +dev_t iio_devt; +EXPORT_SYMBOL(iio_devt); + +#define IIO_DEV_MAX 256 +static char *iio_nodename(struct device *dev) +{ + return kasprintf(GFP_KERNEL, "iio/%s", dev_name(dev)); +} + +struct class iio_class = { + .name = "iio", + .nodename = iio_nodename, +}; +EXPORT_SYMBOL(iio_class); + +void __iio_change_event(struct iio_detected_event_list *ev, + int ev_code, + s64 timestamp) +{ + ev->ev.id = ev_code; + ev->ev.timestamp = timestamp; +} +EXPORT_SYMBOL(__iio_change_event); + +/* Used both in the interrupt line put events and the ring buffer ones */ + +/* Note that in it's current form someone has to be listening before events + * are queued. Hence a client MUST open the chrdev before the ring buffer is + * switched on. + */ + int __iio_push_event(struct iio_event_interface *ev_int, + int ev_code, + s64 timestamp, + struct iio_shared_ev_pointer * + shared_pointer_p) +{ + struct iio_detected_event_list *ev; + int ret = 0; + + /* Does anyone care? */ + mutex_lock(&ev_int->event_list_lock); + if (test_bit(IIO_BUSY_BIT_POS, &ev_int->handler.flags)) { + if (ev_int->current_events == ev_int->max_events) + return 0; + ev = kmalloc(sizeof(*ev), GFP_KERNEL); + if (ev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ev->ev.id = ev_code; + ev->ev.timestamp = timestamp; + ev->shared_pointer = shared_pointer_p; + if (ev->shared_pointer) + shared_pointer_p->ev_p = ev; + + list_add_tail(&ev->list, &ev_int->det_events.list); + ev_int->current_events++; + mutex_unlock(&ev_int->event_list_lock); + wake_up_interruptible(&ev_int->wait); + } else + mutex_unlock(&ev_int->event_list_lock); + +error_ret: + return ret; +} +EXPORT_SYMBOL(__iio_push_event); + +int iio_push_event(struct iio_dev *dev_info, + int ev_line, + int ev_code, + s64 timestamp) +{ + return __iio_push_event(&dev_info->event_interfaces[ev_line], + ev_code, timestamp, NULL); +} +EXPORT_SYMBOL(iio_push_event); + +/* Generic interrupt line interrupt handler */ +irqreturn_t iio_interrupt_handler(int irq, void *_int_info) +{ + struct iio_interrupt *int_info = _int_info; + struct iio_dev *dev_info = int_info->dev_info; + struct iio_event_handler_list *p; + s64 time_ns; + unsigned long flags; + + spin_lock_irqsave(&int_info->ev_list_lock, flags); + if (list_empty(&int_info->ev_list)) { + spin_unlock_irqrestore(&int_info->ev_list_lock, flags); + return IRQ_NONE; + } + + time_ns = iio_get_time_ns(); + /* detect single element list*/ + if (list_is_singular(&int_info->ev_list)) { + disable_irq_nosync(irq); + p = list_first_entry(&int_info->ev_list, + struct iio_event_handler_list, + list); + /* single event handler - maybe shared */ + p->handler(dev_info, 1, time_ns, !(p->refcount > 1)); + } else + list_for_each_entry(p, &int_info->ev_list, list) { + disable_irq_nosync(irq); + p->handler(dev_info, 1, time_ns, 0); + } + spin_unlock_irqrestore(&int_info->ev_list_lock, flags); + + return IRQ_HANDLED; +} + +static struct iio_interrupt *iio_allocate_interrupt(void) +{ + struct iio_interrupt *i = kmalloc(sizeof *i, GFP_KERNEL); + if (i) { + spin_lock_init(&i->ev_list_lock); + INIT_LIST_HEAD(&i->ev_list); + } + return i; +} + +/* Confirming the validity of supplied irq is left to drivers.*/ +int iio_register_interrupt_line(unsigned int irq, + struct iio_dev *dev_info, + int line_number, + unsigned long type, + const char *name) +{ + int ret; + + dev_info->interrupts[line_number] = iio_allocate_interrupt(); + if (dev_info->interrupts[line_number] == NULL) { + ret = -ENOMEM; + goto error_ret; + } + dev_info->interrupts[line_number]->line_number = line_number; + dev_info->interrupts[line_number]->irq = irq; + dev_info->interrupts[line_number]->dev_info = dev_info; + + /* Possibly only request on demand? + * Can see this may complicate the handling of interrupts. + * However, with this approach we might end up handling lots of + * events no-one cares about.*/ + ret = request_irq(irq, + &iio_interrupt_handler, + type, + name, + dev_info->interrupts[line_number]); + +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_register_interrupt_line); + +/* This turns up an awful lot */ +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", to_iio_const_attr(attr)->string); +} +EXPORT_SYMBOL(iio_read_const_attr); + +/* Before this runs the interrupt generator must have been disabled */ +void iio_unregister_interrupt_line(struct iio_dev *dev_info, int line_number) +{ + /* make sure the interrupt handlers are all done */ + flush_scheduled_work(); + free_irq(dev_info->interrupts[line_number]->irq, + dev_info->interrupts[line_number]); + kfree(dev_info->interrupts[line_number]); +} +EXPORT_SYMBOL(iio_unregister_interrupt_line); + +/* Reference counted add and remove */ +void iio_add_event_to_list(struct iio_event_handler_list *el, + struct list_head *head) +{ + unsigned long flags; + struct iio_interrupt *inter = to_iio_interrupt(head); + + /* take mutex to protect this element */ + mutex_lock(&el->exist_lock); + if (el->refcount == 0) { + /* Take the event list spin lock */ + spin_lock_irqsave(&inter->ev_list_lock, flags); + list_add(&el->list, head); + spin_unlock_irqrestore(&inter->ev_list_lock, flags); + } + el->refcount++; + mutex_unlock(&el->exist_lock); +} +EXPORT_SYMBOL(iio_add_event_to_list); + +void iio_remove_event_from_list(struct iio_event_handler_list *el, + struct list_head *head) +{ + unsigned long flags; + struct iio_interrupt *inter = to_iio_interrupt(head); + + mutex_lock(&el->exist_lock); + el->refcount--; + if (el->refcount == 0) { + /* Take the event list spin lock */ + spin_lock_irqsave(&inter->ev_list_lock, flags); + list_del_init(&el->list); + spin_unlock_irqrestore(&inter->ev_list_lock, flags); + } + mutex_unlock(&el->exist_lock); +} +EXPORT_SYMBOL(iio_remove_event_from_list); + +ssize_t iio_event_chrdev_read(struct file *filep, + char *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_event_interface *ev_int = filep->private_data; + struct iio_detected_event_list *el; + int ret; + size_t len; + + mutex_lock(&ev_int->event_list_lock); + if (list_empty(&ev_int->det_events.list)) { + if (filep->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error_mutex_unlock; + } + mutex_unlock(&ev_int->event_list_lock); + /* Blocking on device; waiting for something to be there */ + ret = wait_event_interruptible(ev_int->wait, + !list_empty(&ev_int + ->det_events.list)); + if (ret) + goto error_ret; + /* Single access device so noone else can get the data */ + mutex_lock(&ev_int->event_list_lock); + } + + el = list_first_entry(&ev_int->det_events.list, + struct iio_detected_event_list, + list); + len = sizeof el->ev; + if (copy_to_user(buf, &(el->ev), len)) { + ret = -EFAULT; + goto error_mutex_unlock; + } + list_del(&el->list); + ev_int->current_events--; + mutex_unlock(&ev_int->event_list_lock); + /* + * Possible concurency issue if an update of this event is on its way + * through. May lead to new even being removed whilst the reported event + * was the unescalated event. In typical use case this is not a problem + * as userspace will say read half the buffer due to a 50% full event + * which would make the correct 100% full incorrect anyway. + */ + spin_lock(&el->shared_pointer->lock); + if (el->shared_pointer) + (el->shared_pointer->ev_p) = NULL; + spin_unlock(&el->shared_pointer->lock); + + kfree(el); + + return len; + +error_mutex_unlock: + mutex_unlock(&ev_int->event_list_lock); +error_ret: + + return ret; +} + +int iio_event_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_handler *hand = iio_cdev_to_handler(inode->i_cdev); + struct iio_event_interface *ev_int = hand->private; + struct iio_detected_event_list *el; + + mutex_lock(&ev_int->event_list_lock); + clear_bit(IIO_BUSY_BIT_POS, &ev_int->handler.flags); + /* + * In order to maintain a clean state for reopening, + * clear out any awaiting events. The mask will prevent + * any new __iio_push_event calls running. + */ + list_for_each_entry(el, &ev_int->det_events.list, list) { + list_del(&el->list); + kfree(el); + } + mutex_unlock(&ev_int->event_list_lock); + + return 0; +} + +int iio_event_chrdev_open(struct inode *inode, struct file *filep) +{ + struct iio_handler *hand = iio_cdev_to_handler(inode->i_cdev); + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &hand->flags)) { + fops_put(filep->f_op); + return -EBUSY; + } + filep->private_data = hand->private; + + return 0; +} + +static const struct file_operations iio_event_chrdev_fileops = { + .read = iio_event_chrdev_read, + .release = iio_event_chrdev_release, + .open = iio_event_chrdev_open, + .owner = THIS_MODULE, +}; + +static void iio_event_dev_release(struct device *dev) +{ + struct iio_event_interface *ev_int + = container_of(dev, struct iio_event_interface, dev); + cdev_del(&ev_int->handler.chrdev); + iio_device_free_chrdev_minor(MINOR(dev->devt)); +}; + +static struct device_type iio_event_type = { + .release = iio_event_dev_release, +}; + +int iio_device_get_chrdev_minor(void) +{ + int ret, val; + +idr_again: + if (unlikely(idr_pre_get(&iio_chrdev_idr, GFP_KERNEL) == 0)) + return -ENOMEM; + spin_lock(&iio_idr_lock); + ret = idr_get_new(&iio_chrdev_idr, NULL, &val); + spin_unlock(&iio_idr_lock); + if (unlikely(ret == -EAGAIN)) + goto idr_again; + else if (unlikely(ret)) + return ret; + if (val > IIO_DEV_MAX) + return -ENOMEM; + return val; +} + +void iio_device_free_chrdev_minor(int val) +{ + spin_lock(&iio_idr_lock); + idr_remove(&iio_chrdev_idr, val); + spin_unlock(&iio_idr_lock); +} + +int iio_setup_ev_int(struct iio_event_interface *ev_int, + const char *name, + struct module *owner, + struct device *dev) +{ + int ret, minor; + + ev_int->dev.class = &iio_class; + ev_int->dev.parent = dev; + ev_int->dev.type = &iio_event_type; + device_initialize(&ev_int->dev); + + minor = iio_device_get_chrdev_minor(); + if (minor < 0) { + ret = minor; + goto error_device_put; + } + ev_int->dev.devt = MKDEV(MAJOR(iio_devt), minor); + dev_set_name(&ev_int->dev, "%s", name); + + ret = device_add(&ev_int->dev); + if (ret) + goto error_free_minor; + + cdev_init(&ev_int->handler.chrdev, &iio_event_chrdev_fileops); + ev_int->handler.chrdev.owner = owner; + + mutex_init(&ev_int->event_list_lock); + /* discussion point - make this variable? */ + ev_int->max_events = 10; + ev_int->current_events = 0; + INIT_LIST_HEAD(&ev_int->det_events.list); + init_waitqueue_head(&ev_int->wait); + ev_int->handler.private = ev_int; + ev_int->handler.flags = 0; + + ret = cdev_add(&ev_int->handler.chrdev, ev_int->dev.devt, 1); + if (ret) + goto error_unreg_device; + + return 0; + +error_unreg_device: + device_unregister(&ev_int->dev); +error_free_minor: + iio_device_free_chrdev_minor(minor); +error_device_put: + put_device(&ev_int->dev); + + return ret; +} + +void iio_free_ev_int(struct iio_event_interface *ev_int) +{ + device_unregister(&ev_int->dev); + put_device(&ev_int->dev); +} + +static int __init iio_dev_init(void) +{ + int err; + + err = alloc_chrdev_region(&iio_devt, 0, IIO_DEV_MAX, "iio"); + if (err < 0) + printk(KERN_ERR "%s: failed to allocate char dev region\n", + __FILE__); + + return err; +} + +static void __exit iio_dev_exit(void) +{ + if (iio_devt) + unregister_chrdev_region(iio_devt, IIO_DEV_MAX); +} + +static int __init iio_init(void) +{ + int ret; + + /* Create sysfs class */ + ret = class_register(&iio_class); + if (ret < 0) { + printk(KERN_ERR + "%s could not create sysfs class\n", + __FILE__); + goto error_nothing; + } + + ret = iio_dev_init(); + if (ret < 0) + goto error_unregister_class; + + return 0; + +error_unregister_class: + class_unregister(&iio_class); +error_nothing: + return ret; +} + +static void __exit iio_exit(void) +{ + iio_dev_exit(); + class_unregister(&iio_class); +} + +static int iio_device_register_sysfs(struct iio_dev *dev_info) +{ + int ret = 0; + + ret = sysfs_create_group(&dev_info->dev.kobj, dev_info->attrs); + if (ret) { + dev_err(dev_info->dev.parent, + "Failed to register sysfs hooks\n"); + goto error_ret; + } + + if (dev_info->scan_el_attrs) { + ret = sysfs_create_group(&dev_info->dev.kobj, + dev_info->scan_el_attrs); + if (ret) + dev_err(&dev_info->dev, + "Failed to add sysfs scan els\n"); + } + +error_ret: + return ret; +} + +static void iio_device_unregister_sysfs(struct iio_dev *dev_info) +{ + if (dev_info->scan_el_attrs) + sysfs_remove_group(&dev_info->dev.kobj, + dev_info->scan_el_attrs); + + sysfs_remove_group(&dev_info->dev.kobj, dev_info->attrs); +} + +int iio_get_new_idr_val(struct idr *this_idr) +{ + int ret; + int val; + +idr_again: + if (unlikely(idr_pre_get(this_idr, GFP_KERNEL) == 0)) + return -ENOMEM; + + spin_lock(&iio_idr_lock); + ret = idr_get_new(this_idr, NULL, &val); + spin_unlock(&iio_idr_lock); + if (unlikely(ret == -EAGAIN)) + goto idr_again; + else if (unlikely(ret)) + return ret; + + return val; +} +EXPORT_SYMBOL(iio_get_new_idr_val); + +void iio_free_idr_val(struct idr *this_idr, int id) +{ + spin_lock(&iio_idr_lock); + idr_remove(this_idr, id); + spin_unlock(&iio_idr_lock); +} +EXPORT_SYMBOL(iio_free_idr_val); + +static int iio_device_register_id(struct iio_dev *dev_info, + struct idr *this_idr) +{ + + dev_info->id = iio_get_new_idr_val(&iio_idr); + if (dev_info->id < 0) + return dev_info->id; + return 0; +} + +static void iio_device_unregister_id(struct iio_dev *dev_info) +{ + iio_free_idr_val(&iio_idr, dev_info->id); +} + +static inline int __iio_add_event_config_attrs(struct iio_dev *dev_info, int i) +{ + int ret; + /*p for adding, q for removing */ + struct attribute **attrp, **attrq; + + if (dev_info->event_conf_attrs && dev_info->event_conf_attrs[i].attrs) { + attrp = dev_info->event_conf_attrs[i].attrs; + while (*attrp) { + ret = sysfs_add_file_to_group(&dev_info->dev.kobj, + *attrp, + dev_info + ->event_attrs[i].name); + if (ret) + goto error_ret; + attrp++; + } + } + return 0; + +error_ret: + attrq = dev_info->event_conf_attrs[i].attrs; + while (attrq != attrp) { + sysfs_remove_file_from_group(&dev_info->dev.kobj, + *attrq, + dev_info->event_attrs[i].name); + attrq++; + } + + return ret; +} + +static inline int __iio_remove_event_config_attrs(struct iio_dev *dev_info, + int i) +{ + struct attribute **attrq; + + if (dev_info->event_conf_attrs + && dev_info->event_conf_attrs[i].attrs) { + attrq = dev_info->event_conf_attrs[i].attrs; + while (*attrq) { + sysfs_remove_file_from_group(&dev_info->dev.kobj, + *attrq, + dev_info + ->event_attrs[i].name); + attrq++; + } + } + + return 0; +} + +static int iio_device_register_eventset(struct iio_dev *dev_info) +{ + int ret = 0, i, j; + + if (dev_info->num_interrupt_lines == 0) + return 0; + + dev_info->event_interfaces = + kzalloc(sizeof(struct iio_event_interface) + *dev_info->num_interrupt_lines, + GFP_KERNEL); + if (dev_info->event_interfaces == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + dev_info->interrupts = kzalloc(sizeof(struct iio_interrupt *) + *dev_info->num_interrupt_lines, + GFP_KERNEL); + if (dev_info->interrupts == NULL) { + ret = -ENOMEM; + goto error_free_event_interfaces; + } + + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + dev_info->event_interfaces[i].owner = dev_info->driver_module; + ret = iio_get_new_idr_val(&iio_event_idr); + if (ret) + goto error_free_setup_ev_ints; + else + dev_info->event_interfaces[i].id = ret; + + snprintf(dev_info->event_interfaces[i]._name, 20, + "event_line%d", + dev_info->event_interfaces[i].id); + + ret = iio_setup_ev_int(&dev_info->event_interfaces[i], + (const char *)(dev_info + ->event_interfaces[i] + ._name), + dev_info->driver_module, + &dev_info->dev); + if (ret) { + dev_err(&dev_info->dev, + "Could not get chrdev interface\n"); + iio_free_idr_val(&iio_event_idr, + dev_info->event_interfaces[i].id); + goto error_free_setup_ev_ints; + } + } + + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + snprintf(dev_info->event_interfaces[i]._attrname, 20, + "event_line%d_sources", i); + dev_info->event_attrs[i].name + = (const char *) + (dev_info->event_interfaces[i]._attrname); + ret = sysfs_create_group(&dev_info->dev.kobj, + &dev_info->event_attrs[i]); + if (ret) { + dev_err(&dev_info->dev, + "Failed to register sysfs for event attrs"); + goto error_remove_sysfs_interfaces; + } + } + + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + ret = __iio_add_event_config_attrs(dev_info, i); + if (ret) + goto error_unregister_config_attrs; + } + + return 0; + +error_unregister_config_attrs: + for (j = 0; j < i; j++) + __iio_remove_event_config_attrs(dev_info, i); + i = dev_info->num_interrupt_lines - 1; +error_remove_sysfs_interfaces: + for (j = 0; j < i; j++) + sysfs_remove_group(&dev_info->dev.kobj, + &dev_info->event_attrs[j]); + i = dev_info->num_interrupt_lines - 1; +error_free_setup_ev_ints: + for (j = 0; j < i; j++) { + iio_free_idr_val(&iio_event_idr, + dev_info->event_interfaces[i].id); + iio_free_ev_int(&dev_info->event_interfaces[j]); + } + kfree(dev_info->interrupts); +error_free_event_interfaces: + kfree(dev_info->event_interfaces); +error_ret: + + return ret; +} + +static void iio_device_unregister_eventset(struct iio_dev *dev_info) +{ + int i; + + if (dev_info->num_interrupt_lines == 0) + return; + for (i = 0; i < dev_info->num_interrupt_lines; i++) + sysfs_remove_group(&dev_info->dev.kobj, + &dev_info->event_attrs[i]); + + for (i = 0; i < dev_info->num_interrupt_lines; i++) { + iio_free_idr_val(&iio_event_idr, + dev_info->event_interfaces[i].id); + iio_free_ev_int(&dev_info->event_interfaces[i]); + } + kfree(dev_info->interrupts); + kfree(dev_info->event_interfaces); +} + +static void iio_dev_release(struct device *device) +{ + struct iio_dev *dev = to_iio_dev(device); + + iio_put(); + kfree(dev); +} + +static struct device_type iio_dev_type = { + .name = "iio_device", + .release = iio_dev_release, +}; + +struct iio_dev *iio_allocate_device(void) +{ + struct iio_dev *dev = kzalloc(sizeof *dev, GFP_KERNEL); + + if (dev) { + dev->dev.type = &iio_dev_type; + dev->dev.class = &iio_class; + device_initialize(&dev->dev); + dev_set_drvdata(&dev->dev, (void *)dev); + mutex_init(&dev->mlock); + iio_get(); + } + + return dev; +} +EXPORT_SYMBOL(iio_allocate_device); + +void iio_free_device(struct iio_dev *dev) +{ + if (dev) + iio_put_device(dev); +} +EXPORT_SYMBOL(iio_free_device); + +int iio_device_register(struct iio_dev *dev_info) +{ + int ret; + + ret = iio_device_register_id(dev_info, &iio_idr); + if (ret) { + dev_err(&dev_info->dev, "Failed to get id\n"); + goto error_ret; + } + dev_set_name(&dev_info->dev, "device%d", dev_info->id); + + ret = device_add(&dev_info->dev); + if (ret) + goto error_free_idr; + ret = iio_device_register_sysfs(dev_info); + if (ret) { + dev_err(dev_info->dev.parent, + "Failed to register sysfs interfaces\n"); + goto error_del_device; + } + ret = iio_device_register_eventset(dev_info); + if (ret) { + dev_err(dev_info->dev.parent, + "Failed to register event set \n"); + goto error_free_sysfs; + } + if (dev_info->modes & INDIO_RING_TRIGGERED) + iio_device_register_trigger_consumer(dev_info); + + return 0; + +error_free_sysfs: + iio_device_unregister_sysfs(dev_info); +error_del_device: + device_del(&dev_info->dev); +error_free_idr: + iio_device_unregister_id(dev_info); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_device_register); + +void iio_device_unregister(struct iio_dev *dev_info) +{ + if (dev_info->modes & INDIO_RING_TRIGGERED) + iio_device_unregister_trigger_consumer(dev_info); + iio_device_unregister_eventset(dev_info); + iio_device_unregister_sysfs(dev_info); + device_unregister(&dev_info->dev); + iio_device_unregister_id(dev_info); +} +EXPORT_SYMBOL(iio_device_unregister); + +void iio_put(void) +{ + module_put(THIS_MODULE); +} + +void iio_get(void) +{ + __module_get(THIS_MODULE); +} + +subsys_initcall(iio_init); +module_exit(iio_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Industrial I/O core"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/iio/chrdev.h b/include/linux/iio/chrdev.h new file mode 100644 index 0000000..8bc64bf --- /dev/null +++ b/include/linux/iio/chrdev.h @@ -0,0 +1,118 @@ +/* The industrial I/O core - character device related + * + * 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. + */ + +#ifndef _IIO_CHRDEV_H_ +#define _IIO_CHRDEV_H_ +struct iio_dev; + +/** + * struct iio_handler - Structure used to specify file operations + * for a particular chrdev + * @chrdev: character device structure + * @id: the location in the handler table - used for deallocation. + * @flags: file operations related flags including busy flag. + * @private: handler specific data used by the fileops registered with + * the chrdev. + */ +struct iio_handler { + struct cdev chrdev; + int id; + unsigned long flags; + void *private; +}; + +#define iio_cdev_to_handler(cd) \ + container_of(cd, struct iio_handler, chrdev) + +/** + * struct iio_event_data - The actual event being pushed to userspace + * @id: event identifier + * @timestamp: best estimate of time of event occurance (often from + * the interrupt handler) + */ +struct iio_event_data { + int id; + s64 timestamp; +}; + +/** + * struct iio_detected_event_list - list element for events that have occured + * @list: linked list header + * @ev: the event itself + * @shared_pointer: used when the event is shared - i.e. can be escallated + * on demand (eg ring buffer 50%->100% full) + */ +struct iio_detected_event_list { + struct list_head list; + struct iio_event_data ev; + struct iio_shared_ev_pointer *shared_pointer; +}; +/** + * struct iio_shared_ev_pointer - allows shared events to identify if currently + * in the detected event list + * @ev_p: pointer to detected event list element (null if not in list) + * @lock: protect this element to prevent simultaneous edit and remove + */ +struct iio_shared_ev_pointer { + struct iio_detected_event_list *ev_p; + spinlock_t lock; +}; + +/** + * struct iio_event_interface - chrdev interface for an event line + * @dev: device assocated with event interface + * @handler: fileoperations and related control for the chrdev + * @wait: wait queue to allow blocking reads of events + * @event_list_lock: mutex to protect the list of detected events + * @det_events: list of detected events + * @max_events: maximum number of events before new ones are dropped + * @current_events: number of events in detected list + * @id: indentifier to allow the event interface to know which + * physical line it corresponds to + * @owner: ensure the driver module owns the file, not iio + * @private: driver specific data + * @_name: used internally to store the sysfs name for minor id + * attribute + */ +struct iio_event_interface { + struct device dev; + struct iio_handler handler; + wait_queue_head_t wait; + struct mutex event_list_lock; + struct iio_detected_event_list det_events; + int max_events; + int current_events; + int id; + struct iio_chrdev_minor_attr attr; + struct module *owner; + void *private; + char _name[20]; + char _attrname[20]; +}; + +/** + * struct iio_event_handler_list - element in list of handlers for events + * @list: list header + * @refcount: as the handler may be shared between multiple device + * side events, reference counting ensures clean removal + * @exist_lock: prevents race conditions related to refcount useage. + * @handler: event handler function - called on event if this + * event_handler is enabled. + * + * Each device has one list of these per interrupt line + **/ +struct iio_event_handler_list { + struct list_head list; + int refcount; + struct mutex exist_lock; + int (*handler)(struct iio_dev *dev_info, int index, s64 timestamp, + int no_test); +}; + +#endif diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h new file mode 100644 index 0000000..2eaf8f6 --- /dev/null +++ b/include/linux/iio/iio.h @@ -0,0 +1,411 @@ +/* The industrial I/O core + * + * 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. + */ + +#ifndef _INDUSTRIAL_IO_H_ +#define _INDUSTRIAL_IO_H_ + +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/chrdev.h> + +/* IIO TODO LIST */ +/* Static device specific elements (conversion factors etc) + * should be exported via sysfs + * + * Provide means of adjusting timer accuracy. + * Currently assumes nano seconds. + */ + +/* Event interface flags */ +#define IIO_BUSY_BIT_POS 1 + +struct iio_dev; + +/** + * iio_get_time_ns() - utility function to get a time stamp for events etc + **/ +static inline s64 iio_get_time_ns(void) +{ + struct timespec ts; + /* + * calls getnstimeofday. + * If hrtimers then up to ns accurate, if not microsecond. + */ + ktime_get_real_ts(&ts); + + return timespec_to_ns(&ts); +} + +/** + * iio_add_event_to_list() - Wraps adding to event lists + * @el: the list element of the event to be handled. + * @head: the list associated with the event handler being used. + * + * Does reference counting to allow shared handlers. + **/ +void iio_add_event_to_list(struct iio_event_handler_list *el, + struct list_head *head); + +/** + * iio_remove_event_from_list() - Wraps removing from event list + * @el: element to be removed + * @head: associate list head for the interrupt handler. + * + * Does reference counting to allow shared handlers. + **/ +void iio_remove_event_from_list(struct iio_event_handler_list *el, + struct list_head *head); + +/* Device operating modes */ +#define INDIO_DIRECT_MODE 0x01 +#define INDIO_RING_TRIGGERED 0x02 +#define INDIO_RING_HARDWARE_BUFFER 0x08 + +#define INDIO_ALL_RING_MODES (INDIO_RING_TRIGGERED | INDIO_RING_HARDWARE_BUFFER) + +/* Vast majority of this is set by the industrialio subsystem on a + * call to iio_device_register. */ + +/** + * struct iio_dev - industrial I/O device + * @id: [INTERN] used to identify device internally + * @dev_data: [DRIVER] device specific data + * @modes: [DRIVER] operating modes supported by device + * @currentmode: [DRIVER] current operating mode + * @dev: [DRIVER] device structure, should be assigned a parent + * and owner + * @attrs: [DRIVER] general purpose device attributes + * @driver_module: [DRIVER] module structure used to ensure correct + * ownership of chrdevs etc + * @num_interrupt_lines:[DRIVER] number of physical interrupt lines from device + * @interrupts: [INTERN] interrupt line specific event lists etc + * @event_attrs: [DRIVER] event control attributes + * @event_conf_attrs: [DRIVER] event configuration attributes + * @event_interfaces: [INTERN] event chrdevs associated with interrupt lines + * @ring: [DRIVER] any ring buffer present + * @mlock: [INTERN] lock used to prevent simultaneous device state + * changes + * @scan_el_attrs: [DRIVER] control of scan elements if that scan mode + * control method is used + * @scan_count: [INTERN] the number of elements in the current scan mode + * @scan_mask: [INTERN] bitmask used in masking scan mode elements + * @scan_timestamp: [INTERN] does the scan mode include a timestamp + * @trig: [INTERN] current device trigger (ring buffer modes) + * @pollfunc: [DRIVER] function run on trigger being recieved + **/ +struct iio_dev { + int id; + void *dev_data; + int modes; + int currentmode; + struct device dev; + const struct attribute_group *attrs; + struct module *driver_module; + + int num_interrupt_lines; + struct iio_interrupt **interrupts; + struct attribute_group *event_attrs; + struct attribute_group *event_conf_attrs; + + struct iio_event_interface *event_interfaces; + + struct iio_ring_buffer *ring; + struct mutex mlock; + + struct attribute_group *scan_el_attrs; + int scan_count; + + u16 scan_mask; + bool scan_timestamp; + struct iio_trigger *trig; + struct iio_poll_func *pollfunc; +}; + +/* + * These are mainly provided to allow for a change of implementation if a device + * has a large number of scan elements + */ +#define IIO_MAX_SCAN_LENGTH 15 + +static inline int iio_scan_mask_query(struct iio_dev *dev_info, int bit) +{ + if (bit > IIO_MAX_SCAN_LENGTH) + return -EINVAL; + else + return !!(dev_info->scan_mask & (1 << bit)); +}; + +static inline int iio_scan_mask_set(struct iio_dev *dev_info, int bit) +{ + if (bit > IIO_MAX_SCAN_LENGTH) + return -EINVAL; + dev_info->scan_mask |= (1 << bit); + dev_info->scan_count++; + return 0; +}; + +static inline int iio_scan_mask_clear(struct iio_dev *dev_info, int bit) +{ + if (bit > IIO_MAX_SCAN_LENGTH) + return -EINVAL; + dev_info->scan_mask &= ~(1 << bit); + dev_info->scan_count--; + return 0; +}; + +/** + * iio_scan_mask_count_to_right() - how many scan elements occur before here + * @dev_info: the iio_device whose scan mode we are querying + * @bit: which number scan element is this + **/ +static inline int iio_scan_mask_count_to_right(struct iio_dev *dev_info, +int bit) +{ + int count = 0; + int mask = (1 << bit); + if (bit > IIO_MAX_SCAN_LENGTH) + return -EINVAL; + while (mask) { + mask >>= 1; + if (mask & dev_info->scan_mask) + count++; + } + + return count; +} + +/** + * iio_device_register() - register a device with the IIO subsystem + * @dev_info: Device structure filled by the device driver + **/ +int iio_device_register(struct iio_dev *dev_info); + +/** + * iio_device_unregister() - unregister a device from the IIO subsystem + * @dev_info: Device structure representing the device. + **/ +void iio_device_unregister(struct iio_dev *dev_info); + +/** + * struct iio_interrupt - wrapper used to allow easy handling of multiple + * physical interrupt lines + * @dev_info: the iio device for which the is an interrupt line + * @line_number: associated line number + * @id: idr allocated unique id number + * @irq: associate interrupt number + * @ev_list: event handler list for associated events + * @ev_list_lock: ensure only one access to list at a time + **/ +struct iio_interrupt { + struct iio_dev *dev_info; + int line_number; + int id; + int irq; + struct list_head ev_list; + spinlock_t ev_list_lock; +}; + +#define to_iio_interrupt(i) container_of(i, struct iio_interrupt, ev_list) + +/** + * iio_register_interrupt_line() - Tell IIO about interrupt lines + * + * @irq: Typically provided via platform data + * @dev_info: IIO device info structure for device + * @line_number: Which interrupt line of the device is this? + * @type: Interrupt type (e.g. edge triggered etc) + * @name: Identifying name. + **/ +int iio_register_interrupt_line(unsigned int irq, + struct iio_dev *dev_info, + int line_number, + unsigned long type, + const char *name); + +void iio_unregister_interrupt_line(struct iio_dev *dev_info, + int line_number); + + + +/** + * iio_push_event() - try to add event to the list for userspace reading + * @dev_info: IIO device structure + * @ev_line: Which event line (hardware interrupt) + * @ev_code: What event + * @timestamp: When the event occured + **/ +int iio_push_event(struct iio_dev *dev_info, + int ev_line, + int ev_code, + s64 timestamp); + +/** + * struct iio_work_cont - container for when singleton handler case matters + * @ws: [DEVICE]work_struct when not only possible event + * @ws_nocheck: [DEVICE]work_struct when only possible event + * @address: [DEVICE]associated register address + * @mask: [DEVICE]associated mask for identifying event source + * @st: [DEVICE]device specific state information + **/ +struct iio_work_cont { + struct work_struct ws; + struct work_struct ws_nocheck; + int address; + int mask; + void *st; +}; + +#define to_iio_work_cont_check(_ws) \ + container_of(_ws, struct iio_work_cont, ws) + +#define to_iio_work_cont_no_check(_ws) \ + container_of(_ws, struct iio_work_cont, ws_nocheck) + +/** + * iio_init_work_cont() - intiialize the elements of a work container + * @cont: the work container + * @_checkfunc: function called when there are multiple possible int sources + * @_nocheckfunc: function for when there is only one int source + * @_add: driver dependant, typically a register address + * @_mask: driver dependant, typically a bit mask for a register + * @_st: driver dependant, typically pointer to a device state structure + **/ +static inline void +iio_init_work_cont(struct iio_work_cont *cont, + void (*_checkfunc)(struct work_struct *), + void (*_nocheckfunc)(struct work_struct *), + int _add, int _mask, void *_st) +{ + INIT_WORK(&(cont)->ws, _checkfunc); + INIT_WORK(&(cont)->ws_nocheck, _nocheckfunc); + cont->address = _add; + cont->mask = _mask; + cont->st = _st; +} +/** + * __iio_push_event() tries to add an event to the list associated with a chrdev + * @ev_int: the event interface to which we are pushing the event + * @ev_code: the outgoing event code + * @timestamp: timestamp of the event + * @shared_pointer_p: the shared event pointer + **/ +int __iio_push_event(struct iio_event_interface *ev_int, + int ev_code, + s64 timestamp, + struct iio_shared_ev_pointer* + shared_pointer_p); +/** + * __iio_change_event() change an event code in case of event escallation + * @ev: the evnet to be changed + * @ev_code: new event code + * @timestamp: new timestamp + **/ +void __iio_change_event(struct iio_detected_event_list *ev, + int ev_code, + s64 timestamp); + +/** + * iio_setup_ev_int() Configure an event interface (chrdev) + * @name: name used for resulting sysfs directory etc. + * @ev_int: interface we are configuring + * @owner: module that is responsible for registering this ev_int + * @dev: device whose ev_int this is + **/ +int iio_setup_ev_int(struct iio_event_interface *ev_int, + const char *name, + struct module *owner, + struct device *dev); + +void iio_free_ev_int(struct iio_event_interface *ev_int); + +/** + * iio_allocate_chrdev() - Allocate a chrdev + * @handler: struct that contains relevant file handling for chrdev + * @dev_info: iio_dev for which chrdev is being created + **/ +int iio_allocate_chrdev(struct iio_handler *handler, struct iio_dev *dev_info); +void iio_deallocate_chrdev(struct iio_handler *handler); + +/* Used to distinguish between bipolar and unipolar scan elemenents. + * Whilst this may seem obvious, we may well want to change the representation + * in the future!*/ +#define IIO_SIGNED(a) -(a) +#define IIO_UNSIGNED(a) (a) + +extern dev_t iio_devt; +extern struct class iio_class; + +/** + * iio_put_device() - reference counted deallocated of struct device + * @dev: the iio_device containing the device + **/ +static inline void iio_put_device(struct iio_dev *dev) +{ + if (dev) + put_device(&dev->dev); +}; + +/** + * to_iio_dev() - get iio_dev for which we have have the struct device + * @d: the struct device + **/ +static inline struct iio_dev *to_iio_dev(struct device *d) +{ + return container_of(d, struct iio_dev, dev); +}; + +/** + * iio_dev_get_devdata() - helper function gets device specific data + * @d: the iio_dev associated with the device + **/ +static inline void *iio_dev_get_devdata(struct iio_dev *d) +{ + return d->dev_data; +} + +/** + * iio_allocate_device() - allocate an iio_dev from a driver + **/ +struct iio_dev *iio_allocate_device(void); + +/** + * iio_free_device() - free an iio_dev from a driver + **/ +void iio_free_device(struct iio_dev *dev); + +/** + * iio_put() - internal module reference count reduce + **/ +void iio_put(void); + +/** + * iio_get() - internal module reference count increase + **/ +void iio_get(void); + +/* Ring buffer related */ +int iio_device_get_chrdev_minor(void); +void iio_device_free_chrdev_minor(int val); + +/** + * iio_ring_enabled() helper function to test if any form of ring enabled + **/ +static inline bool iio_ring_enabled(struct iio_dev *dev_info) +{ + return dev_info->currentmode + & (INDIO_RING_TRIGGERED + | INDIO_RING_HARDWARE_BUFFER); +}; + +struct idr; + +int iio_get_new_idr_val(struct idr *this_idr); +void iio_free_idr_val(struct idr *this_idr, int id); +#endif /* _INDUSTRIAL_IO_H_ */ diff --git a/include/linux/iio/sysfs.h b/include/linux/iio/sysfs.h new file mode 100644 index 0000000..d003d36 --- /dev/null +++ b/include/linux/iio/sysfs.h @@ -0,0 +1,293 @@ +/* The industrial I/O core + * + *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. + * + * General attributes + */ + +#ifndef _INDUSTRIAL_IO_SYSFS_H_ +#define _INDUSTRIAL_IO_SYSFS_H_ + +#include <linux/iio/iio.h> + +/** + * struct iio_event_attribute - event control attribute + * @dev_attr: underlying device attribute + * @mask: mask for the event when detecting + * @listel: list header to allow addition to list of event handlers +*/ +struct iio_event_attr { + struct device_attribute dev_attr; + int mask; + struct iio_event_handler_list *listel; +}; + +#define to_iio_event_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_event_attr, dev_attr) + +/** + * struct iio_chrdev_minor_attr - simple attribute to allow reading of chrdev + * minor number + * @dev_attr: underlying device attribute + * @minor: the minor number + */ +struct iio_chrdev_minor_attr { + struct device_attribute dev_attr; + int minor; +}; + +void +__init_iio_chrdev_minor_attr(struct iio_chrdev_minor_attr *minor_attr, + const char *name, + struct module *owner, + int id); + + +#define to_iio_chrdev_minor_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_chrdev_minor_attr, dev_attr); + +/** + * struct iio_dev_attr - iio specific device attribute + * @dev_attr: underlying device attribute + * @address: associated register address + */ +struct iio_dev_attr { + struct device_attribute dev_attr; + int address; + int val2; +}; + +#define to_iio_dev_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_dev_attr, dev_attr) + +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *len); + +/** + * struct iio_const_attr - constant device specific attribute + * often used for things like available modes + */ +struct iio_const_attr { + const char *string; + struct device_attribute dev_attr; +}; + +#define to_iio_const_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_const_attr, dev_attr) + +/* Some attributes will be hard coded (device dependant) and not require an + address, in these cases pass a negative */ +#define IIO_ATTR(_name, _mode, _show, _store, _addr) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .address = _addr } + +#define IIO_ATTR_2(_name, _mode, _show, _store, _addr, _val2) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .address = _addr, \ + .val2 = _val2 } + +#define IIO_DEVICE_ATTR(_name, _mode, _show, _store, _addr) \ + struct iio_dev_attr iio_dev_attr_##_name \ + = IIO_ATTR(_name, _mode, _show, _store, _addr) + + +#define IIO_DEVICE_ATTR_2(_name, _mode, _show, _store, _addr, _val2) \ + struct iio_dev_attr iio_dev_attr_##_name \ + = IIO_ATTR_2(_name, _mode, _show, _store, _addr, _val2) + +#define IIO_CONST_ATTR(_name, _string) \ + struct iio_const_attr iio_const_attr_##_name \ + = { .string = _string, \ + .dev_attr = __ATTR(_name, S_IRUGO, iio_read_const_attr, NULL)} + +/* Generic attributes of onetype or another */ + +/** + * IIO_DEV_ATTR_REG: revision number for the device + * + * Very much device dependent. + **/ +#define IIO_DEV_ATTR_REV(_show) \ + IIO_DEVICE_ATTR(revision, S_IRUGO, _show, NULL, 0) +/** + * IIO_DEV_ATTR_NAME: chip type dependant identifier + **/ +#define IIO_DEV_ATTR_NAME(_show) \ + IIO_DEVICE_ATTR(name, S_IRUGO, _show, NULL, 0) + +/** + * IIO_DEV_ATTR_SAMP_FREQ: sets any internal clock frequency + **/ +#define IIO_DEV_ATTR_SAMP_FREQ(_mode, _show, _store) \ + IIO_DEVICE_ATTR(sampling_frequency, _mode, _show, _store, 0) + +/** + * IIO_DEV_ATTR_AVAIL_SAMP_FREQ: list available sampling frequencies. + * + * May be mode dependant on some devices + **/ +#define IIO_DEV_ATTR_AVAIL_SAMP_FREQ(_show) \ + IIO_DEVICE_ATTR(available_sampling_frequency, S_IRUGO, _show, NULL, 0) + +/** + * IIO_DEV_ATTR_CONST_AVAIL_SAMP_FREQ: list available sampling frequencies. + * + * Constant version + **/ +#define IIO_CONST_ATTR_AVAIL_SAMP_FREQ(_string) \ + IIO_CONST_ATTR(available_sampling_frequency, _string) +/** + * IIO_DEV_ATTR_SCAN_MODE: select a scan mode + * + * This is used when only certain combinations of inputs may be read in one + * scan. + **/ +#define IIO_DEV_ATTR_SCAN_MODE(_mode, _show, _store) \ + IIO_DEVICE_ATTR(scan_mode, _mode, _show, _store, 0) +/** + * IIO_DEV_ATTR_AVAIL_SCAN_MODES: list available scan modes + **/ +#define IIO_DEV_ATTR_AVAIL_SCAN_MODES(_show) \ + IIO_DEVICE_ATTR(available_scan_modes, S_IRUGO, _show, NULL, 0) + +/** + * IIO_DEV_ATTR_SCAN: result of scan of multiple channels + **/ +#define IIO_DEV_ATTR_SCAN(_show) \ + IIO_DEVICE_ATTR(scan, S_IRUGO, _show, NULL, 0); + +/** + * IIO_DEV_ATTR_INPUT: direct read of a single input channel + **/ +#define IIO_DEV_ATTR_INPUT(_number, _show) \ + IIO_DEVICE_ATTR(in##_number, S_IRUGO, _show, NULL, _number) + + +/** + * IIO_DEV_ATTR_SW_RING_ENABLE: enable software ring buffer + * + * Success may be dependant on attachment of trigger previously + **/ +#define IIO_DEV_ATTR_SW_RING_ENABLE(_show, _store) \ + IIO_DEVICE_ATTR(sw_ring_enable, S_IRUGO | S_IWUSR, _show, _store, 0) + +/** + * IIO_DEV_ATTR_HW_RING_ENABLE: enable hardware ring buffer + * + * This is a different attribute from the software one as one can invision + * schemes where a combination of the two may be used. + **/ +#define IIO_DEV_ATTR_HW_RING_ENABLE(_show, _store) \ + IIO_DEVICE_ATTR(hw_ring_enable, S_IRUGO | S_IWUSR, _show, _store, 0) + +/** + * IIO_DEV_ATTR_BPSE: set number of bits per scan element + **/ +#define IIO_DEV_ATTR_BPSE(_mode, _show, _store) \ + IIO_DEVICE_ATTR(bpse, _mode, _show, _store, 0) + +/** + * IIO_DEV_ATTR_BPSE_AVAILABLE: no of bits per scan element supported + **/ +#define IIO_DEV_ATTR_BPSE_AVAILABLE(_show) \ + IIO_DEVICE_ATTR(bpse_available, S_IRUGO, _show, NULL, 0) + +/** + * IIO_DEV_ATTR_TEMP: many sensors have auxiliary temperature sensors + **/ +#define IIO_DEV_ATTR_TEMP(_show) \ + IIO_DEVICE_ATTR(temp, S_IRUGO, _show, NULL, 0) +/** + * IIO_EVENT_SH: generic shared event handler + * + * This is used in cases where more than one event may result from a single + * handler. Often the case that some alarm register must be read and multiple + * alarms may have been triggered. + **/ +#define IIO_EVENT_SH(_name, _handler) \ + static struct iio_event_handler_list \ + iio_event_##_name = { \ + .handler = _handler, \ + .refcount = 0, \ + .exist_lock = __MUTEX_INITIALIZER(iio_event_##_name \ + .exist_lock), \ + .list = { \ + .next = &iio_event_##_name.list, \ + .prev = &iio_event_##_name.list, \ + }, \ + }; +/** + * IIO_EVENT_ATTR_SH: generic shared event attribute + * + * An attribute with an associated IIO_EVENT_SH + **/ +#define IIO_EVENT_ATTR_SH(_name, _ev_list, _show, _store, _mask) \ + static struct iio_event_attr \ + iio_event_attr_##_name \ + = { .dev_attr = __ATTR(_name, S_IRUGO | S_IWUSR, \ + _show, _store), \ + .mask = _mask, \ + .listel = &_ev_list }; + +/** + * IIO_EVENT_ATTR: non shared event attribute + **/ +#define IIO_EVENT_ATTR(_name, _show, _store, _mask, _handler) \ + static struct iio_event_handler_list \ + iio_event_##_name = { \ + .handler = _handler, \ + }; \ + static struct \ + iio_event_attr \ + iio_event_attr_##_name \ + = { .dev_attr = __ATTR(_name, S_IRUGO | S_IWUSR, \ + _show, _store), \ + .mask = _mask, \ + .listel = &iio_event_##_name }; \ + +/** + * IIO_EVENT_ATTR_DATA_RDY: event driven by data ready signal + * + * Not typically implemented in devices where full triggering support + * has been implemented + **/ +#define IIO_EVENT_ATTR_DATA_RDY(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(data_rdy, _show, _store, _mask, _handler) + +#define IIO_EVENT_CODE_DATA_RDY 100 +#define IIO_EVENT_CODE_RING_BASE 200 +#define IIO_EVENT_CODE_ACCEL_BASE 300 +#define IIO_EVENT_CODE_GYRO_BASE 400 +#define IIO_EVENT_CODE_ADC_BASE 500 +#define IIO_EVENT_CODE_MISC_BASE 600 + +#define IIO_EVENT_CODE_DEVICE_SPECIFIC 1000 + +/** + * IIO_EVENT_ATTR_RING_50_FULL: ring buffer event to indicate 50% full + **/ +#define IIO_EVENT_ATTR_RING_50_FULL(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(ring_50_full, _show, _store, _mask, _handler) + +/** + * IIO_EVENT_ATTR_RING_50_FULL_SH: shared ring event to indicate 50% full + **/ +#define IIO_EVENT_ATTR_RING_50_FULL_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ring_50_full, _evlist, _show, _store, _mask) + +/** + * IIO_EVENT_ATTR_RING_75_FULL_SH: shared ring event to indicate 75% full + **/ +#define IIO_EVENT_ATTR_RING_75_FULL_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ring_75_full, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_RING_50_FULL IIO_EVENT_CODE_RING_BASE +#define IIO_EVENT_CODE_RING_75_FULL (IIO_EVENT_CODE_RING_BASE + 1) +#define IIO_EVENT_CODE_RING_100_FULL (IIO_EVENT_CODE_RING_BASE + 2) + +#endif /* _INDUSTRIAL_IO_SYSFS_H_ */ diff --git a/include/linux/iio/trigger_consumer.h b/include/linux/iio/trigger_consumer.h new file mode 100644 index 0000000..a605389 --- /dev/null +++ b/include/linux/iio/trigger_consumer.h @@ -0,0 +1,26 @@ + +/* The industrial I/O core, trigger consumer handling functions + * + * 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. + */ + +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers. + * @dev_info: iio_dev associated with the device that will consume the trigger + **/ +int iio_device_register_trigger_consumer(struct iio_dev *dev_info) +{ + return 0; +}; +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process +. * @dev_info: iio_dev associated with the device that consumed the trigger + **/ +int iio_device_unregister_trigger_consumer(struct iio_dev *dev_info) +{ + return 0; +}; -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 02/14] IIO: max1363 ADC driver 2009-07-02 13:47 ` [PATCH 01/14] IIO: core support for device registration and management Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 03/14] IIO: tsl2561 digital light sensor core support Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Core support for MAX1361, MAX1362, MAX1363, MAX1364, MAX1136, MAX1137, MAX1138, MAX1139, MAX1236, MAX1237, MAX1238, MAX1239. Ring buffer support later in series. --- Changes since V2: * Moved to new iio registration functions * General cleanups --- drivers/iio/Kconfig | 2 + drivers/iio/Makefile | 2 + drivers/iio/adc/Kconfig | 14 + drivers/iio/adc/Makefile | 7 + drivers/iio/adc/max1363.h | 257 +++++++++++++++++ drivers/iio/adc/max1363_core.c | 622 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 904 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/adc/Kconfig create mode 100644 drivers/iio/adc/Makefile create mode 100644 drivers/iio/adc/max1363.h create mode 100644 drivers/iio/adc/max1363_core.c diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 09e4101..ca54ab3 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -9,3 +9,5 @@ menuconfig IIO drivers for many different types of embedded sensors using a number of different physical interfaces (i2c, spi etc). See Documentation/industrialio for more information. + +source drivers/iio/adc/Kconfig \ No newline at end of file diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 92b462a..9d11f64 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o + +obj-y += adc/ \ No newline at end of file diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig new file mode 100644 index 0000000..cb773d2 --- /dev/null +++ b/drivers/iio/adc/Kconfig @@ -0,0 +1,14 @@ +# +# ADC drivers +# +comment "Analog to digital convertors" + depends on IIO + +config MAX1363 + tristate "MAXIM max1363 ADC driver" + depends on IIO + help + Say yes here to build support for many MAXIM i2c analog to digital + convertors (ADC). (max1361, max1362, max1363, max1364, max1136, + max1136, max1137, max1138, max1139, max1236, max1237, max11238, + max1239) Provides direct access via sysfs. diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile new file mode 100644 index 0000000..2a37fee --- /dev/null +++ b/drivers/iio/adc/Makefile @@ -0,0 +1,7 @@ + +# Makefile for industrial I/O ADC drivers +# + +max1363-y := max1363_core.o + +obj-$(CONFIG_MAX1363) += max1363.o diff --git a/drivers/iio/adc/max1363.h b/drivers/iio/adc/max1363.h new file mode 100644 index 0000000..a9d5c21 --- /dev/null +++ b/drivers/iio/adc/max1363.h @@ -0,0 +1,257 @@ +#ifndef _MAX1363_H_ +#define _MAX1363_H_ + +#define MAX1363_SETUP_BYTE(a) ((a) | 0x80) + +/* There is a fair bit more defined here than currently + * used, but the intention is to support everything these + * chips do in the long run */ + +/* see data sheets */ +/* max1363 and max1236, max1237, max1238, max1239 */ +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD 0x00 +#define MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF 0x20 +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT 0x40 +#define MAX1363_SETUP_AIN3_IS_REF_REF_IS_INT 0x60 +#define MAX1363_SETUP_POWER_UP_INT_REF 0x10 +#define MAX1363_SETUP_POWER_DOWN_INT_REF 0x00 + +/* think about includeing max11600 etc - more settings */ +#define MAX1363_SETUP_EXT_CLOCK 0x08 +#define MAX1363_SETUP_INT_CLOCK 0x00 +#define MAX1363_SETUP_UNIPOLAR 0x00 +#define MAX1363_SETUP_BIPOLAR 0x04 +#define MAX1363_SETUP_RESET 0x00 +#define MAX1363_SETUP_NORESET 0x02 +/* max1363 only - though don't care on others. + * For now monitor modes are not implemented as the relevant + * line is not connected on my test board. + * The definitions are here as I intend to add this soon. + */ +#define MAX1363_SETUP_MONITOR_SETUP 0x01 + +/* Specific to the max1363 */ +#define MAX1363_MON_RESET_CHAN(a) (1 << ((a) + 4)) +#define MAX1363_MON_CONV_RATE_133ksps 0 +#define MAX1363_MON_CONV_RATE_66_5ksps 0x02 +#define MAX1363_MON_CONV_RATE_33_3ksps 0x04 +#define MAX1363_MON_CONV_RATE_16_6ksps 0x06 +#define MAX1363_MON_CONV_RATE_8_3ksps 0x08 +#define MAX1363_MON_CONV_RATE_4_2ksps 0x0A +#define MAX1363_MON_CONV_RATE_2_0ksps 0x0C +#define MAX1363_MON_CONV_RATE_1_0ksps 0x0E +#define MAX1363_MON_INT_ENABLE 0x01 + +/* defined for readability reasons */ +/* All chips */ +#define MAX1363_CONFIG_BYTE(a) ((a)) + +#define MAX1363_CONFIG_SE 0x01 +#define MAX1363_CONFIG_DE 0x00 +#define MAX1363_CONFIG_SCAN_TO_CS 0x00 +#define MAX1363_CONFIG_SCAN_SINGLE_8 0x20 +#define MAX1363_CONFIG_SCAN_MONITOR_MODE 0x40 +#define MAX1363_CONFIG_SCAN_SINGLE_1 0x60 +/* max123{6-9} only */ +#define MAX1236_SCAN_MID_TO_CHANNEL 0x40 + +/* max1363 only - merely part of channel selects or don't care for others*/ +#define MAX1363_CONFIG_EN_MON_MODE_READ 0x18 + +#define MAX1363_CHANNEL_SEL(a) ((a) << 1) + +/* max1363 strictly 0x06 - but doesn't matter */ +#define MAX1363_CHANNEL_SEL_MASK 0x1E +#define MAX1363_SCAN_MASK 0x60 +#define MAX1363_SE_DE_MASK 0x01 + +/** + * struct max1363_mode - scan mode information + * @name: Name used to identify the scan mode. + * @conf: The corresponding value of the configuration register + * @numvals: The number of values returned by a single scan + */ +struct max1363_mode { + const char *name; + int8_t conf; + int numvals; +}; + +#define MAX1363_MODE_SINGLE(_num) { \ + .name = #_num, \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_SE, \ + .numvals = 1, \ + } + +#define MAX1363_MODE_SINGLE_TIMES_8(_num) { \ + .name = #_num"x8", \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_SINGLE_8 \ + | MAX1363_CONFIG_SE, \ + .numvals = 8, \ + } + +#define MAX1363_MODE_SCAN_TO_CHANNEL(_num) { \ + .name = "0..."#_num, \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_SE, \ + .numvals = _num + 1, \ + } + + +/* note not available for max1363 hence naming */ +#define MAX1236_MODE_SCAN_MID_TO_CHANNEL(_mid, _num) { \ + .name = #_mid"..."#_num, \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .numvals = _num - _mid + 1 \ +} + +#define MAX1363_MODE_DIFF_SINGLE(_nump, _numm) { \ + .name = #_nump"-"#_numm, \ + .conf = MAX1363_CHANNEL_SEL(_nump) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_DE, \ + .numvals = 1, \ + } + +#define MAX1363_MODE_DIFF_SINGLE_TIMES_8(_nump, _numm) { \ + .name = #_nump"-"#_numm, \ + .conf = MAX1363_CHANNEL_SEL(_nump) \ + | MAX1363_CONFIG_SCAN_SINGLE_8 \ + | MAX1363_CONFIG_DE, \ + .numvals = 1, \ + } + +/* Can't think how to automate naming so specify for now */ +#define MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(_name, _num, _numvals) { \ + .name = #_name, \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_DE, \ + .numvals = _numvals, \ + } + +/* note only available for max1363 hence naming */ +#define MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL_NAMED(_name, _num, _numvals) { \ + .name = #_name, \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .numvals = _numvals, \ +} + +/* Not currently handled */ +#define MAX1363_MODE_MONITOR { \ + .name = "monitor", \ + .conf = MAX1363_CHANNEL_SEL(3) \ + | MAX1363_CONFIG_SCAN_MONITOR_MODE \ + | MAX1363_CONFIG_SE, \ + .numvals = 10, \ + } + +/* This may seem an overly long winded way to do this, but at least it makes + * clear what all the various options actually do. Alternative suggestions + * that don't require user to have intimate knowledge of the chip welcomed. + */ + +/* This must be maintained along side the max1363_mode_table in max1363_core */ +enum max1363_modes { + /* Single read of a single channel */ + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + /* Eight reads of a single channel */ + se0, se1, se2, se3, se4, se5, se6, se7, se8, se9, se10, se11, + /* Scan to channel */ + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, + s0to7, s0to8, s0to9, s0to10, s0to11, + /* Differential single read */ + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + /* Differential single read 8 times */ + de0m1, de2m3, de4m5, de6m7, de8m9, de10m11, + de1m0, de3m2, de5m4, de7m6, de9m8, de11m10, + /* Differential scan to channel */ + d0m1to2m3, d0m1to4m5, d0m1to6m7, d0m1to8m9, d0m1to10m11, + d1m0to3m2, d1m0to5m4, d1m0to7m6, d1m0to9m8, d1m0to11m10, + /* Scan mid to channel max123{6-9} only */ + s2to3, s6to7, s6to8, s6to9, s6to10, s6to11, + /* Differential scan mid to channel */ + s6m7to8m9, s6m7to10m11, s7m6to9m8, s7m6to11m10, +}; + +/** + * struct max1363_chip_info - chip specifc information + * @name: indentification string for chip + * @num_inputs: number of physical inputs on chip + * @int_vref_mv: the internal reference voltage + * @monitor_mode: whether the chip supports monitor interrupts + * @mode_list: array of available scan modes + * @num_modes: the number of scan modes available + * @default_mode: the scan mode in which the chip starts up + */ +struct max1363_chip_info { + const char *name; + u8 num_inputs; + u16 int_vref_mv; + bool monitor_mode; + const enum max1363_modes *mode_list; + int num_modes; + enum max1363_modes default_mode; +}; + + +/** + * struct max1363_state - driver instance specific data + * @indio_dev: the industrial I/O device + * @client: i2c_client + * @setupbyte: cache of current device setup byte + * @configbyte: cache of current device config byte + * @chip_info: chip model specific constants, available modes etc + * @current_mode: the scan mode of this chip + * @poll_work: bottom half of polling interrupt handler + * @protect_ring: used to ensure only one polling bh running at a time + * @reg: supply regulator + */ +struct max1363_state { + struct iio_dev *indio_dev; + struct i2c_client *client; + char setupbyte; + char configbyte; + const struct max1363_chip_info *chip_info; + const struct max1363_mode *current_mode; + struct work_struct poll_work; + atomic_t protect_ring; + struct iio_trigger *trig; + struct regulator *reg; +}; + +static inline void max1363_uninitialize_ring(struct iio_ring_buffer *ring) +{ +}; + +static inline int max1363_initialize_ring(struct iio_ring_buffer *ring) +{ + return 0; +}; + + +static inline ssize_t max1363_scan_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +}; + +static inline int +max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline void max1363_ring_cleanup(struct iio_dev *indio_dev) {}; + +#endif /* _MAX1363_H_ */ diff --git a/drivers/iio/adc/max1363_core.c b/drivers/iio/adc/max1363_core.c new file mode 100644 index 0000000..b9025a4 --- /dev/null +++ b/drivers/iio/adc/max1363_core.c @@ -0,0 +1,622 @@ + /* + * linux/drivers/industrialio/adc/max1363.c + * Copyright (C) 2008 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * 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. + * + * max1363.c + * + * Partial support for max1363 and similar chips. + * + * Not currently implemented. + * + * - Monitor interrrupt generation. + * - Control of internal reference. + * - Sysfs scan interface currently assumes unipolar mode. + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/i2c.h> +#include <linux/rtc.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include "max1363.h" + +/* Available scan modes. + * Awkwardly the associated enum is in the header so it is available to + * the ring buffer code. + */ +static const struct max1363_mode max1363_mode_table[] = { + MAX1363_MODE_SINGLE(0), + MAX1363_MODE_SINGLE(1), + MAX1363_MODE_SINGLE(2), + MAX1363_MODE_SINGLE(3), + MAX1363_MODE_SINGLE(4), + MAX1363_MODE_SINGLE(5), + MAX1363_MODE_SINGLE(6), + MAX1363_MODE_SINGLE(7), + MAX1363_MODE_SINGLE(8), + MAX1363_MODE_SINGLE(9), + MAX1363_MODE_SINGLE(10), + MAX1363_MODE_SINGLE(11), + + MAX1363_MODE_SINGLE_TIMES_8(0), + MAX1363_MODE_SINGLE_TIMES_8(1), + MAX1363_MODE_SINGLE_TIMES_8(2), + MAX1363_MODE_SINGLE_TIMES_8(3), + MAX1363_MODE_SINGLE_TIMES_8(4), + MAX1363_MODE_SINGLE_TIMES_8(5), + MAX1363_MODE_SINGLE_TIMES_8(6), + MAX1363_MODE_SINGLE_TIMES_8(7), + MAX1363_MODE_SINGLE_TIMES_8(8), + MAX1363_MODE_SINGLE_TIMES_8(9), + MAX1363_MODE_SINGLE_TIMES_8(10), + MAX1363_MODE_SINGLE_TIMES_8(11), + + MAX1363_MODE_SCAN_TO_CHANNEL(1), + MAX1363_MODE_SCAN_TO_CHANNEL(2), + MAX1363_MODE_SCAN_TO_CHANNEL(3), + MAX1363_MODE_SCAN_TO_CHANNEL(4), + MAX1363_MODE_SCAN_TO_CHANNEL(5), + MAX1363_MODE_SCAN_TO_CHANNEL(6), + MAX1363_MODE_SCAN_TO_CHANNEL(7), + MAX1363_MODE_SCAN_TO_CHANNEL(8), + MAX1363_MODE_SCAN_TO_CHANNEL(9), + MAX1363_MODE_SCAN_TO_CHANNEL(10), + MAX1363_MODE_SCAN_TO_CHANNEL(11), + + MAX1363_MODE_DIFF_SINGLE(0, 1), + MAX1363_MODE_DIFF_SINGLE(2, 3), + MAX1363_MODE_DIFF_SINGLE(4, 5), + MAX1363_MODE_DIFF_SINGLE(6, 7), + MAX1363_MODE_DIFF_SINGLE(8, 9), + MAX1363_MODE_DIFF_SINGLE(10, 11), + MAX1363_MODE_DIFF_SINGLE(1, 0), + MAX1363_MODE_DIFF_SINGLE(3, 2), + MAX1363_MODE_DIFF_SINGLE(5, 4), + MAX1363_MODE_DIFF_SINGLE(7, 6), + MAX1363_MODE_DIFF_SINGLE(9, 8), + MAX1363_MODE_DIFF_SINGLE(11, 10), + + MAX1363_MODE_DIFF_SINGLE_TIMES_8(0, 1), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(2, 3), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(4, 5), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(6, 7), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(8, 9), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(10, 11), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(1, 0), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(3, 2), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(5, 4), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(7, 6), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(9, 8), + MAX1363_MODE_DIFF_SINGLE_TIMES_8(11, 10), + + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(0-1...2-3, 2, 2), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(0-1...4-5, 4, 3), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(0-1...6-7, 6, 4), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(0-1...8-9, 8, 5), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(0-1...10-11, 10, 6), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(1-0...3-2, 3, 2), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(1-0...5-4, 5, 3), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(1-0...7-6, 7, 4), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(1-0...9-8, 9, 5), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL_NAMED(1-0...11-10, 11, 6), + + MAX1236_MODE_SCAN_MID_TO_CHANNEL(2, 3), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 7), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 8), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 9), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 10), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 11), + + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL_NAMED(6-7...8-9, 8, 2), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL_NAMED(6-7...10-11, 10, 3), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL_NAMED(7-6...9-8, 9, 2), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL_NAMED(7-6...11-10, 11, 3), +}; + +/* Applies to max1363 */ +static const enum max1363_modes max1363_mode_list[] = { + _s0, _s1, _s2, _s3, + se0, se1, se2, se3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + de0m1, de2m3, de1m0, de3m2, + d0m1to2m3, d1m0to3m2, +}; + +/* Appies to max1236, max1237 */ +static const enum max1363_modes max1236_mode_list[] = { + _s0, _s1, _s2, _s3, + se0, se1, se2, se3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + de0m1, de2m3, de1m0, de3m2, + d0m1to2m3, d1m0to3m2, + s2to3, +}; + +/* Applies to max1238, max1239 */ +static const enum max1363_modes max1238_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + se0, se1, se2, se3, se4, se5, se6, se7, se8, se9, se10, se11, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, + s0to7, s0to8, s0to9, s0to10, s0to11, + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + de0m1, de2m3, de4m5, de6m7, de8m9, de10m11, + de1m0, de3m2, de5m4, de7m6, de9m8, de11m10, + d0m1to2m3, d0m1to4m5, d0m1to6m7, d0m1to8m9, d0m1to10m11, + d1m0to3m2, d1m0to5m4, d1m0to7m6, d1m0to9m8, d1m0to11m10, + s6to7, s6to8, s6to9, s6to10, s6to11, + s6m7to8m9, s6m7to10m11, s7m6to9m8, s7m6to11m10, +}; + + +enum { max1361, + max1362, + max1363, + max1364, + max1136, + max1137, + max1138, + max1139, + max1236, + max1237, + max1238, + max1239, +}; + +/* max1363 and max1368 tested - rest from data sheet */ +static const struct max1363_chip_info max1363_chip_info_tbl[] = { + { + .name = "max1361", + .num_inputs = 4, + .monitor_mode = 1, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + }, { + .name = "max1362", + .num_inputs = 4, + .monitor_mode = 1, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + }, { + .name = "max1363", + .num_inputs = 4, + .monitor_mode = 1, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + }, { + .name = "max1364", + .num_inputs = 4, + .monitor_mode = 1, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + }, { + .name = "max1136", + .num_inputs = 4, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + }, { + .name = "max1137", + .num_inputs = 4, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + }, { + .name = "max1138", + .num_inputs = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + }, { + .name = "max1139", + .num_inputs = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + }, { + .name = "max1236", + .num_inputs = 4, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + }, { + .name = "max1237", + .num_inputs = 4, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + }, { + .name = "max1238", + .num_inputs = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + }, { + .name = "max1239", + .num_inputs = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + }, +}; + +static int max1363_write_basic_config(struct i2c_client *client, + unsigned char d1, + unsigned char d2) +{ + int ret; + u8 *tx_buf = kmalloc(2 , GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + tx_buf[0] = d1; + tx_buf[1] = d2; + + ret = i2c_master_send(client, tx_buf, 2); + kfree(tx_buf); + return (ret > 0) ? 0 : ret; +} + +static int max1363_set_scan_mode(struct max1363_state *st) +{ + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= st->current_mode->conf; + + return max1363_write_basic_config(st->client, + st->setupbyte, + st->configbyte); +} + +static int max1363_initial_setup(struct max1363_state *st) +{ + st->setupbyte = MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD + | MAX1363_SETUP_POWER_UP_INT_REF + | MAX1363_SETUP_INT_CLOCK + | MAX1363_SETUP_UNIPOLAR + | MAX1363_SETUP_NORESET; + + /* Set scan mode writes the config anyway so wait until then*/ + st->setupbyte = MAX1363_SETUP_BYTE(st->setupbyte); + st->current_mode = &max1363_mode_table[st->chip_info->default_mode]; + st->configbyte = MAX1363_CONFIG_BYTE(st->configbyte); + + return max1363_set_scan_mode(st); +} + +static ssize_t max1363_show_av_scan_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *st = dev_info->dev_data; + int i, len = 0; + + for (i = 0; i < st->chip_info->num_modes; i++) + len += sprintf(buf + len, "%s ", + max1363_mode_table[st->chip_info + ->mode_list[i]].name); + len += sprintf(buf + len, "\n"); + + return len; +} + + +/* The dev here is the sysfs related one, not the underlying i2c one */ +static ssize_t max1363_scan_direct(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *st = dev_info->dev_data; + int len = 0, ret, i; + struct i2c_client *client = st->client; + char *rxbuf; + + if (st->current_mode->numvals == 0) + return 0; + rxbuf = kmalloc(st->current_mode->numvals*2, GFP_KERNEL); + if (rxbuf == NULL) + return -ENOMEM; + + /* Interpretation depends on whether these are signed or not!*/ + /* Assume not for now */ + ret = i2c_master_recv(client, rxbuf, st->current_mode->numvals*2); + + if (ret < 0) + return ret; + for (i = 0; i < st->current_mode->numvals; i++) + len += sprintf(buf+len, "%d ", + ((int)(rxbuf[i*2+0]&0x0F) << 8) + + ((int)(rxbuf[i*2+1]))); + kfree(rxbuf); + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t max1363_scan(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + int ret; + + mutex_lock(&dev_info->mlock); + if (dev_info->currentmode == INDIO_RING_TRIGGERED) + ret = max1363_scan_from_ring(dev, attr, buf); + else + ret = max1363_scan_direct(dev, attr, buf); + mutex_unlock(&dev_info->mlock); + + return ret; +} + +/* Cannot query the device, so use local copy of state */ +static ssize_t max1363_show_scan_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *st = dev_info->dev_data; + + return sprintf(buf, "%s\n", st->current_mode->name); +} + +static const struct max1363_mode +*__max1363_find_mode_in_ci(const struct max1363_chip_info *info, + const char *buf) +{ + int i; + for (i = 0; i < info->num_modes; i++) + if (strcmp(max1363_mode_table[info->mode_list[i]].name, buf) + == 0) + return &max1363_mode_table[info->mode_list[i]]; + return NULL; +} + +static ssize_t max1363_store_scan_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *st = dev_info->dev_data; + const struct max1363_mode *new_mode; + int ret; + + mutex_lock(&dev_info->mlock); + new_mode = NULL; + /* Avoid state changes if a ring buffer is enabled */ + if (!iio_ring_enabled(dev_info)) { + new_mode + = __max1363_find_mode_in_ci(st->chip_info, buf); + if (!new_mode) { + ret = -EINVAL; + goto error_ret; + } + st->current_mode = new_mode; + ret = max1363_set_scan_mode(st); + if (ret) + goto error_ret; + } else { + ret = -EBUSY; + goto error_ret; + } + mutex_unlock(&dev_info->mlock); + + return len; + +error_ret: + mutex_unlock(&dev_info->mlock); + + return ret; +} + +IIO_DEV_ATTR_AVAIL_SCAN_MODES(max1363_show_av_scan_modes); +IIO_DEV_ATTR_SCAN_MODE(S_IRUGO | S_IWUSR, + max1363_show_scan_mode, + max1363_store_scan_mode); + +IIO_DEV_ATTR_SCAN(max1363_scan); + +static ssize_t max1363_show_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *st = dev_info->dev_data; + return sprintf(buf, "%s\n", st->chip_info->name); +} + +IIO_DEVICE_ATTR(name, S_IRUGO, max1363_show_name, NULL, 0); + +/*name export */ + +static struct attribute *max1363_attributes[] = { + &iio_dev_attr_available_scan_modes.dev_attr.attr, + &iio_dev_attr_scan_mode.dev_attr.attr, + &iio_dev_attr_scan.dev_attr.attr, + &iio_dev_attr_name.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max1363_attribute_group = { + .attrs = max1363_attributes, +}; + +static int __devinit max1363_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret, i; + struct max1363_state *st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, st); + + atomic_set(&st->protect_ring, 0); + + /* Find the chip model specific data */ + for (i = 0; i < ARRAY_SIZE(max1363_chip_info_tbl); i++) + if (!strcmp(max1363_chip_info_tbl[i].name, id->name)) { + st->chip_info = &max1363_chip_info_tbl[i]; + break; + }; + /* Unsupported chip */ + if (!st->chip_info) { + dev_err(&client->dev, "%s is not supported\n", id->name); + ret = -ENODEV; + goto error_free_st; + } + st->reg = regulator_get(&client->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + st->client = client; + + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + + /* Estabilish that the iio_dev is a child of the i2c device */ + st->indio_dev->dev.parent = &client->dev; + st->indio_dev->attrs = &max1363_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + + ret = max1363_initial_setup(st); + if (ret) + goto error_free_device; + + ret = max1363_register_ring_funcs_and_init(st->indio_dev); + if (ret) + goto error_free_device; + + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_cleanup_ring; + ret = max1363_initialize_ring(st->indio_dev->ring); + if (ret) + goto error_dev_unreg; + return 0; +error_dev_unreg: + iio_device_unregister(st->indio_dev); +error_cleanup_ring: + max1363_ring_cleanup(st->indio_dev); +error_free_device: + iio_free_device(st->indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); +error_free_st: + kfree(st); + +error_ret: + return ret; +} + +static int max1363_remove(struct i2c_client *client) +{ + struct max1363_state *st = i2c_get_clientdata(client); + struct iio_dev *indio_dev = st->indio_dev; + max1363_uninitialize_ring(indio_dev->ring); + iio_device_unregister(indio_dev); + max1363_ring_cleanup(indio_dev); + iio_free_device(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + kfree(st); + + return 0; +} + +static const struct i2c_device_id max1363_id[] = { + { "max1361", max1361 }, + { "max1362", max1362 }, + { "max1363", max1363 }, + { "max1364", max1364 }, + { "max1136", max1136 }, + { "max1137", max1137 }, + { "max1138", max1138 }, + { "max1139", max1139 }, + { "max1236", max1236 }, + { "max1237", max1237 }, + { "max1238", max1238 }, + { "max1239", max1239 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max1363_id); + +static struct i2c_driver max1363_driver = { + .driver = { + .name = "max1363", + }, + .probe = max1363_probe, + .remove = max1363_remove, + .id_table = max1363_id, +}; + +static __init int max1363_init(void) +{ + return i2c_add_driver(&max1363_driver); +} + +static __exit void max1363_exit(void) +{ + i2c_del_driver(&max1363_driver); +} + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Maxim 1363 ADC"); +MODULE_LICENSE("GPL v2"); + +module_init(max1363_init); +module_exit(max1363_exit); -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 03/14] IIO: tsl2561 digital light sensor core support 2009-07-02 13:47 ` [PATCH 02/14] IIO: max1363 ADC driver Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 04/14] IIO: lis3l02dq accelerometer " Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> This is a pretty minimalist example of an IIO driver. --- Unlike the existing light sensor driver tsl2550 all conversion to units is left to userspace. This is similar to the isl29003 driver. IIO may in future provide a home for such light sensor drivers. --- drivers/iio/Kconfig | 3 +- drivers/iio/Makefile | 3 +- drivers/iio/light/Kconfig | 14 ++ drivers/iio/light/Makefile | 5 + drivers/iio/light/tsl2561.c | 275 +++++++++++++++++++++++++++++++++++++++++++ include/linux/iio/light.h | 12 ++ 6 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 drivers/iio/light/Kconfig create mode 100644 drivers/iio/light/Makefile create mode 100644 drivers/iio/light/tsl2561.c create mode 100644 include/linux/iio/light.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index ca54ab3..d37859d 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -10,4 +10,5 @@ menuconfig IIO number of different physical interfaces (i2c, spi etc). See Documentation/industrialio for more information. -source drivers/iio/adc/Kconfig \ No newline at end of file +source drivers/iio/adc/Kconfig +source drivers/iio/light/Kconfig diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 9d11f64..4e78f98 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o -obj-y += adc/ \ No newline at end of file +obj-y += adc/ +obj-y += light/ \ No newline at end of file diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig new file mode 100644 index 0000000..620e249 --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,14 @@ +# +# Light sensors +# +comment "Light sensors" + depends on IIO + +config TSL2561 + tristate "TAOS TSL2561 light-to-digital convertor" + depends on IIO + help + Say yes bere to build support for the TAOS light to digital + convertor. This chip has two light sensors. One is broadband + including infrared whilst the other measures only infrared. + Provides direct access via sysfs. diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile new file mode 100644 index 0000000..ccff151 --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for industrial I/O Light sensors +# + +obj-$(CONFIG_TSL2561) += tsl2561.o diff --git a/drivers/iio/light/tsl2561.c b/drivers/iio/light/tsl2561.c new file mode 100644 index 0000000..db1443c --- /dev/null +++ b/drivers/iio/light/tsl2561.c @@ -0,0 +1,275 @@ +/* + * tsl2561.c - Linux kernel modules for light to digital convertor + * + * Copyright (C) 2008-2009 Jonathan Cameron <jic23@cam.ac.uk> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Some portions based upon the tsl2550 driver. + * + * This driver could probably be adapted easily to talk to the tsl2560 (smbus) + * + * Needs some work to support the events this can generate. + * Todo: Implement interrupt handling. Currently a hardware bug means + * this isn't available on my test board. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/light.h> + +#define TSL2561_CONTROL_REGISTER 0x00 +#define TSL2561_TIMING_REGISTER 0x01 +#define TSL2561_THRESHLOW_LOW_REGISTER 0x02 +#define TSL2561_THRESHLOW_HIGH_REGISTER 0x03 +#define TSL2561_THRESHHIGH_LOW_REGISTER 0x04 +#define TSL2561_THRESHHIGH_HIGH_REGISTER 0x05 +#define TSL2561_INT_CONTROL_REGISTER 0x06 + +#define TSL2561_INT_REG_INT_OFF 0x00 +#define TSL2561_INT_REG_INT_LEVEL 0x08 +#define TSL2561_INT_REG_INT_SMBUS 0x10 +#define TSL2561_INT_REG_INT_TEST 0x18 + +#define TSL2561_ID_REGISTER 0x0A + +#define TSL2561_DATA_0_LOW 0x0C +#define TSL2561_DATA_1_LOW 0x0E + +/* Control Register Values */ +#define TSL2561_CONT_REG_PWR_ON 0x03 +#define TSL2561_CONT_REG_PWR_OFF 0x00 + +/** + * struct tsl2561_state - device specific state + * @indio_dev: the industrialio I/O info structure + * @client: i2c client + * @command_buf: single command buffer used for all operations + * @command_buf_lock: ensure unique access to command_buf + */ +struct tsl2561_state { + struct iio_dev *indio_dev; + struct i2c_client *client; + struct tsl2561_command *command_buf; + struct mutex command_buf_lock; +}; + +/** + * struct tsl2561_command - command byte for smbus + * @address: register address + * @block: is this a block r/w + * @word: is this a word r/w + * @clear: set to 1 to clear pending interrupt + * @cmd: select the command register - always 1. + */ +struct tsl2561_command { + unsigned int address:4; + unsigned int block:1; + unsigned int word:1; + unsigned int clear:1; + unsigned int cmd:1; +}; + +static inline void tsl2561_init_command_buf(struct tsl2561_command *buf) +{ + buf->address = 0; + buf->block = 0; + buf->word = 0; + buf->clear = 0; + buf->cmd = 1; +} + +static ssize_t tsl2561_read_val(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0, data; + ssize_t len = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2561_state *st = indio_dev->dev_data; + + mutex_lock(&st->command_buf_lock); + st->command_buf->cmd = 1; + st->command_buf->word = 1; + st->command_buf->address = this_attr->address; + + data = i2c_smbus_read_word_data(st->client, *(char *)(st->command_buf)); + if (data < 0) { + ret = data; + goto error_ret; + } + len = sprintf(buf, "%u\n", data); + +error_ret: + mutex_unlock(&st->command_buf_lock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_LIGHT_INFRARED(0, tsl2561_read_val, TSL2561_DATA_0_LOW); +static IIO_DEV_ATTR_LIGHT_BROAD(0, tsl2561_read_val, TSL2561_DATA_1_LOW); + +static struct attribute *tsl2561_attributes[] = { + &iio_dev_attr_light_infrared0.dev_attr.attr, + &iio_dev_attr_light_broadspectrum0.dev_attr.attr, + NULL, +}; + +static const struct attribute_group tsl2561_attribute_group = { + .attrs = tsl2561_attributes, +}; + +static int tsl2561_initialize(struct tsl2561_state *st) +{ + int err; + + mutex_lock(&st->command_buf_lock); + st->command_buf->word = 0; + st->command_buf->block = 0; + st->command_buf->address = TSL2561_CONTROL_REGISTER; + err = i2c_smbus_write_byte_data(st->client, *(char *)(st->command_buf), + TSL2561_CONT_REG_PWR_ON); + if (err) + goto error_ret; + + st->command_buf->address = TSL2561_INT_CONTROL_REGISTER; + err = i2c_smbus_write_byte_data(st->client, *(char *)(st->command_buf), + TSL2561_INT_REG_INT_TEST); + +error_ret: + mutex_unlock(&st->command_buf_lock); + + return err; +} + +static int tsl2561_powerdown(struct i2c_client *client) +{ + int err; + struct tsl2561_command Command = { + .cmd = 1, + .clear = 0, + .word = 0, + .block = 0, + .address = TSL2561_CONTROL_REGISTER, + }; + + err = i2c_smbus_write_byte_data(client, *(char *)(&Command), + TSL2561_CONT_REG_PWR_OFF); + return (err < 0) ? err : 0; +} +static int __devinit tsl2561_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct tsl2561_state *st = kzalloc(sizeof(*st), GFP_KERNEL); + + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + i2c_set_clientdata(client, st); + st->client = client; + mutex_init(&st->command_buf_lock); + + st->command_buf = kmalloc(sizeof(*st->command_buf), GFP_KERNEL); + if (st->command_buf == NULL) { + ret = -ENOMEM; + goto error_free_state; + } + tsl2561_init_command_buf(st->command_buf); + + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_command_buf; + } + st->indio_dev->attrs = &tsl2561_attribute_group; + st->indio_dev->dev.parent = &client->dev; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_free_iiodev; + + /* Intialize the chip */ + ret = tsl2561_initialize(st); + if (ret) + goto error_unregister_iiodev; + + return 0; +error_unregister_iiodev: + iio_device_unregister(st->indio_dev); +error_free_iiodev: + iio_free_device(st->indio_dev); +error_free_command_buf: + kfree(st->command_buf); +error_free_state: + kfree(st); +error_ret: + return ret; + +} + +static int __devexit tsl2561_remove(struct i2c_client *client) +{ + struct tsl2561_state *st = i2c_get_clientdata(client); + + iio_device_unregister(st->indio_dev); + iio_free_device(st->indio_dev); + kfree(st); + + return tsl2561_powerdown(client); +} + +static unsigned short normal_i2c[] = { 0x29, 0x39, 0x49, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +static const struct i2c_device_id tsl2561_id[] = { + { "tsl2561", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2561_id); + + +static struct i2c_driver tsl2561_driver = { + .driver = { + .name = "tsl2561", + }, + .probe = tsl2561_probe, + .remove = __devexit_p(tsl2561_remove), + .id_table = tsl2561_id, +}; + +static __init int tsl2561_init(void) +{ + return i2c_add_driver(&tsl2561_driver); +} +module_init(tsl2561_init); + +static __exit void tsl2561_exit(void) +{ + i2c_del_driver(&tsl2561_driver); +} +module_exit(tsl2561_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("TSL2561 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/iio/light.h b/include/linux/iio/light.h new file mode 100644 index 0000000..60a8640 --- /dev/null +++ b/include/linux/iio/light.h @@ -0,0 +1,12 @@ +#include <linux/iio/sysfs.h> + +/* Light to digital sensor attributes */ + +#define IIO_DEV_ATTR_LIGHT_INFRARED(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_infrared##_num, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LIGHT_BROAD(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_broadspectrum##_num, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LIGHT_VISIBLE(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_visible##_num, S_IRUGO, _show, NULL, _addr) -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 04/14] IIO: lis3l02dq accelerometer core support 2009-07-02 13:47 ` [PATCH 03/14] IIO: tsl2561 digital light sensor core support Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 05/14] IIO: kxsd9 accelerometer minimal support Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> A later patch in the series will add data ready triggering and ring buffer support. This core patch provides an event interface and sysfs based reading of values. --- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/accel/Kconfig | 13 + drivers/iio/accel/Makefile | 5 + drivers/iio/accel/lis3l02dq.h | 212 ++++++++ drivers/iio/accel/lis3l02dq_core.c | 925 ++++++++++++++++++++++++++++++++++++ include/linux/iio/accel.h | 167 +++++++ 7 files changed, 1324 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/accel/Kconfig create mode 100644 drivers/iio/accel/Makefile create mode 100644 drivers/iio/accel/lis3l02dq.h create mode 100644 drivers/iio/accel/lis3l02dq_core.c create mode 100644 include/linux/iio/accel.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index d37859d..f3545c8 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -10,5 +10,6 @@ menuconfig IIO number of different physical interfaces (i2c, spi etc). See Documentation/industrialio for more information. +source drivers/iio/accel/Kconfig source drivers/iio/adc/Kconfig source drivers/iio/light/Kconfig diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 4e78f98..14c661b 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o +obj-y += accel/ obj-y += adc/ obj-y += light/ \ No newline at end of file diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig new file mode 100644 index 0000000..a3abffc --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,13 @@ +# +# Accelerometer drivers +# +comment "Accelerometers" + depends on IIO + +config LIS3L02DQ + tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" + depends on IIO && SPI + help + Say yes here to build SPI support for the ST microelectronics + accelerometer. The driver supplies direct access via sysfs files + and an event interface via a character device. diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 0000000..5a7ef9d --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for industrial I/O accelerometer drivers +# +lis3l02dq-y := lis3l02dq_core.o +obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o diff --git a/drivers/iio/accel/lis3l02dq.h b/drivers/iio/accel/lis3l02dq.h new file mode 100644 index 0000000..18cb49c --- /dev/null +++ b/drivers/iio/accel/lis3l02dq.h @@ -0,0 +1,212 @@ +/* + * LISL02DQ.h -- support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron <jic23@cam.ac.uk> + * + * Loosely based upon tle62x0.c + * + * 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. + */ + +#ifndef SPI_LIS3L02DQ_H_ +#define SPI_LIS3L02DQ_H_ +#define LIS3L02DQ_READ_REG(a) ((a) | 0x80) +#define LIS3L02DQ_WRITE_REG(a) a + +/* Calibration parameters */ +#define LIS3L02DQ_REG_OFFSET_X_ADDR 0x16 +#define LIS3L02DQ_REG_OFFSET_Y_ADDR 0x17 +#define LIS3L02DQ_REG_OFFSET_Z_ADDR 0x18 + +#define LIS3L02DQ_REG_GAIN_X_ADDR 0x19 +#define LIS3L02DQ_REG_GAIN_Y_ADDR 0x1A +#define LIS3L02DQ_REG_GAIN_Z_ADDR 0x1B + +/* Control Register (1 of 2) */ +#define LIS3L02DQ_REG_CTRL_1_ADDR 0x20 +/* Power ctrl - either bit set corresponds to on*/ +#define LIS3L02DQ_REG_CTRL_1_PD_ON 0xC0 + +/* Decimation Factor */ +#define LIS3L02DQ_DEC_MASK 0x30 +#define LIS3L02DQ_REG_CTRL_1_DF_128 0x00 +#define LIS3L02DQ_REG_CTRL_1_DF_64 0x10 +#define LIS3L02DQ_REG_CTRL_1_DF_32 0x20 +#define LIS3L02DQ_REG_CTRL_1_DF_8 (0x10 | 0x20) + +/* Self Test Enable */ +#define LIS3L02DQ_REG_CTRL_1_SELF_TEST_ON 0x08 + +/* Axes enable ctrls */ +#define LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE 0x04 +#define LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE 0x02 +#define LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE 0x01 + +/* Control Register (2 of 2) */ +#define LIS3L02DQ_REG_CTRL_2_ADDR 0x21 + +/* Block Data Update only after MSB and LSB read */ +#define LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE 0x40 + +/* Set to big endian output */ +#define LIS3L02DQ_REG_CTRL_2_BIG_ENDIAN 0x20 + +/* Reboot memory content */ +#define LIS3L02DQ_REG_CTRL_2_REBOOT_MEMORY 0x10 + +/* Interupt Enable - applies data ready to the RDY pad */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT 0x08 + +/* Enable Data Ready Generation - relationship with previous unclear in docs */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION 0x04 + +/* SPI 3 wire mode */ +#define LIS3L02DQ_REG_CTRL_2_THREE_WIRE_SPI_MODE 0x02 + +/* Data alignment, default is 12 bit right justified + * - option for 16 bit left justified */ +#define LIS3L02DQ_REG_CTRL_2_DATA_ALIGNMENT_16_BIT_LEFT_JUSTIFIED 0x01 + +/* Interupt related stuff */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_ADDR 0x23 + +/* Switch from or combination fo conditions to and */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_BOOLEAN_AND 0x80 + +/* Latch interupt request, + * if on ack must be given by reading the ack register */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC 0x40 + +/* Z Interupt on High (above threshold)*/ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH 0x20 +/* Z Interupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW 0x10 +/* Y Interupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH 0x08 +/* Y Interupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW 0x04 +/* X Interupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH 0x02 +/* X Interupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW 0x01 + +/* Register that gives description of what caused interupt + * - latched if set in CFG_ADDRES */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_ADDR 0x24 +/* top bit ignored */ +/* Interupt Active */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_ACTIVATED 0x40 +/* Interupts that have been triggered */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH 0x20 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW 0x10 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH 0x08 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW 0x04 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH 0x02 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW 0x01 + +#define LIS3L02DQ_REG_WAKE_UP_ACK_ADDR 0x25 + +/* Status register */ +#define LIS3L02DQ_REG_STATUS_ADDR 0x27 +/* XYZ axis data overrun - first is all overrun? */ +#define LIS3L02DQ_REG_STATUS_XYZ_OVERRUN 0x80 +#define LIS3L02DQ_REG_STATUS_Z_OVERRUN 0x40 +#define LIS3L02DQ_REG_STATUS_Y_OVERRUN 0x20 +#define LIS3L02DQ_REG_STATUS_X_OVERRUN 0x10 +/* XYZ new data available - first is all 3 available? */ +#define LIS3L02DQ_REG_STATUS_XYZ_NEW_DATA 0x08 +#define LIS3L02DQ_REG_STATUS_Z_NEW_DATA 0x04 +#define LIS3L02DQ_REG_STATUS_Y_NEW_DATA 0x02 +#define LIS3L02DQ_REG_STATUS_X_NEW_DATA 0x01 + +/* The accelerometer readings - low and high bytes. +Form of high byte dependant on justification set in ctrl reg */ +#define LIS3L02DQ_REG_OUT_X_L_ADDR 0x28 +#define LIS3L02DQ_REG_OUT_X_H_ADDR 0x29 +#define LIS3L02DQ_REG_OUT_Y_L_ADDR 0x2A +#define LIS3L02DQ_REG_OUT_Y_H_ADDR 0x2B +#define LIS3L02DQ_REG_OUT_Z_L_ADDR 0x2C +#define LIS3L02DQ_REG_OUT_Z_H_ADDR 0x2D + +/* Threshold values for all axes and both above and below thresholds + * - i.e. there is only one value */ +#define LIS3L02DQ_REG_THS_L_ADDR 0x2E +#define LIS3L02DQ_REG_THS_H_ADDR 0x2F + +#define LIS3L02DQ_DEFAULT_CTRL1 (LIS3L02DQ_REG_CTRL_1_PD_ON \ + | LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_DF_128) + +#define LIS3L02DQ_DEFAULT_CTRL2 0 + +#define LIS3L02DQ_MAX_TX 12 +#define LIS3L02DQ_MAX_RX 12 +/** + * struct lis3l02dq_state - device instance specific data + * @us: actual spi_device + * @work_trigger_to_ring: bh for triggered event handling + * @work_cont_thresh: CLEAN + * @inter: used to check if new interrupt has been triggered + * @last_timestamp: passing timestamp from th to bh of interrupt handler + * @indio_dev: industrial I/O device structure + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: recieve buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct lis3l02dq_state { + struct spi_device *us; + struct work_struct work_trigger_to_ring; + struct iio_work_cont work_cont_thresh; + bool inter; + s64 last_timestamp; + struct iio_dev *indio_dev; + struct iio_trigger *trig; + u8 *tx; + u8 *rx; + struct mutex buf_lock; +}; + +int lis3l02dq_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val); + +int lis3l02dq_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 *val); +#define LIS3L02DQ_SCAN_ACC_X 0 +#define LIS3L02DQ_SCAN_ACC_Y 1 +#define LIS3L02DQ_SCAN_ACC_Z 2 + +static inline void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) {}; +static inline int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline ssize_t +lis3l02dq_read_accel_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +}; + +static int lis3l02dq_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +}; +static inline void lis3l02dq_unconfigure_ring(struct iio_dev *indio_dev) +{}; +static inline int lis3l02dq_initialize_ring(struct iio_ring_buffer *ring) +{ + return 0; +}; +static inline void lis3l02dq_uninitialize_ring(struct iio_ring_buffer *ring) {}; + +#endif /* SPI_LIS3L02DQ_H_ */ diff --git a/drivers/iio/accel/lis3l02dq_core.c b/drivers/iio/accel/lis3l02dq_core.c new file mode 100644 index 0000000..503d6ed --- /dev/null +++ b/drivers/iio/accel/lis3l02dq_core.c @@ -0,0 +1,925 @@ +/* + * lis3l02dq.c support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron <jic23@cam.ac.uk> + * + * 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. + * + * Settings: + * 16 bit left justified mode used. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> + +#include <linux/sysfs.h> +#include <linux/list.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/accel.h> + +#include "lis3l02dq.h" + +/* At the moment the spi framework doesn't allow global setting of cs_change. + * It's in the likely to be added comment at the top of spi.h. + * This means that use cannot be made of spi_write etc. + */ + +/** + * lis3l02dq_spi_read_reg_8() - read single byte from a single register + * @dev: device asosciated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be read + * @val: pass back the resulting value + **/ +int lis3l02dq_spi_read_reg_8(struct device *dev, u8 reg_address, u8 *val) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = iio_dev_get_devdata(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + *val = st->rx[1]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lis3l02dq_spi_write_reg_8() - write single byte to a register + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be writen + * @val: the value to write + **/ +int lis3l02dq_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = iio_dev_get_devdata(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(reg_address); + st->tx[1] = *val; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lisl302dq_spi_write_reg_s16() - write 2 bytes to a pair of registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int lis3l02dq_spi_write_reg_s16(struct device *dev, + u8 lower_reg_address, + s16 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = iio_dev_get_devdata(indio_dev); + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = LIS3L02DQ_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lisl302dq_spi_read_reg_s16() - write 2 bytes to a pair of registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int lis3l02dq_spi_read_reg_s16(struct device *dev, + u8 lower_reg_address, + s16 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = iio_dev_get_devdata(indio_dev); + int ret; + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .rx_buf = st->rx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(lower_reg_address); + st->tx[1] = 0; + st->tx[2] = LIS3L02DQ_READ_REG(lower_reg_address+1); + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register"); + goto error_ret; + } + *val = (s16)(st->rx[1]) | ((s16)(st->rx[3]) << 8); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * lis3l02dq_read_signed() - attribute function used for 8 bit signed values + * @dev: the child device associated with the iio_dev or iio_trigger + * @attr: the attribute being processed + * @buf: buffer into which put the output string + **/ +static ssize_t lis3l02dq_read_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + s8 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = lis3l02dq_spi_read_reg_8(dev, this_attr->address, (u8 *)&val); + + return ret ? ret : sprintf(buf, "%d\n", val); +} + +static ssize_t lis3l02dq_read_unsigned(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = lis3l02dq_spi_read_reg_8(dev, this_attr->address, &val); + + return ret ? ret : sprintf(buf, "%d\n", val); +} + +static ssize_t lis3l02dq_write_signed(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + long valin; + s8 val; + int ret; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strict_strtol(buf, 10, &valin); + if (ret) + goto error_ret; + val = valin; + ret = lis3l02dq_spi_write_reg_8(dev, this_attr->address, (u8 *)&val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t lis3l02dq_write_unsigned(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + ulong valin; + u8 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strict_strtoul(buf, 10, &valin); + if (ret) + goto err_ret; + val = valin; + ret = lis3l02dq_spi_write_reg_8(dev, this_attr->address, &val); + +err_ret: + return ret ? ret : len; +} + +static ssize_t lis3l02dq_read_16bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + s16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = lis3l02dq_spi_read_reg_s16(dev, this_attr->address, &val); + + if (ret) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t lis3l02dq_read_accel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + ssize_t ret; + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_RING_TRIGGERED) + ret = lis3l02dq_read_accel_from_ring(dev, attr, buf); + else + ret = lis3l02dq_read_16bit_signed(dev, attr, buf); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ssize_t lis3l02dq_write_16bit_signed(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_write_reg_s16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t lis3l02dq_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len = 0; + s8 t; + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + (u8 *)&t); + if (ret) + return ret; + t &= LIS3L02DQ_DEC_MASK; + switch (t) { + case LIS3L02DQ_REG_CTRL_1_DF_128: + len = sprintf(buf, "280\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_64: + len = sprintf(buf, "560\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_32: + len = sprintf(buf, "1120\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_8: + len = sprintf(buf, "4480\n"); + break; + } + return len; +} + +static ssize_t lis3l02dq_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + long val; + int ret; + u8 t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret_mutex; + /* Wipe the bits clean */ + t &= ~LIS3L02DQ_DEC_MASK; + switch (val) { + case 280: + t |= LIS3L02DQ_REG_CTRL_1_DF_128; + break; + case 560: + t |= LIS3L02DQ_REG_CTRL_1_DF_64; + break; + case 1120: + t |= LIS3L02DQ_REG_CTRL_1_DF_32; + break; + case 4480: + t |= LIS3L02DQ_REG_CTRL_1_DF_8; + break; + default: + ret = -EINVAL; + goto error_ret_mutex; + }; + + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int lis3l02dq_initial_setup(struct lis3l02dq_state *st) +{ + int ret; + u8 val, valtest; + + st->us->mode = SPI_MODE_3; + + spi_setup(st->us); + + val = LIS3L02DQ_DEFAULT_CTRL1; + /* Write suitable defaults to ctrl1 */ + ret = lis3l02dq_spi_write_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + /* Repeat as sometimes doesn't work first time?*/ + ret = lis3l02dq_spi_write_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + + /* Read back to check this has worked acts as loose test of correct + * chip */ + ret = lis3l02dq_spi_read_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &valtest); + if (ret || (valtest != val)) { + dev_err(&st->indio_dev->dev, "device not playing ball"); + ret = -EINVAL; + goto err_ret; + } + + val = LIS3L02DQ_DEFAULT_CTRL2; + ret = lis3l02dq_spi_write_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 2"); + goto err_ret; + } + + val = LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC; + ret = lis3l02dq_spi_write_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret) + dev_err(&st->us->dev, "problem with interrupt cfg register"); +err_ret: + + return ret; +} + +static IIO_DEV_ATTR_ACCEL_X_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_X_ADDR); + +static IIO_DEV_ATTR_ACCEL_Y_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_Y_ADDR); + +static IIO_DEV_ATTR_ACCEL_Z_OFFSET(S_IWUSR | S_IRUGO, + lis3l02dq_read_signed, + lis3l02dq_write_signed, + LIS3L02DQ_REG_OFFSET_Z_ADDR); + +static IIO_DEV_ATTR_ACCEL_X_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_X_ADDR); + +static IIO_DEV_ATTR_ACCEL_Y_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_Y_ADDR); + +static IIO_DEV_ATTR_ACCEL_Z_GAIN(S_IWUSR | S_IRUGO, + lis3l02dq_read_unsigned, + lis3l02dq_write_unsigned, + LIS3L02DQ_REG_GAIN_Z_ADDR); + +static IIO_DEV_ATTR_ACCEL_THRESH(S_IWUSR | S_IRUGO, + lis3l02dq_read_16bit_signed, + lis3l02dq_write_16bit_signed, + LIS3L02DQ_REG_THS_L_ADDR); + +/* RFC The reading method for these will change depending on whether + * ring buffer capture is in use. Is it worth making these take two + * functions and let the core handle which to call, or leave as in this + * driver where it is the drivers problem to manage this? + */ + +static IIO_DEV_ATTR_ACCEL_X(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_X_L_ADDR); + +static IIO_DEV_ATTR_ACCEL_Y(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_Y_L_ADDR); + +static IIO_DEV_ATTR_ACCEL_Z(lis3l02dq_read_accel, + LIS3L02DQ_REG_OUT_Z_L_ADDR); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + lis3l02dq_read_frequency, + lis3l02dq_write_frequency); + +static IIO_CONST_ATTR_AVAIL_SAMP_FREQ("280 560 1120 4480"); + +static ssize_t lis3l02dq_read_interrupt_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + s8 val; + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + (u8 *)&val); + + return ret ? ret : sprintf(buf, "%d\n", + (val & this_attr->mask) ? 1 : 0);; +} + +static ssize_t lis3l02dq_write_interrupt_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret, currentlyset, changed = 0; + u8 valold, controlold; + bool val; + + val = !(buf[0] == '0'); + + mutex_lock(&indio_dev->mlock); + /* read current value */ + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &valold); + if (ret) + goto error_mutex_unlock; + + /* read current control */ + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &controlold); + if (ret) + goto error_mutex_unlock; + currentlyset = !!(valold & this_attr->mask); + if (val == false && currentlyset) { + valold &= ~this_attr->mask; + changed = 1; + iio_remove_event_from_list(this_attr->listel, + &indio_dev->interrupts[0] + ->ev_list); + } else if (val == true && !currentlyset) { + changed = 1; + valold |= this_attr->mask; + iio_add_event_to_list(this_attr->listel, + &indio_dev->interrupts[0]->ev_list); + } + + if (changed) { + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &valold); + if (ret) + goto error_mutex_unlock; + /* This always enables the interrupt, even if we've remove the + * last thing using it. For this device we can use the reference + * count on the handler to tell us if anyone wants the interrupt + */ + controlold = this_attr->listel->refcount ? + (controlold | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT) : + (controlold & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &controlold); + if (ret) + goto error_mutex_unlock; + } +error_mutex_unlock: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + + +static int lis3l02dq_thresh_handler_th(struct iio_dev *dev_info, + int index, + s64 timestamp, + int no_test) +{ + struct lis3l02dq_state *st = dev_info->dev_data; + + /* Stash the timestamp somewhere convenient for the bh */ + st->last_timestamp = timestamp; + schedule_work(&st->work_cont_thresh.ws); + + return 0; +} + + +/* Unforunately it appears the interrupt won't clear unless you read from the + * src register. + */ +static void lis3l02dq_thresh_handler_bh_no_check(struct work_struct *work_s) +{ + struct iio_work_cont *wc + = container_of(work_s, struct iio_work_cont, ws_nocheck); + struct lis3l02dq_state *st = wc->st; + u8 t; + + lis3l02dq_spi_read_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Z_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Z_LOW, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Y_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Y_LOW, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_X_HIGH, + st->last_timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_X_LOW, + st->last_timestamp); + /* reenable the irq */ + enable_irq(st->us->irq); + /* Ack and allow for new interrupts */ + lis3l02dq_spi_read_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_ACK_ADDR, + &t); + + return; +} + +/* A shared handler for a number of threshold types */ +IIO_EVENT_SH(threshold, &lis3l02dq_thresh_handler_th); + +IIO_EVENT_ATTR_ACCEL_X_HIGH_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH); + +IIO_EVENT_ATTR_ACCEL_Y_HIGH_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH); + +IIO_EVENT_ATTR_ACCEL_Z_HIGH_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH); + +IIO_EVENT_ATTR_ACCEL_X_LOW_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW); + +IIO_EVENT_ATTR_ACCEL_Y_LOW_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW); + +IIO_EVENT_ATTR_ACCEL_Z_LOW_SH(iio_event_threshold, + lis3l02dq_read_interrupt_config, + lis3l02dq_write_interrupt_config, + LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW); + +static struct attribute *lis3l02dq_event_attributes[] = { + &iio_event_attr_accel_x_high.dev_attr.attr, + &iio_event_attr_accel_y_high.dev_attr.attr, + &iio_event_attr_accel_z_high.dev_attr.attr, + &iio_event_attr_accel_x_low.dev_attr.attr, + &iio_event_attr_accel_y_low.dev_attr.attr, + &iio_event_attr_accel_z_low.dev_attr.attr, + NULL +}; + +static struct attribute_group lis3l02dq_event_attribute_group = { + .attrs = lis3l02dq_event_attributes, +}; + +static IIO_CONST_ATTR(name, "lis3l02dq"); + +static struct attribute *lis3l02dq_attributes[] = { + &iio_dev_attr_accel_x_offset.dev_attr.attr, + &iio_dev_attr_accel_y_offset.dev_attr.attr, + &iio_dev_attr_accel_z_offset.dev_attr.attr, + &iio_dev_attr_accel_x_gain.dev_attr.attr, + &iio_dev_attr_accel_y_gain.dev_attr.attr, + &iio_dev_attr_accel_z_gain.dev_attr.attr, + &iio_dev_attr_thresh.dev_attr.attr, + &iio_dev_attr_accel_x.dev_attr.attr, + &iio_dev_attr_accel_y.dev_attr.attr, + &iio_dev_attr_accel_z.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_available_sampling_frequency.dev_attr.attr, + &iio_const_attr_name.dev_attr.attr, + NULL +}; + +static const struct attribute_group lis3l02dq_attribute_group = { + .attrs = lis3l02dq_attributes, +}; + +static int __devinit lis3l02dq_probe(struct spi_device *spi) +{ + int ret; + struct lis3l02dq_state *st = kzalloc(sizeof *st, GFP_KERNEL); + if (!st) { + ret = -ENOMEM; + goto error_ret; + } + /* this is only used tor removal purposes */ + spi_set_drvdata(spi, st); + + /* Allocate the comms buffers */ + st->rx = kzalloc(sizeof(*st->rx)*LIS3L02DQ_MAX_RX, GFP_KERNEL); + if (st->rx == NULL) { + ret = -ENOMEM; + goto error_free_st; + } + st->tx = kzalloc(sizeof(*st->tx)*LIS3L02DQ_MAX_TX, GFP_KERNEL); + if (st->tx == NULL) { + ret = -ENOMEM; + goto error_free_rx; + } + st->us = spi; + mutex_init(&st->buf_lock); + /* setup the industrialio driver allocated elements */ + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + + st->indio_dev->dev.parent = &spi->dev; + st->indio_dev->num_interrupt_lines = 1; + st->indio_dev->event_attrs = &lis3l02dq_event_attribute_group; + st->indio_dev->attrs = &lis3l02dq_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + + ret = lis3l02dq_configure_ring(st->indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_unreg_ring_funcs; + + ret = lis3l02dq_initialize_ring(st->indio_dev->ring); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unregister_dev; + } + + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) { + /* This is a little unusual, in that the device seems + to need a full read of the interrupt source reg before + the interrupt will reset. + Hence the two handlers are the same */ + iio_init_work_cont(&st->work_cont_thresh, + lis3l02dq_thresh_handler_bh_no_check, + lis3l02dq_thresh_handler_bh_no_check, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + 0, + st); + st->inter = 0; + ret = iio_register_interrupt_line(spi->irq, + st->indio_dev, + 0, + IRQF_TRIGGER_RISING, + "lis3l02dq"); + if (ret) + goto error_unregister_dev; + + } + + ret = lis3l02dq_probe_trigger(st->indio_dev); + if (ret) + goto error_unregister_line; + + /* Get the device into a sane initial state */ + ret = lis3l02dq_initial_setup(st); + if (ret) + goto error_remove_trigger; + return 0; + +error_remove_trigger: + lis3l02dq_remove_trigger(st->indio_dev); +error_unregister_line: + if (st->indio_dev->modes & INDIO_RING_TRIGGERED) + iio_unregister_interrupt_line(st->indio_dev, 0); +error_unregister_dev: + iio_device_unregister(st->indio_dev); +error_unreg_ring_funcs: + lis3l02dq_unconfigure_ring(st->indio_dev); +error_free_dev: + iio_free_device(st->indio_dev); +error_free_tx: + kfree(st->tx); +error_free_rx: + kfree(st->rx); +error_free_st: + kfree(st); +error_ret: + return ret; +} + +/* Power down the device */ +static int lis3l02dq_stop_device(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *st = indio_dev->dev_data; + u8 val = 0; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_write_reg_8(&indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &val); + if (ret) { + dev_err(&st->us->dev, "problem with turning device off: ctrl1"); + goto err_ret; + } + + ret = lis3l02dq_spi_write_reg_8(&indio_dev->dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &val); + if (ret) + dev_err(&st->us->dev, "problem with turning device off: ctrl2"); +err_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +/* fixme, confirm ordering in this function */ +static int lis3l02dq_remove(struct spi_device *spi) +{ + int ret; + struct lis3l02dq_state *st = spi_get_drvdata(spi); + struct iio_dev *indio_dev = st->indio_dev; + + ret = lis3l02dq_stop_device(indio_dev); + if (ret) + goto err_ret; + + flush_scheduled_work(); + + lis3l02dq_remove_trigger(indio_dev); + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) + iio_unregister_interrupt_line(indio_dev, 0); + + lis3l02dq_uninitialize_ring(indio_dev->ring); + + iio_device_unregister(indio_dev); + + lis3l02dq_unconfigure_ring(indio_dev); + + iio_free_device(indio_dev); + kfree(st->tx); + kfree(st->rx); + kfree(st); + return 0; + +err_ret: + return ret; +} + +static struct spi_driver lis3l02dq_driver = { + .driver = { + .name = "lis3l02dq", + .owner = THIS_MODULE, + }, + .probe = lis3l02dq_probe, + .remove = __devexit_p(lis3l02dq_remove), +}; + +static __init int lis3l02dq_init(void) +{ + return spi_register_driver(&lis3l02dq_driver); +} +module_init(lis3l02dq_init); + +static __exit void lis3l02dq_exit(void) +{ + spi_unregister_driver(&lis3l02dq_driver); +} +module_exit(lis3l02dq_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/accel.h b/include/linux/iio/accel.h new file mode 100644 index 0000000..c66df20 --- /dev/null +++ b/include/linux/iio/accel.h @@ -0,0 +1,167 @@ + +#include <linux/iio/sysfs.h> + +/* Accelerometer types of attribute */ + +#define IIO_DEV_ATTR_ACCEL_X_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_x_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_Y_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_y_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_Z_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_z_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_X_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_x_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_Y_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_y_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_Z_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(accel_z_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_X(_show, _addr) \ + IIO_DEVICE_ATTR(accel_x, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ACCEL_Y(_show, _addr) \ + IIO_DEVICE_ATTR(accel_y, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ACCEL_Z(_show, _addr) \ + IIO_DEVICE_ATTR(accel_z, S_IRUGO, _show, NULL, _addr) + +/* Thresholds are somewhat chip dependent - may need quite a few defs here */ +/* For unified thesholds (shared across all directions */ + +/** + * IIO_DEV_ATTR_ACCEL_THRESH: unified threshold + * @_mode: read/write + * @_show: read detector threshold value + * @_store: write detector theshold value + * @_addr: driver specific data, typically a register address + * + * This one is for cases where as single threshold covers all directions + **/ +#define IIO_DEV_ATTR_ACCEL_THRESH(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(thresh, _mode, _show, _store, _addr) + +/** + * IIO_DEV_ATTR_ACCEL_THRESH_X: independant direction threshold, x axis + * @_mode: readable / writable + * @_show: read x axis detector theshold value + * @_store: write x axis detector threshold value + * @_addr: device driver dependant, typically a register address + **/ +#define IIO_DEV_ATTR_ACCEL_THRESH_X(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(thresh_accel_x, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_THRESH_Y(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(thresh_accel_y, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACCEL_THRESH_Z(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(thresh_accel_z, _mode, _show, _store, _addr) + + +/** + * IIO_EVENT_ATTR_ACCEL_X_HIGH: threshold event, x acceleration + * @_show: read x acceleration high threshold + * @_store: write x acceleration high threshold + * @_mask: device dependant, typically a bit mask + * @_handler: the iio_handler associated with this attribute + **/ +#define IIO_EVENT_ATTR_ACCEL_X_HIGH(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_x_high, _show, _store, _mask, _handler) + +/** + * IIO_EVENT_ATTR_ACCEL_X_HIGH_SH: threshold event, x accel high, shared handler + * @_evlist: event list used to share the handler + * @_show: attribute read + * @_store: attribute write + * @_mask: driver specific data, typically a bit mask + **/ +#define IIO_EVENT_ATTR_ACCEL_X_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_x_high, _evlist, _show, _store, _mask) + +/** + * IIO_EVENT_CODE_ACCEL_X_HIGH - event code for x axis high accel threshold + **/ +#define IIO_EVENT_CODE_ACCEL_X_HIGH IIO_EVENT_CODE_ACCEL_BASE + +#define IIO_EVENT_ATTR_ACCEL_Y_HIGH(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_y_high, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_ACCEL_Y_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_y_high, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Y_HIGH (IIO_EVENT_CODE_ACCEL_BASE + 1) + +#define IIO_EVENT_ATTR_ACCEL_Z_HIGH(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_z_high, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_ACCEL_Z_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_z_high, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Z_HIGH (IIO_EVENT_CODE_ACCEL_BASE + 2) + +#define IIO_EVENT_ATTR_ACCEL_X_LOW(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_x_low, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_ACCEL_X_LOW_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_x_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_X_LOW (IIO_EVENT_CODE_ACCEL_BASE + 3) + +#define IIO_EVENT_ATTR_ACCEL_Y_LOW(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_y_low, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_ACCEL_Y_LOW_SH(_evlist, _show, _store, _mask)\ + IIO_EVENT_ATTR_SH(accel_y_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Y_LOW (IIO_EVENT_CODE_ACCEL_BASE + 4) + +#define IIO_EVENT_ATTR_ACCEL_Z_LOW(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(accel_z_low, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_ACCEL_Z_LOW_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_z_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Z_LOW (IIO_EVENT_CODE_ACCEL_BASE + 5) + +#define IIO_EVENT_ATTR_FREE_FALL_DETECT(_show, _store, _mask, _handler) \ + IIO_EVENT_ATTR(free_fall, _show, _store, _mask, _handler) + +#define IIO_EVENT_ATTR_FREE_FALL_DETECT_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(free_fall, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_FREE_FALL (IIO_EVENT_CODE_ACCEL_BASE + 6) + + +#define IIO_EVENT_ATTR_ACCEL_X_ROC_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_x_roc_high, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_X_ROC_HIGH (IIO_EVENT_CODE_ACCEL_BASE + 10) + +#define IIO_EVENT_ATTR_ACCEL_X_ROC_LOW_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_x_roc_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_X_ROC_LOW (IIO_EVENT_CODE_ACCEL_BASE + 11) + +#define IIO_EVENT_ATTR_ACCEL_Y_ROC_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_y_roc_high, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Y_ROC_HIGH (IIO_EVENT_CODE_ACCEL_BASE + 12) + +#define IIO_EVENT_ATTR_ACCEL_Y_ROC_LOW_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_y_roc_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Y_ROC_LOW (IIO_EVENT_CODE_ACCEL_BASE + 13) + +#define IIO_EVENT_ATTR_ACCEL_Z_ROC_HIGH_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_z_roc_high, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Z_ROC_HIGH (IIO_EVENT_CODE_ACCEL_BASE + 14) + +#define IIO_EVENT_ATTR_ACCEL_Z_ROC_LOW_SH(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(accel_z_roc_low, _evlist, _show, _store, _mask) + +#define IIO_EVENT_CODE_ACCEL_Z_ROC_LOW (IIO_EVENT_CODE_ACCEL_BASE + 15) -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 05/14] IIO: kxsd9 accelerometer minimal support 2009-07-02 13:47 ` [PATCH 04/14] IIO: lis3l02dq accelerometer " Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 06/14] IIO: Add generic ring buffer support to the IIO core Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> This provides only very minimal support for this device. Note that an alternate driver has been posted to the input mailing list. When the original LMKL discussion that led to the descision to develop IIO occured, the question on whether the differing requirements of IIO and input drivers made it a good idea to have unified drivers was left as an open question. It still is. All opinions on this question welcome. --- drivers/iio/accel/Kconfig | 7 + drivers/iio/accel/Makefile | 2 + drivers/iio/accel/kxsd9.c | 395 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/iio/adc.h | 13 ++ 4 files changed, 417 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/accel/kxsd9.c create mode 100644 include/linux/iio/adc.h diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index a3abffc..548059f 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -4,6 +4,13 @@ comment "Accelerometers" depends on IIO +config KXSD9 + tristate "Kionix KXSD9 Accelerometer Driver" + depends on IIO && SPI + help + Say yes here to build support for the Kionix KXSD9 accelerometer. + Currently this only supports the device via an SPI interface. + config LIS3L02DQ tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" depends on IIO && SPI diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 5a7ef9d..cbec688 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -1,5 +1,7 @@ # # Makefile for industrial I/O accelerometer drivers # +obj-$(CONFIG_KXSD9) += kxsd9.o + lis3l02dq-y := lis3l02dq_core.o obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 0000000..c213295 --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,395 @@ +/* + * kxsd9.c simple support for the Kionix KXSD9 3D + * accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron <jic23@cam.ac.uk> + * + * 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. + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO: Support the motion detector + * Uses register address incrementing so could have a + * heavily optimized ring buffer access function. + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/rtc.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/adc.h> +#include <linux/iio/accel.h> + +#define KXSD9_REG_X 0x00 +#define KXSD9_REG_Y 0x02 +#define KXSD9_REG_Z 0x04 +#define KXSD9_REG_AUX 0x06 +#define KXSD9_REG_RESET 0x0a +#define KXSD9_REG_CTRL_C 0x0c + +#define KXSD9_FS_8 0x00 +#define KXSD9_FS_6 0x01 +#define KXSD9_FS_4 0x02 +#define KXSD9_FS_2 0x03 +#define KXSD9_FS_MASK 0x03 + +#define KXSD9_REG_CTRL_B 0x0d +#define KXSD9_REG_CTRL_A 0x0e + +#define KXSD9_READ(a) (0x80 | (a)) +#define KXSD9_WRITE(a) (a) + +#define IIO_DEV_ATTR_ACCEL_SET_RANGE(_mode, _show, _store) \ + IIO_DEVICE_ATTR(accel_range, _mode, _show, _store, 0) + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 4 +/** + * struct kxsd9_state - device related storage + * @buf_lock: protect the rx and tx buffers. + * @indio_dev: associated industrial IO device + * @us: spi device + * @rx: single rx buffer storage + * @tx: single tx buffer storage + **/ +struct kxsd9_state { + struct mutex buf_lock; + struct iio_dev *indio_dev; + struct spi_device *us; + u8 *rx; + u8 *tx; +}; + +/* This may want to move to mili g to allow for non integer ranges */ +static ssize_t kxsd9_read_accel_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + ssize_t len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + struct spi_transfer xfer = { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + .rx_buf = st->rx, + }; + struct spi_message msg; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); + st->tx[1] = 0; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + + switch (st->rx[1] & KXSD9_FS_MASK) { + case KXSD9_FS_8: + len += sprintf(buf, "8\n"); + break; + case KXSD9_FS_6: + len += sprintf(buf, "6\n"); + break; + case KXSD9_FS_4: + len += sprintf(buf, "4\n"); + break; + case KXSD9_FS_2: + len += sprintf(buf, "2\n"); + break; + } + +error_ret: + mutex_unlock(&st->buf_lock); + + return ret ? ret : len; +} +static ssize_t kxsd9_write_accel_range(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + long readin; + struct spi_message msg; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + u8 val; + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + .rx_buf = st->rx, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, + }; + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + switch (readin) { + case 8: + val = KXSD9_FS_8; + break; + case 6: + val = KXSD9_FS_6; + break; + case 4: + val = KXSD9_FS_4; + break; + case 2: + val = KXSD9_FS_2; + break; + default: + return -EINVAL; + } + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); + st->tx[1] = 0; + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); + st->tx[1] = (st->rx[1] & ~KXSD9_FS_MASK) | val; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); +error_ret: + mutex_unlock(&st->buf_lock); + return ret ? ret : len; +} +static ssize_t kxsd9_read_accel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_message msg; + int ret; + ssize_t len = 0; + u16 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 1, + .cs_change = 0, + .delay_usecs = 200, + .tx_buf = st->tx, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .rx_buf = st->rx, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(this_attr->address); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + val = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); + len = sprintf(buf, "%d\n", val); +error_ret: + mutex_unlock(&st->buf_lock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_ACCEL_X(kxsd9_read_accel, KXSD9_REG_X); +static IIO_DEV_ATTR_ACCEL_Y(kxsd9_read_accel, KXSD9_REG_Y); +static IIO_DEV_ATTR_ACCEL_Z(kxsd9_read_accel, KXSD9_REG_Z); +static IIO_DEV_ATTR_ADC(0, kxsd9_read_accel, KXSD9_REG_AUX); +static IIO_DEV_ATTR_ACCEL_SET_RANGE(S_IRUGO | S_IWUSR, + kxsd9_read_accel_range, + kxsd9_write_accel_range); + +static struct attribute *kxsd9_attributes[] = { + &iio_dev_attr_accel_x.dev_attr.attr, + &iio_dev_attr_accel_y.dev_attr.attr, + &iio_dev_attr_accel_z.dev_attr.attr, + &iio_dev_attr_adc_0.dev_attr.attr, + &iio_dev_attr_accel_range.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kxsd9_attribute_group = { + .attrs = kxsd9_attributes, +}; + +static int __devinit kxsd9_power_up(struct spi_device *spi) +{ + int ret; + struct spi_transfer xfers[2] = { + { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, + }; + struct spi_message msg; + u8 *tx2; + u8 *tx = kmalloc(2, GFP_KERNEL); + + if (tx == NULL) { + ret = -ENOMEM; + goto error_ret; + } + tx2 = kmalloc(2, GFP_KERNEL); + if (tx2 == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + tx[0] = 0x0d; + tx[1] = 0x40; + + tx2[0] = 0x0c; + tx2[1] = 0x9b; + + xfers[0].tx_buf = tx; + xfers[1].tx_buf = tx2; + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(spi, &msg); + + kfree(tx2); +error_free_tx: + kfree(tx); +error_ret: + return ret; + +}; + +static int __devinit kxsd9_probe(struct spi_device *spi) +{ + + struct kxsd9_state *st; + int ret = 0; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, st); + + st->rx = kmalloc(sizeof(*st->rx)*KXSD9_STATE_RX_SIZE, + GFP_KERNEL); + if (st->rx == NULL) { + ret = -ENOMEM; + goto error_free_st; + } + st->tx = kmalloc(sizeof(*st->tx)*KXSD9_STATE_TX_SIZE, + GFP_KERNEL); + if (st->tx == NULL) { + ret = -ENOMEM; + goto error_free_rx; + } + + st->us = spi; + mutex_init(&st->buf_lock); + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + st->indio_dev->dev.parent = &spi->dev; + /* for now */ + st->indio_dev->num_interrupt_lines = 0; + st->indio_dev->event_attrs = NULL; + + st->indio_dev->attrs = &kxsd9_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_free_dev; + + spi->mode = SPI_MODE_0; + spi_setup(spi); + kxsd9_power_up(spi); + + return 0; + +error_free_dev: + iio_free_device(st->indio_dev); +error_free_tx: + kfree(st->tx); +error_free_rx: + kfree(st->rx); +error_free_st: + kfree(st); +error_ret: + return ret; +} + +static int __devexit kxsd9_remove(struct spi_device *spi) +{ + struct kxsd9_state *st = spi_get_drvdata(spi); + + iio_device_unregister(st->indio_dev); + iio_free_device(st->indio_dev); + kfree(st->tx); + kfree(st->rx); + kfree(st); + + return 0; +} + +static struct spi_driver kxsd9_driver = { + .driver = { + .name = "kxsd9", + .owner = THIS_MODULE, + }, + .probe = kxsd9_probe, + .remove = __devexit_p(kxsd9_remove), +}; + +static __init int kxsd9_spi_init(void) +{ + return spi_register_driver(&kxsd9_driver); +} +module_init(kxsd9_spi_init); + +static __exit void kxsd9_spi_exit(void) +{ + spi_unregister_driver(&kxsd9_driver); +} +module_exit(kxsd9_spi_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc.h b/include/linux/iio/adc.h new file mode 100644 index 0000000..d925b2c --- /dev/null +++ b/include/linux/iio/adc.h @@ -0,0 +1,13 @@ +/* + * adc.h - sysfs attributes associated with ADCs + * + * 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. + * + * Copyright (c) 2008 Jonathan Cameron <jic23@cam.ac.uk> + * + */ + +#define IIO_DEV_ATTR_ADC(_num, _show, _addr) \ + IIO_DEVICE_ATTR(adc_##_num, S_IRUGO, _show, NULL, _addr) -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 06/14] IIO: Add generic ring buffer support to the IIO core 2009-07-02 13:47 ` [PATCH 05/14] IIO: kxsd9 accelerometer minimal support Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 07/14] IIO: VTI sca3000 series accelerometer driver (spi) Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> This provides a unified interface for hardware and software ring buffers. Changes since V2: * Moved to a more consistent structure. Now the ring buffer has an associated struct device which is a child of the relevant iio_dev. This in turn has two children, one for the event interface and one for the access interface. These two interfaces are now managed via cdev structures. * Numerous minor cleanups --- drivers/iio/Kconfig | 7 + drivers/iio/Makefile | 1 + drivers/iio/industrialio-ring.c | 566 ++++++++++++++++++++++++++++++++++++++ include/linux/iio/ring_generic.h | 283 +++++++++++++++++++ 4 files changed, 857 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/industrialio-ring.c create mode 100644 include/linux/iio/ring_generic.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index f3545c8..8eb3d4c 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -10,6 +10,13 @@ menuconfig IIO number of different physical interfaces (i2c, spi etc). See Documentation/industrialio for more information. +config IIO_RING_BUFFER + depends on IIO + bool "Enable ring buffer support within IIO" + help + Provide core support for various ring buffer based data + acquisition methods. + source drivers/iio/accel/Kconfig source drivers/iio/adc/Kconfig source drivers/iio/light/Kconfig diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 14c661b..670b8f3 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o +industrialio-$(CONFIG_IIO_RING_BUFFER) += industrialio-ring.o obj-y += accel/ obj-y += adc/ diff --git a/drivers/iio/industrialio-ring.c b/drivers/iio/industrialio-ring.c new file mode 100644 index 0000000..6153d98 --- /dev/null +++ b/drivers/iio/industrialio-ring.c @@ -0,0 +1,566 @@ +/* The industrial I/O core + * + * 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. + * + * Handling of ring allocation / resizing. + * + * + * Things to look at here. + * - Better memory allocation techniques? + * - Alternative access techniques? + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/idr.h> +#include <linux/iio/iio.h> +#include <linux/iio/ring_generic.h> +/* IDR for ring buffer identifier */ +static DEFINE_IDR(iio_ring_idr); +/* IDR for ring event identifier */ +static DEFINE_IDR(iio_ring_event_idr); +/* IDR for ring access identifier */ +static DEFINE_IDR(iio_ring_access_idr); + +int iio_push_ring_event(struct iio_ring_buffer *ring_buf, + int event_code, + s64 timestamp) +{ + return __iio_push_event(&ring_buf->ev_int, + event_code, + timestamp, + &ring_buf->shared_ev_pointer); +} +EXPORT_SYMBOL(iio_push_ring_event); + +int iio_push_or_escallate_ring_event(struct iio_ring_buffer *ring_buf, + int event_code, + s64 timestamp) +{ + if (ring_buf->shared_ev_pointer.ev_p) + __iio_change_event(ring_buf->shared_ev_pointer.ev_p, + event_code, + timestamp); + else + return iio_push_ring_event(ring_buf, + event_code, + timestamp); + return 0; +} +EXPORT_SYMBOL(iio_push_or_escallate_ring_event); + +/** + * iio_ring_open() chrdev file open for ring buffer access + * + * This function relies on all ring buffer implementations having an + * iio_ring_buffer as their first element. + **/ +int iio_ring_open(struct inode *inode, struct file *filp) +{ + struct iio_handler *hand + = container_of(inode->i_cdev, struct iio_handler, chrdev); + struct iio_ring_buffer *rb = hand->private; + + filp->private_data = hand->private; + if (rb->access.mark_in_use) + rb->access.mark_in_use(rb); + + return 0; +} + +/** + * iio_ring_release() -chrdev file close ring buffer access + * + * This function relies on all ring buffer implementations having an + * iio_ring_buffer as their first element. + **/ +int iio_ring_release(struct inode *inode, struct file *filp) +{ + struct cdev *cd = inode->i_cdev; + struct iio_handler *hand = iio_cdev_to_handler(cd); + struct iio_ring_buffer *rb = hand->private; + + clear_bit(IIO_BUSY_BIT_POS, &rb->access_handler.flags); + if (rb->access.unmark_in_use) + rb->access.unmark_in_use(rb); + + return 0; +} + +/** + * iio_ring_rip_outer() chrdev read for ring buffer access + * + * This function relies on all ring buffer implementations having an + * iio_ring _bufer as their first element. + **/ +ssize_t iio_ring_rip_outer(struct file *filp, + char *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_ring_buffer *rb = filp->private_data; + int ret, dead_offset, copied; + u8 *data; + /* rip lots must exist. */ + if (!rb->access.rip_lots) + return -EINVAL; + copied = rb->access.rip_lots(rb, count, &data, &dead_offset); + + if (copied < 0) { + ret = copied; + goto error_ret; + } + if (copy_to_user(buf, data + dead_offset, copied)) { + ret = -EFAULT; + goto error_free_data_cpy; + } + /* In clever ring buffer designs this may not need to be freed. + * When such a design exists I'll add this to ring access funcs. + */ + kfree(data); + + return copied; + +error_free_data_cpy: + kfree(data); +error_ret: + return ret; +} + +static const struct file_operations iio_ring_fileops = { + .read = iio_ring_rip_outer, + .release = iio_ring_release, + .open = iio_ring_open, + .owner = THIS_MODULE, +}; + +/** + * __iio_request_ring_buffer_event_chrdev() allocate ring event chrdev + * @buf: ring buffer whose event chrdev we are allocating + * @owner: the module who owns the ring buffer (for ref counting) + * @dev: device with which the chrdev is associated + **/ +static inline int +__iio_request_ring_buffer_event_chrdev(struct iio_ring_buffer *buf, + int id, + struct module *owner, + struct device *dev) +{ + int ret; + ret = iio_get_new_idr_val(&iio_ring_event_idr); + if (ret < 0) + goto error_ret; + else + buf->ev_int.id = ret; + + snprintf(buf->ev_int._name, 20, + "ring_event_line%d", + buf->ev_int.id); + ret = iio_setup_ev_int(&(buf->ev_int), + buf->ev_int._name, + owner, + dev); + if (ret) + goto error_free_id; + return 0; + +error_free_id: + iio_free_idr_val(&iio_ring_event_idr, buf->ev_int.id); +error_ret: + return ret; +} + +static inline void +__iio_free_ring_buffer_event_chrdev(struct iio_ring_buffer *buf) +{ + iio_free_ev_int(&(buf->ev_int)); + iio_free_idr_val(&iio_ring_event_idr, buf->ev_int.id); +} + +static void iio_ring_access_release(struct device *dev) +{ + struct iio_ring_buffer *buf + = access_dev_to_iio_ring_buffer(dev); + cdev_del(&buf->access_handler.chrdev); + iio_device_free_chrdev_minor(MINOR(dev->devt)); +} + +static struct device_type iio_ring_access_type = { + .release = iio_ring_access_release, +}; + +static inline int +__iio_request_ring_buffer_access_chrdev(struct iio_ring_buffer *buf, + int id, + struct module *owner) +{ + int ret, minor; + + buf->access_handler.flags = 0; + + buf->access_dev.parent = &buf->dev; + buf->access_dev.class = &iio_class; + buf->access_dev.type = &iio_ring_access_type; + device_initialize(&buf->access_dev); + + minor = iio_device_get_chrdev_minor(); + if (minor < 0) { + ret = minor; + goto error_device_put; + } + buf->access_dev.devt = MKDEV(MAJOR(iio_devt), minor); + + ret = iio_get_new_idr_val(&iio_ring_access_idr); + if (ret < 0) + goto error_device_put; + else + buf->access_id = ret; + dev_set_name(&buf->access_dev, "ring_access%d", buf->access_id); + ret = device_add(&buf->access_dev); + if (ret < 0) { + printk(KERN_ERR "failed to add the ring access dev\n"); + goto error_free_idr; + } + + cdev_init(&buf->access_handler.chrdev, &iio_ring_fileops); + buf->access_handler.chrdev.owner = owner; + + ret = cdev_add(&buf->access_handler.chrdev, buf->access_dev.devt, 1); + if (ret) { + printk(KERN_ERR "failed to allocate ring access chrdev\n"); + goto error_device_unregister; + } + return 0; +error_device_unregister: + device_unregister(&buf->access_dev); +error_free_idr: + iio_free_idr_val(&iio_ring_access_idr, buf->access_id); +error_device_put: + put_device(&buf->access_dev); + + return ret; +} + +static void __iio_free_ring_buffer_access_chrdev(struct iio_ring_buffer *buf) +{ + iio_free_idr_val(&iio_ring_access_idr, buf->access_id); + device_unregister(&buf->access_dev); +} + +void iio_ring_buffer_init(struct iio_ring_buffer *ring, + struct iio_dev *dev_info) +{ + if (ring->access.mark_param_change) + ring->access.mark_param_change(ring); + ring->indio_dev = dev_info; + ring->ev_int.private = ring; + ring->access_handler.private = ring; +} +EXPORT_SYMBOL(iio_ring_buffer_init); + +int iio_ring_buffer_register(struct iio_ring_buffer *ring) +{ + int ret; + ret = iio_get_new_idr_val(&iio_ring_idr); + if (ret < 0) + goto error_ret; + else + ring->id = ret; + + dev_set_name(&ring->dev, "ring_buffer%d", ring->id); + ret = device_add(&ring->dev); + if (ret) + goto error_free_id; + + ret = __iio_request_ring_buffer_event_chrdev(ring, + 0, + ring->owner, + &ring->dev); + if (ret) + goto error_remove_device; + + ret = __iio_request_ring_buffer_access_chrdev(ring, + 0, + ring->owner); + + if (ret) + goto error_free_ring_buffer_event_chrdev; + + return ret; +error_free_ring_buffer_event_chrdev: + __iio_free_ring_buffer_event_chrdev(ring); +error_remove_device: + device_del(&ring->dev); +error_free_id: + iio_free_idr_val(&iio_ring_idr, ring->id); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_ring_buffer_register); + +void iio_ring_buffer_unregister(struct iio_ring_buffer *ring) +{ + __iio_free_ring_buffer_access_chrdev(ring); + __iio_free_ring_buffer_event_chrdev(ring); + device_del(&ring->dev); + iio_free_idr_val(&iio_ring_idr, ring->id); +} +EXPORT_SYMBOL(iio_ring_buffer_unregister); + +ssize_t iio_read_ring_length(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0; + struct iio_ring_buffer *ring = dev_get_drvdata(dev); + + if (ring->access.get_length) + len = sprintf(buf, "%d\n", + ring->access.get_length(ring)); + + return len; +} +EXPORT_SYMBOL(iio_read_ring_length); + + ssize_t iio_write_ring_length(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + ulong val; + struct iio_ring_buffer *ring = dev_get_drvdata(dev); + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + if (ring->access.get_length) + if (val == ring->access.get_length(ring)) + return len; + + if (ring->access.set_length) { + ring->access.set_length(ring, val); + if (ring->access.mark_param_change) + ring->access.mark_param_change(ring); + } + + return len; +} +EXPORT_SYMBOL(iio_write_ring_length); + +ssize_t iio_read_ring_bps(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0; + struct iio_ring_buffer *ring = dev_get_drvdata(dev); + + if (ring->access.get_bpd) + len = sprintf(buf, "%d\n", + ring->access.get_bpd(ring)); + + return len; +} +EXPORT_SYMBOL(iio_read_ring_bps); + +ssize_t iio_store_ring_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + bool requested_state, current_state; + int previous_mode; + struct iio_ring_buffer *ring = dev_get_drvdata(dev); + struct iio_dev *dev_info = ring->indio_dev; + + mutex_lock(&dev_info->mlock); + previous_mode = dev_info->currentmode; + requested_state = !(buf[0] == '0'); + current_state = !!(previous_mode & INDIO_ALL_RING_MODES); + if (current_state == requested_state) { + printk(KERN_INFO "iio-ring, current state requested again\n"); + goto done; + } + if (requested_state) { + if (ring->preenable) { + ret = ring->preenable(dev_info); + if (ret) { + printk(KERN_ERR + "Buffer not started:" + "ring preenable failed\n"); + goto error_ret; + } + } + if (ring->access.request_update) { + ret = ring->access.request_update(ring); + if (ret) { + printk(KERN_INFO + "Buffer not started:" + "ring parameter update failed\n"); + goto error_ret; + } + } + if (ring->access.mark_in_use) + ring->access.mark_in_use(ring); + /* Definitely possible for devices to support both of these.*/ + if (dev_info->modes & INDIO_RING_TRIGGERED) { + if (!dev_info->trig) { + printk(KERN_INFO + "Buffer not started: no trigger\n"); + ret = -EINVAL; + if (ring->access.unmark_in_use) + ring->access.unmark_in_use(ring); + goto error_ret; + } + dev_info->currentmode = INDIO_RING_TRIGGERED; + } else if (dev_info->modes & INDIO_RING_HARDWARE_BUFFER) + dev_info->currentmode = INDIO_RING_HARDWARE_BUFFER; + else { /* should never be reached */ + ret = -EINVAL; + goto error_ret; + } + + if (ring->postenable) { + + ret = ring->postenable(dev_info); + if (ret) { + printk(KERN_INFO + "Buffer not started:" + "postenable failed\n"); + if (ring->access.unmark_in_use) + ring->access.unmark_in_use(ring); + dev_info->currentmode = previous_mode; + if (ring->postdisable) + ring->postdisable(dev_info); + goto error_ret; + } + } + } else { + if (ring->predisable) { + ret = ring->predisable(dev_info); + if (ret) + goto error_ret; + } + if (ring->access.unmark_in_use) + ring->access.unmark_in_use(ring); + dev_info->currentmode = INDIO_DIRECT_MODE; + if (ring->postdisable) { + ret = ring->postdisable(dev_info); + if (ret) + goto error_ret; + } + } +done: + mutex_unlock(&dev_info->mlock); + return len; + +error_ret: + mutex_unlock(&dev_info->mlock); + return ret; +} +EXPORT_SYMBOL(iio_store_ring_enable); +ssize_t iio_show_ring_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_ring_buffer *ring = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", !!(ring->indio_dev->currentmode + & INDIO_ALL_RING_MODES)); +} +EXPORT_SYMBOL(iio_show_ring_enable); + +ssize_t iio_scan_el_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_scan_el *this_el = to_iio_scan_el(attr); + + ret = iio_scan_mask_query(indio_dev, this_el->number); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", ret); +} +EXPORT_SYMBOL(iio_scan_el_show); + +ssize_t iio_scan_el_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret = 0; + bool state; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_scan_el *this_el = to_iio_scan_el(attr); + + state = !(buf[0] == '0'); + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_RING_TRIGGERED) { + ret = -EBUSY; + goto error_ret; + } + ret = iio_scan_mask_query(indio_dev, this_el->number); + if (ret < 0) + goto error_ret; + if (!state && ret) { + ret = iio_scan_mask_clear(indio_dev, this_el->number); + if (ret) + goto error_ret; + indio_dev->scan_count--; + } else if (state && !ret) { + ret = iio_scan_mask_set(indio_dev, this_el->number); + if (ret) + goto error_ret; + indio_dev->scan_count++; + } + if (this_el->set_state) + ret = this_el->set_state(this_el, indio_dev, state); +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; + +} +EXPORT_SYMBOL(iio_scan_el_store); + +ssize_t iio_scan_el_ts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", indio_dev->scan_timestamp); +} +EXPORT_SYMBOL(iio_scan_el_ts_show); + +ssize_t iio_scan_el_ts_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + bool state; + state = !(buf[0] == '0'); + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_RING_TRIGGERED) { + ret = -EBUSY; + goto error_ret; + } + indio_dev->scan_timestamp = state; +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +EXPORT_SYMBOL(iio_scan_el_ts_store); + diff --git a/include/linux/iio/ring_generic.h b/include/linux/iio/ring_generic.h new file mode 100644 index 0000000..46e0c4b --- /dev/null +++ b/include/linux/iio/ring_generic.h @@ -0,0 +1,283 @@ +/* The industrial I/O core - generic ring buffer interfaces. + * + * 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. + */ + +#ifndef _IIO_RING_GENERIC_H_ +#define _IIO_RING_GENERIC_H_ +#include <linux/iio/iio.h> + +struct iio_handler; +struct iio_ring_buffer; +struct iio_dev; + +/** + * iio_push_ring_event() - ring buffer specific push to event chrdev + * @ring_buf: ring buffer that is the event source + * @event_code: event indentification code + * @timestamp: time of event + **/ +int iio_push_ring_event(struct iio_ring_buffer *ring_buf, + int event_code, + s64 timestamp); +/** + * iio_push_or_escallate_ring_event() - escallate or add as appropriate + * + * Typical usecase is to escallate a 50% ring full to 75% full if noone has yet + * read the first event. Clearly the 50% full is no longer of interest in + * typical use case. + **/ +int iio_push_or_escallate_ring_event(struct iio_ring_buffer *ring_buf, + int event_code, + s64 timestamp); + +/** + * struct iio_ring_access_funcs - access functions for ring buffers. + * @create: perform allocation + * @init: get ring buffer ready for use + * @_exit: reverse steps in init + * @_free: deallocate ring buffer + * @mark_in_use: reference counting, typically to prevent module removal + * @unmark_in_use: reduce reference count when no longer using ring buffer + * @store_to: actually store stuff to the ring buffer + * @read_last: get the last element stored + * @rip_lots: try to get a specified number of elements (must exist) + * @mark_param_change: notify ring that some relevant parameter has changed + * Often this means the underlying storage may need to + * change. + * @request_update: if a parameter change has been marked, update underlying + * storage. + * @get_bpd: get current bytes per datum + * @set_bpd: set number of bytes per datum + * @get_length: get number of datums in ring + * @set_length: set number of datums in ring + * @is_enabled: query if ring is currently being used + * @enable: enable the ring + * + * The purpose of this structure is to make the ring buffer element + * modular as event for a given driver, different usecases may require + * different ring designs (space efficiency vs speed for example. + * + * It is worth noting that a given ring implementation may only support a small + * proportion of these functions. The core code 'should' cope fine with any of + * them not existing. + **/ +struct iio_ring_access_funcs { + void (*mark_in_use)(struct iio_ring_buffer *ring); + void (*unmark_in_use)(struct iio_ring_buffer *ring); + + int (*store_to)(struct iio_ring_buffer *ring, u8 *data, s64 timestamp); + int (*read_last)(struct iio_ring_buffer *ring, u8 *data); + int (*rip_lots)(struct iio_ring_buffer *ring, + size_t count, + u8 **data, + int *dead_offset); + + int (*mark_param_change)(struct iio_ring_buffer *ring); + int (*request_update)(struct iio_ring_buffer *ring); + + int (*get_bpd)(struct iio_ring_buffer *ring); + int (*set_bpd)(struct iio_ring_buffer *ring, size_t bpd); + int (*get_length)(struct iio_ring_buffer *ring); + int (*set_length)(struct iio_ring_buffer *ring, int length); + + int (*is_enabled)(struct iio_ring_buffer *ring); + int (*enable)(struct iio_ring_buffer *ring); +}; + +/** + * struct iio_ring_buffer - general ring buffer structure + * @length: [DEVICE]number of datums in ring + * @bpd: [DEVICE]size of individual datum including timestamp + * @loopcount: [INTERN]number of times the ring has looped + * @access_minor_name: [INTERN]store of name of the access chrdev minor number + * sysfs attribute + * @access_handler: [INTERN]chrdev access handling + * @event_minor_name: [INTERN]store of name of the event chrdev minor number + * sysfs attribute + * @ev_int: [INTERN]chrdev interface for the event chrdev + * @shared_ev_pointer: [INTERN]the shared event pointer to allow escalation of + * events + * @ring_access: [DRIVER]ring access functions associated with the + * implementation. + * @ring_prenable: [DRIVER] function to run prior to marking ring enabled + * @ring_postenable: [DRIVER] function to run after marking ring enabled + * @ring_predisable: [DRIVER] function to run prior to marking ring disabled + * @ring_postdisable: [DRIVER] function to run after marking ring disabled + **/ +struct iio_ring_buffer { + struct device dev; + struct device access_dev; + struct iio_dev *indio_dev; + struct module *owner; + int id; + int access_id; + int length; + int bpd; + int loopcount; + struct iio_handler access_handler; + struct iio_event_interface ev_int; + struct iio_shared_ev_pointer shared_ev_pointer; + struct iio_ring_access_funcs access; + int (*preenable)(struct iio_dev *); + int (*postenable)(struct iio_dev *); + int (*predisable)(struct iio_dev *); + int (*postdisable)(struct iio_dev *); + +}; +void iio_ring_buffer_init(struct iio_ring_buffer *ring, + struct iio_dev *dev_info); + +/** + * __iio_init_ring_buffer() - initialize common elements of ring buffers. + **/ +static inline void __iio_init_ring_buffer(struct iio_ring_buffer *ring, + int bytes_per_datum, int length) +{ + ring->bpd = bytes_per_datum; + ring->length = length; + ring->loopcount = 0; + ring->shared_ev_pointer.ev_p = 0; + ring->shared_ev_pointer.lock = + __SPIN_LOCK_UNLOCKED(ring->shared_ev_pointer->loc); +} + +/** + * struct iio_scan_el - an individual element of a scan + * @dev_attr: control attribute (if directly controllable) + * @number: unique identifier of element (used for bit mask) + * @bit_count: number of bits in scan element + * @label: useful data for the scan el (often reg address) + * @set_state: for some devices datardy signals are generated + * for any enabled lines. This allows unwanted lines + * to be disabled and hence not get in the way. + **/ +struct iio_scan_el { + struct device_attribute dev_attr; + unsigned int number; + int bit_count; + unsigned int label; + + int (*set_state)(struct iio_scan_el *scanel, + struct iio_dev *dev_info, + bool state); +}; + +#define to_iio_scan_el(_dev_attr) \ + container_of(_dev_attr, struct iio_scan_el, dev_attr); + +/** + * iio_scan_el_store() - sysfs scan element selection interface. + * + * A generic function used to enable various scan elements. In some + * devices explicit read commands for each channel mean this is merely + * a software switch. In others this must actively disable the channel. + * Complexities occur when this interacts with data ready type triggers + * which may not reset unless every channel that is enabled is explicitly + * read. + **/ +ssize_t iio_scan_el_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len); +/** + * iio_scal_el_show() - sysfs interface to query whether a scan element is + * is enabled or not. + **/ +ssize_t iio_scan_el_show(struct device *dev, struct device_attribute *attr, + char *buf); +/** + * IIO_SCAN_EL: - declare and initialize a scan element without control func + * @_name: identifying name. Resulting struct is iio_scan_el_##_name, + * sysfs element, scan_en_##_name. + * @_number: unique id number for the scan element. + * @_bits: number of bits in the scan element result (used in mixed bit + * length devices). + * @_label: indentification variable used by drivers. Often a reg address. + **/ +#define IIO_SCAN_EL(_name, _number, _bits, _label) \ + struct iio_scan_el iio_scan_el_##_name = { \ + .dev_attr = __ATTR(scan_en_##_name, \ + S_IRUGO | S_IWUSR, \ + iio_scan_el_show, \ + iio_scan_el_store), \ + .mask = (1 << _number), \ + .bit_count = _bits, \ + .label = _label, \ + } + +ssize_t iio_scan_el_ts_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len); + +ssize_t iio_scan_el_ts_show(struct device *dev, struct device_attribute *attr, + char *buf); +/** + * IIO_SCAN_EL_C: - declare and initialize a scan element with a control func + * + * @_controlfunc: function used to notify hardware of whether state changes + **/ +#define IIO_SCAN_EL_C(_name, _number, _bits, _label, _controlfunc) \ + struct iio_scan_el iio_scan_el_##_name = { \ + .dev_attr = __ATTR(scan_en_##_name, \ + S_IRUGO | S_IWUSR, \ + iio_scan_el_show, \ + iio_scan_el_store), \ + .number = _number, \ + .bit_count = _bits, \ + .label = _label, \ + .set_state = _controlfunc, \ + } +/** + * IIO_SCAN_EL_TIMESTAMP: - declare a special scan element for timestamps + * + * Odd one out. Handled slightly differently from other scan elements. + **/ +#define IIO_SCAN_EL_TIMESTAMP \ + struct iio_scan_el iio_scan_el_timestamp = { \ + .dev_attr = __ATTR(scan_en_timestamp, \ + S_IRUGO | S_IWUSR, \ + iio_scan_el_ts_show, \ + iio_scan_el_ts_store), \ + } + +static inline void iio_put_ring_buffer(struct iio_ring_buffer *ring) +{ + put_device(&ring->dev); +}; + +#define to_iio_ring_buffer(d) \ + container_of(d, struct iio_ring_buffer, dev) +#define access_dev_to_iio_ring_buffer(d) \ + container_of(d, struct iio_ring_buffer, access_dev) +int iio_ring_buffer_register(struct iio_ring_buffer *ring); +void iio_ring_buffer_unregister(struct iio_ring_buffer *ring); + +ssize_t iio_read_ring_length(struct device *dev, + struct device_attribute *attr, + char *buf); +ssize_t iio_write_ring_length(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len); +ssize_t iio_read_ring_bps(struct device *dev, + struct device_attribute *attr, + char *buf); +ssize_t iio_store_ring_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len); +ssize_t iio_show_ring_enable(struct device *dev, + struct device_attribute *attr, + char *buf); +#define IIO_RING_LENGTH_ATTR DEVICE_ATTR(length, S_IRUGO | S_IWUSR, \ + iio_read_ring_length, \ + iio_write_ring_length) +#define IIO_RING_BPS_ATTR DEVICE_ATTR(bps, S_IRUGO | S_IWUSR, \ + iio_read_ring_bps, NULL) +#define IIO_RING_ENABLE_ATTR DEVICE_ATTR(ring_enable, S_IRUGO | S_IWUSR, \ + iio_show_ring_enable, \ + iio_store_ring_enable) + +#endif /* _IIO_RING_GENERIC_H_ */ -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 07/14] IIO: VTI sca3000 series accelerometer driver (spi) 2009-07-02 13:47 ` [PATCH 06/14] IIO: Add generic ring buffer support to the IIO core Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 08/14] IIO: Trigger support added to core Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Example of how a device with a hardware ring buffer is handled within IIO. Changes since V2: * Moved to new registration functions giving much cleaner interface. --- drivers/iio/accel/Kconfig | 8 + drivers/iio/accel/Makefile | 3 + drivers/iio/accel/sca3000.h | 273 +++++++ drivers/iio/accel/sca3000_core.c | 1509 ++++++++++++++++++++++++++++++++++++++ drivers/iio/accel/sca3000_ring.c | 334 +++++++++ include/linux/iio/ring_hw.h | 22 + include/linux/iio/sca3000.h | 37 + 7 files changed, 2186 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/accel/sca3000.h create mode 100644 drivers/iio/accel/sca3000_core.c create mode 100644 drivers/iio/accel/sca3000_ring.c create mode 100644 include/linux/iio/ring_hw.h create mode 100644 include/linux/iio/sca3000.h diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 548059f..0264e55 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -18,3 +18,11 @@ config LIS3L02DQ Say yes here to build SPI support for the ST microelectronics accelerometer. The driver supplies direct access via sysfs files and an event interface via a character device. + +config SCA3000 + depends on IIO && IIO_RING_BUFFER + depends on SPI + tristate "VTI SCA3000 series accelerometers" + help + Say yes here to build support for the VTI SCA3000 series of SPI + accelerometers. These devices use a hardware ring buffer. \ No newline at end of file diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index cbec688..d543208 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -5,3 +5,6 @@ obj-$(CONFIG_KXSD9) += kxsd9.o lis3l02dq-y := lis3l02dq_core.o obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o + +sca3000-y := sca3000_core.o sca3000_ring.o +obj-$(CONFIG_SCA3000) += sca3000.o \ No newline at end of file diff --git a/drivers/iio/accel/sca3000.h b/drivers/iio/accel/sca3000.h new file mode 100644 index 0000000..ed393c4 --- /dev/null +++ b/drivers/iio/accel/sca3000.h @@ -0,0 +1,273 @@ +/* + * sca3000.c -- support VTI sca3000 series accelerometers + * via SPI + * + * Copyright (c) 2007 Jonathan Cameron <jic23@cam.ac.uk> + * + * Partly based upon tle62x0.c + * + * 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. + * + * Initial mode is direct measurement. + * + * Untested things + * + * Temperature reading (the e05 I'm testing with doesn't have a sensor) + * + * Free fall detection mode - supported but untested as I'm not droping my + * dubious wire rig far enough to test it. + * + * Unsupported as yet + * + * Time stamping of data from ring. Various ideas on how to do this but none + * are remotely simple. Suggestions welcome. + * + * Individual enabling disabling of channels going into ring buffer + * + * Overflow handling (this is signaled for all but 8 bit ring buffer mode.) + * + * Motion detector using AND combinations of signals. + * + * Note: Be very careful about not touching an register bytes marked + * as reserved on the data sheet. They really mean it as changing convents of + * some will cause the device to lock up. + * + * Known issues - on rare occasions the interrupts lock up. Not sure why as yet. + * Can probably alleviate this by reading the interrupt register on start, but + * that is really just brushing the problem under the carpet. + */ +#define SCA3000_WRITE_REG(a) (((a) << 2) | 0x02) +#define SCA3000_READ_REG(a) ((a) << 2) + +#define SCA3000_REG_ADDR_REVID 0x00 +#define SCA3000_REVID_MAJOR_MASK 0xf0 +#define SCA3000_REVID_MINOR_MASK 0x0f + +#define SCA3000_REG_ADDR_STATUS 0x02 +#define SCA3000_LOCKED 0x20 +#define SCA3000_EEPROM_CS_ERROR 0x02 +#define SCA3000_SPI_FRAME_ERROR 0x01 + +/* All reads done using register decrement so no need to directly access LSBs */ +#define SCA3000_REG_ADDR_X_MSB 0x05 +#define SCA3000_REG_ADDR_Y_MSB 0x07 +#define SCA3000_REG_ADDR_Z_MSB 0x09 + +#define SCA3000_REG_ADDR_RING_OUT 0x0f + +/* Temp read untested - the e05 doesn't have the sensor */ +#define SCA3000_REG_ADDR_TEMP_MSB 0x13 + +#define SCA3000_REG_ADDR_MODE 0x14 +#define SCA3000_MODE_PROT_MASK 0x28 + +#define SCA3000_RING_BUF_ENABLE 0x80 +#define SCA3000_RING_BUF_8BIT 0x40 +/* Free fall detection triggers an interrupt if the acceleration + * is below a threshold for equivalent of 25cm drop + */ +#define SCA3000_FREE_FALL_DETECT 0x10 +#define SCA3000_MEAS_MODE_NORMAL 0x00 +#define SCA3000_MEAS_MODE_OP_1 0x01 +#define SCA3000_MEAS_MODE_OP_2 0x02 + +/* In motion detection mode the accelerations are band pass filtered + * (aprox 1 - 25Hz) and then a programmable theshold used to trigger + * and interrupt. + */ +#define SCA3000_MEAS_MODE_MOT_DET 0x03 + +#define SCA3000_REG_ADDR_BUF_COUNT 0x15 + +#define SCA3000_REG_ADDR_INT_STATUS 0x16 + +#define SCA3000_INT_STATUS_THREE_QUARTERS 0x80 +#define SCA3000_INT_STATUS_HALF 0x40 + +#define SCA3000_INT_STATUS_FREE_FALL 0x08 +#define SCA3000_INT_STATUS_Y_TRIGGER 0x04 +#define SCA3000_INT_STATUS_X_TRIGGER 0x02 +#define SCA3000_INT_STATUS_Z_TRIGGER 0x01 + +/* Used to allow accesss to multiplexed registers */ +#define SCA3000_REG_ADDR_CTRL_SEL 0x18 +/* Only available for SCA3000-D03 and SCA3000-D01 */ +#define SCA3000_REG_CTRL_SEL_I2C_DISABLE 0x01 +#define SCA3000_REG_CTRL_SEL_MD_CTRL 0x02 +#define SCA3000_REG_CTRL_SEL_MD_Y_TH 0x03 +#define SCA3000_REG_CTRL_SEL_MD_X_TH 0x04 +#define SCA3000_REG_CTRL_SEL_MD_Z_TH 0x05 +/* BE VERY CAREFUL WITH THIS, IF 3 BITS ARE NOT SET the device + will not function */ +#define SCA3000_REG_CTRL_SEL_OUT_CTRL 0x0B +#define SCA3000_OUT_CTRL_PROT_MASK 0xE0 +#define SCA3000_OUT_CTRL_BUF_X_EN 0x10 +#define SCA3000_OUT_CTRL_BUF_Y_EN 0x08 +#define SCA3000_OUT_CTRL_BUF_Z_EN 0x04 +#define SCA3000_OUT_CTRL_BUF_DIV_4 0x02 +#define SCA3000_OUT_CTRL_BUF_DIV_2 0x01 + +/* Control which motion detector interrupts are on. + * For now only OR combinations are supported.x + */ +#define SCA3000_MD_CTRL_PROT_MASK 0xC0 +#define SCA3000_MD_CTRL_OR_Y 0x01 +#define SCA3000_MD_CTRL_OR_X 0x02 +#define SCA3000_MD_CTRL_OR_Z 0x04 +/* Currently unsupported */ +#define SCA3000_MD_CTRL_AND_Y 0x08 +#define SCA3000_MD_CTRL_AND_X 0x10 +#define SAC3000_MD_CTRL_AND_Z 0x20 + +/* Some control registers of complex access methods requiring this register to + * be used to remove a lock. + */ +#define SCA3000_REG_ADDR_UNLOCK 0x1e + +#define SCA3000_REG_ADDR_INT_MASK 0x21 +#define SCA3000_INT_MASK_PROT_MASK 0x1C + +#define SCA3000_INT_MASK_RING_THREE_QUARTER 0x80 +#define SCA3000_INT_MASK_RING_HALF 0x40 + +#define SCA3000_INT_MASK_ALL_INTS 0x02 +#define SCA3000_INT_MASK_ACTIVE_HIGH 0x01 +#define SCA3000_INT_MASK_ACTIVE_LOW 0x00 + +/* Values of mulipexed registers (write to ctrl_data after select) */ +#define SCA3000_REG_ADDR_CTRL_DATA 0x22 + +/* Measurment modes available on some sca3000 series chips. Code assumes others + * may become available in the future. + * + * Bypass - Bypass the low-pass filter in the signal channel so as to increase + * signal bandwidth. + * + * Narrow - Narrow low-pass filtering of the signal channel and half output + * data rate by decimation. + * + * Wide - Widen low-pass filtering of signal channel to increase bandwidth + */ +#define SCA3000_OP_MODE_BYPASS 0x01 +#define SCA3000_OP_MODE_NARROW 0x02 +#define SCA3000_OP_MODE_WIDE 0x04 +#define SCA3000_MAX_TX 6 +#define SCA3000_MAX_RX 2 + +/** + * struct sca3000_state - device instance state information + * @us: the associated spi device + * @info: chip variant information + * @indio_dev: device information used by the IIO core + * @interrupt_handler_ws: event interrupt handler for all events + * @last_timestamp: the timestamp of the last event + * @mo_det_use_count: reference counter for the motion detection unit + * @lock: lock used to protect elements of sca3000_state + * and the underlying device state. + * @bpse: number of bits per scan element + * @tx: dma-able transmit buffer + * @rx: dma-able receive buffer + **/ +struct sca3000_state { + struct spi_device *us; + const struct sca3000_chip_info *info; + struct iio_dev *indio_dev; + struct work_struct interrupt_handler_ws; + s64 last_timestamp; + int mo_det_use_count; + struct mutex lock; + int bpse; + u8 *tx; + /* not used during a ring buffer read */ + u8 *rx; +}; + +/** + * struct sca3000_chip_info - model dependant parameters + * @name: model identification + * @temp_output: some devices have temperature sensors. + * @measurement_mode_freq: normal mode sampling frequency + * @option_mode_1: first optional mode. Not all models have one + * @option_mode_1_freq: option mode 1 sampling frequency + * @option_mode_2: second optional mode. Not all chips have one + * @option_mode_2_freq: option mode 2 sampling frequency + * + * This structure is used to hold information about the functionality of a given + * sca3000 variant. + **/ +struct sca3000_chip_info { + const char *name; + bool temp_output; + int measurement_mode_freq; + int option_mode_1; + int option_mode_1_freq; + int option_mode_2; + int option_mode_2_freq; +}; + +/** + * sca3000_read_data() read a series of values from the device + * @dev: device + * @reg_address_high: start address (decremented read) + * @rx: pointer where recieved data is placed. Callee + * responsible for freeing this. + * @len: number of bytes to read + * + * The main lock must be held. + **/ +int sca3000_read_data(struct sca3000_state *st, + u8 reg_address_high, + u8 **rx_p, + int len); + +/** + * sca3000_write_reg() write a single register + * @address: address of register on chip + * @val: value to be written to register + * + * The main lock must be held. + **/ +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val); + +#ifdef CONFIG_IIO_RING_BUFFER +/** + * sca3000_register_ring_funcs() setup the ring state change functions + **/ +void sca3000_register_ring_funcs(struct iio_dev *indio_dev); + +/** + * sca3000_configure_ring() - allocate and configure ring buffer + * @indio_dev: iio-core device whose ring is to be configured + * + * The hardware ring buffer needs far fewer ring buffer functions than + * a software one as a lot of things are handled automatically. + * This function also tells the iio core that our device supports a + * hardware ring buffer mode. + **/ +int sca3000_configure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_unconfigure_ring() - deallocate the ring buffer + * @indio_dev: iio-core device whose ring we are freeing + **/ +void sca3000_unconfigure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_ring_int_process() handles ring related event pushing and escalation + * @val: the event code + **/ +void sca3000_ring_int_process(u8 val, struct iio_ring_buffer *ring); + +#else +static inline void sca3000_register_ring_funcs(struct iio_dev *indio_dev) {}; + +static inline +int sca3000_register_ring_access_and_init(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline void sca3000_ring_int_process(u8 val, void *ring) {}; +#endif diff --git a/drivers/iio/accel/sca3000_core.c b/drivers/iio/accel/sca3000_core.c new file mode 100644 index 0000000..b79e46c --- /dev/null +++ b/drivers/iio/accel/sca3000_core.c @@ -0,0 +1,1509 @@ +/* + * sca3000_core.c -- support VTI sca3000 series accelerometers via SPI + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron <jic23@cam.ac.uk> + * + * See industrialio/accels/sca3000.h for comments. + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/rtc.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/accel.h> +#include <linux/iio/ring_generic.h> + +#include <linux/iio/sca3000.h> +#include "sca3000.h" + +enum sca3000_variant { + d01, + d03, + e02, + e04, + e05, + l01, +}; + +/* Note where option modes are not defined, the chip simply does not + * support any. + * Other chips in the sca3000 series use i2c and are not included here. + * + * Some of these devices are only listed in the family data sheet and + * do not actually appear to be available. + */ +static const struct sca3000_chip_info sca3000_spi_chip_info_tbl[] = { + { + .name = "sca3000-d01", + .temp_output = true, + .measurement_mode_freq = 250, + .option_mode_1 = SCA3000_OP_MODE_BYPASS, + .option_mode_1_freq = 250, + }, { + /* No data sheet available - may be the same as the 3100-d03?*/ + .name = "sca3000-d03", + .temp_output = true, + }, { + .name = "sca3000-e02", + .measurement_mode_freq = 125, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 63, + }, { + .name = "sca3000-e04", + .measurement_mode_freq = 100, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + }, { + .name = "sca3000-e05", + .measurement_mode_freq = 200, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + }, { + /* No data sheet available. + * Frequencies are unknown. + */ + .name = "sca3000-l01", + .temp_output = true, + .option_mode_1 = SCA3000_OP_MODE_BYPASS, + }, +}; + + +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val) +{ + struct spi_transfer xfer = { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }; + struct spi_message msg; + + st->tx[0] = SCA3000_WRITE_REG(address); + st->tx[1] = val; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + return spi_sync(st->us, &msg); +} + +int sca3000_read_data(struct sca3000_state *st, + uint8_t reg_address_high, + u8 **rx_p, + int len) +{ + int ret; + struct spi_message msg; + struct spi_transfer xfer = { + .bits_per_word = 8, + .len = len + 1, + .cs_change = 1, + .tx_buf = st->tx, + }; + + *rx_p = kmalloc(len + 1, GFP_KERNEL); + if (*rx_p == NULL) { + ret = -ENOMEM; + goto error_ret; + } + xfer.rx_buf = *rx_p; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(st->us, &msg); + + if (ret) { + dev_err(get_device(&st->us->dev), "problem reading register"); + goto error_free_rx; + } + + return 0; +error_free_rx: + kfree(*rx_p); +error_ret: + return ret; + +} +/** + * sca3000_reg_lock_on() test if the ctrl register lock is on + * + * Lock must be held. + **/ +static int sca3000_reg_lock_on(struct sca3000_state *st) +{ + u8 *rx; + int ret; + + ret = sca3000_read_data(st, SCA3000_REG_ADDR_STATUS, &rx, 1); + + if (ret < 0) + return ret; + ret = !(rx[1] & SCA3000_LOCKED); + kfree(rx); + + return ret; +} + +/** + * __sca3000_unlock_reg_lock() unlock the control registers + * + * Note the device does not appear to support doing this in a single transfer. + * This should only ever be used as part of ctrl reg read. + * Lock must be held before calling this + **/ +static int __sca3000_unlock_reg_lock(struct sca3000_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer[3] = { + { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx + 2, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx + 4, + }, + }; + st->tx[0] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[1] = 0x00; + st->tx[2] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[3] = 0x50; + st->tx[4] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[5] = 0xA0; + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + spi_message_add_tail(&xfer[1], &msg); + spi_message_add_tail(&xfer[2], &msg); + + return spi_sync(st->us, &msg); +} + +/** + * sca3000_write_ctrl_reg() write to a lock protect ctrl register + * @sel: selects which registers we wish to write to + * @val: the value to be written + * + * Certain control registers are protected against overwriting by the lock + * register and use a shared write address. This function allows writing of + * these registers. + * Lock must be held. + **/ +static int sca3000_write_ctrl_reg(struct sca3000_state *st, + uint8_t sel, + uint8_t val) +{ + + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, sel); + if (ret) + goto error_ret; + + /* Write the actual value into the register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_DATA, val); + +error_ret: + return ret; +} + +/* Crucial that lock is called before calling this */ +/** + * sca3000_read_ctrl_reg() read from lock protected control register. + * + * Lock must be held. + **/ +static int sca3000_read_ctrl_reg(struct sca3000_state *st, + u8 ctrl_reg, + u8 **rx_p) +{ + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, ctrl_reg); + if (ret) + goto error_ret; + ret = sca3000_read_data(st, SCA3000_REG_ADDR_CTRL_DATA, rx_p, 1); + +error_ret: + return ret; +} + +#ifdef SCA3000_DEBUG +/** + * sca3000_check_status() check the status register + * + * Only used for debugging purposes + **/ +static int sca3000_check_status(struct device *dev) +{ + u8 *rx; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_STATUS, &rx, 1); + if (ret < 0) + goto error_ret; + if (rx[1] & SCA3000_EEPROM_CS_ERROR) + dev_err(dev, "eeprom error \n"); + if (rx[1] & SCA3000_SPI_FRAME_ERROR) + dev_err(dev, "Previous SPI Frame was corrupt\n"); + kfree(rx); + +error_ret: + mutex_unlock(&st->lock); + return ret; +} +#endif /* SCA3000_DEBUG */ + +/** + * sca3000_read_13bit_signed() sysfs interface to read 13 bit signed registers + * + * These are described as signed 12 bit on the data sheet, which appears + * to be a conventional 2's complement 13 bit. + **/ +static ssize_t sca3000_read_13bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0, ret; + int val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 *rx; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, this_attr->address, &rx, 2); + if (ret < 0) + goto error_ret; + val = sca3000_13bit_convert(rx[1], rx[2]); + len += sprintf(buf + len, "%d\n", val); + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + + +static ssize_t sca3000_show_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct sca3000_state *st = dev_info->dev_data; + return sprintf(buf, "%s\n", st->info->name); +} +/** + * sca3000_show_reg() - sysfs interface to read the chip revision number + **/ +static ssize_t sca3000_show_rev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0, ret; + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct sca3000_state *st = dev_info->dev_data; + + u8 *rx; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_REVID, &rx, 1); + if (ret < 0) + goto error_ret; + len += sprintf(buf + len, + "major=%d, minor=%d\n", + rx[1] & SCA3000_REVID_MAJOR_MASK, + rx[1] & SCA3000_REVID_MINOR_MASK); + kfree(rx); + +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_show_available_measurement_modes() display available modes + * + * This is all read from chip specific data in the driver. Not all + * of the sca3000 series support modes other than normal. + **/ +static ssize_t +sca3000_show_available_measurement_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct sca3000_state *st = dev_info->dev_data; + int len = 0; + + len += sprintf(buf + len, "0 - normal mode"); + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, ", 1 - narrow mode"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, ", 1 - bypass mode"); + break; + }; + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, ", 2 - wide mode"); + break; + } + /* always supported */ + len += sprintf(buf + len, " 3 - motion detection \n"); + + return len; +} + +/** + * sca3000_show_measurmenet_mode() sysfs read of current mode + **/ +static ssize_t +sca3000_show_measurement_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct sca3000_state *st = dev_info->dev_data; + int len = 0, ret; + u8 *rx; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + /* mask bottom 2 bits - only ones that are relevant */ + rx[1] &= 0x03; + switch (rx[1]) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "0 - normal mode\n"); + break; + case SCA3000_MEAS_MODE_MOT_DET: + len += sprintf(buf + len, "3 - motion detection\n"); + break; + case SCA3000_MEAS_MODE_OP_1: + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, "1 - narrow mode\n"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, "1 - bypass mode\n"); + break; + }; + break; + case SCA3000_MEAS_MODE_OP_2: + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, "2 - wide mode\n"); + break; + } + break; + }; + +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_store_measurement_mode() set the current mode + **/ +static ssize_t +sca3000_store_measurement_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct sca3000_state *st = dev_info->dev_data; + int ret; + u8 *rx; + int mask = 0x03; + long val; + + mutex_lock(&st->lock); + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + rx[1] &= ~mask; + rx[1] |= (val & mask); + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, rx[1]); + if (ret) + goto error_free_rx; + mutex_unlock(&st->lock); + + return len; + +error_free_rx: + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + + +/* Not even vaguely standard attributes so defined here rather than + * in the relevant IIO core headers + */ +static IIO_DEVICE_ATTR(available_measurement_modes, S_IRUGO, + sca3000_show_available_measurement_modes, + NULL, 0); + +static IIO_DEVICE_ATTR(measurement_mode, S_IRUGO | S_IWUSR, + sca3000_show_measurement_mode, + sca3000_store_measurement_mode, + 0); + +/* More standard attributes */ + +static IIO_DEV_ATTR_NAME(sca3000_show_name); +static IIO_DEV_ATTR_REV(sca3000_show_rev); + +static IIO_DEV_ATTR_ACCEL_X(sca3000_read_13bit_signed, + SCA3000_REG_ADDR_X_MSB); +static IIO_DEV_ATTR_ACCEL_Y(sca3000_read_13bit_signed, + SCA3000_REG_ADDR_Y_MSB); +static IIO_DEV_ATTR_ACCEL_Z(sca3000_read_13bit_signed, + SCA3000_REG_ADDR_Z_MSB); + + +/** + * sca3000_read_av_freq() sysfs function to get available frequencies + * + * The later modes are only relevant to the ring buffer - and depend on current + * mode. Note that data sheet gives rather wide tolerances for these so integer + * division will give good enough answer and not all chips have them specified + * at all. + **/ +static ssize_t sca3000_read_av_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + int len = 0, ret; + u8 *rx; + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + rx[1] &= 0x03; + switch (rx[1]) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "%d %d %d\n", + st->info->measurement_mode_freq, + st->info->measurement_mode_freq/2, + st->info->measurement_mode_freq/4); + break; + case SCA3000_MEAS_MODE_OP_1: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_1_freq, + st->info->option_mode_1_freq/2, + st->info->option_mode_1_freq/4); + break; + case SCA3000_MEAS_MODE_OP_2: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_2_freq, + st->info->option_mode_2_freq/2, + st->info->option_mode_2_freq/4); + break; + }; + kfree(rx); + return len; +error_ret: + return ret; +} +/** + * __sca3000_get_base_frequency() obtain mode specific base frequency + * + * lock must be held + **/ +static inline int __sca3000_get_base_freq(struct sca3000_state *st, + const struct sca3000_chip_info *info, + int *base_freq) +{ + int ret; + u8 *rx; + + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + switch (0x03 & rx[1]) { + case SCA3000_MEAS_MODE_NORMAL: + *base_freq = info->measurement_mode_freq; + break; + case SCA3000_MEAS_MODE_OP_1: + *base_freq = info->option_mode_1_freq; + break; + case SCA3000_MEAS_MODE_OP_2: + *base_freq = info->option_mode_2_freq; + break; + }; + kfree(rx); +error_ret: + return ret; +} + +/** + * sca3000_read_frequency() sysfs interface to get the current frequency + **/ +static ssize_t sca3000_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + int ret, len = 0, base_freq = 0; + u8 *rx; + mutex_lock(&st->lock); + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_ret_mut; + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, &rx); + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + if (base_freq > 0) + switch (rx[1]&0x03) { + case 0x00: + case 0x03: + len = sprintf(buf, "%d\n", base_freq); + break; + case 0x01: + len = sprintf(buf, "%d\n", base_freq/2); + break; + case 0x02: + len = sprintf(buf, "%d\n", base_freq/4); + break; + }; + kfree(rx); + return len; +error_ret_mut: + mutex_unlock(&st->lock); +error_ret: + return ret; +} + +/** + * sca3000_set_frequency() sysfs interface to set the current frequency + **/ +static ssize_t sca3000_set_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + int ret, base_freq = 0; + u8 *rx; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + /* What mode are we in? */ + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_free_lock; + + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, &rx); + if (ret) + goto error_free_lock; + /* clear the bits */ + rx[1] &= ~0x03; + + if (val == base_freq/2) { + rx[1] |= SCA3000_OUT_CTRL_BUF_DIV_2; + } else if (val == base_freq/4) { + rx[1] |= SCA3000_OUT_CTRL_BUF_DIV_4; + } else if (val != base_freq) { + ret = -EINVAL; + goto error_free_lock; + } + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, rx[1]); +error_free_lock: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/* Should only really be registered if ring buffer support is compiled in. + * Does no harm however and doing it right would add a fair bit of complexity + */ +static IIO_DEV_ATTR_AVAIL_SAMP_FREQ(sca3000_read_av_freq); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + sca3000_read_frequency, + sca3000_set_frequency); + + +/** + * sca3000_read_temp() sysfs interface to get the temperature when available + * +* The alignment of data in here is downright odd. See data sheet. +* Converting this into a meaningful value is left to inline functions in +* userspace part of header. +**/ +static ssize_t sca3000_read_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + int len = 0, ret; + int val; + u8 *rx; + ret = sca3000_read_data(st, SCA3000_REG_ADDR_TEMP_MSB, &rx, 2); + if (ret < 0) + goto error_ret; + val = ((rx[1]&0x3F) << 3) | ((rx[2] & 0xE0) >> 5); + len += sprintf(buf + len, "%d\n", val); + kfree(rx); + + return len; + +error_ret: + return ret; +} +static IIO_DEV_ATTR_TEMP(sca3000_read_temp); + +/** + * sca3000_show_thresh() sysfs query of a theshold + **/ +static ssize_t sca3000_show_thresh(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int len = 0, ret; + u8 *rx; + + mutex_lock(&st->lock); + ret = sca3000_read_ctrl_reg(st, + this_attr->address, + &rx); + mutex_unlock(&st->lock); + if (ret) + return ret; + len += sprintf(buf + len, "%d\n", rx[1]); + kfree(rx); + + return len; +} + +/** + * sca3000_write_thresh() sysfs control of threshold + **/ +static ssize_t sca3000_write_thresh(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + mutex_lock(&st->lock); + ret = sca3000_write_ctrl_reg(st, this_attr->address, val); + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_ACCEL_THRESH_X(S_IRUGO | S_IWUSR, + sca3000_show_thresh, + sca3000_write_thresh, + SCA3000_REG_CTRL_SEL_MD_X_TH); +static IIO_DEV_ATTR_ACCEL_THRESH_Y(S_IRUGO | S_IWUSR, + sca3000_show_thresh, + sca3000_write_thresh, + SCA3000_REG_CTRL_SEL_MD_Y_TH); +static IIO_DEV_ATTR_ACCEL_THRESH_Z(S_IRUGO | S_IWUSR, + sca3000_show_thresh, + sca3000_write_thresh, + SCA3000_REG_CTRL_SEL_MD_Z_TH); + +static struct attribute *sca3000_attributes[] = { + &iio_dev_attr_name.dev_attr.attr, + &iio_dev_attr_revision.dev_attr.attr, + &iio_dev_attr_accel_x.dev_attr.attr, + &iio_dev_attr_accel_y.dev_attr.attr, + &iio_dev_attr_accel_z.dev_attr.attr, + &iio_dev_attr_thresh_accel_x.dev_attr.attr, + &iio_dev_attr_thresh_accel_y.dev_attr.attr, + &iio_dev_attr_thresh_accel_z.dev_attr.attr, + &iio_dev_attr_available_measurement_modes.dev_attr.attr, + &iio_dev_attr_measurement_mode.dev_attr.attr, + &iio_dev_attr_available_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL, +}; + +static struct attribute *sca3000_attributes_with_temp[] = { + &iio_dev_attr_name.dev_attr.attr, + &iio_dev_attr_revision.dev_attr.attr, + &iio_dev_attr_accel_x.dev_attr.attr, + &iio_dev_attr_accel_y.dev_attr.attr, + &iio_dev_attr_accel_z.dev_attr.attr, + &iio_dev_attr_thresh_accel_x.dev_attr.attr, + &iio_dev_attr_thresh_accel_y.dev_attr.attr, + &iio_dev_attr_thresh_accel_z.dev_attr.attr, + &iio_dev_attr_available_measurement_modes.dev_attr.attr, + &iio_dev_attr_measurement_mode.dev_attr.attr, + &iio_dev_attr_available_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + /* Only present if temp sensor is */ + &iio_dev_attr_temp.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sca3000_attribute_group = { + .attrs = sca3000_attributes, +}; + +static const struct attribute_group sca3000_attribute_group_with_temp = { + .attrs = sca3000_attributes_with_temp, +}; + +/* RING RELATED interrupt handler */ +/* depending on event, push to the ring buffer event chrdev or the event one */ + +/** + * sca3000_interrupt_handler_bh() - handling ring and non ring events + * + * This function is complicated by the fact that the devices can signify ring + * and non ring events via the same interrupt line and they can only + * be distinguished via a read of the relevant status register. + **/ +static void sca3000_interrupt_handler_bh(struct work_struct *work_s) +{ + struct sca3000_state *st + = container_of(work_s, struct sca3000_state, + interrupt_handler_ws); + u8 *rx; + int ret; + + /* Could lead if badly timed to an extra read of status reg, + * but ensures no interrupt is missed. + */ + enable_irq(st->us->irq); + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_INT_STATUS, + &rx, 1); + mutex_unlock(&st->lock); + if (ret) + goto done; + + sca3000_ring_int_process(rx[1], st->indio_dev->ring); + + if (rx[1] & SCA3000_INT_STATUS_FREE_FALL) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_FREE_FALL, + st->last_timestamp); + + if (rx[1] & SCA3000_INT_STATUS_Y_TRIGGER) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Y_HIGH, + st->last_timestamp); + + if (rx[1] & SCA3000_INT_STATUS_X_TRIGGER) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_X_HIGH, + st->last_timestamp); + + if (rx[1] & SCA3000_INT_STATUS_Z_TRIGGER) + iio_push_event(st->indio_dev, 0, + IIO_EVENT_CODE_ACCEL_Z_HIGH, + st->last_timestamp); + +done: + kfree(rx); + return; +} + +/** + * sca3000_handler_th() handles all interrupt events from device + * + * These devices deploy unified interrupt status registers meaning + * all interrupts must be handled together + **/ +static int sca3000_handler_th(struct iio_dev *dev_info, + int index, + s64 timestamp, + int no_test) +{ + struct sca3000_state *st = dev_info->dev_data; + + st->last_timestamp = timestamp; + schedule_work(&st->interrupt_handler_ws); + + return 0; +} + +/** + * sca3000_query_mo_det() is motion detection enabled for this axis + * + * First queries if motion detection is enabled and then if this axis is + * on. + **/ +static ssize_t sca3000_query_mo_det(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + int ret, len = 0; + u8 *rx; + u8 protect_mask = 0x03; + + /* read current value of mode register */ + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + + if ((rx[1]&protect_mask) != SCA3000_MEAS_MODE_MOT_DET) + len += sprintf(buf + len, "0\n"); + else { + kfree(rx); + ret = sca3000_read_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + &rx); + if (ret) + goto error_ret; + /* only supporting logical or's for now */ + len += sprintf(buf + len, "%d\n", + (rx[1] & this_attr->mask) ? 1 : 0); + } + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} +/** + * sca3000_query_free_fall_mode() is free fall mode enabled + **/ +static ssize_t sca3000_query_free_fall_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len; + u8 *rx; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + mutex_unlock(&st->lock); + if (ret) + return ret; + len = sprintf(buf, "%d\n", + !!(rx[1] & SCA3000_FREE_FALL_DETECT)); + kfree(rx); + + return len; +} +/** + * sca3000_query_ring_int() is the hardware ring status interrupt enabled + **/ +static ssize_t sca3000_query_ring_int(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + int ret, len; + u8 *rx; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_INT_MASK, &rx, 1); + mutex_unlock(&st->lock); + if (ret) + return ret; + len = sprintf(buf, "%d\n", (rx[1] & this_attr->mask) ? 1 : 0); + kfree(rx); + + return len; +} +/** + * sca3000_set_ring_int() set state of ring status interrupt + **/ +static ssize_t sca3000_set_ring_int(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + + long val; + int ret; + u8 *rx; + + mutex_lock(&st->lock); + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = sca3000_read_data(st, SCA3000_REG_ADDR_INT_MASK, &rx, 1); + if (ret) + goto error_ret; + if (val) + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + rx[1] | this_attr->mask); + else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + rx[1] & ~this_attr->mask); + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_set_free_fall_mode() simple on off control for free fall int + * + * In these chips the free fall detector should send an interrupt if + * the device falls more than 25cm. This has not been tested due + * to fragile wiring. + **/ + +static ssize_t sca3000_set_free_fall_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + long val; + int ret; + u8 *rx; + u8 protect_mask = SCA3000_FREE_FALL_DETECT; + + mutex_lock(&st->lock); + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + + /* read current value of mode register */ + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + + /*if off and should be on*/ + if (val && !(rx[1] & protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (rx[1] | SCA3000_FREE_FALL_DETECT)); + /* if on and should be off */ + else if (!val && (rx[1]&protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (rx[1] & ~protect_mask)); + + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_set_mo_det() simple on off control for motion detector + * + * This is a per axis control, but enabling any will result in the + * motion detector unit being enabled. + * N.B. enabling motion detector stops normal data acquisition. + * There is a complexity in knowing which mode to return to when + * this mode is disabled. Currently normal mode is assumed. + **/ +static ssize_t sca3000_set_mo_det(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = indio_dev->dev_data; + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + long val; + int ret; + u8 *rx; + u8 protect_mask = 0x03; + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + /* First read the motion detector config to find out if + * this axis is on*/ + ret = sca3000_read_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + &rx); + if (ret) + goto exit_point; + /* Off and should be on */ + if (val && !(rx[1] & this_attr->mask)) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + rx[1] | this_attr->mask); + if (ret) + goto exit_point_free_rx; + st->mo_det_use_count++; + } else if (!val && (rx[1]&this_attr->mask)) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + rx[1] & ~(this_attr->mask)); + if (ret) + goto exit_point_free_rx; + st->mo_det_use_count--; + } else /* relies on clean state for device on boot */ + goto exit_point_free_rx; + kfree(rx); + /* read current value of mode register */ + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto exit_point; + /*if off and should be on*/ + if ((st->mo_det_use_count) + && ((rx[1]&protect_mask) != SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (rx[1] & ~protect_mask) + | SCA3000_MEAS_MODE_MOT_DET); + /* if on and should be off */ + else if (!(st->mo_det_use_count) + && ((rx[1]&protect_mask) == SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (rx[1] & ~protect_mask)); +exit_point_free_rx: + kfree(rx); +exit_point: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/* Shared event handler for all events as single event status register */ +IIO_EVENT_SH(all, &sca3000_handler_th); + +/* Free fall detector related event attribute */ +IIO_EVENT_ATTR_FREE_FALL_DETECT_SH(iio_event_all, + sca3000_query_free_fall_mode, + sca3000_set_free_fall_mode, + 0) + +/* Motion detector related event attributes */ +IIO_EVENT_ATTR_ACCEL_X_HIGH_SH(iio_event_all, + sca3000_query_mo_det, + sca3000_set_mo_det, + SCA3000_MD_CTRL_OR_X); + +IIO_EVENT_ATTR_ACCEL_Y_HIGH_SH(iio_event_all, + sca3000_query_mo_det, + sca3000_set_mo_det, + SCA3000_MD_CTRL_OR_Y); + +IIO_EVENT_ATTR_ACCEL_Z_HIGH_SH(iio_event_all, + sca3000_query_mo_det, + sca3000_set_mo_det, + SCA3000_MD_CTRL_OR_Z); + +/* Hardware ring buffer related event attributes */ +IIO_EVENT_ATTR_RING_50_FULL_SH(iio_event_all, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_HALF); + +IIO_EVENT_ATTR_RING_75_FULL_SH(iio_event_all, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_THREE_QUARTER); + +static struct attribute *sca3000_event_attributes[] = { + &iio_event_attr_free_fall.dev_attr.attr, + &iio_event_attr_accel_x_high.dev_attr.attr, + &iio_event_attr_accel_y_high.dev_attr.attr, + &iio_event_attr_accel_z_high.dev_attr.attr, + &iio_event_attr_ring_50_full.dev_attr.attr, + &iio_event_attr_ring_75_full.dev_attr.attr, + NULL, +}; + +static struct attribute_group sca3000_event_attribute_group = { + .attrs = sca3000_event_attributes, +}; + +/** + * sca3000_clean_setup() get the device into a predictable state + * + * Devices use flash memory to store many of the register values + * and hence can come up in somewhat unpredictable states. + * Hence reset everything on driver load. + **/ +static int sca3000_clean_setup(struct sca3000_state *st) +{ + int ret; + u8 *rx; + + mutex_lock(&st->lock); + /* Ensure all interrupts have been acknowledged */ + ret = sca3000_read_data(st, SCA3000_REG_ADDR_INT_STATUS, &rx, 1); + if (ret) + goto error_ret; + kfree(rx); + + /* Turn off all motion detection channels */ + ret = sca3000_read_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + &rx); + if (ret) + goto error_ret; + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + rx[1] & SCA3000_MD_CTRL_PROT_MASK); + kfree(rx); + if (ret) + goto error_ret; + + /* Disable ring buffer */ + sca3000_read_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_OUT_CTRL, + &rx); + /* Frequency of ring buffer sampling deliberately restricted to make + * debugging easier - add control of this later */ + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_OUT_CTRL, + (rx[1] & SCA3000_OUT_CTRL_PROT_MASK) + | SCA3000_OUT_CTRL_BUF_X_EN + | SCA3000_OUT_CTRL_BUF_Y_EN + | SCA3000_OUT_CTRL_BUF_Z_EN + | SCA3000_OUT_CTRL_BUF_DIV_4); + kfree(rx); + + if (ret) + goto error_ret; + /* Enable interrupts, relevant to mode and set up as active low */ + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_INT_MASK, + &rx, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + (rx[1] & SCA3000_INT_MASK_PROT_MASK) + | SCA3000_INT_MASK_ACTIVE_LOW); + kfree(rx); + if (ret) + goto error_ret; + /* Select normal measurement mode, free fall off, ring off */ + /* Ring in 12 bit mode - it is fine to overwrite reserved bits 3,5 + * as that occurs in one of the example on the datasheet */ + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_MODE, + &rx, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (rx[1] & SCA3000_MODE_PROT_MASK)); + kfree(rx); + st->bpse = 11; + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static int __devinit __sca3000_probe(struct spi_device *spi, + enum sca3000_variant variant) +{ + int ret; + struct sca3000_state *st; + + st = kzalloc(sizeof(struct sca3000_state), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, st); + + st->tx = kmalloc(sizeof(*st->tx)*6, GFP_KERNEL); + if (st->tx == NULL) { + ret = -ENOMEM; + goto error_clear_st; + } + st->rx = kmalloc(sizeof(*st->rx)*3, GFP_KERNEL); + if (st->rx == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + st->us = spi; + mutex_init(&st->lock); + st->info = &sca3000_spi_chip_info_tbl[variant]; + + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_rx; + } + + st->indio_dev->dev.parent = &spi->dev; + st->indio_dev->num_interrupt_lines = 1; + st->indio_dev->event_attrs = &sca3000_event_attribute_group; + if (st->info->temp_output) + st->indio_dev->attrs = &sca3000_attribute_group_with_temp; + else + st->indio_dev->attrs = &sca3000_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->modes = INDIO_DIRECT_MODE; + + sca3000_configure_ring(st->indio_dev); + + ret = iio_device_register(st->indio_dev); + if (ret < 0) + goto error_free_dev; + ret = iio_ring_buffer_register(st->indio_dev->ring); + if (ret < 0) + goto error_unregister_dev; + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) { + INIT_WORK(&st->interrupt_handler_ws, + sca3000_interrupt_handler_bh); + ret = iio_register_interrupt_line(spi->irq, + st->indio_dev, + 0, + IRQF_TRIGGER_FALLING, + "sca3000"); + if (ret) + goto error_unregister_ring; + /* RFC + * Probably a common situation. All interrupts need an ack + * and there is only one handler so the complicated list system + * is overkill. At very least a simpler registration method + * might be worthwhile. + */ + iio_add_event_to_list(iio_event_attr_accel_z_high.listel, + &st->indio_dev + ->interrupts[0]->ev_list); + } + sca3000_register_ring_funcs(st->indio_dev); + ret = sca3000_clean_setup(st); + if (ret) + goto error_unregister_interrupt_line; + return 0; + +error_unregister_interrupt_line: + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) + iio_unregister_interrupt_line(st->indio_dev, 0); +error_unregister_ring: + iio_ring_buffer_unregister(st->indio_dev->ring); +error_unregister_dev: + iio_device_unregister(st->indio_dev); +error_free_dev: + iio_free_device(st->indio_dev); +error_free_rx: + kfree(st->rx); +error_free_tx: + kfree(st->tx); +error_clear_st: + kfree(st); +error_ret: + return ret; +} + +static int sca3000_stop_all_interrupts(struct sca3000_state *st) +{ + int ret; + u8 *rx; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_INT_MASK, &rx, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_INT_MASK, + (rx[1] & ~(SCA3000_INT_MASK_RING_THREE_QUARTER + | SCA3000_INT_MASK_RING_HALF + | SCA3000_INT_MASK_ALL_INTS))); +error_ret: + kfree(rx); + return ret; + +} + +static int sca3000_remove(struct spi_device *spi) +{ + struct sca3000_state *st = spi_get_drvdata(spi); + struct iio_dev *indio_dev = st->indio_dev; + int ret; + /* Must ensure no interrupts can be generated after this!*/ + ret = sca3000_stop_all_interrupts(st); + if (ret) + return ret; + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) + iio_unregister_interrupt_line(indio_dev, 0); + iio_ring_buffer_unregister(indio_dev->ring); + iio_device_unregister(indio_dev); + sca3000_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + kfree(st->tx); + kfree(st->rx); + kfree(st); + + return 0; +} + +/* These macros save on an awful lot of repeated code */ +#define SCA3000_VARIANT_PROBE(_name) \ + static int __devinit \ + sca3000_##_name##_probe(struct spi_device *spi) \ + { \ + return __sca3000_probe(spi, _name); \ + } + +#define SCA3000_VARIANT_SPI_DRIVER(_name) \ + struct spi_driver sca3000_##_name##_driver = { \ + .driver = { \ + .name = "sca3000_" #_name, \ + .owner = THIS_MODULE, \ + }, \ + .probe = sca3000_##_name##_probe, \ + .remove = __devexit_p(sca3000_remove), \ + } + +SCA3000_VARIANT_PROBE(d01); +static SCA3000_VARIANT_SPI_DRIVER(d01); + +SCA3000_VARIANT_PROBE(d03); +static SCA3000_VARIANT_SPI_DRIVER(d03); + +SCA3000_VARIANT_PROBE(e02); +static SCA3000_VARIANT_SPI_DRIVER(e02); + +SCA3000_VARIANT_PROBE(e04); +static SCA3000_VARIANT_SPI_DRIVER(e04); + +SCA3000_VARIANT_PROBE(e05); +static SCA3000_VARIANT_SPI_DRIVER(e05); + +SCA3000_VARIANT_PROBE(l01); +static SCA3000_VARIANT_SPI_DRIVER(l01); + +static __init int sca3000_init(void) +{ + int ret; + + ret = spi_register_driver(&sca3000_d01_driver); + if (ret) + goto error_ret; + ret = spi_register_driver(&sca3000_d03_driver); + if (ret) + goto error_unreg_d01; + ret = spi_register_driver(&sca3000_e02_driver); + if (ret) + goto error_unreg_d03; + ret = spi_register_driver(&sca3000_e04_driver); + if (ret) + goto error_unreg_e02; + ret = spi_register_driver(&sca3000_e05_driver); + if (ret) + goto error_unreg_e04; + ret = spi_register_driver(&sca3000_l01_driver); + if (ret) + goto error_unreg_e05; + + return 0; + +error_unreg_e05: + spi_unregister_driver(&sca3000_e05_driver); +error_unreg_e04: + spi_unregister_driver(&sca3000_e04_driver); +error_unreg_e02: + spi_unregister_driver(&sca3000_e02_driver); +error_unreg_d03: + spi_unregister_driver(&sca3000_d03_driver); +error_unreg_d01: + spi_unregister_driver(&sca3000_d01_driver); +error_ret: + + return ret; +} + +static __exit void sca3000_exit(void) +{ + spi_unregister_driver(&sca3000_l01_driver); + spi_unregister_driver(&sca3000_e05_driver); + spi_unregister_driver(&sca3000_e04_driver); + spi_unregister_driver(&sca3000_e02_driver); + spi_unregister_driver(&sca3000_d03_driver); + spi_unregister_driver(&sca3000_d01_driver); +} + +module_init(sca3000_init); +module_exit(sca3000_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("VTI SCA3000 Series Accelerometers SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/sca3000_ring.c b/drivers/iio/accel/sca3000_ring.c new file mode 100644 index 0000000..b8ceaf3 --- /dev/null +++ b/drivers/iio/accel/sca3000_ring.c @@ -0,0 +1,334 @@ +/* + * sca3000_ring.c -- support VTI sca3000 series accelerometers via SPI + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron <jic23@cam.ac.uk> + * + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/rtc.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/accel.h> +/* Shared kernel / userspace conversion functions */ +#include <linux/iio/sca3000.h> +#include <linux/iio/ring_generic.h> +#include <linux/iio/ring_hw.h> +/* Local stuff such as register locations */ +#include "sca3000.h" + +/* RFC / future work + * + * The internal ring buffer doesn't actually change what it holds depending + * on which signals are enabled etc, merely whether you can read them. + * As such the scan mode selection is somewhat different than for a software + * ring buffer and changing it actually covers any data already in the buffer. + * Currently scan elements aren't configured so it doesn't matter. + */ + +/** + * sca3000_rip_hw_rb() - main ring access function, pulls data from ring + * @r: the ring + * @count: number of samples to try and pull + * @data: output the actual samples pulled from the hw ring + * @dead_offset: cheating a bit here: Set to 1 so as to allow for the + * leading byte used in bus comms. + * + * Currently does not provide timestamps. As the hardware doesn't add them they + * can only be inferred aproximately from ring buffer events such as 50% full + * and knowledge of when buffer was last emptied. This is left to userspace. + **/ +static int sca3000_rip_hw_rb(struct iio_ring_buffer *r, + size_t count, u8 **data, int *dead_offset) +{ + struct iio_hw_ring_buffer *hw_ring = iio_to_hw_ring_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct sca3000_state *st = indio_dev->dev_data; + u8 *rx; + int ret, num_available, num_read = 0; + int bytes_per_sample = 1; + + if (st->bpse == 11) + bytes_per_sample = 2; + + mutex_lock(&st->lock); + /* Check how much data is available: + * RFC: Implement an ioctl to not bother checking whether there + * is enough data in the ring? Afterall, if we are responding + * to an interrupt we have a minimum content guaranteed so it + * seems slight silly to waste time checking it is there. + */ + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_BUF_COUNT, + &rx, 1); + if (ret) + goto error_ret; + else + num_available = rx[1]; + /* num_available is the total number of samples available + * i.e. number of time points * number of channels. + */ + kfree(rx); + if (count > num_available * bytes_per_sample) + num_read = num_available*bytes_per_sample; + else + num_read = count - (count % (bytes_per_sample)); + + /* Avoid the read request byte */ + *dead_offset = 1; + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_RING_OUT, + data, num_read); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : num_read; +} + +/* This is only valid with all 3 elements enabled */ +static int sca3000_ring_get_length(struct iio_ring_buffer *r) +{ + return 64; +} + +/* only valid if resolution is kept at 11bits */ +static int sca3000_ring_get_bpd(struct iio_ring_buffer *r) +{ + return 6; +} +static void sca3000_ring_release(struct device *dev) +{ + struct iio_ring_buffer *r = to_iio_ring_buffer(dev); + kfree(iio_to_hw_ring_buf(r)); +} + +static IIO_RING_ENABLE_ATTR; +static IIO_RING_BPS_ATTR; +static IIO_RING_LENGTH_ATTR; + +/** + * sca3000_show_ring_bpse() -sysfs function to query bits per sample from ring + * @dev: ring buffer device + * @attr: this device attribute + * @buf: buffer to write to + **/ +static ssize_t sca3000_show_ring_bpse(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0, ret; + u8 *rx; + struct iio_ring_buffer *r = dev_get_drvdata(dev); + struct sca3000_state *st = r->indio_dev->dev_data; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + len = sprintf(buf, "%d\n", (rx[1] & SCA3000_RING_BUF_8BIT) ? 8 : 11); + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_store_ring_bpse() - bits per scan element + * @dev: ring buffer device + * @attr: attribute called from + * @buf: input from userspace + * @len: length of input + **/ +static ssize_t sca3000_store_ring_bpse(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_ring_buffer *r = dev_get_drvdata(dev); + struct sca3000_state *st = r->indio_dev->dev_data; + int ret; + u8 *rx; + long val; + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (!ret) + switch (val) { + case 8: + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + rx[1] | SCA3000_RING_BUF_8BIT); + st->bpse = 8; + break; + case 11: + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + rx[1] & ~SCA3000_RING_BUF_8BIT); + st->bpse = 11; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_CONST_ATTR(bpse_available, "8 11"); + +static IIO_DEV_ATTR_BPSE(S_IRUGO | S_IWUSR, + sca3000_show_ring_bpse, + sca3000_store_ring_bpse); + +/* + * Ring buffer attributes + * This device is a bit unusual in that the sampling frequency and bpse + * only apply to the ring buffer. At all times full rate and accuracy + * is available via direct reading from registers. + */ +static struct attribute *iio_ring_attributes[] = { + &dev_attr_length.attr, + &dev_attr_bps.attr, + &dev_attr_ring_enable.attr, + &iio_dev_attr_bpse.dev_attr.attr, + &iio_const_attr_bpse_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group sca3000_ring_attr = { + .attrs = iio_ring_attributes, +}; + +static struct attribute_group *sca3000_ring_attr_groups[] = { + &sca3000_ring_attr, + NULL +}; + +static struct device_type sca3000_ring_type = { + .release = sca3000_ring_release, + .groups = sca3000_ring_attr_groups, +}; + +static struct iio_ring_buffer *sca3000_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_ring_buffer *buf; + struct iio_hw_ring_buffer *ring; + + ring = kzalloc(sizeof *ring, GFP_KERNEL); + if (!ring) + return 0; + ring->private = indio_dev; + buf = &ring->buf; + iio_ring_buffer_init(buf, indio_dev); + buf->dev.type = &sca3000_ring_type; + device_initialize(&buf->dev); + buf->dev.parent = &indio_dev->dev; + dev_set_drvdata(&buf->dev, (void *)buf); + + return buf; +} + +static inline void sca3000_rb_free(struct iio_ring_buffer *r) +{ + if (r) + iio_put_ring_buffer(r); +} + +int sca3000_configure_ring(struct iio_dev *indio_dev) +{ + indio_dev->ring = sca3000_rb_allocate(indio_dev); + if (indio_dev->ring == NULL) + return -ENOMEM; + indio_dev->modes |= INDIO_RING_HARDWARE_BUFFER; + + indio_dev->ring->access.rip_lots = &sca3000_rip_hw_rb; + indio_dev->ring->access.get_length = &sca3000_ring_get_length; + indio_dev->ring->access.get_bpd = &sca3000_ring_get_bpd; + + return 0; +} + +void sca3000_unconfigure_ring(struct iio_dev *indio_dev) +{ + sca3000_rb_free(indio_dev->ring); +} + +static inline +int __sca3000_hw_ring_state_set(struct iio_dev *indio_dev, bool state) +{ + struct sca3000_state *st = indio_dev->dev_data; + int ret; + u8 *rx; + + mutex_lock(&st->lock); + ret = sca3000_read_data(st, SCA3000_REG_ADDR_MODE, &rx, 1); + if (ret) + goto error_ret; + if (state) { + printk(KERN_INFO "supposedly enabling ring buffer\n"); + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (rx[1] | SCA3000_RING_BUF_ENABLE)); + } else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (rx[1] & ~SCA3000_RING_BUF_ENABLE)); + kfree(rx); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} +/** + * sca3000_hw_ring_preenable() hw ring buffer preenable function + * + * Very simple enable function as the chip will allows normal reads + * during ring buffer operation so as long as it is indeed running + * before we notify the core, the precise ordering does not matter. + **/ +static int sca3000_hw_ring_preenable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 1); +} + +static int sca3000_hw_ring_postdisable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 0); +} + +void sca3000_register_ring_funcs(struct iio_dev *indio_dev) +{ + indio_dev->ring->preenable = &sca3000_hw_ring_preenable; + indio_dev->ring->postdisable = &sca3000_hw_ring_postdisable; +} + +/** + * sca3000_ring_int_process() ring specific interrupt handling. + * + * This is only split from the main interrupt handler so as to + * reduce the amount of code if the ring buffer is not enabled. + **/ +void sca3000_ring_int_process(u8 val, struct iio_ring_buffer *ring) +{ + if (val & SCA3000_INT_STATUS_THREE_QUARTERS) + iio_push_or_escallate_ring_event(ring, + IIO_EVENT_CODE_RING_75_FULL, + 0); + else if (val & SCA3000_INT_STATUS_HALF) + iio_push_ring_event(ring, + IIO_EVENT_CODE_RING_50_FULL, 0); +} diff --git a/include/linux/iio/ring_hw.h b/include/linux/iio/ring_hw.h new file mode 100644 index 0000000..bb8cfd2 --- /dev/null +++ b/include/linux/iio/ring_hw.h @@ -0,0 +1,22 @@ +/* + * ring_hw.h - common functionality for iio hardware ring buffers + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron <jic23@cam.ac.uk> + * + */ + +/** + * struct iio_hw_ring_buffer- hardware ring buffer + * @buf: generic ring buffer elements + * @private: device specific data + */ +struct iio_hw_ring_buffer { + struct iio_ring_buffer buf; + void *private; +}; + +#define iio_to_hw_ring_buf(r) container_of(r, struct iio_hw_ring_buffer, buf) diff --git a/include/linux/iio/sca3000.h b/include/linux/iio/sca3000.h new file mode 100644 index 0000000..a056ddb --- /dev/null +++ b/include/linux/iio/sca3000.h @@ -0,0 +1,37 @@ +/* + * sca3000.h -- conversion functions related to VTI sca3000 + * series accelerometers + * + * 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. + * + * Copyright (c) 2007 Jonathan Cameron <jic23@cam.ac.uk> + * + */ + +#ifndef _SCA3000_H_USER_ +#define _SCA3000_H_USER_ + +/* Conversion function for use with the ring buffer when in 11bit mode */ +static inline int sca3000_11bit_convert(uint8_t msb, uint8_t lsb) +{ + int16_t val; + + val = ((lsb >> 3) & 0x1C) | (msb << 5); + val |= (val & (1 << 12)) ? 0xE000 : 0; + + return val; +}; + +static inline int sca3000_13bit_convert(uint8_t msb, uint8_t lsb) +{ + s16 val; + + val = ((lsb >> 3) & 0x1F) | (msb << 5); + /* sign fill */ + val |= (val & (1 << 12)) ? 0xE000 : 0; + + return val; +} +#endif /* _SCA3000_H_USER_ */ -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 08/14] IIO: Trigger support added to core. 2009-07-02 13:47 ` [PATCH 07/14] IIO: VTI sca3000 series accelerometer driver (spi) Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 09/14] IIO: Ring buffer: Initial pass at rarely locked ring buffer Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Add general registration support for IIO triggers. These are currently only used to initialize a 'poll' of a given device. Examples include the lis3l02dq's data ready signal being used to initialize a read and gpio triggers being used to allow externally synchronized sensor reading. Each trigger can cause any number of 'consumer' devices to be polled with each storing data into a related ring buffer. Two stage triggering is supported with 'fast' and 'slow' paths. The first is used for things like pulling a data hold line high and the second for actual read which may take far longer. Changes since V2: * As with IIO triggers now use a registration approach much closer to that of input leading to cleaner code. --- drivers/iio/Kconfig | 9 + drivers/iio/Makefile | 1 + drivers/iio/industrialio-trigger.c | 400 ++++++++++++++++++++++++++++++++++ include/linux/iio/trigger.h | 148 +++++++++++++ include/linux/iio/trigger_consumer.h | 21 ++- 5 files changed, 578 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/industrialio-trigger.c create mode 100644 include/linux/iio/trigger.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 8eb3d4c..3aa2886 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -17,6 +17,15 @@ config IIO_RING_BUFFER Provide core support for various ring buffer based data acquisition methods. +config IIO_TRIGGER + boolean "Enable triggered sampling support" + depends on IIO + help + Provides IIO core support for triggers. Currently these + are used to initialize capture of samples to push into + ring buffers. The triggers are effectively a 'capture + data now' interrupt. + source drivers/iio/accel/Kconfig source drivers/iio/adc/Kconfig source drivers/iio/light/Kconfig diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 670b8f3..3271518 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o industrialio-$(CONFIG_IIO_RING_BUFFER) += industrialio-ring.o +industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o obj-y += accel/ obj-y += adc/ diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c new file mode 100644 index 0000000..88fcc8a --- /dev/null +++ b/drivers/iio/industrialio-trigger.c @@ -0,0 +1,400 @@ +/* The industrial I/O core, trigger handling functions + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/idr.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/list.h> + +#include <linux/platform_device.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +/* RFC - Question of approach + * Make the common case (single sensor single trigger) + * simple by starting trigger capture from when first sensors + * is added. + * + * Complex simultaneous start requires use of 'hold' functionality + * of the trigger. (not implemented) + * + * Any other suggestions? + */ + + +static DEFINE_IDR(iio_trigger_idr); +static DEFINE_SPINLOCK(iio_trigger_idr_lock); + +/* Single list of all available triggers */ +static LIST_HEAD(iio_trigger_list); +static DEFINE_MUTEX(iio_trigger_list_lock); + +/** + * iio_trigger_register_sysfs() - create a device for this trigger + * @trig_info: the trigger + * + * Also adds any control attribute registered by the trigger driver + **/ +static int iio_trigger_register_sysfs(struct iio_trigger *trig_info) +{ + int ret = 0; + + if (trig_info->control_attrs) + ret = sysfs_create_group(&trig_info->dev.kobj, + trig_info->control_attrs); + + return ret; +} + +static void iio_trigger_unregister_sysfs(struct iio_trigger *trig_info) +{ + if (trig_info->control_attrs) + sysfs_remove_group(&trig_info->dev.kobj, + trig_info->control_attrs); +} + + +/** + * iio_trigger_register_id() - get a unique id for this trigger + * @trig_info: the trigger + **/ +static int iio_trigger_register_id(struct iio_trigger *trig_info) +{ + int ret = 0; + +idr_again: + if (unlikely(idr_pre_get(&iio_trigger_idr, GFP_KERNEL) == 0)) + return -ENOMEM; + + spin_lock(&iio_trigger_idr_lock); + ret = idr_get_new(&iio_trigger_idr, NULL, &trig_info->id); + spin_unlock(&iio_trigger_idr_lock); + if (unlikely(ret == -EAGAIN)) + goto idr_again; + else if (likely(!ret)) + trig_info->id = trig_info->id & MAX_ID_MASK; + + return ret; +} + +/** + * iio_trigger_unregister_id() - free up unique id for use by another trigger + * @trig_info: the trigger + **/ +static void iio_trigger_unregister_id(struct iio_trigger *trig_info) +{ + spin_lock(&iio_trigger_idr_lock); + idr_remove(&iio_trigger_idr, trig_info->id); + spin_unlock(&iio_trigger_idr_lock); +} + +int iio_trigger_register(struct iio_trigger *trig_info) +{ + int ret; + + ret = iio_trigger_register_id(trig_info); + if (ret) + goto error_ret; + /* Set the name used for the sysfs directory etc */ + dev_set_name(&trig_info->dev, "trigger%ld", + (unsigned long) trig_info->id); + + ret = device_add(&trig_info->dev); + if (ret) + goto error_unregister_id; + + ret = iio_trigger_register_sysfs(trig_info); + if (ret) + goto error_device_del; + + /* Add to list of available triggers held by the IIO core */ + mutex_lock(&iio_trigger_list_lock); + list_add_tail(&trig_info->list, &iio_trigger_list); + mutex_unlock(&iio_trigger_list_lock); + + return 0; + +error_device_del: + device_del(&trig_info->dev); +error_unregister_id: + iio_trigger_unregister_id(trig_info); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_trigger_register); + +void iio_trigger_unregister(struct iio_trigger *trig_info) +{ + struct iio_trigger *cursor; + + mutex_lock(&iio_trigger_list_lock); + list_for_each_entry(cursor, &iio_trigger_list, list) + if (cursor == trig_info) { + list_del(&cursor->list); + break; + } + mutex_unlock(&iio_trigger_list_lock); + + iio_trigger_unregister_sysfs(trig_info); + iio_trigger_unregister_id(trig_info); + /* Possible issue in here */ + device_unregister(&trig_info->dev); +} +EXPORT_SYMBOL(iio_trigger_unregister); + +struct iio_trigger *iio_trigger_find_by_name(const char *name, size_t len) +{ + struct iio_trigger *trig; + bool found = false; + + mutex_lock(&iio_trigger_list_lock); + list_for_each_entry(trig, &iio_trigger_list, list) { + if (strncmp(trig->name, name, len) == 0) { + found = true; + break; + } + } + mutex_unlock(&iio_trigger_list_lock); + + return found ? trig : NULL; +}; +EXPORT_SYMBOL(iio_trigger_find_by_name); + +void iio_trigger_poll(struct iio_trigger *trig) +{ + struct iio_poll_func *pf_cursor; + + list_for_each_entry(pf_cursor, &trig->pollfunc_list, list) { + if (pf_cursor->poll_func_immediate) { + pf_cursor->poll_func_immediate(pf_cursor->private_data); + trig->use_count++; + } + } + list_for_each_entry(pf_cursor, &trig->pollfunc_list, list) { + if (pf_cursor->poll_func_main) { + pf_cursor->poll_func_main(pf_cursor->private_data); + trig->use_count++; + } + } +} +EXPORT_SYMBOL(iio_trigger_poll); + +void iio_trigger_notify_done(struct iio_trigger *trig) +{ + trig->use_count--; + if (trig->use_count == 0 && trig->try_reenable) + if (trig->try_reenable(trig)) { + /* Missed and interrupt so launch new poll now */ + trig->timestamp = 0; + iio_trigger_poll(trig); + } +} +EXPORT_SYMBOL(iio_trigger_notify_done); + +/** + * iio_trigger_read_name() - retrieve useful identifying name + **/ +ssize_t iio_trigger_read_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", trig->name); +} +EXPORT_SYMBOL(iio_trigger_read_name); + +/* Trigger Consumer related functions */ + +/* Complexity in here. With certain triggers (datardy) an acknowledgement + * may be needed if the pollfuncs do not include the data read for the + * triggering device. + * This is not currently handled. Alternative of not enabling trigger unless + * the relevant function is in there may be the best option. + */ +/* Worth protecting against double additions?*/ +int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&trig->pollfunc_list_lock, flags); + list_add_tail(&pf->list, &trig->pollfunc_list); + spin_unlock_irqrestore(&trig->pollfunc_list_lock, flags); + + if (trig->set_trigger_state) + ret = trig->set_trigger_state(trig, true); + if (ret) { + printk(KERN_ERR "set trigger state failed\n"); + list_del(&pf->list); + } + return ret; +} +EXPORT_SYMBOL(iio_trigger_attach_poll_func); + +int iio_trigger_dettach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + struct iio_poll_func *pf_cursor; + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&trig->pollfunc_list_lock, flags); + list_for_each_entry(pf_cursor, &trig->pollfunc_list, list) + if (pf_cursor == pf) { + ret = 0; + break; + } + if (!ret) { + if (list_is_singular(&trig->pollfunc_list) + && trig->set_trigger_state) { + spin_unlock_irqrestore(&trig->pollfunc_list_lock, + flags); + /* May sleep hence cannot hold the spin lock */ + ret = trig->set_trigger_state(trig, false); + if (ret) + goto error_ret; + spin_lock_irqsave(&trig->pollfunc_list_lock, flags); + } + /* + * Now we can delete safe in the knowledge that, if this is + * the last pollfunc then we have disabled the trigger anyway + * and so nothing should be able to call the pollfunc. + */ + list_del(&pf_cursor->list); + } + spin_unlock_irqrestore(&trig->pollfunc_list_lock, flags); + +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_trigger_dettach_poll_func); + +/** + * iio_trigger_read_currrent() trigger consumer sysfs query which trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used by the device to be queried. + **/ +static ssize_t iio_trigger_read_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + int len = 0; + if (dev_info->trig) + len = snprintf(buf, + IIO_TRIGGER_NAME_LENGTH, + "%s\n", + dev_info->trig->name); + return len; +} + +/** + * iio_trigger_write_current() trigger consumer sysfs set current trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used for this device to be specified at run time based on the triggers + * name. + **/ +static ssize_t iio_trigger_write_current(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct iio_trigger *oldtrig = dev_info->trig; + mutex_lock(&dev_info->mlock); + if (dev_info->currentmode == INDIO_RING_TRIGGERED) { + mutex_unlock(&dev_info->mlock); + return -EBUSY; + } + mutex_unlock(&dev_info->mlock); + + len = len < IIO_TRIGGER_NAME_LENGTH ? len : IIO_TRIGGER_NAME_LENGTH; + + dev_info->trig = iio_trigger_find_by_name(buf, len); + if (oldtrig && dev_info->trig != oldtrig) + iio_put_trigger(oldtrig); + if (dev_info->trig) + iio_get_trigger(dev_info->trig); + + return len; +} + +DEVICE_ATTR(current_trigger, S_IRUGO | S_IWUSR, + iio_trigger_read_current, + iio_trigger_write_current); + +static struct attribute *iio_trigger_consumer_attrs[] = { + &dev_attr_current_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_trigger_consumer_attr_group = { + .name = "trigger", + .attrs = iio_trigger_consumer_attrs, +}; + +static void iio_trig_release(struct device *device) +{ + struct iio_trigger *trig = to_iio_trigger(device); + kfree(trig); + iio_put(); +} + +static struct device_type iio_trig_type = { + .release = iio_trig_release, +}; + +struct iio_trigger *iio_allocate_trigger(void) +{ + struct iio_trigger *trig; + trig = kzalloc(sizeof *trig, GFP_KERNEL); + if (trig) { + trig->dev.type = &iio_trig_type; + trig->dev.class = &iio_class; + device_initialize(&trig->dev); + dev_set_drvdata(&trig->dev, (void *)trig); + spin_lock_init(&trig->pollfunc_list_lock); + INIT_LIST_HEAD(&trig->list); + INIT_LIST_HEAD(&trig->pollfunc_list); + iio_get(); + } + return trig; +} +EXPORT_SYMBOL(iio_allocate_trigger); + +void iio_free_trigger(struct iio_trigger *trig) +{ + if (trig) + iio_put_trigger(trig); +} +EXPORT_SYMBOL(iio_free_trigger); + +int iio_device_register_trigger_consumer(struct iio_dev *dev_info) +{ + int ret; + ret = sysfs_create_group(&dev_info->dev.kobj, + &iio_trigger_consumer_attr_group); + return ret; +} +EXPORT_SYMBOL(iio_device_register_trigger_consumer); + +int iio_device_unregister_trigger_consumer(struct iio_dev *dev_info) +{ + sysfs_remove_group(&dev_info->dev.kobj, + &iio_trigger_consumer_attr_group); + return 0; +} +EXPORT_SYMBOL(iio_device_unregister_trigger_consumer); + diff --git a/include/linux/iio/trigger.h b/include/linux/iio/trigger.h new file mode 100644 index 0000000..d53908c --- /dev/null +++ b/include/linux/iio/trigger.h @@ -0,0 +1,148 @@ +/* The industrial I/O core, trigger handling functions + * + * 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. + */ +#ifndef _IIO_TRIGGER_H_ +#define _IIO_TRIGGER_H_ +#define IIO_TRIGGER_NAME_LENGTH 20 +#define IIO_TRIGGER_ID_PREFIX "iio:trigger" +#define IIO_TRIGGER_ID_FORMAT IIO_TRIGGER_ID_PREFIX "%d" + + +/** + * struct iio_trigger - industrial I/O trigger device + * + * @id: [INTERN] unique id number + * @name: [DRIVER] unique name + * @dev: [DRIVER] associated device (if relevant) + * @sysfs_dev: [INTERN] sysfs relevant device + * @private_data: [DRIVER] device specific data + * @list: [INTERN] used in maintenance of global trigger list + * @alloc_list: [DRIVER] used for driver specific trigger list + * @poll_func_list_lock:[INTERN] protection of the polling function list + * @pollfunc_list: [INTERN] list of functions to run on trigger. + * @control_attrs: [DRIVER] sysfs attributes relevant to trigger type + * @set_trigger_state: [DRIVER] switch on/off the trigger on demand + * @timestamp: [INTERN] timestamp usesd by some trigs (e.g. datardy) + * @owner: [DRIVER] used to monitor usage count of the trigger. + **/ +struct iio_trigger { + int id; + const char *name; + struct device dev; + + void *private_data; + struct list_head list; + struct list_head alloc_list; + spinlock_t pollfunc_list_lock; + struct list_head pollfunc_list; + const struct attribute_group *control_attrs; + s64 timestamp; + struct module *owner; + int use_count; + + int (*set_trigger_state)(struct iio_trigger *trig, bool state); + int (*try_reenable)(struct iio_trigger *trig); +}; + +static inline struct iio_trigger *to_iio_trigger(struct device *d) +{ + return container_of(d, struct iio_trigger, dev); +}; + +static inline void iio_put_trigger(struct iio_trigger *trig) +{ + put_device(&trig->dev); +}; + +static inline void iio_get_trigger(struct iio_trigger *trig) +{ + get_device(&trig->dev); +}; +/** + * iio_trigger_read_name() - sysfs access function to get the trigger name + **/ +ssize_t iio_trigger_read_name(struct device *dev, + struct device_attribute *attr, + char *buf); + +#define IIO_TRIGGER_NAME_ATTR DEVICE_ATTR(name, S_IRUGO, \ + iio_trigger_read_name, \ + NULL); + +/** + * iio_trigger_find_by_name() - search global trigger list + **/ +struct iio_trigger *iio_trigger_find_by_name(const char *name, size_t len); + +/** + * iio_trigger_register() - register a trigger with the IIO core + * @trig_info: trigger to be registered + **/ +int iio_trigger_register(struct iio_trigger *trig_info); + +/** + * iio_trigger_unregister() - unregister a trigger from the core + **/ +void iio_trigger_unregister(struct iio_trigger *trig_info); + +/** + * iio_trigger_attach_poll_func() - add a function pair to be run on trigger + * @trig: trigger to which the function pair are being added + * @pf: poll function pair + **/ +int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf); + +/** + * iio_trigger_dettach_poll_func() - remove function pair from those to be + * run on trigger. + * @trig: trigger from which the function is being removed. + * @pf: poll function pair + **/ +int iio_trigger_dettach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf); + +/** + * iio_trigger_poll() - called on a trigger occuring + * Typically called in relevant hardware interrupt handler. + **/ +void iio_trigger_poll(struct iio_trigger *); +void iio_trigger_notify_done(struct iio_trigger *); + +/** + * struct iio_poll_func - poll function pair + * + * @list: associate this with a triggers pollfunc_list + * @private_data: data specific to device (passed into poll func) + * @poll_func_immediate: function in here is run first. They should be + * extremely lightweight. Typically used for latch + * control on sensor supporting it. + * @poll_func_main: function in here is run after all immediates. + * Reading from sensor etc typically involves + * scheduling + * from here. + * + * The two stage approach used here only important when multiple sensors are + * being triggered by a single trigger. This really comes into it's own with + * simultaneous sampling devices where a simple latch command can be used to + * make the device store the values on all inputs. + **/ +struct iio_poll_func { + struct list_head list; + void *private_data; + void (*poll_func_immediate)(struct iio_dev *indio_dev); + void (*poll_func_main)(struct iio_dev *private_data); + +}; + +struct iio_trigger *iio_allocate_trigger(void); + +void iio_free_trigger(struct iio_trigger *trig); + + +#endif /* _IIO_TRIGGER_H_ */ diff --git a/include/linux/iio/trigger_consumer.h b/include/linux/iio/trigger_consumer.h index a605389..4c7f527 100644 --- a/include/linux/iio/trigger_consumer.h +++ b/include/linux/iio/trigger_consumer.h @@ -8,6 +8,20 @@ * the Free Software Foundation. */ +#ifdef CONFIG_IIO_TRIGGER +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers. + * @dev_info: iio_dev associated with the device that will consume the trigger + **/ +int iio_device_register_trigger_consumer(struct iio_dev *dev_info); +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process. + * @dev_info: iio_dev associated with the device that consumed the trigger + **/ +int iio_device_unregister_trigger_consumer(struct iio_dev *dev_info); + +#else + /** * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers. * @dev_info: iio_dev associated with the device that will consume the trigger @@ -18,9 +32,14 @@ int iio_device_register_trigger_consumer(struct iio_dev *dev_info) }; /** * iio_device_unregister_trigger_consumer() - reverse the registration process -. * @dev_info: iio_dev associated with the device that consumed the trigger + * @dev_info: iio_dev associated with the device that consumed the trigger **/ int iio_device_unregister_trigger_consumer(struct iio_dev *dev_info) { return 0; }; + +#endif /* CONFIG_TRIGGER_CONSUMER */ + + + -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 09/14] IIO: Ring buffer: Initial pass at rarely locked ring buffer 2009-07-02 13:47 ` [PATCH 08/14] IIO: Trigger support added to core Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 10/14] IIO: lis3l02dq ring buffer and data ready trigger support Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Please note this ring buffer implementation is very much a work in progress (and hence RFC). In it's current form it is stable and reasonably efficient. There are a couple of unlikely cases that will lead to more data being lost that is strictly necessary. The target was for the case of requiring regular sampling even during user space reads. All comments welcome. The intention is to make this only one of several implementations with run time selection. For now there is only one, so it is hard coded into the drivers using it. --- drivers/iio/Kconfig | 9 + drivers/iio/Makefile | 2 + drivers/iio/ring_sw.c | 431 +++++++++++++++++++++++++++++++++++++++++++ include/linux/iio/ring_sw.h | 189 +++++++++++++++++++ 4 files changed, 631 insertions(+), 0 deletions(-) create mode 100644 drivers/iio/ring_sw.c create mode 100644 include/linux/iio/ring_sw.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 3aa2886..936f954 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -17,6 +17,15 @@ config IIO_RING_BUFFER Provide core support for various ring buffer based data acquisition methods. +config IIO_SW_RING + tristate "Industrial I/O lock free software ring" + depends on IIO_RING_BUFFER && IIO + help + example software ring buffer implementation. The design aim + of this particular realization was to minize write locking + with the intention that some devices would be able to write + in interrupt context. + config IIO_TRIGGER boolean "Enable triggered sampling support" depends on IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 3271518..dea4536 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -7,6 +7,8 @@ industrialio-y := industrialio-core.o industrialio-$(CONFIG_IIO_RING_BUFFER) += industrialio-ring.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_SW_RING) += ring_sw.o + obj-y += accel/ obj-y += adc/ obj-y += light/ \ No newline at end of file diff --git a/drivers/iio/ring_sw.c b/drivers/iio/ring_sw.c new file mode 100644 index 0000000..e39df05 --- /dev/null +++ b/drivers/iio/ring_sw.c @@ -0,0 +1,431 @@ +/* The industrial I/O simple minimally locked ring buffer. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/iio/ring_sw.h> + +static inline int __iio_init_sw_ring_buffer(struct iio_sw_ring_buffer *ring, + int bytes_per_datum, int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + + __iio_init_ring_buffer(&ring->buf, bytes_per_datum, length); + ring->use_lock = __SPIN_LOCK_UNLOCKED((ring)->use_lock); + ring->data = kmalloc(length*ring->buf.bpd, GFP_KERNEL); + ring->read_p = 0; + ring->write_p = 0; + ring->last_written_p = 0; + ring->half_p = 0; + return ring->data ? 0 : -ENOMEM; +} + +static inline void __iio_free_sw_ring_buffer(struct iio_sw_ring_buffer *ring) +{ + kfree(ring->data); +} + +void iio_mark_sw_rb_in_use(struct iio_ring_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + spin_lock(&ring->use_lock); + ring->use_count++; + spin_unlock(&ring->use_lock); +} +EXPORT_SYMBOL(iio_mark_sw_rb_in_use); + +void iio_unmark_sw_rb_in_use(struct iio_ring_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + spin_lock(&ring->use_lock); + ring->use_count--; + spin_unlock(&ring->use_lock); +} +EXPORT_SYMBOL(iio_unmark_sw_rb_in_use); + + +/* Ring buffer related functionality */ +/* Store to ring is typically called in the bh of a data ready interrupt handler + * in the device driver */ +/* Lock always held if their is a chance this may be called */ +/* Only one of these per ring may run concurrently - enforced by drivers */ +int iio_store_to_sw_ring(struct iio_sw_ring_buffer *ring, + unsigned char *data, + s64 timestamp) +{ + int ret = 0; + int code; + unsigned char *temp_ptr, *change_test_ptr; + + /* initial store */ + if (unlikely(ring->write_p == 0)) { + ring->write_p = ring->data; + /* Doesn't actually matter if this is out of the set + * as long as the read pointer is valid before this + * passes it - guaranteed as set later in this function. + */ + ring->half_p = ring->data - ring->buf.length*ring->buf.bpd/2; + } + /* Copy data to where ever the current write pointer says */ + memcpy(ring->write_p, data, ring->buf.bpd); + barrier(); + /* Update the pointer used to get most recent value. + * Always valid as either points to latest or second latest value. + * Before this runs it is null and read attempts fail with -EAGAIN. + */ + ring->last_written_p = ring->write_p; + barrier(); + /* temp_ptr used to ensure we never have an invalid pointer + * it may be slightly lagging, but never invalid + */ + temp_ptr = ring->write_p + ring->buf.bpd; + /* End of ring, back to the beginning */ + if (temp_ptr == ring->data + ring->buf.length*ring->buf.bpd) + temp_ptr = ring->data; + /* Update the write pointer + * always valid as long as this is the only function able to write. + * Care needed with smp systems to ensure more than one ring fill + * is never scheduled. + */ + ring->write_p = temp_ptr; + + if (ring->read_p == 0) + ring->read_p = ring->data; + /* Buffer full - move the read pointer and create / escalate + * ring event */ + /* Tricky case - if the read pointer moves before we adjust it. + * Handle by not pushing if it has moved - may result in occasional + * unnecessary buffer full events when it wasn't quite true. + */ + else if (ring->write_p == ring->read_p) { + change_test_ptr = ring->read_p; + temp_ptr = change_test_ptr + ring->buf.bpd; + if (temp_ptr + == ring->data + ring->buf.length*ring->buf.bpd) { + temp_ptr = ring->data; + } + /* We are moving pointer on one because the ring is full. Any + * change to the read pointer will be this or greater. + */ + if (change_test_ptr == ring->read_p) + ring->read_p = temp_ptr; + + spin_lock(&ring->buf.shared_ev_pointer.lock); + + ret = iio_push_or_escallate_ring_event(&ring->buf, + IIO_EVENT_CODE_RING_100_FULL, + timestamp); + spin_unlock(&ring->buf.shared_ev_pointer.lock); + if (ret) + goto error_ret; + } + /* investigate if our event barrier has been passed */ + /* There are definite 'issues' with this and chances of + * simultaneous read */ + /* Also need to use loop count to ensure this only happens once */ + ring->half_p += ring->buf.bpd; + if (ring->half_p == ring->data + ring->buf.length*ring->buf.bpd) + ring->half_p = ring->data; + if (ring->half_p == ring->read_p) { + spin_lock(&ring->buf.shared_ev_pointer.lock); + code = IIO_EVENT_CODE_RING_50_FULL; + ret = __iio_push_event(&ring->buf.ev_int, + code, + timestamp, + &ring->buf.shared_ev_pointer); + spin_unlock(&ring->buf.shared_ev_pointer.lock); + } +error_ret: + return ret; +} + +int iio_rip_sw_rb(struct iio_ring_buffer *r, + size_t count, u8 **data, int *dead_offset) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + + u8 *initial_read_p, *initial_write_p, *current_read_p, *end_read_p; + int ret, max_copied; + int bytes_to_rip; + + /* A userspace program has probably made an error if it tries to + * read something that is not a whole number of bpds. + * Return an error. + */ + if (count % ring->buf.bpd) { + ret = -EINVAL; + printk(KERN_INFO "Ring buffer read request not whole number of" + "samples: Request bytes %d, Current bpd %d\n", + count, ring->buf.bpd); + goto error_ret; + } + /* Limit size to whole of ring buffer */ + bytes_to_rip = min((size_t)(ring->buf.bpd*ring->buf.length), count); + + *data = kmalloc(bytes_to_rip, GFP_KERNEL); + if (*data == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* build local copy */ + initial_read_p = ring->read_p; + if (unlikely(initial_read_p == 0)) { /* No data here as yet */ + ret = 0; + goto error_free_data_cpy; + } + + initial_write_p = ring->write_p; + + /* Need a consistent pair */ + while ((initial_read_p != ring->read_p) + || (initial_write_p != ring->write_p)) { + initial_read_p = ring->read_p; + initial_write_p = ring->write_p; + } + if (initial_write_p == initial_read_p) { + /* No new data available.*/ + ret = 0; + goto error_free_data_cpy; + } + + if (initial_write_p >= initial_read_p + bytes_to_rip) { + /* write_p is greater than necessary, all is easy */ + max_copied = bytes_to_rip; + memcpy(*data, initial_read_p, max_copied); + end_read_p = initial_read_p + max_copied; + } else if (initial_write_p > initial_read_p) { + /*not enough data to cpy */ + max_copied = initial_write_p - initial_read_p; + memcpy(*data, initial_read_p, max_copied); + end_read_p = initial_write_p; + } else { + /* going through 'end' of ring buffer */ + max_copied = ring->data + + ring->buf.length*ring->buf.bpd - initial_read_p; + memcpy(*data, initial_read_p, max_copied); + /* possible we are done if we align precisely with end */ + if (max_copied == bytes_to_rip) + end_read_p = ring->data; + else if (initial_write_p + > ring->data + bytes_to_rip - max_copied) { + /* enough data to finish */ + memcpy(*data + max_copied, ring->data, + bytes_to_rip - max_copied); + max_copied = bytes_to_rip; + end_read_p = ring->data + (bytes_to_rip - max_copied); + } else { /* not enough data */ + memcpy(*data + max_copied, ring->data, + initial_write_p - ring->data); + max_copied += initial_write_p - ring->data; + end_read_p = initial_write_p; + } + } + /* Now to verify which section was cleanly copied - i.e. how far + * read pointer has been pushed */ + current_read_p = ring->read_p; + + if (initial_read_p <= current_read_p) + *dead_offset = current_read_p - initial_read_p; + else + *dead_offset = ring->buf.length*ring->buf.bpd + - (initial_read_p - current_read_p); + + /* possible issue if the initial write has been lapped or indeed + * the point we were reading to has been passed */ + /* No valid data read. + * In this case the read pointer is already correct having been + * pushed further than we would look. */ + if (max_copied - *dead_offset < 0) { + ret = 0; + goto error_free_data_cpy; + } + + /* setup the next read position */ + /* Beware, this may fail due to concurrency fun and games. + * Possible that sufficient fill commands have run to push the read + * pointer past where we would be after the rip. If this occurs, leave + * it be. + */ + /* Tricky - deal with loops */ + + while (ring->read_p != end_read_p) + ring->read_p = end_read_p; + + return max_copied - *dead_offset; + +error_free_data_cpy: + kfree(*data); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_rip_sw_rb); + +int iio_store_to_sw_rb(struct iio_ring_buffer *r, u8 *data, s64 timestamp) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + return iio_store_to_sw_ring(ring, data, timestamp); +} +EXPORT_SYMBOL(iio_store_to_sw_rb); + +int iio_read_last_from_sw_ring(struct iio_sw_ring_buffer *ring, + unsigned char *data) +{ + unsigned char *last_written_p_copy; + + iio_mark_sw_rb_in_use(&ring->buf); +again: + barrier(); + last_written_p_copy = ring->last_written_p; + barrier(); /*unnessecary? */ + /* Check there is anything here */ + if (last_written_p_copy == 0) + return -EAGAIN; + memcpy(data, last_written_p_copy, ring->buf.bpd); + + if (unlikely(ring->last_written_p >= last_written_p_copy)) + goto again; + + iio_unmark_sw_rb_in_use(&ring->buf); + return 0; +} + +int iio_read_last_from_sw_rb(struct iio_ring_buffer *r, + unsigned char *data) +{ + return iio_read_last_from_sw_ring(iio_to_sw_ring(r), data); +} +EXPORT_SYMBOL(iio_read_last_from_sw_rb); + +int iio_request_update_sw_rb(struct iio_ring_buffer *r) +{ + int ret; + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + + spin_lock(&ring->use_lock); + if (ring->use_count || !ring->update_needed) { + ret = -EAGAIN; + goto error_ret; + } + __iio_free_sw_ring_buffer(ring); + ret = __iio_init_sw_ring_buffer(ring, ring->buf.bpd, ring->buf.length); +error_ret: + spin_unlock(&ring->use_lock); + return ret; +} +EXPORT_SYMBOL(iio_request_update_sw_rb); + +int iio_get_bpd_sw_rb(struct iio_ring_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + return ring->buf.bpd; +} +EXPORT_SYMBOL(iio_get_bpd_sw_rb); + +int iio_set_bpd_sw_rb(struct iio_ring_buffer *r, size_t bpd) +{ + if (r->bpd != bpd) { + r->bpd = bpd; + if (r->access.mark_param_change) + r->access.mark_param_change(r); + } + return 0; +} +EXPORT_SYMBOL(iio_set_bpd_sw_rb); + +int iio_get_length_sw_rb(struct iio_ring_buffer *r) +{ + return r->length; +} +EXPORT_SYMBOL(iio_get_length_sw_rb); + +int iio_set_length_sw_rb(struct iio_ring_buffer *r, int length) +{ + if (r->length != length) { + r->length = length; + if (r->access.mark_param_change) + r->access.mark_param_change(r); + } + return 0; +} +EXPORT_SYMBOL(iio_set_length_sw_rb); + +int iio_mark_update_needed_sw_rb(struct iio_ring_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + ring->update_needed = true; + return 0; +} +EXPORT_SYMBOL(iio_mark_update_needed_sw_rb); + +static void iio_sw_rb_release(struct device *dev) +{ + struct iio_ring_buffer *r = to_iio_ring_buffer(dev); + kfree(iio_to_sw_ring(r)); +} + +static IIO_RING_ENABLE_ATTR; +static IIO_RING_BPS_ATTR; +static IIO_RING_LENGTH_ATTR; + +/* Standard set of ring buffer attributes */ +static struct attribute *iio_ring_attributes[] = { + &dev_attr_length.attr, + &dev_attr_bps.attr, + &dev_attr_ring_enable.attr, + NULL, +}; + +static struct attribute_group iio_ring_attribute_group = { + .attrs = iio_ring_attributes, +}; + +static struct attribute_group *iio_ring_attribute_groups[] = { + &iio_ring_attribute_group, + NULL +}; + +static struct device_type iio_sw_ring_type = { + .release = iio_sw_rb_release, + .groups = iio_ring_attribute_groups, +}; + +struct iio_ring_buffer *iio_sw_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_ring_buffer *buf; + struct iio_sw_ring_buffer *ring; + + ring = kzalloc(sizeof *ring, GFP_KERNEL); + if (!ring) + return 0; + buf = &ring->buf; + + iio_ring_buffer_init(buf, indio_dev); + buf->dev.type = &iio_sw_ring_type; + device_initialize(&buf->dev); + buf->dev.parent = &indio_dev->dev; + buf->dev.class = &iio_class; + dev_set_drvdata(&buf->dev, (void *)buf); + + return buf; +} +EXPORT_SYMBOL(iio_sw_rb_allocate); + +void iio_sw_rb_free(struct iio_ring_buffer *r) +{ + if (r) + iio_put_ring_buffer(r); +} +EXPORT_SYMBOL(iio_sw_rb_free); +MODULE_DESCRIPTION("Industrialio I/O software ring buffer"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/iio/ring_sw.h b/include/linux/iio/ring_sw.h new file mode 100644 index 0000000..6365d5a --- /dev/null +++ b/include/linux/iio/ring_sw.h @@ -0,0 +1,189 @@ +/* The industrial I/O simple minimally locked ring buffer. + * + * 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 code is deliberately kept separate from the main industrialio I/O core + * as it is intended that in the future a number of different software ring + * buffer implementations will exist with different characteristics to suit + * different applications. + * + * This particular one was designed for a data capture application where it was + * particularly important that no userspace reads would interrupt the capture + * process. To this end the ring is not locked during a read. + * + * Comments on this buffer design welcomed. It's far from efficient and some of + * my understanding of the effects of scheduling on this are somewhat limited. + * Frankly, to my mind, this is the current weak point in the industrial I/O + * patch set. + */ + +#ifndef _IIO_RING_SW_H_ +#define _IIO_RING_SW_H_ +/* NEEDS COMMENTS */ +/* The intention is that this should be a separate module from the iio core. + * This is a bit like supporting algorithms dependent on what the device + * driver requests - some may support multiple options */ + + +#include <linux/autoconf.h> +#include <linux/iio/iio.h> +#include <linux/iio/ring_generic.h> + +#if defined CONFIG_IIO_SW_RING || defined CONFIG_IIO_SW_RING_MODULE + +/** + * iio_create_sw_rb() software ring buffer allocation + * @r: pointer to ring buffer pointer + **/ +int iio_create_sw_rb(struct iio_ring_buffer **r); + +/** + * iio_init_sw_rb() initialize the software ring buffer + * @r: pointer to a software ring buffer created by an + * iio_create_sw_rb call. + **/ +int iio_init_sw_rb(struct iio_ring_buffer *r, struct iio_dev *indio_dev); +/** + * iio_exit_sw_rb() reverse what was done in iio_init_sw_rb + **/ +void iio_exit_sw_rb(struct iio_ring_buffer *r); + +/** + * iio_free_sw_rb() free memory occupied by the core ring buffer struct + **/ +void iio_free_sw_rb(struct iio_ring_buffer *r); + +/** + * iio_mark_sw_rb_in_use() reference counting to prevent incorrect chances + **/ +void iio_mark_sw_rb_in_use(struct iio_ring_buffer *r); + +/** + * iio_unmark_sw_rb_in_use() notify the ring buffer that we don't care anymore + **/ +void iio_unmark_sw_rb_in_use(struct iio_ring_buffer *r); + +/** + * iio_read_last_from_sw_rb() attempt to read the last stored datum from the rb + **/ +int iio_read_last_from_sw_rb(struct iio_ring_buffer *r, u8 *data); + +/** + * iio_store_to_sw_rb() store a new datum to the ring buffer + * @rb: pointer to ring buffer instance + * @data: the datum to be stored including timestamp if relevant. + * @timestamp: timestamp which will be attached to buffer events if relevant. + **/ +int iio_store_to_sw_rb(struct iio_ring_buffer *r, u8 *data, s64 timestamp); + +/** + * iio_rip_sw_rb() attempt to read data from the ring buffer + * @r: ring buffer instance + * @count: number of datum's to try and read + * @data: where the data will be stored. + * @dead_offset: how much of the stored data was possibly invalidated by + * the end of the copy. + **/ +int iio_rip_sw_rb(struct iio_ring_buffer *r, + size_t count, + u8 **data, + int *dead_offset); + +/** + * iio_request_update_sw_rb() update params if update needed + **/ +int iio_request_update_sw_rb(struct iio_ring_buffer *r); + +/** + * iio_mark_update_needed_sw_rb() tell the ring buffer it needs a param update + **/ +int iio_mark_update_needed_sw_rb(struct iio_ring_buffer *r); + + +/** + * iio_get_bpd_sw_rb() get the datum size in bytes + **/ +int iio_get_bpd_sw_rb(struct iio_ring_buffer *r); + +/** + * iio_set_bpd_sw_rb() set the datum size in bytes + **/ +int iio_set_bpd_sw_rb(struct iio_ring_buffer *r, size_t bpd); + +/** + * iio_get_length_sw_rb() get how many datums the rb may contain + **/ +int iio_get_length_sw_rb(struct iio_ring_buffer *r); + +/** + * iio_set_length_sw_rb() set how many datums the rb may contain + **/ +int iio_set_length_sw_rb(struct iio_ring_buffer *r, int length); + +/** + * iio_ring_sw_register_funcs() helper function to set up rb access + **/ +static inline void iio_ring_sw_register_funcs(struct iio_ring_access_funcs *ra) +{ + ra->mark_in_use = &iio_mark_sw_rb_in_use; + ra->unmark_in_use = &iio_unmark_sw_rb_in_use; + + ra->store_to = &iio_store_to_sw_rb; + ra->read_last = &iio_read_last_from_sw_rb; + ra->rip_lots = &iio_rip_sw_rb; + + ra->mark_param_change = &iio_mark_update_needed_sw_rb; + ra->request_update = &iio_request_update_sw_rb; + + ra->get_bpd = &iio_get_bpd_sw_rb; + ra->set_bpd = &iio_set_bpd_sw_rb; + + ra->get_length = &iio_get_length_sw_rb; + ra->set_length = &iio_set_length_sw_rb; +}; + +/** + * struct iio_sw_ring_buffer - software ring buffer + * @buf: generic ring buffer elements + * @data: the ring buffer memory + * @read_p: read pointer (oldest available) + * @write_p: write pointer + * @last_written_p: read pointer (newest available) + * @half_p: half buffer length behind write_p (event generation) + * @use_count: reference count to prevent resizing when in use + * @update_needed: flag to indicated change in size requested + * @use_lock: lock to prevent change in size when in use + * + * Note that the first element of all ring buffers must be a + * struct iio_ring_buffer. +**/ + +struct iio_sw_ring_buffer { + struct iio_ring_buffer buf; + unsigned char *data; + unsigned char *read_p; + unsigned char *write_p; + unsigned char *last_written_p; + /* used to act as a point at which to signal an event */ + unsigned char *half_p; + int use_count; + int update_needed; + spinlock_t use_lock; +}; + +#define iio_to_sw_ring(r) container_of(r, struct iio_sw_ring_buffer, buf) + +struct iio_ring_buffer *iio_sw_rb_allocate(struct iio_dev *indio_dev); +void iio_sw_rb_free(struct iio_ring_buffer *ring); + + + +#else /* CONFIG_IIO_RING_BUFFER*/ +static inline void iio_ring_sw_register_funcs(struct iio_ring_access_funcs *ra) +{}; +#endif /* !CONFIG_IIO_RING_BUFFER */ +#endif /* _IIO_RING_SW_H_ */ -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 10/14] IIO: lis3l02dq ring buffer and data ready trigger support 2009-07-02 13:47 ` [PATCH 09/14] IIO: Ring buffer: Initial pass at rarely locked ring buffer Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 11/14] IIO: max1363 add software ring buffer support using ring_sw Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Example of relatively common case of device sampling based on internal clock and providing a data ready signal to indicate that new data is available to be read out. Generic trigger approach used to allow other devices to be sampled 'at the same time' as this the accelerometer. This is very useful in various motion estimation algorithms. --- drivers/iio/accel/Makefile | 1 + drivers/iio/accel/lis3l02dq.h | 22 ++- drivers/iio/accel/lis3l02dq_ring.c | 599 ++++++++++++++++++++++++++++++++++++ 3 files changed, 621 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/accel/lis3l02dq_ring.c diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index d543208..d5335f9 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_KXSD9) += kxsd9.o lis3l02dq-y := lis3l02dq_core.o +lis3l02dq-$(CONFIG_IIO_RING_BUFFER) += lis3l02dq_ring.o obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o sca3000-y := sca3000_core.o sca3000_ring.o diff --git a/drivers/iio/accel/lis3l02dq.h b/drivers/iio/accel/lis3l02dq.h index 18cb49c..91a5375 100644 --- a/drivers/iio/accel/lis3l02dq.h +++ b/drivers/iio/accel/lis3l02dq.h @@ -183,6 +183,26 @@ int lis3l02dq_spi_write_reg_8(struct device *dev, #define LIS3L02DQ_SCAN_ACC_Y 1 #define LIS3L02DQ_SCAN_ACC_Z 2 + +#ifdef CONFIG_IIO_RING_BUFFER +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev); +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev); + +ssize_t lis3l02dq_read_accel_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int lis3l02dq_configure_ring(struct iio_dev *indio_dev); +void lis3l02dq_unconfigure_ring(struct iio_dev *indio_dev); + +int lis3l02dq_initialize_ring(struct iio_ring_buffer *ring); +void lis3l02dq_uninitialize_ring(struct iio_ring_buffer *ring); +#else /* CONFIG_IIO_RING_BUFFER */ + static inline void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) {}; static inline int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) { @@ -208,5 +228,5 @@ static inline int lis3l02dq_initialize_ring(struct iio_ring_buffer *ring) return 0; }; static inline void lis3l02dq_uninitialize_ring(struct iio_ring_buffer *ring) {}; - +#endif /* CONFIG_IIO_RING_BUFFER */ #endif /* SPI_LIS3L02DQ_H_ */ diff --git a/drivers/iio/accel/lis3l02dq_ring.c b/drivers/iio/accel/lis3l02dq_ring.c new file mode 100644 index 0000000..133d0c9 --- /dev/null +++ b/drivers/iio/accel/lis3l02dq_ring.c @@ -0,0 +1,599 @@ +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/list.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/ring_sw.h> +#include <linux/iio/accel.h> +#include <linux/iio/trigger.h> +#include "lis3l02dq.h" + +/** + * combine_8_to_16() utility function to munge to u8s into u16 + **/ +static inline u16 combine_8_to_16(u8 lower, u8 upper) +{ + u16 _lower = lower; + u16 _upper = upper; + return _lower | (_upper << 8); +} + +/** + * lis3l02dq_scan_el_set_state() set whether a scan contains a given channel + * @scan_el: associtate iio scan element attribute + * @indio_dev: the device structure + * @bool: desired state + * + * mlock already held when this is called. + **/ +static int lis3l02dq_scan_el_set_state(struct iio_scan_el *scan_el, + struct iio_dev *indio_dev, + bool state) +{ + u8 t, mask; + int ret; + + ret = lis3l02dq_spi_read_reg_8(&indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret; + switch (scan_el->label) { + case LIS3L02DQ_REG_OUT_X_L_ADDR: + mask = LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE; + break; + case LIS3L02DQ_REG_OUT_Y_L_ADDR: + mask = LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE; + break; + case LIS3L02DQ_REG_OUT_Z_L_ADDR: + mask = LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + break; + default: + ret = -EINVAL; + goto error_ret; + } + + if (!(mask & t) == state) { + if (state) + t |= mask; + else + t &= ~mask; + ret = lis3l02dq_spi_write_reg_8(&indio_dev->dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + } +error_ret: + return ret; + +} +static IIO_SCAN_EL_C(accel_x, LIS3L02DQ_SCAN_ACC_X, IIO_SIGNED(16), + LIS3L02DQ_REG_OUT_X_L_ADDR, + &lis3l02dq_scan_el_set_state); +static IIO_SCAN_EL_C(accel_y, LIS3L02DQ_SCAN_ACC_Y, IIO_SIGNED(16), + LIS3L02DQ_REG_OUT_Y_L_ADDR, + &lis3l02dq_scan_el_set_state); +static IIO_SCAN_EL_C(accel_z, LIS3L02DQ_SCAN_ACC_Z, IIO_SIGNED(16), + LIS3L02DQ_REG_OUT_Z_L_ADDR, + &lis3l02dq_scan_el_set_state); +static IIO_SCAN_EL_TIMESTAMP; + +static struct attribute *lis3l02dq_scan_el_attrs[] = { + &iio_scan_el_accel_x.dev_attr.attr, + &iio_scan_el_accel_y.dev_attr.attr, + &iio_scan_el_accel_z.dev_attr.attr, + &iio_scan_el_timestamp.dev_attr.attr, + NULL, +}; + +static struct attribute_group lis3l02dq_scan_el_group = { + .attrs = lis3l02dq_scan_el_attrs, + .name = "scan_elements", +}; + +/** + * lis3l02dq_poll_func_th() top half interrupt handler called by trigger + * @private_data: iio_dev + **/ +static void lis3l02dq_poll_func_th(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *st = iio_dev_get_devdata(indio_dev); + st->last_timestamp = indio_dev->trig->timestamp; + schedule_work(&st->work_trigger_to_ring); + /* Indicate that this interrupt is being handled */ + + /* Technically this is trigger related, but without this + * handler running there is currently now way for the interrupt + * to clear. + */ + st->inter = 1; +} + +/** + * lis3l02dq_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static int lis3l02dq_data_rdy_trig_poll(struct iio_dev *dev_info, + int index, + s64 timestamp, + int no_test) +{ + struct lis3l02dq_state *st = iio_dev_get_devdata(dev_info); + struct iio_trigger *trig = st->trig; + + trig->timestamp = timestamp; + iio_trigger_poll(trig); + + return IRQ_HANDLED; +} + +/* This is an event as it is a response to a physical interrupt */ +IIO_EVENT_SH(data_rdy_trig, &lis3l02dq_data_rdy_trig_poll); + +/** + * lis3l02dq_read_accel_from_ring() individual acceleration read from ring + **/ +ssize_t lis3l02dq_read_accel_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_scan_el *el = NULL; + int ret, len = 0, i = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *dev_info = dev_get_drvdata(dev); + s16 *data; + + while (dev_info->scan_el_attrs->attrs[i]) { + el = to_iio_scan_el((struct device_attribute *) + (dev_info->scan_el_attrs->attrs[i])); + /* label is in fact the address */ + if (el->label == this_attr->address) + break; + i++; + } + if (!dev_info->scan_el_attrs->attrs[i]) { + ret = -EINVAL; + goto error_ret; + } + /* If this element is in the scan mask */ + ret = iio_scan_mask_query(dev_info, el->number); + if (ret < 0) + goto error_ret; + if (ret) { + data = kmalloc(dev_info->ring->access.get_bpd(dev_info->ring), + GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + ret = dev_info->ring->access.read_last(dev_info->ring, + (u8 *)data); + if (ret) + goto error_free_data; + } else { + ret = -EINVAL; + goto error_ret; + } + len = iio_scan_mask_count_to_right(dev_info, el->number); + if (len < 0) { + ret = len; + goto error_free_data; + } + len = sprintf(buf, "ring %d\n", data[len]); +error_free_data: + kfree(data); +error_ret: + return ret ? ret : len; + +} + +static const u8 read_all_tx_array[] = +{ + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_H_ADDR), 0, +}; + +/** + * lis3l02dq_read_all() Reads all channels currently selected + * @st: device specific state + * @rx_array: (dma capable) recieve array, must be at least + * 4*number of channels + **/ +int lis3l02dq_read_all(struct lis3l02dq_state *st, u8 *rx_array) +{ + struct spi_transfer *xfers; + struct spi_message msg; + int ret, i, j = 0; + + xfers = kzalloc((st->indio_dev->scan_count) * 2 + * sizeof(*xfers), GFP_KERNEL); + if (!xfers) + return -ENOMEM; + + mutex_lock(&st->buf_lock); + + for (i = 0; i < ARRAY_SIZE(read_all_tx_array)/4; i++) { + if (st->indio_dev->scan_mask & (1 << i)) { + /* lower byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + + /* upper byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4 + 2]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + } + } + /* After these are transmitted, the rx_buff should have + * values in alternate bytes + */ + spi_message_init(&msg); + for (j = 0; j < st->indio_dev->scan_count * 2; j++) + spi_message_add_tail(&xfers[j], &msg); + + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + kfree(xfers); + + return ret; +} + + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static void lis3l02dq_trigger_bh_to_ring(struct work_struct *work_s) +{ + struct lis3l02dq_state *st + = container_of(work_s, struct lis3l02dq_state, + work_trigger_to_ring); + + u8 *rx_array; + int i = 0; + u16 *data; + size_t datasize = st->indio_dev + ->ring->access.get_bpd(st->indio_dev->ring); + + data = kmalloc(datasize , GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return; + } + /* Due to interleaved nature of transmission this buffer must be + * twice the number of bytes, or 4 times the number of channels + */ + rx_array = kmalloc(4 * (st->indio_dev->scan_count), GFP_KERNEL); + if (rx_array == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return; + } + + /* whilst trigger specific, if this read does nto occur the data + ready interrupt will not be cleared. Need to add a mechanism + to provide a dummy read function if this is not triggering on + the data ready function but something else is. + */ + st->inter = 0; + + if (st->indio_dev->scan_count) + if (lis3l02dq_read_all(st, rx_array) >= 0) + for (; i < st->indio_dev->scan_count; i++) + data[i] = combine_8_to_16(rx_array[i*4+1], + rx_array[i*4+3]); + /* Guaranteed to be aligned with 8 byte boundary */ + if (st->indio_dev->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = st->last_timestamp; + + st->indio_dev->ring->access.store_to(st->indio_dev->ring, + (u8 *)data, + st->last_timestamp); + + iio_trigger_notify_done(st->indio_dev->trig); + kfree(rx_array); + kfree(data); + + return; +} +/* in these circumstances is it better to go with unaligned packing and + * deal with the cost?*/ +static int lis3l02dq_data_rdy_ring_preenable(struct iio_dev *indio_dev) +{ + size_t size; + /* Check if there are any scan elements enabled, if not fail*/ + if (!(indio_dev->scan_count || indio_dev->scan_timestamp)) + return -EINVAL; + + if (indio_dev->ring->access.set_bpd) { + if (indio_dev->scan_timestamp) + if (indio_dev->scan_count) /* Timestamp and data */ + size = 2*sizeof(s64); + else /* Timestamp only */ + size = sizeof(s64); + else /* Data only */ + size = indio_dev->scan_count*sizeof(s16); + indio_dev->ring->access.set_bpd(indio_dev->ring, size); + } + + return 0; +} + +static int lis3l02dq_data_rdy_ring_postenable(struct iio_dev *indio_dev) +{ + return indio_dev->trig + ? iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc) + : 0; +} + +static int lis3l02dq_data_rdy_ring_predisable(struct iio_dev *indio_dev) +{ + return indio_dev->trig + ? iio_trigger_dettach_poll_func(indio_dev->trig, + indio_dev->pollfunc) + : 0; +} + + +/* Caller responsible for locking as necessary. */ +static int __lis3l02dq_write_data_ready_config(struct device *dev, + struct + iio_event_handler_list *list, + bool state) +{ + int ret; + u8 valold; + bool currentlyset; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + +/* Get the current event mask register */ + ret = lis3l02dq_spi_read_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; +/* Find out if data ready is already on */ + currentlyset + = valold & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + +/* Disable requested */ + if (!state && currentlyset) { + + valold &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + /* The double write is to overcome a hardware bug?*/ + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; + + iio_remove_event_from_list(list, + &indio_dev->interrupts[0] + ->ev_list); + +/* Enable requested */ + } else if (state && !currentlyset) { + /* if not set, enable requested */ + valold |= LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + iio_add_event_to_list(list, &indio_dev->interrupts[0]->ev_list); + ret = lis3l02dq_spi_write_reg_8(dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; + } + + return 0; +error_ret: + return ret; +} + +/** + * lis3l02dq_data_rdy_trigger_set_state() set datardy interrupt state + * + * If disabling the interrupt also does a final read to ensure it is clear. + * This is only important in some cases where the scan enable elements are + * switched before the ring is reenabled. + **/ +static int lis3l02dq_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct lis3l02dq_state *st = trig->private_data; + int ret = 0; + u8 t; + __lis3l02dq_write_data_ready_config(&st->indio_dev->dev, + &iio_event_data_rdy_trig, + state); + if (state == false) { + /* possible quirk with handler currently worked around + by ensuring the work queue is empty */ + flush_scheduled_work(); + /* Clear any outstanding ready events */ + ret = lis3l02dq_read_all(st, NULL); + } + lis3l02dq_spi_read_reg_8(&st->indio_dev->dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + return ret; +} +DEVICE_ATTR(name, S_IRUGO, iio_trigger_read_name, NULL); + +static struct attribute *lis3l02dq_trigger_attrs[] = { + &dev_attr_name.attr, + NULL, +}; + +static const struct attribute_group lis3l02dq_trigger_attr_group = { + .attrs = lis3l02dq_trigger_attrs, +}; + +/** + * lis3l02dq_trig_try_reen() try renabling irq for data rdy trigger + * @trig: the datardy trigger + * + * As the trigger may occur on any data element being updated it is + * really rather likely to occur during the read from the previous + * trigger event. The only way to discover if this has occured on + * boards not supporting level interrupts is to take a look at the line. + * If it is indicating another interrupt and we don't seem to have a + * handler looking at it, then we need to notify the core that we need + * to tell the triggering core to try reading all these again. + **/ +static int lis3l02dq_trig_try_reen(struct iio_trigger *trig) +{ + struct lis3l02dq_state *st = trig->private_data; + enable_irq(st->us->irq); + /* If gpio still high (or high again) */ + if (gpio_get_value(irq_to_gpio(st->us->irq))) + if (st->inter == 0) { + /* already interrupt handler dealing with it */ + disable_irq_nosync(st->us->irq); + if (st->inter == 1) { + /* interrupt handler snuck in between test + * and disable */ + enable_irq(st->us->irq); + return 0; + } + return -EAGAIN; + } + /* irq reenabled so success! */ + return 0; +} + +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *state = indio_dev->dev_data; + + state->trig = iio_allocate_trigger(); + state->trig->name = kmalloc(IIO_TRIGGER_NAME_LENGTH, GFP_KERNEL); + if (!state->trig->name) { + ret = -ENOMEM; + goto error_free_trig; + } + snprintf((char *)state->trig->name, + IIO_TRIGGER_NAME_LENGTH, + "lis3l02dq-dev%d", indio_dev->id); + state->trig->dev.parent = &state->us->dev; + state->trig->owner = THIS_MODULE; + state->trig->private_data = state; + state->trig->set_trigger_state = &lis3l02dq_data_rdy_trigger_set_state; + state->trig->try_reenable = &lis3l02dq_trig_try_reen; + state->trig->control_attrs = &lis3l02dq_trigger_attr_group; + ret = iio_trigger_register(state->trig); + if (ret) + goto error_free_trig_name; + + return 0; + +error_free_trig_name: + kfree(state->trig->name); +error_free_trig: + iio_free_trigger(state->trig); + + return ret; +} + +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *state = indio_dev->dev_data; + + iio_trigger_unregister(state->trig); + kfree(state->trig->name); + iio_free_trigger(state->trig); +} + +void lis3l02dq_unconfigure_ring(struct iio_dev *indio_dev) +{ + kfree(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->ring); +} + +int lis3l02dq_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct lis3l02dq_state *st = indio_dev->dev_data; + struct iio_ring_buffer *ring; + INIT_WORK(&st->work_trigger_to_ring, lis3l02dq_trigger_bh_to_ring); + /* Set default scan mode */ + + iio_scan_mask_set(indio_dev, iio_scan_el_accel_x.number); + iio_scan_mask_set(indio_dev, iio_scan_el_accel_y.number); + iio_scan_mask_set(indio_dev, iio_scan_el_accel_z.number); + indio_dev->scan_timestamp = true; + + indio_dev->scan_el_attrs = &lis3l02dq_scan_el_group; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->ring = ring; + /* Effectively select the ring buffer implementation */ + iio_ring_sw_register_funcs(&ring->access); + ring->preenable = &lis3l02dq_data_rdy_ring_preenable; + ring->postenable = &lis3l02dq_data_rdy_ring_postenable; + ring->predisable = &lis3l02dq_data_rdy_ring_predisable; + ring->owner = THIS_MODULE; + + indio_dev->pollfunc = kzalloc(sizeof(*indio_dev->pollfunc), GFP_KERNEL); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free;; + } + indio_dev->pollfunc->poll_func_main = &lis3l02dq_poll_func_th; + indio_dev->pollfunc->private_data = indio_dev; + indio_dev->modes |= INDIO_RING_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->ring); + return ret; +} + +int lis3l02dq_initialize_ring(struct iio_ring_buffer *ring) +{ + return iio_ring_buffer_register(ring); +} + +void lis3l02dq_uninitialize_ring(struct iio_ring_buffer *ring) +{ + iio_ring_buffer_unregister(ring); +} + + +int lis3l02dq_set_ring_length(struct iio_dev *indio_dev, int length) +{ + /* Set sensible defaults for the ring buffer */ + if (indio_dev->ring->access.set_length) + return indio_dev->ring->access.set_length(indio_dev->ring, 500); + return 0; +} + + -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 11/14] IIO: max1363 add software ring buffer support using ring_sw 2009-07-02 13:47 ` [PATCH 10/14] IIO: lis3l02dq ring buffer and data ready trigger support Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 12/14] IIO: Periodic timer based trigger Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Changes since V2: * Moved to new registration methodology. --- drivers/iio/adc/Makefile | 1 + drivers/iio/adc/max1363.h | 14 +++- drivers/iio/adc/max1363_ring.c | 235 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/max1363_ring.c diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 2a37fee..0c2b6f3 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -3,5 +3,6 @@ # max1363-y := max1363_core.o +max1363-$(CONFIG_IIO_RING_BUFFER) += max1363_ring.o obj-$(CONFIG_MAX1363) += max1363.o diff --git a/drivers/iio/adc/max1363.h b/drivers/iio/adc/max1363.h index a9d5c21..8aca81f 100644 --- a/drivers/iio/adc/max1363.h +++ b/drivers/iio/adc/max1363.h @@ -228,6 +228,18 @@ struct max1363_state { struct iio_trigger *trig; struct regulator *reg; }; +#ifdef CONFIG_IIO_RING_BUFFER + +ssize_t max1363_scan_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); +int max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void max1363_ring_cleanup(struct iio_dev *indio_dev); + +int max1363_initialize_ring(struct iio_ring_buffer *ring); +void max1363_uninitialize_ring(struct iio_ring_buffer *ring); + +#else /* CONFIG_IIO_RING_BUFFER */ static inline void max1363_uninitialize_ring(struct iio_ring_buffer *ring) { @@ -253,5 +265,5 @@ max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev) }; static inline void max1363_ring_cleanup(struct iio_dev *indio_dev) {}; - +#endif /* CONFIG_IIO_RING_BUFFER */ #endif /* _MAX1363_H_ */ diff --git a/drivers/iio/adc/max1363_ring.c b/drivers/iio/adc/max1363_ring.c new file mode 100644 index 0000000..6c66b43 --- /dev/null +++ b/drivers/iio/adc/max1363_ring.c @@ -0,0 +1,235 @@ +/* + * 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. + * + * max1363_ring.c + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/i2c.h> + +#include <linux/iio/iio.h> +#include <linux/iio/ring_generic.h> +#include <linux/iio/ring_sw.h> +#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h> + +#include "max1363.h" + +ssize_t max1363_scan_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct max1363_state *info = dev_info->dev_data; + int i, ret, len = 0; + char *ring_data; + + ring_data = kmalloc(info->current_mode->numvals*2, GFP_KERNEL); + if (ring_data == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = dev_info->ring->access.read_last(dev_info->ring, ring_data); + if (ret) + goto error_free_ring_data; + len += sprintf(buf+len, "ring "); + for (i = 0; i < info->current_mode->numvals; i++) + len += sprintf(buf + len, "%d ", + ((int)(ring_data[i*2 + 0] & 0x0F) << 8) + + ((int)(ring_data[i*2 + 1]))); + len += sprintf(buf + len, "\n"); + kfree(ring_data); + + return len; + +error_free_ring_data: + kfree(ring_data); +error_ret: + return ret; +} + +/** + * max1363_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the nuber of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int max1363_ring_preenable(struct iio_dev *indio_dev) +{ + struct max1363_state *st = indio_dev->dev_data; + size_t d_size; + + if (indio_dev->ring->access.set_bpd) { + d_size = st->current_mode->numvals*2 + sizeof(s64); + if (d_size % 8) + d_size += 8 - (d_size % 8); + indio_dev->ring->access.set_bpd(indio_dev->ring, d_size); + } + + return 0; +} + +/** + * max1363_ring_postenable() typical ring post enable + * + * Only not moved into the core for the hardware ring buffer cases + * that are more sophisticated. + **/ +static int max1363_ring_postenable(struct iio_dev *indio_dev) +{ + if (indio_dev->trig == NULL) + return 0; + return iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc); +} + +/** + * max1363_ring_predisable() runs just prior to ring buffer being disabled + * + * Typical predisable function which ensures that no trigger events can + * occur before we disable the ring buffer (and hence would have no idea + * what to do with them) + **/ +static int max1363_ring_predisable(struct iio_dev *indio_dev) +{ + if (indio_dev->trig) + return iio_trigger_dettach_poll_func(indio_dev->trig, + indio_dev->pollfunc); + else + return 0; +} + +/** + * max1363_poll_func_th() th of trigger launched polling to ring buffer + * + * As sampling only occurs on i2c comms occuring, leave timestamping until + * then. Some triggers will generate their own time stamp. Currently + * there is no way of notifying them when no one cares. + **/ +void max1363_poll_func_th(struct iio_dev *indio_dev) +{ + struct max1363_state *st = indio_dev->dev_data; + + schedule_work(&st->poll_work); + + return; +} +/** + * max1363_poll_bh_to_ring() bh of trigger launched polling to ring buffer + * @work_s: the work struct through which this was scheduled + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + * I think the one copy of this at a time was to avoid problems if the + * trigger was set far too high and the reads then locked up the computer. + **/ +static void max1363_poll_bh_to_ring(struct work_struct *work_s) +{ + struct max1363_state *st = container_of(work_s, struct max1363_state, + poll_work); + struct iio_dev *indio_dev = st->indio_dev; + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(indio_dev->ring); + s64 time_ns; + __u8 *rxbuf; + int b_sent; + size_t d_size; + + /* Ensure the timestamp is 8 byte aligned */ + d_size = st->current_mode->numvals*2 + sizeof(s64); + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + + /* Ensure only one copy of this function running at a time */ + if (atomic_inc_return(&st->protect_ring) > 1) + return; + + /* Monitor mode prevents reading. Whilst not currently implemented + * might as well have this test in here in the meantime as it does + * no harm. + */ + if (st->current_mode->numvals == 0) + return; + + rxbuf = kmalloc(d_size, GFP_KERNEL); + if (rxbuf == NULL) + return; + + b_sent = i2c_master_recv(st->client, + rxbuf, + st->current_mode->numvals*2); + if (b_sent < 0) + goto done; + + time_ns = iio_get_time_ns(); + + memcpy(rxbuf + d_size - sizeof(s64), &time_ns, sizeof(time_ns)); + + indio_dev->ring->access.store_to(&ring->buf, rxbuf, time_ns); +done: + kfree(rxbuf); + atomic_dec(&st->protect_ring); +} + + +int max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct max1363_state *st = indio_dev->dev_data; + int ret = 0; + + indio_dev->ring = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->ring) { + ret = -ENOMEM; + goto error_ret; + } + /* Effectively select the ring buffer implementation */ + iio_ring_sw_register_funcs(&st->indio_dev->ring->access); + indio_dev->pollfunc = kzalloc(sizeof(*indio_dev->pollfunc), GFP_KERNEL); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + /* Configure the polling function called on trigger interrupts */ + indio_dev->pollfunc->poll_func_main = &max1363_poll_func_th; + indio_dev->pollfunc->private_data = indio_dev; + + /* Ring buffer functions - here trigger setup related */ + indio_dev->ring->postenable = &max1363_ring_postenable; + indio_dev->ring->preenable = &max1363_ring_preenable; + indio_dev->ring->predisable = &max1363_ring_predisable; + INIT_WORK(&st->poll_work, &max1363_poll_bh_to_ring); + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_RING_TRIGGERED; + return 0; +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->ring); +error_ret: + return ret; +} + +void max1363_ring_cleanup(struct iio_dev *indio_dev) +{ + kfree(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->ring); +} + +void max1363_uninitialize_ring(struct iio_ring_buffer *ring) +{ + iio_ring_buffer_unregister(ring); +}; + +int max1363_initialize_ring(struct iio_ring_buffer *ring) +{ + return iio_ring_buffer_register(ring); +}; -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 12/14] IIO: Periodic timer based trigger 2009-07-02 13:47 ` [PATCH 11/14] IIO: max1363 add software ring buffer support using ring_sw Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 13/14] IIO: Proof of concept gpio trigger Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> The original posting of this driver led to a discussion in which it was commented that a better system was needed for dealing with the many possible periodic interrupt sources available on some SoCs. Unfortunately that is a big task and as far as I know, no-one has taken it on as yet. So in the meantime this driver is still in here. --- drivers/iio/Kconfig | 2 + drivers/iio/Makefile | 4 +- drivers/iio/trigger/Kconfig | 12 ++ drivers/iio/trigger/Makefile | 5 + drivers/iio/trigger/iio-trig-periodic-rtc.c | 230 +++++++++++++++++++++++++++ include/linux/iio/prtc_trigger.h | 7 + 6 files changed, 259 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/trigger/Kconfig create mode 100644 drivers/iio/trigger/Makefile create mode 100644 drivers/iio/trigger/iio-trig-periodic-rtc.c create mode 100644 include/linux/iio/prtc_trigger.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 936f954..3586984 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -38,3 +38,5 @@ config IIO_TRIGGER source drivers/iio/accel/Kconfig source drivers/iio/adc/Kconfig source drivers/iio/light/Kconfig + +source drivers/iio/trigger/Kconfig \ No newline at end of file diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index dea4536..7ec0218 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -11,4 +11,6 @@ obj-$(CONFIG_IIO_SW_RING) += ring_sw.o obj-y += accel/ obj-y += adc/ -obj-y += light/ \ No newline at end of file +obj-y += light/ + +obj-y += trigger/ \ No newline at end of file diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig new file mode 100644 index 0000000..0c73ccc --- /dev/null +++ b/drivers/iio/trigger/Kconfig @@ -0,0 +1,12 @@ +# +# Industrial I/O standalone triggers +# +comment "Triggers - standalone" + depends on IIO + +config IIO_PERIODIC_RTC_TRIGGER + tristate "Periodic RTC triggers" + depends on IIO && IIO_TRIGGER && RTC_CLASS + help + Provides support for using periodic capable real time + clocks as IIO triggers. diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile new file mode 100644 index 0000000..4ae55b9 --- /dev/null +++ b/drivers/iio/trigger/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for triggers not associated with iio-devices +# +obj-$(CONFIG_IIO_PERIODIC_RTC_TRIGGER) += iio-trig-periodic-rtc.o + diff --git a/drivers/iio/trigger/iio-trig-periodic-rtc.c b/drivers/iio/trigger/iio-trig-periodic-rtc.c new file mode 100644 index 0000000..feac4f3 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-periodic-rtc.c @@ -0,0 +1,230 @@ +/* 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/rtc.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/prtc_trigger.h> + +LIST_HEAD(iio_prtc_trigger_list); +DEFINE_MUTEX(iio_prtc_trigger_list_lock); + +struct iio_prtc_trigger_info { + struct rtc_device *rtc; + int frequency; + char *name; + 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 = trig->private_data; + 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 = dev_get_drvdata(dev); + struct iio_prtc_trigger_info *trig_info = trig->private_data; + 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 = dev_get_drvdata(dev); + struct iio_prtc_trigger_info *trig_info = trig->private_data; + 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 ssize_t iio_trig_periodic_read_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + struct iio_prtc_trigger_info *trig_info = trig->private_data; + return sprintf(buf, "%s\n", trig_info->name); +} + +DEVICE_ATTR(name, S_IRUGO, + iio_trig_periodic_read_name, + NULL); +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, + &dev_attr_name.attr, + NULL, +}; +static const struct attribute_group iio_trig_prtc_attr_group = { + .attrs = iio_trig_prtc_attrs, +}; + +static void iio_prtc_trigger_poll(void *private_data) +{ + iio_trigger_poll(private_data); +} + + + +static int iio_trig_periodic_rtc_probe(struct platform_device *dev) +{ + struct iio_prtc_trigger_group *pdata = dev->dev.platform_data; + struct iio_prtc_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + + int i, ret; + for (i = 0; i < pdata->num_triggers; i++) { + trig = iio_allocate_trigger(); + 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; + } + trig->private_data = trig_info; + trig->owner = THIS_MODULE; + trig->set_trigger_state = &iio_trig_periodic_rtc_set_state; + trig->name = kmalloc(IIO_TRIGGER_NAME_LENGTH, GFP_KERNEL); + if (trig->name == NULL) { + ret = -ENOMEM; + goto error_free_trig_info; + } + snprintf((char *)trig->name, + IIO_TRIGGER_NAME_LENGTH, + "periodic%s", + pdata->triggers[i].name); + trig_info->name = (char *)trig->name; + /* RTC access */ + trig_info->rtc + = rtc_class_open((char *)(pdata->triggers[i].name)); + if (trig_info->rtc == NULL) { + ret = -EINVAL; + goto error_free_name; + } + 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->control_attrs = &iio_trig_prtc_attr_group; + 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_name: + kfree(trig->name); +error_free_trig_info: + kfree(trig_info); +error_put_trigger_and_remove_from_list: + list_del(&trig->alloc_list); + iio_put_trigger(trig); +error_free_completed_registrations: + list_for_each_entry_safe(trig, + trig2, + &iio_prtc_trigger_list, + alloc_list) { + trig_info = trig->private_data; + iio_trigger_unregister(trig); + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig->name); + kfree(trig_info); + iio_put_trigger(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 = trig->private_data; + iio_trigger_unregister(trig); + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig->name); + kfree(trig_info); + iio_put_trigger(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, + }, +}; + +static int __init iio_trig_periodic_rtc_init(void) +{ + return platform_driver_register(&iio_trig_periodic_rtc_driver); +} + +static void __exit iio_trig_periodic_rtc_exit(void) +{ + return platform_driver_unregister(&iio_trig_periodic_rtc_driver); +} + +module_init(iio_trig_periodic_rtc_init); +module_exit(iio_trig_periodic_rtc_exit); +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Periodic realtime clock trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/prtc_trigger.h b/include/linux/iio/prtc_trigger.h new file mode 100644 index 0000000..9dabc19 --- /dev/null +++ b/include/linux/iio/prtc_trigger.h @@ -0,0 +1,7 @@ +struct iio_prtc_trigger { + const char *name; +}; +struct iio_prtc_trigger_group { + int num_triggers; + struct iio_prtc_trigger *triggers; +}; -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 13/14] IIO: Proof of concept gpio trigger 2009-07-02 13:47 ` [PATCH 12/14] IIO: Periodic timer based trigger Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-02 13:47 ` [PATCH 14/14] IIO: Initial documentation Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> Simple example of how a gpio trigger driver would work. Things to be added include interupt type control (high, low). --- drivers/iio/trigger/Kconfig | 6 + drivers/iio/trigger/Makefile | 2 +- drivers/iio/trigger/iio-trig-gpio.c | 203 +++++++++++++++++++++++++++++++++++ include/linux/iio/gpio_trigger.h | 6 + 4 files changed, 216 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/trigger/iio-trig-gpio.c create mode 100644 include/linux/iio/gpio_trigger.h diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 0c73ccc..e7e1b6e 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -10,3 +10,9 @@ config IIO_PERIODIC_RTC_TRIGGER help Provides support for using periodic capable real time clocks as IIO triggers. + +config IIO_GPIO_TRIGGER + tristate "GPIO trigger" + depends on IIO && IIO_TRIGGER && GENERIC_GPIO + help + Provides support for using GPIO pins as IIO triggers. \ No newline at end of file diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index 4ae55b9..e5f96d2 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -2,4 +2,4 @@ # 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 \ No newline at end of file diff --git a/drivers/iio/trigger/iio-trig-gpio.c b/drivers/iio/trigger/iio-trig-gpio.c new file mode 100644 index 0000000..80c7a54 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-gpio.c @@ -0,0 +1,203 @@ +/* + * 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 that a fully + * fledged trigger driver. + * + * TODO: + * + * Add board config elements to allow specification of startup settings. + * Add configuration settings (irq type etc) + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/gpio_trigger.h> + +LIST_HEAD(iio_gpio_trigger_list); +DEFINE_MUTEX(iio_gpio_trigger_list_lock); + +struct iio_gpio_trigger_info { + struct mutex in_use; + int gpio; +}; +/* + * 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) +{ + iio_trigger_poll(private); + return IRQ_HANDLED; +} + +DEVICE_ATTR(name, S_IRUGO, iio_trigger_read_name, NULL); + +static struct attribute *iio_gpio_trigger_attrs[] = { + &dev_attr_name.attr, + NULL, +}; + +static const struct attribute_group iio_gpio_trigger_attr_group = { + .attrs = iio_gpio_trigger_attrs, +}; + +static int iio_gpio_trigger_probe(struct platform_device *dev) +{ + struct iio_gpio_trigger_pdata *pdata = dev->dev.platform_data; + struct iio_gpio_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + int i, irq, ret = 0; + if (!pdata) { + printk(KERN_ERR "No IIO gpio trigger platform data found\n"); + goto error_ret; + } + for (i = 0; i < pdata->num_gpios; i++) { + + trig = iio_allocate_trigger(); + 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; + } + trig->control_attrs = &iio_gpio_trigger_attr_group; + trig->private_data = trig_info; + trig_info->gpio = pdata->gpios[i]; + trig->owner = THIS_MODULE; + trig->name = kmalloc(IIO_TRIGGER_NAME_LENGTH, GFP_KERNEL); + if (!trig->name) { + ret = -ENOMEM; + goto error_free_trig_info; + } + snprintf((char *)trig->name, + IIO_TRIGGER_NAME_LENGTH, + "gpiotrig%d", + pdata->gpios[i]); + ret = gpio_request(trig_info->gpio, trig->name); + if (ret) + goto error_free_name; + + ret = gpio_direction_input(trig_info->gpio); + if (ret) + goto error_release_gpio; + + irq = gpio_to_irq(trig_info->gpio); + if (irq < 0) { + ret = irq; + goto error_release_gpio; + } + + ret = request_irq(irq, iio_gpio_trigger_poll, + IRQF_TRIGGER_RISING, + trig->name, + trig); + if (ret) + goto error_release_gpio; + + ret = iio_trigger_register(trig); + if (ret) + goto error_release_irq; + + list_add_tail(&trig->alloc_list, &iio_gpio_trigger_list); + + } + return 0; + +/* First clean up the partly allocated trigger */ +error_release_irq: + free_irq(irq, trig); +error_release_gpio: + gpio_free(trig_info->gpio); +error_free_name: + kfree(trig->name); +error_free_trig_info: + kfree(trig_info); +error_put_trigger: + iio_put_trigger(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 = trig->private_data; + iio_trigger_unregister(trig); + free_irq(gpio_to_irq(trig_info->gpio), trig); + gpio_free(trig_info->gpio); + kfree(trig->name); + kfree(trig_info); + iio_put_trigger(trig); + } + +error_ret: + return ret; +} + +static int iio_gpio_trigger_remove(struct platform_device *dev) +{ + 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 = trig->private_data; + iio_trigger_unregister(trig); + free_irq(gpio_to_irq(trig_info->gpio), trig); + gpio_free(trig_info->gpio); + kfree(trig->name); + kfree(trig_info); + iio_put_trigger(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, + }, +}; + +static int __init iio_gpio_trig_init(void) +{ + return platform_driver_register(&iio_gpio_trigger_driver); +} +module_init(iio_gpio_trig_init); + +static void __exit iio_gpio_trig_exit(void) +{ + platform_driver_unregister(&iio_gpio_trigger_driver); +} +module_exit(iio_gpio_trig_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Example gpio trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/gpio_trigger.h b/include/linux/iio/gpio_trigger.h new file mode 100644 index 0000000..25be3da --- /dev/null +++ b/include/linux/iio/gpio_trigger.h @@ -0,0 +1,6 @@ + + +struct iio_gpio_trigger_pdata { + int num_gpios; + int *gpios; +}; -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 14/14] IIO: Initial documentation 2009-07-02 13:47 ` [PATCH 13/14] IIO: Proof of concept gpio trigger Jonathan Cameron @ 2009-07-02 13:47 ` Jonathan Cameron 2009-07-03 17:12 ` [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-02 13:47 UTC (permalink / raw) To: linux-kernel Cc: chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali, Jonathan Cameron Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> This needs considerably more work, all comments / suggestions welcomed. --- Documentation/iio/device.txt | 49 ++++++++ Documentation/iio/iio_utils.h | 163 +++++++++++++++++++++++++++ Documentation/iio/lis3l02dqbuffersimple.c | 172 +++++++++++++++++++++++++++++ Documentation/iio/overview.txt | 62 ++++++++++ Documentation/iio/ring.txt | 62 ++++++++++ Documentation/iio/trigger.txt | 38 +++++++ Documentation/iio/userspace.txt | 60 ++++++++++ 7 files changed, 606 insertions(+), 0 deletions(-) create mode 100644 Documentation/iio/device.txt create mode 100644 Documentation/iio/iio_utils.h create mode 100644 Documentation/iio/lis3l02dqbuffersimple.c create mode 100644 Documentation/iio/overview.txt create mode 100644 Documentation/iio/ring.txt create mode 100644 Documentation/iio/trigger.txt create mode 100644 Documentation/iio/userspace.txt diff --git a/Documentation/iio/device.txt b/Documentation/iio/device.txt new file mode 100644 index 0000000..6916cd3 --- /dev/null +++ b/Documentation/iio/device.txt @@ -0,0 +1,49 @@ +IIO Device drivers + +This is not intended to provide a comprehensive guide to writing an +IIO device driver. For further information see the drivers within the +subsystem. + +The crucial structure for device drivers in iio is iio_dev. + +First allocate one using: + +struct iio_dev *indio_dev = iio_allocate_device(); + +The fill in the following. + +indio_dev->dev.parent + the struct device associated with the underlying hardware. + +indio_dev->num_interrupt_lines + number of event triggering hardware lines the device has. + +indio_dev->event_attrs + attributes used to enable / disable hardware events - note the + attributes are embedded in iio_event_attr structures with an + associated iio_event_handler which may or may note be shared. + If num_interrupt_lines = 0, then no need to fill this in. + +indio_dev->attrs + general attributes such as polled access to device channels. + +indio_dev->dev_data + private device specific data. + +indio_dev->driver_module + typically set to THIS_MODULE. Used to specify ownership of some + iio created resources. + +indio_dev->modes + whether direct access and / or ring buffer access is supported. + +Once these are set up, a call to iio_device_register(indio_dev), +will register the device with the iio core. + +Worth noting here is that, if a ring buffer is to be used, it can be +allocated prior to registering the device with the iio-core, but must +be registered afterwards (otherwise the whole parentage of devices +gets confused) + +On remove iio_device_unregister(indio_dev) will remove the device from +the core, and iio_free_device will clean up. diff --git a/Documentation/iio/iio_utils.h b/Documentation/iio/iio_utils.h new file mode 100644 index 0000000..2801f86 --- /dev/null +++ b/Documentation/iio/iio_utils.h @@ -0,0 +1,163 @@ +/* IIO - useful set of util functionality + * + * 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. + */ + +#define IIO_EVENT_CODE_RING_50_FULL 200 +#define IIO_EVENT_CODE_RING_75_FULL 201 +#define IIO_EVENT_CODE_RING_100_FULL 202 + +struct iio_event_data { + int id; + __s64 timestamp; +}; + + +inline char *find_ring_subelement(const char *directory, const char *subelement) +{ + DIR *dp; + const struct dirent *ent; + int pos; + char temp[100]; + char *returnstring; + dp = opendir(directory); + if (dp ==NULL) { + printf("could not directory: %s\n", directory); + return NULL; + } + while (ent = readdir(dp), ent != NULL) { + if (strcmp(ent->d_name,".") != 0 && + strcmp(ent->d_name,"..") != 0) { + if (strncmp(ent->d_name, subelement, strlen(subelement)) == 0) { + int length = sprintf(temp,"%s%s%s", directory, ent->d_name, "/"); + returnstring = malloc(length+1); + strncpy(returnstring, temp, length+1); + return returnstring; + + } + } + } + return 0; +} + + +char *find_type_by_name(const char *name, const char *type) +{ + const char *iio_dir = "/sys/class/iio/"; + const struct dirent *ent; + int cnt, pos, pos2; + + FILE *nameFile; + DIR *dp; + char thisname[100]; + char temp[100]; + + char *returnstring = NULL; + struct stat Stat; + pos = sprintf(temp,"%s", iio_dir); + dp = opendir(iio_dir); + if(dp == NULL) { + printf("No industrialio devices available"); + return NULL; + } + while (ent = readdir(dp), ent != NULL) { + cnt++; + /*reject . and .. */ + if (strcmp(ent->d_name,".") != 0 && + strcmp(ent->d_name,"..") != 0) { + /*make sure it isn't a trigger!*/ + if(strncmp(ent->d_name,type,strlen(type)) == 0) { + /* build full path to new file */ + pos2 = pos + sprintf(temp+pos,"%s/", ent->d_name); + sprintf(temp+pos2, "name"); + printf("search location %s\n", temp); + nameFile = fopen(temp, "r"); + if (!nameFile) { + sprintf(temp + pos2,"modalias", ent->d_name); + nameFile = fopen(temp, "r"); + if(!nameFile) { + printf("Failed to find a name for device\n"); + return NULL; + } + } + fscanf(nameFile, "%s", thisname); + if (strcmp(name, thisname) ==0) { + returnstring = malloc(strlen(temp) + 1); + sprintf(temp + pos2,""); + strcpy(returnstring, temp); + return returnstring; + } + fclose(nameFile); + + } + } + } +} + +int write_sysfs_int(char *filename, char *basedir, int val) +{ + int ret; + FILE *sysfsfp; + char temp[100]; + sprintf(temp, "%s%s", basedir, filename); + sysfsfp = fopen(temp, "w"); + if (sysfsfp == NULL) { + return -1; + } + fprintf(sysfsfp, "%d", val); + fclose(sysfsfp); + return 0; +} + +/** + * write_sysfs_string_and_verify() - string write, readback and verify + * @filename: name of file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: the string to write + **/ +int write_sysfs_string_and_verify(char *filename, char *basedir, char *val) +{ + int ret; + FILE *sysfsfp; + char temp[100]; + sprintf(temp, "%s%s", basedir, filename); + sysfsfp = fopen(temp, "w"); + if (sysfsfp == NULL) { + return -1; + } + fprintf(sysfsfp, "%s", val); + fclose(sysfsfp); + + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + return -1; + } + fscanf(sysfsfp, "%s", temp); + if (strcmp(temp, val) != 0) { + printf("Possible failure in string write %s to %s%s \n", + val, + basedir, + filename); + return -1; + } + return 0; +} + +int read_sysfs_posint(char *filename, char *basedir) +{ + int ret; + FILE *sysfsfp; + char temp[100]; + sprintf(temp, "%s%s", basedir, filename); + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + return -1; + } + fscanf(sysfsfp, "%d\n", &ret); + fclose(sysfsfp); + return ret; +} diff --git a/Documentation/iio/lis3l02dqbuffersimple.c b/Documentation/iio/lis3l02dqbuffersimple.c new file mode 100644 index 0000000..4f129dd --- /dev/null +++ b/Documentation/iio/lis3l02dqbuffersimple.c @@ -0,0 +1,172 @@ +/* Industrialio test ring buffer with a lis3l02dq acceleromter + * + * 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. + * + * Assumes suitable udev rules are used to create the dev nodes as named here. + */ + +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/dir.h> + +#include <linux/types.h> +#include <dirent.h> +#include "iio_util.h" + +static const char *ring_access = "/dev/iio/lis3l02dq_ring_access"; +static const char *ring_event = "/dev/iio/lis3l02dq_ring_event"; +static const char *device_name = "lis3l02dq"; +static const char *trigger_name = "lis3l02dq-dev0"; +static int NumVals = 3; +static int scan_ts = 1; +static int RingLength = 128; + +/* + * Could get this from ring bps, but only after starting the ring + * which is a bit late for it to be useful + */ +int size_from_scanmode(int numVals, int timestamp) +{ + if (numVals && timestamp) + return 16; + else if (timestamp) + return 8; + else + return numVals*2; +} + +int main(int argc, char **argv) +{ + int i, j, k, toread; + FILE *fp_ev; + int fp; + char *data; + size_t read_size; + struct iio_event_data dat; + + char *BaseDirectoryName, + *TriggerDirectoryName, + *RingBufferDirectoryName; + + BaseDirectoryName = find_type_by_name(device_name, "device"); + if (BaseDirectoryName == NULL) { + printf("Failed to find the %s \n", device_name); + return -1; + } + TriggerDirectoryName = find_type_by_name(trigger_name, "trigger"); + if (TriggerDirectoryName == NULL) { + printf("Failed to find the %s\n", trigger_name); + return -1; + } + RingBufferDirectoryName = find_ring_subelement(BaseDirectoryName, + "ring_buffer"); + if (RingBufferDirectoryName == NULL) { + printf("Failed to find ring buffer\n"); + return -1; + } + + if (write_sysfs_string_and_verify("trigger/current_trigger", + BaseDirectoryName, + (char *)trigger_name) < 0) { + printf("Failed to write current_trigger file \n"); + return -1; + } + + /* Setup ring buffer parameters */ + if (write_sysfs_int("length", RingBufferDirectoryName, + RingLength) < 0) { + printf("Failed to open the ring buffer length file \n"); + return -1; + } + + /* Enable the ring buffer */ + if (write_sysfs_int("ring_enable", RingBufferDirectoryName, 1) < 0) { + printf("Failed to open the ring buffer control file \n"); + return -1; + }; + + data = malloc(size_from_scanmode(NumVals, scan_ts)*RingLength); + if (!data) { + printf("Could not allocate space for usespace data store\n"); + return -1; + } + + /* Attempt to open non blocking the access dev */ + fp = open(ring_access, O_RDONLY | O_NONBLOCK); + if (fp == -1) { /*If it isn't there make the node */ + printf("Failed to open %s\n", ring_access); + return -1; + } + /* Attempt to open the event access dev (blocking this time) */ + fp_ev = fopen(ring_event, "rb"); + if (fp_ev == NULL) { + printf("Failed to open %s\n", ring_event); + return -1; + } + + /* Wait for events 10 times */ + for(j = 0; j < 10; j++) + { + read_size = fread(&dat, 1, sizeof(struct iio_event_data), + fp_ev); + switch(dat.id) { + case IIO_EVENT_CODE_RING_100_FULL: + toread = RingLength; + break; + case IIO_EVENT_CODE_RING_75_FULL: + toread = RingLength*3/4; + break; + case IIO_EVENT_CODE_RING_50_FULL: + toread = RingLength/2; + break; + default: + printf("Unexpecteded event code\n"); + continue; + } + read_size = read(fp, + data, + toread*size_from_scanmode(NumVals, scan_ts)); + if (read_size == -EAGAIN) { + printf("nothing available \n"); + continue; + } + + for (i = 0; + i < read_size/size_from_scanmode(NumVals, scan_ts); + i++) { + for (k = 0; k < NumVals; k++) { + __s16 val = *(__s16 *)(&data[i*size_from_scanmode(NumVals, scan_ts) + + (k)*2]); + printf("%05d ", val); + } + printf(" %lld\n", + *(__s64 *)(&data[(i+1)*size_from_scanmode(NumVals,scan_ts) + - sizeof(__s64)])); + } + } + + /* Stop the ring buffer */ + if (write_sysfs_int("ring_enable", RingBufferDirectoryName, 0) < 0) { + printf("Failed to open the ring buffer control file \n"); + return -1; + }; + + /* Disconnect from the trigger - writing something that doesn't exist.*/ + write_sysfs_string_and_verify("trigger/current_trigger", + BaseDirectoryName, "NULL"); + free(BaseDirectoryName); + free(TriggerDirectoryName); + free(RingBufferDirectoryName); + free(data); + + return 0; +} diff --git a/Documentation/iio/overview.txt b/Documentation/iio/overview.txt new file mode 100644 index 0000000..6c77945 --- /dev/null +++ b/Documentation/iio/overview.txt @@ -0,0 +1,62 @@ +Overview of IIO + +The Industrial I/O subsytem is intended to provide support for devices +that in some sense are analog to digital convertors (ADCs). As many +actual devices combine some ADCs with digital to analog convertors +(DACs) the intention is to add that functionality at a future date +(hence the name). + +The aim is to fill the gap between the somewhat similar hwmon and +input subsystems. Hwmon is very much directed at low sample rate +sensors used in applications such as fan speed control and temperature +measurement. Input is, as it's name suggests focused on input +devices. In some cases there is considerable overlap between these and +IIO. + +A typical device falling into this category would be connected via SPI +or I2C. + +Functionality of IIO + +* Basic device registration and handling. This is very similar to +hwmon with simple polled access to device channels via sysfs. + +* Event chrdevs. These are similar to input in that they provide a +route to user space for hardware triggered events. Such events include +threshold detectors, free-fall detectors and more complex action +detection. They events themselves are currently very simple with +merely an event code and a timestamp. Any data associated with the +event must be accessed via polling. Note a given device may have one +or more event channel. These events are turned on or off (if possible) +via sysfs interfaces. + +* Hardware ring buffer support. Some recent sensors have included +fifo / ring buffers on the sensor chip. These greatly reduce the load +on the host CPU by buffering relatively large numbers of data samples +based on an internal sampling clock. Examples include VTI SCA3000 +series and Analog Device ADXL345 accelerometers. Each ring buffer +typically has an event chrdev (similar to the more general ones above) +to pass on events such as buffer 50% full and an access chrdev via +which the raw data it self may be read back. + +* Trigger and software ring buffer support. In many data analysis +applications it it useful to be able to capture data based on some +external signal (trigger). These triggers might be a data ready +signal, a gpio line connected to some external system or an on +processor periodic interrupt. A single trigger many initialize data +capture or reading from a number of sensors. These triggers are +used in iio to fill software ring buffers acting in a very similar +fashion to the hardware buffers described above. + +Other documentation: + +userspace.txt - overview of ring buffer reading form userspace + +device.txt - elemennts of a typical device driver. + +trigger.txt - elements of a typical trigger driver. + +ring.txt - additional elements required for ring buffer support + + + diff --git a/Documentation/iio/ring.txt b/Documentation/iio/ring.txt new file mode 100644 index 0000000..7e03b3c --- /dev/null +++ b/Documentation/iio/ring.txt @@ -0,0 +1,62 @@ +Ring buffer support within IIO + +This document is intended as a general overview of the functionality +a ring buffer may supply and how it is specified within IIO. For more +specific information on a given ring buffer implementation, see the +comments in the source code. Note that the intention is to allow +some drivers to specify ring buffers choice at probe or runtime, but +for now the selection is hard coded within a given driver. + +A given ring buffer implementation typically embedded a struct +iio_ring_buffer and it is a pointer to this that is provided to the +IIO core. Access to the embedding structure is typically done via +container_of functions. + +struct iio_ring_buffer contains 4 function pointers +(preenable, postenable, predisable, postdisable). +These are used to perform implementation specific steps on either side +of the core changing it's current mode to indicate that the ring buffer +is enabled or disabled (along with enabling triggering etc as appropriate). + +Also in struct iio_ring_buffer is a struct iio_ring_access_funcs. +The function pointers within here are used to allow the core to handle +as much ring buffer functionality as possible. Note almost all of these +are optional. + +mark_in_use, unmark_in_use + Basically indicate that not changes should be made to the ring + buffer state that will effect the form of the data being captures + (e.g. scan elements or length) + +store_to + If possible, push data to ring buffer. + +read_last + If possible get the most recent entry from the buffer (without removal). + This provides polling like functionality whilst the ring buffering is in + use without a separate read from the device. + +rip_lots + The primary ring buffer reading function. Note that it may well not return + as much data as requested. The deadoffset is used to indicate that some + initial data in the data array is not guaranteed to be valid. + +mark_param_changed + Used to indicate that something has changed. Used in conjunction with +request_update + If parameters have changed that require reinitialization or configuration of + the ring buffer this will trigger it. + +get_bpd, set_bpd + Get/set the number of bytes for a given reading (single element, not sample set) + The value of bps (bytes per set) is created from a combination of this and the + enabled scan elements. + +get_length / set_length + Get/set the number of sample sets that may be held by the buffer. + +is_enabled + Query if ring buffer is in use +enable + Start the ring buffer. + diff --git a/Documentation/iio/trigger.txt b/Documentation/iio/trigger.txt new file mode 100644 index 0000000..650157f --- /dev/null +++ b/Documentation/iio/trigger.txt @@ -0,0 +1,38 @@ +IIO trigger drivers. + +Many triggers are provided by hardware that will also be registered as +an IIO device. Whilst this can create device specific complexities +such triggers are registered with the core in the same way as +stand-alone triggers. + +struct iio_trig *trig = iio_allocate_trigger(); + +allocates a trigger structure. The key elements to then fill in within +a driver are: + +trig->control_attrs + Any sysfs attributes needed to control parameters of the trigger + +trig->private_data + Device specific private data. + +trig->owner + Typically set to THIS_MODULE. Used to ensure correct + ownership of core allocated resources. + +trig->name + A unique name for the trigger. + +When these have been set call: + +iio_trigger_register(trig); + +to register the trigger with the core, making it available to trigger +consumers. + + +Trigger Consumers + +Currently triggers are only used for the filling of software ring +buffers and as such any device supporting INDIO_RING_TRIGGERED has the +consumer interface automatically created. diff --git a/Documentation/iio/userspace.txt b/Documentation/iio/userspace.txt new file mode 100644 index 0000000..2d3f264 --- /dev/null +++ b/Documentation/iio/userspace.txt @@ -0,0 +1,60 @@ +Userspace access to IIO + +Example, ST Microelectronics LIS3L02DQ accelerometer. + +Typical sysfs entries (pruned for clarity) + +/sys/class/iio + device0 - iio_dev related elements + name - driver specific identifier (here lis3l02dq) + accel_x - polled (or from ring) raw readout of acceleration + accel_x_gain - hardware gain (calibration) + accel_x_offset - hardware offset (calibration) + available_sampling_frequency + + available_sampling_frequency - what options are there + sampling_frequency - control of internal sampling frequency + scan_elements - controls which channels will be stored in the ring buffer + scan_en_accel_x + scan_en_accel_y + scan_en_timestamp + device - link to underlying hardware device + uevent - udev related element + + thresh - unified threshold used for detection on all axis + event_line0_sources - which events are enabled + accel_x_high - enable x axis high threshold event + accel_x_low - enable x axis low threshold event + + event_line0 - event interface + dev - major:minor for the chrdev (note major allocation dynamic) + trigger - consumer attachement + current_trigger - name based association with a trigger + ring_buffer0 - ring buffer interface + bps - byptes per sample (read only), dependant on scan element selection + length - (rw) specificy length fo software ring buffer (typically ro in hw case) + ring_enable - turn the ring on. If its the first to be enabled attached to this + trigger will also enable the trigger. + ring_access0 + dev - major:minor for ring buffer access chrdev + ring_event_line0 + dev - major:minor for ring buffer event chrdev + + trigger0 - data ready trigger elements + name - unqiue name of trigger + +Udev will create the following entries under /dev by default: + +ring_access0 - ring access chrdev +ring_event0 - ring event chrdev +event_line0 - general event chrdev. + +For the example code we assume the following rules have been used to ensure +unique and consistent naming of these for the lis3l02dq in question: + +KERNEL="ring_event_line*", ID="spi1.0", DRIVER="lis3l02dq", NAME="iio/lis3l02dq_ring_event" +KERNEL="event_line*", ID="spi1.0", DRIVER="lis3l02dq", NAME="iio/lis3l02dq_event" +KERNEL="ring_access*", ID="spi1.0", DRIVER="lis3l02dq", NAME="iio/lis3l02dq_ring_access" + +The files, lis3l02dqbuffersimple.c and iio_util.h in this directory provide an example +of how to use the ring buffer and event interfaces. -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 2009-07-02 13:47 ` [PATCH 14/14] IIO: Initial documentation Jonathan Cameron @ 2009-07-03 17:12 ` Jonathan Cameron 2009-07-03 19:13 ` Jean Delvare 0 siblings, 1 reply; 18+ messages in thread From: Jonathan Cameron @ 2009-07-03 17:12 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-kernel, chrisv, daniel, iws, dmitry.torokhov, hmh, david-b, khali Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> I will roll these into the original patches at some point, but for now they are here. Thanks to David Brownell for spotting this. --- drivers/iio/adc/Kconfig | 2 +- drivers/iio/light/Kconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index cb773d2..ab2066c 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -6,7 +6,7 @@ comment "Analog to digital convertors" config MAX1363 tristate "MAXIM max1363 ADC driver" - depends on IIO + depends on IIO && I2C help Say yes here to build support for many MAXIM i2c analog to digital convertors (ADC). (max1361, max1362, max1363, max1364, max1136, diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 620e249..62d502c 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -6,7 +6,7 @@ comment "Light sensors" config TSL2561 tristate "TAOS TSL2561 light-to-digital convertor" - depends on IIO + depends on IIO && I2C help Say yes bere to build support for the TAOS light to digital convertor. This chip has two light sensors. One is broadband -- 1.6.2 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 2009-07-03 17:12 ` [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 Jonathan Cameron @ 2009-07-03 19:13 ` Jean Delvare 2009-07-04 12:22 ` Jonathan Cameron 0 siblings, 1 reply; 18+ messages in thread From: Jean Delvare @ 2009-07-03 19:13 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-kernel, chrisv, daniel, iws, dmitry.torokhov, hmh, david-b On Fri, 03 Jul 2009 17:12:13 +0000, Jonathan Cameron wrote: > Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> > > I will roll these into the original patches at some point, but > for now they are here. > > Thanks to David Brownell for spotting this. > --- > drivers/iio/adc/Kconfig | 2 +- > drivers/iio/light/Kconfig | 2 +- > 2 files changed, 2 insertions(+), 2 deletions(-) > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index cb773d2..ab2066c 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -6,7 +6,7 @@ comment "Analog to digital convertors" > > config MAX1363 > tristate "MAXIM max1363 ADC driver" > - depends on IIO > + depends on IIO && I2C > help > Say yes here to build support for many MAXIM i2c analog to digital > convertors (ADC). (max1361, max1362, max1363, max1364, max1136, > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index 620e249..62d502c 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -6,7 +6,7 @@ comment "Light sensors" > > config TSL2561 > tristate "TAOS TSL2561 light-to-digital convertor" > - depends on IIO > + depends on IIO && I2C > help > Say yes bere to build support for the TAOS light to digital > convertor. This chip has two light sensors. One is broadband Unrelated suggestion: you could surround all the driver configuration entries with a big if IIO (...) endif # IIO This would save you the hassle of adding "depends on IIO" to every driver. Many subsystems have resorted to this technique by now. -- Jean Delvare ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 2009-07-03 19:13 ` Jean Delvare @ 2009-07-04 12:22 ` Jonathan Cameron 0 siblings, 0 replies; 18+ messages in thread From: Jonathan Cameron @ 2009-07-04 12:22 UTC (permalink / raw) To: Jean Delvare Cc: linux-kernel, chrisv, daniel, iws, dmitry.torokhov, hmh, david-b Jean Delvare wrote: > On Fri, 03 Jul 2009 17:12:13 +0000, Jonathan Cameron wrote: >> Signed-off-by: Jonathan Cameron <jic23@cam.ac.uk> >> >> I will roll these into the original patches at some point, but >> for now they are here. >> >> Thanks to David Brownell for spotting this. >> --- >> drivers/iio/adc/Kconfig | 2 +- >> drivers/iio/light/Kconfig | 2 +- >> 2 files changed, 2 insertions(+), 2 deletions(-) >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index cb773d2..ab2066c 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -6,7 +6,7 @@ comment "Analog to digital convertors" >> >> config MAX1363 >> tristate "MAXIM max1363 ADC driver" >> - depends on IIO >> + depends on IIO && I2C >> help >> Say yes here to build support for many MAXIM i2c analog to digital >> convertors (ADC). (max1361, max1362, max1363, max1364, max1136, >> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig >> index 620e249..62d502c 100644 >> --- a/drivers/iio/light/Kconfig >> +++ b/drivers/iio/light/Kconfig >> @@ -6,7 +6,7 @@ comment "Light sensors" >> >> config TSL2561 >> tristate "TAOS TSL2561 light-to-digital convertor" >> - depends on IIO >> + depends on IIO && I2C >> help >> Say yes bere to build support for the TAOS light to digital >> convertor. This chip has two light sensors. One is broadband > > Unrelated suggestion: you could surround all the driver configuration > entries with a big > > if IIO > > (...) > > endif # IIO > > This would save you the hassle of adding "depends on IIO" to every > driver. Many subsystems have resorted to this technique by now. > Thanks Jean, I hadn't realized you could do that. Jonathan ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2009-07-04 12:23 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2009-07-02 13:47 [PATCH 00/14] IIO V4. Industrial I/O Subsystem Jonathan Cameron 2009-07-02 13:47 ` [PATCH 01/14] IIO: core support for device registration and management Jonathan Cameron 2009-07-02 13:47 ` [PATCH 02/14] IIO: max1363 ADC driver Jonathan Cameron 2009-07-02 13:47 ` [PATCH 03/14] IIO: tsl2561 digital light sensor core support Jonathan Cameron 2009-07-02 13:47 ` [PATCH 04/14] IIO: lis3l02dq accelerometer " Jonathan Cameron 2009-07-02 13:47 ` [PATCH 05/14] IIO: kxsd9 accelerometer minimal support Jonathan Cameron 2009-07-02 13:47 ` [PATCH 06/14] IIO: Add generic ring buffer support to the IIO core Jonathan Cameron 2009-07-02 13:47 ` [PATCH 07/14] IIO: VTI sca3000 series accelerometer driver (spi) Jonathan Cameron 2009-07-02 13:47 ` [PATCH 08/14] IIO: Trigger support added to core Jonathan Cameron 2009-07-02 13:47 ` [PATCH 09/14] IIO: Ring buffer: Initial pass at rarely locked ring buffer Jonathan Cameron 2009-07-02 13:47 ` [PATCH 10/14] IIO: lis3l02dq ring buffer and data ready trigger support Jonathan Cameron 2009-07-02 13:47 ` [PATCH 11/14] IIO: max1363 add software ring buffer support using ring_sw Jonathan Cameron 2009-07-02 13:47 ` [PATCH 12/14] IIO: Periodic timer based trigger Jonathan Cameron 2009-07-02 13:47 ` [PATCH 13/14] IIO: Proof of concept gpio trigger Jonathan Cameron 2009-07-02 13:47 ` [PATCH 14/14] IIO: Initial documentation Jonathan Cameron 2009-07-03 17:12 ` [PATCH] IIO: Add missing I2C dependencies for max1363 and tsl2561 Jonathan Cameron 2009-07-03 19:13 ` Jean Delvare 2009-07-04 12:22 ` Jonathan Cameron
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox