* [PATCH v7 0/3] counter: add GPIO-based counter driver
@ 2026-07-14 8:17 Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding Wadim Mueller
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Wadim Mueller @ 2026-07-14 8:17 UTC (permalink / raw)
To: William Breathitt Gray
Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
devicetree, linux-kernel
This series adds a counter subsystem driver that does quadrature
encoder position tracking with plain GPIO pins and edge interrupts.
Compared to interrupt-cnt.c (pulse-only) it provides full A/B/Index
decoding and exposes the counter sysfs ABI. Target hardware is
low- to medium-speed rotary encoders on SoCs without a free eQEP /
FTM / etc. Benchmark rig: github.com/wafgo/qenc-bench.
v7 addresses Williams review comments on v6 [1]. Main change is the
reworked index GPIO handling, rest is smaller stuff.
Tested in quadrature x4 mode (both directions) on a AM642 board with
a real encoder on GPIOs.
== Changes in v7 ==
Driver:
- check the enabled state in the ISRs before reading the GPIOs.
This also moves the reads under the spinlock, so concurrent A/B
ISRs cant evaluate the state out of order anymore.
- dropped the spinlock from the simple single-field read callbacks
- smaller cleanups from the review (b_irq_users increment split
from the zero check, current_state variable gone, ternary in
index_pulse, GPIO_COUNTER_NUM_COUNTS_MAX rename,
GPIO_COUNTER_MAX_INDEX removed)
- index GPIOs now live per Count (idx_gpio/idx_irq/has_index in
gpio_counter_count_priv), the n_idx based arrays are gone
- enable_write: dont resample prev_a/prev_b when Count 2 gets
enabled, that would corrupt the quadrature state of an already
running Count 1. Also sync the direction from the current B level
on enable in pulse-direction mode, otherwise the counter steps in
the wrong direction until the next B edge if B moved while
disabled (both reported by the Sashiko review on v6).
Regarding the IRQ enable ordering question from the review: I kept
setting cp->enabled = true before enable_irq(). The prev sampling and
the enabled flag are done atomic under the lock, so the first edge
after enable_irq() sees consistent state. With the other order there
is a window where an edge fires and gets droped.
Binding:
- index-gpios (array, position selects the Count) is replaced by
two seperate properties index1-gpios and index2-gpios, matching
the signal-a-gpios/signal-b-gpios naming. So Count 2 can have an
index line without a dummy entry for Count 1.
- dropped Conors Ack [2] because of the binding change, a fresh
one would be appreciated
The 2D synapse array preparation for a future pulse-direction mode
on Count 2 is not part of this revision, that was flagged as
follow-up material.
[1] https://lore.kernel.org/all/20260707104501.977372-1-wbg@kernel.org/
[2] https://lore.kernel.org/all/20260629-trekker-estranged-754e9bfada16@spud/
Wadim Mueller (3):
dt-bindings: counter: add gpio-counter binding
counter: add GPIO-based counter driver
MAINTAINERS: add entry for GPIO counter driver
.../bindings/counter/gpio-counter.yaml | 60 +
MAINTAINERS | 7 +
drivers/counter/Kconfig | 17 +
drivers/counter/Makefile | 1 +
drivers/counter/gpio-counter.c | 1011 +++++++++++++++++
5 files changed, 1096 insertions(+)
create mode 100644 Documentation/devicetree/bindings/counter/gpio-counter.yaml
create mode 100644 drivers/counter/gpio-counter.c
base-commit: 3cd8b194bf3428dfa53120fee47e827a7c495815
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding
2026-07-14 8:17 [PATCH v7 0/3] counter: add GPIO-based counter driver Wadim Mueller
@ 2026-07-14 8:17 ` Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 2/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 3/3] MAINTAINERS: add entry for GPIO " Wadim Mueller
2 siblings, 0 replies; 6+ messages in thread
From: Wadim Mueller @ 2026-07-14 8:17 UTC (permalink / raw)
To: William Breathitt Gray
Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
devicetree, linux-kernel
Add a binding for a generic GPIO-based counter. Two GPIOs (signal-a,
signal-b) drive two Counts; optional index GPIOs load a preset. The
counter function (quadrature, pulse-direction, increase/decrease) is
selected at runtime via the counter sysfs interface.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
v7:
- index-gpios split into seperate index1-gpios/index2-gpios, so
Count 2 can have an index line without a dummy entry for Count 1
- dropped Conors Ack since the binding changed
---
.../bindings/counter/gpio-counter.yaml | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 Documentation/devicetree/bindings/counter/gpio-counter.yaml
diff --git a/Documentation/devicetree/bindings/counter/gpio-counter.yaml b/Documentation/devicetree/bindings/counter/gpio-counter.yaml
new file mode 100644
index 000000000000..782cb177ed0b
--- /dev/null
+++ b/Documentation/devicetree/bindings/counter/gpio-counter.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/counter/gpio-counter.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: GPIO-based Counter
+
+maintainers:
+ - Wadim Mueller <wafgo01@gmail.com>
+
+description: |
+ GPIO-based software counter with two Counts:
+ * Count 1: A + B. Quadrature X1/X2/X4, pulse-direction, inc/dec.
+ * Count 2: B. Inc / dec.
+ The function is selected at runtime via the counter sysfs ABI. Each
+ Count has an optional Index (Z) that loads its preset on rising edge.
+
+properties:
+ compatible:
+ const: gpio-counter
+
+ signal-a-gpios:
+ maxItems: 1
+ description: Signal A (phase A; pulse input in pulse-direction/inc/dec).
+
+ signal-b-gpios:
+ maxItems: 1
+ description:
+ Signal B (phase B; direction in pulse-direction; pulse input for
+ Count 2).
+
+ index1-gpios:
+ maxItems: 1
+ description: Optional Index (Z) input for Count 1.
+
+ index2-gpios:
+ maxItems: 1
+ description: Optional Index (Z) input for Count 2.
+
+required:
+ - compatible
+ - signal-a-gpios
+ - signal-b-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ counter {
+ compatible = "gpio-counter";
+ signal-a-gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;
+ signal-b-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
+ index1-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
+ index2-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
+ };
+
+...
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v7 2/3] counter: add GPIO-based counter driver
2026-07-14 8:17 [PATCH v7 0/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding Wadim Mueller
@ 2026-07-14 8:17 ` Wadim Mueller
2026-07-14 8:27 ` sashiko-bot
2026-07-14 12:20 ` Uwe Kleine-König
2026-07-14 8:17 ` [PATCH v7 3/3] MAINTAINERS: add entry for GPIO " Wadim Mueller
2 siblings, 2 replies; 6+ messages in thread
From: Wadim Mueller @ 2026-07-14 8:17 UTC (permalink / raw)
To: William Breathitt Gray
Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
devicetree, linux-kernel
Add a platform driver that turns plain GPIOs into a counter device.
Edge interrupts on signal-a, signal-b (and optional index) are
decoded in software. Quadrature steps are validated with a 2-bit
Gray-code parity check; direction comes from the same identity.
The driver exposes two Counts on the same wires:
"Count 1" - Synapses on A, B and optional Index 1. Supports
QUADRATURE_X1_{A,B} / X2_{A,B} / X4, PULSE_DIRECTION,
INCREASE and DECREASE.
"Count 2" - Synapse on B and optional Index 2. Supports INCREASE
and DECREASE only.
Per-Count state (value, ceiling, preset, enable, function, direction)
lives in struct gpio_counter_count_priv so new per-Count features
(e.g. floor) can be added without touching parallel arrays.
Sleepable GPIO providers (I2C/SPI expanders) are rejected at probe
since the ISRs run in hardirq context.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
v7:
- enabled check before the GPIO reads in the ISRs (this also moves
the reads under the lock)
- no spinlock in the simple single-field read callbacks anymore
- b_irq_users increment/decrement split from the zero check,
current_state variable removed, ternary in index_pulse,
GPIO_COUNTER_NUM_COUNTS_MAX rename, GPIO_COUNTER_MAX_INDEX gone
- index GPIOs tracked per Count now (idx_gpio/idx_irq/has_index),
matches the index1-gpios/index2-gpios split in patch 1
- enable_write: dont resample prev_a/prev_b on Count 2 enable and
sync the direction from B on enable in pulse-direction mode
---
drivers/counter/Kconfig | 17 +
drivers/counter/Makefile | 1 +
drivers/counter/gpio-counter.c | 1011 ++++++++++++++++++++++++++++++++
3 files changed, 1029 insertions(+)
create mode 100644 drivers/counter/gpio-counter.c
diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
index d30d22dfe577..c20044032964 100644
--- a/drivers/counter/Kconfig
+++ b/drivers/counter/Kconfig
@@ -68,6 +68,23 @@ config INTEL_QEP
To compile this driver as a module, choose M here: the module
will be called intel-qep.
+config GPIO_COUNTER
+ tristate "GPIO-based counter driver"
+ depends on GPIOLIB
+ help
+ Select this option to enable the GPIO-based counter driver. It
+ reads A/B and an optional index signal via edge-triggered GPIO
+ interrupts and decodes them according to the selected mode:
+ Quadrature X1/X2/X4 (rotary or linear encoders), pulse-direction,
+ and pure increase / decrease pulse counters.
+
+ This is useful on SoCs that lack a dedicated hardware quadrature
+ decoder or pulse counter, or where the signals are wired to
+ generic GPIO pins rather than to a dedicated peripheral.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gpio-counter.
+
config INTERRUPT_CNT
tristate "Interrupt counter driver"
depends on GPIOLIB
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
index fa3c1d08f706..3959d69fb04e 100644
--- a/drivers/counter/Makefile
+++ b/drivers/counter/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o
obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o
obj-$(CONFIG_TI_EQEP) += ti-eqep.o
obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o
+obj-$(CONFIG_GPIO_COUNTER) += gpio-counter.o
obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o
obj-$(CONFIG_INTEL_QEP) += intel-qep.o
obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o
diff --git a/drivers/counter/gpio-counter.c b/drivers/counter/gpio-counter.c
new file mode 100644
index 000000000000..ceb79375d85b
--- /dev/null
+++ b/drivers/counter/gpio-counter.c
@@ -0,0 +1,1011 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO-based Counter Driver
+ *
+ * Software A/B (+ optional Z) edge decoding exposed as two Counts:
+ * Count 1: A + B (+ Index 1). Quadrature, pulse-direction, inc/dec.
+ * Count 2: B (+ Index 2). Inc / dec.
+ *
+ * Copyright (C) 2026 CMBlu Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/counter.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+enum gpio_counter_signal_id {
+ GPIO_COUNTER_SIGNAL_A = 0,
+ GPIO_COUNTER_SIGNAL_B,
+ GPIO_COUNTER_SIGNAL_INDEX1,
+ GPIO_COUNTER_SIGNAL_INDEX2,
+ GPIO_COUNTER_NUM_SIGNALS_MAX,
+};
+
+enum gpio_counter_count_id {
+ GPIO_COUNTER_COUNT_1 = 0,
+ GPIO_COUNTER_COUNT_2,
+ GPIO_COUNTER_NUM_COUNTS_MAX,
+};
+
+#define GPIO_COUNTER_COUNT1_NUM_SYNAPSES 3 /* A, B, Index 1 */
+#define GPIO_COUNTER_COUNT2_NUM_SYNAPSES 2 /* B, Index 2 */
+
+/* per-count state */
+struct gpio_counter_count_priv {
+ u64 value;
+ u64 ceiling;
+ u64 preset;
+ bool preset_enabled;
+ bool enabled;
+ enum counter_function function;
+ enum counter_count_direction direction;
+
+ struct gpio_desc *idx_gpio;
+ int idx_irq;
+ bool has_index;
+};
+
+struct gpio_counter_priv {
+ struct gpio_desc *gpio_a;
+ struct gpio_desc *gpio_b;
+
+ int irq_a;
+ int irq_b;
+
+ /* protects count state, prev_a, prev_b */
+ spinlock_t lock;
+
+ /* serialises enable_write() callers and b_irq_users */
+ struct mutex enable_lock;
+
+ /* irq_b is shared by Count 1 and Count 2 (under enable_lock) */
+ int b_irq_users;
+
+ int prev_a;
+ int prev_b;
+
+ struct gpio_counter_count_priv count_priv[GPIO_COUNTER_NUM_COUNTS_MAX];
+
+ struct counter_signal signals[GPIO_COUNTER_NUM_SIGNALS_MAX];
+ struct counter_synapse synapses_count1[GPIO_COUNTER_COUNT1_NUM_SYNAPSES];
+ struct counter_synapse synapses_count2[GPIO_COUNTER_COUNT2_NUM_SYNAPSES];
+ struct counter_count counts[GPIO_COUNTER_NUM_COUNTS_MAX];
+};
+
+/* Gray-code: valid step <=> exactly one of A/B toggled. */
+#define GPIO_COUNTER_STATE_CHANGED(pa, pb, ca, cb) \
+ ((pa) ^ (pb) ^ (ca) ^ (cb))
+#define GPIO_COUNTER_GET_DIRECTION(pb, ca) \
+ (((pb) ^ (ca)) ? COUNTER_COUNT_DIRECTION_FORWARD : \
+ COUNTER_COUNT_DIRECTION_BACKWARD)
+
+/* priv->lock held. */
+static void gpio_counter_value_step(struct gpio_counter_count_priv *cp)
+{
+ if (cp->direction == COUNTER_COUNT_DIRECTION_FORWARD) {
+ if (cp->value < cp->ceiling)
+ cp->value++;
+ } else {
+ if (cp->value > 0)
+ cp->value--;
+ }
+}
+
+/* priv->lock held. */
+static void gpio_counter_quadrature_x1_step(struct gpio_counter_count_priv *cp,
+ int level)
+{
+ if (level && cp->direction == COUNTER_COUNT_DIRECTION_FORWARD)
+ gpio_counter_value_step(cp);
+ else if (!level && cp->direction == COUNTER_COUNT_DIRECTION_BACKWARD)
+ gpio_counter_value_step(cp);
+}
+
+static irqreturn_t gpio_counter_a_isr(int irq, void *dev_id)
+{
+ struct counter_device *counter = dev_id;
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp =
+ &priv->count_priv[GPIO_COUNTER_COUNT_1];
+ unsigned long flags;
+ int a, b;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (!cp->enabled) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ /* !! drops negative gpiod_get_value() errors. */
+ a = !!gpiod_get_value(priv->gpio_a);
+ b = !!gpiod_get_value(priv->gpio_b);
+
+ switch (cp->function) {
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ case COUNTER_FUNCTION_QUADRATURE_X1_A:
+ if (!GPIO_COUNTER_STATE_CHANGED(priv->prev_a, priv->prev_b,
+ a, b))
+ break;
+ cp->direction = GPIO_COUNTER_GET_DIRECTION(priv->prev_b, a);
+ if (cp->function == COUNTER_FUNCTION_QUADRATURE_X1_A)
+ gpio_counter_quadrature_x1_step(cp, a);
+ else
+ gpio_counter_value_step(cp);
+ break;
+
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ /* direction set in the B-ISR */
+ if (a)
+ gpio_counter_value_step(cp);
+ break;
+
+ case COUNTER_FUNCTION_INCREASE:
+ case COUNTER_FUNCTION_DECREASE:
+ gpio_counter_value_step(cp);
+ break;
+
+ default:
+ break;
+ }
+
+ priv->prev_a = a;
+ priv->prev_b = b;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE,
+ GPIO_COUNTER_COUNT_1);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t gpio_counter_b_isr(int irq, void *dev_id)
+{
+ struct counter_device *counter = dev_id;
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *c1 =
+ &priv->count_priv[GPIO_COUNTER_COUNT_1];
+ struct gpio_counter_count_priv *c2 =
+ &priv->count_priv[GPIO_COUNTER_COUNT_2];
+ bool c1_enabled, c2_enabled;
+ unsigned long flags;
+ int a, b;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ c1_enabled = c1->enabled;
+ c2_enabled = c2->enabled;
+ if (!c1_enabled && !c2_enabled) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ a = !!gpiod_get_value(priv->gpio_a);
+ b = !!gpiod_get_value(priv->gpio_b);
+
+ /* Count 1: A+B decoding on this B edge */
+ if (c1_enabled) {
+ switch (c1->function) {
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ case COUNTER_FUNCTION_QUADRATURE_X2_B:
+ case COUNTER_FUNCTION_QUADRATURE_X1_B:
+ if (!GPIO_COUNTER_STATE_CHANGED(priv->prev_a,
+ priv->prev_b, a, b))
+ break;
+ c1->direction =
+ GPIO_COUNTER_GET_DIRECTION(priv->prev_b, a);
+ if (c1->function == COUNTER_FUNCTION_QUADRATURE_X1_B)
+ gpio_counter_quadrature_x1_step(c1, b);
+ else
+ gpio_counter_value_step(c1);
+ break;
+
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ /* B is the direction line */
+ c1->direction = b ? COUNTER_COUNT_DIRECTION_BACKWARD
+ : COUNTER_COUNT_DIRECTION_FORWARD;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Count 2: B-only */
+ if (c2_enabled)
+ gpio_counter_value_step(c2);
+
+ priv->prev_a = a;
+ priv->prev_b = b;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (c1_enabled)
+ counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE,
+ GPIO_COUNTER_COUNT_1);
+ if (c2_enabled)
+ counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE,
+ GPIO_COUNTER_COUNT_2);
+
+ return IRQ_HANDLED;
+}
+
+static void gpio_counter_index_pulse(struct counter_device *counter,
+ enum gpio_counter_count_id count_id)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count_id];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (cp->preset_enabled)
+ cp->value = (cp->preset > cp->ceiling) ? cp->ceiling : cp->preset;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ counter_push_event(counter, COUNTER_EVENT_INDEX, count_id);
+}
+
+static irqreturn_t gpio_counter_index1_isr(int irq, void *dev_id)
+{
+ gpio_counter_index_pulse(dev_id, GPIO_COUNTER_COUNT_1);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t gpio_counter_index2_isr(int irq, void *dev_id)
+{
+ gpio_counter_index_pulse(dev_id, GPIO_COUNTER_COUNT_2);
+ return IRQ_HANDLED;
+}
+
+static int gpio_counter_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *val = cp->value;
+
+ return 0;
+}
+
+static int gpio_counter_count_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (val > cp->ceiling) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return -EINVAL;
+ }
+ cp->value = val;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static const enum counter_function gpio_counter_count1_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_DECREASE,
+ COUNTER_FUNCTION_PULSE_DIRECTION,
+ COUNTER_FUNCTION_QUADRATURE_X1_A,
+ COUNTER_FUNCTION_QUADRATURE_X1_B,
+ COUNTER_FUNCTION_QUADRATURE_X2_A,
+ COUNTER_FUNCTION_QUADRATURE_X2_B,
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static const enum counter_function gpio_counter_count2_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_DECREASE,
+};
+
+static int gpio_counter_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *function = cp->function;
+
+ return 0;
+}
+
+static int gpio_counter_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ cp->function = function;
+
+ switch (function) {
+ case COUNTER_FUNCTION_INCREASE:
+ cp->direction = COUNTER_COUNT_DIRECTION_FORWARD;
+ break;
+ case COUNTER_FUNCTION_DECREASE:
+ cp->direction = COUNTER_COUNT_DIRECTION_BACKWARD;
+ break;
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ cp->direction = !!gpiod_get_value(priv->gpio_b)
+ ? COUNTER_COUNT_DIRECTION_BACKWARD
+ : COUNTER_COUNT_DIRECTION_FORWARD;
+ break;
+ default:
+ /* quadrature: direction is set in the ISR */
+ break;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int gpio_counter_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ enum gpio_counter_signal_id signal_id = synapse->signal->id;
+ enum counter_function function;
+ enum counter_count_direction direction;
+ unsigned long flags;
+
+ if (signal_id == GPIO_COUNTER_SIGNAL_INDEX1 ||
+ signal_id == GPIO_COUNTER_SIGNAL_INDEX2) {
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ function = cp->function;
+ direction = cp->direction;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+
+ switch (function) {
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ if (signal_id == GPIO_COUNTER_SIGNAL_A)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+
+ case COUNTER_FUNCTION_QUADRATURE_X2_B:
+ if (signal_id == GPIO_COUNTER_SIGNAL_B)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+
+ case COUNTER_FUNCTION_QUADRATURE_X1_A:
+ if (signal_id == GPIO_COUNTER_SIGNAL_A) {
+ if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
+ }
+ return 0;
+
+ case COUNTER_FUNCTION_QUADRATURE_X1_B:
+ if (signal_id == GPIO_COUNTER_SIGNAL_B) {
+ if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
+ }
+ return 0;
+
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ if (signal_id == GPIO_COUNTER_SIGNAL_A)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+
+ case COUNTER_FUNCTION_INCREASE:
+ case COUNTER_FUNCTION_DECREASE:
+ if ((count->id == GPIO_COUNTER_COUNT_1 &&
+ signal_id == GPIO_COUNTER_SIGNAL_A) ||
+ (count->id == GPIO_COUNTER_COUNT_2 &&
+ signal_id == GPIO_COUNTER_SIGNAL_B))
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int gpio_counter_signal_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ enum counter_signal_level *level)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_desc *gpio;
+ int ret;
+
+ switch (signal->id) {
+ case GPIO_COUNTER_SIGNAL_A:
+ gpio = priv->gpio_a;
+ break;
+ case GPIO_COUNTER_SIGNAL_B:
+ gpio = priv->gpio_b;
+ break;
+ case GPIO_COUNTER_SIGNAL_INDEX1:
+ gpio = priv->count_priv[GPIO_COUNTER_COUNT_1].idx_gpio;
+ break;
+ case GPIO_COUNTER_SIGNAL_INDEX2:
+ gpio = priv->count_priv[GPIO_COUNTER_COUNT_2].idx_gpio;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = gpiod_get_value(gpio);
+ if (ret < 0)
+ return ret;
+
+ *level = ret ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
+ return 0;
+}
+
+static int gpio_counter_watch_validate(struct counter_device *counter,
+ const struct counter_watch *watch)
+{
+ if (watch->channel >= GPIO_COUNTER_NUM_COUNTS_MAX)
+ return -EINVAL;
+
+ switch (watch->event) {
+ case COUNTER_EVENT_CHANGE_OF_STATE:
+ case COUNTER_EVENT_INDEX:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct counter_ops gpio_counter_ops = {
+ .count_read = gpio_counter_count_read,
+ .count_write = gpio_counter_count_write,
+ .function_read = gpio_counter_function_read,
+ .function_write = gpio_counter_function_write,
+ .action_read = gpio_counter_action_read,
+ .signal_read = gpio_counter_signal_read,
+ .watch_validate = gpio_counter_watch_validate,
+};
+
+static int gpio_counter_ceiling_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *val = cp->ceiling;
+
+ return 0;
+}
+
+static int gpio_counter_ceiling_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ cp->ceiling = val;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int gpio_counter_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *enable = cp->enabled;
+
+ return 0;
+}
+
+/* enable_lock held */
+static void gpio_counter_enable_b(struct gpio_counter_priv *priv)
+{
+ priv->b_irq_users++;
+ if (priv->b_irq_users == 1)
+ enable_irq(priv->irq_b);
+}
+
+/* enable_lock held */
+static void gpio_counter_disable_b(struct gpio_counter_priv *priv)
+{
+ priv->b_irq_users--;
+ if (priv->b_irq_users == 0)
+ disable_irq(priv->irq_b);
+}
+
+static void gpio_counter_enable_irqs(struct gpio_counter_priv *priv,
+ enum gpio_counter_count_id count_id)
+{
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count_id];
+
+ switch (count_id) {
+ case GPIO_COUNTER_COUNT_1:
+ enable_irq(priv->irq_a);
+ gpio_counter_enable_b(priv);
+ break;
+ case GPIO_COUNTER_COUNT_2:
+ gpio_counter_enable_b(priv);
+ break;
+ default:
+ break;
+ }
+
+ if (cp->has_index)
+ enable_irq(cp->idx_irq);
+}
+
+static void gpio_counter_disable_irqs(struct gpio_counter_priv *priv,
+ enum gpio_counter_count_id count_id)
+{
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count_id];
+
+ if (cp->has_index)
+ disable_irq(cp->idx_irq);
+
+ switch (count_id) {
+ case GPIO_COUNTER_COUNT_1:
+ gpio_counter_disable_b(priv);
+ disable_irq(priv->irq_a);
+ break;
+ case GPIO_COUNTER_COUNT_2:
+ gpio_counter_disable_b(priv);
+ break;
+ default:
+ break;
+ }
+}
+
+static int gpio_counter_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+
+ guard(mutex)(&priv->enable_lock);
+
+ if (cp->enabled == enable)
+ return 0;
+
+ /* disable_irq() may sleep, so it can not run under the spinlock. */
+ if (enable) {
+ spin_lock_irqsave(&priv->lock, flags);
+ /*
+ * prev_a/prev_b are only used by the Count 1 quadrature
+ * decoding; resampling them for Count 2 would corrupt the
+ * state of an already running Count 1.
+ */
+ if (count->id == GPIO_COUNTER_COUNT_1) {
+ priv->prev_a = !!gpiod_get_value(priv->gpio_a);
+ priv->prev_b = !!gpiod_get_value(priv->gpio_b);
+ /* B may have moved while we were disabled. */
+ if (cp->function == COUNTER_FUNCTION_PULSE_DIRECTION)
+ cp->direction = priv->prev_b
+ ? COUNTER_COUNT_DIRECTION_BACKWARD
+ : COUNTER_COUNT_DIRECTION_FORWARD;
+ }
+ cp->enabled = true;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ gpio_counter_enable_irqs(priv, count->id);
+ } else {
+ spin_lock_irqsave(&priv->lock, flags);
+ cp->enabled = false;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ gpio_counter_disable_irqs(priv, count->id);
+ }
+
+ return 0;
+}
+
+static int gpio_counter_direction_read(struct counter_device *counter,
+ struct counter_count *count,
+ u32 *direction)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *direction = cp->direction;
+
+ return 0;
+}
+
+static int gpio_counter_preset_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *val = cp->preset;
+
+ return 0;
+}
+
+static int gpio_counter_preset_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (val > cp->ceiling) {
+ ret = -EINVAL;
+ goto out;
+ }
+ cp->preset = val;
+out:
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ret;
+}
+
+static int gpio_counter_preset_enable_read(struct counter_device *counter,
+ struct counter_count *count,
+ u8 *val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+
+ *val = cp->preset_enabled;
+
+ return 0;
+}
+
+static int gpio_counter_preset_enable_write(struct counter_device *counter,
+ struct counter_count *count,
+ u8 val)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ cp->preset_enabled = val;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static struct counter_comp gpio_counter_count_ext[] = {
+ COUNTER_COMP_CEILING(gpio_counter_ceiling_read,
+ gpio_counter_ceiling_write),
+ COUNTER_COMP_ENABLE(gpio_counter_enable_read,
+ gpio_counter_enable_write),
+ COUNTER_COMP_DIRECTION(gpio_counter_direction_read),
+ COUNTER_COMP_PRESET(gpio_counter_preset_read,
+ gpio_counter_preset_write),
+ COUNTER_COMP_PRESET_ENABLE(gpio_counter_preset_enable_read,
+ gpio_counter_preset_enable_write),
+};
+
+static const enum counter_synapse_action gpio_counter_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+ COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+};
+
+static const enum counter_synapse_action gpio_counter_index_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+static int gpio_counter_get_index_gpios(struct device *dev,
+ struct gpio_counter_priv *priv)
+{
+ struct gpio_counter_count_priv *c1 =
+ &priv->count_priv[GPIO_COUNTER_COUNT_1];
+ struct gpio_counter_count_priv *c2 =
+ &priv->count_priv[GPIO_COUNTER_COUNT_2];
+
+ c1->idx_gpio = devm_gpiod_get_optional(dev, "index1", GPIOD_IN);
+ if (IS_ERR(c1->idx_gpio))
+ return dev_err_probe(dev, PTR_ERR(c1->idx_gpio),
+ "failed to get index1 GPIO\n");
+ c1->has_index = !!c1->idx_gpio;
+
+ c2->idx_gpio = devm_gpiod_get_optional(dev, "index2", GPIOD_IN);
+ if (IS_ERR(c2->idx_gpio))
+ return dev_err_probe(dev, PTR_ERR(c2->idx_gpio),
+ "failed to get index2 GPIO\n");
+ c2->has_index = !!c2->idx_gpio;
+
+ return 0;
+}
+
+static int gpio_counter_setup_signals(struct gpio_counter_priv *priv)
+{
+ priv->signals[GPIO_COUNTER_SIGNAL_A].id = GPIO_COUNTER_SIGNAL_A;
+ priv->signals[GPIO_COUNTER_SIGNAL_A].name = "Signal A";
+ priv->signals[GPIO_COUNTER_SIGNAL_B].id = GPIO_COUNTER_SIGNAL_B;
+ priv->signals[GPIO_COUNTER_SIGNAL_B].name = "Signal B";
+
+ if (priv->count_priv[GPIO_COUNTER_COUNT_1].has_index) {
+ priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].id =
+ GPIO_COUNTER_SIGNAL_INDEX1;
+ priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].name = "Index 1";
+ }
+ if (priv->count_priv[GPIO_COUNTER_COUNT_2].has_index) {
+ priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].id =
+ GPIO_COUNTER_SIGNAL_INDEX2;
+ priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].name = "Index 2";
+ }
+
+ return 2 + priv->count_priv[GPIO_COUNTER_COUNT_1].has_index +
+ priv->count_priv[GPIO_COUNTER_COUNT_2].has_index;
+}
+
+static int gpio_counter_setup_synapses(struct gpio_counter_priv *priv)
+{
+ int n_c1 = 2, n_c2 = 1;
+
+ priv->synapses_count1[0].actions_list = gpio_counter_synapse_actions;
+ priv->synapses_count1[0].num_actions =
+ ARRAY_SIZE(gpio_counter_synapse_actions);
+ priv->synapses_count1[0].signal = &priv->signals[GPIO_COUNTER_SIGNAL_A];
+
+ priv->synapses_count1[1].actions_list = gpio_counter_synapse_actions;
+ priv->synapses_count1[1].num_actions =
+ ARRAY_SIZE(gpio_counter_synapse_actions);
+ priv->synapses_count1[1].signal = &priv->signals[GPIO_COUNTER_SIGNAL_B];
+
+ if (priv->count_priv[GPIO_COUNTER_COUNT_1].has_index) {
+ priv->synapses_count1[2].actions_list =
+ gpio_counter_index_synapse_actions;
+ priv->synapses_count1[2].num_actions =
+ ARRAY_SIZE(gpio_counter_index_synapse_actions);
+ priv->synapses_count1[2].signal =
+ &priv->signals[GPIO_COUNTER_SIGNAL_INDEX1];
+ n_c1 = 3;
+ }
+
+ priv->synapses_count2[0].actions_list = gpio_counter_synapse_actions;
+ priv->synapses_count2[0].num_actions =
+ ARRAY_SIZE(gpio_counter_synapse_actions);
+ priv->synapses_count2[0].signal = &priv->signals[GPIO_COUNTER_SIGNAL_B];
+
+ if (priv->count_priv[GPIO_COUNTER_COUNT_2].has_index) {
+ priv->synapses_count2[1].actions_list =
+ gpio_counter_index_synapse_actions;
+ priv->synapses_count2[1].num_actions =
+ ARRAY_SIZE(gpio_counter_index_synapse_actions);
+ priv->synapses_count2[1].signal =
+ &priv->signals[GPIO_COUNTER_SIGNAL_INDEX2];
+ n_c2 = 2;
+ }
+
+ priv->counts[GPIO_COUNTER_COUNT_1].id = GPIO_COUNTER_COUNT_1;
+ priv->counts[GPIO_COUNTER_COUNT_1].name = "Count 1";
+ priv->counts[GPIO_COUNTER_COUNT_1].functions_list =
+ gpio_counter_count1_functions;
+ priv->counts[GPIO_COUNTER_COUNT_1].num_functions =
+ ARRAY_SIZE(gpio_counter_count1_functions);
+ priv->counts[GPIO_COUNTER_COUNT_1].synapses = priv->synapses_count1;
+ priv->counts[GPIO_COUNTER_COUNT_1].num_synapses = n_c1;
+ priv->counts[GPIO_COUNTER_COUNT_1].ext = gpio_counter_count_ext;
+ priv->counts[GPIO_COUNTER_COUNT_1].num_ext =
+ ARRAY_SIZE(gpio_counter_count_ext);
+
+ priv->counts[GPIO_COUNTER_COUNT_2].id = GPIO_COUNTER_COUNT_2;
+ priv->counts[GPIO_COUNTER_COUNT_2].name = "Count 2";
+ priv->counts[GPIO_COUNTER_COUNT_2].functions_list =
+ gpio_counter_count2_functions;
+ priv->counts[GPIO_COUNTER_COUNT_2].num_functions =
+ ARRAY_SIZE(gpio_counter_count2_functions);
+ priv->counts[GPIO_COUNTER_COUNT_2].synapses = priv->synapses_count2;
+ priv->counts[GPIO_COUNTER_COUNT_2].num_synapses = n_c2;
+ priv->counts[GPIO_COUNTER_COUNT_2].ext = gpio_counter_count_ext;
+ priv->counts[GPIO_COUNTER_COUNT_2].num_ext =
+ ARRAY_SIZE(gpio_counter_count_ext);
+
+ return 0;
+}
+
+static int gpio_counter_request_irqs(struct device *dev,
+ struct counter_device *counter)
+{
+ struct gpio_counter_priv *priv = counter_priv(counter);
+ irq_handler_t idx_handlers[GPIO_COUNTER_NUM_COUNTS_MAX] = {
+ gpio_counter_index1_isr,
+ gpio_counter_index2_isr,
+ };
+ const char *idx_names[GPIO_COUNTER_NUM_COUNTS_MAX] = {
+ "gpio-counter-index1",
+ "gpio-counter-index2",
+ };
+ int ret, i;
+
+ ret = devm_request_irq(dev, priv->irq_a, gpio_counter_a_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_NO_AUTOEN,
+ "gpio-counter-a", counter);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request IRQ for signal-a\n");
+
+ ret = devm_request_irq(dev, priv->irq_b, gpio_counter_b_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_NO_AUTOEN,
+ "gpio-counter-b", counter);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request IRQ for signal-b\n");
+
+ for (i = 0; i < GPIO_COUNTER_NUM_COUNTS_MAX; i++) {
+ struct gpio_counter_count_priv *cp = &priv->count_priv[i];
+
+ if (!cp->has_index)
+ continue;
+
+ ret = devm_request_irq(dev, cp->idx_irq, idx_handlers[i],
+ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN,
+ idx_names[i], counter);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request IRQ for index %d\n",
+ i);
+ }
+
+ return 0;
+}
+
+static int gpio_counter_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct counter_device *counter;
+ struct gpio_counter_priv *priv;
+ int num_signals;
+ int i, ret;
+
+ counter = devm_counter_alloc(dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+
+ priv = counter_priv(counter);
+ spin_lock_init(&priv->lock);
+ ret = devm_mutex_init(dev, &priv->enable_lock);
+ if (ret)
+ return ret;
+
+ priv->gpio_a = devm_gpiod_get(dev, "signal-a", GPIOD_IN);
+ if (IS_ERR(priv->gpio_a))
+ return dev_err_probe(dev, PTR_ERR(priv->gpio_a),
+ "failed to get signal-a GPIO\n");
+
+ priv->gpio_b = devm_gpiod_get(dev, "signal-b", GPIOD_IN);
+ if (IS_ERR(priv->gpio_b))
+ return dev_err_probe(dev, PTR_ERR(priv->gpio_b),
+ "failed to get signal-b GPIO\n");
+
+ ret = gpio_counter_get_index_gpios(dev, priv);
+ if (ret)
+ return ret;
+
+ if (gpiod_cansleep(priv->gpio_a) || gpiod_cansleep(priv->gpio_b))
+ return dev_err_probe(dev, -EINVAL,
+ "signal GPIO may sleep, not usable in hardirq\n");
+ for (i = 0; i < GPIO_COUNTER_NUM_COUNTS_MAX; i++) {
+ if (priv->count_priv[i].has_index &&
+ gpiod_cansleep(priv->count_priv[i].idx_gpio))
+ return dev_err_probe(dev, -EINVAL,
+ "index GPIO %d may sleep, not usable in hardirq\n",
+ i);
+ }
+
+ priv->irq_a = gpiod_to_irq(priv->gpio_a);
+ if (priv->irq_a < 0)
+ return dev_err_probe(dev, priv->irq_a,
+ "failed to get IRQ for signal-a\n");
+
+ priv->irq_b = gpiod_to_irq(priv->gpio_b);
+ if (priv->irq_b < 0)
+ return dev_err_probe(dev, priv->irq_b,
+ "failed to get IRQ for signal-b\n");
+
+ for (i = 0; i < GPIO_COUNTER_NUM_COUNTS_MAX; i++) {
+ struct gpio_counter_count_priv *cp = &priv->count_priv[i];
+
+ if (!cp->has_index)
+ continue;
+
+ cp->idx_irq = gpiod_to_irq(cp->idx_gpio);
+ if (cp->idx_irq < 0)
+ return dev_err_probe(dev, cp->idx_irq,
+ "failed to get IRQ for index %d\n",
+ i);
+ }
+
+ priv->prev_a = !!gpiod_get_value(priv->gpio_a);
+ priv->prev_b = !!gpiod_get_value(priv->gpio_b);
+
+ priv->count_priv[GPIO_COUNTER_COUNT_1].function =
+ COUNTER_FUNCTION_QUADRATURE_X4;
+ priv->count_priv[GPIO_COUNTER_COUNT_1].direction =
+ COUNTER_COUNT_DIRECTION_FORWARD;
+ priv->count_priv[GPIO_COUNTER_COUNT_1].ceiling = U64_MAX;
+
+ priv->count_priv[GPIO_COUNTER_COUNT_2].function =
+ COUNTER_FUNCTION_INCREASE;
+ priv->count_priv[GPIO_COUNTER_COUNT_2].direction =
+ COUNTER_COUNT_DIRECTION_FORWARD;
+ priv->count_priv[GPIO_COUNTER_COUNT_2].ceiling = U64_MAX;
+
+ num_signals = gpio_counter_setup_signals(priv);
+ gpio_counter_setup_synapses(priv);
+
+ counter->name = dev_name(dev);
+ counter->parent = dev;
+ counter->ops = &gpio_counter_ops;
+ counter->signals = priv->signals;
+ counter->num_signals = num_signals;
+ counter->counts = priv->counts;
+ counter->num_counts = ARRAY_SIZE(priv->counts);
+
+ ret = gpio_counter_request_irqs(dev, counter);
+ if (ret)
+ return ret;
+
+ ret = devm_counter_add(dev, counter);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to add counter\n");
+
+ dev_info(dev,
+ "GPIO counter registered (signals: A, B%s%s)\n",
+ priv->count_priv[GPIO_COUNTER_COUNT_1].has_index ? ", Index 1" : "",
+ priv->count_priv[GPIO_COUNTER_COUNT_2].has_index ? ", Index 2" : "");
+
+ return 0;
+}
+
+static const struct of_device_id gpio_counter_of_match[] = {
+ { .compatible = "gpio-counter" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gpio_counter_of_match);
+
+static struct platform_driver gpio_counter_driver = {
+ .probe = gpio_counter_probe,
+ .driver = {
+ .name = "gpio-counter",
+ .of_match_table = gpio_counter_of_match,
+ },
+};
+module_platform_driver(gpio_counter_driver);
+
+MODULE_ALIAS("platform:gpio-counter");
+MODULE_AUTHOR("Wadim Mueller <wafgo01@gmail.com>");
+MODULE_DESCRIPTION("GPIO-based counter driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("COUNTER");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v7 3/3] MAINTAINERS: add entry for GPIO counter driver
2026-07-14 8:17 [PATCH v7 0/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 2/3] counter: add GPIO-based counter driver Wadim Mueller
@ 2026-07-14 8:17 ` Wadim Mueller
2 siblings, 0 replies; 6+ messages in thread
From: Wadim Mueller @ 2026-07-14 8:17 UTC (permalink / raw)
To: William Breathitt Gray
Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
devicetree, linux-kernel
Cover the gpio-counter driver and its device-tree binding.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 06a8c74577aa..14f1a4e9fcf5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10984,6 +10984,13 @@ S: Supported
F: Documentation/admin-guide/gpio/gpio-aggregator.rst
F: drivers/gpio/gpio-aggregator.c
+GPIO COUNTER DRIVER
+M: Wadim Mueller <wafgo01@gmail.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/counter/gpio-counter.yaml
+F: drivers/counter/gpio-counter.c
+
GPIO IR Transmitter
M: Sean Young <sean@mess.org>
L: linux-media@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v7 2/3] counter: add GPIO-based counter driver
2026-07-14 8:17 ` [PATCH v7 2/3] counter: add GPIO-based counter driver Wadim Mueller
@ 2026-07-14 8:27 ` sashiko-bot
2026-07-14 12:20 ` Uwe Kleine-König
1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-07-14 8:27 UTC (permalink / raw)
To: Wadim Mueller; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Sparse initialization of the `signals` array causes a NULL pointer dereference and missed registrations if `index1` is omitted but `index2` is present.
- [High] Counter values incorrectly saturate at 0 and ceiling instead of wrapping around, permanently breaking position tracking for quadrature encoders.
- [Medium] Torn reads of 64-bit state variables (`value`, `preset`, `ceiling`) on 32-bit architectures due to lockless access.
- [Medium] Missing `cp->enabled` check in index pulse ISR allows disabled counters to be modified and generate events.
--
--- Patch [2]: [PATCH v7 2/3] counter: add GPIO-based counter driver ---
commit 71f827659851f1023e187d6b98a7113441670997
Author: Wadim Mueller <wafgo01@gmail.com>
counter: add GPIO-based counter driver
Add a platform driver that turns plain GPIOs into a counter device
supporting quadrature, pulse-direction, and increase/decrease modes.
> diff --git a/drivers/counter/gpio-counter.c b/drivers/counter/gpio-counter.c
> new file mode 100644
> index 0000000000000..ceb79375d85b3
> --- /dev/null
> +++ b/drivers/counter/gpio-counter.c
[ ... ]
> +/* priv->lock held. */
> +static void gpio_counter_value_step(struct gpio_counter_count_priv *cp)
> +{
> + if (cp->direction == COUNTER_COUNT_DIRECTION_FORWARD) {
> + if (cp->value < cp->ceiling)
> + cp->value++;
> + } else {
> + if (cp->value > 0)
> + cp->value--;
> + }
> +}
[Severity: High]
Will this clamp the counter instead of letting it wrap around?
If the hardware oscillates or moves backward past 0, the counter will stick
at 0. Moving forward again will increment from 0, which could destroy the
absolute position mapping for quadrature encoders. Should this wrap around
modulo the ceiling instead?
[ ... ]
> +static void gpio_counter_index_pulse(struct counter_device *counter,
> + enum gpio_counter_count_id count_id)
> +{
> + struct gpio_counter_priv *priv = counter_priv(counter);
> + struct gpio_counter_count_priv *cp = &priv->count_priv[count_id];
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + if (cp->preset_enabled)
> + cp->value = (cp->preset > cp->ceiling) ? cp->ceiling : cp->preset;
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + counter_push_event(counter, COUNTER_EVENT_INDEX, count_id);
> +}
[Severity: Medium]
Is a check for cp->enabled needed here?
When disabling a counter in gpio_counter_enable_write(), cp->enabled is set
to false and the spinlock is released before disable_irq() is called. If an
index edge fires in this narrow window, it looks like this could update
cp->value and push a COUNTER_EVENT_INDEX event despite the driver state
being logically disabled.
[ ... ]
> +static int gpio_counter_count_read(struct counter_device *counter,
> + struct counter_count *count, u64 *val)
> +{
> + struct gpio_counter_priv *priv = counter_priv(counter);
> + struct gpio_counter_count_priv *cp = &priv->count_priv[count->id];
> +
> + *val = cp->value;
> +
> + return 0;
> +}
[Severity: Medium]
Can this cause torn reads on 32-bit architectures?
Since cp->value is a 64-bit variable, reading it locklessly on a 32-bit
architecture requires two 32-bit loads. If the hardware ISR interrupts
these loads to increment the value across a 32-bit boundary, the reader
might fuse the old and new halves together resulting in a large, spurious
jump in the reported value.
This also seems to apply to the lockless reads of cp->ceiling and
cp->preset later in this file.
[ ... ]
> +static int gpio_counter_setup_signals(struct gpio_counter_priv *priv)
> +{
> + priv->signals[GPIO_COUNTER_SIGNAL_A].id = GPIO_COUNTER_SIGNAL_A;
> + priv->signals[GPIO_COUNTER_SIGNAL_A].name = "Signal A";
> + priv->signals[GPIO_COUNTER_SIGNAL_B].id = GPIO_COUNTER_SIGNAL_B;
> + priv->signals[GPIO_COUNTER_SIGNAL_B].name = "Signal B";
> +
> + if (priv->count_priv[GPIO_COUNTER_COUNT_1].has_index) {
> + priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].id =
> + GPIO_COUNTER_SIGNAL_INDEX1;
> + priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].name = "Index 1";
> + }
> + if (priv->count_priv[GPIO_COUNTER_COUNT_2].has_index) {
> + priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].id =
> + GPIO_COUNTER_SIGNAL_INDEX2;
> + priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].name = "Index 2";
> + }
> +
> + return 2 + priv->count_priv[GPIO_COUNTER_COUNT_1].has_index +
> + priv->count_priv[GPIO_COUNTER_COUNT_2].has_index;
> +}
[Severity: High]
Does this setup handle the case where a device tree omits index1 but
includes index2?
If index1 is missing but index2 is present, the index 2 signal is mapped to
priv->signals[3] (GPIO_COUNTER_SIGNAL_INDEX2), leaving priv->signals[2]
zero-initialized.
Since this function returns a compressed count of 3, the counter framework
will iterate from index 0 to 2, causing a NULL pointer dereference on the
empty name at signals[2] and skipping the valid signal at signals[3].
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260714081709.17846-1-wafgo01@gmail.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v7 2/3] counter: add GPIO-based counter driver
2026-07-14 8:17 ` [PATCH v7 2/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:27 ` sashiko-bot
@ 2026-07-14 12:20 ` Uwe Kleine-König
1 sibling, 0 replies; 6+ messages in thread
From: Uwe Kleine-König @ 2026-07-14 12:20 UTC (permalink / raw)
To: Wadim Mueller
Cc: William Breathitt Gray, Krzysztof Kozlowski, Rob Herring,
Conor Dooley, linux-iio, devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 499 bytes --]
Hello,
On Tue, Jul 14, 2026 at 10:17:08AM +0200, Wadim Mueller wrote:
> +#include <linux/mod_devicetable.h>
> [..]
> +#include <linux/platform_device.h>
Please don't use <linux/mod_devicetable.h>. of_device_id is also
provided by <linux/platform_device.h>, so you can just drop the former
include.
> +static const struct of_device_id gpio_counter_of_match[] = {
> + { .compatible = "gpio-counter" },
> + {}
{ } please.
> +};
> +MODULE_DEVICE_TABLE(of, gpio_counter_of_match);
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-14 12:20 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 8:17 [PATCH v7 0/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding Wadim Mueller
2026-07-14 8:17 ` [PATCH v7 2/3] counter: add GPIO-based counter driver Wadim Mueller
2026-07-14 8:27 ` sashiko-bot
2026-07-14 12:20 ` Uwe Kleine-König
2026-07-14 8:17 ` [PATCH v7 3/3] MAINTAINERS: add entry for GPIO " Wadim Mueller
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox