public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support
@ 2026-03-21 20:24 charles.embedded
  2026-03-21 20:24 ` [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe() charles.embedded
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias

From: Charles Dias <charlesdias.cd@outlook.com>

This series improves the Adafruit seesaw gamepad driver in two steps.

The first patch switches to using dev_err_probe() in seesaw_probe()
to improve error handling.

The second patch adds optional interrupt support for button events when
an IRQ is described in DTS. Joystick axes remain polled because the
default Adafruit seesaw gamepad firmware exposes button interrupts
through the GPIO module, while the joystick positions are read from ADC
channels.

When no IRQ is described in DTS, the driver continues to operate in
pure polling mode.

This series was validated on a BeaglePlay board with the Adafruit
Seesaw Gamepad, both with the interrupt enabled in DTS and without it.

Charles Dias (3):
  Input: adafruit-seesaw - switch to using dev_err_probe()
  Input: adafruit-seesaw - add interrupt support
  dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity

 .../input/adafruit,seesaw-gamepad.yaml        |  12 +-
 drivers/input/joystick/adafruit-seesaw.c      | 160 ++++++++++++------
 2 files changed, 119 insertions(+), 53 deletions(-)


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe()
  2026-03-21 20:24 [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support charles.embedded
@ 2026-03-21 20:24 ` charles.embedded
  2026-03-21 20:24 ` [PATCH 2/3] Input: adafruit-seesaw - add interrupt support charles.embedded
  2026-03-21 20:24 ` [PATCH 3/3] dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity charles.embedded
  2 siblings, 0 replies; 6+ messages in thread
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias

From: Charles Dias <charlesdias.cd@outlook.com>

Use dev_err_probe() instead of dev_err() in seesaw_probe function
to improve error handling.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 drivers/input/joystick/adafruit-seesaw.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c
index c248c15b849d..177b42446e9b 100644
--- a/drivers/input/joystick/adafruit-seesaw.c
+++ b/drivers/input/joystick/adafruit-seesaw.c
@@ -277,17 +277,12 @@ static int seesaw_probe(struct i2c_client *client)
 			     SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT);
 
 	err = sparse_keymap_setup(seesaw->input_dev, seesaw_buttons_new, NULL);
-	if (err) {
-		dev_err(&client->dev,
-			"failed to set up input device keymap: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to set up input device keymap\n");
 
 	err = input_setup_polling(seesaw->input_dev, seesaw_poll);
-	if (err) {
-		dev_err(&client->dev, "failed to set up polling: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to set up polling\n");
 
 	input_set_poll_interval(seesaw->input_dev,
 				SEESAW_GAMEPAD_POLL_INTERVAL_MS);
@@ -295,10 +290,8 @@ static int seesaw_probe(struct i2c_client *client)
 	input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
 
 	err = input_register_device(seesaw->input_dev);
-	if (err) {
-		dev_err(&client->dev, "failed to register joystick: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to register joystick\n");
 
 	return 0;
 }

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/3] Input: adafruit-seesaw - add interrupt support
  2026-03-21 20:24 [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support charles.embedded
  2026-03-21 20:24 ` [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe() charles.embedded
@ 2026-03-21 20:24 ` charles.embedded
  2026-03-23  5:12   ` Dmitry Torokhov
  2026-03-21 20:24 ` [PATCH 3/3] dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity charles.embedded
  2 siblings, 1 reply; 6+ messages in thread
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias

From: Charles Dias <charlesdias.cd@outlook.com>

Use IRQ-driven reporting for button events when an interrupt is
described in DTS. Joystick axis values still have no interrupt source,
so axis reporting continues to use polling.

When the DTS specifies an interrupt, the IRQ thread calls only
seesaw_report_buttons(), avoiding unnecessary ADC reads on each button
event. The polling path always calls seesaw_report_axes(), and calls
seesaw_report_buttons() only when no IRQ is configured. This avoids
concurrent access to button_state between the IRQ thread and the poll
timer.

When no interrupt is available, the driver falls back to fully polled
operation.

Also remove the now-unused seesaw_data structure. Axis values are read
directly in seesaw_report_axes(), and button_state already lives in the
driver data, where it persists across calls for state comparison.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 drivers/input/joystick/adafruit-seesaw.c | 141 +++++++++++++++++------
 1 file changed, 103 insertions(+), 38 deletions(-)

diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c
index 177b42446e9b..ed8896c639ce 100644
--- a/drivers/input/joystick/adafruit-seesaw.c
+++ b/drivers/input/joystick/adafruit-seesaw.c
@@ -11,8 +11,10 @@
  * Product page: https://www.adafruit.com/product/5743
  * Firmware and hardware sources: https://github.com/adafruit/Adafruit_Seesaw
  *
- * TODO:
- *	- Add interrupt support
+ * Interrupt support is available when the DTS defines an interrupt for the
+ * device. Button events are then driven by the seesaw INT line, while joystick
+ * axes are always polled since the seesaw ADC has no interrupt source.
+ * Without an interrupt, the driver falls back to fully polling mode.
  */
 
 #include <linux/unaligned.h>
@@ -21,6 +23,7 @@
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 
@@ -31,12 +34,14 @@
 #define SEESAW_GPIO_DIRCLR_BULK		0x0103
 #define SEESAW_GPIO_BULK		0x0104
 #define SEESAW_GPIO_BULK_SET		0x0105
+#define SEESAW_GPIO_INTENSET		0x0108
 #define SEESAW_GPIO_PULLENSET		0x010b
 
 #define SEESAW_STATUS_HW_ID		0x0001
 #define SEESAW_STATUS_SWRST		0x007f
 
 #define SEESAW_ADC_OFFSET		0x07
+#define SEESAW_ADC_REG(ch)		(SEESAW_ADC_BASE | (SEESAW_ADC_OFFSET + (ch)))
 
 #define SEESAW_BUTTON_A			0x05
 #define SEESAW_BUTTON_B			0x01
@@ -67,12 +72,6 @@ struct seesaw_gamepad {
 	u32 button_state;
 };
 
-struct seesaw_data {
-	u16 x;
-	u16 y;
-	u32 button_state;
-};
-
 static const struct key_entry seesaw_buttons_new[] = {
 	{ KE_KEY, SEESAW_BUTTON_A, .keycode = BTN_SOUTH },
 	{ KE_KEY, SEESAW_BUTTON_B, .keycode = BTN_EAST },
@@ -142,39 +141,61 @@ static int seesaw_register_write_u32(struct i2c_client *client, u16 reg,
 	return 0;
 }
 
-static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data)
+static int seesaw_report_buttons(struct seesaw_gamepad *private)
 {
-	__be16 adc_data;
+	struct input_dev *input = private->input_dev;
+	unsigned long changed;
+	u32 button_state;
 	__be32 read_buf;
-	int err;
+	int err, i;
 
-	err = seesaw_register_read(client, SEESAW_GPIO_BULK,
+	err = seesaw_register_read(private->i2c_client, SEESAW_GPIO_BULK,
 				   &read_buf, sizeof(read_buf));
 	if (err)
 		return err;
 
-	data->button_state = ~be32_to_cpu(read_buf);
+	button_state = ~be32_to_cpu(read_buf) & SEESAW_BUTTON_MASK;
+	changed = private->button_state ^ button_state;
+	private->button_state = button_state;
+
+	for_each_set_bit(i, &changed, fls(SEESAW_BUTTON_MASK)) {
+		if (!sparse_keymap_report_event(input, i,
+						button_state & BIT(i), false))
+			dev_err_ratelimited(&input->dev,
+						"failed to report keymap event");
+	}
+
+	input_sync(input);
+	return 0;
+}
+
+static int seesaw_report_axes(struct seesaw_gamepad *private)
+{
+	struct input_dev *input = private->input_dev;
+	__be16 adc_data;
+	int err;
 
-	err = seesaw_register_read(client,
-				   SEESAW_ADC_BASE |
-					(SEESAW_ADC_OFFSET + SEESAW_ANALOG_X),
+	err = seesaw_register_read(private->i2c_client,
+				   SEESAW_ADC_REG(SEESAW_ANALOG_X),
 				   &adc_data, sizeof(adc_data));
 	if (err)
 		return err;
+
 	/*
 	 * ADC reads left as max and right as 0, must be reversed since kernel
 	 * expects reports in opposite order.
 	 */
-	data->x = SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data);
+	input_report_abs(input, ABS_X,
+			 SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data));
 
-	err = seesaw_register_read(client,
-				   SEESAW_ADC_BASE |
-					(SEESAW_ADC_OFFSET + SEESAW_ANALOG_Y),
+	err = seesaw_register_read(private->i2c_client,
+				   SEESAW_ADC_REG(SEESAW_ANALOG_Y),
 				   &adc_data, sizeof(adc_data));
 	if (err)
 		return err;
 
-	data->y = be16_to_cpu(adc_data);
+	input_report_abs(input, ABS_Y, be16_to_cpu(adc_data));
+	input_sync(input);
 
 	return 0;
 }
@@ -182,42 +203,72 @@ static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data)
 static int seesaw_open(struct input_dev *input)
 {
 	struct seesaw_gamepad *private = input_get_drvdata(input);
+	int err;
 
 	private->button_state = 0;
 
+	if (private->i2c_client->irq) {
+		/*
+		 * Read and report current button state before enabling the
+		 * edge-triggered IRQ. This deasserts any pending INT already
+		 * latched by the chip since probe(), preventing the IRQ line
+		 * from being stuck low on the first open.
+		 */
+		err = seesaw_report_buttons(private);
+		if (err)
+			return err;
+
+		enable_irq(private->i2c_client->irq);
+	}
+
 	return 0;
 }
 
+static void seesaw_close(struct input_dev *input)
+{
+	struct seesaw_gamepad *private = input_get_drvdata(input);
+
+	if (private->i2c_client->irq)
+		disable_irq(private->i2c_client->irq);
+}
+
 static void seesaw_poll(struct input_dev *input)
 {
 	struct seesaw_gamepad *private = input_get_drvdata(input);
-	struct seesaw_data data;
-	unsigned long changed;
-	int err, i;
+	int err;
 
-	err = seesaw_read_data(private->i2c_client, &data);
+	err = seesaw_report_axes(private);
 	if (err) {
 		dev_err_ratelimited(&input->dev,
-				    "failed to read joystick state: %d\n", err);
+					"failed to read joystick axes: %d\n", err);
 		return;
 	}
 
-	input_report_abs(input, ABS_X, data.x);
-	input_report_abs(input, ABS_Y, data.y);
+	/*
+	 * In interrupt mode, buttons are reported exclusively by
+	 * seesaw_irq_thread() to avoid concurrent access to button_state.
+	 */
+	if (!private->i2c_client->irq) {
+		err = seesaw_report_buttons(private);
+		if (err)
+			dev_err_ratelimited(&input->dev,
+					"failed to read button state: %d\n", err);
+	}
+}
 
-	data.button_state &= SEESAW_BUTTON_MASK;
-	changed = private->button_state ^ data.button_state;
-	private->button_state = data.button_state;
+static irqreturn_t seesaw_irq_thread(int irq, void *dev_id)
+{
+	struct seesaw_gamepad *private = dev_id;
+	int err;
 
-	for_each_set_bit(i, &changed, fls(SEESAW_BUTTON_MASK)) {
-		if (!sparse_keymap_report_event(input, i,
-						data.button_state & BIT(i),
-						false))
-			dev_err_ratelimited(&input->dev,
-					    "failed to report keymap event");
+	err = seesaw_report_buttons(private);
+	if (err) {
+		dev_err_ratelimited(&private->input_dev->dev,
+					"failed to read button state: %d\n", err);
+		return IRQ_NONE;
 	}
 
-	input_sync(input);
+	return IRQ_HANDLED;
 }
 
 static int seesaw_probe(struct i2c_client *client)
@@ -268,6 +319,7 @@ static int seesaw_probe(struct i2c_client *client)
 	seesaw->input_dev->name = "Adafruit Seesaw Gamepad";
 	seesaw->input_dev->phys = "i2c/" SEESAW_DEVICE_NAME;
 	seesaw->input_dev->open = seesaw_open;
+	seesaw->input_dev->close = seesaw_close;
 	input_set_drvdata(seesaw->input_dev, seesaw);
 	input_set_abs_params(seesaw->input_dev, ABS_X,
 			     0, SEESAW_JOYSTICK_MAX_AXIS,
@@ -289,6 +341,19 @@ static int seesaw_probe(struct i2c_client *client)
 	input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX);
 	input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
 
+	if (client->irq) {
+		err = seesaw_register_write_u32(client, SEESAW_GPIO_INTENSET, SEESAW_BUTTON_MASK);
+		if (err)
+			return dev_err_probe(&client->dev, err,
+						"failed to enable hardware interrupts\n");
+
+		err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+						seesaw_irq_thread, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+						SEESAW_DEVICE_NAME, seesaw);
+		if (err)
+			return dev_err_probe(&client->dev, err, "failed to request IRQ\n");
+	}
+
 	err = input_register_device(seesaw->input_dev);
 	if (err)
 		return dev_err_probe(&client->dev, err, "failed to register joystick\n");

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/3] dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity
  2026-03-21 20:24 [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support charles.embedded
  2026-03-21 20:24 ` [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe() charles.embedded
  2026-03-21 20:24 ` [PATCH 2/3] Input: adafruit-seesaw - add interrupt support charles.embedded
@ 2026-03-21 20:24 ` charles.embedded
  2 siblings, 0 replies; 6+ messages in thread
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias

From: Charles Dias <charlesdias.cd@outlook.com>

The INT line is open-drain and asserts low on button GPIO changes, so
the binding should describe a falling-edge trigger rather than rising
edge. Also update the example to use IRQ_TYPE_EDGE_FALLING and add
interrupt-parent, and clarify that the driver can fall back to polling
when no IRQ is wired.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 .../bindings/input/adafruit,seesaw-gamepad.yaml      | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml b/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
index 5e86f6de6978..f0ebb326bf74 100644
--- a/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
+++ b/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
@@ -25,6 +25,11 @@ description: |
   SE -> Select
   X, A, B, Y -> Digital action buttons
 
+  The gamepad exposes button events through the seesaw GPIO block and joystick
+  axes through the seesaw ADC block. If the optional IRQ pin is wired, button
+  presses can be interrupt-driven while joystick axes remain polled. Without an
+  IRQ, the driver falls back to fully polled operation.
+
   Datasheet: https://cdn-learn.adafruit.com/downloads/pdf/gamepad-qt.pdf
   Product page: https://www.adafruit.com/product/5743
   Arduino Driver: https://github.com/adafruit/Adafruit_Seesaw
@@ -39,7 +44,9 @@ properties:
   interrupts:
     maxItems: 1
     description:
-      The gamepad's IRQ pin triggers a rising edge if interrupts are enabled.
+      Optional interrupt from the gamepad's open-drain INT pin. The device
+      asserts INT low on button GPIO changes when interrupts are enabled in the
+      seesaw firmware, so the host should typically use a falling-edge trigger.
 
 required:
   - compatible
@@ -57,7 +64,8 @@ examples:
 
         joystick@50 {
             compatible = "adafruit,seesaw-gamepad";
-            interrupts = <18 IRQ_TYPE_EDGE_RISING>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <18 IRQ_TYPE_EDGE_FALLING>;
             reg = <0x50>;
         };
     };

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/3] Input: adafruit-seesaw - add interrupt support
  2026-03-21 20:24 ` [PATCH 2/3] Input: adafruit-seesaw - add interrupt support charles.embedded
@ 2026-03-23  5:12   ` Dmitry Torokhov
  2026-03-24 13:59     ` Charles Dias
  0 siblings, 1 reply; 6+ messages in thread
From: Dmitry Torokhov @ 2026-03-23  5:12 UTC (permalink / raw)
  To: charles.embedded
  Cc: Anshul Dalal, Shuah Khan, Brigham Campbell, linux-input,
	linux-kernel, Charles Dias

Hi Charles,

On Sat, Mar 21, 2026 at 05:24:45PM -0300, charles.embedded@gmail.com wrote:
> @@ -289,6 +341,19 @@ static int seesaw_probe(struct i2c_client *client)
>  	input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX);
>  	input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
>  
> +	if (client->irq) {
> +		err = seesaw_register_write_u32(client, SEESAW_GPIO_INTENSET, SEESAW_BUTTON_MASK);
> +		if (err)
> +			return dev_err_probe(&client->dev, err,
> +						"failed to enable hardware interrupts\n");

Maybe this should be in seesaw_open()?

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/3] Input: adafruit-seesaw - add interrupt support
  2026-03-23  5:12   ` Dmitry Torokhov
@ 2026-03-24 13:59     ` Charles Dias
  0 siblings, 0 replies; 6+ messages in thread
From: Charles Dias @ 2026-03-24 13:59 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Anshul Dalal, Shuah Khan, Brigham Campbell, linux-input,
	linux-kernel, Charles Dias

On Mon, Mar 23, 2026 at 2:12 AM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>
> Hi Charles,
>
> On Sat, Mar 21, 2026 at 05:24:45PM -0300, charles.embedded@gmail.com wrote:
> > @@ -289,6 +341,19 @@ static int seesaw_probe(struct i2c_client *client)
> >       input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX);
> >       input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
> >
> > +     if (client->irq) {
> > +             err = seesaw_register_write_u32(client, SEESAW_GPIO_INTENSET, SEESAW_BUTTON_MASK);
> > +             if (err)
> > +                     return dev_err_probe(&client->dev, err,
> > +                                             "failed to enable hardware interrupts\n");
>
> Maybe this should be in seesaw_open()?
>
> Thanks.
>
> --
> Dmitry

Hi Dmitry. Thank you for your review!

Since this is a one-time setup, I believe it should remain as is
within the seesaw_probe() function, similar to other pin
configurations.

Please let me know if I'm missing any point here.

Best regards,
Charles Dias

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-03-24 13:59 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-21 20:24 [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support charles.embedded
2026-03-21 20:24 ` [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe() charles.embedded
2026-03-21 20:24 ` [PATCH 2/3] Input: adafruit-seesaw - add interrupt support charles.embedded
2026-03-23  5:12   ` Dmitry Torokhov
2026-03-24 13:59     ` Charles Dias
2026-03-21 20:24 ` [PATCH 3/3] dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity charles.embedded

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