stmfts: update touch handling

This commit is contained in:
tmakin committed 2026-10-10 11:18:03 -05:00
1 parent 3e090e8a5a
commit 08bd2cf2fd
1 file changed
+282 -86
+282 -86
View File
@@ -66,6 +66,7 @@
#define STMFTS_EV_SLEEP_OUT_CONTROLLER_READY 0x11
#define STMFTS_EV_STATUS 0x16
#define STMFTS_EV_DEBUG 0xdb
#define STMFTM4_PRESSURE_MAX 500
/* FTM4 done status */
#define STMFTM4_STATUS_MS_CX_TUNING_DONE 0x01
@@ -148,11 +149,22 @@ struct stmfts_data {
bool hover_enabled;
bool stylus_enabled;
bool running;
unsigned int touch_count;
unsigned int n_events;
bool palm_pressed;
u8 palm_id;
u8 finger_state[STMFTS_MAX_FINGERS];
bool coord_factor;
u32 x_real_max;
u32 y_real_max;
u32 x_edge;
u32 y_edge;
};
struct stmfts_chip_ops {
int (*configure)(struct stmfts_data *sdata);
int (*setup_input)(struct stmfts_data *sdata);
int (*read_events)(struct stmfts_data *sdata);
void (*parse_events)(struct stmfts_data *sdata);
int (*set_scan)(struct stmfts_data *sdata, bool enable);
int (*set_hover)(struct stmfts_data *sdata, bool enable);
@@ -223,48 +235,47 @@ static int stmfts_read_events(struct stmfts_data *sdata)
return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
}
static int ftm4_write_reg(struct stmfts_data *sdata, unsigned char *reg,
unsigned short num_com)
static int ftm4_write_reg(struct stmfts_data *sdata, u8 *reg, unsigned int len)
{
struct i2c_msg xfer_msg[1] = {
struct i2c_msg msgs[] = {
{
.addr = sdata->client->addr,
.len = num_com,
.len = len,
.buf = reg,
},
};
int ret;
ret = i2c_transfer(sdata->client->adapter, xfer_msg, 1);
ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
return ret == 1 ? 0 : -EIO;
return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
}
static int ftm4_read_reg(struct stmfts_data *sdata, unsigned char *reg,
int cnum, unsigned char *buf, int num)
static int ftm4_read_reg(struct stmfts_data *sdata, u8 *reg, unsigned int wlen,
u8 *buf, unsigned int rlen)
{
struct i2c_msg xfer_msg[2] = {
struct i2c_msg msgs[] = {
{
.addr = sdata->client->addr,
.len = cnum,
.len = wlen,
.buf = reg,
},
{
.addr = sdata->client->addr,
.flags = I2C_M_RD,
.len = num,
.len = rlen,
.buf = buf,
},
};
int ret;
ret = i2c_transfer(sdata->client->adapter, xfer_msg, 2);
ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
return ret == 2 ? 0 : -EIO;
return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
}
/* FTS4 event handling functions */
@@ -387,70 +398,6 @@ static void stmfts5_report_contact_release(struct stmfts_data *sdata,
input_mt_report_slot_inactive(sdata->input);
}
/* FTM4 event handling functions */
static void stmftm4_report_contact_event(struct stmfts_data *sdata,
const u8 event[])
{
u8 slot = event[0] >> 4;
u16 x = (event[1] << 4) | (event[3] >> 4);
u16 y = (event[2] << 4) | (event[3] & 0x0f);
unsigned int gain = event[6] & 0x3f;
unsigned int z = (event[4] | (event[5] << 8)) << 6;
unsigned int orient = ((event[7] & 0x80) >> 5) | (event[6] >> 6);
if (gain == 0 || gain == 0x3f)
gain = 0x40;
z /= gain + 0x40;
input_mt_slot(sdata->input, slot);
input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true);
input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
input_report_abs(sdata->input, ABS_MT_PRESSURE, z);
input_report_abs(sdata->input, ABS_MT_ORIENTATION, orient);
}
static void stmftm4_parse_events(struct stmfts_data *sdata)
{
int i;
for (i = 0; i < STMFTS_STACK_DEPTH; i++) {
u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
switch (event[0]) {
case STMFTS_EV_CONTROLLER_READY:
case STMFTS_EV_STATUS:
complete(&sdata->cmd_done);
fallthrough;
case STMFTS_EV_NO_EVENT:
case STMFTS_EV_DEBUG:
goto sync;
}
switch (event[0] & STMFTS_MASK_EVENT_ID) {
case STMFTS_EV_MULTI_TOUCH_ENTER:
case STMFTS_EV_MULTI_TOUCH_MOTION:
stmftm4_report_contact_event(sdata, event);
break;
case STMFTS_EV_MULTI_TOUCH_LEAVE:
stmfts_report_contact_release(sdata, event);
break;
case STMFTS_EV_ERROR:
dev_warn(&sdata->client->dev,
"error code: %02x %02x %02x\n",
event[0], event[1], event[2]);
break;
default:
dev_err(&sdata->client->dev,
"unknown event %#02x\n", event[0]);
break;
}
}
sync:
input_sync(sdata->input);
}
static void stmfts_report_hover_event(struct stmfts_data *sdata,
const u8 event[])
{
@@ -464,6 +411,219 @@ static void stmfts_report_hover_event(struct stmfts_data *sdata,
input_sync(sdata->input);
}
static void stmftm4_release_all(struct stmfts_data *sdata)
{
unsigned int i;
for (i = 0; i < STMFTS_MAX_FINGERS; i++) {
input_mt_slot(sdata->input, i);
input_mt_report_slot_inactive(sdata->input);
sdata->finger_state[i] = STMFTS_EV_MULTI_TOUCH_LEAVE;
}
sdata->touch_count = 0;
input_report_key(sdata->input, BTN_TOUCH, 0);
input_report_key(sdata->input, BTN_TOOL_FINGER, 0);
}
static void stmftm4_factor_coords(struct stmfts_data *sdata, int *x, int *y)
{
unsigned int x_work = max_t(unsigned int, *x, sdata->x_edge);
unsigned int y_work = max_t(unsigned int, *y, sdata->y_edge);
unsigned int x_span = sdata->x_real_max - sdata->x_edge;
unsigned int y_span = sdata->y_real_max - sdata->y_edge;
unsigned int x_max = input_abs_get_max(sdata->input, ABS_MT_POSITION_X);
unsigned int y_max = input_abs_get_max(sdata->input, ABS_MT_POSITION_Y);
if (!x_span || !y_span)
return;
x_work = min(x_work, sdata->x_real_max) - sdata->x_edge;
y_work = min(y_work, sdata->y_real_max) - sdata->y_edge;
*x = x_work * (x_max * 1000 / x_span) / 1000;
*y = y_work * (y_max * 1000 / y_span) / 1000;
}
static int stmftm4_orientation(const u8 event[])
{
unsigned int code = ((event[7] & 0x80) >> 5) | (event[6] >> 6);
switch (code) {
case 1: return 45000;
case 2: return 90000;
case 3: return -45000;
case 4: return 22500;
case 5: return 67500;
case 6: return -22500;
case 7: return -67500;
default: return 0;
}
}
static void stmftm4_report_contact_event(struct stmfts_data *sdata,
const u8 event[], u8 slot, u8 id)
{
int x = (event[1] << 4) | (event[3] >> 4);
int y = (event[2] << 4) | (event[3] & 0x0f);
unsigned int gain = event[6] & 0x3f;
unsigned int z = (event[4] | (event[5] << 8)) << 6;
if (gain == 0 || gain == 0x3f)
gain = 0x40;
z /= gain + 0x40;
if (z > STMFTM4_PRESSURE_MAX) {
dev_info(&sdata->client->dev, "palm detected on slot %u\n", slot);
stmftm4_release_all(sdata);
sdata->palm_id = slot;
sdata->palm_pressed = true;
return;
}
if (sdata->coord_factor)
stmftm4_factor_coords(sdata, &x, &y);
input_mt_slot(sdata->input, slot);
input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true);
touchscreen_report_pos(sdata->input, &sdata->prop, x, y, true);
input_report_abs(sdata->input, ABS_MT_PRESSURE, z);
input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, 48);
input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, 48);
input_report_abs(sdata->input, ABS_MT_ORIENTATION,
stmftm4_orientation(event));
input_report_key(sdata->input, BTN_TOUCH, 1);
input_report_key(sdata->input, BTN_TOOL_FINGER, 1);
sdata->finger_state[slot] = id;
}
static void stmftm4_report_release(struct stmfts_data *sdata, u8 slot)
{
if (slot >= STMFTS_MAX_FINGERS)
return;
input_mt_slot(sdata->input, slot);
input_mt_report_slot_inactive(sdata->input);
sdata->finger_state[slot] = STMFTS_EV_MULTI_TOUCH_LEAVE;
if (sdata->touch_count)
sdata->touch_count--;
if (!sdata->touch_count) {
input_report_key(sdata->input, BTN_TOUCH, 0);
input_report_key(sdata->input, BTN_TOOL_FINGER, 0);
}
}
static int stmftm4_read_events(struct stmfts_data *sdata)
{
u8 count_reg[3] = { STMFTS_WRITE_REG, 0x00, 0x23 };
u8 raw[2] = { };
u8 cmd = STMFTS_READ_ALL_EVENT;
unsigned int count;
int ret;
ret = ftm4_read_reg(sdata, count_reg, sizeof(count_reg), raw, sizeof(raw));
if (ret)
return ret;
/* Old driver shifts the little-endian short, leaving the second byte. */
count = raw[1] / 2;
if (count > STMFTS_STACK_DEPTH)
count = STMFTS_STACK_DEPTH;
sdata->n_events = count;
if (!count)
return 0;
memset(sdata->data, 0, count * STMFTS_EVENT_SIZE);
return ftm4_read_reg(sdata, &cmd, 1, sdata->data,
count * STMFTS_EVENT_SIZE);
}
static void stmftm4_parse_events(struct stmfts_data *sdata)
{
unsigned int i;
for (i = 0; i < sdata->n_events; i++) {
u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
u8 id = event[0] & STMFTS_MASK_EVENT_ID;
u8 slot = event[0] >> 4;
if (id < STMFTS_EV_MULTI_TOUCH_ENTER ||
id > STMFTS_EV_MULTI_TOUCH_MOTION) {
id = event[0];
slot = event[1] & 0x0f;
}
if (slot >= STMFTS_MAX_FINGERS &&
(id == STMFTS_EV_MULTI_TOUCH_ENTER ||
id == STMFTS_EV_MULTI_TOUCH_MOTION ||
id == STMFTS_EV_MULTI_TOUCH_LEAVE ||
id == STMFTS_EV_HOVER_LEAVE))
continue;
switch (id) {
case STMFTS_EV_NO_EVENT:
break;
case STMFTS_EV_ERROR:
if (event[1] == 0x08)
dev_err(&sdata->client->dev,
"auto tune failed: %02x\n", event[2]);
else if (event[1] == 0x09)
dev_err(&sdata->client->dev, "sync detect failed\n");
else
dev_warn(&sdata->client->dev,
"error code: %02x %02x %02x\n",
event[0], event[1], event[2]);
break;
case STMFTS_EV_HOVER_ENTER:
case STMFTS_EV_HOVER_MOTION:
break;
case STMFTS_EV_HOVER_LEAVE:
stmftm4_report_release(sdata, slot);
break;
case STMFTS_EV_MULTI_TOUCH_ENTER:
sdata->touch_count++;
fallthrough;
case STMFTS_EV_MULTI_TOUCH_MOTION:
if (!sdata->touch_count) {
stmftm4_release_all(sdata);
break;
}
if (id == STMFTS_EV_MULTI_TOUCH_MOTION &&
sdata->finger_state[slot] == STMFTS_EV_MULTI_TOUCH_LEAVE)
break;
if (sdata->palm_pressed)
break;
stmftm4_report_contact_event(sdata, event, slot, id);
break;
case STMFTS_EV_MULTI_TOUCH_LEAVE:
if (sdata->palm_pressed) {
if (sdata->palm_id == slot) {
stmftm4_release_all(sdata);
sdata->palm_pressed = false;
}
break;
}
if (!sdata->touch_count) {
stmftm4_release_all(sdata);
break;
}
stmftm4_report_release(sdata, slot);
break;
case STMFTS_EV_CONTROLLER_READY:
case STMFTS_EV_STATUS:
complete(&sdata->cmd_done);
break;
default:
dev_dbg(&sdata->client->dev,
"event %02x %02x %02x %02x %02x %02x %02x %02x\n",
event[0], event[1], event[2], event[3],
event[4], event[5], event[6], event[7]);
break;
}
}
input_mt_sync_frame(sdata->input);
input_sync(sdata->input);
sdata->n_events = 0;
}
static void stmfts_report_key_event(struct stmfts_data *sdata, const u8 event[])
{
switch (event[2]) {
@@ -582,7 +742,7 @@ static irqreturn_t stmfts_irq_handler(int irq, void *dev)
guard(mutex)(&sdata->mutex);
err = stmfts_read_events(sdata);
err = sdata->ops->read_events(sdata);
if (unlikely(err))
dev_err(&sdata->client->dev,
"failed to read events: %d\n", err);
@@ -832,6 +992,7 @@ static int stmfts_read_system_info(struct stmfts_data *sdata)
sizeof(reg), reg);
if (err < 0)
return err;
if (err != sizeof(reg))
return -EIO;
@@ -863,7 +1024,7 @@ static int stmftm4_read_system_info(struct stmfts_data *sdata)
sdata->config_id = 0;
sdata->config_ver = 0;
dev_info(&sdata->client->dev, "FTM4: chip_id: %04x fw_ver: %04x\n",
dev_info(&sdata->client->dev, "chip_id: %04x fw_ver: %04x\n",
sdata->chip_id, sdata->fw_ver);
return 0;
@@ -965,6 +1126,7 @@ static int stmfts_configure(struct stmfts_data *sdata)
dev_warn(&sdata->client->dev,
"failed to perform self auto tune: %d\n", err);
dev_info(&sdata->client->dev, "calib\n");
err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION);
if (err)
return err;
@@ -990,10 +1152,11 @@ static int stmftm4_configure(struct stmfts_data *sdata)
if (err)
return err;
/* Trim low power oscillator */
/* Trim low power oscillator. No completion event. */
stmftm4_command(sdata, STMFTS_TRIM_LOW_POWER_OSCILLATOR);
msleep(200);
dev_info(&sdata->client->dev, "ms tuning\n");
err = stmftm4_command(sdata, STMFTS_MS_CX_TUNING);
if (!err)
err = stmftm4_wait_status(sdata, STMFTM4_STATUS_MS_CX_TUNING_DONE);
@@ -1001,6 +1164,7 @@ static int stmftm4_configure(struct stmfts_data *sdata)
dev_warn(&sdata->client->dev,
"failed to perform mutual auto tune: %d\n", err);
dev_info(&sdata->client->dev, "cx tuning\n");
err = stmftm4_command(sdata, STMFTS_SS_CX_TUNING);
if (!err)
err = stmftm4_wait_status(sdata, STMFTM4_STATUS_SS_CX_TUNING_DONE);
@@ -1136,10 +1300,8 @@ static void stmfts_power_off(void *data)
struct stmfts_data *sdata = data;
disable_irq(sdata->client->irq);
if (sdata->reset_gpio)
gpiod_set_value_cansleep(sdata->reset_gpio, 1);
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
}
@@ -1162,6 +1324,38 @@ static int stmfts_setup_input(struct stmfts_data *sdata)
INPUT_MT_DIRECT);
}
static int stmftm4_setup_input(struct stmfts_data *sdata)
{
struct device *dev = &sdata->client->dev;
u32 coords[2];
unsigned int i;
input_set_capability(sdata->input, EV_KEY, BTN_TOUCH);
input_set_capability(sdata->input, EV_KEY, BTN_TOOL_FINGER);
input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0,
STMFTM4_PRESSURE_MAX, 0, 0);
input_set_abs_params(sdata->input, ABS_MT_ORIENTATION,
-67500, 90000, 0, 0);
for (i = 0; i < STMFTS_MAX_FINGERS; i++)
sdata->finger_state[i] = STMFTS_EV_MULTI_TOUCH_LEAVE;
if (!device_property_read_u32_array(dev, "st,max-real-coords",
coords, 2)) {
sdata->coord_factor = true;
sdata->x_real_max = coords[0];
sdata->y_real_max = coords[1];
if (!device_property_read_u32_array(dev, "st,edge-offset",
coords, 2)) {
sdata->x_edge = coords[0];
sdata->y_edge = coords[1];
}
}
return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
INPUT_MT_DIRECT);
}
static int stmfts5_setup_input(struct stmfts_data *sdata)
{
struct device *dev = &sdata->client->dev;
@@ -1231,7 +1425,6 @@ static int stmfts_probe(struct i2c_client *client)
sdata->client = client;
mutex_init(&sdata->mutex);
init_completion(&sdata->cmd_done);
sdata->ops = i2c_get_match_data(client);
err = devm_regulator_bulk_get_const(dev,
@@ -1366,11 +1559,13 @@ static int stmfts5_chip_runtime_resume(struct stmfts_data *sdata)
err = i2c_smbus_write_byte(client, STMFTS_MS_CX_TUNING);
if (err)
dev_warn(dev, "MS_CX_TUNING failed: %d\n", err);
msleep(20);
err = i2c_smbus_write_byte(client, STMFTS_SS_CX_TUNING);
if (err)
dev_warn(dev, "SS_CX_TUNING failed: %d\n", err);
msleep(20);
/* Force calibration */
@@ -1426,6 +1621,7 @@ static const struct dev_pm_ops stmfts_pm_ops = {
static const struct stmfts_chip_ops stmfts4_ops = {
.configure = stmfts_configure,
.setup_input = stmfts_setup_input,
.read_events = stmfts_read_events,
.parse_events = stmfts_parse_events,
.set_scan = stmfts_set_scan,
.set_hover = stmfts_set_hover,
@@ -1437,6 +1633,7 @@ static const struct stmfts_chip_ops stmfts4_ops = {
static const struct stmfts_chip_ops stmfts5_ops = {
.configure = stmfts5_configure,
.setup_input = stmfts5_setup_input,
.read_events = stmfts_read_events,
.parse_events = stmfts5_parse_events,
.set_scan = stmfts5_set_scan,
.power_on = stmfts_chip_power_on,
@@ -1446,10 +1643,10 @@ static const struct stmfts_chip_ops stmfts5_ops = {
static const struct stmfts_chip_ops stmftm4_ops = {
.configure = stmftm4_configure,
.setup_input = stmfts_setup_input,
.setup_input = stmftm4_setup_input,
.read_events = stmftm4_read_events,
.parse_events = stmftm4_parse_events,
.set_scan = stmfts_set_scan,
.set_hover = stmfts_set_hover,
.power_on = stmftm4_power_on,
.runtime_suspend = stmftm4_runtime_suspend,
.runtime_resume = stmftm4_configure,
@@ -1485,7 +1682,6 @@ static struct i2c_driver stmfts_driver = {
.remove = stmfts_remove,
.id_table = stmfts_id,
};
module_i2c_driver(stmfts_driver);
MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");