From nobody Sat Jul 25 20:00:06 2026 Received: from mail-ed1-f41.google.com (mail-ed1-f41.google.com [209.85.208.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7BFF22DA756 for ; Tue, 14 Jul 2026 08:17:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017042; cv=none; b=EkdxtwQT2lQJ9AU10UjZV81V8J0nyZEM71uBDCqoF99188fRS041jgUJ/ON96Gp8Se6g3oDb4MuszbeLgQiAlfKRAj+bj1uGi/5/Q7LG7zG7J96NgR5dfvodjQgWP/H/QlViKFPQchY8g0WMzzbUxLCiS+QBWrjstuL7YvRvuWg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017042; c=relaxed/simple; bh=xEEGwX2WirNbeBAzB5AL0aELp4pmQKWtE6Dq3wSUlo0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Yb2BAlJQcfQWCQZ5gNcdV9PGXJ0ukP54bMWhYE4x4LuddNlrc9gs1OA+a+O2UDJkT3fKwZIakk5o8AOvfqQkGXlIGrz/IOlSyG3j3Wxeh4zCbtuWWatgt/VbWQ9BDOZY6sKrHBEoQ0NsD92ODLfJ1sEeLq2q/ogo9UtGqfmhaFI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=N4SIuYA1; arc=none smtp.client-ip=209.85.208.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="N4SIuYA1" Received: by mail-ed1-f41.google.com with SMTP id 4fb4d7f45d1cf-69531108f25so6584055a12.2 for ; Tue, 14 Jul 2026 01:17:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784017031; x=1784621831; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=CoQnMOKpiJRr1uZ4EPacW9ACutARLVYGOVPpLwGEQDc=; b=N4SIuYA1YpE+X4zPakS58ufaufjsMyKuKHkF0PzSROVKnTh0UWN3POFbKuusvr38Zx xnXfb1JzX+PHLjWy/dT8/VxeEQkwrPqubXlkr5EXFVbi4qF3VLMMSi5Fw0u5qIjpg/tD 7ywUjcxKcLJrXaS786DpipEDrdYWlCuPAbVD6+TRR2XV5u8QZCrue7m5w9KlBbyz8L00 Uremnl/3UGhEsK2a6QwdxQucu/tVUASNfoAsgwykO7FDuqtLpvYLr2eXIaRX7rmZqKoy TOEkVcZz0E4xaturFGmYW493ntFpOYl5sURYUQMTLXC0PSzk8Zye+HaKELHQEkU1V/Q/ yfZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784017031; x=1784621831; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=CoQnMOKpiJRr1uZ4EPacW9ACutARLVYGOVPpLwGEQDc=; b=cNpieWiVbx6O9s7/WTVCu9laUPXFzH+RPA2Jb064HNCKJFl/jMHauwhW4s//OC30+g rvaSzzyTseUogMJ1kbpL4PxOfQbzshlP2Ebg9Wf7YxNtM8ZE/F92d67HEnpnHb3FwYzV cihW6FGegGnFmY/HunW5PVMGZVvjVN85MCUxX0RbO4iqxI/uthJO3cZTGBN89AJPAcWz lddvGizL4rxE2oET4IluYH1kezWDmt/I5a1RaRhSS1NWw4LdO5oc1Ev9BKmOJ+QiU5QQ vId8BL3OjTvaXn0iIa8/ym+kzbFecnaY+4zQK2XtpD2tx9HfRLM1KUySIMjLRLC8uCMO N0Eg== X-Forwarded-Encrypted: i=1; AHgh+RrgJzZqEqLhbeiTIWBbZdZsJdYsKxudkhqbzQcF+Y8S89CR6sUonX+GllXpv0q0i1RECOKLvyCl71TIVN0=@vger.kernel.org X-Gm-Message-State: AOJu0Yz8xh3YpQnCjYGrQcxS2T8MUpDDZn6EJhN4TCJvPSaSv+tywCgb A5MWqNf7kRCd5OfbvrVImchJ+ZhQi+SOO1IuTexLgpcQscrQSrQq7cyt X-Gm-Gg: AfdE7cmj8+3rkBU/rQgB3yRka+p3fYW9hN3P3vQeiGRdhqvfdNs4MMfiwsRYkgzxUPa /THzc6tqH/om3Vy0Iy7m+63snw+Ca5A6iGKfzznhsC7r/6SOkckDcoUyySNIZofGP3bm8NU9Ydl JLdmQuiksvVHLJnO7l+yuqt7BRWqZ2/l+JeX9MmOgaeDrEanH0CPr46UKkyXLC9P4zvndCKTFHF YD2NSTcKwdm87cTCnMzSI8lki/ZI3NaDurmcYbvfvpguI1+FcfDh0/wa7CflqCVN3XKiFprpCyN sm6kY/3niO3vPjRaT75y7CYOwASOOylCaVH16RFlUirkSOQWZf4LdZsdjdWXUrlazhlmAce8onF DupESXRUa9fW4vP6s1M2lImW0b30fTbwfR+tACiuCb53zjOsJIuiGSTETWJeA+k9Gtv8TTw1gBv SuQcFIKudvyWdiPn9Lww89MqyhWZBbNjDCNq8= X-Received: by 2002:a17:906:7304:b0:c16:12b6:243c with SMTP id a640c23a62f3a-c161e973a85mr554612966b.14.1784017030626; Tue, 14 Jul 2026 01:17:10 -0700 (PDT) Received: from DE-PF5B95TD.embedded.cmblu.dev ([87.129.199.250]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c15e353d2f8sm723085166b.34.2026.07.14.01.17.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 01:17:10 -0700 (PDT) From: Wadim Mueller To: William Breathitt Gray Cc: Krzysztof Kozlowski , Rob Herring , Conor Dooley , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v7 1/3] dt-bindings: counter: add gpio-counter binding Date: Tue, 14 Jul 2026 10:17:07 +0200 Message-ID: <20260714081709.17846-2-wafgo01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260714081709.17846-1-wafgo01@gmail.com> References: <20260714081709.17846-1-wafgo01@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 + +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 + + counter { + compatible =3D "gpio-counter"; + signal-a-gpios =3D <&gpio0 10 GPIO_ACTIVE_LOW>; + signal-b-gpios =3D <&gpio0 11 GPIO_ACTIVE_LOW>; + index1-gpios =3D <&gpio0 12 GPIO_ACTIVE_LOW>; + index2-gpios =3D <&gpio0 13 GPIO_ACTIVE_LOW>; + }; + +... --=20 2.43.0 From nobody Sat Jul 25 20:00:06 2026 Received: from mail-ej1-f48.google.com (mail-ej1-f48.google.com [209.85.218.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A1A83F8EC9 for ; Tue, 14 Jul 2026 08:17:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017044; cv=none; b=gqcA3/anjG1Iwu8WwYjE9n5JYSCxqCpsG1zIFJQkCwzhg6UAahNXqFnHk8RSt0uYSYe39hIGgsUMG3f+CZyoD3/VMEcp5E8R+yC3uvAx/89GLQI1R0TiGbY915TtQUIse1DMySzLkZSa2J2ecbhEI/j3kan8eUgiSqum6Irhv20= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017044; c=relaxed/simple; bh=hYugm5Wu6quh8sHye69F/Hy/B6PZ1z8+rfnIctt856A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=twSI58BM2kBMlZoE+yRl6TAJ6mS+aXOzkj+GfpwdNc3m24R2VuwozG3n5yQ3Kc6tnnhJP6AacZ0TN9p0VFaPpYPsgc49ZYnWGdDAWVcK8kw5B91lfsZDkNZu4t3tjl2+4T3kRdH8syw7GuuYgMEPZPdhun4XtWdcdvvtScEld3k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=QVabhvt3; arc=none smtp.client-ip=209.85.218.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="QVabhvt3" Received: by mail-ej1-f48.google.com with SMTP id a640c23a62f3a-c15d47266baso92102066b.3 for ; Tue, 14 Jul 2026 01:17:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784017032; x=1784621832; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=uKOXR+Od47DYi5j33XaznZhj36R34+p88ULkg2O2T08=; b=QVabhvt3fmQ3/BO1OrxGWhNmOC4kuvCyf+uWmC6O3hv0heI4zcRg3t9nPF61M2iiAY +210aDZmWwvfbQ0T30on5Ke4jKmKn1ds7SWuaUroRkxSsW6t3b87vWqD4z8IDG/vn7/g /To+fE0xff5nVgJ6HKxjVLmSa9ifLOjWkiISK1oZp656c8T9yoNULd1MnB3xAvFu+zEC osU/FjQyVNeXbe8sVDqwOov6/liMfeY8C9KkVLua7xJupt8/K0l5LcuWYTzaqbsjpJaD zc9V0hd1M17ogl7+KrjJAvwZMY3vCqqljKNA8P3GXGVK9bMY7f1lPXOOGwtEhjGEA6ZT 0Syw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784017032; x=1784621832; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=uKOXR+Od47DYi5j33XaznZhj36R34+p88ULkg2O2T08=; b=i9W7l7KbvIqydlP36m3MBryV7AXQsqhDGKefpyUvKxgHCGEE9M//hL0xfbX7cnfjqi 6aK/wHDRBVanTtqoRHFk3EAJYS4+XSXrLex4VZsE32YOVSPKGqo2PFkyJ2IxBREGBi1T 30TEYuGfGRmorfG6Xm/tfNiWAILdy0mzoO8ehw9aK1lNs41D2Rr7dsr63LCgA8ZJ0G94 3A1lUDmR4jeA127Ef/kcpKKYviljZ0EKlDo8NNc3wAZx0p0zZ2Dg0dOQAyupyBdH/Iyj YeZaWU0HCLgt1dfsJo25r+IJ72LeRpivCq9j5xfovjX8sVe7r/y4tA03D6wImb20Xo6k Qs0g== X-Forwarded-Encrypted: i=1; AHgh+RqcWymDIjg/wfFKzLraJPPK2sag957NlhnK+vbAEeRaITl6LnqLBlHQzDbWVVdR0QXYYabLjhd+SuID5UY=@vger.kernel.org X-Gm-Message-State: AOJu0Yyl27qhrQ8dz8XgCUUxCG5Kpwv4SKjhqwb7kEIWdKUO9X26Q7Ul ffKhDqqlQLFtOmNe0XQOZRkjRhW3f+W6PKrJ8OWCy9yJr5h7/i1MDul4 X-Gm-Gg: AfdE7cnZGMonPZoxfboCvLP7bsB4x1Z05Sv+dlaAAvLlb+oAX8qXq1X3m+yadXPEZ9X PYdtC1wwMj15zI3j1ZRMgDhKNTm7IqS1KAixEsHO0cVc8w372RfTj5ovTsckKWXX1UZAq4z1g2p QXW5d0FErjetIKkDBlwFym1MhZe1gQhpDsVxiTK3cGPGCBXjjRlcGs242JC8j3oPkhZ3FzLpSvl YGmEj0kkxSpTLjGY7KMU8cz4q4kZHMvsMjXhlvVAS2cDP1w8+2CpaYzAD6fXRVaDxCFnWSYATKU 0MgwK4nbz4DZg6BVAtxoH4UrFruMpi8HPtcypwlRopzumSfhB+Wxj3jctR+EN2BtsaIeYkxg7p4 sU7oWQbuh1uEYsjfvAhum+yxzdCoTxqhzznMhEOjuV/TbMrhAVUID58B897xA7eblUeHoPeV+DO r9VdnFCekisq1vkBZ1wbTu/tqR56ACFk4zTuSHW9MUNEkFrA== X-Received: by 2002:a17:907:7b99:b0:c16:5fc9:e832 with SMTP id a640c23a62f3a-c165fc9e94cmr135002466b.50.1784017031497; Tue, 14 Jul 2026 01:17:11 -0700 (PDT) Received: from DE-PF5B95TD.embedded.cmblu.dev ([87.129.199.250]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c15e353d2f8sm723085166b.34.2026.07.14.01.17.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 01:17:11 -0700 (PDT) From: Wadim Mueller To: William Breathitt Gray Cc: Krzysztof Kozlowski , Rob Herring , Conor Dooley , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v7 2/3] counter: add GPIO-based counter driver Date: Tue, 14 Jul 2026 10:17:08 +0200 Message-ID: <20260714081709.17846-3-wafgo01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260714081709.17846-1-wafgo01@gmail.com> References: <20260714081709.17846-1-wafgo01@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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. =20 +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) +=3D stm32-timer-cnt.o obj-$(CONFIG_STM32_LPTIMER_CNT) +=3D stm32-lptimer-cnt.o obj-$(CONFIG_TI_EQEP) +=3D ti-eqep.o obj-$(CONFIG_FTM_QUADDEC) +=3D ftm-quaddec.o +obj-$(CONFIG_GPIO_COUNTER) +=3D gpio-counter.o obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) +=3D microchip-tcb-capture.o obj-$(CONFIG_INTEL_QEP) +=3D intel-qep.o obj-$(CONFIG_TI_ECAP_CAPTURE) +=3D 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum gpio_counter_signal_id { + GPIO_COUNTER_SIGNAL_A =3D 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 =3D 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 <=3D> 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 =3D=3D 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 =3D=3D COUNTER_COUNT_DIRECTION_FORWARD) + gpio_counter_value_step(cp); + else if (!level && cp->direction =3D=3D 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 =3D dev_id; + struct gpio_counter_priv *priv =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D + &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 =3D !!gpiod_get_value(priv->gpio_a); + b =3D !!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 =3D GPIO_COUNTER_GET_DIRECTION(priv->prev_b, a); + if (cp->function =3D=3D 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 =3D a; + priv->prev_b =3D 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 =3D dev_id; + struct gpio_counter_priv *priv =3D counter_priv(counter); + struct gpio_counter_count_priv *c1 =3D + &priv->count_priv[GPIO_COUNTER_COUNT_1]; + struct gpio_counter_count_priv *c2 =3D + &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 =3D c1->enabled; + c2_enabled =3D c2->enabled; + if (!c1_enabled && !c2_enabled) { + spin_unlock_irqrestore(&priv->lock, flags); + return IRQ_HANDLED; + } + + a =3D !!gpiod_get_value(priv->gpio_a); + b =3D !!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 =3D + GPIO_COUNTER_GET_DIRECTION(priv->prev_b, a); + if (c1->function =3D=3D 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 =3D 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 =3D a; + priv->prev_b =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count_id]; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (cp->preset_enabled) + cp->value =3D (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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *val =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &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 =3D val; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static const enum counter_function gpio_counter_count1_functions[] =3D { + 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[] =3D { + 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *function =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + cp->function =3D function; + + switch (function) { + case COUNTER_FUNCTION_INCREASE: + cp->direction =3D COUNTER_COUNT_DIRECTION_FORWARD; + break; + case COUNTER_FUNCTION_DECREASE: + cp->direction =3D COUNTER_COUNT_DIRECTION_BACKWARD; + break; + case COUNTER_FUNCTION_PULSE_DIRECTION: + cp->direction =3D !!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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + enum gpio_counter_signal_id signal_id =3D synapse->signal->id; + enum counter_function function; + enum counter_count_direction direction; + unsigned long flags; + + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_INDEX1 || + signal_id =3D=3D GPIO_COUNTER_SIGNAL_INDEX2) { + *action =3D COUNTER_SYNAPSE_ACTION_RISING_EDGE; + return 0; + } + + spin_lock_irqsave(&priv->lock, flags); + function =3D cp->function; + direction =3D cp->direction; + spin_unlock_irqrestore(&priv->lock, flags); + + *action =3D COUNTER_SYNAPSE_ACTION_NONE; + + switch (function) { + case COUNTER_FUNCTION_QUADRATURE_X4: + *action =3D COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + + case COUNTER_FUNCTION_QUADRATURE_X2_A: + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_A) + *action =3D COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + + case COUNTER_FUNCTION_QUADRATURE_X2_B: + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_B) + *action =3D COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + + case COUNTER_FUNCTION_QUADRATURE_X1_A: + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_A) { + if (direction =3D=3D COUNTER_COUNT_DIRECTION_FORWARD) + *action =3D COUNTER_SYNAPSE_ACTION_RISING_EDGE; + else + *action =3D COUNTER_SYNAPSE_ACTION_FALLING_EDGE; + } + return 0; + + case COUNTER_FUNCTION_QUADRATURE_X1_B: + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_B) { + if (direction =3D=3D COUNTER_COUNT_DIRECTION_FORWARD) + *action =3D COUNTER_SYNAPSE_ACTION_RISING_EDGE; + else + *action =3D COUNTER_SYNAPSE_ACTION_FALLING_EDGE; + } + return 0; + + case COUNTER_FUNCTION_PULSE_DIRECTION: + if (signal_id =3D=3D GPIO_COUNTER_SIGNAL_A) + *action =3D COUNTER_SYNAPSE_ACTION_RISING_EDGE; + return 0; + + case COUNTER_FUNCTION_INCREASE: + case COUNTER_FUNCTION_DECREASE: + if ((count->id =3D=3D GPIO_COUNTER_COUNT_1 && + signal_id =3D=3D GPIO_COUNTER_SIGNAL_A) || + (count->id =3D=3D GPIO_COUNTER_COUNT_2 && + signal_id =3D=3D GPIO_COUNTER_SIGNAL_B)) + *action =3D 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 =3D counter_priv(counter); + struct gpio_desc *gpio; + int ret; + + switch (signal->id) { + case GPIO_COUNTER_SIGNAL_A: + gpio =3D priv->gpio_a; + break; + case GPIO_COUNTER_SIGNAL_B: + gpio =3D priv->gpio_b; + break; + case GPIO_COUNTER_SIGNAL_INDEX1: + gpio =3D priv->count_priv[GPIO_COUNTER_COUNT_1].idx_gpio; + break; + case GPIO_COUNTER_SIGNAL_INDEX2: + gpio =3D priv->count_priv[GPIO_COUNTER_COUNT_2].idx_gpio; + break; + default: + return -EINVAL; + } + + ret =3D gpiod_get_value(gpio); + if (ret < 0) + return ret; + + *level =3D 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 >=3D 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 =3D { + .count_read =3D gpio_counter_count_read, + .count_write =3D gpio_counter_count_write, + .function_read =3D gpio_counter_function_read, + .function_write =3D gpio_counter_function_write, + .action_read =3D gpio_counter_action_read, + .signal_read =3D gpio_counter_signal_read, + .watch_validate =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *val =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + cp->ceiling =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *enable =3D 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 =3D=3D 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 =3D=3D 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 =3D &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 =3D &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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + unsigned long flags; + + guard(mutex)(&priv->enable_lock); + + if (cp->enabled =3D=3D 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 =3D=3D GPIO_COUNTER_COUNT_1) { + priv->prev_a =3D !!gpiod_get_value(priv->gpio_a); + priv->prev_b =3D !!gpiod_get_value(priv->gpio_b); + /* B may have moved while we were disabled. */ + if (cp->function =3D=3D COUNTER_FUNCTION_PULSE_DIRECTION) + cp->direction =3D priv->prev_b + ? COUNTER_COUNT_DIRECTION_BACKWARD + : COUNTER_COUNT_DIRECTION_FORWARD; + } + cp->enabled =3D true; + spin_unlock_irqrestore(&priv->lock, flags); + gpio_counter_enable_irqs(priv, count->id); + } else { + spin_lock_irqsave(&priv->lock, flags); + cp->enabled =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *direction =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *val =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + unsigned long flags; + int ret =3D 0; + + spin_lock_irqsave(&priv->lock, flags); + if (val > cp->ceiling) { + ret =3D -EINVAL; + goto out; + } + cp->preset =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + + *val =3D 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 =3D counter_priv(counter); + struct gpio_counter_count_priv *cp =3D &priv->count_priv[count->id]; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + cp->preset_enabled =3D val; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static struct counter_comp gpio_counter_count_ext[] =3D { + 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[] = =3D { + 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_action= s[] =3D { + 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 =3D + &priv->count_priv[GPIO_COUNTER_COUNT_1]; + struct gpio_counter_count_priv *c2 =3D + &priv->count_priv[GPIO_COUNTER_COUNT_2]; + + c1->idx_gpio =3D 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 =3D !!c1->idx_gpio; + + c2->idx_gpio =3D 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 =3D !!c2->idx_gpio; + + return 0; +} + +static int gpio_counter_setup_signals(struct gpio_counter_priv *priv) +{ + priv->signals[GPIO_COUNTER_SIGNAL_A].id =3D GPIO_COUNTER_SIGNAL_A; + priv->signals[GPIO_COUNTER_SIGNAL_A].name =3D "Signal A"; + priv->signals[GPIO_COUNTER_SIGNAL_B].id =3D GPIO_COUNTER_SIGNAL_B; + priv->signals[GPIO_COUNTER_SIGNAL_B].name =3D "Signal B"; + + if (priv->count_priv[GPIO_COUNTER_COUNT_1].has_index) { + priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].id =3D + GPIO_COUNTER_SIGNAL_INDEX1; + priv->signals[GPIO_COUNTER_SIGNAL_INDEX1].name =3D "Index 1"; + } + if (priv->count_priv[GPIO_COUNTER_COUNT_2].has_index) { + priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].id =3D + GPIO_COUNTER_SIGNAL_INDEX2; + priv->signals[GPIO_COUNTER_SIGNAL_INDEX2].name =3D "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 =3D 2, n_c2 =3D 1; + + priv->synapses_count1[0].actions_list =3D gpio_counter_synapse_actions; + priv->synapses_count1[0].num_actions =3D + ARRAY_SIZE(gpio_counter_synapse_actions); + priv->synapses_count1[0].signal =3D &priv->signals[GPIO_COUNTER_SIGNAL_A]; + + priv->synapses_count1[1].actions_list =3D gpio_counter_synapse_actions; + priv->synapses_count1[1].num_actions =3D + ARRAY_SIZE(gpio_counter_synapse_actions); + priv->synapses_count1[1].signal =3D &priv->signals[GPIO_COUNTER_SIGNAL_B]; + + if (priv->count_priv[GPIO_COUNTER_COUNT_1].has_index) { + priv->synapses_count1[2].actions_list =3D + gpio_counter_index_synapse_actions; + priv->synapses_count1[2].num_actions =3D + ARRAY_SIZE(gpio_counter_index_synapse_actions); + priv->synapses_count1[2].signal =3D + &priv->signals[GPIO_COUNTER_SIGNAL_INDEX1]; + n_c1 =3D 3; + } + + priv->synapses_count2[0].actions_list =3D gpio_counter_synapse_actions; + priv->synapses_count2[0].num_actions =3D + ARRAY_SIZE(gpio_counter_synapse_actions); + priv->synapses_count2[0].signal =3D &priv->signals[GPIO_COUNTER_SIGNAL_B]; + + if (priv->count_priv[GPIO_COUNTER_COUNT_2].has_index) { + priv->synapses_count2[1].actions_list =3D + gpio_counter_index_synapse_actions; + priv->synapses_count2[1].num_actions =3D + ARRAY_SIZE(gpio_counter_index_synapse_actions); + priv->synapses_count2[1].signal =3D + &priv->signals[GPIO_COUNTER_SIGNAL_INDEX2]; + n_c2 =3D 2; + } + + priv->counts[GPIO_COUNTER_COUNT_1].id =3D GPIO_COUNTER_COUNT_1; + priv->counts[GPIO_COUNTER_COUNT_1].name =3D "Count 1"; + priv->counts[GPIO_COUNTER_COUNT_1].functions_list =3D + gpio_counter_count1_functions; + priv->counts[GPIO_COUNTER_COUNT_1].num_functions =3D + ARRAY_SIZE(gpio_counter_count1_functions); + priv->counts[GPIO_COUNTER_COUNT_1].synapses =3D priv->synapses_count1; + priv->counts[GPIO_COUNTER_COUNT_1].num_synapses =3D n_c1; + priv->counts[GPIO_COUNTER_COUNT_1].ext =3D gpio_counter_count_ext; + priv->counts[GPIO_COUNTER_COUNT_1].num_ext =3D + ARRAY_SIZE(gpio_counter_count_ext); + + priv->counts[GPIO_COUNTER_COUNT_2].id =3D GPIO_COUNTER_COUNT_2; + priv->counts[GPIO_COUNTER_COUNT_2].name =3D "Count 2"; + priv->counts[GPIO_COUNTER_COUNT_2].functions_list =3D + gpio_counter_count2_functions; + priv->counts[GPIO_COUNTER_COUNT_2].num_functions =3D + ARRAY_SIZE(gpio_counter_count2_functions); + priv->counts[GPIO_COUNTER_COUNT_2].synapses =3D priv->synapses_count2; + priv->counts[GPIO_COUNTER_COUNT_2].num_synapses =3D n_c2; + priv->counts[GPIO_COUNTER_COUNT_2].ext =3D gpio_counter_count_ext; + priv->counts[GPIO_COUNTER_COUNT_2].num_ext =3D + 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 =3D counter_priv(counter); + irq_handler_t idx_handlers[GPIO_COUNTER_NUM_COUNTS_MAX] =3D { + gpio_counter_index1_isr, + gpio_counter_index2_isr, + }; + const char *idx_names[GPIO_COUNTER_NUM_COUNTS_MAX] =3D { + "gpio-counter-index1", + "gpio-counter-index2", + }; + int ret, i; + + ret =3D 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 =3D 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 =3D 0; i < GPIO_COUNTER_NUM_COUNTS_MAX; i++) { + struct gpio_counter_count_priv *cp =3D &priv->count_priv[i]; + + if (!cp->has_index) + continue; + + ret =3D 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 =3D &pdev->dev; + struct counter_device *counter; + struct gpio_counter_priv *priv; + int num_signals; + int i, ret; + + counter =3D devm_counter_alloc(dev, sizeof(*priv)); + if (!counter) + return -ENOMEM; + + priv =3D counter_priv(counter); + spin_lock_init(&priv->lock); + ret =3D devm_mutex_init(dev, &priv->enable_lock); + if (ret) + return ret; + + priv->gpio_a =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 0; i < GPIO_COUNTER_NUM_COUNTS_MAX; i++) { + struct gpio_counter_count_priv *cp =3D &priv->count_priv[i]; + + if (!cp->has_index) + continue; + + cp->idx_irq =3D 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 =3D !!gpiod_get_value(priv->gpio_a); + priv->prev_b =3D !!gpiod_get_value(priv->gpio_b); + + priv->count_priv[GPIO_COUNTER_COUNT_1].function =3D + COUNTER_FUNCTION_QUADRATURE_X4; + priv->count_priv[GPIO_COUNTER_COUNT_1].direction =3D + COUNTER_COUNT_DIRECTION_FORWARD; + priv->count_priv[GPIO_COUNTER_COUNT_1].ceiling =3D U64_MAX; + + priv->count_priv[GPIO_COUNTER_COUNT_2].function =3D + COUNTER_FUNCTION_INCREASE; + priv->count_priv[GPIO_COUNTER_COUNT_2].direction =3D + COUNTER_COUNT_DIRECTION_FORWARD; + priv->count_priv[GPIO_COUNTER_COUNT_2].ceiling =3D U64_MAX; + + num_signals =3D gpio_counter_setup_signals(priv); + gpio_counter_setup_synapses(priv); + + counter->name =3D dev_name(dev); + counter->parent =3D dev; + counter->ops =3D &gpio_counter_ops; + counter->signals =3D priv->signals; + counter->num_signals =3D num_signals; + counter->counts =3D priv->counts; + counter->num_counts =3D ARRAY_SIZE(priv->counts); + + ret =3D gpio_counter_request_irqs(dev, counter); + if (ret) + return ret; + + ret =3D 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[] =3D { + { .compatible =3D "gpio-counter" }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_counter_of_match); + +static struct platform_driver gpio_counter_driver =3D { + .probe =3D gpio_counter_probe, + .driver =3D { + .name =3D "gpio-counter", + .of_match_table =3D gpio_counter_of_match, + }, +}; +module_platform_driver(gpio_counter_driver); + +MODULE_ALIAS("platform:gpio-counter"); +MODULE_AUTHOR("Wadim Mueller "); +MODULE_DESCRIPTION("GPIO-based counter driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("COUNTER"); --=20 2.43.0 From nobody Sat Jul 25 20:00:06 2026 Received: from mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 24BAD3F926B for ; Tue, 14 Jul 2026 08:17:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017044; cv=none; b=IX9wf2IgqpazkbmokuDUdISpFt3wvu0ocFhak4W0gBiU9ajeCPv0lU9tVWS1Yqm2aINjIfBOKQvs7uYBu23/wMa/T0TjITlzzJ6YMm/Zz7T7AdDdx5XUad5zfJJjaLw11mSSux+A+Agb/9BDqNlt6r80Y/u88Ba+9lVCiisQ0ps= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784017044; c=relaxed/simple; bh=0Q9ueRtJpn+0V8fYajXPPgDhnxfHEHQJcd5MZ4mr6Ak=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=E4J6B7+2MiSq1hTvlwLMFikrSz5IDr+f8phMu7kxkfKuro3vU+RCoWThHg7mha393UykK+9rbVZvD7glDOwQASRu/ff5Lq1llXY3QUiKs5BPQWmLEukg+/jj57rThbEH75sN+X5T4441yYNvjSUO3t1ABSec/qgTj5AOYvOMgAc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OoPf2NgZ; arc=none smtp.client-ip=209.85.218.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OoPf2NgZ" Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-c15b509c323so525977866b.0 for ; Tue, 14 Jul 2026 01:17:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784017032; x=1784621832; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qmXY1tinEamMA2gpoRwrr0YINADk+qjq8kgIZNm3+Os=; b=OoPf2NgZVRHvFeI8zG55Hxp8Zex1dK6//4U3HVECoAX05l0hgAtzlC5jMJIANK9yVI w5S2+ci/Zs4Xau82/xE2xIi+b7LFfxwxcmZjCqkWBA8B78q3VKbM5l2tuKMSjD3EkG0+ GzEYKINNESMcMXGn8kjRINZuKZHnppyi3qwZ8KIWRhbHO+uYhucHBnSQxRyBQ7MQCvpx eMvKK98a3jyMUHjbTR6staWQXOCRvCjmNNJNoxLeJvxaUYAXIvrje3G3ue4mON6NGEMy 42lfOZ0w+wUGGeVCDIMnwKu0dPUKyIGmppgSVMj/7GKnxIgnZ5Uz4u0VJDnV1qGSV7Dc EPxQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784017032; x=1784621832; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=qmXY1tinEamMA2gpoRwrr0YINADk+qjq8kgIZNm3+Os=; b=E67WWIbw3dgfkAAvctD7qeFGo7TM6z1BvEUeHVOD4h7x3KHIX6dV9CC6lOSP8PqH1w TeSANlVJe1etDezAbBPnd7wgqt49VNjMfzpQ/aEWgeXNFr5Ypd9UZIf6pRX0w4ql3Ky3 cVgKMbEgBIKPEv+ATzSB3dYDk6zTBSrKqJIjr8DK3WEHXu5cde76T7Rrl6MUlOGCWAd2 mI4TH6mCBj0Rz1xAiEH7RwgFvi12mO2zKG3FzasWvj7H03yjtxB3mvYKMjzvgkM7bLgN 5O6e0UYlSd1Ht2hTChqyJepQz/GFYZMWxmf/sAnwwTpYPtW+zMdN9w+GLHAqfxYZqQ4P poeg== X-Forwarded-Encrypted: i=1; AHgh+Rr14TdiPapF7bZETkQlGMbryLHMy4L9bOG9Gj7szE8Z9Eiv37HtJyC3q6rFLCd2VN2ji9Jar0yi8nfXg0k=@vger.kernel.org X-Gm-Message-State: AOJu0YyJLA8PoC8O4IlRpg4briF191CsEobYb3+iQ5DFGNxbKaIoA89U TY6wuqlS4VYMapEOpILf2nxCX+ABmwg1b0ObYN92vy4bEdbDpECsf6xQ X-Gm-Gg: AfdE7cl7De+Oun1tbqbUpr6zJ3mviYNVcZx3QaQIgilqa7v6dsR/qfResZp7CL1MT8Z AuzwsCangsstKg6Dgq3Fg2Ze5ZdovZePz35UBtE0qqAH3oRe5LRQY2XatTj9dGfGx+q5OO6ipQE CweucscHzteQcV2iWWSShQFMc8d3YAOwKHuSeVFVvpJXJd4Xlc39b/+HYyGK226OLYwanmTLs6R QriR+E5HpMU8359aNOqgN546GU8ZoaILxI4kQsWvS6mhEDin40lTOUMLCYn2sqgpNFPz/SBJmDG mdttNQM5X9/Ub85PRFzEj922hvQ0KNTpX2BOzNqcOba8KON5bIOUcSe5RV4DoFAYqVqGXla7e8Y 5yJrxFChXb5GjviCISnbBSmKVRodhbD3lOVYFt1NSBs8IInA//3lg4+AmyRpnZa1pQdX28AbQRj spoT9li/e7MD7LBLfZTIox5nMZJ1USQjxVqykJUspItTJHAQ== X-Received: by 2002:a17:907:60d1:b0:c16:558:e6a6 with SMTP id a640c23a62f3a-c16678c8425mr68187366b.5.1784017032296; Tue, 14 Jul 2026 01:17:12 -0700 (PDT) Received: from DE-PF5B95TD.embedded.cmblu.dev ([87.129.199.250]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c15e353d2f8sm723085166b.34.2026.07.14.01.17.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 01:17:11 -0700 (PDT) From: Wadim Mueller To: William Breathitt Gray Cc: Krzysztof Kozlowski , Rob Herring , Conor Dooley , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v7 3/3] MAINTAINERS: add entry for GPIO counter driver Date: Tue, 14 Jul 2026 10:17:09 +0200 Message-ID: <20260714081709.17846-4-wafgo01@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260714081709.17846-1-wafgo01@gmail.com> References: <20260714081709.17846-1-wafgo01@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Cover the gpio-counter driver and its device-tree binding. Signed-off-by: Wadim Mueller --- 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 =20 +GPIO COUNTER DRIVER +M: Wadim Mueller +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 L: linux-media@vger.kernel.org --=20 2.43.0