| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Google Touch Interface for Pixel devices. |
| * |
| * Copyright 2022 Google LLC. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/input/mt.h> |
| #include <linux/of.h> |
| #include <samsung/exynos_drm_connector.h> |
| |
| #include "goog_touch_interface.h" |
| #include "touch_bus_negotiator.h" |
| #include "../../../gs-google/drivers/soc/google/vh/kernel/systrace.h" |
| |
| static struct class *gti_class; |
| static u8 gti_dev_num; |
| |
| static ssize_t offload_enable_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size); |
| static ssize_t offload_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf); |
| static ssize_t v4l2_enable_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size); |
| static ssize_t v4l2_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf); |
| |
| static DEVICE_ATTR_RW(offload_enable); |
| static DEVICE_ATTR_RW(v4l2_enable); |
| |
| static struct attribute *goog_attributes[] = { |
| &dev_attr_offload_enable.attr, |
| &dev_attr_v4l2_enable.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group goog_attr_group = { |
| .attrs = goog_attributes, |
| }; |
| |
| static ssize_t offload_enable_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct goog_touch_interface *gti = dev_get_drvdata(dev); |
| |
| if (kstrtobool(buf, >i->offload_enable)) |
| GOOG_ERR("invalid input!\n"); |
| else |
| GOOG_LOG("offload_enable= %d.\n", gti->offload_enable); |
| return size; |
| } |
| |
| static ssize_t offload_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| size_t size = 0; |
| struct goog_touch_interface *gti = dev_get_drvdata(dev); |
| |
| size += scnprintf(buf, PAGE_SIZE, "offload_enable= %d.\n", |
| gti->offload_enable); |
| GOOG_LOG("%s", buf); |
| return size; |
| } |
| |
| static ssize_t v4l2_enable_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct goog_touch_interface *gti = dev_get_drvdata(dev); |
| |
| if (kstrtobool(buf, >i->v4l2_enable)) |
| GOOG_ERR("invalid input!\n"); |
| else |
| GOOG_LOG("v4l2_enable= %d.\n", gti->v4l2_enable); |
| return size; |
| } |
| |
| static ssize_t v4l2_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| size_t size = 0; |
| struct goog_touch_interface *gti = dev_get_drvdata(dev); |
| |
| size += scnprintf(buf, PAGE_SIZE, "v4l2_enable= %d.\n", |
| gti->v4l2_enable); |
| GOOG_LOG("%s", buf); |
| return size; |
| } |
| |
| static void panel_bridge_enable(struct drm_bridge *bridge) |
| { |
| int ret = 0; |
| struct goog_touch_interface *gti = |
| container_of(bridge, struct goog_touch_interface, panel_bridge); |
| |
| if (gti->panel_is_lp_mode) { |
| GOOG_LOG("skip screen-on because of panel_is_lp_mode enabled!\n"); |
| return; |
| } |
| |
| GOOG_LOG("screen-on.\n"); |
| if (gti->pm_state == GTI_RESUME) { |
| GOOG_WARN("GTI already resumed!\n"); |
| return; |
| } |
| if (gti->vendor_dev_pm_state == GTI_VENDOR_DEV_RESUME) { |
| GOOG_WARN("unexpected vendor_dev_pm_state(%d)!\n", |
| gti->vendor_dev_pm_state); |
| } |
| if (gti->tbn_register_mask) { |
| ret = tbn_request_bus(gti->tbn_register_mask); |
| if (ret) |
| GOOG_ERR("tbn_request_bus failed, ret %d!\n", ret); |
| } |
| gti->cmd.display_state_cmd.setting = GTI_DISPLAY_STATE_ON; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_NOTIFY_DISPLAY_STATE); |
| if (ret) |
| GOOG_WARN("unexpected vendor_cmd return(%d)!\n", ret); |
| gti->pm_state = GTI_RESUME; |
| } |
| |
| static void panel_bridge_disable(struct drm_bridge *bridge) |
| { |
| int ret = 0; |
| struct goog_touch_interface *gti = |
| container_of(bridge, struct goog_touch_interface, panel_bridge); |
| |
| if (bridge->encoder && bridge->encoder->crtc) { |
| const struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; |
| |
| if (drm_atomic_crtc_effectively_active(crtc_state)) |
| return; |
| } |
| |
| GOOG_LOG("screen-off.\n"); |
| if (gti->pm_state == GTI_SUSPEND) { |
| GOOG_WARN("GTI already suspended!\n"); |
| return; |
| } |
| if (gti->vendor_dev_pm_state == GTI_VENDOR_DEV_SUSPEND) |
| GOOG_WARN("unexpected vendor_dev_pm_state(%d)!\n", gti->vendor_dev_pm_state); |
| gti->cmd.display_state_cmd.setting = GTI_DISPLAY_STATE_OFF; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_NOTIFY_DISPLAY_STATE); |
| if (ret) |
| GOOG_WARN("unexpected vendor_cmd return(%d)!\n", ret); |
| gti->pm_state = GTI_SUSPEND; |
| } |
| |
| struct drm_connector *get_bridge_connector(struct drm_bridge *bridge) |
| { |
| struct drm_connector *connector; |
| struct drm_connector_list_iter conn_iter; |
| |
| drm_connector_list_iter_begin(bridge->dev, &conn_iter); |
| drm_for_each_connector_iter(connector, &conn_iter) { |
| if (connector->encoder == bridge->encoder) |
| break; |
| } |
| drm_connector_list_iter_end(&conn_iter); |
| return connector; |
| } |
| |
| static bool panel_bridge_is_lp_mode(struct drm_connector *connector) |
| { |
| if (connector && connector->state) { |
| struct exynos_drm_connector_state *s = |
| to_exynos_connector_state(connector->state); |
| |
| return s->exynos_mode.is_lp_mode; |
| } |
| return false; |
| } |
| |
| static void panel_bridge_mode_set(struct drm_bridge *bridge, |
| const struct drm_display_mode *mode, |
| const struct drm_display_mode *adjusted_mode) |
| { |
| int ret = 0; |
| bool panel_is_lp_mode; |
| struct goog_touch_interface *gti = |
| container_of(bridge, struct goog_touch_interface, panel_bridge); |
| |
| if (!gti->connector || !gti->connector->state) |
| gti->connector = get_bridge_connector(bridge); |
| |
| panel_is_lp_mode = panel_bridge_is_lp_mode(gti->connector); |
| if (gti->panel_is_lp_mode != panel_is_lp_mode) { |
| |
| GOOG_LOG("panel_is_lp_mode changed from %d to %d.\n", |
| gti->panel_is_lp_mode, panel_is_lp_mode); |
| if (panel_is_lp_mode) |
| gti->cmd.display_state_cmd.setting = GTI_DISPLAY_STATE_OFF; |
| else |
| gti->cmd.display_state_cmd.setting = GTI_DISPLAY_STATE_ON; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_NOTIFY_DISPLAY_STATE); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| } |
| gti->panel_is_lp_mode = panel_is_lp_mode; |
| |
| if (mode) { |
| int vrefresh = drm_mode_vrefresh(mode); |
| |
| if (gti->display_vrefresh != vrefresh) { |
| GOOG_DBG("display_vrefresh(Hz) changed to %d from %d.\n", |
| vrefresh, gti->display_vrefresh); |
| gti->display_vrefresh = vrefresh; |
| gti->cmd.display_vrefresh_cmd.setting = vrefresh; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_NOTIFY_DISPLAY_VREFRESH); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| } |
| } |
| } |
| |
| static const struct drm_bridge_funcs panel_bridge_funcs = { |
| .enable = panel_bridge_enable, |
| .disable = panel_bridge_disable, |
| .mode_set = panel_bridge_mode_set, |
| }; |
| |
| static int register_panel_bridge(struct goog_touch_interface *gti) |
| { |
| GOOG_LOG("\n"); |
| #ifdef CONFIG_OF |
| gti->panel_bridge.of_node = gti->vendor_dev->of_node; |
| #endif |
| gti->panel_bridge.funcs = &panel_bridge_funcs; |
| drm_bridge_add(>i->panel_bridge); |
| |
| return 0; |
| } |
| |
| static void unregister_panel_bridge(struct drm_bridge *bridge) |
| { |
| struct drm_bridge *node; |
| |
| GOOG_LOG("\n"); |
| drm_bridge_remove(bridge); |
| |
| if (!bridge->dev) /* not attached */ |
| return; |
| |
| drm_modeset_lock(&bridge->dev->mode_config.connection_mutex, NULL); |
| list_for_each_entry(node, &bridge->encoder->bridge_chain, chain_node) { |
| if (node == bridge) { |
| if (bridge->funcs->detach) |
| bridge->funcs->detach(bridge); |
| list_del(&bridge->chain_node); |
| break; |
| } |
| } |
| drm_modeset_unlock(&bridge->dev->mode_config.connection_mutex); |
| bridge->dev = NULL; |
| } |
| |
| int goog_process_vendor_cmd(struct goog_touch_interface *gti, enum gti_cmd_type cmd_type) |
| { |
| void *private_data = gti->vendor_private_data; |
| int ret = -ESRCH; |
| |
| /* Use optional vendor operation if available. */ |
| switch (cmd_type) { |
| case GTI_CMD_GET_SENSOR_DATA: |
| if (gti->cmd.sensor_data_cmd.type & TOUCH_SCAN_TYPE_MUTUAL) { |
| ret = gti->options.get_mutual_sensor_data( |
| private_data, >i->cmd.sensor_data_cmd); |
| } else if (gti->cmd.sensor_data_cmd.type & TOUCH_SCAN_TYPE_SELF) { |
| ret = gti->options.get_self_sensor_data( |
| private_data, >i->cmd.sensor_data_cmd); |
| } |
| break; |
| case GTI_CMD_SET_GRIP: |
| ret = gti->options.set_grip(private_data, >i->cmd.grip_cmd); |
| break; |
| case GTI_CMD_SET_PALM: |
| ret = gti->options.set_palm(private_data, >i->cmd.palm_cmd); |
| break; |
| case GTI_CMD_SET_CONTINUOUS_REPORT: |
| ret = gti->options.set_continuous_report(private_data, |
| >i->cmd.continuous_report_cmd); |
| break; |
| case GTI_CMD_NOTIFY_DISPLAY_STATE: |
| ret = gti->options.notify_display_state(private_data, |
| >i->cmd.display_state_cmd); |
| break; |
| case GTI_CMD_NOTIFY_DISPLAY_VREFRESH: |
| ret = gti->options.notify_display_vrefresh(private_data, |
| >i->cmd.display_vrefresh_cmd); |
| break; |
| default: |
| break; |
| } |
| |
| /* Back to vendor default handler if no optional operation available. */ |
| if (ret == -ESRCH) |
| ret = gti->vendor_default_handler(private_data, cmd_type, >i->cmd); |
| |
| return ret; |
| } |
| |
| void goog_update_motion_filter(struct goog_touch_interface *gti, unsigned long slot_bit) |
| { |
| int ret = 0; |
| const u32 mf_timeout_ms = 500; |
| unsigned long touches = hweight_long(slot_bit); |
| u32 next_state = gti->mf_state; |
| |
| switch (gti->mf_mode) { |
| case GTI_MF_MODE_AUTO_REPORT: |
| case GTI_MF_MODE_UNFILTER: |
| next_state = GTI_MF_STATE_UNFILTERED; |
| break; |
| case GTI_MF_MODE_FILTER: |
| next_state = GTI_MF_STATE_FILTERED; |
| break; |
| case GTI_MF_MODE_DYNAMIC: |
| default: |
| /* |
| * Determine the next filter state. The motion filter is enabled by |
| * default and it is disabled while a single finger is touching the |
| * screen. If another finger is touched down or if a timeout expires, |
| * the motion filter is reenabled and remains enabled until all fingers |
| * are lifted. |
| */ |
| switch (next_state) { |
| case GTI_MF_STATE_FILTERED: |
| if (touches == 1) { |
| next_state = GTI_MF_STATE_UNFILTERED; |
| gti->mf_downtime = ktime_get(); |
| } |
| break; |
| case GTI_MF_STATE_UNFILTERED: |
| if (touches == 0) { |
| next_state = GTI_MF_STATE_FILTERED; |
| } else if (touches > 1 || |
| ktime_after(ktime_get(), |
| ktime_add_ms(gti->mf_downtime, mf_timeout_ms))) { |
| next_state = GTI_MF_STATE_FILTERED_LOCKED; |
| } |
| break; |
| case GTI_MF_STATE_FILTERED_LOCKED: |
| if (touches == 0) |
| next_state = GTI_MF_STATE_FILTERED; |
| break; |
| } |
| break; |
| } |
| |
| /* Send command to setup continuous report. */ |
| if ((next_state == GTI_MF_STATE_UNFILTERED) != |
| (gti->mf_state == GTI_MF_STATE_UNFILTERED)) { |
| gti->cmd.continuous_report_cmd.setting = GTI_CONTINUOUS_REPORT_DISABLE; |
| |
| if (next_state == GTI_MF_STATE_UNFILTERED) |
| gti->cmd.continuous_report_cmd.setting = GTI_CONTINUOUS_REPORT_ENABLE; |
| |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_CONTINUOUS_REPORT); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| } |
| |
| gti->mf_state = next_state; |
| } |
| |
| bool goog_v4l2_read_frame_cb(struct v4l2_heatmap *v4l2) |
| { |
| struct goog_touch_interface *gti = container_of(v4l2, struct goog_touch_interface, v4l2); |
| bool ret = false; |
| u32 v4l2_size = gti->v4l2.width * gti->v4l2.height * 2; |
| |
| if (gti->heatmap_buf && v4l2_size == gti->heatmap_buf_size) { |
| memcpy(v4l2->frame, gti->heatmap_buf, v4l2_size); |
| ret = true; |
| } else { |
| GOOG_ERR("wrong pointer(%p) or size (W: %lu, H: %lu) vs %u\n", |
| gti->heatmap_buf, gti->v4l2.width, gti->v4l2.height, gti->heatmap_buf_size); |
| } |
| |
| return ret; |
| } |
| |
| void goog_v4l2_read(struct goog_touch_interface *gti, ktime_t timestamp) |
| { |
| if (gti->v4l2_enable) |
| heatmap_read(>i->v4l2, ktime_to_ns(timestamp)); |
| } |
| |
| void goog_offload_populate_coordinate_channel(struct goog_touch_interface *gti, |
| struct touch_offload_frame *frame, int channel) |
| { |
| int i; |
| |
| 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 (i = 0; i < MAX_COORDS; i++) { |
| dc->coords[i].x = gti->offload.coords[i].x; |
| dc->coords[i].y = gti->offload.coords[i].y; |
| dc->coords[i].major = gti->offload.coords[i].major; |
| dc->coords[i].minor = gti->offload.coords[i].minor; |
| dc->coords[i].pressure = gti->offload.coords[i].pressure; |
| dc->coords[i].status = gti->offload.coords[i].status; |
| } |
| } |
| |
| void goog_offload_populate_mutual_channel(struct goog_touch_interface *gti, |
| struct touch_offload_frame *frame, int channel, u8 *buffer, u32 size) |
| { |
| struct TouchOffloadData2d *mutual = |
| (struct TouchOffloadData2d *)frame->channel_data[channel]; |
| |
| mutual->tx_size = gti->offload.caps.tx_size; |
| mutual->rx_size = gti->offload.caps.rx_size; |
| mutual->header.channel_type = frame->channel_type[channel]; |
| mutual->header.channel_size = |
| TOUCH_OFFLOAD_FRAME_SIZE_2D(mutual->rx_size, mutual->tx_size); |
| |
| memcpy(mutual->data, buffer, size); |
| } |
| |
| void goog_offload_populate_self_channel(struct goog_touch_interface *gti, |
| struct touch_offload_frame *frame, int channel, u8 *buffer, u32 size) |
| { |
| struct TouchOffloadData1d *self = |
| (struct TouchOffloadData1d *)frame->channel_data[channel]; |
| |
| self->tx_size = gti->offload.caps.tx_size; |
| self->rx_size = gti->offload.caps.rx_size; |
| self->header.channel_type = frame->channel_type[channel]; |
| self->header.channel_size = |
| TOUCH_OFFLOAD_FRAME_SIZE_1D(self->rx_size, self->tx_size); |
| |
| memcpy(self->data, buffer, size); |
| } |
| |
| void goog_offload_populate_frame(struct goog_touch_interface *gti, |
| struct touch_offload_frame *frame) |
| { |
| static u64 index; |
| u32 channel_type; |
| int i; |
| int ret; |
| u16 tx = gti->offload.caps.tx_size; |
| u16 rx = gti->offload.caps.rx_size; |
| struct gti_sensor_data_cmd *cmd = >i->cmd.sensor_data_cmd; |
| |
| frame->header.index = index++; |
| frame->header.timestamp = gti->input_timestamp; |
| |
| ATRACE_BEGIN(__func__); |
| |
| /* |
| * TODO(b/201610482): |
| * Porting for other channels, like driver status, stylus status |
| * and others. |
| */ |
| |
| /* Populate all channels */ |
| for (i = 0; i < frame->num_channels; i++) { |
| channel_type = frame->channel_type[i]; |
| GOOG_DBG("#%d: get data(type %#x) from vendor driver", i, channel_type); |
| ret = 0; |
| cmd->type = channel_type; |
| cmd->buffer = NULL; |
| cmd->size = 0; |
| if (channel_type == TOUCH_DATA_TYPE_COORD) { |
| ATRACE_BEGIN("populate coord"); |
| goog_offload_populate_coordinate_channel(gti, frame, i); |
| ATRACE_END(); |
| } else if (channel_type & TOUCH_SCAN_TYPE_MUTUAL) { |
| ATRACE_BEGIN("populate mutual data"); |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_GET_SENSOR_DATA); |
| if (ret == 0 && cmd->buffer && |
| cmd->size == TOUCH_OFFLOAD_DATA_SIZE_2D(rx, tx)) { |
| goog_offload_populate_mutual_channel(gti, frame, i, |
| cmd->buffer, cmd->size); |
| /* Backup strength data for v4l2. */ |
| if (channel_type & TOUCH_DATA_TYPE_STRENGTH) |
| memcpy(gti->heatmap_buf, cmd->buffer, cmd->size); |
| } |
| ATRACE_END(); |
| } else if (channel_type & TOUCH_SCAN_TYPE_SELF) { |
| ATRACE_BEGIN("populate self data"); |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_GET_SENSOR_DATA); |
| if (ret == 0 && cmd->buffer && |
| cmd->size == TOUCH_OFFLOAD_DATA_SIZE_1D(rx, tx)) { |
| goog_offload_populate_self_channel(gti, frame, i, |
| cmd->buffer, cmd->size); |
| } |
| ATRACE_END(); |
| } |
| |
| if (ret) { |
| GOOG_DBG("skip to populate data(type %#x, ret %d)!\n", |
| channel_type, ret); |
| } |
| } |
| |
| ATRACE_END(); |
| } |
| |
| void goog_offload_set_running(struct goog_touch_interface *gti, bool running) |
| { |
| int ret = 0; |
| if (gti->offload.offload_running != running) { |
| |
| gti->offload.offload_running = running; |
| if (running && gti->offload.config.filter_grip) |
| gti->cmd.grip_cmd.setting = GTI_GRIP_DISABLE; |
| else |
| gti->cmd.grip_cmd.setting = GTI_GRIP_DRIVER_DEFAULT; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_GRIP); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| gti->grip_setting = gti->cmd.grip_cmd.setting; |
| |
| if (running && gti->offload.config.filter_palm) |
| gti->cmd.palm_cmd.setting = GTI_PALM_DISABLE; |
| else |
| gti->cmd.palm_cmd.setting = GTI_PALM_DRIVER_DEFAULT; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_PALM); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| gti->palm_setting = gti->cmd.palm_cmd.setting; |
| } |
| } |
| |
| void goog_offload_input_report(void *handle, |
| struct TouchOffloadIocReport *report) |
| { |
| struct goog_touch_interface *gti = (struct goog_touch_interface *)handle; |
| bool touch_down = 0; |
| unsigned int tool_type = MT_TOOL_FINGER; |
| int i; |
| unsigned long active_slot_bit = 0; |
| |
| ATRACE_BEGIN(__func__); |
| |
| goog_input_lock(gti); |
| input_set_timestamp(gti->vendor_input_dev, report->timestamp); |
| for (i = 0; i < MAX_COORDS; i++) { |
| if (report->coords[i].status != COORD_STATUS_INACTIVE) { |
| switch (report->coords[i].status) { |
| case COORD_STATUS_EDGE: |
| case COORD_STATUS_PALM: |
| case COORD_STATUS_CANCEL: |
| tool_type = MT_TOOL_PALM; |
| break; |
| case COORD_STATUS_FINGER: |
| case COORD_STATUS_PEN: |
| default: |
| tool_type = MT_TOOL_FINGER; |
| break; |
| } |
| __set_bit(i, &active_slot_bit); |
| input_mt_slot(gti->vendor_input_dev, i); |
| touch_down = 1; |
| input_report_key(gti->vendor_input_dev, BTN_TOUCH, touch_down); |
| input_mt_report_slot_state(gti->vendor_input_dev, tool_type, 1); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_POSITION_X, |
| report->coords[i].x); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_POSITION_Y, |
| report->coords[i].y); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_TOUCH_MAJOR, |
| report->coords[i].major); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_TOUCH_MINOR, |
| report->coords[i].minor); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_PRESSURE, |
| report->coords[i].pressure); |
| } else { |
| __clear_bit(i, &active_slot_bit); |
| input_mt_slot(gti->vendor_input_dev, i); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_PRESSURE, 0); |
| input_mt_report_slot_state(gti->vendor_input_dev, MT_TOOL_FINGER, 0); |
| input_report_abs(gti->vendor_input_dev, ABS_MT_TRACKING_ID, -1); |
| } |
| } |
| input_report_key(gti->vendor_input_dev, BTN_TOUCH, touch_down); |
| input_sync(gti->vendor_input_dev); |
| goog_input_unlock(gti); |
| |
| if (touch_down) |
| goog_v4l2_read(gti, report->timestamp); |
| |
| goog_update_motion_filter(gti, active_slot_bit); |
| |
| ATRACE_END(); |
| } |
| |
| int goog_offload_probe(struct goog_touch_interface *gti) |
| { |
| int ret; |
| u16 values[2]; |
| struct device_node *np = gti->vendor_dev->of_node; |
| |
| if (of_property_read_u8_array(np, "goog,touch_offload_id", |
| gti->offload_id_byte, 4)) { |
| GOOG_LOG("set default offload id: GOOG!\n"); |
| gti->offload_id_byte[0] = 'G'; |
| gti->offload_id_byte[1] = 'O'; |
| gti->offload_id_byte[2] = 'O'; |
| gti->offload_id_byte[3] = 'G'; |
| } |
| |
| gti->offload.caps.touch_offload_major_version = TOUCH_OFFLOAD_INTERFACE_MAJOR_VERSION; |
| gti->offload.caps.touch_offload_minor_version = TOUCH_OFFLOAD_INTERFACE_MINOR_VERSION; |
| gti->offload.caps.device_id = gti->offload_id; |
| |
| if (of_property_read_u16_array(np, "goog,display-resolution", |
| values, 2) == 0) { |
| gti->offload.caps.display_width = values[0]; |
| gti->offload.caps.display_height = values[1]; |
| } else { |
| GOOG_ERR("Plesae set \"goog,display-resolution\" in dts!"); |
| } |
| |
| if (of_property_read_u16_array(np, "goog,channel-num", |
| values, 2) == 0) { |
| gti->offload.caps.tx_size = values[0]; |
| gti->offload.caps.rx_size = values[1]; |
| } else { |
| GOOG_ERR("Plesae set \"goog,channel-num\" in dts!"); |
| ret = -EINVAL; |
| goto err_offload_probe; |
| } |
| |
| /* |
| * TODO(b/201610482): Set more offload caps from parameters or from dtsi? |
| */ |
| gti->offload.caps.heatmap_size = HEATMAP_SIZE_FULL; |
| gti->offload.caps.bus_type = BUS_TYPE_SPI; |
| if (of_property_read_u32(np, "spi-max-frequency", |
| >i->offload.caps.bus_speed_hz)) |
| gti->offload.caps.bus_speed_hz = 0; |
| |
| gti->offload.caps.touch_data_types = |
| TOUCH_DATA_TYPE_COORD | TOUCH_DATA_TYPE_STRENGTH | |
| TOUCH_DATA_TYPE_RAW | TOUCH_DATA_TYPE_BASELINE; |
| gti->offload.caps.touch_scan_types = |
| TOUCH_SCAN_TYPE_MUTUAL; |
| |
| gti->offload.caps.continuous_reporting = true; |
| gti->offload.caps.noise_reporting = false; |
| gti->offload.caps.cancel_reporting = false; |
| gti->offload.caps.size_reporting = true; |
| gti->offload.caps.filter_grip = true; |
| gti->offload.caps.filter_palm = true; |
| gti->offload.caps.num_sensitivity_settings = 1; |
| |
| gti->offload.hcallback = (void *)gti; |
| gti->offload.report_cb = goog_offload_input_report; |
| ret = touch_offload_init(>i->offload); |
| if (ret) { |
| GOOG_ERR("offload init failed, ret %d!\n", ret); |
| goto err_offload_probe; |
| } |
| |
| gti->offload_enable = of_property_read_bool(np, "goog,offload-enable"); |
| GOOG_LOG("offload configucation: %d * %d (%d * %d)\n", |
| gti->offload.caps.display_width, gti->offload.caps.display_height, |
| gti->offload.caps.tx_size, gti->offload.caps.rx_size); |
| |
| GOOG_LOG("offload ID: \"%c%c%c%c\" / 0x%08X, offload_enable=%d.\n", |
| gti->offload_id_byte[0], gti->offload_id_byte[1], gti->offload_id_byte[2], |
| gti->offload_id_byte[3], gti->offload_id, gti->offload_enable); |
| |
| gti->heatmap_buf_size = gti->offload.caps.tx_size * gti->offload.caps.rx_size * sizeof(u16); |
| gti->heatmap_buf = devm_kzalloc(gti->vendor_dev, gti->heatmap_buf_size, GFP_KERNEL); |
| if (!gti->heatmap_buf) { |
| GOOG_ERR("heamap alloc failed!\n"); |
| ret = -ENOMEM; |
| goto err_offload_probe; |
| } |
| |
| /* |
| * Heatmap_probe must be called before irq routine is registered, |
| * because heatmap_read is called from the irq context. |
| * If the ISR runs before heatmap_probe is finished, it will invoke |
| * heatmap_read and cause NPE, since read_frame would not yet be set. |
| */ |
| gti->v4l2.parent_dev = gti->vendor_dev; |
| gti->v4l2.input_dev = gti->vendor_input_dev; |
| gti->v4l2.read_frame = goog_v4l2_read_frame_cb; |
| gti->v4l2.width = gti->offload.caps.tx_size; |
| gti->v4l2.height = gti->offload.caps.rx_size; |
| |
| /* 120 Hz operation */ |
| gti->v4l2.timeperframe.numerator = 1; |
| if (of_property_read_u32(np, "goog,report-rate", |
| >i->v4l2.timeperframe.denominator)) |
| gti->v4l2.timeperframe.denominator = 120; |
| |
| ret = heatmap_probe(>i->v4l2); |
| if (ret) { |
| GOOG_ERR("v4l2 init failed, ret %d!\n", ret); |
| goto err_offload_probe; |
| } |
| gti->v4l2_enable = of_property_read_bool(np, "goog,v4l2-enable"); |
| GOOG_LOG("v4l2 W/H=(%lu, %lu), v4l2_enable=%d.\n", |
| gti->v4l2.width, gti->v4l2.height, gti->v4l2_enable); |
| |
| err_offload_probe: |
| return ret; |
| } |
| |
| void goog_offload_remove(struct goog_touch_interface *gti) |
| { |
| touch_offload_cleanup(>i->offload); |
| } |
| |
| bool goog_input_legacy_report(struct goog_touch_interface *gti) |
| { |
| if (!gti->offload.offload_running || gti->force_legacy_report) |
| return true; |
| |
| return false; |
| } |
| |
| int goog_input_process(struct goog_touch_interface *gti) |
| { |
| int ret = 0; |
| struct touch_offload_frame **frame = >i->offload_frame; |
| |
| if (!gti->coord_changed) |
| return -EPERM; |
| |
| if (gti->offload_enable) { |
| ret = touch_offload_reserve_frame(>i->offload, frame); |
| if (ret != 0 || frame == NULL) { |
| GOOG_ERR("could not reserve a frame(ret %d)!\n", ret); |
| /* Stop offload when there are no buffers available. */ |
| goog_offload_set_running(gti, false); |
| /* |
| * TODO(b/193467748): |
| * How to handle current coord if offload running |
| * terminating in the halfway(not beginning case)? |
| */ |
| ret = -EBUSY; |
| } else { |
| goog_offload_set_running(gti, true); |
| goog_offload_populate_frame(gti, *frame); |
| ret = touch_offload_queue_frame(>i->offload, *frame); |
| if (ret) |
| GOOG_ERR("failed to queue reserved frame(ret %d)!\n", ret); |
| else |
| gti->offload_frame = NULL; |
| } |
| } |
| |
| /* |
| * If offload is NOT running, read heatmap directly by callback. |
| * Otherwise, heatmap will be handled for both offload and v4l2 |
| * during goog_offload_populate_frame(). |
| */ |
| if (!gti->offload.offload_running && gti->v4l2_enable) { |
| int ret; |
| struct gti_sensor_data_cmd *cmd = >i->cmd.sensor_data_cmd; |
| |
| |
| cmd->buffer = NULL; |
| cmd->size = 0; |
| cmd->type = GTI_SENSOR_DATA_TYPE_MS; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_GET_SENSOR_DATA); |
| if (ret == 0 && cmd->buffer && cmd->size) |
| memcpy(gti->heatmap_buf, cmd->buffer, cmd->size); |
| goog_v4l2_read(gti, gti->input_timestamp); |
| goog_update_motion_filter(gti, gti->active_slot_bit); |
| } |
| |
| gti->input_timestamp_changed = false; |
| gti->coord_changed = false; |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(goog_input_process); |
| |
| void goog_input_lock(struct goog_touch_interface *gti) |
| { |
| mutex_lock(>i->input_lock); |
| } |
| EXPORT_SYMBOL(goog_input_lock); |
| |
| void goog_input_unlock(struct goog_touch_interface *gti) |
| { |
| mutex_unlock(>i->input_lock); |
| } |
| EXPORT_SYMBOL(goog_input_unlock); |
| |
| void goog_input_set_timestamp( |
| struct goog_touch_interface *gti, |
| struct input_dev *dev, ktime_t timestamp) |
| { |
| int ret = 0; |
| /* Specific case to handle all fingers release. */ |
| if (!ktime_compare(timestamp, KTIME_RELEASE_ALL)) { |
| GOOG_DBG("Enable force_legacy_report for all fingers release.\n"); |
| /* Enable FW palm and grip for low power sensing during suspend. */ |
| if (gti->offload.offload_running) { |
| gti->cmd.grip_cmd.setting = GTI_GRIP_ENABLE; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_GRIP); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| gti->cmd.palm_cmd.setting = GTI_PALM_ENABLE; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_PALM); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| } |
| timestamp = ktime_get(); |
| gti->force_legacy_report = true; |
| } else { |
| /* Once device is from suspend to resume, recover last grip/palm state. */ |
| if (gti->offload.offload_running && gti->force_legacy_report) { |
| gti->cmd.grip_cmd.setting = gti->grip_setting; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_GRIP); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| gti->cmd.palm_cmd.setting = gti->palm_setting; |
| ret = goog_process_vendor_cmd(gti, GTI_CMD_SET_PALM); |
| if (ret) |
| GOOG_WARN("unexpected return(%d)!", ret); |
| } |
| GOOG_DBG("Disable force_legacy_report as usual state.\n"); |
| gti->force_legacy_report = false; |
| } |
| |
| if (goog_input_legacy_report(gti)) |
| input_set_timestamp(dev, timestamp); |
| |
| gti->input_timestamp = timestamp; |
| gti->input_timestamp_changed = true; |
| } |
| EXPORT_SYMBOL(goog_input_set_timestamp); |
| |
| void goog_input_mt_slot( |
| struct goog_touch_interface *gti, |
| struct input_dev *dev, int slot) |
| { |
| if (goog_input_legacy_report(gti)) |
| input_mt_slot(dev, slot); |
| |
| if (slot < MAX_COORDS) { |
| gti->slot = slot; |
| /* |
| * Make sure the input timestamp should be set before updating 1st mt_slot. |
| * This is for input report switch between offload and legacy. |
| */ |
| if (!gti->coord_changed && !gti->input_timestamp_changed) |
| GOOG_ERR("please exec goog_input_set_timestamp before %s!\n", __func__); |
| gti->coord_changed = true; |
| } |
| } |
| EXPORT_SYMBOL(goog_input_mt_slot); |
| |
| void goog_input_mt_report_slot_state( |
| struct goog_touch_interface *gti, |
| struct input_dev *dev, unsigned int tool_type, bool active) |
| { |
| if (goog_input_legacy_report(gti)) |
| input_mt_report_slot_state(dev, tool_type, active); |
| |
| if (tool_type == MT_TOOL_FINGER) { |
| if (active) { |
| gti->offload.coords[gti->slot].status = COORD_STATUS_FINGER; |
| __set_bit(gti->slot, >i->active_slot_bit); |
| } else { |
| gti->offload.coords[gti->slot].status = COORD_STATUS_INACTIVE; |
| __clear_bit(gti->slot, >i->active_slot_bit); |
| } |
| } |
| } |
| EXPORT_SYMBOL(goog_input_mt_report_slot_state); |
| |
| void goog_input_report_abs( |
| struct goog_touch_interface *gti, |
| struct input_dev *dev, unsigned int code, int value) |
| { |
| if (goog_input_legacy_report(gti)) |
| input_report_abs(dev, code, value); |
| |
| if (gti->slot < MAX_COORDS) { |
| switch (code) { |
| case ABS_MT_POSITION_X: |
| gti->offload.coords[gti->slot].x = value; |
| break; |
| case ABS_MT_POSITION_Y: |
| gti->offload.coords[gti->slot].y = value; |
| break; |
| case ABS_MT_TOUCH_MAJOR: |
| gti->offload.coords[gti->slot].major = value; |
| break; |
| case ABS_MT_TOUCH_MINOR: |
| gti->offload.coords[gti->slot].minor = value; |
| break; |
| case ABS_MT_PRESSURE: |
| gti->offload.coords[gti->slot].pressure = value; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| EXPORT_SYMBOL(goog_input_report_abs); |
| |
| void goog_input_report_key( |
| struct goog_touch_interface *gti, |
| struct input_dev *dev, unsigned int code, int value) |
| { |
| if (goog_input_legacy_report(gti)) |
| input_report_key(dev, code, value); |
| } |
| EXPORT_SYMBOL(goog_input_report_key); |
| |
| void goog_input_sync(struct goog_touch_interface *gti, struct input_dev *dev) |
| { |
| if (goog_input_legacy_report(gti)) |
| input_sync(dev); |
| } |
| EXPORT_SYMBOL(goog_input_sync); |
| |
| void goog_register_tbn(struct goog_touch_interface *gti) |
| { |
| struct device_node *np = gti->vendor_dev->of_node; |
| |
| gti->tbn_enable = of_property_read_bool(np, "goog,tbn-enable"); |
| if (gti->tbn_enable) { |
| if (register_tbn(>i->tbn_register_mask)) { |
| GOOG_ERR("failed to register tbn context!\n"); |
| gti->tbn_enable = false; |
| } else { |
| GOOG_LOG("tbn_register_mask = %#x.\n", gti->tbn_register_mask); |
| } |
| } |
| } |
| |
| static int goog_get_mutual_sensor_data_nop( |
| void *private_data, struct gti_sensor_data_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_get_self_sensor_data_nop( |
| void *private_data, struct gti_sensor_data_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_set_grip_nop( |
| void *private_data, struct gti_grip_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_set_palm_nop( |
| void *private_data, struct gti_palm_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_set_continuous_report_nop( |
| void *private_data, struct gti_continuous_report_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_notify_display_state_nop( |
| void *private_data, struct gti_display_state_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| static int goog_notify_display_vrefresh_nop( |
| void *private_data, struct gti_display_vrefresh_cmd *cmd) |
| { |
| return -ESRCH; |
| } |
| |
| void goog_init_options(struct goog_touch_interface *gti, |
| struct gti_optional_configuration *options) |
| { |
| /* Initialize default functions. */ |
| gti->options.get_mutual_sensor_data = goog_get_mutual_sensor_data_nop; |
| gti->options.get_self_sensor_data = goog_get_self_sensor_data_nop; |
| gti->options.set_grip = goog_set_grip_nop; |
| gti->options.set_palm = goog_set_palm_nop; |
| gti->options.set_continuous_report = goog_set_continuous_report_nop; |
| gti->options.notify_display_state = goog_notify_display_state_nop; |
| gti->options.notify_display_vrefresh = goog_notify_display_vrefresh_nop; |
| |
| /* Set optional operation if available. */ |
| if (options) { |
| if (options->get_mutual_sensor_data) |
| gti->options.get_mutual_sensor_data = options->get_mutual_sensor_data; |
| if (options->get_self_sensor_data) |
| gti->options.get_self_sensor_data = options->get_self_sensor_data; |
| if (options->set_grip) |
| gti->options.set_grip = options->set_grip; |
| if (options->set_palm) |
| gti->options.set_palm = options->set_palm; |
| if (options->set_continuous_report) |
| gti->options.set_continuous_report = options->set_continuous_report; |
| if (options->notify_display_state) |
| gti->options.notify_display_state = options->notify_display_state; |
| if (options->notify_display_vrefresh) { |
| gti->options.notify_display_vrefresh = |
| options->notify_display_vrefresh; |
| } |
| } |
| } |
| |
| void goog_notify_vendor_dev_pm_state_done(struct goog_touch_interface *gti, |
| enum gti_vendor_dev_pm_state state) |
| { |
| int ret = 0; |
| |
| if (!gti) |
| return; |
| |
| if (gti->vendor_dev_pm_state != state) { |
| GOOG_LOG("pm state changed: %d -> %d.\n", |
| gti->vendor_dev_pm_state, state); |
| gti->vendor_dev_pm_state = state; |
| } |
| if (gti->tbn_register_mask && |
| gti->vendor_dev_pm_state == GTI_VENDOR_DEV_SUSPEND) { |
| ret = tbn_release_bus(gti->tbn_register_mask); |
| if (ret) |
| GOOG_ERR("tbn_release_bus failed, ret %d!\n", ret); |
| } |
| } |
| EXPORT_SYMBOL(goog_notify_vendor_dev_pm_state_done); |
| |
| struct goog_touch_interface *goog_touch_interface_probe( |
| void *private_data, |
| struct device *dev, |
| struct input_dev *input_dev, |
| int (*default_handler)(void *private_data, |
| u32 cmd_type, struct gti_union_cmd_data *cmd), |
| struct gti_optional_configuration *options) |
| { |
| int ret; |
| struct goog_touch_interface *gti; |
| |
| if (!dev || !input_dev || !default_handler) { |
| GOOG_ERR("invalid dev/input_dev or default_handler!\n"); |
| return NULL; |
| } |
| |
| gti = devm_kzalloc(dev, sizeof(struct goog_touch_interface), GFP_KERNEL); |
| if (gti) { |
| gti->vendor_private_data = private_data; |
| gti->vendor_dev = dev; |
| gti->vendor_input_dev = input_dev; |
| gti->vendor_default_handler = default_handler; |
| gti->mf_mode = GTI_MF_MODE_DEFAULT; |
| mutex_init(>i->input_lock); |
| goog_offload_probe(gti); |
| register_panel_bridge(gti); |
| goog_register_tbn(gti); |
| goog_init_options(gti, options); |
| } |
| |
| if (!gti_class) |
| gti_class = class_create(THIS_MODULE, "goog_touch_interface"); |
| |
| if (gti && gti_class) { |
| char *name = kasprintf(GFP_KERNEL, "gti.%d", gti_dev_num); |
| |
| if (name && |
| !alloc_chrdev_region(>i->dev_id, 0, 1, name)) { |
| gti->dev = device_create(gti_class, NULL, |
| gti->dev_id, gti, name); |
| if (gti->dev) { |
| gti_dev_num++; |
| GOOG_LOG("device create \"%s\".\n", name); |
| if (gti->vendor_dev) { |
| ret = sysfs_create_link(>i->dev->kobj, |
| >i->vendor_dev->kobj, "vendor"); |
| if (ret) |
| GOOG_ERR("sysfs_create_link() failed for vendor, ret=%d!\n", ret); |
| } |
| if (gti->vendor_input_dev) { |
| ret = sysfs_create_link(>i->dev->kobj, |
| >i->vendor_input_dev->dev.kobj, "vendor_input"); |
| if (ret) |
| GOOG_ERR("sysfs_create_link() failed for vendor_input, ret=%d!\n", |
| ret); |
| } |
| } |
| } |
| kfree(name); |
| } |
| |
| if (gti && gti->dev) { |
| ret = sysfs_create_group(>i->dev->kobj, &goog_attr_group); |
| if (ret) |
| GOOG_ERR("sysfs_create_group() failed, ret= %d!\n", ret); |
| } |
| |
| return gti; |
| } |
| EXPORT_SYMBOL(goog_touch_interface_probe); |
| |
| int goog_touch_interface_remove(struct goog_touch_interface *gti) |
| { |
| if (!gti) |
| return -ENODEV; |
| |
| |
| if (gti->tbn_enable && gti->tbn_register_mask) |
| unregister_tbn(>i->tbn_register_mask); |
| |
| unregister_panel_bridge(>i->panel_bridge); |
| |
| if (gti->vendor_dev) |
| sysfs_remove_link(>i->dev->kobj, "vendor"); |
| if (gti->vendor_input_dev) |
| sysfs_remove_link(>i->dev->kobj, "vendor_input"); |
| |
| if (gti_class) { |
| unregister_chrdev_region(gti->dev_id, 1); |
| device_destroy(gti_class, gti->dev_id); |
| gti_dev_num--; |
| } |
| |
| gti->offload_enable = false; |
| gti->v4l2_enable = false; |
| goog_offload_remove(gti); |
| heatmap_remove(>i->v4l2); |
| devm_kfree(gti->vendor_dev, gti->heatmap_buf); |
| devm_kfree(gti->vendor_dev, gti); |
| |
| if (gti_class && !gti_dev_num) |
| class_destroy(gti_class); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(goog_touch_interface_remove); |
| |
| MODULE_DESCRIPTION("Google Touch Interface"); |
| MODULE_AUTHOR("Super Liu<supercjliu@google.com>"); |
| MODULE_LICENSE("GPL v2"); |