diff options
Diffstat (limited to 'drivers/input/touchscreen/bu21013_ts.c')
-rwxr-xr-x | drivers/input/touchscreen/bu21013_ts.c | 1357 |
1 files changed, 1357 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c new file mode 100755 index 00000000000..1b7f8c165df --- /dev/null +++ b/drivers/input/touchscreen/bu21013_ts.c @@ -0,0 +1,1357 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * Author: Naveen Kumar G <naveen.gaddipati@stericsson.com> for ST-Ericsson + * License terms:GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/bu21013.h> + +/** + * Touchpanel related macros declaration + */ +#define PEN_DOWN_INTR 0 +#define PEN_UP_INTR 1 +#define RESET_DELAY 30 +#define POLLING_DELAY 100 +#define MAX_TOOL_WIDTH 15 +#define MAX_TOUCH_MAJOR 255 +#define MAX_PRESSURE 1 +#define PENUP_TIMEOUT (20)/* msec */ +#define SCALE_FACTOR 1000 +#define I2C_ADDR_TP2 0x5D + +#define START (0) +#define STOP (-1) + +/*Direction Indicator */ +#define DIR_INVALID (0) +#define DIR_LEFT (1) +#define DIR_RIGHT (2) +#define DIR_UP (3) +#define DIR_DOWN (4) + +/* Pinch */ +#define PINCH_KEEP (0) +#define PINCH_IN (1) +#define PINCH_OUT (2) + +/* Rotate */ +#define ROTATE_INVALID (0) +#define ROTATE_R_UR (1) +#define ROTATE_R_RD (2) +#define ROTATE_R_DL (3) +#define ROTATE_R_LU (4) +#define ROTATE_L_LD (5) +#define ROTATE_L_DR (6) +#define ROTATE_L_RU (7) +#define ROTATE_L_UL (8) + +/* Gesture Information */ +#define GES_FLICK 0x01 +#define GES_TAP 0x02 +#define GES_PINCH 0x03 +#define GES_DRAGDROP 0x04 +#define GES_TOUCHSTART 0x05 +#define GES_TOUCHEND 0x06 +#define GES_MOVE 0x07 +#define GES_ROTATE 0x08 +#define GES_UNKNOWN 0xff + +/* Speed */ +#define LOWSPEED 1 +#define HIGHSPEED 2 +#define THRESHOLD_TAPLIMIT 60 +#define THRESHOLD_FLICK 60 +#define THRESHOLD_FLICK_SPEED 300 +#define THRESHOLD_PINCH_SPEED 3000 +#define THRESHOLD_DRAGDROP 100 +#define THRESHOLD_ROTATE 3 +#define THRESHOLD_ROTATE_HIST 8 +#define THRESHOLD_PINCH 500 +#define DIRHEADER 6 +#define DIRTRACEN 32 +#define DIR_TRACE_VALUE 0x05 +#define DRIVER_TP "bu21013_tp" + +static struct i2c_driver bu21013_tp_driver; +struct bu21013_ts_data *tp_sys_data; + +static int bu21013_init_chip(struct bu21013_ts_data *data); + +static ssize_t touchp_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + u8 timeout; + u8 sys_th_on; + u8 sys_th_off; + u8 sys_gain; + u8 sys_ext_clock; + u8 sys_edge_mode; + + if (strcmp(attr->name, "timeout_value") == 0) { + timeout = tp_sys_data->penup_timer; + sprintf(buf, "%d\n", timeout); + } else if (strcmp(attr->name, "th_on") == 0) { + sys_th_on = tp_sys_data->th_on; + sprintf(buf, "%d\n", sys_th_on); + } else if (strcmp(attr->name, "th_off") == 0) { + sys_th_off = tp_sys_data->th_off; + sprintf(buf, "%d\n", sys_th_off); + } else if (strcmp(attr->name, "gain") == 0) { + sys_gain = tp_sys_data->gain; + sprintf(buf, "%d\n", sys_gain); + } else if (strcmp(attr->name, "ext_clk") == 0) { + sys_ext_clock = tp_sys_data->chip->ext_clk; + sprintf(buf, "%d\n", sys_ext_clock); + } else if (strcmp(attr->name, "edge_mode") == 0) { + sys_edge_mode = tp_sys_data->chip->edge_mode; + sprintf(buf, "%d\n", sys_edge_mode); + } + return strlen(buf); +} +static ssize_t touchp_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t len) +{ + int ret = 0; + int timeout; + int sys_th_on; + int sys_th_off; + int sys_gain; + int sys_ext_clock; + int sys_edge_mode; + + if (strcmp(attr->name, "timeout_value") == 0) { + ret = sscanf(buf, "%d", &timeout); + if (ret == 1) + tp_sys_data->penup_timer = timeout; + } else if (strcmp(attr->name, "th_on") == 0) { + ret = sscanf(buf, "%d", &sys_th_on); + if ((ret == 1) && (tp_sys_data->th_on != sys_th_on)) { + tp_sys_data->th_on = sys_th_on; + if (tp_sys_data->th_on < BU21013_TH_ON_MAX) + bu21013_init_chip(tp_sys_data); + } + } else if (strcmp(attr->name, "th_off") == 0) { + ret = sscanf(buf, "%d", &sys_th_off); + if ((ret == 1) && (tp_sys_data->th_off != sys_th_off)) { + tp_sys_data->th_off = sys_th_off; + if (tp_sys_data->th_off < BU21013_TH_OFF_MAX) + bu21013_init_chip(tp_sys_data); + } + } else if (strcmp(attr->name, "gain") == 0) { + ret = sscanf(buf, "%d", &sys_gain); + if ((ret == 1) && (tp_sys_data->gain != sys_gain)) { + tp_sys_data->gain = sys_gain; + bu21013_init_chip(tp_sys_data); + } + } else if (strcmp(attr->name, "ext_clk") == 0) { + ret = sscanf(buf, "%d", &sys_ext_clock); + if ((ret == 1) && (tp_sys_data->chip->ext_clk != sys_ext_clock)) { + tp_sys_data->chip->ext_clk = sys_ext_clock; + bu21013_init_chip(tp_sys_data); + } + } else if (strcmp(attr->name, "edge_mode") == 0) { + ret = sscanf(buf, "%d", &sys_edge_mode); + if ((ret == 1) && (tp_sys_data->chip->edge_mode != sys_edge_mode)) { + tp_sys_data->chip->edge_mode = sys_edge_mode; + bu21013_init_chip(tp_sys_data); + } + } + return ret == 1 ? len : -EINVAL; +} + +static struct attribute touchp_timeout = {.name = "timeout_value", + .mode = 0666}; +static struct attribute touchp_th_on = {.name = "th_on", .mode = 0666}; +static struct attribute touchp_th_off = {.name = "th_off", .mode = 0666}; +static struct attribute touchp_gain = {.name = "gain", .mode = 0666}; +static struct attribute touchp_ext_clk = {.name = "ext_clk", .mode = 0666}; +static struct attribute touchp_edge_mode = {.name = "edge_mode", .mode = 0666}; + +static struct attribute *touchp_attribute[] = { + &touchp_timeout, + &touchp_th_on, + &touchp_th_off, + &touchp_gain, + &touchp_ext_clk, + &touchp_edge_mode, + NULL +}; + +static struct sysfs_ops touchp_sysfs_ops = { + .show = touchp_attr_show, + .store = touchp_attr_store, +}; + +static struct kobj_type ktype_touchp = { + .sysfs_ops = &touchp_sysfs_ops, + .default_attrs = touchp_attribute, +}; + +/* + * bu21013_do_calibrate() - Do the software Calibration + * @i2c: i2c_client structure pointer + * Do the soft calibration on the touch screen + * and returns integer + */ +int bu21013_do_calibrate(struct i2c_client *i2c) +{ + int retval; + retval = i2c_smbus_write_byte_data(i2c, BU21013_CALIB, + BU21013_CALIB_ENABLE); + if (retval < 0) + dev_err(&i2c->dev, "reset failed retval=0x%x \n", retval); + return retval; +} +/** + * bu21013_touch_calc(): Get the exact co-ordinates of the touch interrupt + * @data:bu21013_ts_data structure pointer + * Transfer the position information from touch sensor + * coordinate to display coordinate and returns none. + */ +void bu21013_touch_calc(struct bu21013_ts_data *data) +{ + signed short x1, y1; + struct bu21013_gesture_info *p_gesture_info = &data->gesture_info; + int tmpx, tmpy; + signed short x2, y2; + + tmpx = (data->chip->x_max_res * SCALE_FACTOR / data->chip->touch_x_max); + tmpy = (data->chip->y_max_res * SCALE_FACTOR / data->chip->touch_y_max); + x1 = p_gesture_info->pt[0].x; + y1 = p_gesture_info->pt[0].y; + x1 = x1 * tmpx / SCALE_FACTOR; + y1 = y1 * tmpy / SCALE_FACTOR; + if ((data->chip->portrait) && (y1 != 0)) + y1 = data->chip->y_max_res - y1; + p_gesture_info->pt[0].x = x1; + p_gesture_info->pt[0].y = y1; + x2 = p_gesture_info->pt[1].x; + y2 = p_gesture_info->pt[1].y; + x2 = x2 * tmpx / SCALE_FACTOR; + y2 = y2 * tmpy / SCALE_FACTOR; + if ((data->chip->portrait) && (y2 != 0)) + y2 = data->chip->y_max_res - y2; + p_gesture_info->pt[1].x = x2; + p_gesture_info->pt[1].y = y2; + +} + +/** + * bu21013_get_touch(): Get the co-ordinates of the touch interrupt + * @data:bu21013_ts_data structure pointer + * Get touched point position + * from touch sensor registers and write to global variables. + */ +void bu21013_get_touch(struct bu21013_ts_data *data) +{ + struct i2c_client *i2c = data->client; + int retval; + u8 sensors_buf[3]; + u8 finger1_buf[4]; + int count; + signed short tmp1x = 0; + signed short tmp1y = 0; + u8 finger2_buf[4]; + signed short tmp2x = 0; + signed short tmp2y = 0; + short touch_x_max = data->chip->touch_x_max; + short touch_y_max = data->chip->touch_y_max; + + for (count = 0; count < 2; count++) { + if (!data->chip->edge_mode) { + retval = i2c_smbus_read_i2c_block_data(i2c, BU21013_SENSORS_BTN_0_7, 3, sensors_buf); + if (retval < 0) + goto end; + data->last_press = sensors_buf[0] | sensors_buf[1] | sensors_buf[2]; + } + retval = i2c_smbus_read_i2c_block_data(i2c, BU21013_X1_POS_MSB, 4, finger1_buf); + if (retval < 0) + goto end; + tmp1x = (finger1_buf[0] << 2) | finger1_buf[1]; + tmp1y = (finger1_buf[2] << 2) | finger1_buf[3]; + if ((tmp1x != 0) && (tmp1y != 0)) + break; + } + data->finger1_pressed = false; + if ((data->chip->edge_mode || data->last_press) & + (tmp1x != 0 && tmp1y != 0) && + (tmp1x < touch_x_max && tmp1y < touch_y_max)) + data->finger1_pressed = true; + + if (data->finger1_pressed) { + data->x1 = tmp1x; + data->y1 = tmp1y; + } else { + data->x1 = 0; + data->y1 = 0; + } + for (count = 0; count < 2; count++) { + retval = i2c_smbus_read_i2c_block_data(i2c, BU21013_X2_POS_MSB, 4, finger2_buf); + if (retval < 0) + goto end; + tmp2x = (finger2_buf[0] << 2) | finger2_buf[1]; + tmp2y = (finger2_buf[2] << 2) | finger2_buf[3]; + if ((tmp2x != 0) && (tmp2y != 0)) + break; + } + data->finger2_pressed = false; + if ((data->chip->edge_mode || data->last_press) && + (tmp2x != 0 && tmp2y != 0) && + (tmp2x < touch_x_max && tmp2y < touch_y_max)) + data->finger2_pressed = true; + data->finger2_count = 1; +end: + if (data->finger2_pressed) { + data->x2 = tmp2x; + data->y2 = tmp2y; + } else { + data->x2 = 0; + data->y2 = 0; + } + if (data->finger1_pressed || data->finger2_pressed) { + data->touch_en = true; + data->touchflag = true; + } else { + data->touch_en = false; + data->touchflag = false; + } + if ((!data->finger1_pressed) && (data->finger2_pressed)) { + data->x1 = tmp2x; + data->y1 = tmp2y; + data->x2 = 0; + data->y2 = 0; + data->finger2_pressed = false; + } +} +/** + * bu21013_update_ges_no_touch(): Generate the gesture message for single touch + * @data: a pointer to the device structure + * Generate the gesture message for single touch. + */ +void bu21013_update_ges_st_touch(struct bu21013_ts_data *data) +{ + struct bu21013_gesture_info *p_gesture_info = &data->gesture_info; + signed short x_delta, y_delta; + unsigned char dir_left_right, dir_up_down; + unsigned char tmp; + unsigned char dir1, dir2; + static struct bu21013_touch_point pre_gesture_point; + x_delta = p_gesture_info->pt[0].x - pre_gesture_point.x; + y_delta = p_gesture_info->pt[0].y - pre_gesture_point.y; + if ((x_delta < THRESHOLD_ROTATE) || + (-x_delta < THRESHOLD_ROTATE)) + x_delta = 0; + + if ((y_delta < THRESHOLD_ROTATE) || + (-y_delta < THRESHOLD_ROTATE)) + y_delta = 0; + + p_gesture_info->dir = DIR_INVALID; + if (x_delta != y_delta) { + dir_left_right = DIR_RIGHT; + if (x_delta < 0) { + dir_left_right = DIR_LEFT; + x_delta = -x_delta; + } + dir_up_down = DIR_DOWN; + if (y_delta < 0) { + y_delta = -y_delta; + dir_up_down = DIR_UP; + } + p_gesture_info->dir = dir_up_down; + if (x_delta > y_delta) + p_gesture_info->dir = dir_left_right; + tmp = p_gesture_info->dir; + data->dir_trace[tmp]++; + data->dir_trace[data->dir_idx] = tmp; + if (data->dir_idx < (THRESHOLD_ROTATE_HIST + DIRHEADER)) + data->dir_idx++; + else + data->dir_idx = DIRHEADER; + data->dir_trace[data->dir_trace[data->dir_idx]]--; + dir1 = data->dir_trace[0]; + for (dir2 = 1; dir2 < 5; dir2++) { + if (data->dir_trace[data->dir_trace[0]] + < data->dir_trace[dir2]) { + data->dir_trace[0] = dir2; + p_gesture_info->dir + = data->rotate_data[dir1][dir2]; + if (p_gesture_info->dir) + p_gesture_info->gesture_kind + = GES_ROTATE; + break; + } + } + } + if (p_gesture_info->dir == ROTATE_INVALID) { + p_gesture_info->gesture_kind = GES_MOVE; + p_gesture_info->speed = data->touch_count; + } +} +/** + * bu21013_update_ges_no_touch(): Generate the gesture message for multi touch + * @data: a pointer to the device structure + * Generate the gesture message for multi touch. + */ +void bu21013_update_ges_mt_touch(struct bu21013_ts_data *data) +{ + struct bu21013_gesture_info *p_gesture_info = &data->gesture_info; + signed short x_delta, y_delta; + signed short area_diff; + unsigned long area; + static unsigned long pre_area; + + x_delta = p_gesture_info->pt[0].x - p_gesture_info->pt[1].x; + y_delta = p_gesture_info->pt[0].y - p_gesture_info->pt[1].y; + if (x_delta < 0) + x_delta = -x_delta; + if (y_delta < 0) + y_delta = -y_delta; + x_delta++; + y_delta++; + area = x_delta * y_delta; + p_gesture_info->dir = PINCH_KEEP; + area_diff = area - pre_area; + if (area_diff > 0) { + if (area_diff > THRESHOLD_PINCH) { + p_gesture_info->dir = PINCH_OUT; + if (area_diff < THRESHOLD_PINCH_SPEED) + p_gesture_info->speed = LOWSPEED; + else + p_gesture_info->speed = HIGHSPEED; + } + } else if (area_diff < 0) { + if ((pre_area - area) > THRESHOLD_PINCH) { + p_gesture_info->dir = PINCH_IN; + if ((pre_area - area) < THRESHOLD_PINCH_SPEED) + p_gesture_info->speed = LOWSPEED; + else + p_gesture_info->speed = HIGHSPEED; + } + } + if (data->pinch_start == false) { + p_gesture_info->dir = PINCH_KEEP; + pre_area = 0; + data->pinch_start = true; + } + p_gesture_info->gesture_kind = GES_PINCH; + pre_area = area; +} +/** + * bu21013_update_ges_no_touch(): Generate the gesture message for no touch + * @data: a pointer to the device structure + * Generate the gesture message for no touch. + */ +void bu21013_update_ges_no_touch(struct bu21013_ts_data *data) +{ + struct bu21013_gesture_info *p_gesture_info = &data->gesture_info; + signed short x_delta, y_delta; + bool flick_series_flag; + signed short i; + unsigned char dir_left_right, dir_up_down; + static struct bu21013_touch_point pre_gesture_point; + + flick_series_flag = false; + if (data->flick_flag) { + x_delta = p_gesture_info->pt[0].x - data->tap_start_point.x; + y_delta = p_gesture_info->pt[0].y - data->tap_start_point.y; + dir_left_right = DIR_RIGHT; + dir_up_down = DIR_DOWN; + if (x_delta < 0) { + dir_left_right = DIR_LEFT; + x_delta = -x_delta; + } + if (y_delta < 0) { + y_delta = -y_delta; + dir_up_down = DIR_UP; + } + if ((THRESHOLD_FLICK <= x_delta) || (THRESHOLD_FLICK <= y_delta)) { + if (THRESHOLD_DRAGDROP <= data->touch_count) { + p_gesture_info->gesture_kind = GES_DRAGDROP; + p_gesture_info->speed = data->touch_count; + p_gesture_info->pt[0].x = data->tap_start_point.x; + p_gesture_info->pt[0].y = data->tap_start_point.y; + p_gesture_info->pt[1].x = pre_gesture_point.x; + p_gesture_info->pt[1].y = pre_gesture_point.y; + } else { + p_gesture_info->speed = LOWSPEED; + p_gesture_info->gesture_kind = GES_FLICK; + p_gesture_info->dir = dir_up_down; + if ((THRESHOLD_FLICK_SPEED < x_delta) || + (THRESHOLD_FLICK_SPEED < y_delta)) + p_gesture_info->speed = HIGHSPEED; + if (x_delta > y_delta) + p_gesture_info->dir = dir_left_right; + } + data->pre_tap_flag_level = 0; + data->flick_flag = false; + data->touch_count = STOP; + flick_series_flag = true; + } + } + if (flick_series_flag == false) { + if (THRESHOLD_TAPLIMIT <= data->touch_count) { + p_gesture_info->gesture_kind = GES_TAP; + p_gesture_info->times = data->pre_tap_flag_level; + p_gesture_info->speed = data->touch_count; + data->pre_tap_flag_level = 0; + data->flick_flag = false; + data->touch_count = STOP; + data->dir_idx = DIRHEADER; + for (i = 0; i < DIRHEADER; i++) + data->dir_trace[i] = 0x0; + for (i = DIRHEADER; i < DIRTRACEN; i++) + data->dir_trace[i] = DIR_TRACE_VALUE; + } + } +} +/** + * bu21013_get_touch_mesg(): Generate the gesture message + * @data: a pointer to the device structure + * Generate the gesture message according to the + * information collected and returns integer. + */ +int bu21013_get_touch_mesg(struct bu21013_ts_data *data) +{ + struct bu21013_gesture_info *p_gesture_info = &data->gesture_info; + static struct bu21013_touch_point pre_gesture_point; + p_gesture_info->gesture_kind = GES_UNKNOWN; + + bu21013_get_touch(data); + if (!data->touchflag) { + if (data->touch_continue) { + data->touch_continue = false; + p_gesture_info->gesture_kind = GES_TOUCHEND; + if (data->pre_tap_flag_level <= 2) + data->pre_tap_flag_level++; + data->pre_tap_flag = false; + } else { + if (!data->pinch_start) { + bu21013_update_ges_no_touch(data); + } else { + data->pinch_start = false; + data->pre_tap_flag_level = 0; + data->flick_flag = false; + data->touch_count = STOP; + } + } + } else { + p_gesture_info->pt[0].x = data->x1; + p_gesture_info->pt[0].y = data->y1; + p_gesture_info->pt[1].x = data->x2; + p_gesture_info->pt[1].y = data->y2; + bu21013_touch_calc(data); + + if (data->touch_continue) { + if ((data->x2 != 0) && (data->y2 != 0)) + bu21013_update_ges_mt_touch(data); + else + bu21013_update_ges_st_touch(data); + data->flick_flag = true; + } else { + p_gesture_info->gesture_kind = GES_TOUCHSTART; + data->tap_start_point.x = p_gesture_info->pt[0].x; + data->tap_start_point.y = p_gesture_info->pt[0].y; + data->touch_count = START; + } + data->touch_continue = true; + pre_gesture_point.x = p_gesture_info->pt[0].x; + pre_gesture_point.y = p_gesture_info->pt[0].y; + } + return 0; +} + +/** + * bu21013_timer_start() - restart the timer + * @data:bu21013_ts_data structure pointer + * + * This funtion used to run the timer and returns none. + */ +static inline void bu21013_timer_start(struct bu21013_ts_data *data) +{ + mod_timer(&data->penirq_timer, jiffies + msecs_to_jiffies(data->penup_timer)); +} + +/** + * tsc_clear_irq() - clear the tsc interrupt + * @i2c:i2c_client structure pointer + * + * This funtion used to clear the + * bu21013 controller interrupt for next Pen interrupts and returns none. + */ +static void tsc_clear_irq(struct i2c_client *i2c) +{ + int retval; + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_CLR, + BU21013_INTR_CLEAR); + if (retval < 0) + dev_err(&i2c->dev, "int cleared failed \n"); +} + +/** + * tsc_en_irq() - Reactivate the tsc interrupt + * @i2c:i2c_client structure pointer + * + * This funtion used to activate the bu21013 controller + * interrupt for next Pen interrupts. + */ +static void tsc_en_irq(struct i2c_client *i2c) +{ + int retval; + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_CLR, + BU21013_INTR_ENABLE); + if (retval < 0) + dev_err(&i2c->dev, "interrupt enable failed\n"); +} +/** + * tsc_input_report_pen_down() - report the touch point + * @data:bu21013_ts_data structure pointer + * This funtion calls, when we need to report the Pen down interrupt + */ +static void tsc_input_report_pen_down(struct bu21013_ts_data *data) +{ + short pt0x; + short pt0y; + short pt1x; + short pt1y; + if (data->chip->portrait) { + pt0x = data->gesture_info.pt[0].x; + pt0y = data->gesture_info.pt[0].y; + pt1x = data->gesture_info.pt[1].x; + pt1y = data->gesture_info.pt[1].y; + } else { + pt0x = data->gesture_info.pt[0].y; + pt0y = data->gesture_info.pt[0].x; + pt1x = data->gesture_info.pt[1].y; + pt1y = data->gesture_info.pt[1].x; + } + + input_report_abs(data->pin_dev, ABS_X, pt0x); + input_report_abs(data->pin_dev, ABS_Y, pt0y); + + input_report_abs(data->pin_dev, ABS_PRESSURE, 1); + input_report_abs(data->pin_dev, ABS_TOOL_WIDTH, 1); + input_report_key(data->pin_dev, BTN_TOUCH, 1); + + if (data->finger2_pressed) { + input_report_key(data->pin_dev, BTN_2, 1); + input_report_abs(data->pin_dev, ABS_HAT0X, pt1x); + input_report_abs(data->pin_dev, ABS_HAT0Y, pt1y); + } + input_report_abs(data->pin_dev, ABS_MT_TOUCH_MAJOR, 1); + input_report_key(data->pin_dev, ABS_MT_WIDTH_MAJOR, 1); + input_report_abs(data->pin_dev, ABS_MT_POSITION_X, pt0x); + input_report_abs(data->pin_dev, ABS_MT_POSITION_Y, pt0y); + input_mt_sync(data->pin_dev); + if (data->finger2_pressed) { + input_report_abs(data->pin_dev, ABS_MT_TOUCH_MAJOR, 1); + input_report_key(data->pin_dev, ABS_MT_WIDTH_MAJOR, 1); + input_report_abs(data->pin_dev, ABS_MT_POSITION_X, pt1x); + input_report_abs(data->pin_dev, ABS_MT_POSITION_Y, pt1y); + input_mt_sync(data->pin_dev); + } + input_sync(data->pin_dev); +} +/** + * tsc_input_report_pen_up() - report the touch point + * @data:bu21013_ts_data structure pointer + * This funtion calls, when we need to report the Pen up interrupt + */ +static void tsc_input_report_pen_up(struct bu21013_ts_data *data) +{ + input_report_abs(data->pin_dev, ABS_PRESSURE, 0); + input_report_abs(data->pin_dev, ABS_TOOL_WIDTH, 0); + input_report_key(data->pin_dev, BTN_TOUCH, 0); + if (data->finger2_count) { + input_report_key(data->pin_dev, BTN_2, 0); + input_report_abs(data->pin_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_key(data->pin_dev, ABS_MT_WIDTH_MAJOR, 0); + input_mt_sync(data->pin_dev); + data->finger2_count = 0; + } + input_sync(data->pin_dev); +} +/** + * tsc_input_report() - report the touch point + * @data:bu21013_ts_data structure pointer + * @value:value to be passed for en down or pen up + * This funtion calls, when we need to report the Pen down/up interrupt + */ +static void tsc_input_report(struct bu21013_ts_data *data, bool value) +{ + if (value) + tsc_input_report_pen_down(data); + else + tsc_input_report_pen_up(data); +} + +/** + * tsc_callback() - callback handler for Pen down + * @device_data:void pointer + * + * This funtion calls, when we get the Pen down interrupt from Egpio Pin + * and assigns the task and returns none. + */ + +static void tsc_callback(void *device_data) +{ + struct bu21013_ts_data *data = (struct bu21013_ts_data *)device_data; + schedule_work(&data->tp_gpio_handler); +} + +/** + * tsc_gpio_callback() - callback handler for Pen down + * @device_data:void pointer + * @irq: irq value + * + * This funtion calls for HREF v1, when we get the Pen down interrupt from GPIO Pin + * and assigns the task and returns irqreturn_t. + */ + +static irqreturn_t tsc_gpio_callback(int irq, void *device_data) +{ + struct bu21013_ts_data *data = (struct bu21013_ts_data *)device_data; + schedule_work(&data->tp_gpio_handler); + return IRQ_HANDLED; +} + +/** + * tsc_timer_callback() - callback handler for Timer + * @dev_data:touch screen data + * + * This callback handler used to schedule the work for Pen up + * and Pen down interrupts and returns none + */ +static void tsc_timer_callback(unsigned long dev_data) +{ + struct bu21013_ts_data *data = (struct bu21013_ts_data *)dev_data; + schedule_work(&data->tp_timer_handler); +} +/** + * tsc_level_mode_report() - report the touch point in level mode + * @data:bu21013_ts_data structure pointer + * This funtion used to report the Pen down/up interrupt for level mode. + */ +static void tsc_level_mode_report(struct bu21013_ts_data *data) +{ + if (data->touch_en) { + data->prev_press_report = true; + tsc_input_report(data, true); + } else { + if (data->prev_press_report) { + data->prev_press_report = false; + tsc_input_report(data, false); + } + } +} +/** + * tp_timer_wq() - Work Queue for timer handler + * @work:work_struct structure pointer + * + * This work queue used to get the co-ordinates + * of the Pen up and Pen down interrupts and returns none + */ +static void tp_timer_wq(struct work_struct *work) +{ + struct bu21013_ts_data *data = container_of(work, struct bu21013_ts_data, tp_timer_handler); + struct task_struct *tsk = current; + + set_task_state(tsk, TASK_INTERRUPTIBLE); + data->intr_pin = data->chip->pirq_read_val(); + bu21013_get_touch_mesg(data); + + if (data->chip->edge_mode) + tsc_input_report(data, data->touch_en); + else + tsc_level_mode_report(data); + + if (data->intr_pin == PEN_DOWN_INTR) { + bu21013_timer_start(data); + } else { + if (!data->chip->edge_mode) { + tsc_clear_irq(data->client); + tsc_en_irq(data->client); + } + enable_irq(data->chip->irq); + } +} + +/** + * tp_gpio_int_wq() - Work Queue for GPIO_INT handler + * @work:work_struct structure pointer + * + * This work queue used to get the co-ordinates + * of the Pen up and Pen down interrupts and returns none + */ +static void tp_gpio_int_wq(struct work_struct *work) +{ + struct bu21013_ts_data *tp_data = container_of(work, struct bu21013_ts_data, tp_gpio_handler); + struct task_struct *tsk = current; + + set_task_state(tsk, TASK_INTERRUPTIBLE); + tp_data->intr_pin = tp_data->chip->pirq_read_val(); + if (tp_data->intr_pin == PEN_DOWN_INTR) { + disable_irq(tp_data->chip->irq); + bu21013_get_touch_mesg(tp_data); + if (tp_data->chip->edge_mode) + tsc_input_report(tp_data, tp_data->touch_en); + else + tsc_level_mode_report(tp_data); + if (!tp_data->chip->edge_mode) { + tsc_clear_irq(tp_data->client); + tsc_en_irq(tp_data->client); + } + bu21013_timer_start(tp_data); + } else { + if (tp_data->chip->edge_mode) { + tsc_input_report(tp_data, false); + } else if (tp_data->prev_press_report) { + tp_data->prev_press_report = false; + tsc_input_report(tp_data, false); + } + } +} + +/* + * bu21013_init_config(): Initialize the Global variables + * @data: device structure pointer + * This function used to initialize the device variables and returns none + */ +void bu21013_init_config(struct bu21013_ts_data *data) +{ + signed int i; + + data->touch_count = STOP; + /* turning right */ + data->rotate_data[DIR_UP][DIR_RIGHT] = ROTATE_R_UR; + data->rotate_data[DIR_RIGHT][DIR_DOWN] = ROTATE_R_RD; + data->rotate_data[DIR_DOWN][DIR_LEFT] = ROTATE_R_DL; + data->rotate_data[DIR_LEFT][DIR_UP] = ROTATE_R_LU; + + /* turning left */ + data->rotate_data[DIR_LEFT][DIR_DOWN] = ROTATE_L_LD; + data->rotate_data[DIR_RIGHT][DIR_UP] = ROTATE_L_DR; + data->rotate_data[DIR_DOWN][DIR_RIGHT] = ROTATE_L_RU; + data->rotate_data[DIR_UP][DIR_LEFT] = ROTATE_L_UL; + + data->dir_idx = DIRHEADER; + + for (i = DIRHEADER; i < DIRTRACEN; i++) + data->dir_trace[i] = DIR_TRACE_VALUE; + + return; +} +/** + * bu21013_init_chip() - Power on sequence for the bu21013 controller + * @data: device structure pointer + * + * This funtion is used to power on + * the bu21013 controller and returns integer. + **/ +static int bu21013_init_chip(struct bu21013_ts_data *data) +{ + int retval; + struct i2c_client *i2c = data->client; + + dev_dbg(&i2c->dev, "bu21013_init_chip start\n"); + + retval = i2c_smbus_write_byte_data(i2c, BU21013_RESET, + BU21013_RESET_ENABLE); + if (retval < 0) { + dev_err(&i2c->dev, "ED reg write failed\n"); + goto err; + } + mdelay(RESET_DELAY); + + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_0_7, + BU21013_SENSORS_EN_0_7); + if (retval < 0) { + dev_err(&i2c->dev, "F0 reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_8_15, + BU21013_SENSORS_EN_8_15); + if (retval < 0) { + dev_err(&i2c->dev, "F1 reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_16_23, + BU21013_SENSORS_EN_16_23); + if (retval < 0) { + dev_err(&i2c->dev, "F2 reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE1, + (BU21013_POS_MODE1_0 | BU21013_POS_MODE1_1)); + if (retval < 0) { + dev_err(&i2c->dev, "F3 reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE2, + (BU21013_POS_MODE2_ZERO | BU21013_POS_MODE2_AVG1 | BU21013_POS_MODE2_AVG2 | + BU21013_POS_MODE2_EN_RAW | BU21013_POS_MODE2_MULTI)); + if (retval < 0) { + dev_err(&i2c->dev, "F4 reg write failed\n"); + goto err; + } + if (data->chip->ext_clk) { + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE, + (BU21013_CLK_MODE_EXT | BU21013_CLK_MODE_CALIB)); + if (retval < 0) { + dev_err(&i2c->dev, "F5 reg write failed\n"); + goto err; + } + } else { + retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE, + (BU21013_CLK_MODE_DIV | BU21013_CLK_MODE_CALIB)); + if (retval < 0) { + dev_err(&i2c->dev, "F5 reg write failed\n"); + goto err; + } + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_IDLE, + (BU21013_IDLET_0 | BU21013_IDLE_INTERMIT_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "FA reg write failed\n"); + goto err; + } + if (data->chip->edge_mode) { + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_MODE, + BU21013_INT_MODE_EDGE); + if (retval < 0) { + dev_err(&i2c->dev, "E9 reg write failed\n"); + goto err; + } + } else { + retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_MODE, + BU21013_INT_MODE_LEVEL); + if (retval < 0) { + dev_err(&i2c->dev, "E9 reg write failed\n"); + goto err; + } + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_FILTER, + (BU21013_DELTA_0_6 | BU21013_FILTER_EN)); + if (retval < 0) { + dev_err(&i2c->dev, "FB reg write failed\n"); + goto err; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_ON, data->th_on); + if (retval < 0) { + dev_err(&i2c->dev, "FC reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_OFF, data->th_off); + if (retval < 0) { + dev_err(&i2c->dev, "FD reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_GAIN, data->gain); + if (retval < 0) { + dev_err(&i2c->dev, "EA reg write failed\n"); + goto err; + } + + retval = i2c_smbus_write_byte_data(i2c, BU21013_OFFSET_MODE, + BU21013_OFFSET_MODE_DEFAULT); + if (retval < 0) { + dev_err(&i2c->dev, "EB reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_XY_EDGE, + (BU21013_X_EDGE_0 | BU21013_X_EDGE_2 | + BU21013_Y_EDGE_1 | BU21013_Y_EDGE_3)); + if (retval < 0) { + dev_err(&i2c->dev, "EC reg write failed\n"); + goto err; + } + retval = i2c_smbus_write_byte_data(i2c, BU21013_REG_DONE, BU21013_DONE); + if (retval < 0) { + dev_err(&i2c->dev, "EF reg write failed\n"); + goto err; + } +err: + return retval; +} +/* + * tsc_driver_register() - Used for input driver registeration + * @pdata: pointer to bu21013_tsc_data structure + * This function used to register the + * touch screen as input device and returns integer + */ +static int tsc_driver_register(struct bu21013_ts_data *pdata) +{ + int ret; + short x_max; + short y_max; + + struct input_dev *in_dev; + struct i2c_client *i2c = pdata->client; + dev_dbg(&i2c->dev, "input register start\n"); + + /* register the touch screen driver to input device */ + if (pdata->chip->portrait) { + x_max = pdata->chip->x_max_res; + y_max = pdata->chip->y_max_res; + } else { + x_max = pdata->chip->y_max_res; + y_max = pdata->chip->x_max_res; + } + in_dev = input_allocate_device(); + pdata->pin_dev = in_dev; + if (!in_dev) { + ret = -ENOMEM; + goto err; + } + + in_dev->name = DRIVER_TP; + set_bit(EV_SYN, in_dev->evbit); + set_bit(EV_KEY, in_dev->evbit); + set_bit(EV_ABS, in_dev->evbit); + set_bit(BTN_TOUCH, in_dev->keybit); + input_set_abs_params(in_dev, ABS_X, 0, x_max, 0, 0); + input_set_abs_params(in_dev, ABS_Y, 0, y_max, 0, 0); + input_set_abs_params(in_dev, ABS_PRESSURE, 0, MAX_PRESSURE, 0, 0); + input_set_abs_params(in_dev, ABS_TOOL_WIDTH, 0, MAX_TOOL_WIDTH, 0, 0); + set_bit(BTN_2, in_dev->keybit); + input_set_abs_params(in_dev, ABS_HAT0X, 0, x_max, 0, 0); + input_set_abs_params(in_dev, ABS_HAT0Y, 0, y_max, 0, 0); + input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, x_max, 0, 0); + input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, y_max, 0, 0); + input_set_abs_params(in_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_TOUCH_MAJOR, 0, 0); + input_set_abs_params(in_dev, ABS_MT_WIDTH_MAJOR, 0, MAX_TOOL_WIDTH, 0, 0); + ret = input_register_device(in_dev); + if (ret) { + dev_err(&i2c->dev, " input register error \n"); + goto err; + } + dev_dbg(&i2c->dev, "input register done \n"); +err: + return ret; +} +/** + * tsc_config() - configure the touch screen controller + * @pdev_data: pointer to bu21013_ts_data structure + * + * This funtion is used to configure + * the bu21013 controller and returns tsc error. + **/ +static int tsc_config(struct bu21013_ts_data *pdev_data) +{ + int retval; + + retval = bu21013_init_chip(pdev_data); + if (retval < 0) + goto err; + + bu21013_init_config(pdev_data); + if ((!pdev_data->board_flag) && + ((pdev_data->chip->pirq_en) && (pdev_data->chip->pirq_dis) && + (pdev_data->chip->irq_init))) { + retval = pdev_data->chip->pirq_dis(); + if (retval < 0) { + dev_err(&pdev_data->client->dev, "irq dis failed \n"); + goto err; + } + retval = pdev_data->chip->pirq_en(); + if (retval < 0) { + dev_err(&pdev_data->client->dev, "irq en failed \n"); + goto err_init_irq; + } + if (pdev_data->chip->irq_init(tsc_callback, pdev_data)) { + dev_err(&pdev_data->client->dev, "irq init failed \n"); + goto err_init_irq; + } + } + retval = bu21013_do_calibrate(pdev_data->client); + if (retval < 0) { + dev_err(&pdev_data->client->dev, "calibration not done \n"); + goto err_init_irq; + } + return retval; +err_init_irq: + pdev_data->chip->pirq_dis(); + pdev_data->chip->irq_exit(); +err: + pdev_data->chip->cs_dis(pdev_data->chip->cs_pin); + return retval; +} +#ifdef CONFIG_PM +/** + * bu21013_tp_suspend() - suspend the touch screen controller + * @client: pointer to i2c client structure + * @mesg: message from power manager + * + * This funtion is used to suspend the + * touch panel controller and returns integer + **/ +static int bu21013_tp_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int retval; + struct bu21013_ts_data *tsc_data = i2c_get_clientdata(client); + + if (!tsc_data->board_flag) { + retval = tsc_data->chip->pirq_dis(); + if (retval < 0) { + dev_err(&client->dev, "irq disable failed \n"); + goto err; + } + retval = tsc_data->chip->irq_exit(); + if (retval < 0) { + dev_err(&client->dev, "irq exit failed \n"); + goto err; + } + } else + disable_irq(tsc_data->chip->irq); + return 0; +err: + kfree(tsc_data); + return retval; +} + +/** + * bu21013_tp_resume() - resume the touch screen controller + * @client: pointer to i2c client structure + * + * This funtion is used to resume the touch panel + * controller and returns integer. + **/ +static int bu21013_tp_resume(struct i2c_client *client) +{ + int retval; + struct bu21013_ts_data *tsc_data = i2c_get_clientdata(client); + + retval = tsc_config(tsc_data); + if (retval < 0) { + dev_err(&client->dev, "tsc config failed \n"); + goto err; + } + if (tsc_data->board_flag) + enable_irq(tsc_data->chip->irq); + return 0; +err: + kfree(tsc_data); + return retval; +} +#endif +/** + * bu21013_tp_sysfs() - sys fs parameters + * @tp_kobj: kernel object structure pointer + * + * This funtion uses to initialize the sysfs for touch panel + **/ +int bu21013_tp_sysfs(struct kobject *tp_kobj) +{ + int retval; + tp_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); + if (tp_kobj == NULL) { + retval = -ENOMEM; + goto err; + } + tp_kobj->ktype = &ktype_touchp; + kobject_init(tp_kobj, tp_kobj->ktype); + retval = kobject_set_name(tp_kobj, "touchp1"); + if (retval) { + kfree(tp_kobj); + goto err; + } + retval = kobject_add(tp_kobj, NULL, "touchp1"); + if (retval) { + kfree(tp_kobj); + goto err; + } + return 0; +err: + return retval; +} +/** + * bu21013_tp_probe() - Initialze the i2c-client touchscreen driver + * @i2c: i2c client structure pointer + * @id:i2c device id pointer + * + * This funtion uses to Initializes the i2c-client touchscreen + * driver and returns integer. + **/ +static int bu21013_tp_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + int retval; + struct bu21013_ts_data *tsc_data; + struct bu21013_platform_device *pdata = i2c->dev.platform_data; + dev_dbg(&i2c->dev, "u8500_tsc_probe:: start\n"); + + tsc_data = kzalloc(sizeof(struct bu21013_ts_data), GFP_KERNEL); + if (!tsc_data) { + dev_err(&i2c->dev, "tp memory alloc failed \n"); + retval = -ENOMEM; + return retval; + } + if (!pdata) { + dev_err(&i2c->dev, "tp platform data not defined \n"); + retval = -EINVAL; + goto err_alloc; + } + tsc_data->chip = pdata; + tsc_data->client = i2c; + /* + * Hack for dynamic detection of HREF v1 board + */ + tsc_data->board_flag = tsc_data->chip->board_check(); + /* + * Hack for not supporting touch panel controller2 + * for the ED and MOP500 v1 boards, due to no support + * of shared irq callback registeration in tc35892 + * and stmpe2401 driver. + */ + if ((!tsc_data->chip->tp_cntl) || + ((tsc_data->chip->tp_cntl == 2) && + (!tsc_data->board_flag))) { + dev_err(&i2c->dev, "tp is not supported \n"); + retval = -EINVAL; + goto err_alloc; + } + /* + * Hardcoded for portrait mode + */ + tsc_data->chip->portrait = true; + INIT_WORK(&tsc_data->tp_timer_handler, tp_timer_wq); + INIT_WORK(&tsc_data->tp_gpio_handler, tp_gpio_int_wq); + + init_timer(&tsc_data->penirq_timer); + tsc_data->penirq_timer.data = (unsigned long)tsc_data; + tsc_data->penirq_timer.function = tsc_timer_callback; + i2c_set_clientdata(i2c, tsc_data); + /* configure the gpio pins */ + if (tsc_data->chip->cs_en) { + retval = tsc_data->chip->cs_en(tsc_data->chip->cs_pin); + if (retval != 0) { + dev_err(&i2c->dev, "error in chip initialization\n"); + goto err; + } + } + /* sys_fs value configuration */ + tsc_data->th_on = BU21013_TH_ON_5; + tsc_data->th_off = (BU21013_TH_OFF_3 | BU21013_TH_OFF_4); + tsc_data->gain = (BU21013_GAIN_0 | BU21013_GAIN_1); + tsc_data->penup_timer = PENUP_TIMEOUT; + + /* configure the touch panel controller */ + retval = tsc_config(tsc_data); + if (retval < 0) { + dev_err(&i2c->dev, "error in tp config\n"); + goto err; + } + if (tsc_data->board_flag) { + retval = request_irq(tsc_data->chip->irq, tsc_gpio_callback, + (IRQF_TRIGGER_FALLING | IRQF_SHARED), DRIVER_TP, tsc_data); + if (retval) { + dev_err(&i2c->dev, "request irq %d failed\n", tsc_data->chip->irq); + free_irq(tsc_data->chip->irq, tsc_data); + goto err; + } + } + retval = tsc_driver_register(tsc_data); + if (retval) + goto err_init; + if (tsc_data->chip->tp_cntl != 2) { + retval = bu21013_tp_sysfs(&tsc_data->touchp_kobj); + if (retval) + goto err_init; + else + tp_sys_data = tsc_data; + } + dev_dbg(&i2c->dev, "u8500_tsc_probe : done \n"); + return retval; +err_init: + input_free_device(tsc_data->pin_dev); +err: + del_timer_sync(&tsc_data->penirq_timer); +err_alloc: + kfree(tsc_data); + return retval; +} +/** + * bu21013_tp_remove() - Removes the i2c-client touchscreen driver + * @client: i2c client structure pointer + * + * This funtion uses to remove the i2c-client + * touchscreen driver and returns integer. + **/ +static int __exit bu21013_tp_remove(struct i2c_client *client) +{ + struct bu21013_ts_data *data = i2c_get_clientdata(client); + del_timer_sync(&data->penirq_timer); + + if (data->chip != NULL) { + if (!data->board_flag) { + data->chip->irq_exit(); + data->chip->pirq_dis(); + } else + free_irq(data->chip->irq, data); + data->chip->cs_dis(data->chip->cs_pin); + } + kfree(&data->touchp_kobj); + input_unregister_device(data->pin_dev); + input_free_device(data->pin_dev); + kfree(data); + i2c_set_clientdata(client, NULL); + return 0; +} + +static const struct i2c_device_id bu21013_tp_id[] = { + { DRIVER_TP, 0 }, + { DRIVER_TP, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bu21013_tp_id); + +static struct i2c_driver bu21013_tp_driver = { + .driver = { + .name = DRIVER_TP, + .owner = THIS_MODULE, + }, + .probe = bu21013_tp_probe, +#ifdef CONFIG_PM + .suspend = bu21013_tp_suspend, + .resume = bu21013_tp_resume, +#endif + .remove = __exit_p(bu21013_tp_remove), + .id_table = bu21013_tp_id, +}; + +/** + * bu21013_tp_init() - Initialize the paltform touchscreen driver + * + * This funtion uses to initialize the platform + * touchscreen driver and returns integer. + **/ +static int __init bu21013_tp_init(void){ + return i2c_add_driver(&bu21013_tp_driver); +} + +/** + * bu21013_tp_exit() - De-initialize the paltform touchscreen driver + * + * This funtion uses to de-initialize the platform + * touchscreen driver and returns none. + **/ +static void __exit bu21013_tp_exit(void){ + i2c_del_driver(&bu21013_tp_driver); +} + +module_init(bu21013_tp_init); +module_exit(bu21013_tp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("NAVEEN KUMAR G"); +MODULE_DESCRIPTION("Touch Screen driver for Bu21013 controller"); |