diff options
author | davidycchen <davidycchen@google.com> | 2021-11-11 10:30:52 +0800 |
---|---|---|
committer | davidycchen <davidycchen@google.com> | 2022-01-25 17:49:40 +0800 |
commit | c5bd23436e3def9a81a33d6fced267ae1333616d (patch) | |
tree | 30c8d96345a34380c74a518a4a364895ef068092 | |
parent | 32c71eb4c1ce0d97fbdf29fa999f8af1a02f42d5 (diff) | |
download | synaptics_touch-c5bd23436e3def9a81a33d6fced267ae1333616d.tar.gz |
synaptics: support touch offload
Add touch offload to upsteam the touch heatmap.
Bug: 199104316
Signed-off-by: davidycchen <davidycchen@google.com>
Change-Id: I96177718ec42d8c55867d35f27789e627cc46292
Signed-off-by: davidycchen <davidycchen@google.com>
-rw-r--r-- | syna_tcm2.c | 340 | ||||
-rw-r--r-- | syna_tcm2.h | 12 | ||||
-rw-r--r-- | syna_tcm2_platform.h | 3 | ||||
-rw-r--r-- | syna_tcm2_platform_spi.c | 19 |
4 files changed, 374 insertions, 0 deletions
diff --git a/syna_tcm2.c b/syna_tcm2.c index 048f024..b32a29f 100644 --- a/syna_tcm2.c +++ b/syna_tcm2.c @@ -452,6 +452,13 @@ static void syna_dev_free_input_events(struct syna_tcm *tcm) input_mt_slot(input_dev, idx); input_report_abs(input_dev, ABS_MT_PRESSURE, 0); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + tcm->offload.coords[idx].status = COORD_STATUS_INACTIVE; + tcm->offload.coords[idx].major = 0; + tcm->offload.coords[idx].minor = 0; + tcm->offload.coords[idx].pressure = 0; +#endif } #endif input_report_key(input_dev, BTN_TOUCH, 0); @@ -530,12 +537,19 @@ static void syna_dev_report_input_events(struct syna_tcm *tcm) switch (status) { case LIFT: +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + tcm->offload.coords[idx].status = COORD_STATUS_INACTIVE; + if (!tcm->offload.offload_running) { +#endif #ifdef TYPE_B_PROTOCOL input_mt_slot(input_dev, idx); input_report_abs(input_dev, ABS_MT_PRESSURE, 0); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + } +#endif break; case FINGER: case GLOVED_OBJECT: @@ -561,6 +575,15 @@ static void syna_dev_report_input_events(struct syna_tcm *tcm) #ifdef REPORT_FLIP_Y y = tcm->input_dev_params.max_y - y; #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + tcm->offload.coords[idx].status = COORD_STATUS_FINGER; + tcm->offload.coords[idx].x = x; + tcm->offload.coords[idx].y = y; + tcm->offload.coords[idx].pressure = z; + tcm->offload.coords[idx].major = wx; + tcm->offload.coords[idx].minor = wy; + if (!tcm->offload.offload_running) { +#endif #ifdef TYPE_B_PROTOCOL input_mt_slot(input_dev, idx); input_mt_report_slot_state(input_dev, @@ -580,6 +603,9 @@ static void syna_dev_report_input_events(struct syna_tcm *tcm) #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + } +#endif LOGD("Finger %d: x = %d, y = %d, z = %d\n", idx, x, y, z); touch_count++; break; @@ -590,6 +616,9 @@ static void syna_dev_report_input_events(struct syna_tcm *tcm) tcm->prev_obj_status[idx] = object_data[idx].status; } +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + if (!tcm->offload.offload_running) { +#endif if (touch_count == 0) { input_report_key(input_dev, BTN_TOUCH, 0); input_report_key(input_dev, BTN_TOOL_FINGER, 0); @@ -601,6 +630,10 @@ static void syna_dev_report_input_events(struct syna_tcm *tcm) input_set_timestamp(input_dev, tcm->timestamp); input_sync(input_dev); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + } +#endif + exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); @@ -800,6 +833,228 @@ exit: return retval; } +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) +static void syna_offload_set_running(struct syna_tcm *tcm, bool running) +{ + if (tcm->offload.offload_running != running) { + tcm->offload.offload_running = running; + } +} + +static void syna_offload_report(void *handle, + struct TouchOffloadIocReport *report) +{ + struct syna_tcm *tcm = (struct syna_tcm *)handle; + bool touch_down = 0; + int i; + + syna_pal_mutex_lock(&tcm->tp_event_mutex); + + input_set_timestamp(tcm->input_dev, report->timestamp); + + for (i = 0; i < MAX_COORDS; i++) { + if (report->coords[i].status == COORD_STATUS_FINGER) { + input_mt_slot(tcm->input_dev, i); + touch_down = 1; + input_report_key(tcm->input_dev, BTN_TOUCH, + touch_down); + input_report_key(tcm->input_dev, BTN_TOOL_FINGER, + touch_down); + input_mt_report_slot_state(tcm->input_dev, + MT_TOOL_FINGER, 1); + input_report_abs(tcm->input_dev, ABS_MT_POSITION_X, + report->coords[i].x); + input_report_abs(tcm->input_dev, ABS_MT_POSITION_Y, + report->coords[i].y); + input_report_abs(tcm->input_dev, ABS_MT_TOUCH_MAJOR, + report->coords[i].major); + input_report_abs(tcm->input_dev, ABS_MT_TOUCH_MINOR, + report->coords[i].minor); + input_report_abs(tcm->input_dev, ABS_MT_PRESSURE, + report->coords[i].pressure); + } else { + input_mt_slot(tcm->input_dev, i); + input_report_abs(tcm->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(tcm->input_dev, + MT_TOOL_FINGER, 0); + input_report_abs(tcm->input_dev, ABS_MT_TRACKING_ID, + -1); + } + } + input_report_key(tcm->input_dev, BTN_TOUCH, touch_down); + input_report_key(tcm->input_dev, BTN_TOOL_FINGER, touch_down); + + input_sync(tcm->input_dev); + + syna_pal_mutex_unlock(&tcm->tp_event_mutex); +} + +static int syna_dev_ptflib_decoder(struct syna_tcm *tcm, const u16 *in_array, + const int in_array_size, u16 *out_array, + const int out_array_max_size) +{ + const u16 ESCAPE_MASK = 0xF000; + const u16 ESCAPE_BIT = 0x8000; + + int i; + int j; + int out_array_size = 0; + u16 prev_word = 0; + u16 repetition = 0; + + for (i = 0; i < in_array_size; i++) { + u16 curr_word = in_array[i]; + if ((curr_word & ESCAPE_MASK) == ESCAPE_BIT) { + repetition = (curr_word & ~ESCAPE_MASK); + if (out_array_size + repetition > out_array_max_size) + break; + for (j = 0; j < repetition; j++) { + *out_array++ = prev_word; + out_array_size++; + } + } else { + if (out_array_size >= out_array_max_size) + break; + *out_array++ = curr_word; + out_array_size++; + prev_word = curr_word; + } + } + + if (i != in_array_size || out_array_size != out_array_max_size) { + LOGE("%d (in=%d, out=%d, rep=%d, out_max=%d).\n", + i, in_array_size, out_array_size, + repetition, out_array_max_size); + return -1; + } + + return out_array_size; +} + +static void syna_populate_coordinate_channel(struct syna_tcm *tcm, + struct touch_offload_frame *frame, + int channel) +{ + int j; + + struct TouchOffloadDataCoord *dc = + (struct TouchOffloadDataCoord *)frame->channel_data[channel]; + memset(dc, 0, frame->channel_data_size[channel]); + dc->header.channel_type = TOUCH_DATA_TYPE_COORD; + dc->header.channel_size = TOUCH_OFFLOAD_FRAME_SIZE_COORD; + + for (j = 0; j < MAX_COORDS; j++) { + dc->coords[j].x = tcm->offload.coords[j].x; + dc->coords[j].y = tcm->offload.coords[j].y; + dc->coords[j].major = tcm->offload.coords[j].major; + dc->coords[j].minor = tcm->offload.coords[j].minor; + dc->coords[j].pressure = tcm->offload.coords[j].pressure; + dc->coords[j].status = tcm->offload.coords[j].status; + } +} + +static void syna_populate_mutual_channel(struct syna_tcm *tcm, + struct touch_offload_frame *frame, + int channel) +{ + int i, j; + struct TouchOffloadData2d *mutual_strength = + (struct TouchOffloadData2d *)frame->channel_data[channel]; + + mutual_strength->tx_size = tcm->tcm_dev->rows; + mutual_strength->rx_size = tcm->tcm_dev->cols; + mutual_strength->header.channel_type = frame->channel_type[channel]; + mutual_strength->header.channel_size = + TOUCH_OFFLOAD_FRAME_SIZE_2D(mutual_strength->rx_size, + mutual_strength->tx_size); + + /* for 'heat map' ($c1) report, + * report data has been stored at tcm->event_data.buf; + * while, tcm->event_data.data_length is the size of data + */ + syna_dev_ptflib_decoder(tcm, + &((u16 *) tcm->event_data.buf)[tcm->tcm_dev->rows + tcm->tcm_dev->cols], + (tcm->event_data.data_length) / 2 - tcm->tcm_dev->rows - tcm->tcm_dev->cols, + tcm->heatmap_buff, + tcm->tcm_dev->rows * tcm->tcm_dev->cols); + + for (i = 0; i < tcm->tcm_dev->cols; i++) { + for (j = 0; j < tcm->tcm_dev->rows; j++) { + ((u16 *) mutual_strength->data)[tcm->tcm_dev->rows * i + j] = + tcm->heatmap_buff[tcm->tcm_dev->cols * j + i]; + } + } +} + +static void syna_populate_self_channel(struct syna_tcm *tcm, + struct touch_offload_frame *frame, + int channel) +{ + int i; + struct TouchOffloadData1d *self_strength = + (struct TouchOffloadData1d *)frame->channel_data[channel]; + + self_strength->tx_size = tcm->tcm_dev->rows; + self_strength->rx_size = tcm->tcm_dev->cols; + self_strength->header.channel_type = frame->channel_type[channel]; + self_strength->header.channel_size = + TOUCH_OFFLOAD_FRAME_SIZE_1D(self_strength->rx_size, + self_strength->tx_size); + + for (i = 0; i < tcm->tcm_dev->rows; i++) { + ((u16 *) self_strength->data)[i] = + ((u16 *) tcm->event_data.buf)[tcm->tcm_dev->cols + i]; + } + + for (i = 0; i < tcm->tcm_dev->cols; i++) { + ((u16 *) self_strength->data)[tcm->tcm_dev->rows + i] = + ((u16 *) tcm->event_data.buf)[i]; + } +} + +static void syna_populate_frame(struct syna_tcm *tcm, bool has_heatmap) +{ + static u64 index; + int i; + struct touch_offload_frame *frame = tcm->reserved_frame; + + frame->header.index = index++; + + /* Populate all channels */ + for (i = 0; i < frame->num_channels; i++) { + if (frame->channel_type[i] == TOUCH_DATA_TYPE_COORD) { + syna_populate_coordinate_channel(tcm, frame, i); + } else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_MUTUAL) != 0) { + if (has_heatmap) + syna_populate_mutual_channel(tcm, frame, i); + } else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_SELF) != 0) { + if (has_heatmap) + syna_populate_self_channel(tcm, frame, i); + } + } +} + +static enum hrtimer_restart syna_heatmap_timer(struct hrtimer *timer) +{ + int retval = 0; + struct syna_tcm *tcm = + container_of(timer, struct syna_tcm, heatmap_timer); + + LOGD("Miss a frame of touch heatmap."); + + syna_populate_frame(tcm, false); + + retval = touch_offload_queue_frame(&tcm->offload, + tcm->reserved_frame); + if (retval != 0) { + LOGE("Failed to queue reserved frame: error=%d.\n", + retval); + } + + return HRTIMER_NORESTART; +} +#endif /* CONFIG_TOUCHSCREEN_OFFLOAD */ + static irqreturn_t syna_dev_isr(int irq, void *handle) { struct syna_tcm *tcm = handle; @@ -880,6 +1135,23 @@ static irqreturn_t syna_dev_interrupt_thread(int irq, void *data) LOGE("Fail to parse touch report\n"); goto exit; } + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + retval = touch_offload_reserve_frame(&tcm->offload, &tcm->reserved_frame); + if (retval != 0) { + LOGD("Could not reserve a frame: error=%d.\n", retval); + + /* Stop offload when there are no buffers available. */ + syna_offload_set_running(tcm, false); + } else { + tcm->reserved_frame->header.timestamp = tcm->timestamp; + syna_offload_set_running(tcm, true); + hrtimer_start(&tcm->heatmap_timer, + ktime_set(0, 2000000), /* 2ms. */ + HRTIMER_MODE_REL); + } +#endif + /* forward the touch event to system */ syna_dev_report_input_events(tcm); } else if (code == tcm->raw_data_report_code) { @@ -912,6 +1184,24 @@ static irqreturn_t syna_dev_interrupt_thread(int irq, void *data) */ LOGD("Heat map data received, size:%d\n", tcm->event_data.data_length); + + if (tcm->offload.offload_running) { + /* 0 when the timer was not active. + * 1 when the timer was active. + * -1 when the timer is currently excuting the callback + * function and cannot be stopped. */ + if (hrtimer_try_to_cancel(&tcm->heatmap_timer) != 1) + break; + + syna_populate_frame(tcm, true); + + retval = touch_offload_queue_frame(&tcm->offload, + tcm->reserved_frame); + if (retval != 0) { + LOGE("Failed to queue reserved frame: error=%d.\n", + retval); + } + } break; case REPORT_FW_STATUS: /* for 'fw status' ($c2) report, @@ -2104,6 +2394,48 @@ static int syna_dev_probe(struct platform_device *pdev) init_completion(&tcm->raw_data_completion); complete_all(&tcm->raw_data_completion); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + hrtimer_init(&tcm->heatmap_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + tcm->heatmap_timer.function = syna_heatmap_timer; + + tcm->offload.caps.touch_offload_major_version = TOUCH_OFFLOAD_INTERFACE_MAJOR_VERSION; + tcm->offload.caps.touch_offload_minor_version = TOUCH_OFFLOAD_INTERFACE_MINOR_VERSION; + tcm->offload.caps.device_id = tcm->hw_if->offload_id; + tcm->offload.caps.display_width = tcm->tcm_dev->max_x; + tcm->offload.caps.display_height = tcm->tcm_dev->max_y; + tcm->offload.caps.tx_size = tcm->tcm_dev->rows; + tcm->offload.caps.rx_size = tcm->tcm_dev->cols; + + tcm->offload.caps.bus_type = BUS_TYPE_SPI; + tcm->offload.caps.bus_speed_hz = 10000000; + tcm->offload.caps.heatmap_size = HEATMAP_SIZE_FULL; + tcm->offload.caps.touch_data_types = TOUCH_DATA_TYPE_COORD | + TOUCH_DATA_TYPE_STRENGTH; + tcm->offload.caps.touch_scan_types = TOUCH_SCAN_TYPE_MUTUAL | + TOUCH_SCAN_TYPE_SELF; + tcm->offload.caps.continuous_reporting = true; + tcm->offload.caps.noise_reporting = false; + tcm->offload.caps.cancel_reporting = false; + tcm->offload.caps.size_reporting = true; + tcm->offload.caps.filter_grip = true; + tcm->offload.caps.filter_palm = true; + tcm->offload.caps.num_sensitivity_settings = 1; + + tcm->offload.hcallback = (void *)tcm; + tcm->offload.report_cb = syna_offload_report; + touch_offload_init(&tcm->offload); + + if (!tcm->heatmap_buff) { + tcm->heatmap_buff = kmalloc( + sizeof(u16) * tcm->tcm_dev->rows * tcm->tcm_dev->cols, + GFP_KERNEL); + if (!tcm->heatmap_buff) { + LOGE("allocate heatmap_buff failed\n"); + goto err_connect; + } + } +#endif + #ifdef HAS_SYSFS_INTERFACE /* create the device file and register to char device classes */ retval = syna_cdev_create_sysfs(tcm, pdev); @@ -2161,6 +2493,9 @@ static int syna_dev_probe(struct platform_device *pdev) #ifdef HAS_SYSFS_INTERFACE err_create_cdev: +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + touch_offload_cleanup(&tcm->offload); +#endif syna_tcm_remove_device(tcm->tcm_dev); #endif #if defined(TCM_CONNECT_IN_PROBE) @@ -2232,6 +2567,11 @@ static int syna_dev_remove(struct platform_device *pdev) syna_cdev_remove_sysfs(tcm); #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + touch_offload_cleanup(&tcm->offload); + hrtimer_cancel(&tcm->heatmap_timer); +#endif + /* check the connection statusm, and do disconnection */ if (tcm->dev_disconnect(tcm) < 0) LOGE("Fail to do device disconnection\n"); diff --git a/syna_tcm2.h b/syna_tcm2.h index 2addad4..98c8d49 100644 --- a/syna_tcm2.h +++ b/syna_tcm2.h @@ -48,6 +48,11 @@ #include <touch_bus_negotiator.h> #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) +#include <touch_offload.h> +#include <linux/hrtimer.h> +#endif + #define PLATFORM_DRIVER_NAME "synaptics_tcm" #define TOUCH_INPUT_NAME "synaptics_tcm_touch" @@ -411,6 +416,13 @@ struct syna_tcm { * touch IC, acquired during hard interrupt, in * CLOCK_MONOTONIC */ +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + struct touch_offload_context offload; + u16 *heatmap_buff; + struct hrtimer heatmap_timer; + struct touch_offload_frame *reserved_frame; +#endif + /* IOCTL-related variables */ pid_t proc_pid; struct task_struct *proc_task; diff --git a/syna_tcm2_platform.h b/syna_tcm2_platform.h index d6d5a68..0e73b50 100644 --- a/syna_tcm2_platform.h +++ b/syna_tcm2_platform.h @@ -146,6 +146,9 @@ struct syna_hw_interface { struct syna_hw_rst_data bdata_rst; struct syna_hw_pwr_data bdata_pwr; const char *fw_name; +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + u32 offload_id; +#endif /* Operation to do power on/off, if supported * diff --git a/syna_tcm2_platform_spi.c b/syna_tcm2_platform_spi.c index 547b798..ec3c921 100644 --- a/syna_tcm2_platform_spi.c +++ b/syna_tcm2_platform_spi.c @@ -456,6 +456,10 @@ static int syna_spi_parse_dt(struct syna_hw_interface *hw_if, struct of_phandle_args panelmap; struct drm_panel *panel = NULL; +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + u8 offload_id[4]; +#endif + if (of_property_read_bool(np, "synaptics,panel_map")) { for (index = 0 ;; index++) { retval = of_parse_phandle_with_fixed_args(np, @@ -653,6 +657,21 @@ static int syna_spi_parse_dt(struct syna_hw_interface *hw_if, bus->spi_mode = 0; } +#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) + hw_if->offload_id = 0; + retval = of_property_read_u8_array(np, "synaptics,touch_offload_id", + offload_id, 4); + if (retval == -EINVAL) + dev_err(dev, + "Failed to read synaptics,touch_offload_id with error = %d\n", + retval); + else { + hw_if->offload_id = *(u32 *)offload_id; + dev_info(dev, "Offload device ID = \"%c%c%c%c\" / 0x%08X\n", + offload_id[0], offload_id[1], offload_id[2], offload_id[3], + hw_if->offload_id); + } +#endif return 0; } |