[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <ae131029e31c4ad54092dd247e411dd3929b0e2d.1507220144.git.vilhelm.gray@gmail.com>
Date: Thu, 5 Oct 2017 14:13:44 -0400
From: William Breathitt Gray <vilhelm.gray@...il.com>
To: jic23@...nel.org, benjamin.gaignard@...aro.org, knaack.h@....de,
lars@...afoo.de, pmeerw@...erw.net
Cc: linux-iio@...r.kernel.org, linux-kernel@...r.kernel.org,
William Breathitt Gray <vilhelm.gray@...il.com>
Subject: [PATCH v3 2/6] iio: Introduce the generic counter interface
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 (Triggers) 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 Signals and Values, and set and get the "trigger mode"
and "function mode" for various Triggers and Values respectively.
To support a counter device, a driver must first allocate the available
Counter Signals via iio_counter_signal structures. These Signals should
be stored as an array and set to the signals array member of an
allocated iio_counter structure before the Counter is registered to the
system.
Counter Values may be allocated via iio_counter_value structures, and
respective Counter Signal associations (Triggers) made via
iio_counter_trigger structures. Associated iio_counter_trigger
structures are stored as an array and set to the the triggers array
member of the respective iio_counter_value structure. These
iio_counter_value structures are set to the values array member of an
allocated iio_counter structure before the Counter is registered to the
system.
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. The devm_iio_counter_register and
iio_devm_iio_counter_unregister functions serve as device memory-managed
versions of the iio_counter_register and iio_counter unregister
functions respectively.
Signed-off-by: William Breathitt Gray <vilhelm.gray@...il.com>
---
MAINTAINERS | 7 +
drivers/iio/Kconfig | 8 +
drivers/iio/Makefile | 1 +
drivers/iio/counter/Kconfig | 1 +
drivers/iio/industrialio-counter.c | 900 +++++++++++++++++++++++++++++++++++++
include/linux/iio/counter.h | 166 +++++++
6 files changed, 1083 insertions(+)
create mode 100644 drivers/iio/industrialio-counter.c
create mode 100644 include/linux/iio/counter.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2281af4b41b6..8b7c37bed252 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6693,6 +6693,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 474e1ac4e7c0..c8becfe78e28 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..dfb982dae3a8
--- /dev/null
+++ b/drivers/iio/industrialio-counter.c
@@ -0,0 +1,900 @@
+/*
+ * 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/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gfp.h>
+#include <linux/kernel.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>
+
+#define iio_priv_get_counter(_indio_dev) \
+ (*(struct iio_counter **)iio_priv(_indio_dev))
+
+static struct iio_counter_signal *iio_counter_signal_find_by_id(
+ const struct iio_counter *const counter, const int id)
+{
+ size_t i;
+ struct iio_counter_signal *signal;
+
+ for (i = 0; i < counter->num_signals; i++) {
+ signal = counter->signals + i;
+ if (signal->id == id)
+ return signal;
+ }
+
+ return NULL;
+}
+
+static struct iio_counter_trigger *iio_counter_trigger_find_by_id(
+ const struct iio_counter_value *const value, const int id)
+{
+ size_t i;
+ struct iio_counter_trigger *trigger;
+
+ for (i = 0; i < value->num_triggers; i++) {
+ trigger = value->triggers + i;
+ if (trigger->signal->id == id)
+ return trigger;
+ }
+
+ return NULL;
+}
+
+static struct iio_counter_value *iio_counter_value_find_by_id(
+ const struct iio_counter *const counter, const int id)
+{
+ size_t i;
+ struct iio_counter_value *value;
+
+ for (i = 0; i < counter->num_values; i++) {
+ value = counter->values + i;
+ if (value->id == id)
+ return value;
+ }
+
+ return NULL;
+}
+
+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_get_counter(indio_dev);
+ const struct iio_counter_signal *signal;
+
+ signal = iio_counter_signal_find_by_id(counter, chan->channel2);
+ 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_get_counter(indio_dev);
+ const struct iio_counter_value *value;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ 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_get_counter(indio_dev);
+ struct iio_counter_value *value;
+ size_t i;
+ const struct iio_counter_trigger *trigger;
+ ssize_t len = 0;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ /* Print out a list of every Signal association to Value */
+ for (i = 0; i < value->num_triggers; i++) {
+ trigger = value->triggers + i;
+ 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)
+ return -ENOMEM;
+ }
+
+ 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_get_counter(indio_dev);
+ struct iio_counter_value *value;
+ struct iio_counter_trigger *trigger;
+ const int signal_id = *(int *)((void *)priv);
+ int mode;
+
+ if (!counter->ops->trigger_mode_get)
+ return -EINVAL;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ trigger = iio_counter_trigger_find_by_id(value, signal_id);
+ if (!trigger)
+ return -EINVAL;
+
+ mode = counter->ops->trigger_mode_get(counter, value, trigger);
+
+ if (mode < 0)
+ return mode;
+ else if (mode >= trigger->num_trigger_modes)
+ return -EINVAL;
+
+ trigger->mode = mode;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", trigger->trigger_modes[mode]);
+}
+
+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_get_counter(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;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ trigger = iio_counter_trigger_find_by_id(value, signal_id);
+ if (!trigger)
+ return -EINVAL;
+
+ for (mode = 0; mode < trigger->num_trigger_modes; mode++)
+ if (sysfs_streq(buf, trigger->trigger_modes[mode]))
+ break;
+
+ if (mode >= trigger->num_trigger_modes)
+ return -EINVAL;
+
+ err = counter->ops->trigger_mode_set(counter, value, trigger, mode);
+ if (err)
+ return err;
+
+ trigger->mode = mode;
+
+ return len;
+}
+
+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_get_counter(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;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ trigger = iio_counter_trigger_find_by_id(value, signal_id);
+ if (!trigger)
+ return -EINVAL;
+
+ for (i = 0; i < trigger->num_trigger_modes; i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%s ",
+ trigger->trigger_modes[i]);
+
+ buf[len - 1] = '\n';
+
+ 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_get_counter(indio_dev);
+ struct iio_counter_value *value;
+ int err;
+
+ if (!counter->ops->value_function_set)
+ return -EINVAL;
+
+ /* Find relevant Value */
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ /* Map IIO core function_set to Generic Counter value_function_set */
+ err = counter->ops->value_function_set(counter, value, mode);
+ if (err)
+ return err;
+
+ value->mode = mode;
+
+ return 0;
+}
+
+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_get_counter(indio_dev);
+ struct iio_counter_value *value;
+ int retval;
+
+ if (!counter->ops->value_function_get)
+ return -EINVAL;
+
+ /* Find relevant Value */
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ /* Map IIO core function_get to Generic Counter value_function_get */
+ retval = counter->ops->value_function_get(counter, value);
+ if (retval < 0)
+ return retval;
+ else if (retval >= value->num_function_modes)
+ return -EINVAL;
+
+ value->mode = retval;
+
+ 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
+ },
+ {
+ .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 size_t num_triggers_ext_info = num_ext_info_trigger *
+ value->num_triggers;
+ const size_t num_ext_info = num_default + num_triggers_ext_info + 1;
+ int err;
+ struct iio_chan_spec_ext_info *ext_info;
+ const struct iio_counter_trigger *trigger;
+ size_t i;
+ size_t j;
+
+ /* Construct function_enum for Value */
+ 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;
+
+ ext_info = kmalloc_array(num_ext_info, sizeof(*ext_info), GFP_KERNEL);
+ if (!ext_info)
+ return -ENOMEM;
+ ext_info[num_ext_info - 1].name = NULL;
+
+ /* Add ext_info for the name, function, function_available, and triggers
+ * attributes
+ */
+ memcpy(ext_info, ext_info_default, sizeof(ext_info_default));
+ /* Add ext_info for the trigger_signalX-Z and
+ * trigger_signalX-Z_available attributes for each Trigger
+ */
+ 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));
+
+ /* Set name for each trigger_signalX-Z and trigger_signalX-Z_available
+ * attribute; store the respective Signal ID address in each ext_info
+ * private member
+ */
+ for (i = num_default, j = 0; j < value->num_triggers; i++, j++) {
+ trigger = value->triggers + j;
+
+ ext_info[i].name = kasprintf(GFP_KERNEL, "trigger_signal%d-%d",
+ chan->channel, trigger->signal->id);
+ if (!ext_info[i].name) {
+ err = -ENOMEM;
+ goto err_name_alloc;
+ }
+ ext_info[i].private = (void *)&trigger->signal->id;
+ i++;
+
+ ext_info[i].name = kasprintf(GFP_KERNEL,
+ "trigger_signal%d-%d_available",
+ chan->channel, trigger->signal->id);
+ if (!ext_info[i].name) {
+ err = -ENOMEM;
+ goto err_name_alloc;
+ }
+ ext_info[i].private = (void *)&trigger->signal->id;
+ }
+
+ chan->ext_info = ext_info;
+
+ return 0;
+
+err_name_alloc:
+ while (i-- > num_default)
+ kfree(ext_info[i].name);
+ kfree(ext_info);
+ 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 size_t num_channels = counter->num_signals + counter->num_values +
+ counter->num_channels;
+ int err;
+ struct iio_chan_spec *channels;
+ size_t j;
+ struct iio_counter_value *value;
+ size_t i = counter->num_channels;
+ const struct iio_counter_signal *signal;
+
+ channels = kcalloc(num_channels, sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ /* If any channels were supplied on Counter registration,
+ * we add them here to the front of the array
+ */
+ memcpy(channels, counter->channels,
+ counter->num_channels * sizeof(*counter->channels));
+
+ /* Add channel for each Value */
+ for (j = 0; j < counter->num_values; j++) {
+ value = counter->values + j;
+
+ channels[i].type = IIO_COUNT;
+ channels[i].channel = counter->id;
+ channels[i].channel2 = value->id;
+ channels[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ channels[i].indexed = 1;
+ channels[i].counter = 1;
+
+ /* Add channels for Triggers associated with Value */
+ err = iio_counter_value_ext_info_alloc(channels + i, value);
+ if (err)
+ goto err_value_ext_info_alloc;
+
+ i++;
+ }
+
+ /* Add channel for each Signal */
+ for (j = 0; j < counter->num_signals; j++) {
+ signal = counter->signals + j;
+
+ channels[i].type = IIO_SIGNAL;
+ channels[i].channel = counter->id;
+ channels[i].channel2 = signal->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++;
+ }
+
+ 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);
+ 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)
+ /* Only IIO_COUNT channels need to be freed here */
+ 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_get_counter(indio_dev);
+ struct iio_counter_signal *signal;
+ struct iio_counter_value *value;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ switch (chan->type) {
+ /* Map read_raw to signal_read for Signal */
+ case IIO_SIGNAL:
+ if (!counter->ops->signal_read)
+ return -EINVAL;
+
+ signal = iio_counter_signal_find_by_id(counter, chan->channel2);
+ if (!signal)
+ return -EINVAL;
+
+ return counter->ops->signal_read(counter, signal, val, val2);
+ /* Map read_raw to value_read for Value */
+ case IIO_COUNT:
+ if (!counter->ops->value_read)
+ return -EINVAL;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ return counter->ops->value_read(counter, value, val, val2);
+ /* Map read_raw to read_raw for non-counter channel */
+ 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_get_counter(indio_dev);
+ struct iio_counter_signal *signal;
+ struct iio_counter_value *value;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ switch (chan->type) {
+ /* Map write_raw to signal_write for Signal */
+ case IIO_SIGNAL:
+ if (!counter->ops->signal_write)
+ return -EINVAL;
+
+ signal = iio_counter_signal_find_by_id(counter, chan->channel2);
+ if (!signal)
+ return -EINVAL;
+
+ return counter->ops->signal_write(counter, signal, val, val2);
+ /* Map write_raw to value_write for Value */
+ case IIO_COUNT:
+ if (!counter->ops->value_write)
+ return -EINVAL;
+
+ value = iio_counter_value_find_by_id(counter, chan->channel2);
+ if (!value)
+ return -EINVAL;
+
+ return counter->ops->value_write(counter, value, val, val2);
+ /* Map write_raw to write_raw for non-counter channel */
+ 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_signals_validate(const struct iio_counter *const counter)
+{
+ size_t i;
+ const struct iio_counter_signal *signal;
+ size_t j;
+ int curr_id;
+
+ /* At least one Signal must be defined */
+ if (!counter->num_signals || !counter->signals) {
+ pr_err("Counter '%d' Signals undefined\n", counter->id);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < counter->num_signals; i++) {
+ signal = counter->signals + i;
+ /* No two Signals may share the same ID */
+ for (j = 0; j < i; j++) {
+ curr_id = counter->signals[j].id;
+ if (curr_id == signal->id) {
+ pr_err("Duplicate Counter '%d' Signal '%d'\n",
+ counter->id, signal->id);
+ return -EEXIST;
+ }
+ }
+ for (j = i + 1; j < counter->num_signals; j++) {
+ curr_id = counter->signals[j].id;
+ if (curr_id == signal->id) {
+ pr_err("Duplicate Counter '%d' Signal '%d'\n",
+ counter->id, signal->id);
+ return -EEXIST;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int iio_counter_triggers_validate(const int counter_id,
+ const struct iio_counter_value *const value)
+{
+ size_t i;
+ const struct iio_counter_trigger *trigger;
+ size_t j;
+ int curr_id;
+
+ /* At least one Trigger must be defined */
+ if (!value->num_triggers || !value->triggers) {
+ pr_err("Counter '%d' Value '%d' Triggers undefined\n",
+ counter_id, value->id);
+ return -EINVAL;
+ }
+
+ /* Ensure all Triggers have a defined Signal; this prevents a possible
+ * NULL pointer dereference when later validating Trigger Signal IDs
+ */
+ for (i = 0; i < value->num_triggers; i++)
+ if (!value->triggers[i].signal) {
+ pr_err("Counter '%d' Trigger '%zu' Signal undefined\n",
+ counter_id, i);
+ return -EINVAL;
+ }
+
+ /* Verify validity of each Trigger */
+ for (i = 0; i < value->num_triggers; i++) {
+ trigger = value->triggers + i;
+ /* No two Trigger Signals may share the same ID */
+ for (j = 0; j < i; j++) {
+ curr_id = value->triggers[j].signal->id;
+ if (curr_id == trigger->signal->id) {
+ pr_err("Signal '%d' is already linked to Counter '%d' Value '%d'\n",
+ trigger->signal->id, counter_id,
+ value->id);
+ return -EEXIST;
+ }
+ }
+ for (j = i + 1; j < value->num_triggers; j++) {
+ curr_id = value->triggers[j].signal->id;
+ if (curr_id == trigger->signal->id) {
+ pr_err("Signal '%d' is already linked to Counter '%d' Value '%d'\n",
+ trigger->signal->id, counter_id,
+ value->id);
+ return -EEXIST;
+ }
+ }
+
+ /* At least one trigger mode must be defined for each Trigger */
+ if (!trigger->num_trigger_modes || !trigger->trigger_modes) {
+ pr_err("Counter '%d' Signal '%d' trigger modes undefined\n",
+ counter_id, trigger->signal->id);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int iio_counter_values_validate(const struct iio_counter *const counter)
+{
+ size_t i;
+ const struct iio_counter_value *value;
+ size_t j;
+ int curr_id;
+ int err;
+
+ /* At least one Value must be defined */
+ if (!counter->num_values || !counter->values) {
+ pr_err("Counter '%d' Values undefined\n", counter->id);
+ return -EINVAL;
+ }
+
+ /* Verify validity of each Value */
+ for (i = 0; i < counter->num_values; i++) {
+ value = counter->values + i;
+ /* No two Values may share the same ID */
+ for (j = 0; j < i; j++) {
+ curr_id = counter->values[j].id;
+ if (curr_id == value->id) {
+ pr_err("Duplicate Counter '%d' Value '%d'\n",
+ counter->id, value->id);
+ return -EEXIST;
+ }
+ }
+ for (j = i + 1; j < counter->num_values; j++) {
+ curr_id = counter->values[j].id;
+ if (curr_id == value->id) {
+ pr_err("Duplicate Counter '%d' Value '%d'\n",
+ counter->id, value->id);
+ return -EEXIST;
+ }
+ }
+
+ /* At least one function mode must be defined for each Value */
+ if (!value->num_function_modes || !value->function_modes) {
+ pr_err("Counter '%d' Value '%d' function modes undefined\n",
+ counter->id, value->id);
+ return -EINVAL;
+ }
+
+ /* Verify the Triggers associated with each Value */
+ err = iio_counter_triggers_validate(counter->id, value);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * 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 validated; the signals and values members must be defined
+ * and populated by valid Signal and Value structures respectively. 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 address is stored 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;
+
+ /* Verify that Signals are valid and IDs do not conflict */
+ err = iio_counter_signals_validate(counter);
+ if (err)
+ return err;
+
+ /* Verify that Values are valid and IDs do not conflict;
+ * Triggers for each Value are also verified for validity
+ */
+ err = iio_counter_values_validate(counter);
+ if (err)
+ return err;
+
+ counter->indio_dev = iio_device_alloc(sizeof(counter));
+ if (!counter->indio_dev)
+ return -ENOMEM;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto err_info_alloc;
+ }
+ /* If an iio_info has been supplied than we use that,
+ * otherwise we set all callbacks to NULL; iio_counter_read_raw
+ * and iio_counter_write_raw is used for read_raw and write_raw
+ * for either case in order to support counter functionality
+ * (supplied read_raw/write_raw will be called from within
+ * iio_counter_read_raw/iio_counter_write_raw for non-counter
+ * channels)
+ */
+ 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;
+ counter->indio_dev->name = counter->name;
+ counter->indio_dev->dev.parent = counter->dev;
+
+ /* IIO channels are allocated and set for Signals, Values, and Triggers;
+ * any auxiliary IIO channels provided in iio_counter are also set
+ */
+ err = iio_counter_channels_alloc(counter);
+ if (err)
+ goto err_channels_alloc;
+
+ /* Pointer to the counter is stored in indio_dev as a way to refer
+ * back to the counter from within various IIO callbacks
+ */
+ priv = iio_priv(counter->indio_dev);
+ memcpy(priv, &counter, sizeof(*priv));
+
+ err = iio_device_register(counter->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);
+ 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
+ * and all allocated memory is freed.
+ */
+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);
+}
+EXPORT_SYMBOL(iio_counter_unregister);
+
+static void devm_iio_counter_unreg(struct device *dev, void *res)
+{
+ iio_counter_unregister(*(struct iio_counter **)res);
+}
+
+/**
+ * devm_iio_counter_register - Resource-managed iio_counter_register
+ * @dev: Device to allocate iio_counter 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_counter 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);
diff --git a/include/linux/iio/counter.h b/include/linux/iio/counter.h
new file mode 100644
index 000000000000..35645406711a
--- /dev/null
+++ b/include/linux/iio/counter.h
@@ -0,0 +1,166 @@
+/*
+ * 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/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
+ */
+struct iio_counter_signal {
+ int id;
+ const char *name;
+};
+
+/**
+ * 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
+ */
+struct iio_counter_trigger {
+ unsigned int mode;
+ const char *const *trigger_modes;
+ unsigned int num_trigger_modes;
+ struct iio_counter_signal *signal;
+};
+
+/**
+ * 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
+ * @triggers: [DRIVER] array of triggers for initialization
+ * @num_triggers: [DRIVER] number of triggers specified in @triggers
+ * @function_enum: [INTERN] used internally to generate function attributes
+ */
+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 *triggers;
+ size_t num_triggers;
+
+ struct iio_enum function_enum;
+};
+
+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.
+ * @signal_write: function to write a signal value to the device.
+ * Parameters are interpreted 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.
+ * @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.
+ * @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.
+ * @value_write: function to write a value value to the device.
+ * Parameters are interpreted 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.
+ * @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.
+ */
+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);
+};
+
+/**
+ * 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
+ * @signals: [DRIVER] array of signals for initialization
+ * @num_signals: [DRIVER] number of signals specified in @signals
+ * @values: [DRIVER] array of values for initialization
+ * @num_values: [DRIVER] number of values specified in @values
+ * @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;
+ const struct iio_counter_ops *ops;
+
+ struct iio_counter_signal *signals;
+ size_t num_signals;
+ struct iio_counter_value *values;
+ size_t num_values;
+
+ const struct iio_chan_spec *channels;
+ size_t num_channels;
+ const struct iio_info *info;
+
+ struct iio_dev *indio_dev;
+ void *driver_data;
+};
+
+int iio_counter_register(struct iio_counter *const counter);
+void iio_counter_unregister(struct iio_counter *const counter);
+int devm_iio_counter_register(struct device *dev,
+ struct iio_counter *const counter);
+void devm_iio_counter_unregister(struct device *dev,
+ struct iio_counter *const counter);
+
+#endif /* CONFIG_IIO_COUNTER */
+
+#endif /* _IIO_COUNTER_H_ */
--
2.14.1
Powered by blists - more mailing lists