From: charles.embedded@gmail.com
To: Anshul Dalal <anshulusr@gmail.com>,
Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Shuah Khan <shuah@kernel.org>,
Brigham Campbell <me@brighamcampbell.com>,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Charles Dias <charlesdias.cd@outlook.com>
Subject: [PATCH 2/3] Input: adafruit-seesaw - add interrupt support
Date: Sat, 21 Mar 2026 17:24:45 -0300 [thread overview]
Message-ID: <20260321202446.724277-3-charles.embedded@gmail.com> (raw)
In-Reply-To: <20260321202446.724277-1-charles.embedded@gmail.com>
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");
next prev parent reply other threads:[~2026-03-21 20:26 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
2026-03-23 5:12 ` [PATCH 2/3] Input: adafruit-seesaw - add interrupt support 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260321202446.724277-3-charles.embedded@gmail.com \
--to=charles.embedded@gmail.com \
--cc=anshulusr@gmail.com \
--cc=charlesdias.cd@outlook.com \
--cc=dmitry.torokhov@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=me@brighamcampbell.com \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox