This is a VERY early version - lots to work out yet. --- drivers/staging/iio/chrdev.h | 3 + drivers/staging/iio/iio.h | 157 +++++++++ drivers/staging/iio/industrialio-core.c | 535 ++++++++++++++++++++++++++++++- drivers/staging/iio/industrialio-ring.c | 188 +++++++++++- drivers/staging/iio/ring_generic.h | 19 ++ drivers/staging/iio/sysfs.h | 31 ++- 6 files changed, 910 insertions(+), 23 deletions(-) diff --git a/drivers/staging/iio/chrdev.h b/drivers/staging/iio/chrdev.h index 98d1a2c..95b439e 100644 --- a/drivers/staging/iio/chrdev.h +++ b/drivers/staging/iio/chrdev.h @@ -91,6 +91,9 @@ struct iio_event_interface { void *private; char _name[35]; char _attrname[20]; + + struct list_head event_attr_list; + struct list_head dev_attr_list; }; /** diff --git a/drivers/staging/iio/iio.h b/drivers/staging/iio/iio.h index bf84799..43da02c 100644 --- a/drivers/staging/iio/iio.h +++ b/drivers/staging/iio/iio.h @@ -26,6 +26,138 @@ struct iio_dev; +/* naughty temporary hack to match these against the event version + - need to flattern these together */ +enum iio_chan_type { + /* Need this here for now to support buffer events + * set to 0 to avoid changes to ring_generic.c */ + IIO_BUFFER = 0, + + /* real channel types */ + IIO_IN, + IIO_ACCEL, + IIO_IN_DIFF, + IIO_GYRO, + IIO_MAGN, + IIO_LIGHT, + IIO_PROXIMITY, + IIO_TIMESTAMP, +}; + +/* Could add the raw attributes as well - allowing buffer only devices */ +enum iio_chan_info_enum { + IIO_CHAN_INFO_SCALE_SHARED, + IIO_CHAN_INFO_SCALE_SEPARATE, + IIO_CHAN_INFO_OFFSET_SHARED, + IIO_CHAN_INFO_OFFSET_SEPARATE, + IIO_CHAN_INFO_CALIBSCALE_SHARED, + IIO_CHAN_INFO_CALIBSCALE_SEPARATE, + IIO_CHAN_INFO_CALIBBIAS_SHARED, + IIO_CHAN_INFO_CALIBBIAS_SEPARATE +}; + +/** + * struct iio_chan_spec - specification of a single channel + * @type: what type of measurement is the channel making + * @channel: what number or name do we wish to asign the channel + * @channel2: if there is a second number for a differential + * channel then this is it. + * @address: driver specific identifier. + * @scan_index: monotonic index to give ordering in scans when read + * from a buffer. + * @scan_type: sign is 's' or 'u' to specify signed or unsigned + * realbits is the number of valid bits of data + * storage bits is realbits + padding + * shift tells you how much to shift right before masking + * out realbits. + * @info_mask: what information is to be exported about this channel. + * This includes calibbias, scale etc. + * @event_mask: what events can this channel produce. + * @shared_handler: single handler for the events registered. + */ +struct iio_chan_spec { + enum iio_chan_type type; + int channel; + int channel2; + int address; + int scan_index; + struct { + char sign; + u8 realbits; + u8 storagebits; + u8 shift; + } scan_type; + const long info_mask; + const long event_mask; + //how to handle device with sets of shared events? + //perhaps we push the event handling out into the drivers. + struct iio_event_handler_list *shared_handler; +}; +/* Meant for internal use only */ +void __iio_device_attr_deinit(struct device_attribute *dev_attr); +int __iio_device_attr_init(struct device_attribute *dev_attr, + const char *postfix, + struct iio_chan_spec *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); +#define IIO_ST(si, rb, sb, sh) \ + { .sign = si, .realbits = rb, .storagebits = sb, .shift = sh } + +#define IIO_CHAN(_type, _chan, _inf_mask, _address, _si, _stype) \ + { .type = _type, .channel = _chan, .info_mask = _inf_mask, \ + .address = _address, \ + .scan_index = _si, .scan_type = _stype } + +#define IIO_CHAN_EV(_type, _chan, _inf_mask, _address, _si, \ + _stype, _event_mask, _shared_h) \ + { .type = _type, \ + .channel = _chan, \ + .info_mask = _inf_mask, \ + .address = _address, \ + .scan_index = _si, .scan_type = _stype, \ + .event_mask = _event_mask, \ + .shared_handler = _shared_h } + +#define IIO_CHAN_COMPOUND(_type, _chan1, _chan2, _inf_mask,_address, _si, _stype) \ + { .type = _type, .channel = _chan1, .channel2 = _chan2, \ + .info_mask = _inf_mask, \ + .address = _address, \ + .scan_index = _si, .scan_type = _stype } + +#define IIO_CHAN_COMPOUND_EV(_type, _chan1, _chan2, _inf_mask, \ + _address, _si, _stype, _event_mask, _shared_h) \ + { .type = _type, \ + .channel = _chan1, .channel2 = _chan2, \ + .info_mask = _inf_mask, \ + .address = _address, \ + .scan_index = _si, .scan_type = _stype, \ + .event_mask = _event_mask, \ + .shared_handler = _shared_h} + +#define IIO_CHAN_SOFT_TIMESTAMP(_si) \ + { .type = IIO_TIMESTAMP, .channel = -1, \ + .scan_index = _si, .scan_type = IIO_ST('s', 64, 64, 0) } + +int __iio_add_chan_devattr(const char *postfix, + const char *group, + struct iio_chan_spec *chan, + ssize_t (*func)(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); /** * iio_get_time_ns() - utility function to get a time stamp for events etc **/ @@ -116,6 +248,31 @@ struct iio_dev { u32 *available_scan_masks; struct iio_trigger *trig; struct iio_poll_func *pollfunc; + + struct iio_chan_spec *channels; + int num_channels; + struct list_head channel_attr_list; + + char *name; /*device name - IMPLEMENT */ + int (*read_raw)(struct iio_dev *indio_dev, + struct iio_chan_spec *chan, + int *val, + long mask); + + int (*read_event_config)(struct iio_dev *indio_dev, + int event_code); + + int (*write_event_config)(struct iio_dev *indio_dev, + int event_code, + struct iio_event_handler_list *listel, + int state); + + int (*read_event_value)(struct iio_dev *indio_dev, + int event_code, + int *val); + int (*write_event_value)(struct iio_dev *indio_dev, + int event_code, + int val); }; /** diff --git a/drivers/staging/iio/industrialio-core.c b/drivers/staging/iio/industrialio-core.c index 54a61a3..e3b76b4 100644 --- a/drivers/staging/iio/industrialio-core.c +++ b/drivers/staging/iio/industrialio-core.c @@ -44,6 +44,28 @@ struct bus_type iio_bus_type = { }; EXPORT_SYMBOL(iio_bus_type); +static const char *iio_chan_type_name_spec_shared[] = { + [IIO_TIMESTAMP] = "timestamp", + [IIO_ACCEL] = "accel", + [IIO_IN] = "in", + [IIO_IN_DIFF] = "in-in", +}; + +static const char *iio_chan_type_name_spec[] = { + [IIO_TIMESTAMP] = "timestamp", + [IIO_ACCEL] = "accel_%c", + [IIO_IN] = "in%d", + [IIO_IN_DIFF] = "in%d-in%d", +}; + +/* relies on pairs of these shared then separate */ +static const char *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", +}; + void __iio_change_event(struct iio_detected_event_list *ev, int ev_code, s64 timestamp) @@ -488,9 +510,218 @@ static void __exit iio_exit(void) bus_unregister(&iio_bus_type); } +static ssize_t iio_read_single_channel(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->read_raw(indio_dev, this_attr->c, &val, 0); + + return ret < 0 ? ret : sprintf(buf, "%d\n", val); +} + +static ssize_t iio_read_channel_info(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; + int ret = indio_dev->read_raw(indio_dev, this_attr->c, + &val, this_attr->address); + + if (ret < 0) + return ret; + else /* may want a longer type for val */ + return sprintf(buf, "%d.%06ld\n", val/1000000, + abs(val)%1000000); +} + +static ssize_t iio_write_channel_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + /* To be implemented - need to decide if we pass a number + on to the drivers or a string and let them do the handling + */ + return len; +} + +int __iio_device_attr_init(struct device_attribute *dev_attr, + const char *postfix, + struct iio_chan_spec *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; + sysfs_attr_init(&dev_attr->attr); + if (generic) + name_format + = kasprintf(GFP_KERNEL, "%s_%s", + iio_chan_type_name_spec_shared[chan->type], + postfix); + else + name_format = kasprintf(GFP_KERNEL, "%s_%s", + iio_chan_type_name_spec[chan->type], + postfix); + if (name_format == NULL) { + ret = ENOMEM; + goto error_ret; + } + + 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); + + return 0; + +error_free_name_format: + kfree(name_format); +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 *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_iio_dev_attr_free; + } + + 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 *chan) +{ + int ret, i; + + + if (chan->channel < 0) + return 0; + + ret = __iio_add_chan_devattr("raw", NULL, chan, + &iio_read_single_channel, + 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 int iio_device_register_sysfs(struct iio_dev *dev_info) { - int ret = 0; + int i, ret = 0; + struct iio_dev_attr *p, *n; ret = sysfs_create_group(&dev_info->dev.kobj, dev_info->attrs); if (ret) { @@ -499,12 +730,39 @@ static int iio_device_register_sysfs(struct iio_dev *dev_info) goto error_ret; } + /* New channel registration method - currently assumes at there + * is an existing group to which attributes are to be added*/ + if (dev_info->channels) { + INIT_LIST_HEAD(&dev_info->channel_attr_list); + 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; + } + } + return 0; +error_clear_attrs: + list_for_each_entry_safe(p, n, + &dev_info->channel_attr_list, l) + iio_device_remove_and_free_read_attr(dev_info, p); + + sysfs_remove_group(&dev_info->dev.kobj, dev_info->attrs); error_ret: return ret; + } static void iio_device_unregister_sysfs(struct iio_dev *dev_info) { + + struct iio_dev_attr *p, *n; + if (dev_info->channels) + list_for_each_entry_safe(p, n, + &dev_info->channel_attr_list, l) + iio_device_remove_and_free_read_attr(dev_info, p); + sysfs_remove_group(&dev_info->dev.kobj, dev_info->attrs); } @@ -552,6 +810,225 @@ static void iio_device_unregister_id(struct iio_dev *dev_info) iio_free_ida_val(&iio_ida, dev_info->id); } +static const char *iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc" +}; + +static const char *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) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + int ret; + unsigned long val; + ret = strict_strtoul(buf, 10, &val); + if (ret || val < 0 || val > 1) + return -EINVAL; + + ret = indio_dev->write_event_config(indio_dev, this_attr->mask, + this_attr->listel, + val); + return (ret < 0) ? ret : len; +} + +static ssize_t iio_ev_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_event_attr *this_attr = to_iio_event_attr(attr); + int val = indio_dev->read_event_config(indio_dev, this_attr->mask); + + 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->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->write_event_value(indio_dev, this_attr->address, + val); + if (ret < 0) + return ret; + + return len; +} + +static int __iio_add_chan_event_attr(const char *postfix, + const char *group, + struct iio_chan_spec *chan, + unsigned int mask, + struct device *dev, + struct list_head *attr_list) +{ + char *name_format; + int ret; + struct iio_event_attr *iio_ev_attr; + + iio_ev_attr = kzalloc(sizeof *iio_ev_attr, GFP_KERNEL); + if (iio_ev_attr == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + sysfs_attr_init(&iio_ev_attr->dev_attr.attr); + + name_format = kasprintf(GFP_KERNEL, "%s_%s", + iio_chan_type_name_spec[chan->type], + postfix); + if (name_format == NULL) { + ret = -ENOMEM; + goto error_free_attr; + } + + iio_ev_attr->dev_attr.attr.name = kasprintf(GFP_KERNEL, + name_format, + chan->channel, + chan->channel2); + if (iio_ev_attr->dev_attr.attr.name == NULL) { + ret = -ENOMEM; + goto error_free_name_format; + } + + iio_ev_attr->dev_attr.attr.mode = S_IRUGO | S_IWUSR; + iio_ev_attr->dev_attr.show = &iio_ev_state_show; + iio_ev_attr->dev_attr.store = &iio_ev_state_store; + iio_ev_attr->mask = mask; + iio_ev_attr->listel = chan->shared_handler; + ret = sysfs_add_file_to_group(&dev->kobj, + &iio_ev_attr->dev_attr.attr, + group); + if (ret < 0) + goto error_free_name; + list_add(&iio_ev_attr->l, attr_list); + kfree(name_format); + return 0; + +error_free_name: + kfree(iio_ev_attr->dev_attr.attr.name); +error_free_name_format: + kfree(name_format); +error_free_attr: + kfree(iio_ev_attr); +error_ret: + return ret; +} + + +static int iio_device_add_event_sysfs(struct iio_dev *dev_info, + struct iio_chan_spec *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) { + //TODO, use a table so as to make it easy to add more types + 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; + default: + printk("currently unhandled\n"); + } + ret = __iio_add_chan_event_attr(postfix, + NULL, + chan, + mask, + /*HACK. - limits us to one + event interface - fix by + extending the bitmask - but + how far*/ + &dev_info->event_interfaces[0] + .dev, + &dev_info->event_interfaces[0]. + event_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 int __iio_add_event_config_attrs(struct iio_dev *dev_info, int i) { int ret; @@ -570,7 +1047,22 @@ static inline int __iio_add_event_config_attrs(struct iio_dev *dev_info, int i) attrp++; } } + INIT_LIST_HEAD(&dev_info->event_interfaces[0].event_attr_list); + INIT_LIST_HEAD(&dev_info->event_interfaces[0].dev_attr_list); + /* Dynically created from the channels array */ + if (dev_info->channels) { + for (i = 0; i < dev_info->num_channels; i++) { + ret = iio_device_add_event_sysfs(dev_info, + &dev_info + ->channels[i]); + if (ret) + goto error_clear_attrs; + + } + } return 0; +error_clear_attrs: + //to implement - shared code with the similar bits elsewhere. error_ret: attrq = dev_info->event_conf_attrs[i].attrs; @@ -603,6 +1095,14 @@ static inline int __iio_remove_event_config_attrs(struct iio_dev *dev_info, return 0; } +static struct attribute *iio_event_dummy_attrs[] = { + NULL +}; + +static struct attribute_group iio_event_dummy_attr_group = { + .name = NULL, + .attrs = iio_event_dummy_attrs, +}; static int iio_device_register_eventset(struct iio_dev *dev_info) { @@ -650,10 +1150,17 @@ 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->event_attrs == NULL) + ret = sysfs_create_group(&dev_info + ->event_interfaces[i] + .dev.kobj, + &iio_event_dummy_attr_group); + else + ret = sysfs_create_group(&dev_info + ->event_interfaces[i] + .dev.kobj, + &dev_info->event_attrs[i]); if (ret) { dev_err(&dev_info->dev, @@ -676,7 +1183,12 @@ error_unregister_config_attrs: i = dev_info->num_interrupt_lines - 1; error_remove_sysfs_interfaces: for (j = 0; j < i; j++) - sysfs_remove_group(&dev_info + if (dev_info->event_attrs == NULL) + sysfs_remove_group(&dev_info + ->event_interfaces[j].dev.kobj, + &iio_event_dummy_attr_group); + else + sysfs_remove_group(&dev_info ->event_interfaces[j].dev.kobj, &dev_info->event_attrs[j]); error_free_setup_ev_ints: @@ -697,9 +1209,14 @@ static void iio_device_unregister_eventset(struct iio_dev *dev_info) if (dev_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]); + if (dev_info->event_attrs == NULL) + sysfs_remove_group(&dev_info + ->event_interfaces[i].dev.kobj, + &iio_event_dummy_attr_group); + else + sysfs_remove_group(&dev_info + ->event_interfaces[i].dev.kobj, + &dev_info->event_attrs[i]); for (i = 0; i < dev_info->num_interrupt_lines; i++) iio_free_ev_int(&dev_info->event_interfaces[i]); diff --git a/drivers/staging/iio/industrialio-ring.c b/drivers/staging/iio/industrialio-ring.c index 3f6bee0..1dfbc6e 100644 --- a/drivers/staging/iio/industrialio-ring.c +++ b/drivers/staging/iio/industrialio-ring.c @@ -233,9 +233,162 @@ void iio_ring_buffer_init(struct iio_ring_buffer *ring, } EXPORT_SYMBOL(iio_ring_buffer_init); -int iio_ring_buffer_register(struct iio_ring_buffer *ring, int id) +static ssize_t iio_show_scan_index(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + return sprintf(buf, "%u\n", this_attr->c->scan_index); +} + +static ssize_t iio_show_fixed_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + return sprintf(buf, "%c%d/%d>>%u\n", + this_attr->c->scan_type.sign, + this_attr->c->scan_type.realbits, + this_attr->c->scan_type.storagebits, + this_attr->c->scan_type.shift); +} + +static int __iio_add_chan_scan_elattr(const char *postfix, + const char *group, + struct iio_chan_spec *chan, + struct device *dev, + struct list_head *attr_list) { int ret; + struct iio_scan_el *scan_el; + + scan_el = kzalloc(sizeof *scan_el, GFP_KERNEL); + if (scan_el == NULL) { + ret = -ENOMEM; + goto error_ret; + } + if (chan->type != IIO_TIMESTAMP) + ret = __iio_device_attr_init(&scan_el->dev_attr, postfix, chan, + iio_scan_el_show, + iio_scan_el_store, 0); + else /* + * Timestamp handled separately because it simplifies a lot of + * drivers by ensuring they don't have to know its magic index + */ + ret = __iio_device_attr_init(&scan_el->dev_attr, postfix, chan, + iio_scan_el_ts_show, + iio_scan_el_ts_store, 0); + if (ret) + goto error_free_scan_el; + + scan_el->number = chan->scan_index; + + ret = sysfs_add_file_to_group(&dev->kobj, + &scan_el->dev_attr.attr, + group); + if (ret < 0) + goto error_device_attr_deinit; + + list_add(&scan_el->l, attr_list); + + return 0; +error_device_attr_deinit: + __iio_device_attr_deinit(&scan_el->dev_attr); +error_free_scan_el: + kfree(scan_el); +error_ret: + return ret; +} + +static int iio_ring_add_channel_sysfs(struct iio_ring_buffer *ring, + struct iio_chan_spec *chan) +{ + int ret; + + ret = __iio_add_chan_devattr("index", "scan_elements", + chan, + &iio_show_scan_index, + NULL, + 0, + 0, + &ring->dev, + &ring->scan_el_dev_attr_list); + if (ret) + goto error_ret; + + ret = __iio_add_chan_devattr("type", "scan_elements", + chan, + &iio_show_fixed_type, + NULL, + 0, + 0, + &ring->dev, + &ring->scan_el_dev_attr_list); + + if (ret) + goto error_ret; + + ret = __iio_add_chan_scan_elattr("en", "scan_elements", + chan, &ring->dev, + &ring->scan_el_en_attr_list); + +error_ret: + return ret; +} + +static void iio_ring_remove_and_free_scan_el_attr(struct iio_ring_buffer *ring, + struct iio_scan_el *p) +{ + sysfs_remove_file_from_group(&ring->dev.kobj, + &p->dev_attr.attr, "scan_elements"); + kfree(p->dev_attr.attr.name); + kfree(p); +} + +static void iio_ring_remove_and_free_scan_dev_attr(struct iio_ring_buffer *ring, + struct iio_dev_attr *p) +{ + sysfs_remove_file_from_group(&ring->dev.kobj, + &p->dev_attr.attr, "scan_elements"); + kfree(p->dev_attr.attr.name); + kfree(p); +} + +static struct attribute *iio_scan_el_dummy_attrs[] = { + NULL +}; + +static struct attribute_group iio_scan_el_dummy_group = { + .name = "scan_elements", + .attrs = iio_scan_el_dummy_attrs +}; + +static void __iio_ring_attr_cleanup(struct iio_ring_buffer *ring) +{ + struct iio_dev_attr *p, *n; + struct iio_scan_el *q, *m; + int anydynamic = !(list_empty(&ring->scan_el_dev_attr_list) && + list_empty(&ring->scan_el_en_attr_list)); + list_for_each_entry_safe(p, n, + &ring->scan_el_dev_attr_list, l) + iio_ring_remove_and_free_scan_dev_attr(ring, p); + list_for_each_entry_safe(q, m, + &ring->scan_el_en_attr_list, l) + iio_ring_remove_and_free_scan_el_attr(ring, q); + + if (ring->scan_el_attrs) + sysfs_remove_group(&ring->dev.kobj, + ring->scan_el_attrs); + else if (anydynamic) + sysfs_remove_group(&ring->dev.kobj, + &iio_scan_el_dummy_group); +} + +int iio_ring_buffer_register_ex(struct iio_ring_buffer *ring, int id, + struct iio_chan_spec *channels, + int num_channels) +{ + int ret, i; ring->id = id; @@ -268,9 +421,28 @@ int iio_ring_buffer_register(struct iio_ring_buffer *ring, int id) "Failed to add sysfs scan elements\n"); goto error_free_ring_buffer_event_chrdev; } + } else if (channels) { + ret = sysfs_create_group(&ring->dev.kobj, + &iio_scan_el_dummy_group); + if (ret) + goto error_free_ring_buffer_event_chrdev; } - return ret; + + INIT_LIST_HEAD(&ring->scan_el_dev_attr_list); + INIT_LIST_HEAD(&ring->scan_el_en_attr_list); + if (channels) { + /* new magic */ + for (i = 0; i < num_channels; i++) { + ret = iio_ring_add_channel_sysfs(ring, &channels[i]); + if (ret < 0) + goto error_cleanup_dynamic; + } + } + + return 0; +error_cleanup_dynamic: + __iio_ring_attr_cleanup(ring); error_free_ring_buffer_event_chrdev: __iio_free_ring_buffer_event_chrdev(ring); error_remove_device: @@ -278,14 +450,17 @@ error_remove_device: error_ret: return ret; } +EXPORT_SYMBOL(iio_ring_buffer_register_ex); + +int iio_ring_buffer_register(struct iio_ring_buffer *ring, int id) +{ + return iio_ring_buffer_register_ex(ring, id, NULL, 0); +} EXPORT_SYMBOL(iio_ring_buffer_register); void iio_ring_buffer_unregister(struct iio_ring_buffer *ring) { - if (ring->scan_el_attrs) - sysfs_remove_group(&ring->dev.kobj, - ring->scan_el_attrs); - + __iio_ring_attr_cleanup(ring); __iio_free_ring_buffer_access_chrdev(ring); __iio_free_ring_buffer_event_chrdev(ring); device_del(&ring->dev); @@ -540,4 +715,3 @@ error_ret: return ret ? ret : len; } EXPORT_SYMBOL(iio_scan_el_ts_store); - diff --git a/drivers/staging/iio/ring_generic.h b/drivers/staging/iio/ring_generic.h index bae2d6d..8ffbdc5 100644 --- a/drivers/staging/iio/ring_generic.h +++ b/drivers/staging/iio/ring_generic.h @@ -140,6 +140,8 @@ struct iio_ring_buffer { int (*predisable)(struct iio_dev *); int (*postdisable)(struct iio_dev *); + struct list_head scan_el_dev_attr_list; + struct list_head scan_el_en_attr_list; }; /** @@ -177,6 +179,7 @@ struct iio_scan_el { struct device_attribute dev_attr; unsigned int number; unsigned int label; + struct list_head l; int (*set_state)(struct iio_scan_el *scanel, struct iio_dev *dev_info, @@ -430,6 +433,13 @@ static inline void iio_put_ring_buffer(struct iio_ring_buffer *ring) **/ int iio_ring_buffer_register(struct iio_ring_buffer *ring, int id); +/** iio_ring_buffer_register_ex() - register the buffer with IIO core + * @ring: the buffer to be registered + * @id: the id of the buffer (typically 0) + **/ +int iio_ring_buffer_register_ex(struct iio_ring_buffer *ring, int id, + struct iio_chan_spec *channels, int num_channels); + /** * iio_ring_buffer_unregister() - unregister the buffer from IIO core * @ring: the buffer to be unregistered @@ -481,6 +491,15 @@ static inline int iio_ring_buffer_register(struct iio_ring_buffer *ring, int id) { return 0; }; + +static inline int iio_ring_buffer_register_ex(struct iio_ring_buffer *ring, + int id, + struct iio_chan_spec *channels, + int num_channels) +{ + return 0; +} + static inline void iio_ring_buffer_unregister(struct iio_ring_buffer *ring) {}; diff --git a/drivers/staging/iio/sysfs.h b/drivers/staging/iio/sysfs.h index 24b74dd..17ddf36 100644 --- a/drivers/staging/iio/sysfs.h +++ b/drivers/staging/iio/sysfs.h @@ -24,6 +24,7 @@ struct iio_event_attr { struct device_attribute dev_attr; int mask; struct iio_event_handler_list *listel; + struct list_head l; }; #define to_iio_event_attr(_dev_attr) \ @@ -34,11 +35,15 @@ struct iio_event_attr { * @dev_attr: underlying device attribute * @address: associated register address * @val2: secondary attribute value + * @l: list head for maintaining list of dynamically created attrs. */ struct iio_dev_attr { struct device_attribute dev_attr; int address; int val2; + struct list_head l; + //should be able to get this out of here. But is it worth it? + struct iio_chan_spec *c; }; #define to_iio_dev_attr(_dev_attr) \ @@ -259,13 +264,14 @@ struct iio_const_attr { #define IIO_EVENT_ATTR_DATA_RDY(_show, _store, _mask, _handler) \ IIO_EVENT_ATTR(data_rdy, _show, _store, _mask, _handler) -#define IIO_EV_CLASS_BUFFER 0 -#define IIO_EV_CLASS_IN 1 -#define IIO_EV_CLASS_ACCEL 2 -#define IIO_EV_CLASS_GYRO 3 -#define IIO_EV_CLASS_MAGN 4 -#define IIO_EV_CLASS_LIGHT 5 -#define IIO_EV_CLASS_PROXIMITY 6 +/* must match our channel defs */ +#define IIO_EV_CLASS_IN IIO_IN +#define IIO_EV_CLASS_ACCEL IIO_ACCEL +#define IIO_EV_CLASS_GYRO IIO_GYRO +#define IIO_EV_CLASS_MAGN IIO_MAGN +#define IIO_EV_CLASS_LIGHT IIO_LIGHT +#define IIO_EV_CLASS_PROXIMITY IIO_PROXIMITY +#define IIO_EV_CLASS_BUFFER IIO_BUFFER #define IIO_EV_MOD_X 0 #define IIO_EV_MOD_Y 1 @@ -287,6 +293,10 @@ struct iio_const_attr { #define IIO_EV_DIR_RISING 1 #define IIO_EV_DIR_FALLING 2 +#define IIO_EV_TYPE_MAX 8 +#define IIO_EV_BIT(type, direction) \ + 1 << (type*IIO_EV_TYPE_MAX + direction) + #define IIO_EVENT_CODE(channelclass, orient_bit, number, \ modifier, type, direction) \ (channelclass | (orient_bit << 8) | ((number) << 9) | \ @@ -303,6 +313,13 @@ struct iio_const_attr { #define IIO_BUFFER_EVENT_CODE(code) \ (IIO_EV_CLASS_BUFFER | (code << 8)) +#define IIO_EVENT_CODE_EXTRACT_DIR(mask) ((mask >> 24) & 0xf) + +//FIXME - mask probably completely wrong +#define IIO_EVENT_CODE_EXTRACT_NUM(mask) ((mask >> 9) & 0xff) + +#define IIO_EVENT_CODE_EXTRACT_MODIFIER(mask) ((mask >> 13) & 0x7) + /** * IIO_EVENT_ATTR_RING_50_FULL - ring buffer event to indicate 50% full * @_show: output method for the attribute -- 1.7.3.4 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html