Input: ads7846 - restore half-duplex support

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: 9c9509717b ("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>
This commit is contained in:
Aaro Koskinen 2026-04-19 19:18:47 +03:00 committed by Dmitry Torokhov
parent 17222f981c
commit 7698e338f4

View 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)) {