stmfts: support FTM4 chips
Some FingerTipS chips use FTM4 variants like Ftm4cd60d Based loosely on https://github.com/android-linux-stable/bluecross/blob/android-msm-bluecross-4.9/drivers/input/touchscreen/stm/ftm4_ts.c and https://github.com/CTCaer/switch-l4t-kernel-4.9/blob/linux-dev/drivers/input/touchscreen/stm/ftm4_ts.c Assisted-by: Grok:grok-4.6
This commit is contained in:
1 parent
55d66b816f
commit
a52522ec20
1 file changed
+358
-69
@@ -6,7 +6,6 @@
|
||||
* Copyright David Heidelberg <david@ixit.cz>
|
||||
* Copyright Petr Hodina <petr.hodina@protonmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
@@ -32,6 +31,7 @@
|
||||
#define STMFTS_MS_MT_SENSE_ON 0x93
|
||||
#define STMFTS_SS_HOVER_SENSE_OFF 0x94
|
||||
#define STMFTS_SS_HOVER_SENSE_ON 0x95
|
||||
#define STMFTS_TRIM_LOW_POWER_OSCILLATOR 0x97
|
||||
#define STMFTS_MS_KEY_SENSE_OFF 0x9a
|
||||
#define STMFTS_MS_KEY_SENSE_ON 0x9b
|
||||
#define STMFTS_SYSTEM_RESET 0xa0
|
||||
@@ -40,11 +40,17 @@
|
||||
#define STMFTS_MS_CX_TUNING 0xa3
|
||||
#define STMFTS_SS_CX_TUNING 0xa4
|
||||
#define STMFTS5_SET_SCAN_MODE 0xa0
|
||||
#define STMFTS_WRITE_REG 0xB6
|
||||
#define STMFTM4_SWITCH_SENSE_MODE 0xC3
|
||||
#define STMFTM4_INT_ENABLE 0x48
|
||||
|
||||
/* FTS5 scan mode commands */
|
||||
#define STMFTS5_SCAN_MODE_ACTIVE 0x00
|
||||
#define STMFTS5_SCAN_ACTIVE_MULTI_TOUCH BIT(0)
|
||||
|
||||
/* FTM4 modes */
|
||||
#define STMFTM4_FINGER_MODE 0x01
|
||||
|
||||
/* events */
|
||||
#define STMFTS_EV_NO_EVENT 0x00
|
||||
#define STMFTS_EV_MULTI_TOUCH_DETECTED 0x02
|
||||
@@ -61,6 +67,10 @@
|
||||
#define STMFTS_EV_STATUS 0x16
|
||||
#define STMFTS_EV_DEBUG 0xdb
|
||||
|
||||
/* FTM4 done status */
|
||||
#define STMFTM4_STATUS_MS_CX_TUNING_DONE 0x01
|
||||
#define STMFTM4_STATUS_SS_CX_TUNING_DONE 0x02
|
||||
|
||||
/*
|
||||
* FTS5 events: the event ID is in the high nibble of event[0], the low
|
||||
* nibble carries additional payload (bits 5:4 of the touch major axis for
|
||||
@@ -100,7 +110,6 @@
|
||||
#define STMFTS_MASK_KEY_BACK 0x02
|
||||
|
||||
#define STMFTS_RESET_TIMEOUT_MS 300
|
||||
|
||||
#define STMFTS_EVENT_SIZE 8
|
||||
#define STMFTS_STACK_DEPTH 32
|
||||
#define STMFTS_DATA_MAX_SIZE (STMFTS_EVENT_SIZE * STMFTS_STACK_DEPTH)
|
||||
@@ -114,34 +123,26 @@ static const struct regulator_bulk_data stmfts_supplies[] = {
|
||||
|
||||
struct stmfts_data {
|
||||
const struct stmfts_chip_ops *ops;
|
||||
|
||||
struct i2c_client *client;
|
||||
struct input_dev *input;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct gpio_desc *mode_switch_gpio;
|
||||
struct led_classdev led_cdev;
|
||||
struct mutex mutex;
|
||||
|
||||
struct touchscreen_properties prop;
|
||||
|
||||
struct regulator_bulk_data *supplies;
|
||||
|
||||
/*
|
||||
* Presence of ledvdd will be used also to check
|
||||
* whether the LED is supported.
|
||||
*/
|
||||
struct regulator *ledvdd;
|
||||
|
||||
u16 chip_id;
|
||||
u8 chip_ver;
|
||||
u16 fw_ver;
|
||||
u8 config_id;
|
||||
u8 config_ver;
|
||||
|
||||
u8 data[STMFTS_DATA_MAX_SIZE];
|
||||
|
||||
struct completion cmd_done;
|
||||
|
||||
bool use_key;
|
||||
bool led_status;
|
||||
bool hover_enabled;
|
||||
@@ -155,6 +156,8 @@ struct stmfts_chip_ops {
|
||||
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);
|
||||
int (*power_on)(struct stmfts_data *sdata);
|
||||
int (*runtime_suspend)(struct stmfts_data *sdata);
|
||||
int (*runtime_resume)(struct stmfts_data *sdata);
|
||||
};
|
||||
|
||||
@@ -220,6 +223,50 @@ 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)
|
||||
{
|
||||
struct i2c_msg xfer_msg[1] = {
|
||||
{
|
||||
.addr = sdata->client->addr,
|
||||
.len = num_com,
|
||||
.buf = reg,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = i2c_transfer(sdata->client->adapter, xfer_msg, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret == 1 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int ftm4_read_reg(struct stmfts_data *sdata, unsigned char *reg,
|
||||
int cnum, unsigned char *buf, int num)
|
||||
{
|
||||
struct i2c_msg xfer_msg[2] = {
|
||||
{
|
||||
.addr = sdata->client->addr,
|
||||
.len = cnum,
|
||||
.buf = reg,
|
||||
},
|
||||
{
|
||||
.addr = sdata->client->addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = num,
|
||||
.buf = buf,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = i2c_transfer(sdata->client->adapter, xfer_msg, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret == 2 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
/* FTS4 event handling functions */
|
||||
static void stmfts_report_contact_event(struct stmfts_data *sdata,
|
||||
const u8 event[])
|
||||
@@ -233,7 +280,6 @@ static void stmfts_report_contact_event(struct stmfts_data *sdata,
|
||||
u8 area = event[7];
|
||||
|
||||
input_mt_slot(sdata->input, slot_id);
|
||||
|
||||
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);
|
||||
@@ -252,7 +298,6 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata,
|
||||
|
||||
input_mt_slot(sdata->input, slot_id);
|
||||
input_mt_report_slot_inactive(sdata->input);
|
||||
|
||||
input_sync(sdata->input);
|
||||
}
|
||||
|
||||
@@ -289,22 +334,18 @@ static void stmfts5_report_contact_event(struct stmfts_data *sdata,
|
||||
break;
|
||||
}
|
||||
fallthrough; /* Report as finger if stylus not enabled */
|
||||
|
||||
case STMFTS_TOUCH_TYPE_FINGER:
|
||||
case STMFTS_TOUCH_TYPE_GLOVE:
|
||||
tool = MT_TOOL_FINGER;
|
||||
break;
|
||||
|
||||
case STMFTS_TOUCH_TYPE_PALM:
|
||||
/* Palm touch - report but can be filtered by userspace */
|
||||
tool = MT_TOOL_PALM;
|
||||
break;
|
||||
|
||||
case STMFTS_TOUCH_TYPE_HOVER:
|
||||
tool = MT_TOOL_FINGER;
|
||||
distance = 255;
|
||||
break;
|
||||
|
||||
case STMFTS_TOUCH_TYPE_INVALID:
|
||||
default:
|
||||
dev_warn(&sdata->client->dev,
|
||||
@@ -328,7 +369,6 @@ static void stmfts5_report_contact_event(struct stmfts_data *sdata,
|
||||
|
||||
input_mt_slot(sdata->input, touch_id);
|
||||
input_mt_report_slot_state(sdata->input, tool, 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_TOUCH_MAJOR, maj);
|
||||
@@ -347,6 +387,70 @@ 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[])
|
||||
{
|
||||
@@ -357,7 +461,6 @@ static void stmfts_report_hover_event(struct stmfts_data *sdata,
|
||||
input_report_abs(sdata->input, ABS_X, x);
|
||||
input_report_abs(sdata->input, ABS_Y, y);
|
||||
input_report_abs(sdata->input, ABS_DISTANCE, z);
|
||||
|
||||
input_sync(sdata->input);
|
||||
}
|
||||
|
||||
@@ -368,15 +471,12 @@ static void stmfts_report_key_event(struct stmfts_data *sdata, const u8 event[])
|
||||
input_report_key(sdata->input, KEY_BACK, 0);
|
||||
input_report_key(sdata->input, KEY_MENU, 0);
|
||||
break;
|
||||
|
||||
case STMFTS_MASK_KEY_BACK:
|
||||
input_report_key(sdata->input, KEY_BACK, 1);
|
||||
break;
|
||||
|
||||
case STMFTS_MASK_KEY_MENU:
|
||||
input_report_key(sdata->input, KEY_MENU, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_warn(&sdata->client->dev,
|
||||
"unknown key event: %#02x\n", event[2]);
|
||||
@@ -399,7 +499,6 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
|
||||
case STMFTS_EV_STATUS:
|
||||
complete(&sdata->cmd_done);
|
||||
fallthrough;
|
||||
|
||||
case STMFTS_EV_NO_EVENT:
|
||||
case STMFTS_EV_DEBUG:
|
||||
return;
|
||||
@@ -410,28 +509,23 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
|
||||
case STMFTS_EV_MULTI_TOUCH_MOTION:
|
||||
stmfts_report_contact_event(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS_EV_MULTI_TOUCH_LEAVE:
|
||||
stmfts_report_contact_release(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS_EV_HOVER_ENTER:
|
||||
case STMFTS_EV_HOVER_LEAVE:
|
||||
case STMFTS_EV_HOVER_MOTION:
|
||||
stmfts_report_hover_event(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS_EV_KEY_STATUS:
|
||||
stmfts_report_key_event(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS_EV_ERROR:
|
||||
dev_warn(&sdata->client->dev,
|
||||
"error code: 0x%x%x%x%x%x%x",
|
||||
event[6], event[5], event[4],
|
||||
event[3], event[2], event[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(&sdata->client->dev,
|
||||
"unknown event %#02x\n", event[0]);
|
||||
@@ -448,7 +542,6 @@ static void stmfts5_parse_events(struct stmfts_data *sdata)
|
||||
case STMFTS5_EV_CONTROLLER_READY:
|
||||
complete(&sdata->cmd_done);
|
||||
fallthrough;
|
||||
|
||||
case STMFTS_EV_NO_EVENT:
|
||||
goto sync;
|
||||
}
|
||||
@@ -458,23 +551,19 @@ static void stmfts5_parse_events(struct stmfts_data *sdata)
|
||||
case STMFTS5_EV_USER_REPORT:
|
||||
case STMFTS5_EV_DEBUG:
|
||||
goto sync;
|
||||
|
||||
case STMFTS5_EV_MULTI_TOUCH_ENTER:
|
||||
case STMFTS5_EV_MULTI_TOUCH_MOTION:
|
||||
stmfts5_report_contact_event(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS5_EV_MULTI_TOUCH_LEAVE:
|
||||
stmfts5_report_contact_release(sdata, event);
|
||||
break;
|
||||
|
||||
case STMFTS5_EV_ERROR:
|
||||
dev_warn(&sdata->client->dev,
|
||||
"error code: 0x%x%x%x%x%x%x",
|
||||
event[6], event[5], event[4],
|
||||
event[3], event[2], event[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(&sdata->client->dev,
|
||||
"unknown FTS5 event %#02x\n", event[0]);
|
||||
@@ -707,7 +796,6 @@ static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
sdata->hover_enabled = hover;
|
||||
}
|
||||
|
||||
@@ -756,6 +844,99 @@ static int stmfts_read_system_info(struct stmfts_data *sdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stmftm4_read_system_info(struct stmfts_data *sdata)
|
||||
{
|
||||
unsigned char regAdd[3] = { STMFTS_WRITE_REG, 0x00, 0x04 };
|
||||
unsigned char val[7] = { };
|
||||
int ret;
|
||||
|
||||
ret = ftm4_read_reg(sdata, regAdd, 3, val, 7);
|
||||
if (ret < 0) {
|
||||
dev_err(&sdata->client->dev, "%s failed. ret: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sdata->chip_id = be16_to_cpup((__be16 *)&val[1]);
|
||||
sdata->chip_ver = 0;
|
||||
sdata->fw_ver = be16_to_cpup((__be16 *)&val[5]);
|
||||
sdata->config_id = 0;
|
||||
sdata->config_ver = 0;
|
||||
|
||||
dev_info(&sdata->client->dev, "FTM4: chip_id: %04x fw_ver: %04x\n",
|
||||
sdata->chip_id, sdata->fw_ver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stmftm4_command(struct stmfts_data *sdata, unsigned char cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ftm4_write_reg(sdata, &cmd, 1);
|
||||
dev_info(&sdata->client->dev, "FTS Command (%02X), ret = %d\n",
|
||||
cmd, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stmftm4_systemreset(struct stmfts_data *sdata)
|
||||
{
|
||||
unsigned char addr[4] = { STMFTS_WRITE_REG, 0x00, 0x28, 0x80 };
|
||||
int ret;
|
||||
|
||||
dev_info(&sdata->client->dev, "FTM4 SystemReset\n");
|
||||
ret = ftm4_write_reg(sdata, addr, 4);
|
||||
msleep(10);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stmftm4_wait_ready(struct stmfts_data *sdata)
|
||||
{
|
||||
u8 cmd = STMFTS_READ_ONE_EVENT;
|
||||
u8 event[STMFTS_EVENT_SIZE];
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
|
||||
do {
|
||||
int ret = ftm4_read_reg(sdata, &cmd, 1, event, sizeof(event));
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
if (event[0] == STMFTS_EV_CONTROLLER_READY)
|
||||
return 0;
|
||||
msleep(20);
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Poll 0x85 for {0x16, status, 0x00}. Tuning does not complete on the IRQ. */
|
||||
static int stmftm4_wait_status(struct stmfts_data *sdata, u8 status)
|
||||
{
|
||||
u8 cmd = STMFTS_READ_ONE_EVENT;
|
||||
u8 event[STMFTS_EVENT_SIZE];
|
||||
int retry = 200;
|
||||
|
||||
while (retry--) {
|
||||
int ret;
|
||||
|
||||
msleep(10);
|
||||
ret = ftm4_read_reg(sdata, &cmd, 1, event, sizeof(event));
|
||||
if (ret)
|
||||
return ret;
|
||||
if (event[0] == STMFTS_EV_STATUS && event[1] == status &&
|
||||
event[2] == 0)
|
||||
return 0;
|
||||
if (event[0] == STMFTS_EV_ERROR)
|
||||
dev_warn(&sdata->client->dev,
|
||||
"status error %02x %02x %02x\n",
|
||||
event[0], event[1], event[2]);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int stmfts_configure(struct stmfts_data *sdata)
|
||||
{
|
||||
int err;
|
||||
@@ -791,6 +972,117 @@ static int stmfts_configure(struct stmfts_data *sdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stmftm4_configure(struct stmfts_data *sdata)
|
||||
{
|
||||
u8 sense_mode[2] = { STMFTM4_SWITCH_SENSE_MODE, STMFTM4_FINGER_MODE };
|
||||
u8 int_enable[4] = { STMFTS_WRITE_REG, 0x00, 0x2C, STMFTM4_INT_ENABLE };
|
||||
int err;
|
||||
|
||||
err = stmftm4_read_system_info(sdata);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = stmftm4_systemreset(sdata);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = stmftm4_wait_ready(sdata);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Trim low power oscillator */
|
||||
stmftm4_command(sdata, STMFTS_TRIM_LOW_POWER_OSCILLATOR);
|
||||
msleep(200);
|
||||
|
||||
err = stmftm4_command(sdata, STMFTS_MS_CX_TUNING);
|
||||
if (!err)
|
||||
err = stmftm4_wait_status(sdata, STMFTM4_STATUS_MS_CX_TUNING_DONE);
|
||||
if (err)
|
||||
dev_warn(&sdata->client->dev,
|
||||
"failed to perform mutual auto tune: %d\n", err);
|
||||
|
||||
err = stmftm4_command(sdata, STMFTS_SS_CX_TUNING);
|
||||
if (!err)
|
||||
err = stmftm4_wait_status(sdata, STMFTM4_STATUS_SS_CX_TUNING_DONE);
|
||||
if (err)
|
||||
dev_warn(&sdata->client->dev,
|
||||
"failed to perform self auto tune: %d\n", err);
|
||||
|
||||
err = ftm4_write_reg(sdata, sense_mode, sizeof(sense_mode));
|
||||
if (err)
|
||||
return err;
|
||||
msleep(10);
|
||||
|
||||
err = stmftm4_command(sdata, STMFTS_MS_MT_SENSE_ON);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = stmftm4_command(sdata, STMFTS_CLEAR_EVENT_STACK);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return ftm4_write_reg(sdata, int_enable, sizeof(int_enable));
|
||||
}
|
||||
|
||||
static int stmfts_chip_power_on(struct stmfts_data *sdata)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (sdata->reset_gpio) {
|
||||
reinit_completion(&sdata->cmd_done);
|
||||
enable_irq(sdata->client->irq);
|
||||
if (!wait_for_completion_timeout(&sdata->cmd_done,
|
||||
msecs_to_jiffies(STMFTS_RESET_TIMEOUT_MS))) {
|
||||
dev_err(&sdata->client->dev, "controller not ready after reset");
|
||||
err = -ETIMEDOUT;
|
||||
goto err_disable_irq;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We do not know the real controller state (was it powered
|
||||
* off or reset). Let's hope that this is enough time to
|
||||
* initialize.
|
||||
*/
|
||||
msleep(70);
|
||||
enable_irq(sdata->client->irq);
|
||||
}
|
||||
|
||||
dev_info(&sdata->client->dev, "configure\n");
|
||||
err = sdata->ops->configure(sdata);
|
||||
if (err)
|
||||
goto err_disable_irq;
|
||||
|
||||
/*
|
||||
* At this point no one is using the touchscreen
|
||||
* and I don't really care about the return value
|
||||
*/
|
||||
(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_irq:
|
||||
disable_irq(sdata->client->irq);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stmftm4_power_on(struct stmfts_data *sdata)
|
||||
{
|
||||
int err;
|
||||
|
||||
/*
|
||||
* FTM4 does not signal ready on the IRQ until 0x2C is set, and it
|
||||
* has no sleep-in command. Poll inside configure, then enable the IRQ.
|
||||
*/
|
||||
msleep(20);
|
||||
dev_info(&sdata->client->dev, "configure\n");
|
||||
err = sdata->ops->configure(sdata);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
enable_irq(sdata->client->irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stmfts_power_on(struct stmfts_data *sdata)
|
||||
{
|
||||
int err;
|
||||
@@ -807,8 +1099,6 @@ static int stmfts_power_on(struct stmfts_data *sdata)
|
||||
return err;
|
||||
|
||||
if (sdata->reset_gpio) {
|
||||
reinit_completion(&sdata->cmd_done);
|
||||
|
||||
/*
|
||||
* The datasheet does not specify the power on time, but
|
||||
* considering that the reset time is < 10ms, sleep for 20ms
|
||||
@@ -816,42 +1106,13 @@ static int stmfts_power_on(struct stmfts_data *sdata)
|
||||
*/
|
||||
msleep(20);
|
||||
gpiod_set_value_cansleep(sdata->reset_gpio, 0);
|
||||
|
||||
enable_irq(sdata->client->irq);
|
||||
|
||||
if (!wait_for_completion_timeout(&sdata->cmd_done,
|
||||
msecs_to_jiffies(STMFTS_RESET_TIMEOUT_MS))) {
|
||||
dev_err(&sdata->client->dev, "controller not ready after reset");
|
||||
err = -ETIMEDOUT;
|
||||
goto err_disable_irq;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We do not know the real controller state (was it powered
|
||||
* off or reset). Let's hope that this is enough time to
|
||||
* initialize.
|
||||
*/
|
||||
msleep(70);
|
||||
|
||||
enable_irq(sdata->client->irq);
|
||||
}
|
||||
|
||||
err = sdata->ops->configure(sdata);
|
||||
err = sdata->ops->power_on(sdata);
|
||||
if (err)
|
||||
goto err_disable_irq;
|
||||
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
|
||||
sdata->supplies);
|
||||
|
||||
/*
|
||||
* At this point no one is using the touchscreen
|
||||
* and I don't really care about the return value
|
||||
*/
|
||||
(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_irq:
|
||||
disable_irq(sdata->client->irq);
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1061,12 +1322,22 @@ static void stmfts_remove(struct i2c_client *client)
|
||||
pm_runtime_disable(&client->dev);
|
||||
}
|
||||
|
||||
static int stmfts_chip_runtime_suspend(struct stmfts_data *sdata)
|
||||
{
|
||||
return i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
|
||||
}
|
||||
|
||||
static int stmftm4_runtime_suspend(struct stmfts_data *sdata)
|
||||
{
|
||||
return i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_OFF);
|
||||
}
|
||||
|
||||
static int stmfts_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct stmfts_data *sdata = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
|
||||
ret = sdata->ops->runtime_suspend(sdata);
|
||||
if (ret)
|
||||
dev_warn(dev, "failed to suspend device: %d\n", ret);
|
||||
|
||||
@@ -1082,7 +1353,7 @@ static int stmfts5_chip_runtime_resume(struct stmfts_data *sdata)
|
||||
{
|
||||
struct i2c_client *client = sdata->client;
|
||||
struct device *dev = &client->dev;
|
||||
u8 int_enable_cmd[4] = { 0xB6, 0x00, 0x2C, 0x01 };
|
||||
u8 int_enable_cmd[4] = { STMFTS_WRITE_REG, 0x00, 0x2C, 0x01 };
|
||||
int err;
|
||||
|
||||
err = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
|
||||
@@ -1106,6 +1377,7 @@ static int stmfts5_chip_runtime_resume(struct stmfts_data *sdata)
|
||||
err = i2c_smbus_write_byte(client, STMFTS_FULL_FORCE_CALIBRATION);
|
||||
if (err)
|
||||
dev_warn(dev, "FORCE_CALIBRATION failed: %d\n", err);
|
||||
|
||||
msleep(50);
|
||||
|
||||
/* Enable controller interrupts */
|
||||
@@ -1157,6 +1429,8 @@ static const struct stmfts_chip_ops stmfts4_ops = {
|
||||
.parse_events = stmfts_parse_events,
|
||||
.set_scan = stmfts_set_scan,
|
||||
.set_hover = stmfts_set_hover,
|
||||
.power_on = stmfts_chip_power_on,
|
||||
.runtime_suspend = stmfts_chip_runtime_suspend,
|
||||
.runtime_resume = stmfts_chip_runtime_resume,
|
||||
};
|
||||
|
||||
@@ -1165,13 +1439,27 @@ static const struct stmfts_chip_ops stmfts5_ops = {
|
||||
.setup_input = stmfts5_setup_input,
|
||||
.parse_events = stmfts5_parse_events,
|
||||
.set_scan = stmfts5_set_scan,
|
||||
.power_on = stmfts_chip_power_on,
|
||||
.runtime_suspend = stmfts_chip_runtime_suspend,
|
||||
.runtime_resume = stmfts5_chip_runtime_resume,
|
||||
};
|
||||
|
||||
static const struct stmfts_chip_ops stmftm4_ops = {
|
||||
.configure = stmftm4_configure,
|
||||
.setup_input = stmfts_setup_input,
|
||||
.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,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id stmfts_of_match[] = {
|
||||
{ .compatible = "st,stmfts", .data = &stmfts4_ops },
|
||||
{ .compatible = "st,stmfts5", .data = &stmfts5_ops },
|
||||
{ .compatible = "st,stmftm4", .data = &stmftm4_ops },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stmfts_of_match);
|
||||
@@ -1180,6 +1468,7 @@ MODULE_DEVICE_TABLE(of, stmfts_of_match);
|
||||
static const struct i2c_device_id stmfts_id[] = {
|
||||
{ .name = "stmfts", .driver_data = (kernel_ulong_t)&stmfts4_ops },
|
||||
{ .name = "stmfts5", .driver_data = (kernel_ulong_t)&stmfts5_ops },
|
||||
{ .name = "stmftm4", .driver_data = (kernel_ulong_t)&stmftm4_ops },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, stmfts_id);
|
||||
|
||||
Reference in new issue
Block a user