]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
Input: ads7846 - restore half-duplex support
authorAaro Koskinen <aaro.koskinen@iki.fi>
Sun, 19 Apr 2026 16:18:47 +0000 (19:18 +0300)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Wed, 10 Jun 2026 18:31:29 +0000 (11:31 -0700)
On some boards, the SPI controller is limited to half-duplex and the driver
fails spamming "ads7846 spi2.1: spi_sync --> -22". Restore half-duplex
support with multiple SPI transfers.

Fixes: 9c9509717b53 ("Input: ads7846 - convert to full duplex")
Signed-off-by: Aaro Koskinen <aaro.koskinen@iki.fi>
Link: https://patch.msgid.link/20260419161848.825831-2-aaro.koskinen@iki.fi
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/touchscreen/ads7846.c

index 4b39f7212d35c6111033fee95d0670251fc13ea6..d3b529333ca2d4682351bd209d6d8558a5f90da4 100644 (file)
@@ -134,6 +134,9 @@ struct ads7846 {
        bool                    disabled;       /* P: lock */
        bool                    suspended;      /* P: lock */
 
+       int                     (*setup_spi_msg)(struct ads7846 *ts,
+                                       const struct ads7846_platform_data *pdata);
+       void                    (*read_state)(struct ads7846 *ts);
        int                     (*filter)(void *data, int data_idx, int *val);
        void                    *filter_data;
        int                     (*get_pendown_state)(void);
@@ -789,6 +792,22 @@ static int ads7846_filter(struct ads7846 *ts)
        return 0;
 }
 
+static int ads7846_filter_one(struct ads7846 *ts, unsigned int cmd_idx)
+{
+       struct ads7846_packet *packet = ts->packet;
+       struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+       int action, val;
+
+       val = ads7846_get_value(&packet->rx[l->offset + l->count - 1]);
+       action = ts->filter(ts->filter_data, cmd_idx, &val);
+       if (action == ADS7846_FILTER_REPEAT)
+               return -EAGAIN;
+       else if (action != ADS7846_FILTER_OK)
+               return -EIO;
+       ads7846_set_cmd_val(ts, cmd_idx, val);
+       return 0;
+}
+
 static void ads7846_wait_for_hsync(struct ads7846 *ts)
 {
        if (ts->wait_for_sync) {
@@ -811,6 +830,45 @@ static void ads7846_wait_for_hsync(struct ads7846 *ts)
                cpu_relax();
 }
 
+static void ads7846_halfd_read_state(struct ads7846 *ts)
+{
+       struct ads7846_packet *packet = ts->packet;
+       int msg_idx = 0;
+
+       packet->ignore = false;
+
+       while (msg_idx < ts->msg_count) {
+               int error;
+
+               ads7846_wait_for_hsync(ts);
+
+               error = spi_sync(ts->spi, &ts->msg[msg_idx]);
+               if (error) {
+                       dev_err_ratelimited(&ts->spi->dev, "spi_sync --> %d\n",
+                                           error);
+                       packet->ignore = true;
+                       return;
+               }
+
+               /*
+                * Last message is power down request, no need to convert
+                * or filter the value.
+                */
+               if (msg_idx == ts->msg_count - 1)
+                       break;
+
+               error = ads7846_filter_one(ts, msg_idx);
+               if (error == -EAGAIN) {
+                       continue;
+               } else if (error) {
+                       packet->ignore = true;
+                       msg_idx = ts->msg_count - 1;
+               } else {
+                       msg_idx++;
+               }
+       }
+}
+
 static void ads7846_read_state(struct ads7846 *ts)
 {
        struct ads7846_packet *packet = ts->packet;
@@ -939,7 +997,7 @@ static irqreturn_t ads7846_irq(int irq, void *handle)
        while (!ts->stopped && get_pendown_state(ts)) {
 
                /* pen is down, continue with the measurement */
-               ads7846_read_state(ts);
+               ts->read_state(ts);
 
                if (!ts->stopped)
                        ads7846_report_state(ts);
@@ -1022,6 +1080,102 @@ static int ads7846_setup_pendown(struct spi_device *spi,
        return 0;
 }
 
+/*
+ * Set up the transfers to read touchscreen state; this assumes we
+ * use formula #2 for pressure, not #3.
+ */
+static int ads7846_halfd_spi_msg(struct ads7846 *ts,
+                                const struct ads7846_platform_data *pdata)
+{
+       struct spi_message *m = ts->msg;
+       struct spi_transfer *x = ts->xfer;
+       struct ads7846_packet *packet = ts->packet;
+       int vref = pdata->keep_vref_on;
+       unsigned int offset = 0;
+       unsigned int cmd_idx, b;
+       size_t size = 0;
+
+       if (pdata->settle_delay_usecs)
+               packet->count = 2;
+       else
+               packet->count = 1;
+
+       if (ts->model == 7846)
+               packet->cmds = 5; /* x, y, z1, z2, pwdown */
+       else
+               packet->cmds = 3; /* x, y, pwdown */
+
+       for (cmd_idx = 0; cmd_idx < packet->cmds; cmd_idx++) {
+               struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+               unsigned int max_count;
+
+               if (cmd_idx == packet->cmds - 1) {
+                       cmd_idx = ADS7846_PWDOWN;
+                       max_count = 1;
+               } else {
+                       max_count = packet->count;
+               }
+
+               l->offset = offset;
+               offset += max_count;
+               l->count = max_count;
+               l->skip = 0;
+               size += sizeof(*packet->rx) * max_count;
+       }
+
+       /* We use two transfers per command. */
+       if (ARRAY_SIZE(ts->xfer) < offset * 2)
+               return -ENOMEM;
+
+       packet->rx = devm_kzalloc(&ts->spi->dev, size, GFP_KERNEL);
+       if (!packet->rx)
+               return -ENOMEM;
+
+       if (ts->model == 7873) {
+               /*
+                * The AD7873 is almost identical to the ADS7846
+                * keep VREF off during differential/ratiometric
+                * conversion modes.
+                */
+               ts->model = 7846;
+               vref = 0;
+       }
+
+       ts->msg_count = 0;
+
+       for (cmd_idx = 0; cmd_idx < packet->cmds; cmd_idx++) {
+               struct ads7846_buf_layout *l = &packet->l[cmd_idx];
+               u8 cmd;
+
+               ts->msg_count++;
+               spi_message_init(m);
+               m->context = ts;
+
+               if (cmd_idx == packet->cmds - 1)
+                       cmd_idx = ADS7846_PWDOWN;
+
+               cmd = ads7846_get_cmd(cmd_idx, vref);
+
+               for (b = 0; b < l->count; b++) {
+                       packet->rx[l->offset + b].cmd = cmd;
+                       x->tx_buf = &packet->rx[l->offset + b].cmd;
+                       x->len = 1;
+                       spi_message_add_tail(x, m);
+                       x++;
+                       x->rx_buf = &packet->rx[l->offset + b].data;
+                       x->len = 2;
+                       if (b < l->count - 1 && l->count > 1) {
+                               x->delay.value = pdata->settle_delay_usecs;
+                               x->delay.unit = SPI_DELAY_UNIT_USECS;
+                       }
+                       spi_message_add_tail(x, m);
+                       x++;
+               }
+               m++;
+       }
+       return 0;
+}
+
 /*
  * Set up the transfers to read touchscreen state; this assumes we
  * use formula #2 for pressure, not #3.
@@ -1236,6 +1390,14 @@ static int ads7846_probe(struct spi_device *spi)
        if (!ts)
                return -ENOMEM;
 
+       if (spi->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) {
+               ts->setup_spi_msg = ads7846_halfd_spi_msg;
+               ts->read_state    = ads7846_halfd_read_state;
+       } else {
+               ts->setup_spi_msg = ads7846_setup_spi_msg;
+               ts->read_state    = ads7846_read_state;
+       }
+
        packet = devm_kzalloc(dev, sizeof(struct ads7846_packet), GFP_KERNEL);
        if (!packet)
                return -ENOMEM;
@@ -1330,7 +1492,9 @@ static int ads7846_probe(struct spi_device *spi)
                ts->core_prop.swap_x_y = true;
        }
 
-       ads7846_setup_spi_msg(ts, pdata);
+       err = ts->setup_spi_msg(ts, pdata);
+       if (err)
+               return err;
 
        ts->reg = devm_regulator_get(dev, "vcc");
        if (IS_ERR(ts->reg)) {