iio: industrialio-core: iio_write_channel_info accept IIO_VAL_INT_PLUS_NANO
[cascardo/linux.git] / drivers / staging / iio / industrialio-core.c
index 1795ee1..744153e 100644 (file)
@@ -16,7 +16,6 @@
 #include <linux/err.h>
 #include <linux/device.h>
 #include <linux/fs.h>
-#include <linux/interrupt.h>
 #include <linux/poll.h>
 #include <linux/sched.h>
 #include <linux/wait.h>
@@ -44,27 +43,55 @@ struct bus_type iio_bus_type = {
 };
 EXPORT_SYMBOL(iio_bus_type);
 
-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);
+static const char * const iio_chan_type_name_spec_shared[] = {
+       [IIO_TIMESTAMP] = "timestamp",
+       [IIO_ACCEL] = "accel",
+       [IIO_IN] = "in",
+       [IIO_CURRENT] = "current",
+       [IIO_POWER] = "power",
+       [IIO_IN_DIFF] = "in-in",
+       [IIO_GYRO] = "gyro",
+       [IIO_TEMP] = "temp",
+       [IIO_MAGN] = "magn",
+       [IIO_INCLI] = "incli",
+       [IIO_ROT] = "rot",
+       [IIO_INTENSITY] = "intensity",
+       [IIO_LIGHT] = "illuminance",
+       [IIO_ANGL] = "angl",
+};
+
+static const char * const iio_chan_type_name_spec_complex[] = {
+       [IIO_IN_DIFF] = "in%d-in%d",
+};
 
-/* Used both in the interrupt line put events and the ring buffer ones */
+static const char * const iio_modifier_names_light[] = {
+       [IIO_MOD_LIGHT_BOTH] = "both",
+       [IIO_MOD_LIGHT_IR] = "ir",
+};
 
-/* 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)
+static const char * const iio_modifier_names_axial[] = {
+       [IIO_MOD_X] = "x",
+       [IIO_MOD_Y] = "y",
+       [IIO_MOD_Z] = "z",
+};
+
+/* relies on pairs of these shared then separate */
+static const char * const iio_chan_info_postfix[] = {
+       [IIO_CHAN_INFO_SCALE_SHARED/2] = "scale",
+       [IIO_CHAN_INFO_OFFSET_SHARED/2] = "offset",
+       [IIO_CHAN_INFO_CALIBSCALE_SHARED/2] = "calibscale",
+       [IIO_CHAN_INFO_CALIBBIAS_SHARED/2] = "calibbias",
+       [IIO_CHAN_INFO_PEAK_SHARED/2] = "peak_raw",
+       [IIO_CHAN_INFO_PEAK_SCALE_SHARED/2] = "peak_scale",
+};
+
+int iio_push_event(struct iio_dev *dev_info,
+                  int ev_line,
+                  int ev_code,
+                  s64 timestamp)
 {
+       struct iio_event_interface *ev_int
+               = &dev_info->event_interfaces[ev_line];
        struct iio_detected_event_list *ev;
        int ret = 0;
 
@@ -83,11 +110,8 @@ int __iio_push_event(struct iio_event_interface *ev_int,
                }
                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);
+               list_add_tail(&ev->list, &ev_int->det_events);
                ev_int->current_events++;
                mutex_unlock(&ev_int->event_list_lock);
                wake_up_interruptible(&ev_int->wait);
@@ -97,85 +121,8 @@ int __iio_push_event(struct iio_event_interface *ev_int,
 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 */
-static 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();
-       list_for_each_entry(p, &int_info->ev_list, list) {
-               disable_irq_nosync(irq);
-               p->handler(dev_info, 1, time_ns, !(p->refcount > 1));
-       }
-       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,
@@ -186,54 +133,6 @@ ssize_t iio_read_const_attr(struct device *dev,
 }
 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);
 
 static ssize_t iio_event_chrdev_read(struct file *filep,
                                     char __user *buf,
@@ -246,7 +145,7 @@ static ssize_t iio_event_chrdev_read(struct file *filep,
        size_t len;
 
        mutex_lock(&ev_int->event_list_lock);
-       if (list_empty(&ev_int->det_events.list)) {
+       if (list_empty(&ev_int->det_events)) {
                if (filep->f_flags & O_NONBLOCK) {
                        ret = -EAGAIN;
                        goto error_mutex_unlock;
@@ -255,14 +154,14 @@ static ssize_t iio_event_chrdev_read(struct file *filep,
                /* Blocking on device; waiting for something to be there */
                ret = wait_event_interruptible(ev_int->wait,
                                               !list_empty(&ev_int
-                                                          ->det_events.list));
+                                                          ->det_events));
                if (ret)
                        goto error_ret;
                /* Single access device so no one else can get the data */
                mutex_lock(&ev_int->event_list_lock);
        }
 
-       el = list_first_entry(&ev_int->det_events.list,
+       el = list_first_entry(&ev_int->det_events,
                              struct iio_detected_event_list,
                              list);
        len = sizeof el->ev;
@@ -273,18 +172,6 @@ static ssize_t iio_event_chrdev_read(struct file *filep,
        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 event 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.
-        */
-       if (el->shared_pointer) {
-               spin_lock(&el->shared_pointer->lock);
-               (el->shared_pointer->ev_p) = NULL;
-               spin_unlock(&el->shared_pointer->lock);
-       }
        kfree(el);
 
        return len;
@@ -309,7 +196,7 @@ static int iio_event_chrdev_release(struct inode *inode, struct file *filep)
         * clear out any awaiting events. The mask will prevent
         * any new __iio_push_event calls running.
         */
-       list_for_each_entry_safe(el, t, &ev_int->det_events.list, list) {
+       list_for_each_entry_safe(el, t, &ev_int->det_events, list) {
                list_del(&el->list);
                kfree(el);
        }
@@ -381,10 +268,11 @@ void iio_device_free_chrdev_minor(int val)
        spin_unlock(&iio_ida_lock);
 }
 
-int iio_setup_ev_int(struct iio_event_interface *ev_int,
-                    const char *name,
-                    struct module *owner,
-                    struct device *dev)
+static int iio_setup_ev_int(struct iio_event_interface *ev_int,
+                           const char *dev_name,
+                           int index,
+                           struct module *owner,
+                           struct device *dev)
 {
        int ret, minor;
 
@@ -399,7 +287,7 @@ int iio_setup_ev_int(struct iio_event_interface *ev_int,
                goto error_device_put;
        }
        ev_int->dev.devt = MKDEV(MAJOR(iio_devt), minor);
-       dev_set_name(&ev_int->dev, "%s", name);
+       dev_set_name(&ev_int->dev, "%s:event%d", dev_name, index);
 
        ret = device_add(&ev_int->dev);
        if (ret)
@@ -412,7 +300,7 @@ int iio_setup_ev_int(struct iio_event_interface *ev_int,
        /* discussion point - make this variable? */
        ev_int->max_events = 10;
        ev_int->current_events = 0;
-       INIT_LIST_HEAD(&ev_int->det_events.list);
+       INIT_LIST_HEAD(&ev_int->det_events);
        init_waitqueue_head(&ev_int->wait);
        ev_int->handler.private = ev_int;
        ev_int->handler.flags = 0;
@@ -433,7 +321,7 @@ error_device_put:
        return ret;
 }
 
-void iio_free_ev_int(struct iio_event_interface *ev_int)
+static void iio_free_ev_int(struct iio_event_interface *ev_int)
 {
        device_unregister(&ev_int->dev);
        put_device(&ev_int->dev);
@@ -488,24 +376,417 @@ static void __exit iio_exit(void)
        bus_unregister(&iio_bus_type);
 }
 
-static int iio_device_register_sysfs(struct iio_dev *dev_info)
+static ssize_t iio_read_channel_info(struct device *dev,
+                                    struct device_attribute *attr,
+                                    char *buf)
 {
-       int ret = 0;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int val, val2;
+       int ret = indio_dev->info->read_raw(indio_dev, this_attr->c,
+                                           &val, &val2, this_attr->address);
 
-       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");
+       if (ret < 0)
+               return ret;
+
+       if (ret == IIO_VAL_INT)
+               return sprintf(buf, "%d\n", val);
+       else if (ret == IIO_VAL_INT_PLUS_MICRO) {
+               if (val2 < 0)
+                       return sprintf(buf, "-%d.%06u\n", val, -val2);
+               else
+                       return sprintf(buf, "%d.%06u\n", val, val2);
+       } else if (ret == IIO_VAL_INT_PLUS_NANO) {
+               if (val2 < 0)
+                       return sprintf(buf, "-%d.%09u\n", val, -val2);
+               else
+                       return sprintf(buf, "%d.%09u\n", val, val2);
+       } else
+               return 0;
+}
+
+static ssize_t iio_write_channel_info(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf,
+                                     size_t len)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int ret, integer = 0, fract = 0, fract_mult = 100000;
+       bool integer_part = true, negative = false;
+
+       /* Assumes decimal - precision based on number of digits */
+       if (!indio_dev->info->write_raw)
+               return -EINVAL;
+
+       if (indio_dev->info->write_raw_get_fmt)
+               switch (indio_dev->info->write_raw_get_fmt(indio_dev,
+                       this_attr->c, this_attr->address)) {
+               case IIO_VAL_INT_PLUS_MICRO:
+                       fract_mult = 100000;
+                       break;
+               case IIO_VAL_INT_PLUS_NANO:
+                       fract_mult = 100000000;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+       if (buf[0] == '-') {
+               negative = true;
+               buf++;
+       }
+
+       while (*buf) {
+               if ('0' <= *buf && *buf <= '9') {
+                       if (integer_part)
+                               integer = integer*10 + *buf - '0';
+                       else {
+                               fract += fract_mult*(*buf - '0');
+                               if (fract_mult == 1)
+                                       break;
+                               fract_mult /= 10;
+                       }
+               } else if (*buf == '\n') {
+                       if (*(buf + 1) == '\0')
+                               break;
+                       else
+                               return -EINVAL;
+               } else if (*buf == '.') {
+                       integer_part = false;
+               } else {
+                       return -EINVAL;
+               }
+               buf++;
+       }
+       if (negative) {
+               if (integer)
+                       integer = -integer;
+               else
+                       fract = -fract;
+       }
+
+       ret = indio_dev->info->write_raw(indio_dev, this_attr->c,
+                                        integer, fract, this_attr->address);
+       if (ret)
+               return ret;
+
+       return len;
+}
+
+static int __iio_build_postfix(struct iio_chan_spec const *chan,
+                              bool generic,
+                              const char *postfix,
+                              char **result)
+{
+       char *all_post;
+       /* 3 options - generic, extend_name, modified - if generic, extend_name
+       * and modified cannot apply.*/
+
+       if (generic || (!chan->modified && !chan->extend_name)) {
+               all_post = kasprintf(GFP_KERNEL, "%s", postfix);
+       } else if (chan->modified) {
+               const char *intermediate;
+               switch (chan->type) {
+               case IIO_INTENSITY:
+                       intermediate
+                               = iio_modifier_names_light[chan->channel2];
+                       break;
+               case IIO_ACCEL:
+               case IIO_GYRO:
+               case IIO_MAGN:
+               case IIO_INCLI:
+               case IIO_ROT:
+               case IIO_ANGL:
+                       intermediate
+                               = iio_modifier_names_axial[chan->channel2];
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               if (chan->extend_name)
+                       all_post = kasprintf(GFP_KERNEL, "%s_%s_%s",
+                                            intermediate,
+                                            chan->extend_name,
+                                            postfix);
+               else
+                       all_post = kasprintf(GFP_KERNEL, "%s_%s",
+                                            intermediate,
+                                            postfix);
+       } else
+               all_post = kasprintf(GFP_KERNEL, "%s_%s", chan->extend_name,
+                                    postfix);
+       if (all_post == NULL)
+               return -ENOMEM;
+       *result = all_post;
+       return 0;
+}
+
+int __iio_device_attr_init(struct device_attribute *dev_attr,
+                          const char *postfix,
+                          struct iio_chan_spec const *chan,
+                          ssize_t (*readfunc)(struct device *dev,
+                                              struct device_attribute *attr,
+                                              char *buf),
+                          ssize_t (*writefunc)(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf,
+                                               size_t len),
+                          bool generic)
+{
+       int ret;
+       char *name_format, *full_postfix;
+       sysfs_attr_init(&dev_attr->attr);
+       ret = __iio_build_postfix(chan, generic, postfix, &full_postfix);
+       if (ret)
                goto error_ret;
+
+       /* Special case for types that uses both channel numbers in naming */
+       if (chan->type == IIO_IN_DIFF && !generic)
+               name_format
+                       = kasprintf(GFP_KERNEL, "%s_%s",
+                                   iio_chan_type_name_spec_complex[chan->type],
+                                   full_postfix);
+       else if (generic || !chan->indexed)
+               name_format
+                       = kasprintf(GFP_KERNEL, "%s_%s",
+                                   iio_chan_type_name_spec_shared[chan->type],
+                                   full_postfix);
+       else
+               name_format
+                       = kasprintf(GFP_KERNEL, "%s%d_%s",
+                                   iio_chan_type_name_spec_shared[chan->type],
+                                   chan->channel,
+                                   full_postfix);
+
+       if (name_format == NULL) {
+               ret = -ENOMEM;
+               goto error_free_full_postfix;
+       }
+       dev_attr->attr.name = kasprintf(GFP_KERNEL,
+                                       name_format,
+                                       chan->channel,
+                                       chan->channel2);
+       if (dev_attr->attr.name == NULL) {
+               ret = -ENOMEM;
+               goto error_free_name_format;
+       }
+
+       if (readfunc) {
+               dev_attr->attr.mode |= S_IRUGO;
+               dev_attr->show = readfunc;
+       }
+
+       if (writefunc) {
+               dev_attr->attr.mode |= S_IWUSR;
+               dev_attr->store = writefunc;
        }
+       kfree(name_format);
+       kfree(full_postfix);
+
+       return 0;
 
+error_free_name_format:
+       kfree(name_format);
+error_free_full_postfix:
+       kfree(full_postfix);
 error_ret:
        return ret;
 }
 
+void __iio_device_attr_deinit(struct device_attribute *dev_attr)
+{
+       kfree(dev_attr->attr.name);
+}
+
+int __iio_add_chan_devattr(const char *postfix,
+                          const char *group,
+                          struct iio_chan_spec const *chan,
+                          ssize_t (*readfunc)(struct device *dev,
+                                              struct device_attribute *attr,
+                                              char *buf),
+                          ssize_t (*writefunc)(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf,
+                                               size_t len),
+                          int mask,
+                          bool generic,
+                          struct device *dev,
+                          struct list_head *attr_list)
+{
+       int ret;
+       struct iio_dev_attr *iio_attr, *t;
+
+       iio_attr = kzalloc(sizeof *iio_attr, GFP_KERNEL);
+       if (iio_attr == NULL) {
+               ret = -ENOMEM;
+               goto error_ret;
+       }
+       ret = __iio_device_attr_init(&iio_attr->dev_attr,
+                                    postfix, chan,
+                                    readfunc, writefunc, generic);
+       if (ret)
+               goto error_iio_dev_attr_free;
+       iio_attr->c = chan;
+       iio_attr->address = mask;
+       list_for_each_entry(t, attr_list, l)
+               if (strcmp(t->dev_attr.attr.name,
+                          iio_attr->dev_attr.attr.name) == 0) {
+                       if (!generic)
+                               dev_err(dev, "tried to double register : %s\n",
+                                       t->dev_attr.attr.name);
+                       ret = -EBUSY;
+                       goto error_device_attr_deinit;
+               }
+
+       ret = sysfs_add_file_to_group(&dev->kobj,
+                                     &iio_attr->dev_attr.attr, group);
+       if (ret < 0)
+               goto error_device_attr_deinit;
+
+       list_add(&iio_attr->l, attr_list);
+
+       return 0;
+
+error_device_attr_deinit:
+       __iio_device_attr_deinit(&iio_attr->dev_attr);
+error_iio_dev_attr_free:
+       kfree(iio_attr);
+error_ret:
+       return ret;
+}
+
+static int iio_device_add_channel_sysfs(struct iio_dev *dev_info,
+                                       struct iio_chan_spec const *chan)
+{
+       int ret, i;
+
+
+       if (chan->channel < 0)
+               return 0;
+       if (chan->processed_val)
+               ret = __iio_add_chan_devattr("input", NULL, chan,
+                                            &iio_read_channel_info,
+                                            NULL,
+                                            0,
+                                            0,
+                                            &dev_info->dev,
+                                            &dev_info->channel_attr_list);
+       else
+               ret = __iio_add_chan_devattr("raw", NULL, chan,
+                                            &iio_read_channel_info,
+                                            NULL,
+                                            0,
+                                            0,
+                                            &dev_info->dev,
+                                            &dev_info->channel_attr_list);
+       if (ret)
+               goto error_ret;
+
+       for_each_set_bit(i, &chan->info_mask, sizeof(long)*8) {
+               ret = __iio_add_chan_devattr(iio_chan_info_postfix[i/2],
+                                            NULL, chan,
+                                            &iio_read_channel_info,
+                                            &iio_write_channel_info,
+                                            (1 << i),
+                                            !(i%2),
+                                            &dev_info->dev,
+                                            &dev_info->channel_attr_list);
+               if (ret == -EBUSY && (i%2 == 0)) {
+                       ret = 0;
+                       continue;
+               }
+               if (ret < 0)
+                       goto error_ret;
+       }
+error_ret:
+       return ret;
+}
+
+static void iio_device_remove_and_free_read_attr(struct iio_dev *dev_info,
+                                                struct iio_dev_attr *p)
+{
+       sysfs_remove_file_from_group(&dev_info->dev.kobj,
+                                    &p->dev_attr.attr, NULL);
+       kfree(p->dev_attr.attr.name);
+       kfree(p);
+}
+
+static ssize_t iio_show_dev_name(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       return sprintf(buf, "%s\n", indio_dev->name);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, iio_show_dev_name, NULL);
+
+static int iio_device_register_sysfs(struct iio_dev *dev_info)
+{
+       int i, ret = 0;
+       struct iio_dev_attr *p, *n;
+
+       if (dev_info->info->attrs) {
+               ret = sysfs_create_group(&dev_info->dev.kobj,
+                                        dev_info->info->attrs);
+               if (ret) {
+                       dev_err(dev_info->dev.parent,
+                               "Failed to register sysfs hooks\n");
+                       goto error_ret;
+               }
+       }
+
+       /*
+        * New channel registration method - relies on the fact a group does
+        *  not need to be initialized if it is name is NULL.
+        */
+       INIT_LIST_HEAD(&dev_info->channel_attr_list);
+       if (dev_info->channels)
+               for (i = 0; i < dev_info->num_channels; i++) {
+                       ret = iio_device_add_channel_sysfs(dev_info,
+                                                          &dev_info
+                                                          ->channels[i]);
+                       if (ret < 0)
+                               goto error_clear_attrs;
+               }
+       if (dev_info->name) {
+               ret = sysfs_add_file_to_group(&dev_info->dev.kobj,
+                                             &dev_attr_name.attr,
+                                             NULL);
+               if (ret)
+                       goto error_clear_attrs;
+       }
+       return 0;
+
+error_clear_attrs:
+       list_for_each_entry_safe(p, n,
+                                &dev_info->channel_attr_list, l) {
+               list_del(&p->l);
+               iio_device_remove_and_free_read_attr(dev_info, p);
+       }
+       if (dev_info->info->attrs)
+               sysfs_remove_group(&dev_info->dev.kobj, dev_info->info->attrs);
+error_ret:
+       return ret;
+
+}
+
 static void iio_device_unregister_sysfs(struct iio_dev *dev_info)
 {
-       sysfs_remove_group(&dev_info->dev.kobj, dev_info->attrs);
+
+       struct iio_dev_attr *p, *n;
+       if (dev_info->name)
+               sysfs_remove_file_from_group(&dev_info->dev.kobj,
+                                            &dev_attr_name.attr,
+                                            NULL);
+       list_for_each_entry_safe(p, n, &dev_info->channel_attr_list, l) {
+               list_del(&p->l);
+               iio_device_remove_and_free_read_attr(dev_info, p);
+       }
+
+       if (dev_info->info->attrs)
+               sysfs_remove_group(&dev_info->dev.kobj, dev_info->info->attrs);
 }
 
 /* Return a negative errno on failure */
@@ -538,48 +819,209 @@ void iio_free_ida_val(struct ida *this_ida, int id)
 }
 EXPORT_SYMBOL(iio_free_ida_val);
 
-static int iio_device_register_id(struct iio_dev *dev_info,
-                                 struct ida *this_ida)
+static const char * const iio_ev_type_text[] = {
+       [IIO_EV_TYPE_THRESH] = "thresh",
+       [IIO_EV_TYPE_MAG] = "mag",
+       [IIO_EV_TYPE_ROC] = "roc"
+};
+
+static const char * const iio_ev_dir_text[] = {
+       [IIO_EV_DIR_EITHER] = "either",
+       [IIO_EV_DIR_RISING] = "rising",
+       [IIO_EV_DIR_FALLING] = "falling"
+};
+
+static ssize_t iio_ev_state_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf,
+                                 size_t len)
 {
-       dev_info->id = iio_get_new_ida_val(&iio_ida);
-       if (dev_info->id < 0)
-               return dev_info->id;
-       return 0;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int ret;
+       bool val;
+
+       ret = strtobool(buf, &val);
+       if (ret < 0)
+               return ret;
+
+       ret = indio_dev->info->write_event_config(indio_dev,
+                                                 this_attr->address,
+                                                 val);
+       return (ret < 0) ? ret : len;
 }
 
-static void iio_device_unregister_id(struct iio_dev *dev_info)
+static ssize_t iio_ev_state_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
 {
-       iio_free_ida_val(&iio_ida, dev_info->id);
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int val = indio_dev->info->read_event_config(indio_dev,
+                                                    this_attr->address);
+
+       if (val < 0)
+               return val;
+       else
+               return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t iio_ev_value_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       int val, ret;
+
+       ret = indio_dev->info->read_event_value(indio_dev,
+                                               this_attr->address, &val);
+       if (ret < 0)
+               return ret;
+
+       return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t iio_ev_value_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf,
+                                 size_t len)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       unsigned long val;
+       int ret;
+
+       ret = strict_strtoul(buf, 10, &val);
+       if (ret)
+               return ret;
+
+       ret = indio_dev->info->write_event_value(indio_dev, this_attr->address,
+                                                val);
+       if (ret < 0)
+               return ret;
+
+       return len;
+}
+
+static int iio_device_add_event_sysfs(struct iio_dev *dev_info,
+                                     struct iio_chan_spec const *chan)
+{
+
+       int ret = 0, i, mask;
+       char *postfix;
+       if (!chan->event_mask)
+               return 0;
+
+       for_each_set_bit(i, &chan->event_mask, sizeof(chan->event_mask)*8) {
+               postfix = kasprintf(GFP_KERNEL, "%s_%s_en",
+                                   iio_ev_type_text[i/IIO_EV_TYPE_MAX],
+                                   iio_ev_dir_text[i%IIO_EV_TYPE_MAX]);
+               if (postfix == NULL) {
+                       ret = -ENOMEM;
+                       goto error_ret;
+               }
+               switch (chan->type) {
+                       /* Switch this to a table at some point */
+               case IIO_IN:
+                       mask = IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
+                                                   i/IIO_EV_TYPE_MAX,
+                                                   i%IIO_EV_TYPE_MAX);
+                       break;
+               case IIO_ACCEL:
+                       mask = IIO_MOD_EVENT_CODE(chan->type, 0, chan->channel,
+                                                 i/IIO_EV_TYPE_MAX,
+                                                 i%IIO_EV_TYPE_MAX);
+                       break;
+               case IIO_IN_DIFF:
+                       mask = IIO_MOD_EVENT_CODE(chan->type, chan->channel,
+                                                 chan->channel2,
+                                                 i/IIO_EV_TYPE_MAX,
+                                                 i%IIO_EV_TYPE_MAX);
+                       break;
+               default:
+                       printk(KERN_INFO "currently unhandled type of event\n");
+               }
+               ret = __iio_add_chan_devattr(postfix,
+                                            NULL,
+                                            chan,
+                                            &iio_ev_state_show,
+                                            iio_ev_state_store,
+                                            mask,
+                                            /*HACK. - limits us to one
+                                              event interface - fix by
+                                              extending the bitmask - but
+                                              how far*/
+                                            0,
+                                            &dev_info->event_interfaces[0].dev,
+                                            &dev_info->event_interfaces[0].
+                                            dev_attr_list);
+               kfree(postfix);
+               if (ret)
+                       goto error_ret;
+
+               postfix = kasprintf(GFP_KERNEL, "%s_%s_value",
+                                   iio_ev_type_text[i/IIO_EV_TYPE_MAX],
+                                   iio_ev_dir_text[i%IIO_EV_TYPE_MAX]);
+               if (postfix == NULL) {
+                       ret = -ENOMEM;
+                       goto error_ret;
+               }
+               ret = __iio_add_chan_devattr(postfix, NULL, chan,
+                                            iio_ev_value_show,
+                                            iio_ev_value_store,
+                                            mask,
+                                            0,
+                                            &dev_info->event_interfaces[0]
+                                            .dev,
+                                            &dev_info->event_interfaces[0]
+                                            .dev_attr_list);
+               kfree(postfix);
+               if (ret)
+                       goto error_ret;
+
+       }
+
+error_ret:
+       return ret;
+}
+
+static inline void __iio_remove_all_event_sysfs(struct iio_dev *dev_info,
+                                               const char *groupname,
+                                               int num)
+{
+       struct iio_dev_attr *p, *n;
+       list_for_each_entry_safe(p, n,
+                                &dev_info->event_interfaces[num].
+                                dev_attr_list, l) {
+               sysfs_remove_file_from_group(&dev_info
+                                            ->event_interfaces[num].dev.kobj,
+                                            &p->dev_attr.attr,
+                                            groupname);
+               kfree(p->dev_attr.attr.name);
+               kfree(p);
+       }
 }
 
 static inline int __iio_add_event_config_attrs(struct iio_dev *dev_info, int i)
 {
+       int j;
        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);
+       INIT_LIST_HEAD(&dev_info->event_interfaces[0].dev_attr_list);
+       /* Dynically created from the channels array */
+       if (dev_info->channels) {
+               for (j = 0; j < dev_info->num_channels; j++) {
+                       ret = iio_device_add_event_sysfs(dev_info,
+                                                        &dev_info
+                                                        ->channels[j]);
                        if (ret)
-                               goto error_ret;
-                       attrp++;
+                               goto error_clear_attrs;
                }
        }
        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++;
-       }
+error_clear_attrs:
+       __iio_remove_all_event_sysfs(dev_info, NULL, i);
 
        return ret;
 }
@@ -587,20 +1029,7 @@ error_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++;
-               }
-       }
-
+       __iio_remove_all_event_sysfs(dev_info, NULL, i);
        return 0;
 }
 
@@ -608,39 +1037,23 @@ static int iio_device_register_eventset(struct iio_dev *dev_info)
 {
        int ret = 0, i, j;
 
-       if (dev_info->num_interrupt_lines == 0)
+       if (dev_info->info->num_interrupt_lines == 0)
                return 0;
 
        dev_info->event_interfaces =
                kzalloc(sizeof(struct iio_event_interface)
-                       *dev_info->num_interrupt_lines,
+                       *dev_info->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;
-
-               snprintf(dev_info->event_interfaces[i]._name, 20,
-                        "%s:event%d",
-                        dev_name(&dev_info->dev),
-                        i);
-
+       for (i = 0; i < dev_info->info->num_interrupt_lines; i++) {
                ret = iio_setup_ev_int(&dev_info->event_interfaces[i],
-                                      (const char *)(dev_info
-                                                     ->event_interfaces[i]
-                                                     ._name),
-                                      dev_info->driver_module,
+                                      dev_name(&dev_info->dev),
+                                      i,
+                                      dev_info->info->driver_module,
                                       &dev_info->dev);
                if (ret) {
                        dev_err(&dev_info->dev,
@@ -650,10 +1063,13 @@ static int iio_device_register_eventset(struct iio_dev *dev_info)
 
                dev_set_drvdata(&dev_info->event_interfaces[i].dev,
                                (void *)dev_info);
-               ret = sysfs_create_group(&dev_info
-                                       ->event_interfaces[i]
-                                       .dev.kobj,
-                                       &dev_info->event_attrs[i]);
+
+               if (dev_info->info->event_attrs != NULL)
+                       ret = sysfs_create_group(&dev_info
+                                                ->event_interfaces[i]
+                                                .dev.kobj,
+                                                &dev_info->info
+                                                ->event_attrs[i]);
 
                if (ret) {
                        dev_err(&dev_info->dev,
@@ -662,7 +1078,7 @@ static int iio_device_register_eventset(struct iio_dev *dev_info)
                }
        }
 
-       for (i = 0; i < dev_info->num_interrupt_lines; i++) {
+       for (i = 0; i < dev_info->info->num_interrupt_lines; i++) {
                ret = __iio_add_event_config_attrs(dev_info, i);
                if (ret)
                        goto error_unregister_config_attrs;
@@ -673,17 +1089,16 @@ static int iio_device_register_eventset(struct iio_dev *dev_info)
 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;
+       i = dev_info->info->num_interrupt_lines - 1;
 error_remove_sysfs_interfaces:
        for (j = 0; j < i; j++)
-               sysfs_remove_group(&dev_info
+               if (dev_info->info->event_attrs != NULL)
+                       sysfs_remove_group(&dev_info
                                   ->event_interfaces[j].dev.kobj,
-                                  &dev_info->event_attrs[j]);
+                                  &dev_info->info->event_attrs[j]);
 error_free_setup_ev_ints:
        for (j = 0; j < i; j++)
                iio_free_ev_int(&dev_info->event_interfaces[j]);
-       kfree(dev_info->interrupts);
-error_free_event_interfaces:
        kfree(dev_info->event_interfaces);
 error_ret:
 
@@ -694,25 +1109,25 @@ static void iio_device_unregister_eventset(struct iio_dev *dev_info)
 {
        int i;
 
-       if (dev_info->num_interrupt_lines == 0)
+       if (dev_info->info->num_interrupt_lines == 0)
                return;
-       for (i = 0; i < dev_info->num_interrupt_lines; i++)
-               sysfs_remove_group(&dev_info
-                                  ->event_interfaces[i].dev.kobj,
-                                  &dev_info->event_attrs[i]);
+       for (i = 0; i < dev_info->info->num_interrupt_lines; i++) {
+               __iio_remove_event_config_attrs(dev_info, i);
+               if (dev_info->info->event_attrs != NULL)
+                       sysfs_remove_group(&dev_info
+                                          ->event_interfaces[i].dev.kobj,
+                                          &dev_info->info->event_attrs[i]);
+       }
 
-       for (i = 0; i < dev_info->num_interrupt_lines; i++)
+       for (i = 0; i < dev_info->info->num_interrupt_lines; i++)
                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);
+       kfree(to_iio_dev(device));
 }
 
 static struct device_type iio_dev_type = {
@@ -720,9 +1135,20 @@ static struct device_type iio_dev_type = {
        .release = iio_dev_release,
 };
 
-struct iio_dev *iio_allocate_device(void)
+struct iio_dev *iio_allocate_device(int sizeof_priv)
 {
-       struct iio_dev *dev = kzalloc(sizeof *dev, GFP_KERNEL);
+       struct iio_dev *dev;
+       size_t alloc_size;
+
+       alloc_size = sizeof(struct iio_dev);
+       if (sizeof_priv) {
+               alloc_size = ALIGN(alloc_size, IIO_ALIGN);
+               alloc_size += sizeof_priv;
+       }
+       /* ensure 32-byte alignment of whole construct ? */
+       alloc_size += IIO_ALIGN - 1;
+
+       dev = kzalloc(alloc_size, GFP_KERNEL);
 
        if (dev) {
                dev->dev.type = &iio_dev_type;
@@ -748,8 +1174,9 @@ int iio_device_register(struct iio_dev *dev_info)
 {
        int ret;
 
-       ret = iio_device_register_id(dev_info, &iio_ida);
-       if (ret) {
+       dev_info->id = iio_get_new_ida_val(&iio_ida);
+       if (dev_info->id < 0) {
+               ret = dev_info->id;
                dev_err(&dev_info->dev, "Failed to get id\n");
                goto error_ret;
        }
@@ -780,7 +1207,7 @@ error_free_sysfs:
 error_del_device:
        device_del(&dev_info->dev);
 error_free_ida:
-       iio_device_unregister_id(dev_info);
+       iio_free_ida_val(&iio_ida, dev_info->id);
 error_ret:
        return ret;
 }
@@ -792,7 +1219,7 @@ void iio_device_unregister(struct iio_dev *dev_info)
                iio_device_unregister_trigger_consumer(dev_info);
        iio_device_unregister_eventset(dev_info);
        iio_device_unregister_sysfs(dev_info);
-       iio_device_unregister_id(dev_info);
+       iio_free_ida_val(&iio_ida, dev_info->id);
        device_unregister(&dev_info->dev);
 }
 EXPORT_SYMBOL(iio_device_unregister);