* [PATCH V2] HID: i2c-hid: fix race condition reading reports
From: Antonio Borneo @ 2014-11-19 16:46 UTC (permalink / raw)
To: Jiri Kosina, linux-input, Benjamin Tissoires
Cc: linux-kernel, Jean-Baptiste Maneyrol
In-Reply-To: <1416149064-25655-1-git-send-email-borneo.antonio@gmail.com>
From: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
Current driver uses a common buffer for reading reports either
synchronously in i2c_hid_get_raw_report() and asynchronously in
the interrupt handler.
There is race condition if an interrupt arrives immediately after
the report is received in i2c_hid_get_raw_report(); the common
buffer is modified by the interrupt handler with the new report
and then i2c_hid_get_raw_report() proceed using wrong data.
Fix it by using a separate buffers for synchronous reports.
Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
[Antonio Borneo: cleanup, rebase to v3.17, submit mainline]
Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com>
Cc: stable@vger.kernel.org
---
V1 -> V2
rename the synchronous buffer as rawbuf (instead of the
asynchronous one)
drivers/hid/i2c-hid/i2c-hid.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index 933bf10..c66e6ac 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -137,6 +137,7 @@ struct i2c_hid {
* descriptor. */
unsigned int bufsize; /* i2c buffer size */
char *inbuf; /* Input buffer */
+ char *rawbuf; /* Raw Input buffer */
char *cmdbuf; /* Command buffer */
char *argsbuf; /* Command arguments buffer */
@@ -504,9 +505,11 @@ static void i2c_hid_find_max_report(struct hid_device *hid, unsigned int type,
static void i2c_hid_free_buffers(struct i2c_hid *ihid)
{
kfree(ihid->inbuf);
+ kfree(ihid->rawbuf);
kfree(ihid->argsbuf);
kfree(ihid->cmdbuf);
ihid->inbuf = NULL;
+ ihid->rawbuf = NULL;
ihid->cmdbuf = NULL;
ihid->argsbuf = NULL;
ihid->bufsize = 0;
@@ -522,10 +525,11 @@ static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size)
report_size; /* report */
ihid->inbuf = kzalloc(report_size, GFP_KERNEL);
+ ihid->rawbuf = kzalloc(report_size, GFP_KERNEL);
ihid->argsbuf = kzalloc(args_len, GFP_KERNEL);
ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL);
- if (!ihid->inbuf || !ihid->argsbuf || !ihid->cmdbuf) {
+ if (!ihid->inbuf || !ihid->rawbuf || !ihid->argsbuf || !ihid->cmdbuf) {
i2c_hid_free_buffers(ihid);
return -ENOMEM;
}
@@ -552,12 +556,12 @@ static int i2c_hid_get_raw_report(struct hid_device *hid,
ret = i2c_hid_get_report(client,
report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
- report_number, ihid->inbuf, ask_count);
+ report_number, ihid->rawbuf, ask_count);
if (ret < 0)
return ret;
- ret_count = ihid->inbuf[0] | (ihid->inbuf[1] << 8);
+ ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8);
if (ret_count <= 2)
return 0;
@@ -566,7 +570,7 @@ static int i2c_hid_get_raw_report(struct hid_device *hid,
/* The query buffer contains the size, dropping it in the reply */
count = min(count, ret_count - 2);
- memcpy(buf, ihid->inbuf + 2, count);
+ memcpy(buf, ihid->rawbuf + 2, count);
return count;
}
--
2.1.3
^ permalink raw reply related
* Re: [PATCH] driver: input: touchscreen: add Raydium i2c touchscreen driver
From: Daniel Mack @ 2014-11-19 16:44 UTC (permalink / raw)
To: jeffrey.lin, dmitry.torokhov, rydberg, bleung, dh.herrmann,
charliemooney, floe
Cc: jeffrey.lin, roger.yang, KP.li, linux-kernel, linux-input,
jeffrey.lin
In-Reply-To: <1416411048-23526-1-git-send-email-jeffrey.lin@rad-ic.com>
On 11/19/2014 04:30 PM, jeffrey.lin wrote:
> From: "jeffrey.lin" <jeffrey.lin@gmail.com>
>
> this patch is porting Raydium I2C touch driver. Developer can enable
> raydium touch driver by modifying define "CONFIG_TOUCHSCREEN_RM_TS".
>
> BUG: None
> TEST: built and test with peppy
>
> Signed-off-by: jeffrey.lin@rad-ic.com
> Change-Id: I05d54e5ef29249d2a6eae97222c90bed11e839f9
> ---
Just some general remarks that IMO need to be addressed before a more
thorough review can happen:
* Please remove all dead code, such as code in comments and in #if 0
blocks.
* Use the regmap framework to abstract the hardware access on the I2C
layer (see drivers/input/keyboard/cap1106.c and many other drivers
as an example, and check include/linux/regmap.h). That makes the code
a lot shorter and more comprehensible to read.
* By using request_threaded_irq(), you don't have to manually control
your worker and can simplify your code quite a bit.
* See if you can claim all the resources the driver needs by using
devm_* variants.
* Don't use uppercase names for filenames, structs, functions and IDs
* Why do you need a miscdevice for this? Isn't the input event layer
enough?
* Also, run scripts/checkpatch.pl on the new patch, which will help
you find some more typical pitfalls.
Thanks,
Daniel
> drivers/input/touchscreen/Kconfig | 12 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/RM31100.c | 972 ++++++++++++++++++++++++++++++++++++
> include/linux/input/RM31100.h | 60 +++
> 4 files changed, 1045 insertions(+)
> create mode 100644 drivers/input/touchscreen/RM31100.c
> create mode 100644 include/linux/input/RM31100.h
>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 3ce9181..b692036 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -98,6 +98,18 @@ config TOUCHSCREEN_ATMEL_MXT
> To compile this driver as a module, choose M here: the
> module will be called atmel_mxt_ts.
>
> +config TOUCHSCREEN_RM_TS
> + tristate "Raydium I2C Touchscreen"
> + depends on I2C
> + help
> + Say Y here if you have Raydium series I2C touchscreen,
> + such as RM31100 , connected to your system.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called RM31100.
> +
> config TOUCHSCREEN_AUO_PIXCIR
> tristate "AUO in-cell touchscreen using Pixcir ICs"
> depends on I2C
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 687d5a7..d991815 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -73,6 +73,7 @@ wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
> wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
> wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o
> obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o
> +obj-$(CONFIG_TOUCHSCREEN_RM_TS) += RM31100.o
> obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
> obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
> obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
> diff --git a/drivers/input/touchscreen/RM31100.c b/drivers/input/touchscreen/RM31100.c
> new file mode 100644
> index 0000000..78cc80d
> --- /dev/null
> +++ b/drivers/input/touchscreen/RM31100.c
> @@ -0,0 +1,972 @@
> +/* Source for:
> + * Raydium RM31100 Prototype touchscreen driver.
> + * drivers/input/touchscreen/RM31100.c
> + *
> + * Copyright (C) 2012,
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2, and only version 2, as published by the
> + * Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * Raydium reserves the right to make changes without further notice
> + * to the materials described herein. Raydium does not assume any
> + * liability arising out of the application described herein.
> + *
> + * Contact Raydium Semiconductor Corporation at www.rad-ic.com
> + *
> + * History:
> + * (C) 2012 Raydium - Update for GPL distribution
> + * (C) 2009 Enea - Original prototype
> + *
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/slab.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/gpio.h>
> +#include <linux/workqueue.h>
> +#include <linux/mutex.h>
> +#include <linux/delay.h>
> +#include <linux/input/RM31100.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
> +/*#include <plat/gpio-cfg.h>*/
> +#include <linux/miscdevice.h>
> +/*#include <asm/uaccess.h> copy_to_user() */
> +#include <linux/uaccess.h>
> +
> +
> +#if 0/*defined(CONFIG_HAS_EARLYSUSPEND)*/
> +#include <linux/earlysuspend.h>
> +
> +/* Early-suspend level */
> +#define RM31100_TS_SUSPEND_LEVEL 1
> +#endif
> +
> +#define RM31100 0x0
> +#define RM3110x 0x1
> +
> +#define INVALID_DATA 0xff
> +
> +#define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(10))
> +#define INITIAL_DELAY (msecs_to_jiffies(25000))
> +
> +#define EVAL_REPORT_RATE 1
> +
> +#define I2C_CLIENT_ADDR 0x39
> +#define I2C_DMA_CLIENT_ADDR 0x5A
> +#undef CONFIG_PM
> +struct RM31100_ts_data {
> + u8 x_index;
> + u8 y_index;
> + u8 z_index;
> + u8 id_index;
> + u8 touch_index;
> + u8 data_reg;
> + u8 status_reg;
> + u8 data_size;
> + u8 touch_bytes;
> + u8 update_data;
> + u8 touch_meta_data;
> + u8 finger_size;
> +};
> +
> +static struct RM31100_ts_data devices[] = {
> + [0] = {
> + .x_index = 2,
> + .y_index = 4,
> + .z_index = 6,
> + .id_index = 1,
> + .data_reg = 0x1,
> + .status_reg = 0,
> + .update_data = 0x0,/*0x4*/
> + .touch_bytes = 6,
> + .touch_meta_data = 1,
> + .finger_size = 70,
> + },
> +};
> +
> +struct RM31100_ts {
> + struct i2c_client *client;
> + struct input_dev *input;
> + struct delayed_work work;
> + struct workqueue_struct *wq;
> + struct RM3110x_ts_platform_data *pdata;
> + struct RM31100_ts_data *dd;
> + u8 *touch_data;
> + u8 device_id;
> + u8 prev_touches;
> + bool is_suspended;
> + bool int_pending;
> + struct mutex sus_lock;
> + struct mutex access_lock;
> + u32 pen_irq;
> +#if 0/*defined(CONFIG_HAS_EARLYSUSPEND)*/
> + struct early_suspend early_suspend;
> +#endif
> +};
> +
> +struct RM31100_ts *pts;
> +
> +static inline u16 join_bytes(u8 a, u8 b)
> +{
> + u16 ab = 0;
> + ab = ab | a;
> + ab = ab << 8 | b;
> + return ab;
> +}
> +
> +static s32 RM31100_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val)
> +{
> + s32 data;
> +
> + data = i2c_smbus_write_byte_data(client, reg, val);
> + if (data < 0)
> + dev_err(&client->dev, "error %d in writing reg 0x%x\n",
> + data, reg);
> +
> + return data;
> +}
> +
> +static s32 RM31100_ts_read_reg_u8(struct i2c_client *client, u8 reg)
> +{
> + s32 data;
> +
> + data = i2c_smbus_read_byte_data(client, reg);
> + if (data < 0)
> + dev_err(&client->dev, "error %d in reading reg 0x%x\n",
> + data, reg);
> +
> + return data;
> +}
> +
> +static int RM31100_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num)
> +{
> + struct i2c_msg xfer_msg[2];
> +
> + xfer_msg[0].addr = client->addr;
> + xfer_msg[0].len = 1;
> + xfer_msg[0].flags = 0;
> + xfer_msg[0].buf = ®
> +
> + xfer_msg[1].addr = client->addr;
> + xfer_msg[1].len = num;
> + xfer_msg[1].flags = I2C_M_RD;
> + xfer_msg[1].buf = buf;
> +
> + return i2c_transfer(client->adapter, xfer_msg, 2);
> +}
> +
> +static int RM31100_ts_write(struct i2c_client *client, u8 *buf, int num)
> +{
> + struct i2c_msg xfer_msg[1];
> +
> + xfer_msg[0].addr = client->addr;
> + xfer_msg[0].len = num;
> + xfer_msg[0].flags = 0;
> + xfer_msg[0].buf = buf;
> +
> + return i2c_transfer(client->adapter, xfer_msg, 1);
> +}
> +
> +static int dev_open(struct inode *inode, struct file *filp)
> +{
> + mutex_lock(&pts->access_lock);
> + return 0;
> +}
> +
> +static int dev_release(struct inode *inode, struct file *filp)
> +{
> + mutex_unlock(&pts->access_lock);
> + return 0;
> +}
> +
> +static ssize_t
> +dev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
> +{
> + u8 *kbuf;
> + struct i2c_msg xfer_msg;
> + /*static char out[] = "1234567890";*/
> + /*static int idx;*//*= 0; remove by checkpatch*/
> + int i;
> +
> + kbuf = kmalloc(count, GFP_KERNEL);
> + if (kbuf == NULL)
> + return -ENOMEM;
> +
> + /*xfer_msg.addr = pts->client->addr;*/
> + xfer_msg.addr = I2C_CLIENT_ADDR;
> + xfer_msg.len = count;
> + xfer_msg.flags = I2C_M_RD;
> + xfer_msg.buf = kbuf;
> +
> + i2c_transfer(pts->client->adapter, &xfer_msg, 1);
> +
> + if (copy_to_user(buf, kbuf, count) == 0)
> + return count;
> + else
> + return -EFAULT;
> +}
> +
> +static ssize_t
> +dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
> +{
> + u8 *kbuf;
> + ssize_t status = 0;
> + int i;
> +
> + kbuf = kmalloc(count, GFP_KERNEL);
> + if (kbuf == NULL) {
> + dev_err("kmalloc() fail\n");
> + return -ENOMEM;
> + }
> +
> + if (copy_from_user(kbuf, buf, count) == 0) {
> + pts->client->addr = I2C_CLIENT_ADDR;
> + if (RM31100_ts_write(pts->client, kbuf, count) < 0)
> + status = -EFAULT;
> + else
> + status = count;
> + } else {
> + dev_err("copy_from_user() fail\n");
> + status = -EFAULT;
> + }
> +
> + kfree(kbuf);
> + return status;
> +}
> +
> +static struct file_operations dev_fops = {
> + .owner = THIS_MODULE,
> + .open = dev_open,
> + .release = dev_release,
> + .read = dev_read,
> + .write = dev_write,
> + /*.unlocked_ioctl = dev_ioctl,*/
> +};
> +
> +static struct miscdevice raydium_ts_miscdev = {
> + .minor = MISC_DYNAMIC_MINOR,
> + .name = "raydium_ts",
> + .fops = &dev_fops,
> +};
> +
> +
> +
> +ssize_t show(struct device_driver *drv, char *buff)
> +{
> + struct i2c_msg xfer_msg;
> + int num = 10;
> + char buf[100];
> + /*int i;*/
> +
> + xfer_msg.addr = pts->client->addr;
> + xfer_msg.len = num;
> + xfer_msg.flags = I2C_M_RD;
> + xfer_msg.buf = buf;
> + pts->client->addr = I2C_CLIENT_ADDR;
> + i2c_transfer(pts->client->adapter, &xfer_msg, 1);
> +
> + return 0;
> +}
> +
> +ssize_t store(struct device_driver *drv, const char *buf, size_t count)
> +{
> + /*unsigned char pkt[] = { 0xF2, 5, 1, 1 };*/
> + unsigned char pkt[] = { 0xF1, 5, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
> +
> + pts->client->addr = I2C_CLIENT_ADDR;
> + RM31100_ts_write(pts->client, pkt, sizeof(pkt));
> +
> + return sizeof(pkt);
> +}
> +
> +DRIVER_ATTR(myAttr, 0x777, show, store);
> +
> +static void report_data(struct RM31100_ts *ts, u16 x, u16 y, u8 pressure, u8 id)
> +{
> + if (ts->pdata->swap_xy)
> + swap(x, y);
> +
> + /* handle inverting coordinates */
> + if (ts->pdata->invert_x)
> + x = ts->pdata->res_x - x;
> + if (ts->pdata->invert_y)
> + y = ts->pdata->res_y - y;
> +
> + input_report_abs(ts->input, ABS_MT_TRACKING_ID, id);
> + input_report_abs(ts->input, ABS_MT_POSITION_X, x);
> + input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
> + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, pressure);
> + input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, ts->dd->finger_size);
> + input_mt_sync(ts->input);
> +
> + /*dev_dbg("%s(): id =%2hhd, x =%4hd, y =%4hd, pressure = %hhd\n",
> + __func__, id, x, y, pressure);*/
> +}
> +
> +static void process_RM31100_data(struct RM31100_ts *ts)
> +{
> + u8 id, pressure, touches, i;
> + u16 x, y;
> +
> + touches = ts->touch_data[ts->dd->touch_index];
> +
> + if (touches > 0) {
> + for (i = 0; i < touches; i++) {
> + id = ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->id_index];
> + pressure = ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->z_index];
> + x = join_bytes(ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->x_index + 1],
> + ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->x_index]);
> + y = join_bytes(ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->y_index + 1],
> + ts->touch_data[i * ts->dd->touch_bytes +
> + ts->dd->y_index]);
> + report_data(ts, x, y, pressure, id);
> + }
> + } else
> + input_mt_sync(ts->input);
> +
> +#if 0
> + for (i = 0; i < ts->prev_touches - (char)touches; i++) {
> + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 0);
> + input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 0);
> + input_mt_sync(ts->input);
> + }
> +#endif
> +
> + ts->prev_touches = touches;
> + input_report_key(ts->input, BTN_TOUCH, 1);
> + input_sync(ts->input);
> +}
> +
> +static void RM31100_ts_xy_worker(struct work_struct *work)
> +{
> + int rc;
> + u8 DMAAddress[4];
> + struct RM31100_ts *ts;
> +#if EVAL_REPORT_RATE
> + static struct timeval tv_start;
> + struct timeval tv_now;
> + static int cnt = -1;
> + int us;
> +#endif /* EVAL_REPORT_RATE*/
> +
> +
> + ts = container_of(work, struct RM31100_ts,
> + work.work);
> + /*dev_dbg("****RM31100_ts_xy_worker******\n");*/
> + mutex_lock(&ts->sus_lock);
> + if (ts->is_suspended == true) {
> + dev_dbg(&ts->client->dev, "TS is supended\n");
> + ts->int_pending = true;
> + mutex_unlock(&ts->sus_lock);
> + return;
> + }
> + mutex_unlock(&ts->sus_lock);
> +
> + mutex_lock(&ts->access_lock);
> + /* read data from DATA_REG */
> + /*RM31100 DMA Mode*/
> +#if 1 /*T010 OR w001+T012*/
> + DMAAddress[0] = 0x0F;
> + DMAAddress[1] = 0x00;
> + DMAAddress[2] = 0x20;
> + DMAAddress[3] = 0x81;/* Turn on DMA Mode*/
> + ts->client->addr = I2C_DMA_CLIENT_ADDR;
> + rc = RM31100_ts_write(ts->client, DMAAddress, 0x04);
> + if (rc < 0) {
> + dev_err(&ts->client->dev, "write failed\n");
> + goto schedule;
> + }
> + ts->client->addr = I2C_CLIENT_ADDR;
> + rc = RM31100_ts_read(ts->client, ts->dd->data_reg, ts->touch_data,
> + ts->dd->data_size);
> +#endif
> +
> + if (rc < 0) {
> + dev_err(&ts->client->dev, "read failed\n");
> + goto schedule;
> + }
> +
> + if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA)
> + goto schedule;
> +
> + /* write to STATUS_REG to release lock */
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg, ts->dd->update_data);
> + if (rc < 0) {
> + dev_err(&ts->client->dev, "write failed, try once more\n");
> +
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg, ts->dd->update_data);
> + if (rc < 0)
> + dev_err(&ts->client->dev, "write failed, exiting\n");
> + }
> +
> + process_RM31100_data(ts);
> +
> +#if EVAL_REPORT_RATE
> + cnt++;
> +
> + if (cnt == 0)/* first time this function is executed.*/
> + do_gettimeofday(&tv_start);
> + else if (cnt == 100) {
> + do_gettimeofday(&tv_now);
> + us = 1000000 * (tv_now.tv_sec - tv_start.tv_sec)
> + + tv_now.tv_usec - tv_start.tv_usec;
> + tv_start.tv_sec = tv_now.tv_sec;
> + tv_start.tv_usec = tv_now.tv_usec;
> + cnt = 0;
> + }
> +#endif /* EVAL_REPORT_RATE*/
> +schedule:
> +
> + mutex_unlock(&ts->access_lock);
> + /*dev_dbg("****Leave RM31100_ts_xy_worker******\n");*/
> + enable_irq(ts->pen_irq);
> +}
> +
> +static irqreturn_t RM31100_ts_irq(int irq, void *dev_id)
> +{
> + struct RM31100_ts *ts = dev_id;
> +
> + disable_irq_nosync(irq);
> +
> + queue_delayed_work(ts->wq, &ts->work, 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int RM31100_ts_init_ts(struct i2c_client *client, struct RM31100_ts *ts)
> +{
> + struct input_dev *input_device;
> + int rc = 0;
> +
> + ts->dd = &devices[ts->device_id];
> +
> + if (!ts->pdata->nfingers) {
> + dev_err(&client->dev, "Touches information not specified\n");
> + return -EINVAL;
> + }
> +
> + if (ts->device_id == RM3110x) {
> + if (ts->pdata->nfingers > 2) {
> + dev_err(&client->dev, "Touches >=1 & <= 2\n");
> + return -EINVAL;
> + }
> + ts->dd->data_size = ts->dd->touch_bytes;
> + ts->dd->touch_index = 0x0;
> + } else if (ts->device_id == RM31100) {
> + if (ts->pdata->nfingers > 10) {
> + dev_err(&client->dev, "Touches >=1 & <= 10\n");
> + return -EINVAL;
> + }
> + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes +
> + ts->dd->touch_meta_data;
> + ts->dd->touch_index = 0x0;
> + }
> +#if 1 /* w001 */
> + else {
> + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes +
> + ts->dd->touch_meta_data;
> + ts->dd->touch_index = 0x0;
> + }
> +#endif
> + ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
> + if (!ts->touch_data) {
> + pr_err("%s: Unable to allocate memory\n", __func__);
> + return -ENOMEM;
> + }
> +
> + ts->prev_touches = 0;
> +
> + input_device = input_allocate_device();
> + if (!input_device) {
> + rc = -ENOMEM;
> + goto error_alloc_dev;
> + }
> +
> + ts->input = input_device;
> + input_device->name = ts->pdata->ts_name;
> + input_device->id.bustype = BUS_I2C;
> + input_device->dev.parent = &client->dev;
> + input_set_drvdata(input_device, ts);
> +
> + __set_bit(EV_ABS, input_device->evbit);
> + __set_bit(INPUT_PROP_DIRECT, input_device->propbit);
> + /*__set_bit(EV_SYN, input_device->evbit);*/
> + /*__set_bit(BTN_TOUCH, input_device->keybit);*/
> +
> +
> + if (ts->device_id == RM31100) {
> + /* set up virtual key */
> + __set_bit(EV_KEY, input_device->evbit);
> + /* set dummy key to make driver work with virtual keys */
> + input_set_capability(input_device, EV_KEY, KEY_PROG1);
> + }
> +
> + input_set_abs_params(input_device, ABS_MT_POSITION_X,
> + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0);
> + input_set_abs_params(input_device, ABS_MT_POSITION_Y,
> + ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0);
> +#if 0
> + input_set_abs_params(input_device, ABS_MT_TOUCH_MAJOR,
> + ts->pdata->min_touch, ts->pdata->max_touch, 0, 0);
> + input_set_abs_params(input_device, ABS_MT_WIDTH_MAJOR,
> + ts->pdata->min_width, ts->pdata->max_width, 0, 0);
> +#endif
> + input_set_abs_params(input_device, ABS_MT_TRACKING_ID,
> + ts->pdata->min_tid, ts->pdata->max_tid, 0, 0);
> +
> + ts->wq = create_singlethread_workqueue("kworkqueue_ts");
> + if (!ts->wq) {
> + dev_err(&client->dev, "Could not create workqueue\n");
> + goto error_wq_create;
> + }
> +
> + INIT_DELAYED_WORK(&ts->work, RM31100_ts_xy_worker);
> +
> + rc = input_register_device(input_device);
> + if (rc)
> + goto error_unreg_device;
> +
> + return 0;
> +
> +error_unreg_device:
> + destroy_workqueue(ts->wq);
> +error_wq_create:
> + input_free_device(input_device);
> +error_alloc_dev:
> + kfree(ts->touch_data);
> + return rc;
> +}
> +
> +#ifdef CONFIG_PM
> +static int RM31100_ts_suspend(struct device *dev)
> +{
> + struct RM31100_ts *ts = dev_get_drvdata(dev);
> + int rc = 0;
> +
> + if (device_may_wakeup(dev)) {
> + /* mark suspend flag */
> + mutex_lock(&ts->sus_lock);
> + ts->is_suspended = true;
> + mutex_unlock(&ts->sus_lock);
> +
> + enable_irq_wake(ts->pen_irq);
> + } else {
> + disable_irq_nosync(ts->pen_irq);
> +
> + rc = cancel_delayed_work_sync(&ts->work);
> +
> + if (rc) {
> + /* missed the worker, write to STATUS_REG to
> + acknowledge interrupt */
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg, ts->dd->update_data);
> + if (rc < 0) {
> + dev_err(&ts->client->dev,
> + "write failed, try once more\n");
> +
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg,
> + ts->dd->update_data);
> + if (rc < 0)
> + dev_err(&ts->client->dev,
> + "write failed, exiting\n");
> + }
> +
> + enable_irq(ts->pen_irq);
> + }
> +
> + gpio_free(ts->pdata->irq_gpio);
> +
> + if (ts->pdata->power_on) {
> + rc = ts->pdata->power_on(0);
> + if (rc) {
> + dev_err(dev, "unable to goto suspend\n");
> + return rc;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int RM31100_ts_resume(struct device *dev)
> +{
> + struct RM31100_ts *ts = dev_get_drvdata(dev);
> +
> + int rc = 0;
> +
> + if (device_may_wakeup(dev)) {
> + disable_irq_wake(ts->pen_irq);
> +
> + mutex_lock(&ts->sus_lock);
> + ts->is_suspended = false;
> +
> + if (ts->int_pending == true) {
> + ts->int_pending = false;
> +
> + /* start a delayed work */
> + queue_delayed_work(ts->wq, &ts->work, 0);
> + }
> + mutex_unlock(&ts->sus_lock);
> +
> + } else {
> + if (ts->pdata->power_on) {
> + rc = ts->pdata->power_on(1);
> + if (rc) {
> + dev_err(dev, "unable to resume\n");
> + return rc;
> + }
> + }
> +
> + /* configure touchscreen interrupt gpio */
> + rc = gpio_request(ts->pdata->irq_gpio, "RM31100_irq_gpio");
> + if (rc) {
> + pr_err("%s: unable to request gpio %d\n",
> + __func__, ts->pdata->irq_gpio);
> + goto err_power_off;
> + }
> + if (ts->pdata->irq_cfg) {
> + s3c_gpio_cfgpin(ts->pdata->irq_gpio,
> + ts->pdata->irq_cfg);
> + s3c_gpio_setpull(ts->pdata->irq_gpio,
> + S3C_GPIO_PULL_NONE);
> + }
> +
> + rc = gpio_direction_input(ts->pdata->irq_gpio);
> + if (rc) {
> + pr_err("%s: unable to set direction for gpio %d\n",
> + __func__, ts->pdata->irq_gpio);
> + goto err_gpio_free;
> + }
> +
> + enable_irq(ts->pen_irq);
> +
> + /* Clear the status register of the TS controller */
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg, ts->dd->update_data);
> + if (rc < 0) {
> + dev_err(&ts->client->dev,
> + "write failed, try once more\n");
> +
> + rc = RM31100_ts_write_reg_u8(ts->client,
> + ts->dd->status_reg,
> + ts->dd->update_data);
> + if (rc < 0)
> + dev_err(&ts->client->dev,
> + "write failed, exiting\n");
> + }
> + }
> +
> + return 0;
> +err_gpio_free:
> + gpio_free(ts->pdata->irq_gpio);
> +err_power_off:
> + if (ts->pdata->power_on)
> + rc = ts->pdata->power_on(0);
> + return rc;
> +}
> +
> +#if 0/*def CONFIG_HAS_EARLYSUSPEND*/
> +static void RM31100_ts_early_suspend(struct early_suspend *h)
> +{
> + struct RM31100_ts *ts =
> + container_of(h, struct RM31100_ts, early_suspend);
> +
> + RM31100_ts_suspend(&ts->client->dev);
> +}
> +
> +static void RM31100_ts_late_resume(struct early_suspend *h)
> +{
> + struct RM31100_ts *ts = container_of(h,
> + struct RM31100_ts, early_suspend);
> +
> + RM31100_ts_resume(&ts->client->dev);
> +}
> +#endif
> +
> +static struct dev_pm_ops RM31100_ts_pm_ops = {
> +/*#ifndef CONFIG_HAS_EARLYSUSPEND*/
> + .suspend = RM31100_ts_suspend,
> + .resume = RM31100_ts_resume,
> +/*#endif*/
> +};
> +#endif
> +
> +/*static int __devinit RM31100_ts_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)*/
> +static int __init RM31100_ts_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct RM31100_ts *ts;
> + struct RM3110x_ts_platform_data *pdata = client->dev.platform_data;
> + int rc, temp_reg;
> +
> + if (!pdata) {
> + dev_err(&client->dev, "platform data is required!\n");
> + return -EINVAL;
> + }
> + if (!i2c_check_functionality(client->adapter,
> + I2C_FUNC_SMBUS_READ_WORD_DATA)) {
> + dev_err(&client->dev, "I2C functionality not supported\n");
> + return -EIO;
> + }
> +
> + ts = kzalloc(sizeof(*ts), GFP_KERNEL);
> + if (!ts)
> + return -ENOMEM;
> + pts = ts;
> +
> + /* Enable runtime PM ops, start in ACTIVE mode */
> + rc = pm_runtime_set_active(&client->dev);
> + if (rc < 0)
> + dev_warn(&client->dev, "unable to set runtime pm state\n");
> + pm_runtime_enable(&client->dev);
> +
> + ts->client = client;
> + ts->pdata = pdata;
> + i2c_set_clientdata(client, ts);
> + ts->device_id = id->driver_data;
> +
> + if (ts->pdata->dev_setup) {
> + rc = ts->pdata->dev_setup(1);
> + if (rc < 0) {
> + dev_err(&client->dev, "dev setup failed\n");
> + goto error_touch_data_alloc;
> + }
> + }
> +
> + /* power on the device */
> + if (ts->pdata->power_on) {
> + rc = ts->pdata->power_on(1);
> + if (rc) {
> + pr_err("%s: Unable to power on the device\n", __func__);
> + goto error_dev_setup;
> + }
> + }
> +
> + /* read one byte to make sure i2c device exists */
> + if (id->driver_data == RM3110x)
> + temp_reg = 0x01;
> + else if (id->driver_data == RM31100)
> + temp_reg = 0x00;
> + else
> + temp_reg = 0x05;
> +
> + rc = RM31100_ts_read_reg_u8(client, temp_reg);
> + if (rc < 0) {
> + dev_err(&client->dev, "i2c sanity check failed\n");
> + goto error_power_on;
> + }
> +
> + ts->is_suspended = false;
> + ts->int_pending = false;
> + mutex_init(&ts->sus_lock);
> + mutex_init(&ts->access_lock);
> +
> + rc = RM31100_ts_init_ts(client, ts);
> + if (rc < 0) {
> + dev_err(&client->dev, "RM31100 init failed\n");
> + goto error_mutex_destroy;
> + }
> +
> + if (ts->pdata->resout_gpio < 0)
> + goto config_irq_gpio;
> +
> + /* configure touchscreen reset out gpio */
> + rc = gpio_request(ts->pdata->resout_gpio, "RM31100_resout_gpio");
> + if (rc) {
> + pr_err("%s: unable to request gpio %d\n",
> + __func__, ts->pdata->resout_gpio);
> + goto error_uninit_ts;
> + }
> +
> + rc = gpio_direction_output(ts->pdata->resout_gpio, 0);
> + if (rc) {
> + pr_err("%s: unable to set direction for gpio %d\n",
> + __func__, ts->pdata->resout_gpio);
> + goto error_resout_gpio_dir;
> + }
> + /* reset gpio stabilization time */
> + msleep(20);
> +
> +config_irq_gpio:
> + /* configure touchscreen interrupt gpio */
> + rc = gpio_request(ts->pdata->irq_gpio, "RM31100_irq_gpio");
> + if (rc) {
> + pr_err("%s: unable to request gpio %d\n",
> + __func__, ts->pdata->irq_gpio);
> + goto error_irq_gpio_req;
> + }
> +
> + rc = gpio_direction_input(ts->pdata->irq_gpio);
> + if (rc) {
> + pr_err("%s: unable to set direction for gpio %d\n",
> + __func__, ts->pdata->irq_gpio);
> + goto error_irq_gpio_dir;
> + }
> +
> + ts->pen_irq = gpio_to_irq(ts->pdata->irq_gpio);
> + rc = request_irq(ts->pen_irq, RM31100_ts_irq,
> + IRQF_TRIGGER_FALLING,
> + ts->client->dev.driver->name, ts);
> + if (rc) {
> + dev_err(&ts->client->dev, "could not request irq\n");
> + goto error_req_irq_fail;
> + }
> +
> + device_init_wakeup(&client->dev, ts->pdata->wakeup);
> +
> +#if 0/*def CONFIG_HAS_EARLYSUSPEND*/
> + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
> + RM31100_TS_SUSPEND_LEVEL;
> + ts->early_suspend.suspend = RM31100_ts_early_suspend;
> + ts->early_suspend.resume = RM31100_ts_late_resume;
> + register_early_suspend(&ts->early_suspend);
> +#endif
> +
> + rc = misc_register(&raydium_ts_miscdev);
> + if (rc) {
> + dev_err(&ts->client->dev,
> + "Raydium TS: cannot register miscdev:%d\n", rc);
> + goto error_reg_misc_dev;
> + }
> +
> +
> + return 0;
> +error_reg_misc_dev:
> +error_req_irq_fail:
> +error_irq_gpio_dir:
> + gpio_free(ts->pdata->irq_gpio);
> +error_irq_gpio_req:
> +error_resout_gpio_dir:
> + if (ts->pdata->resout_gpio >= 0)
> + gpio_free(ts->pdata->resout_gpio);
> +error_uninit_ts:
> + destroy_workqueue(ts->wq);
> + input_unregister_device(ts->input);
> + kfree(ts->touch_data);
> +error_mutex_destroy:
> + mutex_destroy(&ts->sus_lock);
> + mutex_destroy(&ts->access_lock);
> +error_power_on:
> +/* if (ts->pdata->power_on)
> + ts->pdata->power_on(0);*/
> +error_dev_setup:
> + if (ts->pdata->dev_setup)
> + ts->pdata->dev_setup(0);
> +error_touch_data_alloc:
> + pm_runtime_set_suspended(&client->dev);
> + pm_runtime_disable(&client->dev);
> + kfree(ts);
> + return rc;
> +}
> +
> +/*static int __devexit RM31100_ts_remove(struct i2c_client *client)*/
> +static int __exit RM31100_ts_remove(struct i2c_client *client)
> +{
> + struct RM31100_ts *ts = i2c_get_clientdata(client);
> +
> +#if 0/* defined(CONFIG_HAS_EARLYSUSPEND)*/
> + unregister_early_suspend(&ts->early_suspend);
> +#endif
> + pm_runtime_set_suspended(&client->dev);
> + pm_runtime_disable(&client->dev);
> +
> + device_init_wakeup(&client->dev, 0);
> +
> + cancel_delayed_work_sync(&ts->work);
> +
> + free_irq(ts->pen_irq, ts);
> +
> + gpio_free(ts->pdata->irq_gpio);
> +
> + if (ts->pdata->resout_gpio >= 0)
> + gpio_free(ts->pdata->resout_gpio);
> +
> + destroy_workqueue(ts->wq);
> +
> + input_unregister_device(ts->input);
> +
> + mutex_destroy(&ts->sus_lock);
> + mutex_destroy(&ts->access_lock);
> +
> + if (ts->pdata->power_on)
> + ts->pdata->power_on(0);
> +
> + if (ts->pdata->dev_setup)
> + ts->pdata->dev_setup(0);
> +
> + kfree(ts->touch_data);
> + kfree(ts);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id RM31100_ts_id[] = {
> + {"RM31100", RM31100},
> + {"RM3110x", RM3110x},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, RM31100_ts_id);
> +
> +
> +static struct i2c_driver RM31100_ts_driver = {
> + .driver = {
> + .name = "RM31100_ts",
> + .owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> + .pm = &RM31100_ts_pm_ops,
> +#endif
> + },
> + .probe = RM31100_ts_probe,
> + /*.remove = __devexit_p(RM31100_ts_remove),*/
> + .remove = __exit_p(RM31100_ts_remove),
> + .id_table = RM31100_ts_id,
> +};
> +
> +static int __init RM31100_ts_init(void)
> +{
> + int rc;
> + int rc2;
> +
> + rc = i2c_add_driver(&RM31100_ts_driver);
> +
> + rc2 = driver_create_file(&RM31100_ts_driver.driver,
> + &driver_attr_myAttr);
> +
> + return rc;
> +}
> +/* Making this as late init to avoid power fluctuations
> + * during LCD initialization.
> + */
> +late_initcall(RM31100_ts_init);
> +
> +static void __exit RM31100_ts_exit(void)
> +{
> + return i2c_del_driver(&RM31100_ts_driver);
> +}
> +module_exit(RM31100_ts_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("RM31100-RM3110x touchscreen controller driver");
> +MODULE_AUTHOR("Raydium");
> +MODULE_ALIAS("platform:RM31100_ts");
> diff --git a/include/linux/input/RM31100.h b/include/linux/input/RM31100.h
> new file mode 100644
> index 0000000..714a14b
> --- /dev/null
> +++ b/include/linux/input/RM31100.h
> @@ -0,0 +1,60 @@
> +/* Header file for:
> + * Raydium RM31100 Prototype touchscreen driver.
> + *
> + * Copyright (C) 2012, Raydium Semiconductor, Inc.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2, and only version 2, as published by the
> + * Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * Raydium reserves the right to make changes without further notice
> + * to the materials described herein. Raydium does not assume any
> + * liability arising out of the application described herein.
> + *
> + * Contact Raydium Semiconductor at www.rad-ic.com
> + *
> + * History:
> + * (C) 2012 Raydium - Update for GPL distribution
> + * (C) 2009 Enea - Original prototype
> + *
> + */
> +#ifndef __RM3110xTS_H__
> +#define __RM3110xTS_H__
> +
> +
> +/* RM3110x platform data
> + */
> +struct RM3110x_ts_platform_data {
> + int (*power_on)(int on);
> + int (*dev_setup)(bool on);
> + const char *ts_name;
> + u32 dis_min_x; /* display resoltion */
> + u32 dis_max_x;
> + u32 dis_min_y;
> + u32 dis_max_y;
> + u32 min_touch; /* no.of touches supported */
> + u32 max_touch;
> + u32 min_tid; /* track id */
> + u32 max_tid;
> + u32 min_width;/* size of the finger */
> + u32 max_width;
> + u32 res_x; /* TS resolution */
> + u32 res_y;
> + u32 swap_xy;
> + u32 flags;
> + u16 invert_x;
> + u16 invert_y;
> + u8 nfingers;
> + u32 irq_gpio;
> + int resout_gpio;
> + bool wakeup;
> + u32 irq_cfg;
> +};
> +
> +#endif
>
^ permalink raw reply
* Re: [PATCH] HID: yet another buggy ELAN touchscreen
From: Jiri Kosina @ 2014-11-19 16:39 UTC (permalink / raw)
To: Greg Kroah-Hartman; +Cc: Oliver Neukum, linux-input, stable, linux-usb
In-Reply-To: <20141119163324.GA2418@kroah.com>
On Wed, 19 Nov 2014, Greg Kroah-Hartman wrote:
> > On Mon, 17 Nov 2014, Oliver Neukum wrote:
> >
> > > The touchscreen needs the same quirk as the other models.
> > >
> > > Signed-off-by: Oliver Neukum <oneukum@suse.de>
> > > Reported-by: Bryan Poling <poli0048@umn.edu>
> > > CC: stable@vger.kernel.org
> > > ---
> > > drivers/hid/hid-ids.h | 1 +
> > > drivers/hid/usbhid/hid-quirks.c | 1 +
> > > drivers/usb/core/quirks.c | 3 +++
> >
> > Greg, are you OK with me taking the whole lot through hid.git in one
> > commit? (USB quirks and HID quirks for Elan devices have been merged
> > independently for previous 3 devices, but I don't think it's worth the
> > hassle splitting it).
>
> Yes, no objection from me:
>
> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Thanks, now applied.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: i2c-hid: fix race condition reading reports
From: Antonio Borneo @ 2014-11-19 16:37 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Jiri Kosina, linux-input, linux-kernel@vger.kernel.org,
Jean-Baptiste Maneyrol, stable
In-Reply-To: <20141117214305.GC30324@mail.corp.redhat.com>
Hi Benjamin,
On Tue, Nov 18, 2014 at 5:43 AM, Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
> Hey Antonio,
>
> On Nov 16 2014 or thereabouts, Antonio Borneo wrote:
>> From: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
>>
>> From: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
>>
>> Current driver uses a common buffer for reading reports either
>> synchronously in i2c_hid_get_raw_report() and asynchronously in
>> the interrupt handler.
>> There is race condition if an interrupt arrives immediately after
>> the report is received in i2c_hid_get_raw_report(); the common
>> buffer is modified by the interrupt handler with the new report
>> and then i2c_hid_get_raw_report() proceed using wrong data.
>>
>> Fix it by using a separate buffers for asynchronous reports.
>>
>> Signed-off-by: Jean-Baptiste Maneyrol <jmaneyrol@invensense.com>
>> [Antonio Borneo: cleanup and rebase to v3.17]
>> Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com>
>> Cc: stable@vger.kernel.org
>
> For your next submission, when you want a patch to go in stable, put CC
> here, but please do not CC the actual mail to stable@. Stable should receive
> either mails which are already in Linus' tree, or which refer a commit
> in Linus' tree in case it does not applies smoothly.
>
> [keeping stable@ here to show them that this one should not get picked
> right now]
I agree with you to lower the noise in -stable, even if Greg does not
feel annoyed.
I'll take care in the future.
>
>> ---
>>
>> Hi Jiri, Benjamin,
>>
>> I think this patch should also go through linux-stable.
>> Confirmation from our side is welcome.
>
> I think the patch is definitively valuable. However, my personal taste
> would go for having a new .rawbuf buffer and use it in
> i2c_hid_get_raw_report(). The rationale would be that it's actually
> i2c_hid_get_raw_report() which is problematic, not the generic irq
> handling. Also I prefer rawbuf to irqinbuf.
>
> I am perfectly aware that this is just bikeshedding, so if changing the
> patch is too cumbersome (looks like Jean-Baptiste is involved), and if
> Jiri agree, this can go into the hid tree with my reviewed-by.
>
> I am fine having this version or the rawbuf one in stable BTW.
No major issue changing the patch in line with your suggestion; just
some offline work to get also Jean-Baptiste check and approve it.
I'll send shortly a V2 that adds rawbuf instead of irqinbuf.
I will not add your "reviewed-by" since the code in V2 is quite
different than V1.
Thanks!
Antonio
^ permalink raw reply
* Re: [PATCH] HID: yet another buggy ELAN touchscreen
From: Greg Kroah-Hartman @ 2014-11-19 16:33 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Oliver Neukum, linux-input, stable, linux-usb
In-Reply-To: <alpine.LNX.2.00.1411191551160.4454@pobox.suse.cz>
On Wed, Nov 19, 2014 at 03:52:29PM +0100, Jiri Kosina wrote:
>
> [ Greg and linux-usb@ added to CC ]
>
> On Mon, 17 Nov 2014, Oliver Neukum wrote:
>
> > The touchscreen needs the same quirk as the other models.
> >
> > Signed-off-by: Oliver Neukum <oneukum@suse.de>
> > Reported-by: Bryan Poling <poli0048@umn.edu>
> > CC: stable@vger.kernel.org
> > ---
> > drivers/hid/hid-ids.h | 1 +
> > drivers/hid/usbhid/hid-quirks.c | 1 +
> > drivers/usb/core/quirks.c | 3 +++
>
> Greg, are you OK with me taking the whole lot through hid.git in one
> commit? (USB quirks and HID quirks for Elan devices have been merged
> independently for previous 3 devices, but I don't think it's worth the
> hassle splitting it).
Yes, no objection from me:
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
^ permalink raw reply
* Re: [git pull] Input updates for 3.18-rc4
From: Benjamin Tissoires @ 2014-11-19 15:32 UTC (permalink / raw)
To: Ulrik De Bie
Cc: Hans de Goede, Dmitry Torokhov, linux-kernel@vger.kernel.org,
linux-input, Jiri Kosina
In-Reply-To: <20141114222640.GA39748@dtor-ws>
Hi Ulrik,
On Fri, Nov 14, 2014 at 5:26 PM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> Hi Linus,
>
> Please pull from:
>
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
> or
> master.kernel.org:/pub/scm/linux/kernel/git/dtor/input.git for-linus
>
> to receive updates for the input subsystem. Mostly small fixups to PS/2
> tochpad drivers (ALPS, Elantech, Synaptics) to better deal with specific
> hardware.
>
> Changelog:
> ---------
>
> NeilBrown (1):
> Input: twl4030-pwrbutton - ensure a wakeup event is recorded.
>
> Pali Rohár (3):
> Input: alps - ignore potential bare packets when device is out of sync
> Input: alps - allow up to 2 invalid packets without resetting device
> Input: alps - ignore bad data on Dell Latitudes E6440 and E7440
>
> Takashi Iwai (1):
> Input: synaptics - add min/max quirk for Lenovo T440s
>
> Ulrik De Bie (5):
> Input: elantech - use elantech_report_trackpoint for hardware v4 too
> Input: elantech - fix crc_enabled for Fujitsu H730
> Input: elantech - report the middle button of the touchpad
> Input: elantech - provide a sysfs knob for crc_enabled
> Input: elantech - update the documentation
It looks like this elantech changes raise some warnings on a Samsung
laptop. Can you have a look please?
https://bugzilla.redhat.com/show_bug.cgi?id=1165390
Hans and myself are in CC of the bug report, we can ask further
details to the reporter if requested.
Cheers,
Benjamin
>
>
> Diffstat:
> --------
>
> Documentation/input/elantech.txt | 81 +++++++++++++++++++++++++++++++---
> drivers/input/misc/twl4030-pwrbutton.c | 1 +
> drivers/input/mouse/alps.c | 28 +++++++++++-
> drivers/input/mouse/elantech.c | 56 +++++++++++++++++++++--
> drivers/input/mouse/synaptics.c | 5 ++-
> 5 files changed, 158 insertions(+), 13 deletions(-)
>
> --
> Dmitry
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] driver: input: touchscreen: add Raydium i2c touchscreen driver
From: jeffrey.lin @ 2014-11-19 15:30 UTC (permalink / raw)
To: dmitry.torokhov, rydberg, bleung, dh.herrmann, charliemooney,
floe
Cc: jeffrey.lin, roger.yang, KP.li, linux-kernel, linux-input,
jeffrey.lin
From: "jeffrey.lin" <jeffrey.lin@gmail.com>
this patch is porting Raydium I2C touch driver. Developer can enable
raydium touch driver by modifying define "CONFIG_TOUCHSCREEN_RM_TS".
BUG: None
TEST: built and test with peppy
Signed-off-by: jeffrey.lin@rad-ic.com
Change-Id: I05d54e5ef29249d2a6eae97222c90bed11e839f9
---
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/RM31100.c | 972 ++++++++++++++++++++++++++++++++++++
include/linux/input/RM31100.h | 60 +++
4 files changed, 1045 insertions(+)
create mode 100644 drivers/input/touchscreen/RM31100.c
create mode 100644 include/linux/input/RM31100.h
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3ce9181..b692036 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -98,6 +98,18 @@ config TOUCHSCREEN_ATMEL_MXT
To compile this driver as a module, choose M here: the
module will be called atmel_mxt_ts.
+config TOUCHSCREEN_RM_TS
+ tristate "Raydium I2C Touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have Raydium series I2C touchscreen,
+ such as RM31100 , connected to your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called RM31100.
+
config TOUCHSCREEN_AUO_PIXCIR
tristate "AUO in-cell touchscreen using Pixcir ICs"
depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 687d5a7..d991815 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -73,6 +73,7 @@ wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_RM_TS) += RM31100.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
diff --git a/drivers/input/touchscreen/RM31100.c b/drivers/input/touchscreen/RM31100.c
new file mode 100644
index 0000000..78cc80d
--- /dev/null
+++ b/drivers/input/touchscreen/RM31100.c
@@ -0,0 +1,972 @@
+/* Source for:
+ * Raydium RM31100 Prototype touchscreen driver.
+ * drivers/input/touchscreen/RM31100.c
+ *
+ * Copyright (C) 2012,
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, and only version 2, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Raydium reserves the right to make changes without further notice
+ * to the materials described herein. Raydium does not assume any
+ * liability arising out of the application described herein.
+ *
+ * Contact Raydium Semiconductor Corporation at www.rad-ic.com
+ *
+ * History:
+ * (C) 2012 Raydium - Update for GPL distribution
+ * (C) 2009 Enea - Original prototype
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input/RM31100.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+/*#include <plat/gpio-cfg.h>*/
+#include <linux/miscdevice.h>
+/*#include <asm/uaccess.h> copy_to_user() */
+#include <linux/uaccess.h>
+
+
+#if 0/*defined(CONFIG_HAS_EARLYSUSPEND)*/
+#include <linux/earlysuspend.h>
+
+/* Early-suspend level */
+#define RM31100_TS_SUSPEND_LEVEL 1
+#endif
+
+#define RM31100 0x0
+#define RM3110x 0x1
+
+#define INVALID_DATA 0xff
+
+#define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(10))
+#define INITIAL_DELAY (msecs_to_jiffies(25000))
+
+#define EVAL_REPORT_RATE 1
+
+#define I2C_CLIENT_ADDR 0x39
+#define I2C_DMA_CLIENT_ADDR 0x5A
+#undef CONFIG_PM
+struct RM31100_ts_data {
+ u8 x_index;
+ u8 y_index;
+ u8 z_index;
+ u8 id_index;
+ u8 touch_index;
+ u8 data_reg;
+ u8 status_reg;
+ u8 data_size;
+ u8 touch_bytes;
+ u8 update_data;
+ u8 touch_meta_data;
+ u8 finger_size;
+};
+
+static struct RM31100_ts_data devices[] = {
+ [0] = {
+ .x_index = 2,
+ .y_index = 4,
+ .z_index = 6,
+ .id_index = 1,
+ .data_reg = 0x1,
+ .status_reg = 0,
+ .update_data = 0x0,/*0x4*/
+ .touch_bytes = 6,
+ .touch_meta_data = 1,
+ .finger_size = 70,
+ },
+};
+
+struct RM31100_ts {
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct delayed_work work;
+ struct workqueue_struct *wq;
+ struct RM3110x_ts_platform_data *pdata;
+ struct RM31100_ts_data *dd;
+ u8 *touch_data;
+ u8 device_id;
+ u8 prev_touches;
+ bool is_suspended;
+ bool int_pending;
+ struct mutex sus_lock;
+ struct mutex access_lock;
+ u32 pen_irq;
+#if 0/*defined(CONFIG_HAS_EARLYSUSPEND)*/
+ struct early_suspend early_suspend;
+#endif
+};
+
+struct RM31100_ts *pts;
+
+static inline u16 join_bytes(u8 a, u8 b)
+{
+ u16 ab = 0;
+ ab = ab | a;
+ ab = ab << 8 | b;
+ return ab;
+}
+
+static s32 RM31100_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val)
+{
+ s32 data;
+
+ data = i2c_smbus_write_byte_data(client, reg, val);
+ if (data < 0)
+ dev_err(&client->dev, "error %d in writing reg 0x%x\n",
+ data, reg);
+
+ return data;
+}
+
+static s32 RM31100_ts_read_reg_u8(struct i2c_client *client, u8 reg)
+{
+ s32 data;
+
+ data = i2c_smbus_read_byte_data(client, reg);
+ if (data < 0)
+ dev_err(&client->dev, "error %d in reading reg 0x%x\n",
+ data, reg);
+
+ return data;
+}
+
+static int RM31100_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num)
+{
+ struct i2c_msg xfer_msg[2];
+
+ xfer_msg[0].addr = client->addr;
+ xfer_msg[0].len = 1;
+ xfer_msg[0].flags = 0;
+ xfer_msg[0].buf = ®
+
+ xfer_msg[1].addr = client->addr;
+ xfer_msg[1].len = num;
+ xfer_msg[1].flags = I2C_M_RD;
+ xfer_msg[1].buf = buf;
+
+ return i2c_transfer(client->adapter, xfer_msg, 2);
+}
+
+static int RM31100_ts_write(struct i2c_client *client, u8 *buf, int num)
+{
+ struct i2c_msg xfer_msg[1];
+
+ xfer_msg[0].addr = client->addr;
+ xfer_msg[0].len = num;
+ xfer_msg[0].flags = 0;
+ xfer_msg[0].buf = buf;
+
+ return i2c_transfer(client->adapter, xfer_msg, 1);
+}
+
+static int dev_open(struct inode *inode, struct file *filp)
+{
+ mutex_lock(&pts->access_lock);
+ return 0;
+}
+
+static int dev_release(struct inode *inode, struct file *filp)
+{
+ mutex_unlock(&pts->access_lock);
+ return 0;
+}
+
+static ssize_t
+dev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
+{
+ u8 *kbuf;
+ struct i2c_msg xfer_msg;
+ /*static char out[] = "1234567890";*/
+ /*static int idx;*//*= 0; remove by checkpatch*/
+ int i;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (kbuf == NULL)
+ return -ENOMEM;
+
+ /*xfer_msg.addr = pts->client->addr;*/
+ xfer_msg.addr = I2C_CLIENT_ADDR;
+ xfer_msg.len = count;
+ xfer_msg.flags = I2C_M_RD;
+ xfer_msg.buf = kbuf;
+
+ i2c_transfer(pts->client->adapter, &xfer_msg, 1);
+
+ if (copy_to_user(buf, kbuf, count) == 0)
+ return count;
+ else
+ return -EFAULT;
+}
+
+static ssize_t
+dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
+{
+ u8 *kbuf;
+ ssize_t status = 0;
+ int i;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (kbuf == NULL) {
+ dev_err("kmalloc() fail\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(kbuf, buf, count) == 0) {
+ pts->client->addr = I2C_CLIENT_ADDR;
+ if (RM31100_ts_write(pts->client, kbuf, count) < 0)
+ status = -EFAULT;
+ else
+ status = count;
+ } else {
+ dev_err("copy_from_user() fail\n");
+ status = -EFAULT;
+ }
+
+ kfree(kbuf);
+ return status;
+}
+
+static struct file_operations dev_fops = {
+ .owner = THIS_MODULE,
+ .open = dev_open,
+ .release = dev_release,
+ .read = dev_read,
+ .write = dev_write,
+ /*.unlocked_ioctl = dev_ioctl,*/
+};
+
+static struct miscdevice raydium_ts_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "raydium_ts",
+ .fops = &dev_fops,
+};
+
+
+
+ssize_t show(struct device_driver *drv, char *buff)
+{
+ struct i2c_msg xfer_msg;
+ int num = 10;
+ char buf[100];
+ /*int i;*/
+
+ xfer_msg.addr = pts->client->addr;
+ xfer_msg.len = num;
+ xfer_msg.flags = I2C_M_RD;
+ xfer_msg.buf = buf;
+ pts->client->addr = I2C_CLIENT_ADDR;
+ i2c_transfer(pts->client->adapter, &xfer_msg, 1);
+
+ return 0;
+}
+
+ssize_t store(struct device_driver *drv, const char *buf, size_t count)
+{
+ /*unsigned char pkt[] = { 0xF2, 5, 1, 1 };*/
+ unsigned char pkt[] = { 0xF1, 5, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+
+ pts->client->addr = I2C_CLIENT_ADDR;
+ RM31100_ts_write(pts->client, pkt, sizeof(pkt));
+
+ return sizeof(pkt);
+}
+
+DRIVER_ATTR(myAttr, 0x777, show, store);
+
+static void report_data(struct RM31100_ts *ts, u16 x, u16 y, u8 pressure, u8 id)
+{
+ if (ts->pdata->swap_xy)
+ swap(x, y);
+
+ /* handle inverting coordinates */
+ if (ts->pdata->invert_x)
+ x = ts->pdata->res_x - x;
+ if (ts->pdata->invert_y)
+ y = ts->pdata->res_y - y;
+
+ input_report_abs(ts->input, ABS_MT_TRACKING_ID, id);
+ input_report_abs(ts->input, ABS_MT_POSITION_X, x);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, pressure);
+ input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, ts->dd->finger_size);
+ input_mt_sync(ts->input);
+
+ /*dev_dbg("%s(): id =%2hhd, x =%4hd, y =%4hd, pressure = %hhd\n",
+ __func__, id, x, y, pressure);*/
+}
+
+static void process_RM31100_data(struct RM31100_ts *ts)
+{
+ u8 id, pressure, touches, i;
+ u16 x, y;
+
+ touches = ts->touch_data[ts->dd->touch_index];
+
+ if (touches > 0) {
+ for (i = 0; i < touches; i++) {
+ id = ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->id_index];
+ pressure = ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->z_index];
+ x = join_bytes(ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->x_index + 1],
+ ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->x_index]);
+ y = join_bytes(ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->y_index + 1],
+ ts->touch_data[i * ts->dd->touch_bytes +
+ ts->dd->y_index]);
+ report_data(ts, x, y, pressure, id);
+ }
+ } else
+ input_mt_sync(ts->input);
+
+#if 0
+ for (i = 0; i < ts->prev_touches - (char)touches; i++) {
+ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 0);
+ input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 0);
+ input_mt_sync(ts->input);
+ }
+#endif
+
+ ts->prev_touches = touches;
+ input_report_key(ts->input, BTN_TOUCH, 1);
+ input_sync(ts->input);
+}
+
+static void RM31100_ts_xy_worker(struct work_struct *work)
+{
+ int rc;
+ u8 DMAAddress[4];
+ struct RM31100_ts *ts;
+#if EVAL_REPORT_RATE
+ static struct timeval tv_start;
+ struct timeval tv_now;
+ static int cnt = -1;
+ int us;
+#endif /* EVAL_REPORT_RATE*/
+
+
+ ts = container_of(work, struct RM31100_ts,
+ work.work);
+ /*dev_dbg("****RM31100_ts_xy_worker******\n");*/
+ mutex_lock(&ts->sus_lock);
+ if (ts->is_suspended == true) {
+ dev_dbg(&ts->client->dev, "TS is supended\n");
+ ts->int_pending = true;
+ mutex_unlock(&ts->sus_lock);
+ return;
+ }
+ mutex_unlock(&ts->sus_lock);
+
+ mutex_lock(&ts->access_lock);
+ /* read data from DATA_REG */
+ /*RM31100 DMA Mode*/
+#if 1 /*T010 OR w001+T012*/
+ DMAAddress[0] = 0x0F;
+ DMAAddress[1] = 0x00;
+ DMAAddress[2] = 0x20;
+ DMAAddress[3] = 0x81;/* Turn on DMA Mode*/
+ ts->client->addr = I2C_DMA_CLIENT_ADDR;
+ rc = RM31100_ts_write(ts->client, DMAAddress, 0x04);
+ if (rc < 0) {
+ dev_err(&ts->client->dev, "write failed\n");
+ goto schedule;
+ }
+ ts->client->addr = I2C_CLIENT_ADDR;
+ rc = RM31100_ts_read(ts->client, ts->dd->data_reg, ts->touch_data,
+ ts->dd->data_size);
+#endif
+
+ if (rc < 0) {
+ dev_err(&ts->client->dev, "read failed\n");
+ goto schedule;
+ }
+
+ if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA)
+ goto schedule;
+
+ /* write to STATUS_REG to release lock */
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg, ts->dd->update_data);
+ if (rc < 0) {
+ dev_err(&ts->client->dev, "write failed, try once more\n");
+
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg, ts->dd->update_data);
+ if (rc < 0)
+ dev_err(&ts->client->dev, "write failed, exiting\n");
+ }
+
+ process_RM31100_data(ts);
+
+#if EVAL_REPORT_RATE
+ cnt++;
+
+ if (cnt == 0)/* first time this function is executed.*/
+ do_gettimeofday(&tv_start);
+ else if (cnt == 100) {
+ do_gettimeofday(&tv_now);
+ us = 1000000 * (tv_now.tv_sec - tv_start.tv_sec)
+ + tv_now.tv_usec - tv_start.tv_usec;
+ tv_start.tv_sec = tv_now.tv_sec;
+ tv_start.tv_usec = tv_now.tv_usec;
+ cnt = 0;
+ }
+#endif /* EVAL_REPORT_RATE*/
+schedule:
+
+ mutex_unlock(&ts->access_lock);
+ /*dev_dbg("****Leave RM31100_ts_xy_worker******\n");*/
+ enable_irq(ts->pen_irq);
+}
+
+static irqreturn_t RM31100_ts_irq(int irq, void *dev_id)
+{
+ struct RM31100_ts *ts = dev_id;
+
+ disable_irq_nosync(irq);
+
+ queue_delayed_work(ts->wq, &ts->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int RM31100_ts_init_ts(struct i2c_client *client, struct RM31100_ts *ts)
+{
+ struct input_dev *input_device;
+ int rc = 0;
+
+ ts->dd = &devices[ts->device_id];
+
+ if (!ts->pdata->nfingers) {
+ dev_err(&client->dev, "Touches information not specified\n");
+ return -EINVAL;
+ }
+
+ if (ts->device_id == RM3110x) {
+ if (ts->pdata->nfingers > 2) {
+ dev_err(&client->dev, "Touches >=1 & <= 2\n");
+ return -EINVAL;
+ }
+ ts->dd->data_size = ts->dd->touch_bytes;
+ ts->dd->touch_index = 0x0;
+ } else if (ts->device_id == RM31100) {
+ if (ts->pdata->nfingers > 10) {
+ dev_err(&client->dev, "Touches >=1 & <= 10\n");
+ return -EINVAL;
+ }
+ ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes +
+ ts->dd->touch_meta_data;
+ ts->dd->touch_index = 0x0;
+ }
+#if 1 /* w001 */
+ else {
+ ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes +
+ ts->dd->touch_meta_data;
+ ts->dd->touch_index = 0x0;
+ }
+#endif
+ ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
+ if (!ts->touch_data) {
+ pr_err("%s: Unable to allocate memory\n", __func__);
+ return -ENOMEM;
+ }
+
+ ts->prev_touches = 0;
+
+ input_device = input_allocate_device();
+ if (!input_device) {
+ rc = -ENOMEM;
+ goto error_alloc_dev;
+ }
+
+ ts->input = input_device;
+ input_device->name = ts->pdata->ts_name;
+ input_device->id.bustype = BUS_I2C;
+ input_device->dev.parent = &client->dev;
+ input_set_drvdata(input_device, ts);
+
+ __set_bit(EV_ABS, input_device->evbit);
+ __set_bit(INPUT_PROP_DIRECT, input_device->propbit);
+ /*__set_bit(EV_SYN, input_device->evbit);*/
+ /*__set_bit(BTN_TOUCH, input_device->keybit);*/
+
+
+ if (ts->device_id == RM31100) {
+ /* set up virtual key */
+ __set_bit(EV_KEY, input_device->evbit);
+ /* set dummy key to make driver work with virtual keys */
+ input_set_capability(input_device, EV_KEY, KEY_PROG1);
+ }
+
+ input_set_abs_params(input_device, ABS_MT_POSITION_X,
+ ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0);
+ input_set_abs_params(input_device, ABS_MT_POSITION_Y,
+ ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0);
+#if 0
+ input_set_abs_params(input_device, ABS_MT_TOUCH_MAJOR,
+ ts->pdata->min_touch, ts->pdata->max_touch, 0, 0);
+ input_set_abs_params(input_device, ABS_MT_WIDTH_MAJOR,
+ ts->pdata->min_width, ts->pdata->max_width, 0, 0);
+#endif
+ input_set_abs_params(input_device, ABS_MT_TRACKING_ID,
+ ts->pdata->min_tid, ts->pdata->max_tid, 0, 0);
+
+ ts->wq = create_singlethread_workqueue("kworkqueue_ts");
+ if (!ts->wq) {
+ dev_err(&client->dev, "Could not create workqueue\n");
+ goto error_wq_create;
+ }
+
+ INIT_DELAYED_WORK(&ts->work, RM31100_ts_xy_worker);
+
+ rc = input_register_device(input_device);
+ if (rc)
+ goto error_unreg_device;
+
+ return 0;
+
+error_unreg_device:
+ destroy_workqueue(ts->wq);
+error_wq_create:
+ input_free_device(input_device);
+error_alloc_dev:
+ kfree(ts->touch_data);
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int RM31100_ts_suspend(struct device *dev)
+{
+ struct RM31100_ts *ts = dev_get_drvdata(dev);
+ int rc = 0;
+
+ if (device_may_wakeup(dev)) {
+ /* mark suspend flag */
+ mutex_lock(&ts->sus_lock);
+ ts->is_suspended = true;
+ mutex_unlock(&ts->sus_lock);
+
+ enable_irq_wake(ts->pen_irq);
+ } else {
+ disable_irq_nosync(ts->pen_irq);
+
+ rc = cancel_delayed_work_sync(&ts->work);
+
+ if (rc) {
+ /* missed the worker, write to STATUS_REG to
+ acknowledge interrupt */
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg, ts->dd->update_data);
+ if (rc < 0) {
+ dev_err(&ts->client->dev,
+ "write failed, try once more\n");
+
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg,
+ ts->dd->update_data);
+ if (rc < 0)
+ dev_err(&ts->client->dev,
+ "write failed, exiting\n");
+ }
+
+ enable_irq(ts->pen_irq);
+ }
+
+ gpio_free(ts->pdata->irq_gpio);
+
+ if (ts->pdata->power_on) {
+ rc = ts->pdata->power_on(0);
+ if (rc) {
+ dev_err(dev, "unable to goto suspend\n");
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int RM31100_ts_resume(struct device *dev)
+{
+ struct RM31100_ts *ts = dev_get_drvdata(dev);
+
+ int rc = 0;
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(ts->pen_irq);
+
+ mutex_lock(&ts->sus_lock);
+ ts->is_suspended = false;
+
+ if (ts->int_pending == true) {
+ ts->int_pending = false;
+
+ /* start a delayed work */
+ queue_delayed_work(ts->wq, &ts->work, 0);
+ }
+ mutex_unlock(&ts->sus_lock);
+
+ } else {
+ if (ts->pdata->power_on) {
+ rc = ts->pdata->power_on(1);
+ if (rc) {
+ dev_err(dev, "unable to resume\n");
+ return rc;
+ }
+ }
+
+ /* configure touchscreen interrupt gpio */
+ rc = gpio_request(ts->pdata->irq_gpio, "RM31100_irq_gpio");
+ if (rc) {
+ pr_err("%s: unable to request gpio %d\n",
+ __func__, ts->pdata->irq_gpio);
+ goto err_power_off;
+ }
+ if (ts->pdata->irq_cfg) {
+ s3c_gpio_cfgpin(ts->pdata->irq_gpio,
+ ts->pdata->irq_cfg);
+ s3c_gpio_setpull(ts->pdata->irq_gpio,
+ S3C_GPIO_PULL_NONE);
+ }
+
+ rc = gpio_direction_input(ts->pdata->irq_gpio);
+ if (rc) {
+ pr_err("%s: unable to set direction for gpio %d\n",
+ __func__, ts->pdata->irq_gpio);
+ goto err_gpio_free;
+ }
+
+ enable_irq(ts->pen_irq);
+
+ /* Clear the status register of the TS controller */
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg, ts->dd->update_data);
+ if (rc < 0) {
+ dev_err(&ts->client->dev,
+ "write failed, try once more\n");
+
+ rc = RM31100_ts_write_reg_u8(ts->client,
+ ts->dd->status_reg,
+ ts->dd->update_data);
+ if (rc < 0)
+ dev_err(&ts->client->dev,
+ "write failed, exiting\n");
+ }
+ }
+
+ return 0;
+err_gpio_free:
+ gpio_free(ts->pdata->irq_gpio);
+err_power_off:
+ if (ts->pdata->power_on)
+ rc = ts->pdata->power_on(0);
+ return rc;
+}
+
+#if 0/*def CONFIG_HAS_EARLYSUSPEND*/
+static void RM31100_ts_early_suspend(struct early_suspend *h)
+{
+ struct RM31100_ts *ts =
+ container_of(h, struct RM31100_ts, early_suspend);
+
+ RM31100_ts_suspend(&ts->client->dev);
+}
+
+static void RM31100_ts_late_resume(struct early_suspend *h)
+{
+ struct RM31100_ts *ts = container_of(h,
+ struct RM31100_ts, early_suspend);
+
+ RM31100_ts_resume(&ts->client->dev);
+}
+#endif
+
+static struct dev_pm_ops RM31100_ts_pm_ops = {
+/*#ifndef CONFIG_HAS_EARLYSUSPEND*/
+ .suspend = RM31100_ts_suspend,
+ .resume = RM31100_ts_resume,
+/*#endif*/
+};
+#endif
+
+/*static int __devinit RM31100_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)*/
+static int __init RM31100_ts_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct RM31100_ts *ts;
+ struct RM3110x_ts_platform_data *pdata = client->dev.platform_data;
+ int rc, temp_reg;
+
+ if (!pdata) {
+ dev_err(&client->dev, "platform data is required!\n");
+ return -EINVAL;
+ }
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_WORD_DATA)) {
+ dev_err(&client->dev, "I2C functionality not supported\n");
+ return -EIO;
+ }
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+ pts = ts;
+
+ /* Enable runtime PM ops, start in ACTIVE mode */
+ rc = pm_runtime_set_active(&client->dev);
+ if (rc < 0)
+ dev_warn(&client->dev, "unable to set runtime pm state\n");
+ pm_runtime_enable(&client->dev);
+
+ ts->client = client;
+ ts->pdata = pdata;
+ i2c_set_clientdata(client, ts);
+ ts->device_id = id->driver_data;
+
+ if (ts->pdata->dev_setup) {
+ rc = ts->pdata->dev_setup(1);
+ if (rc < 0) {
+ dev_err(&client->dev, "dev setup failed\n");
+ goto error_touch_data_alloc;
+ }
+ }
+
+ /* power on the device */
+ if (ts->pdata->power_on) {
+ rc = ts->pdata->power_on(1);
+ if (rc) {
+ pr_err("%s: Unable to power on the device\n", __func__);
+ goto error_dev_setup;
+ }
+ }
+
+ /* read one byte to make sure i2c device exists */
+ if (id->driver_data == RM3110x)
+ temp_reg = 0x01;
+ else if (id->driver_data == RM31100)
+ temp_reg = 0x00;
+ else
+ temp_reg = 0x05;
+
+ rc = RM31100_ts_read_reg_u8(client, temp_reg);
+ if (rc < 0) {
+ dev_err(&client->dev, "i2c sanity check failed\n");
+ goto error_power_on;
+ }
+
+ ts->is_suspended = false;
+ ts->int_pending = false;
+ mutex_init(&ts->sus_lock);
+ mutex_init(&ts->access_lock);
+
+ rc = RM31100_ts_init_ts(client, ts);
+ if (rc < 0) {
+ dev_err(&client->dev, "RM31100 init failed\n");
+ goto error_mutex_destroy;
+ }
+
+ if (ts->pdata->resout_gpio < 0)
+ goto config_irq_gpio;
+
+ /* configure touchscreen reset out gpio */
+ rc = gpio_request(ts->pdata->resout_gpio, "RM31100_resout_gpio");
+ if (rc) {
+ pr_err("%s: unable to request gpio %d\n",
+ __func__, ts->pdata->resout_gpio);
+ goto error_uninit_ts;
+ }
+
+ rc = gpio_direction_output(ts->pdata->resout_gpio, 0);
+ if (rc) {
+ pr_err("%s: unable to set direction for gpio %d\n",
+ __func__, ts->pdata->resout_gpio);
+ goto error_resout_gpio_dir;
+ }
+ /* reset gpio stabilization time */
+ msleep(20);
+
+config_irq_gpio:
+ /* configure touchscreen interrupt gpio */
+ rc = gpio_request(ts->pdata->irq_gpio, "RM31100_irq_gpio");
+ if (rc) {
+ pr_err("%s: unable to request gpio %d\n",
+ __func__, ts->pdata->irq_gpio);
+ goto error_irq_gpio_req;
+ }
+
+ rc = gpio_direction_input(ts->pdata->irq_gpio);
+ if (rc) {
+ pr_err("%s: unable to set direction for gpio %d\n",
+ __func__, ts->pdata->irq_gpio);
+ goto error_irq_gpio_dir;
+ }
+
+ ts->pen_irq = gpio_to_irq(ts->pdata->irq_gpio);
+ rc = request_irq(ts->pen_irq, RM31100_ts_irq,
+ IRQF_TRIGGER_FALLING,
+ ts->client->dev.driver->name, ts);
+ if (rc) {
+ dev_err(&ts->client->dev, "could not request irq\n");
+ goto error_req_irq_fail;
+ }
+
+ device_init_wakeup(&client->dev, ts->pdata->wakeup);
+
+#if 0/*def CONFIG_HAS_EARLYSUSPEND*/
+ ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
+ RM31100_TS_SUSPEND_LEVEL;
+ ts->early_suspend.suspend = RM31100_ts_early_suspend;
+ ts->early_suspend.resume = RM31100_ts_late_resume;
+ register_early_suspend(&ts->early_suspend);
+#endif
+
+ rc = misc_register(&raydium_ts_miscdev);
+ if (rc) {
+ dev_err(&ts->client->dev,
+ "Raydium TS: cannot register miscdev:%d\n", rc);
+ goto error_reg_misc_dev;
+ }
+
+
+ return 0;
+error_reg_misc_dev:
+error_req_irq_fail:
+error_irq_gpio_dir:
+ gpio_free(ts->pdata->irq_gpio);
+error_irq_gpio_req:
+error_resout_gpio_dir:
+ if (ts->pdata->resout_gpio >= 0)
+ gpio_free(ts->pdata->resout_gpio);
+error_uninit_ts:
+ destroy_workqueue(ts->wq);
+ input_unregister_device(ts->input);
+ kfree(ts->touch_data);
+error_mutex_destroy:
+ mutex_destroy(&ts->sus_lock);
+ mutex_destroy(&ts->access_lock);
+error_power_on:
+/* if (ts->pdata->power_on)
+ ts->pdata->power_on(0);*/
+error_dev_setup:
+ if (ts->pdata->dev_setup)
+ ts->pdata->dev_setup(0);
+error_touch_data_alloc:
+ pm_runtime_set_suspended(&client->dev);
+ pm_runtime_disable(&client->dev);
+ kfree(ts);
+ return rc;
+}
+
+/*static int __devexit RM31100_ts_remove(struct i2c_client *client)*/
+static int __exit RM31100_ts_remove(struct i2c_client *client)
+{
+ struct RM31100_ts *ts = i2c_get_clientdata(client);
+
+#if 0/* defined(CONFIG_HAS_EARLYSUSPEND)*/
+ unregister_early_suspend(&ts->early_suspend);
+#endif
+ pm_runtime_set_suspended(&client->dev);
+ pm_runtime_disable(&client->dev);
+
+ device_init_wakeup(&client->dev, 0);
+
+ cancel_delayed_work_sync(&ts->work);
+
+ free_irq(ts->pen_irq, ts);
+
+ gpio_free(ts->pdata->irq_gpio);
+
+ if (ts->pdata->resout_gpio >= 0)
+ gpio_free(ts->pdata->resout_gpio);
+
+ destroy_workqueue(ts->wq);
+
+ input_unregister_device(ts->input);
+
+ mutex_destroy(&ts->sus_lock);
+ mutex_destroy(&ts->access_lock);
+
+ if (ts->pdata->power_on)
+ ts->pdata->power_on(0);
+
+ if (ts->pdata->dev_setup)
+ ts->pdata->dev_setup(0);
+
+ kfree(ts->touch_data);
+ kfree(ts);
+
+ return 0;
+}
+
+static const struct i2c_device_id RM31100_ts_id[] = {
+ {"RM31100", RM31100},
+ {"RM3110x", RM3110x},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, RM31100_ts_id);
+
+
+static struct i2c_driver RM31100_ts_driver = {
+ .driver = {
+ .name = "RM31100_ts",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &RM31100_ts_pm_ops,
+#endif
+ },
+ .probe = RM31100_ts_probe,
+ /*.remove = __devexit_p(RM31100_ts_remove),*/
+ .remove = __exit_p(RM31100_ts_remove),
+ .id_table = RM31100_ts_id,
+};
+
+static int __init RM31100_ts_init(void)
+{
+ int rc;
+ int rc2;
+
+ rc = i2c_add_driver(&RM31100_ts_driver);
+
+ rc2 = driver_create_file(&RM31100_ts_driver.driver,
+ &driver_attr_myAttr);
+
+ return rc;
+}
+/* Making this as late init to avoid power fluctuations
+ * during LCD initialization.
+ */
+late_initcall(RM31100_ts_init);
+
+static void __exit RM31100_ts_exit(void)
+{
+ return i2c_del_driver(&RM31100_ts_driver);
+}
+module_exit(RM31100_ts_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RM31100-RM3110x touchscreen controller driver");
+MODULE_AUTHOR("Raydium");
+MODULE_ALIAS("platform:RM31100_ts");
diff --git a/include/linux/input/RM31100.h b/include/linux/input/RM31100.h
new file mode 100644
index 0000000..714a14b
--- /dev/null
+++ b/include/linux/input/RM31100.h
@@ -0,0 +1,60 @@
+/* Header file for:
+ * Raydium RM31100 Prototype touchscreen driver.
+ *
+ * Copyright (C) 2012, Raydium Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, and only version 2, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Raydium reserves the right to make changes without further notice
+ * to the materials described herein. Raydium does not assume any
+ * liability arising out of the application described herein.
+ *
+ * Contact Raydium Semiconductor at www.rad-ic.com
+ *
+ * History:
+ * (C) 2012 Raydium - Update for GPL distribution
+ * (C) 2009 Enea - Original prototype
+ *
+ */
+#ifndef __RM3110xTS_H__
+#define __RM3110xTS_H__
+
+
+/* RM3110x platform data
+ */
+struct RM3110x_ts_platform_data {
+ int (*power_on)(int on);
+ int (*dev_setup)(bool on);
+ const char *ts_name;
+ u32 dis_min_x; /* display resoltion */
+ u32 dis_max_x;
+ u32 dis_min_y;
+ u32 dis_max_y;
+ u32 min_touch; /* no.of touches supported */
+ u32 max_touch;
+ u32 min_tid; /* track id */
+ u32 max_tid;
+ u32 min_width;/* size of the finger */
+ u32 max_width;
+ u32 res_x; /* TS resolution */
+ u32 res_y;
+ u32 swap_xy;
+ u32 flags;
+ u16 invert_x;
+ u16 invert_y;
+ u8 nfingers;
+ u32 irq_gpio;
+ int resout_gpio;
+ bool wakeup;
+ u32 irq_cfg;
+};
+
+#endif
--
2.1.2
^ permalink raw reply related
* Re: [PATCH] HID: yet another buggy ELAN touchscreen
From: Jiri Kosina @ 2014-11-19 14:52 UTC (permalink / raw)
To: Oliver Neukum
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA, stable-u79uwXL29TY76Z2rM5mHXA,
Greg Kroah-Hartman, linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1416240702-28988-1-git-send-email-oneukum-l3A5Bk7waGM@public.gmane.org>
[ Greg and linux-usb@ added to CC ]
On Mon, 17 Nov 2014, Oliver Neukum wrote:
> The touchscreen needs the same quirk as the other models.
>
> Signed-off-by: Oliver Neukum <oneukum-l3A5Bk7waGM@public.gmane.org>
> Reported-by: Bryan Poling <poli0048-OJFnDUYgAso@public.gmane.org>
> CC: stable-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> ---
> drivers/hid/hid-ids.h | 1 +
> drivers/hid/usbhid/hid-quirks.c | 1 +
> drivers/usb/core/quirks.c | 3 +++
Greg, are you OK with me taking the whole lot through hid.git in one
commit? (USB quirks and HID quirks for Elan devices have been merged
independently for previous 3 devices, but I don't think it's worth the
hassle splitting it).
Thanks.
> 3 files changed, 5 insertions(+)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index e23ab8b..282ffbe 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -299,6 +299,7 @@
> #define USB_VENDOR_ID_ELAN 0x04f3
> #define USB_DEVICE_ID_ELAN_TOUCHSCREEN 0x0089
> #define USB_DEVICE_ID_ELAN_TOUCHSCREEN_009B 0x009b
> +#define USB_DEVICE_ID_ELAN_TOUCHSCREEN_010c 0x010c
> #define USB_DEVICE_ID_ELAN_TOUCHSCREEN_016F 0x016f
>
> #define USB_VENDOR_ID_ELECOM 0x056e
> diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
> index 5014bb5..08b9626 100644
> --- a/drivers/hid/usbhid/hid-quirks.c
> +++ b/drivers/hid/usbhid/hid-quirks.c
> @@ -72,6 +72,7 @@ static const struct hid_blacklist {
> { USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET },
> { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN, HID_QUIRK_ALWAYS_POLL },
> { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_009B, HID_QUIRK_ALWAYS_POLL },
> + { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_010c, HID_QUIRK_ALWAYS_POLL },
> { USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ELAN_TOUCHSCREEN_016F, HID_QUIRK_ALWAYS_POLL },
> { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET },
> { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
> diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
> index 39b4081..8e8bc4f 100644
> --- a/drivers/usb/core/quirks.c
> +++ b/drivers/usb/core/quirks.c
> @@ -100,6 +100,9 @@ static const struct usb_device_id usb_quirk_list[] = {
> { USB_DEVICE(0x04f3, 0x009b), .driver_info =
> USB_QUIRK_DEVICE_QUALIFIER },
>
> + { USB_DEVICE(0x04f3, 0x010c), .driver_info =
> + USB_QUIRK_DEVICE_QUALIFIER },
> +
> { USB_DEVICE(0x04f3, 0x016f), .driver_info =
> USB_QUIRK_DEVICE_QUALIFIER },
>
> --
> 1.8.4.5
>
--
Jiri Kosina
SUSE Labs
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 2/2] DT: i2c: Add more devices handled by the adxl34x-i2c driver
From: Wolfram Sang @ 2014-11-19 14:21 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Dmitry Torokhov, Alessandro Zummo, devicetree, linux-input,
rtc-linux, linux-kernel, linux-i2c
In-Reply-To: <1415981011-13423-2-git-send-email-geert+renesas@glider.be>
[-- Attachment #1: Type: text/plain, Size: 220 bytes --]
On Fri, Nov 14, 2014 at 05:03:31PM +0100, Geert Uytterhoeven wrote:
> This allows checkpatch to validate more DTSes.
>
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Applied to for-next, thanks!
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH 1/2] DT: i2c: Add more devices handled by the rtc-rs5c372 driver
From: Wolfram Sang @ 2014-11-19 14:21 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Dmitry Torokhov, Alessandro Zummo, devicetree, linux-input,
rtc-linux, linux-kernel, linux-i2c
In-Reply-To: <1415981011-13423-1-git-send-email-geert+renesas@glider.be>
[-- Attachment #1: Type: text/plain, Size: 220 bytes --]
On Fri, Nov 14, 2014 at 05:03:30PM +0100, Geert Uytterhoeven wrote:
> This allows checkpatch to validate more DTSes.
>
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Applied to for-next, thanks!
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH v3] hid: sony: Use kernel allocated buffers for HID reports
From: Jiri Kosina @ 2014-11-19 9:24 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Frank Praznik, linux-input, ao2
In-Reply-To: <20141113235830.GB40569@dtor-ws>
On Thu, 13 Nov 2014, Dmitry Torokhov wrote:
> On Wed, Nov 12, 2014 at 02:10:09PM -0500, Frank Praznik wrote:
> > Replace stack buffers with kernel allocated buffers for sending
> > and receiving HID reports to prevent issues with DMA transfers
> > on certain hardware.
> >
> > Output report buffers are allocated at initialization time to avoid
> > excessive calls to kmalloc and kfree.
> >
> > Signed-off-by: Frank Praznik <frank.praznik@oh.rr.com>
>
> Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Now applied, thanks guys.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* [PATCH v11 18/19] input: cyapa: add gen5 trackpad device read raw data function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read raw data function supported for gen5 trackpad device,
it can be used through debugfs raw_data interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen5.c | 138 +++++++++++++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index aee3e57..52c915b 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -2363,6 +2363,143 @@ resume_scanning:
return size;
}
+static int cyapa_gen5_read_electrodies_rx_tx(struct cyapa *cyapa)
+{
+ u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x71 };
+ u8 resp_data[7];
+ int resp_len;
+ int error;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 150, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x71) ||
+ !resp_data[5] || !resp_data[6])
+ return -EINVAL;
+
+ cyapa->electrodes_rx = resp_data[6];
+
+ return 0;
+}
+
+static int cyapa_gen5_read_raw_data(struct cyapa *cyapa)
+{
+ int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+ int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+ int offset;
+ int data_size, max, min, ave;
+ ktime_t time_mono;
+ int error, resume_error;
+
+ offset = 0;
+ if (!cyapa->tp_raw_data) {
+ if (cyapa->state != CYAPA_STATE_GEN5_APP ||
+ !cyapa->electrodes_x || !cyapa->electrodes_y)
+ return -EINVAL;
+
+ 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 = devm_kzalloc(&cyapa->client->dev,
+ cyapa->tp_raw_data_size, GFP_KERNEL);
+ if (!cyapa->tp_raw_data)
+ return -ENOMEM;
+ }
+
+ /*
+ * 1. Suspend Scanning.
+ *
+ * After suspend scanning, the raw data will not be updated,
+ * so the time of the raw data is before scanning suspended.
+ */
+ time_mono = ktime_get();
+ error = cyapa_gen5_suspend_scanning(cyapa);
+ if (error)
+ return error;
+
+ /* 2. Get the correct electrodes_rx number. */
+ if (cyapa->electrodes_rx == 0) {
+ error = cyapa_gen5_read_electrodies_rx_tx(cyapa);
+ if (error || cyapa->electrodes_rx == 0) {
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /*
+ * Since, old firmware doesn't support the command to
+ * read the electrodies' Rx and Tx values, so using
+ * the read global idac interface to get the Rx number,
+ * this value is useful to analyze and
+ * display the raw data map in userspace.
+ */
+ data_size = 0;
+ error = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size, &max, &min, &ave);
+ if (error || cyapa->electrodes_rx == 0)
+ goto resume_scanning;
+ }
+ }
+
+ /* 3. Execuate panel scan. It must be executed before read data. */
+ error = cyapa_gen5_execute_panel_scan(cyapa);
+ if (error)
+ goto resume_scanning;
+
+ /* 4. Retrieve panel scan, mutual cap raw data. */
+ offset = GEN5_RAW_DATA_HEAD_SIZE;
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &raw_cap_mutual_max, &raw_cap_mutual_min,
+ &raw_cap_mutual_ave,
+ cyapa->tp_raw_data + offset);
+ if (error)
+ goto resume_scanning;
+
+ offset += sizeof(s32) * cyapa->electrodes_x * cyapa->electrodes_y;
+
+ /* 5. Retrieve panel scan, self cap raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &raw_cap_self_max, &raw_cap_self_min,
+ &raw_cap_self_ave,
+ cyapa->tp_raw_data + offset);
+ if (error)
+ goto resume_scanning;
+
+ offset += sizeof(s32) * (cyapa->electrodes_x + cyapa->electrodes_y);
+
+resume_scanning:
+ /* 6. Resume Scanning*/
+ resume_error = cyapa_gen5_resume_scanning(cyapa);
+ if (resume_error || error)
+ return resume_error ? resume_error : error;
+
+ *((struct timeval *)&cyapa->tp_raw_data[0]) =
+ ktime_to_timeval(time_mono);
+ cyapa->tp_raw_data[16] = (u8)cyapa->electrodes_x;
+ cyapa->tp_raw_data[17] = (u8)cyapa->electrodes_y;
+ cyapa->tp_raw_data[18] = (u8)cyapa->x_origin;
+ cyapa->tp_raw_data[19] = (u8)cyapa->y_origin;
+ cyapa->tp_raw_data[20] = (u8)sizeof(s32);
+ cyapa->tp_raw_data[21] = (u8)sizeof(s32);
+ cyapa->tp_raw_data[22] = (u8)cyapa->electrodes_rx;
+ cyapa->tp_raw_data[23] = 0; /* Reserved. */
+
+ cyapa->tp_raw_data_size = offset;
+ return 0;
+}
+
static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
u8 *buf, int len)
{
@@ -2776,6 +2913,7 @@ const struct cyapa_dev_ops cyapa_gen5_ops = {
.calibrate_store = cyapa_gen5_do_calibrate,
.read_fw = cyapa_gen5_read_fw,
+ .read_raw_data = cyapa_gen5_read_raw_data,
.initialize = cyapa_gen5_initialize,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 17/19] input: cyapa: add read sensors raw data debugfs interface support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read sensors' raw data from trackpad device interface supported in cyapa
driver through debugfs raw_data interface.
Through this interface, user can read difference count map of each sensors
directly from trackpad device (some customers want). And it's useful to help
users to find out the root cause when there is performance gap happened.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 105 ++++++++++++++++++++++++++++++++++++++++++++
drivers/input/mouse/cyapa.h | 4 ++
2 files changed, 109 insertions(+)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 2e394c0..ca31119 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -34,6 +34,7 @@
#define CYAPA_ADAPTER_FUNC_BOTH 3
#define CYAPA_DEBUGFS_READ_FW "read_fw"
+#define CYAPA_DEBUGFS_RAW_DATA "raw_data"
#define CYAPA_FW_NAME "cyapa.bin"
const char unique_str[] = "CYTRA";
@@ -604,6 +605,106 @@ static const struct file_operations cyapa_read_fw_fops = {
.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 error;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ /* Start to be supported after Gen5 trackpad devices. */
+ if (cyapa->gen < CYAPA_GEN5)
+ return -ENOTSUPP;
+
+ error = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (error)
+ return error;
+
+ if (!get_device(&cyapa->client->dev)) {
+ error = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ /*
+ * Enale IRQ for raw data read command, and must be enable during
+ * the whole process.
+ */
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ goto out;
+ cyapa_enable_irq_for_cmd(cyapa);
+ mutex_unlock(&cyapa->state_sync_lock);
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return error;
+}
+
+static int cyapa_debugfs_raw_data_release(struct inode *inode,
+ struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int error;
+
+ if (!cyapa)
+ return 0;
+
+ /* Disable IRQ if enabled when raw_data file was opened. */
+ mutex_lock(&cyapa->state_sync_lock);
+ cyapa_disable_irq_for_cmd(cyapa);
+ mutex_unlock(&cyapa->state_sync_lock);
+
+ error = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (error)
+ return error;
+ file->private_data = NULL;
+ put_device(&cyapa->client->dev);
+ 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)
+{
+ int error;
+ struct cyapa *cyapa = file->private_data;
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ if (cyapa->ops->read_raw_data)
+ error = cyapa->ops->read_raw_data(cyapa);
+ else
+ error = -EPERM;
+ mutex_unlock(&cyapa->state_sync_lock);
+ if (error)
+ return error;
+
+ 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;
@@ -622,6 +723,10 @@ static int cyapa_debugfs_init(struct cyapa *cyapa)
debugfs_create_file(CYAPA_DEBUGFS_READ_FW, S_IRUSR, cyapa->dentry_dev,
cyapa, &cyapa_read_fw_fops);
+ if (cyapa->gen >= CYAPA_GEN5)
+ debugfs_create_file(CYAPA_DEBUGFS_RAW_DATA, S_IRUSR,
+ cyapa->dentry_dev, cyapa, &cyapa_read_raw_data_fops);
+
return 0;
}
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index b6e1b22..9dbf92f 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -187,6 +187,7 @@ struct cyapa_dev_ops {
struct device_attribute *, const char *, size_t);
int (*read_fw)(struct cyapa *);
+ int (*read_raw_data)(struct cyapa *);
int (*initialize)(struct cyapa *cyapa);
@@ -308,6 +309,9 @@ struct cyapa {
struct cyapa_tsg_bin_image_head fw_img_head;
u8 *fw_image;
size_t fw_image_size;
+ /* Buffer to store sensors' raw data */
+ u8 *tp_raw_data;
+ size_t tp_raw_data_size;
const struct cyapa_dev_ops *ops;
--
1.9.1
^ permalink raw reply related
* [PATCH v11 01/19] input: cyapa: modify code to following kernel code style
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
This patch modified the code to fix the patch check warning issue with latest
checkpatch.sh tool, and also changed the return variable name from "ret" to
"error" when there is only one error path to follow code style.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 151 ++++++++++++++++++++++----------------------
1 file changed, 75 insertions(+), 76 deletions(-)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index b409c3d..c35f398 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -409,11 +409,11 @@ static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
cmd = cyapa_smbus_cmds[cmd_idx].cmd;
len = cyapa_smbus_cmds[cmd_idx].len;
return cyapa_smbus_read_block(cyapa, cmd, len, values);
- } else {
- cmd = cyapa_i2c_cmds[cmd_idx].cmd;
- len = cyapa_i2c_cmds[cmd_idx].len;
- return cyapa_i2c_reg_read_block(cyapa, cmd, len, values);
}
+
+ cmd = cyapa_i2c_cmds[cmd_idx].cmd;
+ len = cyapa_i2c_cmds[cmd_idx].len;
+ return cyapa_i2c_reg_read_block(cyapa, cmd, len, values);
}
/*
@@ -422,8 +422,8 @@ static ssize_t cyapa_read_block(struct cyapa *cyapa, u8 cmd_idx, u8 *values)
*/
static int cyapa_get_state(struct cyapa *cyapa)
{
- int ret;
u8 status[BL_STATUS_SIZE];
+ int error;
cyapa->state = CYAPA_STATE_NO_DEVICE;
@@ -433,7 +433,7 @@ static int cyapa_get_state(struct cyapa *cyapa)
* If the device is in operation mode, this will be the DATA regs.
*
*/
- ret = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
+ error = cyapa_i2c_reg_read_block(cyapa, BL_HEAD_OFFSET, BL_STATUS_SIZE,
status);
/*
@@ -441,10 +441,10 @@ static int cyapa_get_state(struct cyapa *cyapa)
* -ETIMEDOUT. In this case, try again using the smbus equivalent
* command. This should return a BL_HEAD indicating CYAPA_STATE_OP.
*/
- if (cyapa->smbus && (ret == -ETIMEDOUT || ret == -ENXIO))
- ret = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
+ if (cyapa->smbus && (error == -ETIMEDOUT || error == -ENXIO))
+ error = cyapa_read_block(cyapa, CYAPA_CMD_BL_STATUS, status);
- if (ret != BL_STATUS_SIZE)
+ if (error != BL_STATUS_SIZE)
goto error;
if ((status[REG_OP_STATUS] & OP_STATUS_SRC) == OP_STATUS_SRC) {
@@ -454,7 +454,7 @@ static int cyapa_get_state(struct cyapa *cyapa)
cyapa->state = CYAPA_STATE_OP;
break;
default:
- ret = -EAGAIN;
+ error = -EAGAIN;
goto error;
}
} else {
@@ -468,7 +468,7 @@ static int cyapa_get_state(struct cyapa *cyapa)
return 0;
error:
- return (ret < 0) ? ret : -EAGAIN;
+ return (error < 0) ? error : -EAGAIN;
}
/*
@@ -487,31 +487,31 @@ error:
*/
static int cyapa_poll_state(struct cyapa *cyapa, unsigned int timeout)
{
- int ret;
+ int error;
int tries = timeout / 100;
- ret = cyapa_get_state(cyapa);
- while ((ret || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
+ error = cyapa_get_state(cyapa);
+ while ((error || cyapa->state >= CYAPA_STATE_BL_BUSY) && tries--) {
msleep(100);
- ret = cyapa_get_state(cyapa);
+ error = cyapa_get_state(cyapa);
}
- return (ret == -EAGAIN || ret == -ETIMEDOUT) ? -ETIMEDOUT : ret;
+ return (error == -EAGAIN || error == -ETIMEDOUT) ? -ETIMEDOUT : error;
}
static int cyapa_bl_deactivate(struct cyapa *cyapa)
{
- int ret;
+ int error;
- ret = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_deactivate),
+ error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_deactivate),
bl_deactivate);
- if (ret < 0)
- return ret;
+ if (error)
+ return error;
/* wait for bootloader to switch to idle state; should take < 100ms */
msleep(100);
- ret = cyapa_poll_state(cyapa, 500);
- if (ret < 0)
- return ret;
+ error = cyapa_poll_state(cyapa, 500);
+ if (error)
+ return error;
if (cyapa->state != CYAPA_STATE_BL_IDLE)
return -EAGAIN;
return 0;
@@ -532,11 +532,11 @@ static int cyapa_bl_deactivate(struct cyapa *cyapa)
*/
static int cyapa_bl_exit(struct cyapa *cyapa)
{
- int ret;
+ int error;
- ret = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_exit), bl_exit);
- if (ret < 0)
- return ret;
+ error = cyapa_i2c_reg_write_block(cyapa, 0, sizeof(bl_exit), bl_exit);
+ if (error)
+ return error;
/*
* Wait for bootloader to exit, and operation mode to start.
@@ -548,9 +548,9 @@ static int cyapa_bl_exit(struct cyapa *cyapa)
* updated to new firmware, it must first calibrate its sensors, which
* can take up to an additional 2 seconds.
*/
- ret = cyapa_poll_state(cyapa, 2000);
- if (ret < 0)
- return ret;
+ error = cyapa_poll_state(cyapa, 2000);
+ if (error < 0)
+ return error;
if (cyapa->state != CYAPA_STATE_OP)
return -EAGAIN;
@@ -637,28 +637,28 @@ static int cyapa_check_is_operational(struct cyapa *cyapa)
{
struct device *dev = &cyapa->client->dev;
static const char unique_str[] = "CYTRA";
- int ret;
+ int error;
- ret = cyapa_poll_state(cyapa, 2000);
- if (ret < 0)
- return ret;
+ error = cyapa_poll_state(cyapa, 2000);
+ if (error)
+ return error;
switch (cyapa->state) {
case CYAPA_STATE_BL_ACTIVE:
- ret = cyapa_bl_deactivate(cyapa);
- if (ret)
- return ret;
+ error = cyapa_bl_deactivate(cyapa);
+ if (error)
+ return error;
/* Fallthrough state */
case CYAPA_STATE_BL_IDLE:
- ret = cyapa_bl_exit(cyapa);
- if (ret)
- return ret;
+ error = cyapa_bl_exit(cyapa);
+ if (error)
+ return error;
/* Fallthrough state */
case CYAPA_STATE_OP:
- ret = cyapa_get_query_data(cyapa);
- if (ret < 0)
- return ret;
+ error = cyapa_get_query_data(cyapa);
+ if (error)
+ return error;
/* only support firmware protocol gen3 */
if (cyapa->gen != CYAPA_GEN3) {
@@ -756,8 +756,8 @@ static u8 cyapa_check_adapter_functionality(struct i2c_client *client)
static int cyapa_create_input_dev(struct cyapa *cyapa)
{
struct device *dev = &cyapa->client->dev;
- int ret;
struct input_dev *input;
+ int error;
if (!cyapa->physical_size_x || !cyapa->physical_size_y)
return -EINVAL;
@@ -802,17 +802,18 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
/* handle pointer emulation and unused slots in core */
- ret = input_mt_init_slots(input, CYAPA_MAX_MT_SLOTS,
+ error = input_mt_init_slots(input, CYAPA_MAX_MT_SLOTS,
INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED);
- if (ret) {
- dev_err(dev, "allocate memory for MT slots failed, %d\n", ret);
+ if (error) {
+ dev_err(dev, "allocate memory for MT slots failed, %d\n",
+ error);
goto err_free_device;
}
/* Register the device in input subsystem */
- ret = input_register_device(input);
- if (ret) {
- dev_err(dev, "input device register failed, %d\n", ret);
+ error = input_register_device(input);
+ if (error) {
+ dev_err(dev, "input device register failed, %d\n", error);
goto err_free_device;
}
return 0;
@@ -820,16 +821,16 @@ static int cyapa_create_input_dev(struct cyapa *cyapa)
err_free_device:
input_free_device(input);
cyapa->input = NULL;
- return ret;
+ return error;
}
static int cyapa_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
- int ret;
u8 adapter_func;
struct cyapa *cyapa;
struct device *dev = &client->dev;
+ int error;
adapter_func = cyapa_check_adapter_functionality(client);
if (adapter_func == CYAPA_ADAPTER_FUNC_NONE) {
@@ -838,10 +839,8 @@ static int cyapa_probe(struct i2c_client *client,
}
cyapa = kzalloc(sizeof(struct cyapa), GFP_KERNEL);
- if (!cyapa) {
- dev_err(dev, "allocate memory for cyapa failed\n");
+ if (!cyapa)
return -ENOMEM;
- }
cyapa->gen = CYAPA_GEN3;
cyapa->client = client;
@@ -853,33 +852,33 @@ static int cyapa_probe(struct i2c_client *client,
if (adapter_func == CYAPA_ADAPTER_FUNC_SMBUS)
cyapa->smbus = true;
cyapa->state = CYAPA_STATE_NO_DEVICE;
- ret = cyapa_check_is_operational(cyapa);
- if (ret) {
- dev_err(dev, "device not operational, %d\n", ret);
+ error = cyapa_check_is_operational(cyapa);
+ if (error) {
+ dev_err(dev, "device not operational, %d\n", error);
goto err_mem_free;
}
- ret = cyapa_create_input_dev(cyapa);
- if (ret) {
- dev_err(dev, "create input_dev instance failed, %d\n", ret);
+ error = cyapa_create_input_dev(cyapa);
+ if (error) {
+ dev_err(dev, "create input_dev instance failed, %d\n", error);
goto err_mem_free;
}
- ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
- if (ret) {
- dev_err(dev, "set active power failed, %d\n", ret);
+ error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
+ if (error) {
+ dev_err(dev, "set active power failed, %d\n", error);
goto err_unregister_device;
}
cyapa->irq = client->irq;
- ret = request_threaded_irq(cyapa->irq,
+ error = request_threaded_irq(cyapa->irq,
NULL,
cyapa_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"cyapa",
cyapa);
- if (ret) {
- dev_err(dev, "IRQ request failed: %d\n, ", ret);
+ if (error) {
+ dev_err(dev, "IRQ request failed: %d\n, ", error);
goto err_unregister_device;
}
@@ -890,7 +889,7 @@ err_unregister_device:
err_mem_free:
kfree(cyapa);
- return ret;
+ return error;
}
static int cyapa_remove(struct i2c_client *client)
@@ -908,7 +907,7 @@ static int cyapa_remove(struct i2c_client *client)
#ifdef CONFIG_PM_SLEEP
static int cyapa_suspend(struct device *dev)
{
- int ret;
+ int error;
u8 power_mode;
struct cyapa *cyapa = dev_get_drvdata(dev);
@@ -920,9 +919,9 @@ static int cyapa_suspend(struct device *dev)
*/
power_mode = device_may_wakeup(dev) ? PWR_MODE_IDLE
: PWR_MODE_OFF;
- ret = cyapa_set_power_mode(cyapa, power_mode);
- if (ret < 0)
- dev_err(dev, "set power mode failed, %d\n", ret);
+ error = cyapa_set_power_mode(cyapa, power_mode);
+ if (error)
+ dev_err(dev, "set power mode failed, %d\n", error);
if (device_may_wakeup(dev))
cyapa->irq_wake = (enable_irq_wake(cyapa->irq) == 0);
@@ -931,15 +930,15 @@ static int cyapa_suspend(struct device *dev)
static int cyapa_resume(struct device *dev)
{
- int ret;
+ int error;
struct cyapa *cyapa = dev_get_drvdata(dev);
if (device_may_wakeup(dev) && cyapa->irq_wake)
disable_irq_wake(cyapa->irq);
- ret = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
- if (ret)
- dev_warn(dev, "resume active power failed, %d\n", ret);
+ error = cyapa_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE);
+ if (error)
+ dev_warn(dev, "resume active power failed, %d\n", error);
enable_irq(cyapa->irq);
return 0;
--
1.9.1
^ permalink raw reply related
* [PATCH v11 00/19] input: cyapa: instruction of cyapa patches
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg
Cc: Dudley Du, bleung, dso, linux-input, linux-kernel
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.
This patch set 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 18 patches listed as below.
For these patches each one is patched based on previous one.
patch 1/19: modify code to following kernel code style.
patch 2/19: add device resource management infrastructure support.
patch 3/19: re-design cyapa driver with core functions and interface
to support multi-type trackpad devices.
patch 4/19: add gen5 trackpad device basic functions supported into the
re-design cyapa driver.
patch 5/19: add power management interfaces supported for the deivce.
patch 6/19: add runtime power management interfaces supported for the device.
patch 7/19: 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 8/19: add gen3 trackpad device's firmware update function supported.
patch 9/19: add gen3 trackpad device's read baseline function supported.
patch 10/19: add gen3 trackpad device's force re-calibrate function supported.
patch 11/19: add gen5 trackpad device's firmware update function supported.
patch 12/19: add gen5 trackpad device's read baseline function supported.
patch 13/19: add gen5 trackpad device's force re-calibrate function.
patch 14/19: add read firmware image debugfs interface supported
in the cyapa driver.
patch 15/19: add gen3 trackpad device's read firmware image function supported.
patch 16/19: add gen5 trackpad device's read firmware image function supported.
patch 17/19: add read trackpad device's sensors' raw data debugfs interface
supported in the cyapa driver.
patch 18/19: add gen5 trackpad device's read read raw function supported.
patch 19/19: add acpi device id supported.
^ permalink raw reply
* [PATCH v11 19/19] input: cyapa: add acpi device id supported
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add acpi device tree supported.
acpi device id "CYAP0000" is for old gen3 trackpad devices.
acpi device id "CYAP0001" is for new gen5 trackpad devices.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index ca31119..eb53aa0 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -25,6 +25,7 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
+#include <linux/acpi.h>
#include "cyapa.h"
@@ -1501,11 +1502,23 @@ static const struct i2c_device_id cyapa_id_table[] = {
};
MODULE_DEVICE_TABLE(i2c, cyapa_id_table);
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id cyapa_acpi_id[] = {
+ { "CYAP0000", 0 }, /* Gen3 trackpad with 0x67 I2C address. */
+ { "CYAP0001", 0 }, /* Gen5 trackpad with 0x24 I2C address. */
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, cyapa_acpi_id);
+#endif
+
static struct i2c_driver cyapa_driver = {
.driver = {
.name = "cyapa",
.owner = THIS_MODULE,
.pm = &cyapa_pm_ops,
+#ifdef CONFIG_ACPI
+ .acpi_match_table = ACPI_PTR(cyapa_acpi_id),
+#endif
},
.probe = cyapa_probe,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 16/19] input: cyapa: add gen5 trackpad device read firmware image function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read firmware image function supported for gen5 trackpad device,
it can be used through debugfs read_fw interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.h | 1 +
drivers/input/mouse/cyapa_gen5.c | 155 +++++++++++++++++++++++++++++++++++++++
2 files changed, 156 insertions(+)
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index ceed843..b6e1b22 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -305,6 +305,7 @@ struct cyapa {
/* Buffer to store firmware read using debugfs */
struct mutex debugfs_mutex;
+ struct cyapa_tsg_bin_image_head fw_img_head;
u8 *fw_image;
size_t fw_image_size;
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index 51bdf06..aee3e57 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -1210,6 +1210,154 @@ static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
return 0;
}
+static int cyapa_gen5_read_fw_bytes(struct cyapa *cyapa, u16 row_num, u8 *data)
+{
+ u8 cmd[16];
+ size_t cmd_len;
+ u8 resp_data[CYAPA_TSG_FW_ROW_SIZE / 2 + GEN5_MIN_BL_RESP_LENGTH];
+ int resp_len;
+ u16 offset;
+ u16 cmd_crc;
+ struct cyapa_tsg_bin_image_data_record *fw_img_record;
+ int error;
+
+ fw_img_record = (struct cyapa_tsg_bin_image_data_record *)data;
+
+ cmd[0] = 0x04; /* Register address */
+ cmd[1] = 0x00;
+ cmd[2] = 0x0e;
+ cmd[3] = 0x00;
+ cmd[4] = 0x40; /* Report id 40h */
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_SOP_KEY;
+ cmd[7] = 0x3d; /* Read application image command code */
+ cmd[8] = 0x03;
+ cmd[9] = 0x00;
+ offset = row_num * CYAPA_TSG_FW_ROW_SIZE -
+ CYAPA_TSG_START_OF_APPLICATION;
+ put_unaligned_le16(offset, &cmd[10]);
+ cmd[12] = CYAPA_TSG_IMG_READ_SIZE;
+ cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+ put_unaligned_le16(cmd_crc, &cmd[13]); /* CRC[15:0] */
+ cmd[15] = GEN5_EOP_KEY; /* EOP = 17h */
+ cmd_len = 16;
+
+ resp_len = CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 50, cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+ if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+ error || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EAGAIN;
+
+ /* Copy first 64 bytes in the row. */
+ memcpy(&fw_img_record->record_data[0], &resp_data[8],
+ CYAPA_TSG_IMG_READ_SIZE);
+
+ if (row_num == CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM) {
+ /* Last row's rest 64 bytes are bootloader metadata,
+ * it's not allowed to be read out, will respond with error. */
+ memset(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+ 0, CYAPA_TSG_IMG_READ_SIZE);
+ goto skip_last_row;
+ }
+
+ /* Read next 64 bytes in the row. */
+ offset = offset + CYAPA_TSG_IMG_READ_SIZE;
+ put_unaligned_le16(offset, &cmd[10]);
+ cmd_crc = crc_itu_t(0xffff, &cmd[6], 7);
+ put_unaligned_le16(cmd_crc, &cmd[13]); /* CRC[15:0] */
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, cmd_len,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+ if (resp_len != (CYAPA_TSG_IMG_READ_SIZE + GEN5_MIN_BL_RESP_LENGTH) ||
+ error || resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EAGAIN;
+
+ /* Copy last 64 bytes in the row. */
+ memcpy(&fw_img_record->record_data[CYAPA_TSG_IMG_READ_SIZE],
+ &resp_data[8], CYAPA_TSG_IMG_READ_SIZE);
+
+skip_last_row:
+ fw_img_record->flash_array_id = 0;
+ put_unaligned_be16(row_num, &fw_img_record->row_number);
+ put_unaligned_be16(CYAPA_TSG_FW_ROW_SIZE, &fw_img_record->record_len);
+
+ return 0;
+}
+
+static int cyapa_gen5_read_fw(struct cyapa *cyapa)
+{
+ int fw_img_head_size;
+ int fw_img_record_size;
+ int fw_img_size;
+ int row_index;
+ int array_index;
+ u32 img_start;
+ u16 img_len;
+ u16 img_start_row;
+ u16 img_end_row;
+ struct cyapa_tsg_bin_image_data_record app_integrity;
+ u8 *record_data;
+ int error;
+
+ error = cyapa_gen5_bl_enter(cyapa);
+ if (error)
+ return error;
+
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ fw_img_head_size = sizeof(struct cyapa_tsg_bin_image_head);
+ fw_img_record_size = sizeof(struct cyapa_tsg_bin_image_data_record);
+
+ /* Read app integrity block data. */
+ row_index = CYAPA_TSG_IMG_APP_INTEGRITY_ROW_NUM;
+ error = cyapa_gen5_read_fw_bytes(cyapa,
+ row_index, (u8 *)&app_integrity);
+ if (error)
+ return error;
+ img_start = get_unaligned_le32(&app_integrity.record_data[16]);
+ img_len = get_unaligned_le16(&app_integrity.record_data[20]);
+ if ((img_start + img_len) % CYAPA_TSG_FW_ROW_SIZE)
+ return -EINVAL;
+ img_start_row = img_start / CYAPA_TSG_FW_ROW_SIZE;
+ img_end_row = (img_start + img_len) / CYAPA_TSG_FW_ROW_SIZE - 1;
+
+ /* Allocate memory for image. */
+ fw_img_size = fw_img_head_size +
+ (img_end_row - img_start_row + 2) * fw_img_record_size;
+ cyapa->fw_image = cyapa->fw_image ? cyapa->fw_image :
+ devm_kzalloc(&cyapa->client->dev, fw_img_size, GFP_KERNEL);
+ if (!cyapa->fw_image)
+ return -ENOMEM;
+
+ /* Set image head data. */
+ memcpy(cyapa->fw_image, &cyapa->fw_img_head, fw_img_head_size);
+
+ /* Read image blocks. */
+ for (row_index = img_start_row, array_index = 0;
+ row_index <= img_end_row;
+ row_index++, array_index++) {
+ record_data = &cyapa->fw_image[fw_img_head_size +
+ array_index * fw_img_record_size];
+ error = cyapa_gen5_read_fw_bytes(cyapa, row_index, record_data);
+ if (error)
+ return error;
+ }
+
+ /* Append last app integrity block data. */
+ record_data = &cyapa->fw_image[fw_img_head_size +
+ array_index * fw_img_record_size];
+ memcpy(record_data, &app_integrity, fw_img_record_size);
+
+ cyapa->fw_image_size = fw_img_size;
+ return 0;
+}
+
static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
const struct firmware *fw)
{
@@ -2288,6 +2436,11 @@ static int cyapa_gen5_get_query_data(struct cyapa *cyapa)
if (error || resp_len < sizeof(resp_data))
return error ? error : -EIO;
+ cyapa->fw_img_head.head_size =
+ sizeof(struct cyapa_tsg_bin_image_head) - 1;
+ memcpy(&cyapa->fw_img_head.ttda_driver_major_version,
+ &resp_data[5], cyapa->fw_img_head.head_size);
+
product_family = get_unaligned_le16(&resp_data[7]);
if ((product_family & GEN5_PRODUCT_FAMILY_MASK) !=
GEN5_PRODUCT_FAMILY_TRACKPAD)
@@ -2622,6 +2775,8 @@ const struct cyapa_dev_ops cyapa_gen5_ops = {
.show_baseline = cyapa_gen5_show_baseline,
.calibrate_store = cyapa_gen5_do_calibrate,
+ .read_fw = cyapa_gen5_read_fw,
+
.initialize = cyapa_gen5_initialize,
.state_parse = cyapa_gen5_state_parse,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 15/19] input: cyapa: add gen3 trackpad device read firmware image function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read firmware image function supported for gen3 trackpad device,
it can be used through debugfs read_fw interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen3.c | 67 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c
index 281209f..98513eb 100644
--- a/drivers/input/mouse/cyapa_gen3.c
+++ b/drivers/input/mouse/cyapa_gen3.c
@@ -708,6 +708,36 @@ static int cyapa_gen3_write_fw_block(struct cyapa *cyapa,
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)
+{
+ u8 cmd[] = { 0xff, 0x3c, 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, addr >> 8, addr };
+ int ret, error;
+
+ error = cyapa_gen3_write_buffer(cyapa, cmd, sizeof(cmd));
+ if (error)
+ return error;
+
+ /* 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_write_blocks(struct cyapa *cyapa,
size_t start_block, size_t block_count,
const u8 *image_data)
@@ -754,6 +784,41 @@ static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
return 0;
}
+/*
+ * Read the entire firmware image into ->fw_image.
+ * If the ->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, ->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 error;
+ int addr;
+
+ error = cyapa_gen3_bl_enter(cyapa);
+ if (error)
+ return error;
+
+ cyapa->fw_image = cyapa->fw_image ? cyapa->fw_image :
+ devm_kzalloc(&cyapa->client->dev, CYAPA_FW_SIZE, GFP_KERNEL);
+ if (!cyapa->fw_image)
+ return -ENOMEM;
+
+ for (addr = 0; addr < CYAPA_FW_SIZE; addr += CYAPA_FW_READ_SIZE) {
+ error = cyapa_gen3_read_fw_bytes(cyapa,
+ CYAPA_FW_HDR_START + addr, &cyapa->fw_image[addr]);
+ if (error)
+ return error;
+ }
+
+ cyapa->fw_image_size = CYAPA_FW_SIZE;
+ return 0;
+}
+
static ssize_t cyapa_gen3_do_calibrate(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -1195,6 +1260,8 @@ const struct cyapa_dev_ops cyapa_gen3_ops = {
.show_baseline = cyapa_gen3_show_baseline,
.calibrate_store = cyapa_gen3_do_calibrate,
+ .read_fw = cyapa_gen3_read_fw,
+
.state_parse = cyapa_gen3_state_parse,
.operational_check = cyapa_gen3_do_operational_check,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 14/19] input: cyapa: add read firmware image debugfs interface support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read firmware image from trackpad device interface supported in cyapa
driver through debugfs read_fw interface.
Through this interface user can read out, check and backup the firmware image
of the trackpad device before any firmware update, or can use the backed image
to do firmware image recovery.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa.c | 179 +++++++++++++++++++++++++++++++++++++++++++-
drivers/input/mouse/cyapa.h | 10 +++
2 files changed, 188 insertions(+), 1 deletion(-)
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 305dffe..2e394c0 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -14,6 +14,7 @@
* more details.
*/
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/input.h>
@@ -32,10 +33,14 @@
#define CYAPA_ADAPTER_FUNC_SMBUS 2
#define CYAPA_ADAPTER_FUNC_BOTH 3
+#define CYAPA_DEBUGFS_READ_FW "read_fw"
#define CYAPA_FW_NAME "cyapa.bin"
const char unique_str[] = "CYTRA";
+/* Global root node of the cyapa debugfs directory. */
+static struct dentry *cyapa_debugfs_root;
+
/* Returns 0 on success, else negative errno on failure. */
ssize_t cyapa_i2c_read(struct cyapa *cyapa, u8 reg, size_t len,
u8 *values)
@@ -497,6 +502,138 @@ static void cyapa_disable_irq_for_cmd(struct cyapa *cyapa)
}
/*
+ **************************************************************
+ * debugfs interface
+ **************************************************************
+*/
+static int cyapa_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = inode->i_private;
+ int error;
+
+ if (!cyapa)
+ return -ENODEV;
+
+ error = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (error)
+ return error;
+
+ if (!get_device(&cyapa->client->dev)) {
+ error = -ENODEV;
+ goto out;
+ }
+
+ file->private_data = cyapa;
+
+ if (cyapa->fw_image && cyapa->fw_image_size) {
+ error = 0;
+ goto out;
+ }
+
+ error = mutex_lock_interruptible(&cyapa->state_sync_lock);
+ if (error)
+ goto out;
+
+ /*
+ * 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->ops->read_fw) {
+ cyapa_enable_irq_for_cmd(cyapa);
+ error = cyapa->ops->read_fw(cyapa);
+ cyapa_disable_irq_for_cmd(cyapa);
+
+ /*
+ * Redetect trackpad device states because read_fw will
+ * reset trackpad device into bootloader mode.
+ */
+ cyapa_detect(cyapa);
+ } else {
+ error = -EPERM;
+ }
+
+ mutex_unlock(&cyapa->state_sync_lock);
+out:
+ mutex_unlock(&cyapa->debugfs_mutex);
+ return error;
+}
+
+static int cyapa_debugfs_release(struct inode *inode, struct file *file)
+{
+ struct cyapa *cyapa = file->private_data;
+ int error;
+
+ if (!cyapa)
+ return 0;
+
+ error = mutex_lock_interruptible(&cyapa->debugfs_mutex);
+ if (error)
+ return error;
+ file->private_data = NULL;
+ put_device(&cyapa->client->dev);
+ 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->fw_image)
+ return -EINVAL;
+
+ if (*ppos >= cyapa->fw_image_size)
+ return 0;
+
+ if (count + *ppos > cyapa->fw_image_size)
+ count = cyapa->fw_image_size - *ppos;
+
+ if (copy_to_user(buffer, &cyapa->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_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);
+
+ return 0;
+}
+
+static void cyapa_remove_debugfs(void *data)
+{
+ struct cyapa *cyapa = data;
+
+ debugfs_remove_recursive(cyapa->dentry_dev);
+ mutex_destroy(&cyapa->debugfs_mutex);
+}
+
+/*
* Sysfs Interface.
*/
@@ -1076,6 +1213,20 @@ static int cyapa_probe(struct i2c_client *client,
return error;
}
+ error = cyapa_debugfs_init(cyapa);
+ if (error) {
+ dev_err(dev, "failed to create debugfs entries: %d\n", error);
+ return error;
+ }
+
+ error = devm_add_action(dev, cyapa_remove_debugfs, cyapa);
+ if (error) {
+ cyapa_remove_debugfs(cyapa);
+ dev_err(dev, "failed to add debugfs cleanup action :%d\n",
+ error);
+ return error;
+ }
+
#ifdef CONFIG_PM_SLEEP
if (device_can_wakeup(dev)) {
error = sysfs_merge_group(&client->dev.kobj,
@@ -1256,7 +1407,33 @@ static struct i2c_driver cyapa_driver = {
.id_table = cyapa_id_table,
};
-module_i2c_driver(cyapa_driver);
+static int __init cyapa_init(void)
+{
+ int error;
+
+ /* Create a global debugfs root for all cyapa devices */
+ cyapa_debugfs_root = debugfs_create_dir("cyapa", NULL);
+ if (cyapa_debugfs_root == ERR_PTR(-ENODEV))
+ cyapa_debugfs_root = NULL;
+
+ error = i2c_add_driver(&cyapa_driver);
+ if (error) {
+ pr_err("cyapa driver register FAILED.\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static void __exit cyapa_exit(void)
+{
+ debugfs_remove_recursive(cyapa_debugfs_root);
+
+ i2c_del_driver(&cyapa_driver);
+}
+
+module_init(cyapa_init);
+module_exit(cyapa_exit);
MODULE_DESCRIPTION("Cypress APA I2C Trackpad Driver");
MODULE_AUTHOR("Dudley Du <dudl@cypress.com>");
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index 0dc76d8..ceed843 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -186,6 +186,8 @@ struct cyapa_dev_ops {
ssize_t (*calibrate_store)(struct device *,
struct device_attribute *, const char *, size_t);
+ int (*read_fw)(struct cyapa *);
+
int (*initialize)(struct cyapa *cyapa);
int (*state_parse)(struct cyapa *cyapa, u8 *reg_status, int len);
@@ -298,6 +300,14 @@ struct cyapa {
*/
struct mutex state_sync_lock;
+ /* Per-instance debugfs root */
+ struct dentry *dentry_dev;
+
+ /* Buffer to store firmware read using debugfs */
+ struct mutex debugfs_mutex;
+ u8 *fw_image;
+ size_t fw_image_size;
+
const struct cyapa_dev_ops *ops;
union cyapa_cmd_states cmd_states;
--
1.9.1
^ permalink raw reply related
* [PATCH v11 13/19] input: cyapa: add gen5 trackpad device force re-calibrate function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add force re-calibrate function supported for gen5 trackpad device,
it can be used through sysfs calibrate interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen5.c | 65 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index b401b2a..51bdf06 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -1580,6 +1580,70 @@ static int cyapa_gen5_suspend_scanning(struct cyapa *cyapa)
return 0;
}
+static int cyapa_gen5_calibrate_pwcs(struct cyapa *cyapa,
+ u8 calibrate_sensing_mode_type)
+{
+ u8 cmd[8];
+ u8 resp_data[6];
+ int resp_len;
+ int error;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x06;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_CMD_CALIBRATE;
+ cmd[7] = calibrate_sensing_mode_type;
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, GEN5_CMD_CALIBRATE) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EAGAIN;
+
+ return 0;
+}
+
+static ssize_t cyapa_gen5_do_calibrate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int error, calibrate_error;
+
+ /* 1. Suspend Scanning*/
+ error = cyapa_gen5_suspend_scanning(cyapa);
+ if (error)
+ return error;
+
+ /* 2. Do mutual capacitance fine calibrate. */
+ calibrate_error = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_MUTUAL_CAP_FINE);
+ if (calibrate_error)
+ goto resume_scanning;
+
+ /* 3. Do self capacitance calibrate. */
+ calibrate_error = cyapa_gen5_calibrate_pwcs(cyapa,
+ CYAPA_SENSING_MODE_SELF_CAP);
+ if (calibrate_error)
+ goto resume_scanning;
+
+resume_scanning:
+ /* 4. Resume Scanning*/
+ error = cyapa_gen5_resume_scanning(cyapa);
+ if (error || calibrate_error)
+ return error ? error : calibrate_error;
+
+ return count;
+}
+
static s32 two_complement_to_s32(s32 value, int num_bits)
{
if (value >> (num_bits - 1))
@@ -2556,6 +2620,7 @@ const struct cyapa_dev_ops cyapa_gen5_ops = {
.update_fw = cyapa_gen5_do_fw_update,
.show_baseline = cyapa_gen5_show_baseline,
+ .calibrate_store = cyapa_gen5_do_calibrate,
.initialize = cyapa_gen5_initialize,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 12/19] input: cyapa: add gen5 trackpad device read baseline function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add read baseline function supported for gen5 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.h | 2 +
drivers/input/mouse/cyapa_gen5.c | 621 +++++++++++++++++++++++++++++++++++++++
2 files changed, 623 insertions(+)
diff --git a/drivers/input/mouse/cyapa.h b/drivers/input/mouse/cyapa.h
index a1a42f2..0dc76d8 100644
--- a/drivers/input/mouse/cyapa.h
+++ b/drivers/input/mouse/cyapa.h
@@ -286,6 +286,8 @@ struct cyapa {
u8 y_origin; /* Y Axis Origin: 0 = top; 1 = bottom. */
int electrodes_x; /* Number of electrodes on the X Axis*/
int electrodes_y; /* Number of electrodes on the Y Axis*/
+ int electrodes_rx; /* Number of Rx electrodes */
+ int algined_electrodes_rx; /* 4 aligned */
int max_z;
/*
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index f8f5b15..b401b2a 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -1532,6 +1532,625 @@ static int cyapa_gen5_set_power_mode(struct cyapa *cyapa,
return 0;
}
+static int cyapa_gen5_resume_scanning(struct cyapa *cyapa)
+{
+ u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x04 };
+ u8 resp_data[6];
+ int resp_len;
+ int error;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ resp_len = 6;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x04))
+ return -EINVAL;
+
+ /* Try to dump all bufferred data when resuming scanning. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ return 0;
+}
+
+static int cyapa_gen5_suspend_scanning(struct cyapa *cyapa)
+{
+ u8 cmd[7] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x03 };
+ u8 resp_data[6];
+ int resp_len;
+ int error;
+
+ /* Try to dump all bufferred data before doing command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ resp_len = 6;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x03))
+ return -EINVAL;
+
+ /* Try to dump all bufferred data when suspending scanning. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ return 0;
+}
+
+static s32 two_complement_to_s32(s32 value, int num_bits)
+{
+ if (value >> (num_bits - 1))
+ value |= -1 << num_bits;
+ return value;
+}
+
+static s32 cyapa_parse_structure_data(u8 data_format, u8 *buf, int buf_len)
+{
+ int data_size;
+ bool big_endian;
+ bool unsigned_type;
+ s32 value;
+
+ data_size = (data_format & 0x07);
+ big_endian = ((data_format & 0x10) == 0x00);
+ unsigned_type = ((data_format & 0x20) == 0x00);
+
+ if (buf_len < data_size)
+ return 0;
+
+ switch (data_size) {
+ case 1:
+ value = buf[0];
+ break;
+ case 2:
+ if (big_endian)
+ value = get_unaligned_be16(buf);
+ else
+ value = get_unaligned_le16(buf);
+ break;
+ case 4:
+ if (big_endian)
+ value = get_unaligned_be32(buf);
+ else
+ value = get_unaligned_le32(buf);
+ break;
+ default:
+ /* Should not happen, just as default case here. */
+ value = 0;
+ break;
+ }
+
+ if (!unsigned_type)
+ value = two_complement_to_s32(value, data_size * 8);
+
+ return value;
+}
+
+
+/*
+ * Read all the global mutual or self idac data or mutual or self local PWC
+ * data based on the @idac_data_type.
+ * If the input value of @data_size is 0, then means read global mutual or
+ * self idac data. For read global mutual idac data, @idac_max, @idac_min and
+ * @idac_ave are in order used to return the max value of global mutual idac
+ * data, the min value of global mutual idac and the average value of the
+ * global mutual idac data. For read global self idac data, @idac_max is used
+ * to return the global self cap idac data in Rx direction, @idac_min is used
+ * to return the global self cap idac data in Tx direction. @idac_ave is not
+ * used.
+ * If the input value of @data_size is not 0, than means read the mutual or
+ * self local PWC data. The @idac_max, @idac_min and @idac_ave are used to
+ * return the max, min and average value of the mutual or self local PWC data.
+ * Note, in order to raed mutual local PWC data, must read invoke this function
+ * to read the mutual global idac data firstly to set the correct Rx number
+ * value, otherwise, the read mutual idac and PWC data may not correct.
+ */
+static int cyapa_gen5_read_idac_data(struct cyapa *cyapa,
+ u8 cmd_code, u8 idac_data_type, int *data_size,
+ int *idac_max, int *idac_min, int *idac_ave)
+{
+ int i;
+ u8 cmd[12];
+ u8 resp_data[256];
+ int resp_len;
+ int read_len;
+ int value;
+ u16 offset;
+ int read_elements;
+ bool read_global_idac;
+ int sum, count, max_element_cnt;
+ int tmp_max, tmp_min, tmp_ave, tmp_sum, tmp_count;
+ int electrodes_rx, electrodes_tx;
+ int error;
+
+ if (cmd_code != GEN5_CMD_RETRIEVE_DATA_STRUCTURE ||
+ (idac_data_type != GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+ idac_data_type != GEN5_RETRIEVE_SELF_CAP_PWC_DATA) ||
+ !data_size || !idac_max || !idac_min || !idac_ave)
+ return -EINVAL;
+
+ *idac_max = INT_MIN;
+ *idac_min = INT_MAX;
+ sum = count = tmp_count = 0;
+ electrodes_rx = electrodes_tx = 0;
+ if (*data_size == 0) {
+ /*
+ * Read global idac values firstly.
+ * Currently, no idac data exceed 4 bytes.
+ */
+ read_global_idac = true;
+ offset = 0;
+ *data_size = 4;
+ tmp_max = INT_MIN;
+ tmp_min = INT_MAX;
+ tmp_ave = tmp_sum = tmp_count = 0;
+
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ if (cyapa->algined_electrodes_rx == 0) {
+ if (cyapa->electrodes_rx != 0) {
+ electrodes_rx = cyapa->electrodes_rx;
+ electrodes_tx = (cyapa->electrodes_x ==
+ electrodes_rx) ?
+ cyapa->electrodes_y :
+ cyapa->electrodes_x;
+ } else {
+ electrodes_tx = min(cyapa->electrodes_x,
+ cyapa->electrodes_y);
+ electrodes_rx = max(cyapa->electrodes_x,
+ cyapa->electrodes_y);
+ }
+ cyapa->algined_electrodes_rx =
+ (electrodes_rx + 3) & ~3u;
+ }
+ max_element_cnt =
+ (cyapa->algined_electrodes_rx + 7) & ~7u;
+ } else {
+ max_element_cnt = 2;
+ }
+ } else {
+ read_global_idac = false;
+ if (*data_size > 4)
+ *data_size = 4;
+ /* Calculate the start offset in bytes of local PWC data. */
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ offset = cyapa->algined_electrodes_rx * (*data_size);
+ electrodes_tx =
+ (cyapa->electrodes_rx == cyapa->electrodes_x) ?
+ cyapa->electrodes_y : cyapa->electrodes_x;
+ max_element_cnt = ((cyapa->algined_electrodes_rx + 7) &
+ ~7u) * electrodes_tx;
+ } else if (idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+ offset = 2;
+ max_element_cnt = cyapa->electrodes_x +
+ cyapa->electrodes_y;
+ max_element_cnt = (max_element_cnt + 3) & ~3u;
+ }
+ }
+
+ do {
+ read_elements = (256 - 10) / (*data_size);
+ read_elements = min(read_elements, max_element_cnt - count);
+ read_len = read_elements * (*data_size);
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0a;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = cmd_code;
+ put_unaligned_le16(offset, &cmd[7]); /* Read Offset[15:0] */
+ put_unaligned_le16(read_len, &cmd[9]); /* Read Length[15:0] */
+ cmd[11] = idac_data_type;
+ resp_len = 10 + read_len;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 12,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data,
+ true);
+ if (error || resp_len < 10 ||
+ !VALID_CMD_RESP_HEADER(resp_data, cmd_code) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ resp_data[6] != idac_data_type)
+ return (error < 0) ? error : -EAGAIN;
+ read_len = get_unaligned_le16(&resp_data[7]);
+ if (read_len == 0)
+ break;
+
+ *data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+ if (read_len < *data_size)
+ return -EINVAL;
+
+ if (read_global_idac &&
+ idac_data_type == GEN5_RETRIEVE_SELF_CAP_PWC_DATA) {
+ /* Rx's self global idac data. */
+ *idac_max = cyapa_parse_structure_data(
+ resp_data[9], &resp_data[10],
+ *data_size);
+ /* Tx's self global idac data. */
+ *idac_min = cyapa_parse_structure_data(
+ resp_data[9],
+ &resp_data[10 + *data_size],
+ *data_size);
+ break;
+ }
+
+ /* Read mutual global idac or local mutual/self PWC data. */
+ offset += read_len;
+ for (i = 10; i < (read_len + 10); i += *data_size) {
+ value = cyapa_parse_structure_data(resp_data[9],
+ &resp_data[i], *data_size);
+ *idac_min = min(value, *idac_min);
+ *idac_max = max(value, *idac_max);
+
+ if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
+ tmp_count < cyapa->algined_electrodes_rx &&
+ read_global_idac) {
+ /*
+ * The value gap betwen global and local mutual
+ * idac data must bigger than 50%.
+ * Normally, global value bigger than 50,
+ * local values less than 10.
+ */
+ if (!tmp_ave || value > tmp_ave / 2) {
+ tmp_min = min(value, tmp_min);
+ tmp_max = max(value, tmp_max);
+ tmp_sum += value;
+ tmp_count++;
+
+ tmp_ave = tmp_sum / tmp_count;
+ }
+ }
+
+ sum += value;
+ count++;
+
+ if (count >= max_element_cnt)
+ goto out;
+ }
+ } while (true);
+
+out:
+ *idac_ave = count ? (sum / count) : 0;
+
+ if (read_global_idac &&
+ idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) {
+ if (tmp_count == 0)
+ return 0;
+
+ if (tmp_count == cyapa->algined_electrodes_rx) {
+ cyapa->electrodes_rx = cyapa->electrodes_rx ?
+ cyapa->electrodes_rx : electrodes_rx;
+ } else if (tmp_count == electrodes_rx) {
+ cyapa->electrodes_rx = cyapa->electrodes_rx ?
+ cyapa->electrodes_rx : electrodes_rx;
+ cyapa->algined_electrodes_rx = electrodes_rx;
+ } else {
+ cyapa->electrodes_rx = cyapa->electrodes_rx ?
+ cyapa->electrodes_rx : electrodes_tx;
+ cyapa->algined_electrodes_rx = tmp_count;
+ }
+
+ *idac_min = tmp_min;
+ *idac_max = tmp_max;
+ *idac_ave = tmp_ave;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_read_mutual_idac_data(struct cyapa *cyapa,
+ int *gidac_mutual_max, int *gidac_mutual_min, int *gidac_mutual_ave,
+ int *lidac_mutual_max, int *lidac_mutual_min, int *lidac_mutual_ave)
+{
+ int error;
+ int data_size;
+
+ *gidac_mutual_max = *gidac_mutual_min = *gidac_mutual_ave = 0;
+ *lidac_mutual_max = *lidac_mutual_min = *lidac_mutual_ave = 0;
+
+ data_size = 0;
+ error = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size,
+ gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave);
+ if (error)
+ return error;
+
+ error = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_MUTUAL_PWC_DATA,
+ &data_size,
+ lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave);
+ return error;
+}
+
+static int cyapa_gen5_read_self_idac_data(struct cyapa *cyapa,
+ int *gidac_self_rx, int *gidac_self_tx,
+ int *lidac_self_max, int *lidac_self_min, int *lidac_self_ave)
+{
+ int error;
+ int data_size;
+
+ *gidac_self_rx = *gidac_self_tx = 0;
+ *lidac_self_max = *lidac_self_min = *lidac_self_ave = 0;
+
+ data_size = 0;
+ error = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+ &data_size,
+ lidac_self_max, lidac_self_min, lidac_self_ave);
+ if (error)
+ return error;
+ *gidac_self_rx = *lidac_self_max;
+ *gidac_self_tx = *lidac_self_min;
+
+ error = cyapa_gen5_read_idac_data(cyapa,
+ GEN5_CMD_RETRIEVE_DATA_STRUCTURE,
+ GEN5_RETRIEVE_SELF_CAP_PWC_DATA,
+ &data_size,
+ lidac_self_max, lidac_self_min, lidac_self_ave);
+ return error;
+}
+
+static ssize_t cyapa_gen5_execute_panel_scan(struct cyapa *cyapa)
+{
+ int error;
+ u8 cmd[7];
+ u8 resp_data[6];
+ int resp_len;
+
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x05;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = GEN5_CMD_EXECUTE_PANEL_SCAN; /* Command code */
+ resp_len = 6;
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 7,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || resp_len != 6 ||
+ !VALID_CMD_RESP_HEADER(resp_data,
+ GEN5_CMD_EXECUTE_PANEL_SCAN) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen5_read_panel_scan_raw_data(struct cyapa *cyapa,
+ u8 cmd_code, u8 raw_data_type, int raw_data_max_num,
+ int *raw_data_max, int *raw_data_min, int *raw_data_ave,
+ u8 *buffer)
+{
+ int i;
+ u8 cmd[12];
+ u8 resp_data[256]; /* Max bytes can transfer one time. */
+ int resp_len;
+ int read_elements;
+ int read_len;
+ u16 offset;
+ s32 value;
+ int sum, count;
+ int data_size;
+ s32 *intp;
+ int error;
+
+ if (cmd_code != GEN5_CMD_RETRIEVE_PANEL_SCAN ||
+ (raw_data_type > GEN5_PANEL_SCAN_SELF_DIFFCOUNT) ||
+ !raw_data_max || !raw_data_min || !raw_data_ave)
+ return -EINVAL;
+
+ intp = (s32 *)buffer;
+ *raw_data_max = INT_MIN;
+ *raw_data_min = INT_MAX;
+ sum = count = 0;
+ offset = 0;
+ read_elements = (256 - 10) / 4; /* Currently, max element size is 4. */
+ read_len = read_elements * 4;
+ do {
+ cmd[0] = 0x04;
+ cmd[1] = 0x00;
+ cmd[2] = 0x0a;
+ cmd[3] = 0x00;
+ cmd[4] = GEN5_APP_CMD_REPORT_ID;
+ cmd[5] = 0x00;
+ cmd[6] = cmd_code; /* Command code */
+ put_unaligned_le16(offset, &cmd[7]);
+ put_unaligned_le16(read_elements, &cmd[9]);
+ cmd[11] = raw_data_type;
+ resp_len = 10 + read_len;
+
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, 12,
+ resp_data, &resp_len,
+ 500, cyapa_gen5_sort_tsg_pip_app_resp_data, true);
+ if (error || resp_len < 10 ||
+ !VALID_CMD_RESP_HEADER(resp_data, cmd_code) ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]) ||
+ resp_data[6] != raw_data_type)
+ return error ? error : -EAGAIN;
+
+ read_elements = get_unaligned_le16(&resp_data[7]);
+ if (read_elements == 0)
+ break;
+
+ data_size = (resp_data[9] & GEN5_PWC_DATA_ELEMENT_SIZE_MASK);
+ offset += read_elements;
+ if (read_elements) {
+ for (i = 10;
+ i < (read_elements * data_size + 10);
+ i += data_size) {
+ value = cyapa_parse_structure_data(resp_data[9],
+ &resp_data[i], data_size);
+ *raw_data_min = min(value, *raw_data_min);
+ *raw_data_max = max(value, *raw_data_max);
+
+ if (intp)
+ put_unaligned_le32(value, &intp[count]);
+
+ sum += value;
+ count++;
+
+ }
+ }
+
+ if (count >= raw_data_max_num)
+ break;
+
+ read_elements = (sizeof(resp_data) - 10) / data_size;
+ read_len = read_elements * data_size;
+ } while (true);
+
+ *raw_data_ave = count ? (sum / count) : 0;
+
+ return 0;
+}
+
+static ssize_t cyapa_gen5_show_baseline(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cyapa *cyapa = dev_get_drvdata(dev);
+ int gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave;
+ int lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave;
+ int gidac_self_rx, gidac_self_tx;
+ int lidac_self_max, lidac_self_min, lidac_self_ave;
+ int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave;
+ int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave;
+ int mutual_diffdata_max, mutual_diffdata_min, mutual_diffdata_ave;
+ int self_diffdata_max, self_diffdata_min, self_diffdata_ave;
+ int mutual_baseline_max, mutual_baseline_min, mutual_baseline_ave;
+ int self_baseline_max, self_baseline_min, self_baseline_ave;
+ int error, resume_error;
+ int size;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return -EBUSY;
+
+ /* 1. Suspend Scanning*/
+ error = cyapa_gen5_suspend_scanning(cyapa);
+ if (error)
+ return error;
+
+ /* 2. Read global and local mutual IDAC data. */
+ gidac_self_rx = gidac_self_tx = 0;
+ error = cyapa_gen5_read_mutual_idac_data(cyapa,
+ &gidac_mutual_max, &gidac_mutual_min,
+ &gidac_mutual_ave, &lidac_mutual_max,
+ &lidac_mutual_min, &lidac_mutual_ave);
+ if (error)
+ goto resume_scanning;
+
+ /* 3. Read global and local self IDAC data. */
+ error = cyapa_gen5_read_self_idac_data(cyapa,
+ &gidac_self_rx, &gidac_self_tx,
+ &lidac_self_max, &lidac_self_min,
+ &lidac_self_ave);
+ if (error)
+ goto resume_scanning;
+
+ /* 4. Execuate panel scan. It must be executed before read data. */
+ error = cyapa_gen5_execute_panel_scan(cyapa);
+ if (error)
+ goto resume_scanning;
+
+ /* 5. Retrieve panel scan, mutual cap raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_RAW_DATA,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &raw_cap_mutual_max, &raw_cap_mutual_min,
+ &raw_cap_mutual_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+ /* 6. Retrieve panel scan, self cap raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_RAW_DATA,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &raw_cap_self_max, &raw_cap_self_min,
+ &raw_cap_self_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+ /* 7. Retrieve panel scan, mutual cap diffcount raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_DIFFCOUNT,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &mutual_diffdata_max, &mutual_diffdata_min,
+ &mutual_diffdata_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+ /* 8. Retrieve panel scan, self cap diffcount raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_DIFFCOUNT,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &self_diffdata_max, &self_diffdata_min,
+ &self_diffdata_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+ /* 9. Retrieve panel scan, mutual cap baseline raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_MUTUAL_BASELINE,
+ cyapa->electrodes_x * cyapa->electrodes_y,
+ &mutual_baseline_max, &mutual_baseline_min,
+ &mutual_baseline_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+ /* 10. Retrieve panel scan, self cap baseline raw data. */
+ error = cyapa_gen5_read_panel_scan_raw_data(cyapa,
+ GEN5_CMD_RETRIEVE_PANEL_SCAN,
+ GEN5_PANEL_SCAN_SELF_BASELINE,
+ cyapa->electrodes_x + cyapa->electrodes_y,
+ &self_baseline_max, &self_baseline_min,
+ &self_baseline_ave,
+ NULL);
+ if (error)
+ goto resume_scanning;
+
+resume_scanning:
+ /* 11. Resume Scanning*/
+ resume_error = cyapa_gen5_resume_scanning(cyapa);
+ if (resume_error || error)
+ return resume_error ? resume_error : error;
+
+ /* 12. Output data strings */
+ size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d %d %d %d %d %d ",
+ gidac_mutual_min, gidac_mutual_max, gidac_mutual_ave,
+ lidac_mutual_min, lidac_mutual_max, lidac_mutual_ave,
+ gidac_self_rx, gidac_self_tx,
+ lidac_self_min, lidac_self_max, lidac_self_ave);
+ size += scnprintf(buf + size, PAGE_SIZE - size,
+ "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ raw_cap_mutual_min, raw_cap_mutual_max, raw_cap_mutual_ave,
+ raw_cap_self_min, raw_cap_self_max, raw_cap_self_ave,
+ mutual_diffdata_min, mutual_diffdata_max, mutual_diffdata_ave,
+ self_diffdata_min, self_diffdata_max, self_diffdata_ave,
+ mutual_baseline_min, mutual_baseline_max, mutual_baseline_ave,
+ self_baseline_min, self_baseline_max, self_baseline_ave);
+ return size;
+}
+
static bool cyapa_gen5_sort_system_info_data(struct cyapa *cyapa,
u8 *buf, int len)
{
@@ -1936,6 +2555,8 @@ const struct cyapa_dev_ops cyapa_gen5_ops = {
.bl_initiate = cyapa_gen5_bl_initiate,
.update_fw = cyapa_gen5_do_fw_update,
+ .show_baseline = cyapa_gen5_show_baseline,
+
.initialize = cyapa_gen5_initialize,
.state_parse = cyapa_gen5_state_parse,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 11/19] input: cyapa: add gen5 trackpad device firmware update function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add firmware image update function supported for gen5 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/Kconfig | 2 +-
drivers/input/mouse/cyapa_gen5.c | 290 +++++++++++++++++++++++++++++++++++++++
2 files changed, 291 insertions(+), 1 deletion(-)
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 366fc7a..005d69b 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -205,7 +205,7 @@ config MOUSE_BCM5974
config MOUSE_CYAPA
tristate "Cypress APA I2C Trackpad support"
- depends on I2C
+ depends on I2C && CRC_ITU_T
help
This driver adds support for Cypress All Points Addressable (APA)
I2C Trackpads, including the ones used in 2012 Samsung Chromebooks.
diff --git a/drivers/input/mouse/cyapa_gen5.c b/drivers/input/mouse/cyapa_gen5.c
index ee93e63..f8f5b15 100644
--- a/drivers/input/mouse/cyapa_gen5.c
+++ b/drivers/input/mouse/cyapa_gen5.c
@@ -18,6 +18,7 @@
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/unaligned/access_ok.h>
+#include <linux/crc-itu-t.h>
#include "cyapa.h"
@@ -911,6 +912,86 @@ static int cyapa_gen5_state_parse(struct cyapa *cyapa, u8 *reg_data, int len)
return -EAGAIN;
}
+static int cyapa_gen5_bl_initiate(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ u16 length = 0;
+ u16 data_len = 0;
+ u16 meta_data_crc = 0;
+ u16 cmd_crc = 0;
+ u8 bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE + 3];
+ int bl_gen5_activate_size = 0;
+ u8 resp_data[11];
+ int resp_len;
+ struct cyapa_tsg_bin_image *image;
+ int records_num;
+ u8 *data;
+ int error;
+
+ /* Try to dump all bufferred report data before send any command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ bl_gen5_activate_size = sizeof(bl_gen5_activate);
+ memset(bl_gen5_activate, 0, bl_gen5_activate_size);
+
+ /* Output Report Register Address[15:0] = 0004h */
+ bl_gen5_activate[0] = 0x04;
+ bl_gen5_activate[1] = 0x00;
+
+ /* Total command length[15:0] */
+ length = bl_gen5_activate_size - 2;
+ put_unaligned_le16(length, &bl_gen5_activate[2]);
+ bl_gen5_activate[4] = 0x40; /* Report ID = 40h */
+ bl_gen5_activate[5] = 0x00; /* RSVD = 00h */
+
+ bl_gen5_activate[6] = GEN5_SOP_KEY; /* SOP = 01h */
+ bl_gen5_activate[7] = 0x48; /* Command Code = 48h */
+
+ /* 8 Key bytes and block size */
+ data_len = CYAPA_TSG_BL_KEY_SIZE + CYAPA_TSG_FLASH_MAP_BLOCK_SIZE;
+ /* Data Length[15:0] */
+ put_unaligned_le16(data_len, &bl_gen5_activate[8]);
+ bl_gen5_activate[10] = 0xa5; /* Key Byte 0 */
+ bl_gen5_activate[11] = 0x01;
+ bl_gen5_activate[12] = 0x02; /* . */
+ bl_gen5_activate[13] = 0x03; /* . */
+ bl_gen5_activate[14] = 0xff; /* . */
+ bl_gen5_activate[15] = 0xfe;
+ bl_gen5_activate[16] = 0xfd;
+ bl_gen5_activate[17] = 0x5a; /* Key Byte 7 */
+
+ /* Copy 60 bytes Meta Data Row Parameters */
+ image = (struct cyapa_tsg_bin_image *)fw->data;
+ records_num = (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /* APP_INTEGRITY row is always the last row block */
+ data = image->records[records_num - 1].record_data;
+ memcpy(&bl_gen5_activate[18], data, CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+
+ meta_data_crc = crc_itu_t(0xffff, &bl_gen5_activate[18],
+ CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
+ /* Meta Data CRC[15:0] */
+ put_unaligned_le16(meta_data_crc,
+ &bl_gen5_activate[18 + CYAPA_TSG_FLASH_MAP_METADATA_SIZE]);
+
+ cmd_crc = crc_itu_t(0xffff, &bl_gen5_activate[6], 4 + data_len);
+ put_unaligned_le16(cmd_crc,
+ &bl_gen5_activate[bl_gen5_activate_size - 3]); /* CRC[15:0] */
+ bl_gen5_activate[bl_gen5_activate_size - 1] = GEN5_EOP_KEY;
+
+ resp_len = sizeof(resp_data);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ bl_gen5_activate, sizeof(bl_gen5_activate),
+ resp_data, &resp_len, 12000,
+ cyapa_gen5_sort_tsg_pip_bl_resp_data, true);
+ if (error || resp_len != GEN5_BL_INITIATE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error ? error : -EAGAIN;
+
+ return 0;
+}
+
bool cyapa_gen5_sort_bl_exit_data(struct cyapa *cyapa, u8 *buf, int len)
{
if (buf == NULL || len < GEN5_RESP_LENGTH_SIZE)
@@ -959,6 +1040,210 @@ static int cyapa_gen5_bl_exit(struct cyapa *cyapa)
return -ENODEV;
}
+static int cyapa_gen5_bl_enter(struct cyapa *cyapa)
+{
+ int error;
+ u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x01 };
+ u8 resp_data[2];
+ int resp_len;
+
+ error = cyapa_poll_state(cyapa, 500);
+ if (error < 0)
+ return error;
+ if (cyapa->gen != CYAPA_GEN5)
+ return -EINVAL;
+
+ /* Already in Gen5 BL. Skipping exit. */
+ if (cyapa->state == CYAPA_STATE_GEN5_BL)
+ return 0;
+
+ if (cyapa->state != CYAPA_STATE_GEN5_APP)
+ return -EAGAIN;
+
+ /* Try to dump all bufferred report data before send any command. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ /*
+ * Send bootloader enter command to trackpad device,
+ * after enter bootloader, the response data is two bytes of 0x00 0x00.
+ */
+ resp_len = sizeof(resp_data);
+ memset(resp_data, 0, resp_len);
+ error = cyapa_i2c_pip_cmd_irq_sync(cyapa,
+ cmd, sizeof(cmd),
+ resp_data, &resp_len,
+ 5000, cyapa_gen5_sort_application_launch_data,
+ true);
+ if (error || resp_data[0] != 0x00 || resp_data[1] != 0x00)
+ return error < 0 ? error : -EAGAIN;
+
+ cyapa->state = CYAPA_STATE_GEN5_BL;
+ return 0;
+}
+
+static int cyapa_gen5_check_fw(struct cyapa *cyapa, const struct firmware *fw)
+{
+ int i;
+ struct cyapa_tsg_bin_image *image;
+ int flash_records_count;
+ u16 expected_app_crc;
+ u16 expected_app_integrity_crc;
+ u16 app_crc = 0;
+ u16 app_integrity_crc = 0;
+ u16 row_num;
+ u8 *data;
+ u32 app_start;
+ u16 app_len;
+ u32 img_start;
+ u16 img_len;
+ int record_index;
+ struct device *dev = &cyapa->client->dev;
+
+ image = (struct cyapa_tsg_bin_image *)fw->data;
+ flash_records_count = (fw->size -
+ sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+
+ /* APP_INTEGRITY row is always the last row block,
+ * and the row id must be 0x01ff */
+ row_num = get_unaligned_be16(
+ &image->records[flash_records_count - 1].row_number);
+ if (&image->records[flash_records_count - 1].flash_array_id != 0x00 &&
+ row_num != 0x01ff) {
+ dev_err(dev, "%s: invalid app_integrity data.\n", __func__);
+ return -EINVAL;
+ }
+ data = image->records[flash_records_count - 1].record_data;
+ app_start = get_unaligned_le32(&data[4]);
+ app_len = get_unaligned_le16(&data[8]);
+ expected_app_crc = get_unaligned_le16(&data[10]);
+ img_start = get_unaligned_le32(&data[16]);
+ img_len = get_unaligned_le16(&data[20]);
+ expected_app_integrity_crc = get_unaligned_le16(&data[60]);
+
+ if ((app_start + app_len + img_start + img_len) %
+ CYAPA_TSG_FW_ROW_SIZE) {
+ dev_err(dev, "%s: invalid image alignment.\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Verify app_integrity crc */
+ app_integrity_crc = crc_itu_t(0xffff, data,
+ CYAPA_TSG_APP_INTEGRITY_SIZE);
+ if (app_integrity_crc != expected_app_integrity_crc) {
+ dev_err(dev, "%s: invalid app_integrity crc.\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * Verify application image CRC
+ */
+ record_index = app_start / CYAPA_TSG_FW_ROW_SIZE -
+ CYAPA_TSG_IMG_START_ROW_NUM;
+ data = (u8 *)&image->records[record_index].record_data;
+ app_crc = crc_itu_t(0xffff, data, CYAPA_TSG_FW_ROW_SIZE);
+ for (i = 1; i < (app_len / CYAPA_TSG_FW_ROW_SIZE); i++) {
+ data = (u8 *)&image->records[++record_index].record_data;
+ app_crc = crc_itu_t(app_crc, data, CYAPA_TSG_FW_ROW_SIZE);
+ }
+
+ if (app_crc != expected_app_crc) {
+ dev_err(dev, "%s: invalid firmware app crc check.\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cyapa_gen5_write_fw_block(struct cyapa *cyapa,
+ struct cyapa_tsg_bin_image_data_record *flash_record)
+{
+ u8 flash_array_id;
+ u16 flash_row_id;
+ u16 record_len;
+ u8 *record_data;
+ u8 cmd[144]; /* 13 + 128+ 3 */
+ u16 cmd_len;
+ u16 data_len;
+ u16 crc;
+ u8 resp_data[11];
+ int resp_len;
+ int error;
+
+ flash_array_id = flash_record->flash_array_id;
+ flash_row_id = get_unaligned_be16(&flash_record->row_number);
+ record_len = get_unaligned_be16(&flash_record->record_len);
+ record_data = flash_record->record_data;
+
+ cmd_len = sizeof(cmd) - 2; /* Not include 2 bytes regisetr address. */
+ memset(cmd, 0, cmd_len + 2);
+ cmd[0] = 0x04; /* Register address */
+ cmd[1] = 0x00;
+
+ put_unaligned_le16(cmd_len, &cmd[2]);
+ cmd[4] = 0x40; /* Report id 40h */
+ cmd[5] = 0x00;
+
+ cmd[6] = GEN5_SOP_KEY; /* SOP = 01h */
+ cmd[7] = 0x39; /* Command code = 39h */
+ /* 1 (Flash Array ID) + 2 (Flash Row ID) + 128 (flash data) */
+ data_len = 3 + record_len;
+ put_unaligned_le16(data_len, &cmd[8]);
+ cmd[10] = flash_array_id; /* Flash Array ID = 00h */
+ put_unaligned_le16(flash_row_id, &cmd[11]);
+
+ memcpy(&cmd[13], record_data, record_len);
+ crc = crc_itu_t(0xffff, &cmd[6], 4 + data_len);
+ put_unaligned_le16(crc, &cmd[2 + cmd_len - 3]);
+ cmd[2 + cmd_len - 1] = GEN5_EOP_KEY;
+
+ 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_bl_resp_data, true);
+ if (error || resp_len != GEN5_BL_BLOCK_WRITE_RESP_LEN ||
+ resp_data[2] != GEN5_BL_RESP_REPORT_ID ||
+ !GEN5_CMD_COMPLETE_SUCCESS(resp_data[5]))
+ return error < 0 ? error : -EAGAIN;
+
+ return 0;
+}
+
+static int cyapa_gen5_do_fw_update(struct cyapa *cyapa,
+ const struct firmware *fw)
+{
+ struct device *dev = &cyapa->client->dev;
+ struct cyapa_tsg_bin_image *image =
+ (struct cyapa_tsg_bin_image *)fw->data;
+ struct cyapa_tsg_bin_image_data_record *flash_record;
+ int flash_records_count;
+ int i;
+ int error;
+
+ /* Try to dump all bufferred data if exists before send commands. */
+ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
+
+ flash_records_count =
+ (fw->size - sizeof(struct cyapa_tsg_bin_image_head)) /
+ sizeof(struct cyapa_tsg_bin_image_data_record);
+ /*
+ * The last flash row 0x01ff has been written through bl_initiate
+ * command, so DO NOT write flash 0x01ff to trackpad device.
+ */
+ for (i = 0; i < (flash_records_count - 1); i++) {
+ flash_record = &image->records[i];
+ error = cyapa_gen5_write_fw_block(cyapa, flash_record);
+ if (error) {
+ dev_err(dev, "%s: Gen5 FW update aborted: %d\n",
+ __func__, error);
+ return error;
+ }
+ }
+
+ return 0;
+}
+
static int cyapa_gen5_change_power_state(struct cyapa *cyapa, u8 power_state)
{
u8 cmd[8] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x08, 0x01 };
@@ -1646,6 +1931,11 @@ static int cyapa_gen5_irq_handler(struct cyapa *cyapa)
}
const struct cyapa_dev_ops cyapa_gen5_ops = {
+ .check_fw = cyapa_gen5_check_fw,
+ .bl_enter = cyapa_gen5_bl_enter,
+ .bl_initiate = cyapa_gen5_bl_initiate,
+ .update_fw = cyapa_gen5_do_fw_update,
+
.initialize = cyapa_gen5_initialize,
.state_parse = cyapa_gen5_state_parse,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 10/19] input: cyapa: add gen3 trackpad device force re-calibrate function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-1-git-send-email-dudley.dulixin@gmail.com>
Add force re-calibrate function supported for gen3 trackpad device,
it can be used through sysfs calibrate interface.
TEST=test on Chromebooks.
Signed-off-by: Dudley Du <dudley.dulixin@gmail.com>
---
drivers/input/mouse/cyapa_gen3.c | 58 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/drivers/input/mouse/cyapa_gen3.c b/drivers/input/mouse/cyapa_gen3.c
index bc2be93..281209f 100644
--- a/drivers/input/mouse/cyapa_gen3.c
+++ b/drivers/input/mouse/cyapa_gen3.c
@@ -754,6 +754,63 @@ static int cyapa_gen3_do_fw_update(struct cyapa *cyapa,
return 0;
}
+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;
+
+ ret = cyapa_read_byte(cyapa, CYAPA_CMD_DEV_STATUS);
+ if (ret < 0) {
+ dev_err(dev, "Error reading dev status: %d\n", ret);
+ goto out;
+ }
+ if ((ret & CYAPA_DEV_NORMAL) != CYAPA_DEV_NORMAL) {
+ dev_warn(dev, "Trackpad device is busy, device state: 0x%02x\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: %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: %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:
+ return ret < 0 ? ret : count;
+}
+
static ssize_t cyapa_gen3_show_baseline(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -1136,6 +1193,7 @@ const struct cyapa_dev_ops cyapa_gen3_ops = {
.bl_deactivate = cyapa_gen3_bl_deactivate,
.show_baseline = cyapa_gen3_show_baseline,
+ .calibrate_store = cyapa_gen3_do_calibrate,
.state_parse = cyapa_gen3_state_parse,
.operational_check = cyapa_gen3_do_operational_check,
--
1.9.1
^ permalink raw reply related
* [PATCH v11 09/19] input: cyapa: add gen3 trackpad device read baseline function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-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 fc1b781..bc2be93 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
*
@@ -1066,6 +1135,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 v11 08/19] input: cyapa: add gen3 trackpad device firmware update function support
From: Dudley Du @ 2014-11-19 8:37 UTC (permalink / raw)
To: dmitry.torokhov, rydberg; +Cc: Dudley Du, bleung, linux-input, linux-kernel
In-Reply-To: <1416386271-28167-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 58c23e1..fc1b781 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
*
@@ -782,6 +1060,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
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