[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20171004191222.GA30479@sophia>
Date: Wed, 4 Oct 2017 15:12:22 -0400
From: William Breathitt Gray <vilhelm.gray@...il.com>
To: Benjamin Gaignard <benjamin.gaignard@...aro.org>
Cc: Jonathan Cameron <jic23@...nel.org>,
Hartmut Knaack <knaack.h@....de>,
Lars-Peter Clausen <lars@...afoo.de>,
Peter Meerwald-Stadler <pmeerw@...erw.net>,
linux-iio@...r.kernel.org,
Linux Kernel Mailing List <linux-kernel@...r.kernel.org>,
Fabrice Gasnier <fabrice.gasnier@...com>
Subject: Re: [PATCH v2 2/5] iio: Introduce the generic counter interface
On Tue, Oct 03, 2017 at 04:01:17PM +0200, Benjamin Gaignard wrote:
>2017-09-29 22:01 GMT+02:00 William Breathitt Gray <vilhelm.gray@...il.com>:
>> On Fri, Sep 29, 2017 at 03:42:02PM +0200, Benjamin Gaignard wrote:
>>>2017-09-25 20:08 GMT+02:00 William Breathitt Gray <vilhelm.gray@...il.com>:
>>>> This patch introduces the IIO generic counter interface for supporting
>>>> counter devices. The generic counter interface serves as a catch-all to
>>>> enable rudimentary support for devices that qualify as counters. More
>>>> specific and apt counter interfaces may be developed on top of the
>>>> generic counter interface, and those interfaces should be used by
>>>> drivers when possible rather than the generic counter interface.
>>>>
>>>> In the context of the IIO generic counter interface, a counter is
>>>> defined as a device that reports one or more "counter values" based on
>>>> the state changes of one or more "counter signals" as evaluated by a
>>>> defined "counter function."
>>>>
>>>> The IIO generic counter interface piggybacks off of the IIO core. This
>>>> is primarily used to leverage the existing sysfs setup: the IIO_COUNT
>>>> channel attributes represent the "counter value," while the IIO_SIGNAL
>>>> channel attributes represent the "counter signal;" auxilary IIO_COUNT
>>>> attributes represent the "counter signal" connections and their
>>>> respective state change configurations which trigger an associated
>>>> "counter function" evaluation.
>>>>
>>>> The iio_counter_ops structure serves as a container for driver callbacks
>>>> to communicate with the device; function callbacks are provided to read
>>>> and write various "counter signals" and "counter values," and set and
>>>> get the "trigger mode" and "function mode" for various "counter signals"
>>>> and "counter values" respectively.
>>>>
>>>> To support a counter device, a driver must first allocate the available
>>>> "counter signals" via iio_counter_signal structures. These "counter
>>>> signals" should be stored as an array and set to the init_signals member
>>>> of an allocated iio_counter structure before the counter is registered.
>>>>
>>>> "Counter values" may be allocated via iio_counter_value structures, and
>>>> respective "counter signal" associations made via iio_counter_trigger
>>>> structures. Initial associated iio_counter_trigger structures may be
>>>> stored as an array and set to the the init_triggers member of the
>>>> respective iio_counter_value structure. These iio_counter_value
>>>> structures may be set to the init_values member of an allocated
>>>> iio_counter structure before the counter is registered if so desired.
>>>>
>>>> A counter device is registered to the system by passing the respective
>>>> initialized iio_counter structure to the iio_counter_register function;
>>>> similarly, the iio_counter_unregister function unregisters the
>>>> respective counter.
>>>>
>>>> Signed-off-by: William Breathitt Gray <vilhelm.gray@...il.com>
>>>
>>>Hi William,
>>>
>>>Thanks for your patches, I start try to implement them in stm32-timer driver
>>>but I think it will take me some time before understand all your code.
>>>
>>>I have some warning when compiling the code (see below).
>>
>> Hi Benjamin,
>>
>> Thank you for trying out the patches. I've been focusing on the main API
>> documentation thus far, so unfortunately the current code is still a bit
>> hard to follow -- hopefully I can clean it up some soon. Regardless,
>> I'll be happy to answer any questions you may encounter while reviewing
>> the patches.
>>
>> Regarding the compilation warnings: see my response inline below.
>>
>>>
>>>About wording, I think that using "trigger" to describe signal active
>>>states/edges
>>>could be confusing in IIO context but I haven't found yet a better name.
>>
>> I agree very much with this. "Trigger" was a bad name choice when I
>> developed the Generic Counter paradigm. I would like to come up with a
>> better name for this component before this patchset is merged.
>>
>> The "Trigger" component essentially describes an association between a
>> Signal and Value. The association typically has a defined Signal data
>> condition which triggers the respective Value function operation (or
>> occasionally the trigger condition is just left as "None"), but
>> ultimately it's the association itself that is the key aspect this
>> component.
>>
>> I have considered changing it to "Association" or "Connection" as the
>> name, but both of these seem to sound too vague of terms. I shall keep
>> thinking of various alternative names for this component and change the
>> documentation in a subsequent patchset version once we have a more
>> fitting name.
>
>Yes it isn't easy to find the good name for that, maybe signal capabilities or
>signal behavoir could also be considere.
>
>>
>>>
>>>I also not very sure about what you expect from iio_counter_ops signal_read and
>>>signal_write functions, do you think get/set the value of the signal ?
>>>(i.e read gpio level ?)
>>
>> Yes, for an ordinary simple counter, signal_read and signal_write would
>> typically expose control of the respective hardware input line level.
>>
>> However, it is important to remember that within the context of the
>> Generic Counter paradigm, Signals are abstract components: a single
>> Signal may not directly correlate to a single hardware input line; this
>> is the primary reason for providing the signal_read/signal_write
>> functions.
>>
>> For example, suppose we have a counter with a single Value associated to
>> a single Signal. The Signal data is a 3-bit decimal value, and the Value
>> is an ongoing count of the number of times the Signal data decimal value
>> has had a value greater than "5."
>>
>> The hardware itself could consist of three individual input lines
>> (one for each bit), but there is ultimately only one Signal. In this
>> scenario, I would expect signal_read to simply return the 3-bit decimal
>> value -- not the three physical hardware input line level. Notice the
>> abstraction which has occurred: the Signal is the data represented by
>> the hardware input, but not the raw hardware input itself.
>
>I do not have access to the hardware input value anyway so I will just
>keep those functions empty.
That is a perfectly acceptable practice as many devices do not provide
this data either, so I expect many drivers to lack these definitions.
>
>I have done some progress in driver implementation but I have questions
>and suggestion, see below.
>
>Benjamin
>
>>
>> Sincerely,
>>
>> William Breathitt Gray
>>
>>>
>>>I will continue to review and implement your patches, I hope that end
>>>of next week
>>>have something functionnal to share with you.
>>>
>>>Thansk to have propose this, I do believe it will be helpful
>>>
>>>Benjamin
>>>> ---
>>>> MAINTAINERS | 7 +
>>>> drivers/iio/Kconfig | 8 +
>>>> drivers/iio/Makefile | 1 +
>>>> drivers/iio/counter/Kconfig | 1 +
>>>> drivers/iio/industrialio-counter.c | 1151 ++++++++++++++++++++++++++++++++++++
>>>> include/linux/iio/counter.h | 221 +++++++
>>>> 6 files changed, 1389 insertions(+)
>>>> create mode 100644 drivers/iio/industrialio-counter.c
>>>> create mode 100644 include/linux/iio/counter.h
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index 6f7721d1634c..24fc2dcf1995 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -6577,6 +6577,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector
>>>> F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt
>>>> F: drivers/iio/adc/envelope-detector.c
>>>>
>>>> +IIO GENERIC COUNTER INTERFACE
>>>> +M: William Breathitt Gray <vilhelm.gray@...il.com>
>>>> +L: linux-iio@...r.kernel.org
>>>> +S: Maintained
>>>> +F: drivers/iio/industrialio-counter.c
>>>> +F: include/linux/iio/counter.h
>>>> +
>>>> IIO MULTIPLEXER
>>>> M: Peter Rosin <peda@...ntia.se>
>>>> L: linux-iio@...r.kernel.org
>>>> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
>>>> index b3c8c6ef0dff..78e01f4f5937 100644
>>>> --- a/drivers/iio/Kconfig
>>>> +++ b/drivers/iio/Kconfig
>>>> @@ -30,6 +30,14 @@ config IIO_CONFIGFS
>>>> (e.g. software triggers). For more info see
>>>> Documentation/iio/iio_configfs.txt.
>>>>
>>>> +config IIO_COUNTER
>>>> + bool "Enable IIO counter support"
>>>> + help
>>>> + Provides IIO core support for counters. This API provides
>>>> + a generic interface that serves as the building blocks to
>>>> + create more complex counter interfaces. Rudimentary support
>>>> + for counters is enabled.
>>>> +
>>>> config IIO_TRIGGER
>>>> bool "Enable triggered sampling support"
>>>> help
>>>> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
>>>> index 93c769cd99bf..6427ff38f964 100644
>>>> --- a/drivers/iio/Makefile
>>>> +++ b/drivers/iio/Makefile
>>>> @@ -5,6 +5,7 @@
>>>> obj-$(CONFIG_IIO) += industrialio.o
>>>> industrialio-y := industrialio-core.o industrialio-event.o inkern.o
>>>> industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o
>>>> +industrialio-$(CONFIG_IIO_COUNTER) += industrialio-counter.o
>>>> industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o
>>>>
>>>> obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o
>>>> diff --git a/drivers/iio/counter/Kconfig b/drivers/iio/counter/Kconfig
>>>> index b37e5fc03149..3d46a790d8db 100644
>>>> --- a/drivers/iio/counter/Kconfig
>>>> +++ b/drivers/iio/counter/Kconfig
>>>> @@ -4,6 +4,7 @@
>>>> # When adding new entries keep the list in alphabetical order
>>>>
>>>> menu "Counters"
>>>> + depends on IIO_COUNTER
>>>>
>>>> config 104_QUAD_8
>>>> tristate "ACCES 104-QUAD-8 driver"
>>>> diff --git a/drivers/iio/industrialio-counter.c b/drivers/iio/industrialio-counter.c
>>>> new file mode 100644
>>>> index 000000000000..bdf190d010e4
>>>> --- /dev/null
>>>> +++ b/drivers/iio/industrialio-counter.c
>>>> @@ -0,0 +1,1151 @@
>>>> +/*
>>>> + * Industrial I/O counter interface functions
>>>> + * Copyright (C) 2017 William Breathitt Gray
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify
>>>> + * it under the terms of the GNU General Public License, version 2, as
>>>> + * published by the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>>>> + * General Public License for more details.
>>>> + */
>>>> +#include <linux/err.h>
>>>> +#include <linux/export.h>
>>>> +#include <linux/gfp.h>
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/list.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/printk.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/string.h>
>>>> +
>>>> +#include <linux/iio/iio.h>
>>>> +#include <linux/iio/types.h>
>>>> +
>>>> +#include <linux/iio/counter.h>
>>>> +
>>>> +static struct iio_counter_signal *iio_counter_signal_find_by_id(
>>>> + const struct iio_counter *const counter, const int id)
>>>> +{
>>>> + struct iio_counter_signal *iter;
>>>> +
>>>> + list_for_each_entry(iter, &counter->signal_list, list)
>>>> + if (iter->id == id)
>>>> + return iter;
>>>> +
>>>> + return NULL;
>>>> +}
>>>> +
>>>> +static struct iio_counter_trigger *iio_counter_trigger_find_by_id(
>>>> + const struct iio_counter_value *const value, const int id)
>>>> +{
>>>> + struct iio_counter_trigger *iter;
>>>> +
>>>> + list_for_each_entry(iter, &value->trigger_list, list)
>>>> + if (iter->signal->id == id)
>>>> + return iter;
>>>> +
>>>> + return NULL;
>>>> +}
>>>> +
>>>> +static struct iio_counter_value *iio_counter_value_find_by_id(
>>>> + const struct iio_counter *const counter, const int id)
>>>> +{
>>>> + struct iio_counter_value *iter;
>>>> +
>>>> + list_for_each_entry(iter, &counter->value_list, list)
>>>> + if (iter->id == id)
>>>> + return iter;
>>>> +
>>>> + return NULL;
>>>> +}
>>>> +
>>>> +static void iio_counter_trigger_unregister_all(
>>>> + struct iio_counter_value *const value)
>>>> +{
>>>> + struct iio_counter_trigger *iter, *tmp_iter;
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> + list_for_each_entry_safe(iter, tmp_iter, &value->trigger_list, list)
>>>> + list_del(&iter->list);
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +}
>>>> +
>>>> +static void iio_counter_signal_unregister_all(struct iio_counter *const counter)
>>>> +{
>>>> + struct iio_counter_signal *iter, *tmp_iter;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + list_for_each_entry_safe(iter, tmp_iter, &counter->signal_list, list)
>>>> + list_del(&iter->list);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +}
>>>> +
>>>> +static void iio_counter_value_unregister_all(struct iio_counter *const counter)
>>>> +{
>>>> + struct iio_counter_value *iter, *tmp_iter;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + list_for_each_entry_safe(iter, tmp_iter, &counter->value_list, list) {
>>>> + iio_counter_trigger_unregister_all(iter);
>>>> +
>>>> + list_del(&iter->list);
>>>> + }
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_signal_name_read(struct iio_dev *indio_dev,
>>>> + uintptr_t priv, const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + const struct iio_counter_signal *signal;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + signal = iio_counter_signal_find_by_id(counter, chan->channel2);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> + if (!signal)
>>>> + return -EINVAL;
>>>> +
>>>> + return scnprintf(buf, PAGE_SIZE, "%s\n", signal->name);
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_value_name_read(struct iio_dev *indio_dev,
>>>> + uintptr_t priv, const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + const struct iio_counter_value *value;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + if (!value)
>>>> + return -EINVAL;
>>>> +
>>>> + return scnprintf(buf, PAGE_SIZE, "%s\n", value->name);
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_value_triggers_read(struct iio_dev *indio_dev,
>>>> + uintptr_t priv, const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + const struct iio_counter_trigger *trigger;
>>>> + ssize_t len = 0;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + len = -EINVAL;
>>>> + goto err_find_value;
>>>> + }
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> + list_for_each_entry(trigger, &value->trigger_list, list) {
>>>> + len += snprintf(buf + len, PAGE_SIZE - len, "%d\t%s\t%s\n",
>>>> + trigger->signal->id, trigger->signal->name,
>>>> + trigger->trigger_modes[trigger->mode]);
>>>> + if (len >= PAGE_SIZE) {
>>>> + len = -ENOMEM;
>>>> + goto err_no_buffer_space;
>>>> + }
>>>> + }
>>>> +err_no_buffer_space:
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> +err_find_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return len;
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_trigger_mode_read(struct iio_dev *indio_dev,
>>>> + uintptr_t priv, const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + ssize_t ret;
>>>> + struct iio_counter_trigger *trigger;
>>>> + const int signal_id = *(int *)((void *)priv);
>>>> + int mode;
>>>> +
>>>> + if (!counter->ops->trigger_mode_get)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + ret = -EINVAL;
>>>> + goto err_value;
>>>> + }
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> +
>>>> + trigger = iio_counter_trigger_find_by_id(value, signal_id);
>>>> + if (!trigger) {
>>>> + ret = -EINVAL;
>>>> + goto err_trigger;
>>>> + }
>>>> +
>>>> + mode = counter->ops->trigger_mode_get(counter, value, trigger);
>>>> +
>>>> + if (mode < 0) {
>>>> + ret = mode;
>>>> + goto err_trigger;
>>>> + } else if (mode >= trigger->num_trigger_modes) {
>>>> + ret = -EINVAL;
>>>> + goto err_trigger;
>>>> + }
>>>> +
>>>> + trigger->mode = mode;
>>>> +
>>>> + ret = scnprintf(buf, PAGE_SIZE, "%s\n", trigger->trigger_modes[mode]);
>>>> +
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return ret;
>>>> +
>>>> +err_trigger:
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +err_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_trigger_mode_write(struct iio_dev *indio_dev,
>>>> + uintptr_t priv, const struct iio_chan_spec *chan, const char *buf,
>>>> + size_t len)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + ssize_t err;
>>>> + struct iio_counter_trigger *trigger;
>>>> + const int signal_id = *(int *)((void *)priv);
>>>> + unsigned int mode;
>>>> +
>>>> + if (!counter->ops->trigger_mode_set)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + err = -EINVAL;
>>>> + goto err_value;
>>>> + }
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> +
>>>> + trigger = iio_counter_trigger_find_by_id(value, signal_id);
>>>> + if (!trigger) {
>>>> + err = -EINVAL;
>>>> + goto err_trigger;
>>>> + }
>>>> +
>>>> + for (mode = 0; mode < trigger->num_trigger_modes; mode++)
>>>> + if (sysfs_streq(buf, trigger->trigger_modes[mode]))
>>>> + break;
>>>> +
>>>> + if (mode >= trigger->num_trigger_modes) {
>>>> + err = -EINVAL;
>>>> + goto err_trigger;
>>>> + }
>>>> +
>>>> + err = counter->ops->trigger_mode_set(counter, value, trigger, mode);
>>>> + if (err)
>>>> + goto err_trigger;
>>>> +
>>>> + trigger->mode = mode;
>>>> +
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return len;
>>>> +
>>>> +err_trigger:
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +err_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static ssize_t iio_counter_trigger_mode_available_read(
>>>> + struct iio_dev *indio_dev, uintptr_t priv,
>>>> + const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + ssize_t len = 0;
>>>> + struct iio_counter_trigger *trigger;
>>>> + const int signal_id = *(int *)((void *)priv);
>>>> + unsigned int i;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + len = -EINVAL;
>>>> + goto err_no_value;
>>>> + }
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> +
>>>> + trigger = iio_counter_trigger_find_by_id(value, signal_id);
>>>> + if (!trigger) {
>>>> + len = -EINVAL;
>>>> + goto err_no_trigger;
>>>> + }
>>>> +
>>>> + for (i = 0; i < trigger->num_trigger_modes; i++)
>>>> + len += scnprintf(buf + len, PAGE_SIZE - len, "%s ",
>>>> + trigger->trigger_modes[i]);
>>>> +
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + buf[len - 1] = '\n';
>>>> +
>>>> + return len;
>>>> +
>>>> +err_no_trigger:
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +err_no_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + return len;
>>>> +}
>>>> +
>>>> +static int iio_counter_value_function_set(struct iio_dev *indio_dev,
>>>> + const struct iio_chan_spec *chan, unsigned int mode)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + int err;
>>>> +
>>>> + if (!counter->ops->value_function_set)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + err = -EINVAL;
>>>> + goto err_value;
>>>> + }
>>>> +
>>>> + err = counter->ops->value_function_set(counter, value, mode);
>>>> + if (err)
>>>> + goto err_value;
>>>> +
>>>> + value->mode = mode;
>>>> +
>>>> +err_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return err;
>>>> +}
>>>> +
>>>> +static int iio_counter_value_function_get(struct iio_dev *indio_dev,
>>>> + const struct iio_chan_spec *chan)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_value *value;
>>>> + int retval;
>>>> +
>>>> + if (!counter->ops->value_function_get)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + retval = -EINVAL;
>>>> + goto err_value;
>>>> + }
>>>> +
>>>> + retval = counter->ops->value_function_get(counter, value);
>>>> + if (retval < 0)
>>>> + goto err_value;
>>>> + else if (retval >= value->num_function_modes) {
>>>> + retval = -EINVAL;
>>>> + goto err_value;
>>>> + }
>>>> +
>>>> + value->mode = retval;
>>>> +
>>>> +err_value:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return retval;
>>>> +}
>>>> +
>>>> +static int iio_counter_value_ext_info_alloc(struct iio_chan_spec *const chan,
>>>> + struct iio_counter_value *const value)
>>>> +{
>>>> + const struct iio_chan_spec_ext_info ext_info_default[] = {
>>>> + {
>>>> + .name = "name",
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_counter_value_name_read
>>>> + },
>>>> + IIO_ENUM("function", IIO_SEPARATE, &value->function_enum),
>>>> + {
>>>> + .name = "function_available",
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_enum_available_read,
>>>> + .private = (void *)&value->function_enum
>>>I have this warning the 3 times that private field is set
>>>drivers/iio/industrialio-counter.c:401:15: warning: initialization
>>>makes integer from pointer without a cast [-Wint-conversion]
>>> .private = (void *)&value->function_enum
>>>
>>>I think you can change it to "void *" instead of a uintptr_t
>>
>> These are pretty benign warnings which you may ignore: I'm doing an
>> implicit conversion to uintptr_t which is ultimately an integer type; in
>> general, pointer to integer conversations are unsafe, but there is a
>> special exception in the C language for the uintptr_t data type. I could
>> have used an explicit cast to avoid the warning but opted to leave it
>> implicit to keep the code easier to read.
>>
>> However, it would be nice if the private member was void *, since the
>> intent of my code would be clearer in that case (passing a pointer for
>> later dereference). Unfortunately, I'm hesitant to submit a patch to
>> change the "private" member data type to void * since other drivers may
>> require the uintptr_t data type -- my suspicion is that there are some
>> drivers out there which do since the choice to introduce the "private"
>> member as a uintptr_t appears deliberate.
>>
>>>> + },
>>>> + {
>>>> + .name = "triggers",
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_counter_value_triggers_read
>>>> + }
>>>> + };
>>>> + const size_t num_default = ARRAY_SIZE(ext_info_default);
>>>> + const struct iio_chan_spec_ext_info ext_info_trigger[] = {
>>>> + {
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_counter_trigger_mode_read,
>>>> + .write = iio_counter_trigger_mode_write
>>>> + },
>>>> + {
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_counter_trigger_mode_available_read
>>>> + }
>>>> + };
>>>> + const size_t num_ext_info_trigger = ARRAY_SIZE(ext_info_trigger);
>>>> + const struct list_head *pos;
>>>> + size_t num_triggers = 0;
>>>> + size_t num_triggers_ext_info;
>>>> + size_t num_ext_info;
>>>> + int err;
>>>> + struct iio_chan_spec_ext_info *ext_info;
>>>> + const struct iio_counter_trigger *trigger_pos;
>>>> + size_t i;
>>>> +
>>>> + value->function_enum.items = value->function_modes;
>>>> + value->function_enum.num_items = value->num_function_modes;
>>>> + value->function_enum.set = iio_counter_value_function_set;
>>>> + value->function_enum.get = iio_counter_value_function_get;
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> +
>>>> + list_for_each(pos, &value->trigger_list)
>>>> + num_triggers++;
>>>> +
>>>> + num_triggers_ext_info = num_ext_info_trigger * num_triggers;
>>>> + num_ext_info = num_default + num_triggers_ext_info + 1;
>>>> +
>>>> + ext_info = kmalloc_array(num_ext_info, sizeof(*ext_info), GFP_KERNEL);
>>>> + if (!ext_info) {
>>>> + err = -ENOMEM;
>>>> + goto err_ext_info_alloc;
>>>> + }
>>>> + ext_info[num_ext_info - 1].name = NULL;
>>>> +
>>>> + memcpy(ext_info, ext_info_default, sizeof(ext_info_default));
>>>> + for (i = 0; i < num_triggers_ext_info; i += num_ext_info_trigger)
>>>> + memcpy(ext_info + num_default + i, ext_info_trigger,
>>>> + sizeof(ext_info_trigger));
>>>> +
>>>> + i = num_default;
>>>> + list_for_each_entry(trigger_pos, &value->trigger_list, list) {
>>>> + ext_info[i].name = kasprintf(GFP_KERNEL, "trigger_signal%d-%d",
>>>> + chan->channel, trigger_pos->signal->id);
>>>> + if (!ext_info[i].name) {
>>>> + err = -ENOMEM;
>>>> + goto err_name_alloc;
>>>> + }
>>>> + ext_info[i].private = (void *)&trigger_pos->signal->id;
>>>> + i++;
>>>> +
>>>> + ext_info[i].name = kasprintf(GFP_KERNEL,
>>>> + "trigger_signal%d-%d_available",
>>>> + chan->channel, trigger_pos->signal->id);
>>>> + if (!ext_info[i].name) {
>>>> + err = -ENOMEM;
>>>> + goto err_name_alloc;
>>>> + }
>>>> + ext_info[i].private = (void *)&trigger_pos->signal->id;
>>>> + i++;
>>>> + }
>>>> +
>>>> + chan->ext_info = ext_info;
>>>> +
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_name_alloc:
>>>> + while (i-- > num_default)
>>>> + kfree(ext_info[i].name);
>>>> + kfree(ext_info);
>>>> +err_ext_info_alloc:
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static void iio_counter_value_ext_info_free(
>>>> + const struct iio_chan_spec *const channel)
>>>> +{
>>>> + size_t i;
>>>> + const char *const prefix = "trigger_signal";
>>>> + const size_t prefix_len = strlen(prefix);
>>>> +
>>>> + for (i = 0; channel->ext_info[i].name; i++)
>>>> + if (!strncmp(channel->ext_info[i].name, prefix, prefix_len))
>>>> + kfree(channel->ext_info[i].name);
>>>> + kfree(channel->ext_info);
>>>> +}
>>>> +
>>>> +static const struct iio_chan_spec_ext_info iio_counter_signal_ext_info[] = {
>>>> + {
>>>> + .name = "name",
>>>> + .shared = IIO_SEPARATE,
>>>> + .read = iio_counter_signal_name_read
>>>> + },
>>>> + {}
>>>> +};
>>>> +
>>>> +static int iio_counter_channels_alloc(struct iio_counter *const counter)
>>>> +{
>>>> + const struct list_head *pos;
>>>> + size_t num_channels = 0;
>>>> + int err;
>>>> + struct iio_chan_spec *channels;
>>>> + struct iio_counter_value *value_pos;
>>>> + size_t i = counter->num_channels;
>>>> + const struct iio_counter_signal *signal_pos;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> +
>>>> + list_for_each(pos, &counter->signal_list)
>>>> + num_channels++;
>>>> +
>>>> + if (!num_channels) {
>>>> + err = -EINVAL;
>>>> + goto err_no_signals;
>>>> + }
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> +
>>>> + list_for_each(pos, &counter->value_list)
>>>> + num_channels++;
>>>> +
>>>> + num_channels += counter->num_channels;
>>>> +
>>>> + channels = kcalloc(num_channels, sizeof(*channels), GFP_KERNEL);
>>>> + if (!channels) {
>>>> + err = -ENOMEM;
>>>> + goto err_channels_alloc;
>>>> + }
>>>> +
>>>> + memcpy(channels, counter->channels,
>>>> + counter->num_channels * sizeof(*counter->channels));
>>>> +
>>>> + list_for_each_entry(value_pos, &counter->value_list, list) {
>>>> + channels[i].type = IIO_COUNT;
>>>> + channels[i].channel = counter->id;
>>>> + channels[i].channel2 = value_pos->id;
>>>> + channels[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
>>>> + channels[i].indexed = 1;
>>>> + channels[i].counter = 1;
>>>> +
>>>> + err = iio_counter_value_ext_info_alloc(channels + i, value_pos);
>>>> + if (err)
>>>> + goto err_value_ext_info_alloc;
>>>> +
>>>> + i++;
>>>> + }
>>>> +
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + list_for_each_entry(signal_pos, &counter->signal_list, list) {
>>>> + channels[i].type = IIO_SIGNAL;
>>>> + channels[i].channel = counter->id;
>>>> + channels[i].channel2 = signal_pos->id;
>>>> + channels[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
>>>> + channels[i].indexed = 1;
>>>> + channels[i].counter = 1;
>>>> + channels[i].ext_info = iio_counter_signal_ext_info;
>>>> +
>>>> + i++;
>>>> + }
>>>> +
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +
>>>> + counter->indio_dev->num_channels = num_channels;
>>>> + counter->indio_dev->channels = channels;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_value_ext_info_alloc:
>>>> + while (i-- > counter->num_channels)
>>>> + iio_counter_value_ext_info_free(channels + i);
>>>> + kfree(channels);
>>>> +err_channels_alloc:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +err_no_signals:
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static void iio_counter_channels_free(const struct iio_counter *const counter)
>>>> +{
>>>> + size_t i = counter->num_channels + counter->indio_dev->num_channels;
>>>> + const struct iio_chan_spec *const chans = counter->indio_dev->channels;
>>>> +
>>>> + while (i-- > counter->num_channels)
>>>> + if (chans[i].type == IIO_COUNT)
>>>> + iio_counter_value_ext_info_free(chans + i);
>>>> +
>>>> + kfree(chans);
>>>> +}
>>>> +
>>>> +static int iio_counter_read_raw(struct iio_dev *indio_dev,
>>>> + struct iio_chan_spec const *chan, int *val, int *val2, long mask)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_signal *signal;
>>>> + int retval;
>>>> + struct iio_counter_value *value;
>>>> +
>>>> + if (mask != IIO_CHAN_INFO_RAW)
>>>> + return -EINVAL;
>>>> +
>>>> + switch (chan->type) {
>>>> + case IIO_SIGNAL:
>>>> + if (!counter->ops->signal_read)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + signal = iio_counter_signal_find_by_id(counter, chan->channel2);
>>>> + if (!signal) {
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + retval = counter->ops->signal_read(counter, signal, val, val2);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +
>>>> + return retval;
>>>> + case IIO_COUNT:
>>>> + if (!counter->ops->value_read)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + retval = counter->ops->value_read(counter, value, val, val2);
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return retval;
>>>> + default:
>>>> + if (counter->info && counter->info->read_raw)
>>>> + return counter->info->read_raw(indio_dev, chan, val,
>>>> + val2, mask);
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static int iio_counter_write_raw(struct iio_dev *indio_dev,
>>>> + struct iio_chan_spec const *chan, int val, int val2, long mask)
>>>> +{
>>>> + struct iio_counter *const counter = iio_priv(indio_dev);
>>>> + struct iio_counter_signal *signal;
>>>> + int retval;
>>>> + struct iio_counter_value *value;
>>>> +
>>>> + if (mask != IIO_CHAN_INFO_RAW)
>>>> + return -EINVAL;
>
>I have an issue with this check because I believe it make impossible to call
>the callback registered in iio_info since the channel isn't always
>IIO_CHAN_INFO_RAW
>but could be IIO_CHAN_INFO_ENABLE or IIO_CHAN_INFO_SCALE in my driver.
>Maybe it is comming from my code be I don't understand how it could
>works in 104-quad-8 either
This may be a bug I overlooked because my intention is to pass
non-counter channels directly to write_raw. I will inspect this more
throughly when I finish the next version of this patchset.
>
>>>> +
>>>> + switch (chan->type) {
>>>> + case IIO_SIGNAL:
>>>> + if (!counter->ops->signal_write)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + signal = iio_counter_signal_find_by_id(counter, chan->channel2);
>>>> + if (!signal) {
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + retval = counter->ops->signal_write(counter, signal, val, val2);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +
>>>> + return retval;
>>>> + case IIO_COUNT:
>>>> + if (!counter->ops->value_write)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + value = iio_counter_value_find_by_id(counter, chan->channel2);
>>>> + if (!value) {
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + retval = counter->ops->value_write(counter, value, val, val2);
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return retval;
>>>> + default:
>>>> + if (counter->info && counter->info->write_raw)
>>>> + return counter->info->write_raw(indio_dev, chan, val,
>>>> + val2, mask);
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static int iio_counter_signal_register(struct iio_counter *const counter,
>>>> + struct iio_counter_signal *const signal)
>>>> +{
>>>> + int err;
>>>> +
>>>> + if (!counter || !signal)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + if (iio_counter_signal_find_by_id(counter, signal->id)) {
>>>> + pr_err("Duplicate counter signal ID '%d'\n", signal->id);
>>>> + err = -EEXIST;
>>>> + goto err_duplicate_id;
>>>> + }
>>>> + list_add_tail(&signal->list, &counter->signal_list);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_duplicate_id:
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static void iio_counter_signal_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_signal *const signal)
>>>> +{
>>>> + if (!counter || !signal)
>>>> + return;
>>>> +
>>>> + mutex_lock(&counter->signal_list_lock);
>>>> + list_del(&signal->list);
>>>> + mutex_unlock(&counter->signal_list_lock);
>>>> +}
>>>> +
>>>> +static int iio_counter_signals_register(struct iio_counter *const counter,
>>>> + struct iio_counter_signal *const signals, const size_t num_signals)
>>>> +{
>>>> + size_t i;
>>>> + int err;
>>>> +
>>>> + if (!counter || !signals)
>>>> + return -EINVAL;
>>>> +
>>>> + for (i = 0; i < num_signals; i++) {
>>>> + err = iio_counter_signal_register(counter, signals + i);
>>>> + if (err)
>>>> + goto err_signal_register;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_signal_register:
>>>> + while (i--)
>>>> + iio_counter_signal_unregister(counter, signals + i);
>>>> + return err;
>>>> +}
>>>> +
>>>> +static void iio_counter_signals_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_signal *signals, size_t num_signals)
>>>> +{
>>>> + if (!counter || !signals)
>>>> + return;
>>>> +
>>>> + while (num_signals--) {
>>>> + iio_counter_signal_unregister(counter, signals);
>>>> + signals++;
>>>> + }
>>>> +}
>>>> +
>>>> +/**
>>>> + * iio_counter_trigger_register - register Trigger to Value
>>>> + * @value: pointer to IIO Counter Value for association
>>>> + * @trigger: pointer to IIO Counter Trigger to register
>>>> + *
>>>> + * The Trigger is added to the Value's trigger_list. A check is first performed
>>>> + * to verify that the respective Signal is not already linked to the Value; if
>>>> + * the respective Signal is already linked to the Value, the Trigger is not
>>>> + * added to the Value's trigger_list.
>>>> + *
>>>> + * NOTE: This function will acquire and release the Value's trigger_list_lock
>>>> + * during execution.
>>>> + */
>>>> +int iio_counter_trigger_register(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const trigger)
>>>> +{
>>>> + if (!value || !trigger || !trigger->signal)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> + if (iio_counter_trigger_find_by_id(value, trigger->signal->id)) {
>>>> + pr_err("Signal%d is already linked to counter value%d\n",
>>>> + trigger->signal->id, value->id);
>>>> + return -EEXIST;
>>>> + }
>>>> + list_add_tail(&trigger->list, &value->trigger_list);
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_trigger_register);
>>>> +
>>>> +/**
>>>> + * iio_counter_trigger_unregister - unregister Trigger from Value
>>>> + * @value: pointer to IIO Counter Value of association
>>>> + * @trigger: pointer to IIO Counter Trigger to unregister
>>>> + *
>>>> + * The Trigger is removed from the Value's trigger_list.
>>>> + *
>>>> + * NOTE: This function will acquire and release the Value's trigger_list_lock
>>>> + * during execution.
>>>> + */
>>>> +void iio_counter_trigger_unregister(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const trigger)
>>>> +{
>>>> + if (!value || !trigger || !trigger->signal)
>>>> + return;
>>>> +
>>>> + mutex_lock(&value->trigger_list_lock);
>>>> + list_del(&trigger->list);
>>>> + mutex_unlock(&value->trigger_list_lock);
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_trigger_unregister);
>>>> +
>>>> +/**
>>>> + * iio_counter_triggers_register - register an array of Triggers to Value
>>>> + * @value: pointer to IIO Counter Value for association
>>>> + * @triggers: array of pointers to IIO Counter Triggers to register
>>>> + *
>>>> + * The iio_counter_trigger_register function is called for each Trigger in the
>>>> + * array. The @triggers array is traversed for the first @num_triggers Triggers.
>>>> + *
>>>> + * NOTE: @num_triggers must not be greater than the size of the @triggers array.
>>>> + */
>>>> +int iio_counter_triggers_register(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const triggers, const size_t num_triggers)
>>>> +{
>>>> + size_t i;
>>>> + int err;
>>>> +
>>>> + if (!value || !triggers)
>>>> + return -EINVAL;
>>>> +
>>>> + for (i = 0; i < num_triggers; i++) {
>>>> + err = iio_counter_trigger_register(value, triggers + i);
>>>> + if (err)
>>>> + goto err_trigger_register;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_trigger_register:
>>>> + while (i--)
>>>> + iio_counter_trigger_unregister(value, triggers + i);
>>>> + return err;
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_triggers_register);
>>>> +
>>>> +/**
>>>> + * iio_counter_triggers_unregister - unregister Triggers from Value
>>>> + * @value: pointer to IIO Counter Value of association
>>>> + * @triggers: array of pointers to IIO Counter Triggers to unregister
>>>> + *
>>>> + * The iio_counter_trigger_unregister function is called for each Trigger in the
>>>> + * array. The @triggers array is traversed for the first @num_triggers Triggers.
>>>> + *
>>>> + * NOTE: @num_triggers must not be greater than the size of the @triggers array.
>>>> + */
>>>> +void iio_counter_triggers_unregister(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *triggers, size_t num_triggers)
>>>> +{
>>>> + if (!value || !triggers)
>>>> + return;
>>>> +
>>>> + while (num_triggers--) {
>>>> + iio_counter_trigger_unregister(value, triggers);
>>>> + triggers++;
>>>> + }
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_triggers_unregister);
>>>> +
>>>> +/**
>>>> + * iio_counter_value_register - register Value to Counter
>>>> + * @counter: pointer to IIO Counter for association
>>>> + * @value: pointer to IIO Counter Value to register
>>>> + *
>>>> + * The registration process occurs in two major steps. First, the Value is
>>>> + * initialized: trigger_list_lock is initialized, trigger_list is initialized,
>>>> + * and init_triggers if not NULL is passed to iio_counter_triggers_register.
>>>> + * Second, the Value is added to the Counter's value_list. A check is first
>>>> + * performed to verify that the Value is not already associated to the Counter
>>>> + * (via the Value's unique ID); if the Value is already associated to the
>>>> + * Counter, the Value is not added to the Counter's value_list and all of the
>>>> + * Value's Triggers are unregistered.
>>>> + *
>>>> + * NOTE: This function will acquire and release the Counter's value_list_lock
>>>> + * during execution.
>>>> + */
>>>> +int iio_counter_value_register(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const value)
>>>> +{
>>>> + int err;
>>>> +
>>>> + if (!counter || !value)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_init(&value->trigger_list_lock);
>>>> + INIT_LIST_HEAD(&value->trigger_list);
>>>> +
>>>> + if (value->init_triggers) {
>>>> + err = iio_counter_triggers_register(value,
>>>> + value->init_triggers, value->num_init_triggers);
>>>> + if (err)
>>>> + return err;
>>>> + }
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + if (iio_counter_value_find_by_id(counter, value->id)) {
>>>> + pr_err("Duplicate counter value ID '%d'\n", value->id);
>>>> + err = -EEXIST;
>>>> + goto err_duplicate_id;
>>>> + }
>>>> + list_add_tail(&value->list, &counter->value_list);
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_duplicate_id:
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> + iio_counter_trigger_unregister_all(value);
>>>> + return err;
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_value_register);
>>>> +
>>>> +/**
>>>> + * iio_counter_value_unregister - unregister Value from Counter
>>>> + * @counter: pointer to IIO Counter of association
>>>> + * @value: pointer to IIO Counter Value to unregister
>>>> + *
>>>> + * The Value is removed from the Counter's value_list and all of the Value's
>>>> + * Triggers are unregistered.
>>>> + *
>>>> + * NOTE: This function will acquire and release the Counter's value_list_lock
>>>> + * during execution.
>>>> + */
>>>> +void iio_counter_value_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const value)
>>>> +{
>>>> + if (!counter || !value)
>>>> + return;
>>>> +
>>>> + mutex_lock(&counter->value_list_lock);
>>>> + list_del(&value->list);
>>>> + mutex_unlock(&counter->value_list_lock);
>>>> +
>>>> + iio_counter_trigger_unregister_all(value);
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_value_unregister);
>>>> +
>>>> +/**
>>>> + * iio_counter_values_register - register an array of Values to Counter
>>>> + * @counter: pointer to IIO Counter for association
>>>> + * @values: array of pointers to IIO Counter Values to register
>>>> + *
>>>> + * The iio_counter_value_register function is called for each Value in the
>>>> + * array. The @values array is traversed for the first @num_values Values.
>>>> + *
>>>> + * NOTE: @num_values must not be greater than the size of the @values array.
>>>> + */
>>>> +int iio_counter_values_register(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const values, const size_t num_values)
>>>> +{
>>>> + size_t i;
>>>> + int err;
>>>> +
>>>> + if (!counter || !values)
>>>> + return -EINVAL;
>>>> +
>>>> + for (i = 0; i < num_values; i++) {
>>>> + err = iio_counter_value_register(counter, values + i);
>>>> + if (err)
>>>> + goto err_values_register;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_values_register:
>>>> + while (i--)
>>>> + iio_counter_value_unregister(counter, values + i);
>>>> + return err;
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_values_register);
>>>> +
>>>> +/**
>>>> + * iio_counter_values_unregister - unregister Values from Counter
>>>> + * @counter: pointer to IIO Counter of association
>>>> + * @values: array of pointers to IIO Counter Values to unregister
>>>> + *
>>>> + * The iio_counter_value_unregister function is called for each Value in the
>>>> + * array. The @values array is traversed for the first @num_values Values.
>>>> + *
>>>> + * NOTE: @num_values must not be greater than the size of the @values array.
>>>> + */
>>>> +void iio_counter_values_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_value *values, size_t num_values)
>>>> +{
>>>> + if (!counter || !values)
>>>> + return;
>>>> +
>>>> + while (num_values--) {
>>>> + iio_counter_value_unregister(counter, values);
>>>> + values++;
>>>> + }
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_values_unregister);
>>>> +
>>>> +/**
>>>> + * iio_counter_register - register Counter to the system
>>>> + * @counter: pointer to IIO Counter to register
>>>> + *
>>>> + * This function piggybacks off of iio_device_register. First, the relevant
>>>> + * Counter members are initialized; if init_signals is not NULL it is passed to
>>>> + * iio_counter_signals_register, and similarly if init_values is not NULL it is
>>>> + * passed to iio_counter_values_register. Next, a struct iio_dev is allocated by
>>>> + * a call to iio_device_alloc and initialized for the Counter, IIO channels are
>>>> + * allocated, the Counter is copied as the private data, and finally
>>>> + * iio_device_register is called.
>>>> + */
>>>> +int iio_counter_register(struct iio_counter *const counter)
>>>> +{
>>>> + const struct iio_info info_default = {
>>>> + .read_raw = iio_counter_read_raw,
>>>> + .write_raw = iio_counter_write_raw
>>>> + };
>>>> + int err;
>>>> + struct iio_info *info;
>>>> + struct iio_counter *priv;
>>>> +
>>>> + if (!counter)
>>>> + return -EINVAL;
>>>> +
>>>> + mutex_init(&counter->signal_list_lock);
>>>> + INIT_LIST_HEAD(&counter->signal_list);
>>>> +
>>>> + if (counter->init_signals) {
>>>> + err = iio_counter_signals_register(counter,
>>>> + counter->init_signals, counter->num_init_signals);
>>>> + if (err)
>>>> + return err;
>>>> + }
>>>> +
>>>> + mutex_init(&counter->value_list_lock);
>>>> + INIT_LIST_HEAD(&counter->value_list);
>>>> +
>>>> + if (counter->init_values) {
>>>> + err = iio_counter_values_register(counter,
>>>> + counter->init_values, counter->num_init_values);
>>>> + if (err)
>>>> + goto err_values_register;
>>>> + }
>>>> +
>>>> + counter->indio_dev = iio_device_alloc(sizeof(*counter));
>>>> + if (!counter->indio_dev) {
>>>> + err = -ENOMEM;
>>>> + goto err_iio_device_alloc;
>>>> + }
>>>> +
>>>> + info = kmalloc(sizeof(*info), GFP_KERNEL);
>>>> + if (!info) {
>>>> + err = -ENOMEM;
>>>> + goto err_info_alloc;
>>>> + }
>>>> + if (counter->info) {
>>>> + memcpy(info, counter->info, sizeof(*counter->info));
>>>> + info->read_raw = iio_counter_read_raw;
>>>> + info->write_raw = iio_counter_write_raw;
>>>> + } else {
>>>> + memcpy(info, &info_default, sizeof(info_default));
>>>> + }
>>>> +
>>>> + counter->indio_dev->info = info;
>>>> + counter->indio_dev->modes = INDIO_DIRECT_MODE;
>
>For stm32 triggerd river I need to be able to change indio_dev->modes
>to INDIO_HARDWARE_TRIGGERED
>Could you add a parameter to be able to configure teh modes ?
The more time I work on the Generic Counter interface, the more I want
to separate the IIO core components from it. I feel that embedding the
iio_dev to the iio_counter structure ends up restricting the
configurability of IIO core components as you have encountered here. I
also fear the possibility of API coupling issues down the road, where
IIO core functionality modifications will lead to a snowball effect of
changes to the Generic Counter iterface (and subsequent derivative
Counter classes) in order to maintain compatibility.
This is an architectural decision I have to make before we merge, so
I'll give it some thought throughout this process. For now, I will
consider adding a modes member to struct iio_counter so that you may
configure indio_dev->modes; this will likely be introduced in version 4
of this patchset after the higher-level paradigm has been solidified.
>
>>>> + counter->indio_dev->name = counter->name;
>>>> + counter->indio_dev->dev.parent = counter->dev;
>>>> +
>>>> + err = iio_counter_channels_alloc(counter);
>>>> + if (err)
>>>> + goto err_channels_alloc;
>>>> +
>>>> + priv = iio_priv(counter->indio_dev);
>>>> + memcpy(priv, counter, sizeof(*priv));
>>>> +
>>>> + err = iio_device_register(priv->indio_dev);
>>>> + if (err)
>>>> + goto err_iio_device_register;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_iio_device_register:
>>>> + iio_counter_channels_free(counter);
>>>> +err_channels_alloc:
>>>> + kfree(info);
>>>> +err_info_alloc:
>>>> + iio_device_free(counter->indio_dev);
>>>> +err_iio_device_alloc:
>>>> + iio_counter_values_unregister(counter, counter->init_values,
>>>> + counter->num_init_values);
>>>> +err_values_register:
>>>> + iio_counter_signals_unregister(counter, counter->init_signals,
>>>> + counter->num_init_signals);
>>>> + return err;
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_register);
>>>> +
>>>> +/**
>>>> + * iio_counter_unregister - unregister Counter from the system
>>>> + * @counter: pointer to IIO Counter to unregister
>>>> + *
>>>> + * The Counter is unregistered from the system. The indio_dev is unregistered,
>>>> + * allocated memory is freed, and all associated Values and Signals are
>>>> + * unregistered.
>>>> + */
>>>> +void iio_counter_unregister(struct iio_counter *const counter)
>>>> +{
>>>> + const struct iio_info *const info = counter->indio_dev->info;
>>>> +
>>>> + if (!counter)
>>>> + return;
>>>> +
>>>> + iio_device_unregister(counter->indio_dev);
>>>> +
>>>> + iio_counter_channels_free(counter);
>>>> +
>>>> + kfree(info);
>>>> + iio_device_free(counter->indio_dev);
>>>> +
>>>> + iio_counter_value_unregister_all(counter);
>>>> + iio_counter_signal_unregister_all(counter);
>>>> +}
>>>> +EXPORT_SYMBOL(iio_counter_unregister);
>
>I have added devm_iio_counter_register and devm_iio_counter_unregister to avoid
>add remove in driver code. If that make sense for you can add tehm in your code
>/**
> * devm_iio_counter_register - Resource-managed iio_counter_register()
> * @dev: Device to allocate iio_dev for
> * @counter: pointer to IIO Counter to register
> *
> * Managed iio_counter_register. The IIO counter registered with this
> * function is automatically unregistered on driver detach. This function
> * calls iio_counter_register() internally. Refer to that function for more
> * information.
> *
> * If an iio counter registered with this function needs to be unregistered
> * separately, devm_iio_counter_unregister() must be used.
> *
> * RETURNS:
> * 0 on success, negative error number on failure.
> */
>int devm_iio_counter_register(struct device *dev,
> struct iio_counter *const counter)
>{
>struct iio_counter **ptr;
>int ret;
>
>ptr = devres_alloc(devm_iio_counter_unreg, sizeof(*ptr), GFP_KERNEL);
>if (!ptr)
>return -ENOMEM;
>
>*ptr = counter;
>ret = iio_counter_register(counter);
>if (!ret)
>devres_add(dev, ptr);
>else
>devres_free(ptr);
>
>return ret;
>
>}
>EXPORT_SYMBOL(devm_iio_counter_register);
>
>static int devm_iio_counter_match(struct device *dev, void *res, void *data)
>{
>struct iio_counter **r = res;
>if (!r || !*r) {
>WARN_ON(!r || !*r);
>return 0;
>}
>return *r == data;
>}
>
>/**
> * devm_iio_counter_unregister - Resource-managed iio_counter_unregister()
> * @dev: Device this iio_dev belongs to
> * @counter: the iio counter associated with the device
> *
> * Unregister iio counter registered with devm_iio_counter_register().
> */
>void devm_iio_counter_unregister(struct device *dev,
>struct iio_counter *const counter)
>{
>int rc;
>
>rc = devres_release(dev, devm_iio_counter_unreg,
> devm_iio_counter_match, counter);
>WARN_ON(rc);
>}
>EXPORT_SYMBOL(devm_iio_counter_unregister);
Thanks! I will incorporate this code into the patchset. :)
>
>>>> diff --git a/include/linux/iio/counter.h b/include/linux/iio/counter.h
>>>> new file mode 100644
>>>> index 000000000000..1c1ca13a6053
>>>> --- /dev/null
>>>> +++ b/include/linux/iio/counter.h
>>>> @@ -0,0 +1,221 @@
>>>> +/*
>>>> + * Industrial I/O counter interface
>>>> + * Copyright (C) 2017 William Breathitt Gray
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify
>>>> + * it under the terms of the GNU General Public License, version 2, as
>>>> + * published by the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>>>> + * General Public License for more details.
>>>> + */
>>>> +#ifndef _IIO_COUNTER_H_
>>>> +#define _IIO_COUNTER_H_
>>>> +
>>>> +#ifdef CONFIG_IIO_COUNTER
>>>> +
>>>> +#include <linux/device.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/types.h>
>>>> +
>>>> +#include <linux/iio/iio.h>
>>>> +
>>>> +/**
>>>> + * struct iio_counter_signal - IIO Counter Signal node
>>>> + * @id: [DRIVER] unique ID used to identify signal
>>>> + * @name: [DRIVER] device-specific signal name
>>>> + * @list: [INTERN] list of all signals currently registered to counter
>>>> + */
>>>> +struct iio_counter_signal {
>>>> + int id;
>>>> + const char *name;
>>>> +
>>>> + struct list_head list;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct iio_counter_trigger - IIO Counter Trigger node
>>>> + * @mode: [DRIVER] current trigger mode state
>>>> + * @trigger_modes: [DRIVER] available trigger modes
>>>> + * @num_trigger_modes: [DRIVER] number of modes specified in @trigger_modes
>>>> + * @signal: [DRIVER] pointer to associated signal
>>>> + * @list: [INTERN] list of all triggers currently registered to
>>>> + * value
>>>> + */
>>>> +struct iio_counter_trigger {
>>>> + unsigned int mode;
>>>> + const char *const *trigger_modes;
>>>> + unsigned int num_trigger_modes;
>>>> + struct iio_counter_signal *signal;
>>>> +
>>>> + struct list_head list;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct iio_counter_value - IIO Counter Value node
>>>> + * @id: [DRIVER] unique ID used to identify value
>>>> + * @name: [DRIVER] device-specific value name
>>>> + * @mode: [DRIVER] current function mode state
>>>> + * @function_modes: [DRIVER] available function modes
>>>> + * @num_function_modes: [DRIVER] number of modes specified in @function_modes
>>>> + * @init_triggers: [DRIVER] array of triggers for initialization
>>>> + * @num_init_triggers: [DRIVER] number of triggers specified in @init_triggers
>>>> + * @function_enum: [INTERN] used internally to generate function attributes
>>>> + * @trigger_list_lock: [INTERN] lock for accessing @trigger_list
>>>> + * @trigger_list: [INTERN] list of all triggers currently registered to
>>>> + * value
>>>> + * @list: [INTERN] list of all values currently registered to
>>>> + * counter
>>>> + */
>>>> +struct iio_counter_value {
>>>> + int id;
>>>> + const char *name;
>>>> + unsigned int mode;
>>>> + const char *const *function_modes;
>>>> + unsigned int num_function_modes;
>>>> +
>>>> + struct iio_counter_trigger *init_triggers;
>>>> + size_t num_init_triggers;
>>>> +
>>>> + struct iio_enum function_enum;
>>>> + struct mutex trigger_list_lock;
>>>> + struct list_head trigger_list;
>>>> +
>>>> + struct list_head list;
>>>> +};
>>>> +
>>>> +struct iio_counter;
>>>> +
>>>> +/**
>>>> + * struct iio_counter_ops - IIO Counter related callbacks
>>>> + * @signal_read: function to request a signal value from the device.
>>>> + * Return value will specify the type of value returned by
>>>> + * the device. val and val2 will contain the elements
>>>> + * making up the returned value. Note that the counter
>>>> + * signal_list_lock is acquired before this function is
>>>> + * called, and released after this function returns.
>>>> + * @signal_write: function to write a signal value to the device.
>>>> + * Parameters and locking behavior are the same as
>>>> + * signal_read.
>>>> + * @trigger_mode_set: function to set the trigger mode. mode is the index of
>>>> + * the requested mode from the value trigger_modes array.
>>>> + * Note that the counter value_list_lock and value
>>>> + * trigger_list_lock are acquired before this function is
>>>> + * called, and released after this function returns.
>>>> + * @trigger_mode_get: function to get the current trigger mode. Return value
>>>> + * will specify the index of the current mode from the
>>>> + * value trigger_modes array. Locking behavior is the same
>>>> + * as trigger_mode_set.
>>>> + * @value_read: function to request a value value from the device.
>>>> + * Return value will specify the type of value returned by
>>>> + * the device. val and val2 will contain the elements
>>>> + * making up the returned value. Note that the counter
>>>> + * value_list_lock is acquired before this function is
>>>> + * called, and released after this function returns.
>>>> + * @value_write: function to write a value value to the device.
>>>> + * Parameters and locking behavior are the same as
>>>> + * value_read.
>>>> + * @value_function_set: function to set the value function mode. mode is the
>>>> + * index of the requested mode from the value
>>>> + * function_modes array. Note that the counter
>>>> + * value_list_lock is acquired before this function is
>>>> + * called, and released after this function returns.
>>>> + * @value_function_get: function to get the current value function mode. Return
>>>> + * value will specify the index of the current mode from
>>>> + * the value function_modes array. Locking behavior is the
>>>> + * same as value_function_get.
>>>> + */
>>>> +struct iio_counter_ops {
>>>> + int (*signal_read)(struct iio_counter *counter,
>>>> + struct iio_counter_signal *signal, int *val, int *val2);
>>>> + int (*signal_write)(struct iio_counter *counter,
>>>> + struct iio_counter_signal *signal, int val, int val2);
>>>> + int (*trigger_mode_set)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value,
>>>> + struct iio_counter_trigger *trigger, unsigned int mode);
>>>> + int (*trigger_mode_get)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value,
>>>> + struct iio_counter_trigger *trigger);
>>>> + int (*value_read)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value, int *val, int *val2);
>>>> + int (*value_write)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value, int val, int val2);
>>>> + int (*value_function_set)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value, unsigned int mode);
>>>> + int (*value_function_get)(struct iio_counter *counter,
>>>> + struct iio_counter_value *value);
>>>> +};
>
>I think it would be more clear if structures iio_counter_signal,
>iio_counter_trigger
>and iio_counter_value embedded the functions related to their use.
>For example struct iio_counter_trigger could have trigger_mode_set and
>trigger_mode_get
>functions. If you do like that you can remove iio_counter_ops.
Hmm, this looks possible and would allow callbacks to be
component-specific too. However, I wonder if this would make
initialization burdensome (you would have to set every component with
their respective callbacks), and also whether component-specific
callbacks are that useful if all the components end up using the same
callback.
I will have to compare the advantages and disadvantages to each
approach.
>
>Do you think that some functions like get/set counter value, direction
>and counter
>range (min, max) are generic enough to be included in
>iio_counter_value structure ?
Yes, these are functions I believe will end up in a Counter interface at
some point as most counter devices have them in some way or another. For
this patchset however, I would like to focus on the bare necessities of
a Generic Counter. I intend to migrate these functionalities from IIO
core in future patches after the merge, where I will be able to focus on
finding an appropriate spot for them in a relevant Counter API.
>
>>>> +
>>>> +/**
>>>> + * struct iio_counter - IIO Counter data structure
>>>> + * @id: [DRIVER] unique ID used to identify counter
>>>> + * @name: [DRIVER] name of the device
>>>> + * @dev: [DRIVER] device structure, should be assigned a parent
>>>> + * and owner
>>>> + * @ops: [DRIVER] callbacks from driver for counter components
>>>> + * @init_signals: [DRIVER] array of signals for initialization
>>>> + * @num_init_signals: [DRIVER] number of signals specified in @init_signals
>>>> + * @init_values: [DRIVER] array of values for initialization
>>>> + * @num_init_values: [DRIVER] number of values specified in @init_values
>>>> + * @signal_list_lock: [INTERN] lock for accessing @signal_list
>>>> + * @signal_list: [INTERN] list of all signals currently registered to
>>>> + * counter
>>>> + * @value_list_lock: [INTERN] lock for accessing @value_list
>>>> + * @value_list: [INTERN] list of all values currently registered to
>>>> + * counter
>>>> + * @channels: [DRIVER] channel specification structure table
>>>> + * @num_channels: [DRIVER] number of channels specified in @channels
>>>> + * @info: [DRIVER] callbacks and constant info from driver
>>>> + * @indio_dev: [INTERN] industrial I/O device structure
>>>> + * @driver_data: [DRIVER] driver data
>>>> + */
>>>> +struct iio_counter {
>>>> + int id;
>>>> + const char *name;
>>>> + struct device *dev;
>
>id, name and dev are already in struct iio_dev, maybe we can remove them ?
I originally anticipated multiple Counters associated with a single
iio_dev, however my code right now assumes a one-to-one relationship. If
it ends up staying this way, then we can remove these members since they
will likely be redundant with the indio_dev member.
>
>>>> + const struct iio_counter_ops *ops;
>>>> +
>>>> + struct iio_counter_signal *init_signals;
>>>> + size_t num_init_signals;
>
>I think we can remove init_signals and num_init_signals from this structure
>because they are already linked with the values through trigger structure.
It is possible for a single Signal to be associated to multiple Values
via Triggers to each. For this reason, I believe there is value in
having an array of all the Signals together, rather than try to generate
this array by inferring the relationships between Values and their
Triggers -- although possible, building the array is more error-prone
and computationally expensive when dealing with multiple possible
mappings than simply requesting the array of all Signals is provided by
the driver.
>
>>>> + struct iio_counter_value *init_values;
>>>> + size_t num_init_values;
>>>> +
>>>> + struct mutex signal_list_lock;
>>>> + struct list_head signal_list;
>>>> + struct mutex value_list_lock;
>>>> + struct list_head value_list;
>>>> +
>>>> + const struct iio_chan_spec *channels;
>>>> + size_t num_channels;
>>>> + const struct iio_info *info;
>>>> +
>>>> + struct iio_dev *indio_dev;
>>>> + void *driver_data;
>
>"driver_data" => "priv" to do like iio_dev
I'm somewhat indifferent to the naming convention, but I do think 'priv'
may be a little too vague: a driver author may not initially realize
it's for their use and not internally modified by the system; although
'priv' does benefit by matching the iio_dev naming convention. Perhaps
Jonathan may have a suggestion.
William Breathitt Gray
>
>>>> +};
>>>> +
>>>> +int iio_counter_trigger_register(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const trigger);
>>>> +void iio_counter_trigger_unregister(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const trigger);
>>>> +int iio_counter_triggers_register(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *const triggers, const size_t num_triggers);
>>>> +void iio_counter_triggers_unregister(struct iio_counter_value *const value,
>>>> + struct iio_counter_trigger *triggers, size_t num_triggers);
>>>> +
>>>> +int iio_counter_value_register(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const value);
>>>> +void iio_counter_value_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const value);
>>>> +int iio_counter_values_register(struct iio_counter *const counter,
>>>> + struct iio_counter_value *const values, const size_t num_values);
>>>> +void iio_counter_values_unregister(struct iio_counter *const counter,
>>>> + struct iio_counter_value *values, size_t num_values);
>>>> +
>>>> +int iio_counter_register(struct iio_counter *const counter);
>>>> +void iio_counter_unregister(struct iio_counter *const counter);
>>>> +
>>>> +#endif /* CONFIG_IIO_COUNTER */
>>>> +
>>>> +#endif /* _IIO_COUNTER_H_ */
>>>> --
>>>> 2.14.1
>>>>
Powered by blists - more mailing lists