* [PATCH 00/11] Synaptics RMI4 over SMBus
@ 2016-08-18 9:24 Benjamin Tissoires
2016-08-18 9:24 ` [PATCH 01/11] Input: synaptics-rmi4 - add SMBus support Benjamin Tissoires
` (10 more replies)
0 siblings, 11 replies; 24+ messages in thread
From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw)
To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny,
Dennis Wassenberg
Cc: Peter Hutterer, linux-kernel, linux-input
Hi,
this is finally my first whole submission of RMI4 over SMbus and the binding
of such devices found on the Thinkpad T series.
It has been a long time since we wanted to have those drivers in, but few
hiccups were on the road:
- lack of SMBus Host Notify (now merged upstream, still few fixes needed, but
nothing preventing us to include this series).
- a unexpected behavior where the touchpad disables the SMBus commands if
psmouse_activate has been called.
- some hairy problems with the 2 big locks found in the serio and psmouse
driver.
This series also supports the RMI4 PS/2 pass-through required for the Thinkpad
X1 tablet even if it uses RMI4 over HID over USB.
Cheers,
Benjamin
Benjamin Tissoires (8):
Input: synaptics-rmi4 - add SMBus support
Input: serio - store the pt_buttons in the struct serio directly
Input: synaptics-rmi4 - have only one struct platform data
Input: synaptics-rmi4 - Add rmi_find_function()
Input: synaptics - allocate a Synaptics Intertouch device
Input: synaptics-rmi4 - add rmi_platform
Input: synaptics-rmi4 - smbus: call psmouse_deactivate before
binding/resume
Input: synaptics-rmi4 - smbus: on resume, try 3 times if init fails
Dennis Wassenberg (1):
Input: synaptics-rmi4 - f03: grab data passed by transport device
Lyude Paul (2):
Input: synaptics-rmi4 - add support for F03
Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on
buttonpads to PS/2 guest
drivers/input/mouse/psmouse-base.c | 12 +
drivers/input/mouse/psmouse.h | 1 +
drivers/input/mouse/synaptics.c | 150 +++++++++++-
drivers/input/mouse/synaptics.h | 5 +-
drivers/input/rmi4/Kconfig | 33 +++
drivers/input/rmi4/Makefile | 3 +
drivers/input/rmi4/rmi_bus.c | 3 +
drivers/input/rmi4/rmi_bus.h | 12 +
drivers/input/rmi4/rmi_driver.c | 13 +
drivers/input/rmi4/rmi_driver.h | 15 ++
drivers/input/rmi4/rmi_f03.c | 271 +++++++++++++++++++++
drivers/input/rmi4/rmi_f11.c | 4 +-
drivers/input/rmi4/rmi_f12.c | 4 +-
drivers/input/rmi4/rmi_f30.c | 71 +++++-
drivers/input/rmi4/rmi_platform.c | 235 ++++++++++++++++++
drivers/input/rmi4/rmi_smbus.c | 478 +++++++++++++++++++++++++++++++++++++
include/linux/rmi.h | 17 +-
include/linux/serio.h | 8 +
include/uapi/linux/serio.h | 1 +
19 files changed, 1308 insertions(+), 28 deletions(-)
create mode 100644 drivers/input/rmi4/rmi_f03.c
create mode 100644 drivers/input/rmi4/rmi_platform.c
create mode 100644 drivers/input/rmi4/rmi_smbus.c
--
2.5.5
^ permalink raw reply [flat|nested] 24+ messages in thread* [PATCH 01/11] Input: synaptics-rmi4 - add SMBus support 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly Benjamin Tissoires ` (9 subsequent siblings) 10 siblings, 0 replies; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Code obtained from https://raw.githubusercontent.com/mightybigcar/synaptics-rmi4/jf/drivers/input/rmi4/rmi_smbus.c and updated to match upstream. And fixed to make it work. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Andrew Duggan <aduggan@synaptics.com> --- Changes in this series: - rely on PM functions for resume handling Changes from the Host Notify series: new in v5 no changes in v6 changes in v7: - fixed typos as per Andrew's requests - changed module author from Allie to Andrew as per Andrew's request - add suspend/resume callbacks to match upstream rmi4 behavior no changes in v8 --- drivers/input/rmi4/Kconfig | 12 ++ drivers/input/rmi4/Makefile | 1 + drivers/input/rmi4/rmi_bus.h | 12 ++ drivers/input/rmi4/rmi_smbus.c | 449 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 474 insertions(+) create mode 100644 drivers/input/rmi4/rmi_smbus.c diff --git a/drivers/input/rmi4/Kconfig b/drivers/input/rmi4/Kconfig index f73df24..86a180b 100644 --- a/drivers/input/rmi4/Kconfig +++ b/drivers/input/rmi4/Kconfig @@ -27,6 +27,18 @@ config RMI4_SPI If unsure, say N. +config RMI4_SMB + tristate "RMI4 SMB Support" + depends on RMI4_CORE && I2C + help + Say Y here if you want to support RMI4 devices connected to an SMB + bus. + + If unsure, say N. + + To compile this driver as a module, choose M here: the module will be + called rmi_smbus. + config RMI4_2D_SENSOR bool depends on RMI4_CORE diff --git a/drivers/input/rmi4/Makefile b/drivers/input/rmi4/Makefile index 95c00a7..3c8ebf2 100644 --- a/drivers/input/rmi4/Makefile +++ b/drivers/input/rmi4/Makefile @@ -11,3 +11,4 @@ rmi_core-$(CONFIG_RMI4_F30) += rmi_f30.o # Transports obj-$(CONFIG_RMI4_I2C) += rmi_i2c.o obj-$(CONFIG_RMI4_SPI) += rmi_spi.o +obj-$(CONFIG_RMI4_SMB) += rmi_smbus.o diff --git a/drivers/input/rmi4/rmi_bus.h b/drivers/input/rmi4/rmi_bus.h index 8995798..b7625a9 100644 --- a/drivers/input/rmi4/rmi_bus.h +++ b/drivers/input/rmi4/rmi_bus.h @@ -105,6 +105,18 @@ rmi_get_platform_data(struct rmi_device *d) bool rmi_is_physical_device(struct device *dev); /** + * rmi_reset - reset a RMI4 device + * @d: Pointer to an RMI device + * + * Calls for a reset of each function implemented by a specific device. + * Returns 0 on success or a negative error code. + */ +static inline int rmi_reset(struct rmi_device *d) +{ + return d->driver->reset_handler(d); +} + +/** * rmi_read - read a single byte * @d: Pointer to an RMI device * @addr: The address to read from diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c new file mode 100644 index 0000000..4d6f228 --- /dev/null +++ b/drivers/input/rmi4/rmi_smbus.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2015 - 2016 Red Hat, Inc + * Copyright (c) 2011, 2012 Synaptics Incorporated + * Copyright (c) 2011 Unixphere + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kconfig.h> +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/rmi.h> +#include <linux/slab.h> +#include "rmi_driver.h" + +#define SMB_PROTOCOL_VERSION_ADDRESS 0xfd +#define SMB_MAX_COUNT 32 +#define RMI_SMB2_MAP_SIZE 8 /* 8 entry of 4 bytes each */ +#define RMI_SMB2_MAP_FLAGS_WE 0x01 + +struct mapping_table_entry { + __le16 rmiaddr; + u8 readcount; + u8 flags; +}; + +struct rmi_smb_xport { + struct rmi_transport_dev xport; + struct i2c_client *client; + + struct mutex page_mutex; + int page; + u8 table_index; + struct mutex mappingtable_mutex; + struct mapping_table_entry mapping_table[RMI_SMB2_MAP_SIZE]; +}; + +static int rmi_smb_get_version(struct rmi_smb_xport *rmi_smb) +{ + struct i2c_client *client = rmi_smb->client; + int retval; + + /* Check if for SMBus new version device by reading version byte. */ + retval = i2c_smbus_read_byte_data(client, SMB_PROTOCOL_VERSION_ADDRESS); + if (retval < 0) { + dev_err(&client->dev, "failed to get SMBus version number!\n"); + return retval; + } + return retval + 1; +} + +/* SMB block write - wrapper over ic2_smb_write_block */ +static int smb_block_write(struct rmi_transport_dev *xport, + u8 commandcode, const void *buf, size_t len) +{ + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; + int retval; + + retval = i2c_smbus_write_block_data(client, commandcode, len, buf); + + rmi_dbg(RMI_DEBUG_XPORT, &client->dev, + "wrote %zd bytes at %#04x: %d (%*ph)\n", + len, commandcode, retval, (int)len, buf); + + return retval; +} + +/* + * The function to get command code for smbus operations and keeps + * records to the driver mapping table + */ +static int rmi_smb_get_command_code(struct rmi_transport_dev *xport, + u16 rmiaddr, int bytecount, bool isread, u8 *commandcode) +{ + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + int i; + int retval; + struct mapping_table_entry mapping_data[1]; + + mutex_lock(&rmi_smb->mappingtable_mutex); + for (i = 0; i < RMI_SMB2_MAP_SIZE; i++) { + if (rmi_smb->mapping_table[i].rmiaddr == rmiaddr) { + if (isread) { + if (rmi_smb->mapping_table[i].readcount + == bytecount) { + *commandcode = i; + retval = 0; + goto exit; + } + } else { + if (rmi_smb->mapping_table[i].flags & + RMI_SMB2_MAP_FLAGS_WE) { + *commandcode = i; + retval = 0; + goto exit; + } + } + } + } + i = rmi_smb->table_index; + rmi_smb->table_index = (i + 1) % RMI_SMB2_MAP_SIZE; + + /* constructs mapping table data entry. 4 bytes each entry */ + memset(mapping_data, 0, sizeof(mapping_data)); + + mapping_data[0].rmiaddr = cpu_to_le16(rmiaddr); + mapping_data[0].readcount = bytecount; + mapping_data[0].flags = !isread ? RMI_SMB2_MAP_FLAGS_WE : 0; + + retval = smb_block_write(xport, i + 0x80, mapping_data, + sizeof(mapping_data)); + + if (retval < 0) { + /* + * if not written to device mapping table + * clear the driver mapping table records + */ + rmi_smb->mapping_table[i].rmiaddr = 0x0000; + rmi_smb->mapping_table[i].readcount = 0; + rmi_smb->mapping_table[i].flags = 0; + goto exit; + } + /* save to the driver level mapping table */ + rmi_smb->mapping_table[i].rmiaddr = rmiaddr; + rmi_smb->mapping_table[i].readcount = bytecount; + rmi_smb->mapping_table[i].flags = !isread ? RMI_SMB2_MAP_FLAGS_WE : 0; + *commandcode = i; + +exit: + mutex_unlock(&rmi_smb->mappingtable_mutex); + + return retval; +} + +static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, + const void *databuff, size_t len) +{ + int retval = 0; + u8 commandcode; + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + int cur_len = (int)len; + + mutex_lock(&rmi_smb->page_mutex); + + while (cur_len > 0) { + /* + * break into 32 bytes chunks to write get command code + */ + int block_len = min_t(int, len, SMB_MAX_COUNT); + + retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, + false, &commandcode); + if (retval < 0) + goto exit; + + retval = smb_block_write(xport, commandcode, + databuff, block_len); + if (retval < 0) + goto exit; + + /* prepare to write next block of bytes */ + cur_len -= SMB_MAX_COUNT; + databuff += SMB_MAX_COUNT; + rmiaddr += SMB_MAX_COUNT; + } +exit: + mutex_unlock(&rmi_smb->page_mutex); + return retval; +} + +/* SMB block read - wrapper over ic2_smb_read_block */ +static int smb_block_read(struct rmi_transport_dev *xport, + u8 commandcode, void *buf, size_t len) +{ + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; + int retval; + + retval = i2c_smbus_read_block_data(client, commandcode, buf); + if (retval < 0) + return retval; + + return retval; +} + +static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, + void *databuff, size_t len) +{ + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + int retval; + u8 commandcode; + int cur_len = (int)len; + + mutex_lock(&rmi_smb->page_mutex); + memset(databuff, 0, len); + + while (cur_len > 0) { + /* break into 32 bytes chunks to write get command code */ + int block_len = min_t(int, cur_len, SMB_MAX_COUNT); + + retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, + true, &commandcode); + if (retval < 0) + goto exit; + + retval = smb_block_read(xport, commandcode, + databuff, block_len); + if (retval < 0) + goto exit; + + /* prepare to read next block of bytes */ + cur_len -= SMB_MAX_COUNT; + databuff += SMB_MAX_COUNT; + rmiaddr += SMB_MAX_COUNT; + } + + retval = 0; + +exit: + mutex_unlock(&rmi_smb->page_mutex); + return retval; +} + +static void rmi_smb_clear_state(struct rmi_smb_xport *rmi_smb) +{ + /* the mapping table has been flushed, discard the current one */ + mutex_lock(&rmi_smb->mappingtable_mutex); + memset(rmi_smb->mapping_table, 0, sizeof(rmi_smb->mapping_table)); + mutex_unlock(&rmi_smb->mappingtable_mutex); +} + +static int rmi_smb_enable_smbus_mode(struct rmi_smb_xport *rmi_smb) +{ + int retval; + + /* we need to get the smbus version to activate the touchpad */ + retval = rmi_smb_get_version(rmi_smb); + if (retval < 0) + return retval; + + return 0; +} + +static int rmi_smb_reset(struct rmi_transport_dev *xport, u16 reset_addr) +{ + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + + rmi_smb_clear_state(rmi_smb); + + /* + * we do not call the actual reset command, it has to be handled in + * PS/2 or there will be races between PS/2 and SMBus. + * PS/2 should ensure that a psmouse_reset is called before + * intializing the device and after it has been removed to be in a known + * state. + */ + return rmi_smb_enable_smbus_mode(rmi_smb); +} + +static const struct rmi_transport_ops rmi_smb_ops = { + .write_block = rmi_smb_write_block, + .read_block = rmi_smb_read_block, + .reset = rmi_smb_reset, +}; + +static void rmi_smb_alert(struct i2c_client *client, + enum i2c_alert_protocol type, unsigned int data) +{ + struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); + struct rmi_device *rmi_dev = rmi_smb->xport.rmi_dev; + struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev); + + if (type != I2C_PROTOCOL_SMBUS_HOST_NOTIFY) + return; + + if (!drv_data) { + dev_err(&client->dev, + "Something went wrong, driver data is NULL.\n"); + return; + } + + rmi_process_interrupt_requests(rmi_dev); +} + +static int rmi_smb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rmi_device_platform_data *pdata = dev_get_platdata(&client->dev); + struct rmi_smb_xport *rmi_smb; + int retval; + int smbus_version; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_HOST_NOTIFY)) { + dev_err(&client->dev, + "adapter does not support required functionality.\n"); + return -ENODEV; + } + + rmi_smb = devm_kzalloc(&client->dev, sizeof(struct rmi_smb_xport), + GFP_KERNEL); + if (!rmi_smb) + return -ENOMEM; + + if (!pdata) { + dev_err(&client->dev, "no platform data, aborting\n"); + return -ENOMEM; + } + + rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "Probing %s.\n", + dev_name(&client->dev)); + + rmi_smb->client = client; + mutex_init(&rmi_smb->page_mutex); + mutex_init(&rmi_smb->mappingtable_mutex); + + rmi_smb->xport.dev = &client->dev; + rmi_smb->xport.pdata = *pdata; + rmi_smb->xport.proto_name = "smb2"; + rmi_smb->xport.ops = &rmi_smb_ops; + + retval = rmi_smb_get_version(rmi_smb); + if (retval < 0) + return retval; + + smbus_version = retval; + rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "Smbus version is %d", + smbus_version); + + if (smbus_version != 2) { + dev_err(&client->dev, "Unrecognized SMB version %d.\n", + smbus_version); + return -ENODEV; + } + + i2c_set_clientdata(client, rmi_smb); + + retval = rmi_register_transport_device(&rmi_smb->xport); + if (retval) { + dev_err(&client->dev, "Failed to register transport driver at 0x%.2X.\n", + client->addr); + i2c_set_clientdata(client, NULL); + return retval; + } + + dev_info(&client->dev, "registered rmi smb driver at %#04x.\n", + client->addr); + return 0; + +} + +static int rmi_smb_remove(struct i2c_client *client) +{ + struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); + + rmi_unregister_transport_device(&rmi_smb->xport); + + return 0; +} + +static int __maybe_unused rmi_smb_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); + int ret; + + ret = rmi_driver_suspend(rmi_smb->xport.rmi_dev); + if (ret) + dev_warn(dev, "Failed to suspend device: %d\n", ret); + + return ret; +} + +static int __maybe_unused rmi_smb_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); + struct rmi_device *rmi_dev = rmi_smb->xport.rmi_dev; + int ret; + + rmi_smb_reset(&rmi_smb->xport, 0); + + rmi_reset(rmi_dev); + + ret = rmi_driver_resume(rmi_smb->xport.rmi_dev); + if (ret) + dev_warn(dev, "Failed to resume device: %d\n", ret); + + return 0; +} + +static int __maybe_unused rmi_smb_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); + int ret; + + ret = rmi_driver_resume(rmi_smb->xport.rmi_dev); + if (ret) + dev_warn(dev, "Failed to resume device: %d\n", ret); + + return 0; +} + +static const struct dev_pm_ops rmi_smb_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rmi_smb_suspend, rmi_smb_resume) + SET_RUNTIME_PM_OPS(rmi_smb_suspend, rmi_smb_runtime_resume, + NULL) +}; + +static const struct i2c_device_id rmi_id[] = { + { "rmi4_smbus", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rmi_id); + +static struct i2c_driver rmi_smb_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "rmi4_smbus", + .pm = &rmi_smb_pm, + }, + .id_table = rmi_id, + .probe = rmi_smb_probe, + .remove = rmi_smb_remove, + .alert = rmi_smb_alert, +}; + +module_i2c_driver(rmi_smb_driver); + +MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>"); +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); +MODULE_DESCRIPTION("RMI4 SMBus driver"); +MODULE_LICENSE("GPL"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 01/11] Input: synaptics-rmi4 - add SMBus support Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:31 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data Benjamin Tissoires ` (8 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input With RMI4 over SMBus, the pass-through device can be instantiated in a SMBus driver. However, compared to the psmouse-synaptics driver, this pass-through PS/2 driver has no clue whether the current serio_interrupt() is the beginning of the frame or not. Instead of adding a protocol analysis in RMI4 function F03, we can add an extra byte in struct serio to handle the extra data we want to append to the first byte. Convert the psmouse-synaptics device to use it too. Partially reverts cdd9dc1 ("Input: synaptics - re-route tracksticks buttons on the Lenovo 2015 series") Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/mouse/psmouse-base.c | 3 +++ drivers/input/mouse/synaptics.c | 19 ++++++++++--------- drivers/input/mouse/synaptics.h | 1 - include/linux/serio.h | 8 ++++++++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 5784e20..dbc002a 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -365,6 +365,9 @@ static irqreturn_t psmouse_interrupt(struct serio *serio, goto out; } + if (psmouse->pktcnt == 1) + psmouse->packet[0] |= serio->extra_byte; + psmouse->last = jiffies; psmouse_handle_byte(psmouse); diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index a41d832..8781e23 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -597,15 +597,13 @@ static int synaptics_is_pt_packet(unsigned char *buf) return (buf[0] & 0xFC) == 0x84 && (buf[3] & 0xCC) == 0xC4; } -static void synaptics_pass_pt_packet(struct psmouse *psmouse, - struct serio *ptport, +static void synaptics_pass_pt_packet(struct serio *ptport, unsigned char *packet) { - struct synaptics_data *priv = psmouse->private; struct psmouse *child = serio_get_drvdata(ptport); if (child && child->state == PSMOUSE_ACTIVATED) { - serio_interrupt(ptport, packet[1] | priv->pt_buttons, 0); + serio_interrupt(ptport, packet[1], 0); serio_interrupt(ptport, packet[4], 0); serio_interrupt(ptport, packet[5], 0); if (child->pktsize == 4) @@ -857,6 +855,7 @@ static void synaptics_report_ext_buttons(struct psmouse *psmouse, struct synaptics_data *priv = psmouse->private; int ext_bits = (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap) + 1) >> 1; char buf[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + int pt_buttons; int i; if (!SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap)) @@ -887,11 +886,13 @@ static void synaptics_report_ext_buttons(struct psmouse *psmouse, return; /* The trackstick expects at most 3 buttons */ - priv->pt_buttons = SYN_CAP_EXT_BUTTON_STICK_L(hw->ext_buttons) | - SYN_CAP_EXT_BUTTON_STICK_R(hw->ext_buttons) << 1 | - SYN_CAP_EXT_BUTTON_STICK_M(hw->ext_buttons) << 2; + pt_buttons = SYN_CAP_EXT_BUTTON_STICK_L(hw->ext_buttons) | + SYN_CAP_EXT_BUTTON_STICK_R(hw->ext_buttons) << 1 | + SYN_CAP_EXT_BUTTON_STICK_M(hw->ext_buttons) << 2; + + priv->pt_port->extra_byte = pt_buttons; - synaptics_pass_pt_packet(psmouse, priv->pt_port, buf); + synaptics_pass_pt_packet(priv->pt_port, buf); } static void synaptics_report_buttons(struct psmouse *psmouse, @@ -1132,7 +1133,7 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse) if (SYN_CAP_PASS_THROUGH(priv->capabilities) && synaptics_is_pt_packet(psmouse->packet)) { if (priv->pt_port) - synaptics_pass_pt_packet(psmouse, priv->pt_port, + synaptics_pass_pt_packet(priv->pt_port, psmouse->packet); } else synaptics_process_packet(psmouse); diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h index 56faa7e..116ae25 100644 --- a/drivers/input/mouse/synaptics.h +++ b/drivers/input/mouse/synaptics.h @@ -183,7 +183,6 @@ struct synaptics_data { bool disable_gesture; /* disable gestures */ struct serio *pt_port; /* Pass-through serio port */ - unsigned char pt_buttons; /* Pass-through buttons */ /* * Last received Advanced Gesture Mode (AGM) packet. An AGM packet diff --git a/include/linux/serio.h b/include/linux/serio.h index c733cff..f3b75c8 100644 --- a/include/linux/serio.h +++ b/include/linux/serio.h @@ -64,6 +64,14 @@ struct serio { * may get indigestion when exposed to concurrent access (i8042). */ struct mutex *ps2_cmd_mutex; + + /* + * For use with Synaptics devices that have the trackstick buttons + * not actually wired to the trackstick PS/2 device. + * This byte will be OR-ed with the first byte of the incoming packet + * that contains actual data (not commands). + */ + u8 extra_byte; }; #define to_serio_port(d) container_of(d, struct serio, dev) -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly 2016-08-18 9:24 ` [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly Benjamin Tissoires @ 2016-08-27 1:31 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:31 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > With RMI4 over SMBus, the pass-through device can be instantiated > in a SMBus driver. However, compared to the psmouse-synaptics driver, > this pass-through PS/2 driver has no clue whether the current > serio_interrupt() is the beginning of the frame or not. Instead of > adding a protocol analysis in RMI4 function F03, we can add an extra > byte in struct serio to handle the extra data we want to append to the > first byte. > > Convert the psmouse-synaptics device to use it too. > > Partially reverts cdd9dc1 ("Input: synaptics - re-route tracksticks > buttons on the Lenovo 2015 series") > > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Acked-by: Andrew Duggan <aduggan@synaptics.com> > --- > drivers/input/mouse/psmouse-base.c | 3 +++ > drivers/input/mouse/synaptics.c | 19 ++++++++++--------- > drivers/input/mouse/synaptics.h | 1 - > include/linux/serio.h | 8 ++++++++ > 4 files changed, 21 insertions(+), 10 deletions(-) > > diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c > index 5784e20..dbc002a 100644 > --- a/drivers/input/mouse/psmouse-base.c > +++ b/drivers/input/mouse/psmouse-base.c > @@ -365,6 +365,9 @@ static irqreturn_t psmouse_interrupt(struct serio *serio, > goto out; > } > > + if (psmouse->pktcnt == 1) > + psmouse->packet[0] |= serio->extra_byte; > + > psmouse->last = jiffies; > psmouse_handle_byte(psmouse); > > diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c > index a41d832..8781e23 100644 > --- a/drivers/input/mouse/synaptics.c > +++ b/drivers/input/mouse/synaptics.c > @@ -597,15 +597,13 @@ static int synaptics_is_pt_packet(unsigned char *buf) > return (buf[0] & 0xFC) == 0x84 && (buf[3] & 0xCC) == 0xC4; > } > > -static void synaptics_pass_pt_packet(struct psmouse *psmouse, > - struct serio *ptport, > +static void synaptics_pass_pt_packet(struct serio *ptport, > unsigned char *packet) > { > - struct synaptics_data *priv = psmouse->private; > struct psmouse *child = serio_get_drvdata(ptport); > > if (child && child->state == PSMOUSE_ACTIVATED) { > - serio_interrupt(ptport, packet[1] | priv->pt_buttons, 0); > + serio_interrupt(ptport, packet[1], 0); > serio_interrupt(ptport, packet[4], 0); > serio_interrupt(ptport, packet[5], 0); > if (child->pktsize == 4) > @@ -857,6 +855,7 @@ static void synaptics_report_ext_buttons(struct psmouse *psmouse, > struct synaptics_data *priv = psmouse->private; > int ext_bits = (SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap) + 1) >> 1; > char buf[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; > + int pt_buttons; > int i; > > if (!SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap)) > @@ -887,11 +886,13 @@ static void synaptics_report_ext_buttons(struct psmouse *psmouse, > return; > > /* The trackstick expects at most 3 buttons */ > - priv->pt_buttons = SYN_CAP_EXT_BUTTON_STICK_L(hw->ext_buttons) | > - SYN_CAP_EXT_BUTTON_STICK_R(hw->ext_buttons) << 1 | > - SYN_CAP_EXT_BUTTON_STICK_M(hw->ext_buttons) << 2; > + pt_buttons = SYN_CAP_EXT_BUTTON_STICK_L(hw->ext_buttons) | > + SYN_CAP_EXT_BUTTON_STICK_R(hw->ext_buttons) << 1 | > + SYN_CAP_EXT_BUTTON_STICK_M(hw->ext_buttons) << 2; > + > + priv->pt_port->extra_byte = pt_buttons; > > - synaptics_pass_pt_packet(psmouse, priv->pt_port, buf); > + synaptics_pass_pt_packet(priv->pt_port, buf); > } > > static void synaptics_report_buttons(struct psmouse *psmouse, > @@ -1132,7 +1133,7 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse) > if (SYN_CAP_PASS_THROUGH(priv->capabilities) && > synaptics_is_pt_packet(psmouse->packet)) { > if (priv->pt_port) > - synaptics_pass_pt_packet(psmouse, priv->pt_port, > + synaptics_pass_pt_packet(priv->pt_port, > psmouse->packet); > } else > synaptics_process_packet(psmouse); > diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h > index 56faa7e..116ae25 100644 > --- a/drivers/input/mouse/synaptics.h > +++ b/drivers/input/mouse/synaptics.h > @@ -183,7 +183,6 @@ struct synaptics_data { > bool disable_gesture; /* disable gestures */ > > struct serio *pt_port; /* Pass-through serio port */ > - unsigned char pt_buttons; /* Pass-through buttons */ > > /* > * Last received Advanced Gesture Mode (AGM) packet. An AGM packet > diff --git a/include/linux/serio.h b/include/linux/serio.h > index c733cff..f3b75c8 100644 > --- a/include/linux/serio.h > +++ b/include/linux/serio.h > @@ -64,6 +64,14 @@ struct serio { > * may get indigestion when exposed to concurrent access (i8042). > */ > struct mutex *ps2_cmd_mutex; > + > + /* > + * For use with Synaptics devices that have the trackstick buttons > + * not actually wired to the trackstick PS/2 device. > + * This byte will be OR-ed with the first byte of the incoming packet > + * that contains actual data (not commands). > + */ > + u8 extra_byte; > }; > #define to_serio_port(d) container_of(d, struct serio, dev) > > ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 01/11] Input: synaptics-rmi4 - add SMBus support Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 Benjamin Tissoires ` (7 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input If struct rmi_device_platform_data contains pointers to other struct, it gets difficult to allocate a fixed size struct and copy it over between drivers. Change the pointers into a struct and change the code in rmi4 accordingly. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- this patch will conflict with Andrew's patch to switch hid-rmi to use rmi4_core... --- drivers/input/rmi4/rmi_f11.c | 4 ++-- drivers/input/rmi4/rmi_f12.c | 4 ++-- drivers/input/rmi4/rmi_f30.c | 7 +++---- include/linux/rmi.h | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/input/rmi4/rmi_f11.c b/drivers/input/rmi4/rmi_f11.c index 20c7134..b14a7b6 100644 --- a/drivers/input/rmi4/rmi_f11.c +++ b/drivers/input/rmi4/rmi_f11.c @@ -1063,8 +1063,8 @@ static int rmi_f11_initialize(struct rmi_function *fn) rc = rmi_2d_sensor_of_probe(&fn->dev, &f11->sensor_pdata); if (rc) return rc; - } else if (pdata->sensor_pdata) { - f11->sensor_pdata = *pdata->sensor_pdata; + } else { + f11->sensor_pdata = pdata->sensor_pdata; } f11->rezero_wait_ms = f11->sensor_pdata.rezero_wait; diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c index 332c02f..a631bed 100644 --- a/drivers/input/rmi4/rmi_f12.c +++ b/drivers/input/rmi4/rmi_f12.c @@ -274,8 +274,8 @@ static int rmi_f12_probe(struct rmi_function *fn) ret = rmi_2d_sensor_of_probe(&fn->dev, &f12->sensor_pdata); if (ret) return ret; - } else if (pdata->sensor_pdata) { - f12->sensor_pdata = *pdata->sensor_pdata; + } else { + f12->sensor_pdata = pdata->sensor_pdata; } ret = rmi_read_register_desc(rmi_dev, query_addr, diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c index 760aff1..7990bb0 100644 --- a/drivers/input/rmi4/rmi_f30.c +++ b/drivers/input/rmi4/rmi_f30.c @@ -192,7 +192,7 @@ static int rmi_f30_config(struct rmi_function *fn) rmi_get_platform_data(fn->rmi_dev); int error; - if (pdata->f30_data && pdata->f30_data->disable) { + if (pdata && pdata->f30_data.disable) { drv->clear_irq_bits(fn->rmi_dev, fn->irq_mask); } else { /* Write Control Register values back to device */ @@ -362,8 +362,7 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) * f30->has_mech_mouse_btns, but I am * not sure, so use only the pdata info */ - if (pdata->f30_data && - pdata->f30_data->buttonpad) + if (pdata && pdata->f30_data.buttonpad) break; } } @@ -378,7 +377,7 @@ static int rmi_f30_probe(struct rmi_function *fn) const struct rmi_device_platform_data *pdata = rmi_get_platform_data(fn->rmi_dev); - if (pdata->f30_data && pdata->f30_data->disable) + if (pdata && pdata->f30_data.disable) return 0; rc = rmi_f30_initialize(fn); diff --git a/include/linux/rmi.h b/include/linux/rmi.h index e0aca14..4a071e8 100644 --- a/include/linux/rmi.h +++ b/include/linux/rmi.h @@ -211,9 +211,9 @@ struct rmi_device_platform_data { struct rmi_device_platform_data_spi spi_data; /* function handler pdata */ - struct rmi_2d_sensor_platform_data *sensor_pdata; + struct rmi_2d_sensor_platform_data sensor_pdata; struct rmi_f01_power_management power_management; - struct rmi_f30_data *f30_data; + struct rmi_f30_data f30_data; }; /** -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data 2016-08-18 9:24 ` [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data Benjamin Tissoires @ 2016-08-27 1:35 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:35 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > If struct rmi_device_platform_data contains pointers to other struct, > it gets difficult to allocate a fixed size struct and copy it over between > drivers. > > Change the pointers into a struct and change the code in rmi4 accordingly. > > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> > > --- > > this patch will conflict with Andrew's patch to switch hid-rmi > to use rmi4_core... > --- > drivers/input/rmi4/rmi_f11.c | 4 ++-- > drivers/input/rmi4/rmi_f12.c | 4 ++-- > drivers/input/rmi4/rmi_f30.c | 7 +++---- > include/linux/rmi.h | 4 ++-- > 4 files changed, 9 insertions(+), 10 deletions(-) > > diff --git a/drivers/input/rmi4/rmi_f11.c b/drivers/input/rmi4/rmi_f11.c > index 20c7134..b14a7b6 100644 > --- a/drivers/input/rmi4/rmi_f11.c > +++ b/drivers/input/rmi4/rmi_f11.c > @@ -1063,8 +1063,8 @@ static int rmi_f11_initialize(struct rmi_function *fn) > rc = rmi_2d_sensor_of_probe(&fn->dev, &f11->sensor_pdata); > if (rc) > return rc; > - } else if (pdata->sensor_pdata) { > - f11->sensor_pdata = *pdata->sensor_pdata; > + } else { > + f11->sensor_pdata = pdata->sensor_pdata; > } > > f11->rezero_wait_ms = f11->sensor_pdata.rezero_wait; > diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c > index 332c02f..a631bed 100644 > --- a/drivers/input/rmi4/rmi_f12.c > +++ b/drivers/input/rmi4/rmi_f12.c > @@ -274,8 +274,8 @@ static int rmi_f12_probe(struct rmi_function *fn) > ret = rmi_2d_sensor_of_probe(&fn->dev, &f12->sensor_pdata); > if (ret) > return ret; > - } else if (pdata->sensor_pdata) { > - f12->sensor_pdata = *pdata->sensor_pdata; > + } else { > + f12->sensor_pdata = pdata->sensor_pdata; > } > > ret = rmi_read_register_desc(rmi_dev, query_addr, > diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c > index 760aff1..7990bb0 100644 > --- a/drivers/input/rmi4/rmi_f30.c > +++ b/drivers/input/rmi4/rmi_f30.c > @@ -192,7 +192,7 @@ static int rmi_f30_config(struct rmi_function *fn) > rmi_get_platform_data(fn->rmi_dev); > int error; > > - if (pdata->f30_data && pdata->f30_data->disable) { > + if (pdata && pdata->f30_data.disable) { My one comment is that pdata struct is embedded in the transport device so rmi_get_platform_data() will not return NULL. Making the check of pdata unnecessary. > drv->clear_irq_bits(fn->rmi_dev, fn->irq_mask); > } else { > /* Write Control Register values back to device */ > @@ -362,8 +362,7 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) > * f30->has_mech_mouse_btns, but I am > * not sure, so use only the pdata info > */ > - if (pdata->f30_data && > - pdata->f30_data->buttonpad) > + if (pdata && pdata->f30_data.buttonpad) Same with this check of pdata. > break; > } > } > @@ -378,7 +377,7 @@ static int rmi_f30_probe(struct rmi_function *fn) > const struct rmi_device_platform_data *pdata = > rmi_get_platform_data(fn->rmi_dev); > > - if (pdata->f30_data && pdata->f30_data->disable) > + if (pdata && pdata->f30_data.disable) And this one. That's a fairly minor comment and I could see an argument for keeping the checks in the event that the implementation of rmi_get_platform_data() changes. So: Reviewed-by: Andrew Duggan <aduggan@synaptics.com> Andrew > return 0; > > rc = rmi_f30_initialize(fn); > diff --git a/include/linux/rmi.h b/include/linux/rmi.h > index e0aca14..4a071e8 100644 > --- a/include/linux/rmi.h > +++ b/include/linux/rmi.h > @@ -211,9 +211,9 @@ struct rmi_device_platform_data { > struct rmi_device_platform_data_spi spi_data; > > /* function handler pdata */ > - struct rmi_2d_sensor_platform_data *sensor_pdata; > + struct rmi_2d_sensor_platform_data sensor_pdata; > struct rmi_f01_power_management power_management; > - struct rmi_f30_data *f30_data; > + struct rmi_f30_data f30_data; > }; > > /** > ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (2 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device Benjamin Tissoires ` (6 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input From: Lyude Paul <thatslyude@gmail.com> This adds basic functionality for PS/2 passthrough on Synaptics Touchpads using RMI4 through smbus. Signed-off-by: Lyude Paul <thatslyude@gmail.com> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/mouse/psmouse-base.c | 6 + drivers/input/rmi4/Kconfig | 9 ++ drivers/input/rmi4/Makefile | 1 + drivers/input/rmi4/rmi_bus.c | 3 + drivers/input/rmi4/rmi_driver.h | 1 + drivers/input/rmi4/rmi_f03.c | 226 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/serio.h | 1 + 7 files changed, 247 insertions(+) create mode 100644 drivers/input/rmi4/rmi_f03.c diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index dbc002a..fa2d700 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -1666,6 +1666,12 @@ static struct serio_device_id psmouse_serio_ids[] = { .id = SERIO_ANY, .extra = SERIO_ANY, }, + { + .type = SERIO_RMI_PSTHRU, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, { 0 } }; diff --git a/drivers/input/rmi4/Kconfig b/drivers/input/rmi4/Kconfig index 86a180b..cfc14b3 100644 --- a/drivers/input/rmi4/Kconfig +++ b/drivers/input/rmi4/Kconfig @@ -39,6 +39,15 @@ config RMI4_SMB To compile this driver as a module, choose M here: the module will be called rmi_smbus. +config RMI4_F03 + bool "RMI4 Function 03 (PS2 Guest)" + depends on RMI4_CORE + help + Say Y here if you want to add support for RMI4 function 03. + + Function 03 provides PS2 guest support for RMI4 devices. This + includes support for TrackPoints on TouchPads. + config RMI4_2D_SENSOR bool depends on RMI4_CORE diff --git a/drivers/input/rmi4/Makefile b/drivers/input/rmi4/Makefile index 3c8ebf2..676e636 100644 --- a/drivers/input/rmi4/Makefile +++ b/drivers/input/rmi4/Makefile @@ -4,6 +4,7 @@ rmi_core-y := rmi_bus.o rmi_driver.o rmi_f01.o rmi_core-$(CONFIG_RMI4_2D_SENSOR) += rmi_2d_sensor.o # Function drivers +rmi_core-$(CONFIG_RMI4_F03) += rmi_f03.o rmi_core-$(CONFIG_RMI4_F11) += rmi_f11.o rmi_core-$(CONFIG_RMI4_F12) += rmi_f12.o rmi_core-$(CONFIG_RMI4_F30) += rmi_f30.o diff --git a/drivers/input/rmi4/rmi_bus.c b/drivers/input/rmi4/rmi_bus.c index a735806..e1e3b80 100644 --- a/drivers/input/rmi4/rmi_bus.c +++ b/drivers/input/rmi4/rmi_bus.c @@ -303,6 +303,9 @@ struct bus_type rmi_bus_type = { static struct rmi_function_handler *fn_handlers[] = { &rmi_f01_handler, +#ifdef CONFIG_RMI4_F03 + &rmi_f03_handler, +#endif #ifdef CONFIG_RMI4_F11 &rmi_f11_handler, #endif diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h index 6e140fa..a7cb383 100644 --- a/drivers/input/rmi4/rmi_driver.h +++ b/drivers/input/rmi4/rmi_driver.h @@ -99,6 +99,7 @@ void rmi_unregister_physical_driver(void); char *rmi_f01_get_product_ID(struct rmi_function *fn); extern struct rmi_function_handler rmi_f01_handler; +extern struct rmi_function_handler rmi_f03_handler; extern struct rmi_function_handler rmi_f11_handler; extern struct rmi_function_handler rmi_f12_handler; extern struct rmi_function_handler rmi_f30_handler; diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c new file mode 100644 index 0000000..9945512 --- /dev/null +++ b/drivers/input/rmi4/rmi_f03.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2015-2016 Red Hat + * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/serio.h> +#include <linux/notifier.h> +#include "rmi_driver.h" + +#define RMI_F03_RX_DATA_OFB 0x01 +#define RMI_F03_OB_SIZE 2 + +#define RMI_F03_OB_OFFSET 2 +#define RMI_F03_OB_DATA_OFFSET 1 +#define RMI_F03_OB_FLAG_TIMEOUT (1 << 6) +#define RMI_F03_OB_FLAG_PARITY (1 << 7) + +#define RMI_F03_DEVICE_COUNT 0x07 +#define RMI_F03_BYTES_PER_DEVICE_MASK 0x70 +#define RMI_F03_BYTES_PER_DEVICE_SHIFT 4 +#define RMI_F03_QUEUE_LENGTH 0x0F + +struct f03_data { + struct rmi_function *fn; + + struct serio *serio; + + unsigned int overwrite_buttons; + + u8 device_count; + u8 rx_queue_length; +}; + +static int rmi_f03_pt_write(struct serio *id, unsigned char val) +{ + struct f03_data *f03 = id->port_data; + int rc; + + dev_dbg(&f03->fn->dev, "%s: Wrote %.2hhx to PS/2 passthrough address", + __func__, val); + + rc = rmi_write(f03->fn->rmi_dev, f03->fn->fd.data_base_addr, val); + if (rc) { + dev_err(&f03->fn->dev, + "%s: Failed to write to F03 TX register.\n", __func__); + return rc; + } + + return 0; +} + +static inline int rmi_f03_initialize(struct rmi_function *fn) +{ + struct f03_data *f03; + struct device *dev = &fn->dev; + int rc; + u8 bytes_per_device; + u8 query1; + size_t query2_len; + + rc = rmi_read(fn->rmi_dev, fn->fd.query_base_addr, &query1); + if (rc) { + dev_err(dev, "Failed to read query register.\n"); + return rc; + } + + f03 = devm_kzalloc(dev, sizeof(struct f03_data), GFP_KERNEL); + if (!f03) + return -ENOMEM; + + f03->device_count = query1 & RMI_F03_DEVICE_COUNT; + bytes_per_device = (query1 & RMI_F03_BYTES_PER_DEVICE_MASK) >> + RMI_F03_BYTES_PER_DEVICE_SHIFT; + + query2_len = f03->device_count * bytes_per_device; + + /* + * The first generation of image sensors don't have a second part to + * their f03 query, as such we have to set some of these values manually + */ + if (query2_len < 1) { + f03->device_count = 1; + f03->rx_queue_length = 7; + } else { + u8 query2[query2_len]; + + rc = rmi_read_block(fn->rmi_dev, fn->fd.query_base_addr + 1, + query2, query2_len); + if (rc) { + dev_err(dev, "Failed to read second set of query registers.\n"); + return rc; + } + + f03->rx_queue_length = query2[0] & RMI_F03_QUEUE_LENGTH; + } + + f03->fn = fn; + + dev_set_drvdata(dev, f03); + + return f03->device_count; +} + +static inline int rmi_f03_register_pt(struct rmi_function *fn) +{ + struct f03_data *f03 = dev_get_drvdata(&fn->dev); + struct serio *serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + + if (!serio) + return -ENOMEM; + + serio->id.type = SERIO_RMI_PSTHRU; + serio->write = rmi_f03_pt_write; + serio->port_data = f03; + + strlcpy(serio->name, "Synaptics RMI4 PS2 pass-through", + sizeof(serio->name)); + strlcpy(serio->phys, "synaptics-rmi4-pt/serio1", + sizeof(serio->phys)); + serio->dev.parent = &fn->dev; + + f03->serio = serio; + + serio_register_port(serio); + + return 0; +} + +static int rmi_f03_probe(struct rmi_function *fn) +{ + int rc; + + rc = rmi_f03_initialize(fn); + if (rc < 0) + return rc; + + dev_dbg(&fn->dev, "%d devices on PS/2 passthrough", rc); + + rc = rmi_f03_register_pt(fn); + if (rc) + return rc; + + return 0; +} + +static int rmi_f03_config(struct rmi_function *fn) +{ + fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask); + + return 0; +} + +static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) +{ + struct f03_data *f03 = dev_get_drvdata(&fn->dev); + u16 data_addr = fn->fd.data_base_addr; + const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; + u8 obs[ob_len]; + u8 ob_status; + u8 ob_data; + unsigned int serio_flags; + int i; + int retval; + + /* Grab all of the data registers, and check them for data */ + retval = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, + &obs, ob_len); + if (retval) { + dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", + __func__); + serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); + return retval; + } + + for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) { + ob_status = obs[i]; + ob_data = obs[i + RMI_F03_OB_DATA_OFFSET]; + serio_flags = 0; + + if (!(ob_status & RMI_F03_RX_DATA_OFB)) + continue; + + if (ob_status & RMI_F03_OB_FLAG_TIMEOUT) + serio_flags |= SERIO_TIMEOUT; + if (ob_status & RMI_F03_OB_FLAG_PARITY) + serio_flags |= SERIO_PARITY; + + dev_dbg(&fn->dev, + "%s: Received %.2hhx from PS2 guest T: %c P: %c\n", + __func__, ob_data, + serio_flags & SERIO_TIMEOUT ? 'Y' : 'N', + serio_flags & SERIO_PARITY ? 'Y' : 'N'); + + serio_interrupt(f03->serio, ob_data, serio_flags); + } + + return 0; +} + +static void rmi_f03_remove(struct rmi_function *fn) +{ + struct f03_data *f03 = dev_get_drvdata(&fn->dev); + + serio_unregister_port(f03->serio); +} + +struct rmi_function_handler rmi_f03_handler = { + .driver = { + .name = "rmi4_f03", + }, + .func = 0x03, + .probe = rmi_f03_probe, + .config = rmi_f03_config, + .attention = rmi_f03_attention, + .remove = rmi_f03_remove, +}; + +MODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>"); +MODULE_DESCRIPTION("RMI F03 module"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/serio.h b/include/uapi/linux/serio.h index f2447a8..7012178 100644 --- a/include/uapi/linux/serio.h +++ b/include/uapi/linux/serio.h @@ -30,6 +30,7 @@ #define SERIO_HIL_MLC 0x03 #define SERIO_PS_PSTHRU 0x05 #define SERIO_8042_XL 0x06 +#define SERIO_RMI_PSTHRU 0x07 /* * Serio protocols -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 2016-08-18 9:24 ` [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 Benjamin Tissoires @ 2016-08-27 1:35 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:35 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > From: Lyude Paul <thatslyude@gmail.com> > > This adds basic functionality for PS/2 passthrough on Synaptics > Touchpads using RMI4 through smbus. > > Signed-off-by: Lyude Paul <thatslyude@gmail.com> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Reviewed-by: Andrew Duggan <aduggan@synaptics.com> > --- > drivers/input/mouse/psmouse-base.c | 6 + > drivers/input/rmi4/Kconfig | 9 ++ > drivers/input/rmi4/Makefile | 1 + > drivers/input/rmi4/rmi_bus.c | 3 + > drivers/input/rmi4/rmi_driver.h | 1 + > drivers/input/rmi4/rmi_f03.c | 226 +++++++++++++++++++++++++++++++++++++ > include/uapi/linux/serio.h | 1 + > 7 files changed, 247 insertions(+) > create mode 100644 drivers/input/rmi4/rmi_f03.c > > diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c > index dbc002a..fa2d700 100644 > --- a/drivers/input/mouse/psmouse-base.c > +++ b/drivers/input/mouse/psmouse-base.c > @@ -1666,6 +1666,12 @@ static struct serio_device_id psmouse_serio_ids[] = { > .id = SERIO_ANY, > .extra = SERIO_ANY, > }, > + { > + .type = SERIO_RMI_PSTHRU, > + .proto = SERIO_ANY, > + .id = SERIO_ANY, > + .extra = SERIO_ANY, > + }, > { 0 } > }; > > diff --git a/drivers/input/rmi4/Kconfig b/drivers/input/rmi4/Kconfig > index 86a180b..cfc14b3 100644 > --- a/drivers/input/rmi4/Kconfig > +++ b/drivers/input/rmi4/Kconfig > @@ -39,6 +39,15 @@ config RMI4_SMB > To compile this driver as a module, choose M here: the module will be > called rmi_smbus. > > +config RMI4_F03 > + bool "RMI4 Function 03 (PS2 Guest)" > + depends on RMI4_CORE > + help > + Say Y here if you want to add support for RMI4 function 03. > + > + Function 03 provides PS2 guest support for RMI4 devices. This > + includes support for TrackPoints on TouchPads. > + > config RMI4_2D_SENSOR > bool > depends on RMI4_CORE > diff --git a/drivers/input/rmi4/Makefile b/drivers/input/rmi4/Makefile > index 3c8ebf2..676e636 100644 > --- a/drivers/input/rmi4/Makefile > +++ b/drivers/input/rmi4/Makefile > @@ -4,6 +4,7 @@ rmi_core-y := rmi_bus.o rmi_driver.o rmi_f01.o > rmi_core-$(CONFIG_RMI4_2D_SENSOR) += rmi_2d_sensor.o > > # Function drivers > +rmi_core-$(CONFIG_RMI4_F03) += rmi_f03.o > rmi_core-$(CONFIG_RMI4_F11) += rmi_f11.o > rmi_core-$(CONFIG_RMI4_F12) += rmi_f12.o > rmi_core-$(CONFIG_RMI4_F30) += rmi_f30.o > diff --git a/drivers/input/rmi4/rmi_bus.c b/drivers/input/rmi4/rmi_bus.c > index a735806..e1e3b80 100644 > --- a/drivers/input/rmi4/rmi_bus.c > +++ b/drivers/input/rmi4/rmi_bus.c > @@ -303,6 +303,9 @@ struct bus_type rmi_bus_type = { > > static struct rmi_function_handler *fn_handlers[] = { > &rmi_f01_handler, > +#ifdef CONFIG_RMI4_F03 > + &rmi_f03_handler, > +#endif > #ifdef CONFIG_RMI4_F11 > &rmi_f11_handler, > #endif > diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h > index 6e140fa..a7cb383 100644 > --- a/drivers/input/rmi4/rmi_driver.h > +++ b/drivers/input/rmi4/rmi_driver.h > @@ -99,6 +99,7 @@ void rmi_unregister_physical_driver(void); > char *rmi_f01_get_product_ID(struct rmi_function *fn); > > extern struct rmi_function_handler rmi_f01_handler; > +extern struct rmi_function_handler rmi_f03_handler; > extern struct rmi_function_handler rmi_f11_handler; > extern struct rmi_function_handler rmi_f12_handler; > extern struct rmi_function_handler rmi_f30_handler; > diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c > new file mode 100644 > index 0000000..9945512 > --- /dev/null > +++ b/drivers/input/rmi4/rmi_f03.c > @@ -0,0 +1,226 @@ > +/* > + * Copyright (C) 2015-2016 Red Hat > + * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com> > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation. > + */ > + > +#include <linux/kernel.h> > +#include <linux/slab.h> > +#include <linux/serio.h> > +#include <linux/notifier.h> > +#include "rmi_driver.h" > + > +#define RMI_F03_RX_DATA_OFB 0x01 > +#define RMI_F03_OB_SIZE 2 > + > +#define RMI_F03_OB_OFFSET 2 > +#define RMI_F03_OB_DATA_OFFSET 1 > +#define RMI_F03_OB_FLAG_TIMEOUT (1 << 6) > +#define RMI_F03_OB_FLAG_PARITY (1 << 7) > + > +#define RMI_F03_DEVICE_COUNT 0x07 > +#define RMI_F03_BYTES_PER_DEVICE_MASK 0x70 > +#define RMI_F03_BYTES_PER_DEVICE_SHIFT 4 > +#define RMI_F03_QUEUE_LENGTH 0x0F > + > +struct f03_data { > + struct rmi_function *fn; > + > + struct serio *serio; > + > + unsigned int overwrite_buttons; > + > + u8 device_count; > + u8 rx_queue_length; > +}; > + > +static int rmi_f03_pt_write(struct serio *id, unsigned char val) > +{ > + struct f03_data *f03 = id->port_data; > + int rc; > + > + dev_dbg(&f03->fn->dev, "%s: Wrote %.2hhx to PS/2 passthrough address", > + __func__, val); Note, at some point these dev_dbg messages should be switched to rmi_dbg to allow them to be turned on then the RMI_DEBUG_FN bit is set in the debug_flags. > + > + rc = rmi_write(f03->fn->rmi_dev, f03->fn->fd.data_base_addr, val); > + if (rc) { > + dev_err(&f03->fn->dev, > + "%s: Failed to write to F03 TX register.\n", __func__); > + return rc; > + } > + > + return 0; > +} > + > +static inline int rmi_f03_initialize(struct rmi_function *fn) > +{ > + struct f03_data *f03; > + struct device *dev = &fn->dev; > + int rc; > + u8 bytes_per_device; > + u8 query1; > + size_t query2_len; > + > + rc = rmi_read(fn->rmi_dev, fn->fd.query_base_addr, &query1); > + if (rc) { > + dev_err(dev, "Failed to read query register.\n"); > + return rc; > + } > + > + f03 = devm_kzalloc(dev, sizeof(struct f03_data), GFP_KERNEL); > + if (!f03) > + return -ENOMEM; > + > + f03->device_count = query1 & RMI_F03_DEVICE_COUNT; > + bytes_per_device = (query1 & RMI_F03_BYTES_PER_DEVICE_MASK) >> > + RMI_F03_BYTES_PER_DEVICE_SHIFT; > + > + query2_len = f03->device_count * bytes_per_device; > + > + /* > + * The first generation of image sensors don't have a second part to > + * their f03 query, as such we have to set some of these values manually > + */ > + if (query2_len < 1) { > + f03->device_count = 1; > + f03->rx_queue_length = 7; > + } else { > + u8 query2[query2_len]; > + > + rc = rmi_read_block(fn->rmi_dev, fn->fd.query_base_addr + 1, > + query2, query2_len); > + if (rc) { > + dev_err(dev, "Failed to read second set of query registers.\n"); > + return rc; > + } > + > + f03->rx_queue_length = query2[0] & RMI_F03_QUEUE_LENGTH; > + } > + > + f03->fn = fn; > + > + dev_set_drvdata(dev, f03); > + > + return f03->device_count; > +} > + > +static inline int rmi_f03_register_pt(struct rmi_function *fn) > +{ > + struct f03_data *f03 = dev_get_drvdata(&fn->dev); > + struct serio *serio = kzalloc(sizeof(struct serio), GFP_KERNEL); > + > + if (!serio) > + return -ENOMEM; > + > + serio->id.type = SERIO_RMI_PSTHRU; > + serio->write = rmi_f03_pt_write; > + serio->port_data = f03; > + > + strlcpy(serio->name, "Synaptics RMI4 PS2 pass-through", > + sizeof(serio->name)); > + strlcpy(serio->phys, "synaptics-rmi4-pt/serio1", > + sizeof(serio->phys)); > + serio->dev.parent = &fn->dev; > + > + f03->serio = serio; > + > + serio_register_port(serio); > + > + return 0; > +} > + > +static int rmi_f03_probe(struct rmi_function *fn) > +{ > + int rc; > + > + rc = rmi_f03_initialize(fn); > + if (rc < 0) > + return rc; > + > + dev_dbg(&fn->dev, "%d devices on PS/2 passthrough", rc); > + > + rc = rmi_f03_register_pt(fn); > + if (rc) > + return rc; > + > + return 0; > +} > + > +static int rmi_f03_config(struct rmi_function *fn) > +{ > + fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask); > + > + return 0; > +} > + > +static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) > +{ > + struct f03_data *f03 = dev_get_drvdata(&fn->dev); > + u16 data_addr = fn->fd.data_base_addr; > + const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; > + u8 obs[ob_len]; > + u8 ob_status; > + u8 ob_data; > + unsigned int serio_flags; > + int i; > + int retval; > + > + /* Grab all of the data registers, and check them for data */ > + retval = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, > + &obs, ob_len); > + if (retval) { > + dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", > + __func__); > + serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); > + return retval; > + } > + > + for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) { > + ob_status = obs[i]; > + ob_data = obs[i + RMI_F03_OB_DATA_OFFSET]; > + serio_flags = 0; > + > + if (!(ob_status & RMI_F03_RX_DATA_OFB)) > + continue; > + > + if (ob_status & RMI_F03_OB_FLAG_TIMEOUT) > + serio_flags |= SERIO_TIMEOUT; > + if (ob_status & RMI_F03_OB_FLAG_PARITY) > + serio_flags |= SERIO_PARITY; > + > + dev_dbg(&fn->dev, > + "%s: Received %.2hhx from PS2 guest T: %c P: %c\n", > + __func__, ob_data, > + serio_flags & SERIO_TIMEOUT ? 'Y' : 'N', > + serio_flags & SERIO_PARITY ? 'Y' : 'N'); > + > + serio_interrupt(f03->serio, ob_data, serio_flags); > + } > + > + return 0; > +} > + > +static void rmi_f03_remove(struct rmi_function *fn) > +{ > + struct f03_data *f03 = dev_get_drvdata(&fn->dev); > + > + serio_unregister_port(f03->serio); > +} > + > +struct rmi_function_handler rmi_f03_handler = { > + .driver = { > + .name = "rmi4_f03", > + }, > + .func = 0x03, > + .probe = rmi_f03_probe, > + .config = rmi_f03_config, > + .attention = rmi_f03_attention, > + .remove = rmi_f03_remove, > +}; > + > +MODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>"); > +MODULE_DESCRIPTION("RMI F03 module"); > +MODULE_LICENSE("GPL"); > diff --git a/include/uapi/linux/serio.h b/include/uapi/linux/serio.h > index f2447a8..7012178 100644 > --- a/include/uapi/linux/serio.h > +++ b/include/uapi/linux/serio.h > @@ -30,6 +30,7 @@ > #define SERIO_HIL_MLC 0x03 > #define SERIO_PS_PSTHRU 0x05 > #define SERIO_8042_XL 0x06 > +#define SERIO_RMI_PSTHRU 0x07 > > /* > * Serio protocols > ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (3 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() Benjamin Tissoires ` (5 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input From: Dennis Wassenberg <dennis.wassenberg@secunet.com> First check if there are data available passed by the transport device. If data available use these data. If there are no data available try to read the rmi block if dsata are passed this way. This is the way the other rmi function handlers will do this. That why apply this to f03 as well. This will fix corrupted or missing data issues. Signed-off-by: Dennis Wassenberg <dennis.wassenberg@secunet.com> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/rmi4/rmi_f03.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c index 9945512..daae1c95 100644 --- a/drivers/input/rmi4/rmi_f03.c +++ b/drivers/input/rmi4/rmi_f03.c @@ -158,6 +158,7 @@ static int rmi_f03_config(struct rmi_function *fn) static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) { + struct rmi_device *rmi_dev = fn->rmi_dev; struct f03_data *f03 = dev_get_drvdata(&fn->dev); u16 data_addr = fn->fd.data_base_addr; const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; @@ -166,16 +167,32 @@ static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) u8 ob_data; unsigned int serio_flags; int i; - int retval; - - /* Grab all of the data registers, and check them for data */ - retval = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, - &obs, ob_len); - if (retval) { - dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", - __func__); - serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); - return retval; + int ret; + + if (!rmi_dev || !rmi_dev->xport) + return -ENODEV; + + if (rmi_dev->xport->attn_data) { + /* First grab the data passed by the transport device */ + if (rmi_dev->xport->attn_size < ob_len) { + dev_warn(&fn->dev, "F03 interrupted, but data is missing!\n"); + return 0; + } + + memcpy(obs, rmi_dev->xport->attn_data, ob_len); + + rmi_dev->xport->attn_data += ob_len; + rmi_dev->xport->attn_size -= ob_len; + } else { + /* Grab all of the data registers, and check them for data */ + ret = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, + &obs, ob_len); + if (ret) { + dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", + __func__); + serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); + return ret; + } } for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) { -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device 2016-08-18 9:24 ` [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device Benjamin Tissoires @ 2016-08-27 1:35 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:35 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > From: Dennis Wassenberg <dennis.wassenberg@secunet.com> > > First check if there are data available passed by the transport device. > If data available use these data. If there are no data available > try to read the rmi block if dsata are passed this way. > > This is the way the other rmi function handlers will do this. > That why apply this to f03 as well. > > This will fix corrupted or missing data issues. > This patch is needed on HID devices because the firmware reads F03 data registers and adds them to the HID attention report. Reading those registers from the driver after the firmware read them will result in invalid data. Which is exactly what Dennis is describing here. > Signed-off-by: Dennis Wassenberg <dennis.wassenberg@secunet.com> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Reviewed-by: Andrew Duggan <aduggan@synaptics.com> > --- > drivers/input/rmi4/rmi_f03.c | 37 +++++++++++++++++++++++++++---------- > 1 file changed, 27 insertions(+), 10 deletions(-) > > diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c > index 9945512..daae1c95 100644 > --- a/drivers/input/rmi4/rmi_f03.c > +++ b/drivers/input/rmi4/rmi_f03.c > @@ -158,6 +158,7 @@ static int rmi_f03_config(struct rmi_function *fn) > > static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) > { > + struct rmi_device *rmi_dev = fn->rmi_dev; > struct f03_data *f03 = dev_get_drvdata(&fn->dev); > u16 data_addr = fn->fd.data_base_addr; > const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE; > @@ -166,16 +167,32 @@ static int rmi_f03_attention(struct rmi_function *fn, unsigned long *irq_bits) > u8 ob_data; > unsigned int serio_flags; > int i; > - int retval; > - > - /* Grab all of the data registers, and check them for data */ > - retval = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, > - &obs, ob_len); > - if (retval) { > - dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", > - __func__); > - serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); > - return retval; > + int ret; > + > + if (!rmi_dev || !rmi_dev->xport) > + return -ENODEV; > + > + if (rmi_dev->xport->attn_data) { > + /* First grab the data passed by the transport device */ > + if (rmi_dev->xport->attn_size < ob_len) { > + dev_warn(&fn->dev, "F03 interrupted, but data is missing!\n"); > + return 0; > + } > + > + memcpy(obs, rmi_dev->xport->attn_data, ob_len); > + > + rmi_dev->xport->attn_data += ob_len; > + rmi_dev->xport->attn_size -= ob_len; > + } else { > + /* Grab all of the data registers, and check them for data */ > + ret = rmi_read_block(fn->rmi_dev, data_addr + RMI_F03_OB_OFFSET, > + &obs, ob_len); > + if (ret) { > + dev_err(&fn->dev, "%s: Failed to read F03 output buffers.\n", > + __func__); > + serio_interrupt(f03->serio, 0, SERIO_TIMEOUT); > + return ret; > + } > } > > for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) { > ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (4 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest Benjamin Tissoires ` (4 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input If a function needs to communicate with an other, it's better to have a way to retrieve this other. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/rmi4/rmi_driver.c | 13 +++++++++++++ drivers/input/rmi4/rmi_driver.h | 1 + 2 files changed, 14 insertions(+) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index faa295e..304f142 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -181,6 +181,19 @@ int rmi_process_interrupt_requests(struct rmi_device *rmi_dev) } EXPORT_SYMBOL_GPL(rmi_process_interrupt_requests); +struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number) +{ + struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev); + struct rmi_function *entry; + + list_for_each_entry(entry, &data->function_list, node) { + if (entry->fd.function_number == number) + return entry; + } + + return NULL; +} + static int suspend_one_function(struct rmi_function *fn) { struct rmi_function_handler *fh; diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h index a7cb383..e4be773 100644 --- a/drivers/input/rmi4/rmi_driver.h +++ b/drivers/input/rmi4/rmi_driver.h @@ -95,6 +95,7 @@ bool rmi_register_desc_has_subpacket(const struct rmi_register_desc_item *item, bool rmi_is_physical_driver(struct device_driver *); int rmi_register_physical_driver(void); void rmi_unregister_physical_driver(void); +struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); char *rmi_f01_get_product_ID(struct rmi_function *fn); -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() 2016-08-18 9:24 ` [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() Benjamin Tissoires @ 2016-08-27 1:35 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:35 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > If a function needs to communicate with an other, it's better to have > a way to retrieve this other. > > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Reviewed-by: Andrew Duggan <aduggan@synaptics.com> > --- > drivers/input/rmi4/rmi_driver.c | 13 +++++++++++++ > drivers/input/rmi4/rmi_driver.h | 1 + > 2 files changed, 14 insertions(+) > > diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c > index faa295e..304f142 100644 > --- a/drivers/input/rmi4/rmi_driver.c > +++ b/drivers/input/rmi4/rmi_driver.c > @@ -181,6 +181,19 @@ int rmi_process_interrupt_requests(struct rmi_device *rmi_dev) > } > EXPORT_SYMBOL_GPL(rmi_process_interrupt_requests); > > +struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number) > +{ > + struct rmi_driver_data *data = dev_get_drvdata(&rmi_dev->dev); > + struct rmi_function *entry; > + > + list_for_each_entry(entry, &data->function_list, node) { > + if (entry->fd.function_number == number) > + return entry; > + } > + > + return NULL; > +} > + > static int suspend_one_function(struct rmi_function *fn) > { > struct rmi_function_handler *fh; > diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h > index a7cb383..e4be773 100644 > --- a/drivers/input/rmi4/rmi_driver.h > +++ b/drivers/input/rmi4/rmi_driver.h > @@ -95,6 +95,7 @@ bool rmi_register_desc_has_subpacket(const struct rmi_register_desc_item *item, > bool rmi_is_physical_driver(struct device_driver *); > int rmi_register_physical_driver(void); > void rmi_unregister_physical_driver(void); > +struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); > > char *rmi_f01_get_product_ID(struct rmi_function *fn); > > ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (5 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 08/11] Input: synaptics - allocate a Synaptics Intertouch device Benjamin Tissoires ` (3 subsequent siblings) 10 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input From: Lyude Paul <thatslyude@gmail.com> On the latest series of ThinkPads, the button events for the TrackPoint are reported through the touchpad itself as opposed to the TrackPoint device. In order to report these buttons properly, we need to forward them to the TrackPoint device and send the button presses/releases through there instead. Signed-off-by: Lyude Paul <thatslyude@gmail.com> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/rmi4/rmi_driver.h | 13 +++++++++ drivers/input/rmi4/rmi_f03.c | 28 ++++++++++++++++++ drivers/input/rmi4/rmi_f30.c | 64 +++++++++++++++++++++++++++++++++++------ 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h index e4be773..a0b1978 100644 --- a/drivers/input/rmi4/rmi_driver.h +++ b/drivers/input/rmi4/rmi_driver.h @@ -99,6 +99,19 @@ struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); char *rmi_f01_get_product_ID(struct rmi_function *fn); +#ifdef CONFIG_RMI4_F03 +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, + int value); +void rmi_f03_commit_buttons(struct rmi_function *fn); +#else +static inline int rmi_f03_overwrite_button(struct rmi_function *fn, + unsigned int button, int value) +{ + return 0; +} +static inline void rmi_f03_commit_buttons(struct rmi_function *fn) {} +#endif + extern struct rmi_function_handler rmi_f01_handler; extern struct rmi_function_handler rmi_f03_handler; extern struct rmi_function_handler rmi_f11_handler; diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c index daae1c95..535f426 100644 --- a/drivers/input/rmi4/rmi_f03.c +++ b/drivers/input/rmi4/rmi_f03.c @@ -37,6 +37,34 @@ struct f03_data { u8 rx_queue_length; }; +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, + int value) +{ + struct f03_data *f03 = dev_get_drvdata(&fn->dev); + unsigned int bit = BIT(button); + + if (button > 2) + return -EINVAL; + + if (value) + f03->overwrite_buttons |= bit; + else + f03->overwrite_buttons &= ~bit; + + return 0; +} + +void rmi_f03_commit_buttons(struct rmi_function *fn) +{ + struct f03_data *f03 = dev_get_drvdata(&fn->dev); + int i; + + f03->serio->extra_byte = f03->overwrite_buttons; + + for (i = 0; i < 3; i++) + serio_interrupt(f03->serio, 0x00, 0x00); +} + static int rmi_f03_pt_write(struct serio *id, unsigned char val) { struct f03_data *f03 = id->port_data; diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c index 7990bb0..14e3221 100644 --- a/drivers/input/rmi4/rmi_f30.c +++ b/drivers/input/rmi4/rmi_f30.c @@ -74,8 +74,11 @@ struct f30_data { u8 data_regs[RMI_F30_CTRL_MAX_BYTES]; u16 *gpioled_key_map; + u16 *gpio_passthrough_key_map; struct input_dev *input; + bool trackstick_buttons; + struct rmi_function *f03; }; static int rmi_f30_read_control_parameters(struct rmi_function *fn, @@ -108,6 +111,13 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) if (!f30->input) return 0; + if (f30->trackstick_buttons && !f30->f03) { + f30->f03 = rmi_find_function(rmi_dev, 3); + + if (!f30->f03) + return -EBUSY; + } + /* Read the gpi led data. */ if (rmi_dev->xport->attn_data) { memcpy(f30->data_regs, rmi_dev->xport->attn_data, @@ -128,23 +138,29 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) for (reg_num = 0; reg_num < f30->register_count; ++reg_num) { for (i = 0; gpiled < f30->gpioled_count && i < 8; ++i, ++gpiled) { - if (f30->gpioled_key_map[gpiled] != 0) { - /* buttons have pull up resistors */ - value = (((f30->data_regs[reg_num] >> i) & 0x01) - == 0); + /* buttons have pull up resistors */ + value = (((f30->data_regs[reg_num] >> i) & 0x01) == 0); + if (f30->gpioled_key_map[gpiled] != 0) { rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: call input report key (0x%04x) value (0x%02x)", __func__, f30->gpioled_key_map[gpiled], value); + input_report_key(f30->input, f30->gpioled_key_map[gpiled], value); + } else if (f30->gpio_passthrough_key_map[gpiled]) { + rmi_f03_overwrite_button(f30->f03, + f30->gpio_passthrough_key_map[gpiled] - BTN_LEFT, + value); } - } } + if (f30->trackstick_buttons) + rmi_f03_commit_buttons(f30->f03); + return 0; } @@ -242,10 +258,10 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) int retval = 0; int control_address; int i; - int button; + int button, extra_button; u8 buf[RMI_F30_QUERY_SIZE]; u8 *ctrl_reg; - u8 *map_memory; + u8 *map_memory, *pt_memory; f30 = devm_kzalloc(&fn->dev, sizeof(struct f30_data), GFP_KERNEL); @@ -343,15 +359,47 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) map_memory = devm_kzalloc(&fn->dev, (f30->gpioled_count * (sizeof(u16))), GFP_KERNEL); - if (!map_memory) { + pt_memory = devm_kzalloc(&fn->dev, + (f30->gpioled_count * (sizeof(u16))), + GFP_KERNEL); + if (!map_memory || !pt_memory) { dev_err(&fn->dev, "Failed to allocate gpioled map memory.\n"); return -ENOMEM; } f30->gpioled_key_map = (u16 *)map_memory; + f30->gpio_passthrough_key_map = (u16 *)pt_memory; pdata = rmi_get_platform_data(rmi_dev); if (pdata && f30->has_gpio) { + /* + * For touchpads the buttons are mapped as: + * - bit 0 = Left, bit 1 = right, bit 2 = middle / clickbutton + * - 3, 4, 5 are extended buttons and + * - 6 and 7 are other sorts of GPIOs + */ + button = BTN_LEFT; + extra_button = BTN_LEFT; + for (i = 0; i < f30->gpioled_count - 2 && i < 3; i++) { + if (rmi_f30_is_valid_button(i, f30->ctrl)) + f30->gpioled_key_map[i] = button++; + } + + f30->trackstick_buttons = pdata && + pdata->f30_data.trackstick_buttons; + + if (f30->trackstick_buttons) { + for (i = 3; i < f30->gpioled_count - 2; i++) { + if (rmi_f30_is_valid_button(i, f30->ctrl)) + f30->gpio_passthrough_key_map[i] = extra_button++; + } + } else { + for (i = 3; i < f30->gpioled_count - 2; i++) { + if (rmi_f30_is_valid_button(i, f30->ctrl)) + f30->gpioled_key_map[i] = button++; + } + } + } else if (f30->has_gpio) { button = BTN_LEFT; for (i = 0; i < f30->gpioled_count; i++) { if (rmi_f30_is_valid_button(i, f30->ctrl)) { -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest 2016-08-18 9:24 ` [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest Benjamin Tissoires @ 2016-08-27 1:35 ` Andrew Duggan 2016-08-30 15:13 ` Benjamin Tissoires 0 siblings, 1 reply; 24+ messages in thread From: Andrew Duggan @ 2016-08-27 1:35 UTC (permalink / raw) To: Benjamin Tissoires, Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Resending as plain text Hi Benjamin, This patch causes standard clickpads without extended buttons to not work. I'll explain some more below. On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > From: Lyude Paul <thatslyude@gmail.com> > > On the latest series of ThinkPads, the button events for the TrackPoint > are reported through the touchpad itself as opposed to the TrackPoint > device. In order to report these buttons properly, we need to forward > them to the TrackPoint device and send the button presses/releases > through there instead. > > Signed-off-by: Lyude Paul <thatslyude@gmail.com> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> > --- > drivers/input/rmi4/rmi_driver.h | 13 +++++++++ > drivers/input/rmi4/rmi_f03.c | 28 ++++++++++++++++++ > drivers/input/rmi4/rmi_f30.c | 64 +++++++++++++++++++++++++++++++++++------ > 3 files changed, 97 insertions(+), 8 deletions(-) > > diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h > index e4be773..a0b1978 100644 > --- a/drivers/input/rmi4/rmi_driver.h > +++ b/drivers/input/rmi4/rmi_driver.h > @@ -99,6 +99,19 @@ struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); > > char *rmi_f01_get_product_ID(struct rmi_function *fn); > > +#ifdef CONFIG_RMI4_F03 > +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, > + int value); > +void rmi_f03_commit_buttons(struct rmi_function *fn); > +#else > +static inline int rmi_f03_overwrite_button(struct rmi_function *fn, > + unsigned int button, int value) > +{ > + return 0; > +} > +static inline void rmi_f03_commit_buttons(struct rmi_function *fn) {} > +#endif > + > extern struct rmi_function_handler rmi_f01_handler; > extern struct rmi_function_handler rmi_f03_handler; > extern struct rmi_function_handler rmi_f11_handler; > diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c > index daae1c95..535f426 100644 > --- a/drivers/input/rmi4/rmi_f03.c > +++ b/drivers/input/rmi4/rmi_f03.c > @@ -37,6 +37,34 @@ struct f03_data { > u8 rx_queue_length; > }; > > +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, > + int value) > +{ > + struct f03_data *f03 = dev_get_drvdata(&fn->dev); > + unsigned int bit = BIT(button); > + > + if (button > 2) > + return -EINVAL; > + > + if (value) > + f03->overwrite_buttons |= bit; > + else > + f03->overwrite_buttons &= ~bit; > + > + return 0; > +} > + > +void rmi_f03_commit_buttons(struct rmi_function *fn) > +{ > + struct f03_data *f03 = dev_get_drvdata(&fn->dev); > + int i; > + > + f03->serio->extra_byte = f03->overwrite_buttons; > + > + for (i = 0; i < 3; i++) > + serio_interrupt(f03->serio, 0x00, 0x00); > +} > + > static int rmi_f03_pt_write(struct serio *id, unsigned char val) > { > struct f03_data *f03 = id->port_data; > diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c > index 7990bb0..14e3221 100644 > --- a/drivers/input/rmi4/rmi_f30.c > +++ b/drivers/input/rmi4/rmi_f30.c > @@ -74,8 +74,11 @@ struct f30_data { > > u8 data_regs[RMI_F30_CTRL_MAX_BYTES]; > u16 *gpioled_key_map; > + u16 *gpio_passthrough_key_map; > > struct input_dev *input; > + bool trackstick_buttons; > + struct rmi_function *f03; > }; > > static int rmi_f30_read_control_parameters(struct rmi_function *fn, > @@ -108,6 +111,13 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) > if (!f30->input) > return 0; > > + if (f30->trackstick_buttons && !f30->f03) { > + f30->f03 = rmi_find_function(rmi_dev, 3); > + > + if (!f30->f03) > + return -EBUSY; > + } > + > /* Read the gpi led data. */ > if (rmi_dev->xport->attn_data) { > memcpy(f30->data_regs, rmi_dev->xport->attn_data, > @@ -128,23 +138,29 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) > for (reg_num = 0; reg_num < f30->register_count; ++reg_num) { > for (i = 0; gpiled < f30->gpioled_count && i < 8; ++i, > ++gpiled) { > - if (f30->gpioled_key_map[gpiled] != 0) { > - /* buttons have pull up resistors */ > - value = (((f30->data_regs[reg_num] >> i) & 0x01) > - == 0); > + /* buttons have pull up resistors */ > + value = (((f30->data_regs[reg_num] >> i) & 0x01) == 0); > > + if (f30->gpioled_key_map[gpiled] != 0) { > rmi_dbg(RMI_DEBUG_FN, &fn->dev, > "%s: call input report key (0x%04x) value (0x%02x)", > __func__, > f30->gpioled_key_map[gpiled], value); > + > input_report_key(f30->input, > f30->gpioled_key_map[gpiled], > value); > + } else if (f30->gpio_passthrough_key_map[gpiled]) { > + rmi_f03_overwrite_button(f30->f03, > + f30->gpio_passthrough_key_map[gpiled] - BTN_LEFT, > + value); > } > - > } > } > > + if (f30->trackstick_buttons) > + rmi_f03_commit_buttons(f30->f03); > + > return 0; > } > > @@ -242,10 +258,10 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) > int retval = 0; > int control_address; > int i; > - int button; > + int button, extra_button; > u8 buf[RMI_F30_QUERY_SIZE]; > u8 *ctrl_reg; > - u8 *map_memory; > + u8 *map_memory, *pt_memory; > > f30 = devm_kzalloc(&fn->dev, sizeof(struct f30_data), > GFP_KERNEL); > @@ -343,15 +359,47 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) > map_memory = devm_kzalloc(&fn->dev, > (f30->gpioled_count * (sizeof(u16))), > GFP_KERNEL); > - if (!map_memory) { > + pt_memory = devm_kzalloc(&fn->dev, > + (f30->gpioled_count * (sizeof(u16))), > + GFP_KERNEL); > + if (!map_memory || !pt_memory) { > dev_err(&fn->dev, "Failed to allocate gpioled map memory.\n"); > return -ENOMEM; > } > > f30->gpioled_key_map = (u16 *)map_memory; > + f30->gpio_passthrough_key_map = (u16 *)pt_memory; > > pdata = rmi_get_platform_data(rmi_dev); > if (pdata && f30->has_gpio) { This existing check of pdata is not needed because pdata is embedded in the transport device and will never be NULL. That means that in this patch the else case will never be called. > + /* > + * For touchpads the buttons are mapped as: > + * - bit 0 = Left, bit 1 = right, bit 2 = middle / clickbutton > + * - 3, 4, 5 are extended buttons and > + * - 6 and 7 are other sorts of GPIOs > + */ > + button = BTN_LEFT; > + extra_button = BTN_LEFT; > + for (i = 0; i < f30->gpioled_count - 2 && i < 3; i++) { Subtracting 2 from gpioled_count does not have the intended affect. The name gpioled_count comes from the spec, but that byte is really a bit map. On a typical clickpad bit two is set as mentioned in the new comment. The result is that this for loop only runs once and it only checks the first bit of ctrl 2 and ctrl 3 for a valid button. Since bit 0 is not set then no valid buttons are reported for the clickpad. It looks like the Lenovo systems which this patch is targeting actually have 6 gpios defined (bits 2 - 7 are set) so this code works on those system since the "count" is sufficiently large. Also, maybe it's time to rename gpioled_count to something like gpioled_map. > + if (rmi_f30_is_valid_button(i, f30->ctrl)) > + f30->gpioled_key_map[i] = button++; > + } > + > + f30->trackstick_buttons = pdata && > + pdata->f30_data.trackstick_buttons; > + > + if (f30->trackstick_buttons) { > + for (i = 3; i < f30->gpioled_count - 2; i++) { > + if (rmi_f30_is_valid_button(i, f30->ctrl)) > + f30->gpio_passthrough_key_map[i] = extra_button++; > + } > + } else { > + for (i = 3; i < f30->gpioled_count - 2; i++) { > + if (rmi_f30_is_valid_button(i, f30->ctrl)) > + f30->gpioled_key_map[i] = button++; > + } > + } > + } else if (f30->has_gpio) { As noted above this else block is never called. Andrew > button = BTN_LEFT; > for (i = 0; i < f30->gpioled_count; i++) { > if (rmi_f30_is_valid_button(i, f30->ctrl)) { > ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest 2016-08-27 1:35 ` Andrew Duggan @ 2016-08-30 15:13 ` Benjamin Tissoires 2016-08-30 19:16 ` Andrew Duggan 0 siblings, 1 reply; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-30 15:13 UTC (permalink / raw) To: Andrew Duggan Cc: Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg, Peter Hutterer, linux-kernel, linux-input Hi Andrew, On Aug 26 2016 or thereabouts, Andrew Duggan wrote: > Resending as plain text > > Hi Benjamin, > > This patch causes standard clickpads without extended buttons to not work. > I'll explain some more below. > > On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: > >From: Lyude Paul <thatslyude@gmail.com> > > > >On the latest series of ThinkPads, the button events for the TrackPoint > >are reported through the touchpad itself as opposed to the TrackPoint > >device. In order to report these buttons properly, we need to forward > >them to the TrackPoint device and send the button presses/releases > >through there instead. > > > >Signed-off-by: Lyude Paul <thatslyude@gmail.com> > >Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> > >--- > > drivers/input/rmi4/rmi_driver.h | 13 +++++++++ > > drivers/input/rmi4/rmi_f03.c | 28 ++++++++++++++++++ > > drivers/input/rmi4/rmi_f30.c | 64 +++++++++++++++++++++++++++++++++++------ > > 3 files changed, 97 insertions(+), 8 deletions(-) > > > >diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h > >index e4be773..a0b1978 100644 > >--- a/drivers/input/rmi4/rmi_driver.h > >+++ b/drivers/input/rmi4/rmi_driver.h > >@@ -99,6 +99,19 @@ struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); > > > > char *rmi_f01_get_product_ID(struct rmi_function *fn); > > > >+#ifdef CONFIG_RMI4_F03 > >+int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, > >+ int value); > >+void rmi_f03_commit_buttons(struct rmi_function *fn); > >+#else > >+static inline int rmi_f03_overwrite_button(struct rmi_function *fn, > >+ unsigned int button, int value) > >+{ > >+ return 0; > >+} > >+static inline void rmi_f03_commit_buttons(struct rmi_function *fn) {} > >+#endif > >+ > > extern struct rmi_function_handler rmi_f01_handler; > > extern struct rmi_function_handler rmi_f03_handler; > > extern struct rmi_function_handler rmi_f11_handler; > >diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c > >index daae1c95..535f426 100644 > >--- a/drivers/input/rmi4/rmi_f03.c > >+++ b/drivers/input/rmi4/rmi_f03.c > >@@ -37,6 +37,34 @@ struct f03_data { > > u8 rx_queue_length; > > }; > > > >+int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, > >+ int value) > >+{ > >+ struct f03_data *f03 = dev_get_drvdata(&fn->dev); > >+ unsigned int bit = BIT(button); > >+ > >+ if (button > 2) > >+ return -EINVAL; > >+ > >+ if (value) > >+ f03->overwrite_buttons |= bit; > >+ else > >+ f03->overwrite_buttons &= ~bit; > >+ > >+ return 0; > >+} > >+ > >+void rmi_f03_commit_buttons(struct rmi_function *fn) > >+{ > >+ struct f03_data *f03 = dev_get_drvdata(&fn->dev); > >+ int i; > >+ > >+ f03->serio->extra_byte = f03->overwrite_buttons; > >+ > >+ for (i = 0; i < 3; i++) > >+ serio_interrupt(f03->serio, 0x00, 0x00); > >+} > >+ > > static int rmi_f03_pt_write(struct serio *id, unsigned char val) > > { > > struct f03_data *f03 = id->port_data; > >diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c > >index 7990bb0..14e3221 100644 > >--- a/drivers/input/rmi4/rmi_f30.c > >+++ b/drivers/input/rmi4/rmi_f30.c > >@@ -74,8 +74,11 @@ struct f30_data { > > > > u8 data_regs[RMI_F30_CTRL_MAX_BYTES]; > > u16 *gpioled_key_map; > >+ u16 *gpio_passthrough_key_map; > > > > struct input_dev *input; > >+ bool trackstick_buttons; > >+ struct rmi_function *f03; > > }; > > > > static int rmi_f30_read_control_parameters(struct rmi_function *fn, > >@@ -108,6 +111,13 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) > > if (!f30->input) > > return 0; > > > >+ if (f30->trackstick_buttons && !f30->f03) { > >+ f30->f03 = rmi_find_function(rmi_dev, 3); > >+ > >+ if (!f30->f03) > >+ return -EBUSY; > >+ } > >+ > > /* Read the gpi led data. */ > > if (rmi_dev->xport->attn_data) { > > memcpy(f30->data_regs, rmi_dev->xport->attn_data, > >@@ -128,23 +138,29 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) > > for (reg_num = 0; reg_num < f30->register_count; ++reg_num) { > > for (i = 0; gpiled < f30->gpioled_count && i < 8; ++i, > > ++gpiled) { > >- if (f30->gpioled_key_map[gpiled] != 0) { > >- /* buttons have pull up resistors */ > >- value = (((f30->data_regs[reg_num] >> i) & 0x01) > >- == 0); > >+ /* buttons have pull up resistors */ > >+ value = (((f30->data_regs[reg_num] >> i) & 0x01) == 0); > > > >+ if (f30->gpioled_key_map[gpiled] != 0) { > > rmi_dbg(RMI_DEBUG_FN, &fn->dev, > > "%s: call input report key (0x%04x) value (0x%02x)", > > __func__, > > f30->gpioled_key_map[gpiled], value); > >+ > > input_report_key(f30->input, > > f30->gpioled_key_map[gpiled], > > value); > >+ } else if (f30->gpio_passthrough_key_map[gpiled]) { > >+ rmi_f03_overwrite_button(f30->f03, > >+ f30->gpio_passthrough_key_map[gpiled] - BTN_LEFT, > >+ value); > > } > >- > > } > > } > > > >+ if (f30->trackstick_buttons) > >+ rmi_f03_commit_buttons(f30->f03); > >+ > > return 0; > > } > > > >@@ -242,10 +258,10 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) > > int retval = 0; > > int control_address; > > int i; > >- int button; > >+ int button, extra_button; > > u8 buf[RMI_F30_QUERY_SIZE]; > > u8 *ctrl_reg; > >- u8 *map_memory; > >+ u8 *map_memory, *pt_memory; > > > > f30 = devm_kzalloc(&fn->dev, sizeof(struct f30_data), > > GFP_KERNEL); > >@@ -343,15 +359,47 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) > > map_memory = devm_kzalloc(&fn->dev, > > (f30->gpioled_count * (sizeof(u16))), > > GFP_KERNEL); > >- if (!map_memory) { > >+ pt_memory = devm_kzalloc(&fn->dev, > >+ (f30->gpioled_count * (sizeof(u16))), > >+ GFP_KERNEL); > >+ if (!map_memory || !pt_memory) { > > dev_err(&fn->dev, "Failed to allocate gpioled map memory.\n"); > > return -ENOMEM; > > } > > > > f30->gpioled_key_map = (u16 *)map_memory; > >+ f30->gpio_passthrough_key_map = (u16 *)pt_memory; > > > > pdata = rmi_get_platform_data(rmi_dev); > > if (pdata && f30->has_gpio) { > > This existing check of pdata is not needed because pdata is embedded in the > transport device and will never be NULL. That means that in this patch the > else case will never be called. oops. Good catch > > >+ /* > >+ * For touchpads the buttons are mapped as: > >+ * - bit 0 = Left, bit 1 = right, bit 2 = middle / clickbutton > >+ * - 3, 4, 5 are extended buttons and > >+ * - 6 and 7 are other sorts of GPIOs > >+ */ > >+ button = BTN_LEFT; > >+ extra_button = BTN_LEFT; > >+ for (i = 0; i < f30->gpioled_count - 2 && i < 3; i++) { > > Subtracting 2 from gpioled_count does not have the intended affect. The name > gpioled_count comes from the spec, but that byte is really a bit map. On a > typical clickpad bit two is set as mentioned in the new comment. The result I really doubt this is a bit map. On the T450s, only bit 3 (the 4th bit then) is set and this corresponds to the numerical value "8". If it were a bit map, I would expect bits 0-7 to be set -> 0xFF. Aren't you mixing the gpioled_count and the gpioled_key_map? Because bit 2 set on gpioled_count would be 4, and I can't figure out a proper use of this number :) > is that this for loop only runs once and it only checks the first bit of > ctrl 2 and ctrl 3 for a valid button. Since bit 0 is not set then no valid > buttons are reported for the clickpad. > > It looks like the Lenovo systems which this patch is targeting actually have > 6 gpios defined (bits 2 - 7 are set) so this code works on those system Yes, the conditions in the for loops are wrong. I think they could be fixed easily by having one case for (f30->has_gpio && f30->trackstick_buttons) and one other when f30->trackstick_buttons is not set. In the trackstick button case, I should also only take into account the first 6 buttons (it's a special case after all :-P ). > since the "count" is sufficiently large. Also, maybe it's time to rename > gpioled_count to something like gpioled_map. > > >+ if (rmi_f30_is_valid_button(i, f30->ctrl)) > >+ f30->gpioled_key_map[i] = button++; > >+ } > >+ > >+ f30->trackstick_buttons = pdata && > >+ pdata->f30_data.trackstick_buttons; > >+ > >+ if (f30->trackstick_buttons) { > >+ for (i = 3; i < f30->gpioled_count - 2; i++) { > >+ if (rmi_f30_is_valid_button(i, f30->ctrl)) > >+ f30->gpio_passthrough_key_map[i] = extra_button++; > >+ } > >+ } else { > >+ for (i = 3; i < f30->gpioled_count - 2; i++) { > >+ if (rmi_f30_is_valid_button(i, f30->ctrl)) > >+ f30->gpioled_key_map[i] = button++; > >+ } > >+ } > >+ } else if (f30->has_gpio) { > > As noted above this else block is never called. Yep :( Thanks for the other reviews BTW. I amended the patches locally and will resubmit this week or the week after if there are more reviews coming in. Cheers, Benjamin > > Andrew > > > button = BTN_LEFT; > > for (i = 0; i < f30->gpioled_count; i++) { > > if (rmi_f30_is_valid_button(i, f30->ctrl)) { > > > ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest 2016-08-30 15:13 ` Benjamin Tissoires @ 2016-08-30 19:16 ` Andrew Duggan 0 siblings, 0 replies; 24+ messages in thread From: Andrew Duggan @ 2016-08-30 19:16 UTC (permalink / raw) To: Benjamin Tissoires Cc: Dmitry Torokhov, Lyude Paul, Christopher Heiny, Dennis Wassenberg, Peter Hutterer, linux-kernel, linux-input On 08/30/2016 08:13 AM, Benjamin Tissoires wrote: > Hi Andrew, > > On Aug 26 2016 or thereabouts, Andrew Duggan wrote: >> Resending as plain text >> >> Hi Benjamin, >> >> This patch causes standard clickpads without extended buttons to not work. >> I'll explain some more below. >> >> On 08/18/2016 02:24 AM, Benjamin Tissoires wrote: >>> From: Lyude Paul <thatslyude@gmail.com> >>> >>> On the latest series of ThinkPads, the button events for the TrackPoint >>> are reported through the touchpad itself as opposed to the TrackPoint >>> device. In order to report these buttons properly, we need to forward >>> them to the TrackPoint device and send the button presses/releases >>> through there instead. >>> >>> Signed-off-by: Lyude Paul <thatslyude@gmail.com> >>> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> >>> --- >>> drivers/input/rmi4/rmi_driver.h | 13 +++++++++ >>> drivers/input/rmi4/rmi_f03.c | 28 ++++++++++++++++++ >>> drivers/input/rmi4/rmi_f30.c | 64 +++++++++++++++++++++++++++++++++++------ >>> 3 files changed, 97 insertions(+), 8 deletions(-) >>> >>> diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h >>> index e4be773..a0b1978 100644 >>> --- a/drivers/input/rmi4/rmi_driver.h >>> +++ b/drivers/input/rmi4/rmi_driver.h >>> @@ -99,6 +99,19 @@ struct rmi_function *rmi_find_function(struct rmi_device *rmi_dev, u8 number); >>> >>> char *rmi_f01_get_product_ID(struct rmi_function *fn); >>> >>> +#ifdef CONFIG_RMI4_F03 >>> +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, >>> + int value); >>> +void rmi_f03_commit_buttons(struct rmi_function *fn); >>> +#else >>> +static inline int rmi_f03_overwrite_button(struct rmi_function *fn, >>> + unsigned int button, int value) >>> +{ >>> + return 0; >>> +} >>> +static inline void rmi_f03_commit_buttons(struct rmi_function *fn) {} >>> +#endif >>> + >>> extern struct rmi_function_handler rmi_f01_handler; >>> extern struct rmi_function_handler rmi_f03_handler; >>> extern struct rmi_function_handler rmi_f11_handler; >>> diff --git a/drivers/input/rmi4/rmi_f03.c b/drivers/input/rmi4/rmi_f03.c >>> index daae1c95..535f426 100644 >>> --- a/drivers/input/rmi4/rmi_f03.c >>> +++ b/drivers/input/rmi4/rmi_f03.c >>> @@ -37,6 +37,34 @@ struct f03_data { >>> u8 rx_queue_length; >>> }; >>> >>> +int rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button, >>> + int value) >>> +{ >>> + struct f03_data *f03 = dev_get_drvdata(&fn->dev); >>> + unsigned int bit = BIT(button); >>> + >>> + if (button > 2) >>> + return -EINVAL; >>> + >>> + if (value) >>> + f03->overwrite_buttons |= bit; >>> + else >>> + f03->overwrite_buttons &= ~bit; >>> + >>> + return 0; >>> +} >>> + >>> +void rmi_f03_commit_buttons(struct rmi_function *fn) >>> +{ >>> + struct f03_data *f03 = dev_get_drvdata(&fn->dev); >>> + int i; >>> + >>> + f03->serio->extra_byte = f03->overwrite_buttons; >>> + >>> + for (i = 0; i < 3; i++) >>> + serio_interrupt(f03->serio, 0x00, 0x00); >>> +} >>> + >>> static int rmi_f03_pt_write(struct serio *id, unsigned char val) >>> { >>> struct f03_data *f03 = id->port_data; >>> diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c >>> index 7990bb0..14e3221 100644 >>> --- a/drivers/input/rmi4/rmi_f30.c >>> +++ b/drivers/input/rmi4/rmi_f30.c >>> @@ -74,8 +74,11 @@ struct f30_data { >>> >>> u8 data_regs[RMI_F30_CTRL_MAX_BYTES]; >>> u16 *gpioled_key_map; >>> + u16 *gpio_passthrough_key_map; >>> >>> struct input_dev *input; >>> + bool trackstick_buttons; >>> + struct rmi_function *f03; >>> }; >>> >>> static int rmi_f30_read_control_parameters(struct rmi_function *fn, >>> @@ -108,6 +111,13 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) >>> if (!f30->input) >>> return 0; >>> >>> + if (f30->trackstick_buttons && !f30->f03) { >>> + f30->f03 = rmi_find_function(rmi_dev, 3); >>> + >>> + if (!f30->f03) >>> + return -EBUSY; >>> + } >>> + >>> /* Read the gpi led data. */ >>> if (rmi_dev->xport->attn_data) { >>> memcpy(f30->data_regs, rmi_dev->xport->attn_data, >>> @@ -128,23 +138,29 @@ static int rmi_f30_attention(struct rmi_function *fn, unsigned long *irq_bits) >>> for (reg_num = 0; reg_num < f30->register_count; ++reg_num) { >>> for (i = 0; gpiled < f30->gpioled_count && i < 8; ++i, >>> ++gpiled) { >>> - if (f30->gpioled_key_map[gpiled] != 0) { >>> - /* buttons have pull up resistors */ >>> - value = (((f30->data_regs[reg_num] >> i) & 0x01) >>> - == 0); >>> + /* buttons have pull up resistors */ >>> + value = (((f30->data_regs[reg_num] >> i) & 0x01) == 0); >>> >>> + if (f30->gpioled_key_map[gpiled] != 0) { >>> rmi_dbg(RMI_DEBUG_FN, &fn->dev, >>> "%s: call input report key (0x%04x) value (0x%02x)", >>> __func__, >>> f30->gpioled_key_map[gpiled], value); >>> + >>> input_report_key(f30->input, >>> f30->gpioled_key_map[gpiled], >>> value); >>> + } else if (f30->gpio_passthrough_key_map[gpiled]) { >>> + rmi_f03_overwrite_button(f30->f03, >>> + f30->gpio_passthrough_key_map[gpiled] - BTN_LEFT, >>> + value); >>> } >>> - >>> } >>> } >>> >>> + if (f30->trackstick_buttons) >>> + rmi_f03_commit_buttons(f30->f03); >>> + >>> return 0; >>> } >>> >>> @@ -242,10 +258,10 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) >>> int retval = 0; >>> int control_address; >>> int i; >>> - int button; >>> + int button, extra_button; >>> u8 buf[RMI_F30_QUERY_SIZE]; >>> u8 *ctrl_reg; >>> - u8 *map_memory; >>> + u8 *map_memory, *pt_memory; >>> >>> f30 = devm_kzalloc(&fn->dev, sizeof(struct f30_data), >>> GFP_KERNEL); >>> @@ -343,15 +359,47 @@ static inline int rmi_f30_initialize(struct rmi_function *fn) >>> map_memory = devm_kzalloc(&fn->dev, >>> (f30->gpioled_count * (sizeof(u16))), >>> GFP_KERNEL); >>> - if (!map_memory) { >>> + pt_memory = devm_kzalloc(&fn->dev, >>> + (f30->gpioled_count * (sizeof(u16))), >>> + GFP_KERNEL); >>> + if (!map_memory || !pt_memory) { >>> dev_err(&fn->dev, "Failed to allocate gpioled map memory.\n"); >>> return -ENOMEM; >>> } >>> >>> f30->gpioled_key_map = (u16 *)map_memory; >>> + f30->gpio_passthrough_key_map = (u16 *)pt_memory; >>> >>> pdata = rmi_get_platform_data(rmi_dev); >>> if (pdata && f30->has_gpio) { >> This existing check of pdata is not needed because pdata is embedded in the >> transport device and will never be NULL. That means that in this patch the >> else case will never be called. > oops. Good catch > >>> + /* >>> + * For touchpads the buttons are mapped as: >>> + * - bit 0 = Left, bit 1 = right, bit 2 = middle / clickbutton >>> + * - 3, 4, 5 are extended buttons and >>> + * - 6 and 7 are other sorts of GPIOs >>> + */ >>> + button = BTN_LEFT; >>> + extra_button = BTN_LEFT; >>> + for (i = 0; i < f30->gpioled_count - 2 && i < 3; i++) { >> Subtracting 2 from gpioled_count does not have the intended affect. The name >> gpioled_count comes from the spec, but that byte is really a bit map. On a >> typical clickpad bit two is set as mentioned in the new comment. The result > I really doubt this is a bit map. On the T450s, only bit 3 (the 4th bit > then) is set and this corresponds to the numerical value "8". If it were > a bit map, I would expect bits 0-7 to be set -> 0xFF. > > Aren't you mixing the gpioled_count and the gpioled_key_map? Because bit > 2 set on gpioled_count would be 4, and I can't figure out a proper use > of this number :) Yes, I was confused and that last email from me needs to be deleted from the internet. I remembered that gpioled_count doesn't work the way I expect it to, but I confused a few things when trying to explain why subtracting 2 from gpioled_count doesn't work. So let me try this again. The gpioled_count is the number of bits used to represent gpios or leds in ctrl 2 and ctrl 3. On a standard clickpad the gpioled_count is set to 3 which means the first 3 bits of ctrl 2 and ctrl 3 need to be checked. This is because the click button is defined in bit 2 (like the comment above). The Lenovos have more GPIOs so the count is 8. Which means all 8 bits need to be checked since gpios are defined up to bit 7. >> is that this for loop only runs once and it only checks the first bit of >> ctrl 2 and ctrl 3 for a valid button. Since bit 0 is not set then no valid >> buttons are reported for the clickpad. >> >> It looks like the Lenovo systems which this patch is targeting actually have >> 6 gpios defined (bits 2 - 7 are set) so this code works on those system > Yes, the conditions in the for loops are wrong. I think they could > be fixed easily by having one case for (f30->has_gpio && > f30->trackstick_buttons) and one other when f30->trackstick_buttons is > not set. In the trackstick button case, I should also only take into > account the first 6 buttons (it's a special case after all :-P ). > >> since the "count" is sufficiently large. Also, maybe it's time to rename >> gpioled_count to something like gpioled_map. >> >>> + if (rmi_f30_is_valid_button(i, f30->ctrl)) >>> + f30->gpioled_key_map[i] = button++; >>> + } >>> + >>> + f30->trackstick_buttons = pdata && >>> + pdata->f30_data.trackstick_buttons; >>> + >>> + if (f30->trackstick_buttons) { >>> + for (i = 3; i < f30->gpioled_count - 2; i++) { >>> + if (rmi_f30_is_valid_button(i, f30->ctrl)) >>> + f30->gpio_passthrough_key_map[i] = extra_button++; >>> + } >>> + } else { >>> + for (i = 3; i < f30->gpioled_count - 2; i++) { >>> + if (rmi_f30_is_valid_button(i, f30->ctrl)) >>> + f30->gpioled_key_map[i] = button++; >>> + } >>> + } >>> + } else if (f30->has_gpio) { >> As noted above this else block is never called. > Yep :( > > Thanks for the other reviews BTW. I amended the patches locally and will > resubmit this week or the week after if there are more reviews coming > in. I'll take a look at the rest of the patches in the series when I have a chance. But, this was the only patch which caused an issue for me. Thanks, Andrew > Cheers, > Benjamin > >> Andrew >> >>> button = BTN_LEFT; >>> for (i = 0; i < f30->gpioled_count; i++) { >>> if (rmi_f30_is_valid_button(i, f30->ctrl)) { >>> ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 08/11] Input: synaptics - allocate a Synaptics Intertouch device 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (6 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform Benjamin Tissoires ` (2 subsequent siblings) 10 siblings, 0 replies; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input Most of the Synaptics devices are connected through PS/2 and a different bus (SMBus or HID over I2C). The secondary bus capability is indicated by the InterTouch bit in extended capability 0x0C. When we encounter such a device, we can create a platform device with the information gathered through the PS/2 enumeration as some information might be missing through the other bus. Using a platform device allows to not add any dependency on the psmouse driver. We only enable the InterTouch device to be created for the laptops registered with the top software button property or those we know that are functional. In the future, we might change the default to always rely on the InterTouch bus. Currently, users can enable/disable the feature with the psmouse parameter synaptics_intertouch. The SMBus devices keep their PS/2 connection alive. If the initialization process goes too far (psmouse_activate called), the device disconnects from the I2C bus and stays on the PS/2 bus. We need to be sure the psmouse driver will stop communicating with the device (and the pass-through trackstick too). This part is not addressed here but will be in a following patch. The HID over I2C devices are enumerated through the ACPI DSDT, and their PS/2 device also exports the InterTouch bit in the extended capability 0x0C. However, the firmware keeps its I2C connection open even after going further in the PS/2 initialization. We don't need to take extra precautions with those device, especially because they block their PS/2 communication when HID over I2C is used. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/mouse/synaptics.c | 99 +++++++++++++++++++++++++++++++++++++++++ drivers/input/mouse/synaptics.h | 4 ++ 2 files changed, 103 insertions(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index 8781e23..eba6358 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -29,6 +29,8 @@ #include <linux/input/mt.h> #include <linux/serio.h> #include <linux/libps2.h> +#include <linux/platform_device.h> +#include <linux/rmi.h> #include <linux/slab.h> #include "psmouse.h" #include "synaptics.h" @@ -70,6 +72,21 @@ /* maximum ABS_MT_POSITION displacement (in mm) */ #define DMAX 10 +/* + * The newest Synaptics device can use a secondary bus (called InterTouch) which + * provides a better bandwidth and allow a better control of the touchpads. + * This is used to decide if we need to use this bus or not. + */ +enum { + SYNAPTICS_INTERTOUCH_NOT_SET = -1, + SYNAPTICS_INTERTOUCH_OFF, + SYNAPTICS_INTERTOUCH_ON, +}; + +static int synaptics_intertouch = SYNAPTICS_INTERTOUCH_NOT_SET; +module_param_named(synaptics_intertouch, synaptics_intertouch, int, 0644); +MODULE_PARM_DESC(synaptics_intertouch, "Use a secondary bus for the Synaptics device."); + /***************************************************************************** * Stuff we need even when we do not want native Synaptics support ****************************************************************************/ @@ -218,6 +235,80 @@ static const char * const forcepad_pnp_ids[] = { NULL }; +static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ + "LEN004a", /* W541 */ + "LEN200f", /* T450s */ +}; + +static int rmi4_id; + +static int synaptics_create_intertouch(struct psmouse *psmouse) +{ + struct synaptics_data *priv = psmouse->private; + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct rmi_device_platform_data pdata = { + .sensor_pdata = { + .sensor_type = rmi_sensor_touchpad, + .axis_align.flip_y = true, + /* to prevent cursors jumps: */ + .kernel_tracking = true, + }, + .f30_data = { + .trackstick_buttons = + !!SYN_CAP_EXT_BUTTONS_STICK(priv->ext_cap_10), + }, + }; + + if (priv->intertouch) + return -EINVAL; + + pdata.sensor_pdata.topbuttonpad = + psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids) && + !SYN_CAP_EXT_BUTTONS_STICK(priv->ext_cap_10); + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.name = "rmi4"; + pdevinfo.id = rmi4_id++; + pdevinfo.data = &pdata; + pdevinfo.size_data = sizeof(pdata); + pdevinfo.parent = &psmouse->ps2dev.serio->dev; + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + priv->intertouch = pdev; + + return 0; +} + +/** + * synaptics_setup_intertouch - called once the PS/2 devices are enumerated + * and decides to instantiate a SMBus InterTouch device. + */ +static void synaptics_setup_intertouch(struct psmouse *psmouse) +{ + int ret; + + if (synaptics_intertouch == SYNAPTICS_INTERTOUCH_OFF) + return; + + if (synaptics_intertouch == SYNAPTICS_INTERTOUCH_NOT_SET) { + if (!psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids) && + !psmouse_matches_pnp_id(psmouse, smbus_pnp_ids)) + return; + } + + psmouse_info(psmouse, "device also supported by an other bus.\n"); + ret = synaptics_create_intertouch(psmouse); + if (ret) + psmouse_info(psmouse, + "unable to create intertouch device.\n"); +} /***************************************************************************** * Synaptics communications functions ****************************************************************************/ @@ -1305,6 +1396,11 @@ static void synaptics_disconnect(struct psmouse *psmouse) device_remove_file(&psmouse->ps2dev.serio->dev, &psmouse_attr_disable_gesture.dattr); + if (priv->intertouch) { + platform_device_unregister(priv->intertouch); + priv->intertouch = NULL; + } + synaptics_reset(psmouse); kfree(priv); psmouse->private = NULL; @@ -1547,6 +1643,9 @@ static int __synaptics_init(struct psmouse *psmouse, bool absolute_mode) } } + if (SYN_CAP_INTERTOUCH(priv->ext_cap_0c)) + synaptics_setup_intertouch(psmouse); + return 0; init_fail: diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h index 116ae25..b88755d 100644 --- a/drivers/input/mouse/synaptics.h +++ b/drivers/input/mouse/synaptics.h @@ -90,6 +90,7 @@ #define SYN_CAP_ADV_GESTURE(ex0c) ((ex0c) & 0x080000) #define SYN_CAP_REDUCED_FILTERING(ex0c) ((ex0c) & 0x000400) #define SYN_CAP_IMAGE_SENSOR(ex0c) ((ex0c) & 0x000800) +#define SYN_CAP_INTERTOUCH(ex0c) ((ex0c) & 0x004000) /* * The following descibes response for the 0x10 query. @@ -196,6 +197,9 @@ struct synaptics_data { bool press; bool report_press; bool is_forcepad; + + /* Intertouch handling */ + struct platform_device *intertouch; }; void synaptics_module_init(void); -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (7 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 08/11] Input: synaptics - allocate a Synaptics Intertouch device Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-24 2:47 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot 2016-08-24 2:47 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform kbuild test robot 2016-08-18 9:24 ` [PATCH 10/11] Input: synaptics-rmi4 - smbus: call psmouse_deactivate before binding/resume Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 11/11] Input: synaptics-rmi4 - smbus: on resume, try 3 times if init fails Benjamin Tissoires 10 siblings, 2 replies; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input This driver is a glue between PS/2 devices that enumerate the RMI4 device and the RMI4 SMBus driver. We mostly use an intermediate platform device to not add a dependency between psmouse and I2C. It also handles the subtleties of going around the serio mutex lock by deferring the i2c creation/destruction in a separate thread. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/rmi4/Kconfig | 12 ++ drivers/input/rmi4/Makefile | 1 + drivers/input/rmi4/rmi_platform.c | 235 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 drivers/input/rmi4/rmi_platform.c diff --git a/drivers/input/rmi4/Kconfig b/drivers/input/rmi4/Kconfig index cfc14b3..2f11157 100644 --- a/drivers/input/rmi4/Kconfig +++ b/drivers/input/rmi4/Kconfig @@ -30,6 +30,7 @@ config RMI4_SPI config RMI4_SMB tristate "RMI4 SMB Support" depends on RMI4_CORE && I2C + select RMI4_PLATFORM help Say Y here if you want to support RMI4 devices connected to an SMB bus. @@ -39,6 +40,17 @@ config RMI4_SMB To compile this driver as a module, choose M here: the module will be called rmi_smbus. +config RMI4_PLATFORM + tristate "RMI4 Platform Support" + depends on RMI4_CORE && RMI4_SMB && I2C + help + Say Y here if you want to support RMI4 devices connected to an SMB + bus but enumerated through PS/2. + + if unsure, say N. + To compile this driver as a module, choose M here: the module will be + called rmi_platform. + config RMI4_F03 bool "RMI4 Function 03 (PS2 Guest)" depends on RMI4_CORE diff --git a/drivers/input/rmi4/Makefile b/drivers/input/rmi4/Makefile index 676e636..23af592 100644 --- a/drivers/input/rmi4/Makefile +++ b/drivers/input/rmi4/Makefile @@ -13,3 +13,4 @@ rmi_core-$(CONFIG_RMI4_F30) += rmi_f30.o obj-$(CONFIG_RMI4_I2C) += rmi_i2c.o obj-$(CONFIG_RMI4_SPI) += rmi_spi.o obj-$(CONFIG_RMI4_SMB) += rmi_smbus.o +obj-$(CONFIG_RMI4_PLATFORM) += rmi_platform.o diff --git a/drivers/input/rmi4/rmi_platform.c b/drivers/input/rmi4/rmi_platform.c new file mode 100644 index 0000000..8cd11d9 --- /dev/null +++ b/drivers/input/rmi4/rmi_platform.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2016 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/rmi.h> +#include <linux/slab.h> + +#define DRIVER_DESC "RMI4 Platform PS/2 - SMBus bridge driver" + +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static struct workqueue_struct *krmi_wq; +DEFINE_MUTEX(rmi_mutex); + +struct rmi_pltf { + struct i2c_client *smbus_client; + struct notifier_block i2c_notifier; + struct rmi_device_platform_data *pdata; +}; + +enum rmi_event_type { + RMI_REGISTER_DEVICE, + RMI_UNREGISTER_DEVICE, +}; + +struct rmi_work { + struct work_struct work; + enum rmi_event_type type; + struct rmi_pltf *rmi; + struct i2c_adapter *adap; +}; + +static void rmi_create_intertouch(struct rmi_pltf *rmi_pltf, + struct i2c_adapter *adap) +{ + const struct i2c_board_info i2c_info = { + I2C_BOARD_INFO("rmi4_smbus", 0x2c), + .platform_data = rmi_pltf->pdata, + }; + + rmi_pltf->smbus_client = i2c_new_device(adap, &i2c_info); +} + +static void rmi_worker(struct work_struct *work) +{ + struct rmi_work *rmi_work = container_of(work, struct rmi_work, work); + + mutex_lock(&rmi_mutex); + + switch (rmi_work->type) { + case RMI_REGISTER_DEVICE: + rmi_create_intertouch(rmi_work->rmi, rmi_work->adap); + break; + case RMI_UNREGISTER_DEVICE: + if (rmi_work->rmi->smbus_client) + i2c_unregister_device(rmi_work->rmi->smbus_client); + break; + }; + + kfree(rmi_work); + + mutex_unlock(&rmi_mutex); +} + +static int rmi_schedule_work(enum rmi_event_type type, + struct rmi_pltf *rmi, + struct i2c_adapter *adap) +{ + struct rmi_work *rmi_work = kzalloc(sizeof(*rmi_work), GFP_KERNEL); + + if (!rmi_work) + return -ENOMEM; + + rmi_work->type = type; + rmi_work->rmi = rmi; + rmi_work->adap = adap; + + INIT_WORK(&rmi_work->work, rmi_worker); + + queue_work(krmi_wq, &rmi_work->work); + + return 0; +} + +static int rmi_attach_i2c_device(struct device *dev, void *data) +{ + struct rmi_pltf *rmi_pltf = data; + struct i2c_adapter *adap; + + if (dev->type != &i2c_adapter_type) + return 0; + + adap = to_i2c_adapter(dev); + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_HOST_NOTIFY)) + return 0; + + if (rmi_pltf->smbus_client) + return 0; + + rmi_schedule_work(RMI_REGISTER_DEVICE, rmi_pltf, adap); + + pr_debug("rmi_platform: adapter [%s] registered\n", adap->name); + return 0; +} + +static int rmi_detach_i2c_device(struct device *dev, struct rmi_pltf *rmi_pltf) +{ + struct i2c_client *client; + + if (dev->type == &i2c_adapter_type) + return 0; + + mutex_lock(&rmi_mutex); + + client = to_i2c_client(dev); + if (client == rmi_pltf->smbus_client) + rmi_pltf->smbus_client = NULL; + + mutex_unlock(&rmi_mutex); + + pr_debug("rmi_platform: client [%s] unregistered\n", client->name); + return 0; +} + +static int rmi_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + struct rmi_pltf *rmi_pltf; + + rmi_pltf = container_of(nb, struct rmi_pltf, i2c_notifier); + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return rmi_attach_i2c_device(dev, rmi_pltf); + case BUS_NOTIFY_DEL_DEVICE: + return rmi_detach_i2c_device(dev, rmi_pltf); + } + + return 0; +} + +static int rmi_probe(struct platform_device *pdev) +{ + struct rmi_device_platform_data *pdata = pdev->dev.platform_data; + struct rmi_pltf *rmi_pltf; + int error; + + rmi_pltf = devm_kzalloc(&pdev->dev, sizeof(struct rmi_pltf), + GFP_KERNEL); + if (!rmi_pltf) + return -ENOMEM; + + rmi_pltf->i2c_notifier.notifier_call = rmi_notifier_call; + + rmi_pltf->pdata = pdata; + + /* Keep track of adapters which will be added or removed later */ + error = bus_register_notifier(&i2c_bus_type, &rmi_pltf->i2c_notifier); + if (error) + return error; + + /* Bind to already existing adapters right away */ + i2c_for_each_dev(rmi_pltf, rmi_attach_i2c_device); + + platform_set_drvdata(pdev, rmi_pltf); + + return 0; +} + +static int rmi_remove(struct platform_device *pdev) +{ + struct rmi_pltf *rmi_pltf = platform_get_drvdata(pdev); + + bus_unregister_notifier(&i2c_bus_type, &rmi_pltf->i2c_notifier); + + if (rmi_pltf->smbus_client) + rmi_schedule_work(RMI_UNREGISTER_DEVICE, rmi_pltf, NULL); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct platform_device_id rmi_id_table[] = { + { .name = "rmi4" }, + { } +}; +MODULE_DEVICE_TABLE(platform, rmi_id_table); + +static struct platform_driver rmi_drv = { + .driver = { + .name = "rmi4", + }, + .probe = rmi_probe, + .remove = rmi_remove, + .id_table = rmi_id_table, +}; + +static int __init rmi_init(void) +{ + int err; + + krmi_wq = create_singlethread_workqueue("krmid"); + if (!krmi_wq) { + pr_err("failed to create krmid workqueue\n"); + return -ENOMEM; + } + + err = platform_driver_register(&rmi_drv); + if (err) + destroy_workqueue(krmi_wq); + + return err; +} + +static void __exit rmi_exit(void) +{ + platform_driver_unregister(&rmi_drv); + destroy_workqueue(krmi_wq); +} + +module_init(rmi_init); +module_exit(rmi_exit); -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH] Input: fix semicolon.cocci warnings 2016-08-18 9:24 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform Benjamin Tissoires @ 2016-08-24 2:47 ` kbuild test robot 2016-08-24 2:47 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform kbuild test robot 1 sibling, 0 replies; 24+ messages in thread From: kbuild test robot @ 2016-08-24 2:47 UTC (permalink / raw) To: Benjamin Tissoires Cc: kbuild-all, Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg, Peter Hutterer, linux-kernel, linux-input drivers/input/rmi4/rmi_platform.c:68:2-3: Unneeded semicolon Remove unneeded semicolon. Generated by: scripts/coccinelle/misc/semicolon.cocci CC: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Fengguang Wu <fengguang.wu@intel.com> --- rmi_platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/input/rmi4/rmi_platform.c +++ b/drivers/input/rmi4/rmi_platform.c @@ -65,7 +65,7 @@ static void rmi_worker(struct work_struc if (rmi_work->rmi->smbus_client) i2c_unregister_device(rmi_work->rmi->smbus_client); break; - }; + } kfree(rmi_work); ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform 2016-08-18 9:24 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform Benjamin Tissoires 2016-08-24 2:47 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot @ 2016-08-24 2:47 ` kbuild test robot 1 sibling, 0 replies; 24+ messages in thread From: kbuild test robot @ 2016-08-24 2:47 UTC (permalink / raw) To: Benjamin Tissoires Cc: kbuild-all, Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg, Peter Hutterer, linux-kernel, linux-input Hi Benjamin, [auto build test WARNING on input/next] [also build test WARNING on v4.8-rc3 next-20160823] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] [Suggest to use git(>=2.9.0) format-patch --base=<commit> (or --base=auto for convenience) to record what (public, well-known) commit your patch series was built on] [Check https://git-scm.com/docs/git-format-patch for more information] url: https://github.com/0day-ci/linux/commits/Benjamin-Tissoires/Synaptics-RMI4-over-SMBus/20160818-174713 base: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next coccinelle warnings: (new ones prefixed by >>) >> drivers/input/rmi4/rmi_platform.c:68:2-3: Unneeded semicolon Please review and possibly fold the followup patch. --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 10/11] Input: synaptics-rmi4 - smbus: call psmouse_deactivate before binding/resume 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (8 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 11/11] Input: synaptics-rmi4 - smbus: on resume, try 3 times if init fails Benjamin Tissoires 10 siblings, 0 replies; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input The SMBus Synaptics devices enumerated as PS/2 devices have the problem of being deaf to I2C if the touchpad has been fully initialized over PS/2 (psmouse_activate being called). A simple PS/2 deactivate command is enough to make it back alive. To make sure the pass-through device does not interfere, we also remove it from serio while using InterTouch. Add a platform_data callback to reset the PS/2 device and prevent it to be asynchronously re-enabled at resume. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/mouse/psmouse-base.c | 3 +++ drivers/input/mouse/psmouse.h | 1 + drivers/input/mouse/synaptics.c | 32 ++++++++++++++++++++++++++++++++ drivers/input/rmi4/rmi_smbus.c | 30 ++++++++++++++++++++---------- include/linux/rmi.h | 13 +++++++++++++ 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index fa2d700..5d84e0c 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -1607,6 +1607,9 @@ static int psmouse_reconnect(struct serio *serio) unsigned char type; int rc = -1; + if (psmouse->ignore_reconnect) + return 0; + mutex_lock(&psmouse_mutex); if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h index e0ca6cd..e9dc1a1 100644 --- a/drivers/input/mouse/psmouse.h +++ b/drivers/input/mouse/psmouse.h @@ -81,6 +81,7 @@ struct psmouse { void (*pt_activate)(struct psmouse *psmouse); void (*pt_deactivate)(struct psmouse *psmouse); + bool ignore_reconnect; }; enum psmouse_type { diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index eba6358..e1a84a6 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -245,6 +245,30 @@ static const char * const smbus_pnp_ids[] = { static int rmi4_id; +static void synaptics_pt_create(struct psmouse *psmouse); + +static int synaptics_intertouch_enable(void *data, bool value) +{ + struct psmouse *psmouse = data; + struct synaptics_data *priv = psmouse->private; + + if (!psmouse) + return 0; + + psmouse->ignore_reconnect = value; + + if (value) { + serio_unregister_child_port(psmouse->ps2dev.serio); + psmouse_deactivate(psmouse); + } else { + psmouse_activate(psmouse); + if (SYN_CAP_PASS_THROUGH(priv->capabilities)) + synaptics_pt_create(psmouse); + } + + return 0; +} + static int synaptics_create_intertouch(struct psmouse *psmouse) { struct synaptics_data *priv = psmouse->private; @@ -261,6 +285,8 @@ static int synaptics_create_intertouch(struct psmouse *psmouse) .trackstick_buttons = !!SYN_CAP_EXT_BUTTONS_STICK(priv->ext_cap_10), }, + .transport_enable = synaptics_intertouch_enable, + .transport_data = psmouse, }; if (priv->intertouch) @@ -1397,6 +1423,12 @@ static void synaptics_disconnect(struct psmouse *psmouse) &psmouse_attr_disable_gesture.dattr); if (priv->intertouch) { + struct rmi_device_platform_data *pdata; + + /* reset transport_data as it will get eventually freed */ + pdata = priv->intertouch->dev.platform_data; + pdata->transport_data = NULL; + platform_device_unregister(priv->intertouch); priv->intertouch = NULL; } diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 4d6f228..023dbd5 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -244,8 +244,15 @@ static void rmi_smb_clear_state(struct rmi_smb_xport *rmi_smb) static int rmi_smb_enable_smbus_mode(struct rmi_smb_xport *rmi_smb) { + struct rmi_device_platform_data *pdata; int retval; + pdata = dev_get_platdata(&rmi_smb->client->dev); + + retval = rmi_transport_enable(pdata, true); + if (retval) + return retval; + /* we need to get the smbus version to activate the touchpad */ retval = rmi_smb_get_version(rmi_smb); if (retval < 0) @@ -261,13 +268,6 @@ static int rmi_smb_reset(struct rmi_transport_dev *xport, u16 reset_addr) rmi_smb_clear_state(rmi_smb); - /* - * we do not call the actual reset command, it has to be handled in - * PS/2 or there will be races between PS/2 and SMBus. - * PS/2 should ensure that a psmouse_reset is called before - * intializing the device and after it has been removed to be in a known - * state. - */ return rmi_smb_enable_smbus_mode(rmi_smb); } @@ -334,9 +334,13 @@ static int rmi_smb_probe(struct i2c_client *client, rmi_smb->xport.proto_name = "smb2"; rmi_smb->xport.ops = &rmi_smb_ops; + retval = rmi_transport_enable(pdata, true); + if (retval) + return retval; + retval = rmi_smb_get_version(rmi_smb); if (retval < 0) - return retval; + goto fail; smbus_version = retval; rmi_dbg(RMI_DEBUG_XPORT, &client->dev, "Smbus version is %d", @@ -345,7 +349,8 @@ static int rmi_smb_probe(struct i2c_client *client, if (smbus_version != 2) { dev_err(&client->dev, "Unrecognized SMB version %d.\n", smbus_version); - return -ENODEV; + retval = -ENODEV; + goto fail; } i2c_set_clientdata(client, rmi_smb); @@ -355,20 +360,25 @@ static int rmi_smb_probe(struct i2c_client *client, dev_err(&client->dev, "Failed to register transport driver at 0x%.2X.\n", client->addr); i2c_set_clientdata(client, NULL); - return retval; + goto fail; } dev_info(&client->dev, "registered rmi smb driver at %#04x.\n", client->addr); return 0; +fail: + rmi_transport_enable(pdata, false); + return retval; } static int rmi_smb_remove(struct i2c_client *client) { + struct rmi_device_platform_data *pdata = dev_get_platdata(&client->dev); struct rmi_smb_xport *rmi_smb = i2c_get_clientdata(client); rmi_unregister_transport_device(&rmi_smb->xport); + rmi_transport_enable(pdata, false); return 0; } diff --git a/include/linux/rmi.h b/include/linux/rmi.h index 4a071e8..02e1dae 100644 --- a/include/linux/rmi.h +++ b/include/linux/rmi.h @@ -214,6 +214,9 @@ struct rmi_device_platform_data { struct rmi_2d_sensor_platform_data sensor_pdata; struct rmi_f01_power_management power_management; struct rmi_f30_data f30_data; + + int (*transport_enable)(void*, bool); + void *transport_data; }; /** @@ -350,6 +353,16 @@ struct rmi_driver_data { void *data; }; +static inline int rmi_transport_enable(struct rmi_device_platform_data *pdata, + bool value) +{ + if (!pdata->transport_enable) + return 0; + + return pdata->transport_enable(pdata->transport_data, value); +} + + int rmi_register_transport_device(struct rmi_transport_dev *xport); void rmi_unregister_transport_device(struct rmi_transport_dev *xport); int rmi_process_interrupt_requests(struct rmi_device *rmi_dev); -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH 11/11] Input: synaptics-rmi4 - smbus: on resume, try 3 times if init fails 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires ` (9 preceding siblings ...) 2016-08-18 9:24 ` [PATCH 10/11] Input: synaptics-rmi4 - smbus: call psmouse_deactivate before binding/resume Benjamin Tissoires @ 2016-08-18 9:24 ` Benjamin Tissoires 10 siblings, 0 replies; 24+ messages in thread From: Benjamin Tissoires @ 2016-08-18 9:24 UTC (permalink / raw) To: Dmitry Torokhov, Lyude Paul, Andrew Duggan, Christopher Heiny, Dennis Wassenberg Cc: Peter Hutterer, linux-kernel, linux-input In some rare cases, we can't retrieve the SMBus version and so we fail binding the touchpad back. Instead of leaving a touchpad dead, try again to reinitialize it. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/input/rmi4/rmi_smbus.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 023dbd5..a8e4af6 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -265,10 +265,29 @@ static int rmi_smb_reset(struct rmi_transport_dev *xport, u16 reset_addr) { struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; + struct rmi_device_platform_data *pdata; + int tries, ret; rmi_smb_clear_state(rmi_smb); - return rmi_smb_enable_smbus_mode(rmi_smb); + for (tries = 3; tries > 0; tries--) { + ret = rmi_smb_enable_smbus_mode(rmi_smb); + if (!ret) + break; + + /* we failed enabling SMBus, try again later */ + msleep(500); + } + + if (ret < 0) { + dev_warn(&client->dev, + "failed to enable SMBus mode, giving up.\n"); + pdata = dev_get_platdata(&rmi_smb->client->dev); + rmi_transport_enable(pdata, false); + } + + return ret; } static const struct rmi_transport_ops rmi_smb_ops = { -- 2.5.5 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v2] Input: Add driver for GOODiX GTx5 series touchsereen 2017-06-19 9:29 ` Wang Yafei @ 2017-06-19 13:24 kbuild test robot 2017-06-19 9:29 ` Wang Yafei 0 siblings, 1 reply; 24+ messages in thread From: kbuild test robot @ 2017-06-19 13:24 UTC (permalink / raw) To: Wang Yafei Cc: kbuild-all, Dmitry Torokhov, Henrik Rydberg, linux-input, andrew, mouse, linux-kernel Hi Wang, [auto build test WARNING on input/next] [also build test WARNING on v4.12-rc5 next-20170619] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] url: https://github.com/0day-ci/linux/commits/Wang-Yafei/Input-Add-driver-for-GOODiX-GTx5-series-touchsereen/20170619-200451 base: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next coccinelle warnings: (new ones prefixed by >>) >> drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c:872:3-8: No need to set .owner here. The core will do it. -- >> drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c:1335:3-8: No need to set .owner here. The core will do it. -- >> drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c:752:2-3: Unneeded semicolon Please review and possibly fold the followup patch. --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2] Input: Add driver for GOODiX GTx5 series touchsereen @ 2017-06-19 9:29 ` Wang Yafei 2017-06-19 13:24 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot 0 siblings, 1 reply; 24+ messages in thread From: Wang Yafei @ 2017-06-19 9:29 UTC (permalink / raw) To: Dmitry Torokhov, Henrik Rydberg; +Cc: linux-input, andrew, mouse, linux-kernel This driver is for GOODiX GTx5 series touchscreen controllers such as GT8589, GT7589. This driver designed with hierarchial structure, for that can be modified to support subsequent controllers easily. Some zones of the touchscreen can be set to buttons(according to the hardware). That is why it handles button and multitouch events. A brief description of driver structure - Core Layer: This layer responsible for basic input events report, GPIO pinctrl, Interrupt, Power resources manager and submodules manager. - Hardware Layer: This layer responsible for controllers initialization, irq handle as well as bus read/write. - External Module Layer: This layer used for support more features such as firmware update, debug tools and gesture wakeup. Signed-off-by: Wang Yafei <wangyafei@goodix.com> --- Goodix driver that the kernel already have, is for our gt9xx series touchscreen controllers. This new driver is for out new generation touchscreen controllers, consider this two generations are very different with each other. We wrote a new driver instead of make a patch on the old driver. Since v1: - replace touchscreen properties according to the description in Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt - Droped all compat stuff for older kernels - Removed Android stuff (EARLY_SUSPEND, CONFIG_FB) - Use device_property_read_* get device properties - use get-unaligned_*() API - Use dev_err() dev_dbg() for logging - remove pinctrl functions - remove some unused functions --- drivers/input/touchscreen/Kconfig | 1 + drivers/input/touchscreen/Makefile | 1 + .../input/touchscreen/goodix-ts-sunrise/Kconfig | 36 + .../input/touchscreen/goodix-ts-sunrise/Makefile | 6 + .../goodix-ts-sunrise/goodix_gtx5_i2c.c | 895 ++++++++++++ .../goodix-ts-sunrise/goodix_gtx5_update.c | 1454 ++++++++++++++++++++ .../touchscreen/goodix-ts-sunrise/goodix_ts_core.c | 1366 ++++++++++++++++++ .../touchscreen/goodix-ts-sunrise/goodix_ts_core.h | 553 ++++++++ .../goodix-ts-sunrise/goodix_ts_tools.c | 542 ++++++++ 9 files changed, 4854 insertions(+) create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/Kconfig create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/Makefile create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h create mode 100755 drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index cf26ca4..f3642bb 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -15,6 +15,7 @@ config TOUCHSCREEN_PROPERTIES def_tristate INPUT depends on INPUT +source "drivers/input/touchscreen/goodix-ts-sunrise/Kconfig" config TOUCHSCREEN_88PM860X tristate "Marvell 88PM860x touchscreen" depends on MFD_88PM860X diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 18e4769..d9408c0 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -6,6 +6,7 @@ wm97xx-ts-y := wm97xx-core.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix-ts-sunrise/ obj-$(CONFIG_TOUCHSCREEN_PROPERTIES) += of_touchscreen.o obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig b/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig new file mode 100755 index 0000000..8e16595 --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/Kconfig @@ -0,0 +1,36 @@ +# +# Goodix touchscreen driver configuration +# +menuconfig TOUCHSCREEN_GOODIX_GTX5 + bool "Goodix GTx5 touchscreen" + depends on I2C + default y + help + Say Y here if you have a Goodix GTx5xx touchscreen connected + to your system. + + If unsure, say N. + +if TOUCHSCREEN_GOODIX_GTX5 + +config TOUCHSCREEN_GOODIX_GTX5_UPDATE + tristate "Goodix GTx5xx firmware update module" + default y + help + Say Y here to enable support for doing firmware update. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +config TOUCHSCREEN_GOODIX_GTX5_TOOLS + tristate "Goodix touch tools support" + default n + help + Say Y here to enable debug tools. + + If unsure, say N. + + To compile this driver as a module, choose M here. + +endif diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/Makefile b/drivers/input/touchscreen/goodix-ts-sunrise/Makefile new file mode 100755 index 0000000..690f256 --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/Makefile @@ -0,0 +1,6 @@ +# Goodix Touchscreen Makefile + +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix_gtx5_i2c.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5) += goodix_ts_core.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5_TOOLS) += goodix_ts_tools.o +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX5_UPDATE) += goodix_gtx5_update.o diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c new file mode 100755 index 0000000..a02a2eb --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_i2c.c @@ -0,0 +1,895 @@ +/* + * Goodix GTx5 Touchscreen Dirver + * Hardware interface layer of touchdriver architecture. + * + * Copyright (C) 2015 - 2016 Goodix, Inc. + * Authors: Wang Yafei <wangyafei@goodix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/property.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include "goodix_ts_core.h" + +#define TS_DT_COMPATIBLE "goodix,gtx5" +#define TS_DRIVER_NAME "goodix_i2c" +#define I2C_MAX_TRANSFER_SIZE 256 +#define TS_ADDR_LENGTH 2 + +#define TS_REG_COORDS_BASE 0x824E +#define TS_REG_CMD 0x8040 +#define TS_REG_REQUEST 0x8044 +#define TS_REG_VERSION 0x8240 +#define TS_REG_CFG_BASE 0x8050 + +#define CFG_XMAX_OFFSET (0x8052 - 0x8050) +#define CFG_YMAX_OFFSET (0x8054 - 0x8050) + +#define REQUEST_HANDLED 0x00 +#define REQUEST_CONFIG 0x01 +#define REQUEST_BAKREF 0x02 +#define REQUEST_RESET 0x03 +#define REQUEST_MAINCLK 0x04 +#define REQUEST_IDLE 0x05 + +#define TS_MAX_SENSORID 5 +#define TS_CFG_MAX_LEN 495 +/* set defalut irq flags as Falling edge */ +#define DEFAULT_IRQ_FLAGS 2 +#if TS_CFG_MAX_LEN > GOODIX_CFG_MAX_SIZE +#error GOODIX_CFG_MAX_SIZE too small, please fix. +#endif + +#ifdef CONFIG_OF +/* + * goodix_parse_dt_resolution - parse resolution from dt + * @dev: device + * @board_data: pointer to board data structure + * return: 0 - no error, <0 error + */ +static int goodix_parse_dt_resolution(struct device *dev, + struct goodix_ts_board_data *board_data) +{ + int r, err = 0; + + r = device_property_read_u32(dev, "touchscreen-max-id", + &board_data->panel_max_id); + if (r || board_data->panel_max_id > GOODIX_MAX_TOUCH) + board_data->panel_max_id = GOODIX_MAX_TOUCH; + + r = device_property_read_u32(dev, "touchscreen-size-x", + &board_data->panel_max_x); + if (r) + err = -ENOENT; + + r = device_property_read_u32(dev, "touchscreen-size-y", + &board_data->panel_max_y); + if (r) + err = -ENOENT; + + r = device_property_read_u32(dev, "touchscreen-max-w", + &board_data->panel_max_w); + if (r) + err = -ENOENT; + + board_data->swap_axis = device_property_read_bool(dev, + "touchscreen-swapped-x-y"); + + return err; +} + +/** + * goodix_parse_dt - parse board data from dt + * @dev: pointer to device + * @board_data: pointer to board data structure + * return: 0 - no error, <0 error + */ +static int goodix_parse_dt(struct device *dev, + struct goodix_ts_board_data *board_data) +{ + int r; + + if (!board_data) { + dev_err(dev, "Invalid board data\n"); + return -EINVAL; + } + + r = device_property_read_u32(dev, "irq-flags", + &board_data->irq_flags); + if (r) { + dev_info(dev, "Use default irq flags:falling_edge\n"); + board_data->irq_flags = DEFAULT_IRQ_FLAGS; + } + + board_data->avdd_name = "vtouch"; + r = device_property_read_u32(dev, "power-on-delay-us", + &board_data->power_on_delay_us); + if (!r) { + /* 1000ms is too large, maybe you have pass a wrong value */ + if (board_data->power_on_delay_us > 1000 * 1000) { + dev_warn(dev, "Power on delay time exceed 1s\n"); + board_data->power_on_delay_us = 0; + } + } + + r = device_property_read_u32(dev, "power-off-delay-us", + &board_data->power_off_delay_us); + if (!r) { + /* 1000ms is too large, maybe you have pass a wrong value */ + if (board_data->power_off_delay_us > 1000 * 1000) { + dev_warn(dev, "Power off delay time exceed 1s\n"); + board_data->power_off_delay_us = 0; + } + } + + /* get xyz resolutions */ + r = goodix_parse_dt_resolution(dev, board_data); + if (r < 0) { + dev_err(dev, "Failed to parse resolutions:%d\n", r); + return r; + } + + /* parse key map */ + r = device_property_read_u32_array(dev, "panel-key-map", + NULL, GOODIX_MAX_KEY); + if (r > 0 && r <= GOODIX_MAX_KEY) { + board_data->panel_max_key = r; + r = device_property_read_u32_array(dev, + "panel-key-map", + &board_data->panel_key_map[0], + board_data->panel_max_key); + if (r) + dev_err(dev, "Failed get key map info\n"); + } else { + dev_info(dev, "No key map found\n"); + } + + dev_dbg(dev, "[DT]id:%d, x:%d, y:%d, w:%d\n", + board_data->panel_max_id, + board_data->panel_max_x, + board_data->panel_max_y, + board_data->panel_max_w); + return 0; +} + +/** + * goodix_parse_dt_cfg - pares config data from devicetree dev + * @dev: pointer to device + * @cfg_type: config type such as normal_config, highsense_cfg ... + * @config: pointer to config data structure + * @sensor_id: sensor id + * return: 0 - no error, <0 error + */ +static int goodix_parse_dt_cfg(struct goodix_ts_device *ts_dev, + char *cfg_type, struct goodix_ts_config *config, + unsigned int sensor_id) +{ + int r, len; + char sub_node_name[24] = {0}; + struct fwnode_handle *fwnode; + struct device *dev = ts_dev->dev; + struct goodix_ts_board_data *ts_bdata = ts_dev->board_data; + + u16 checksum; + + BUG_ON(config == NULL); + if (sensor_id > TS_MAX_SENSORID) { + dev_err(dev, "Invalid sensor id\n"); + return -EINVAL; + } + + if (config->initialized) { + dev_dbg(dev, "Config already initialized\n"); + return 0; + } + + /* + * config data are located in child node called + * 'sensorx', x is the sensor ID got from touch + * device. + */ + snprintf(sub_node_name, sizeof(sub_node_name), + "sensor%u", sensor_id); + fwnode = device_get_named_child_node(dev, "sub_node_name"); + if (!fwnode) { + dev_dbg(dev, "Child property[%s] not found\n", + sub_node_name); + return -EINVAL; + } + + len = fwnode_property_read_u8_array(fwnode, cfg_type, + NULL, TS_CFG_MAX_LEN); + if (len <= 0 || len % 2 != 1) { + dev_err(dev, "Invalid cfg type%s, size:%u\n", cfg_type, len); + return -EINVAL; + } + + config->length = len; + + mutex_init(&config->lock); + mutex_lock(&config->lock); + + r = fwnode_property_read_u8_array(fwnode, cfg_type, + config->data, TS_CFG_MAX_LEN); + if (r) { + mutex_unlock(&config->lock); + return r; + } + + /* modify max-x max-y resolution, little-endian */ + config->data[CFG_XMAX_OFFSET] = (u8)ts_bdata->panel_max_x; + config->data[CFG_XMAX_OFFSET + 1] = (u8)(ts_bdata->panel_max_x >> 8); + config->data[CFG_YMAX_OFFSET] = (u8)ts_bdata->panel_max_y; + config->data[CFG_YMAX_OFFSET + 1] = (u8)(ts_bdata->panel_max_y >> 8); + + /* + * checksum: u16 little-endian format + * the last byte of config is the config update flag + */ + checksum = checksum_le16(config->data, len - 3); + checksum = 0 - checksum; + config->data[len - 3] = (u8)checksum; + config->data[len - 2] = (u8)(checksum >> 8 & 0xff); + config->data[len - 1] = 0x01; + + strlcpy(config->name, cfg_type, sizeof(config->name)); + config->reg_base = TS_REG_CFG_BASE; + config->delay = 0; + config->initialized = true; + mutex_unlock(&config->lock); + + dev_dbg(dev, "Config name:%s,ver:%02xh,size:%d,checksum:%04xh\n", + config->name, config->data[0], + config->length, checksum); + return 0; +} +#endif + +/** + * goodix_i2c_read - read device register through i2c bus + * @dev: pointer to device data + * @addr: register address + * @data: read buffer + * @len: bytes to read + * return: 0 - read ok, < 0 - i2c transter error + */ +static int goodix_i2c_read(struct goodix_ts_device *dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(dev->dev); + unsigned int transfer_length = 0; + unsigned int pos = 0, address = reg; + unsigned char get_buf[64], addr_buf[2]; + int retry, r = 0; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = !I2C_M_RD, + .buf = &addr_buf[0], + .len = TS_ADDR_LENGTH, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + } + }; + + if (likely(len < sizeof(get_buf))) { + /* code optimize, use stack memory */ + msgs[1].buf = &get_buf[0]; + } else { + msgs[1].buf = kzalloc(len > I2C_MAX_TRANSFER_SIZE + ? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL); + if (!msgs[1].buf) + return -ENOMEM; + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE)) + transfer_length = I2C_MAX_TRANSFER_SIZE; + else + transfer_length = len - pos; + + msgs[0].buf[0] = (address >> 8) & 0xFF; + msgs[0].buf[1] = address & 0xFF; + msgs[1].len = transfer_length; + + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { + if (likely(i2c_transfer(client->adapter, msgs, 2) == 2)) { + memcpy(&data[pos], msgs[1].buf, transfer_length); + pos += transfer_length; + address += transfer_length; + break; + } + dev_info(&client->dev, "I2c read retry[%d]:0x%x\n", + retry + 1, reg); + msleep(20); + } + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { + dev_err(&client->dev, + "I2c read failed,dev:%02x,reg:%04x,size:%u\n", + client->addr, reg, len); + r = -EBUS; + goto read_exit; + } + } + +read_exit: + if (unlikely(len >= sizeof(get_buf))) + kfree(msgs[1].buf); + return r; +} + +/** + * goodix_i2c_write - write device register through i2c bus + * @ts_dev: pointer to goodix device data + * @addr: register address + * @data: write buffer + * @len: bytes to write + * return: 0 - write ok; < 0 - i2c transter error. + */ +static int goodix_i2c_write(struct goodix_ts_device *ts_dev, unsigned int reg, + unsigned char *data, unsigned int len) +{ + struct i2c_client *client = to_i2c_client(ts_dev->dev); + unsigned int pos = 0, transfer_length = 0; + unsigned int address = reg; + unsigned char put_buf[64]; + int retry, r = 0; + struct i2c_msg msg = { + .addr = client->addr, + .flags = !I2C_M_RD, + }; + + if (likely(len + TS_ADDR_LENGTH < sizeof(put_buf))) { + /* code optimize,use stack memory*/ + msg.buf = &put_buf[0]; + } else { + msg.buf = kmalloc(len + TS_ADDR_LENGTH > I2C_MAX_TRANSFER_SIZE + ? I2C_MAX_TRANSFER_SIZE : len + TS_ADDR_LENGTH, GFP_KERNEL); + if (!msg.buf) + return -ENOMEM; + } + + while (pos != len) { + if (unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH)) + transfer_length = I2C_MAX_TRANSFER_SIZE - TS_ADDR_LENGTH; + else + transfer_length = len - pos; + + msg.buf[0] = (unsigned char)((address >> 8) & 0xFF); + msg.buf[1] = (unsigned char)(address & 0xFF); + msg.len = transfer_length + 2; + memcpy(&msg.buf[2], &data[pos], transfer_length); + + for (retry = 0; retry < GOODIX_BUS_RETRY_TIMES; retry++) { + if (likely(i2c_transfer(client->adapter, &msg, 1) == 1)) { + pos += transfer_length; + address += transfer_length; + break; + } + dev_info(&client->dev, "I2c write retry[%d]\n", retry + 1); + msleep(20); + } + if (unlikely(retry == GOODIX_BUS_RETRY_TIMES)) { + dev_err(&client->dev, + "I2c write failed,dev:%02x,reg:%04x,size:%u", + client->addr, reg, len); + r = -EBUS; + goto write_exit; + } + } + +write_exit: + if (likely(len + TS_ADDR_LENGTH >= sizeof(put_buf))) + kfree(msg.buf); + return r; +} + +static int goodix_read_version(struct goodix_ts_device *ts_dev, + struct goodix_ts_version *version) +{ + u8 buffer[12]; + int r; + + r = goodix_i2c_read(ts_dev, TS_REG_VERSION, + buffer, sizeof(buffer)); + if (r < 0) { + dev_err(ts_dev->dev, "Read chip version failed\n"); + if (version) + version->valid = false; + return r; + } + + /* if checksum is right and first 4 bytes are not invalid value */ + if (checksum_u8(buffer, sizeof(buffer)) == 0 && + isalnum(buffer[0]) && isalnum(buffer[1]) && + isalnum(buffer[2]) && isalnum(buffer[3])) { + if (version) { + memcpy(&version->pid[0], buffer, 4); + version->pid[4] = '\0'; + version->cid = buffer[4]; + /* vid = main version + minor version */ + version->vid = get_unaligned_be16(&buffer[5]); + version->sensor_id = buffer[10] & 0x0F; + version->valid = true; + + if (version->cid) + dev_info(ts_dev->dev, + "PID:%s,CID: %c,VID:%04x,SensorID:%u\n", + version->pid, version->cid + 'A' - 1, + version->vid, version->sensor_id); + else + dev_info(ts_dev->dev, + "PID:%s,VID:%04x,SensorID:%u\n", + version->pid, version->vid, + version->sensor_id); + } + } else { + dev_warn(ts_dev->dev, "Checksum error:%*ph\n", + (int)sizeof(buffer), buffer); + /* mark this version is invalid */ + if (version) + version->valid = false; + r = -EINVAL; + } + + return r; +} + +/** + * goodix_send_config - send config data to device. + * @ts_dev: pointer to goodix device data + * @config: pointer to config data struct to be send + * @return: 0 - succeed, < 0 - failed + */ +static int goodix_send_config(struct goodix_ts_device *ts_dev, + struct goodix_ts_config *config) +{ + int r = 0; + + if (!config || !config->data) { + dev_warn(ts_dev->dev, "Null config data\n"); + return -EINVAL; + } + + dev_dbg(ts_dev->dev, "Send %s,ver:%02xh,size:%d\n", + config->name, config->data[0], + config->length); + + mutex_lock(&config->lock); + r = goodix_i2c_write(ts_dev, config->reg_base, + config->data, config->length); + if (r) + goto exit; + + /* make sure the firmware accept the config data*/ + if (config->delay) + msleep(config->delay); +exit: + mutex_unlock(&config->lock); + return r; +} + +static inline int goodix_cmds_init(struct goodix_ts_device *ts_dev) +{ + /* low power mode command */ + ts_dev->sleep_cmd.cmd_reg = TS_REG_CMD; + ts_dev->sleep_cmd.length = 3; + ts_dev->sleep_cmd.cmds[0] = 0x05; + ts_dev->sleep_cmd.cmds[1] = 0x0; + ts_dev->sleep_cmd.cmds[2] = 0 - 0x05; + ts_dev->sleep_cmd.initialized = true; + + return 0; +} + +/** + * goodix_hw_init - hardware initialize + * Called by touch core module when bootup + * @ts_dev: pointer to touch device + * return: 0 - no error, <0 error + */ +static int goodix_hw_init(struct goodix_ts_device *ts_dev) +{ + int r; + + BUG_ON(!ts_dev); + goodix_cmds_init(ts_dev); + + /* goodix_hw_init may be called many times */ + if (!ts_dev->normal_cfg) { + ts_dev->normal_cfg = devm_kzalloc(ts_dev->dev, + sizeof(*ts_dev->normal_cfg), GFP_KERNEL); + if (!ts_dev->normal_cfg) { + dev_err(ts_dev->dev, + "Failed to alloc memory for normal cfg\n"); + return -ENOMEM; + } + } + + /* read chip version: PID/VID/sensor ID,etc.*/ + r = goodix_read_version(ts_dev, &ts_dev->chip_version); + if (r < 0) + return r; + +#ifdef CONFIG_OF + /* parse normal-cfg from devicetree node */ + r = goodix_parse_dt_cfg(ts_dev, "normal-cfg", + ts_dev->normal_cfg, + ts_dev->chip_version.sensor_id); + if (r < 0) { + dev_warn(ts_dev->dev, "Failed to obtain normal-cfg\n"); + return r; + } +#endif + + ts_dev->normal_cfg->delay = 500; + /* send normal-cfg to firmware */ + r = goodix_send_config(ts_dev, ts_dev->normal_cfg); + + return r; +} + +/** + * goodix_hw_reset - reset device + * + * @dev: pointer to touch device + * Returns 0 - succeed,<0 - failed + */ +static void goodix_hw_reset(struct goodix_ts_device *dev) +{ + dev_dbg(dev->dev, "HW reset\n"); + + if (!dev->board_data->reset_gpiod) { + msleep(80); + return; + } + gpiod_direction_output(dev->board_data->reset_gpiod, 0); + udelay(200); + gpiod_direction_output(dev->board_data->reset_gpiod, 1); + msleep(80); +} + +/** + * goodix_request_handler - handle firmware request + * + * @dev: pointer to touch device + * @request_data: requset information + * Returns 0 - succeed,<0 - failed + */ +static int goodix_request_handler(struct goodix_ts_device *dev, + struct goodix_request_data *request_data) { + unsigned char buffer[1]; + int r; + + r = goodix_i2c_read(dev, TS_REG_REQUEST, buffer, 1); + if (r < 0) + return r; + + switch (buffer[0]) { + case REQUEST_CONFIG: + dev_dbg(dev->dev, "HW request config\n"); + goodix_send_config(dev, dev->normal_cfg); + goto clear_requ; + case REQUEST_BAKREF: + dev_dbg(dev->dev, "HW request bakref\n"); + goto clear_requ; + case REQUEST_RESET: + dev_dbg(dev->dev, "HW requset reset\n"); + goto clear_requ; + case REQUEST_MAINCLK: + dev_dbg(dev->dev, "HW request mainclk\n"); + goto clear_requ; + default: + dev_dbg(dev->dev, "Unknown hw request:%d\n", buffer[0]); + return 0; + } + +clear_requ: + buffer[0] = 0x00; + r = goodix_i2c_write(dev, TS_REG_REQUEST, buffer, 1); + return r; +} + +/** + * goodix_eventt_handler - handle firmware event + * + * @dev: pointer to touch device + * @ts_event: pointer to touch event structure + * Returns 0 - succeed,<0 - failed + */ +static int goodix_event_handler(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event) +{ +#define BYTES_PER_COORD 8 + struct goodix_touch_data *touch_data = + &ts_event->event_data.touch_data; + struct goodix_ts_coords *coords = &touch_data->coords[0]; + int max_touch_num = dev->board_data->panel_max_id; + unsigned char buffer[2 + BYTES_PER_COORD * max_touch_num]; + unsigned char coord_sta; + int touch_num = 0, i, r; + unsigned char chksum = 0; + + r = goodix_i2c_read(dev, TS_REG_COORDS_BASE, + buffer, 3 + BYTES_PER_COORD/* * 1*/); + if (unlikely(r < 0)) + return r; + + /* buffer[0]: event state */ + coord_sta = buffer[0]; + if (unlikely(coord_sta == 0x00)) { + /* handle request event */ + ts_event->event_type = EVENT_REQUEST; + goodix_request_handler(dev, + &ts_event->event_data.request_data); + goto exit_clean_sta; + } else if (unlikely((coord_sta & 0x80) != 0x80)) { + r = -EINVAL; + return r; + } + + /* bit7 of coord_sta is 1, touch data is ready */ + /* handle touch event */ + touch_data->key_value = (coord_sta >> 4) & 0x01; + touch_num = coord_sta & 0x0F; + if (unlikely(touch_num > max_touch_num)) { + touch_num = -EINVAL; + goto exit_clean_sta; + } else if (unlikely(touch_num > 1)) { + r = goodix_i2c_read(dev, + TS_REG_COORDS_BASE + 3 + BYTES_PER_COORD, + &buffer[3 + BYTES_PER_COORD], + (touch_num - 1) * BYTES_PER_COORD); + if (unlikely(r < 0)) + goto exit_clean_sta; + } + + /* touch_num * BYTES_PER_COORD + 1(touch event state) + 1(checksum) + * + 1(key value) + */ + chksum = checksum_u8(&buffer[0], touch_num * BYTES_PER_COORD + 3); + if (unlikely(chksum != 0)) { + dev_warn(dev->dev, "Checksum error:%X\n", chksum); + r = -EINVAL; + goto exit_clean_sta; + } + + memset(touch_data->coords, 0x00, sizeof(touch_data->coords)); + for (i = 0; i < touch_num; i++) { + coords->id = buffer[i * BYTES_PER_COORD + 1] & 0x0f; + coords->x = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 2]); + coords->y = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 4]); + coords->w = get_unaligned_le16(&buffer[i * BYTES_PER_COORD + 6]); + + dev_dbg(dev->dev, "D:[%d](%d, %d)[%d]\n", + coords->id, coords->x, coords->y, coords->w); + coords++; + } + + touch_data->touch_num = touch_num; + /* mark this event as touch event */ + ts_event->event_type = EVENT_TOUCH; + r = 0; + +exit_clean_sta: + /* handshake */ + buffer[0] = 0x00; + goodix_i2c_write(dev, TS_REG_COORDS_BASE, buffer, 1); + return r; +} + +/** + * goodix_send_command - seng cmd to firmware + * + * @dev: pointer to device + * @cmd: pointer to command struct which cotain command data + * Returns 0 - succeed,<0 - failed + */ +int goodix_send_command(struct goodix_ts_device *dev, + struct goodix_ts_cmd *cmd) +{ + int ret; + + if (!cmd || !cmd->initialized) + return -EINVAL; + ret = goodix_i2c_write(dev, cmd->cmd_reg, cmd->cmds, + cmd->length); + return ret; +} + +/** + * goodix_hw_suspend - Let touch deivce stay in lowpower mode. + * @dev: pointer to goodix touch device + * @return: 0 - succeed, < 0 - failed + */ +static int goodix_hw_suspend(struct goodix_ts_device *dev) +{ + struct goodix_ts_cmd *sleep_cmd = + &dev->sleep_cmd; + int r = 0; + + if (sleep_cmd->initialized) { + r = goodix_send_command(dev, sleep_cmd); + if (!r) + dev_dbg(dev->dev, "Chip in sleep mode\n"); + } else { + dev_dbg(dev->dev, "Uninitialized sleep command\n"); + } + + return r; +} + +/** + * goodix_hw_resume - Let touch deivce stay in active mode. + * @dev: pointer to goodix touch device + * @return: 0 - succeed, < 0 - failed + */ +static int goodix_hw_resume(struct goodix_ts_device *dev) +{ + struct goodix_ts_version ver; + int r, retry = GOODIX_BUS_RETRY_TIMES; + + for (; retry--;) { + goodix_hw_reset(dev); + r = goodix_read_version(dev, &ver); + if (!r) + break; + } + + return r; +} + +/* hardware opeation funstions */ +static const struct goodix_ts_hw_ops hw_i2c_ops = { + .init = goodix_hw_init, + .read = goodix_i2c_read, + .write = goodix_i2c_write, + .reset = goodix_hw_reset, + .event_handler = goodix_event_handler, + .send_config = goodix_send_config, + .send_cmd = goodix_send_command, + .read_version = goodix_read_version, + .suspend = goodix_hw_suspend, + .resume = goodix_hw_resume, +}; + +static struct platform_device *goodix_pdev; +static void goodix_pdev_release(struct device *dev) +{ + kfree(goodix_pdev); +} + +static int goodix_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct goodix_ts_device *ts_device = NULL; + struct goodix_ts_board_data *ts_bdata = NULL; + int r = 0; + + r = i2c_check_functionality(client->adapter, + I2C_FUNC_I2C); + if (!r) + return -EIO; + + /* board data */ + ts_bdata = devm_kzalloc(&client->dev, + sizeof(struct goodix_ts_board_data), GFP_KERNEL); + if (!ts_bdata) + return -ENOMEM; + +#ifdef CONFIG_OF + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + /* parse devicetree property */ + r = goodix_parse_dt(&client->dev, ts_bdata); + if (r < 0) + return r; + } else +#endif + { + /* use platform data */ + dev_info(&client->dev, "use platform data\n"); + devm_kfree(&client->dev, ts_bdata); + ts_bdata = client->dev.platform_data; + } + + if (!ts_bdata) + return -ENODEV; + + ts_device = devm_kzalloc(&client->dev, + sizeof(struct goodix_ts_device), GFP_KERNEL); + if (!ts_device) + return -ENOMEM; + + ts_bdata->irq = client->irq; + ts_device->name = "GTx5 TouchDevcie"; + ts_device->dev = &client->dev; + ts_device->board_data = ts_bdata; + ts_device->hw_ops = &hw_i2c_ops; + + /* ts core device */ + goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL); + if (!goodix_pdev) + return -ENOMEM; + + goodix_pdev->name = GOODIX_CORE_DRIVER_NAME; + goodix_pdev->id = 0; + goodix_pdev->num_resources = 0; + /* + * you could find this platform dev in + * /sys/devices/platform/goodix_ts.0 + * goodix_pdev->dev.parent = &client->dev; + */ + goodix_pdev->dev.platform_data = ts_device; + goodix_pdev->dev.release = goodix_pdev_release; + + /* register platform device, then the goodix_ts_core module will probe + * the touch deivce. + */ + r = platform_device_register(goodix_pdev); + return r; +} + +static int goodix_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(goodix_pdev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id gtx5_of_matchs[] = { + {.compatible = TS_DT_COMPATIBLE,}, + {}, +}; +MODULE_DEVICE_TABLE(of, gtx5_of_matchs); +#endif + +static const struct i2c_device_id gtx5_id_table[] = { + {TS_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, gtx5_id_table); + +static struct i2c_driver goodix_i2c_driver = { + .driver = { + .name = TS_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(gtx5_of_matchs), + }, + .probe = goodix_i2c_probe, + .remove = goodix_i2c_remove, + .id_table = gtx5_id_table, +}; + +static int __init goodix_i2c_init(void) +{ + return i2c_add_driver(&goodix_i2c_driver); +} + +static void __exit goodix_i2c_exit(void) +{ + i2c_del_driver(&goodix_i2c_driver); +} + +module_init(goodix_i2c_init); +module_exit(goodix_i2c_exit); + +MODULE_DESCRIPTION("Goodix GTx5 Touchscreen Hardware Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c new file mode 100755 index 0000000..48b6077 --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c @@ -0,0 +1,1454 @@ +/* + * Goodix GTx5 Touchscreen Driver. + * + * Copyright (C) 2015 - 2016 Goodix, Inc. + * Authors: Wang Yafei <wangyafei@goodix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include "goodix_ts_core.h" + +/* COMMON PART - START */ +#define TS_DEFAULT_FIRMWARE "goodix_ts_fw.bin" + +#define FW_HEADER_SIZE 256 +#define FW_SUBSYS_INFO_SIZE 8 +#define FW_SUBSYS_INFO_OFFSET 32 +#define FW_SUBSYS_MAX_NUM 24 +#define FW_NAME_MAX 128 + +#define ISP_MAX_BUFFERSIZE (1024 * 16) + +#define HW_REG_CPU_EN 0x4180 +#define HW_REG_ILM_ACCESS 0x50C0 +#define HW_REG_BANK_SELECT 0x50C4 +#define HW_REG_ISP_ADDR 0x8000 +#define HW_REG_ISP_STAT 0x4195 +#define HW_REG_ISP_CMD 0x4196 +#define HW_REG_ISP_PKT_INFO 0xFFF0 +#define HW_REG_ISP_RESULT 0x4197 +#define HW_REG_ISP_BUFFER 0x8000 +#define HW_REG_BOOT_FLAG 0x434C +#define HW_REG_BOOT_CTRL0 0xF7CC +#define HW_REG_BOOT_CTRL1 0xF7EC +#define HW_REG_WDT 0x40B0 + +#define CPU_CTRL_PENDING 0x00 +#define CPU_CTRL_RUNNING 0x01 + +#define ISP_STAT_IDLE 0xFF +#define ISP_STAT_READY 0xAA +#define ISP_STAT_WRITING 0xCC +#define ISP_FLASH_ERROR 0xEE +#define ISP_FLASH_SUCCESS 0xDD +#define ISP_CMD_PREPARE 0x55 +#define ISP_CMD_FLASH 0xAA + +/** + * fw_subsys_info - subsytem firmware information + * @type: sybsystem type + * @size: firmware size + * @flash_addr: flash address + * @data: firmware data + */ +struct fw_subsys_info { + u8 type; + u32 size; + u32 flash_addr; + const u8 *data; +}; + +#pragma pack(1) +/** + * firmware_info + * @size: fw total length + * @checksum: checksum of fw + * @hw_pid: mask pid string + * @hw_pid: mask vid code + * @fw_pid: fw pid string + * @fw_vid: fw vid code + * @subsys_num: number of fw subsystem + * @chip_type: chip type + * @protocol_ver: firmware packing + * protocol version + * @subsys: sybsystem info + */ +struct firmware_info { + u32 size; + u16 checksum; + u8 hw_pid[6]; + u8 hw_vid[3]; + u8 fw_pid[8]; + u8 fw_vid[3]; + u8 subsys_num; + u8 chip_type; + u8 protocol_ver; + u8 reserved[3]; + struct fw_subsys_info subsys[FW_SUBSYS_MAX_NUM]; +}; + +/** + * firmware_packet - firmware packet information + * @packet_size: firmware packet size, max 4Kbytes. + * @flash_addr: device flash address + * @packet_checksum: checksum of the firmware in this packet + * @data: pointer to firmware data. + */ +struct firmware_packet { + u32 packet_size; + u32 flash_addr; + u32 packet_checksum; + const u8 *data; +}; +#pragma pack() + +/** + * firmware_data - firmware data structure + * @fw_info: firmware infromation + * @firmware: firmware data structure + */ +struct firmware_data { + struct firmware_info fw_info; + const struct firmware *firmware; +}; + +enum update_status { + UPSTA_NOTWORK = 0, + UPSTA_PREPARING, + UPSTA_UPDATING, + UPSTA_ABORT, + UPSTA_SUCCESS, + UPSTA_FAILED +}; + +/** + * fw_update_ctrl - structure used to control the + * firmware update process + * @status: update status + * @progress: indicate the progress of update + * @allow_reset: control the reset callback + * @allow_irq: control the irq callback + * @allow_suspend: control the suspend callback + * @allow_resume: allow resume callback + * @fw_data: firmware data + * @ts_dev: touch device + * @fw_name: firmware name + * @attr_fwimage: sysfs bin attrs, for storing fw image + * @fw_from_sysfs: whether the firmware image is loadind + * from sysfs + */ +struct fw_update_ctrl { + enum update_status status; + unsigned int progress; + bool force_update; + + bool allow_reset; + bool allow_irq; + bool allow_suspend; + bool allow_resume; + + struct firmware_data fw_data; + struct goodix_ts_device *ts_dev; + + char fw_name[FW_NAME_MAX]; + struct bin_attribute attr_fwimage; + bool fw_from_sysfs; +}; + +static struct goodix_ext_module goodix_fwu_module; +/** + * goodix_parse_firmware - parse firmware header information + * and subsystem information from firmware data buffer + * + * @fw_data: firmware struct, contains firmware header info + * and firmware data. + * return: 0 - OK, < 0 - error + */ +static int goodix_parse_firmware(struct fw_update_ctrl *fwu_ctrl) +{ + const struct firmware *firmware; + struct firmware_info *fw_info; + struct firmware_data *fw_data = &fwu_ctrl->fw_data; + const struct device *dev = fwu_ctrl->ts_dev->dev; + unsigned int i, fw_offset, info_offset; + u16 checksum; + int r = 0; + + if (!fw_data || !fw_data->firmware) { + dev_err(dev, "Invalid firmware data\n"); + return -EINVAL; + } + fw_info = &fw_data->fw_info; + + /* copy firmware head info */ + firmware = fw_data->firmware; + if (firmware->size < FW_SUBSYS_INFO_OFFSET) { + dev_err(dev, "Invalid firmware size:%zu\n", firmware->size); + r = -EINVAL; + goto err_size; + } + memcpy(fw_info, firmware->data, FW_SUBSYS_INFO_OFFSET); + + /* check firmware size */ + fw_info->size = be32_to_cpu(fw_info->size); + if (firmware->size != fw_info->size + 6) { + dev_err(dev, "Bad firmware, size not match\n"); + r = -EINVAL; + goto err_size; + } + + /* calculate checksum, note: sum of bytes, but check by u16 checksum */ + for (i = 6, checksum = 0; i < firmware->size; i++) + checksum += firmware->data[i]; + + /* byte order change, and check */ + fw_info->checksum = be16_to_cpu(fw_info->checksum); + if (checksum != fw_info->checksum) { + dev_err(dev, "Bad firmware, cheksum error\n"); + r = -EINVAL; + goto err_size; + } + + if (fw_info->subsys_num > FW_SUBSYS_MAX_NUM) { + dev_err(dev, "Bad firmware, invalid subsys num\n"); + r = -EINVAL; + goto err_size; + } + + /* parse subsystem info */ + fw_offset = FW_HEADER_SIZE; + for (i = 0; i < fw_info->subsys_num; i++) { + info_offset = FW_SUBSYS_INFO_OFFSET + + i * FW_SUBSYS_INFO_SIZE; + + fw_info->subsys[i].type = firmware->data[info_offset]; + fw_info->subsys[i].size = + be32_to_cpup((__be32 *)&firmware->data[info_offset + 1]); + fw_info->subsys[i].flash_addr = + be16_to_cpup((__be16 *)&firmware->data[info_offset + 5]); + fw_info->subsys[i].flash_addr <<= 8; /* important! */ + + if (fw_offset > firmware->size) { + dev_err(dev, "Sybsys offset exceed Firmware size\n"); + goto err_size; + } + + fw_info->subsys[i].data = firmware->data + fw_offset; + fw_offset += fw_info->subsys[i].size; + } + + dev_info(dev, "Firmware package protocol: V%u\n", fw_info->protocol_ver); + dev_info(dev, "Fimware PID:GT%s\n", fw_info->fw_pid); + dev_info(dev, "Fimware VID:%02X%02X%02X\n", fw_info->fw_vid[0], + fw_info->fw_vid[1], fw_info->fw_vid[2]); + dev_info(dev, "Firmware chip type:%02X\n", fw_info->chip_type); + dev_info(dev, "Firmware size:%u\n", fw_info->size); + dev_info(dev, "Firmware subsystem num:%u\n", fw_info->subsys_num); + + for (i = 0; i < fw_info->subsys_num; i++) { + dev_dbg(dev, "Index:%d\n", i); + dev_dbg(dev, "Subsystem type:%02X\n", fw_info->subsys[i].type); + dev_dbg(dev, "Subsystem size:%u\n", fw_info->subsys[i].size); + dev_dbg(dev, "Subsystem flash_addr:%08X\n", + fw_info->subsys[i].flash_addr); + dev_dbg(dev, "Subsystem Ptr:%p\n", fw_info->subsys[i].data); + } + +err_size: + return r; +} + +/** + * goodix_check_update - compare the version of firmware running in + * touch device with the version getting from the firmware file. + * @fw_info: firmware information to be compared + * return: 0 firmware in the touch device needs to be updated + * < 0 no need to update firmware + */ +static int goodix_check_update(struct goodix_ts_device *ts_dev, + const struct firmware_info *fw_info) +{ + struct goodix_ts_version fw_ver = {0}; + const struct device *dev = ts_dev->dev; + u16 fwimg_vid; + u8 fwimg_cid; + int r = 0; + + /* read version from chip, if we got invalid firmware version, maybe + * fimware in flash is incorrect, so we need to update firmware + */ + r = ts_dev->hw_ops->read_version(ts_dev, &fw_ver); + if (r == -EBUS) + return r; + + if (fw_ver.valid) { + if (memcmp(fw_ver.pid, fw_info->fw_pid, 4)) { + dev_err(dev, "Product ID is not match\n"); + return -EPERM; + } + + fwimg_cid = fw_info->fw_vid[0]; + fwimg_vid = fw_info->fw_vid[1] << 8 | fw_info->fw_vid[2]; + if (fw_ver.vid == fwimg_vid && fw_ver.cid == fwimg_cid) { + dev_err(dev, "FW version is equal to the IC's\n"); + return -EPERM; + } else if (fw_ver.vid > fwimg_vid) { + dev_info(dev, "Warning: fw version is lower the IC's\n"); + } + } /* else invalid firmware, update firmware */ + + dev_info(dev, "Firmware needs to be updated\n"); + return 0; +} + +/** + * goodix_reg_write_confirm - write register and confirm the value + * in the register. + * @ts_dev: pointer to touch device + * @addr: register address + * @data: pointer to data buffer + * @len: data length + * return: 0 write success and confirm ok + * < 0 failed + */ +static int goodix_reg_write_confirm(struct goodix_ts_device *ts_dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + u8 *cfm, cfm_buf[32]; + int r, i; + + if (len > sizeof(cfm_buf)) { + cfm = kzalloc(len, GFP_KERNEL); + if (!cfm) + return -ENOMEM; + } else { + cfm = &cfm_buf[0]; + } + + for (i = 0; i < GOODIX_BUS_RETRY_TIMES; i++) { + r = ts_dev->hw_ops->write(ts_dev, addr, data, len); + if (r < 0) + goto exit; + + r = ts_dev->hw_ops->read(ts_dev, addr, cfm, len); + if (r < 0) + goto exit; + + if (memcmp(data, cfm, len)) { + r = -EMEMCMP; + continue; + } else { + r = 0; + break; + } + } + +exit: + if (cfm != &cfm_buf[0]) + kfree(cfm); + return r; +} + +static inline int goodix_reg_write(struct goodix_ts_device *ts_dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + return ts_dev->hw_ops->write(ts_dev, addr, data, len); +} + +static inline int goodix_reg_read(struct goodix_ts_device *ts_dev, + unsigned int addr, unsigned char *data, unsigned int len) +{ + return ts_dev->hw_ops->read(ts_dev, addr, data, len); +} + +/** + * goodix_cpu_ctrl - Let cpu stay in pending state or running state + * @ts_dev: pointer to touch device + * @flag: control flag, which can be: + * CPU_CTRL_PENDING - Pending cpu + * Other type of control to cpu is not support. + * return: 0 OK, < 0 Failed, -EAGAIN try again + */ +static int goodix_cpu_ctrl(struct goodix_ts_device *ts_dev, int flag) +{ + const struct device *dev = ts_dev->dev; + u8 ctrl; + int r; + + if (flag == CPU_CTRL_PENDING) { + dev_info(dev, "Pending CPU\n"); + ctrl = 0x04; + } else if (flag == CPU_CTRL_RUNNING) { + dev_info(dev, "Running CPU\n"); + ctrl = 0x00; + } else { + dev_err(dev, "Invalid cpu ctrl flag\n"); + return -EPERM; + } + + /* Pending Cpu */ + r = goodix_reg_write_confirm(ts_dev, HW_REG_CPU_EN, &ctrl, 1); + if (unlikely(r < 0)) { + dev_err(dev, "CPU ctrl failed:%d\n", r); + r = -EAGAIN; /* hw reset and try again */ + } + + return r; +} + +/** + * goodix_isp_wait_stat - waitting ISP state + * @ts_dev: pointer to touch device + * @state: state to wait + * return: 0 - ok, < 0 error, -ETIMEOUT timeout + */ +static int goodix_isp_wait_stat(struct goodix_ts_device *ts_dev, u16 state) +{ + const struct device *dev = ts_dev->dev; + static u8 last_state; + u8 isp_state; + int i, r, err_cnt = 0; + + for (i = 0; i < 200; i++) { + /* read isp state */ + r = goodix_reg_read(ts_dev, HW_REG_ISP_STAT, + &isp_state, 1); + if (r < 0) { + dev_err(dev, "Failed to read ISP state\n"); + if (++err_cnt > GOODIX_BUS_RETRY_TIMES) + return r; + continue; + } + err_cnt = 0; + + if (isp_state != last_state) { + switch (isp_state) { + case ISP_STAT_IDLE: + dev_info(dev, "ISP state: Idle\n"); + break; + case ISP_STAT_WRITING: + dev_info(dev, "ISP state: Writing...\n"); + break; + case ISP_STAT_READY: + dev_info(dev, "ISP state: Ready to write\n"); + break; + default: + dev_err(dev, "ISP state: Unknown\n"); + break; + } + } + + last_state = isp_state; + r = -ETIMEOUT; + if (isp_state == state) { + r = 0; + break; + } + + usleep_range(5000, 5010); + } + + return r; +} + +/** + * goodix_isp_flash_done - check whether flash is successful + * @ts_dev: pointer to touch device + * return: 0 - ok, < 0 error + */ +static int goodix_isp_flash_done(struct goodix_ts_device *ts_dev) +{ + u8 isp_result; + int r, i; + + for (i = 0; i < 2; i++) { + r = goodix_reg_read(ts_dev, HW_REG_ISP_RESULT, + &isp_result, 1); + if (r < 0) { + /* bus error */ + break; + } else if (isp_result == ISP_FLASH_SUCCESS) { + dev_info(ts_dev->dev, "ISP result: OK!\n"); + r = 0; + break; + } else if (isp_result == ISP_FLASH_ERROR) { + dev_err(ts_dev->dev, "ISP result: ERROR!\n"); + r = -EAGAIN; + } + } + return r; +} + +/** + * goodix_isp_command - communication with ISP. + * @cmd: ISP command. + * return: 0 ok, <0 error + */ +static int goodix_isp_command(struct goodix_ts_device *ts_dev, u8 cmd) +{ + switch (cmd) { + case ISP_CMD_PREPARE: + break; + case ISP_CMD_FLASH: + break; + default: + dev_err(ts_dev->dev, "Invalid ISP cmd\n"); + return -EINVAL; + } + + return goodix_reg_write(ts_dev, HW_REG_ISP_CMD, &cmd, 1); +} + +/** + * goodix_load_isp - load ISP program to deivce ram + * @ts_dev: pointer to touch device + * @fw_data: firmware data + * return 0 ok, <0 error + */ +static inline int goodix_load_isp(struct goodix_ts_device *ts_dev, + struct firmware_data *fw_data) +{ + struct fw_subsys_info *fw_isp; + int r; + + fw_isp = &fw_data->fw_info.subsys[0]; + + dev_info(ts_dev->dev, "Loading ISP program\n"); + r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_ADDR, + (u8 *)fw_isp->data, fw_isp->size); + if (r < 0) + dev_err(ts_dev->dev, "Loading ISP error\n"); + + return r; +} + +/** + * goodix_enter_update - update prepare, loading ISP program + * and make sure the ISP is running. + * @fwu_ctrl: pointer to fimrware control structure + * return: 0 ok, <0 error + */ +static int goodix_update_prepare(struct fw_update_ctrl *fwu_ctrl) +{ + struct goodix_ts_device *ts_dev = fwu_ctrl->ts_dev; + const struct device *dev = fwu_ctrl->ts_dev->dev; + u8 boot_val0[4] = {0xb8, 0x3f, 0x35, 0x56}; + u8 boot_val1[4] = {0xb9, 0x3e, 0xb5, 0x54}; + u8 reg_val[4] = {0x00}; + int r; + + fwu_ctrl->allow_reset = true; + ts_dev->hw_ops->reset(ts_dev); + fwu_ctrl->allow_reset = false; + + /* enable ILM access */ + reg_val[0] = 0x06; + r = goodix_reg_write_confirm(ts_dev, HW_REG_ILM_ACCESS, + reg_val, 1); + if (r < 0) { + dev_err(dev, "Failed to enable ILM access\n"); + return r; + } + + /* Pending CPU */ + r = goodix_cpu_ctrl(ts_dev, CPU_CTRL_PENDING); + if (r < 0) + return r; + + /* disable watchdog timer */ + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_WDT, + reg_val, 1); + if (r < 0) { + dev_err(dev, "Failed to disable watchdog\n"); + return r; + } + + /* select bank 2 */ + reg_val[0] = 0x02; + r = goodix_reg_write_confirm(ts_dev, HW_REG_BANK_SELECT, + reg_val, 1); + if (r < 0) { + dev_err(dev, "Failed to select bank2\n"); + return r; + } + + /* load ISP code */ + r = goodix_load_isp(ts_dev, &fwu_ctrl->fw_data); + if (r < 0) + return r; + + /* Clear ISP state */ + reg_val[0] = reg_val[1] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_ISP_STAT, + reg_val, 2); + if (r < 0) { + dev_err(dev, "Failed to clear ISP state\n"); + return r; + } + + /* set boot flag */ + reg_val[0] = 0; + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_FLAG, + reg_val, 1); + if (r < 0) { + dev_err(dev, "Failed to set boot flag\n"); + return r; + } + + /* set boot from sRam */ + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_CTRL0, + boot_val0, sizeof(boot_val0)); + if (r < 0) { + dev_err(dev, "Failed to set boot flag\n"); + return r; + } + + /* set boot from sRam */ + r = goodix_reg_write_confirm(ts_dev, HW_REG_BOOT_CTRL1, + boot_val1, sizeof(boot_val1)); + if (r < 0) { + dev_err(dev, "Failed to set boot flag\n"); + return r; + } + + /* disbale ILM access */ + reg_val[0] = 0x00; + r = goodix_reg_write_confirm(ts_dev, HW_REG_ILM_ACCESS, + reg_val, 1); + if (r < 0) { + dev_err(dev, "Failed to disable ILM access\n"); + return r; + } + + /* Release CPU */ + r = goodix_cpu_ctrl(ts_dev, CPU_CTRL_RUNNING); + if (r < 0) + return r; + + /* wait isp idel */ + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE); + if (r < 0) { + dev_err(dev, "Wait ISP IDLE timeout\n"); + return r; + } + + return r; +} + +/** + * goodix_write_fwdata - write firmware data to ISP buffer + * @ts_dev: pointer to touch device + * @fw_data: firmware data + * @size: size of data, size can not exceed ISP_MAX_BUFFERSIZE + * + checksum size{2}, + * return: 0 ok, <0 error + */ +static int goodix_write_fwdata(struct goodix_ts_device *ts_dev, + const u8 *fw_data, u32 size) +{ + if (!fw_data || size > ISP_MAX_BUFFERSIZE) + return -EINVAL; + + return goodix_reg_write(ts_dev, HW_REG_ISP_BUFFER, + (u8 *)fw_data, size); +} + +/** + * goodix_format_fw_packet - formate one flash packet + * @pkt: target firmware packet + * @flash_addr: flash address + * @size: packet size + * @data: packet data + */ +static int goodix_format_fw_packet(struct firmware_packet *pkt, + u32 flash_addr, u32 size, const u8 *data) +{ + if (!pkt || !data || size % 4) + return -EINVAL; + + /* + * checksum rule:sum of data in one format is equal to zero + * data format: byte/le16/be16/le32/be32/le64/be64 + */ + pkt->flash_addr = cpu_to_le32(flash_addr); + pkt->packet_size = cpu_to_le32(size); + pkt->packet_checksum = checksum_le32((u8 *)data, size); + pkt->data = data; + return 0; +} + +/** + * goodix_send_fw_packet - send one firmware packet to ISP + * @ts_dev: target touch device + * @pkt: firmware packet + * return:0 ok, <0 error + */ +static int goodix_send_fw_packet(struct goodix_ts_device *ts_dev, + struct firmware_packet *pkt) +{ + u8 pkt_info[12]; + int r; + + if (!pkt) + return -EINVAL; + + /* 1: wait ISP idle */ + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE); + if (r < 0) + return r; + + /* 2: write packet information */ + memcpy(pkt_info, pkt, sizeof(pkt_info)); + r = goodix_reg_write(ts_dev, HW_REG_ISP_PKT_INFO, + pkt_info, sizeof(pkt_info)); + if (r < 0) { + dev_err(ts_dev->dev, "Failed to write packet info\n"); + return r; + } + + /* 3: Make ISP ready to flash */ + r = goodix_isp_command(ts_dev, ISP_CMD_PREPARE); + if (r < 0) { + dev_err(ts_dev->dev, "Failed to make ISP ready\n"); + return r; + } + + /* 4: write packet data(firmware block) to ISP buffer */ + r = goodix_write_fwdata(ts_dev, pkt->data, pkt->packet_size); + if (r < 0) { + dev_err(ts_dev->dev, "Failed to write firmware packet\n"); + return r; + } + + /* 5: wait ISP ready */ + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_READY); + if (r < 0) { + dev_err(ts_dev->dev, "Failed to wait ISP ready\n"); + return r; + } + + /* 6: start writting to flash */ + r = goodix_isp_command(ts_dev, ISP_CMD_FLASH); + if (r < 0) { + dev_err(ts_dev->dev, "Failed to start flash\n"); + return r; + } + + /* 7: wait idle */ + r = goodix_isp_wait_stat(ts_dev, ISP_STAT_IDLE); + if (r < 0) { + dev_err(ts_dev->dev, "Error occurred when wait ISP idle\n"); + return r; + }; + + /* check ISP result */ + r = goodix_isp_flash_done(ts_dev); + if (r < 0) { + dev_err(ts_dev->dev, "Flash fw packet failed:%d\n", r); + return r; + } + + return 0; +} + +/** + * goodix_flash_subsystem - flash subsystem firmware, + * Main flow of flashing firmware. + * Each firmware subsystem is divided into several + * packets, the max size of packet is limited to + * @{ISP_MAX_BUFFERSIZE} + * @ts_dev: pointer to touch device + * @subsys: subsystem information + * return: 0 ok, < 0 error + */ +static int goodix_flash_subsystem(struct goodix_ts_device *ts_dev, + struct fw_subsys_info *subsys) +{ + struct firmware_packet fw_pkt; + u32 data_size, total_size, offset; + int r = 0; + + /* + * if bus(i2c/spi) error occued, then exit, we will do + * hardware reset and re-prepare ISP and then retry + * flashing + */ + total_size = subsys->size; + offset = 0; + while (total_size > 0) { + data_size = total_size > ISP_MAX_BUFFERSIZE ? + ISP_MAX_BUFFERSIZE : total_size; + dev_info(ts_dev->dev, "Flash firmware to %08x,size:%u bytes\n", + subsys->flash_addr + offset, data_size); + + /* format one firmware packet */ + r = goodix_format_fw_packet(&fw_pkt, subsys->flash_addr + offset, + data_size, &subsys->data[offset]); + if (r < 0) { + dev_err(ts_dev->dev, "Invalid packet params\n"); + goto exit; + } + + /* send one firmware packet */ + r = goodix_send_fw_packet(ts_dev, &fw_pkt); + if (r < 0) { + dev_err(ts_dev->dev, + "Failed to send firmware packet,err:%d\n", r); + goto exit; + } + + offset += data_size; + total_size -= data_size; + } /* end while */ + +exit: + return r; +} + +/** + * goodix_flash_firmware - flash firmware + * @ts_dev: pointer to touch device + * @fw_data: firmware data + * return: 0 ok, < 0 error + */ +static int goodix_flash_firmware(struct goodix_ts_device *ts_dev, + struct firmware_data *fw_data) +{ + struct fw_update_ctrl *fw_ctrl; + struct firmware_info *fw_info; + struct fw_subsys_info *fw_x; + int retry = GOODIX_BUS_RETRY_TIMES; + int i, r = 0, fw_num, prog_step; + + /* start from subsystem 1, subsystem 0 is the ISP program */ + fw_ctrl = container_of(fw_data, struct fw_update_ctrl, fw_data); + fw_info = &fw_data->fw_info; + fw_num = fw_info->subsys_num; + + /* we have 80% work here */ + prog_step = 80 / (fw_num - 1); + + for (i = 1; i < fw_num && retry;) { + dev_info(ts_dev->dev, + "--- Start to flash subsystem[%d] ---", i); + fw_x = &fw_info->subsys[i]; + r = goodix_flash_subsystem(ts_dev, fw_x); + if (r == 0) { + dev_info(ts_dev->dev, + "--- End flash subsystem[%d]: OK ---", i); + fw_ctrl->progress += prog_step; + i++; + } else if (r == -EAGAIN) { + retry--; + dev_err(ts_dev->dev, + "--- End flash subsystem%d: Fail, errno:%d, retry:%d ---", + i, r, GOODIX_BUS_RETRY_TIMES - retry); + } else if (r < 0) { /* bus error */ + dev_err(ts_dev->dev, + "--- End flash subsystem%d: Fatal error:%d exit ---", + i, r); + goto exit_flash; + } + } + +exit_flash: + return r; +} + +/** + * goodix_update_finish - update finished, free resource + * and reset flags--- + * @fwu_ctrl: pointer to fw_update_ctrl structrue + * return: 0 ok, < 0 error + */ +static int goodix_update_finish(struct fw_update_ctrl *fwu_ctrl) +{ + struct goodix_ts_version ver; + int r = 0; + + fwu_ctrl->ts_dev->hw_ops->reset(fwu_ctrl->ts_dev); + r = fwu_ctrl->ts_dev->hw_ops->read_version(fwu_ctrl->ts_dev, &ver); + return r; +} + +/** + * goodix_fw_update_proc - firmware update process, the entry of + * firmware update flow + * @fwu_ctrl: firmware control + * return: 0 ok, < 0 error + */ +int goodix_fw_update_proc(struct fw_update_ctrl *fwu_ctrl) +{ +#define FW_UPDATE_RETRY 2 + const struct device *dev = fwu_ctrl->ts_dev->dev; + int retry0 = FW_UPDATE_RETRY, retry1 = FW_UPDATE_RETRY; + int r = 0; + + if (fwu_ctrl->status == UPSTA_PREPARING || + fwu_ctrl->status == UPSTA_UPDATING) { + dev_err(dev, "Firmware update already in progress\n"); + return -EBUSY; + } + fwu_ctrl->progress = 0; + fwu_ctrl->status = UPSTA_PREPARING; + r = goodix_parse_firmware(fwu_ctrl); + if (r < 0) { + fwu_ctrl->status = UPSTA_ABORT; + goto err_parse_fw; + } + fwu_ctrl->progress = 10; + if (fwu_ctrl->force_update == false) { + r = goodix_check_update(fwu_ctrl->ts_dev, + &fwu_ctrl->fw_data.fw_info); + if (r < 0) { + fwu_ctrl->status = UPSTA_ABORT; + goto err_check_update; + } + } +start_update: + fwu_ctrl->progress = 20; + fwu_ctrl->status = UPSTA_UPDATING; /* show upgrading status */ + r = goodix_update_prepare(fwu_ctrl); + if ((r == -EBUS || r == -EAGAIN) && --retry0 > 0) { + dev_err(dev, "Bus error, retry prepare ISP:%d\n", + FW_UPDATE_RETRY - retry0); + goto start_update; + } else if (r < 0) { + dev_err(dev, "Failed to prepare ISP, exit update:%d\n", r); + fwu_ctrl->status = UPSTA_FAILED; + goto err_fw_prepare; + } + /* progress: 20%~100% */ + r = goodix_flash_firmware(fwu_ctrl->ts_dev, &fwu_ctrl->fw_data); + if ((r == -EBUS || r == -ETIMEOUT) && --retry1 > 0) { + /* we will retry[twice] if returns bus error[i2c/spi] + * we will do hardware reset and re-prepare ISP and then retry + * flashing + */ + dev_err(dev, "Bus error, retry firmware update:%d\n", + FW_UPDATE_RETRY - retry1); + goto start_update; + } else if (r < 0) { + dev_err(dev, "Fatal error, exit update:%d\n", r); + fwu_ctrl->status = UPSTA_FAILED; + goto err_fw_flash; + } + fwu_ctrl->status = UPSTA_SUCCESS; +err_fw_flash: +err_fw_prepare: + goodix_update_finish(fwu_ctrl); +err_check_update: +err_parse_fw: + if (fwu_ctrl->status == UPSTA_SUCCESS) + dev_info(dev, "Firmware update successfully\n"); + else if (fwu_ctrl->status == UPSTA_FAILED) + dev_err(dev, "Firmware update failed\n"); + fwu_ctrl->progress = 100; /* 100% */ + return r; +} +/* COMMON PART - END */ + +/** + * goodix_request_firmware - request firmware data from user space + * + * @fw_data: firmware struct, contains firmware header info + * and firmware data pointer. + * return: 0 - OK, < 0 - error + */ +static int goodix_request_firmware(struct firmware_data *fw_data, + const char *name) +{ + struct fw_update_ctrl *fw_ctrl = + container_of(fw_data, struct fw_update_ctrl, fw_data); + struct device *dev = fw_ctrl->ts_dev->dev; + int r; + + r = request_firmware(&fw_data->firmware, name, dev); + if (r < 0) + dev_err(dev, + "Firmware image [%s] not available,errno:%d\n", name, r); + else + dev_info(dev, "Firmware image [%s] is ready\n", name); + return r; +} + +/** + * relase firmware resources + * + */ +static inline void goodix_release_firmware(struct firmware_data *fw_data) +{ + if (fw_data->firmware) { + release_firmware(fw_data->firmware); + fw_data->firmware = NULL; + } +} + +static int goodix_fw_update_thread(void *data) +{ + struct fw_update_ctrl *fwu_ctrl = data; + static DEFINE_MUTEX(fwu_lock); + int r = -EINVAL; + + if (!fwu_ctrl) + return r; + + if (goodix_register_ext_module(&goodix_fwu_module)) + return -EIO; + + mutex_lock(&fwu_lock); + /* judge where to get firmware data */ + if (!fwu_ctrl->fw_from_sysfs) { + r = goodix_request_firmware(&fwu_ctrl->fw_data, + fwu_ctrl->fw_name); + if (r < 0) { + fwu_ctrl->status = UPSTA_ABORT; + fwu_ctrl->progress = 100; + goto out; + } + } else { + if (!fwu_ctrl->fw_data.firmware) { + fwu_ctrl->status = UPSTA_ABORT; + fwu_ctrl->progress = 100; + r = -EINVAL; + goto out; + } + } + + /* DONT allow reset/irq/suspend/resume during update */ + fwu_ctrl->allow_irq = false; + fwu_ctrl->allow_suspend = false; + fwu_ctrl->allow_resume = false; + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_START, NULL); + + /* ready to update */ + r = goodix_fw_update_proc(fwu_ctrl); + + goodix_ts_blocking_notify(NOTIFY_FWUPDATE_END, NULL); + fwu_ctrl->allow_reset = true; + fwu_ctrl->allow_irq = true; + fwu_ctrl->allow_suspend = true; + fwu_ctrl->allow_resume = true; + + /* clean */ + if (!fwu_ctrl->fw_from_sysfs) { + goodix_release_firmware(&fwu_ctrl->fw_data); + } else { + fwu_ctrl->fw_from_sysfs = false; + vfree(fwu_ctrl->fw_data.firmware); + fwu_ctrl->fw_data.firmware = NULL; + } + +out: + goodix_unregister_ext_module(&goodix_fwu_module); + mutex_unlock(&fwu_lock); + return r; +} + +/* sysfs attributes */ +static ssize_t goodix_sysfs_update_fw_store( + struct goodix_ext_module *module, + const char *buf, size_t count) +{ + int ret; + + ret = goodix_fw_update_thread(module->priv_data); + if (ret) + count = ret; + + return count; +} + +static ssize_t goodix_sysfs_update_progress_show( + struct goodix_ext_module *module, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + + return scnprintf(buf, PAGE_SIZE, "%d\n", fw_ctrl->progress); +} + +static ssize_t goodix_sysfs_update_result_show( + struct goodix_ext_module *module, + char *buf) +{ + char *result = NULL; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + + switch (fw_ctrl->status) { + case UPSTA_NOTWORK: + result = "notwork"; + break; + case UPSTA_PREPARING: + result = "preparing"; + break; + case UPSTA_UPDATING: + result = "upgrading"; + break; + case UPSTA_ABORT: + result = "abort"; + break; + case UPSTA_SUCCESS: + result = "success"; + break; + case UPSTA_FAILED: + result = "failed"; + break; + default: + break; + } + + return scnprintf(buf, PAGE_SIZE, "%s\n", result); +} + +static ssize_t goodix_sysfs_update_fwversion_show( + struct goodix_ext_module *module, + char *buf) +{ + struct goodix_ts_version fw_ver; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int r = 0; + char str[5]; + + /* read version from chip */ + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev, + &fw_ver); + if (!r) { + memcpy(str, fw_ver.pid, 4); + str[4] = '\0'; + return scnprintf(buf, PAGE_SIZE, "PID:%s VID:%04x SENSOR_ID:%d\n", + str, fw_ver.vid, fw_ver.sensor_id); + } + return 0; +} + +static ssize_t goodix_sysfs_fwsize_show(struct goodix_ext_module *module, + char *buf) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int r = -EINVAL; + + if (fw_ctrl && fw_ctrl->fw_data.firmware) + r = snprintf(buf, PAGE_SIZE, "%zu\n", + fw_ctrl->fw_data.firmware->size); + return r; +} + +static ssize_t goodix_sysfs_fwsize_store(struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + struct firmware *fw; + u8 **data; + size_t size = 0; + + if (!fw_ctrl) + return -EINVAL; + + if (sscanf(buf, "%zu", &size) < 0 || !size) { + dev_err(fw_ctrl->ts_dev->dev, "Failed to get fwsize"); + return -EFAULT; + } + + fw = vmalloc(sizeof(*fw) + size); + if (!fw) + return -ENOMEM; + + memset(fw, 0x00, sizeof(*fw) + size); + data = (u8 **)&fw->data; + *data = (u8 *)fw + sizeof(struct firmware); + fw->size = size; + fw_ctrl->fw_data.firmware = fw; + fw_ctrl->fw_from_sysfs = true; + + return count; +} + +static ssize_t goodix_sysfs_fwimage_store(struct file *file, + struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + struct fw_update_ctrl *fw_ctrl; + struct firmware_data *fw_data; + + fw_ctrl = container_of(attr, struct fw_update_ctrl, + attr_fwimage); + fw_data = &fw_ctrl->fw_data; + + if (!fw_data->firmware) { + dev_err(fw_ctrl->ts_dev->dev, "Need set fw image size first"); + return -ENOMEM; + } + + if (fw_data->firmware->size == 0) { + dev_err(fw_ctrl->ts_dev->dev, "Invalid firmware size"); + return -EINVAL; + } + + if (pos + count > fw_data->firmware->size) + return -EFAULT; + + memcpy((u8 *)&fw_data->firmware->data[pos], buf, count); + fw_ctrl->force_update = true; + + return count; +} + +static ssize_t goodix_sysfs_force_update_store( + struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + if (val) + fw_ctrl->force_update = true; + else + fw_ctrl->force_update = false; + + return count; +} + + +static ssize_t goodix_sysfs_update_hwversion_show( + struct goodix_ext_module *module, + char *buf) +{ + struct goodix_ts_version fw_ver; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int r = 0; + char str[5]; + + /* read version from chip */ + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev, + &fw_ver); + if (!r) { + memcpy(str, fw_ver.pid, 4); + str[4] = '\0'; + return scnprintf(buf, PAGE_SIZE, "%s\n", str); + } + return 0; +} + +static ssize_t goodix_sysfs_update_fw_version_show( + struct goodix_ext_module *module, + char *buf) +{ + struct goodix_ts_version fw_ver; + struct fw_update_ctrl *fw_ctrl = module->priv_data; + int r = 0; + + /* read version from chip */ + r = fw_ctrl->ts_dev->hw_ops->read_version(fw_ctrl->ts_dev, + &fw_ver); + if (!r) { + /* firmversion major+minor store formate is 2byte compress BCD */ + return scnprintf(buf, PAGE_SIZE, "%2x.%2x\n", + fw_ver.vid >> 8, fw_ver.vid & 0xff); + + } + return 0; +} + +static ssize_t goodix_sysfs_fw_name_store( + struct goodix_ext_module *module, + const char *buf, size_t count) +{ + struct fw_update_ctrl *fwu_ctrl; + + if (!module || !module->priv_data) + return -ENOMEM; + + fwu_ctrl = module->priv_data; + if (count > FW_NAME_MAX) { + dev_err(fwu_ctrl->ts_dev->dev, "Firmware name too long"); + return -EINVAL; + } + memset(fwu_ctrl->fw_name, 0, FW_NAME_MAX); + memcpy(fwu_ctrl->fw_name, buf, count); + + return count; +} + +static struct goodix_ext_attribute goodix_fwu_attrs[] = { + __EXTMOD_ATTR(progress, 0444, goodix_sysfs_update_progress_show, NULL), + __EXTMOD_ATTR(result, 0444, goodix_sysfs_update_result_show, NULL), + __EXTMOD_ATTR(fwversion, 0444, goodix_sysfs_update_fwversion_show, NULL), + __EXTMOD_ATTR(fwsize, 0644, goodix_sysfs_fwsize_show, + goodix_sysfs_fwsize_store), + __EXTMOD_ATTR(force_update, 0200, NULL, goodix_sysfs_force_update_store), + __EXTMOD_ATTR(update_fw, 0200, NULL, goodix_sysfs_update_fw_store), + __EXTMOD_ATTR(fw_version, 0444, goodix_sysfs_update_fw_version_show, NULL), + __EXTMOD_ATTR(fw_name, 0200, NULL, goodix_sysfs_fw_name_store), + __EXTMOD_ATTR(hw_version, 0444, goodix_sysfs_update_hwversion_show, NULL), +}; + +static int goodix_syfs_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fw_ctrl = module->priv_data; + const struct device *dev = &core_data->pdev->dev; + struct kobj_type *ktype; + int ret = 0, i; + + ktype = goodix_get_default_ktype(); + ret = kobject_init_and_add(&module->kobj, + ktype, + &core_data->pdev->dev.kobj, + "fwupdate"); + if (ret) { + dev_err(dev, "Create fwupdate sysfs node error!\n"); + goto exit_sysfs_init; + } + + for (i = 0; i < ARRAY_SIZE(goodix_fwu_attrs); i++) { + if (sysfs_create_file(&module->kobj, + &goodix_fwu_attrs[i].attr)) { + dev_warn(dev, "Create sysfs attr file error\n"); + kobject_put(&module->kobj); + ret = -EINVAL; + goto exit_sysfs_init; + } + } + + fw_ctrl->attr_fwimage.attr.name = "fwimage"; + fw_ctrl->attr_fwimage.attr.mode = 0200; + fw_ctrl->attr_fwimage.size = 0; + fw_ctrl->attr_fwimage.write = goodix_sysfs_fwimage_store; + ret = sysfs_create_bin_file(&module->kobj, + &fw_ctrl->attr_fwimage); + +exit_sysfs_init: + return ret; +} + +static int goodix_fw_update_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct fw_update_ctrl *fwu_ctrl; + static bool init_sysfs = true; + + if (!core_data->ts_dev) + return -ENODEV; + + if (!module->priv_data) { + module->priv_data = kzalloc(sizeof(struct fw_update_ctrl), + GFP_KERNEL); + if (!module->priv_data) + return -ENOMEM; + } + fwu_ctrl = module->priv_data; + fwu_ctrl->ts_dev = core_data->ts_dev; + fwu_ctrl->allow_reset = true; + fwu_ctrl->allow_irq = true; + fwu_ctrl->allow_suspend = true; + fwu_ctrl->allow_resume = true; + + /* find a valid firmware image name */ + if (strlen(fwu_ctrl->fw_name) == 0) { + if (ts_bdata && ts_bdata->fw_name) + strlcpy(fwu_ctrl->fw_name, ts_bdata->fw_name, + sizeof(fwu_ctrl->fw_name)); + else + strlcpy(fwu_ctrl->fw_name, TS_DEFAULT_FIRMWARE, + sizeof(fwu_ctrl->fw_name)); + } + + /* create sysfs interface */ + if (init_sysfs) { + if (!goodix_syfs_init(core_data, module)) + init_sysfs = false; + } + + return 0; +} + +static int goodix_fw_update_exit(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + return 0; +} + +static int goodix_fw_before_suspend(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_suspend ? + EVT_HANDLED : EVT_CANCEL_SUSPEND; +} + +static int goodix_fw_before_resume(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_resume ? + EVT_HANDLED : EVT_CANCEL_RESUME; +} + +static int goodix_fw_irq_event(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_irq ? + EVT_HANDLED : EVT_CANCEL_IRQEVT; +} + +static int goodix_fw_before_reset(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct fw_update_ctrl *fwu_ctrl = module->priv_data; + + return fwu_ctrl->allow_reset ? + EVT_HANDLED : EVT_CANCEL_RESET; +} + +static const struct goodix_ext_module_funcs goodix_ext_funcs = { + .init = goodix_fw_update_init, + .exit = goodix_fw_update_exit, + .before_reset = goodix_fw_before_reset, + .after_reset = NULL, + .before_suspend = goodix_fw_before_suspend, + .after_suspend = NULL, + .before_resume = goodix_fw_before_resume, + .after_resume = NULL, + .irq_event = goodix_fw_irq_event, +}; + +static struct goodix_ext_module goodix_fwu_module = { + .name = "goodix-fwu", + .funcs = &goodix_ext_funcs, + .priority = EXTMOD_PRIO_FWUPDATE, +}; + +static int __init goodix_fwu_module_init(void) +{ + return goodix_register_ext_module(&goodix_fwu_module); +} + +static void __exit goodix_fwu_module_exit(void) +{ +} + +module_init(goodix_fwu_module_init); +module_exit(goodix_fwu_module_exit); + +MODULE_DESCRIPTION("Goodix FWU Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c new file mode 100755 index 0000000..f562b36 --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.c @@ -0,0 +1,1366 @@ + /* + * Goodix GTx5 Touchscreen Driver + * Core layer of touchdriver architecture. + * + * Copyright (C) 2015 - 2016 Goodix, Inc. + * Authors: Wang Yafei <wangyafei@goodix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/of_platform.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/of_irq.h> +#include <linux/regulator/consumer.h> +#include <linux/input/mt.h> +#include "goodix_ts_core.h" + +#define INPUT_TYPE_B_PROTOCOL + +#define GOOIDX_INPUT_PHYS "goodix_ts/input0" +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" + +/* + * struct goodix_modules - external modules container + * @head: external modules list + * @initilized: whether this struct is initilized + * @mutex: mutex lock + * @count: current number of registered external module + * @wq: workqueue to do register work + * @core_exit: if goodix touch core exit, then no + * registration is allowed. + * @core_data: core_data pointer + */ +struct goodix_modules { + struct list_head head; + bool initilized; + struct mutex mutex; + unsigned int count; + struct workqueue_struct *wq; + bool core_exit; + struct completion core_comp; + struct goodix_ts_core *core_data; +}; +static struct goodix_modules goodix_modules; + +/** + * __do_register_ext_module - register external module + * to register into touch core modules structure + */ +static void __do_register_ext_module(struct work_struct *work) +{ + struct goodix_ext_module *module = + container_of(work, struct goodix_ext_module, work); + struct goodix_ext_module *ext_module; + struct list_head *insert_point = &goodix_modules.head; + + /* waitting for core layer */ + if (!wait_for_completion_timeout(&goodix_modules.core_comp, 5 * HZ)) + return; + + /* driver probe failed */ + if (goodix_modules.core_exit) + return; + + /* prority level *must* be set */ + if (module->priority == EXTMOD_PRIO_RESERVED) + return; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (ext_module == module) { + mutex_unlock(&goodix_modules.mutex); + return; + } + } + + list_for_each_entry(ext_module, &goodix_modules.head, list) { + /* small value of priority have higher priority level */ + if (ext_module->priority >= module->priority) { + insert_point = &ext_module->list; + break; + } + } + /* else module will be inserted to goodix_modules->head */ + } + + if (module->funcs && module->funcs->init) { + if (module->funcs->init(goodix_modules.core_data, + module) < 0) { + mutex_unlock(&goodix_modules.mutex); + return; + } + } + + list_add(&module->list, insert_point->prev); + goodix_modules.count++; + mutex_unlock(&goodix_modules.mutex); +} + +/** + * goodix_register_ext_module - interface for external module + * to register into touch core modules structure + * + * @module: pointer to external module to be register + * return: 0 ok, <0 failed + */ +int goodix_register_ext_module(struct goodix_ext_module *module) +{ + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) { + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); + init_completion(&goodix_modules.core_comp); + } + + if (goodix_modules.core_exit) + return -EFAULT; + + INIT_WORK(&module->work, __do_register_ext_module); + schedule_work(&module->work); + + return 0; +} +EXPORT_SYMBOL_GPL(goodix_register_ext_module); + +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module) +{ + struct goodix_ext_module *ext_module; + bool found = false; + + if (!module) + return -EINVAL; + + if (!goodix_modules.initilized) + return -EINVAL; + + if (!goodix_modules.core_data) + return -ENODEV; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (ext_module == module) { + found = true; + break; + } + } + } else { + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + if (!found) { + mutex_unlock(&goodix_modules.mutex); + return -EFAULT; + } + + list_del(&module->list); + mutex_unlock(&goodix_modules.mutex); + + if (module->funcs && module->funcs->exit) + module->funcs->exit(goodix_modules.core_data, module); + goodix_modules.count--; + + return 0; +} +EXPORT_SYMBOL_GPL(goodix_unregister_ext_module); + +static void goodix_ext_sysfs_release(struct kobject *kobj) +{ + return; +} + +#define to_ext_module(kobj) container_of(kobj,\ + struct goodix_ext_module, kobj) +#define to_ext_attr(attr) container_of(attr,\ + struct goodix_ext_attribute, attr) + +static ssize_t goodix_ext_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct goodix_ext_module *module = to_ext_module(kobj); + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr); + + if (ext_attr->show) + return ext_attr->show(module, buf); + + return -EIO; +} + +static ssize_t goodix_ext_sysfs_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t count) +{ + struct goodix_ext_module *module = to_ext_module(kobj); + struct goodix_ext_attribute *ext_attr = to_ext_attr(attr); + + if (ext_attr->store) + return ext_attr->store(module, buf, count); + + return -EIO; +} + +static const struct sysfs_ops goodix_ext_ops = { + .show = goodix_ext_sysfs_show, + .store = goodix_ext_sysfs_store +}; + +static struct kobj_type goodix_ext_ktype = { + .release = goodix_ext_sysfs_release, + .sysfs_ops = &goodix_ext_ops, +}; + +struct kobj_type *goodix_get_default_ktype(void) +{ + return &goodix_ext_ktype; +} +EXPORT_SYMBOL_GPL(goodix_get_default_ktype); + +struct kobject *goodix_get_default_kobj(void) +{ + struct kobject *kobj = NULL; + + if (goodix_modules.core_data && + goodix_modules.core_data->pdev) + kobj = &goodix_modules.core_data->pdev->dev.kobj; + return kobj; +} +EXPORT_SYMBOL_GPL(goodix_get_default_kobj); + +/* show external module information */ +static ssize_t goodix_ts_extmod_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ext_module *module; + size_t offset = 0; + int r; + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(module, &goodix_modules.head, list) { + r = snprintf(&buf[offset], PAGE_SIZE, + "priority:%u module:%s\n", + module->priority, module->name); + if (r < 0) { + mutex_unlock(&goodix_modules.mutex); + return -EINVAL; + } + offset += r; + } + } + + mutex_unlock(&goodix_modules.mutex); + return offset; +} + +/* show driver information */ +static ssize_t goodix_ts_driver_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "DriverVersion:%s\n", + GOODIX_DRIVER_VERSION); +} + +/* show chip infoamtion */ +static ssize_t goodix_ts_chip_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + struct goodix_ts_version chip_ver; + int r, cnt = 0; + + cnt += snprintf(buf, PAGE_SIZE, + "TouchDeviceName:%s\n", ts_dev->name); + if (ts_dev->hw_ops->read_version) { + r = ts_dev->hw_ops->read_version(ts_dev, &chip_ver); + if (!r && chip_ver.valid) { + cnt += snprintf(&buf[cnt], PAGE_SIZE, + "PID:%s\nVID:%04x\nSensorID:%02x\n", + chip_ver.pid, chip_ver.vid, + chip_ver.sensor_id); + } + } + + return cnt; +} + +/* show chip configuration data */ +static ssize_t goodix_ts_config_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + struct goodix_ts_config *ncfg = ts_dev->normal_cfg; + u8 *data; + int i, r, offset = 0; + + if (ncfg && ncfg->initialized && ncfg->length < PAGE_SIZE) { + data = kmalloc(ncfg->length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + r = ts_dev->hw_ops->read(ts_dev, ncfg->reg_base, + &data[0], ncfg->length); + if (r < 0) { + kfree(data); + return -EINVAL; + } + + for (i = 0; i < ncfg->length; i++) { + if (i != 0 && i % 20 == 0) + buf[offset++] = '\n'; + offset += snprintf(&buf[offset], PAGE_SIZE - offset, + "%02x ", data[i]); + } + buf[offset++] = '\n'; + buf[offset++] = '\0'; + kfree(data); + return offset; + } + + return -EINVAL; +} + +/* reset chip */ +static ssize_t goodix_ts_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + int en; + + if (kstrtoint(buf, 10, &en)) + return -EINVAL; + + if (en != 1) + return -EINVAL; + + if (ts_dev->hw_ops->reset) + ts_dev->hw_ops->reset(ts_dev); + return count; + +} + +/* show irq information */ +static ssize_t goodix_ts_irq_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + struct irq_desc *desc; + size_t offset = 0; + int r; + + r = snprintf(&buf[offset], PAGE_SIZE, "irq:%u\n", + core_data->irq); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "state:%s\n", + atomic_read(&core_data->irq_enabled) ? + "enabled" : "disabled"); + if (r < 0) + return -EINVAL; + + desc = irq_to_desc(core_data->irq); + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "disable-depth:%d\n", + desc->depth); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, "trigger-count:%zu\n", + core_data->irq_trig_cnt); + if (r < 0) + return -EINVAL; + + offset += r; + r = snprintf(&buf[offset], PAGE_SIZE - offset, + "echo 0/1 > irq_info to disable/enable irq"); + if (r < 0) + return -EINVAL; + + offset += r; + return offset; +} + +/* enable/disable irq */ +static ssize_t goodix_ts_irq_info_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + int en; + + if (kstrtoint(buf, 10, &en)) + return -EINVAL; + + goodix_ts_irq_enable(core_data, en); + return count; +} + +static DEVICE_ATTR(extmod_info, 0444, goodix_ts_extmod_show, NULL); +static DEVICE_ATTR(driver_info, 0444, goodix_ts_driver_info_show, NULL); +static DEVICE_ATTR(chip_info, 0444, goodix_ts_chip_info_show, NULL); +static DEVICE_ATTR(config_data, 0444, goodix_ts_config_data_show, NULL); +static DEVICE_ATTR(reset, 0200, NULL, goodix_ts_reset_store); +static DEVICE_ATTR(irq_info, 0644, + goodix_ts_irq_info_show, goodix_ts_irq_info_store); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_extmod_info.attr, + &dev_attr_driver_info.attr, + &dev_attr_chip_info.attr, + &dev_attr_config_data.attr, + &dev_attr_reset.attr, + &dev_attr_irq_info.attr, + NULL, +}; + +static const struct attribute_group sysfs_group = { + .attrs = sysfs_attrs, +}; + +static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data) +{ + return sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group); +} + +static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data) +{ + sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group); +} + +/* event notifier */ +static BLOCKING_NOTIFIER_HEAD(ts_notifier_list); +/** + * goodix_ts_register_client - register a client notifier + * @nb: notifier block to callback on events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&ts_notifier_list, nb); +} +EXPORT_SYMBOL(goodix_ts_register_notifier); + +/** + * goodix_ts_unregister_client - unregister a client notifier + * @nb: notifier block to callback on events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ts_notifier_list, nb); +} +EXPORT_SYMBOL(goodix_ts_unregister_notifier); + +/** + * goodix_ts_blocking_notify - notify clients of certain events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v) +{ + return blocking_notifier_call_chain(&ts_notifier_list, + (unsigned long)evt, v); +} +EXPORT_SYMBOL_GPL(goodix_ts_blocking_notify); + +/** + * goodix_ts_input_report - report touch event to input subsystem + * + * @dev: input device pointer + * @touch_data: touch data pointer + * return: 0 ok, <0 failed + */ +static int goodix_ts_input_report(struct input_dev *dev, + struct goodix_touch_data *touch_data) +{ + struct goodix_ts_coords *coords = &touch_data->coords[0]; + struct goodix_ts_core *core_data = input_get_drvdata(dev); + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + unsigned int touch_num = touch_data->touch_num, x, y; + static u16 pre_fin; + int i, id; + + /* report touch-key */ + if (unlikely(touch_data->key_value)) { + for (i = 0; i < ts_bdata->panel_max_key; i++) { + input_report_key(dev, ts_bdata->panel_key_map[i], + touch_data->key_value & (1 << i)); + } + } + + /* first touch down and last touch up condition */ + if (touch_num != 0 && pre_fin == 0x0000) { + /* first touch down event */ + input_report_key(dev, BTN_TOUCH, 1); + input_report_key(dev, BTN_TOOL_FINGER, 1); + } else if (touch_num == 0 && pre_fin != 0x0000) { + /* no finger exist */ + input_report_key(dev, BTN_TOUCH, 0); + input_report_key(dev, BTN_TOOL_FINGER, 0); + } else if (touch_num == 0 && pre_fin == 0x0000) { + return 0; + } + + /* report abs */ + id = coords->id; + for (i = 0; i < ts_bdata->panel_max_id; i++) { + if (touch_num && i == id) { + /* this is a valid touch down event */ +#ifdef INPUT_TYPE_B_PROTOCOL + input_mt_slot(dev, id); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); +#else + input_report_abs(dev, ABS_MT_TRACKING_ID, id); +#endif + if (unlikely(ts_bdata->swap_axis)) { + x = coords->y; + y = coords->x; + } else { + x = coords->x; + y = coords->y; + } + input_report_abs(dev, ABS_MT_POSITION_X, x); + input_report_abs(dev, ABS_MT_POSITION_Y, y); + input_report_abs(dev, ABS_MT_TOUCH_MAJOR, coords->w); + pre_fin |= 1 << i; + id = (++coords)->id; +#ifndef INPUT_TYPE_B_PROTOCOL + input_mt_sync(dev); +#endif + } else { + if (pre_fin & (1 << i)) {/* release touch */ +#ifdef INPUT_TYPE_B_PROTOCOL + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, + false); +#endif + pre_fin &= ~(1 << i); + } + } + } + +#ifndef INPUT_TYPE_B_PROTOCOL + if (!pre_fin) + input_mt_sync(dev); +#endif + input_sync(dev); + return 0; +} + +/** + * goodix_ts_threadirq_func - Bottom half of interrupt + * This functions is excuted in thread context, + * sleep in this function is permit. + * + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static irqreturn_t goodix_ts_threadirq_func(int irq, void *data) +{ + struct goodix_ts_core *core_data = data; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + struct goodix_ext_module *ext_module; + struct goodix_ts_event *ts_event = &core_data->ts_event; + int r; + + core_data->irq_trig_cnt++; + /* inform external module */ + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (!ext_module->funcs->irq_event) + continue; + r = ext_module->funcs->irq_event(core_data, ext_module); + if (r == EVT_CANCEL_IRQEVT) + return IRQ_HANDLED; + } + + /* read touch data from touch device */ + r = ts_dev->hw_ops->event_handler(ts_dev, ts_event); + if (likely(r >= 0)) { + if (ts_event->event_type == EVENT_TOUCH) { + /* report touch */ + goodix_ts_input_report(core_data->input_dev, + &ts_event->event_data.touch_data); + } + } + + return IRQ_HANDLED; +} + +/** + * goodix_ts_init_irq - Requset interrupt line from system + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_irq_setup(struct goodix_ts_core *core_data) +{ + const struct goodix_ts_board_data *ts_bdata = + board_data(core_data); + const struct device *dev = &core_data->pdev->dev; + int r; + + /* if ts_bdata->irq is invalid get it from irq-gpio */ + if (ts_bdata->irq <= 0) + core_data->irq = gpiod_to_irq(ts_bdata->irq_gpiod); + else + core_data->irq = ts_bdata->irq; + + dev_info(dev, "IRQ:%u,flags:%d\n", core_data->irq, (int)ts_bdata->irq_flags); + r = devm_request_threaded_irq(&core_data->pdev->dev, + core_data->irq, NULL, + goodix_ts_threadirq_func, + ts_bdata->irq_flags | IRQF_ONESHOT, + GOODIX_CORE_DRIVER_NAME, + core_data); + if (r < 0) + dev_err(dev, "Failed to requeset threaded irq:%d\n", r); + else + atomic_set(&core_data->irq_enabled, 1); + + return r; +} + +/** + * goodix_ts_irq_enable - Enable/Disable a irq + * @core_data: pointer to touch core data + * enable: enable or disable irq + * return: 0 ok, <0 failed + */ +int goodix_ts_irq_enable(struct goodix_ts_core *core_data, + bool enable) +{ + const struct device *dev = &core_data->pdev->dev; + + if (enable) { + if (!atomic_cmpxchg(&core_data->irq_enabled, 0, 1)) { + enable_irq(core_data->irq); + dev_dbg(dev, "Irq enabled\n"); + } + } else { + if (atomic_cmpxchg(&core_data->irq_enabled, 1, 0)) { + disable_irq(core_data->irq); + dev_dbg(dev, "Irq disabled\n"); + } + } + + return 0; +} +EXPORT_SYMBOL(goodix_ts_irq_enable); +/** + * goodix_ts_power_init - Get regulator for touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_power_init(struct goodix_ts_core *core_data) +{ + struct device *dev = NULL; + struct goodix_ts_board_data *ts_bdata; + + /* dev:i2c client device or spi slave device*/ + dev = core_data->ts_dev->dev; + ts_bdata = board_data(core_data); + + if (ts_bdata->avdd_name) { + core_data->avdd = devm_regulator_get(dev, ts_bdata->avdd_name); + if (IS_ERR_OR_NULL(core_data->avdd)) { + core_data->avdd = NULL; + return -ENOENT; + } + } else { + return -EINVAL; + } + + return 0; +} + +/** + * goodix_ts_power_on - Turn on power to the touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_power_on(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = + board_data(core_data); + const struct device *dev = &core_data->pdev->dev; + int r; + + dev_info(dev, "Device power on\n"); + if (core_data->power_on) + return 0; + + if (core_data->avdd) { + r = regulator_enable(core_data->avdd); + if (!r) { + if (ts_bdata->power_on_delay_us) + usleep_range(ts_bdata->power_on_delay_us, + ts_bdata->power_on_delay_us); + } else { + dev_err(dev, "Failed to enable analog power:%d\n", r); + return r; + } + } + + core_data->power_on = 1; + return 0; +} + +/** + * goodix_ts_power_off - Turn off power to the touch device + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_power_off(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = + board_data(core_data); + const struct device *dev = &core_data->pdev->dev; + int r; + + dev_info(dev, "Device power off\n"); + if (!core_data->power_on) + return 0; + + if (core_data->avdd) { + r = regulator_disable(core_data->avdd); + if (!r) { + if (ts_bdata->power_off_delay_us) + usleep_range(ts_bdata->power_off_delay_us, + ts_bdata->power_off_delay_us); + } else { + dev_err(dev, "Failed to disable analog power:%d\n", r); + return r; + } + } + + core_data->power_on = 0; + return 0; +} + +/** + * goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten + * reset_gpio and irq_gpio number are obtained from goodix_ts_device + * which created in hardware layer driver. e.g.goodix_xx_i2c.c + * A goodix_ts_device should set those two fileds to right value + * before registed to touch core driver. + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static void goodix_ts_gpio_setup(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct goodix_ts_device *ts_dev = core_data->ts_dev; + const struct device *dev = &core_data->pdev->dev; + + ts_bdata->reset_gpiod = devm_gpiod_get_optional(ts_dev->dev, + "reset", GPIOD_OUT_LOW); + if (!ts_bdata->reset_gpiod) + dev_info(dev, "No reset gpio found\n"); + + ts_bdata->irq_gpiod = devm_gpiod_get_optional(ts_dev->dev, + "irq", GPIOD_IN); + if (!ts_bdata->irq_gpiod) + dev_info(dev, "No irq gpio found\n"); + +} + +/** + * goodix_input_set_params - set input parameters + */ +static void goodix_ts_set_input_params(struct input_dev *input_dev, + struct goodix_ts_board_data *ts_bdata) +{ + int i; + + if (ts_bdata->swap_axis) + swap(ts_bdata->panel_max_x, ts_bdata->panel_max_y); + + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, + 0, ts_bdata->panel_max_id, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, ts_bdata->panel_max_x, 0, 0); + + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, ts_bdata->panel_max_y, 0, 0); + + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, ts_bdata->panel_max_w, 0, 0); + if (ts_bdata->panel_max_key) { + for (i = 0; i < ts_bdata->panel_max_key; i++) + input_set_capability(input_dev, EV_KEY, + ts_bdata->panel_key_map[i]); + } +} + +/** + * goodix_ts_input_dev_config - Requset and config a input device + * then register it to input sybsystem. + * NOTE that some hardware layer may provide a input device + * (ts_dev->input_dev not NULL). + * @core_data: pointer to touch core data + * return: 0 ok, <0 failed + */ +static int goodix_ts_input_dev_config(struct goodix_ts_core *core_data) +{ + struct goodix_ts_board_data *ts_bdata = board_data(core_data); + struct device *dev = &core_data->pdev->dev; + struct input_dev *input_dev = NULL; + int r; + + input_dev = devm_input_allocate_device(dev); + if (!input_dev) { + dev_err(dev, "Failed to allocated input device\n"); + return -ENOMEM; + } + + core_data->input_dev = input_dev; + input_set_drvdata(input_dev, core_data); + + input_dev->name = GOODIX_CORE_DRIVER_NAME; + input_dev->phys = GOOIDX_INPUT_PHYS; + input_dev->id.product = 0xDEAD; + input_dev->id.vendor = 0xBEEF; + input_dev->id.version = 10427; + + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, input_dev->keybit); + +#ifdef INPUT_PROP_DIRECT + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); +#endif + + /* set input parameters */ + goodix_ts_set_input_params(input_dev, ts_bdata); + +#ifdef INPUT_TYPE_B_PROTOCOL + input_mt_init_slots(input_dev, ts_bdata->panel_max_id, + INPUT_MT_DIRECT); +#endif + + input_set_capability(input_dev, EV_KEY, KEY_POWER); + r = input_register_device(input_dev); + if (r < 0) { + dev_err(dev, "Unable to register input device\n"); + return r; + } + + return 0; +} + +/** + * goodix_ts_hw_init - Hardware initialize + * poweron - hardware reset - sendconfig + * @core_data: pointer to touch core data + * return: 0 intilize ok, <0 failed + */ +static int goodix_ts_hw_init(struct goodix_ts_core *core_data) +{ + const struct goodix_ts_hw_ops *hw_ops = + ts_hw_ops(core_data); + int r; + + r = goodix_ts_power_on(core_data); + if (r < 0) + goto exit; + + /* reset touch device */ + if (hw_ops->reset) + hw_ops->reset(core_data->ts_dev); + + /* init */ + if (hw_ops->init) { + r = hw_ops->init(core_data->ts_dev); + if (r < 0) { + core_data->hw_err = true; + goto exit; + } + } + +exit: + /* if bus communication error occurred then exit driver binding, other + * errors will be ignored + */ + if (r != -EBUS) + r = 0; + return r; +} + +/** + * goodix_ts_esd_work - check hardware status and recovery + * the hardware if needed. + */ +static void goodix_ts_esd_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct goodix_ts_esd *ts_esd = container_of(dwork, + struct goodix_ts_esd, esd_work); + struct goodix_ts_core *core = container_of(ts_esd, + struct goodix_ts_core, ts_esd); + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(core); + int r = 0; + + if (ts_esd->esd_on == false) + return; + + if (hw_ops->check_hw) + r = hw_ops->check_hw(core->ts_dev); + if (r < 0) { + goodix_ts_power_off(core); + goodix_ts_power_on(core); + if (hw_ops->reset) + hw_ops->reset(core->ts_dev); + } + + mutex_lock(&ts_esd->esd_mutex); + if (ts_esd->esd_on) + schedule_delayed_work(&ts_esd->esd_work, 2 * HZ); + mutex_unlock(&ts_esd->esd_mutex); +} + +/** + * goodix_ts_esd_on - turn on esd protection + */ +static void goodix_ts_esd_on(struct goodix_ts_core *core_data) +{ + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + const struct device *dev = &core_data->pdev->dev; + + mutex_lock(&ts_esd->esd_mutex); + if (ts_esd->esd_on == false) { + ts_esd->esd_on = true; + schedule_delayed_work(&ts_esd->esd_work, 2 * HZ); + mutex_unlock(&ts_esd->esd_mutex); + dev_info(dev, "Esd on\n"); + return; + } + mutex_unlock(&ts_esd->esd_mutex); +} + +/** + * goodix_ts_esd_off - turn off esd protection + */ +static void goodix_ts_esd_off(struct goodix_ts_core *core_data) +{ + struct goodix_ts_esd *ts_esd = &core_data->ts_esd; + const struct device *dev = &core_data->pdev->dev; + + mutex_lock(&ts_esd->esd_mutex); + if (ts_esd->esd_on == true) { + ts_esd->esd_on = false; + cancel_delayed_work(&ts_esd->esd_work); + mutex_unlock(&ts_esd->esd_mutex); + dev_info(dev, "Esd off\n"); + return; + } + mutex_unlock(&ts_esd->esd_mutex); +} + +/** + * goodix_esd_notifier_callback - notification callback + * under certain condition, we need to turn off/on the esd + * protector, we use kernel notify call chain to achieve this. + * + * for example: before firmware update we need to turn off the + * esd protector and after firmware update finished, we should + * turn on the esd protector. + */ +static int goodix_esd_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct goodix_ts_esd *ts_esd = container_of(nb, + struct goodix_ts_esd, esd_notifier); + + switch (action) { + case NOTIFY_FWUPDATE_START: + case NOTIFY_SUSPEND: + goodix_ts_esd_off(ts_esd->ts_core); + break; + case NOTIFY_FWUPDATE_END: + case NOTIFY_RESUME: + goodix_ts_esd_on(ts_esd->ts_core); + break; + } + + return 0; +} + +/** + * goodix_ts_esd_init - initialize esd protection + */ +static int goodix_ts_esd_init(struct goodix_ts_core *core) +{ + struct goodix_ts_esd *ts_esd = &core->ts_esd; + + INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work); + mutex_init(&ts_esd->esd_mutex); + ts_esd->ts_core = core; + ts_esd->esd_on = false; + ts_esd->esd_notifier.notifier_call = goodix_esd_notifier_callback; + goodix_ts_register_notifier(&ts_esd->esd_notifier); + + if (core->ts_dev->board_data->esd_default_on == true + && core->ts_dev->hw_ops->check_hw) + goodix_ts_esd_on(core); + return 0; +} + +/** + * goodix_ts_suspend - Touchscreen suspend function + */ +static int goodix_ts_suspend(struct goodix_ts_core *core_data) +{ + struct goodix_ext_module *ext_module; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + const struct device *dev = &core_data->pdev->dev; + int r; + + dev_dbg(dev, "Suspend start\n"); + /* + * notify suspend event, inform the esd protector + * and charger detector to turn off the work + */ + goodix_ts_blocking_notify(NOTIFY_SUSPEND, NULL); + + /* inform external module */ + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (!ext_module->funcs->before_suspend) + continue; + + r = ext_module->funcs->before_suspend(core_data, ext_module); + if (r == EVT_CANCEL_SUSPEND) { + mutex_unlock(&goodix_modules.mutex); + dev_dbg(dev, "Canceled by module:%s\n", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + /* disable irq */ + goodix_ts_irq_enable(core_data, false); + + /* let touch ic work in sleep mode */ + if (ts_dev && ts_dev->hw_ops->suspend) + ts_dev->hw_ops->suspend(ts_dev); + atomic_set(&core_data->suspended, 1); + + /* inform exteranl modules */ + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (!ext_module->funcs->after_suspend) + continue; + + r = ext_module->funcs->after_suspend(core_data, ext_module); + if (r == EVT_CANCEL_SUSPEND) { + mutex_unlock(&goodix_modules.mutex); + dev_dbg(dev, "Canceled by module:%s\n", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + +out: + /* release all the touch IDs */ + core_data->ts_event.event_data.touch_data.touch_num = 0; + goodix_ts_input_report(core_data->input_dev, + &core_data->ts_event.event_data.touch_data); + dev_dbg(dev, "Suspend end\n"); + return 0; +} + +/** + * goodix_ts_resume - Touchscreen resume function + * Called by PM/FB/EARLYSUSPEN module to wakeup device + */ +static int goodix_ts_resume(struct goodix_ts_core *core_data) +{ + struct goodix_ext_module *ext_module; + struct goodix_ts_device *ts_dev = core_data->ts_dev; + const struct device *dev = &core_data->pdev->dev; + int r; + + dev_dbg(dev, "Resume start\n"); + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (!ext_module->funcs->before_resume) + continue; + + r = ext_module->funcs->before_resume(core_data, ext_module); + if (r == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + dev_dbg(dev, "Canceled by module:%s\n", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + + atomic_set(&core_data->suspended, 0); + /* resume device */ + if (ts_dev && ts_dev->hw_ops->resume) + ts_dev->hw_ops->resume(ts_dev); + + goodix_ts_irq_enable(core_data, true); + + mutex_lock(&goodix_modules.mutex); + if (!list_empty(&goodix_modules.head)) { + list_for_each_entry(ext_module, &goodix_modules.head, list) { + if (!ext_module->funcs->after_resume) + continue; + + r = ext_module->funcs->after_resume(core_data, ext_module); + if (r == EVT_CANCEL_RESUME) { + mutex_unlock(&goodix_modules.mutex); + dev_dbg(dev, "Canceled by module:%s\n", + ext_module->name); + goto out; + } + } + } + mutex_unlock(&goodix_modules.mutex); + +out: + /* + * notify resume event, inform the esd protector + * and charger detector to turn on the work + */ + goodix_ts_blocking_notify(NOTIFY_RESUME, NULL); + dev_dbg(dev, "Resume end\n"); + return 0; +} + +/** + * goodix_ts_pm_suspend - PM suspend function + * Called by kernel during system suspend phrase + */ +static int __maybe_unused goodix_ts_pm_suspend(struct device *dev) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + + return goodix_ts_suspend(core_data); +} +/** + * goodix_ts_pm_resume - PM resume function + * Called by kernel during system wakeup + */ +static int __maybe_unused goodix_ts_pm_resume(struct device *dev) +{ + struct goodix_ts_core *core_data = + dev_get_drvdata(dev); + + return goodix_ts_resume(core_data); +} + +/** + * goodix_generic_noti_callback - generic notifier callback + * for goodix touch notification event. + */ +static int goodix_generic_noti_callback(struct notifier_block *self, + unsigned long action, void *data) +{ + struct goodix_ts_core *ts_core = container_of(self, + struct goodix_ts_core, ts_notifier); + const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(ts_core); + int r; + + switch (action) { + case NOTIFY_FWUPDATE_END: + if (ts_core->hw_err && hw_ops->init) { + /* Firmware has been updated, we need to reinit + * the chip, read the sensor ID and send the + * correct config data based on sensor ID. + * The input parameters also needs to be updated. + */ + r = hw_ops->init(ts_core->ts_dev); + if (r < 0) + goto exit; + + goodix_ts_set_input_params(ts_core->input_dev, + ts_core->ts_dev->board_data); + ts_core->hw_err = false; + } + break; + } + +exit: + return 0; +} + +/** + * goodix_ts_probe - called by kernel when a Goodix touch + * platform driver is added. + */ +static int goodix_ts_probe(struct platform_device *pdev) +{ + struct goodix_ts_core *core_data = NULL; + struct goodix_ts_device *ts_device; + int r; + + ts_device = pdev->dev.platform_data; + if (!ts_device || !ts_device->hw_ops || !ts_device->board_data) { + dev_err(&pdev->dev, "Invalid touch device\n"); + return -ENODEV; + } + + core_data = devm_kzalloc(&pdev->dev, sizeof(struct goodix_ts_core), + GFP_KERNEL); + if (!core_data) + return -ENOMEM; + + /* touch core layer is a platform driver */ + core_data->pdev = pdev; + core_data->ts_dev = ts_device; + platform_set_drvdata(pdev, core_data); + + r = goodix_ts_power_init(core_data); + if (r < 0) + dev_err(&pdev->dev, "Failed power init\n"); + + /* get GPIO resource if have */ + goodix_ts_gpio_setup(core_data); + + /* initialize firmware */ + r = goodix_ts_hw_init(core_data); + if (r < 0) + goto out; + + /* alloc/config/register input device */ + r = goodix_ts_input_dev_config(core_data); + if (r < 0) + goto out; + + /* request irq line */ + r = goodix_ts_irq_setup(core_data); + if (r < 0) + goto out; + + /* inform the external module manager that + * touch core layer is ready now + */ + goodix_modules.core_data = core_data; + complete_all(&goodix_modules.core_comp); + + /* create sysfs files */ + goodix_ts_sysfs_init(core_data); + + /* esd protector */ + goodix_ts_esd_init(core_data); + + /* generic notifier callback */ + core_data->ts_notifier.notifier_call = goodix_generic_noti_callback; + goodix_ts_register_notifier(&core_data->ts_notifier); + + return 0; + /* we use resource managed api(devm_), no need to free resource */ +out: + goodix_modules.core_exit = true; + complete_all(&goodix_modules.core_comp); + dev_err(&pdev->dev, "Core layer probe failed"); + return r; +} + +static int goodix_ts_remove(struct platform_device *pdev) +{ + struct goodix_ts_core *core_data = + platform_get_drvdata(pdev); + + goodix_ts_power_off(core_data); + goodix_ts_sysfs_exit(core_data); + return 0; +} + +static SIMPLE_DEV_PM_OPS(dev_pm_ops, goodix_ts_pm_suspend, goodix_ts_pm_resume); + +static const struct platform_device_id ts_core_ids[] = { + {.name = GOODIX_CORE_DRIVER_NAME}, + {} +}; +MODULE_DEVICE_TABLE(platform, ts_core_ids); + +static struct platform_driver goodix_ts_driver = { + .driver = { + .name = GOODIX_CORE_DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &dev_pm_ops, + }, + .probe = goodix_ts_probe, + .remove = goodix_ts_remove, + .id_table = ts_core_ids, +}; + +static int __init goodix_ts_core_init(void) +{ + if (!goodix_modules.initilized) { + goodix_modules.initilized = true; + INIT_LIST_HEAD(&goodix_modules.head); + mutex_init(&goodix_modules.mutex); + init_completion(&goodix_modules.core_comp); + } + + return platform_driver_register(&goodix_ts_driver); +} + + +static void __exit goodix_ts_core_exit(void) +{ + platform_driver_unregister(&goodix_ts_driver); +} + +module_init(goodix_ts_core_init); +module_exit(goodix_ts_core_exit); + +MODULE_DESCRIPTION("Goodix Touchscreen Core Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h new file mode 100755 index 0000000..400746e --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_core.h @@ -0,0 +1,553 @@ +/* + * Goodix GTx5 Touchscreen Driver + * Core layer of touchdriver architecture. + * + * Copyright (C) 2015 - 2016 Goodix, Inc. + * Authors: Wang Yafei <wangyafei@goodix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ +#ifndef _GOODIX_TS_CORE_H_ +#define _GOODIX_TS_CORE_H_ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/kthread.h> +#include <linux/version.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <asm/unaligned.h> +#ifdef CONFIG_OF +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#endif + +#include <linux/gpio/consumer.h> + +/* macros definition */ +#define GOODIX_CORE_DRIVER_NAME "goodix_ts" +#define GOODIX_DRIVER_VERSION "v0.8" +#define GOODIX_BUS_RETRY_TIMES 3 +#define GOODIX_MAX_TOUCH 10 +#define GOODIX_MAX_KEY 3 +#define GOODIX_CFG_MAX_SIZE 1024 + +/* + * struct goodix_ts_board_data - board data + * @avdd_name: name of analoy regulator + * @reset_gpio: reset gpio number + * @irq_gpio: interrupt gpio number + * @irq_flag: irq trigger type + * @power_on_delay_us: power on delay time (us) + * @power_off_delay_us: power off delay time (us) + * @swap_axis: whether swaw x y axis + * @panel_max_id: max supported fingers + * @panel_max_x/y/w/p: resolution and size + * @panel_max_key: max supported keys + * @pannel_key_map: key map + * @fw_name: name of the firmware image + */ +struct goodix_ts_board_data { + const char *avdd_name; + struct gpio_desc *reset_gpiod; + struct gpio_desc *irq_gpiod; + int irq; + unsigned int irq_flags; + + unsigned int power_on_delay_us; + unsigned int power_off_delay_us; + + unsigned int swap_axis; + unsigned int panel_max_id; /*max touch id*/ + unsigned int panel_max_x; + unsigned int panel_max_y; + unsigned int panel_max_w; /*major and minor*/ + unsigned int panel_max_key; + unsigned int panel_key_map[GOODIX_MAX_KEY]; + + const char *fw_name; + bool esd_default_on; +}; + +/* + * struct goodix_ts_config - chip config data + * @initialized: whether intialized + * @name: name of this config + * @lock: mutex + * @reg_base: register base of config data + * @length: bytes of the config + * @delay: delay time after sending config + * @data: config data buffer + */ +struct goodix_ts_config { + bool initialized; + char name[24]; + struct mutex lock; + unsigned int reg_base; + unsigned int length; + unsigned int delay; /*ms*/ + unsigned char data[GOODIX_CFG_MAX_SIZE]; +}; + +/* + * struct goodix_ts_cmd - command package + * @initialized: whether initialized + * @cmd_reg: command register + * @length: command length in bytes + * @cmds: command data + */ +#pragma pack(4) +struct goodix_ts_cmd { + u32 initialized; + u32 cmd_reg; + u32 length; + u8 cmds[3]; +}; +#pragma pack() + +/* interrupt event type */ +enum ts_event_type { + EVENT_INVALID, + EVENT_TOUCH, + EVENT_REQUEST, +}; + +/* requset event type */ +enum ts_request_type { + REQUEST_INVALID, + REQUEST_CONFIG, + REQUEST_BAKREF, + REQUEST_RESET, + REQUEST_MAINCLK, +}; + +/* notifier event */ + +enum ts_notify_event { + NOTIFY_FWUPDATE_START, + NOTIFY_FWUPDATE_END, + NOTIFY_SUSPEND, + NOTIFY_RESUME, +}; + +/* coordinate package */ +struct goodix_ts_coords { + int id; + unsigned int x, y, w, p; +}; + +/* touch event data */ +struct goodix_touch_data { + /* finger */ + int touch_num; + struct goodix_ts_coords coords[GOODIX_MAX_TOUCH]; + /* key */ + u16 key_value; +}; + +/* request event data */ +struct goodix_request_data { + enum ts_request_type request_type; +}; + +/* + * struct goodix_ts_event - touch event struct + * @event_type: touch event type, touch data or + * request event + * @event_data: event data + */ +struct goodix_ts_event { + enum ts_event_type event_type; + union { + struct goodix_touch_data touch_data; + struct goodix_request_data request_data; + } event_data; +}; + +/* + * struct goodix_ts_version - firmware version + * @valid: whether these infomation is valid + * @pid: product id string + * @vid: firmware version code + * @cid: customer id code + * @sensor_id: sendor id + */ +struct goodix_ts_version { + bool valid; + char pid[5]; + u16 vid; + u8 cid; + u8 sensor_id; +}; + +/* + * struct goodix_ts_device - ts device data + * @name: device name + * @version: reserved + * @bus_type: i2c or spi + * @board_data: board data obtained from dts + * @normal_cfg: normal config data + * @highsense_cfg: high sense config data + * @hw_ops: hardware operations + * @chip_version: firmware version infomation + * @sleep_cmd: sleep commang + * @gesture_cmd: gesture command + * @dev: device pointer,may be a i2c or spi device + * @of_node: device node + */ +struct goodix_ts_device { + char *name; + int version; + int bus_type; + + struct goodix_ts_board_data *board_data; + struct goodix_ts_config *normal_cfg; + struct goodix_ts_config *highsense_cfg; + const struct goodix_ts_hw_ops *hw_ops; + + struct goodix_ts_version chip_version; + struct goodix_ts_cmd sleep_cmd; + struct goodix_ts_cmd gesture_cmd; + + struct device *dev; +}; + +/* + * struct goodix_ts_hw_ops - hardware opeartions + * @init: hardware initialization + * @reset: hardware reset + * @read: read data from touch device + * @write: write data to touch device + * @send_cmd: send command to touch device + * @send_config: send configuration data + * @read_version: read firmware version + * @event_handler: touch event handler + * @suspend: put touch device into low power mode + * @resume: put touch device into working mode + */ +struct goodix_ts_hw_ops { + + int (*init)(struct goodix_ts_device *dev); + void (*reset)(struct goodix_ts_device *dev); + int (*read)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*write)(struct goodix_ts_device *dev, unsigned int addr, + unsigned char *data, unsigned int len); + int (*send_cmd)(struct goodix_ts_device *dev, + struct goodix_ts_cmd *cmd); + int (*send_config)(struct goodix_ts_device *dev, + struct goodix_ts_config *config); + int (*read_version)(struct goodix_ts_device *dev, + struct goodix_ts_version *version); + int (*event_handler)(struct goodix_ts_device *dev, + struct goodix_ts_event *ts_event); + int (*check_hw)(struct goodix_ts_device *dev); + int (*suspend)(struct goodix_ts_device *dev); + int (*resume)(struct goodix_ts_device *dev); +}; + +/* + * struct goodix_ts_esd - esd protector structure + * @esd_work: esd delayed work + * @esd_on: true - turn on esd protection, false - turn + * off esd protection + * @esd_mutex: protect @esd_on flag + */ +struct goodix_ts_esd { + struct delayed_work esd_work; + struct mutex esd_mutex; + struct notifier_block esd_notifier; + struct goodix_ts_core *ts_core; + bool esd_on; +}; + +/* + * struct godix_ts_core - core layer data struct + * @pdev: core layer platform device + * @ts_dev: hardware layer touch device + * @input_dev: input device + * @avdd: analog regulator + * @pinctrl: pinctrl handler + * @pin_sta_active: active/normal pin state + * @pin_sta_suspend: suspend/sleep pin state + * @ts_event: touch event data struct + * @power_on: power on/off flag + * @irq: irq number + * @irq_enabled: irq enabled/disabled flag + * @suspended: suspend/resume flag + * @hw_err: indicate that hw_ops->init() failed + * @ts_notifier: generic notifier + * @ts_esd: esd protector structure + * @fb_notifier: framebuffer notifier + * @early_suspend: early suspend + */ +struct goodix_ts_core { + struct platform_device *pdev; + struct goodix_ts_device *ts_dev; + struct input_dev *input_dev; + + struct regulator *avdd; + struct goodix_ts_event ts_event; + int power_on; + int irq; + size_t irq_trig_cnt; + + atomic_t irq_enabled; + atomic_t suspended; + bool hw_err; + + struct notifier_block ts_notifier; + struct goodix_ts_esd ts_esd; + +#ifdef CONFIG_FB + struct notifier_block fb_notifier; +#endif +}; + +/* external module structures */ +enum goodix_ext_priority { + EXTMOD_PRIO_RESERVED = 0, + EXTMOD_PRIO_FWUPDATE, + EXTMOD_PRIO_GESTURE, + EXTMOD_PRIO_HOTKNOT, + EXTMOD_PRIO_DBGTOOL, + EXTMOD_PRIO_DEFAULT, +}; + +struct goodix_ext_module; +/* external module's operations callback */ +struct goodix_ext_module_funcs { + int (*init)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*exit)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + + int (*before_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_reset)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + + int (*before_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_suspend)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + + int (*before_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + int (*after_resume)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); + + int (*irq_event)(struct goodix_ts_core *core_data, + struct goodix_ext_module *module); +}; + +/* + * struct goodix_ext_module - external module struct + * @list: list used to link into modules manager + * @name: name of external module + * @priority: module priority vlaue, zero is invalid + * @funcs: operations callback + * @priv_data: private data region + * @kobj: kobject + * @work: used to queue one work to do registration + */ +struct goodix_ext_module { + struct list_head list; + char *name; + enum goodix_ext_priority priority; + const struct goodix_ext_module_funcs *funcs; + void *priv_data; + struct kobject kobj; + struct work_struct work; +}; + +/* + * struct goodix_ext_attribute - exteranl attribute struct + * @attr: attribute + * @show: show interface of external attribute + * @store: store interface of external attribute + */ +struct goodix_ext_attribute { + struct attribute attr; + ssize_t (*show)(struct goodix_ext_module *, char *); + ssize_t (*store)(struct goodix_ext_module *, const char *, size_t); +}; + +/* external attrs helper macro */ +#define __EXTMOD_ATTR(_name, _mode, _show, _store) { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +/* external attrs helper macro, used to define external attrs */ +#define DEFINE_EXTMOD_ATTR(_name, _mode, _show, _store) \ +static struct goodix_ext_attribute ext_attr_##_name = \ + __EXTMOD_ATTR(_name, _mode, _show, _store) + +/* + * get board data pointer + */ +static inline struct goodix_ts_board_data *board_data( + struct goodix_ts_core *core) +{ + return core->ts_dev->board_data; +} + +/* + * get touch hardware operations pointer + */ +static inline const struct goodix_ts_hw_ops *ts_hw_ops( + struct goodix_ts_core *core) +{ + return core->ts_dev->hw_ops; +} + +/* + * checksum helper functions + * checksum can be u8/le16/be16/le32/be32 format + * NOTE: the caller shoule be responsible for the + * legality of @data and @size parameters, so be + * careful when call these functions. + */ +static inline u8 checksum_u8(u8 *data, u32 size) +{ + u8 checksum = 0; + u32 i; + + for (i = 0; i < size; i++) + checksum += data[i]; + return checksum; +} + +static inline u16 checksum_le16(u8 *data, u32 size) +{ + u16 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 2) + checksum += le16_to_cpup((__le16 *)(data + i)); + return checksum; +} + +static inline u16 checksum_be16(u8 *data, u32 size) +{ + u16 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 2) + checksum += be16_to_cpup((__be16 *)(data + i)); + return checksum; +} + +static inline u32 checksum_le32(u8 *data, u32 size) +{ + u32 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 4) + checksum += le32_to_cpup((__le32 *)(data + i)); + return checksum; +} + +static inline u32 checksum_be32(u8 *data, u32 size) +{ + u32 checksum = 0; + u32 i; + + for (i = 0; i < size; i += 4) + checksum += be32_to_cpup((__be32 *)(data + i)); + return checksum; +} + +/* + * define event action + * EVT_xxx macros are used in opeartions callback + * defined in @goodix_ext_module_funcs to control + * the behaviors of event such as suspend/resume/ + * irq_event. + * + * generally there are two types of behaviors: + * 1. you want the flow of this event be canceled, + * in this condition, you should return EVT_CANCEL_XXX + * in the operations callback. + * e.g. the firmware update module is updating + * the firmware, you want to cancel suspend flow, + * so you need to return EVT_CANCEL_SUSPEND in + * suspend callback function. + * 2. you want the flow of this event continue, in + * this condition, you should return EVT_HANDLED in + * the callback function. + */ +#define EVT_HANDLED 0 +#define EVT_CONTINUE 0 +#define EVT_CANCEL 1 +#define EVT_CANCEL_IRQEVT 1 +#define EVT_CANCEL_SUSPEND 1 +#define EVT_CANCEL_RESUME 1 +#define EVT_CANCEL_RESET 1 + +/* + * errno define + * Note: + * 1. bus read/write functions defined in hardware + * layer code(e.g. goodix_xxx_i2c.c) *must* return + * -EBUS if failed to transfer data on bus. + */ +#define EBUS 1000 +#define ETIMEOUT 1001 +#define ECHKSUM 1002 +#define EMEMCMP 1003 + +/** + * goodix_register_ext_module - interface for external module + * to register into touch core modules structure + * + * @module: pointer to external module to be register + * return: 0 ok, <0 failed + */ +int goodix_register_ext_module(struct goodix_ext_module *module); + +/** + * goodix_unregister_ext_module - interface for external module + * to unregister external modules + * + * @module: pointer to external module + * return: 0 ok, <0 failed + */ +int goodix_unregister_ext_module(struct goodix_ext_module *module); + +/** + * goodix_ts_irq_enable - Enable/Disable a irq + + * @core_data: pointer to touch core data + * enable: enable or disable irq + * return: 0 ok, <0 failed + */ +int goodix_ts_irq_enable(struct goodix_ts_core *core_data, bool enable); + +struct kobj_type *goodix_get_default_ktype(void); + +/** + * goodix_ts_blocking_notify - notify clients of certain events + * see enum ts_notify_event in goodix_ts_core.h + */ +int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v); + +#endif diff --git a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c new file mode 100755 index 0000000..b0dd121 --- /dev/null +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_ts_tools.c @@ -0,0 +1,542 @@ +/* + * Goodix GTx5 Touchscreen Dirver + * + * Copyright (C) 2015 - 2016 Goodix, Inc. + * Authors: Wang Yafei <wangyafei@goodix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/compat.h> +#include <linux/kernel.h> +#include <linux/atomic.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/ioctl.h> +#include <linux/wait.h> +#include "goodix_ts_core.h" + +#define GOODIX_TOOLS_NAME "gtp_tools" +#define GOODIX_TS_IOC_MAGIC 'G' +#define NEGLECT_SIZE_MASK (~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) + +#define GTP_IRQ_ENABLE _IO(GOODIX_TS_IOC_MAGIC, 0) +#define GTP_DEV_RESET _IO(GOODIX_TS_IOC_MAGIC, 1) +#define GTP_SEND_COMMAND (_IOW(GOODIX_TS_IOC_MAGIC, 2, u8) & NEGLECT_SIZE_MASK) +#define GTP_SEND_CONFIG (_IOW(GOODIX_TS_IOC_MAGIC, 3, u8) & NEGLECT_SIZE_MASK) +#define GTP_ASYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 4, u8) & NEGLECT_SIZE_MASK) +#define GTP_SYNC_READ (_IOR(GOODIX_TS_IOC_MAGIC, 5, u8) & NEGLECT_SIZE_MASK) +#define GTP_ASYNC_WRITE (_IOW(GOODIX_TS_IOC_MAGIC, 6, u8) & NEGLECT_SIZE_MASK) + +#define GOODIX_TS_IOC_MAXNR 6 + +#define IRQ_FALG (0x01 << 2) + +#define I2C_MSG_HEAD_LEN 20 +#define MAX_DATA_LEN 4096 +#define TS_REG_COORDS_BASE 0x824E +/* + * struct goodix_tools_data - goodix tools data message used in sync read + * @data: The buffer into which data is written + * @reg_addr: Slave device register start address to start read data + * @length: Number of data bytes in @data being read from slave device + * @filled: When buffer @data be filled will set this flag with 1, outhrwise 0 + * @list_head:Eonnet every goodix_tools_data struct into a list + */ + +struct goodix_tools_data { + u32 reg_addr; + u32 length; + u8 *data; + bool filled; + struct list_head list; +}; + + +/* + * struct goodix_tools_dev - goodix tools device struct + * @ts_core: The core data struct of ts driver + * @ops_mode: represent device work mode + * @rawdiffcmd: Set slave device into rawdata mode + * @normalcmd: Set slave device into normal mode + * @wq: Wait queue struct use in synchronous data read + * @mutex: Protect goodix_tools_dev + * @ref_count: reference count + * @ref_mutex: Protect ref_count + */ +struct goodix_tools_dev { + struct goodix_ts_core *ts_core; + struct list_head head; + unsigned int ops_mode; + struct goodix_ts_cmd rawdiffcmd, normalcmd; + wait_queue_head_t wq; + struct mutex mutex; + int ref_count; + struct mutex ref_mutex; + struct goodix_ext_module module; +} *goodix_tools_dev; + +static int goodix_tools_open(struct inode *inode, struct file *filp); +static int goodix_tools_release(struct inode *inode, struct file *filp); +static long goodix_tools_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); +#ifdef CONFIG_COMPAT +static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#endif + +static const struct file_operations goodix_tools_fops = { + .owner = THIS_MODULE, + .open = goodix_tools_open, + .release = goodix_tools_release, + .unlocked_ioctl = goodix_tools_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = goodix_tools_compat_ioctl, +#endif +}; + +static struct miscdevice goodix_tools_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = GOODIX_TOOLS_NAME, + .fops = &goodix_tools_fops, +}; +/* read data from i2c asynchronous, + * success return bytes read, else return <= 0 + */ +static int async_read(struct goodix_tools_dev *dev, void __user *arg) +{ + u8 *databuf = NULL; + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) + return ret; + + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]); + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]); + if (length > MAX_DATA_LEN) + length = MAX_DATA_LEN; + + databuf = kzalloc(length, GFP_KERNEL); + if (!databuf) + return -ENOMEM; + + if (!hw_ops->read(ts_dev, reg_addr, databuf, length)) { + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, + databuf, length)) + ret = -EFAULT; + else + ret = length; + } else { + ret = -EBUSY; + } + + kfree(databuf); + return ret; +} + +/* read data from i2c synchronous, + * success return bytes read, else return <= 0 + */ +static int sync_read(struct goodix_tools_dev *dev, void __user *arg) +{ + int ret = 0; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + struct goodix_tools_data tools_data; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) + return ret; + + tools_data.reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]); + tools_data.length = le32_to_cpup((__le32 *)&i2c_msg_head[4]); + tools_data.filled = 0; + if (tools_data.length > MAX_DATA_LEN) + tools_data.length = MAX_DATA_LEN; + + tools_data.data = kzalloc(tools_data.length, GFP_KERNEL); + if (!tools_data.data) + return -ENOMEM; + + mutex_lock(&dev->mutex); + list_add_tail(&tools_data.list, &dev->head); + mutex_unlock(&dev->mutex); + /* wait queue will timeout after 1 seconds */ + wait_event_interruptible_timeout(dev->wq, tools_data.filled == 1, HZ); + + mutex_lock(&dev->mutex); + list_del(&tools_data.list); + mutex_unlock(&dev->mutex); + if (tools_data.filled == 1) { + if (copy_to_user((u8 *)arg + I2C_MSG_HEAD_LEN, tools_data.data, + tools_data.length)) + ret = -EFAULT; + else + ret = tools_data.length; + } else { + ret = -EAGAIN; + dev_dbg(goodix_tools_miscdev.this_device, + "Wait queue timeout\n"); + } + + kfree(tools_data.data); + return ret; +} + +/* write data to i2c asynchronous, + * success return bytes write, else return <= 0 + */ +static int async_write(struct goodix_tools_dev *dev, void __user *arg) +{ + u8 *databuf; + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops; + + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) + return -EFAULT; + + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]); + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]); + if (length > MAX_DATA_LEN) + length = MAX_DATA_LEN; + + databuf = kzalloc(length, GFP_KERNEL); + if (!databuf) + return -ENOMEM; + + ret = copy_from_user(databuf, (u8 *)arg + I2C_MSG_HEAD_LEN, length); + if (ret) { + ret = -EFAULT; + goto err_out; + } + + if (hw_ops->write(ts_dev, reg_addr, databuf, length)) + ret = -EBUSY; + else + ret = length; + +err_out: + kfree(databuf); + return ret; +} + +static int init_cfg_data(struct goodix_ts_config *cfg, void __user *arg) +{ + int ret = 0; + u32 reg_addr, length; + u8 i2c_msg_head[I2C_MSG_HEAD_LEN]; + + cfg->initialized = 0; + mutex_init(&cfg->lock); + ret = copy_from_user(&i2c_msg_head, arg, I2C_MSG_HEAD_LEN); + if (ret) + return -EFAULT; + + reg_addr = le32_to_cpup((__le32 *)&i2c_msg_head[0]); + length = le32_to_cpup((__le32 *)&i2c_msg_head[4]); + if (length > MAX_DATA_LEN) + length = MAX_DATA_LEN; + + ret = copy_from_user(cfg->data, (u8 *)arg + I2C_MSG_HEAD_LEN, length); + if (ret) { + ret = -EFAULT; + dev_dbg(goodix_tools_miscdev.this_device, + "Copy data from user failed\n"); + goto err_out; + } + cfg->reg_base = reg_addr; + cfg->length = length; + strlcpy(cfg->name, "tools-send-cfg", sizeof(cfg->name)); + cfg->delay = 50; + cfg->initialized = true; + return 0; + +err_out: + return ret; +} +/** + * goodix_tools_ioctl - ioctl implementation + * + * @filp: Pointer to file opened + * @cmd: Ioctl opertion command + * @arg: Command data + * Returns >=0 - succeed, else failed + */ +static long goodix_tools_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct goodix_tools_dev *dev = filp->private_data; + struct goodix_ts_device *ts_dev; + const struct goodix_ts_hw_ops *hw_ops; + struct goodix_ts_cmd temp_cmd; + struct goodix_ts_config *temp_cfg; + + if (dev->ts_core == NULL) { + dev_err(goodix_tools_miscdev.this_device, + "Tools module not register\n"); + return -EINVAL; + } + ts_dev = dev->ts_core->ts_dev; + hw_ops = ts_dev->hw_ops; + + if (_IOC_TYPE(cmd) != GOODIX_TS_IOC_MAGIC) + return -ENOTTY; + if (_IOC_NR(cmd) > GOODIX_TS_IOC_MAXNR) + return -ENOTTY; + + + switch (cmd & NEGLECT_SIZE_MASK) { + case GTP_IRQ_ENABLE: + if (arg == 1) { + goodix_ts_irq_enable(dev->ts_core, true); + mutex_lock(&dev->mutex); + dev->ops_mode |= IRQ_FALG; + mutex_unlock(&dev->mutex); + dev_dbg(goodix_tools_miscdev.this_device, + "IRQ enabled\n"); + } else if (arg == 0) { + goodix_ts_irq_enable(dev->ts_core, false); + mutex_lock(&dev->mutex); + dev->ops_mode &= ~IRQ_FALG; + mutex_unlock(&dev->mutex); + dev_dbg(goodix_tools_miscdev.this_device, + "IRQ disabled\n"); + } else { + dev_dbg(goodix_tools_miscdev.this_device, + "Irq already set with, arg = %ld\n", arg); + } + ret = 0; + break; + case GTP_DEV_RESET: + hw_ops->reset(ts_dev); + break; + case GTP_SEND_COMMAND: + ret = copy_from_user(&temp_cmd, (void __user *)arg, + sizeof(struct goodix_ts_cmd)); + if (ret) { + ret = -EINVAL; + goto err_out; + } + + ret = hw_ops->send_cmd(ts_dev, &temp_cmd); + if (ret) { + dev_warn(goodix_tools_miscdev.this_device, + "Send command failed\n"); + ret = -EAGAIN; + } + break; + case GTP_SEND_CONFIG: + temp_cfg = kzalloc(sizeof(struct goodix_ts_config), GFP_KERNEL); + if (!temp_cfg) { + ret = -ENOMEM; + goto err_out; + } + ret = init_cfg_data(temp_cfg, (void __user *)arg); + + if (!ret) { + ret = hw_ops->send_config(ts_dev, temp_cfg); + if (ret) { + dev_warn(goodix_tools_miscdev.this_device, + "Failed send config\n"); + ret = -EAGAIN; + } + } + kfree(temp_cfg); + break; + case GTP_ASYNC_READ: + ret = async_read(dev, (void __user *)arg); + if (ret < 0) + dev_warn(goodix_tools_miscdev.this_device, + "Async data read failed"); + break; + case GTP_SYNC_READ: + if (filp->f_flags & O_NONBLOCK) { + dev_dbg(goodix_tools_miscdev.this_device, + "Goodix tools now worked in sync_bus mode\n"); + ret = -EAGAIN; + goto err_out; + } + ret = sync_read(dev, (void __user *)arg); + if (ret < 0) + dev_warn(goodix_tools_miscdev.this_device, + "Sync data read failed\n"); + break; + case GTP_ASYNC_WRITE: + ret = async_write(dev, (void __user *)arg); + if (ret < 0) + dev_warn(goodix_tools_miscdev.this_device, + "Async data write failed\n"); + break; + default: + dev_info(goodix_tools_miscdev.this_device, "Invalid cmd\n"); + ret = -ENOTTY; + break; + } + +err_out: + return ret; +} + +#ifdef CONFIG_COMPAT +static long goodix_tools_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (!file->f_op || !file->f_op->unlocked_ioctl) + return -ENOTTY; + + return goodix_tools_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int goodix_tools_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + + filp->private_data = goodix_tools_dev; + + mutex_lock(&goodix_tools_dev->ref_mutex); + + /* Only the first time open device need to register module */ + if (goodix_tools_dev->ref_count == 0) + ret = goodix_register_ext_module(&goodix_tools_dev->module); + if (!ret) + goodix_tools_dev->ref_count++; + + mutex_unlock(&goodix_tools_dev->ref_mutex); + return ret; +} + +static int goodix_tools_release(struct inode *inode, struct file *filp) +{ + int ret = 0; + + mutex_lock(&goodix_tools_dev->ref_mutex); + + goodix_tools_dev->ref_count--; + /* when the last close this dev node unregister the module */ + if (goodix_tools_dev->ref_count == 0) + ret = goodix_unregister_ext_module(&goodix_tools_dev->module); + + mutex_unlock(&goodix_tools_dev->ref_mutex); + return ret; +} + +/** + * goodix_tools_module_irq - goodix tools Irq handle + * This functions is excuted when interrupt happened + * + * @core_data: pointer to touch core data + * @module: pointer to goodix_ext_module struct + * return: EVT_CONTINUE let other module handle this irq + */ +static int goodix_tools_module_irq(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_tools_dev *dev = module->priv_data; + struct goodix_ts_device *ts_dev = dev->ts_core->ts_dev; + const struct goodix_ts_hw_ops *hw_ops = ts_dev->hw_ops; + struct goodix_tools_data *tools_data; + int r = 0; + u8 evt_sta = 0; + + if (!list_empty(&dev->head)) { + r = hw_ops->read(ts_dev, TS_REG_COORDS_BASE, &evt_sta, 1); + if (r < 0 || ((evt_sta & 0x80) == 0)) + return EVT_CONTINUE; + + mutex_lock(&dev->mutex); + list_for_each_entry(tools_data, &dev->head, list) { + if (!hw_ops->read(ts_dev, tools_data->reg_addr, + tools_data->data, tools_data->length)) { + tools_data->filled = 1; + } + } + mutex_unlock(&dev->mutex); + wake_up(&dev->wq); + } + return EVT_CONTINUE; +} + +static int goodix_tools_module_init(struct goodix_ts_core *core_data, + struct goodix_ext_module *module) +{ + struct goodix_tools_dev *dev = module->priv_data; + + if (core_data) { + dev->ts_core = core_data; + return 0; + } else { + return -ENODEV; + } +} + +static struct goodix_ext_module_funcs goodix_tools_module_funcs = { + .irq_event = goodix_tools_module_irq, + .init = goodix_tools_module_init, +}; + +/** + * goodix_tools_init - init goodix tools device and register a miscdevice + * + * return: 0 success, else failed + */ +static int __init goodix_tools_init(void) +{ + int ret; + + goodix_tools_dev = kzalloc(sizeof(struct goodix_tools_dev), GFP_KERNEL); + if (!goodix_tools_dev) + return -ENOMEM; + + INIT_LIST_HEAD(&goodix_tools_dev->head); + goodix_tools_dev->ops_mode = 0; + goodix_tools_dev->ops_mode |= IRQ_FALG; + init_waitqueue_head(&goodix_tools_dev->wq); + mutex_init(&goodix_tools_dev->mutex); + goodix_tools_dev->ref_count = 0; + mutex_init(&goodix_tools_dev->ref_mutex); + + goodix_tools_dev->module.funcs = &goodix_tools_module_funcs; + goodix_tools_dev->module.name = GOODIX_TOOLS_NAME; + goodix_tools_dev->module.priv_data = goodix_tools_dev; + goodix_tools_dev->module.priority = EXTMOD_PRIO_DBGTOOL; + + ret = misc_register(&goodix_tools_miscdev); + + return ret; +} + +static void __exit goodix_tools_exit(void) +{ + misc_deregister(&goodix_tools_miscdev); + kfree(goodix_tools_dev); +} + +module_init(goodix_tools_init); +module_exit(goodix_tools_exit); + +MODULE_DESCRIPTION("Goodix tools Module"); +MODULE_AUTHOR("Goodix, Inc."); +MODULE_LICENSE("GPL v2"); -- 2.7.4 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH] Input: fix semicolon.cocci warnings 2017-06-19 9:29 ` Wang Yafei @ 2017-06-19 13:24 ` kbuild test robot 0 siblings, 0 replies; 24+ messages in thread From: kbuild test robot @ 2017-06-19 13:24 UTC (permalink / raw) To: Wang Yafei Cc: kbuild-all, Dmitry Torokhov, Henrik Rydberg, linux-input, andrew, mouse, linux-kernel drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c:752:2-3: Unneeded semicolon Remove unneeded semicolon. Generated by: scripts/coccinelle/misc/semicolon.cocci CC: Wang Yafei <wangyafei@goodix.com> Signed-off-by: Fengguang Wu <fengguang.wu@intel.com> --- goodix_gtx5_update.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c +++ b/drivers/input/touchscreen/goodix-ts-sunrise/goodix_gtx5_update.c @@ -749,7 +749,7 @@ static int goodix_send_fw_packet(struct if (r < 0) { dev_err(ts_dev->dev, "Error occurred when wait ISP idle\n"); return r; - }; + } /* check ISP result */ r = goodix_isp_flash_done(ts_dev); ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2 1/2] Input: edt-ft5x06 - Add support for regulator @ 2017-12-28 16:33 Mylène Josserand 2017-12-30 3:15 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot 0 siblings, 1 reply; 24+ messages in thread From: Mylène Josserand @ 2017-12-28 16:33 UTC (permalink / raw) To: dmitry.torokhov, robh+dt, mark.rutland, linux, maxime.ripard, wens Cc: linux-arm-kernel, linux-input, devicetree, linux-kernel, mylene.josserand, thomas.petazzoni, quentin.schulz Add the support of regulator to use it as VCC source. Signed-off-by: Mylène Josserand <mylene.josserand@free-electrons.com> --- .../bindings/input/touchscreen/edt-ft5x06.txt | 1 + drivers/input/touchscreen/edt-ft5x06.c | 33 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt index 025cf8c9324a..48e975b9c1aa 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt @@ -30,6 +30,7 @@ Required properties: Optional properties: - reset-gpios: GPIO specification for the RESET input - wake-gpios: GPIO specification for the WAKE input + - vcc-supply: Regulator that supplies the touchscreen - pinctrl-names: should be "default" - pinctrl-0: a phandle pointing to the pin settings for the diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index c53a3d7239e7..5ee14a25a382 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -39,6 +39,7 @@ #include <linux/input/mt.h> #include <linux/input/touchscreen.h> #include <linux/of_device.h> +#include <linux/regulator/consumer.h> #define WORK_REGISTER_THRESHOLD 0x00 #define WORK_REGISTER_REPORT_RATE 0x08 @@ -91,6 +92,7 @@ struct edt_ft5x06_ts_data { struct touchscreen_properties prop; u16 num_x; u16 num_y; + struct regulator *vcc; struct gpio_desc *reset_gpio; struct gpio_desc *wake_gpio; @@ -993,6 +995,23 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, tsdata->max_support_points = chip_data->max_support_points; + tsdata->vcc = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(tsdata->vcc)) { + error = PTR_ERR(tsdata->vcc); + dev_err(&client->dev, "failed to request regulator: %d\n", + error); + return error; + }; + + if (tsdata->vcc) { + error = regulator_enable(tsdata->vcc); + if (error < 0) { + dev_err(&client->dev, "failed to enable vcc: %d\n", + error); + return error; + } + } + tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(tsdata->reset_gpio)) { @@ -1122,20 +1141,34 @@ static int edt_ft5x06_ts_remove(struct i2c_client *client) 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); if (device_may_wakeup(dev)) enable_irq_wake(client->irq); + if (tsdata->vcc) + regulator_disable(tsdata->vcc); + 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; if (device_may_wakeup(dev)) disable_irq_wake(client->irq); + if (tsdata->vcc) { + ret = regulator_enable(tsdata->vcc); + if (ret < 0) { + dev_err(dev, "failed to enable vcc: %d\n", ret); + return ret; + } + } + return 0; } -- 2.11.0 ^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH] Input: fix semicolon.cocci warnings 2017-12-28 16:33 [PATCH v2 1/2] Input: edt-ft5x06 - Add support for regulator Mylène Josserand @ 2017-12-30 3:15 ` kbuild test robot 0 siblings, 0 replies; 24+ messages in thread From: kbuild test robot @ 2017-12-30 3:15 UTC (permalink / raw) Cc: kbuild-all, dmitry.torokhov, robh+dt, mark.rutland, linux, maxime.ripard, wens, linux-arm-kernel, linux-input, devicetree, linux-kernel, mylene.josserand, thomas.petazzoni, quentin.schulz From: Fengguang Wu <fengguang.wu@intel.com> drivers/input/touchscreen/edt-ft5x06.c:1004:2-3: Unneeded semicolon Remove unneeded semicolon. Generated by: scripts/coccinelle/misc/semicolon.cocci Fixes: 5969d946e8aa ("Input: edt-ft5x06 - Add support for regulator") CC: Mylène Josserand <mylene.josserand@free-electrons.com> Signed-off-by: Fengguang Wu <fengguang.wu@intel.com> --- edt-ft5x06.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -1001,7 +1001,7 @@ static int edt_ft5x06_ts_probe(struct i2 dev_err(&client->dev, "failed to request regulator: %d\n", error); return error; - }; + } if (tsdata->vcc) { error = regulator_enable(tsdata->vcc); ^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2017-12-30 3:16 UTC | newest] Thread overview: 24+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2016-08-18 9:24 [PATCH 00/11] Synaptics RMI4 over SMBus Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 01/11] Input: synaptics-rmi4 - add SMBus support Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 02/11] Input: serio - store the pt_buttons in the struct serio directly Benjamin Tissoires 2016-08-27 1:31 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 03/11] Input: synaptics-rmi4 - have only one struct platform data Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 04/11] Input: synaptics-rmi4 - add support for F03 Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 05/11] Input: synaptics-rmi4 - f03: grab data passed by transport device Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 06/11] Input: synaptics-rmi4 - Add rmi_find_function() Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 07/11] Input: synaptics-rmi4 - f30/f03: Forward mechanical buttons on buttonpads to PS/2 guest Benjamin Tissoires 2016-08-27 1:35 ` Andrew Duggan 2016-08-30 15:13 ` Benjamin Tissoires 2016-08-30 19:16 ` Andrew Duggan 2016-08-18 9:24 ` [PATCH 08/11] Input: synaptics - allocate a Synaptics Intertouch device Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform Benjamin Tissoires 2016-08-24 2:47 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot 2016-08-24 2:47 ` [PATCH 09/11] Input: synaptics-rmi4 - add rmi_platform kbuild test robot 2016-08-18 9:24 ` [PATCH 10/11] Input: synaptics-rmi4 - smbus: call psmouse_deactivate before binding/resume Benjamin Tissoires 2016-08-18 9:24 ` [PATCH 11/11] Input: synaptics-rmi4 - smbus: on resume, try 3 times if init fails Benjamin Tissoires -- strict thread matches above, loose matches on Subject: below -- 2017-06-19 13:24 [PATCH v2] Input: Add driver for GOODiX GTx5 series touchsereen kbuild test robot 2017-06-19 9:29 ` Wang Yafei 2017-06-19 13:24 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot 2017-12-28 16:33 [PATCH v2 1/2] Input: edt-ft5x06 - Add support for regulator Mylène Josserand 2017-12-30 3:15 ` [PATCH] Input: fix semicolon.cocci warnings kbuild test robot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).