* [PATCH v13 07/12] input: cyapa: add gen3 trackpad device read baseline function support
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Add read baseline function supported for gen3 trackpad device,
it can be used through sysfs baseline interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen3.c | 71 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c
index 1110668..3b2ad88 100644
--- a/drivers/input/mouse/cyapa_gen3.c
+++ b/drivers/input/mouse/cyapa_gen3.c
@@ -754,6 +754,75 @@ static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
return 0;
}
+static ssize_t cyapa_gen3_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int max_baseline, min_baseline;
+ int tries = 3;
+ int ret;
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n", ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+ dev_warn(dev, "Trackpad device is busy. device state = 0x%x\n",
+ ret);
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET,
+ OP_REPORT_BASELINE_MASK);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send report baseline command. %d\n",
+ ret);
+ goto out;
+ }
+
+ do {
+ usleep_range(10000, 20000);
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status. err = %d\n",
+ ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) == CYAPA_DEV_NORMAL)
+ break;
+ } while (--tries);
+
+ if (tries == 0) {
+ dev_err(dev, "Device timed out going to Normal state.\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_MAX_BASELINE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read max baseline. err = %d\n", ret);
+ goto out;
+ }
+ max_baseline = ret;
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_MIN_BASELINE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read min baseline. err = %d\n", ret);
+ goto out;
+ }
+ min_baseline = ret;
+
+ dev_dbg(dev, "Baseline report successful. Max: %d Min: %d\n",
+ max_baseline, min_baseline);
+ ret = scnprintf(buf, PAGE_SIZE, "%d %d\n", max_baseline, min_baseline);
+
+out:
+ return ret;
+}
+
/*
* cyapa_get_wait_time_for_pwr_cmd
*
@@ -1067,6 +1136,8 @@ const struct cyapa_dev_ops cyapa_gen3_ops = {
.update_fw = cyapa_gen3_do_fw_update,
.bl_deactivate = cyapa_gen3_bl_deactivate,
+ .show_baseline = cyapa_gen3_show_baseline,
+
.state_parse = cyapa_gen3_state_parse,
.operational_check = cyapa_gen3_do_operational_check,
--
1.9.1
^ permalink raw reply related
* [PATCH v13 06/12] input: cyapa: add gen3 trackpad device firmware update function support
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Add firmware image update function supported for gen3 trackpad device,
it can be used through sysfs update_fw interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen3.c | 284 +++++++++++++++++++++++++++++++++++++++
1 file changed, 284 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c
index a50986c..1110668 100644
--- a/drivers/input/mouse/cyapa_gen3.c
+++ b/drivers/input/mouse/cyapa_gen3.c
@@ -416,6 +416,72 @@ static int cyapa_gen3_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
return -EAGAIN;
}
+/*
+ * Enter bootloader by soft resetting the device.
+ *
+ * If device is already in the bootloader, the function just returns.
+ * Otherwise, reset the device; after reset, device enters bootloader idle
+ * state immediately.
+ *
+ * Also, if device was unregister device from input core. Device will
+ * re-register after it is detected following resumption of operational mode.
+ *
+ * Returns:
+ * 0 on success
+ * -EAGAIN device was reset, but is not now in bootloader idle state
+ * < 0 if the device never responds within the timeout
+ */
+static int cyapa_gen3_bl_enter(struct cyapa *cyapa)
+{
+ int error;
+
+ error = cyapa_poll_state(cyapa, 500);
+ if (error)
+ return error;
+ if (cyapa->state == CYAPA_STATE_BL_IDLE) {
+ /* Already in BL_IDLE. Skipping reset. */
+ return 0;
+ }
+
+ if (cyapa->state != CYAPA_STATE_OP)
+ return -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ error = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, 0x01);
+ if (error)
+ return -EIO;
+
+ usleep_range(25000, 50000);
+ error = cyapa_poll_state(cyapa, 500);
+ if (error)
+ return error;
+ if ((cyapa->state != CYAPA_STATE_BL_IDLE) ||
+ (cyapa->status[REG_BL_STATUS] & BL_STATUS_WATCHDOG))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen3_bl_activate(struct cyapa *cyapa)
+{
+ int error;
+
+ error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_activate),
+ bl_activate);
+ if (error)
+ return error;
+
+ /* Wait for bootloader to activate; takes between 2 and 12 seconds */
+ msleep(2000);
+ error = cyapa_poll_state(cyapa, 11000);
+ if (error)
+ return error;
+ if (cyapa->state != CYAPA_STATE_BL_ACTIVE)
+ return -EAGAIN;
+
+ return 0;
+}
+
static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
{
int error;
@@ -476,6 +542,218 @@ static int cyapa_gen3_bl_exit(struct cyapa *cyapa)
return 0;
}
+/* Used in gen3 bootloader commands. */
+static u16 cyapa_gen3_csum(const u8 *buf, size_t count)
+{
+ int i;
+ u16 csum = 0;
+
+ for (i = 0; i < count; i++)
+ csum += buf[i];
+
+ return csum;
+}
+
+/*
+ * Verify the integrity of a CYAPA firmware image file.
+ *
+ * The firmware image file is 30848 bytes, composed of 482 64-byte blocks.
+ *
+ * The first 2 blocks are the firmware header.
+ * The next 480 blocks are the firmware image.
+ *
+ * The first two bytes of the header hold the header checksum, computed by
+ * summing the other 126 bytes of the header.
+ * The last two bytes of the header hold the firmware image checksum, computed
+ * by summing the 30720 bytes of the image modulo 0xffff.
+ *
+ * Both checksums are stored little-endian.
+ */
+static int cyapa_gen3_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ u16 csum;
+ u16 csum_expected;
+
+ /* Firmware must match exact 30848 bytes = 482 64-byte blocks. */
+ if (fw->size != CYAPA_FW_SIZE) {
+ dev_err(dev, "invalid firmware size = %zu, expected %u.\n",
+ fw->size, CYAPA_FW_SIZE);
+ return -EINVAL;
+ }
+
+ /* Verify header block */
+ csum_expected = (fw->data[0] << 8) | fw->data[1];
+ csum = cyapa_gen3_csum(&fw->data[2], CYAPA_FW_HDR_SIZE - 2);
+ if (csum != csum_expected) {
+ dev_err(dev, "%s %04x, expected: %04x\n",
+ "invalid firmware header checksum = ",
+ csum, csum_expected);
+ return -EINVAL;
+ }
+
+ /* Verify firmware image */
+ csum_expected = (fw->data[CYAPA_FW_HDR_SIZE - 2] << 8) |
+ fw->data[CYAPA_FW_HDR_SIZE - 1];
+ csum = cyapa_gen3_csum(&fw->data[CYAPA_FW_HDR_SIZE],
+ CYAPA_FW_DATA_SIZE);
+ if (csum != csum_expected) {
+ dev_err(dev, "%s %04x, expected: %04x\n",
+ "invalid firmware header checksum = ",
+ csum, csum_expected);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Write a |len| byte long buffer |buf| to the device, by chopping it up into a
+ * sequence of smaller |CYAPA_CMD_LEN|-length write commands.
+ *
+ * The data bytes for a write command are prepended with the 1-byte offset
+ * of the data relative to the start of |buf|.
+ */
+static int cyapa_gen3_write_buffer(struct cyapa *cyapa,
+ const u8 *buf, size_t len)
+{
+ int error;
+ size_t i;
+ unsigned char cmd[CYAPA_CMD_LEN + 1];
+ size_t cmd_len;
+
+ for (i = 0; i < len; i += CYAPA_CMD_LEN) {
+ const u8 *payload = &buf[i];
+
+ cmd_len = (len - i >= CYAPA_CMD_LEN) ? CYAPA_CMD_LEN : len - i;
+ cmd[0] = i;
+ memcpy(&cmd[1], payload, cmd_len);
+
+ error = cyapa_i2c_reg_write_block(cyapa, 0, cmd_len + 1, cmd);
+ if (error)
+ return error;
+ }
+ return 0;
+}
+
+/*
+ * A firmware block write command writes 64 bytes of data to a single flash
+ * page in the device. The 78-byte block write command has the format:
+ * <0xff> <CMD> <Key> <Start> <Data> <Data-Checksum> <CMD Checksum>
+ *
+ * <0xff> - every command starts with 0xff
+ * <CMD> - the write command value is 0x39
+ * <Key> - write commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ * <Block> - Memory Block number (address / 64) (16-bit, big-endian)
+ * <Data> - 64 bytes of firmware image data
+ * <Data Checksum> - sum of 64 <Data> bytes, modulo 0xff
+ * <CMD Checksum> - sum of 77 bytes, from 0xff to <Data Checksum>
+ *
+ * Each write command is split into 5 i2c write transactions of up to 16 bytes.
+ * Each transaction starts with an i2c register offset: (00, 10, 20, 30, 40).
+ */
+static int cyapa_gen3_write_fw_block(struct cyapa *cyapa,
+ u16 block, const u8 *data)
+{
+ int ret;
+ u8 cmd[78];
+ u8 status[BL_STATUS_SIZE];
+ /* Programming for one block can take about 100ms. */
+ int tries = 11;
+ u8 bl_status, bl_error;
+
+ /* Set write command and security key bytes. */
+ cmd[0] = 0xff;
+ cmd[1] = 0x39;
+ cmd[2] = 0x00;
+ cmd[3] = 0x01;
+ cmd[4] = 0x02;
+ cmd[5] = 0x03;
+ cmd[6] = 0x04;
+ cmd[7] = 0x05;
+ cmd[8] = 0x06;
+ cmd[9] = 0x07;
+ cmd[10] = block >> 8;
+ cmd[11] = block;
+ memcpy(&cmd[12], data, CYAPA_FW_BLOCK_SIZE);
+ cmd[76] = cyapa_gen3_csum(data, CYAPA_FW_BLOCK_SIZE);
+ cmd[77] = cyapa_gen3_csum(cmd, sizeof(cmd) - 1);
+
+ ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+ if (ret)
+ return ret;
+
+ /* Wait for write to finish */
+ do {
+ usleep_range(10000, 20000);
+
+ /* Check block write command result status. */
+ ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET,
+ BL_STATUS_SIZE, status);
+ if (ret != BL_STATUS_SIZE)
+ return (ret < 0) ? ret : -EIO;
+ } while ((status[REG_BL_STATUS] & BL_STATUS_BUSY) && --tries);
+
+ /* Ignore WATCHDOG bit and reserved bits. */
+ bl_status = status[REG_BL_STATUS] & ~BL_STATUS_REV_MASK;
+ bl_error = status[REG_BL_ERROR] & ~BL_ERROR_RESERVED;
+
+ if (bl_status & BL_STATUS_BUSY)
+ ret = -ETIMEDOUT;
+ else if (bl_status != BL_STATUS_RUNNING ||
+ bl_error != BL_ERROR_BOOTLOADING)
+ ret = -EIO;
+ else
+ ret = 0;
+
+ return ret;
+}
+
+static int cyapa_gen3_write_blocks(struct cyapa *cyapa,
+ size_t start_block, size_t block_count,
+ const u8 *image_data)
+{
+ int error;
+ int i;
+
+ for (i = 0; i < block_count; i++) {
+ size_t block = start_block + i;
+ size_t addr = i * CYAPA_FW_BLOCK_SIZE;
+ const u8 *data = &image_data[addr];
+
+ error = cyapa_gen3_write_fw_block(cyapa, block, data);
+ if (error)
+ return error;
+ }
+ return 0;
+}
+
+static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ int error;
+
+ /* First write data, starting at byte 128 of fw->data */
+ error = cyapa_gen3_write_blocks(cyapa,
+ CYAPA_FW_DATA_BLOCK_START, CYAPA_FW_DATA_BLOCK_COUNT,
+ &fw->data[CYAPA_FW_HDR_BLOCK_COUNT * CYAPA_FW_BLOCK_SIZE]);
+ if (error) {
+ dev_err(dev, "FW update aborted, write image: %d\n", error);
+ return error;
+ }
+
+ /* Then write checksum */
+ error = cyapa_gen3_write_blocks(cyapa,
+ CYAPA_FW_HDR_BLOCK_START, CYAPA_FW_HDR_BLOCK_COUNT,
+ &fw->data[0]);
+ if (error) {
+ dev_err(dev, "FW update aborted, write checksum: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
/*
* cyapa_get_wait_time_for_pwr_cmd
*
@@ -783,6 +1061,12 @@ static int cyapa_gen3_irq_handler(struct cyapa *cyapa)
}
const struct cyapa_dev_ops cyapa_gen3_ops = {
+ .check_fw = cyapa_gen3_check_fw,
+ .bl_enter = cyapa_gen3_bl_enter,
+ .bl_activate = cyapa_gen3_bl_activate,
+ .update_fw = cyapa_gen3_do_fw_update,
+ .bl_deactivate = cyapa_gen3_bl_deactivate,
+
.state_parse = cyapa_gen3_state_parse,
.operational_check = cyapa_gen3_do_operational_check,
--
1.9.1
^ permalink raw reply related
* [PATCH v13 05/12] input: cyapa: add sysfs interfaces supported in the cyapa driver
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Add device's basic control and features supported in cyapa driver through
sysfs file system interfaces. These interfaces are commonly used in
pre- and after production, for trackpad device state checking, managing
and firmware image updating.
These interfaces including mode, firmware_version and product_id interfaces
for reading firmware version and trackpad device product id values,
and including update_fw interface to command firmware image update
process. Also including baseline and calibrate interfaces for
reading and checking trackpad device's sensors states.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 327 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 327 insertions(+)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index f19754c..ff8cd9b 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -32,6 +32,8 @@
#define CYAPA_ADAPTER_FUNC_SMBUS 2
#define CYAPA_ADAPTER_FUNC_BOTH 3
+#define CYAPA_FW_NAME "cyapa.bin"
+
const char unique_str[] = "CYTRA";
static int cyapa_reinitialize(struct cyapa *cyapa);
@@ -449,6 +451,29 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
return 0;
}
+static void cyapa_enable_irq_for_cmd(struct cyapa *cyapa)
+{
+ struct input_dev *input = cyapa->input;
+
+ if (!input || !input->users) {
+ if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
+ cyapa->ops->set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ enable_irq(cyapa->client->irq);
+ }
+}
+
+static void cyapa_disable_irq_for_cmd(struct cyapa *cyapa)
+{
+ struct input_dev *input = cyapa->input;
+
+ if (!input || !input->users) {
+ disable_irq(cyapa->client->irq);
+ if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
+ cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+ }
+}
+
/*
* cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
*
@@ -790,6 +815,295 @@ static int cyapa_start_runtime(struct cyapa *cyapa)
}
#endif /* CONFIG_PM_RUNTIME */
+static ssize_t cyapa_show_fm_ver(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int error;
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+ error = scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+ cyapa->fw_min_ver);
+ mutex_unlock(&cyapa->state_sync_lock);
+ return error;
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int size;
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+ size = scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+ mutex_unlock(&cyapa->state_sync_lock);
+ return size;
+}
+
+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+ struct device *dev = &cyapa->client->dev;
+ const struct firmware *fw;
+ int error;
+
+ error = request_firmware(&fw, fw_name, dev);
+ if (error) {
+ dev_err(dev, "Could not load firmware from %s: %d\n",
+ fw_name, error);
+ return error;
+ }
+
+ if (cyapa->ops->check_fw) {
+ error = cyapa->ops->check_fw(cyapa, fw);
+ if (error) {
+ dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+ fw_name);
+ goto done;
+ }
+ } else {
+ dev_err(dev, "No valid device ops->check_fw handler set.\n");
+ error = -ENODEV;
+ goto done;
+ }
+
+ /*
+ * Resume the potentially suspended device because doing FW
+ * update on a device not in the FULL mode has a chance to
+ * fail.
+ */
+ pm_runtime_get_sync(dev);
+
+ /* Require IRQ support for firmware update commands. */
+ cyapa_enable_irq_for_cmd(cyapa);
+
+ if (cyapa->ops->bl_enter) {
+ error = cyapa->ops->bl_enter(cyapa);
+ if (error) {
+ dev_err(dev, "bl_enter failed, %d\n", error);
+ goto err_detect;
+ }
+ }
+
+ if (cyapa->ops->bl_activate) {
+ error = cyapa->ops->bl_activate(cyapa);
+ if (error) {
+ dev_err(dev, "bl_activate failed, %d\n", error);
+ goto err_detect;
+ }
+ }
+
+ if (cyapa->ops->bl_initiate) {
+ error = cyapa->ops->bl_initiate(cyapa, fw);
+ if (error) {
+ dev_err(dev, "bl_initiate failed, %d\n", error);
+ goto err_detect;
+ }
+ }
+
+ if (cyapa->ops->update_fw) {
+ error = cyapa->ops->update_fw(cyapa, fw);
+ if (error) {
+ dev_err(dev, "update_fw failed, %d\n", error);
+ goto err_detect;
+ }
+ }
+
+ if (cyapa->ops->bl_verify_app_integrity) {
+ error = cyapa->ops->bl_verify_app_integrity(cyapa);
+ if (error) {
+ dev_err(dev, "bl_verify_app_integrity failed, %d\n",
+ error);
+ goto err_detect;
+ }
+ }
+
+err_detect:
+ cyapa_disable_irq_for_cmd(cyapa);
+ pm_runtime_put_noidle(dev);
+
+done:
+ release_firmware(fw);
+ return error;
+}
+
+static ssize_t cyapa_update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ char fw_name[64];
+ int ret, error;
+
+ if (count > 64) {
+ dev_err(dev, "File name too long\n");
+ return -EINVAL;
+ }
+
+ memcpy(fw_name, buf, count);
+ if (fw_name[count - 1] == '\n')
+ fw_name[count - 1] = '\0';
+ else
+ fw_name[count] = '\0';
+
+ if (cyapa->input) {
+ /*
+ * Force the input device to be registered after the firmware
+ * image is updated, so if the corresponding parameters updated
+ * in the new firmware image can taken effect immediately.
+ */
+ input_unregister_device(cyapa->input);
+ cyapa->operational = false;
+ cyapa->input = NULL;
+ }
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error) {
+ /*
+ * Whatever, do reinitialize to try to recover TP state to
+ * previous state just as it entered fw update entrance.
+ */
+ cyapa_reinitialize(cyapa);
+ return error;
+ }
+
+ error = cyapa_firmware(cyapa, fw_name);
+ if (error)
+ dev_err(dev, "firmware update failed: %d\n", error);
+ else
+ dev_dbg(dev, "firmware update successfully done.\n");
+
+ /*
+ * Redetect trackpad device states because firmware update process
+ * will reset trackpad device into bootloader mode.
+ */
+ ret = cyapa_reinitialize(cyapa);
+ if (ret) {
+ dev_err(dev, "failed to redetect after updated: %d\n", ret);
+ error = error ? error : ret;
+ }
+
+ mutex_unlock(&cyapa->state_sync_lock);
+
+ return error ? error : count;
+}
+
+static ssize_t cyapa_calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ if (!cyapa->ops->calibrate_store) {
+ dev_err(dev, "Calibrate operation not supported.\n");
+ error = -ENOTSUPP;
+ } else {
+ cyapa_enable_irq_for_cmd(cyapa);
+ error = cyapa->ops->calibrate_store(dev, attr, buf, count);
+ cyapa_disable_irq_for_cmd(cyapa);
+ }
+
+ mutex_unlock(&cyapa->state_sync_lock);
+ return error < 0 ? error : count;
+}
+
+static ssize_t cyapa_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ ssize_t error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ if (!cyapa->ops->show_baseline) {
+ dev_err(dev, "Calibrate operation not supported.\n");
+ error = -ENOTSUPP;
+ } else {
+ cyapa_enable_irq_for_cmd(cyapa);
+ error = cyapa->ops->show_baseline(dev, attr, buf);
+ cyapa_disable_irq_for_cmd(cyapa);
+ }
+
+ mutex_unlock(&cyapa->state_sync_lock);
+ return error;
+}
+
+static char *cyapa_state_to_string(struct cyapa *cyapa)
+{
+ switch (cyapa->state) {
+ case CYAPA_STATE_BL_BUSY:
+ return "bootloader busy";
+ case CYAPA_STATE_BL_IDLE:
+ return "bootloader idle";
+ case CYAPA_STATE_BL_ACTIVE:
+ return "bootloader active";
+ case CYAPA_STATE_GEN5_BL:
+ return "bootloader";
+ case CYAPA_STATE_OP:
+ case CYAPA_STATE_GEN5_APP:
+ return "operational"; /* Normal valid state. */
+ default:
+ return "invalid mode";
+ }
+}
+
+static ssize_t cyapa_show_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int size;
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ size = scnprintf(buf, PAGE_SIZE, "gen%d %s\n",
+ cyapa->gen, cyapa_state_to_string(cyapa));
+
+ mutex_unlock(&cyapa->state_sync_lock);
+ return size;
+}
+
+static DEVICE_ATTR(firmware_version, S_IRUGO, cyapa_show_fm_ver, NULL);
+static DEVICE_ATTR(product_id, S_IRUGO, cyapa_show_product_id, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, cyapa_update_fw_store);
+static DEVICE_ATTR(baseline, S_IRUGO, cyapa_show_baseline, NULL);
+static DEVICE_ATTR(calibrate, S_IWUSR, NULL, cyapa_calibrate_store);
+static DEVICE_ATTR(mode, S_IRUGO, cyapa_show_mode, NULL);
+
+static struct attribute *cyapa_sysfs_entries[] = {
+ &dev_attr_firmware_version.attr,
+ &dev_attr_product_id.attr,
+ &dev_attr_update_fw.attr,
+ &dev_attr_baseline.attr,
+ &dev_attr_calibrate.attr,
+ &dev_attr_mode.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_sysfs_group = {
+ .attrs = cyapa_sysfs_entries,
+};
+
+static void cyapa_remove_sysfs_group(void *data)
+{
+ struct cyapa *cyapa = data;
+
+ sysfs_remove_group(&cyapa->client->dev.kobj, &cyapa_sysfs_group);
+}
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -829,6 +1143,19 @@ static int cyapa_probe(struct i2c_client *client,
return error;
}
+ error = sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group);
+ if (error) {
+ dev_err(dev, "failed to create sysfs entries: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action(dev, cyapa_remove_sysfs_group, cyapa);
+ if (error) {
+ cyapa_remove_sysfs_group(cyapa);
+ dev_err(dev, "failed to add sysfs cleanup action: %d\n", error);
+ return error;
+ }
+
#ifdef CONFIG_PM_SLEEP
if (device_can_wakeup(dev)) {
error = sysfs_merge_group(&client->dev.kobj,
--
1.9.1
^ permalink raw reply related
* [PATCH v13 04/12] input: cyapa: add runtime power management interfaces supported for the device
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Add runtime_suspend_scanrate_ms power management interfaces in device's
power group, so users or applications can control the runtime power
management strategy of trackpad device as their requirements.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 171 +++++++++++++++++++++++++++++++++++++++++++-
drivers/input/mouse/cyapa.h | 4 ++
2 files changed, 174 insertions(+), 1 deletion(-)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 055f1df..f19754c 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -23,6 +23,7 @@
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/pm_runtime.h>
#include "cyapa.h"
@@ -334,6 +335,8 @@ static int cyapa_open(struct input_dev *input)
}
enable_irq(client->irq);
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->dev);
out:
mutex_unlock(&cyapa->state_sync_lock);
return error;
@@ -347,8 +350,10 @@ static void cyapa_close(struct input_dev *input)
mutex_lock(&cyapa->state_sync_lock);
disable_irq(client->irq);
+ pm_runtime_disable(&client->dev);
if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode)
cyapa->ops->set_power_mode(cyapa, PWR_MODE_OFF, 0);
+ pm_runtime_set_suspended(&client->dev);
mutex_unlock(&cyapa->state_sync_lock);
}
@@ -549,6 +554,7 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
struct device *dev = &cyapa->client->dev;
bool cont;
+ pm_runtime_get_sync(dev);
if (device_may_wakeup(dev))
pm_wakeup_event(dev, 0);
@@ -579,6 +585,8 @@ static irqreturn_t cyapa_irq(int irq, void *dev_id)
}
out:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_sync_autosuspend(dev);
return IRQ_HANDLED;
}
@@ -672,6 +680,116 @@ static void cyapa_remove_power_wakeup_group(void *data)
}
#endif /* CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM_RUNTIME
+static ssize_t cyapa_show_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd;
+ u16 sleep_time;
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+ pwr_cmd = cyapa->runtime_suspend_power_mode;
+ sleep_time = cyapa->runtime_suspend_sleep_time;
+ mutex_unlock(&cyapa->state_sync_lock);
+
+ if (cyapa->gen == CYAPA_GEN3)
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ cyapa_pwr_cmd_to_sleep_time(pwr_cmd));
+ return scnprintf(buf, PAGE_SIZE, "%u\n", sleep_time);
+}
+
+static ssize_t cyapa_update_rt_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 time;
+ int error;
+
+ if (buf == NULL || count == 0 || kstrtou16(buf, 10, &time)) {
+ dev_err(dev, "invalid runtime suspend scanrate ms parameter\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When the suspend scanrate is changed, pm_runtime_get to resume
+ * a potentially suspended device, update to the new pwr_cmd
+ * and then pm_runtime_put to suspend into the new power mode.
+ */
+ pm_runtime_get_sync(dev);
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+ cyapa->runtime_suspend_sleep_time = max_t(u16, time, 1000);
+ cyapa->runtime_suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(cyapa->runtime_suspend_sleep_time);
+ mutex_unlock(&cyapa->state_sync_lock);
+ pm_runtime_put_sync_autosuspend(dev);
+
+ return count;
+}
+
+static DEVICE_ATTR(runtime_suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_rt_suspend_scanrate,
+ cyapa_update_rt_suspend_scanrate);
+
+static struct attribute *cyapa_power_runtime_entries[] = {
+ &dev_attr_runtime_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_runtime_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_runtime_entries,
+};
+
+static void cyapa_remove_power_runtime_group(void *data)
+{
+ struct cyapa *cyapa = data;
+
+ sysfs_unmerge_group(&cyapa->client->dev.kobj,
+ &cyapa_power_runtime_group);
+}
+
+static int cyapa_start_runtime(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ int error;
+
+ cyapa->runtime_suspend_power_mode = PWR_MODE_IDLE;
+ cyapa->runtime_suspend_sleep_time =
+ cyapa_pwr_cmd_to_sleep_time(cyapa->runtime_suspend_power_mode);
+
+ error = sysfs_merge_group(&dev->kobj, &cyapa_power_runtime_group);
+ if (error) {
+ dev_err(dev,
+ "failed to create power runtime group: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action(dev, cyapa_remove_power_runtime_group, cyapa);
+ if (error) {
+ cyapa_remove_power_runtime_group(cyapa);
+ dev_err(dev,
+ "failed to add power runtime cleanup action: %d\n",
+ error);
+ return error;
+ }
+
+ /* runtime is enabled until device is operational and opened. */
+ pm_runtime_set_suspended(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY);
+
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -732,6 +850,14 @@ static int cyapa_probe(struct i2c_client *client,
}
#endif /* CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM_RUNTIME
+ error = cyapa_start_runtime(cyapa);
+ if (error) {
+ dev_err(dev, "failed to start pm_runtime: %d\n", error);
+ return error;
+ }
+#endif /* CONFIG_PM_RUNTIME */
+
error = devm_request_threaded_irq(dev, client->irq,
NULL, cyapa_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
@@ -772,6 +898,7 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
if (error)
return error;
+ pm_runtime_disable(dev);
disable_irq(client->irq);
/*
@@ -788,6 +915,8 @@ static int __maybe_unused cyapa_suspend(struct device *dev)
error);
}
+ pm_runtime_set_suspended(dev);
+
if (device_may_wakeup(dev))
cyapa->irq_wake = (enable_irq_wake(client->irq) == 0);
@@ -808,17 +937,57 @@ static int __maybe_unused cyapa_resume(struct device *dev)
cyapa->irq_wake = false;
}
+ pm_runtime_set_active(dev);
+
error = cyapa_reinitialize(cyapa);
if (error)
dev_warn(dev, "failed to reinitialize TP device: %d\n", error);
enable_irq(client->irq);
+ pm_runtime_enable(dev);
+
mutex_unlock(&cyapa->state_sync_lock);
return 0;
}
-static SIMPLE_DEV_PM_OPS(cyapa_pm_ops, cyapa_suspend, cyapa_resume);
+#ifdef CONFIG_PM_RUNTIME
+static int cyapa_runtime_suspend(struct device *dev)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int error;
+
+ if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode) {
+ error = cyapa->ops->set_power_mode(cyapa,
+ cyapa->runtime_suspend_power_mode,
+ cyapa->runtime_suspend_sleep_time);
+ if (error)
+ dev_warn(dev, "runtime suspend failed: %d\n", error);
+ }
+
+ return 0;
+}
+
+static int cyapa_runtime_resume(struct device *dev)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int error;
+
+ if (!CYAPA_BOOTLOADER(cyapa) && cyapa->ops->set_power_mode) {
+ error = cyapa->ops->set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (error)
+ dev_warn(dev, "runtime resume failed: %d\n", error);
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+static const struct dev_pm_ops cyapa_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cyapa_suspend, cyapa_resume)
+ SET_RUNTIME_PM_OPS(cyapa_runtime_suspend, cyapa_runtime_resume, NULL)
+};
static const struct i2c_device_id cyapa_id_table[] = {
{ "cyapa", 0 },
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index 4a73081..eafeb79 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -262,6 +262,10 @@ struct cyapa {
/* power mode settings */
u8 suspend_power_mode;
u16 suspend_sleep_time;
+#ifdef CONFIG_PM_RUNTIME
+ u8 runtime_suspend_power_mode;
+ u16 runtime_suspend_sleep_time;
+#endif /* CONFIG_PM_RUNTIME */
u8 dev_pwr_mode;
u16 dev_sleep_time;
--
1.9.1
^ permalink raw reply related
* [PATCH v13 03/12] input: cyapa: add power management interfaces supported for the device
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Add suspend_scanrate_ms power management interfaces in device's
power group, so users or applications can control the power management
strategy of trackpad device as their requirements.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 111 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 72eb44a..055f1df 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -582,6 +582,96 @@ out:
return IRQ_HANDLED;
}
+/*
+ **************************************************************
+ * sysfs interface
+ **************************************************************
+*/
+#ifdef CONFIG_PM_SLEEP
+static ssize_t cyapa_show_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u8 pwr_cmd = cyapa->suspend_power_mode;
+ u16 sleep_time;
+ int len;
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+ pwr_cmd = cyapa->suspend_power_mode;
+ sleep_time = cyapa->suspend_sleep_time;
+ mutex_unlock(&cyapa->state_sync_lock);
+
+ if (pwr_cmd == PWR_MODE_BTN_ONLY) {
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", BTN_ONLY_MODE_NAME);
+ } else if (pwr_cmd == PWR_MODE_OFF) {
+ len = scnprintf(buf, PAGE_SIZE, "%s\n", OFF_MODE_NAME);
+ } else {
+ if (cyapa->gen == CYAPA_GEN3)
+ sleep_time = cyapa_pwr_cmd_to_sleep_time(pwr_cmd);
+ len = scnprintf(buf, PAGE_SIZE, "%u\n", sleep_time);
+ }
+
+ return len;
+}
+
+static ssize_t cyapa_update_suspend_scanrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ u16 sleep_time;
+ int error;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ if (sysfs_streq(buf, BTN_ONLY_MODE_NAME)) {
+ cyapa->suspend_power_mode = PWR_MODE_BTN_ONLY;
+ } else if (sysfs_streq(buf, OFF_MODE_NAME)) {
+ cyapa->suspend_power_mode = PWR_MODE_OFF;
+ } else if (!kstrtou16(buf, 10, &sleep_time)) {
+ cyapa->suspend_sleep_time = max_t(u16, sleep_time, 1000);
+ cyapa->suspend_power_mode =
+ cyapa_sleep_time_to_pwr_cmd(cyapa->suspend_sleep_time);
+ } else {
+ count = 0;
+ }
+
+ mutex_unlock(&cyapa->state_sync_lock);
+
+ if (!count)
+ dev_err(dev, "invalid suspend scanrate ms parameters\n");
+ return count ? count : -EINVAL;
+}
+
+static DEVICE_ATTR(suspend_scanrate_ms, S_IRUGO|S_IWUSR,
+ cyapa_show_suspend_scanrate,
+ cyapa_update_suspend_scanrate);
+
+static struct attribute *cyapa_power_wakeup_entries[] = {
+ &dev_attr_suspend_scanrate_ms.attr,
+ NULL,
+};
+
+static const struct attribute_group cyapa_power_wakeup_group = {
+ .name = power_group_name,
+ .attrs = cyapa_power_wakeup_entries,
+};
+
+static void cyapa_remove_power_wakeup_group(void *data)
+{
+ struct cyapa *cyapa = data;
+
+ sysfs_unmerge_group(&cyapa->client->dev.kobj,
+ &cyapa_power_wakeup_group);
+}
+#endif /* CONFIG_PM_SLEEP */
+
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -621,6 +711,27 @@ static int cyapa_probe(struct i2c_client *client,
return error;
}
+#ifdef CONFIG_PM_SLEEP
+ if (device_can_wakeup(dev)) {
+ error = sysfs_merge_group(&client->dev.kobj,
+ &cyapa_power_wakeup_group);
+ if (error) {
+ dev_err(dev, "failed to add power wakeup group: %d\n",
+ error);
+ return error;
+ }
+
+ error = devm_add_action(dev,
+ cyapa_remove_power_wakeup_group, cyapa);
+ if (error) {
+ cyapa_remove_power_wakeup_group(cyapa);
+ dev_err(dev, "failed to add power cleanup action: %d\n",
+ error);
+ return error;
+ }
+ }
+#endif /* CONFIG_PM_SLEEP */
+
error = devm_request_threaded_irq(dev, client->irq,
NULL, cyapa_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
--
1.9.1
^ permalink raw reply related
* [PATCH v13 02/12] input: cyapa: add gen5 trackpad device basic functions support
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1418116304-11392-1-git-send-email-dudley.dulixin@gmail.com>
Based on the cyapa core, add the gen5 trackpad device's basic functions
supported, so gen5 trackpad device can work with kernel input system.
And also based on the state parse interface, the cyapa driver can
automatically determine the attached is gen3 or gen5 protocol trackpad
device, then set the correct protocol to work with the attached
trackpad device.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/Makefile | 2 +-
drivers/input/mouse/cyapa.c | 13 +
drivers/input/mouse/cyapa.h | 1 +
drivers/input/mouse/cyapa_gen5.c | 1660 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1675 insertions(+), 1 deletion(-)
create mode 100644 drivers/input/mouse/cyapa_gen5.c
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 8bd950d..8a9c98e 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -24,7 +24,7 @@ obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o
obj-$(CONFIG_MOUSE_SYNAPTICS_USB) += synaptics_usb.o
obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o
-cyapatp-objs := cyapa.o cyapa_gen3.o
+cyapatp-objs := cyapa.o cyapa_gen3.o cyapa_gen5.o
psmouse-objs := psmouse-base.o synaptics.o focaltech.o
psmouse-$(CONFIG_MOUSE_PS2_ALPS) += alps.o
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index ec25445..72eb44a 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -164,6 +164,14 @@ static int cyapa_get_state(struct cyapa *cyapa)
if (!error)
goto out_detected;
}
+ if ((cyapa->gen == CYAPA_GEN_UNKNOWN ||
+ cyapa->gen == CYAPA_GEN5) &&
+ !smbus && even_addr) {
+ error = cyapa_gen5_ops.state_parse(cyapa,
+ status, BL_STATUS_SIZE);
+ if (!error)
+ goto out_detected;
+ }
/*
* Write 0x00 0x00 to trackpad device to force update its
@@ -247,6 +255,9 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
return error;
switch (cyapa->gen) {
+ case CYAPA_GEN5:
+ cyapa->ops = &cyapa_gen5_ops;
+ break;
case CYAPA_GEN3:
cyapa->ops = &cyapa_gen3_ops;
break;
@@ -483,6 +494,8 @@ static int cyapa_initialize(struct cyapa *cyapa)
if (cyapa_gen3_ops.initialize)
error = cyapa_gen3_ops.initialize(cyapa);
+ if (!error && cyapa_gen5_ops.initialize)
+ error = cyapa_gen5_ops.initialize(cyapa);
if (error)
return error;
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index d79eeb1..4a73081 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -312,5 +312,6 @@ u16 cyapa_pwr_cmd_to_sleep_time(u8 pwr_mode);
extern const char unique_str[];
extern const struct cyapa_dev_ops cyapa_gen3_ops;
+extern const struct cyapa_dev_ops cyapa_gen5_ops;
#endif
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
new file mode 100644
index 0000000..8509046
--- /dev/null
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -0,0 +1,1660 @@
+/*
+ * Cypress APA trackpad with I2C interface
+ *
+ * Author: Dudley Du <dudl@cypress.com>
+ *
+ * Copyright (C) 2014 Cypress Semiconductor, Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/unaligned/access_ok.h>
+#include "cyapa.h"
+
+
+/* Macro of Gen5 */
+#define RECORD_EVENT_NONE 0
+#define RECORD_EVENT_TOUCHDOWN 1
+#define RECORD_EVENT_DISPLACE 2
+#define RECORD_EVENT_LIFTOFF 3
+
+#define CYAPA_TSG_FLASH_MAP_BLOCK_SIZE 0x80
+#define CYAPA_TSG_IMG_FW_HDR_SIZE 13
+#define CYAPA_TSG_FW_ROW_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE)
+#define CYAPA_TSG_IMG_START_ROW_NUM 0x002e
+#define CYAPA_TSG_IMG_END_ROW_NUM 0x01fe
+#define CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM 0x01ff
+#define CYAPA_TSG_IMG_MAX_RECORDS (CYAPA_TSG_IMG_END_ROW_NUM - \
+ CYAPA_TSG_IMG_START_ROW_NUM + 1 + 1)
+#define CYAPA_TSG_IMG_READ_SIZE (CYAPA_TSG_FLASH_MAP_BLOCK_SIZE / 2)
+#define CYAPA_TSG_START_OF_APPLICATION 0x1700
+#define CYAPA_TSG_APP_INTEGRITY_SIZE 60
+#define CYAPA_TSG_FLASH_MAP_METADATA_SIZE 60
+#define CYAPA_TSG_BL_KEY_SIZE 8
+
+/* Macro definitions for Gen5 trackpad device. */
+#define GEN5_TOUCH_REPORT_HEAD_SIZE 7
+#define GEN5_TOUCH_REPORT_MAX_SIZE 127
+#define GEN5_BTN_REPORT_HEAD_SIZE 6
+#define GEN5_BTN_REPORT_MAX_SIZE 14
+#define GEN5_WAKEUP_EVENT_SIZE 4
+#define GEN5_RAW_DATA_HEAD_SIZE 24
+
+#define GEN5_BL_CMD_REPORT_ID 0x40
+#define GEN5_BL_RESP_REPORT_ID 0x30
+#define GEN5_APP_CMD_REPORT_ID 0x2f
+#define GEN5_APP_RESP_REPORT_ID 0x1f
+
+#define GEN5_APP_DEEP_SLEEP_REPORT_ID 0xf0
+#define GEN5_DEEP_SLEEP_RESP_LENGTH 5
+
+#define GEN5_PARAMETER_ACT_INTERVL_ID 0x4d
+#define GEN5_PARAMETER_ACT_INTERVL_SIZE 1
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_ID 0x4f
+#define GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE 2
+#define GEN5_PARAMETER_LP_INTRVL_ID 0x4c
+#define GEN5_PARAMETER_LP_INTRVL_SIZE 2
+
+#define GEN5_PARAMETER_DISABLE_PIP_REPORT 0x08
+
+#define GEN5_POWER_STATE_ACTIVE 0x01
+#define GEN5_POWER_STATE_LOOK_FOR_TOUCH 0x02
+#define GEN5_POWER_STATE_READY 0x03
+#define GEN5_POWER_STATE_IDLE 0x04
+#define GEN5_POWER_STATE_BTN_ONLY 0x05
+#define GEN5_POWER_STATE_OFF 0x06
+
+#define GEN5_DEEP_SLEEP_STATE_MASK 0x03
+#define GEN5_DEEP_SLEEP_STATE_ON 0x00
+#define GEN5_DEEP_SLEEP_STATE_OFF 0x01
+
+#define GEN5_DEEP_SLEEP_OPCODE 0x08
+#define GEN5_DEEP_SLEEP_OPCODE_MASK 0x0f
+
+#define GEN5_POWER_READY_MAX_INTRVL_TIME 50 /* Unit: ms */
+#define GEN5_POWER_IDLE_MAX_INTRVL_TIME 250 /* Unit: ms */
+
+#define GEN5_CMD_REPORT_ID_OFFSET 4
+
+#define GEN5_RESP_REPORT_ID_OFFSET 2
+#define GEN5_RESP_RSVD_OFFSET 3
+#define GEN5_RESP_RSVD_KEY 0x00
+#define GEN5_RESP_BL_SOP_OFFSET 4
+#define GEN5_SOP_KEY 0x01 /* Start of Packet */
+#define GEN5_EOP_KEY 0x17 /* End of Packet */
+#define GEN5_RESP_APP_CMD_OFFSET 4
+#define GET_GEN5_CMD_CODE(reg) ((reg) & 0x7f)
+
+#define VALID_CMD_RESP_HEADER(resp, cmd) \
+ (((resp)[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID) && \
+ ((resp)[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) && \
+ (GET_GEN5_CMD_CODE((resp)[GEN5_RESP_APP_CMD_OFFSET]) == (cmd)))
+
+#define GEN5_MIN_BL_CMD_LENGTH 13
+#define GEN5_MIN_BL_RESP_LENGTH 11
+#define GEN5_MIN_APP_CMD_LENGTH 7
+#define GEN5_MIN_APP_RESP_LENGTH 5
+#define GEN5_UNSUPPORTED_CMD_RESP_LENGTH 6
+
+#define GEN5_RESP_LENGTH_OFFSET 0x00
+#define GEN5_RESP_LENGTH_SIZE 2
+
+#define GEN5_HID_DESCRIPTOR_SIZE 32
+#define GEN5_BL_HID_REPORT_ID 0xff
+#define GEN5_APP_HID_REPORT_ID 0xf7
+#define GEN5_BL_MAX_OUTPUT_LENGTH 0x0100
+#define GEN5_APP_MAX_OUTPUT_LENGTH 0x00fe
+
+#define GEN5_BL_REPORT_DESCRIPTOR_SIZE 0x1d
+#define GEN5_BL_REPORT_DESCRIPTOR_ID 0xfe
+#define GEN5_APP_REPORT_DESCRIPTOR_SIZE 0xee
+#define GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE 0xfa
+#define GEN5_APP_REPORT_DESCRIPTOR_ID 0xf6
+
+#define GEN5_TOUCH_REPORT_ID 0x01
+#define GEN5_BTN_REPORT_ID 0x03
+#define GEN5_WAKEUP_EVENT_REPORT_ID 0x04
+#define GEN5_OLD_PUSH_BTN_REPORT_ID 0x05
+#define GEN5_PUSH_BTN_REPORT_ID 0x06
+
+#define GEN5_CMD_COMPLETE_SUCCESS(status) ((status) == 0x00)
+
+#define GEN5_BL_INITIATE_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_RESP_LEN 11
+#define GEN5_BL_FAIL_EXIT_STATUS_CODE 0x0c
+#define GEN5_BL_VERIFY_INTEGRITY_RESP_LEN 12
+#define GEN5_BL_INTEGRITY_CHEKC_PASS 0x00
+#define GEN5_BL_BLOCK_WRITE_RESP_LEN 11
+#define GEN5_BL_READ_APP_INFO_RESP_LEN 31
+#define GEN5_CMD_CALIBRATE 0x28
+#define CYAPA_SENSING_MODE_MUTUAL_CAP_FINE 0x00
+#define CYAPA_SENSING_MODE_SELF_CAP 0x02
+
+#define GEN5_CMD_RETRIEVE_DATA_STRUCTURE 0x24
+#define GEN5_RETRIEVE_MUTUAL_PWC_DATA 0x00
+#define GEN5_RETRIEVE_SELF_CAP_PWC_DATA 0x01
+
+#define GEN5_RETRIEVE_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_CMD_EXECUTE_PANEL_SCAN 0x2a
+#define GEN5_CMD_RETRIEVE_PANEL_SCAN 0x2b
+#define GEN5_PANEL_SCAN_MUTUAL_RAW_DATA 0x00
+#define GEN5_PANEL_SCAN_MUTUAL_BASELINE 0x01
+#define GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT 0x02
+#define GEN5_PANEL_SCAN_SELF_RAW_DATA 0x03
+#define GEN5_PANEL_SCAN_SELF_BASELINE 0x04
+#define GEN5_PANEL_SCAN_SELF_DIFFCOUNT 0x05
+
+#define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07
+
+#define GEN5_NUMBER_OF_TOUCH_OFFSET 5
+#define GEN5_NUMBER_OF_TOUCH_MASK 0x1f
+#define GEN5_BUTTONS_OFFSET 5
+#define GEN5_BUTTONS_MASK 0x0f
+#define GEN5_GET_EVENT_ID(reg) (((reg) >> 5) & 0x03)
+#define GEN5_GET_TOUCH_ID(reg) ((reg) & 0x1f)
+
+#define GEN5_PRODUCT_FAMILY_MASK 0xf000
+#define GEN5_PRODUCT_FAMILY_TRACKPAD 0x1000
+
+#define TSG_INVALID_CMD 0xff
+
+struct cyapa_gen5_touch_record {
+ /*
+ * Bit 7 - 3: reserved
+ * Bit 2 - 0: touch type;
+ * 0 : standard finger;
+ * 1 - 15 : reserved.
+ */
+ u8 touch_type;
+
+ /*
+ * Bit 7: indicates touch liftoff status.
+ * 0 : touch is currently on the panel.
+ * 1 : touch record indicates a liftoff.
+ * Bit 6 - 5: indicates an event associated with this touch instance
+ * 0 : no event
+ * 1 : touchdown
+ * 2 : significant displacement (> active distance)
+ * 3 : liftoff (record reports last known coordinates)
+ * Bit 4 - 0: An arbitrary ID tag associated with a finger
+ * to allow tracking a touch as it moves around the panel.
+ */
+ u8 touch_tip_event_id;
+
+ /* Bit 7 - 0 of X-axis corrinate of the touch in pixel. */
+ u8 x_lo;
+
+ /* Bit 15 - 8 of X-axis corrinate of the touch in pixel. */
+ u8 x_hi;
+
+ /* Bit 7 - 0 of Y-axis corrinate of the touch in pixel. */
+ u8 y_lo;
+
+ /* Bit 15 - 8 of Y-axis corrinate of the touch in pixel. */
+ u8 y_hi;
+
+ /* Touch intensity in counts, pressure value. */
+ u8 z;
+
+ /*
+ * The length of the major axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MAJOR).
+ */
+ u8 major_axis_len;
+
+ /*
+ * The length of the minor axis of the ellipse of contact between
+ * the finger and the panel (ABS_MT_TOUCH_MINOR).
+ */
+ u8 minor_axis_len;
+
+ /*
+ * The length of the major axis of the approaching tool.
+ * (ABS_MT_WIDTH_MAJOR)
+ */
+ u8 major_tool_len;
+
+ /*
+ * The length of the minor axis of the approaching tool.
+ * (ABS_MT_WIDTH_MINOR)
+ */
+ u8 minor_tool_len;
+
+ /*
+ * The angle between the panel vertical axis and
+ * the major axis of the contact ellipse. This value is an 8-bit
+ * signed integer. The range is -127 to +127 (corresponding to
+ * -90 degree and +90 degree respectively).
+ * The positive direction is clockwise from the vertical axis.
+ * If the ellipse of contact degenerates into a circle,
+ * orientation is reported as 0.
+ */
+ u8 orientation;
+} __packed;
+
+struct cyapa_gen5_report_data {
+ u8 report_head[GEN5_TOUCH_REPORT_HEAD_SIZE];
+ struct cyapa_gen5_touch_record touch_records[10];
+} __packed;
+
+struct cyapa_tsg_bin_image_data_record {
+ u8 flash_array_id;
+ __be16 row_number;
+ /* The number of bytes of flash data contained in this record. */
+ __be16 record_len;
+ /* The flash program data. */
+ u8 record_data[CYAPA_TSG_FW_ROW_SIZE];
+} __packed;
+
+struct cyapa_tsg_bin_image {
+ struct cyapa_tsg_bin_image_head image_head;
+ struct cyapa_tsg_bin_image_data_record records[0];
+} __packed;
+
+/* Variables to record latest gen5 trackpad power states. */
+#define GEN5_DEV_SET_PWR_STATE(cyapa, s) ((cyapa)->dev_pwr_mode = (s))
+#define GEN5_DEV_GET_PWR_STATE(cyapa) ((cyapa)->dev_pwr_mode)
+#define GEN5_DEV_SET_SLEEP_TIME(cyapa, t) ((cyapa)->dev_sleep_time = (t))
+#define GEN5_DEV_GET_SLEEP_TIME(cyapa) ((cyapa)->dev_sleep_time)
+#define GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) \
+ (((cyapa)->dev_sleep_time) == UNINIT_SLEEP_TIME)
+
+
+static int cyapa_gen5_initialize(struct cyapa *cyapa)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ init_completion(&gen5_pip->cmd_ready);
+ atomic_set(&gen5_pip->cmd_issued, 0);
+ mutex_init(&gen5_pip->cmd_lock);
+
+ gen5_pip->resp_sort_func = NULL;
+ gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+ gen5_pip->resp_data = NULL;
+ gen5_pip->resp_len = NULL;
+
+ cyapa->dev_pwr_mode = UNINIT_PWR_MODE;
+ cyapa->dev_sleep_time = UNINIT_SLEEP_TIME;
+
+ return 0;
+}
+
+/* Return negative errno, or else the number of bytes read. */
+static ssize_t cyapa_i2c_pip_read(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (size == 0)
+ return 0;
+
+ if (!buf || size > CYAPA_REG_MAP_SIZE)
+ return -EINVAL;
+
+ ret = i2c_master_recv(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return size;
+}
+
+/**
+ * Return a negative errno code else zero on success.
+ */
+static ssize_t cyapa_i2c_pip_write(struct cyapa *cyapa, u8 *buf, size_t size)
+{
+ int ret;
+
+ if (!buf || !size)
+ return -EINVAL;
+
+ ret = i2c_master_send(cyapa->client, buf, size);
+
+ if (ret != size)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+/**
+ * This function is aimed to dump all not read data in Gen5 trackpad
+ * before send any command, otherwise, the interrupt line will be blocked.
+ */
+int cyapa_empty_pip_output_data(struct cyapa *cyapa,
+ u8 *buf, int *len, cb_sort func)
+{
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+ int length;
+ int report_count;
+ int empty_count;
+ int buf_len;
+ int error;
+
+ buf_len = 0;
+ if (len) {
+ buf_len = (*len < CYAPA_REG_MAP_SIZE) ?
+ *len : CYAPA_REG_MAP_SIZE;
+ *len = 0;
+ }
+
+ report_count = 8; /* max 7 pending data before command response data */
+ empty_count = 0;
+ do {
+ /*
+ * Depnding on testing in cyapa driver, there are max 5 "02 00"
+ * packets between two valid bufferred data report in firmware.
+ * So in order to dump all buffered data out and
+ * make interrupt line release for reassert again,
+ * we must set the empty_count check value bigger than 5 to
+ * make it work. Otherwise, in some situation,
+ * the interrupt line may unable to reactive again,
+ * which will cause trackpad device unable to
+ * report data any more.
+ * for example, it may happen in EFT and ESD testing.
+ */
+ if (empty_count > 5)
+ return 0;
+
+ error = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ if (error < 0)
+ return error;
+
+ length = get_unaligned_le16(gen5_pip->empty_buf);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ continue;
+ } else if (length > CYAPA_REG_MAP_SIZE) {
+ /* Should not happen */
+ return -EINVAL;
+ } else if (length == 0) {
+ /* Application or bootloader launch data polled out. */
+ length = GEN5_RESP_LENGTH_SIZE;
+ if (buf && buf_len && func &&
+ func(cyapa, gen5_pip->empty_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, gen5_pip->empty_buf, length);
+ *len = length;
+ /* Response found, success. */
+ return 0;
+ }
+ continue;
+ }
+
+ error = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+ if (error < 0)
+ return error;
+
+ report_count--;
+ empty_count = 0;
+ length = get_unaligned_le16(gen5_pip->empty_buf);
+ if (length <= GEN5_RESP_LENGTH_SIZE) {
+ empty_count++;
+ } else if (buf && buf_len && func &&
+ func(cyapa, gen5_pip->empty_buf, length)) {
+ length = min(buf_len, length);
+ memcpy(buf, gen5_pip->empty_buf, length);
+ *len = length;
+ /* Response found, success. */
+ return 0;
+ }
+
+ error = -EINVAL;
+ } while (report_count);
+
+ return error;
+}
+
+static int cyapa_do_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, size_t cmd_len,
+ unsigned long timeout)
+{
+ int error;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ /* Wait for interrupt to set ready completion */
+ init_completion(&gen5_pip->cmd_ready);
+
+ atomic_inc(&gen5_pip->cmd_issued);
+ error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (error) {
+ atomic_dec(&gen5_pip->cmd_issued);
+ return (error < 0) ? error : -EIO;
+ }
+
+ /* Wait for interrupt to indicate command is completed. */
+ timeout = wait_for_completion_timeout(&gen5_pip->cmd_ready,
+ msecs_to_jiffies(timeout));
+ if (timeout == 0) {
+ atomic_dec(&gen5_pip->cmd_issued);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int cyapa_do_i2c_pip_cmd_polling(
+ struct cyapa *cyapa,
+ u8 *cmd, size_t cmd_len,
+ u8 *resp_data, int *resp_len,
+ unsigned long timeout,
+ cb_sort func)
+{
+ int error;
+ int tries;
+ int length;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ atomic_inc(&gen5_pip->cmd_issued);
+ error = cyapa_i2c_pip_write(cyapa, cmd, cmd_len);
+ if (error) {
+ atomic_dec(&gen5_pip->cmd_issued);
+ return error < 0 ? error : -EIO;
+ }
+
+ tries = timeout / 5;
+ length = resp_len ? *resp_len : 0;
+ if (resp_data && resp_len && length != 0 && func) {
+ do {
+ usleep_range(3000, 5000);
+ *resp_len = length;
+ error = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (error || *resp_len == 0)
+ continue;
+ else
+ break;
+ } while (--tries > 0);
+ if ((error || *resp_len == 0) || tries <= 0)
+ error = error ? error : -ETIMEDOUT;
+ }
+
+ atomic_dec(&gen5_pip->cmd_issued);
+ return error;
+}
+
+static int cyapa_i2c_pip_cmd_irq_sync(
+ struct cyapa *cyapa,
+ u8 *cmd, int cmd_len,
+ u8 *resp_data, int *resp_len,
+ unsigned long timeout,
+ cb_sort func,
+ bool irq_mode)
+{
+ int error;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ if (!cmd || !cmd_len)
+ return -EINVAL;
+
+ /* Commands must be serialized. */
+ error = mutex_lock_interruptible(&gen5_pip->cmd_lock);
+ if (error)
+ return error;
+
+ gen5_pip->resp_sort_func = func;
+ gen5_pip->resp_data = resp_data;
+ gen5_pip->resp_len = resp_len;
+
+ if (cmd_len >= GEN5_MIN_APP_CMD_LENGTH &&
+ cmd[4] == GEN5_APP_CMD_REPORT_ID) {
+ /* Application command */
+ gen5_pip->in_progress_cmd = cmd[6] & 0x7f;
+ } else if (cmd_len >= GEN5_MIN_BL_CMD_LENGTH &&
+ cmd[4] == GEN5_BL_CMD_REPORT_ID) {
+ /* Bootloader command */
+ gen5_pip->in_progress_cmd = cmd[7];
+ }
+
+ /* Send command data, wait and read output response data's length. */
+ if (irq_mode) {
+ gen5_pip->is_irq_mode = true;
+ error = cyapa_do_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len,
+ timeout);
+ if (error == -ETIMEDOUT && resp_data &&
+ resp_len && *resp_len != 0 && func) {
+ /*
+ * For some old version, there was no interrupt for
+ * the command response data, so need to poll here
+ * to try to get the response data.
+ */
+ error = cyapa_empty_pip_output_data(cyapa,
+ resp_data, resp_len, func);
+ if (error || *resp_len == 0)
+ error = error ? error : -ETIMEDOUT;
+ }
+ } else {
+ gen5_pip->is_irq_mode = false;
+ error = cyapa_do_i2c_pip_cmd_polling(cyapa, cmd, cmd_len,
+ resp_data, resp_len, timeout, func);
+ }
+
+ gen5_pip->resp_sort_func = NULL;
+ gen5_pip->resp_data = NULL;
+ gen5_pip->resp_len = NULL;
+ gen5_pip->in_progress_cmd = TSG_INVALID_CMD;
+
+ mutex_unlock(&gen5_pip->cmd_lock);
+ return error;
+}
+
+bool cyapa_gen5_sort_tsg_pip_bl_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ if (!data || len < GEN5_MIN_BL_RESP_LENGTH)
+ return false;
+
+ /* Bootloader input report id 30h */
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ data[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY)
+ return true;
+
+ return false;
+}
+
+bool cyapa_gen5_sort_tsg_pip_app_resp_data(struct cyapa *cyapa,
+ u8 *data, int len)
+{
+ int resp_len;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ if (!data || len < GEN5_MIN_APP_RESP_LENGTH)
+ return false;
+
+ if (data[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_APP_RESP_REPORT_ID &&
+ data[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY) {
+ resp_len = get_unaligned_le16(&data[GEN5_RESP_LENGTH_OFFSET]);
+ if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) == 0x00 &&
+ resp_len == GEN5_UNSUPPORTED_CMD_RESP_LENGTH &&
+ data[5] == gen5_pip->in_progress_cmd) {
+ /* Unsupported command code */
+ return false;
+ } else if (GET_GEN5_CMD_CODE(data[GEN5_RESP_APP_CMD_OFFSET]) ==
+ gen5_pip->in_progress_cmd) {
+ /* Correct command response received */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool cyapa_gen5_sort_application_launch_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ /*
+ * After reset or power on, trackpad device always sets to 0x00 0x00
+ * to indicate a reset or power on event.
+ */
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_hid_descriptor_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ int resp_len;
+ int max_output_len;
+
+ /* Check hid descriptor. */
+ if (len != GEN5_HID_DESCRIPTOR_SIZE)
+ return false;
+
+ resp_len = get_unaligned_le16(&buf[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&buf[16]);
+ if (resp_len == GEN5_HID_DESCRIPTOR_SIZE) {
+ if (buf[GEN5_RESP_REPORT_ID_OFFSET] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Descriptor */
+ return true;
+ } else if (buf[2] == GEN5_APP_HID_REPORT_ID &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Descriptor */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool cyapa_gen5_sort_deep_sleep_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ if (len == GEN5_DEEP_SLEEP_RESP_LENGTH &&
+ buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_DEEP_SLEEP_REPORT_ID &&
+ (buf[4] & GEN5_DEEP_SLEEP_OPCODE_MASK) ==
+ GEN5_DEEP_SLEEP_OPCODE)
+ return true;
+ return false;
+}
+
+static int gen5_idle_state_parse(struct cyapa *cyapa)
+{
+ int ret;
+ int error;
+ int length;
+ u8 cmd[2];
+ u8 resp_data[GEN5_HID_DESCRIPTOR_SIZE];
+ int max_output_len;
+
+ /*
+ * Dump all buffered data firstly for the situation
+ * when the trackpad is just power on the cyapa go here.
+ */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ memset(resp_data, 0, sizeof(resp_data));
+ ret = cyapa_i2c_pip_read(cyapa, resp_data, 3);
+ if (ret != 3)
+ return ret < 0 ? ret : -EIO;
+
+ length = get_unaligned_le16(&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* Normal state of Gen5 with no data to respose */
+ cyapa->gen = CYAPA_GEN5;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /* Read description from trackpad device */
+ cmd[0] = 0x01;
+ cmd[1] = 0x00;
+ length = GEN5_HID_DESCRIPTOR_SIZE;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, GEN5_RESP_LENGTH_SIZE,
+ resp_data, &length,
+ 300,
+ cyapa_gen5_sort_hid_descriptor_data,
+ false);
+ if (error)
+ return error;
+
+ length = get_unaligned_le16(
+ &resp_data[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_HID_REPORT_ID) &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if ((length == GEN5_HID_DESCRIPTOR_SIZE ||
+ length == GEN5_RESP_LENGTH_SIZE) &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_HID_REPORT_ID) &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read */
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else {
+ /* Should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ }
+ }
+
+ return 0;
+}
+
+static int gen5_hid_description_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+ int ret;
+ int length;
+ u8 resp_data[32];
+ int max_output_len;
+
+ /* 0x20 0x00 0xF7 is Gen5 Application HID Description Header;
+ * 0x20 0x00 0xFF is Gen5 Booloader HID Description Header.
+ *
+ * Must read HID Description content through out,
+ * otherwise Gen5 trackpad cannot response next command
+ * or report any touch or button data.
+ */
+ ret = cyapa_i2c_pip_read(cyapa, resp_data,
+ GEN5_HID_DESCRIPTOR_SIZE);
+ if (ret != GEN5_HID_DESCRIPTOR_SIZE)
+ return ret < 0 ? ret : -EIO;
+ length = get_unaligned_le16(&resp_data[GEN5_RESP_LENGTH_OFFSET]);
+ max_output_len = get_unaligned_le16(&resp_data[16]);
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_HID_REPORT_ID) {
+ /*
+ * BL mode HID Description has been previously
+ * read out.
+ */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else {
+ /*
+ * APP mode HID Description has been previously
+ * read out.
+ */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ }
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ resp_data[2] == GEN5_BL_HID_REPORT_ID &&
+ max_output_len == GEN5_BL_MAX_OUTPUT_LENGTH) {
+ /* BL mode HID Description read. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ (resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_HID_REPORT_ID) &&
+ max_output_len == GEN5_APP_MAX_OUTPUT_LENGTH) {
+ /* APP mode HID Description read. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else {
+ /* Should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ }
+
+ return 0;
+}
+
+static int gen5_report_data_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+ int length;
+
+ length = get_unaligned_le16(®_data[GEN5_RESP_LENGTH_OFFSET]);
+ switch (reg_data[GEN5_RESP_REPORT_ID_OFFSET]) {
+ case GEN5_TOUCH_REPORT_ID:
+ if (length < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ length > GEN5_TOUCH_REPORT_MAX_SIZE)
+ return -EINVAL;
+ break;
+ case GEN5_BTN_REPORT_ID:
+ case GEN5_OLD_PUSH_BTN_REPORT_ID:
+ case GEN5_PUSH_BTN_REPORT_ID:
+ if (length < GEN5_BTN_REPORT_HEAD_SIZE ||
+ length > GEN5_BTN_REPORT_MAX_SIZE)
+ return -EINVAL;
+ break;
+ case GEN5_WAKEUP_EVENT_REPORT_ID:
+ if (length != GEN5_WAKEUP_EVENT_SIZE)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ return 0;
+}
+
+static int gen5_cmd_resp_header_parse(struct cyapa *cyapa, u8 *reg_data)
+{
+ int ret;
+ int length;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ /*
+ * Must read report data through out,
+ * otherwise Gen5 trackpad cannot response next command
+ * or report any touch or button data.
+ */
+ length = get_unaligned_le16(®_data[GEN5_RESP_LENGTH_OFFSET]);
+ ret = cyapa_i2c_pip_read(cyapa, gen5_pip->empty_buf, length);
+ if (ret != length)
+ return ret < 0 ? ret : -EIO;
+
+ if (length == GEN5_RESP_LENGTH_SIZE) {
+ /* Previous command has read the data through out. */
+ if (reg_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ }
+ } else if ((gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID) &&
+ (gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+ GEN5_RESP_RSVD_KEY) &&
+ (gen5_pip->empty_buf[GEN5_RESP_BL_SOP_OFFSET] ==
+ GEN5_SOP_KEY) &&
+ (gen5_pip->empty_buf[length - 1] ==
+ GEN5_EOP_KEY)) {
+ /* Gen5 BL command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (gen5_pip->empty_buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_APP_RESP_REPORT_ID &&
+ gen5_pip->empty_buf[GEN5_RESP_RSVD_OFFSET] ==
+ GEN5_RESP_RSVD_KEY) {
+ /* Gen5 APP command response data detected */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else {
+ /* Should not happen!!! */
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
+{
+ int length;
+
+ if (!reg_data || len < 3)
+ return -EINVAL;
+
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+
+ /* Parse based on Gen5 characteristic registers and bits */
+ length = get_unaligned_le16(®_data[GEN5_RESP_LENGTH_OFFSET]);
+ if (length == 0 || length == GEN5_RESP_LENGTH_SIZE) {
+ gen5_idle_state_parse(cyapa);
+ } else if (length == GEN5_HID_DESCRIPTOR_SIZE &&
+ (reg_data[2] == GEN5_BL_HID_REPORT_ID ||
+ reg_data[2] == GEN5_APP_HID_REPORT_ID)) {
+ gen5_hid_description_header_parse(cyapa, reg_data);
+ } else if ((length == GEN5_APP_REPORT_DESCRIPTOR_SIZE ||
+ length == GEN5_APP_CONTRACT_REPORT_DESCRIPTOR_SIZE) &&
+ reg_data[2] == GEN5_APP_REPORT_DESCRIPTOR_ID) {
+ /* 0xEE 0x00 0xF6 is Gen5 APP Report Description header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+ } else if (length == GEN5_BL_REPORT_DESCRIPTOR_SIZE &&
+ reg_data[2] == GEN5_BL_REPORT_DESCRIPTOR_ID) {
+ /* 0x1D 0x00 0xFE is Gen5 BL Report Descriptior header. */
+ cyapa->gen = CYAPA_GEN5;
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ } else if (reg_data[2] == GEN5_TOUCH_REPORT_ID ||
+ reg_data[2] == GEN5_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_PUSH_BTN_REPORT_ID ||
+ reg_data[2] == GEN5_WAKEUP_EVENT_REPORT_ID) {
+ gen5_report_data_header_parse(cyapa, reg_data);
+ } else if (reg_data[2] == GEN5_BL_RESP_REPORT_ID ||
+ reg_data[2] == GEN5_APP_RESP_REPORT_ID) {
+ gen5_cmd_resp_header_parse(cyapa, reg_data);
+ }
+
+ if (cyapa->gen == CYAPA_GEN5) {
+ /*
+ * Must read the content (e.g.: Report Description and so on)
+ * from trackpad device through out. Otherwise,
+ * Gen5 trackpad cannot response to next command or
+ * report any touch or button data later.
+ */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ if (cyapa->state == CYAPA_STATE_GEN5_APP ||
+ cyapa->state == CYAPA_STATE_GEN5_BL)
+ return 0;
+ }
+
+ return -EAGAIN;
+}
+
+bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
+{
+ if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
+ return false;
+
+ if (buf[0] == 0 && buf[1] == 0)
+ return true;
+
+ /* Exit bootloader failed for some reason. */
+ if (len == GEN5_BL_FAIL_EXIT_RESP_LEN &&
+ buf[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID &&
+ buf[GEN5_RESP_RSVD_OFFSET] == GEN5_RESP_RSVD_KEY &&
+ buf[GEN5_RESP_BL_SOP_OFFSET] == GEN5_SOP_KEY &&
+ buf[10] == GEN5_EOP_KEY)
+ return true;
+
+ return false;
+}
+
+static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
+{
+ int error;
+ u8 resp_data[11];
+ int resp_len;
+ u8 bl_gen5_bl_exit[] = { 0x04, 0x00,
+ 0x0B, 0x00, 0x40, 0x00, 0x01, 0x3b, 0x00, 0x00,
+ 0x20, 0xc7, 0x17
+ };
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_gen5_bl_exit, sizeof(bl_gen5_bl_exit),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_bl_exit_data, false);
+ if (error)
+ return error;
+
+ if (resp_len == GEN5_BL_FAIL_EXIT_RESP_LEN ||
+ resp_data[GEN5_RESP_REPORT_ID_OFFSET] ==
+ GEN5_BL_RESP_REPORT_ID)
+ return -EAGAIN;
+
+ if (resp_data[0] == 0x00 && resp_data[1] == 0x00)
+ return 0;
+
+ return -ENODEV;
+}
+
+static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
+{
+ u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
+ u8 resp_data[6];
+ int resp_len;
+ int error;
+
+ cmd[7] = power_state;
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x08) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 interval_time)
+{
+ u8 cmd[13];
+ int cmd_len;
+ u8 resp_data[7];
+ int resp_len;
+ u8 parameter_size;
+ int error;
+
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ cmd_len = 7 + parameter_size; /* Not incuding 2 bytes address */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(cmd_len, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* Set parameter command code */
+ cmd[7] = parameter_id;
+ cmd[8] = parameter_size;
+ put_unaligned_le16(interval_time, &cmd[9]);
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, cmd_len + 2,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x06) ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] != parameter_size)
+ return error < 0 ? error : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_get_interval_time(struct cyapa *cyapa,
+ u8 parameter_id, u16 *interval_time)
+{
+ u8 cmd[8];
+ u8 resp_data[11];
+ int resp_len;
+ u8 parameter_size;
+ u16 mask, i;
+ int error;
+
+ *interval_time = 0;
+ switch (parameter_id) {
+ case GEN5_PARAMETER_ACT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_ACT_LFT_INTERVL_ID:
+ parameter_size = GEN5_PARAMETER_ACT_LFT_INTERVL_SIZE;
+ break;
+ case GEN5_PARAMETER_LP_INTRVL_ID:
+ parameter_size = GEN5_PARAMETER_LP_INTRVL_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x06;
+ cmd[3] = 0x00;
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x05; /* Get parameter command code */
+ cmd[7] = parameter_id;
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x05) ||
+ resp_data[5] != parameter_id ||
+ resp_data[6] == 0)
+ return error < 0 ? error : -EINVAL;
+
+ mask = 0;
+ for (i = 0; i < parameter_size; i++)
+ mask |= (0xff << (i * 8));
+ *interval_time = get_unaligned_le16(&resp_data[7]) & mask;
+
+ return 0;
+}
+
+static int cyapa_gen5_disable_pip_report(struct cyapa *cyapa)
+{
+ u8 cmd[10];
+ u8 resp_data[7];
+ int resp_len;
+ int error;
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ put_unaligned_le16(8, &cmd[2]);
+ cmd[4] = 0x2f;
+ cmd[5] = 0x00;
+ cmd[6] = 0x06; /* Set parameter command code */
+ cmd[7] = GEN5_PARAMETER_DISABLE_PIP_REPORT;
+ cmd[8] = 0x01;
+ cmd[9] = 0x01;
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, 10,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, false);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x06) ||
+ resp_data[5] != GEN5_PARAMETER_DISABLE_PIP_REPORT ||
+ resp_data[6] != 0x01)
+ return error < 0 ? error : -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_deep_sleep(struct cyapa *cyapa, u8 state)
+{
+ u8 cmd[4] = { 0x05, 0x00, 0x00, 0x08};
+ u8 resp_data[5];
+ int resp_len;
+ int error;
+
+ cmd[0] = 0x05;
+ cmd[1] = 0x00;
+ cmd[2] = state & GEN5_DEEP_SLEEP_STATE_MASK;
+ cmd[3] = 0x08;
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_deep_sleep_data, false);
+ if (error || ((resp_data[3] & GEN5_DEEP_SLEEP_STATE_MASK) != state))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
+ u8 power_mode, u16 sleep_time)
+{
+ struct device *dev = &cyapa->client->dev;
+ u8 power_state;
+ int error;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return 0;
+
+ /* Dump all the report data before do power mode commmands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ if (GEN5_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) {
+ /*
+ * Assume TP in deep sleep mode when driver is loaded,
+ * avoid driver unload and reload command IO issue caused by TP
+ * has been set into deep sleep mode when unloading.
+ */
+ GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+ }
+
+ if (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) &&
+ GEN5_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF)
+ if (cyapa_gen5_get_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ &cyapa->dev_sleep_time) != 0)
+ GEN5_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME);
+
+ if (GEN5_DEV_GET_PWR_STATE(cyapa) == power_mode) {
+ if (power_mode == PWR_MODE_OFF ||
+ power_mode == PWR_MODE_FULL_ACTIVE ||
+ power_mode == PWR_MODE_BTN_ONLY ||
+ GEN5_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) {
+ /* Has in correct power mode state, early return. */
+ return 0;
+ }
+ }
+
+ if (power_mode == PWR_MODE_OFF) {
+ error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_OFF);
+ if (error) {
+ dev_err(dev, "enter deep sleep fail: %d\n", error);
+ return error;
+ }
+
+ GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
+ return 0;
+ }
+
+ /*
+ * When trackpad in power off mode, it cannot change to other power
+ * state directly, must be wake up from sleep firstly, then
+ * continue to do next power sate change.
+ */
+ if (GEN5_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) {
+ error = cyapa_gen5_deep_sleep(cyapa, GEN5_DEEP_SLEEP_STATE_ON);
+ if (error) {
+ dev_err(dev, "deep sleep wake fail: %d\n", error);
+ return error;
+ }
+ }
+
+ if (power_mode == PWR_MODE_FULL_ACTIVE) {
+ error = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_ACTIVE);
+ if (error) {
+ dev_err(dev, "change to active fail: %d\n", error);
+ return error;
+ }
+
+ GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE);
+ } else if (power_mode == PWR_MODE_BTN_ONLY) {
+ error = cyapa_gen5_change_power_state(cyapa,
+ GEN5_POWER_STATE_BTN_ONLY);
+ if (error) {
+ dev_err(dev, "fail to button only mode: %d\n", error);
+ return error;
+ }
+
+ GEN5_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY);
+ } else {
+ /*
+ * Continue to change power mode even failed to set
+ * interval time, it won't affect the power mode change.
+ * except the sleep interval time is not correct.
+ */
+ if (GEN5_DEV_UNINIT_SLEEP_TIME(cyapa) ||
+ sleep_time != GEN5_DEV_GET_SLEEP_TIME(cyapa))
+ if (cyapa_gen5_set_interval_time(cyapa,
+ GEN5_PARAMETER_LP_INTRVL_ID,
+ sleep_time) == 0)
+ GEN5_DEV_SET_SLEEP_TIME(cyapa, sleep_time);
+
+ if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
+ power_state = GEN5_POWER_STATE_READY;
+ else
+ power_state = GEN5_POWER_STATE_IDLE;
+ error = cyapa_gen5_change_power_state(cyapa, power_state);
+ if (error) {
+ dev_err(dev, "set power state to 0x%02x failed: %d\n",
+ power_state, error);
+ return error;
+ }
+
+ /*
+ * Disable pip report for a little time, firmware will
+ * re-enable it automatically. It's used to fix the issue
+ * that trackpad unable to report signal to wake system up
+ * in the special situation that system is in suspending, and
+ * at the same time, user touch trackpad to wake system up.
+ * This function can avoid the data to be buffured when system
+ * is suspending which may cause interrput line unable to be
+ * asserted again.
+ */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+ cyapa_gen5_disable_pip_report(cyapa);
+
+ GEN5_DEV_SET_PWR_STATE(cyapa,
+ cyapa_sleep_time_to_pwr_cmd(sleep_time));
+ }
+
+ return 0;
+}
+
+static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
+ u8 *buf, int len)
+{
+ /* Check the report id and command code */
+ if (VALID_CMD_RESP_HEADER(buf, 0x02))
+ return true;
+
+ return false;
+}
+
+static int cyapa_gen5_bl_query_data(struct cyapa *cyapa)
+{
+ u8 cmd[16];
+ int cmd_len;
+ u8 resp_data[GEN5_BL_READ_APP_INFO_RESP_LEN];
+ int resp_len;
+ int error;
+
+ /* Read application information. */
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0b;
+ cmd[3] = 0x00;
+ cmd[4] = 0x40;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_SOP_KEY;
+ cmd[7] = 0x3c; /* Read application information command code */
+ cmd[8] = 0x00;
+ cmd[9] = 0x00;
+ cmd[10] = 0xb0;
+ cmd[11] = 0x42;
+ cmd[12] = GEN5_EOP_KEY;
+ cmd_len = 13;
+ resp_len = GEN5_BL_READ_APP_INFO_RESP_LEN;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data, false);
+ if (error || resp_len != GEN5_BL_READ_APP_INFO_RESP_LEN ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EIO;
+
+ memcpy(&cyapa->product_id[0], &resp_data[8], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[13], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[19], 2);
+ cyapa->product_id[15] = '\0';
+
+ cyapa->fw_maj_ver = resp_data[22];
+ cyapa->fw_min_ver = resp_data[23];
+
+ return 0;
+}
+
+static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
+{
+ u8 resp_data[71];
+ int resp_len;
+ u8 get_system_information[] = {
+ 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x02
+ };
+ u16 product_family;
+ int error;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ get_system_information, sizeof(get_system_information),
+ resp_data, &resp_len,
+ 2000, cyapa_gen5_sort_system_info_data, false);
+ if (error || resp_len < sizeof(resp_data))
+ return error ? error : -EIO;
+
+ product_family = get_unaligned_le16(&resp_data[7]);
+ if ((product_family & GEN5_PRODUCT_FAMILY_MASK) !=
+ GEN5_PRODUCT_FAMILY_TRACKPAD)
+ return -EINVAL;
+
+ cyapa->fw_maj_ver = resp_data[15];
+ cyapa->fw_min_ver = resp_data[16];
+
+ cyapa->electrodes_x = resp_data[52];
+ cyapa->electrodes_y = resp_data[53];
+
+ cyapa->physical_size_x = get_unaligned_le16(&resp_data[54]) / 100;
+ cyapa->physical_size_y = get_unaligned_le16(&resp_data[56]) / 100;
+
+ cyapa->max_abs_x = get_unaligned_le16(&resp_data[58]);
+ cyapa->max_abs_y = get_unaligned_le16(&resp_data[60]);
+
+ cyapa->max_z = get_unaligned_le16(&resp_data[62]);
+
+ cyapa->x_origin = resp_data[64] & 0x01;
+ cyapa->y_origin = resp_data[65] & 0x01;
+
+ cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK;
+
+ memcpy(&cyapa->product_id[0], &resp_data[33], 5);
+ cyapa->product_id[5] = '-';
+ memcpy(&cyapa->product_id[6], &resp_data[38], 6);
+ cyapa->product_id[12] = '-';
+ memcpy(&cyapa->product_id[13], &resp_data[44], 2);
+ cyapa->product_id[15] = '\0';
+
+ if (!cyapa->electrodes_x || !cyapa->electrodes_y ||
+ !cyapa->physical_size_x || !cyapa->physical_size_y ||
+ !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cyapa_gen5_do_operational_check(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ int error;
+
+ if (cyapa->gen != CYAPA_GEN5)
+ return -ENODEV;
+
+ switch (cyapa->state) {
+ case CYAPA_STATE_GEN5_BL:
+ error = cyapa_gen5_bl_exit(cyapa);
+ if (error) {
+ /* Rry to update trackpad product information. */
+ cyapa_gen5_bl_query_data(cyapa);
+ goto out;
+ }
+
+ cyapa->state = CYAPA_STATE_GEN5_APP;
+
+ case CYAPA_STATE_GEN5_APP:
+ /*
+ * If trackpad device in deep sleep mode,
+ * the app command will fail.
+ * So always try to reset trackpad device to full active when
+ * the device state is requeried.
+ */
+ error = cyapa_gen5_set_power_mode(cyapa,
+ PWR_MODE_FULL_ACTIVE, 0);
+ if (error)
+ dev_warn(dev, "%s: failed to set power active mode.\n",
+ __func__);
+
+ /* Get trackpad product information. */
+ error = cyapa_gen5_get_query_data(cyapa);
+ if (error)
+ goto out;
+ /* Only support product ID starting with CYTRA */
+ if (memcmp(cyapa->product_id, unique_str,
+ strlen(unique_str)) != 0) {
+ dev_err(dev, "%s: unknown product ID (%s)\n",
+ __func__, cyapa->product_id);
+ error = -EINVAL;
+ }
+ break;
+ default:
+ error = -EINVAL;
+ }
+
+out:
+ return error;
+}
+
+/*
+ * Return false, do not continue process
+ * Return true, continue process.
+ */
+static bool cyapa_gen5_irq_cmd_handler(struct cyapa *cyapa)
+{
+ int length;
+ struct cyapa_gen5_cmd_states *gen5_pip = &cyapa->cmd_states.gen5;
+
+ if (atomic_read(&gen5_pip->cmd_issued)) {
+ /* Polling command response data. */
+ if (gen5_pip->is_irq_mode == false)
+ return false;
+
+ /*
+ * Read out all none command response data.
+ * these output data may caused by user put finger on
+ * trackpad when host waiting the command response.
+ */
+ cyapa_i2c_pip_read(cyapa, gen5_pip->irq_cmd_buf,
+ GEN5_RESP_LENGTH_SIZE);
+ length = get_unaligned_le16(gen5_pip->irq_cmd_buf);
+ length = (length <= GEN5_RESP_LENGTH_SIZE) ?
+ GEN5_RESP_LENGTH_SIZE : length;
+ if (length > GEN5_RESP_LENGTH_SIZE)
+ cyapa_i2c_pip_read(cyapa,
+ gen5_pip->irq_cmd_buf, length);
+
+ if (!(gen5_pip->resp_sort_func &&
+ gen5_pip->resp_sort_func(cyapa,
+ gen5_pip->irq_cmd_buf, length))) {
+ /*
+ * Cover the Gen5 V1 firmware issue.
+ * The issue is there is no interrut will be
+ * asserted to notityf host to read a command
+ * data out when always has finger touch on
+ * trackpad during the command is issued to
+ * trackad device.
+ * This issue has the scenario is that,
+ * user always has his fingers touched on
+ * trackpad device when booting/rebooting
+ * their chrome book.
+ */
+ length = *gen5_pip->resp_len;
+ cyapa_empty_pip_output_data(cyapa,
+ gen5_pip->resp_data,
+ &length,
+ gen5_pip->resp_sort_func);
+ if (gen5_pip->resp_len && length != 0) {
+ *gen5_pip->resp_len = length;
+ atomic_dec(&gen5_pip->cmd_issued);
+ complete(&gen5_pip->cmd_ready);
+ }
+ return false;
+ }
+
+ if (gen5_pip->resp_data && gen5_pip->resp_len) {
+ *gen5_pip->resp_len = (*gen5_pip->resp_len < length) ?
+ *gen5_pip->resp_len : length;
+ memcpy(gen5_pip->resp_data, gen5_pip->irq_cmd_buf,
+ *gen5_pip->resp_len);
+ }
+ atomic_dec(&gen5_pip->cmd_issued);
+ complete(&gen5_pip->cmd_ready);
+ return false;
+ }
+
+ return true;
+}
+
+static void cyapa_gen5_report_buttons(struct cyapa *cyapa,
+ const struct cyapa_gen5_report_data *report_data)
+{
+ struct input_dev *input = cyapa->input;
+ u8 buttons = report_data->report_head[GEN5_BUTTONS_OFFSET];
+
+ buttons = (buttons << CAPABILITY_BTN_SHIFT) & CAPABILITY_BTN_MASK;
+
+ if (cyapa->btn_capability & CAPABILITY_LEFT_BTN_MASK) {
+ input_report_key(input, BTN_LEFT,
+ !!(buttons & CAPABILITY_LEFT_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_MIDDLE_BTN_MASK) {
+ input_report_key(input, BTN_MIDDLE,
+ !!(buttons & CAPABILITY_MIDDLE_BTN_MASK));
+ }
+ if (cyapa->btn_capability & CAPABILITY_RIGHT_BTN_MASK) {
+ input_report_key(input, BTN_RIGHT,
+ !!(buttons & CAPABILITY_RIGHT_BTN_MASK));
+ }
+
+ input_sync(input);
+}
+
+static void cyapa_gen5_report_slot_data(struct cyapa *cyapa,
+ const struct cyapa_gen5_touch_record *touch)
+{
+ struct input_dev *input = cyapa->input;
+ u8 event_id = GEN5_GET_EVENT_ID(touch->touch_tip_event_id);
+ int slot = GEN5_GET_TOUCH_ID(touch->touch_tip_event_id);
+ int x, y;
+
+ if (event_id == RECORD_EVENT_LIFTOFF)
+ return;
+
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+ x = (touch->x_hi << 8) | touch->x_lo;
+ if (cyapa->x_origin)
+ x = cyapa->max_abs_x - x;
+ input_report_abs(input, ABS_MT_POSITION_X, x);
+ y = (touch->y_hi << 8) | touch->y_lo;
+ if (cyapa->y_origin)
+ y = cyapa->max_abs_y - y;
+ input_report_abs(input, ABS_MT_POSITION_Y, y);
+ input_report_abs(input, ABS_MT_PRESSURE,
+ touch->z);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ touch->major_axis_len);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ touch->minor_axis_len);
+
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ touch->major_tool_len);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ touch->minor_tool_len);
+
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ touch->orientation);
+}
+
+static void cyapa_gen5_report_touches(struct cyapa *cyapa,
+ const struct cyapa_gen5_report_data *report_data)
+{
+ struct input_dev *input = cyapa->input;
+ unsigned int touch_num;
+ int i;
+
+ touch_num = report_data->report_head[GEN5_NUMBER_OF_TOUCH_OFFSET] &
+ GEN5_NUMBER_OF_TOUCH_MASK;
+
+ for (i = 0; i < touch_num; i++)
+ cyapa_gen5_report_slot_data(cyapa,
+ &report_data->touch_records[i]);
+
+ input_mt_sync_frame(input);
+ input_sync(input);
+}
+
+static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_gen5_report_data report_data;
+ int ret;
+ u8 report_id;
+ unsigned int report_len;
+
+ if (cyapa->gen != CYAPA_GEN5 ||
+ cyapa->state != CYAPA_STATE_GEN5_APP) {
+ dev_err(dev, "invalid device state, gen=%d, state=0x%02x\n",
+ cyapa->gen, cyapa->state);
+ return -EINVAL;
+ }
+
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data,
+ GEN5_RESP_LENGTH_SIZE);
+ if (ret != GEN5_RESP_LENGTH_SIZE) {
+ dev_err(dev, "failed to read length bytes, (%d)\n", ret);
+ return -EINVAL;
+ }
+
+ report_len = get_unaligned_le16(
+ &report_data.report_head[GEN5_RESP_LENGTH_OFFSET]);
+ if (report_len < GEN5_RESP_LENGTH_SIZE) {
+ /* Invliad length or internal reset happened. */
+ dev_err(dev, "invalid report_len=%d. bytes: %02x %02x\n",
+ report_len, report_data.report_head[0],
+ report_data.report_head[1]);
+ return -EINVAL;
+ }
+
+ /* Idle, no data for report. */
+ if (report_len == GEN5_RESP_LENGTH_SIZE)
+ return 0;
+
+ ret = cyapa_i2c_pip_read(cyapa, (u8 *)&report_data, report_len);
+ if (ret != report_len) {
+ dev_err(dev, "failed to read %d bytes report data, (%d)\n",
+ report_len, ret);
+ return -EINVAL;
+ }
+
+ report_id = report_data.report_head[GEN5_RESP_REPORT_ID_OFFSET];
+ if (report_id == GEN5_WAKEUP_EVENT_REPORT_ID &&
+ report_len == GEN5_WAKEUP_EVENT_SIZE) {
+ /*
+ * Device wake event from deep sleep mode for touch.
+ * This interrupt event is used to wake system up.
+ */
+ return 0;
+ } else if (report_id != GEN5_TOUCH_REPORT_ID &&
+ report_id != GEN5_BTN_REPORT_ID &&
+ report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
+ report_id != GEN5_PUSH_BTN_REPORT_ID) {
+ /* Running in BL mode or unknown response data read. */
+ dev_err(dev, "invalid report_id=0x%02x\n", report_id);
+ return -EINVAL;
+ }
+
+ if (report_id == GEN5_TOUCH_REPORT_ID &&
+ (report_len < GEN5_TOUCH_REPORT_HEAD_SIZE ||
+ report_len > GEN5_TOUCH_REPORT_MAX_SIZE)) {
+ /* Invalid report data length for finger packet. */
+ dev_err(dev, "invalid touch packet length=%d\n", report_len);
+ return 0;
+ }
+
+ if ((report_id == GEN5_BTN_REPORT_ID ||
+ report_id == GEN5_OLD_PUSH_BTN_REPORT_ID ||
+ report_id == GEN5_PUSH_BTN_REPORT_ID) &&
+ (report_len < GEN5_BTN_REPORT_HEAD_SIZE ||
+ report_len > GEN5_BTN_REPORT_MAX_SIZE)) {
+ /* Invalid report data length of button packet. */
+ dev_err(dev, "invalid button packet length=%d\n", report_len);
+ return 0;
+ }
+
+ if (report_id == GEN5_TOUCH_REPORT_ID)
+ cyapa_gen5_report_touches(cyapa, &report_data);
+ else
+ cyapa_gen5_report_buttons(cyapa, &report_data);
+
+ return 0;
+}
+
+const struct cyapa_dev_ops cyapa_gen5_ops = {
+ .initialize = cyapa_gen5_initialize,
+
+ .state_parse = cyapa_gen5_state_parse,
+ .operational_check = cyapa_gen5_do_operational_check,
+
+ .irq_handler = cyapa_gen5_irq_handler,
+ .irq_cmd_handler = cyapa_gen5_irq_cmd_handler,
+ .sort_empty_output_data = cyapa_empty_pip_output_data,
+ .set_power_mode = cyapa_gen5_set_power_mode,
+};
--
1.9.1
^ permalink raw reply related
* [PATCH v13 00/12] input: cyapa: instruction of cyapa patches
From: Dudley Du @ 2014-12-09 9:11 UTC (permalink / raw)
To: dmitry.torokhov, rydberg
Cc: Dudley Du, bleung, dso, linux-input, linux-kernel
V13 patches have below updates, details of other updates see history list:
1) Remove all debugfs interface, including read_fw and raw_data interfaces.
2) This patches are made based linux next-20141208.
This patch series is aimed to re-design the cyapa driver to support
old gen3 trackpad devices and new gen5 trackpad devices in one
cyapa driver, it's for easily productions support based on
customers' requirements. And add sysfs functions and interfaces
supported that required by users and customers.
Since the earlier gen3 and the latest gen5 trackpad devices using
two different chipsets, and have different protocols and interfaces,
so if supported these two type trackpad devices in two different drivers,
then it will be difficult to manage productions and later firmware updates.
e.g.: It will cause customer don't know which one trackpad device firmware
image to use and update when it has been used and integrated
in same one productions, so here we support these two trackpad
devices in same on driver.
The new design cyapa driver contains:
cyapa.c - the core of the re-design, supply interfaces and
functions to system and read trackpad devices.
cyapa.h - header file including macros and data structure definitions.
cyapa_gen3.c - functions support for gen3 trackpad devices,
cyapa_gen5.c - functions support for gen5 trackpad devices.
Beside this introduction patch, it has 12 patches listed as below.
For these patches, each one is patched based on previous one.
patch 1/12: re-design cyapa driver with core functions and interface
to support multi-type trackpad devices.
patch 2/12: add gen5 trackpad device basic functions supported into the
re-design cyapa driver.
patch 3/12: add power management interfaces supported for the deivce.
patch 4/12: add runtime power management interfaces supported for the device.
patch 5/12: add sysfs interfaces supported in the cyapa driver.
Including read firmware version, get production ID, read baseline,
re-calibrate trackpad baselines and do trackpad firmware update.
patch 6/12: add gen3 trackpad device's firmware update function supported.
patch 7/12: add gen3 trackpad device's read baseline function supported.
patch 8/12: add gen3 trackpad device's force re-calibrate function supported.
patch 9/12: add gen5 trackpad device's firmware update function supported.
patch 10/12: add gen5 trackpad device's read baseline function supported.
patch 11/12: add gen5 trackpad device's force re-calibrate function.
patch 12/12: add acpi device id supported.
History patch series modifications list:
V12 patches have below main updates compared with v11 patches:
1) Add check that when TP is detected but not operational, do not exit driver
immediately, but wait and export the update_fw interface for recovering.
2) Re-arrange the function codes, remove unnesseary protype definitions in
the header file.
V11 patches have below main updates compared with v10 patches:
1) Add add acpi device id supported for old gen3 and new gen5 trackpad devices.
2) Fix the unable to update firmware issue when cyapa_open is not called
which means the irq for firwmare update process is not enabled. This fix
by checking if the irq is enabled, if not then enable irq before start to
do firmware update.
V10 patches have below main updates compared with v9 patches:
1) Modify code to following kernel code style.
e.g.: correct to use error as return name when there is only error path,
and fix the checkpatch.sh wanting in the driver.
2) Remove cyapa_remove method and use input open and close interface to
following device resouse management infrastructure.
3) Modify cyapa_detect method to return tristate issue to make the return value
much more consistent and clear.
4) Use platform supplied functions as possible instead of driver
specific rewritten version.
V9 patches have below updates compared with v8 patches:
1) Removed all async thread stuff from the driver.
2) Split driver into 18 patches for each function change one patch.
V8 patches have below updates compared with v7 patches:
1) [PATCH v8 01/13] - Remove the async thread for device detect in
probe routine, now the device detect process is completely done within
the device probe routine.
2) [PATCH v8 01/13] - Split the irq cmd hander function to separated
function cyapa_default_irq_cmd_handler() and set it to interface
cyapa_default_ops.irq_cmd_handler.
3) [PATCH v8 06/13] - Add cyapa->gen check in cyapa_gen3_irq_cmd_handler()
to avoid miss-enter when device protocol is still in detecting.
V7 patches have below updates compared with v6 patches:
1) [PATCH v7 01/13] - Split the irq cmd hander function to separated
function cyapa_default_irq_cmd_handler() and set it to interface
cyapa_default_ops.irq_cmd_handler.
2) [PATCH v7 06/13] - Add cyapa->gen check in cyapa_gen3_irq_cmd_handler()
to avoid miss-enter when device protocol is still in detecting.
V6 patches have below updates compared with v5 patches:
1) Remove patch 14 of the lid filtering from the cyapa driver.
V5 patches have below updates compared with v4 patches:
1) Uses get_device()/put_device() instead of kobject_get()/kobject_put();
2) Fix memories freed before debugfs entries issue;
3) Make cyapa_debugs_root valid in driver module level
in module_init()/moudle_exit() ;
4) Fix i2c_transfer() may return partial transfer issues.
5) Add cyapa->removed flag to avoid detecting thread may still running
when driver module is removed.
6) Fix the meanings of some comments and return error code not clear issue.
^ permalink raw reply
* [PATCH v2] input: edt-ft5x06: fixed a macro coding style issue
From: Asaf Vertz @ 2014-12-09 8:08 UTC (permalink / raw)
To: dmitry.torokhov
Cc: LW, B38611, maksqwe1, jg1.han, linux, fabf, joe, asaf.vertz,
linux-input, linux-kernel
Fixed a coding style error, macros with complex values should be
enclosed in parentheses.
Signed-off-by: Asaf Vertz <asaf.vertz@tandemg.com>
---
Changes in v2:
- use do {...} while (0) instead of {...}
drivers/input/touchscreen/edt-ft5x06.c | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index ee3434f..d22ed56 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -850,9 +850,11 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
}
#define EDT_ATTR_CHECKSET(name, reg) \
+do { \
if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
- edt_ft5x06_register_write(tsdata, reg, pdata->name)
+ edt_ft5x06_register_write(tsdata, reg, pdata->name); \
+} while (0)
#define EDT_GET_PROP(name, reg) { \
u32 val; \
--
1.7.0.4
^ permalink raw reply related
* Re: [PATCH] HID: rmi: The address of query8 must be calculated based on which query registers are present
From: Jiri Kosina @ 2014-12-09 8:08 UTC (permalink / raw)
To: Andrew Duggan; +Cc: linux-input, linux-kernel, Benjamin Tissoires
In-Reply-To: <1418079720-11766-2-git-send-email-aduggan@synaptics.com>
On Mon, 8 Dec 2014, Andrew Duggan wrote:
> If a touchpad does not report relative data then query 6 will not be present and the address
> of query 8 will be one less. This patches calculates the location of query 8 instead of
> hardcoding the offset.
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Applied to hid.git#for-3.19/rmi as well.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: rmi: Check for additional ACM registers appended to F11 data report
From: Jiri Kosina @ 2014-12-09 8:07 UTC (permalink / raw)
To: Andrew Duggan
Cc: linux-input, linux-kernel, Benjamin Tissoires, Amordea Whiteoak
In-Reply-To: <1418079720-11766-1-git-send-email-aduggan@synaptics.com>
On Mon, 8 Dec 2014, Andrew Duggan wrote:
> If a touchpad reports the F11 data40 register then this indicates that the touchpad reports
> additional ACM (Accidental Contact Mitigation) data after the F11 data in the HID attention
> report. These additional bytes shift the position of the F30 button data causing the driver
> to incorrectly report button state when this functionality is present. This patch accounts
> for the additional data in the report.
>
> Fixes:
> https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1398533
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Applied to hid.git#for-3.19/rmi. Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 2/3] HID: wacom: Initialize MT slots for generic devices at post_parse_hid
From: Jiri Kosina @ 2014-12-09 8:00 UTC (permalink / raw)
To: Benjamin Tissoires; +Cc: Jason Gerecke, linux-input, Ping Cheng
In-Reply-To: <CAN+gG=E5EfxzKgOBf9wAXrzwq=i8XKNk9d8oSWeQbVGVMojjJQ@mail.gmail.com>
On Fri, 5 Dec 2014, Benjamin Tissoires wrote:
> > If a HID descriptor places HID_DG_CONTACTID before HID_DG_X and HID_DG_Y then
> > the ABS_X and ABS_Y will not be automatically initialized by the call to
> > input_mt_init_slots. To ensure that this is not a problem, we relocate that
> > call to occur after HID parsing has been completed and we've initalized all
> > the multitouch axes.
> >
> > Signed-off-by: Jason Gerecke <killertofu@gmail.com>
> > ---
>
> This one is
> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Benjamin, does your Reviewed-by: apply to both 2/3 and 3/3?
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] input: edt-ft5x06: fixed a macro coding style issue
From: Asaf Vertz @ 2014-12-09 7:51 UTC (permalink / raw)
To: Joe Perches
Cc: dmitry.torokhov, LW, B38611, maksqwe1, jg1.han, linux, fabf,
linux-input, linux-kernel
In-Reply-To: <1418107460.15957.9.camel@perches.com>
On 12/09/2014 08:44 AM, Joe Perches wrote:
> On Tue, 2014-12-09 at 08:34 +0200, Asaf Vertz wrote:
>> Fixed a coding style error, macros with complex values should be
>> enclosed in parentheses.
>>
>> -#define EDT_ATTR_CHECKSET(name, reg) \
>> +#define EDT_ATTR_CHECKSET(name, reg) { \
>> if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
>> pdata->name <= edt_ft5x06_attr_##name.limit_high) \
>> - edt_ft5x06_register_write(tsdata, reg, pdata->name)
>> + edt_ft5x06_register_write(tsdata, reg, pdata->name); \
>> +}
>
> Any time you see a naked if (foo) bar in a macro
> prefer to use a do while (0) like;
>
> #define EDT_ATTR_CHECKSET(name, reg) \
> do { \
> if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
> pdata->name <= edt_ft5x06_attr_##name.limit_high) \
> edt_ft5x06_register_write(tsdata, reg, pdata->name); \
> while (0)
>
thanks for the input, I will update and resend it
^ permalink raw reply
* Re: [PATCH] input: edt-ft5x06: fixed a macro coding style issue
From: Joe Perches @ 2014-12-09 6:44 UTC (permalink / raw)
To: Asaf Vertz
Cc: dmitry.torokhov, LW, B38611, maksqwe1, jg1.han, linux, fabf,
linux-input, linux-kernel
In-Reply-To: <20141209063456.GA15121@ubuntu>
On Tue, 2014-12-09 at 08:34 +0200, Asaf Vertz wrote:
> Fixed a coding style error, macros with complex values should be
> enclosed in parentheses.
[]
> diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
[]
> @@ -849,10 +849,11 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
> return 0;
> }
>
> -#define EDT_ATTR_CHECKSET(name, reg) \
> +#define EDT_ATTR_CHECKSET(name, reg) { \
> if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
> pdata->name <= edt_ft5x06_attr_##name.limit_high) \
> - edt_ft5x06_register_write(tsdata, reg, pdata->name)
> + edt_ft5x06_register_write(tsdata, reg, pdata->name); \
> +}
Any time you see a naked if (foo) bar in a macro
prefer to use a do while (0) like;
#define EDT_ATTR_CHECKSET(name, reg) \
do { \
if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
edt_ft5x06_register_write(tsdata, reg, pdata->name); \
while (0)
^ permalink raw reply
* [PATCH] input: edt-ft5x06: fixed a macro coding style issue
From: Asaf Vertz @ 2014-12-09 6:34 UTC (permalink / raw)
To: dmitry.torokhov
Cc: LW, B38611, maksqwe1, jg1.han, linux, fabf, asaf.vertz,
linux-input, linux-kernel
Fixed a coding style error, macros with complex values should be
enclosed in parentheses.
Signed-off-by: Asaf Vertz <asaf.vertz@tandemg.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 5 +++--
1 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index ee3434f..6db9253 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -849,10 +849,11 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
return 0;
}
-#define EDT_ATTR_CHECKSET(name, reg) \
+#define EDT_ATTR_CHECKSET(name, reg) { \
if (pdata->name >= edt_ft5x06_attr_##name.limit_low && \
pdata->name <= edt_ft5x06_attr_##name.limit_high) \
- edt_ft5x06_register_write(tsdata, reg, pdata->name)
+ edt_ft5x06_register_write(tsdata, reg, pdata->name); \
+}
#define EDT_GET_PROP(name, reg) { \
u32 val; \
--
1.7.0.4
^ permalink raw reply related
* Re: [PATCH v2] HID: rmi: Scan the report descriptor to determine if the device is suitable for the hid-rmi driver
From: Andrew Duggan @ 2014-12-09 0:16 UTC (permalink / raw)
To: linux-input, linux-kernel; +Cc: Jiri Kosina, Benjamin Tissoires
In-Reply-To: <1416872246-9632-1-git-send-email-aduggan@synaptics.com>
On 11/24/2014 03:37 PM, Andrew Duggan wrote:
> On composite HID devices there may be multiple HID devices on separate interfaces, but hid-rmi
> should only bind to the touchpad. The previous version simply checked that the interface protocol
> was set to mouse. Unfortuately, it is not always the case that the touchpad has the mouse interface
> protocol set. This patch takes a different approach and scans the report descriptor looking for
> the Generic Desktop Pointer usage and the Vendor Specific Top Level Collection needed by the
> hid-rmi driver to interface with the device.
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
> ---
> This is a second attempt at the patch I submitted back in October. Instead of looking for specific
> HID reports this patch looks for the Generic Desktop Pointer usage. Having that usage along with
> the vendor specific collection seems to be a distinctive enough to id the Synaptics touchpad in
> the composite USB device.
Any comments on this patch?
Thanks,
Andrew
> drivers/hid/hid-core.c | 21 ++++++++++++++++-----
> include/linux/hid.h | 4 +++-
> 2 files changed, 19 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 12b6e67..ba9dc59 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -698,10 +698,20 @@ static void hid_scan_feature_usage(struct hid_parser *parser, u32 usage)
> static void hid_scan_collection(struct hid_parser *parser, unsigned type)
> {
> struct hid_device *hid = parser->device;
> + int i;
>
> if (((parser->global.usage_page << 16) == HID_UP_SENSOR) &&
> type == HID_COLLECTION_PHYSICAL)
> hid->group = HID_GROUP_SENSOR_HUB;
> +
> + if ((parser->global.usage_page << 16) == HID_UP_GENDESK)
> + for (i = 0; i < parser->local.usage_index; i++)
> + if (parser->local.usage[i] == HID_GD_POINTER)
> + parser->scan_flags
> + |= HID_SCAN_FLAG_GENDESK_POINTER;
> +
> + if ((parser->global.usage_page << 16) == HID_UP_MSVENDOR)
> + parser->scan_flags |= HID_SCAN_FLAG_VENDOR_SPECIFIC;
> }
>
> static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
> @@ -783,11 +793,12 @@ static int hid_scan_report(struct hid_device *hid)
> * Vendor specific handlings
> */
> if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS) &&
> - (hid->group == HID_GROUP_GENERIC) &&
> - /* only bind to the mouse interface of composite USB devices */
> - (hid->bus != BUS_USB || hid->type == HID_TYPE_USBMOUSE))
> - /* hid-rmi should take care of them, not hid-generic */
> - hid->group = HID_GROUP_RMI;
> + (hid->group == HID_GROUP_GENERIC)) {
> + if ((parser->scan_flags & HID_SCAN_FLAG_VENDOR_SPECIFIC)
> + && (parser->scan_flags & HID_SCAN_FLAG_GENDESK_POINTER))
> + /* hid-rmi should take care of them, not hid-generic */
> + hid->group = HID_GROUP_RMI;
> + }
>
> /*
> * Vendor specific handlings
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index f53c4a9..b019f15 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -547,7 +547,9 @@ static inline void hid_set_drvdata(struct hid_device *hdev, void *data)
> #define HID_GLOBAL_STACK_SIZE 4
> #define HID_COLLECTION_STACK_SIZE 4
>
> -#define HID_SCAN_FLAG_MT_WIN_8 0x00000001
> +#define HID_SCAN_FLAG_MT_WIN_8 BIT(0)
> +#define HID_SCAN_FLAG_VENDOR_SPECIFIC BIT(1)
> +#define HID_SCAN_FLAG_GENDESK_POINTER BIT(2)
>
> struct hid_parser {
> struct hid_global global;
^ permalink raw reply
* Re: [PATCH] HID: rmi: Add support for the touchpad in the Razer Blade 14 laptop
From: Andrew Duggan @ 2014-12-09 0:16 UTC (permalink / raw)
To: linux-input, linux-kernel; +Cc: Jiri Kosina, Benjamin Tissoires
In-Reply-To: <1416874819-9914-1-git-send-email-aduggan@synaptics.com>
On 11/24/2014 04:20 PM, Andrew Duggan wrote:
> The Razer Blade 14 has a Synaptic's TouchPad on one of the interfaces of
> a composite USB device. This patch allows the hid-rmi driver to bind
> to that interface. It also adds support for the external click buttons
> on the Razer's touchpad. External buttons are reported using generic
> mouse reports instead of through the F30 like it is on ClickPads.
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
> ---
> This patch depends on the "HID: rmi: Scan the report descriptor to determine if the device is
> suitable for the hid-rmi driver" I submitted earlier today to correctly bind to the touchpad HID
> device in the composite USB device.
Any comments on this patch?
Thanks,
Andrew
> drivers/hid/hid-core.c | 4 +++-
> drivers/hid/hid-ids.h | 3 +++
> drivers/hid/hid-rmi.c | 15 ++++++++++++++-
> 3 files changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index ba9dc59..d69ea16 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -792,7 +792,9 @@ static int hid_scan_report(struct hid_device *hid)
> /*
> * Vendor specific handlings
> */
> - if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS) &&
> + if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS
> + || (hid->vendor == USB_VENDOR_ID_RAZER
> + && hid->product == USB_DEVICE_ID_RAZER_BLADE_14)) &&
> (hid->group == HID_GROUP_GENERIC)) {
> if ((parser->scan_flags & HID_SCAN_FLAG_VENDOR_SPECIFIC)
> && (parser->scan_flags & HID_SCAN_FLAG_GENDESK_POINTER))
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 25cd674..c677aad 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -751,6 +751,9 @@
> #define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001 0x3001
> #define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008 0x3008
>
> +#define USB_VENDOR_ID_RAZER 0x1532
> +#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D
> +
> #define USB_VENDOR_ID_REALTEK 0x0bda
> #define USB_DEVICE_ID_REALTEK_READER 0x0152
>
> diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
> index 3cccff7..1f131df 100644
> --- a/drivers/hid/hid-rmi.c
> +++ b/drivers/hid/hid-rmi.c
> @@ -453,7 +453,15 @@ static int rmi_raw_event(struct hid_device *hdev,
> case RMI_ATTN_REPORT_ID:
> return rmi_input_event(hdev, data, size);
> case RMI_MOUSE_REPORT_ID:
> - rmi_schedule_reset(hdev);
> + /*
> + * touchpads with physical mouse buttons will report those
> + * buttons in mouse reports even in RMI mode. Only reset
> + * the device if we see reports which contain X or Y data.
> + */
> + if (data[2] != 0 || data[3] != 0)
> + rmi_schedule_reset(hdev);
> + else
> + return 1;
> break;
> }
>
> @@ -871,6 +879,11 @@ static int rmi_input_mapping(struct hid_device *hdev,
> struct hid_input *hi, struct hid_field *field,
> struct hid_usage *usage, unsigned long **bit, int *max)
> {
> + if (field->application == HID_GD_POINTER
> + && (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
> + /* Pass mouse button reports to generic code for processing */
> + return 0;
> +
> /* we want to make HID ignore the advertised HID collection */
> return -1;
> }
^ permalink raw reply
* [PATCH] HID: rmi: The address of query8 must be calculated based on which query registers are present
From: Andrew Duggan @ 2014-12-08 23:02 UTC (permalink / raw)
To: linux-input, linux-kernel; +Cc: Andrew Duggan, Jiri Kosina, Benjamin Tissoires
In-Reply-To: <1418079720-11766-1-git-send-email-aduggan@synaptics.com>
If a touchpad does not report relative data then query 6 will not be present and the address
of query 8 will be one less. This patches calculates the location of query 8 instead of
hardcoding the offset.
Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
---
drivers/hid/hid-rmi.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index b069369..bc38c1e 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -640,17 +640,6 @@ static int rmi_populate_f11(struct hid_device *hdev)
has_rel = !!(buf[0] & BIT(3));
has_gestures = !!(buf[0] & BIT(5));
- if (has_gestures) {
- /* query 8 to find out if query 10 exists */
- ret = rmi_read(hdev, data->f11.query_base_addr + 8, buf);
- if (ret) {
- hid_err(hdev, "can not read gesture information: %d.\n",
- ret);
- return ret;
- }
- has_query10 = !!(buf[0] & BIT(2));
- }
-
/*
* At least 4 queries are guaranteed to be present in F11
* +1 for query 5 which is present since absolute events are
@@ -661,8 +650,19 @@ static int rmi_populate_f11(struct hid_device *hdev)
if (has_rel)
++query_offset; /* query 6 is present */
- if (has_gestures)
+ if (has_gestures) {
+ /* query 8 to find out if query 10 exists */
+ ret = rmi_read(hdev,
+ data->f11.query_base_addr + query_offset + 1, buf);
+ if (ret) {
+ hid_err(hdev, "can not read gesture information: %d.\n",
+ ret);
+ return ret;
+ }
+ has_query10 = !!(buf[0] & BIT(2));
+
query_offset += 2; /* query 7 and 8 are present */
+ }
if (has_query9)
++query_offset;
--
2.1.0
^ permalink raw reply related
* [PATCH] HID: rmi: Check for additional ACM registers appended to F11 data report
From: Andrew Duggan @ 2014-12-08 23:01 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: Andrew Duggan, Jiri Kosina, Benjamin Tissoires, Amordea Whiteoak
If a touchpad reports the F11 data40 register then this indicates that the touchpad reports
additional ACM (Accidental Contact Mitigation) data after the F11 data in the HID attention
report. These additional bytes shift the position of the F30 button data causing the driver
to incorrectly report button state when this functionality is present. This patch accounts
for the additional data in the report.
Fixes:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1398533
Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
---
drivers/hid/hid-rmi.c | 61 +++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 52 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 1f131df..5065583 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -592,11 +592,15 @@ static int rmi_populate_f11(struct hid_device *hdev)
bool has_query10 = false;
bool has_query11;
bool has_query12;
+ bool has_query27;
+ bool has_query28;
+ bool has_query36 = false;
bool has_physical_props;
bool has_gestures;
bool has_rel;
+ bool has_data40 = false;
unsigned x_size, y_size;
- u16 query12_offset;
+ u16 query_offset;
if (!data->f11.query_base_addr) {
hid_err(hdev, "No 2D sensor found, giving up.\n");
@@ -612,6 +616,8 @@ static int rmi_populate_f11(struct hid_device *hdev)
has_query9 = !!(buf[0] & BIT(3));
has_query11 = !!(buf[0] & BIT(4));
has_query12 = !!(buf[0] & BIT(5));
+ has_query27 = !!(buf[0] & BIT(6));
+ has_query28 = !!(buf[0] & BIT(7));
/* query 1 to get the max number of fingers */
ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
@@ -650,27 +656,27 @@ static int rmi_populate_f11(struct hid_device *hdev)
* +1 for query 5 which is present since absolute events are
* reported and +1 for query 12.
*/
- query12_offset = 6;
+ query_offset = 6;
if (has_rel)
- ++query12_offset; /* query 6 is present */
+ ++query_offset; /* query 6 is present */
if (has_gestures)
- query12_offset += 2; /* query 7 and 8 are present */
+ query_offset += 2; /* query 7 and 8 are present */
if (has_query9)
- ++query12_offset;
+ ++query_offset;
if (has_query10)
- ++query12_offset;
+ ++query_offset;
if (has_query11)
- ++query12_offset;
+ ++query_offset;
/* query 12 to know if the physical properties are reported */
if (has_query12) {
ret = rmi_read(hdev, data->f11.query_base_addr
- + query12_offset, buf);
+ + query_offset, buf);
if (ret) {
hid_err(hdev, "can not get query 12: %d.\n", ret);
return ret;
@@ -678,9 +684,10 @@ static int rmi_populate_f11(struct hid_device *hdev)
has_physical_props = !!(buf[0] & BIT(5));
if (has_physical_props) {
+ query_offset += 1;
ret = rmi_read_block(hdev,
data->f11.query_base_addr
- + query12_offset + 1, buf, 4);
+ + query_offset, buf, 4);
if (ret) {
hid_err(hdev, "can not read query 15-18: %d.\n",
ret);
@@ -695,9 +702,45 @@ static int rmi_populate_f11(struct hid_device *hdev)
hid_info(hdev, "%s: size in mm: %d x %d\n",
__func__, data->x_size_mm, data->y_size_mm);
+
+ /*
+ * query 15 - 18 contain the size of the sensor
+ * and query 19 - 26 contain bezel dimensions
+ */
+ query_offset += 12;
+ }
+ }
+
+ if (has_query27)
+ ++query_offset;
+
+ if (has_query28) {
+ ret = rmi_read(hdev, data->f11.query_base_addr
+ + query_offset, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 28: %d.\n", ret);
+ return ret;
+ }
+
+ has_query36 = !!(buf[0] & BIT(6));
+ }
+
+ if (has_query36) {
+ query_offset += 2;
+ ret = rmi_read(hdev, data->f11.query_base_addr
+ + query_offset, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 36: %d.\n", ret);
+ return ret;
}
+
+ has_data40 = !!(buf[0] & BIT(5));
}
+
+ if (has_data40)
+ data->f11.report_size += data->max_fingers * 2;
+
/*
* retrieve the ctrl registers
* the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
--
2.1.0
^ permalink raw reply related
* Re: PROBLEM: [HP Stream Notebook - 11-d010nr] clickpad malfunctions after performing a hardware click
From: Peter Hutterer @ 2014-12-08 22:34 UTC (permalink / raw)
To: Amordea Whiteoak; +Cc: Benjamin Tissoires, linux-input, Andrew Duggan
In-Reply-To: <CALmnu0aMX8RuDJFozCwP6GmP11Uep=R6bc0EqPYzBknwpHBmpA@mail.gmail.com>
On Mon, Dec 08, 2014 at 02:16:48PM -0500, Amordea Whiteoak wrote:
> While I have the ears of the touchpad gods, there is one more tiny
> issue I'd like to report. This may be simply a configuration issue,
> but I would not know how to fix it.
>
> The window for double-tapping (tapping the touchpad twice to simulate
> a double-click) is remarkably brief. Let me be clear, double-tapping
> on the touchpad does work, however if you do not get the second tap
> within about a 200ms window after the first, it seems to fail to
> register as a double-click, counting it as two unrelated single
> clicks--this is an uncomfortably short window of opportunity. A
> temporary workaround seems to be triple-tapping which for whatever
> reason allow for a broader window (much closer to a full second) and
> registers as a double-tap, but that just seems to be a happy accident
> and is certainly not an expected feature. Is there any way to broaden
> that window?
look at option MaxDoubleTapTime, see man synaptics. It's been on 180ms for
at least 8 years though, so unless Ubuntu changed the defaults here nothing
should've changed recently. there's always the chance of a bug in the xorg
driver but do try to figure out if that's a Ubuntu issue first please.
Cheers,
Peter
>
> I did not have this problem on my previous laptop with Lubuntu
> installed (though it had 14.04 not 14.10--it's no longer operational
> after suffering an unfortunate accident so I can't do comparison
> testing with it any longer).
>
> I promise this is my last remaining issue with the touchpad. :)
^ permalink raw reply
* Re: [PATCH 1/2] input: Add Qualcomm PM8941 power key driver
From: Bjorn Andersson @ 2014-12-08 22:55 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Bjorn Andersson, Kiran Padwal, Rob Herring, Pawel Moll,
Mark Rutland, Ian Campbell, Grant Likely,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-input@vger.kernel.org, linux-arm-msm@vger.kernel.org,
Cavin, Courtney
In-Reply-To: <20141007234224.GL16469@dtor-ws>
On Tue, Oct 7, 2014 at 4:42 PM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> On Tue, Oct 07, 2014 at 04:30:46PM -0700, Bjorn Andersson wrote:
>> On Tue 07 Oct 02:54 PDT 2014, Kiran Padwal wrote:
>>
[..]
>> > Shouldn't we unregister input device?
>> >
>>
>> It's allocated with devm_input_allocate_device() so I assumed that it goes away
>> upon the device being removed. Looking at input_register_device() seems to
>> confirm that.
>
> Yes, devices allocated with devm_input_allocate_device() will be
> unregistered and freed automatically.
>
> Thanks.
>
Hi Dmitry,
Will you pick this up? (couldn't find it in linux-next...)
Or is there anything else you need from me?
Regards,
Bjorn
^ permalink raw reply
* Re: PROBLEM: [HP Stream Notebook - 11-d010nr] clickpad malfunctions after performing a hardware click
From: Amordea Whiteoak @ 2014-12-08 22:50 UTC (permalink / raw)
To: Peter Hutterer; +Cc: Benjamin Tissoires, linux-input, Andrew Duggan
In-Reply-To: <20141208223448.GA8143@jelly.redhat.com>
> look at option MaxDoubleTapTime, see man synaptics. It's been on 180ms for
> at least 8 years though, so unless Ubuntu changed the defaults here nothing
> should've changed recently. there's always the chance of a bug in the xorg
> driver but do try to figure out if that's a Ubuntu issue first please.
I have, in fact, tried this already. And I re-tested it just now to be
sure. I could be wrong about the actual double-tap window duration (it
may very well be even less than 200ms value I quoted but I wouldn't
know how to time it accurately), but whatever it actually is, it is
mightily brief. Too brief for my comfort, and I'm relatively
nimble-fingered on a computer, so I can't imagine an older user
getting by with it too well.
But aye, I will bother Launchpad with it first. Thank you for your
reply, though!
On Mon, Dec 8, 2014 at 5:34 PM, Peter Hutterer <peter.hutterer@who-t.net> wrote:
> On Mon, Dec 08, 2014 at 02:16:48PM -0500, Amordea Whiteoak wrote:
>> While I have the ears of the touchpad gods, there is one more tiny
>> issue I'd like to report. This may be simply a configuration issue,
>> but I would not know how to fix it.
>>
>> The window for double-tapping (tapping the touchpad twice to simulate
>> a double-click) is remarkably brief. Let me be clear, double-tapping
>> on the touchpad does work, however if you do not get the second tap
>> within about a 200ms window after the first, it seems to fail to
>> register as a double-click, counting it as two unrelated single
>> clicks--this is an uncomfortably short window of opportunity. A
>> temporary workaround seems to be triple-tapping which for whatever
>> reason allow for a broader window (much closer to a full second) and
>> registers as a double-tap, but that just seems to be a happy accident
>> and is certainly not an expected feature. Is there any way to broaden
>> that window?
>
> look at option MaxDoubleTapTime, see man synaptics. It's been on 180ms for
> at least 8 years though, so unless Ubuntu changed the defaults here nothing
> should've changed recently. there's always the chance of a bug in the xorg
> driver but do try to figure out if that's a Ubuntu issue first please.
>
> Cheers,
> Peter
>
>>
>> I did not have this problem on my previous laptop with Lubuntu
>> installed (though it had 14.04 not 14.10--it's no longer operational
>> after suffering an unfortunate accident so I can't do comparison
>> testing with it any longer).
>>
>> I promise this is my last remaining issue with the touchpad. :)
^ permalink raw reply
* Re: [PATCHv2 2/3] sunxi:drivers:input:ps2 Added sunxi A10/A20 ps2 driver
From: Maxime Ripard @ 2014-12-08 22:41 UTC (permalink / raw)
To: vishnupatekar
Cc: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-lFZ/pmaqli7XmaaqVzeoHQ, leafy.myeh-Q9AEpCAkrSgqDJ6do+/SaQ,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, pawel.moll-5wv7dgnIgG8,
mark.rutland-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg,
galak-sgV2jX0FEOL9JmXXK+q4OQ, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1417907719-26775-3-git-send-email-VishnuPatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 14667 bytes --]
Hi,
On Sun, Dec 07, 2014 at 04:45:18AM +0530, vishnupatekar wrote:
> -added compatible as allwinner,sun4i-a10-ps2 and allwinner,sun7i-a20-ps2.
> - added default n depends on ARCH_SUNXI || COMPILE_TEST
> in Kconfig.
> -handled errors and free resources on errors.
> -used BIT(x), DIV_ROUND_UP macros.
> -corrected style errors.
The changelog should be either in your cover letter or just above the
diffstat, but not in your commitlog.
This should have a real commit log, telling what you're doing, why,
and how.
> Signed-off-by: vishnupatekar <VishnuPatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> drivers/input/serio/Kconfig | 10 ++
> drivers/input/serio/Makefile | 1 +
> drivers/input/serio/sunxi-ps2.c | 364 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 375 insertions(+)
> create mode 100644 drivers/input/serio/sunxi-ps2.c
>
> diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
> index bc2d474..3a7599c 100644
> --- a/drivers/input/serio/Kconfig
> +++ b/drivers/input/serio/Kconfig
> @@ -281,4 +281,14 @@ config HYPERV_KEYBOARD
> To compile this driver as a module, choose M here: the module will
> be called hyperv_keyboard.
>
> +config SERIO_SUNXI_PS2
> + tristate "Allwinner Sun4i-A10/Sun7i-A20 PS/2 controller"
Allwinner A10 is enough
> + default n
This is the default.
> + depends on ARCH_SUNXI || COMPILE_TEST
> + help
> + Say Y here if you have Sun4i-A10/Sun7i-A20 Allwinner PS/2 ports.
You can just mention Allwinne A10 here, on sun4i, at your convenience.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called sunxi-ps2.
> +
> endif
> diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
> index 815d874..0fa0f78 100644
> --- a/drivers/input/serio/Makefile
> +++ b/drivers/input/serio/Makefile
> @@ -29,3 +29,4 @@ obj-$(CONFIG_SERIO_ARC_PS2) += arc_ps2.o
> obj-$(CONFIG_SERIO_APBPS2) += apbps2.o
> obj-$(CONFIG_SERIO_OLPC_APSP) += olpc_apsp.o
> obj-$(CONFIG_HYPERV_KEYBOARD) += hyperv-keyboard.o
> +obj-$(CONFIG_SERIO_SUNXI_PS2) += sunxi-ps2.o
> diff --git a/drivers/input/serio/sunxi-ps2.c b/drivers/input/serio/sunxi-ps2.c
> new file mode 100644
> index 0000000..4cd89ae
> --- /dev/null
> +++ b/drivers/input/serio/sunxi-ps2.c
Please call it sun4i-ps2, just in case we have another one coming at
some point.
> @@ -0,0 +1,364 @@
> +/*
> + * Driver for Allwinner A20 PS2 host controller
s/A20/A10/ ?
> + * Author: Vishnu Patekar <vishnupatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> + * Aaron.maoye <leafy.myeh-Q9AEpCAkrSgqDJ6do+/SaQ@public.gmane.org>
> + *
> + * Based on 3.0 kernel
Please mention that it is Allwinner's kernel driver you're talking
about.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/serio.h>
> +#include <linux/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/slab.h>
> +#include <linux/list.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +
> +#define DRIVER_NAME "sunxi-ps2"
> +
> +/* register offset definitions */
> +#define PS2_REG_GCTL (0x00) /* PS2 Module Global Control Reg */
> +#define PS2_REG_DATA (0x04) /* PS2 Module Data Reg */
> +#define PS2_REG_LCTL (0x08) /* PS2 Module Line Control Reg */
> +#define PS2_REG_LSTS (0x0C) /* PS2 Module Line Status Reg */
> +#define PS2_REG_FCTL (0x10) /* PS2 Module FIFO Control Reg */
> +#define PS2_REG_FSTS (0x14) /* PS2 Module FIFO Status Reg */
> +#define PS2_REG_CLKDR (0x18) /* PS2 Module Clock Divider Reg*/
> +
> +/* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */
> +#define PS2_GCTL_INTFLAG BIT(4)
> +#define PS2_GCTL_INTEN BIT(3)
> +#define PS2_GCTL_RESET BIT(2)
> +#define PS2_GCTL_MASTER BIT(1)
> +#define PS2_GCTL_BUSEN BIT(0)
> +
> +/* PS2 LINE CONTROL REGISTER */
> +#define PS2_LCTL_NOACK BIT(18)
> +#define PS2_LCTL_TXDTOEN BIT(8)
> +#define PS2_LCTL_STOPERREN BIT(3)
> +#define PS2_LCTL_ACKERREN BIT(2)
> +#define PS2_LCTL_PARERREN BIT(1)
> +#define PS2_LCTL_RXDTOEN BIT(0)
> +
> +/* PS2 LINE STATUS REGISTER */
> +#define PS2_LSTS_TXTDO BIT(8)
> +#define PS2_LSTS_STOPERR BIT(3)
> +#define PS2_LSTS_ACKERR BIT(2)
> +#define PS2_LSTS_PARERR BIT(1)
> +#define PS2_LSTS_RXTDO BIT(0)
> +
> +/* PS2 FIFO CONTROL REGISTER */
> +#define PS2_FCTL_TXRST BIT(17)
> +#define PS2_FCTL_RXRST BIT(16)
> +#define PS2_FCTL_TXUFIEN BIT(10)
> +#define PS2_FCTL_TXOFIEN BIT(9)
> +#define PS2_FCTL_TXRDYIEN BIT(8)
> +#define PS2_FCTL_RXUFIEN BIT(2)
> +#define PS2_FCTL_RXOFIEN BIT(1)
> +#define PS2_FCTL_RXRDYIEN BIT(0)
> +
> +/* PS2 FIFO STATUS REGISTER */
> +#define PS2_FSTS_TXUF BIT(10)
> +#define PS2_FSTS_TXOF BIT(9)
> +#define PS2_FSTS_TXRDY BIT(8)
> +#define PS2_FSTS_RXUF BIT(2)
> +#define PS2_FSTS_RXOF BIT(1)
> +#define PS2_FSTS_RXRDY BIT(0)
Please define the bits right below the register they belong too, not
separated.
> +#define PS2_LINE_ERROR_BIT \
> + (PS2_LSTS_TXTDO|PS2_LSTS_STOPERR|PS2_LSTS_ACKERR| \
> + PS2_LSTS_PARERR|PS2_LSTS_RXTDO)
> +
> +#define PS2_FIFO_ERROR_BIT \
> + (PS2_FSTS_TXUF|PS2_FSTS_TXOF|PS2_FSTS_TXRDY|PS2_FSTS_RXUF| \
> + PS2_FSTS_RXOF|PS2_FSTS_RXRDY)
Spaces around the operators.
> +
> +#define PS2_SAMPLE_CLK (1000000)
> +#define PS2_SCLK (125000)
> +
> +struct sunxips2data {
> + struct serio *serio;
> + struct device *dev;
> +
> + /* IO mapping base */
> + void __iomem *reg_base;
> +
> + /* clock management */
> + struct clk *clk;
> +
> + /* irq */
> + spinlock_t lock;
> + int irq;
> +};
> +
> +/*********************/
> +/* Interrupt handler */
> +/*********************/
> +static irqreturn_t sunxips2_interrupt(int irq, void *dev_id)
s/sunxips2/sun4i_ps2/ please.
> +{
> + struct sunxips2data *drvdata = dev_id;
> + u32 intr_status;
> + u32 fifo_status;
> + unsigned char byte;
> + u32 rval;
> + u32 error = 0;
> +
> + spin_lock(&drvdata->lock);
> +
> + /* Get the PS/2 interrupts and clear them */
> + intr_status = readl(drvdata->reg_base + PS2_REG_LSTS);
> + fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS);
> +
> + /*Check Line Status Register*/
> + if (intr_status & 0x10f) {
> + if (intr_status & PS2_LSTS_STOPERR)
> + dev_info(drvdata->dev, "PS/2 Stop Bit Error!");
> + if (intr_status & PS2_LSTS_ACKERR)
> + dev_info(drvdata->dev, "PS/2 Acknowledge Error!\n");
> + if (intr_status & PS2_LSTS_PARERR)
> + dev_info(drvdata->dev, "PS/2 Parity Error!\n");
> + if (intr_status & PS2_LSTS_TXTDO)
> + dev_info(drvdata->dev, "PS/2 Transmit Data Timeout!\n");
> + if (intr_status & PS2_LSTS_RXTDO)
> + dev_info(drvdata->dev, "PS/2 Receive Data Timeout!\n");
> +
> + /*reset PS/2 controller*/
> + writel(readl(drvdata->reg_base + PS2_REG_GCTL) | PS2_GCTL_RESET,
> + drvdata->reg_base + PS2_REG_GCTL);
It's usually better to have it as two lines: one for the read, one for
the write.
> +
> + rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR |
> + PS2_LSTS_PARERR | PS2_LSTS_RXTDO;
> + writel(rval, drvdata->reg_base + PS2_REG_LSTS);
> + error = 1;
> + }
> +
> + /*Check FIFO Status Register*/
> + if (fifo_status & 0x0606) {
> + if (fifo_status & PS2_FSTS_TXUF)
> + dev_info(drvdata->dev, "PS/2 Tx FIFO Underflow!\n");
> + if (fifo_status & PS2_FSTS_TXOF)
> + dev_info(drvdata->dev, "PS/2 Tx FIFO Overflow!\n");
> + if (fifo_status & PS2_FSTS_RXUF)
> + dev_info(drvdata->dev, "PS/2 Rx FIFO Underflow!\n");
> + if (fifo_status & PS2_FSTS_RXOF)
> + dev_info(drvdata->dev, "PS/2 Rx FIFO Overflow!\n");
> + /*reset PS/2 controller*/
> + writel(readl(drvdata->reg_base + PS2_REG_GCTL) | PS2_GCTL_RESET,
> + drvdata->reg_base + PS2_REG_GCTL);
Ditto.
> + rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY |
> + PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY;
> + writel(rval, drvdata->reg_base + PS2_REG_FSTS);
> + error = 1;
> + }
> +
> + rval = (fifo_status >> 16) & 0x3;
> + while (!error && rval--) {
> + byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff;
> + /* dev_info(drvdata->dev, "PS/2 Receive %02x\n", byte); */
Remove that line.
> + serio_interrupt(drvdata->serio, byte, 0);
> + }
> +
> + writel(intr_status, drvdata->reg_base + PS2_REG_LSTS);
> + writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS);
> +
> + spin_unlock(&drvdata->lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +
> +static int sunxips2_open(struct serio *pserio)
> +{
> + struct sunxips2data *drvdata = pserio->port_data;
> + u32 src_clk = 0;
> + u32 clk_scdf;
> + u32 clk_pcdf;
> + u32 rval;
> +
> + /*Set Line Control And Enable Interrupt*/
> + rval = PS2_LCTL_TXDTOEN | PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN
> + | PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN;
> + writel(rval, drvdata->reg_base + PS2_REG_LCTL);
> +
> + /*Reset FIFO*/
> + rval = PS2_FCTL_TXRST|PS2_FCTL_RXRST|PS2_FCTL_TXUFIEN|PS2_FCTL_TXOFIEN
> + |PS2_FCTL_RXUFIEN|PS2_FCTL_RXOFIEN|PS2_FCTL_RXRDYIEN;
> + writel(rval, drvdata->reg_base + PS2_REG_FCTL);
> +
> + src_clk = clk_get_rate(drvdata->clk);
> +
> + if (!src_clk) {
> + dev_info(drvdata->dev, "w_ps2c_set_sclk error, source clock is 0.");
> + return -1;
> + }
> +
> + /*Set Clock Divider Register*/
> + clk_scdf = DIV_ROUND_UP(src_clk, PS2_SAMPLE_CLK) - 1;
> + clk_pcdf = DIV_ROUND_UP(PS2_SAMPLE_CLK, PS2_SCLK) - 1;
So this is actually a rounding down?
Why not just using src_clk / PS2_SAMPLE_CLK directly?
> + rval = (clk_scdf<<8) | clk_pcdf;
Spaces between the operators. Remember, run checkpatch.
> + writel(rval, drvdata->reg_base + PS2_REG_CLKDR);
> +
> + /*Set Global Control Register*/
> + rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER
> + | PS2_GCTL_BUSEN;
> + writel(rval, drvdata->reg_base + PS2_REG_GCTL);
You seem to be reading/writing from the same registers than in your
interrupt handler, don't you need some locking in here?
> +
> + return 0;
> +}
> +
> +static void sunxips2_close(struct serio *pserio)
> +{
> + struct sunxips2data *drvdata = pserio->port_data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&drvdata->lock, flags);
> + /* Disable the PS2 interrupts */
> + writel(0, drvdata->reg_base + PS2_REG_GCTL);
> + spin_unlock_irqrestore(&drvdata->lock, flags);
> +}
> +
> +static int sunxips2_write(struct serio *pserio, unsigned char val)
> +{
> + unsigned long expire = jiffies + msecs_to_jiffies(10000);
> + struct sunxips2data *drvdata = (struct sunxips2data *)pserio->port_data;
> +
> + do {
> + if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) {
> + writel(val, drvdata->reg_base + PS2_REG_DATA);
> + return 0;
> + }
> + } while (time_before(jiffies, expire));
> +
> + return 0;
> +}
> +
> +static int sunxips2_probe(struct platform_device *pdev)
> +{
> + struct resource *res; /* IO mem resources */
> + struct sunxips2data *drvdata;
> + struct serio *serio;
> + struct device *dev = &pdev->dev;
> + unsigned int irq;
> + int error;
> +
> + drvdata = devm_kzalloc(dev, sizeof(struct sunxips2data), GFP_KERNEL);
> + serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
> + if (!drvdata || !serio) {
> + error = -ENOMEM;
> + goto err_free_mem;
> + }
> +
> + spin_lock_init(&drvdata->lock);
> +
> + /* IO */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + drvdata->reg_base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(drvdata->reg_base)) {
> + dev_err(dev, "failed to map registers\n");
> + error = PTR_ERR(drvdata->reg_base);
> + goto err_free_mem;
> + }
> +
> + drvdata->clk = devm_clk_get(dev, NULL);
> + if (!IS_ERR(drvdata->clk)) {
> + error = clk_prepare_enable(drvdata->clk);
> + if (error < 0) {
> + dev_err(dev, "failed to enable clock %d\n", error);
> + goto err_free_mem;
> + }
> + } else {
> + error = PTR_ERR(drvdata->clk);
> + dev_err(dev, "couldn't get clock %d\n", error);
> + goto err_free_mem;
> + }
That would be better if you had something like
clk_get()
if (IS_ERR())
goto
clk_prepare_enable()
if (IS_ERR())
Especially since you're using that kind of construct in the rest of
your function.
> + serio->id.type = SERIO_8042;
> + serio->write = sunxips2_write;
> + serio->open = sunxips2_open;
> + serio->close = sunxips2_close;
> + serio->port_data = drvdata;
> + serio->dev.parent = dev;
> + strlcpy(serio->name, dev_name(dev), sizeof(serio->name));
> + strlcpy(serio->phys, dev_name(dev), sizeof(serio->phys));
> +
> + /* Get IRQ for the device */
> + irq = platform_get_irq(pdev, 0);
> + if (!irq) {
> + dev_err(dev, "no IRQ found\n");
> + error = -ENXIO;
> + goto error_disable_clk;
> + }
> +
> + drvdata->irq = irq;
> + drvdata->serio = serio;
> + drvdata->dev = dev;
> + error = devm_request_threaded_irq(dev, drvdata->irq,
> + sunxips2_interrupt, NULL, 0, DRIVER_NAME, drvdata);
This really looks like a case for a regular request_irq.
> + if (error) {
> + dev_err(drvdata->dev, "Interrupt alloc failed %d:error:%d\n",
> + drvdata->irq, error);
> + goto error_disable_clk;
> + }
> +
> + serio_register_port(serio);
> + platform_set_drvdata(pdev, drvdata);
> +
> + return 0; /* success */
> +
> +error_disable_clk:
> + clk_disable_unprepare(drvdata->clk);
> +
> +err_free_mem:
> + kfree(serio);
> + return error;
> +}
> +
> +static int sunxips2_remove(struct platform_device *pdev)
> +{
> + struct sunxips2data *drvdata = platform_get_drvdata(pdev);
> +
> + serio_unregister_port(drvdata->serio);
> + disable_irq(drvdata->irq);
> +
> + if (!IS_ERR(drvdata->clk))
> + clk_disable_unprepare(drvdata->clk);
And you can drop that if.
> + kfree(drvdata->serio);
> +
> + return 0;
> +}
> +
> +/* Match table for of_platform binding */
> +static const struct of_device_id sunxips2_of_match[] = {
> + { .compatible = "allwinner,sun7i-a20-ps2", },
> + { .compatible = "allwinner,sun4i-a10-ps2", },
If the two really are compatible, you just need one of them, the A10
one in that case, since that's the earlier SoCs.
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, sunxips2_of_match);
> +
> +/*platform driver structure*/
> +static struct platform_driver sunxips2_of_driver = {
> + .probe = sunxips2_probe,
> + .remove = sunxips2_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .of_match_table = sunxips2_of_match,
> + },
> +};
> +module_platform_driver(sunxips2_of_driver);
> +
> +MODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
> +MODULE_AUTHOR("Aaron.maoye <leafy.myeh-Q9AEpCAkrSgqDJ6do+/SaQ@public.gmane.org>");
> +MODULE_DESCRIPTION("Sunxi PS/2 driver");
> +MODULE_LICENSE("GPL");
> --
> 1.7.9.5
>
It's looking great otherwise.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCHv2 3/3] ARM:dts:sunxi:ps2 dt nodes for A10/A20 PS2 controller.
From: Maxime Ripard @ 2014-12-08 22:22 UTC (permalink / raw)
To: vishnupatekar
Cc: linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-lFZ/pmaqli7XmaaqVzeoHQ, leafy.myeh-Q9AEpCAkrSgqDJ6do+/SaQ,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, pawel.moll-5wv7dgnIgG8,
mark.rutland-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg,
galak-sgV2jX0FEOL9JmXXK+q4OQ, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-input-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1417907719-26775-4-git-send-email-VishnuPatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 3421 bytes --]
Hi,
Your commit title should be less that 80 chars.
It should also be formatted using "ARM: sunxi: dts:" as a prefix (More
specific to less specific).
On Sun, Dec 07, 2014 at 04:45:19AM +0530, vishnupatekar wrote:
> Added ps2 nodes in lime2 board dts. By default ps20 and ps21 nodes are
> commented as ps20 pins conflict with HDMI connector
> on Lime2 Board.
>
> Signed-off-by: vishnupatekar <VishnuPatekar0510-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> arch/arm/boot/dts/sun4i-a10.dtsi | 27 +++++++++++++++++++++
> arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts | 13 ++++++++++
> arch/arm/boot/dts/sun7i-a20.dtsi | 29 +++++++++++++++++++++++
> 3 files changed, 69 insertions(+)
This patch should also be split into three different patches:
- The one adding the nodes for the controller to the DTSI
- The one adding the pinctrl nodes
- The one enabling the controller on the board.
>
> diff --git a/arch/arm/boot/dts/sun4i-a10.dtsi b/arch/arm/boot/dts/sun4i-a10.dtsi
> index 5e2ec2d..4726e8d 100644
> --- a/arch/arm/boot/dts/sun4i-a10.dtsi
> +++ b/arch/arm/boot/dts/sun4i-a10.dtsi
> @@ -615,6 +615,19 @@
> allwinner,drive = <0>;
> allwinner,pull = <0>;
> };
A newline here please.
> + ps2_0_pins: ps2_0@0 {
You called the node ps20, you probably want to call it the same way
here. And we usually have a suffix like pins_a whenever we have
several muxing options (as it's probably the case).
> + allwinner,pins = "PI20","PI21";
A whitespace after the comma please.
> + allwinner,function = "ps2";
> + allwinner,drive = <0>;
> + allwinner,pull = <0>;
> + };
Newline.
> + ps2_1_pins: ps2_1@0 {
> + allwinner,pins = "PH12","PH13";
> + allwinner,function = "ps2";
> + allwinner,drive = <0>;
> + allwinner,pull = <0>;
> + };
> +
> };
>
> timer@01c20c00 {
> @@ -781,5 +794,19 @@
> #address-cells = <1>;
> #size-cells = <0>;
> };
Newline.
> + ps20: ps2@0x01c2a000 {
Drop the 0x
> + compatible = "allwinner,sun4i-a10-ps2";
> + reg = <0x01c2a000 0x400>;
> + interrupts = <0 62 4>;
> + clocks = <&apb1_gates 6>;
> + status = "disabled";
> + };
Newline
> + ps21: ps2@0x01c2a400 {
> + compatible = "allwinner,sun4i-a10-ps2";
> + reg = <0x01c2a400 0x400>;
> + interrupts = <0 63 4>;
> + clocks = <&apb1_gates 7>;
> + status = "disabled";
> + };
> };
> };
> diff --git a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts
> index ed364d5..5624e63 100644
> --- a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts
> +++ b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts
> @@ -112,6 +112,19 @@
> pinctrl-0 = <&uart0_pins_a>;
> status = "okay";
> };
> + /* PS2 0 and PS2 1 are disabled by default
> + To enable PS2 0 and PS2 1 uncomment below ps20 and ps21 nodes
> + Please note that ps20 pins conflict with HDMI on Lime2 Board*/
> + /*ps20: ps2@0x01c2a000 {
> + pinctrl-names = "default";
> + pinctrl-0 = <&ps2_0_pins>;
> + status = "okay";
> + };
> + ps21: ps2@0x01c2a400 {
> + pinctrl-names = "default";
> + pinctrl-0 = <&ps2_1_pins>;
> + status = "okay";
> + };*/
Hmmm, no, no comments in the DTS.
Especially when it's that trivial to enable.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
^ permalink raw reply
* Re: [PATCHv2 1/3] sunxi:dts-bindings:input:ps2 bindings for A10/A20 ps2.
From: Maxime Ripard @ 2014-12-08 22:15 UTC (permalink / raw)
To: vishnupatekar
Cc: linux-sunxi, dmitry.torokhov, linux, leafy.myeh, robh+dt,
pawel.moll, mark.rutland, ijc+devicetree, galak, devicetree,
linux-arm-kernel, linux-kernel, linux-input
In-Reply-To: <1417907719-26775-2-git-send-email-VishnuPatekar0510@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1869 bytes --]
Hi,
On Sun, Dec 07, 2014 at 04:45:17AM +0530, vishnupatekar wrote:
> A10 and A20 have same PS2 addresses, clocks, interrupts.
> added compatible as allwinner,sun4i-a10-ps2.
>
> Signed-off-by: vishnupatekar <VishnuPatekar0510@gmail.com>
You should have your real name here.
> ---
> .../bindings/input/allwinner,sunxi-ps2.txt | 23 ++++++++++++++++++++
> 1 file changed, 23 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/allwinner,sunxi-ps2.txt
>
> diff --git a/Documentation/devicetree/bindings/input/allwinner,sunxi-ps2.txt b/Documentation/devicetree/bindings/input/allwinner,sunxi-ps2.txt
> new file mode 100644
> index 0000000..3a8919a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/allwinner,sunxi-ps2.txt
> @@ -0,0 +1,23 @@
> +* Device tree bindings for Allwinner A10, A20 PS2 host controller
> +A20 PS2 is dual role controller(PS2 host and PS2 device). These
> +bindings are for PS2 host controller. IBM compliant IBM PS2 and
> AT-compatible keyboard and mouse can be connected.
This should be wrapped to 80 chars.
> +Required properties:
> +
> + - reg : Offset and length of the register set for the device.
> + - compatible : Should one of the following:
> + - "allwinner,sun7i-a20-ps2"
> + - "allwinner,sun4i-a10-ps2"
> + - interrupts : The interrupt line connected to the PS2.
> + - clocks : The gate clk connected to the PS2.
> +
> +
> +Example:
> + ps20: ps2@0x01c2a000 {
> + compatible = "allwinner,sun7i-a20-ps2";
> + reg = <0x01c2a000 0x400>;
> + interrupts = <0 62 4>;
> + clocks = <&apb1_gates 6>;
> + status = "disabled";
> + };
> --
> 1.7.9.5
>
--
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: hid-replay captured data
From: Benjamin Tissoires @ 2014-12-08 22:04 UTC (permalink / raw)
To: josep.sanchez.ferreres
Cc: linux-input, Ping Cheng, Jason Gerecke, Aaron Skomra
In-Reply-To: <20141208215218.w5d4pi1sg8s0s4wo@webmail.fib.upc.es>
Thanks for the logs (both way arrived in my mailbox, attached file or pastebin).
I'll try to have a look at them on Wednesday.
On Mon, Dec 8, 2014 at 3:52 PM, <josep.sanchez.ferreres@est.fib.upc.edu> wrote:
> I attach a .tar.gz with all the captures I took, the filenames should be
> self explanatory. If you prefer me to dump all that into the e-mail text
> field just ask, but it's quite a big amount of data.
>
> Both 3.16 and 3.18 kernels seemed to respond to the same events. Oddly
> enough I've noticed that although 3 devices for rawhid were listed, only two
> captured events. Here's the output from hid-replay that shows my devices:
>
> Available devices:
> /dev/hidraw0: USB Keyboard
> /dev/hidraw1: USB Keyboard
> /dev/hidraw2: USB OPTICAL MOUSE
> /dev/hidraw3: Wacom Co.,Ltd. Bamboo Pad, USB
> /dev/hidraw4: Wacom Co.,Ltd. Bamboo Pad, USB
> /dev/hidraw5: Wacom Co.,Ltd. Bamboo Pad, USB
>
> Only hidraw4 and 5 reported events to hid-replay. Also note that hidraw5
> corresponds to the touchpad while hidraw5 corresponds to the stylus.
>
> I also just found out that the stylus hardware button is captured by the
> touchpad device and not by the stylus one (that actually makes more sense as
> it acts like a mouse button), so all the data I put about the stylus key
> should just contain stylus movement data. I added a file stylus-key.hid
> which is actually capturing the clicks for that button.
>
> I should also say that some events, like clicking the screen with the stylus
> will be mixed with the mouse movement (I tried to avoid that by clicking
> fast enough but I guess it's impossible to completely avoid it).
>
> PD: I see you're using a gmail address to reply, should I reply to that
> address better?
I think that's better to keep all the people in CC of the thread. we
generally have rules to filter linux-input/lkml mails, so it avoids
losing traces of a thread.
Cheers,
Benjamin
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox