mirror of https://gitee.com/openkylin/linux.git
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 <m.felsch@pengutronix.de> Link: https://lore.kernel.org/r/20200227112819.16754-4-m.felsch@pengutronix.de Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
parent
f4ee52f3ad
commit
21d1611a83
|
@ -38,6 +38,9 @@
|
||||||
#define WORK_REGISTER_NUM_X 0x33
|
#define WORK_REGISTER_NUM_X 0x33
|
||||||
#define WORK_REGISTER_NUM_Y 0x34
|
#define WORK_REGISTER_NUM_Y 0x34
|
||||||
|
|
||||||
|
#define PMOD_REGISTER_ACTIVE 0x00
|
||||||
|
#define PMOD_REGISTER_HIBERNATE 0x03
|
||||||
|
|
||||||
#define M09_REGISTER_THRESHOLD 0x80
|
#define M09_REGISTER_THRESHOLD 0x80
|
||||||
#define M09_REGISTER_GAIN 0x92
|
#define M09_REGISTER_GAIN 0x92
|
||||||
#define M09_REGISTER_OFFSET 0x93
|
#define M09_REGISTER_OFFSET 0x93
|
||||||
|
@ -53,6 +56,7 @@
|
||||||
|
|
||||||
#define WORK_REGISTER_OPMODE 0x3c
|
#define WORK_REGISTER_OPMODE 0x3c
|
||||||
#define FACTORY_REGISTER_OPMODE 0x01
|
#define FACTORY_REGISTER_OPMODE 0x01
|
||||||
|
#define PMOD_REGISTER_OPMODE 0xa5
|
||||||
|
|
||||||
#define TOUCH_EVENT_DOWN 0x00
|
#define TOUCH_EVENT_DOWN 0x00
|
||||||
#define TOUCH_EVENT_UP 0x01
|
#define TOUCH_EVENT_UP 0x01
|
||||||
|
@ -65,6 +69,12 @@
|
||||||
#define EDT_RAW_DATA_RETRIES 100
|
#define EDT_RAW_DATA_RETRIES 100
|
||||||
#define EDT_RAW_DATA_DELAY 1000 /* usec */
|
#define EDT_RAW_DATA_DELAY 1000 /* usec */
|
||||||
|
|
||||||
|
enum edt_pmode {
|
||||||
|
EDT_PMODE_NOT_SUPPORTED,
|
||||||
|
EDT_PMODE_HIBERNATE,
|
||||||
|
EDT_PMODE_POWEROFF,
|
||||||
|
};
|
||||||
|
|
||||||
enum edt_ver {
|
enum edt_ver {
|
||||||
EDT_M06,
|
EDT_M06,
|
||||||
EDT_M09,
|
EDT_M09,
|
||||||
|
@ -103,6 +113,7 @@ struct edt_ft5x06_ts_data {
|
||||||
|
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
bool factory_mode;
|
bool factory_mode;
|
||||||
|
enum edt_pmode suspend_mode;
|
||||||
int threshold;
|
int threshold;
|
||||||
int gain;
|
int gain;
|
||||||
int offset;
|
int offset;
|
||||||
|
@ -767,9 +778,8 @@ static const struct file_operations debugfs_raw_data_fops = {
|
||||||
.read = edt_ft5x06_debugfs_raw_data_read,
|
.read = edt_ft5x06_debugfs_raw_data_read,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
|
||||||
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
|
const char *debugfs_name)
|
||||||
const char *debugfs_name)
|
|
||||||
{
|
{
|
||||||
tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL);
|
tsdata->debug_dir = debugfs_create_dir(debugfs_name, NULL);
|
||||||
|
|
||||||
|
@ -782,8 +792,7 @@ edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
|
||||||
tsdata->debug_dir, tsdata, &debugfs_raw_data_fops);
|
tsdata->debug_dir, tsdata, &debugfs_raw_data_fops);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
||||||
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
|
||||||
{
|
{
|
||||||
debugfs_remove_recursive(tsdata->debug_dir);
|
debugfs_remove_recursive(tsdata->debug_dir);
|
||||||
kfree(tsdata->raw_buffer);
|
kfree(tsdata->raw_buffer);
|
||||||
|
@ -791,14 +800,17 @@ edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline void
|
static int edt_ft5x06_factory_mode(struct edt_ft5x06_ts_data *tsdata)
|
||||||
edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
|
{
|
||||||
const char *debugfs_name)
|
return -ENOSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata,
|
||||||
|
const char *debugfs_name)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
||||||
edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1125,6 +1137,19 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client,
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check which sleep modes we can support. Power-off requieres the
|
||||||
|
* reset-pin to ensure correct power-down/power-up behaviour. Start with
|
||||||
|
* the EDT_PMODE_POWEROFF test since this is the deepest possible sleep
|
||||||
|
* mode.
|
||||||
|
*/
|
||||||
|
if (tsdata->reset_gpio)
|
||||||
|
tsdata->suspend_mode = EDT_PMODE_POWEROFF;
|
||||||
|
else if (tsdata->wake_gpio)
|
||||||
|
tsdata->suspend_mode = EDT_PMODE_HIBERNATE;
|
||||||
|
else
|
||||||
|
tsdata->suspend_mode = EDT_PMODE_NOT_SUPPORTED;
|
||||||
|
|
||||||
if (tsdata->wake_gpio) {
|
if (tsdata->wake_gpio) {
|
||||||
usleep_range(5000, 6000);
|
usleep_range(5000, 6000);
|
||||||
gpiod_set_value_cansleep(tsdata->wake_gpio, 1);
|
gpiod_set_value_cansleep(tsdata->wake_gpio, 1);
|
||||||
|
@ -1238,6 +1263,102 @@ static int edt_ft5x06_ts_remove(struct i2c_client *client)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int __maybe_unused edt_ft5x06_ts_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
||||||
|
struct gpio_desc *reset_gpio = tsdata->reset_gpio;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (device_may_wakeup(dev))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Enter hibernate mode. */
|
||||||
|
ret = edt_ft5x06_register_write(tsdata, PMOD_REGISTER_OPMODE,
|
||||||
|
PMOD_REGISTER_HIBERNATE);
|
||||||
|
if (ret)
|
||||||
|
dev_warn(dev, "Failed to set hibernate mode\n");
|
||||||
|
|
||||||
|
if (tsdata->suspend_mode == EDT_PMODE_HIBERNATE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Power-off according the datasheet. Cut the power may leaf the irq
|
||||||
|
* line in an undefined state depending on the host pull resistor
|
||||||
|
* settings. Disable the irq to avoid adjusting each host till the
|
||||||
|
* device is back in a full functional state.
|
||||||
|
*/
|
||||||
|
disable_irq(tsdata->client->irq);
|
||||||
|
|
||||||
|
gpiod_set_value_cansleep(reset_gpio, 1);
|
||||||
|
usleep_range(1000, 2000);
|
||||||
|
|
||||||
|
ret = regulator_disable(tsdata->vcc);
|
||||||
|
if (ret)
|
||||||
|
dev_warn(dev, "Failed to disable vcc\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __maybe_unused edt_ft5x06_ts_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (device_may_wakeup(dev))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (tsdata->suspend_mode == EDT_PMODE_NOT_SUPPORTED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (tsdata->suspend_mode == EDT_PMODE_POWEROFF) {
|
||||||
|
struct gpio_desc *reset_gpio = tsdata->reset_gpio;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can't check if the regulator is a dummy or a real
|
||||||
|
* regulator. So we need to specify the 5ms reset time (T_rst)
|
||||||
|
* here instead of the 100us T_rtp time. We also need to wait
|
||||||
|
* 300ms in case it was a real supply and the power was cutted
|
||||||
|
* of. Toggle the reset pin is also a way to exit the hibernate
|
||||||
|
* mode.
|
||||||
|
*/
|
||||||
|
gpiod_set_value_cansleep(reset_gpio, 1);
|
||||||
|
usleep_range(5000, 6000);
|
||||||
|
|
||||||
|
ret = regulator_enable(tsdata->vcc);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Failed to enable vcc\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep_range(1000, 2000);
|
||||||
|
gpiod_set_value_cansleep(reset_gpio, 0);
|
||||||
|
msleep(300);
|
||||||
|
|
||||||
|
edt_ft5x06_restore_reg_parameters(tsdata);
|
||||||
|
enable_irq(tsdata->client->irq);
|
||||||
|
|
||||||
|
if (tsdata->factory_mode)
|
||||||
|
ret = edt_ft5x06_factory_mode(tsdata);
|
||||||
|
} else {
|
||||||
|
struct gpio_desc *wake_gpio = tsdata->wake_gpio;
|
||||||
|
|
||||||
|
gpiod_set_value_cansleep(wake_gpio, 0);
|
||||||
|
usleep_range(5000, 6000);
|
||||||
|
gpiod_set_value_cansleep(wake_gpio, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SIMPLE_DEV_PM_OPS(edt_ft5x06_ts_pm_ops,
|
||||||
|
edt_ft5x06_ts_suspend, edt_ft5x06_ts_resume);
|
||||||
|
|
||||||
static const struct edt_i2c_chip_data edt_ft5x06_data = {
|
static const struct edt_i2c_chip_data edt_ft5x06_data = {
|
||||||
.max_support_points = 5,
|
.max_support_points = 5,
|
||||||
};
|
};
|
||||||
|
@ -1276,6 +1397,7 @@ static struct i2c_driver edt_ft5x06_ts_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "edt_ft5x06",
|
.name = "edt_ft5x06",
|
||||||
.of_match_table = edt_ft5x06_of_match,
|
.of_match_table = edt_ft5x06_of_match,
|
||||||
|
.pm = &edt_ft5x06_ts_pm_ops,
|
||||||
},
|
},
|
||||||
.id_table = edt_ft5x06_ts_id,
|
.id_table = edt_ft5x06_ts_id,
|
||||||
.probe = edt_ft5x06_ts_probe,
|
.probe = edt_ft5x06_ts_probe,
|
||||||
|
|
Loading…
Reference in New Issue