/* * Cypress APA trackpad with I2C interface * * Author: Dudley Du <dudl@cypress.com> * * Copyright (C) 2015 Cypress Semiconductor, Inc. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for * more details. */ #include <linux/delay.h> #include <linux/i2c.h> #include <linux/input.h> #include <linux/input/mt.h> #include <linux/mutex.h> #include <linux/completion.h> #include <linux/slab.h> #include <asm/unaligned.h> #include <linux/crc-itu-t.h> #include "cyapa.h" #define GEN6_ENABLE_CMD_IRQ 0x41 #define GEN6_DISABLE_CMD_IRQ 0x42 #define GEN6_ENABLE_DEV_IRQ 0x43 #define GEN6_DISABLE_DEV_IRQ 0x44 #define GEN6_POWER_MODE_ACTIVE 0x01 #define GEN6_POWER_MODE_LP_MODE1 0x02 #define GEN6_POWER_MODE_LP_MODE2 0x03 #define GEN6_POWER_MODE_BTN_ONLY 0x04 #define GEN6_SET_POWER_MODE_INTERVAL 0x47 #define GEN6_GET_POWER_MODE_INTERVAL 0x48 #define GEN6_MAX_RX_NUM 14 #define GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC 0x00 #define GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM 0x12 struct pip_app_cmd_head { __le16 addr; __le16 length; u8 report_id; u8 resv; /* Reserved, must be 0 */ u8 cmd_code; /* bit7: resv, set to 0; bit6~0: command code.*/ } __packed; struct pip_app_resp_head { __le16 length; u8 report_id; u8 resv; /* Reserved, must be 0 */ u8 cmd_code; /* bit7: TGL; bit6~0: command code.*/ /* * The value of data_status can be the first byte of data or * the command status or the unsupported command code depending on the * requested command code. */ u8 data_status; } __packed; struct pip_fixed_info { u8 silicon_id_high; u8 silicon_id_low; u8 family_id; }; static u8 pip_get_bl_info[] = { 0x04, 0x00, 0x0B, 0x00, 0x40, 0x00, 0x01, 0x38, 0x00, 0x00, 0x70, 0x9E, 0x17 }; static bool cyapa_sort_pip_hid_descriptor_data(struct cyapa *cyapa, u8 *buf, int len) { if (len != PIP_HID_DESCRIPTOR_SIZE) return false; if (buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID || buf[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) return true; return false; } static int cyapa_get_pip_fixed_info(struct cyapa *cyapa, struct pip_fixed_info *pip_info, bool is_bootloader) { u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; int resp_len; u16 product_family; int error; if (is_bootloader) { /* Read Bootloader Information to determine Gen5 or Gen6. */ resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, pip_get_bl_info, sizeof(pip_get_bl_info), resp_data, &resp_len, 2000, cyapa_sort_tsg_pip_bl_resp_data, false); if (error || resp_len < PIP_BL_GET_INFO_RESP_LENGTH) return error ? error : -EIO; pip_info->family_id = resp_data[8]; pip_info->silicon_id_low = resp_data[10]; pip_info->silicon_id_high = resp_data[11]; return 0; } /* Get App System Information to determine Gen5 or Gen6. */ resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, resp_data, &resp_len, 2000, cyapa_pip_sort_system_info_data, false); if (error || resp_len < PIP_READ_SYS_INFO_RESP_LENGTH) return error ? error : -EIO; product_family = get_unaligned_le16(&resp_data[7]); if ((product_family & PIP_PRODUCT_FAMILY_MASK) != PIP_PRODUCT_FAMILY_TRACKPAD) return -EINVAL; pip_info->family_id = resp_data[19]; pip_info->silicon_id_low = resp_data[21]; pip_info->silicon_id_high = resp_data[22]; return 0; } int cyapa_pip_state_parse(struct cyapa *cyapa, u8 *reg_data, int len) { u8 cmd[] = { 0x01, 0x00}; struct pip_fixed_info pip_info; u8 resp_data[PIP_HID_DESCRIPTOR_SIZE]; int resp_len; bool is_bootloader; int error; cyapa->state = CYAPA_STATE_NO_DEVICE; /* Try to wake from it deep sleep state if it is. */ cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); /* Empty the buffer queue to get fresh data with later commands. */ cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL); /* * Read description info from trackpad device to determine running in * APP mode or Bootloader mode. */ resp_len = PIP_HID_DESCRIPTOR_SIZE; error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), resp_data, &resp_len, 300, cyapa_sort_pip_hid_descriptor_data, false); if (error) return error; if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_BL_REPORT_ID) is_bootloader = true; else if (resp_data[PIP_RESP_REPORT_ID_OFFSET] == PIP_HID_APP_REPORT_ID) is_bootloader = false; else return -EAGAIN; /* Get PIP fixed information to determine Gen5 or Gen6. */ memset(&pip_info, 0, sizeof(struct pip_fixed_info)); error = cyapa_get_pip_fixed_info(cyapa, &pip_info, is_bootloader); if (error) return error; if (pip_info.family_id == 0x9B && pip_info.silicon_id_high == 0x0B) { cyapa->gen = CYAPA_GEN6; cyapa->state = is_bootloader ? CYAPA_STATE_GEN6_BL : CYAPA_STATE_GEN6_APP; } else if (pip_info.family_id == 0x91 && pip_info.silicon_id_high == 0x02) { cyapa->gen = CYAPA_GEN5; cyapa->state = is_bootloader ? CYAPA_STATE_GEN5_BL : CYAPA_STATE_GEN5_APP; } return 0; } static int cyapa_gen6_read_sys_info(struct cyapa *cyapa) { u8 resp_data[PIP_READ_SYS_INFO_RESP_LENGTH]; int resp_len; u16 product_family; u8 rotat_align; int error; /* Get App System Information to determine Gen5 or Gen6. */ resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, pip_read_sys_info, PIP_READ_SYS_INFO_CMD_LENGTH, resp_data, &resp_len, 2000, cyapa_pip_sort_system_info_data, false); if (error || resp_len < sizeof(resp_data)) return error ? error : -EIO; product_family = get_unaligned_le16(&resp_data[7]); if ((product_family & PIP_PRODUCT_FAMILY_MASK) != PIP_PRODUCT_FAMILY_TRACKPAD) return -EINVAL; cyapa->platform_ver = (resp_data[67] >> PIP_BL_PLATFORM_VER_SHIFT) & PIP_BL_PLATFORM_VER_MASK; cyapa->fw_maj_ver = resp_data[9]; cyapa->fw_min_ver = resp_data[10]; cyapa->electrodes_x = resp_data[33]; cyapa->electrodes_y = resp_data[34]; cyapa->physical_size_x = get_unaligned_le16(&resp_data[35]) / 100; cyapa->physical_size_y = get_unaligned_le16(&resp_data[37]) / 100; cyapa->max_abs_x = get_unaligned_le16(&resp_data[39]); cyapa->max_abs_y = get_unaligned_le16(&resp_data[41]); cyapa->max_z = get_unaligned_le16(&resp_data[43]); cyapa->x_origin = resp_data[45] & 0x01; cyapa->y_origin = resp_data[46] & 0x01; cyapa->btn_capability = (resp_data[70] << 3) & CAPABILITY_BTN_MASK; memcpy(&cyapa->product_id[0], &resp_data[51], 5); cyapa->product_id[5] = '-'; memcpy(&cyapa->product_id[6], &resp_data[56], 6); cyapa->product_id[12] = '-'; memcpy(&cyapa->product_id[13], &resp_data[62], 2); cyapa->product_id[15] = '\0'; /* Get the number of Rx electrodes. */ rotat_align = resp_data[68]; cyapa->electrodes_rx = rotat_align ? cyapa->electrodes_y : cyapa->electrodes_x; cyapa->aligned_electrodes_rx = (cyapa->electrodes_rx + 3) & ~3u; if (!cyapa->electrodes_x || !cyapa->electrodes_y || !cyapa->physical_size_x || !cyapa->physical_size_y || !cyapa->max_abs_x || !cyapa->max_abs_y || !cyapa->max_z) return -EINVAL; return 0; } static int cyapa_gen6_bl_read_app_info(struct cyapa *cyapa) { u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH]; int resp_len; int error; resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, pip_bl_read_app_info, PIP_BL_READ_APP_INFO_CMD_LENGTH, resp_data, &resp_len, 500, cyapa_sort_tsg_pip_bl_resp_data, false); if (error || resp_len < PIP_BL_APP_INFO_RESP_LENGTH || !PIP_CMD_COMPLETE_SUCCESS(resp_data)) return error ? error : -EIO; cyapa->fw_maj_ver = resp_data[8]; cyapa->fw_min_ver = resp_data[9]; cyapa->platform_ver = (resp_data[12] >> PIP_BL_PLATFORM_VER_SHIFT) & PIP_BL_PLATFORM_VER_MASK; memcpy(&cyapa->product_id[0], &resp_data[13], 5); cyapa->product_id[5] = '-'; memcpy(&cyapa->product_id[6], &resp_data[18], 6); cyapa->product_id[12] = '-'; memcpy(&cyapa->product_id[13], &resp_data[24], 2); cyapa->product_id[15] = '\0'; return 0; } static int cyapa_gen6_config_dev_irq(struct cyapa *cyapa, u8 cmd_code) { u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, cmd_code }; u8 resp_data[6]; int resp_len; int error; resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), resp_data, &resp_len, 500, cyapa_sort_tsg_pip_app_resp_data, false); if (error || !VALID_CMD_RESP_HEADER(resp_data, cmd_code) || !PIP_CMD_COMPLETE_SUCCESS(resp_data) ) return error < 0 ? error : -EINVAL; return 0; } static int cyapa_gen6_set_proximity(struct cyapa *cyapa, bool enable) { int error; cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); error = cyapa_pip_set_proximity(cyapa, enable); cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); return error; } static int cyapa_gen6_change_power_state(struct cyapa *cyapa, u8 power_mode) { u8 cmd[] = { 0x04, 0x00, 0x06, 0x00, 0x2f, 0x00, 0x46, power_mode }; u8 resp_data[6]; int resp_len; int error; resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), resp_data, &resp_len, 500, cyapa_sort_tsg_pip_app_resp_data, false); if (error || !VALID_CMD_RESP_HEADER(resp_data, 0x46)) return error < 0 ? error : -EINVAL; /* New power state applied in device not match the set power state. */ if (resp_data[5] != power_mode) return -EAGAIN; return 0; } static int cyapa_gen6_set_interval_setting(struct cyapa *cyapa, struct gen6_interval_setting *interval_setting) { struct gen6_set_interval_cmd { __le16 addr; __le16 length; u8 report_id; u8 rsvd; /* Reserved, must be 0 */ u8 cmd_code; __le16 active_interval; __le16 lp1_interval; __le16 lp2_interval; } __packed set_interval_cmd; u8 resp_data[11]; int resp_len; int error; memset(&set_interval_cmd, 0, sizeof(set_interval_cmd)); put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &set_interval_cmd.addr); put_unaligned_le16(sizeof(set_interval_cmd) - 2, &set_interval_cmd.length); set_interval_cmd.report_id = PIP_APP_CMD_REPORT_ID; set_interval_cmd.cmd_code = GEN6_SET_POWER_MODE_INTERVAL; put_unaligned_le16(interval_setting->active_interval, &set_interval_cmd.active_interval); put_unaligned_le16(interval_setting->lp1_interval, &set_interval_cmd.lp1_interval); put_unaligned_le16(interval_setting->lp2_interval, &set_interval_cmd.lp2_interval); resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, (u8 *)&set_interval_cmd, sizeof(set_interval_cmd), resp_data, &resp_len, 500, cyapa_sort_tsg_pip_app_resp_data, false); if (error || !VALID_CMD_RESP_HEADER(resp_data, GEN6_SET_POWER_MODE_INTERVAL)) return error < 0 ? error : -EINVAL; /* Get the real set intervals from response. */ interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); return 0; } static int cyapa_gen6_get_interval_setting(struct cyapa *cyapa, struct gen6_interval_setting *interval_setting) { u8 cmd[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, GEN6_GET_POWER_MODE_INTERVAL }; u8 resp_data[11]; int resp_len; int error; resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, cmd, sizeof(cmd), resp_data, &resp_len, 500, cyapa_sort_tsg_pip_app_resp_data, false); if (error || !VALID_CMD_RESP_HEADER(resp_data, GEN6_GET_POWER_MODE_INTERVAL)) return error < 0 ? error : -EINVAL; interval_setting->active_interval = get_unaligned_le16(&resp_data[5]); interval_setting->lp1_interval = get_unaligned_le16(&resp_data[7]); interval_setting->lp2_interval = get_unaligned_le16(&resp_data[9]); return 0; } static int cyapa_gen6_deep_sleep(struct cyapa *cyapa, u8 state) { u8 ping[] = { 0x04, 0x00, 0x05, 0x00, 0x2f, 0x00, 0x00 }; if (state == PIP_DEEP_SLEEP_STATE_ON) /* * Send ping command to notify device prepare for wake up * when it's in deep sleep mode. At this time, device will * response nothing except an I2C NAK. */ cyapa_i2c_pip_write(cyapa, ping, sizeof(ping)); return cyapa_pip_deep_sleep(cyapa, state); } static int cyapa_gen6_set_power_mode(struct cyapa *cyapa, u8 power_mode, u16 sleep_time, enum cyapa_pm_stage pm_stage) { struct device *dev = &cyapa->client->dev; struct gen6_interval_setting *interval_setting = &cyapa->gen6_interval_setting; u8 lp_mode; int error; if (cyapa->state != CYAPA_STATE_GEN6_APP) return 0; if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) { /* * Assume TP in deep sleep mode when driver is loaded, * avoid driver unload and reload command IO issue caused by TP * has been set into deep sleep mode when unloading. */ PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); } if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) && PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF) PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME); if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) { if (power_mode == PWR_MODE_OFF || power_mode == PWR_MODE_FULL_ACTIVE || power_mode == PWR_MODE_BTN_ONLY || PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) { /* Has in correct power mode state, early return. */ return 0; } } if (power_mode == PWR_MODE_OFF) { cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF); if (error) { dev_err(dev, "enter deep sleep fail: %d\n", error); return error; } PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF); return 0; } /* * When trackpad in power off mode, it cannot change to other power * state directly, must be wake up from sleep firstly, then * continue to do next power sate change. */ if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) { error = cyapa_gen6_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); if (error) { dev_err(dev, "deep sleep wake fail: %d\n", error); return error; } } /* * Disable device assert interrupts for command response to avoid * disturbing system suspending or hibernating process. */ cyapa_gen6_config_dev_irq(cyapa, GEN6_DISABLE_CMD_IRQ); if (power_mode == PWR_MODE_FULL_ACTIVE) { error = cyapa_gen6_change_power_state(cyapa, GEN6_POWER_MODE_ACTIVE); if (error) { dev_err(dev, "change to active fail: %d\n", error); goto out; } PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE); /* Sync the interval setting from device. */ cyapa_gen6_get_interval_setting(cyapa, interval_setting); } else if (power_mode == PWR_MODE_BTN_ONLY) { error = cyapa_gen6_change_power_state(cyapa, GEN6_POWER_MODE_BTN_ONLY); if (error) { dev_err(dev, "fail to button only mode: %d\n", error); goto out; } PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY); } else { /* * Gen6 internally supports to 2 low power scan interval time, * so can help to switch power mode quickly. * such as runtime suspend and system suspend. */ if (interval_setting->lp1_interval == sleep_time) { lp_mode = GEN6_POWER_MODE_LP_MODE1; } else if (interval_setting->lp2_interval == sleep_time) { lp_mode = GEN6_POWER_MODE_LP_MODE2; } else { if (interval_setting->lp1_interval == 0) { interval_setting->lp1_interval = sleep_time; lp_mode = GEN6_POWER_MODE_LP_MODE1; } else { interval_setting->lp2_interval = sleep_time; lp_mode = GEN6_POWER_MODE_LP_MODE2; } cyapa_gen6_set_interval_setting(cyapa, interval_setting); } error = cyapa_gen6_change_power_state(cyapa, lp_mode); if (error) { dev_err(dev, "set power state to 0x%02x failed: %d\n", lp_mode, error); goto out; } PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time); PIP_DEV_SET_PWR_STATE(cyapa, cyapa_sleep_time_to_pwr_cmd(sleep_time)); } out: cyapa_gen6_config_dev_irq(cyapa, GEN6_ENABLE_CMD_IRQ); return error; } static int cyapa_gen6_initialize(struct cyapa *cyapa) { return 0; } static int cyapa_pip_retrieve_data_structure(struct cyapa *cyapa, u16 read_offset, u16 read_len, u8 data_id, u8 *data, int *data_buf_lens) { struct retrieve_data_struct_cmd { struct pip_app_cmd_head head; __le16 read_offset; __le16 read_length; u8 data_id; } __packed cmd; u8 resp_data[GEN6_MAX_RX_NUM + 10]; int resp_len; int error; memset(&cmd, 0, sizeof(cmd)); put_unaligned_le16(PIP_OUTPUT_REPORT_ADDR, &cmd.head.addr); put_unaligned_le16(sizeof(cmd), &cmd.head.length - 2); cmd.head.report_id = PIP_APP_CMD_REPORT_ID; cmd.head.cmd_code = PIP_RETRIEVE_DATA_STRUCTURE; put_unaligned_le16(read_offset, &cmd.read_offset); put_unaligned_le16(read_len, &cmd.read_length); cmd.data_id = data_id; resp_len = sizeof(resp_data); error = cyapa_i2c_pip_cmd_irq_sync(cyapa, (u8 *)&cmd, sizeof(cmd), resp_data, &resp_len, 500, cyapa_sort_tsg_pip_app_resp_data, true); if (error || !PIP_CMD_COMPLETE_SUCCESS(resp_data) || resp_data[6] != data_id || !VALID_CMD_RESP_HEADER(resp_data, PIP_RETRIEVE_DATA_STRUCTURE)) return (error < 0) ? error : -EAGAIN; read_len = get_unaligned_le16(&resp_data[7]); if (*data_buf_lens < read_len) { *data_buf_lens = read_len; return -ENOBUFS; } memcpy(data, &resp_data[10], read_len); *data_buf_lens = read_len; return 0; } static ssize_t cyapa_gen6_show_baseline(struct device *dev, struct device_attribute *attr, char *buf) { struct cyapa *cyapa = dev_get_drvdata(dev); u8 data[GEN6_MAX_RX_NUM]; int data_len; int size = 0; int i; int error; int resume_error; if (!cyapa_is_pip_app_mode(cyapa)) return -EBUSY; /* 1. Suspend Scanning*/ error = cyapa_pip_suspend_scanning(cyapa); if (error) return error; /* 2. IDAC and RX Attenuator Calibration Data (Center Frequency). */ data_len = sizeof(data); error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, GEN6_RETRIEVE_DATA_ID_RX_ATTENURATOR_IDAC, data, &data_len); if (error) goto resume_scanning; size = scnprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d ", data[0], /* RX Attenuator Mutual */ data[1], /* IDAC Mutual */ data[2], /* RX Attenuator Self RX */ data[3], /* IDAC Self RX */ data[4], /* RX Attenuator Self TX */ data[5] /* IDAC Self TX */ ); /* 3. Read Attenuator Trim. */ data_len = sizeof(data); error = cyapa_pip_retrieve_data_structure(cyapa, 0, data_len, GEN6_RETRIEVE_DATA_ID_ATTENURATOR_TRIM, data, &data_len); if (error) goto resume_scanning; /* set attenuator trim values. */ for (i = 0; i < data_len; i++) size += scnprintf(buf + size, PAGE_SIZE - size, "%d ", data[i]); size += scnprintf(buf + size, PAGE_SIZE - size, "\n"); resume_scanning: /* 4. Resume Scanning*/ resume_error = cyapa_pip_resume_scanning(cyapa); if (resume_error || error) { memset(buf, 0, PAGE_SIZE); return resume_error ? resume_error : error; } return size; } static int cyapa_gen6_operational_check(struct cyapa *cyapa) { struct device *dev = &cyapa->client->dev; int error; if (cyapa->gen != CYAPA_GEN6) return -ENODEV; switch (cyapa->state) { case CYAPA_STATE_GEN6_BL: error = cyapa_pip_bl_exit(cyapa); if (error) { /* Try to update trackpad product information. */ cyapa_gen6_bl_read_app_info(cyapa); goto out; } cyapa->state = CYAPA_STATE_GEN6_APP; case CYAPA_STATE_GEN6_APP: /* * If trackpad device in deep sleep mode, * the app command will fail. * So always try to reset trackpad device to full active when * the device state is required. */ error = cyapa_gen6_set_power_mode(cyapa, PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); if (error) dev_warn(dev, "%s: failed to set power active mode.\n", __func__); /* By default, the trackpad proximity function is enabled. */ error = cyapa_pip_set_proximity(cyapa, true); if (error) dev_warn(dev, "%s: failed to enable proximity.\n", __func__); /* Get trackpad product information. */ error = cyapa_gen6_read_sys_info(cyapa); if (error) goto out; /* Only support product ID starting with CYTRA */ if (memcmp(cyapa->product_id, product_id, strlen(product_id)) != 0) { dev_err(dev, "%s: unknown product ID (%s)\n", __func__, cyapa->product_id); error = -EINVAL; } break; default: error = -EINVAL; } out: return error; } const struct cyapa_dev_ops cyapa_gen6_ops = { .check_fw = cyapa_pip_check_fw, .bl_enter = cyapa_pip_bl_enter, .bl_initiate = cyapa_pip_bl_initiate, .update_fw = cyapa_pip_do_fw_update, .bl_activate = cyapa_pip_bl_activate, .bl_deactivate = cyapa_pip_bl_deactivate, .show_baseline = cyapa_gen6_show_baseline, .calibrate_store = cyapa_pip_do_calibrate, .initialize = cyapa_gen6_initialize, .state_parse = cyapa_pip_state_parse, .operational_check = cyapa_gen6_operational_check, .irq_handler = cyapa_pip_irq_handler, .irq_cmd_handler = cyapa_pip_irq_cmd_handler, .sort_empty_output_data = cyapa_empty_pip_output_data, .set_power_mode = cyapa_gen6_set_power_mode, .set_proximity = cyapa_gen6_set_proximity, };