From patchwork Wed May 16 17:50:43 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Breathitt Gray X-Patchwork-Id: 10404549 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 0CD28601F7 for ; Wed, 16 May 2018 17:53:10 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E9EB9284B3 for ; Wed, 16 May 2018 17:53:09 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DE4A9285EB; Wed, 16 May 2018 17:53:09 +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=-2.9 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, DKIM_VALID, FREEMAIL_FROM, MAILING_LIST_MULTI autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 856F2284B3 for ; Wed, 16 May 2018 17:53:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=kgO1daRATJlHOabVqkaoVB8arMVrrMdzaBM5mYMzCR8=; b=czk1bskmAhfAWatyeOeY0OB3F/ KoNKt1EdLuJbSR0ztPdwojupxE7qfOk2MkeFf846EecFbh+IDqoxhB8lZL1c7/pSdOsp7fqZAOSP8 mGkrvL6MiXlRrDhbHOPM+YUFu1NujE5oOoFQU8tpaCRZ4G6CduYhsnDF6zCd7B1yUFHTtWPR5V9dI 9xHPKZJLESXazQiyqbx6VTiAQXgw57R0jdv/cwd/uyhxwBzViNdRLQjRG1hPqISpB1ApPWF+nMsrh FJUCT2l+5buHAriCiRs6J2yKJ+35Emm96QFWRNOdawokjhPbTCSRpRZRiwTD7kVD34zDbWEQFLBzY L8TPRNmw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1fJ0bd-0005n2-T5; Wed, 16 May 2018 17:52:53 +0000 Received: from mail-yb0-x243.google.com ([2607:f8b0:4002:c09::243]) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1fJ0Zs-0004qX-9j for linux-arm-kernel@lists.infradead.org; Wed, 16 May 2018 17:51:11 +0000 Received: by mail-yb0-x243.google.com with SMTP id j143-v6so557096ybg.2 for ; Wed, 16 May 2018 10:50:52 -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=RMU0q6GDuV9hv3oNJde+H7d2CKe03ksxWZvbPx6JGw4=; b=i4ehpW/uFoU+IrDUZp3svUJ9fK5de6w1BckCXsAhdEfRNDnnhwTuX0GkEkmZooG6Wp 2e3yjyBfYTXehwAhqv6vNEoLIsK+xUj4iL1ahZ3k+2L0dufaCBAN6Yhj8voB0/Z3dURt ZKPOrRCjcxI+nB6nIEX8CZvoJOFIoFrKwDy12UGq6AOGYAjqHfge8fGKQVGMIGOjP5Hk FBboCrQQkwHa6Rsi4JjKUEh3ZMvpEDCQWj/n2FnyxIy6OzcUC2Xr+hnIQiDEvAFedIje 6vFv1sLTzqwhaINhJjELlL71yqznjehsLGZszZUR3UG/UYcaEvWUMjr7zeg++6QXDIwY 18Fg== 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=RMU0q6GDuV9hv3oNJde+H7d2CKe03ksxWZvbPx6JGw4=; b=Pf5JzHjMdjHOPppwuauiMtsQ0KdyQcMjeSiiSMC6tsgWsLcz3fGZFD0uI2GIipx73l YPOmI9rYWKvfVEBM7UKUoZVTzGwI2Soc4YtJeZULns7xYjDSqvUJDd3ksuMDD0FGs+5/ B0WtBz2O3ElDrLtgxNa3tOIIrrpxURjFfBM83au66D3bO4iIz6aeT8aEnz193ktRmbOV cwY1oz3oCOfAajgcoIV7xGThrTC2CvEynxMNkAxQTx5uSA/N36BAM9IYv4tNj0GLJh5M bt2OpyYv9cslimSZOErbwf/gUjRfjsgaih8ubtfncrqXX49S3n7ouGwHUZ+tMgeWI9vs HURg== X-Gm-Message-State: ALKqPwfx1AUDoGlYL0VUzm/YDylP8ipCzRJ3P1btQ0diwzUPb0bqwktz gZXZru+KH6wBatAfBwueRoE= X-Google-Smtp-Source: AB8JxZqMEYnCAqdZCMyvrZ4fSsYmXZNUPdP/EToQi7o9oqGeFsfqEzC+Dwx1W5hDIxGudY/CSMuc8Q== X-Received: by 2002:a25:7782:: with SMTP id s124-v6mr1091798ybc.84.1526493050806; Wed, 16 May 2018 10:50:50 -0700 (PDT) Received: from localhost ([72.188.97.40]) by smtp.gmail.com with ESMTPSA id u207-v6sm1278002ywu.25.2018.05.16.10.50.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 16 May 2018 10:50:50 -0700 (PDT) From: William Breathitt Gray To: jic23@kernel.org Subject: [PATCH v6 1/9] counter: Introduce the Generic Counter interface Date: Wed, 16 May 2018 13:50:43 -0400 Message-Id: <6e0c93ccc771033f0a8bc08bd92b453a1dacbd1b.1526487615.git.vilhelm.gray@gmail.com> X-Mailer: git-send-email 2.17.0 In-Reply-To: References: X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180516_105104_578787_3B289988 X-CRM114-Status: GOOD ( 23.29 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, benjamin.gaignard@st.com, linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org, William Breathitt Gray , fabrice.gasnier@st.com, linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP This patch introduces the Generic Counter interface for supporting counter devices. In the context of the Generic Counter interface, a counter is defined as a device that reports one or more "counts" based on the state changes of one or more "signals" as evaluated by a defined "count function." Driver callbacks should be provided to communicate with the device: to read and write various Signals and Counts, and to set and get the "action mode" and "count function" for various Synapses and Counts respectively. To support a counter device, a driver must first allocate the available Counter Signals via counter_signal structures. These Signals should be stored as an array and set to the signals array member of an allocated counter_device structure before the Counter is registered to the system. Counter Counts may be allocated via counter_count structures, and respective Counter Signal associations (Synapses) made via counter_synapse structures. Associated counter_synapse structures are stored as an array and set to the the synapses array member of the respective counter_count structure. These counter_count structures are set to the counts array member of an allocated counter_device structure before the Counter is registered to the system. A counter device is registered to the system by passing the respective initialized counter_device structure to the counter_register function; similarly, the counter_unregister function unregisters the respective Counter. The devm_counter_register and devm_counter_unregister functions serve as device memory-managed versions of the counter_register and counter_unregister functions respectively. Signed-off-by: William Breathitt Gray Reviewed-by: Jonathan Cameron --- MAINTAINERS | 7 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/counter/Kconfig | 18 + drivers/counter/Makefile | 8 + drivers/counter/generic-counter.c | 1541 +++++++++++++++++++++++++++++ include/linux/counter.h | 554 +++++++++++ 7 files changed, 2131 insertions(+) create mode 100644 drivers/counter/Kconfig create mode 100644 drivers/counter/Makefile create mode 100644 drivers/counter/generic-counter.c create mode 100644 include/linux/counter.h diff --git a/MAINTAINERS b/MAINTAINERS index 4b65225d443a..2a016d73ab72 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3669,6 +3669,13 @@ W: http://www.fi.muni.cz/~kas/cosa/ S: Maintained F: drivers/net/wan/cosa* +COUNTER SUBSYSTEM +M: William Breathitt Gray +L: linux-iio@vger.kernel.org +S: Maintained +F: drivers/counter/ +F: include/linux/counter.h + CPMAC ETHERNET DRIVER M: Florian Fainelli L: netdev@vger.kernel.org diff --git a/drivers/Kconfig b/drivers/Kconfig index 95b9ccc08165..70b3cc88dc0b 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -165,6 +165,8 @@ source "drivers/memory/Kconfig" source "drivers/iio/Kconfig" +source "drivers/counter/Kconfig" + source "drivers/ntb/Kconfig" source "drivers/vme/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 24cd47014657..5914c78688c3 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -165,6 +165,7 @@ obj-$(CONFIG_PM_DEVFREQ) += devfreq/ obj-$(CONFIG_EXTCON) += extcon/ obj-$(CONFIG_MEMORY) += memory/ obj-$(CONFIG_IIO) += iio/ +obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_VME_BUS) += vme/ obj-$(CONFIG_IPACK_BUS) += ipack/ obj-$(CONFIG_NTB) += ntb/ diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig new file mode 100644 index 000000000000..65fa92abd5a4 --- /dev/null +++ b/drivers/counter/Kconfig @@ -0,0 +1,18 @@ +# +# Counter devices +# +# When adding new entries keep the list in alphabetical order + +menuconfig COUNTER + tristate "Counter support" + help + Provides Generic Counter interface support for counter devices. + + Counter devices are prevalent within a diverse spectrum of industries. + The ubiquitous presence of these devices necessitates a common + interface and standard of interaction and exposure. This driver API + attempts to resolve the issue of duplicate code found among existing + counter device drivers by providing a generic counter interface for + consumption. The Generic Counter interface enables drivers to support + and expose a common set of components and functionality present in + counter devices. diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile new file mode 100644 index 000000000000..ad1ba7109cdc --- /dev/null +++ b/drivers/counter/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for Counter devices +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_COUNTER) += counter.o +counter-y := generic-counter.o diff --git a/drivers/counter/generic-counter.c b/drivers/counter/generic-counter.c new file mode 100644 index 000000000000..0d83b862453f --- /dev/null +++ b/drivers/counter/generic-counter.c @@ -0,0 +1,1541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const char *const count_direction_str[2] = { + [COUNT_DIRECTION_FORWARD] = "forward", + [COUNT_DIRECTION_BACKWARD] = "backward" +}; +EXPORT_SYMBOL(count_direction_str); + +const char *const count_mode_str[4] = { + [COUNT_MODE_NORMAL] = "normal", + [COUNT_MODE_RANGE_LIMIT] = "range limit", + [COUNT_MODE_NON_RECYCLE] = "non-recycle", + [COUNT_MODE_MODULO_N] = "modulo-n" +}; +EXPORT_SYMBOL(count_mode_str); + +ssize_t counter_signal_enum_read(struct counter_device *counter, + struct counter_signal *signal, void *priv, + char *buf) +{ + const struct counter_signal_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, signal, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%s\n", e->items[index]); +} +EXPORT_SYMBOL(counter_signal_enum_read); + +ssize_t counter_signal_enum_write(struct counter_device *counter, + struct counter_signal *signal, void *priv, + const char *buf, size_t len) +{ + const struct counter_signal_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, signal, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL(counter_signal_enum_write); + +ssize_t counter_signal_enum_available_read(struct counter_device *counter, + struct counter_signal *signal, + void *priv, char *buf) +{ + const struct counter_signal_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + e->items[i]); + + return len; +} +EXPORT_SYMBOL(counter_signal_enum_available_read); + +ssize_t counter_count_enum_read(struct counter_device *counter, + struct counter_count *count, void *priv, + char *buf) +{ + const struct counter_count_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, count, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%s\n", e->items[index]); +} +EXPORT_SYMBOL(counter_count_enum_read); + +ssize_t counter_count_enum_write(struct counter_device *counter, + struct counter_count *count, void *priv, + const char *buf, size_t len) +{ + const struct counter_count_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, count, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL(counter_count_enum_write); + +ssize_t counter_count_enum_available_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + const struct counter_count_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + e->items[i]); + + return len; +} +EXPORT_SYMBOL(counter_count_enum_available_read); + +ssize_t counter_device_enum_read(struct counter_device *counter, void *priv, + char *buf) +{ + const struct counter_device_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%s\n", e->items[index]); +} +EXPORT_SYMBOL(counter_device_enum_read); + +ssize_t counter_device_enum_write(struct counter_device *counter, void *priv, + const char *buf, size_t len) +{ + const struct counter_device_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL(counter_device_enum_write); + +ssize_t counter_device_enum_available_read(struct counter_device *counter, + void *priv, char *buf) +{ + const struct counter_device_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + e->items[i]); + + return len; +} +EXPORT_SYMBOL(counter_device_enum_available_read); + +static const char *const signal_level_str[] = { + [SIGNAL_LEVEL_LOW] = "low", + [SIGNAL_LEVEL_HIGH] = "high" +}; + +/** + * set_signal_read_value - set signal_read_value data + * @val: signal_read_value structure to set + * @type: property Signal data represents + * @data: Signal data + * + * This function sets an opaque signal_read_value structure with the provided + * Signal data. + */ +void set_signal_read_value(struct signal_read_value *const val, + const enum signal_value_type type, void *const data) +{ + if (type == SIGNAL_LEVEL) + val->len = scnprintf(val->buf, PAGE_SIZE, "%s\n", + signal_level_str[*(enum signal_level *)data]); + else + val->len = 0; +} +EXPORT_SYMBOL(set_signal_read_value); + +/** + * set_count_read_value - set count_read_value data + * @val: count_read_value structure to set + * @type: property Count data represents + * @data: Count data + * + * This function sets an opaque count_read_value structure with the provided + * Count data. + */ +void set_count_read_value(struct count_read_value *const val, + const enum count_value_type type, void *const data) +{ + switch (type) { + case COUNT_POSITION_UNSIGNED: + val->len = scnprintf(val->buf, PAGE_SIZE, "%lu\n", + *(unsigned long *)data); + break; + case COUNT_POSITION_SIGNED: + val->len = scnprintf(val->buf, PAGE_SIZE, "%ld\n", + *(long *)data); + break; + default: + val->len = 0; + } +} +EXPORT_SYMBOL(set_count_read_value); + +/** + * get_count_write_value - get count_write_value data + * @data: Count data + * @type: property Count data represents + * @val: count_write_value structure containing data + * + * This function extracts Count data from the provided opaque count_write_value + * structure and stores it at the address provided by @data. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int get_count_write_value(void *const data, const enum count_value_type type, + const struct count_write_value *const val) +{ + int err; + + switch (type) { + case COUNT_POSITION_UNSIGNED: + err = kstrtoul(val->buf, 0, data); + if (err) + return err; + break; + case COUNT_POSITION_SIGNED: + err = kstrtol(val->buf, 0, data); + if (err) + return err; + break; + } + + return 0; +} +EXPORT_SYMBOL(get_count_write_value); + +struct counter_device_attr { + struct device_attribute dev_attr; + struct list_head l; + void *component; +}; + +static int counter_attribute_create( + struct counter_device_attr_group *const group, + const char *const prefix, + const char *const name, + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len), + void *const component) +{ + struct counter_device_attr *counter_attr; + struct device_attribute *dev_attr; + int err; + struct list_head *const attr_list = &group->attr_list; + + /* Allocate a Counter device attribute */ + counter_attr = kzalloc(sizeof(*counter_attr), GFP_KERNEL); + if (!counter_attr) + return -ENOMEM; + dev_attr = &counter_attr->dev_attr; + + sysfs_attr_init(&dev_attr->attr); + + /* Configure device attribute */ + dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s%s", prefix, name); + if (!dev_attr->attr.name) { + err = -ENOMEM; + goto err_free_counter_attr; + } + if (show) { + dev_attr->attr.mode |= 0444; + dev_attr->show = show; + } + if (store) { + dev_attr->attr.mode |= 0200; + dev_attr->store = store; + } + + /* Store associated Counter component with attribute */ + counter_attr->component = component; + + /* Keep track of the attribute for later cleanup */ + list_add(&counter_attr->l, attr_list); + group->num_attr++; + + return 0; + +err_free_counter_attr: + kfree(counter_attr); + return err; +} + +#define to_counter_attr(_dev_attr) \ + container_of(_dev_attr, struct counter_device_attr, dev_attr) + +struct signal_comp_t { + struct counter_signal *signal; +}; + +static ssize_t counter_signal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct signal_comp_t *const component = devattr->component; + struct counter_signal *const signal = component->signal; + int err; + struct signal_read_value val = { .buf = buf }; + + err = counter->ops->signal_read(counter, signal, &val); + if (err) + return err; + + return val.len; +} + +struct name_comp_t { + const char *name; +}; + +static ssize_t counter_device_attr_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct name_comp_t *const comp = to_counter_attr(attr)->component; + + return scnprintf(buf, PAGE_SIZE, "%s\n", comp->name); +} + +static int counter_name_attribute_create( + struct counter_device_attr_group *const group, + const char *const name) +{ + struct name_comp_t *name_comp; + int err; + + /* Skip if no name */ + if (!name) + return 0; + + /* Allocate name attribute component */ + name_comp = kmalloc(sizeof(*name_comp), GFP_KERNEL); + if (!name_comp) + return -ENOMEM; + name_comp->name = name; + + /* Allocate Signal name attribute */ + err = counter_attribute_create(group, "", "name", + counter_device_attr_name_show, NULL, + name_comp); + if (err) + goto err_free_name_comp; + + return 0; + +err_free_name_comp: + kfree(name_comp); + return err; +} + +struct signal_ext_comp_t { + struct counter_signal *signal; + const struct counter_signal_ext *ext; +}; + +static ssize_t counter_signal_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct signal_ext_comp_t *const comp = devattr->component; + const struct counter_signal_ext *const ext = comp->ext; + + return ext->read(dev_get_drvdata(dev), comp->signal, ext->priv, buf); +} + +static ssize_t counter_signal_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct signal_ext_comp_t *const comp = devattr->component; + const struct counter_signal_ext *const ext = comp->ext; + + return ext->write(dev_get_drvdata(dev), comp->signal, ext->priv, buf, + len); +} + +static void free_counter_device_attr_list(struct list_head *attr_list) +{ + struct counter_device_attr *p, *n; + + list_for_each_entry_safe(p, n, attr_list, l) { + kfree(p->dev_attr.attr.name); + kfree(p->component); + list_del(&p->l); + kfree(p); + } +} + +static int counter_signal_ext_register( + struct counter_device_attr_group *const group, + struct counter_signal *const signal) +{ + const size_t num_ext = signal->num_ext; + size_t i; + const struct counter_signal_ext *ext; + struct signal_ext_comp_t *signal_ext_comp; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < num_ext; i++) { + ext = signal->ext + i; + + /* Allocate signal_ext attribute component */ + signal_ext_comp = kmalloc(sizeof(*signal_ext_comp), GFP_KERNEL); + if (!signal_ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + signal_ext_comp->signal = signal; + signal_ext_comp->ext = ext; + + /* Allocate a Counter device attribute */ + err = counter_attribute_create(group, "", ext->name, + (ext->read) ? counter_signal_ext_show : NULL, + (ext->write) ? counter_signal_ext_store : NULL, + signal_ext_comp); + if (err) { + kfree(signal_ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +static int counter_signal_attributes_create( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_signal *const signal) +{ + struct signal_comp_t *signal_comp; + int err; + + /* Allocate Signal attribute component */ + signal_comp = kmalloc(sizeof(*signal_comp), GFP_KERNEL); + if (!signal_comp) + return -ENOMEM; + signal_comp->signal = signal; + + /* Create main Signal attribute */ + err = counter_attribute_create(group, "", "signal", + (counter->ops->signal_read) ? counter_signal_show : NULL, NULL, + signal_comp); + if (err) { + kfree(signal_comp); + return err; + } + + /* Create Signal name attribute */ + err = counter_name_attribute_create(group, signal->name); + if (err) + goto err_free_attr_list; + + /* Register Signal extension attributes */ + err = counter_signal_ext_register(group, signal); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +static int counter_signals_register( + struct counter_device_attr_group *const groups_list, + const struct counter_device *const counter) +{ + const size_t num_signals = counter->num_signals; + size_t i; + struct counter_signal *signal; + const char *name; + int err; + + /* Register each Signal */ + for (i = 0; i < num_signals; i++) { + signal = counter->signals + i; + + /* Generate Signal attribute directory name */ + name = kasprintf(GFP_KERNEL, "signal%d", signal->id); + if (!name) { + err = -ENOMEM; + goto err_free_attr_groups; + } + groups_list[i].attr_group.name = name; + + /* Create all attributes associated with Signal */ + err = counter_signal_attributes_create(groups_list + i, counter, + signal); + if (err) + goto err_free_attr_groups; + } + + return 0; + +err_free_attr_groups: + do { + kfree(groups_list[i].attr_group.name); + free_counter_device_attr_list(&groups_list[i].attr_list); + } while (i--); + return err; +} + +static const char *const synapse_action_str[] = { + [SYNAPSE_ACTION_NONE] = "none", + [SYNAPSE_ACTION_RISING_EDGE] = "rising edge", + [SYNAPSE_ACTION_FALLING_EDGE] = "falling edge", + [SYNAPSE_ACTION_BOTH_EDGES] = "both edges" +}; + +struct action_comp_t { + struct counter_synapse *synapse; + struct counter_count *count; +}; + +static ssize_t counter_action_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + const struct action_comp_t *const component = devattr->component; + struct counter_count *const count = component->count; + struct counter_synapse *const synapse = component->synapse; + size_t action_index; + enum synapse_action action; + + err = counter->ops->action_get(counter, count, synapse, &action_index); + if (err) + return err; + + synapse->action = action_index; + + action = synapse->actions_list[action_index]; + return scnprintf(buf, PAGE_SIZE, "%s\n", synapse_action_str[action]); +} + +static ssize_t counter_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct action_comp_t *const component = devattr->component; + struct counter_synapse *const synapse = component->synapse; + size_t action_index; + const size_t num_actions = synapse->num_actions; + enum synapse_action action; + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + struct counter_count *const count = component->count; + + /* Find requested action mode */ + for (action_index = 0; action_index < num_actions; action_index++) { + action = synapse->actions_list[action_index]; + if (sysfs_streq(buf, synapse_action_str[action])) + break; + } + /* If requested action mode not found */ + if (action_index >= num_actions) + return -EINVAL; + + err = counter->ops->action_set(counter, count, synapse, action_index); + if (err) + return err; + + synapse->action = action_index; + + return len; +} + +struct action_avail_comp_t { + const enum synapse_action *actions_list; + size_t num_actions; +}; + +static ssize_t counter_synapse_action_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct action_avail_comp_t *const component = devattr->component; + const enum synapse_action *const actions_list = component->actions_list; + const size_t num_actions = component->num_actions; + size_t i; + enum synapse_action action; + ssize_t len = 0; + + for (i = 0; i < num_actions; i++) { + action = actions_list[i]; + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + synapse_action_str[action]); + } + + return len; +} + +static int counter_synapses_register( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_count *const count, const char *const count_attr_name) +{ + const size_t num_synapses = count->num_synapses; + size_t i; + struct counter_synapse *synapse; + const char *prefix; + struct action_comp_t *action_comp; + int err; + struct action_avail_comp_t *avail_comp; + + /* Register each Synapse */ + for (i = 0; i < num_synapses; i++) { + synapse = count->synapses + i; + + /* Generate attribute prefix */ + prefix = kasprintf(GFP_KERNEL, "signal%d_", + synapse->signal->id); + if (!prefix) { + err = -ENOMEM; + goto err_free_attr_list; + } + + /* Allocate action attribute component */ + action_comp = kmalloc(sizeof(*action_comp), GFP_KERNEL); + if (!action_comp) { + err = -ENOMEM; + goto err_free_prefix; + } + action_comp->synapse = synapse; + action_comp->count = count; + + /* Create action attribute */ + err = counter_attribute_create(group, prefix, "action", + (counter->ops->action_get) ? counter_action_show : NULL, + (counter->ops->action_set) ? counter_action_store : NULL, + action_comp); + if (err) { + kfree(action_comp); + goto err_free_prefix; + } + + /* Allocate action available attribute component */ + avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); + if (!avail_comp) { + err = -ENOMEM; + goto err_free_prefix; + } + avail_comp->actions_list = synapse->actions_list; + avail_comp->num_actions = synapse->num_actions; + + /* Create action_available attribute */ + err = counter_attribute_create(group, prefix, + "action_available", + counter_synapse_action_available_show, NULL, + avail_comp); + if (err) { + kfree(avail_comp); + goto err_free_prefix; + } + + kfree(prefix); + } + + return 0; + +err_free_prefix: + kfree(prefix); +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +struct count_comp_t { + struct counter_count *count; +}; + +static ssize_t counter_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_comp_t *const component = devattr->component; + struct counter_count *const count = component->count; + int err; + struct count_read_value val = { .buf = buf }; + + err = counter->ops->count_read(counter, count, &val); + if (err) + return err; + + return val.len; +} + +static ssize_t counter_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_comp_t *const component = devattr->component; + struct counter_count *const count = component->count; + int err; + struct count_write_value val = { .buf = buf }; + + err = counter->ops->count_write(counter, count, &val); + if (err) + return err; + + return len; +} + +static const char *const count_function_str[] = { + [COUNT_FUNCTION_INCREASE] = "increase", + [COUNT_FUNCTION_DECREASE] = "decrease", + [COUNT_FUNCTION_PULSE_DIRECTION] = "pulse-direction", + [COUNT_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a", + [COUNT_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b", + [COUNT_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a", + [COUNT_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b", + [COUNT_FUNCTION_QUADRATURE_X2_RISING] = "quadrature x2 rising", + [COUNT_FUNCTION_QUADRATURE_X2_FALLING] = "quadrature x2 falling", + [COUNT_FUNCTION_QUADRATURE_X4] = "quadrature x4" +}; + +static ssize_t counter_function_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_comp_t *const component = devattr->component; + struct counter_count *const count = component->count; + size_t func_index; + enum count_function function; + + err = counter->ops->function_get(counter, count, &func_index); + if (err) + return err; + + count->function = func_index; + + function = count->functions_list[func_index]; + return scnprintf(buf, PAGE_SIZE, "%s\n", count_function_str[function]); +} + +static ssize_t counter_function_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_comp_t *const component = devattr->component; + struct counter_count *const count = component->count; + const size_t num_functions = count->num_functions; + size_t func_index; + enum count_function function; + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + + /* Find requested Count function mode */ + for (func_index = 0; func_index < num_functions; func_index++) { + function = count->functions_list[func_index]; + if (sysfs_streq(buf, count_function_str[function])) + break; + } + /* Return error if requested Count function mode not found */ + if (func_index >= num_functions) + return -EINVAL; + + err = counter->ops->function_set(counter, count, func_index); + if (err) + return err; + + count->function = func_index; + + return len; +} + +struct count_ext_comp_t { + struct counter_count *count; + const struct counter_count_ext *ext; +}; + +static ssize_t counter_count_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_ext_comp_t *const comp = devattr->component; + const struct counter_count_ext *const ext = comp->ext; + + return ext->read(dev_get_drvdata(dev), comp->count, ext->priv, buf); +} + +static ssize_t counter_count_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct count_ext_comp_t *const comp = devattr->component; + const struct counter_count_ext *const ext = comp->ext; + + return ext->write(dev_get_drvdata(dev), comp->count, ext->priv, buf, + len); +} + +static int counter_count_ext_register( + struct counter_device_attr_group *const group, + struct counter_count *const count) +{ + const size_t num_ext = count->num_ext; + size_t i; + const struct counter_count_ext *ext; + struct count_ext_comp_t *count_ext_comp; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < num_ext; i++) { + ext = count->ext + i; + + /* Allocate count_ext attribute component */ + count_ext_comp = kmalloc(sizeof(*count_ext_comp), GFP_KERNEL); + if (!count_ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + count_ext_comp->count = count; + count_ext_comp->ext = ext; + + /* Allocate count_ext attribute */ + err = counter_attribute_create(group, "", ext->name, + (ext->read) ? counter_count_ext_show : NULL, + (ext->write) ? counter_count_ext_store : NULL, + count_ext_comp); + if (err) { + kfree(count_ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +struct func_avail_comp_t { + const enum count_function *functions_list; + size_t num_functions; +}; + +static ssize_t counter_count_function_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct func_avail_comp_t *const component = devattr->component; + const enum count_function *const func_list = component->functions_list; + const size_t num_functions = component->num_functions; + size_t i; + enum count_function function; + ssize_t len = 0; + + for (i = 0; i < num_functions; i++) { + function = func_list[i]; + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + count_function_str[function]); + } + + return len; +} + +static int counter_count_attributes_create( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_count *const count) +{ + struct count_comp_t *count_comp; + int err; + struct count_comp_t *func_comp; + struct func_avail_comp_t *avail_comp; + + /* Allocate count attribute component */ + count_comp = kmalloc(sizeof(*count_comp), GFP_KERNEL); + if (!count_comp) + return -ENOMEM; + count_comp->count = count; + + /* Create main Count attribute */ + err = counter_attribute_create(group, "", "count", + (counter->ops->count_read) ? counter_count_show : NULL, + (counter->ops->count_write) ? counter_count_store : NULL, + count_comp); + if (err) { + kfree(count_comp); + return err; + } + + /* Allocate function attribute component */ + func_comp = kmalloc(sizeof(*func_comp), GFP_KERNEL); + if (!func_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + func_comp->count = count; + + /* Create Count function attribute */ + err = counter_attribute_create(group, "", "function", + (counter->ops->function_get) ? counter_function_show : NULL, + (counter->ops->function_set) ? counter_function_store : NULL, + func_comp); + if (err) { + kfree(func_comp); + goto err_free_attr_list; + } + + /* Allocate function available attribute component */ + avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); + if (!avail_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + avail_comp->functions_list = count->functions_list; + avail_comp->num_functions = count->num_functions; + + /* Create Count function_available attribute */ + err = counter_attribute_create(group, "", "function_available", + counter_count_function_available_show, + NULL, avail_comp); + if (err) { + kfree(avail_comp); + goto err_free_attr_list; + } + + /* Create Count name attribute */ + err = counter_name_attribute_create(group, count->name); + if (err) + goto err_free_attr_list; + + /* Register Count extension attributes */ + err = counter_count_ext_register(group, count); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +static int counter_counts_register( + struct counter_device_attr_group *const groups_list, + const struct counter_device *const counter) +{ + const size_t num_counts = counter->num_counts; + size_t i; + struct counter_count *count; + const char *name; + int err; + + /* Register each Count */ + for (i = 0; i < num_counts; i++) { + count = counter->counts + i; + + /* Generate Count attribute directory name */ + name = kasprintf(GFP_KERNEL, "count%d", count->id); + if (!name) { + err = -ENOMEM; + goto err_free_attr_groups; + } + groups_list[i].attr_group.name = name; + + /* Register the Synapses associated with each Count */ + err = counter_synapses_register(groups_list + i, counter, count, + name); + if (err) + goto err_free_attr_groups; + + /* Create all attributes associated with Count */ + err = counter_count_attributes_create(groups_list + i, counter, + count); + if (err) + goto err_free_attr_groups; + } + + return 0; + +err_free_attr_groups: + do { + kfree(groups_list[i].attr_group.name); + free_counter_device_attr_list(&groups_list[i].attr_list); + } while (i--); + return err; +} + +struct size_comp_t { + size_t size; +}; + +static ssize_t counter_device_attr_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct size_comp_t *const comp = to_counter_attr(attr)->component; + + return scnprintf(buf, PAGE_SIZE, "%zu\n", comp->size); +} + +static int counter_size_attribute_create( + struct counter_device_attr_group *const group, + const size_t size, const char *const name) +{ + struct size_comp_t *size_comp; + int err; + + /* Allocate size attribute component */ + size_comp = kmalloc(sizeof(*size_comp), GFP_KERNEL); + if (!size_comp) + return -ENOMEM; + size_comp->size = size; + + err = counter_attribute_create(group, "", name, + counter_device_attr_size_show, NULL, + size_comp); + if (err) + goto err_free_size_comp; + + return 0; + +err_free_size_comp: + kfree(size_comp); + return err; +} + +struct ext_comp_t { + const struct counter_device_ext *ext; +}; + +static ssize_t counter_device_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct ext_comp_t *const component = devattr->component; + const struct counter_device_ext *const ext = component->ext; + + return ext->read(dev_get_drvdata(dev), ext->priv, buf); +} + +static ssize_t counter_device_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct ext_comp_t *const component = devattr->component; + const struct counter_device_ext *const ext = component->ext; + + return ext->write(dev_get_drvdata(dev), ext->priv, buf, len); +} + +static int counter_device_ext_register( + struct counter_device_attr_group *const group, + struct counter_device *const counter) +{ + const size_t num_ext = counter->num_ext; + struct ext_comp_t *ext_comp; + size_t i; + const struct counter_device_ext *ext; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < num_ext; i++) { + ext = counter->ext + i; + + /* Allocate extension attribute component */ + ext_comp = kmalloc(sizeof(*ext_comp), GFP_KERNEL); + if (!ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + + ext_comp->ext = ext; + + /* Allocate extension attribute */ + err = counter_attribute_create(group, "", ext->name, + (ext->read) ? counter_device_ext_show : NULL, + (ext->write) ? counter_device_ext_store : NULL, + ext_comp); + if (err) { + kfree(ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +static int counter_global_attr_register( + struct counter_device_attr_group *const group, + struct counter_device *const counter) +{ + int err; + + /* Create name attribute */ + err = counter_name_attribute_create(group, counter->name); + if (err) + return err; + + /* Create num_counts attribute */ + err = counter_size_attribute_create(group, counter->num_counts, + "num_counts"); + if (err) + goto err_free_attr_list; + + /* Create num_signals attribute */ + err = counter_size_attribute_create(group, counter->num_signals, + "num_signals"); + if (err) + goto err_free_attr_list; + + /* Register Counter device extension attributes */ + err = counter_device_ext_register(group, counter); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + free_counter_device_attr_list(&group->attr_list); + return err; +} + +static void free_counter_device_groups_list( + struct counter_device_attr_group *const groups_list, + const size_t num_groups) +{ + struct counter_device_attr_group *group; + size_t i; + + for (i = 0; i < num_groups; i++) { + group = groups_list + i; + + kfree(group->attr_group.name); + kfree(group->attr_group.attrs); + free_counter_device_attr_list(&group->attr_list); + } + + kfree(groups_list); +} + +static int prepare_counter_device_groups_list( + struct counter_device *const counter) +{ + const size_t total_num_groups = + counter->num_signals + counter->num_counts + 1; + struct counter_device_attr_group *groups_list; + size_t i; + int err; + size_t num_groups = 0; + + /* Allocate space for attribute groups (signals. counts, and ext) */ + groups_list = kcalloc(total_num_groups, sizeof(*groups_list), + GFP_KERNEL); + if (!groups_list) + return -ENOMEM; + + /* Initialize attribute lists */ + for (i = 0; i < total_num_groups; i++) + INIT_LIST_HEAD(&groups_list[i].attr_list); + + /* Register Signals */ + err = counter_signals_register(groups_list, counter); + if (err) + goto err_free_groups_list; + num_groups += counter->num_signals; + + /* Register Counts and respective Synapses */ + err = counter_counts_register(groups_list + num_groups, counter); + if (err) + goto err_free_groups_list; + num_groups += counter->num_counts; + + /* Register Counter global attributes */ + err = counter_global_attr_register(groups_list + num_groups, counter); + if (err) + goto err_free_groups_list; + num_groups++; + + /* Store groups_list in device_state */ + counter->device_state->groups_list = groups_list; + counter->device_state->num_groups = num_groups; + + return 0; + +err_free_groups_list: + free_counter_device_groups_list(groups_list, num_groups); + return err; +} + +static int prepare_counter_device_groups( + struct counter_device_state *const device_state) +{ + size_t i; + struct counter_device_attr_group *group; + int err; + size_t j; + struct counter_device_attr *p; + + /* Allocate attribute groups for association with device */ + device_state->groups = kcalloc(device_state->num_groups + 1, + sizeof(*device_state->groups), + GFP_KERNEL); + if (!device_state->groups) + return -ENOMEM; + + /* Prepare each group of attributes for association */ + for (i = 0; i < device_state->num_groups; i++) { + group = device_state->groups_list + i; + + /* Allocate space for attribute pointers in attribute group */ + group->attr_group.attrs = kcalloc(group->num_attr + 1, + sizeof(*group->attr_group.attrs), GFP_KERNEL); + if (!group->attr_group.attrs) { + err = -ENOMEM; + goto err_free_groups; + } + + /* Add attribute pointers to attribute group */ + j = 0; + list_for_each_entry(p, &group->attr_list, l) + group->attr_group.attrs[j++] = &p->dev_attr.attr; + + /* Group attributes in attribute group */ + device_state->groups[i] = &group->attr_group; + } + /* Associate attributes with device */ + device_state->dev.groups = device_state->groups; + + return 0; + +err_free_groups: + kfree(device_state->groups); + return err; +} + +/* Provides a unique ID for each counter device */ +static DEFINE_IDA(counter_ida); + +static void counter_device_release(struct device *dev) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + struct counter_device_state *const device_state = counter->device_state; + + kfree(device_state->groups); + free_counter_device_groups_list(device_state->groups_list, + device_state->num_groups); + ida_simple_remove(&counter_ida, device_state->id); + kfree(device_state); +} + +static struct device_type counter_device_type = { + .name = "counter_device", + .release = counter_device_release +}; + +static struct bus_type counter_bus_type = { + .name = "counter" +}; + +/** + * counter_register - register Counter to the system + * @counter: pointer to Counter to register + * + * This function registers a Counter to the system. A sysfs "counter" directory + * will be created and populated with sysfs attributes correlating with the + * Counter Signals, Synapses, and Counts respectively. + */ +int counter_register(struct counter_device *const counter) +{ + struct counter_device_state *device_state; + int err; + + /* Allocate internal state container for Counter device */ + device_state = kzalloc(sizeof(*device_state), GFP_KERNEL); + if (!device_state) + return -ENOMEM; + counter->device_state = device_state; + + /* Acquire unique ID */ + device_state->id = ida_simple_get(&counter_ida, 0, 0, GFP_KERNEL); + if (device_state->id < 0) { + err = device_state->id; + goto err_free_device_state; + } + + /* Configure device structure for Counter */ + device_state->dev.type = &counter_device_type; + device_state->dev.bus = &counter_bus_type; + if (counter->parent) { + device_state->dev.parent = counter->parent; + device_state->dev.of_node = counter->parent->of_node; + } + dev_set_name(&device_state->dev, "counter%d", device_state->id); + device_initialize(&device_state->dev); + dev_set_drvdata(&device_state->dev, counter); + + /* Prepare device attributes */ + err = prepare_counter_device_groups_list(counter); + if (err) + goto err_free_id; + + /* Organize device attributes to groups and match to device */ + err = prepare_counter_device_groups(device_state); + if (err) + goto err_free_groups_list; + + /* Add device to system */ + err = device_add(&device_state->dev); + if (err) + goto err_free_groups; + + return 0; + +err_free_groups: + kfree(device_state->groups); +err_free_groups_list: + free_counter_device_groups_list(device_state->groups_list, + device_state->num_groups); +err_free_id: + ida_simple_remove(&counter_ida, device_state->id); +err_free_device_state: + kfree(device_state); + return err; +} +EXPORT_SYMBOL(counter_register); + +/** + * counter_unregister - unregister Counter from the system + * @counter: pointer to Counter to unregister + * + * The Counter is unregistered from the system; all allocated memory is freed. + */ +void counter_unregister(struct counter_device *const counter) +{ + if (counter) + device_del(&counter->device_state->dev); +} +EXPORT_SYMBOL(counter_unregister); + +static void devm_counter_unreg(struct device *dev, void *res) +{ + counter_unregister(*(struct counter_device **)res); +} + +/** + * devm_counter_register - Resource-managed counter_register + * @dev: device to allocate counter_device for + * @counter: pointer to Counter to register + * + * Managed counter_register. The Counter registered with this function is + * automatically unregistered on driver detach. This function calls + * counter_register internally. Refer to that function for more information. + * + * If an Counter registered with this function needs to be unregistered + * separately, devm_counter_unregister must be used. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_counter_register(struct device *dev, + struct counter_device *const counter) +{ + struct counter_device **ptr; + int ret; + + ptr = devres_alloc(devm_counter_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = counter_register(counter); + if (!ret) { + *ptr = counter; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL(devm_counter_register); + +static int devm_counter_match(struct device *dev, void *res, void *data) +{ + struct counter_device **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +/** + * devm_counter_unregister - Resource-managed counter_unregister + * @dev: device this counter_device belongs to + * @counter: pointer to Counter associated with the device + * + * Unregister Counter registered with devm_counter_register. + */ +void devm_counter_unregister(struct device *dev, + struct counter_device *const counter) +{ + int rc; + + rc = devres_release(dev, devm_counter_unreg, devm_counter_match, + counter); + WARN_ON(rc); +} +EXPORT_SYMBOL(devm_counter_unregister); + +static int __init counter_init(void) +{ + return bus_register(&counter_bus_type); +} + +static void __exit counter_exit(void) +{ + bus_unregister(&counter_bus_type); +} + +subsys_initcall(counter_init); +module_exit(counter_exit); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("Generic Counter interface"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/counter.h b/include/linux/counter.h new file mode 100644 index 000000000000..a0b0349d098a --- /dev/null +++ b/include/linux/counter.h @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * 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 _COUNTER_H_ +#define _COUNTER_H_ + +#include +#include + +enum count_direction { + COUNT_DIRECTION_FORWARD = 0, + COUNT_DIRECTION_BACKWARD +}; +extern const char *const count_direction_str[2]; + +enum count_mode { + COUNT_MODE_NORMAL = 0, + COUNT_MODE_RANGE_LIMIT, + COUNT_MODE_NON_RECYCLE, + COUNT_MODE_MODULO_N +}; +extern const char *const count_mode_str[4]; + +struct counter_device; +struct counter_signal; + +/** + * struct counter_signal_ext - Counter Signal extensions + * @name: attribute name + * @read: read callback for this attribute; may be NULL + * @write: write callback for this attribute; may be NULL + * @priv: data private to the driver + */ +struct counter_signal_ext { + const char *name; + ssize_t (*read)(struct counter_device *counter, + struct counter_signal *signal, void *priv, + char *buf); + ssize_t (*write)(struct counter_device *counter, + struct counter_signal *signal, void *priv, + const char *buf, size_t len); + void *priv; +}; + +/** + * struct counter_signal - Counter Signal node + * @id: unique ID used to identify signal + * @name: device-specific Signal name; ideally, this should match the name + * as it appears in the datasheet documentation + * @ext: optional array of Counter Signal extensions + * @num_ext: number of Counter Signal extensions specified in @ext + * @priv: optional private data supplied by driver + */ +struct counter_signal { + int id; + const char *name; + + const struct counter_signal_ext *ext; + size_t num_ext; + + void *priv; +}; + +/** + * struct counter_signal_enum_ext - Signal enum extension attribute + * @items: Array of strings + * @num_items: Number of items specified in @items + * @set: Set callback function; may be NULL + * @get: Get callback function; may be NULL + * + * The counter_signal_enum_ext structure can be used to implement enum style + * Signal extension attributes. Enum style attributes are those which have a set + * of strings that map to unsigned integer values. The Generic Counter Signal + * enum extension helper code takes care of mapping between value and string, as + * well as generating a "_available" file which contains a list of all available + * items. The get callback is used to query the currently active item; the index + * of the item within the respective items array is returned via the 'item' + * parameter. The set callback is called when the attribute is updated; the + * 'item' parameter contains the index of the newly activated item within the + * respective items array. + */ +struct counter_signal_enum_ext { + const char * const *items; + size_t num_items; + int (*get)(struct counter_device *counter, + struct counter_signal *signal, + size_t *item); + int (*set)(struct counter_device *counter, + struct counter_signal *signal, + size_t item); +}; + +ssize_t counter_signal_enum_read(struct counter_device *counter, + struct counter_signal *signal, void *priv, + char *buf); +ssize_t counter_signal_enum_write(struct counter_device *counter, + struct counter_signal *signal, void *priv, + const char *buf, size_t len); + +/** + * COUNTER_SIGNAL_ENUM() - Initialize Signal enum extension + * @_name: Attribute name + * @_e: Pointer to a counter_count_enum structure + * + * This should usually be used together with COUNTER_SIGNAL_ENUM_AVAILABLE() + */ +#define COUNTER_SIGNAL_ENUM(_name, _e) \ +{ \ + .name = (_name), \ + .read = counter_signal_enum_read, \ + .write = counter_signal_enum_write, \ + .priv = (_e) \ +} + +ssize_t counter_signal_enum_available_read(struct counter_device *counter, + struct counter_signal *signal, + void *priv, char *buf); + +/** + * COUNTER_SIGNAL_ENUM_AVAILABLE() - Initialize Signal enum available extension + * @_name: Attribute name ("_available" will be appended to the name) + * @_e: Pointer to a counter_signal_enum structure + * + * Creates a read only attribute that lists all the available enum items in a + * newline separated list. This should usually be used together with + * COUNTER_SIGNAL_ENUM() + */ +#define COUNTER_SIGNAL_ENUM_AVAILABLE(_name, _e) \ +{ \ + .name = (_name "_available"), \ + .read = counter_signal_enum_available_read, \ + .priv = (_e) \ +} + +enum synapse_action { + SYNAPSE_ACTION_NONE = 0, + SYNAPSE_ACTION_RISING_EDGE, + SYNAPSE_ACTION_FALLING_EDGE, + SYNAPSE_ACTION_BOTH_EDGES +}; + +/** + * struct counter_synapse - Counter Synapse node + * @action: index of current action mode + * @actions_list: array of available action modes + * @num_actions: number of action modes specified in @actions_list + * @signal: pointer to associated signal + */ +struct counter_synapse { + size_t action; + const enum synapse_action *actions_list; + size_t num_actions; + + struct counter_signal *signal; +}; + +struct counter_count; + +/** + * struct counter_count_ext - Counter Count extension + * @name: attribute name + * @read: read callback for this attribute; may be NULL + * @write: write callback for this attribute; may be NULL + * @priv: data private to the driver + */ +struct counter_count_ext { + const char *name; + ssize_t (*read)(struct counter_device *counter, + struct counter_count *count, void *priv, + char *buf); + ssize_t (*write)(struct counter_device *counter, + struct counter_count *count, void *priv, + const char *buf, size_t len); + void *priv; +}; + +enum count_function { + COUNT_FUNCTION_INCREASE = 0, + COUNT_FUNCTION_DECREASE, + COUNT_FUNCTION_PULSE_DIRECTION, + COUNT_FUNCTION_QUADRATURE_X1_A, + COUNT_FUNCTION_QUADRATURE_X1_B, + COUNT_FUNCTION_QUADRATURE_X2_A, + COUNT_FUNCTION_QUADRATURE_X2_B, + COUNT_FUNCTION_QUADRATURE_X2_RISING, + COUNT_FUNCTION_QUADRATURE_X2_FALLING, + COUNT_FUNCTION_QUADRATURE_X4 +}; + +/** + * struct counter_count - Counter Count node + * @id: unique ID used to identify Count + * @name: device-specific Count name; ideally, this should match + * the name as it appears in the datasheet documentation + * @function: index of current function mode + * @functions_list: array available function modes + * @num_functions: number of function modes specified in @functions_list + * @synapses: array of synapses for initialization + * @num_synapses: number of synapses specified in @synapses + * @ext: optional array of Counter Count extensions + * @num_ext: number of Counter Count extensions specified in @ext + * @priv: optional private data supplied by driver + */ +struct counter_count { + int id; + const char *name; + + size_t function; + const enum count_function *functions_list; + size_t num_functions; + + struct counter_synapse *synapses; + size_t num_synapses; + + const struct counter_count_ext *ext; + size_t num_ext; + + void *priv; +}; + +/** + * struct counter_count_enum_ext - Count enum extension attribute + * @items: Array of strings + * @num_items: Number of items specified in @items + * @set: Set callback function; may be NULL + * @get: Get callback function; may be NULL + * + * The counter_count_enum_ext structure can be used to implement enum style + * Count extension attributes. Enum style attributes are those which have a set + * of strings that map to unsigned integer values. The Generic Counter Count + * enum extension helper code takes care of mapping between value and string, as + * well as generating a "_available" file which contains a list of all available + * items. The get callback is used to query the currently active item; the index + * of the item within the respective items array is returned via the 'item' + * parameter. The set callback is called when the attribute is updated; the + * 'item' parameter contains the index of the newly activated item within the + * respective items array. + */ +struct counter_count_enum_ext { + const char * const *items; + size_t num_items; + int (*get)(struct counter_device *counter, + struct counter_count *count, + size_t *item); + int (*set)(struct counter_device *counter, + struct counter_count *count, + size_t item); +}; + +ssize_t counter_count_enum_read(struct counter_device *counter, + struct counter_count *count, void *priv, + char *buf); +ssize_t counter_count_enum_write(struct counter_device *counter, + struct counter_count *count, void *priv, + const char *buf, size_t len); + +/** + * COUNTER_COUNT_ENUM() - Initialize Count enum extension + * @_name: Attribute name + * @_e: Pointer to a counter_count_enum structure + * + * This should usually be used together with COUNTER_COUNT_ENUM_AVAILABLE() + */ +#define COUNTER_COUNT_ENUM(_name, _e) \ +{ \ + .name = (_name), \ + .read = counter_count_enum_read, \ + .write = counter_count_enum_write, \ + .priv = (_e) \ +} + +ssize_t counter_count_enum_available_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf); + +/** + * COUNTER_COUNT_ENUM_AVAILABLE() - Initialize Count enum available extension + * @_name: Attribute name ("_available" will be appended to the name) + * @_e: Pointer to a counter_count_enum structure + * + * Creates a read only attribute that lists all the available enum items in a + * newline separated list. This should usually be used together with + * COUNTER_COUNT_ENUM() + */ +#define COUNTER_COUNT_ENUM_AVAILABLE(_name, _e) \ +{ \ + .name = (_name "_available"), \ + .read = counter_count_enum_available_read, \ + .priv = (_e) \ +} + +/** + * struct counter_device_attr_group - internal container for attribute group + * @attr_group: Counter sysfs attributes group + * @attr_list: list to keep track of created Counter sysfs attributes + * @num_attr: number of Counter sysfs attributes + */ +struct counter_device_attr_group { + struct attribute_group attr_group; + struct list_head attr_list; + size_t num_attr; +}; + +/** + * struct counter_device_state - internal state container for a Counter device + * @id: unique ID used to identify the Counter + * @dev: internal device structure + * @groups_list attribute groups list (groups for Signals, Counts, and ext) + * @num_groups number of attribute groups containers + * @groups: Counter sysfs attribute groups (used to populate @dev.groups) + */ +struct counter_device_state { + int id; + struct device dev; + struct counter_device_attr_group *groups_list; + size_t num_groups; + const struct attribute_group **groups; +}; + +/** + * struct signal_read_value - Opaque Signal read value + * @buf: string representation of Signal read value + * @len: length of string in @buf + */ +struct signal_read_value { + char *buf; + size_t len; +}; + +/** + * struct count_read_value - Opaque Count read value + * @buf: string representation of Count read value + * @len: length of string in @buf + */ +struct count_read_value { + char *buf; + size_t len; +}; + +/** + * struct count_write_value - Opaque Count write value + * @buf: string representation of Count write value + */ +struct count_write_value { + const char *buf; +}; + +/** + * struct counter_ops - Callbacks from driver + * @signal_read: optional read callback for Signal attribute. The read + * value of the respective Signal should be passed back via + * the val parameter. val points to an opaque type which + * should be set only via the set_signal_read_value + * function. + * @count_read: optional read callback for Count attribute. The read + * value of the respective Count should be passed back via + * the val parameter. val points to an opaque type which + * should be set only via the set_count_read_value + * function. + * @count_write: optional write callback for Count attribute. The write + * value for the respective Count is passed in via the val + * parameter. val points to an opaque type which should be + * access only via the get_count_write_value function. + * @function_get: function to get the current count function mode. Returns + * 0 on success and negative error code on error. The index + * of the respective Count's returned function mode should + * be passed back via the function parameter. + * @function_set: function to set the count function mode. function is the + * index of the requested function mode from the respective + * Count's functions_list array. + * @action_get: function to get the current action mode. Returns 0 on + * success and negative error code on error. The index of + * the respective Signal's returned action mode should be + * passed back via the action parameter. + * @action_set: function to set the action mode. action is the index of + * the requested action mode from the respective Synapse's + * actions_list array. + */ +struct counter_ops { + int (*signal_read)(struct counter_device *counter, + struct counter_signal *signal, + struct signal_read_value *val); + int (*count_read)(struct counter_device *counter, + struct counter_count *count, + struct count_read_value *val); + int (*count_write)(struct counter_device *counter, + struct counter_count *count, + struct count_write_value *val); + int (*function_get)(struct counter_device *counter, + struct counter_count *count, size_t *function); + int (*function_set)(struct counter_device *counter, + struct counter_count *count, size_t function); + int (*action_get)(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, size_t *action); + int (*action_set)(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, size_t action); +}; + +/** + * struct counter_device_ext - Counter device extension + * @name: attribute name + * @read: read callback for this attribute; may be NULL + * @write: write callback for this attribute; may be NULL + * @priv: data private to the driver + */ +struct counter_device_ext { + const char *name; + ssize_t (*read)(struct counter_device *counter, void *priv, + char *buf); + ssize_t (*write)(struct counter_device *counter, void *priv, + const char *buf, size_t len); + void *priv; +}; + +/** + * struct counter_device_enum_ext - Counter enum extension attribute + * @items: Array of strings + * @num_items: Number of items specified in @items + * @set: Set callback function; may be NULL + * @get: Get callback function; may be NULL + * + * The counter_device_enum_ext structure can be used to implement enum style + * Counter extension attributes. Enum style attributes are those which have a + * set of strings that map to unsigned integer values. The Generic Counter enum + * extension helper code takes care of mapping between value and string, as well + * as generating a "_available" file which contains a list of all available + * items. The get callback is used to query the currently active item; the index + * of the item within the respective items array is returned via the 'item' + * parameter. The set callback is called when the attribute is updated; the + * 'item' parameter contains the index of the newly activated item within the + * respective items array. + */ +struct counter_device_enum_ext { + const char * const *items; + size_t num_items; + int (*get)(struct counter_device *counter, + size_t *item); + int (*set)(struct counter_device *counter, + size_t item); +}; + +ssize_t counter_device_enum_read(struct counter_device *counter, void *priv, + char *buf); +ssize_t counter_device_enum_write(struct counter_device *counter, void *priv, + const char *buf, size_t len); + +/** + * COUNTER_DEVICE_ENUM() - Initialize Counter enum extension + * @_name: Attribute name + * @_e: Pointer to a counter_device_enum structure + * + * This should usually be used together with COUNTER_DEVICE_ENUM_AVAILABLE() + */ +#define COUNTER_DEVICE_ENUM(_name, _e) \ +{ \ + .name = (_name), \ + .read = counter_device_enum_read, \ + .write = counter_device_enum_write, \ + .priv = (_e) \ +} + +ssize_t counter_device_enum_available_read(struct counter_device *counter, + void *priv, char *buf); + +/** + * COUNTER_DEVICE_ENUM_AVAILABLE() - Initialize Counter enum available extension + * @_name: Attribute name ("_available" will be appended to the name) + * @_e: Pointer to a counter_device_enum structure + * + * Creates a read only attribute that lists all the available enum items in a + * newline separated list. This should usually be used together with + * COUNTER_DEVICE_ENUM() + */ +#define COUNTER_DEVICE_ENUM_AVAILABLE(_name, _e) \ +{ \ + .name = (_name "_available"), \ + .read = counter_device_enum_available_read, \ + .priv = (_e) \ +} + +/** + * struct counter_device - Counter data structure + * @name: name of the device as it appears in the datasheet + * @parent: optional parent device providing the counters + * @device_state: internal device state container + * @ops: callbacks from driver + * @signals: array of Signals + * @num_signals: number of Signals specified in @signals + * @counts: array of Counts + * @num_counts: number of Counts specified in @counts + * @ext: optional array of Counter device extensions + * @num_ext: number of Counter device extensions specified in @ext + * @priv: optional private data supplied by driver + */ +struct counter_device { + const char *name; + struct device *parent; + struct counter_device_state *device_state; + + const struct counter_ops *ops; + + struct counter_signal *signals; + size_t num_signals; + struct counter_count *counts; + size_t num_counts; + + const struct counter_device_ext *ext; + size_t num_ext; + + void *priv; +}; + +enum signal_level { + SIGNAL_LEVEL_LOW = 0, + SIGNAL_LEVEL_HIGH +}; + +enum signal_value_type { + SIGNAL_LEVEL = 0 +}; + +enum count_value_type { + COUNT_POSITION_UNSIGNED = 0, + COUNT_POSITION_SIGNED +}; + +void set_signal_read_value(struct signal_read_value *const val, + const enum signal_value_type type, void *const data); +void set_count_read_value(struct count_read_value *const val, + const enum count_value_type type, void *const data); +int get_count_write_value(void *const data, const enum count_value_type type, + const struct count_write_value *const val); + +int counter_register(struct counter_device *const counter); +void counter_unregister(struct counter_device *const counter); +int devm_counter_register(struct device *dev, + struct counter_device *const counter); +void devm_counter_unregister(struct device *dev, + struct counter_device *const counter); + +#endif /* _COUNTER_H_ */