blob: b1921f779c725313e027482e3de11a2f1316ac79 [file] [log] [blame]
/*
* Synaptics TCM touchscreen driver
*
* Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
*
* Copyright (C) 2017-2019 Scott Lin <scott.lin@tw.synaptics.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
* DOLLARS.
*/
#include <linux/gpio.h>
#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) { \
LOGE(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_TRX_TRX_SHORTS = 0,
TEST_TRX_SENSOR_OPENS = 1,
TEST_TRX_GROUND_SHORTS = 2,
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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to enable wakeup gesture mode\n");
return retval;
}
retval = testing_dynamic_range();
if (retval < 0) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to enable wakeup gesture mode\n");
return retval;
}
retval = testing_noise();
if (retval < 0) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for 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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to enter bootloader mode\n");
return retval;
}
} else {
retval = tcm_hcd->identify(tcm_hcd, false);
if (retval < 0) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for 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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for 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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for 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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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) {
LOGE(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");