From patchwork Thu Oct 5 18:13:44 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Breathitt Gray X-Patchwork-Id: 9987707 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id CA699602B8 for ; Thu, 5 Oct 2017 18:14:24 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B81AE26E97 for ; Thu, 5 Oct 2017 18:14:24 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AC07627CF9; Thu, 5 Oct 2017 18:14:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.3 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D0A0726E97 for ; Thu, 5 Oct 2017 18:14:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751667AbdJESNy (ORCPT ); Thu, 5 Oct 2017 14:13:54 -0400 Received: from mail-qt0-f196.google.com ([209.85.216.196]:37675 "EHLO mail-qt0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751395AbdJESNw (ORCPT ); Thu, 5 Oct 2017 14:13:52 -0400 Received: by mail-qt0-f196.google.com with SMTP id 32so6280428qtp.4; Thu, 05 Oct 2017 11:13:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=aQAQJEJbkvdEbP/Ec7FHgeesaY15g2ESehH8O9LVhUU=; b=CdgHxlDZkyw7n15p2wcg5kCAHoJsFrC85ZbF+0JVV3ZsVKEt2xC0peXmR611OtDgmR gthwEMqflD4bZvOdSSDcAm2bw2c3TbMjukyA8Lg2oHPJHg9E/uaRbSpUUgUkZ1+hGLOG 6tYSixA0dORDqUM+XXXO4dlW2NtHOOw46msYw+6K73n8GjRKCFkDyYNXhoxFofQorOKc 8Y0k5amlcVznZMAa/ZYC/PTFGklDdP7z97jVO6giXLCDEDO2eSoSm3Y9Ec7+rywOQVYB 83SsSkOgaBesP0DXPDc18GKMWdgU7T8cD4gg7SApYWoDM5ozrE4tvjh9vc7qHsSAGyJX l4vQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=aQAQJEJbkvdEbP/Ec7FHgeesaY15g2ESehH8O9LVhUU=; b=KZ28zQ5xr44DrYokASrsXPVaSjlqBB6vQdhETLt4mRasXPje5tbjblN8YbwE88UO7N Bb9dQE0/D7tY+5pGrZnbPw2AmUitfikk+Sa81QGpriiTkJ9uex26F3fk8Bu3fcXJmjPy 8t12M8ZPXSOxSv5ii/8urnLfuxUV6zDK9v5Bax0l2IQ9g7lI6LDwoXSLtRMgLz87nVn7 /iIUWGYmwYg5p2tJEthLT1K4B/memMifa9qzi7JreRAYg+oNXQhj534BEyW9I8YZrje8 hbPeOXBfhKuk7Mu2Tp0ICn54aTda6dTy1gc/fuCEFbtbUE9Fff2Xt+xER43TybSP3ugG EJXA== X-Gm-Message-State: AMCzsaV4JigmTEsZby2KMhIilIrL8LhEQrQckXFQuqMNqRne7Rbmqsr/ vCGnvowgJsVI2lWvuGD2Fl4= X-Google-Smtp-Source: AOwi7QB9A9hpL0vqpioN/m7aROUUUu7ZkVu5tm03SOWAM2hIXGW526RAwPRXv7Z2WT4XlZxi+E+wCw== X-Received: by 10.129.0.197 with SMTP id 188mr3524015ywa.499.1507227230868; Thu, 05 Oct 2017 11:13:50 -0700 (PDT) Received: from localhost (72-188-97-40.res.bhn.net. [72.188.97.40]) by smtp.gmail.com with ESMTPSA id z76sm8293186ywz.30.2017.10.05.11.13.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 05 Oct 2017 11:13:50 -0700 (PDT) From: William Breathitt Gray To: jic23@kernel.org, benjamin.gaignard@linaro.org, knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org, William Breathitt Gray Subject: [PATCH v3 2/6] iio: Introduce the generic counter interface Date: Thu, 5 Oct 2017 14:13:44 -0400 Message-Id: X-Mailer: git-send-email 2.14.1 In-Reply-To: References: Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +L: linux-iio@vger.kernel.org +S: Maintained +F: drivers/iio/industrialio-counter.c +F: include/linux/iio/counter.h + IIO MULTIPLEXER M: Peter Rosin L: linux-iio@vger.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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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 +#include + +#include + +/** + * 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_ */