Devicetree
 help / color / mirror / Atom feed
* [PATCH 01/16] dt-bindings: dma: qcom,gpi: Document GPI DMA engine for Shikra SoC
From: Komal Bajaj @ 2026-05-24 19:49 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Krzysztof Kozlowski, Georgi Djakov, Bjorn Andersson,
	Konrad Dybcio
  Cc: linux-arm-msm, dmaengine, devicetree, linux-kernel, linux-pm,
	Komal Bajaj, Xueyao An
In-Reply-To: <20260525-shikra-dt-m1-v1-0-f51a9838dbaa@oss.qualcomm.com>

From: Xueyao An <xueyao.an@oss.qualcomm.com>

Document the GPI DMA engine on Shikra platform.

Signed-off-by: Xueyao An <xueyao.an@oss.qualcomm.com>
Signed-off-by: Komal Bajaj <komal.bajaj@oss.qualcomm.com>
---
 Documentation/devicetree/bindings/dma/qcom,gpi.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml
index 8f9a552fe30e..54dca623223d 100644
--- a/Documentation/devicetree/bindings/dma/qcom,gpi.yaml
+++ b/Documentation/devicetree/bindings/dma/qcom,gpi.yaml
@@ -37,6 +37,7 @@ properties:
               - qcom,sc7280-gpi-dma
               - qcom,sc8280xp-gpi-dma
               - qcom,sdx75-gpi-dma
+              - qcom,shikra-gpi-dma
               - qcom,sm6115-gpi-dma
               - qcom,sm6375-gpi-dma
               - qcom,sm8350-gpi-dma

-- 
2.34.1


^ permalink raw reply related

* [PATCH 00/16] arm64: dts: qcom: Extend Shikra device tree with peripheral and subsystem support
From: Komal Bajaj @ 2026-05-24 19:49 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Krzysztof Kozlowski, Georgi Djakov, Bjorn Andersson,
	Konrad Dybcio
  Cc: linux-arm-msm, dmaengine, devicetree, linux-kernel, linux-pm,
	Komal Bajaj, Xueyao An, Sayantan Chakraborty, Imran Shaik,
	Aastha Pandey, Raviteja Laggyshetty, Vishnu Santhosh,
	Bibek Kumar Patro, Gaurav Kohli, Yepuri Siddu, Yepuri Siddu,
	Miaoqing Pan

Extend Shikra DT with peripheral and subsystem support across all SoM
variants (CQ2390M, CQ2390S, IQ2390S) and their EVK boards.

The series adds:

- QUPv3 serial engine configuration
- cpufreq-hw node for hardware-assisted CPU frequency scaling
- DDR bandwidth monitor (BWMONv5) nodes with OPP tables for dynamic
  DDR frequency scaling
- EPSS L3 interconnect provider node for L3 cache frequency scaling
- CPU OPP tables to drive DDR and L3 scaling per frequency domain
- SMP2P nodes for CDSP, modem and LMCU inter-processor signalling
- Remoteproc PAS nodes for CDSP, LPAICP and MPSS subsystems
- TSENS instance with 14 thermal sensors and thermal zone definitions
- Bluetooth (WCN3988) node with board-specific regulator supplies on
  all three EVK variants
- WiFi node in the SoC DTSI with board-specific power supply and
  calibration variant selection on all three EVK variants

This series depends on:
- https://lore.kernel.org/all/20260522-shikra-dt-v3-0-80ffde8a3dc4@oss.qualcomm.com/
- https://lore.kernel.org/all/20260521-shikra-rproc-v3-0-2fca0bbe1ad7@oss.qualcomm.com/
- https://lore.kernel.org/linux-devicetree/20260513-tsens_binding-v1-1-1780c6a6caf2@oss.qualcomm.com/
- https://lore.kernel.org/all/20260524-shikra_epss_l3-v1-0-b1528a436134@oss.qualcomm.com/

Signed-off-by: Komal Bajaj <komal.bajaj@oss.qualcomm.com>
---
Bibek Kumar Patro (4):
      arm64: dts: qcom: shikra: Add CDSP, LPAICP, MPSS remoteproc PAS nodes
      arm64: dts: qcom: shikra-cqm: Enable CDSP, LPAICP and MPSS
      arm64: dts: qcom: shikra-cqs: Enable CDSP, LPAICP and MPSS
      arm64: dts: qcom: shikra-iqs: Enable CDSP, LPAICP and MPSS

Gaurav Kohli (1):
      arm64: dts: qcom: shikra: Enable TSENS and thermal zones

Imran Shaik (1):
      arm64: dts: qcom: shikra: Add cpufreq scaling node

Miaoqing Pan (2):
      arm64: dts: qcom: shikra: add WiFi node support
      arm64: dts: qcom: shikra: enable WiFi on EVK boards

Raviteja Laggyshetty (1):
      arm64: dts: qcom: shikra: Add EPSS L3 interconnect provider node

Sayantan Chakraborty (3):
      dt-bindings: interconnect: qcom-bwmon: Add Shikra cpu-bwmon compatible
      arm64: dts: qcom: shikra: Add DDR BWMON support
      arm64: dts: qcom: shikra: Add CPU OPP tables to scale DDR/L3

Vishnu Santhosh (1):
      arm64: dts: qcom: shikra: Add SMP2P nodes

Xueyao An (2):
      dt-bindings: dma: qcom,gpi: Document GPI DMA engine for Shikra SoC
      arm64: dts: qcom: Add QUPv3 configuration for Shikra

Yepuri Siddu (1):
      arm64: dts: qcom: shikra: Enable BT support on EVK boards

 .../devicetree/bindings/dma/qcom,gpi.yaml          |    1 +
 .../bindings/interconnect/qcom,msm8998-bwmon.yaml  |    1 +
 arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts        |   42 +
 arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts        |   42 +
 arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts        |   58 +
 arch/arm64/boot/dts/qcom/shikra.dtsi               | 1687 +++++++++++++++++++-
 6 files changed, 1810 insertions(+), 21 deletions(-)
---
base-commit: c1ecb239fa3456529a32255359fc78b69eb9d847
change-id: 20260525-shikra-dt-m1-082dec382e7f
prerequisite-change-id: 20260511-shikra-dt-d75d97454646:v3
prerequisite-patch-id: 3a689e8dda5fd2755b689d94d095806b3f2e6eed
prerequisite-patch-id: 2acc300a68ed8c5364fb5f2f7d28fc0d56ab07bf
prerequisite-patch-id: 391f9dffceaac9f44df7c2daffafb66fa379ca35
prerequisite-patch-id: 2885f299e711582da312ca9d13983d296a3dd5dc
prerequisite-patch-id: 7e351b93b3a238145ca642143bd0824bb90e98ce
prerequisite-change-id: 20260513-shikra-rproc-0da355c56c69:v3
prerequisite-patch-id: 39475cddaf673b2cbbae703165a782916f199885
prerequisite-patch-id: 6f7f265abfbdffdc0a1fdc5a7e08929e4eec5b7a
prerequisite-change-id: 20260512-tsens_binding-9af005e4b32e:v1
prerequisite-patch-id: 35141047f44b4845f9d94618bcf26ec58ab4a0b2
prerequisite-change-id: 20260524-shikra_epss_l3-522afe4fb8f5:v1
prerequisite-patch-id: 9e0a3b4d7b2033b39287b4382ba3c0c856a62e77
prerequisite-patch-id: 3ce52e07ae57139c2e2b71a29ed7d7250f6fcc87

Best regards,
-- 
Komal Bajaj <komal.bajaj@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH 1/2] dt-bindings: firmware: google,gs101-acpm-ipc: document Exynos850 compatible
From: Krzysztof Kozlowski @ 2026-05-24 19:45 UTC (permalink / raw)
  To: Alexey Klimov, Sam Protsenko, Tudor Ambarus, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Alim Akhtar
  Cc: Peter Griffin, linux-samsung-soc, devicetree, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260513-exynos850-acpm-firmware-support-v1-1-3858d097e433@linaro.org>

On 13/05/2026 01:12, Alexey Klimov wrote:
> The Exynos850 SoC incorporates an APM co-processor. Communication with
> this hardware block is done using the ACPM protocol, which handles IPC
> messages for clocks, power, thermal management and PMIC control.
> 
> Dedicated compatible string is required for the Exynos850 because
> its firmware utilizes a different initialisation data base offset
> (0x7000) compared to the existing GS101 implementation (0xa000).

This is good reason/explanation, but open the binding please - you will
see several specific children, e.g. S2MPG10. You need to be very
explicit in commit msg that your Exynos850 is always fitted with these
PMIcs. If not, then you need some other compatibles, because binding
should be complete.


Best regards,
Krzysztof

^ permalink raw reply

* [PATCH v2 1/2] dt-bindings: display: bridge: Document Renesas RZ/G3L LVDS encoder
From: Biju @ 2026-05-24 19:44 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, David Airlie,
	Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm
  Cc: Biju Das, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Tommaso Merciai, dri-devel, devicetree,
	linux-kernel, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das,
	Conor Dooley
In-Reply-To: <20260524194457.479681-1-biju.das.jz@bp.renesas.com>

From: Biju Das <biju.das.jz@bp.renesas.com>

Document the LVDS encoder IP found on the RZ/G3L SoC. It supports
single-link mode. LVDS and the DSI interface share a peripheral clock and
the MIPI_DSI_PRESET_N reset signal. However, the LVDS module cannot be
used at the same time as MIPI-DSI.

Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v2->v3:
 * No change.
v1->v2:
 * Collected tag.
---
 .../bridge/renesas,r9a08g046-lvds.yaml        | 128 ++++++++++++++++++
 1 file changed, 128 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml b/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml
new file mode 100644
index 000000000000..b1f6d020ae7b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml
@@ -0,0 +1,128 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/renesas,r9a08g046-lvds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/G3L LVDS Encoder
+
+maintainers:
+  - Biju Das <biju.das.jz@bp.renesas.com>
+  - Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
+
+description: |
+  This binding describe the LVDS encoder embedded in the Renesas RZ/G3L
+  SoC. The encoder can operate in LVDS Single-link mode with 4 lanes
+  (Data) + 1 lane (Clock).
+
+properties:
+  compatible:
+    const: renesas,r9a08g046-lvds
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Peripheral clock
+      - description: PHY clock
+      - description: Dot clock
+
+  clock-names:
+    items:
+      - const: pclk
+      - const: phyclk
+      - const: dotclk
+
+  resets:
+    items:
+      - description: LVDS_RESET_N
+      - description: MIPI_DSI_PRESET_N
+      - description: MIPI_DSI_CMN_RSTB
+      - description: MIPI_DSI_ARESET_N
+
+  reset-names:
+    items:
+      - const: lvdrst
+      - const: prst
+      - const: rst
+      - const: arst
+
+  power-domains:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Input channel, directly connected to the Display Unit.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: |
+          Output channel, directly connected to the LVDS panel or bridge.
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - power-domains
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/renesas,r9a08g046-cpg.h>
+
+    lvds-cmn@108a0000 {
+        compatible = "renesas,r9a08g046-lvds-cmn",
+                     "simple-mfd", "syscon";
+        reg = <0x108a0000 0x10000>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+
+        lvds0: lvds@10 {
+            compatible = "renesas,r9a08g046-lvds";
+            reg = <0x10 0x8>;
+            clocks = <&cpg CPG_MOD R9A08G046_MIPI_DSI_PCLK>,
+                     <&cpg CPG_MOD R9A08G046_LVDS_PLLCLK>,
+                     <&cpg CPG_MOD R9A08G046_LVDS_CLK_DOT0>;
+            clock-names = "pclk", "phyclk", "dotclk";
+            resets = <&cpg R9A08G046_LVDS_RESET_N>,
+                     <&cpg R9A08G046_MIPI_DSI_PRESET_N>,
+                     <&cpg R9A08G046_MIPI_DSI_CMN_RSTB>,
+                     <&cpg R9A08G046_MIPI_DSI_ARESET_N>;
+            reset-names = "lvdrst", "prst", "rst", "arst";
+            power-domains = <&cpg>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    lvds0_in: endpoint {
+                        remote-endpoint = <&du_out_lvds0>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    lvds0_out: endpoint {
+                        remote-endpoint = <&panel_in>;
+                    };
+                };
+            };
+        };
+    };
+...
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 0/2] Add support for Renesas RZ/G3L LVDS encoder
From: Biju @ 2026-05-24 19:44 UTC (permalink / raw)
  To: Andrzej Hajda, Neil Armstrong, Robert Foss, David Airlie,
	Simona Vetter, Philipp Zabel, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm
  Cc: Biju Das, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Luca Ceresoli, Tommaso Merciai, dri-devel, devicetree,
	linux-kernel, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das

From: Biju Das <biju.das.jz@bp.renesas.com>

Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
from 25 to 87 MHz. The LVDS module cannot be used at the same time as
MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
module.

v2->v3:
 * Dropped patch#1 as it is accepted.
 * Replace drm_atomic_state with drm_atomic_commit in
   rzg3l_lvds_atomic_{en,dis}able().
 * Drop local variable ret and dev_err() messages in
   rzg3l_lvds_atomic_enable(); use WARN_ON() instead to
   capture unexpected failures since atomic_enable should not fail.
 * Drop local variable next_bridge from rzg3l_lvds_probe().
v1->v2:
 * Collected the tags for binding patches.
 * Dropped unused function rzg3l_lvds_is_connected() and removed the 
   corresponding header file rzg3l_lvds.h
 * Dropped next_bridge from struct rzg3l_lvds instead using bridge's
   next_bridge.
 * Replaced pm_runtime_resume_and_get()->pm_runtime_get_sync() as
   atomic_enable doesn't fail and for each enable there always will be an
   atomic_disable() call.
 * Started using DEFINE_RUNTIME_DEV_PM_OPS for PM callback.
 * Replaced rzg3l_lvds_parse_dt() with devm_drm_of_get_bridge() in probe()
 * Started using reset_control_bulk_*() in rzg3l_lvds_pm_runtime_{suspend,
   resume}().

Biju Das (2):
  dt-bindings: display: bridge: Document Renesas RZ/G3L LVDS encoder
  drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder

 .../bridge/renesas,r9a08g046-lvds.yaml        | 128 ++++++++
 drivers/gpu/drm/renesas/rz-du/Kconfig         |  13 +
 drivers/gpu/drm/renesas/rz-du/Makefile        |   1 +
 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c    | 277 ++++++++++++++++++
 .../gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h   |  26 ++
 5 files changed, 445 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml
 create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
 create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h

-- 
2.43.0


^ permalink raw reply

* [PATCH v5 3/3] MAINTAINERS: add entry for GPIO counter driver
From: Wadim Mueller @ 2026-05-24 19:38 UTC (permalink / raw)
  To: wbg
  Cc: krzk+dt, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	Wadim Mueller
In-Reply-To: <20260524193846.19216-1-wafgo01@gmail.com>

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 06a8c7457..14f1a4e9f 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.52.0


^ permalink raw reply related

* [PATCH v5 2/3] counter: add GPIO-based counter driver
From: Wadim Mueller @ 2026-05-24 19:38 UTC (permalink / raw)
  To: wbg
  Cc: krzk+dt, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	Wadim Mueller
In-Reply-To: <20260524193846.19216-1-wafgo01@gmail.com>

Add a platform driver that turns plain GPIOs into a counter device.
Edge interrupts on the signal-a, signal-b (and optional index) lines
are decoded in software using a 2-bit Gray-code parity trick for the
quadrature X4 mode and direct edge checks for the other modes.

Supports COUNTER_FUNCTION_QUADRATURE_X1_{A,B} / X2_{A,B} / X4,
PULSE_DIRECTION, INCREASE and DECREASE.  Sleepable GPIO providers
(I2C/SPI expanders) are rejected at probe time becouse the ISRs run
in hardirq context.

Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
 drivers/counter/Kconfig        |  17 +
 drivers/counter/Makefile       |   1 +
 drivers/counter/gpio-counter.c | 744 +++++++++++++++++++++++++++++++++
 3 files changed, 762 insertions(+)
 create mode 100644 drivers/counter/gpio-counter.c

diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
index d30d22dfe..c20044032 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 fa3c1d08f..3959d69fb 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 000000000..f50cec33a
--- /dev/null
+++ b/drivers/counter/gpio-counter.c
@@ -0,0 +1,744 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO-based Counter Driver
+ *
+ * Decodes A/B (and optional Index) signals from GPIO lines in software.
+ * Supports quadrature X1/X2/X4, pulse-direction, and pure
+ * increase/decrease modes.
+ *
+ * Copyright (C) 2026 CMBlu Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ */
+
+#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/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_INDEX,
+};
+
+struct gpio_counter_priv {
+	struct gpio_desc *gpio_a;
+	struct gpio_desc *gpio_b;
+	struct gpio_desc *gpio_index;
+
+	int irq_a;
+	int irq_b;
+	int irq_index;
+
+	spinlock_t lock; /* protects count, ceiling, preset, function, direction, enabled */
+
+	u64 count;
+	u64 ceiling;
+	u64 preset;
+	bool preset_enabled;
+	bool enabled;
+	enum counter_count_direction direction;
+	enum counter_function function;
+
+	int prev_a;
+	int prev_b;
+
+	struct counter_count cnts[1];
+	struct counter_signal signals[3];
+	struct counter_synapse synapses[3];
+};
+
+/* X4 decode via 2-bit Gray-code parity. */
+#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)
+
+static void gpio_counter_update(struct gpio_counter_priv *priv, int delta)
+{
+	if (delta > 0) {
+		priv->direction = COUNTER_COUNT_DIRECTION_FORWARD;
+		if (priv->count >= priv->ceiling)
+			return;
+		priv->count++;
+	} else if (delta < 0) {
+		priv->direction = COUNTER_COUNT_DIRECTION_BACKWARD;
+		if (priv->count == 0)
+			return;
+		priv->count--;
+	}
+}
+
+static int gpio_counter_a_delta(struct gpio_counter_priv *priv, int a, int b,
+				int prev_a, int prev_b)
+{
+	enum counter_count_direction dir;
+
+	switch (priv->function) {
+	case COUNTER_FUNCTION_QUADRATURE_X4:
+		if (!GPIO_COUNTER_STATE_CHANGED(prev_a, prev_b, a, b))
+			return 0;
+		dir = GPIO_COUNTER_GET_DIRECTION(prev_b, a);
+		return (dir == COUNTER_COUNT_DIRECTION_FORWARD) ? 1 : -1;
+
+	case COUNTER_FUNCTION_QUADRATURE_X2_A:
+		return (a == b) ? -1 : 1;
+
+	case COUNTER_FUNCTION_QUADRATURE_X1_A:
+		if (a && priv->direction == COUNTER_COUNT_DIRECTION_FORWARD)
+			return 1;
+		if (!a && priv->direction == COUNTER_COUNT_DIRECTION_BACKWARD)
+			return -1;
+		return 0;
+
+	case COUNTER_FUNCTION_PULSE_DIRECTION:
+		if (!prev_a && a)
+			return b ? -1 : 1;
+		return 0;
+
+	case COUNTER_FUNCTION_INCREASE:
+		if (!prev_a && a)
+			return 1;
+		return 0;
+
+	case COUNTER_FUNCTION_DECREASE:
+		if (!prev_a && a)
+			return -1;
+		return 0;
+
+	default:
+		return 0;
+	}
+}
+
+static int gpio_counter_b_delta(struct gpio_counter_priv *priv, int a, int b,
+				int prev_a, int prev_b)
+{
+	enum counter_count_direction dir;
+
+	switch (priv->function) {
+	case COUNTER_FUNCTION_QUADRATURE_X4:
+		if (!GPIO_COUNTER_STATE_CHANGED(prev_a, prev_b, a, b))
+			return 0;
+		dir = GPIO_COUNTER_GET_DIRECTION(prev_b, a);
+		return (dir == COUNTER_COUNT_DIRECTION_FORWARD) ? 1 : -1;
+
+	case COUNTER_FUNCTION_QUADRATURE_X2_B:
+		return (a == b) ? 1 : -1;
+
+	case COUNTER_FUNCTION_QUADRATURE_X1_B:
+		if (b && priv->direction == COUNTER_COUNT_DIRECTION_FORWARD)
+			return 1;
+		if (!b && priv->direction == COUNTER_COUNT_DIRECTION_BACKWARD)
+			return -1;
+		return 0;
+
+	default:
+		return 0;
+	}
+}
+
+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);
+	unsigned long flags;
+	int a, b, delta;
+
+	/* !! normalises away negative gpiod_get_value() errors. */
+	a = !!gpiod_get_value(priv->gpio_a);
+	b = !!gpiod_get_value(priv->gpio_b);
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	delta = gpio_counter_a_delta(priv, a, b, priv->prev_a, priv->prev_b);
+	gpio_counter_update(priv, delta);
+
+	priv->prev_a = a;
+	priv->prev_b = b;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE, 0);
+
+	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);
+	unsigned long flags;
+	int a, b, delta;
+
+	a = !!gpiod_get_value(priv->gpio_a);
+	b = !!gpiod_get_value(priv->gpio_b);
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	delta = gpio_counter_b_delta(priv, a, b, priv->prev_a, priv->prev_b);
+	gpio_counter_update(priv, delta);
+
+	priv->prev_a = a;
+	priv->prev_b = b;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE, 0);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t gpio_counter_index_isr(int irq, void *dev_id)
+{
+	struct counter_device *counter = dev_id;
+	struct gpio_counter_priv *priv = counter_priv(counter);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (priv->preset_enabled) {
+		priv->count = priv->preset;
+		if (priv->count > priv->ceiling)
+			priv->count = priv->ceiling;
+	}
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	counter_push_event(counter, COUNTER_EVENT_INDEX, 0);
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*val = priv->count;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (val > priv->ceiling) {
+		spin_unlock_irqrestore(&priv->lock, flags);
+		return -EINVAL;
+	}
+
+	priv->count = val;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+static const enum counter_function gpio_counter_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 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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*function = priv->function;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	priv->function = function;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+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_NONE,
+	COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+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);
+	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_INDEX) {
+		*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+		return 0;
+	}
+
+	spin_lock_irqsave(&priv->lock, flags);
+	function = priv->function;
+	direction = priv->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:
+	case COUNTER_FUNCTION_INCREASE:
+	case COUNTER_FUNCTION_DECREASE:
+		if (signal_id == GPIO_COUNTER_SIGNAL_A)
+			*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+		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_INDEX:
+		gpio = priv->gpio_index;
+		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 != 0)
+		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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*val = priv->ceiling;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+
+	/* Leave count untouched on shrink; matches intel-qep / ti-eqep / stm32-timer-cnt. */
+	spin_lock_irqsave(&priv->lock, flags);
+	priv->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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*enable = priv->enabled;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return 0;
+}
+
+static int gpio_counter_enable_write(struct counter_device *counter,
+				     struct counter_count *count, u8 enable)
+{
+	struct gpio_counter_priv *priv = counter_priv(counter);
+	unsigned long flags;
+	bool want = enable;
+	bool changed;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	changed = priv->enabled != want;
+	if (changed)
+		priv->enabled = want;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	if (!changed)
+		return 0;
+
+	if (want) {
+		enable_irq(priv->irq_a);
+		enable_irq(priv->irq_b);
+		if (priv->irq_index)
+			enable_irq(priv->irq_index);
+	} else {
+		disable_irq(priv->irq_a);
+		disable_irq(priv->irq_b);
+		if (priv->irq_index)
+			disable_irq(priv->irq_index);
+	}
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*direction = priv->direction;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*val = priv->preset;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	if (val > priv->ceiling) {
+		ret = -EINVAL;
+		goto out;
+	}
+	priv->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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	*val = priv->preset_enabled;
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	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);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+	priv->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 int gpio_counter_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct counter_device *counter;
+	struct gpio_counter_priv *priv;
+	bool has_index;
+	int num_signals;
+	int num_synapses;
+	int ret;
+
+	counter = devm_counter_alloc(dev, sizeof(*priv));
+	if (!counter)
+		return -ENOMEM;
+
+	priv = counter_priv(counter);
+	spin_lock_init(&priv->lock);
+
+	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");
+
+	priv->gpio_index = devm_gpiod_get_optional(dev, "index", GPIOD_IN);
+	if (IS_ERR(priv->gpio_index))
+		return dev_err_probe(dev, PTR_ERR(priv->gpio_index),
+				     "failed to get index GPIO\n");
+
+	has_index = !!priv->gpio_index;
+
+	if (gpiod_cansleep(priv->gpio_a) || gpiod_cansleep(priv->gpio_b) ||
+	    (has_index && gpiod_cansleep(priv->gpio_index)))
+		return dev_err_probe(dev, -EINVAL,
+				     "GPIO controller may sleep; not supported in IRQ context\n");
+
+	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");
+
+	if (has_index) {
+		priv->irq_index = gpiod_to_irq(priv->gpio_index);
+		if (priv->irq_index < 0)
+			return dev_err_probe(dev, priv->irq_index,
+					     "failed to get IRQ for index\n");
+	}
+
+	priv->prev_a = !!gpiod_get_value(priv->gpio_a);
+	priv->prev_b = !!gpiod_get_value(priv->gpio_b);
+
+	priv->function = COUNTER_FUNCTION_QUADRATURE_X4;
+	priv->direction = COUNTER_COUNT_DIRECTION_FORWARD;
+	priv->ceiling = U64_MAX;
+	priv->enabled = false;
+
+	num_signals = has_index ? 3 : 2;
+	num_synapses = num_signals;
+
+	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";
+
+	priv->synapses[0].actions_list = gpio_counter_synapse_actions;
+	priv->synapses[0].num_actions =
+		ARRAY_SIZE(gpio_counter_synapse_actions);
+	priv->synapses[0].signal = &priv->signals[GPIO_COUNTER_SIGNAL_A];
+
+	priv->synapses[1].actions_list = gpio_counter_synapse_actions;
+	priv->synapses[1].num_actions =
+		ARRAY_SIZE(gpio_counter_synapse_actions);
+	priv->synapses[1].signal = &priv->signals[GPIO_COUNTER_SIGNAL_B];
+
+	if (has_index) {
+		priv->signals[GPIO_COUNTER_SIGNAL_INDEX].id =
+			GPIO_COUNTER_SIGNAL_INDEX;
+		priv->signals[GPIO_COUNTER_SIGNAL_INDEX].name = "Index";
+
+		priv->synapses[2].actions_list =
+			gpio_counter_index_synapse_actions;
+		priv->synapses[2].num_actions =
+			ARRAY_SIZE(gpio_counter_index_synapse_actions);
+		priv->synapses[2].signal =
+			&priv->signals[GPIO_COUNTER_SIGNAL_INDEX];
+	}
+
+	priv->cnts[0].id = 0;
+	priv->cnts[0].name = "Count";
+	priv->cnts[0].functions_list = gpio_counter_functions;
+	priv->cnts[0].num_functions = ARRAY_SIZE(gpio_counter_functions);
+	priv->cnts[0].synapses = priv->synapses;
+	priv->cnts[0].num_synapses = num_synapses;
+	priv->cnts[0].ext = gpio_counter_count_ext;
+	priv->cnts[0].num_ext = ARRAY_SIZE(gpio_counter_count_ext);
+
+	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->cnts;
+	counter->num_counts = ARRAY_SIZE(priv->cnts);
+
+	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");
+
+	if (has_index) {
+		ret = devm_request_irq(dev, priv->irq_index,
+				       gpio_counter_index_isr,
+				       IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN,
+				       "gpio-counter-index", counter);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to request IRQ for index\n");
+	}
+
+	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)\n",
+		 has_index ? ", Index" : "");
+
+	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 (quadrature, pulse-direction, increase/decrease)");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("COUNTER");
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 1/3] dt-bindings: counter: add gpio-counter binding
From: Wadim Mueller @ 2026-05-24 19:38 UTC (permalink / raw)
  To: wbg
  Cc: krzk+dt, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	Wadim Mueller
In-Reply-To: <20260524193846.19216-1-wafgo01@gmail.com>

Add a binding for a generic GPIO-based counter.  Two GPIOs (signal-a,
signal-b) drive the counter; an optional index GPIO loads a preset.
The counter function (quadrature, pulse-direction, increase/decrease)
is choosen at runtime through the counter sysfs interface.

Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
 .../bindings/counter/gpio-counter.yaml        | 59 +++++++++++++++++++
 1 file changed, 59 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 000000000..4bd972b61
--- /dev/null
+++ b/Documentation/devicetree/bindings/counter/gpio-counter.yaml
@@ -0,0 +1,59 @@
+# 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 <wadim.mueller@cmblu.de>
+
+description:
+  GPIO-based software counter.  Decodes up to two primary signals (A
+  and B) and an optional index pulse via edge-triggered GPIO interrupts
+  into a count.  Supports quadrature X1/X2/X4, pulse-direction, and
+  pure increase/decrease modes; the mode is selected at runtime via
+  the counter sysfs ABI.
+
+properties:
+  compatible:
+    const: gpio-counter
+
+  signal-a-gpios:
+    maxItems: 1
+    description:
+      Signal A input (encoder phase A in quadrature modes; pulse
+      input in pulse-direction and increase/decrease modes).
+
+  signal-b-gpios:
+    maxItems: 1
+    description:
+      Signal B input (encoder phase B in quadrature modes; direction
+      input in pulse-direction mode; unused in increase/decrease).
+
+  index-gpios:
+    maxItems: 1
+    description:
+      Optional index (Z) input.  When pulsed, loads the configured
+      preset into the count.
+
+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>;
+        index-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
+    };
+
+...
-- 
2.52.0


^ permalink raw reply related

* [PATCH v5 0/3] counter: add GPIO-based counter driver
From: Wadim Mueller @ 2026-05-24 19:38 UTC (permalink / raw)
  To: wbg
  Cc: krzk+dt, robh, conor+dt, linux-iio, devicetree, linux-kernel,
	Wadim Mueller
In-Reply-To: <20260515153616.157605-1-wafgo01@gmail.com>

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.

Changes in v5
-------------

Following William's v4 review the driver and binding are renamed
from "gpio-quadrature-encoder" to "gpio-counter" -- the name now
reflects what the hardware is, not one of its functions.  This
renames the source file, Kconfig symbol, compatible string, binding
file and DT properties (encoder-{a,b}-gpios -> signal-{a,b}-gpios,
encoder-index-gpios -> index-gpios).  Out-of-tree users on v1-v4
will need to update their DTs.

Conor's v4 Acked-by on the binding was dropped because of these
rename changes -- a fresh Ack would be appreciated.

Driver fixes from William's review:
  - X4 decoder rewritten using the 2-bit Gray-code parity trick
    (STATE_CHANGED = pa^pb^ca^cb, DIRECTION via pb^ca) -- no more
    16-entry lookup table.
  - X1_A / X1_B now count on rising-when-forward / falling-when-
    backward in the per-edge ISRs, with the X1 direction caveat
    documented in the source.
  - action_read holds priv->lock while reading function/direction
    and returns from each case directly.
  - ceiling_write no longer touches priv->count (matches intel-qep,
    ti-eqep, stm32-timer-cnt); the >= guard in the update path
    prevents further growth.
  - Dropped the redundant functions_list check in function_write
    and the !!val rewrite in preset_enable_write.

Sashiko AI [1] flagged seven issues on v4, all addressed:
  1. Normalise GPIO reads (a = !!a; b = !!b;) so negative error
     codes from gpiod_get_value() cannot index the state tables.
  2. priv->enabled tracked under priv->lock -- enable_write is
     now idempotent.
  3. preset/ceiling TOCTOU closed by moving the check under
     priv->lock; index ISR clamps after preset load.
  4. probe rejects sleepable GPIOs via gpiod_cansleep().
  5. action_read reports RISING/FALLING based on current direction,
     matching what the ISR counts on.
  6. action_read holds priv->lock (same fix as William's review).
  7. IRQF_NO_AUTOEN replaces irq_set_status_flags(IRQ_NOAUTOEN).

MAINTAINERS: section renamed to "GPIO COUNTER DRIVER" and resorted
alphabetically between "GPIO AGGREGATOR" and "GPIO IR Transmitter".

Thanks to William for the patient review and to the Sashiko bot
for the extra finds.

[1] https://sashiko.dev/#/patchset/20260515153616.157605-1-wafgo01@gmail.com?part=2

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        |  59 ++
 MAINTAINERS                                   |   7 +
 drivers/counter/Kconfig                       |  17 +
 drivers/counter/Makefile                      |   1 +
 drivers/counter/gpio-counter.c                | 744 ++++++++++++++++++
 5 files changed, 828 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/counter/gpio-counter.yaml
 create mode 100644 drivers/counter/gpio-counter.c


base-commit: 3cd8b194bf3428dfa53120fee47e827a7c495815
-- 
2.52.0


^ permalink raw reply

* Re: (subset) [PATCH v9 0/9] Allwinner A31/A83T MIPI CSI-2 and A31 ISP / Platform Support
From: Chen-Yu Tsai @ 2026-05-24 19:36 UTC (permalink / raw)
  To: linux-media, devicetree, linux-arm-kernel, linux-sunxi,
	linux-kernel, Paul Kocialkowski
  Cc: Yong Deng, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jernej Skrabec, Samuel Holland,
	Michael Turquette, Stephen Boyd, Brian Masney, Maxime Ripard
In-Reply-To: <20260518153339.619947-1-paulk@sys-base.io>

On Mon, 18 May 2026 17:33:29 +0200, Paul Kocialkowski wrote:
> This series adds platform support for the V3s/V3/S3 MIPI CSI-2 and ISP units
> as well the as A83T MIPI CSI-2 unit in the respective device-trees.
> Overlays for the BananaPi M3 cameras are also provided as actual users of the
> camera pipeline on A83T.
> 
> The corresponding drivers and dt bindings were merged a long time ago but this
> series was never actually picked up. It seems more than ready to be merged!
> 
> [...]

Applied to sunxi/shared-clk-dt-ids-for-7.2 in sunxi, thanks!

[3/9] clk: sunxi-ng: v3s: Export MBUS and DRAM clocks to the public header
      https://git.kernel.org/sunxi/linux/c/356a74a9325d

Best regards,
-- 
Chen-Yu Tsai <wens@kernel.org>


^ permalink raw reply

* Re: [PATCH v4 2/3] counter: add GPIO-based quadrature encoder driver
From: Wadim Mueller @ 2026-05-24 19:35 UTC (permalink / raw)
  To: William Breathitt Gray
  Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20260521002627.172691-1-wbg@kernel.org>

On 2026-05-21 09:26, William Breathitt Gray wrote:
> From: Wadim Mueller <wafgo01@gmail.com>
> 
> On Wed, May 20, 2026 at 01:45:20PM +0900, William Breathitt Gray wrote:
> > On Fri, May 15, 2026 at 05:36:15PM +0200, Wadim Mueller wrote:
> > > +static int gpio_qenc_a_delta(struct gpio_qenc_priv *priv, int a, int b,
> > > +			     int prev_a, int prev_b)
> > > +{
> > > +	int state = CREATE_QE_STATE(prev_a, prev_b, a, b);
> > > +
> > > +	switch (priv->function) {
> > > +	case COUNTER_FUNCTION_QUADRATURE_X4:
> > > +		return gpio_qenc_quad_x4_table[state];
> > > +
> > > +	case COUNTER_FUNCTION_QUADRATURE_X2_A:
> > > +		/* Both edges of A; sign comes from current A vs B. */
> > > +		return (a == b) ? -1 : 1;
> > > +
> > > +	case COUNTER_FUNCTION_QUADRATURE_X1_A:
> > > +		/* Rising edge of A only. */
> > > +		if (!prev_a && a)
> > > +			return b ? -1 : 1;
> > > +		return 0;
> > 
> > Quadrature X1 count modes trigger on the falling edge when the direction
> > is backward. This isn't simply a requirement by definition, but
> > necessary for the proper interpretation of the quadrature encoding.
> > 
> > Let's evaluate an incremental encoder used in a positioning application
> > as typical use case.[^1] These are commonly implemented using a rotating
> > shaft with a quadrature-offset pattern; aligned sensors detect the
> > physical A/B pattern as the shaft rotates.[^2] As the shaft rotates a
> > quadrature encoding emerges whose A-B phase difference allows us to
> > determine direction: forward when rising edge of signal A leads B, and
> > backward when it trails.[^3]
> > 
> > Now consider what happens to the signals when the rotation changes
> > direction: there is a phase change between Signals A and B.[^4] The A/B
> > pattern on the shaft is physically present so it has not changed; rather
> > the pattern is now fed backwards to the sensors due to the direction
> > reversal. The key point is the physical boundaries of the pattern are
> > located in the same shaft positions they have always been, yet the
> > signal edges representing those boundaries have flipped as a result of
> > the direction change: positions marked by rising edges now appear as
> > falling edges.
> > 
> > In Quadrature X4 and X2, the pattern reversal doesn't affect positioning
> > because we count on both edges, so swapping rising and falling edges
> > nets the same position count. Quadrature X1 presents a problem because
> > we count on a single edge type, so a phase-difference in the encoding
> > results in a physical shift in real-life position. The way to account
> > for that phase shift is to swap counting to the other edge type when the
> > direction changes. That's how dedicated quadrature encoder devices solve
> > this problem.
> > 
> > I'm not sure of the best way to solve the Quadrature X1 problem in this
> > driver. Right now we fire off interrupts on both edges, so perhaps
> > there's a way for us to determine whether we're firing on a rising edge
> > or falling edge and evaluate accordingly. Does the GPIO subsystem
> > provide an indication for which edge triggered the interrupt? Or would
> > it make sense to provide two interrupt service routines (one on rising
> > edge and one on falling edge) and handle it that way?
> 
> The simplest method might be to evaluate the current GPIO level to
> determine the edge polarity. Because we trigger on both edges, we can
> assume a high level means a low-high transition (rising edge) and a low
> level means a high-low transition (falling edge).
> 
> Using that assumption, we can implement the Quadrature X1 case by
> checking the current state and direction, and adjusting the counting
> accordingly when applicable: count up if rising edge and forward
> direction, and count down if falling edge and backward direction.
>

Implemented in following v5 as suggested in both signal-A and signal-B ISRs.  One
caveat I called out in the source: in pure X1 mode the driver never
sees both edges of both signals, so direction is whatever the last
X4/X2 sample produced (or whatever userspace set via sysfs).  In
practice X1 fits applications that already know the direction or
that have just calibrated in X4.

> Quadrature X1 A is handled by the signal A interrupt service routine:
> 
> 	/* COUNTER_FUNCTION_QUADRATURE_X1_A */
> 	if (ca)
> 		if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
> 			count++;
> 	else
> 		if (direction == COUNTER_COUNT_DIRECTION_BACKWARD)
> 			count--;
> 
> Quadrature X1 B is handled by the signal B interrupt service routine:
> 
> 	/* COUNTER_FUNCTION_QUADRATURE_X1_B */
> 	if (cb)
> 		if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
> 			count++;
> 	else
> 		if (direction == COUNTER_COUNT_DIRECTION_BACKWARD)
> 			count--;
> 
> There's an obvious caveat that this method only works if we're able to
> check the GPIO level before the next edge arrives (we're limited to low
> frequencies), but that's a caveat present regardless for our software
> counter so I believe this is an acceptable solution.
> 
> William Breathitt Gray

^ permalink raw reply

* Re: [PATCH RFC v2 1/7] dt-bindings: iio: add Open Sensor Fusion UART device
From: Conor Dooley @ 2026-05-24 19:34 UTC (permalink / raw)
  To: Jinseob Kim
  Cc: Jonathan Cameron, linux-iio, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree, linux-kernel
In-Reply-To: <20260524085312.15369-2-kimjinseob88@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 313 bytes --]

On Sun, May 24, 2026 at 05:53:06PM +0900, Jinseob Kim wrote:
> Add a binding for the OSF0 UART-attached sensor aggregation device.
> 
> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>

Most of my v1 review was ignored it seems. Please go back to v1 and
respond to the points I made.

Thanks,
Conor.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v4 2/3] counter: add GPIO-based quadrature encoder driver
From: Wadim Mueller @ 2026-05-24 19:33 UTC (permalink / raw)
  To: William Breathitt Gray
  Cc: Krzysztof Kozlowski, Rob Herring, Conor Dooley, linux-iio,
	devicetree, linux-kernel
In-Reply-To: <20260520044525.128529-1-wbg@kernel.org>

On 2026-05-20 13:45, William Breathitt Gray wrote:
> On Fri, May 15, 2026 at 05:36:15PM +0200, Wadim Mueller wrote:
> > Add a platform driver that turns ordinary GPIOs into a quadrature
> > encoder counter device.  The driver requests edge-triggered interrupts
> > on the A and B (and optional Index) GPIOs and decodes the quadrature
> > signal in software using a classic state-table approach.
> > 
> > Supported counting modes:
> >   - Quadrature X1 (count on A rising edge only)
> >   - Quadrature X2 (count on both A edges)
> >   - Quadrature X4 (count on every A and B edge)
> >   - Pulse-direction (A = pulse, B = direction)
> > 
> > An optional index signal resets the count to zero on its rising edge
> > when enabled through sysfs.  A configurable ceiling clamps the count
> > to [0, ceiling].
> > 
> > Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
> 
> Hi Wadim,
> 
> This driver supports non-quadrature modes such as pulse-direction and
> increase/decrease modes. Perhaps it's best to rename this driver to
> gpio-counter so that the name indicates the more general capabilities we
> have now. What do you think?

Agreed -- renamed accordingly in v5 (source file, Kconfig symbol,
compatible string, binding file, DT properties and MAINTAINERS).
Conor's v4 Acked-by on the binding is dropped because of this.

> 
> > +/*
> > + * Encode the four quadrature transitions in a single 4-bit state:
> > + *   bit3 = prev_a, bit2 = prev_b, bit1 = curr_a, bit0 = curr_b.
> > + *
> > + * Indexing the table with this value yields the signed delta for an
> > + * X4 decoder.  Illegal transitions (both inputs toggled at once)
> > + * remain 0 so the count is unchanged.
> > + */
> > +#define CREATE_QE_STATE(prev_a, prev_b, curr_a, curr_b) \
> > +	(((prev_a) << 3) | ((prev_b) << 2) | ((curr_a) << 1) | (curr_b))
> > +
> > +static const s8 gpio_qenc_quad_x4_table[16] = {
> > +	[CREATE_QE_STATE(0, 0, 0, 0)] =  0,
> > +	[CREATE_QE_STATE(0, 0, 0, 1)] = -1,
> > +	[CREATE_QE_STATE(0, 0, 1, 0)] =  1,
> > +	[CREATE_QE_STATE(0, 0, 1, 1)] =  0,
> > +	[CREATE_QE_STATE(0, 1, 0, 0)] =  1,
> > +	[CREATE_QE_STATE(0, 1, 0, 1)] =  0,
> > +	[CREATE_QE_STATE(0, 1, 1, 0)] =  0,
> > +	[CREATE_QE_STATE(0, 1, 1, 1)] = -1,
> > +	[CREATE_QE_STATE(1, 0, 0, 0)] = -1,
> > +	[CREATE_QE_STATE(1, 0, 0, 1)] =  0,
> > +	[CREATE_QE_STATE(1, 0, 1, 0)] =  0,
> > +	[CREATE_QE_STATE(1, 0, 1, 1)] =  1,
> > +	[CREATE_QE_STATE(1, 1, 0, 0)] =  0,
> > +	[CREATE_QE_STATE(1, 1, 0, 1)] =  1,
> > +	[CREATE_QE_STATE(1, 1, 1, 0)] = -1,
> > +	[CREATE_QE_STATE(1, 1, 1, 1)] =  0,
> > +};
> 
> I believe we can avoid the lookup table entirely by utilizing some
> simple bitwise operations.
> 
> Let's start with the "0" delta states:
> 
>     (PA, PB, CA, CB) = delta
>     ------------------------
>     (0, 0, 0, 0) =  0
>     (0, 1, 0, 1) =  0
>     (1, 0, 1, 0) =  0
>     (1, 1, 1, 1) =  0
>     (0, 0, 1, 1) =  0
>     (0, 1, 1, 0) =  0
>     (1, 0, 0, 1) =  0
>     (1, 1, 0, 0) =  0
> 
> A "0" delta is possible in two scenarios: either both signal levels
> remain the same, or both signal levels change. We can test for a nonzero
> delta using the following bitwise expression:
> 
>     HAS_NONZERO_DELTA = (PA ^ CA) ^ (PB ^ CB) = PA ^ PB ^ CA ^ CB
> 
> After that test, the remaining states are changes in a single Signal at
> a time:
> 
>     (PA, PB, CA, CB) = delta
>     ------------------------
>     (0, 0, 1, 0) =  1
>     (1, 0, 1, 1) =  1
>     (1, 1, 0, 1) =  1
>     (0, 1, 0, 0) =  1
>     (0, 0, 0, 1) = -1
>     (0, 1, 1, 1) = -1
>     (1, 1, 1, 0) = -1
>     (1, 0, 0, 0) = -1
> 
>     00 -> 10 -> 11 -> 01 -> 00 = FORWARD
>     00 -> 01 -> 11 -> 10 -> 00 = BACKWARD
> 
> The state change between previous and current is essentially a 2-bit
> Gray code. Gray code is designed such that successive values differ in
> only one bit. That means we can determine direction with just the
> previous state of Signal B (PB) and the current state of Signal A (CA):
> FORWARD when those two states differ, and BACKWARD when those two states
> are the same. A simple XOR operation computes such:
> 
>     GET_DIRECTION = PB ^ CA
> 
> So we can now reimplement the table as two macros:
> 
> 	#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)
> 

Done in v5, exactly like proposed.  The two macros plus the >= / > 0
guards replace the 16-entry table; a short comment points at the
Gray-code reasoning.

> With that, gpio_qenc_quad_x4_table[state] can be replaced with something
> like the following:
> 
> 	if (!GPIO_COUNTER_STATE_CHANGED(prev_a, prev_b, a, b))
> 		return;
> 
> 	direction = GPIO_COUNTER_GET_DIRECTION(prev_b, a);
> 
> 	if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
> 		if (count < ceiling)
> 			count++;
> 	else
> 		if (count > 0)
> 			count--;
> 
> I believe the intent of the code becomes clearer to read this way, but
> I'd like to hear what others think of this approach.
> 
> > +static int gpio_qenc_a_delta(struct gpio_qenc_priv *priv, int a, int b,
> > +			     int prev_a, int prev_b)
> > +{
> > +	int state = CREATE_QE_STATE(prev_a, prev_b, a, b);
> > +
> > +	switch (priv->function) {
> > +	case COUNTER_FUNCTION_QUADRATURE_X4:
> > +		return gpio_qenc_quad_x4_table[state];
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X2_A:
> > +		/* Both edges of A; sign comes from current A vs B. */
> > +		return (a == b) ? -1 : 1;
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X1_A:
> > +		/* Rising edge of A only. */
> > +		if (!prev_a && a)
> > +			return b ? -1 : 1;
> > +		return 0;
> 
> Quadrature X1 count modes trigger on the falling edge when the direction
> is backward. This isn't simply a requirement by definition, but
> necessary for the proper interpretation of the quadrature encoding.
> 
> Let's evaluate an incremental encoder used in a positioning application
> as typical use case.[^1] These are commonly implemented using a rotating
> shaft with a quadrature-offset pattern; aligned sensors detect the
> physical A/B pattern as the shaft rotates.[^2] As the shaft rotates a
> quadrature encoding emerges whose A-B phase difference allows us to
> determine direction: forward when rising edge of signal A leads B, and
> backward when it trails.[^3]
> 
> Now consider what happens to the signals when the rotation changes
> direction: there is a phase change between Signals A and B.[^4] The A/B
> pattern on the shaft is physically present so it has not changed; rather
> the pattern is now fed backwards to the sensors due to the direction
> reversal. The key point is the physical boundaries of the pattern are
> located in the same shaft positions they have always been, yet the
> signal edges representing those boundaries have flipped as a result of
> the direction change: positions marked by rising edges now appear as
> falling edges.
> 
> In Quadrature X4 and X2, the pattern reversal doesn't affect positioning
> because we count on both edges, so swapping rising and falling edges
> nets the same position count. Quadrature X1 presents a problem because
> we count on a single edge type, so a phase-difference in the encoding
> results in a physical shift in real-life position. The way to account
> for that phase shift is to swap counting to the other edge type when the
> direction changes. That's how dedicated quadrature encoder devices solve
> this problem.
> 
> I'm not sure of the best way to solve the Quadrature X1 problem in this
> driver. Right now we fire off interrupts on both edges, so perhaps
> there's a way for us to determine whether we're firing on a rising edge
> or falling edge and evaluate accordingly. Does the GPIO subsystem
> provide an indication for which edge triggered the interrupt? Or would
> it make sense to provide two interrupt service routines (one on rising
> edge and one on falling edge) and handle it that way?
> 
> > +static int gpio_qenc_function_write(struct counter_device *counter,
> > +				    struct counter_count *count,
> > +				    enum counter_function function)
> > +{
> > +	struct gpio_qenc_priv *priv = counter_priv(counter);
> > +	unsigned long flags;
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(gpio_qenc_functions); i++)
> > +		if (gpio_qenc_functions[i] == function)
> > +			break;
> > +	if (i == ARRAY_SIZE(gpio_qenc_functions))
> > +		return -EINVAL;
> 
> The Counter subsystem ensures the function argument passed in to the
> function_write() callback exists within the count's functions_list. You
> can remove the gpio_qenc_functions array check entirely.
> 
> > +static int gpio_qenc_action_read(struct counter_device *counter,
> > +				 struct counter_count *count,
> > +				 struct counter_synapse *synapse,
> > +				 enum counter_synapse_action *action)
> > +{
> > +	struct gpio_qenc_priv *priv = counter_priv(counter);
> > +	enum gpio_qenc_signal_id signal_id = synapse->signal->id;
> > +
> > +	/* Index synapse always observes rising edges, regardless of mode. */
> > +	if (signal_id == GPIO_QENC_SIGNAL_INDEX) {
> > +		*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
> > +		return 0;
> > +	}
> > +
> > +	*action = COUNTER_SYNAPSE_ACTION_NONE;
> > +
> > +	switch (priv->function) {
> > +	case COUNTER_FUNCTION_QUADRATURE_X4:
> > +		*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
> > +		break;
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X2_A:
> > +		if (signal_id == GPIO_QENC_SIGNAL_A)
> > +			*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
> > +		break;
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X2_B:
> > +		if (signal_id == GPIO_QENC_SIGNAL_B)
> > +			*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
> > +		break;
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X1_A:
> > +		if (signal_id == GPIO_QENC_SIGNAL_A) {
> > +			if (priv->direction == COUNTER_COUNT_DIRECTION_FORWARD)
> > +				*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
> > +			else
> > +				*action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
> > +		}
> > +		break;
> > +
> > +	case COUNTER_FUNCTION_QUADRATURE_X1_B:
> > +		if (signal_id == GPIO_QENC_SIGNAL_B) {
> > +			if (priv->direction == COUNTER_COUNT_DIRECTION_FORWARD)
> > +				*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
> > +			else
> > +				*action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
> > +		}
> > +		break;
> > +
> > +	case COUNTER_FUNCTION_PULSE_DIRECTION:
> > +	case COUNTER_FUNCTION_INCREASE:
> > +	case COUNTER_FUNCTION_DECREASE:
> > +		if (signal_id == GPIO_QENC_SIGNAL_A)
> > +			*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
> > +		break;
> > +
> > +	default:
> > +		return -EINVAL;
> > +	}
> 
> Instead of break statements, exit early by returning 0;

All removed/applied.  action_read() now also holds priv->lock while
reading function and direction (overlaps with one of the Sashiko
findings).

> 
> > +static int gpio_qenc_ceiling_write(struct counter_device *counter,
> > +				   struct counter_count *count, u64 val)
> > +{
> > +	struct gpio_qenc_priv *priv = counter_priv(counter);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&priv->lock, flags);
> > +	priv->ceiling = val;
> > +	if (priv->count > val)
> > +		priv->count = val;
> 
> Although having a count value above the ceiling isn't a particularly
> useful configuration, I wonder if it's unexpected for the the count
> value to be modified by an update to the ceiling value. It could be
> considered a loss of data in a sense because the user might reasonably
> expect the count value to hold the current position of whatever
> application is running.
> 
> How do other quadrature encoder devices handle this situation? Do they
> also adjust the count value immediately to ceiling, leave the count
> static entirely, or merely prevent the count from increasing further yet
> allow it to decrease gradually below the ceiling?
>

intel-qep, ti-eqep and stm32-timer-cnt all leave the count alone;
ftm-quaddec has no ceiling_write at all.  v5 follows that: ceiling_write
no longer touches priv->count, the update path uses a >= guard so an
out-of-range count can't grow, and the index ISR clamps after loading
the preset.

In addition, Sashiko AI[1] flagged seven issues on v4.  I went through
each against the code -- all real, no hallucinations.  v5 addresses
them as follow:

  1. !! normalisation of GPIO reads so negative errors don't index
     the state tables.
  2. priv->enabled under priv->lock; enable_write idempotent.
  3. preset/ceiling TOCTOU closed (check inside lock + >= guard +
     post-preset clamp).
  4. probe rejects sleepable GPIOs via gpiod_cansleep().
  5. action_read reports RISING/FALLING by current direction to match
     the ISR (overlaps with your X1 fix).
  6. action_read takes priv->lock.
  7. IRQF_NO_AUTOEN passed to devm_request_irq() instead of the global
     irq_set_status_flags() call.

v5 follows in a separate sub-thread under the v4 cover.

Thanks again for the patient review -- the Gray-code rewrite and the
X1 edge fix genuinly improved the driver.

[1] https://sashiko.dev/#/patchset/20260515153616.157605-1-wafgo01@gmail.com?part=2

Wadim

> > +static int gpio_qenc_preset_enable_write(struct counter_device *counter,
> > +					 struct counter_count *count, u8 val)
> > +{
> > +	struct gpio_qenc_priv *priv = counter_priv(counter);
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&priv->lock, flags);
> > +	priv->preset_enabled = !!val;
> 
> All the COUNTER_COMP_*_ENABLE() components are ensured by the Counter
> subsystem to have boolean values, so no need for the double negation.
> 
> The reason the u8 type appears in the callbacks is to have a
> well-defined data width for the chrdev interface; the actual values
> passed to the callback will always be boolean.
> 
> William Breathitt Gray
> 
> [^1] https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_outputs
> [^2] https://upload.wikimedia.org/wikipedia/commons/1/1e/Incremental_directional_encoder.gif
> [^3] https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Quadrature_Diagram.svg/3840px-Quadrature_Diagram.svg.png
> [^4] https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/QuadratureOscillatingShaft.png/500px-QuadratureOscillatingShaft.png

^ permalink raw reply

* Re: [PATCH v4 0/2] Add xSPI support for RZ/T2H and RZ/N2H SoCs
From: Krzysztof Kozlowski @ 2026-05-24 19:29 UTC (permalink / raw)
  To: Rob Herring, Conor Dooley, Geert Uytterhoeven, Magnus Damm,
	Biju Das, Prabhakar
  Cc: linux-kernel, devicetree, linux-renesas-soc, Fabrizio Castro,
	Lad Prabhakar
In-Reply-To: <20260515115202.1515577-1-prabhakar.mahadev-lad.rj@bp.renesas.com>


On Fri, 15 May 2026 12:52:00 +0100, Prabhakar wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> 
> Hi All,
> 
> Add support for the xSPI (Extended SPI) Interface on Renesas RZ/T2H and
> RZ/N2H SoCs. The xSPI IP on these SoCs is identical to that found on the
> RZ/G3E SoC.
> 
> [...]

Applied, thanks!

[1/2] dt-bindings: memory: renesas,rzg3e-xspi: Add RZ/T2H and RZ/N2H support
      https://git.kernel.org/krzk/linux-mem-ctrl/c/a6954060adc9d956a99f909f46bf9fb0348c4fa2
[2/2] memory: renesas-rpc-if: Fix duplicate device name on multi-instance platforms
      https://git.kernel.org/krzk/linux-mem-ctrl/c/3fcf9f334d272989b57acc7b94d4eac717206118

Best regards,
-- 
Krzysztof Kozlowski <krzk@kernel.org>


^ permalink raw reply

* [PATCH v9 5/5] arm64: dts: qcom: monaco: Add OPP-table for ICE UFS and ICE eMMC nodes
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit
In-Reply-To: <20260525-enable-ice-clock-scaling-v9-0-c84613e9ce47@oss.qualcomm.com>

Qualcomm Inline Crypto Engine (ICE) platform driver now, supports
an optional OPP-table.

Add OPP-table for ICE UFS and ICE eMMC device nodes for Monaco
platform.

Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/monaco.dtsi | 37 ++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/monaco.dtsi b/arch/arm64/boot/dts/qcom/monaco.dtsi
index 805feb481943e0684162048b5e665b056588095f..89586a6fda70dde16007fb9d3d6a1fc4459c58ed 100644
--- a/arch/arm64/boot/dts/qcom/monaco.dtsi
+++ b/arch/arm64/boot/dts/qcom/monaco.dtsi
@@ -2742,6 +2742,27 @@ ice: crypto@1d88000 {
 			clock-names = "core",
 				      "iface";
 			power-domains = <&gcc GCC_UFS_PHY_GDSC>;
+
+			operating-points-v2 = <&ice_opp_table>;
+
+			ice_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-75000000 {
+					opp-hz = /bits/ 64 <75000000>;
+					required-opps = <&rpmhpd_opp_svs_l1>;
+				};
+
+				opp-201600000 {
+					opp-hz = /bits/ 64 <201600000>;
+					required-opps = <&rpmhpd_opp_svs_l1>;
+				};
+
+				opp-403200000 {
+					opp-hz = /bits/ 64 <403200000>;
+					required-opps = <&rpmhpd_opp_nom>;
+				};
+			};
 		};
 
 		crypto: crypto@1dfa000 {
@@ -4878,6 +4899,22 @@ sdhc_ice: crypto@87c8000 {
 			clock-names = "core",
 				      "iface";
 			power-domains = <&rpmhpd RPMHPD_CX>;
+
+			operating-points-v2 = <&ice_mmc_opp_table>;
+
+			ice_mmc_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-150000000 {
+					opp-hz = /bits/ 64 <150000000>;
+					required-opps = <&rpmhpd_opp_svs_l1>;
+				};
+
+				opp-300000000 {
+					opp-hz = /bits/ 64 <300000000>;
+					required-opps = <&rpmhpd_opp_nom>;
+				};
+			};
 		};
 
 		usb_1_hsphy: phy@8904000 {

-- 
2.34.1


^ permalink raw reply related

* [PATCH v9 4/5] arm64: dts: qcom: kodiak: Add OPP-table for ICE UFS and ICE eMMC nodes
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit
In-Reply-To: <20260525-enable-ice-clock-scaling-v9-0-c84613e9ce47@oss.qualcomm.com>

Qualcomm Inline Crypto Engine (ICE) platform driver now, supports
an optional OPP-table.

Add OPP-table for ICE UFS and ICE eMMC device nodes for Kodiak
platform.

Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/kodiak.dtsi | 42 ++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
index a8260f695058525e77653fd8005fd3d250715a91..8a8328c5dfa88b69594fca926f4f7c1825416259 100644
--- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
+++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
@@ -1087,6 +1087,27 @@ sdhc_ice: crypto@7c8000 {
 			clock-names = "core",
 				      "iface";
 			power-domains = <&rpmhpd SC7280_CX>;
+
+			operating-points-v2 = <&ice_mmc_opp_table>;
+
+			ice_mmc_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-100000000 {
+					opp-hz = /bits/ 64 <100000000>;
+					required-opps = <&rpmhpd_opp_low_svs>;
+				};
+
+				opp-150000000 {
+					opp-hz = /bits/ 64 <150000000>;
+					required-opps = <&rpmhpd_opp_svs>;
+				};
+
+				opp-300000000 {
+					opp-hz = /bits/ 64 <300000000>;
+					required-opps = <&rpmhpd_opp_svs_l1>;
+				};
+			};
 		};
 
 		gpi_dma0: dma-controller@900000 {
@@ -2597,6 +2618,27 @@ ice: crypto@1d88000 {
 			clock-names = "core",
 				      "iface";
 			power-domains = <&gcc GCC_UFS_PHY_GDSC>;
+
+			operating-points-v2 = <&ice_opp_table>;
+
+			ice_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-75000000 {
+					opp-hz = /bits/ 64 <75000000>;
+					required-opps = <&rpmhpd_opp_low_svs>;
+				};
+
+				opp-150000000 {
+					opp-hz = /bits/ 64 <150000000>;
+					required-opps = <&rpmhpd_opp_svs>;
+				};
+
+				opp-300000000 {
+					opp-hz = /bits/ 64 <300000000>;
+					required-opps = <&rpmhpd_opp_nom>;
+				};
+			};
 		};
 
 		cryptobam: dma-controller@1dc4000 {

-- 
2.34.1


^ permalink raw reply related

* [PATCH v9 3/5] mmc: sdhci-msm: Set ICE clk to TURBO at sdhci ICE init
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit, Konrad Dybcio
In-Reply-To: <20260525-enable-ice-clock-scaling-v9-0-c84613e9ce47@oss.qualcomm.com>

MMC controller lacks a clock scaling mechanism, unlike the UFS
controller. By default, the MMC controller is set to TURBO mode
during probe, but the ICE clock remains at XO frequency,
leading to read/write performance degradation on eMMC.

To address this, set the ICE clock to TURBO during sdhci_msm_ice_init
to align it with the controller clock. This ensures consistent
performance and avoids mismatches between the controller
and ICE clock frequencies.

For platforms where ICE is represented as a separate device,
use the OPP framework to vote for TURBO mode, maintaining
proper voltage and power domain constraints.

Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
 drivers/mmc/host/sdhci-msm.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
index 633462c0be5f43c06c497520faf9f6fa03fa652a..cabcb75ebbac392eb6dce7ac8c756724ef9e1b49 100644
--- a/drivers/mmc/host/sdhci-msm.c
+++ b/drivers/mmc/host/sdhci-msm.c
@@ -1901,6 +1901,8 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock)
 #ifdef CONFIG_MMC_CRYPTO
 
 static const struct blk_crypto_ll_ops sdhci_msm_crypto_ops; /* forward decl */
+static int sdhci_msm_ice_scale_clk(struct sdhci_msm_host *msm_host, unsigned long target_freq,
+				   bool round_ceil); /* forward decl */
 
 static int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host,
 			      struct cqhci_host *cq_host)
@@ -1959,6 +1961,11 @@ static int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host,
 	}
 
 	mmc->caps2 |= MMC_CAP2_CRYPTO;
+
+	err = sdhci_msm_ice_scale_clk(msm_host, ULONG_MAX, false);
+	if (err && err != -EOPNOTSUPP)
+		dev_warn(dev, "Unable to boost ICE clock to TURBO\n");
+
 	return 0;
 }
 
@@ -1984,6 +1991,16 @@ static int sdhci_msm_ice_suspend(struct sdhci_msm_host *msm_host)
 	return 0;
 }
 
+static int sdhci_msm_ice_scale_clk(struct sdhci_msm_host *msm_host,
+				   unsigned long target_freq,
+				   bool round_ceil)
+{
+	if (msm_host->mmc->caps2 & MMC_CAP2_CRYPTO)
+		return qcom_ice_scale_clk(msm_host->ice, target_freq, round_ceil);
+
+	return 0;
+}
+
 static inline struct sdhci_msm_host *
 sdhci_msm_host_from_crypto_profile(struct blk_crypto_profile *profile)
 {
@@ -2149,6 +2166,13 @@ sdhci_msm_ice_suspend(struct sdhci_msm_host *msm_host)
 {
 	return 0;
 }
+
+static inline int
+sdhci_msm_ice_scale_clk(struct sdhci_msm_host *msm_host, unsigned long target_freq,
+			bool round_ceil)
+{
+	return 0;
+}
 #endif /* !CONFIG_MMC_CRYPTO */
 
 /*****************************************************************************\

-- 
2.34.1


^ permalink raw reply related

* [PATCH v9 2/5] ufs: host: Add ICE clock scaling during UFS clock changes
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit
In-Reply-To: <20260525-enable-ice-clock-scaling-v9-0-c84613e9ce47@oss.qualcomm.com>

Implement ICE (Inline Crypto Engine) clock scaling in sync with
UFS controller clock scaling. This ensures that the ICE operates at
an appropriate frequency when the UFS clocks are scaled up or down,
improving performance and maintaining stability for crypto operations.

For scale_up operation ensure to pass ~round_ceil (round_floor)
and vice-versa for scale_down operations.

Incase of OPP scaling is not supported by ICE, ensure to not prevent
devfreq for UFS, as ICE OPP-table is optional.

Acked-by: Manivannan Sadhasivam <mani@kernel.org>
Reviewed-by: Harshal Dev <harshal.dev@oss.qualcomm.com>
Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
 drivers/ufs/host/ufs-qcom.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index bc037db46624adaf494d08f6c2a2c55c9ed24606..b248d8db8997341117d014320d22fdf1ae7b89a6 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -306,6 +306,15 @@ static int ufs_qcom_ice_prepare_key(struct blk_crypto_profile *profile,
 	return qcom_ice_prepare_key(host->ice, lt_key, lt_key_size, eph_key);
 }
 
+static int ufs_qcom_ice_scale_clk(struct ufs_qcom_host *host, unsigned long target_freq,
+				  bool round_ceil)
+{
+	if (host->hba->caps & UFSHCD_CAP_CRYPTO)
+		return qcom_ice_scale_clk(host->ice, target_freq, round_ceil);
+
+	return 0;
+}
+
 static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops = {
 	.keyslot_program	= ufs_qcom_ice_keyslot_program,
 	.keyslot_evict		= ufs_qcom_ice_keyslot_evict,
@@ -340,6 +349,12 @@ static void ufs_qcom_config_ice_allocator(struct ufs_qcom_host *host)
 {
 }
 
+static int ufs_qcom_ice_scale_clk(struct ufs_qcom_host *host, unsigned long target_freq,
+				  bool round_ceil)
+{
+	return 0;
+}
+
 #endif
 
 static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host)
@@ -1933,6 +1948,12 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, bool scale_up,
 			return err;
 		}
 
+		err = ufs_qcom_ice_scale_clk(host, target_freq, !scale_up);
+		if (err && err != -EOPNOTSUPP) {
+			ufshcd_uic_hibern8_exit(hba);
+			return err;
+		}
+
 		ufs_qcom_icc_update_bw(host);
 		ufshcd_uic_hibern8_exit(hba);
 	}

-- 
2.34.1


^ permalink raw reply related

* [PATCH v9 1/5] soc: qcom: ice: Add OPP-based clock scaling support for ICE
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit
In-Reply-To: <20260525-enable-ice-clock-scaling-v9-0-c84613e9ce47@oss.qualcomm.com>

Register optional operation-points-v2 table for ICE device
during device probe. Attach the OPP-table with only the ICE
core clock. Since, dtbinding is on a trasition phase to include
iface clock and clock-names, attaching the opp-table to core clock
remains optional such that it does not cause probe failures.

Introduce clock scaling API qcom_ice_scale_clk which scale ICE
core clock based on the target frequency provided and if a valid
OPP-table is registered. Use round_ceil passed to decide on the
rounding of the clock freq against OPP-table. Clock scaling is
disabled when a valid OPP-table is not registered.

This ensures when an ICE-device specific OPP table is available,
use the PM OPP framework to manage frequency scaling and maintain
proper power-domain constraints.

Also, ensure to drop the votes in suspend to prevent power/thermal
retention. Subsequently restore the frequency in resume from
core_clk_freq which stores the last ICE core clock operating frequency.

Reviewed-by: Harshal Dev <harshal.dev@oss.qualcomm.com>
Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
 drivers/soc/qcom/ice.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/soc/qcom/ice.h |  2 ++
 2 files changed, 95 insertions(+)

diff --git a/drivers/soc/qcom/ice.c b/drivers/soc/qcom/ice.c
index bf4ab2d9e5c0360d8fe6135cc35f93b6b09e7a0e..7b98afec71f6135f580f82497127b0db7e70a6da 100644
--- a/drivers/soc/qcom/ice.c
+++ b/drivers/soc/qcom/ice.c
@@ -16,6 +16,7 @@
 #include <linux/of.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/pm_opp.h>
 
 #include <linux/firmware/qcom/qcom_scm.h>
 
@@ -112,6 +113,8 @@ struct qcom_ice {
 	bool use_hwkm;
 	bool hwkm_init_complete;
 	u8 hwkm_version;
+	unsigned long core_clk_freq;
+	bool has_opp;
 };
 
 static bool qcom_ice_check_supported(struct qcom_ice *ice)
@@ -311,6 +314,10 @@ int qcom_ice_resume(struct qcom_ice *ice)
 	struct device *dev = ice->dev;
 	int err;
 
+	/* Restore the ICE core clk freq */
+	if (ice->has_opp && ice->core_clk_freq)
+		dev_pm_opp_set_rate(ice->dev, ice->core_clk_freq);
+
 	err = clk_prepare_enable(ice->core_clk);
 	if (err) {
 		dev_err(dev, "Failed to enable core clock: %d\n", err);
@@ -331,6 +338,11 @@ int qcom_ice_suspend(struct qcom_ice *ice)
 {
 	clk_disable_unprepare(ice->iface_clk);
 	clk_disable_unprepare(ice->core_clk);
+
+	/* Drop the clock votes while suspend */
+	if (ice->has_opp)
+		dev_pm_opp_set_rate(ice->dev, 0);
+
 	ice->hwkm_init_complete = false;
 
 	return 0;
@@ -556,6 +568,51 @@ int qcom_ice_import_key(struct qcom_ice *ice,
 }
 EXPORT_SYMBOL_GPL(qcom_ice_import_key);
 
+/**
+ * qcom_ice_scale_clk() - Scale ICE clock for DVFS-aware operations
+ * @ice: ICE driver data
+ * @target_freq: requested frequency in Hz
+ * @round_ceil: when true, selects nearest freq >= @target_freq;
+ *              otherwise, selects nearest freq <= @target_freq
+ *
+ * Selects an OPP frequency based on @target_freq and the rounding direction
+ * specified by @round_ceil, then programs it using dev_pm_opp_set_rate(),
+ * including any voltage or power-domain transitions handled by the OPP
+ * framework. Updates ice->core_clk_freq on success.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if no OPP table; or error from
+ *         dev_pm_opp_set_rate()/OPP lookup.
+ */
+int qcom_ice_scale_clk(struct qcom_ice *ice, unsigned long target_freq,
+		       bool round_ceil)
+{
+	unsigned long ice_freq = target_freq;
+	struct dev_pm_opp *opp;
+	int ret;
+
+	if (!ice->has_opp)
+		return -EOPNOTSUPP;
+
+	if (round_ceil)
+		opp = dev_pm_opp_find_freq_ceil(ice->dev, &ice_freq);
+	else
+		opp = dev_pm_opp_find_freq_floor(ice->dev, &ice_freq);
+
+	if (IS_ERR(opp))
+		return PTR_ERR(opp);
+	dev_pm_opp_put(opp);
+
+	ret = dev_pm_opp_set_rate(ice->dev, ice_freq);
+	if (ret) {
+		dev_err(ice->dev, "Unable to scale ICE clock rate\n");
+		return ret;
+	}
+	ice->core_clk_freq = ice_freq;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(qcom_ice_scale_clk);
+
 static struct qcom_ice *qcom_ice_create(struct device *dev,
 					void __iomem *base)
 {
@@ -731,6 +788,7 @@ static int qcom_ice_probe(struct platform_device *pdev)
 {
 	struct qcom_ice *engine;
 	void __iomem *base;
+	int err;
 
 	base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(base)) {
@@ -742,6 +800,41 @@ static int qcom_ice_probe(struct platform_device *pdev)
 	if (IS_ERR(engine))
 		return PTR_ERR(engine);
 
+	/* qcom_ice_create() may return NULL if scm calls are not available */
+	if (!engine)
+		return -EOPNOTSUPP;
+
+	err = devm_pm_opp_set_clkname(&pdev->dev, "core");
+	if (err && err != -ENOENT) {
+		dev_err(&pdev->dev, "Unable to set core clkname to OPP-table\n");
+		return err;
+	}
+
+	/* OPP table is optional */
+	err = devm_pm_opp_of_add_table(&pdev->dev);
+	if (err && err != -ENODEV) {
+		dev_err(&pdev->dev, "Invalid OPP table in Device tree\n");
+		return err;
+	}
+
+	/*
+	 * The OPP table is optional. devm_pm_opp_of_add_table() returns
+	 * -ENODEV when no OPP table is present in DT, which is not treated
+	 * as an error. Therefore, track successful OPP registration only
+	 * when err is not -ENODEV.
+	 */
+	if (err == -ENODEV)
+		dev_info(&pdev->dev, "ICE OPP table is not registered, please update your DT\n");
+	else
+		engine->has_opp = true;
+
+	/*
+	 * Store the core clock rate for suspend resume cycles,
+	 * against OPP aware DVFS operations. core_clk_freq will
+	 * have a valid value only for non-legacy bindings.
+	 */
+	engine->core_clk_freq = clk_get_rate(engine->core_clk);
+
 	platform_set_drvdata(pdev, engine);
 
 	return 0;
diff --git a/include/soc/qcom/ice.h b/include/soc/qcom/ice.h
index 4bee553f0a59d86ec6ce20f7c7b4bce28a706415..4eb58a264d416e71228ed4b13e7f53c549261fdc 100644
--- a/include/soc/qcom/ice.h
+++ b/include/soc/qcom/ice.h
@@ -30,5 +30,7 @@ int qcom_ice_import_key(struct qcom_ice *ice,
 			const u8 *raw_key, size_t raw_key_size,
 			u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
 struct qcom_ice *devm_of_qcom_ice_get(struct device *dev);
+int qcom_ice_scale_clk(struct qcom_ice *ice, unsigned long target_freq,
+		       bool round_ceil);
 
 #endif /* __QCOM_ICE_H__ */

-- 
2.34.1


^ permalink raw reply related

* [PATCH v9 0/5] Enable ICE clock scaling
From: Abhinaba Rakshit @ 2026-05-24 19:25 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Manivannan Sadhasivam,
	James E.J. Bottomley, Martin K. Petersen, Adrian Hunter,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Neeraj Soni, Harshal Dev, Kuldeep Singh
  Cc: linux-arm-msm, linux-kernel, linux-scsi, linux-mmc, devicetree,
	Abhinaba Rakshit, Konrad Dybcio

Introduce support for dynamic clock scaling of the ICE (Inline Crypto Engine)
using the OPP framework. During ICE device probe, the driver now attempts to
parse an optional OPP table from the ICE-specific device tree node for 
DVFS-aware operations. API qcom_ice_scale_clk is exposed by ICE driver
and is invoked by UFS host controller driver in response to clock scaling
requests, ensuring coordination between ICE and host controller.

For MMC controllers that do not support clock scaling, the ICE clock frequency
is kept aligned with the MMC controller’s clock rate (TURBO) to ensure
consistent operation.

Dynamic clock scaling based on OPP tables enables better power-performance
trade-offs. By adjusting ICE clock frequencies according to workload and power
constraints, the system can achieve higher throughput when needed and 
reduce power consumption during idle or low-load conditions.

The OPP table remains optional, absence of the table will not cause
probe failure. However, in the absence of an OPP table, ICE clocks will
remain at their default rates, which may limit performance under
high-load scenarios or prevent performance optimizations during idle periods.

Testing:
* dtbs_check
* Validated on Rb3Gen2 and qcs8300-ride-sx

Merge Order and Dependencies
============================

Patch 2 is dependent on patch 1 for the qcom_ice_scale_clk API to be available.
Patch 3 is dependent on patch 1 for the qcom_ice_scale_clk API to be available.

Due to dependency, all patches should go through Qcom SoC tree.

This patchset supersedes earlier ICE clock scaling series (v1–v8) with updated dependencies.
Hence, this patchset also *Depends-On* the following patchseries:

[1] Add explicit clock vote and enable power-domain for QCOM-ICE
    https://lore.kernel.org/all/20260416-qcom_ice_power_and_clk_vote-v5-0-5ccf5d7e2846@oss.qualcomm.com/

[2] Enable Inline crypto engine for kodiak and monaco
    https://lore.kernel.org/all/20260310113557.348502-1-neeraj.soni@oss.qualcomm.com/

[3] Enable iface clock and power domain for kodiak and monaco ice sdhc
    https://lore.kernel.org/linux-arm-msm/20260409-ice_emmc_clock_addition-v2-0-90bbcc057361@oss.qualcomm.com/

Signed-off-by: Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>
---
Changes in v9: 
- Kodiak ICE eMMC OPP-table entry corresponding to 300MHz is updated with SVS_L1.
- Add 75MHz for Monaco ICE eMMC OPP-table.
- Fix error handling and initialization of has_opp variable.
- Pass ULONG_MAX as target freq instead of INT_MAX from sdhci_ice_init as it better adjusts the data-type of
  the function qcom_ice_scale_clk.
- Link to v8: https://lore.kernel.org/r/20260409-enable-ice-clock-scaling-v8-0-ca1129798606@oss.qualcomm.com

Changes in v8: 
- Instead of scaling to TURBO in ICE probe, sdhci_msm_ice_init calls qcom_ice_scale_clk for setting freq to max.
- Fix error handling in qcom_ice_scale_clk.
- Fix error handling in ufs_qcom_clk_scale_notify for the call to qcom_ice_scale_clk.
- Move the registering of OPP-table to qcom_ice_probe and remove passing legacy_bindings argument to qcom_ice_create.
- Add OPP-table for kodiak and monaco ICE eMMC and UFS device nodes.
- Link to v7: https://lore.kernel.org/r/20260302-enable-ufs-ice-clock-scaling-v7-0-669b96ecadd8@oss.qualcomm.com

Changes in v7: 
- Replace the custom rounding flags with 'bool round_ceil' as suggested.
- Update the dev_info log-line.
- Dropped dt-bindings patch (already applied by in previous patchseries).
- Add merge order and dependencies as suggested.
- Link to v6: https://lore.kernel.org/r/20260219-enable-ufs-ice-clock-scaling-v6-0-0c5245117d45@oss.qualcomm.com

Changes in v6: 
- Remove scale_up parameter from qcom_ice_scale_clk API.
- Remove having max_freq and min_freq as the checks for overclocking and underclocking is no-longer needed.
- UFS driver passes rounding flags depending on scale_up value.
- Ensure UFS driver does not fail devfreq requests if ICE OPP is not supported.
- Link to v5: https://lore.kernel.org/all/20260211-enable-ufs-ice-clock-scaling-v5-0-221c520a1f2e@oss.qualcomm.com/

Changes in v5: 
- Update operating-points-v2 property in dtbindings as suggested.
- Fix comment styles.
- Add argument in qcom_ice_create to distinguish between legacy bindings and newer bindings.
- Ensure to drop votes in suspend and enable the last vote in resume.
- Link to v4: https://lore.kernel.org/r/20260128-enable-ufs-ice-clock-scaling-v4-0-260141e8fce6@oss.qualcomm.com

Changes in v4: 
- Enable multiple frequency scaling based OPP-entries as suggested in v3 patchset.
- Include bindings change: https://lore.kernel.org/all/20260123-add-operating-points-v2-property-for-qcom-ice-bindings-v1-1-2155f7aacc28@oss.qualcomm.com/.
- Link to v3: https://lore.kernel.org/r/20260123-enable-ufs-ice-clock-scaling-v3-0-d0d8532abd98@oss.qualcomm.com

Changes in v3: 
- Avoid clock scaling in case of legacy bindings as suggested.
- Use of_device_is_compatible to distinguish between legacy and non-legacy bindings.
- Link to v2: https://lore.kernel.org/r/20251121-enable-ufs-ice-clock-scaling-v2-0-66cb72998041@oss.qualcomm.com

Changes in v2: 
- Use OPP-table instead of freq-table-hz for clock scaling.
- Enable clock scaling for legacy targets as well, by fetching frequencies from storage opp-table.
- Introduce has_opp variable in qcom_ice structure to keep track, if ICE instance has dedicated OPP-table registered.
- Combined the changes for patch-series <20251001-set-ice-clock-to-turbo-v1-1-7b802cf61dda@oss.qualcomm.com> as suggested.
- Link to v1: https://lore.kernel.org/r/20251001-enable-ufs-ice-clock-scaling-v1-0-ec956160b696@oss.qualcomm.com

---
Abhinaba Rakshit (5):
      soc: qcom: ice: Add OPP-based clock scaling support for ICE
      ufs: host: Add ICE clock scaling during UFS clock changes
      mmc: sdhci-msm: Set ICE clk to TURBO at sdhci ICE init
      arm64: dts: qcom: kodiak: Add OPP-table for ICE UFS and ICE eMMC nodes
      arm64: dts: qcom: monaco: Add OPP-table for ICE UFS and ICE eMMC nodes

 arch/arm64/boot/dts/qcom/kodiak.dtsi | 42 ++++++++++++++++
 arch/arm64/boot/dts/qcom/monaco.dtsi | 37 ++++++++++++++
 drivers/mmc/host/sdhci-msm.c         | 24 ++++++++++
 drivers/soc/qcom/ice.c               | 93 ++++++++++++++++++++++++++++++++++++
 drivers/ufs/host/ufs-qcom.c          | 21 ++++++++
 include/soc/qcom/ice.h               |  2 +
 6 files changed, 219 insertions(+)
---
base-commit: 936c21068d7ade00325e40d82bfd2f3f29d9f659
change-id: 20260525-enable-ice-clock-scaling-b3d70c7e5cdf
prerequisite-change-id: 20260120-qcom_ice_power_and_clk_vote-769704f5036a:v5
prerequisite-patch-id: 1750aded4cac0105fbf943c5bfd9f844acf4f227
prerequisite-patch-id: 8cf945709b92296c73859515bb67820360d785a2
prerequisite-patch-id: bc8821cbbe222f208c5d86d96f3640c169b972d6
prerequisite-patch-id: a1baf04d3cce803fcb47b1a80591bf7759de8a76
prerequisite-patch-id: b7de0f216e54e264e054f6333b3067abce8d05c5
prerequisite-patch-id: 57f21e8a9505564caebbf89cafa9bd80be1dfe9f
prerequisite-patch-id: 5128586130e3f5847e0417de47ef755b2e2fba93
prerequisite-patch-id: fa46b7d6710907c5eb5ad01e84d28f09a0b26e5a
prerequisite-patch-id: e375d6e54a55c055f5d8673c65d35073df396646
prerequisite-patch-id: ec670d98300863c4b68155a3b0feeace56a4a55a
prerequisite-patch-id: c5ee690afd7f7105963e991dff760de62a403d9b
prerequisite-patch-id: 4d792e5cbecd8946fed4c7fb7c192b78b4f30bee
prerequisite-patch-id: 52c56183a63f9b75068635533d37aa9e45d935a2
prerequisite-message-id: <20260310113557.348502-1-neeraj.soni@oss.qualcomm.com>
prerequisite-patch-id: ab9cc8bd28b2e1e27df6e44907e8d758dfeee3df
prerequisite-patch-id: 40f239f7f06573ed45452249f444e54e3565ada7
prerequisite-patch-id: 59129ed0aeba84f6b50f42261d51fe323806a240
prerequisite-change-id: 20260406-ice_emmc_clock_addition-e19f36c1fca5:v2
prerequisite-patch-id: 5b6a436bd949a93e44f912d2565103f6bf0ef55a
prerequisite-patch-id: 7f9ff2b708418a77578e154102f72f0da243eb71

Best regards,
-- 
Abhinaba Rakshit <abhinaba.rakshit@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH v3 3/5] arm64: dts: allwinner: a100: Add LRADC node
From: Chen-Yu Tsai @ 2026-05-24 19:23 UTC (permalink / raw)
  To: Alexander Sverdlin
  Cc: sashiko-reviews, conor+dt, linux-sunxi, devicetree, robh
In-Reply-To: <20260518000221.985CBC2BCB0@smtp.kernel.org>

Hi Alexander,

On Mon, May 18, 2026 at 2:02 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Missing SoC-specific compatible string for the LRADC device tree node.
> - [Low] Non-standard device tree node name 'lradc'.
> --
>
> commit 2888476527aa17fae679cf9ed23a85ccc8db7463
> Author: Alexander Sverdlin <alexander.sverdlin@gmail.com>
>
> arm64: dts: allwinner: a100: Add LRADC node
>
> This commit adds the Low Rate ADC (LRADC) device tree node for the Allwinner
> A100/A133 SoCs. The IP block is primarily used for key applications and
> supports various operating modes.
>
> > diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a100.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a100.dtsi
> > index b3fb1e0ee796b..2bba13c5d25cd 100644
> > --- a/arch/arm64/boot/dts/allwinner/sun50i-a100.dtsi
> > +++ b/arch/arm64/boot/dts/allwinner/sun50i-a100.dtsi
> > @@ -466,6 +466,15 @@ ths: thermal-sensor@5070400 {
> >                       #thermal-sensor-cells = <1>;
> >               };
> >
> > +             lradc: lradc@5070800 {
>
> Could this node use a more generic name like adc@5070800 or keys@5070800?
>
> Other device nodes in this file adhere to the generic naming convention
> recommended by the Devicetree Specification.

Please ignore this comment.

> > +                     compatible = "allwinner,sun50i-r329-lradc";
>
> Is the A100-specific compatible string missing from this node?
>
> Other recent Allwinner SoCs utilizing this IP define their own specific
> compatible alongside the fallback.
>
> Would including an A100-specific string like "allwinner,sun50i-a100-lradc"
> allow handling potential quirks without breaking backward compatibility?

Yes, we always add SoC-specific compatibles, unless they are just the same
die in different packages. Please add another patch to add the compatible,
then use it in this patch. See this series for the H616 [1] as an example.


Thanks
ChenYu

[1] https://lore.kernel.org/all/20240426092924.15489-1-jamcgregor@protonmail.com/


> > +                     reg = <0x05070800 0x400>;
> [ ... ]
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260517234134.2737320-1-alexander.sverdlin@gmail.com?part=3
>

^ permalink raw reply

* Re: [RESEND,v2 1/2] dt-bindings: memory-controllers: mtk-smi: Add support for mt8189
From: Krzysztof Kozlowski @ 2026-05-24 19:08 UTC (permalink / raw)
  To: mtk20898, Yong Wu, Rob Herring, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-mediatek, linux-kernel, devicetree, linux-arm-kernel,
	Project_Global_Chrome_Upstream_Group
In-Reply-To: <20260427070444.20247-2-zhengnan.chen@mediatek.com>

On 27/04/2026 09:04, mtk20898 wrote:
> From: Zhengnan Chen <zhengnan.chen@mediatek.com>
> 
> Add binding description for mt8189.
> 
> The clocks number of mt8189 smi-sub common has a bit difference.
> Its clock count is 2, while mt8195 has 3. Therefore, the minimum
> number of clocks is changed to 2, with the third one being optional.

Then why does the binding say that mt8195 has two clocks? You already
received exactly this question.

> 
> About what smi-sub-common is, please check the below diagram,
> we add it in mediatek,smi-common.yaml file.
> 
> Signed-off-by: Zhengnan Chen <zhengnan.chen@mediatek.com>
> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>


No need to resend this. You received comments at v2 and you should have
implemented them.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH RFC v2 1/7] dt-bindings: iio: add Open Sensor Fusion UART device
From: Krzysztof Kozlowski @ 2026-05-24 18:59 UTC (permalink / raw)
  To: Jinseob Kim, Jonathan Cameron, linux-iio
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
In-Reply-To: <20260524085312.15369-2-kimjinseob88@gmail.com>

On 24/05/2026 10:53, Jinseob Kim wrote:
> Add a binding for the OSF0 UART-attached sensor aggregation device.
> 
> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
> ---
>  .../iio/imu/opensensorfusion,osf-uart.yaml    | 33 +++++++++++++++++++
>  .../devicetree/bindings/vendor-prefixes.yaml  |  2 ++
>  2 files changed, 35 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
> new file mode 100644
> index 000000000..d4a8f6819
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/opensensorfusion,osf-uart.yaml
> @@ -0,0 +1,33 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/opensensorfusion,osf-uart.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Open Sensor Fusion UART sensor aggregation device
> +
> +maintainers:
> +  - Jinseob Kim <kimjinseob88@gmail.com>
> +
> +description:
> +  UART-attached sensor aggregation device using the OSF0 frame format.
> +  The device sends capability, status, and sample frames to the host.
> +
> +properties:
> +  compatible:
> +    const: opensensorfusion,osf-uart

1. No such vendor. You need to test against entire bindings.
2. Looks heavily incomplete - no resources. Compatible and hardware
description looks also incomplete - what is osf? It's exactly ONE
device? Why do you use bus suffix? Please start with writing bindings or
DTS101 slides.

> +
> +required:
> +  - compatible
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    serial {
> +        osf {

Node names should be generic. See also an explanation and list of
examples (not exhaustive) in DT specification:
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
If you cannot find a name matching your device, please check in kernel
sources for similar cases or you can grow the spec (via pull request to
DT spec repo).

> +            compatible = "opensensorfusion,osf-uart";



Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH] ASoC: dt-bindings: sound: atmel_ac97c: Convert to DT schema
From: sashiko-bot @ 2026-05-24 18:48 UTC (permalink / raw)
  To: Manish Baing; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260524183337.111657-1-manishbaing2789@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The YAML schema omits the `clocks` and `clock-names` properties, causing validation failures for existing valid device trees.
- [Medium] The `ac97-gpios` property is incorrectly placed in the `required` list of the schema, which will break validation for existing device trees.
--

commit 1b5d0ed3017fc9e1dbdb1e38381500e3cc380fb7
Author: Manish Baing <manishbaing2789@gmail.com>
ASoC: dt-bindings: sound: atmel_ac97c: Convert to DT schema

This commit converts the Atmel AC97 controller device tree binding from
plain text format to a YAML schema.

> diff --git a/Documentation/devicetree/bindings/sound/atmel,at91sam9263-ac97c.yaml b/Documentation/devicetree/bindings/sound/atmel,at91sam9263-ac97c.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/atmel,at91sam9263-ac97c.yaml
> @@ -0,0 +1,60 @@

[ ... ]

> +properties:
> +  compatible:
> +    const: atmel,at91sam9263-ac97c
> +
> +  reg:
> +    maxItems: 1
> +    description: Address and length of the register set for the device.
> +
> +  interrupts:
> +    maxItems: 1
> +    description: Should contain the AC97 interrupt.
> +
> +  ac97-gpios:
> +    minItems: 3
> +    maxItems: 4
> +    description: |
> +      AC97 link GPIOs- sync, sdata_out, reset, and optional sdata_in.
> +      The driver primarily uses the reset pin.
> +
> +  pinctrl-0: true
> +
> +  pinctrl-names:
> +    const: default

[Severity: Medium]
The driver (sound/atmel/ac97c.c) unconditionally requires a peripheral clock
via clk_get(&pdev->dev, "ac97_clk"). Existing device trees such as
arch/arm/boot/dts/microchip/at91sam9263.dtsi define clocks and clock-names.

Since unevaluatedProperties is set to false below, won't make dtbs_check
flag these valid properties as errors? Should clocks and clock-names be
added to the schema?

> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - ac97-gpios

[Severity: Medium]
Is it correct to make ac97-gpios mandatory here?

The driver appears to treat the reset GPIO as optional, and existing device
trees like at91sam9263.dtsi do not define it. Will making it strictly
required cause validation failures for existing hardware configurations?

> +
> +unevaluatedProperties: false
> +
> +examples:

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260524183337.111657-1-manishbaing2789@gmail.com?part=1

^ permalink raw reply

* Re: [PATCH 1/3] dt-bindings: interconnect: qcom,x1e80100-rpmh: add clocks property to enable QoS
From: Raviteja Laggyshetty @ 2026-05-24 18:44 UTC (permalink / raw)
  To: Dmitry Baryshkov, Krzysztof Kozlowski
  Cc: Georgi Djakov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Rajendra Nayak, Abel Vesa, Bjorn Andersson, Konrad Dybcio,
	Odelu Kukatla, linux-arm-msm, linux-pm, devicetree, linux-kernel
In-Reply-To: <nr62mvz6qrpb6moqyasniqcqtfltsx4qtr3lteeixwutindqri@5joujdvn4r3q>



On 5/21/2026 5:26 AM, Dmitry Baryshkov wrote:
> On Wed, May 20, 2026 at 09:33:52PM +0200, Krzysztof Kozlowski wrote:
>> On 20/05/2026 21:27, Dmitry Baryshkov wrote:
>>> On Wed, May 20, 2026 at 10:11:47PM +0300, Georgi Djakov wrote:
>>>> On 5/20/26 9:51 PM, Krzysztof Kozlowski wrote:
>>>>> On 23/04/2026 10:38, Krzysztof Kozlowski wrote:
>>>>>> On Wed, Apr 22, 2026 at 02:05:11AM +0000, Raviteja Laggyshetty wrote:
>>>>>>> Some interconnect nodes on X1E80100 have QoS registers located inside
>>>>>>> a block whose interface is clock-gated. For those nodes, driver
>>>>>>> must enable the corresponding clock(s) before accessing the
>>>>>>> registers. Add the 'clocks' property so the driver can obtain
>>>>>>> and enable the required clock(s).
>>>>>>>
>>>>>>> Only interconnects that have clock-gated QoS register interface
>>>>>>> use this property; it is not applicable to all interconnect nodes.
>>>>>>>
>>>>>>> Signed-off-by: Raviteja Laggyshetty <raviteja.laggyshetty@oss.qualcomm.com>
>>>>>>> ---
>>>>>>>   .../bindings/interconnect/qcom,x1e80100-rpmh.yaml  | 62 ++++++++++++++++++++++
>>>>>>>   1 file changed, 62 insertions(+)
>>>>>>
>>>>>> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>>>>>
>>>>> And unreviewed as it breaks users:
>>>>> https://krzk.eu/#/builders/102/builds/70/steps/23/logs/warnings__3_
>>>>>
>>>>
>>>> I will just drop these patches for now, i have put them in a separate branch anyway.
>>>
>>> I think, dropping 'required' clause would be the easiest fix. Or just
>>> wait for Bjorn to pick up the DT changes.
>>
>> Could be, initially I thought this is actual impact on users, but indeed
>> now I recall that driver prints "info" message and continues. So the
>> binding is not correct.
> 
> I'd say, the binding was not correct: the hardware has the clocks and
> the requires them to function completely correctly. I think, the problem
> is that we allowed incomplete drivers and incomplete bindings (Without
> QoS support).
> 

Thanks for pointing this out, and sorry for the breakage caused.

This is one of the earlier targets where interconnect support was
upstreamed without QoS support. Making the `clocks` property required broke
existing DTs for that target, if the corresponding DT changes are not 
picked.

For newer targets, this should not be an issue as QoS support is introduced
along with interconnect.

I will respin the patch following commit  
e07f3b8c9e1c ("dt-bindings: interconnect: qcom,qcs615-rpmh: add clocks 
property to enable QoS"),  
keeping the QoS-related clocks optional to maintain backward compatibility.

Thanks,
Raviteja.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox