Skip to content

Commit 21d1611

Browse files
Marco Felschdtor
authored andcommitted
Input: edt-ft5x06 - improve power management operations
It is possible to bring the device into a deep sleep state. To exit this state the reset or wakeup pin must be toggeled as documented in [1]. Because of the poor documentation I used the several downstream kernels [2] and other applications notes [3] to indentify the related registers. Furthermore I added the support to disable the device completely which is obviously the most effective power-saving mechanism. This mechanism needs the reset pin to ensure the power-up/down sequence. We can't apply any of these power-saving mechanism if both pins are missing (not connected) or if it is a wakeup device. [1] https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x26.pdf https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x06.pdf [2] https://github.com/linux-sunxi/linux-sunxi/blob/sunxi-3.4/drivers/input/touchscreen/ft5x_ts.c https://github.com/Pablito2020/focaltech-touch-driver/blob/master/ft5336_driver.c [3] https://www.newhavendisplay.com/appnotes/datasheets/touchpanel/FT5x16_registers.pdf Signed-off-by: Marco Felsch <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Dmitry Torokhov <[email protected]>
1 parent f4ee52f commit 21d1611

File tree

1 file changed

+132
-10
lines changed

1 file changed

+132
-10
lines changed

drivers/input/touchscreen/edt-ft5x06.c

Lines changed: 132 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
#define WORK_REGISTER_NUM_X 0x33
3939
#define WORK_REGISTER_NUM_Y 0x34
4040

41+
#define PMOD_REGISTER_ACTIVE 0x00
42+
#define PMOD_REGISTER_HIBERNATE 0x03
43+
4144
#define M09_REGISTER_THRESHOLD 0x80
4245
#define M09_REGISTER_GAIN 0x92
4346
#define M09_REGISTER_OFFSET 0x93
@@ -53,6 +56,7 @@
5356

5457
#define WORK_REGISTER_OPMODE 0x3c
5558
#define FACTORY_REGISTER_OPMODE 0x01
59+
#define PMOD_REGISTER_OPMODE 0xa5
5660

5761
#define TOUCH_EVENT_DOWN 0x00
5862
#define TOUCH_EVENT_UP 0x01
@@ -65,6 +69,12 @@
6569
#define EDT_RAW_DATA_RETRIES 100
6670
#define EDT_RAW_DATA_DELAY 1000 /* usec */
6771

72+
enum edt_pmode {
73+
EDT_PMODE_NOT_SUPPORTED,
74+
EDT_PMODE_HIBERNATE,
75+
EDT_PMODE_POWEROFF,
76+
};
77+
6878
enum edt_ver {
6979
EDT_M06,
7080
EDT_M09,
@@ -103,6 +113,7 @@ struct edt_ft5x06_ts_data {
103113

104114
struct mutex mutex;
105115
bool factory_mode;
116+
enum edt_pmode suspend_mode;
106117
int threshold;
107118
int gain;
108119
int offset;
@@ -767,9 +778,8 @@ static const struct file_operations debugfs_raw_data_fops = {
767778
.read = edt_ft5x06_debugfs_raw_data_read,
768779
};
769780

770-
static void
771-
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
772-
const char *debugfs_name)
781+
static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
782+
const char *debugfs_name)
773783
{
774784
tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL);
775785

@@ -782,23 +792,25 @@ edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
782792
tsdata->debug_dir, tsdata, &debugfs_raw_data_fops);
783793
}
784794

785-
static void
786-
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
795+
static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
787796
{
788797
debugfs_remove_recursive(tsdata->debug_dir);
789798
kfree(tsdata->raw_buffer);
790799
}
791800

792801
#else
793802

794-
static inline void
795-
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
796-
const char *debugfs_name)
803+
static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
797804
{
805+
return -ENOSYS;
798806
}
799807

800-
static inline void
801-
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
808+
static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
809+
const char *debugfs_name)
810+
{
811+
}
812+
813+
static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
802814
{
803815
}
804816

@@ -1125,6 +1137,19 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
11251137
return error;
11261138
}
11271139

1140+
/*
1141+
* Check which sleep modes we can support. Power-off requieres the
1142+
* reset-pin to ensure correct power-down/power-up behaviour. Start with
1143+
* the EDT_PMODE_POWEROFF test since this is the deepest possible sleep
1144+
* mode.
1145+
*/
1146+
if (tsdata->reset_gpio)
1147+
tsdata->suspend_mode = EDT_PMODE_POWEROFF;
1148+
else if (tsdata->wake_gpio)
1149+
tsdata->suspend_mode = EDT_PMODE_HIBERNATE;
1150+
else
1151+
tsdata->suspend_mode = EDT_PMODE_NOT_SUPPORTED;
1152+
11281153
if (tsdata->wake_gpio) {
11291154
usleep_range(5000, 6000);
11301155
gpiod_set_value_cansleep(tsdata->wake_gpio, 1);
@@ -1238,6 +1263,102 @@ static int edt_ft5x06_ts_remove(struct i2c_client *client)
12381263
return 0;
12391264
}
12401265

1266+
static int __maybe_unused edt_ft5x06_ts_suspend(struct device *dev)
1267+
{
1268+
struct i2c_client *client = to_i2c_client(dev);
1269+
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
1270+
struct gpio_desc *reset_gpio = tsdata->reset_gpio;
1271+
int ret;
1272+
1273+
if (device_may_wakeup(dev))
1274+
return 0;
1275+
1276+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
1277+
return 0;
1278+
1279+
/* Enter hibernate mode. */
1280+
ret = edt_ft5x06_register_write(tsdata, PMOD_REGISTER_OPMODE,
1281+
PMOD_REGISTER_HIBERNATE);
1282+
if (ret)
1283+
dev_warn(dev, "Failed to set hibernate mode\n");
1284+
1285+
if (tsdata->suspend_mode == EDT_PMODE_HIBERNATE)
1286+
return 0;
1287+
1288+
/*
1289+
* Power-off according the datasheet. Cut the power may leaf the irq
1290+
* line in an undefined state depending on the host pull resistor
1291+
* settings. Disable the irq to avoid adjusting each host till the
1292+
* device is back in a full functional state.
1293+
*/
1294+
disable_irq(tsdata->client->irq);
1295+
1296+
gpiod_set_value_cansleep(reset_gpio, 1);
1297+
usleep_range(1000, 2000);
1298+
1299+
ret = regulator_disable(tsdata->vcc);
1300+
if (ret)
1301+
dev_warn(dev, "Failed to disable vcc\n");
1302+
1303+
return 0;
1304+
}
1305+
1306+
static int __maybe_unused edt_ft5x06_ts_resume(struct device *dev)
1307+
{
1308+
struct i2c_client *client = to_i2c_client(dev);
1309+
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
1310+
int ret = 0;
1311+
1312+
if (device_may_wakeup(dev))
1313+
return 0;
1314+
1315+
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
1316+
return 0;
1317+
1318+
if (tsdata->suspend_mode == EDT_PMODE_POWEROFF) {
1319+
struct gpio_desc *reset_gpio = tsdata->reset_gpio;
1320+
1321+
/*
1322+
* We can't check if the regulator is a dummy or a real
1323+
* regulator. So we need to specify the 5ms reset time (T_rst)
1324+
* here instead of the 100us T_rtp time. We also need to wait
1325+
* 300ms in case it was a real supply and the power was cutted
1326+
* of. Toggle the reset pin is also a way to exit the hibernate
1327+
* mode.
1328+
*/
1329+
gpiod_set_value_cansleep(reset_gpio, 1);
1330+
usleep_range(5000, 6000);
1331+
1332+
ret = regulator_enable(tsdata->vcc);
1333+
if (ret) {
1334+
dev_err(dev, "Failed to enable vcc\n");
1335+
return ret;
1336+
}
1337+
1338+
usleep_range(1000, 2000);
1339+
gpiod_set_value_cansleep(reset_gpio, 0);
1340+
msleep(300);
1341+
1342+
edt_ft5x06_restore_reg_parameters(tsdata);
1343+
enable_irq(tsdata->client->irq);
1344+
1345+
if (tsdata->factory_mode)
1346+
ret = edt_ft5x06_factory_mode(tsdata);
1347+
} else {
1348+
struct gpio_desc *wake_gpio = tsdata->wake_gpio;
1349+
1350+
gpiod_set_value_cansleep(wake_gpio, 0);
1351+
usleep_range(5000, 6000);
1352+
gpiod_set_value_cansleep(wake_gpio, 1);
1353+
}
1354+
1355+
1356+
return ret;
1357+
}
1358+
1359+
static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
1360+
edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
1361+
12411362
static const struct edt_i2c_chip_data edt_ft5x06_data = {
12421363
.max_support_points = 5,
12431364
};
@@ -1276,6 +1397,7 @@ static struct i2c_driver edt_ft5x06_ts_driver = {
12761397
.driver = {
12771398
.name = "edt_ft5x06",
12781399
.of_match_table = edt_ft5x06_of_match,
1400+
.pm = &edt_ft5x06_ts_pm_ops,
12791401
},
12801402
.id_table = edt_ft5x06_ts_id,
12811403
.probe = edt_ft5x06_ts_probe,

0 commit comments

Comments
 (0)