diff options
Diffstat (limited to 'drivers/staging/iio/industrialio-core.c')
-rw-r--r-- | drivers/staging/iio/industrialio-core.c | 943 |
1 files changed, 675 insertions, 268 deletions
diff --git a/drivers/staging/iio/industrialio-core.c b/drivers/staging/iio/industrialio-core.c index 1795ee1e8207..94d3bfaa061d 100644 --- a/drivers/staging/iio/industrialio-core.c +++ b/drivers/staging/iio/industrialio-core.c @@ -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,397 @@ 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 + 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, micro = 0, micro_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 (buf[0] == '-') { + negative = true; + buf++; + } + while (*buf) { + if ('0' <= *buf && *buf <= '9') { + if (integer_part) + integer = integer*10 + *buf - '0'; + else { + micro += micro_mult*(*buf - '0'); + if (micro_mult == 1) + break; + micro_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 + micro = -micro; + } + + ret = indio_dev->info->write_raw(indio_dev, this_attr->c, + integer, micro, 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 +799,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 +1009,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 +1017,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 +1043,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 +1058,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 +1069,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 +1089,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 +1115,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 +1154,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 +1187,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 +1199,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); |