2014-04-14 07:59:55

by Dudley Du

[permalink] [raw]
Subject: [PATCH 5/6] input: cyapa: add sysfs interfaces supported for gen3 trackpad device

Add sysfs interfaces for gen3 trackpad devices that required in production,
including read and update firmware image, report baselines, sensors calibrate,
read product id, read firmware version.
TEST=test on Chomebooks.

Signed-off-by: Du, Dudley <[email protected]>
---
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index da03427..917eabe 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -15,6 +15,7 @@
*/

#include <linux/async.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@@ -29,6 +30,7 @@
#include <linux/unaligned/access_ok.h>
#include <linux/pm_runtime.h>

+
/* APA trackpad firmware generation */
#define CYAPA_GEN_UNKNOWN 0x00 /* unknown protocol. */
#define CYAPA_GEN3 0x03 /* support MT-protocol B with tracking ID. */
@@ -50,6 +52,8 @@
#define CYAPA_CMD_BL_ALL 0x0a
#define CYAPA_CMD_BLK_PRODUCT_ID 0x0b
#define CYAPA_CMD_BLK_HEAD 0x0c
+#define CYAPA_CMD_MAX_BASELINE 0x0d
+#define CYAPA_CMD_MIN_BASELINE 0x0e

/* report data start reg offset address. */
#define DATA_REG_START_OFFSET 0x0000
@@ -150,6 +154,10 @@
CAPABILITY_MIDDLE_BTN_MASK)

#define CYAPA_OFFSET_SOFT_RESET REG_OFFSET_COMMAND_BASE
+#define OP_RECALIBRATION_MASK 0x80
+#define OP_REPORT_BASELINE_MASK 0x40
+#define REG_OFFSET_MAX_BASELINE 0x0026
+#define REG_OFFSET_MIN_BASELINE 0x0027

#define REG_OFFSET_POWER_MODE (REG_OFFSET_COMMAND_BASE + 1)
#define SET_POWER_MODE_DELAY 10000 /* unit: us */
@@ -493,6 +501,7 @@ struct cyapa_tsg_bin_image {
/* The main device structure */
struct cyapa {
enum cyapa_state state;
+ u8 status[BL_STATUS_SIZE];

struct i2c_client *client;
struct input_dev *input;
@@ -569,13 +578,29 @@ struct cyapa {
bl_read_fw_func cyapa_read_fw;
read_raw_data_func cyapa_read_raw_data;

+ size_t read_fw_image_size;
+ size_t tp_raw_data_size;
struct cyapa_tsg_bin_image_head fw_img_head;
+
+ struct mutex debugfs_mutex;
+
+ /* per-instance debugfs root */
+ struct dentry *dentry_dev;
+
+ /* Buffer to store firmware read using debugfs */
+ u8 *read_fw_image;
+ /* Buffer to store sensors' raw data */
+ u8 *tp_raw_data;
};

+static const u8 bl_activate[] = { 0x00, 0xff, 0x38, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07 };
static const u8 bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07 };
static const u8 bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07 };
+/* global root node of the cyapa debugfs directory. */
+static struct dentry *cyapa_debugfs_root;

struct cyapa_cmd_len {
u8 cmd;
@@ -601,10 +626,14 @@ struct cyapa_cmd_len {
#define CMD_RESET 0
#define CMD_POWER_MODE 1
#define CMD_DEV_STATUS 2
+#define CMD_REPORT_MAX_BASELINE 3
+#define CMD_REPORT_MIN_BASELINE 4
#define SMBUS_BYTE_CMD(cmd) (((cmd) & 0x3f) << 1)
#define CYAPA_SMBUS_RESET SMBUS_BYTE_CMD(CMD_RESET)
#define CYAPA_SMBUS_POWER_MODE SMBUS_BYTE_CMD(CMD_POWER_MODE)
#define CYAPA_SMBUS_DEV_STATUS SMBUS_BYTE_CMD(CMD_DEV_STATUS)
+#define CYAPA_SMBUS_MAX_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MAX_BASELINE)
+#define CYAPA_SMBUS_MIN_BASELINE SMBUS_BYTE_CMD(CMD_REPORT_MIN_BASELINE)

/* for group registers read/write command */
#define REG_GROUP_DATA 0
@@ -649,7 +678,9 @@ static const struct cyapa_cmd_len cyapa_i2c_cmds[] = {
{ BL_DATA_OFFSET, 16 },
{ BL_HEAD_OFFSET, 32 },
{ REG_OFFSET_QUERY_BASE, PRODUCT_ID_SIZE },
- { REG_OFFSET_DATA_BASE, 32 }
+ { REG_OFFSET_DATA_BASE, 32 },
+ { REG_OFFSET_MAX_BASELINE, 1 },
+ { REG_OFFSET_MIN_BASELINE, 1 },
};

static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
@@ -666,10 +697,30 @@ static const struct cyapa_cmd_len cyapa_smbus_cmds[] = {
{ CYAPA_SMBUS_BL_ALL, 32 },
{ CYAPA_SMBUS_BLK_PRODUCT_ID, PRODUCT_ID_SIZE },
{ CYAPA_SMBUS_BLK_HEAD, 16 },
+ { CYAPA_SMBUS_MAX_BASELINE, 1 },
+ { CYAPA_SMBUS_MIN_BASELINE, 1 },
};

static const char unique_str[] = "CYTRA";

+#define CYAPA_DEBUGFS_READ_FW "read_fw"
+#define CYAPA_DEBUGFS_RAW_DATA "raw_data"
+#define CYAPA_FW_NAME "cyapa.bin"
+#define CYAPA_FW_BLOCK_SIZE 64
+#define CYAPA_FW_READ_SIZE 16
+#define CYAPA_FW_HDR_START 0x0780
+#define CYAPA_FW_HDR_BLOCK_COUNT 2
+#define CYAPA_FW_HDR_BLOCK_START (CYAPA_FW_HDR_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_HDR_SIZE (CYAPA_FW_HDR_BLOCK_COUNT * \
+ CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_START 0x0800
+#define CYAPA_FW_DATA_BLOCK_COUNT 480
+#define CYAPA_FW_DATA_BLOCK_START (CYAPA_FW_DATA_START / CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_DATA_SIZE (CYAPA_FW_DATA_BLOCK_COUNT * \
+ CYAPA_FW_BLOCK_SIZE)
+#define CYAPA_FW_SIZE (CYAPA_FW_HDR_SIZE + CYAPA_FW_DATA_SIZE)
+#define CYAPA_CMD_LEN 16
+
static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout);
static void cyapa_detect(struct cyapa *cyapa);
static void cyapa_detect_async(void *data, async_cookie_t cookie);
@@ -890,6 +941,78 @@ 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 ret;
+
+ if (cyapa->input) {
+ cyapa_disable_irq(cyapa);
+ input_unregister_device(cyapa->input);
+ cyapa->input = NULL;
+ }
+
+ ret = cyapa_poll_state(cyapa, 500);
+ if (ret < 0)
+ return ret;
+ if (cyapa->state == CYAPA_STATE_BL_IDLE) {
+ /* Already in BL_IDLE. Skipping exit. */
+ return 0;
+ }
+
+ if (cyapa->state != CYAPA_STATE_OP)
+ return -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_NO_DEVICE;
+ ret = cyapa_write_byte(cyapa, CYAPA_CMD_SOFT_RESET, 0x01);
+ if (ret < 0)
+ return -EIO;
+
+ usleep_range(25000, 50000);
+ ret = cyapa_poll_state(cyapa, 500);
+ if (ret < 0)
+ return ret;
+ 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 ret;
+
+ ret = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_activate),
+ bl_activate);
+ if (ret < 0)
+ return ret;
+
+ /* Wait for bootloader to activate; takes between 2 and 12 seconds */
+ msleep(2000);
+ ret = cyapa_poll_state(cyapa, 11000);
+ if (ret < 0)
+ return ret;
+ if (cyapa->state != CYAPA_STATE_BL_ACTIVE)
+ return -EAGAIN;
+
+ return 0;
+}
+
static int cyapa_gen3_bl_deactivate(struct cyapa *cyapa)
{
int ret;
@@ -950,6 +1073,412 @@ 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 ret;
+ 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);
+
+ ret = cyapa_i2c_reg_write_block(cyapa, 0, cmd_len + 1, cmd);
+ if (ret < 0)
+ return ret;
+ }
+ 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[1] & BL_STATUS_BUSY) && --tries);
+
+ /* ignore WATCHDOG bit and reserved bits. */
+ bl_status = status[1] & ~BL_STATUS_REV_MASK;
+ bl_error = status[2] & ~BL_ERROR_RESERVED;
+
+ if (status[1] & BL_STATUS_BUSY)
+ ret = -ETIMEDOUT;
+ else if (bl_status != BL_STATUS_RUNNING ||
+ bl_error != BL_ERROR_BOOTLOADING)
+ ret = -EIO;
+ else
+ ret = 0;
+
+ return ret;
+}
+
+/*
+ * A firmware block read command reads 16 bytes of data from flash starting
+ * from a given address. The 12-byte block read command has the format:
+ * <0xff> <CMD> <Key> <Addr>
+ *
+ * <0xff> - every command starts with 0xff
+ * <CMD> - the read command value is 0x3c
+ * <Key> - read commands include an 8-byte key: { 00 01 02 03 04 05 06 07 }
+ * <Addr> - Memory address (16-bit, big-endian)
+ *
+ * The command is followed by an i2c block read to read the 16 bytes of data.
+ */
+static int cyapa_gen3_read_fw_bytes(struct cyapa *cyapa, u16 addr, u8 *data)
+{
+ int ret;
+ u8 cmd[] = { 0xff, 0x3c, 0, 1, 2, 3, 4, 5, 6, 7, addr >> 8, addr };
+
+ ret = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+ if (ret)
+ return ret;
+
+ /* read data buffer starting from offset 16 */
+ ret = cyapa_i2c_reg_read_block(cyapa, 16, CYAPA_FW_READ_SIZE, data);
+ if (ret != CYAPA_FW_READ_SIZE)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ int i;
+
+ /* First write data, starting at byte 128 of fw->data */
+ for (i = 0; i < CYAPA_FW_DATA_BLOCK_COUNT; i++) {
+ size_t block = CYAPA_FW_DATA_BLOCK_START + i;
+ size_t addr = (i + CYAPA_FW_HDR_BLOCK_COUNT) *
+ CYAPA_FW_BLOCK_SIZE;
+ const u8 *data = &fw->data[addr];
+ ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+ if (ret) {
+ dev_err(dev, "FW update aborted, %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* Then write checksum */
+ for (i = 0; i < CYAPA_FW_HDR_BLOCK_COUNT; i++) {
+ size_t block = CYAPA_FW_HDR_BLOCK_START + i;
+ size_t addr = i * CYAPA_FW_BLOCK_SIZE;
+ const u8 *data = &fw->data[addr];
+ ret = cyapa_gen3_write_fw_block(cyapa, block, data);
+ if (ret) {
+ dev_err(dev, "FW update aborted, %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Read the entire firmware image into ->read_fw_image.
+ * If the ->read_fw_image has already been allocated, then this function
+ * doesn't do anything and just returns 0.
+ * If an error occurs while reading the image, ->read_fw_image is freed, and
+ * the error is returned.
+ *
+ * The firmware is a fixed size (CYAPA_FW_SIZE), and is read out in
+ * fixed length (CYAPA_FW_READ_SIZE) chunks.
+ */
+static int cyapa_gen3_read_fw(struct cyapa *cyapa)
+{
+ int ret;
+ int addr;
+
+ if (cyapa->read_fw_image)
+ return 0;
+
+ ret = cyapa_gen3_bl_enter(cyapa);
+ if (ret)
+ goto err_detect;
+
+ cyapa->read_fw_image = kmalloc(CYAPA_FW_SIZE, GFP_KERNEL);
+ if (!cyapa->read_fw_image) {
+ ret = -ENOMEM;
+ goto err_detect;
+ }
+
+ for (addr = 0; addr < CYAPA_FW_SIZE; addr += CYAPA_FW_READ_SIZE) {
+ ret = cyapa_gen3_read_fw_bytes(cyapa, CYAPA_FW_HDR_START + addr,
+ &cyapa->read_fw_image[addr]);
+ if (ret) {
+ kfree(cyapa->read_fw_image);
+ cyapa->read_fw_image = NULL;
+ break;
+ }
+ }
+
+err_detect:
+ if (cyapa->read_fw_image)
+ cyapa->read_fw_image_size = CYAPA_FW_SIZE;
+ cyapa_detect_async(cyapa, 0);
+ return ret;
+}
+
+static ssize_t cyapa_gen3_do_calibrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int tries = 20; /* max recalibration timeout 2s. */
+ int ret;
+
+ cyapa_disable_irq(cyapa);
+
+ 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_RECALIBRATION_MASK);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send calibrate command. ret = %d\n",
+ ret);
+ goto out;
+ }
+
+ do {
+ /*
+ * For this recalibration, the max time will not exceed 2s.
+ * The average time is approximately 500 - 700 ms, and we
+ * will check the status every 100 - 200ms.
+ */
+ usleep_range(100000, 200000);
+
+ 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, "Failed to calibrate. Timeout.\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ dev_dbg(dev, "Calibration successful.\n");
+
+out:
+ cyapa_enable_irq(cyapa);
+ return ret < 0 ? ret : count;
+}
+
+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;
+
+ cyapa_disable_irq(cyapa);
+
+ 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:
+ cyapa_enable_irq(cyapa);
+ return ret;
+}
+
/*
* cyapa_sleep_time_to_pwr_cmd and cyapa_pwr_cmd_to_sleep_time
*
@@ -2541,18 +3070,18 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)

break;
case CYAPA_GEN3:
- cyapa->cyapa_check_fw = NULL;
- cyapa->cyapa_bl_enter = NULL;
- cyapa->cyapa_bl_activate = NULL;
+ cyapa->cyapa_check_fw = cyapa_gen3_check_fw;
+ cyapa->cyapa_bl_enter = cyapa_gen3_bl_enter;
+ cyapa->cyapa_bl_activate = cyapa_gen3_bl_activate;
cyapa->cyapa_bl_initiate = NULL;
- cyapa->cyapa_update_fw = NULL;
+ cyapa->cyapa_update_fw = cyapa_gen3_do_fw_update;
cyapa->cyapa_bl_verify_app_integrity = NULL;
cyapa->cyapa_bl_deactivate = cyapa_gen3_bl_deactivate;
- cyapa->cyapa_show_baseline = NULL;
- cyapa->cyapa_calibrate_store = NULL;
+ cyapa->cyapa_show_baseline = cyapa_gen3_show_baseline;
+ cyapa->cyapa_calibrate_store = cyapa_gen3_do_calibrate;
cyapa->cyapa_irq_handler = cyapa_gen3_irq_handler;
cyapa->cyapa_set_power_mode = cyapa_gen3_set_power_mode;
- cyapa->cyapa_read_fw = NULL;
+ cyapa->cyapa_read_fw = cyapa_gen3_read_fw;
cyapa->cyapa_read_raw_data = NULL;

ret = cyapa_gen3_do_operational_check(cyapa);
@@ -2713,6 +3242,10 @@ static int cyapa_get_state(struct cyapa *cyapa)
* detect trackpad protocol based on characristic registers and bits.
*/
do {
+ cyapa->status[REG_OP_STATUS] = status[REG_OP_STATUS];
+ cyapa->status[REG_BL_STATUS] = status[REG_BL_STATUS];
+ cyapa->status[REG_BL_ERROR] = status[REG_BL_ERROR];
+
if (cyapa->gen == CYAPA_GEN_UNKNOWN ||
cyapa->gen == CYAPA_GEN3) {
cyapa->gen_detecting = CYAPA_GEN3;
@@ -2964,6 +3497,286 @@ static void cyapa_detect(struct cyapa *cyapa)
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
}

+static int cyapa_firmware(struct cyapa *cyapa, const char *fw_name)
+{
+ struct device *dev = &cyapa->client->dev;
+ int ret;
+ const struct firmware *fw;
+
+ ret = request_firmware(&fw, fw_name, dev);
+ if (ret) {
+ dev_err(dev, "Could not load firmware from %s, %d\n",
+ fw_name, ret);
+ return ret;
+ }
+
+ if (cyapa->cyapa_check_fw) {
+ ret = cyapa->cyapa_check_fw(cyapa, fw);
+ if (ret) {
+ dev_err(dev, "Invalid CYAPA firmware image: %s\n",
+ fw_name);
+ goto done;
+ }
+ } else {
+ dev_err(dev, "Unknown status, operation forbidden, gen=%d\n",
+ cyapa->gen);
+ ret = -EPERM;
+ 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);
+
+ if (cyapa->cyapa_bl_enter) {
+ ret = cyapa->cyapa_bl_enter(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_activate) {
+ ret = cyapa->cyapa_bl_activate(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_initiate) {
+ ret = cyapa->cyapa_bl_initiate(cyapa, fw);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_update_fw) {
+ ret = cyapa->cyapa_update_fw(cyapa, fw);
+ if (ret)
+ goto err_detect;
+ }
+
+ if (cyapa->cyapa_bl_verify_app_integrity) {
+ ret = cyapa->cyapa_bl_verify_app_integrity(cyapa);
+ if (ret)
+ goto err_detect;
+ }
+
+err_detect:
+ pm_runtime_put_noidle(dev);
+ cyapa_detect_async(cyapa, 0);
+
+done:
+ release_firmware(fw);
+ return ret;
+}
+
+/*
+ **************************************************************
+ * debugfs interface
+ **************************************************************
+*/
+static int cyapa_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = inode->i_private;
+ int ret;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+
+ if (!kobject_get(&cyapa->client->dev.kobj)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ /*
+ * If firmware hasn't been read yet, read it all in one pass.
+ * Subsequent opens will reuse the data in this same buffer.
+ */
+ if (!cyapa->cyapa_read_fw) {
+ ret = -EPERM;
+ goto out;
+ }
+ ret = cyapa->cyapa_read_fw(cyapa);
+
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return ret;
+}
+
+static int cyapa_debugfs_release(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int ret;
+
+ if (!cyapa)
+ return 0;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+ file->private_data = NULL;
+ kobject_put(&cyapa->client->dev.kobj);
+ mutex_unlock(&cyapa->debugfs_mutex);
+
+ return 0;
+}
+
+/* Return some bytes from the buffered firmware image, starting from *ppos */
+static ssize_t cyapa_debugfs_read_fw(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct cyapa *cyapa = file->private_data;
+
+ if (!cyapa->read_fw_image)
+ return -EINVAL;
+
+ if (*ppos >= cyapa->read_fw_image_size)
+ return 0;
+
+ if (count + *ppos > cyapa->read_fw_image_size)
+ count = cyapa->read_fw_image_size - *ppos;
+
+ if (copy_to_user(buffer, &cyapa->read_fw_image[*ppos], count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static const struct file_operations cyapa_read_fw_fops = {
+ .open = cyapa_debugfs_open,
+ .release = cyapa_debugfs_release,
+ .read = cyapa_debugfs_read_fw
+};
+
+static int cyapa_debugfs_raw_data_open(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = inode->i_private;
+ int ret;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+
+ if (!kobject_get(&cyapa->client->dev.kobj)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ if (!cyapa->tp_raw_data) {
+ if (cyapa->state != CYAPA_STATE_GEN5_APP ||
+ !cyapa->electrodes_x || !cyapa->electrodes_y) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ cyapa->tp_raw_data_size = sizeof(s32) * (cyapa->electrodes_x *
+ cyapa->electrodes_y + cyapa->electrodes_x +
+ cyapa->electrodes_y) + GEN5_RAW_DATA_HEAD_SIZE;
+ /* This buffer will be hold after used until the driver is
+ * unloaded, the purpose of it is to improve the performace
+ * to avoid frequently allocate and release the buffer. */
+ cyapa->tp_raw_data =
+ kmalloc(cyapa->tp_raw_data_size, GFP_KERNEL);
+ if (!cyapa->tp_raw_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ memset(cyapa->tp_raw_data, 0, cyapa->tp_raw_data_size);
+ }
+
+ if (!cyapa->cyapa_read_raw_data) {
+ ret = -EPERM;
+ goto out;
+ }
+ ret = cyapa->cyapa_read_raw_data(cyapa);
+
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return ret;
+}
+
+static int cyapa_debugfs_raw_data_release(struct inode *inode,
+ struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int ret;
+
+ if (!cyapa)
+ return 0;
+
+ ret = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (ret)
+ return ret;
+ file->private_data = NULL;
+ kobject_put(&cyapa->client->dev.kobj);
+ mutex_unlock(&cyapa->debugfs_mutex);
+
+ return 0;
+}
+
+/* Always return the sensors' latest raw data from trackpad device. */
+static ssize_t cyapa_debugfs_read_raw_data(struct file *file,
+ char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct cyapa *cyapa = file->private_data;
+
+ if (!cyapa->tp_raw_data)
+ return -EINVAL;
+
+ if (*ppos >= cyapa->tp_raw_data_size)
+ return 0;
+
+ if (count + *ppos > cyapa->tp_raw_data_size)
+ count = cyapa->tp_raw_data_size - *ppos;
+
+ if (copy_to_user(buffer, &cyapa->tp_raw_data[*ppos], count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static const struct file_operations cyapa_read_raw_data_fops = {
+ .open = cyapa_debugfs_raw_data_open,
+ .release = cyapa_debugfs_raw_data_release,
+ .read = cyapa_debugfs_read_raw_data
+};
+
+static int cyapa_debugfs_init(struct cyapa *cyapa)
+{
+ struct device *dev = &cyapa->client->dev;
+
+ if (!cyapa_debugfs_root)
+ return -ENODEV;
+
+ cyapa->dentry_dev = debugfs_create_dir(kobject_name(&dev->kobj),
+ cyapa_debugfs_root);
+
+ if (!cyapa->dentry_dev)
+ return -ENODEV;
+
+ mutex_init(&cyapa->debugfs_mutex);
+
+ debugfs_create_file(CYAPA_DEBUGFS_READ_FW, S_IRUSR, cyapa->dentry_dev,
+ cyapa, &cyapa_read_fw_fops);
+
+ debugfs_create_file(CYAPA_DEBUGFS_RAW_DATA, S_IRUSR, cyapa->dentry_dev,
+ cyapa, &cyapa_read_raw_data_fops);
+ return 0;
+}

/*
* Sysfs Interface.
@@ -3110,6 +3923,93 @@ static void cyapa_start_runtime(struct cyapa *cyapa)
static void 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)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d.%d\n", cyapa->fw_maj_ver,
+ cyapa->fw_min_ver);
+}
+
+static ssize_t cyapa_show_product_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%s\n", cyapa->product_id);
+}
+
+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);
+ const char *fw_name;
+ int ret;
+
+ /* Do not allow paths that step out of /lib/firmware */
+ if (strstr(buf, "../") != NULL)
+ return -EINVAL;
+
+ fw_name = !strncmp(buf, "1", count) ||
+ !strncmp(buf, "1\n", count) ? CYAPA_FW_NAME : buf;
+
+ ret = cyapa_firmware(cyapa, fw_name);
+ if (ret)
+ dev_err(dev, "firmware update failed, %d\n", ret);
+ else
+ dev_dbg(dev, "firmware update succeeded\n");
+
+ return ret ? ret : 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 ret;
+
+ if (!cyapa->cyapa_calibrate_store) {
+ dev_err(dev, "Calibrate operation not permitted.\n");
+ return -EPERM;
+ }
+
+ ret = cyapa->cyapa_calibrate_store(dev, attr, buf, count);
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t cyapa_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+
+ if (!cyapa->cyapa_show_baseline) {
+ dev_err(dev, "Calibrate operation not permitted.\n");
+ return -EPERM;
+ }
+
+ return cyapa->cyapa_show_baseline(dev, attr, buf);
+}
+
+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 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,
+ NULL,
+};
+
+static const struct attribute_group cyapa_sysfs_group = {
+ .attrs = cyapa_sysfs_entries,
+};
+

/*
* We rely on EV_SW and SW_LID bits to identify a LID device, and hook
@@ -3311,6 +4211,12 @@ static int cyapa_probe(struct i2c_client *client,
}
cyapa_disable_irq(cyapa);

+ if (sysfs_create_group(&client->dev.kobj, &cyapa_sysfs_group))
+ dev_warn(dev, "error creating sysfs entries.\n");
+
+ if (cyapa_debugfs_init(cyapa))
+ dev_warn(dev, "error creating debugfs entries.\n");
+
#ifdef CONFIG_PM_SLEEP
if (device_can_wakeup(dev) &&
sysfs_merge_group(&client->dev.kobj, &cyapa_power_wakeup_group))
@@ -3333,6 +4239,7 @@ static int cyapa_remove(struct i2c_client *client)
struct cyapa *cyapa = i2c_get_clientdata(client);

pm_runtime_disable(&client->dev);
+ sysfs_remove_group(&client->dev.kobj, &cyapa_sysfs_group);

#ifdef CONFIG_PM_SLEEP
sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_wakeup_group);
@@ -3342,7 +4249,17 @@ static int cyapa_remove(struct i2c_client *client)
sysfs_unmerge_group(&client->dev.kobj, &cyapa_power_runtime_group);
#endif

+ kfree(cyapa->read_fw_image);
+ cyapa->read_fw_image = NULL;
+ cyapa->read_fw_image_size = 0;
+ kfree(cyapa->tp_raw_data);
+ cyapa->tp_raw_data = NULL;
+ cyapa->tp_raw_data_size = 0;
free_irq(cyapa->irq, cyapa);
+
+ debugfs_remove_recursive(cyapa->dentry_dev);
+ mutex_destroy(&cyapa->debugfs_mutex);
+
input_unregister_device(cyapa->input);
lid_event_unregister_handler(cyapa);
if (cyapa->cyapa_set_power_mode)
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.


Attachments:
winmail.dat (16.02 kB)