From: Dima Zavin Date: Thu, 23 Apr 2009 23:06:51 +0000 (-0700) Subject: input: Add Quantum OBP touchscreen support X-Git-Tag: firefly_0821_release~9834^2~927 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e5d04143374ca3ba59a198037eaf795389a55b88;p=firefly-linux-kernel-4.4.55.git input: Add Quantum OBP touchscreen support This adds a driver for the Quantum capacitive touch controllers that use the Object Based Protocol (OBP) for communication between the driver and the on-chip firmware. Change-Id: Ib469d626d5f0d11562547f2cbeb71a48e11f6072 Cc: Dan Murphy Cc: Mike Chan Cc: Paul Eastham Signed-off-by: Rebecca Schultz Zavin --- diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index d50fd5603604..a390cbad3049 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -657,4 +657,13 @@ config TOUCHSCREEN_STMPE To compile this driver as a module, choose M here: the module will be called stmpe-ts. +config TOUCHSCREEN_QUANTUM_OBP + tristate "Quantum OBP based touchscreens" + depends on I2C + help + Say Y here if you have a Quantum touchscreen that uses + the Object Based Protocol based firmware. + + If unsure, say N. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 93e641bc320e..ed2c714805e3 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_TOUCHSCREEN_PANJIT_I2C) += panjit_i2c.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_QT602240) += qt602240_ts.o +obj-$(CONFIG_TOUCHSCREEN_QUANTUM_OBP) += qtouch_obp_ts.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI) += synaptics_i2c_rmi.o diff --git a/drivers/input/touchscreen/qtouch_obp_ts.c b/drivers/input/touchscreen/qtouch_obp_ts.c new file mode 100644 index 000000000000..77a49f7fca12 --- /dev/null +++ b/drivers/input/touchscreen/qtouch_obp_ts.c @@ -0,0 +1,1165 @@ +/* + * drivers/input/touchscreen/qtouch_obp_ts.c - driver for Quantum touch IC + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 Motorola, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * Derived from the Motorola OBP touch driver. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IGNORE_CHECKSUM_MISMATCH + +struct qtm_object { + struct qtm_obj_entry entry; + uint8_t report_id_min; + uint8_t report_id_max; +}; + +struct axis_map { + int key; + int x; + int y; +}; + +struct coordinate_map { + int x_data; + int y_data; + int z_data; + int w_data; + int down; +}; + +#define _BITMAP_LEN BITS_TO_LONGS(QTM_OBP_MAX_OBJECT_NUM) +struct qtouch_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct work_struct init_work; + struct work_struct work; + struct qtouch_ts_platform_data *pdata; + struct coordinate_map finger_data[10]; + struct early_suspend early_suspend; + + struct qtm_object obj_tbl[QTM_OBP_MAX_OBJECT_NUM]; + unsigned long obj_map[_BITMAP_LEN]; + + uint32_t last_keystate; + uint16_t eeprom_checksum; + uint8_t checksum_cnt; + int x_delta; + int y_delta; + + /* Note: The message buffer is reused for reading different messages. + * MUST enforce that there is no concurrent access to msg_buf. */ + uint8_t *msg_buf; + int msg_size; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void qtouch_ts_early_suspend(struct early_suspend *handler); +static void qtouch_ts_late_resume(struct early_suspend *handler); +#endif + +static struct workqueue_struct *qtouch_ts_wq; + +static uint32_t qtouch_tsdebug; +module_param_named(tsdebug, qtouch_tsdebug, uint, 0664); + +static irqreturn_t qtouch_ts_irq_handler(int irq, void *dev_id) +{ + struct qtouch_ts_data *ts = dev_id; + + disable_irq_nosync(ts->client->irq); + queue_work(qtouch_ts_wq, &ts->work); + return IRQ_HANDLED; +} + +static int qtouch_write(struct qtouch_ts_data *ts, void *buf, int buf_sz) +{ + int retries = 10; + int ret; + + do { + ret = i2c_master_send(ts->client, (char *)buf, buf_sz); + } while ((ret < buf_sz) && (--retries > 0)); + + if (ret < 0) + pr_info("%s: Error while trying to write %d bytes\n", __func__, + buf_sz); + else if (ret != buf_sz) { + pr_info("%s: Write %d bytes, expected %d\n", __func__, + ret, buf_sz); + ret = -EIO; + } + return ret; +} + +static int qtouch_set_addr(struct qtouch_ts_data *ts, uint16_t addr) +{ + int ret; + + /* Note: addr on the wire is LSB first */ + ret = qtouch_write(ts, (char *)&addr, sizeof(uint16_t)); + if (ret < 0) + pr_info("%s: Can't send obp addr 0x%4x\n", __func__, addr); + + return ret >= 0 ? 0 : ret; +} + +static int qtouch_read(struct qtouch_ts_data *ts, void *buf, int buf_sz) +{ + int retries = 10; + int ret; + + do { + memset(buf, 0, buf_sz); + ret = i2c_master_recv(ts->client, (char *)buf, buf_sz); + } while ((ret < 0) && (--retries > 0)); + + if (ret < 0) + pr_info("%s: Error while trying to read %d bytes\n", __func__, + buf_sz); + else if (ret != buf_sz) { + pr_info("%s: Read %d bytes, expected %d\n", __func__, + ret, buf_sz); + ret = -EIO; + } + + return ret >= 0 ? 0 : ret; +} + +static int qtouch_read_addr(struct qtouch_ts_data *ts, uint16_t addr, + void *buf, int buf_sz) +{ + int ret; + + ret = qtouch_set_addr(ts, addr); + if (ret != 0) + return ret; + + return qtouch_read(ts, buf, buf_sz); +} + +static struct qtm_obj_message *qtouch_read_msg(struct qtouch_ts_data *ts) +{ + int ret; + + ret = qtouch_read(ts, ts->msg_buf, ts->msg_size); + if (!ret) + return (struct qtm_obj_message *)ts->msg_buf; + return NULL; +} + +static int qtouch_write_addr(struct qtouch_ts_data *ts, uint16_t addr, + void *buf, int buf_sz) +{ + int ret; + uint8_t write_buf[128]; + + if (buf_sz + sizeof(uint16_t) > sizeof(write_buf)) { + pr_err("%s: Buffer too large (%d)\n", __func__, buf_sz); + return -EINVAL; + } + + memcpy(write_buf, (void *)&addr, sizeof(addr)); + memcpy((void *)write_buf + sizeof(addr), buf, buf_sz); + + ret = qtouch_write(ts, write_buf, buf_sz + sizeof(addr)); + + if (ret < 0) { + pr_err("%s: Could not write %d bytes.\n", __func__, buf_sz); + return ret; + } + + return 0; +} + +static uint16_t calc_csum(uint16_t curr_sum, void *_buf, int buf_sz) +{ + uint8_t *buf = _buf; + uint32_t new_sum; + int i; + + while (buf_sz-- > 0) { + new_sum = (((uint32_t) curr_sum) << 8) | *(buf++); + for (i = 0; i < 8; ++i) { + if (new_sum & 0x800000) + new_sum ^= 0x800500; + new_sum <<= 1; + } + curr_sum = ((uint32_t) new_sum >> 8) & 0xffff; + } + + return curr_sum; +} + +static inline struct qtm_object *find_obj(struct qtouch_ts_data *ts, int id) +{ + return &ts->obj_tbl[id]; +} + +static struct qtm_object *create_obj(struct qtouch_ts_data *ts, + struct qtm_obj_entry *entry) +{ + struct qtm_object *obj; + + obj = &ts->obj_tbl[entry->type]; + memcpy(&obj->entry, entry, sizeof(*entry)); + set_bit(entry->type, ts->obj_map); + + return obj; +} + +static struct qtm_object *find_object_rid(struct qtouch_ts_data *ts, int rid) +{ + int i; + + for_each_bit(i, ts->obj_map, QTM_OBP_MAX_OBJECT_NUM) { + struct qtm_object *obj = &ts->obj_tbl[i]; + + if ((rid >= obj->report_id_min) && (rid <= obj->report_id_max)) + return obj; + } + + return NULL; +} + +static void qtouch_force_reset(struct qtouch_ts_data *ts, uint8_t sw_reset) +{ + struct qtm_object *obj; + uint16_t addr; + uint8_t val; + int ret; + + if (ts->pdata->hw_reset && !sw_reset) { + pr_info("%s: Forcing HW reset\n", __func__); + ts->pdata->hw_reset(); + } else if (sw_reset) { + pr_info("%s: Forcing SW reset\n", __func__); + obj = find_obj(ts, QTM_OBJ_GEN_CMD_PROC); + addr = + obj->entry.addr + offsetof(struct qtm_gen_cmd_proc, reset); + val = 1; + ret = qtouch_write_addr(ts, addr, &val, 1); + if (ret) + pr_err("%s: Unable to send the reset msg\n", __func__); + } +} + +static int qtouch_force_calibration(struct qtouch_ts_data *ts) +{ + struct qtm_object *obj; + uint16_t addr; + uint8_t val; + int ret; + + pr_info("%s: Forcing calibration\n", __func__); + + obj = find_obj(ts, QTM_OBJ_GEN_CMD_PROC); + + addr = obj->entry.addr + offsetof(struct qtm_gen_cmd_proc, calibrate); + val = 1; + ret = qtouch_write_addr(ts, addr, &val, 1); + if (ret) + pr_err("%s: Unable to send the calibrate message\n", __func__); + return ret; +} + +#undef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +static int qtouch_power_config(struct qtouch_ts_data *ts, int on) +{ + struct qtm_gen_power_cfg pwr_cfg; + struct qtm_object *obj; + + if (!on) { + /* go to standby mode */ + pwr_cfg.idle_acq_int = 0; + pwr_cfg.active_acq_int = 0; + } else { + pwr_cfg.idle_acq_int = ts->pdata->power_cfg.idle_acq_int; + pwr_cfg.active_acq_int = ts->pdata->power_cfg.active_acq_int; + } + + pwr_cfg.active_idle_to = ts->pdata->power_cfg.active_idle_to; + + obj = find_obj(ts, QTM_OBJ_GEN_PWR_CONF); + return qtouch_write_addr(ts, obj->entry.addr, &pwr_cfg, + min(sizeof(pwr_cfg), obj->entry.size)); +} + +/* Apply the configuration provided in the platform_data to the hardware */ +static int qtouch_hw_init(struct qtouch_ts_data *ts) +{ + struct qtm_object *obj; + int i; + int ret; + uint16_t adj_addr; + + pr_info("%s: Doing hw init\n", __func__); + + /* take the IC out of suspend */ + qtouch_power_config(ts, 1); + + /* configure the acquisition object. */ + obj = find_obj(ts, QTM_OBJ_GEN_ACQUIRE_CONF); + ret = qtouch_write_addr(ts, obj->entry.addr, &ts->pdata->acquire_cfg, + min(sizeof(ts->pdata->acquire_cfg), + obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write acquisition config\n", __func__); + return ret; + } + + /* The multitouch and keyarray objects have very similar memory + * layout, but are just different enough where we basically have to + * repeat the same code */ + + /* configure the multi-touch object. */ + obj = find_obj(ts, QTM_OBJ_TOUCH_MULTI); + if (obj && obj->entry.num_inst > 0) { + struct qtm_touch_multi_cfg cfg; + memcpy(&cfg, &ts->pdata->multi_touch_cfg, sizeof(cfg)); + if (ts->pdata->flags & QTOUCH_USE_MULTITOUCH) + cfg.ctrl |= (1 << 1) | (1 << 0); /* reporten | enable */ + else + cfg.ctrl = 0; + ret = qtouch_write_addr(ts, obj->entry.addr, &cfg, + min(sizeof(cfg), obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write multi-touch config\n", + __func__); + return ret; + } + } + + /* configure the key-array object. */ + obj = find_obj(ts, QTM_OBJ_TOUCH_KEYARRAY); + if (obj && obj->entry.num_inst > 0) { + struct qtm_touch_keyarray_cfg cfg; + for (i = 0; i < obj->entry.num_inst; i++) { + if (i > (ts->pdata->key_array.num_keys - 1)) { + pr_info("%s: No entry key instance.\n", + __func__); + memset(&cfg, 0, sizeof(cfg)); + } else if (ts->pdata->flags & QTOUCH_USE_KEYARRAY) { + memcpy(&cfg, &ts->pdata->key_array.cfg[i], sizeof(cfg)); + cfg.ctrl |= (1 << 1) | (1 << 0); /* reporten | enable */ + } else + memset(&cfg, 0, sizeof(cfg)); + + adj_addr = obj->entry.addr + + ((obj->entry.size + 1) * i); + ret = qtouch_write_addr(ts, adj_addr, &cfg, + min(sizeof(cfg), obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write keyarray config\n", + __func__); + return ret; + } + } + } + + /* configure the signal filter */ + obj = find_obj(ts, QTM_OBJ_PROCG_SIG_FILTER); + if (obj && obj->entry.num_inst > 0) { + ret = qtouch_write_addr(ts, obj->entry.addr, + &ts->pdata->sig_filter_cfg, + min(sizeof(ts->pdata->sig_filter_cfg), + obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write signal filter config\n", + __func__); + return ret; + } + } + + /* configure the linearization table */ + obj = find_obj(ts, QTM_OBJ_PROCI_LINEAR_TBL); + if (obj && obj->entry.num_inst > 0) { + ret = qtouch_write_addr(ts, obj->entry.addr, + &ts->pdata->linear_tbl_cfg, + min(sizeof(ts->pdata->linear_tbl_cfg), + obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write linear table config\n", + __func__); + return ret; + } + } + /* configure the grip suppression table */ + obj = find_obj(ts, QTM_OBJ_PROCI_GRIPFACESUPPRESSION); + if (obj && obj->entry.num_inst > 0) { + ret = qtouch_write_addr(ts, obj->entry.addr, + &ts->pdata->grip_suppression_cfg, + min(sizeof(ts->pdata->grip_suppression_cfg), + obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write the grip suppression config\n", + __func__); + return ret; + } + } + + /* configure the noise suppression table */ + obj = find_obj(ts, QTM_OBJ_NOISESUPPRESSION_1); + if (obj && obj->entry.num_inst > 0) { + ret = qtouch_write_addr(ts, obj->entry.addr, + &ts->pdata->noise1_suppression_cfg, + min(sizeof(ts->pdata->noise1_suppression_cfg), + obj->entry.size)); + if (ret != 0) { + pr_err("%s: Can't write the noise suppression config\n", + __func__); + return ret; + } + } + + ret = qtouch_force_calibration(ts); + if (ret != 0) { + pr_err("%s: Unable to recalibrate after reset\n", __func__); + return ret; + } + + /* Write the settings into nvram, if needed */ + if (ts->pdata->flags & QTOUCH_CFG_BACKUPNV) { + uint8_t val; + uint16_t addr; + + obj = find_obj(ts, QTM_OBJ_GEN_CMD_PROC); + addr = obj->entry.addr + offsetof(struct qtm_gen_cmd_proc, + backupnv); + val = 0x55; + ret = qtouch_write_addr(ts, addr, &val, 1); + if (ret != 0) { + pr_err("%s: Can't backup nvram settings\n", __func__); + return ret; + } + /* Since the IC does not indicate that has completed the + backup place a hard wait here. If we communicate with the + IC during backup the EEPROM may be corrupted */ + + msleep(500); + } + + /* reset the address pointer */ + ret = qtouch_set_addr(ts, ts->obj_tbl[QTM_OBJ_GEN_MSG_PROC].entry.addr); + if (ret != 0) { + pr_err("%s: Unable to reset address pointer after reset\n", + __func__); + return ret; + } + + return 0; +} + +/* Handles a message from the command processor object. */ +static int do_cmd_proc_msg(struct qtouch_ts_data *ts, struct qtm_object *obj, + void *_msg) +{ + struct qtm_cmd_proc_msg *msg = _msg; + int ret = 0; + int hw_reset = 0; + + if (msg->status & QTM_CMD_PROC_STATUS_RESET) { + if (qtouch_tsdebug) + pr_info("%s:EEPROM checksum is 0x%X cnt %i\n", + __func__, msg->checksum, ts->checksum_cnt); + if (msg->checksum != ts->eeprom_checksum) { + if (ts->checksum_cnt > 2) { + /* Assume the checksum is what it is, cannot + disable the touch screen so set the checksum*/ + ts->eeprom_checksum = msg->checksum; + ts->checksum_cnt = 0; + } else { + ret = qtouch_hw_init(ts); + if (ret != 0) + pr_err("%s:Cannot init the touch IC\n", + __func__); + hw_reset = 1; + ts->checksum_cnt++; + } + } + pr_info("%s: Reset done.\n", __func__); + } + + if (msg->status & QTM_CMD_PROC_STATUS_CAL) + pr_info("%s: Self-calibration started.\n", __func__); + + if (msg->status & QTM_CMD_PROC_STATUS_OFL) + pr_err("%s: Acquisition cycle length overflow\n", __func__); + + if (msg->status & QTM_CMD_PROC_STATUS_SIGERR) + pr_err("%s: Acquisition error\n", __func__); + + if (msg->status & QTM_CMD_PROC_STATUS_CFGERR) { + ret = qtouch_hw_init(ts); + if (ret != 0) + pr_err("%s:Cannot init the touch IC\n", + __func__); + + pr_err("%s: Configuration error\n", __func__); + } + /* Check the EEPROM checksum. An ESD event may cause + the checksum to change during operation so we need to + reprogram the EEPROM and reset the IC */ + if (ts->pdata->flags & QTOUCH_EEPROM_CHECKSUM) { + if (msg->checksum != ts->eeprom_checksum) { + if (qtouch_tsdebug) + pr_info("%s:EEPROM checksum is 0x%X cnt %i \ + hw_reset %i\n", + __func__, msg->checksum, + ts->checksum_cnt, hw_reset); + if (ts->checksum_cnt > 2) { + /* Assume the checksum is what it is, cannot + disable the touch screen so set the checksum*/ + ts->eeprom_checksum = msg->checksum; + ts->checksum_cnt = 0; + } else { + if (!hw_reset) { + ret = qtouch_hw_init(ts); + if (ret != 0) + pr_err("%s:Cannot init the touch IC\n", + __func__); + qtouch_force_reset(ts, 0); + ts->checksum_cnt++; + } + } + } + } + return ret; +} + +/* Handles a message from a multi-touch object. */ +static int do_touch_multi_msg(struct qtouch_ts_data *ts, struct qtm_object *obj, + void *_msg) +{ + struct qtm_touch_multi_msg *msg = _msg; + int i; + int x; + int y; + int pressure; + int width; + int finger; + int down; + + finger = msg->report_id - obj->report_id_min; + if (finger >= ts->pdata->multi_touch_cfg.num_touch) + return 0; + + /* x/y are 10bit values, with bottom 2 bits inside the xypos_lsb */ + x = (msg->xpos_msb << 2) | ((msg->xypos_lsb >> 6) & 0x3); + y = (msg->ypos_msb << 2) | ((msg->xypos_lsb >> 2) & 0x3); + width = msg->touch_area; + pressure = msg->touch_amp; + + if (ts->pdata->flags & QTOUCH_SWAP_XY) + swap(x, y); + + if (qtouch_tsdebug & 2) + pr_info("%s: stat=%02x, f=%d x=%d y=%d p=%d w=%d\n", __func__, + msg->status, finger, x, y, pressure, width); + + down = !(msg->status & QTM_TOUCH_MULTI_STATUS_RELEASE); + + /* The chip may report erroneous points way + beyond what a user could possibly perform so we filter + these out */ + if (ts->finger_data[finger].down && + (abs(ts->finger_data[finger].x_data - x) > ts->x_delta || + abs(ts->finger_data[finger].y_data - y) > ts->y_delta)) { + down = 0; + if (qtouch_tsdebug & 2) + pr_info("%s: x0 %i x1 %i y0 %i y1 %i\n", + __func__, + ts->finger_data[finger].x_data, x, + ts->finger_data[finger].y_data, y); + } else { + ts->finger_data[finger].x_data = x; + ts->finger_data[finger].y_data = y; + ts->finger_data[finger].w_data = width; + } + + /* The touch IC will not give back a pressure of zero + so send a 0 when a liftoff is produced */ + if (!down) { + ts->finger_data[finger].z_data = 0; + } else { + ts->finger_data[finger].z_data = pressure; + ts->finger_data[finger].down = down; + } + + for (i = 0; i < ts->pdata->multi_touch_cfg.num_touch; i++) { + if (ts->finger_data[i].down == 0) + continue; + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, + ts->finger_data[i].z_data); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, + ts->finger_data[i].w_data); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, + ts->finger_data[i].x_data); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, + ts->finger_data[i].y_data); + input_mt_sync(ts->input_dev); + } + input_sync(ts->input_dev); + + if (!down) { + memset(&ts->finger_data[finger], 0, + sizeof(struct coordinate_map)); + } + + return 0; +} + +/* Handles a message from a keyarray object. */ +static int do_touch_keyarray_msg(struct qtouch_ts_data *ts, + struct qtm_object *obj, void *_msg) +{ + struct qtm_touch_keyarray_msg *msg = _msg; + int i; + + /* nothing changed.. odd. */ + if (ts->last_keystate == msg->keystate) + return 0; + + for (i = 0; i < ts->pdata->key_array.num_keys; ++i) { + struct qtouch_key *key = &ts->pdata->key_array.keys[i]; + uint32_t bit = 1 << (key->channel & 0x1f); + if ((msg->keystate & bit) != (ts->last_keystate & bit)) + input_report_key(ts->input_dev, key->code, + msg->keystate & bit); + } + input_sync(ts->input_dev); + + if (qtouch_tsdebug & 2) + pr_info("%s: key state changed 0x%08x -> 0x%08x\n", __func__, + ts->last_keystate, msg->keystate); + + /* update our internal state */ + ts->last_keystate = msg->keystate; + + return 0; +} + +static int qtouch_handle_msg(struct qtouch_ts_data *ts, struct qtm_object *obj, + struct qtm_obj_message *msg) +{ + int ret = 0; + + /* These are all the known objects that we know how to handle. */ + switch (obj->entry.type) { + case QTM_OBJ_GEN_CMD_PROC: + ret = do_cmd_proc_msg(ts, obj, msg); + break; + + case QTM_OBJ_TOUCH_MULTI: + ret = do_touch_multi_msg(ts, obj, msg); + break; + + case QTM_OBJ_TOUCH_KEYARRAY: + ret = do_touch_keyarray_msg(ts, obj, msg); + break; + + default: + /* probably not fatal? */ + ret = 0; + pr_info("%s: No handler defined for message from object " + "type %d, report_id %d\n", __func__, obj->entry.type, + msg->report_id); + } + + return ret; +} + +static void qtouch_ts_work_func(struct work_struct *work) +{ + struct qtouch_ts_data *ts = + container_of(work, struct qtouch_ts_data, work); + struct qtm_obj_message *msg; + struct qtm_object *obj; + int ret; + + msg = qtouch_read_msg(ts); + if (msg == NULL) { + pr_err("%s: Cannot read message\n", __func__); + goto done; + } + + obj = find_object_rid(ts, msg->report_id); + if (!obj) { + pr_err("%s: Unknown object for report_id %d\n", __func__, + msg->report_id); + goto done; + } + + ret = qtouch_handle_msg(ts, obj, msg); + if (ret != 0) { + pr_err("%s: Unable to process message for obj %d, " + "report_id %d\n", __func__, obj->entry.type, + msg->report_id); + goto done; + } + +done: + enable_irq(ts->client->irq); +} + +static int qtouch_process_info_block(struct qtouch_ts_data *ts) +{ + struct qtm_id_info qtm_info; + uint16_t our_csum = 0x0; + uint16_t their_csum; + uint8_t report_id; + uint16_t addr; + int err; + int i; + + /* query the device and get the info block. */ + err = qtouch_read_addr(ts, QTM_OBP_ID_INFO_ADDR, &qtm_info, + sizeof(qtm_info)); + if (err != 0) { + pr_err("%s: Cannot read info object block\n", __func__); + goto err_read_info_block; + } + our_csum = calc_csum(our_csum, &qtm_info, sizeof(qtm_info)); + + /* TODO: Add a version/family/variant check? */ + pr_info("%s: Build version is 0x%x\n", __func__, qtm_info.version); + + if (qtm_info.num_objs == 0) { + pr_err("%s: Device (0x%x/0x%x/0x%x/0x%x) does not export any " + "objects.\n", __func__, qtm_info.family_id, + qtm_info.variant_id, qtm_info.version, qtm_info.build); + err = -ENODEV; + goto err_no_objects; + } + + addr = QTM_OBP_ID_INFO_ADDR + sizeof(qtm_info); + report_id = 1; + + /* read out the object entries table */ + for (i = 0; i < qtm_info.num_objs; ++i) { + struct qtm_object *obj; + struct qtm_obj_entry entry; + + err = qtouch_read_addr(ts, addr, &entry, sizeof(entry)); + if (err != 0) { + pr_err("%s: Can't read object (%d) entry.\n", + __func__, i); + err = -EIO; + goto err_read_entry; + } + our_csum = calc_csum(our_csum, &entry, sizeof(entry)); + addr += sizeof(entry); + + entry.size++; + entry.num_inst++; + + pr_info("%s: Object %d @ 0x%04x (%d) insts %d rep_ids %d\n", + __func__, entry.type, entry.addr, entry.size, + entry.num_inst, entry.num_rids); + + if (entry.type >= QTM_OBP_MAX_OBJECT_NUM) { + pr_warning("%s: Unknown object type (%d) encountered\n", + __func__, entry.type); + /* Not fatal */ + continue; + } + + /* save the message_procesor msg_size for easy reference. */ + if (entry.type == QTM_OBJ_GEN_MSG_PROC) + ts->msg_size = entry.size; + + obj = create_obj(ts, &entry); + /* set the report_id range that the object is responsible for */ + if ((obj->entry.num_rids * obj->entry.num_inst) != 0) { + obj->report_id_min = report_id; + report_id += obj->entry.num_rids * obj->entry.num_inst; + obj->report_id_max = report_id - 1; + } + } + + if (!ts->msg_size) { + pr_err("%s: Message processing object not found. Bailing.\n", + __func__); + err = -ENODEV; + goto err_no_msg_proc; + } + + /* verify that some basic objects are present. These objects are + * assumed to be present by the rest of the driver, so fail out now + * if the firmware is busted. */ + if (!find_obj(ts, QTM_OBJ_GEN_PWR_CONF) || + !find_obj(ts, QTM_OBJ_GEN_ACQUIRE_CONF) || + !find_obj(ts, QTM_OBJ_GEN_MSG_PROC) || + !find_obj(ts, QTM_OBJ_GEN_CMD_PROC)) { + pr_err("%s: Required objects are missing\n", __func__); + err = -ENOENT; + goto err_missing_objs; + } + + err = qtouch_read_addr(ts, addr, &their_csum, sizeof(their_csum)); + if (err != 0) { + pr_err("%s: Unable to read remote checksum\n", __func__); + err = -ENODEV; + goto err_no_checksum; + } + + /* FIXME: The algorithm described in the datasheet doesn't seem to + * match what the touch firmware is doing on the other side. We + * always get mismatches! */ + if (our_csum != their_csum) { + pr_warning("%s: Checksum mismatch (0x%04x != 0x%04x)\n", + __func__, our_csum, their_csum); +#ifndef IGNORE_CHECKSUM_MISMATCH + err = -ENODEV; + goto err_bad_checksum; +#endif + } + + pr_info("%s: %s found. family 0x%x, variant 0x%x, ver 0x%x, " + "build 0x%x, matrix %dx%d, %d objects.\n", __func__, + QTOUCH_TS_NAME, qtm_info.family_id, qtm_info.variant_id, + qtm_info.version, qtm_info.build, qtm_info.matrix_x_size, + qtm_info.matrix_y_size, qtm_info.num_objs); + + ts->eeprom_checksum = ts->pdata->nv_checksum; + + return 0; + +err_no_checksum: +err_missing_objs: +err_no_msg_proc: +err_read_entry: +err_no_objects: +err_read_info_block: + return err; +} + +static int qtouch_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct qtouch_ts_platform_data *pdata = client->dev.platform_data; + struct qtouch_ts_data *ts; + struct qtm_object *obj; + int err; + int i; + + if (pdata == NULL) { + pr_err("%s: platform data required\n", __func__); + return -ENODEV; + } else if (!client->irq) { + pr_err("%s: polling mode currently not supported\n", __func__); + return -ENODEV; + } else if (!pdata->hw_reset) { + pr_err("%s: Must supply a hw reset function\n", __func__); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: need I2C_FUNC_I2C\n", __func__); + return -ENODEV; + } + + ts = kzalloc(sizeof(struct qtouch_ts_data), GFP_KERNEL); + if (ts == NULL) { + err = -ENOMEM; + goto err_alloc_data_failed; + } + + INIT_WORK(&ts->work, qtouch_ts_work_func); + + ts->pdata = pdata; + ts->client = client; + i2c_set_clientdata(client, ts); + ts->checksum_cnt = 0; + ts->x_delta = ts->pdata->x_delta; + ts->y_delta = ts->pdata->y_delta; + + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + pr_err("%s: failed to alloc input device\n", __func__); + err = -ENOMEM; + goto err_alloc_input_dev; + } + ts->input_dev->name = "qtouch-touchscreen"; + input_set_drvdata(ts->input_dev, ts); + + qtouch_force_reset(ts, 0); + + err = qtouch_process_info_block(ts); + if (err != 0) + goto err_process_info_block; + + ts->msg_buf = kmalloc(ts->msg_size, GFP_KERNEL); + if (ts->msg_buf == NULL) { + pr_err("%s: Cannot allocate msg_buf\n", __func__); + err = -ENOMEM; + goto err_alloc_msg_buf; + } + + /* Point the address pointer to the message processor. + * Must do this before enabling interrupts */ + obj = find_obj(ts, QTM_OBJ_GEN_MSG_PROC); + err = qtouch_set_addr(ts, obj->entry.addr); + if (err != 0) { + pr_err("%s: Can't to set addr to msg processor\n", __func__); + goto err_rst_addr_msg_proc; + } + + set_bit(EV_SYN, ts->input_dev->evbit); + + /* register the harwdare assisted virtual keys, if any */ + obj = find_obj(ts, QTM_OBJ_TOUCH_KEYARRAY); + if (obj && (obj->entry.num_inst > 0) && + (pdata->flags & QTOUCH_USE_KEYARRAY)) { + for (i = 0; i < pdata->key_array.num_keys; ++i) + input_set_capability(ts->input_dev, EV_KEY, + pdata->key_array.keys[i].code); + } + + /* register the software virtual keys, if any are provided */ + for (i = 0; i < pdata->vkeys.count; ++i) + input_set_capability(ts->input_dev, EV_KEY, + pdata->vkeys.keys[i].code); + + obj = find_obj(ts, QTM_OBJ_TOUCH_MULTI); + if (obj && obj->entry.num_inst > 0) { + set_bit(EV_ABS, ts->input_dev->evbit); + /* Legacy support for testing only */ + input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); + input_set_capability(ts->input_dev, EV_KEY, BTN_2); + input_set_abs_params(ts->input_dev, ABS_X, + pdata->abs_min_x, pdata->abs_max_x, + pdata->fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0X, + pdata->abs_min_x, pdata->abs_max_x, + pdata->fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_Y, + pdata->abs_min_y, pdata->abs_max_y, + pdata->fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0Y, + pdata->abs_min_x, pdata->abs_max_x, + pdata->fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_PRESSURE, + pdata->abs_min_p, pdata->abs_max_p, + pdata->fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, + pdata->abs_min_w, pdata->abs_max_w, + pdata->fuzz_w, 0); + + /* multi touch */ + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, + pdata->abs_min_x, pdata->abs_max_x, + pdata->fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, + pdata->abs_min_y, pdata->abs_max_y, + pdata->fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, + pdata->abs_min_p, pdata->abs_max_p, + pdata->fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, + pdata->abs_min_w, pdata->abs_max_w, + pdata->fuzz_w, 0); + } + + memset(&ts->finger_data[0], 0, + (sizeof(struct coordinate_map) * + ts->pdata->multi_touch_cfg.num_touch)); + + err = input_register_device(ts->input_dev); + if (err != 0) { + pr_err("%s: Cannot register input device \"%s\"\n", __func__, + ts->input_dev->name); + goto err_input_register_dev; + } + + err = request_irq(ts->client->irq, qtouch_ts_irq_handler, + IRQ_DISABLED | pdata->irqflags, "qtouch_ts_int", ts); + if (err != 0) { + pr_err("%s: request_irq (%d) failed\n", __func__, + ts->client->irq); + goto err_request_irq; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->early_suspend.suspend = qtouch_ts_early_suspend; + ts->early_suspend.resume = qtouch_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + return 0; + +err_request_irq: + input_unregister_device(ts->input_dev); + +err_input_register_dev: +err_rst_addr_msg_proc: + if (ts->msg_buf) + kfree(ts->msg_buf); + +err_alloc_msg_buf: +err_process_info_block: + input_free_device(ts->input_dev); + +err_alloc_input_dev: + i2c_set_clientdata(client, NULL); + kfree(ts); + +err_alloc_data_failed: + return err; +} + +static int qtouch_ts_remove(struct i2c_client *client) +{ + struct qtouch_ts_data *ts = i2c_get_clientdata(client); + + unregister_early_suspend(&ts->early_suspend); + free_irq(ts->client->irq, ts); + input_unregister_device(ts->input_dev); + input_free_device(ts->input_dev); + i2c_set_clientdata(client, NULL); + kfree(ts); + return 0; +} + +static int qtouch_ts_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct qtouch_ts_data *ts = i2c_get_clientdata(client); + int ret; + if (qtouch_tsdebug & 4) + pr_info("%s: Suspending\n", __func__); + + disable_irq_nosync(ts->client->irq); + ret = cancel_work_sync(&ts->work); + if (ret) { /* if work was pending disable-count is now 2 */ + pr_info("%s: Pending work item\n", __func__); + enable_irq(ts->client->irq); + } + + ret = qtouch_power_config(ts, 0); + if (ret < 0) + pr_err("%s: Cannot write power config\n", __func__); + + return 0; +} + +static int qtouch_ts_resume(struct i2c_client *client) +{ + struct qtouch_ts_data *ts = i2c_get_clientdata(client); + int ret; + int i; + + if (qtouch_tsdebug & 4) + pr_info("%s: Resuming\n", __func__); + + /* If we were suspended while a touch was happening + we need to tell the upper layers so they do not hang + waiting on the liftoff that will not come. */ + for (i = 0; i < ts->pdata->multi_touch_cfg.num_touch; i++) { + if (qtouch_tsdebug & 4) + pr_info("%s: Finger %i down state %i\n", + __func__, i, ts->finger_data[i].down); + if (ts->finger_data[i].down == 0) + continue; + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_mt_sync(ts->input_dev); + memset(&ts->finger_data[i], 0, + sizeof(struct coordinate_map)); + } + input_sync(ts->input_dev); + + ret = qtouch_power_config(ts, 1); + if (ret < 0) { + pr_err("%s: Cannot write power config\n", __func__); + return -EIO; + } + qtouch_force_reset(ts, 0); + + enable_irq(ts->client->irq); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void qtouch_ts_early_suspend(struct early_suspend *handler) +{ + struct qtouch_ts_data *ts; + + ts = container_of(handler, struct qtouch_ts_data, early_suspend); + qtouch_ts_suspend(ts->client, PMSG_SUSPEND); +} + +static void qtouch_ts_late_resume(struct early_suspend *handler) +{ + struct qtouch_ts_data *ts; + + ts = container_of(handler, struct qtouch_ts_data, early_suspend); + qtouch_ts_resume(ts->client); +} +#endif + +/******** init ********/ +static const struct i2c_device_id qtouch_ts_id[] = { + { QTOUCH_TS_NAME, 0 }, + { } +}; + +static struct i2c_driver qtouch_ts_driver = { + .probe = qtouch_ts_probe, + .remove = qtouch_ts_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = qtouch_ts_suspend, + .resume = qtouch_ts_resume, +#endif + .id_table = qtouch_ts_id, + .driver = { + .name = QTOUCH_TS_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __devinit qtouch_ts_init(void) +{ + qtouch_ts_wq = create_singlethread_workqueue("qtouch_obp_ts_wq"); + if (qtouch_ts_wq == NULL) { + pr_err("%s: No memory for qtouch_ts_wq\n", __func__); + return -ENOMEM; + } + return i2c_add_driver(&qtouch_ts_driver); +} + +static void __exit qtouch_ts_exit(void) +{ + i2c_del_driver(&qtouch_ts_driver); + if (qtouch_ts_wq) + destroy_workqueue(qtouch_ts_wq); +} + +module_init(qtouch_ts_init); +module_exit(qtouch_ts_exit); + +MODULE_AUTHOR("Dima Zavin "); +MODULE_DESCRIPTION("Quantum OBP Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/qtouch_obp_ts.h b/include/linux/qtouch_obp_ts.h new file mode 100644 index 000000000000..1091b8930eea --- /dev/null +++ b/include/linux/qtouch_obp_ts.h @@ -0,0 +1,325 @@ +/* + * include/linux/qtouch_obp_ts.h - platform/protocol data for Quantum touch IC + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 Motorola, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * Derived from the Motorola OBP touch driver. + * + */ + +#ifndef _LINUX_QTOUCH_OBP_TS_H +#define _LINUX_QTOUCH_OBP_TS_H + +#define QTOUCH_TS_NAME "qtouch-obp-ts" + +#define QTM_OBP_ID_INFO_ADDR 0 + +enum { + QTM_OBJ_RESERVED0 = 0, + QTM_OBJ_RESERVED1 = 1, + QTM_OBJ_DBG_DELTAS = 2, + QTM_OBJ_DBG_REFS = 3, + QTM_OBJ_DBG_SIGS = 4, + QTM_OBJ_GEN_MSG_PROC = 5, + QTM_OBJ_GEN_CMD_PROC = 6, + QTM_OBJ_GEN_PWR_CONF = 7, + QTM_OBJ_GEN_ACQUIRE_CONF = 8, + QTM_OBJ_TOUCH_MULTI = 9, + QTM_OBJ_TOUCH_SINGLE = 10, + QTM_OBJ_TOUCH_XSLIDER = 11, + QTM_OBJ_TOUCH_SLIDER = 12, + QTM_OBJ_TOUCH_XWHEEL = 13, + QTM_OBJ_TOUCH_YWHEEL = 14, + QTM_OBJ_TOUCH_KEYARRAY = 15, + QTM_OBJ_PROCG_SIG_FILTER = 16, + QTM_OBJ_PROCI_LINEAR_TBL = 17, + QTM_OBJ_PROCI_GESTURES_PROC = 18, + QTM_OBJ_PROCI_GRIPFACESUPPRESSION = 20, + QTM_OBJ_NOISESUPPRESSION_1 = 36, + + /* Max number of objects currently defined */ + QTM_OBP_MAX_OBJECT_NUM = QTM_OBJ_NOISESUPPRESSION_1 + 1, +}; + +/* OBP structures as defined by the wire protocol. */ + +/* Note: Not all the structures below need an explicit packed attribute since + * many of them just contain uint8_t's. However, the protocol is defined in + * such a way that the structures may expand in the future, with + * potential multi-byte fields. Thus, we will mark them all as packed to + * minimize silly bugs in the future. + */ + +/* part of the info block */ +struct qtm_id_info { + uint8_t family_id; + uint8_t variant_id; + uint8_t version; + uint8_t build; + uint8_t matrix_x_size; + uint8_t matrix_y_size; + uint8_t num_objs; +} __attribute__ ((packed)); + +/* an entry in the ote table */ +struct qtm_obj_entry { + uint8_t type; + uint16_t addr; + uint8_t size; + uint8_t num_inst; + uint8_t num_rids; +} __attribute__ ((packed)); + + +/*******************************/ +/*********** messages **********/ +/*******************************/ + +/* generic message received from the message_processor object. size/buffer + * defined at runtime after reading the info block */ +struct qtm_obj_message { + uint8_t report_id; + uint8_t msg[0]; +} __attribute__ ((packed)); + +/* status message sent by the command processor - T6 */ +#define QTM_CMD_PROC_STATUS_RESET (1 << 7) +#define QTM_CMD_PROC_STATUS_OFL (1 << 6) +#define QTM_CMD_PROC_STATUS_SIGERR (1 << 5) +#define QTM_CMD_PROC_STATUS_CAL (1 << 4) +#define QTM_CMD_PROC_STATUS_CFGERR (1 << 3) +struct qtm_cmd_proc_msg { + uint8_t report_id; + uint8_t status; + uint16_t checksum; +} __attribute__ ((packed)); + +/* status message sent by the mutlitouch touch object - T9*/ +#define QTM_TOUCH_MULTI_STATUS_TOUCH (1 << 7) +#define QTM_TOUCH_MULTI_STATUS_PRESS (1 << 6) +#define QTM_TOUCH_MULTI_STATUS_RELEASE (1 << 5) +#define QTM_TOUCH_MULTI_STATUS_MOVE (1 << 4) +#define QTM_TOUCH_MULTI_STATUS_VECTOR (1 << 3) +#define QTM_TOUCH_MULTI_STATUS_AMPLITUDE (1 << 2) +struct qtm_touch_multi_msg { + uint8_t report_id; + uint8_t status; + uint8_t xpos_msb; + uint8_t ypos_msb; + uint8_t xypos_lsb; + uint8_t touch_area; + uint8_t touch_amp; + uint8_t touch_vect; +} __attribute__ ((packed)); + +/* status message sent by the keyarray touch object - T15 */ +#define QTM_TOUCH_KEYARRAY_STATUS_TOUCH (1 << 7) +struct qtm_touch_keyarray_msg { + uint8_t report_id; + uint8_t status; + uint32_t keystate; +} __attribute__ ((packed)); + + + +/*******************************/ +/**** configuration objects ****/ +/*******************************/ + +/* GEN_COMMANDPROCESSOR_T6 */ +struct qtm_gen_cmd_proc { + uint8_t reset; + uint8_t backupnv; + uint8_t calibrate; + uint8_t reportall; + uint8_t debugctrl; +} __attribute__ ((packed)); + +/* GEN_POWERCONFIG_T7 */ +struct qtm_gen_power_cfg { + uint8_t idle_acq_int; /* in ms */ + uint8_t active_acq_int; /* in ms */ + uint8_t active_idle_to; /* in 200ms */ +} __attribute__ ((packed)); + +/* GEN_ACQUIRECONFIG_T8 */ +struct qtm_gen_acquire_cfg { + uint8_t charge_time; /* in 250ns */ + uint8_t atouch_drift; /* in 200ms */ + uint8_t touch_drift; /* in 200ms */ + uint8_t drift_susp; /* in 200ms */ + uint8_t touch_autocal; /* in 200ms */ + uint8_t sync; +} __attribute__ ((packed)); + +/* TOUCH_MULTITOUCHSCREEN_T9 */ +struct qtm_touch_multi_cfg { + uint8_t ctrl; + uint8_t x_origin; + uint8_t y_origin; + uint8_t x_size; + uint8_t y_size; + uint8_t aks_cfg; + uint8_t burst_len; + uint8_t tch_det_thr; + uint8_t tch_det_int; + uint8_t rsvd1; + uint8_t rsvd2; + uint8_t mov_hyst_init; + uint8_t mov_hyst_next; + uint8_t mov_filter; + uint8_t num_touch; + uint8_t merge_hyst; + uint8_t merge_thresh; + uint8_t amp_hyst; + uint16_t x_res; + uint16_t y_res; + uint8_t x_low_clip; + uint8_t x_high_clip; + uint8_t y_low_clip; + uint8_t y_high_clip; + +} __attribute__ ((packed)); + +/* TOUCH_KEYARRAY_T15 */ +struct qtm_touch_keyarray_cfg { + uint8_t ctrl; + uint8_t x_origin; + uint8_t y_origin; + uint8_t x_size; + uint8_t y_size; + uint8_t aks_cfg; + uint8_t burst_len; + uint8_t tch_det_thr; + uint8_t tch_det_int; + uint8_t rsvd1; + uint8_t rsvd2; +} __attribute__ ((packed)); + +/* PROCG_SIGNALFILTER_T16 */ +struct qtm_procg_sig_filter_cfg { + uint8_t slew; + uint8_t median; + uint8_t iir; +} __attribute__ ((packed)); + +/* PROCI_LINEARIZATIONTABLE_T17 */ +struct qtm_proci_linear_tbl_cfg { + uint8_t ctrl; + uint16_t x_offset; + uint8_t x_segment[16]; + uint16_t y_offset; + uint8_t y_segment[16]; +} __attribute__ ((packed)); + +/* PROCI_GRIPFACESUPPRESSION_T20 */ +struct qtm_proci_grip_suppression_cfg { + uint8_t ctrl; + uint8_t xlogrip; + uint8_t xhigrip; + uint8_t ylogrip; + uint8_t yhigrip; + uint8_t maxtchs; + uint8_t reserve0; + uint8_t szthr1; + uint8_t szthr2; + uint8_t shpthr1; + uint8_t shpthr2; +} __attribute__ ((packed)); + +/* QTM_OBJ_NOISESUPPRESSION_1 */ +struct qtm_proci_noise1_suppression_cfg { + uint8_t ctrl; + uint8_t reserved; + uint8_t atchthr; + uint8_t duty_cycle; +} __attribute__ ((packed)); + +/*******************************/ +/******** platform data ********/ +/*******************************/ + +struct vkey { + int code; + int min; + int max; +}; + +struct virt_keys { + struct vkey *keys; + int count; + int start; +}; + +struct qtouch_key { + uint8_t channel; + int code; +}; + +struct qtouch_key_array { + struct qtm_touch_keyarray_cfg *cfg; + struct qtouch_key *keys; + int num_keys; +}; + +#define QTOUCH_FLIP_X (1 << 0) +#define QTOUCH_FLIP_Y (1 << 1) +#define QTOUCH_SWAP_XY (1 << 2) +#define QTOUCH_USE_MULTITOUCH (1 << 3) +#define QTOUCH_USE_KEYARRAY (1 << 4) +#define QTOUCH_CFG_BACKUPNV (1 << 5) +#define QTOUCH_EEPROM_CHECKSUM (1 << 6) + +struct qtouch_ts_platform_data { + uint32_t flags; + unsigned long irqflags; + + uint32_t abs_min_x; + uint32_t abs_max_x; + uint32_t abs_min_y; + uint32_t abs_max_y; + uint32_t abs_min_p; + uint32_t abs_max_p; + uint32_t abs_min_w; + uint32_t abs_max_w; + + uint32_t x_delta; + uint32_t y_delta; + + uint16_t nv_checksum; + + uint32_t fuzz_x; + uint32_t fuzz_y; + uint32_t fuzz_p; + uint32_t fuzz_w; + + int (*hw_reset)(void); + + struct virt_keys vkeys; + + /* TODO: allow multiple key arrays */ + struct qtouch_key_array key_array; + + /* object configuration information from board */ + struct qtm_gen_power_cfg power_cfg; + struct qtm_gen_acquire_cfg acquire_cfg; + struct qtm_touch_multi_cfg multi_touch_cfg; + struct qtm_touch_keyarray_cfg key_array_cfg; + struct qtm_procg_sig_filter_cfg sig_filter_cfg; + struct qtm_proci_linear_tbl_cfg linear_tbl_cfg; + struct qtm_proci_grip_suppression_cfg grip_suppression_cfg; + struct qtm_proci_noise1_suppression_cfg noise1_suppression_cfg; +}; + +#endif /* _LINUX_QTOUCH_OBP_TS_H */ +