// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include "synaptics_tcm_core.h" #include "synaptics_tcm_testing.h" #define SYSFS_DIR_NAME "testing" #define REPORT_TIMEOUT_MS 500 #define testing_sysfs_show(t_name) \ static ssize_t testing_sysfs_##t_name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ int retval; \ struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; \ \ mutex_lock(&tcm_hcd->extif_mutex); \ \ retval = testing_##t_name(); \ if (retval < 0) { \ LOG_ERR(tcm_hcd->pdev->dev.parent, \ "Failed to do "#t_name" test\n"); \ goto exit; \ } \ \ retval = snprintf(buf, PAGE_SIZE, \ "%s\n", \ testing_hcd->result ? "Passed" : "Failed"); \ \ exit: \ mutex_unlock(&tcm_hcd->extif_mutex); \ \ return retval; \ } enum test_code { TEST_NOT_IMPLEMENTED = 0, TEST_TRX_TRX_SHORTS = 1, TEST_TRX_SENSOR_OPENS = 2, TEST_TRX_GROUND_SHORTS = 3, TEST_DYNAMIC_RANGE = 7, TEST_OPEN_SHORT_DETECTOR = 8, TEST_NOISE = 10, TEST_PT11 = 11, TEST_PT12 = 12, TEST_PT13 = 13, TEST_DYNAMIC_RANGE_DOZE = 14, TEST_NOISE_DOZE = 15, }; struct testing_hcd { bool result; unsigned char report_type; unsigned int report_index; unsigned int num_of_reports; struct kobject *sysfs_dir; struct syna_tcm_buffer out; struct syna_tcm_buffer resp; struct syna_tcm_buffer report; struct syna_tcm_buffer process; struct syna_tcm_buffer output; struct syna_tcm_hcd *tcm_hcd; int (*collect_reports)(enum report_type report_type, unsigned int num_of_reports); }; DECLARE_COMPLETION(report_complete); DECLARE_COMPLETION(testing_remove_complete); static struct testing_hcd *testing_hcd; static int testing_dynamic_range(void); static int testing_dynamic_range_lpwg(void); static int testing_dynamic_range_doze(void); static int testing_noise(void); static int testing_noise_lpwg(void); static int testing_noise_doze(void); static int testing_open_short_detector(void); static int testing_pt11(void); static int testing_pt12(void); static int testing_pt13(void); static int testing_reset_open(void); static int testing_lockdown(void); static int testing_trx(enum test_code test_code); SHOW_PROTOTYPE(testing, dynamic_range) SHOW_PROTOTYPE(testing, dynamic_range_lpwg) SHOW_PROTOTYPE(testing, dynamic_range_doze) SHOW_PROTOTYPE(testing, noise) SHOW_PROTOTYPE(testing, noise_lpwg) SHOW_PROTOTYPE(testing, noise_doze) SHOW_PROTOTYPE(testing, open_short_detector) SHOW_PROTOTYPE(testing, pt11) SHOW_PROTOTYPE(testing, pt12) SHOW_PROTOTYPE(testing, pt13) SHOW_PROTOTYPE(testing, reset_open) SHOW_PROTOTYPE(testing, lockdown) SHOW_PROTOTYPE(testing, trx_trx_shorts) SHOW_PROTOTYPE(testing, trx_sensor_opens) SHOW_PROTOTYPE(testing, trx_ground_shorts) SHOW_PROTOTYPE(testing, size) static struct device_attribute *attrs[] = { ATTRIFY(dynamic_range), ATTRIFY(dynamic_range_lpwg), ATTRIFY(dynamic_range_doze), ATTRIFY(noise), ATTRIFY(noise_lpwg), ATTRIFY(noise_doze), ATTRIFY(open_short_detector), ATTRIFY(pt11), ATTRIFY(pt12), ATTRIFY(pt13), ATTRIFY(reset_open), ATTRIFY(lockdown), ATTRIFY(trx_trx_shorts), ATTRIFY(trx_sensor_opens), ATTRIFY(trx_ground_shorts), ATTRIFY(size), }; static ssize_t testing_sysfs_data_show(struct file *data_file, struct kobject *kobj, struct bin_attribute *attributes, char *buf, loff_t pos, size_t count); static struct bin_attribute bin_attr = { .attr = { .name = "data", .mode = 0444, }, .size = 0, .read = testing_sysfs_data_show, }; testing_sysfs_show(dynamic_range) testing_sysfs_show(dynamic_range_lpwg) testing_sysfs_show(dynamic_range_doze) testing_sysfs_show(noise) testing_sysfs_show(noise_lpwg) testing_sysfs_show(noise_doze) testing_sysfs_show(open_short_detector) testing_sysfs_show(pt11) testing_sysfs_show(pt12) testing_sysfs_show(pt13) testing_sysfs_show(reset_open) testing_sysfs_show(lockdown) static ssize_t testing_sysfs_trx_trx_shorts_show(struct device *dev, struct device_attribute *attr, char *buf) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; mutex_lock(&tcm_hcd->extif_mutex); retval = testing_trx(TEST_TRX_TRX_SHORTS); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do TRX-TRX shorts test\n"); goto exit; } retval = snprintf(buf, PAGE_SIZE, "%s\n", testing_hcd->result ? "Passed" : "Failed"); exit: mutex_unlock(&tcm_hcd->extif_mutex); return retval; } static ssize_t testing_sysfs_trx_sensor_opens_show(struct device *dev, struct device_attribute *attr, char *buf) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; mutex_lock(&tcm_hcd->extif_mutex); retval = testing_trx(TEST_TRX_SENSOR_OPENS); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do TRX-sensor opens test\n"); goto exit; } retval = snprintf(buf, PAGE_SIZE, "%s\n", testing_hcd->result ? "Passed" : "Failed"); exit: mutex_unlock(&tcm_hcd->extif_mutex); return retval; } static ssize_t testing_sysfs_trx_ground_shorts_show(struct device *dev, struct device_attribute *attr, char *buf) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; mutex_lock(&tcm_hcd->extif_mutex); retval = testing_trx(TEST_TRX_GROUND_SHORTS); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do TRX-ground shorts test\n"); goto exit; } retval = snprintf(buf, PAGE_SIZE, "%s\n", testing_hcd->result ? "Passed" : "Failed"); exit: mutex_unlock(&tcm_hcd->extif_mutex); return retval; } static ssize_t testing_sysfs_size_show(struct device *dev, struct device_attribute *attr, char *buf) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; mutex_lock(&tcm_hcd->extif_mutex); LOCK_BUFFER(testing_hcd->output); retval = snprintf(buf, PAGE_SIZE, "%u\n", testing_hcd->output.data_length); UNLOCK_BUFFER(testing_hcd->output); mutex_unlock(&tcm_hcd->extif_mutex); return retval; } static ssize_t testing_sysfs_data_show(struct file *data_file, struct kobject *kobj, struct bin_attribute *attributes, char *buf, loff_t pos, size_t count) { int retval; unsigned int readlen; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; mutex_lock(&tcm_hcd->extif_mutex); LOCK_BUFFER(testing_hcd->output); readlen = MIN(count, testing_hcd->output.data_length - pos); retval = secure_memcpy(buf, count, &testing_hcd->output.buf[pos], testing_hcd->output.buf_size - pos, readlen); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy report data\n"); } else { retval = readlen; } UNLOCK_BUFFER(testing_hcd->output); mutex_unlock(&tcm_hcd->extif_mutex); return retval; } static int testing_run_prod_test_item(enum test_code test_code) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; if (tcm_hcd->features.dual_firmware && tcm_hcd->id_info.mode != MODE_PRODUCTION_TEST) { retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_PRODUCTION_TEST); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run production test firmware\n"); return retval; } } else if (tcm_hcd->id_info.mode != MODE_APPLICATION || tcm_hcd->app_status != APP_STATUS_OK) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Application firmware not running\n"); return -ENODEV; } LOCK_BUFFER(testing_hcd->out); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->out, 1); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->out.buf\n"); UNLOCK_BUFFER(testing_hcd->out); return retval; } testing_hcd->out.buf[0] = test_code; LOCK_BUFFER(testing_hcd->resp); retval = tcm_hcd->write_message(tcm_hcd, CMD_PRODUCTION_TEST, testing_hcd->out.buf, 1, &testing_hcd->resp.buf, &testing_hcd->resp.buf_size, &testing_hcd->resp.data_length, NULL, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write command %s\n", STR(CMD_PRODUCTION_TEST)); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); return retval; } UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); return 0; } static int testing_collect_reports(enum report_type report_type, unsigned int num_of_reports) { int retval; bool completed; unsigned int timeout; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; testing_hcd->report_index = 0; testing_hcd->report_type = report_type; testing_hcd->num_of_reports = num_of_reports; reinit_completion(&report_complete); LOCK_BUFFER(testing_hcd->out); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->out, 1); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->out.buf\n"); UNLOCK_BUFFER(testing_hcd->out); goto exit; } testing_hcd->out.buf[0] = testing_hcd->report_type; LOCK_BUFFER(testing_hcd->resp); retval = tcm_hcd->write_message(tcm_hcd, CMD_ENABLE_REPORT, testing_hcd->out.buf, 1, &testing_hcd->resp.buf, &testing_hcd->resp.buf_size, &testing_hcd->resp.data_length, NULL, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write command %s\n", STR(CMD_ENABLE_REPORT)); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); goto exit; } UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); completed = false; timeout = REPORT_TIMEOUT_MS * num_of_reports; retval = wait_for_completion_timeout(&report_complete, msecs_to_jiffies(timeout)); if (retval == 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Timed out waiting for report collection\n"); } else { completed = true; } LOCK_BUFFER(testing_hcd->out); testing_hcd->out.buf[0] = testing_hcd->report_type; LOCK_BUFFER(testing_hcd->resp); retval = tcm_hcd->write_message(tcm_hcd, CMD_DISABLE_REPORT, testing_hcd->out.buf, 1, &testing_hcd->resp.buf, &testing_hcd->resp.buf_size, &testing_hcd->resp.data_length, NULL, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to write command %s\n", STR(CMD_DISABLE_REPORT)); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); goto exit; } UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->out); if (completed) retval = 0; else retval = -EIO; exit: testing_hcd->report_type = 0; return retval; } static void testing_get_frame_size_words(unsigned int *size, bool image_only) { unsigned int rows; unsigned int cols; unsigned int hybrid; unsigned int buttons; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); hybrid = le2_to_uint(app_info->has_hybrid_data); buttons = le2_to_uint(app_info->num_of_buttons); *size = rows * cols; if (!image_only) { if (hybrid) *size += rows + cols; *size += buttons; } } static void testing_doze_frame_output(unsigned int rows, unsigned int cols) { int retval; unsigned int data_size; unsigned int header_size; unsigned int output_size; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; header_size = 2; data_size = rows * cols; if (le2_to_uint(app_info->num_of_buttons)) data_size++; output_size = header_size + data_size * 2; LOCK_BUFFER(testing_hcd->output); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->output, output_size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->output.buf\n"); UNLOCK_BUFFER(testing_hcd->output); return; } testing_hcd->output.buf[0] = rows; testing_hcd->output.buf[1] = cols; output_size = header_size; LOCK_BUFFER(testing_hcd->resp); retval = secure_memcpy(testing_hcd->output.buf + header_size, testing_hcd->output.buf_size - header_size, testing_hcd->resp.buf, testing_hcd->resp.buf_size, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy test data\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } output_size += testing_hcd->resp.data_length; UNLOCK_BUFFER(testing_hcd->resp); testing_hcd->output.data_length = output_size; UNLOCK_BUFFER(testing_hcd->output); } static void testing_standard_frame_output(bool image_only) { int retval; unsigned int data_size; unsigned int header_size; unsigned int output_size; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; testing_get_frame_size_words(&data_size, image_only); header_size = sizeof(app_info->num_of_buttons) + sizeof(app_info->num_of_image_rows) + sizeof(app_info->num_of_image_cols) + sizeof(app_info->has_hybrid_data); output_size = header_size + data_size * 2; LOCK_BUFFER(testing_hcd->output); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->output, output_size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->output.buf\n"); UNLOCK_BUFFER(testing_hcd->output); return; } retval = secure_memcpy(testing_hcd->output.buf, testing_hcd->output.buf_size, &app_info->num_of_buttons[0], header_size, header_size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy header data\n"); UNLOCK_BUFFER(testing_hcd->output); return; } output_size = header_size; LOCK_BUFFER(testing_hcd->resp); retval = secure_memcpy(testing_hcd->output.buf + header_size, testing_hcd->output.buf_size - header_size, testing_hcd->resp.buf, testing_hcd->resp.buf_size, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy test data\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } output_size += testing_hcd->resp.data_length; UNLOCK_BUFFER(testing_hcd->resp); testing_hcd->output.data_length = output_size; UNLOCK_BUFFER(testing_hcd->output); } static int testing_dynamic_range_doze(void) { int retval; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int data; unsigned int rows; unsigned int cols; unsigned int data_size; unsigned int limits_rows; unsigned int limits_cols; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; cols = le2_to_uint(app_info->num_of_image_cols); retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE_DOZE); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); data_size = testing_hcd->resp.data_length / 2; if (le2_to_uint(app_info->num_of_buttons)) data_size--; if (data_size % cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Invalid max number of rows per burst\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } rows = data_size / cols; limits_rows = ARRAY_SIZE(drt_hi_limits); limits_cols = ARRAY_SIZE(drt_hi_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(drt_lo_limits); limits_cols = ARRAY_SIZE(drt_lo_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = le2_to_uint(&buf[idx * 2]); if (data > drt_hi_limits[row][col] || data < drt_lo_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_doze_frame_output(rows, cols); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_dynamic_range_lpwg(void) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_IN_WAKEUP_GESTURE_MODE, 1); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to enable wakeup gesture mode\n"); return retval; } retval = testing_dynamic_range(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do dynamic range test\n"); return retval; } retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_IN_WAKEUP_GESTURE_MODE, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to disable wakeup gesture mode\n"); return retval; } return 0; } static int testing_dynamic_range(void) { int retval; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int data; unsigned int rows; unsigned int cols; unsigned int limits_rows; unsigned int limits_cols; unsigned int frame_size_words; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); testing_get_frame_size_words(&frame_size_words, false); retval = testing_run_prod_test_item(TEST_DYNAMIC_RANGE); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (frame_size_words != testing_hcd->resp.data_length / 2) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Frame size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(drt_hi_limits); limits_cols = ARRAY_SIZE(drt_hi_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(drt_lo_limits); limits_cols = ARRAY_SIZE(drt_lo_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = le2_to_uint(&buf[idx * 2]); if (data > drt_hi_limits[row][col] || data < drt_lo_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_standard_frame_output(false); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_noise_doze(void) { int retval; short data; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int data_size; unsigned int limits_rows; unsigned int limits_cols; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; cols = le2_to_uint(app_info->num_of_image_cols); retval = testing_run_prod_test_item(TEST_NOISE_DOZE); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); data_size = testing_hcd->resp.data_length / 2; if (le2_to_uint(app_info->num_of_buttons)) data_size--; if (data_size % cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Invalid max number of rows per burst\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } rows = data_size / cols; limits_rows = ARRAY_SIZE(noise_limits); limits_cols = ARRAY_SIZE(noise_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = (short)le2_to_uint(&buf[idx * 2]); if (data > noise_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_doze_frame_output(rows, cols); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_noise_lpwg(void) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_IN_WAKEUP_GESTURE_MODE, 1); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to enable wakeup gesture mode\n"); return retval; } retval = testing_noise(); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do noise test\n"); return retval; } retval = tcm_hcd->set_dynamic_config(tcm_hcd, DC_IN_WAKEUP_GESTURE_MODE, 0); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to disable wakeup gesture mode\n"); return retval; } return 0; } static int testing_noise(void) { int retval; short data; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int limits_rows; unsigned int limits_cols; unsigned int frame_size_words; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); testing_get_frame_size_words(&frame_size_words, false); retval = testing_run_prod_test_item(TEST_NOISE); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (frame_size_words != testing_hcd->resp.data_length / 2) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Frame size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(noise_limits); limits_cols = ARRAY_SIZE(noise_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = (short)le2_to_uint(&buf[idx * 2]); if (data > noise_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_standard_frame_output(false); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static void testing_open_short_detector_output(void) { int retval; unsigned int rows; unsigned int cols; unsigned int data_size; unsigned int header_size; unsigned int output_size; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); data_size = (rows * cols + 7) / 8; header_size = sizeof(app_info->num_of_buttons) + sizeof(app_info->num_of_image_rows) + sizeof(app_info->num_of_image_cols) + sizeof(app_info->has_hybrid_data); output_size = header_size + data_size * 2; LOCK_BUFFER(testing_hcd->output); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->output, output_size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->output.buf\n"); UNLOCK_BUFFER(testing_hcd->output); return; } retval = secure_memcpy(testing_hcd->output.buf, testing_hcd->output.buf_size, &app_info->num_of_buttons[0], header_size, header_size); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy header data\n"); UNLOCK_BUFFER(testing_hcd->output); return; } output_size = header_size; LOCK_BUFFER(testing_hcd->resp); retval = secure_memcpy(testing_hcd->output.buf + header_size, testing_hcd->output.buf_size - header_size, testing_hcd->resp.buf, testing_hcd->resp.buf_size, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy test data\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } output_size += testing_hcd->resp.data_length; UNLOCK_BUFFER(testing_hcd->resp); testing_hcd->output.data_length = output_size; UNLOCK_BUFFER(testing_hcd->output); } static int testing_open_short_detector(void) { int retval; unsigned int bit; unsigned int byte; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int data_size; unsigned char *data; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); data_size = (rows * cols + 7) / 8; retval = testing_run_prod_test_item(TEST_OPEN_SHORT_DETECTOR); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (data_size * 2 != testing_hcd->resp.data_length) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Data size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } testing_hcd->result = true; bit = 0; byte = 0; data = &testing_hcd->resp.buf[0]; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { if (data[byte] & (1 << bit)) { testing_hcd->result = false; break; } if (bit++ > 7) { bit = 0; byte++; } } } if (testing_hcd->result == true) { bit = 0; byte = 0; data = &testing_hcd->resp.buf[data_size]; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { if (data[byte] & (1 << bit)) { testing_hcd->result = false; break; } if (bit++ > 7) { bit = 0; byte++; } } } } UNLOCK_BUFFER(testing_hcd->resp); testing_open_short_detector_output(); retval = 0; exit: if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } return retval; } static int testing_pt11(void) { int retval; short data; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int limits_rows; unsigned int limits_cols; unsigned int image_size_words; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); testing_get_frame_size_words(&image_size_words, true); retval = testing_run_prod_test_item(TEST_PT11); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (image_size_words != testing_hcd->resp.data_length / 2) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Image size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(pt11_hi_limits); limits_cols = ARRAY_SIZE(pt11_hi_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(pt11_lo_limits); limits_cols = ARRAY_SIZE(pt11_lo_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = (short)le2_to_uint(&buf[idx * 2]); if (data > pt11_hi_limits[row][col] || data < pt11_lo_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_standard_frame_output(true); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_pt12(void) { int retval; short data; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int limits_rows; unsigned int limits_cols; unsigned int image_size_words; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); testing_get_frame_size_words(&image_size_words, true); retval = testing_run_prod_test_item(TEST_PT12); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (image_size_words != testing_hcd->resp.data_length / 2) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Image size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(pt12_limits); limits_cols = ARRAY_SIZE(pt12_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = (short)le2_to_uint(&buf[idx * 2]); if (data < pt12_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_standard_frame_output(true); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_pt13(void) { int retval; short data; unsigned char *buf; unsigned int idx; unsigned int row; unsigned int col; unsigned int rows; unsigned int cols; unsigned int limits_rows; unsigned int limits_cols; unsigned int image_size_words; struct syna_tcm_app_info *app_info; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; app_info = &tcm_hcd->app_info; rows = le2_to_uint(app_info->num_of_image_rows); cols = le2_to_uint(app_info->num_of_image_cols); testing_get_frame_size_words(&image_size_words, true); retval = testing_run_prod_test_item(TEST_PT13); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); if (image_size_words != testing_hcd->resp.data_length / 2) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Image size mismatch\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } limits_rows = ARRAY_SIZE(pt13_limits); limits_cols = ARRAY_SIZE(pt13_limits[0]); if (rows > limits_rows || cols > limits_cols) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); retval = -EINVAL; goto exit; } idx = 0; buf = testing_hcd->resp.buf; testing_hcd->result = true; for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { data = (short)le2_to_uint(&buf[idx * 2]); if (data < pt13_limits[row][col]) { testing_hcd->result = false; break; } idx++; } } UNLOCK_BUFFER(testing_hcd->resp); testing_standard_frame_output(true); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static int testing_reset_open(void) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata; if (bdata->reset_gpio < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Hardware reset unavailable\n"); return -EINVAL; } mutex_lock(&tcm_hcd->reset_mutex); tcm_hcd->update_watchdog(tcm_hcd, false); gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); msleep(bdata->reset_active_ms); gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); msleep(bdata->reset_delay_ms); tcm_hcd->update_watchdog(tcm_hcd, true); mutex_unlock(&tcm_hcd->reset_mutex); if (tcm_hcd->id_info.mode == MODE_APPLICATION) { retval = tcm_hcd->switch_mode(tcm_hcd, FW_MODE_BOOTLOADER); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to enter bootloader mode\n"); return retval; } } else { retval = tcm_hcd->identify(tcm_hcd, false); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do identification\n"); goto run_app_firmware; } } if (tcm_hcd->boot_info.last_reset_reason == reset_open_limit) testing_hcd->result = true; else testing_hcd->result = false; retval = 0; run_app_firmware: if (tcm_hcd->switch_mode(tcm_hcd, FW_MODE_APPLICATION) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run application firmware\n"); } return retval; } static void testing_lockdown_output(void) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; LOCK_BUFFER(testing_hcd->output); LOCK_BUFFER(testing_hcd->resp); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->output, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->output.buf\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } retval = secure_memcpy(testing_hcd->output.buf, testing_hcd->output.buf_size, testing_hcd->resp.buf, testing_hcd->resp.buf_size, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy test data\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } testing_hcd->output.data_length = testing_hcd->resp.data_length; UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); } static int testing_lockdown(void) { int retval; unsigned int idx; unsigned int lockdown_size; unsigned int limits_size; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; if (tcm_hcd->read_flash_data == NULL) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Unable to read from flash\n"); return -EINVAL; } LOCK_BUFFER(testing_hcd->resp); retval = tcm_hcd->read_flash_data(CUSTOM_OTP, true, &testing_hcd->resp); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to read lockdown data\n"); UNLOCK_BUFFER(testing_hcd->resp); return retval; } lockdown_size = testing_hcd->resp.data_length; limits_size = sizeof(lockdown_limits) / sizeof(*lockdown_limits); if (lockdown_size != limits_size) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Mismatching limits data\n"); UNLOCK_BUFFER(testing_hcd->resp); return -EINVAL; } testing_hcd->result = true; for (idx = 0; idx < lockdown_size; idx++) { if (testing_hcd->resp.buf[idx] != lockdown_limits[idx]) { testing_hcd->result = false; break; } } UNLOCK_BUFFER(testing_hcd->resp); testing_lockdown_output(); return 0; } static void testing_trx_output(void) { int retval; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; LOCK_BUFFER(testing_hcd->output); LOCK_BUFFER(testing_hcd->resp); retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->output, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->output.buf\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } retval = secure_memcpy(testing_hcd->output.buf, testing_hcd->output.buf_size, testing_hcd->resp.buf, testing_hcd->resp.buf_size, testing_hcd->resp.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy test data\n"); UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); return; } testing_hcd->output.data_length = testing_hcd->resp.data_length; UNLOCK_BUFFER(testing_hcd->resp); UNLOCK_BUFFER(testing_hcd->output); } static int testing_trx(enum test_code test_code) { int retval; unsigned char pass_vector; unsigned int idx; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; switch (test_code) { case TEST_TRX_TRX_SHORTS: case TEST_TRX_GROUND_SHORTS: pass_vector = 0xff; break; case TEST_TRX_SENSOR_OPENS: pass_vector = 0x00; break; default: return -EINVAL; } retval = testing_run_prod_test_item(test_code); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to run test\n"); goto exit; } LOCK_BUFFER(testing_hcd->resp); testing_hcd->result = true; for (idx = 0; idx < testing_hcd->resp.data_length; idx++) { if (testing_hcd->resp.buf[idx] != pass_vector) { testing_hcd->result = false; break; } } UNLOCK_BUFFER(testing_hcd->resp); testing_trx_output(); retval = 0; exit: if (tcm_hcd->features.dual_firmware) { if (tcm_hcd->reset(tcm_hcd, false, true) < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to do reset\n"); } } return retval; } static void testing_report(void) { int retval; unsigned int offset; unsigned int report_size; struct syna_tcm_hcd *tcm_hcd = testing_hcd->tcm_hcd; report_size = tcm_hcd->report.buffer.data_length; LOCK_BUFFER(testing_hcd->report); if (testing_hcd->report_index == 0) { retval = syna_tcm_alloc_mem(tcm_hcd, &testing_hcd->report, report_size * testing_hcd->num_of_reports); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd->report.buf\n"); UNLOCK_BUFFER(testing_hcd->report); return; } } if (testing_hcd->report_index < testing_hcd->num_of_reports) { offset = report_size * testing_hcd->report_index; retval = secure_memcpy(testing_hcd->report.buf + offset, testing_hcd->report.buf_size - offset, tcm_hcd->report.buffer.buf, tcm_hcd->report.buffer.buf_size, tcm_hcd->report.buffer.data_length); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to copy report data\n"); UNLOCK_BUFFER(testing_hcd->report); return; } testing_hcd->report_index++; testing_hcd->report.data_length += report_size; } UNLOCK_BUFFER(testing_hcd->report); if (testing_hcd->report_index == testing_hcd->num_of_reports) complete(&report_complete); } static int testing_init(struct syna_tcm_hcd *tcm_hcd) { int retval; int idx; testing_hcd = kzalloc(sizeof(*testing_hcd), GFP_KERNEL); if (!testing_hcd) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to allocate memory for testing_hcd\n"); return -ENOMEM; } testing_hcd->tcm_hcd = tcm_hcd; testing_hcd->collect_reports = testing_collect_reports; INIT_BUFFER(testing_hcd->out, false); INIT_BUFFER(testing_hcd->resp, false); INIT_BUFFER(testing_hcd->report, false); INIT_BUFFER(testing_hcd->process, false); INIT_BUFFER(testing_hcd->output, false); testing_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME, tcm_hcd->sysfs_dir); if (!testing_hcd->sysfs_dir) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to create sysfs directory\n"); retval = -EINVAL; goto err_sysfs_create_dir; } for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) { retval = sysfs_create_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to create sysfs file\n"); goto err_sysfs_create_file; } } retval = sysfs_create_bin_file(testing_hcd->sysfs_dir, &bin_attr); if (retval < 0) { LOG_ERR(tcm_hcd->pdev->dev.parent, "Failed to create sysfs bin file\n"); goto err_sysfs_create_bin_file; } return 0; err_sysfs_create_bin_file: err_sysfs_create_file: for (idx--; idx >= 0; idx--) sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); kobject_put(testing_hcd->sysfs_dir); err_sysfs_create_dir: RELEASE_BUFFER(testing_hcd->output); RELEASE_BUFFER(testing_hcd->process); RELEASE_BUFFER(testing_hcd->report); RELEASE_BUFFER(testing_hcd->resp); RELEASE_BUFFER(testing_hcd->out); kfree(testing_hcd); testing_hcd = NULL; return retval; } static int testing_remove(struct syna_tcm_hcd *tcm_hcd) { int idx; if (!testing_hcd) goto exit; sysfs_remove_bin_file(testing_hcd->sysfs_dir, &bin_attr); for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) sysfs_remove_file(testing_hcd->sysfs_dir, &(*attrs[idx]).attr); kobject_put(testing_hcd->sysfs_dir); RELEASE_BUFFER(testing_hcd->output); RELEASE_BUFFER(testing_hcd->process); RELEASE_BUFFER(testing_hcd->report); RELEASE_BUFFER(testing_hcd->resp); RELEASE_BUFFER(testing_hcd->out); kfree(testing_hcd); testing_hcd = NULL; exit: complete(&testing_remove_complete); return 0; } static int testing_reset(struct syna_tcm_hcd *tcm_hcd) { int retval; if (!testing_hcd) { retval = testing_init(tcm_hcd); return retval; } return 0; } static int testing_syncbox(struct syna_tcm_hcd *tcm_hcd) { if (!testing_hcd) return 0; if (tcm_hcd->report.id == testing_hcd->report_type) testing_report(); return 0; } static struct syna_tcm_module_cb testing_module = { .type = TCM_TESTING, .init = testing_init, .remove = testing_remove, .syncbox = testing_syncbox, .asyncbox = NULL, .reset = testing_reset, .suspend = NULL, .resume = NULL, .early_suspend = NULL, }; static int __init testing_module_init(void) { return syna_tcm_add_module(&testing_module, true); } static void __exit testing_module_exit(void) { syna_tcm_add_module(&testing_module, false); wait_for_completion(&testing_remove_complete); } module_init(testing_module_init); module_exit(testing_module_exit); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics TCM Testing Module"); MODULE_LICENSE("GPL v2");