/* * Goodix Touchscreen Driver * Copyright (C) 2020 - 2021 Goodix, Inc. * * 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 a reference * to you, when you are integrating the GOODiX's CTP IC into your system, * 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. * */ #include "goodix_ts_core.h" #include #include #include #include #include #include #include #define PROC_NODE_NAME "goodix_ts/get_rawdata" #define JITTER1_TEST_ITEM "jitter100-test" #define JITTER2_TEST_ITEM "jitter1000-test" #define SRAM_TEST_ITEM "sram-test" #define SHORT_TEST_ITEM "short-test" #define OPEN_TEST_ITEM "open-test" #define SEC_TEST_OK 0 #define SEC_TEST_NG 1 #define MAX_TEST_TIME_MS 15000 #define DEFAULT_TEST_TIME_MS 7000 #define CHN_VDD 0xFF #define CHN_GND 0x7F #define DRV_CHANNEL_FLAG 0x80 extern struct goodix_module goodix_modules; static bool module_initialized; static u32 short_parms[] = {10, 800, 800, 800, 800, 800, 30}; typedef struct __attribute__((packed)) { u8 result; u8 drv_drv_num; u8 sen_sen_num; u8 drv_sen_num; u8 drv_gnd_avdd_num; u8 sen_gnd_avdd_num; u16 checksum; } test_result_t; struct goodix_jitter_data { short mutualDiffMax; short mutualDiffMin; short selfDiffMax; short selfDiffMin; /* 1000 frames */ short min_of_min_matrix; short max_of_min_matrix; short min_of_max_matrix; short max_of_max_matrix; short min_of_avg_matrix; short max_of_avg_matrix; }; static int ts_test_read(struct goodix_ts_core *cd, unsigned int addr, unsigned char *data, unsigned int len) { return cd->bus->read(cd->bus->dev, addr, data, len); } static int ts_test_write(struct goodix_ts_core *cd, unsigned int addr, unsigned char *data, unsigned int len) { return cd->bus->write(cd->bus->dev, addr, data, len); } static void goodix_data_statistics(s16 *data, int data_size, int *max, int *min) { int i; *min = data[0]; *max = data[0]; for (i = 0; i < data_size; i++) { *max = *max < data[i] ? data[i] : *max; *min = *min > data[i] ? data[i] : *min; } } static int cal_cha_to_cha_res(struct goodix_ts_core *cd, int v1, int v2) { if (cd->bus->ic_type == IC_TYPE_BERLIN_B) return (v1 - v2) * 74 / v2 + 20; else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) return (v1 / v2 - 1) * 55 + 45; else { ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); return (v1 - v2) * 74 / v2 + 20; } } static int cal_cha_to_avdd_res(struct goodix_ts_core *cd, int v1, int v2) { if (cd->bus->ic_type == IC_TYPE_BERLIN_B) return 64 * (2 * v2 - 25) * 99 / v1 - 60; else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) return 64 * (2 * v2 - 25) * 93 / v1 - 20; else { ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); return 64 * (2 * v2 - 25) * 99 / v1 - 60; } } static int cal_cha_to_gnd_res(struct goodix_ts_core *cd, int v) { if (cd->bus->ic_type == IC_TYPE_BERLIN_B) return 150500 / v - 60; else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) return 120000 / v - 16; else { ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); return 150500 / v - 60; } } static u32 map_die2pin(struct goodix_ts_core *cd, u32 chn_num) { int i = 0; u32 res = 255; int max_drv_num = cd->max_drv_num; int max_sen_num = cd->max_sen_num; if (chn_num & DRV_CHANNEL_FLAG) chn_num = (chn_num & ~DRV_CHANNEL_FLAG) + max_sen_num; for (i = 0; i < max_sen_num; i++) { if ((u8)cd->sen_map[i] == chn_num) { res = i; break; } } /* res != 255 mean found the corresponding channel num */ if (res != 255) return res; /* if cannot find in SenMap try find in DrvMap */ for (i = 0; i < max_drv_num; i++) { if ((u8)cd->drv_map[i] == chn_num) { res = i; break; } } if (i >= max_drv_num) ts_err("Faild found corrresponding channel num:%d", chn_num); else res |= DRV_CHANNEL_FLAG; return res; } #define SHORT_TEST_RUN_REG 0x10400 #define SHORT_TEST_RUN_FLAG 0xAA #define INSPECT_FW_SWITCH_CMD 0x85 static int goodix_brl_short_test_prepare(struct goodix_ts_core *cd) { struct goodix_ts_cmd tmp_cmd; int ret; int retry = 5; u8 status; ts_info("short test prepare IN"); tmp_cmd.len = 4; tmp_cmd.cmd = INSPECT_FW_SWITCH_CMD; ret = cd->hw_ops->send_cmd(cd, &tmp_cmd); if (ret < 0) { ts_err("send test mode failed"); return ret; } while (retry--) { sec_delay(40); ret = cd->hw_ops->read(cd, SHORT_TEST_RUN_REG, &status, 1); if (!ret && status == SHORT_TEST_RUN_FLAG) return 0; ts_info("short_mode_status=0x%02x ret=%d", status, ret); } return -EINVAL; } static int gdix_check_tx_tx_shortcircut(struct goodix_ts_core *cd, u8 short_ch_num) { int ret = 0, err = 0; u32 r_threshold = 0, short_r = 0; int size = 0, i = 0, j = 0; u16 adc_signal = 0; u8 master_pin_num, slave_pin_num; u8 *data_buf; u32 data_reg = cd->drv_drv_reg; int max_drv_num = cd->max_drv_num; int max_sen_num = cd->max_sen_num; u16 self_capdata, short_die_num = 0; size = 4 + max_drv_num * 2 + 2; data_buf = kzalloc(size, GFP_KERNEL); if (!data_buf) { ts_err("Failed to alloc memory"); return -ENOMEM; } /* drv&drv shortcircut check */ for (i = 0; i < short_ch_num; i++) { ret = ts_test_read(cd, data_reg, data_buf, size); if (ret < 0) { ts_err("Failed read Drv-to-Drv short rawdata"); err = -EINVAL; break; } if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { ts_err("Drv-to-Drv adc data checksum error"); err = -EINVAL; break; } r_threshold = short_parms[1]; short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); short_die_num -= max_sen_num; if (short_die_num >= max_drv_num) { ts_info("invalid short pad num:%d", short_die_num + max_sen_num); continue; } /* TODO: j start position need recheck */ self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); if (self_capdata == 0xffff || self_capdata == 0) { ts_info("invalid self_capdata:0x%x", self_capdata); continue; } for (j = short_die_num + 1; j < max_drv_num; j++) { adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); if (adc_signal < short_parms[0]) continue; short_r = (u32)cal_cha_to_cha_res(cd, self_capdata, adc_signal); if (short_r < r_threshold) { master_pin_num = map_die2pin(cd, short_die_num + max_sen_num); slave_pin_num = map_die2pin(cd, j + max_sen_num); if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { ts_info("WARNNING invalid pin"); continue; } ts_err("short circut:R=%dK,R_Threshold=%dK", short_r, r_threshold); ts_err("%s%d--%s%d shortcircut", (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (master_pin_num & ~DRV_CHANNEL_FLAG), (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (slave_pin_num & ~DRV_CHANNEL_FLAG)); /* save fail ch on cd->test_data.open_short_test_trx_result[] */ { int offset, num; offset = (master_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (master_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (master_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("master_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); offset = (slave_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (slave_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (slave_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("slave_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); } err = -EINVAL; } } data_reg += size; } kfree(data_buf); return err; } static int gdix_check_rx_rx_shortcircut(struct goodix_ts_core *cd, u8 short_ch_num) { int ret = 0, err = 0; u32 r_threshold = 0, short_r = 0; int size = 0, i = 0, j = 0; u16 adc_signal = 0; u8 master_pin_num, slave_pin_num; u8 *data_buf; u32 data_reg = cd->sen_sen_reg; int max_sen_num = cd->max_sen_num; u16 self_capdata, short_die_num = 0; size = 4 + max_sen_num * 2 + 2; data_buf = kzalloc(size, GFP_KERNEL); if (!data_buf) { ts_err("Failed to alloc memory"); return -ENOMEM; } /* drv&drv shortcircut check */ for (i = 0; i < short_ch_num; i++) { ret = ts_test_read(cd, data_reg, data_buf, size); if (ret) { ts_err("Failed read Sen-to-Sen short rawdata"); err = -EINVAL; break; } if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { ts_err("Sen-to-Sen adc data checksum error"); err = -EINVAL; break; } r_threshold = short_parms[3]; short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); if (short_die_num >= max_sen_num) { ts_info("invalid short pad num:%d", short_die_num); continue; } /* TODO: j start position need recheck */ self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); if (self_capdata == 0xffff || self_capdata == 0) { ts_info("invalid self_capdata:0x%x", self_capdata); continue; } for (j = short_die_num + 1; j < max_sen_num; j++) { adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); if (adc_signal < short_parms[0]) continue; short_r = (u32)cal_cha_to_cha_res(cd, self_capdata, adc_signal); if (short_r < r_threshold) { master_pin_num = map_die2pin(cd, short_die_num); slave_pin_num = map_die2pin(cd, j); if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { ts_info("WARNNING invalid pin"); continue; } ts_err("short circut:R=%dK,R_Threshold=%dK", short_r, r_threshold); ts_err("%s%d--%s%d shortcircut", (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (master_pin_num & ~DRV_CHANNEL_FLAG), (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (slave_pin_num & ~DRV_CHANNEL_FLAG)); /* save fail ch on cd->test_data.open_short_test_trx_result[] */ { int offset, num; offset = (master_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (master_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (master_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("master_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); offset = (slave_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (slave_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (slave_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("slave_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); } err = -EINVAL; } } data_reg += size; } kfree(data_buf); return err; } static int gdix_check_tx_rx_shortcircut(struct goodix_ts_core *cd, u8 short_ch_num) { int ret = 0, err = 0; u32 r_threshold = 0, short_r = 0; int size = 0, i = 0, j = 0; u16 adc_signal = 0; u8 master_pin_num, slave_pin_num; u8 *data_buf = NULL; u32 data_reg = cd->drv_sen_reg; int max_drv_num = cd->max_drv_num; int max_sen_num = cd->max_sen_num; u16 self_capdata, short_die_num = 0; size = 4 + max_drv_num * 2 + 2; data_buf = kzalloc(size, GFP_KERNEL); if (!data_buf) { ts_err("Failed to alloc memory"); return -ENOMEM; } /* drv&sen shortcircut check */ for (i = 0; i < short_ch_num; i++) { ret = ts_test_read(cd, data_reg, data_buf, size); if (ret) { ts_err("Failed read Drv-to-Sen short rawdata"); err = -EINVAL; break; } if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { ts_err("Drv-to-Sen adc data checksum error"); err = -EINVAL; break; } r_threshold = short_parms[2]; short_die_num = le16_to_cpup((__le16 *)&data_buf[0]); if (short_die_num >= max_sen_num) { ts_info("invalid short pad num:%d", short_die_num); continue; } /* TODO: j start position need recheck */ self_capdata = le16_to_cpup((__le16 *)&data_buf[2]); if (self_capdata == 0xffff || self_capdata == 0) { ts_info("invalid self_capdata:0x%x", self_capdata); continue; } for (j = 0; j < max_drv_num; j++) { adc_signal = le16_to_cpup((__le16 *)&data_buf[4 + j * 2]); if (adc_signal < short_parms[0]) continue; short_r = (u32)cal_cha_to_cha_res(cd, self_capdata, adc_signal); if (short_r < r_threshold) { master_pin_num = map_die2pin(cd, short_die_num); slave_pin_num = map_die2pin(cd, j + max_sen_num); if (master_pin_num == 0xFF || slave_pin_num == 0xFF) { ts_info("WARNNING invalid pin"); continue; } ts_err("short circut:R=%dK,R_Threshold=%dK", short_r, r_threshold); ts_err("%s%d--%s%d shortcircut", (master_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (master_pin_num & ~DRV_CHANNEL_FLAG), (slave_pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (slave_pin_num & ~DRV_CHANNEL_FLAG)); /* save fail ch on cd->test_data.open_short_test_trx_result[] */ { int offset, num; offset = (master_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (master_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (master_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("master_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); offset = (slave_pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (slave_pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (slave_pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("slave_pin_num=%d, offset=%d, num=%d", master_pin_num, offset, num); } err = -EINVAL; } } data_reg += size; } kfree(data_buf); return err; } static int gdix_check_resistance_to_gnd(struct goodix_ts_core *cd, u16 adc_signal, u32 pos) { long r = 0; u16 r_th = 0, avdd_value = 0; u16 chn_id_tmp = 0; u8 pin_num = 0; unsigned short short_type; int max_drv_num = cd->max_drv_num; int max_sen_num = cd->max_sen_num; avdd_value = short_parms[6]; short_type = adc_signal & 0x8000; adc_signal &= ~0x8000; if (adc_signal == 0) adc_signal = 1; if (short_type == 0) { /* short to GND */ r = cal_cha_to_gnd_res(cd, adc_signal); } else { /* short to VDD */ r = cal_cha_to_avdd_res(cd, adc_signal, avdd_value); } if (pos < max_drv_num) r_th = short_parms[4]; else r_th = short_parms[5]; chn_id_tmp = pos; if (chn_id_tmp < max_drv_num) chn_id_tmp += max_sen_num; else chn_id_tmp -= max_drv_num; if (r < r_th) { pin_num = map_die2pin(cd, chn_id_tmp); ts_err("%s%d shortcircut to %s,R=%ldK,R_Threshold=%dK", (pin_num & DRV_CHANNEL_FLAG) ? "DRV" : "SEN", (pin_num & ~DRV_CHANNEL_FLAG), short_type ? "VDD" : "GND", r, r_th); /* save fail ch on cd->test_data.open_short_test_trx_result[] */ { int offset, num; offset = (pin_num & DRV_CHANNEL_FLAG) ? 0 : DRV_CHAN_BYTES; offset += (pin_num & ~DRV_CHANNEL_FLAG) / 8; num = (pin_num & ~DRV_CHANNEL_FLAG) % 8; cd->test_data.open_short_test_trx_result[offset] |= (1 << num); ts_err("pin_num=%d, offset=%d, num=%d", pin_num, offset, num); } return -EINVAL; } return 0; } static int gdix_check_gndvdd_shortcircut(struct goodix_ts_core *cd) { int ret = 0, err = 0; int size = 0, i = 0; u16 adc_signal = 0; u32 data_reg = cd->diff_code_reg; u8 *data_buf = NULL; int max_drv_num = cd->max_drv_num; int max_sen_num = cd->max_sen_num; size = (max_drv_num + max_sen_num) * 2 + 2; data_buf = kzalloc(size, GFP_KERNEL); if (!data_buf) { ts_err("Failed to alloc memory"); return -ENOMEM; } /* read diff code, diff code will be used to calculate * resistance between channel and GND */ ret = ts_test_read(cd, data_reg, data_buf, size); if (ret < 0) { ts_err("Failed read to-gnd rawdata"); err = -EINVAL; goto err_out; } if (checksum_cmp(data_buf, size, CHECKSUM_MODE_U8_LE)) { ts_err("diff code checksum error"); err = -EINVAL; goto err_out; } for (i = 0; i < max_drv_num + max_sen_num; i++) { adc_signal = le16_to_cpup((__le16 *)&data_buf[i * 2]); ret = gdix_check_resistance_to_gnd(cd, adc_signal, i); if (ret != 0) { ts_err("Resistance to-gnd/vdd short"); err = ret; } } err_out: kfree(data_buf); return err; } static int goodix_short_analysis(struct goodix_ts_core *cd) { int ret; int err = 0; test_result_t test_result; unsigned int result_reg = cd->short_test_result_reg; ret = cd->hw_ops->read(cd, result_reg, (u8 *)&test_result, sizeof(test_result)); if (ret < 0) { ts_err("Read TEST_RESULT_REG failed"); return ret; } if (checksum_cmp((u8 *)&test_result, sizeof(test_result), CHECKSUM_MODE_U8_LE)) { ts_err("shrot result checksum err"); return -EINVAL; } if (!(test_result.result & 0x0F)) { ts_raw_info(">>>>> No shortcircut"); cd->test_data.info[SEC_SHORT_TEST].data[0] = SEC_TEST_OK; } else { ts_raw_info("short flag 0x%02x, drv&drv:%d, sen&sen:%d, drv&sen:%d, drv/GNDVDD:%d, sen/GNDVDD:%d", test_result.result, test_result.drv_drv_num, test_result.sen_sen_num, test_result.drv_sen_num, test_result.drv_gnd_avdd_num, test_result.sen_gnd_avdd_num); /* get short channel and resistance */ if (test_result.drv_drv_num) err |= gdix_check_tx_tx_shortcircut(cd, test_result.drv_drv_num); if (test_result.sen_sen_num) err |= gdix_check_rx_rx_shortcircut(cd, test_result.sen_sen_num); if (test_result.drv_sen_num) err |= gdix_check_tx_rx_shortcircut(cd, test_result.drv_sen_num); if (test_result.drv_gnd_avdd_num || test_result.sen_gnd_avdd_num) err |= gdix_check_gndvdd_shortcircut(cd); if (err) cd->test_data.info[SEC_SHORT_TEST].data[0] = SEC_TEST_NG; else cd->test_data.info[SEC_SHORT_TEST].data[0] = SEC_TEST_OK; } cd->test_data.info[SEC_SHORT_TEST].isFinished = true; if (err) return -EINVAL; return 0; } #define JITTER_LEN 24 static int goodix_brl_jitter_test(struct goodix_ts_core *cd, u8 type) { int ret; int retry; struct goodix_ts_cmd cmd; struct goodix_jitter_data raw_data; unsigned int jitter_addr = cd->production_test_addr; u8 buf[JITTER_LEN]; /* switch test config */ cmd.len = 5; cmd.cmd = cd->switch_cfg_cmd; cmd.data[0] = 1; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("switch test config failed"); return ret; } sec_delay(20); cmd.cmd = 0x88; cmd.data[0] = type; cmd.len = 5; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("send jitter test cmd failed"); return ret; } if (type == JITTER_100_FRAMES) sec_delay(100); else sec_delay(8000); retry = 20; while (retry--) { ret = cd->hw_ops->read(cd, jitter_addr, buf, (int)sizeof(buf)); if (ret < 0) { ts_err("read raw data failed"); return ret; } if (type == JITTER_100_FRAMES) { if (buf[0] == 0xAA && buf[1] == 0xAA) break; } else { if (buf[0] == 0xBB && buf[1] == 0xBB) break; } sec_delay(50); } if (retry < 0) { ts_err("raw data not ready, status = %x%x", buf[0], buf[1]); return -EINVAL; } if (type == JITTER_100_FRAMES) { raw_data.mutualDiffMax = le16_to_cpup((__le16 *)&buf[2]); raw_data.mutualDiffMin = le16_to_cpup((__le16 *)&buf[4]); raw_data.selfDiffMax = le16_to_cpup((__le16 *)&buf[6]); raw_data.selfDiffMin = le16_to_cpup((__le16 *)&buf[8]); cd->test_data.info[SEC_JITTER1_TEST].data[0] = raw_data.mutualDiffMax; cd->test_data.info[SEC_JITTER1_TEST].data[1] = raw_data.mutualDiffMin; cd->test_data.info[SEC_JITTER1_TEST].data[2] = raw_data.selfDiffMax; cd->test_data.info[SEC_JITTER1_TEST].data[3] = raw_data.selfDiffMin; cd->test_data.info[SEC_JITTER1_TEST].isFinished = true; ts_raw_info("mutualdiff[%d, %d] selfdiff[%d, %d]", raw_data.mutualDiffMin, raw_data.mutualDiffMax, raw_data.selfDiffMin, raw_data.selfDiffMax); } else { if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { raw_data.min_of_min_matrix = le16_to_cpup((__le16 *)&buf[10]); raw_data.max_of_min_matrix = le16_to_cpup((__le16 *)&buf[12]); raw_data.min_of_max_matrix = le16_to_cpup((__le16 *)&buf[14]); raw_data.max_of_max_matrix = le16_to_cpup((__le16 *)&buf[16]); raw_data.min_of_avg_matrix = le16_to_cpup((__le16 *)&buf[18]); raw_data.max_of_avg_matrix = le16_to_cpup((__le16 *)&buf[20]); } else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { raw_data.min_of_min_matrix = le16_to_cpup((__le16 *)&buf[2]); raw_data.max_of_min_matrix = le16_to_cpup((__le16 *)&buf[4]); raw_data.min_of_max_matrix = le16_to_cpup((__le16 *)&buf[6]); raw_data.max_of_max_matrix = le16_to_cpup((__le16 *)&buf[8]); raw_data.min_of_avg_matrix = le16_to_cpup((__le16 *)&buf[10]); raw_data.max_of_avg_matrix = le16_to_cpup((__le16 *)&buf[12]); } else { raw_data.min_of_min_matrix = le16_to_cpup((__le16 *)&buf[10]); raw_data.max_of_min_matrix = le16_to_cpup((__le16 *)&buf[12]); raw_data.min_of_max_matrix = le16_to_cpup((__le16 *)&buf[14]); raw_data.max_of_max_matrix = le16_to_cpup((__le16 *)&buf[16]); raw_data.min_of_avg_matrix = le16_to_cpup((__le16 *)&buf[18]); raw_data.max_of_avg_matrix = le16_to_cpup((__le16 *)&buf[20]); ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); } cd->test_data.info[SEC_JITTER2_TEST].data[0] = raw_data.min_of_min_matrix; cd->test_data.info[SEC_JITTER2_TEST].data[1] = raw_data.max_of_min_matrix; cd->test_data.info[SEC_JITTER2_TEST].data[2] = raw_data.min_of_max_matrix; cd->test_data.info[SEC_JITTER2_TEST].data[3] = raw_data.max_of_max_matrix; cd->test_data.info[SEC_JITTER2_TEST].data[4] = raw_data.min_of_avg_matrix; cd->test_data.info[SEC_JITTER2_TEST].data[5] = raw_data.max_of_avg_matrix; cd->test_data.info[SEC_JITTER2_TEST].isFinished = true; ts_raw_info("min_matrix[%d, %d] max_matrix[%d, %d] avg_matrix[%d, %d]", raw_data.min_of_min_matrix, raw_data.max_of_min_matrix, raw_data.min_of_max_matrix, raw_data.max_of_max_matrix, raw_data.min_of_avg_matrix, raw_data.max_of_avg_matrix); } return 0; } int goodix_jitter_test(struct goodix_ts_core *cd, u8 type) { int ret; if (type == JITTER_100_FRAMES) cd->test_data.info[SEC_JITTER1_TEST].item_name = JITTER1_TEST_ITEM; else cd->test_data.info[SEC_JITTER2_TEST].item_name = JITTER2_TEST_ITEM; ret = goodix_brl_jitter_test(cd, type); if (ret < 0) ts_err("jitter test process failed"); return ret; } static int goodix_brl_open_test(struct goodix_ts_core *cd) { int ret; int retry = 20; int i; struct goodix_ts_cmd cmd; unsigned char buf[OPEN_TEST_RESULT_LEN]; cmd.cmd = 0x89; cmd.len = 4; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("send open test cmd failed"); return ret; } sec_delay(400); while (retry--) { ret = cd->hw_ops->read(cd, OPEN_TEST_RESULT, buf, (int)sizeof(buf)); if (ret < 0) { ts_err("read open test result failed"); return ret; } if (buf[0] == 0xAA && buf[1] == 0xAA) break; sec_delay(50); } if (retry < 0) { ts_err("open test not ready, status = %x%x", buf[0], buf[1]); return -EINVAL; } ret = checksum_cmp(buf + 2, OPEN_TEST_RESULT_LEN - 2, CHECKSUM_MODE_U8_LE); if (ret) { ts_err("open test result checksum error %*ph", OPEN_TEST_RESULT_LEN, buf); return -EINVAL; } if (buf[2] == 0) { ts_raw_info("open test pass"); ret = 0; cd->test_data.info[SEC_OPEN_TEST].data[0] = SEC_TEST_OK; } else { ts_raw_info("open test failed"); /* save result data */ for (i = 0; i < OPEN_SHORT_TEST_RESULT_LEN ; i++) { cd->test_data.open_short_test_trx_result[i] = buf[3 + i]; } cd->test_data.info[SEC_OPEN_TEST].data[0] = SEC_TEST_NG; /* DRV[0~55] total 7 bytes */ for (i = 0; i < DRV_CHAN_BYTES; i++) { if (buf[i + 3]) ts_raw_info("DRV[%d~%d] open circuit, ret=0x%X", i * 8, i * 8 + 7, buf[i + 3]); } /* SEN[0~79] total 10 bytes */ for (i = 0; i < SEN_CHAN_BYTES; i++) { if (buf[i + 10]) ts_raw_info("SEN[%d~%d] open circuit, ret=0x%X", i * 8, i * 8 + 7, buf[i + 10]); } ret = -EINVAL; } cd->test_data.info[SEC_OPEN_TEST].isFinished = true; return ret; } static int goodix_brld_open_test(struct goodix_ts_core *cd) { int ret; int retry = 20; int i; struct goodix_ts_cmd cmd; unsigned char buf[24]; unsigned int result_addr = cd->production_test_addr; cmd.cmd = 0x63; cmd.len = 4; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("send open test cmd failed"); return ret; } sec_delay(200); while (retry--) { ret = cd->hw_ops->read(cd, result_addr, buf, (int)sizeof(buf)); if (ret < 0) { ts_err("read open test result failed"); return ret; } if (buf[0] == 0xCC && buf[1] == 0xCC) break; sec_delay(50); } if (retry < 0) { ts_err("open test not ready, status = %x%x", buf[0], buf[1]); return -EINVAL; } ret = checksum_cmp(buf, sizeof(buf), CHECKSUM_MODE_U8_LE); if (ret) { ts_err("open test result checksum error"); return -EINVAL; } if (buf[2] == 0) { ts_raw_info("open test pass"); ret = 0; cd->test_data.info[SEC_OPEN_TEST].data[0] = SEC_TEST_OK; } else { ts_raw_info("open test failed"); /* save result data */ for (i = 0; i < OPEN_SHORT_TEST_RESULT_LEN ; i++) { cd->test_data.open_short_test_trx_result[i] = buf[3 + i]; } cd->test_data.info[SEC_OPEN_TEST].data[0] = SEC_TEST_NG; /* DRV[0~55] total 7 bytes */ for (i = 0; i < DRV_CHAN_BYTES; i++) { if (buf[i + 3]) ts_raw_info("DRV[%d~%d] open circuit, ret=0x%X", i * 8, i * 8 + 7, buf[i + 3]); } /* SEN[0~79] total 10 bytes */ for (i = 0; i < SEN_CHAN_BYTES; i++) { if (buf[i + 10]) ts_raw_info("SEN[%d~%d] open circuit, ret=0x%X", i * 8, i * 8 + 7, buf[i + 10]); } ret = -EINVAL; } cd->test_data.info[SEC_OPEN_TEST].isFinished = true; return ret; } int goodix_open_test(struct goodix_ts_core *cd) { int ret = 0; int retry = 2; struct goodix_ts_cmd temp_cmd; cd->test_data.info[SEC_OPEN_TEST].item_name = OPEN_TEST_ITEM; restart: /* switch test config */ temp_cmd.len = 5; temp_cmd.cmd = cd->switch_cfg_cmd; temp_cmd.data[0] = 1; ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("switch test cfg failed, exit"); return ret; } sec_delay(20); if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { ret = goodix_brl_open_test(cd); } else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { ret = goodix_brld_open_test(cd); } else { ret = goodix_brl_open_test(cd); ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); } if (ret && retry--) { ts_info("open test failed, retry[%d]", 2 - retry); cd->hw_ops->reset(cd, 200); goto restart; } return ret; } static int goodix_brl_sram_test(struct goodix_ts_core *cd) { int ret; int retry; struct goodix_ts_cmd cmd; u8 buf[2]; cmd.cmd = 0x87; cmd.len = 4; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("send SRAM test cmd failed"); return ret; } sec_delay(50); /* FW 1 test */ ret = cd->hw_ops->read(cd, 0xD8D0, buf, 1); if (ret < 0 || buf[0] != 0xAA) { ts_err("FW1 status[0x%02x] != 0xAA", buf[0]); return -EINVAL; } buf[0] = 0; cd->hw_ops->write(cd, 0xD8D0, buf, 1); retry = 20; while (retry--) { sec_delay(50); ret = cd->hw_ops->read(cd, 0xD8D0, buf, 1); if (!ret && buf[0] == 0xBB) break; } if (retry < 0) { ts_err("FW1 test not ready, status = 0x%02x", buf[0]); return -EINVAL; } /* FW 2 test */ buf[0] = 0; cd->hw_ops->write(cd, 0xD8D0, buf, 1); sec_delay(50); ret = cd->hw_ops->read(cd, 0xD8D0, buf, 1); if (ret < 0 || buf[0] != 0xAA) { ts_err("FW2 status[0x%02x] != 0xAA", buf[0]); return -EINVAL; } buf[0] = 0; cd->hw_ops->write(cd, 0xD8D0, buf, 1); retry = 20; while (retry--) { sec_delay(50); ret = cd->hw_ops->read(cd, 0xD8D0, buf, 2); if (!ret && buf[0] == 0x66) break; } if (retry < 0) { ts_err("FW2 test not ready, status = 0x%02x", buf[0]); return -EINVAL; } if (buf[1] == 0) { ts_raw_info("SRAM test OK"); ret = 0; cd->test_data.info[SEC_SRAM_TEST].data[0] = SEC_TEST_OK; } else { ts_raw_info("SRAM test NG"); ret = -EINVAL; cd->test_data.info[SEC_SRAM_TEST].data[0] = SEC_TEST_NG; } cd->test_data.info[SEC_SRAM_TEST].isFinished = true; return ret; } static int goodix_brld_sram_test(struct goodix_ts_core *cd) { int ret; int retry = 20; struct goodix_ts_cmd cmd; u8 buf[2]; cmd.cmd = 0x61; cmd.len = 4; ret = cd->hw_ops->send_cmd(cd, &cmd); if (ret < 0) { ts_err("send SRAM test cmd failed"); return ret; } sec_delay(50); while (retry--) { ret = cd->hw_ops->read(cd, 0xD8D0, buf, 2); if (buf[0] == 0xAA) break; sec_delay(40); } if (retry < 0) { ts_err("sram test not ready, status = 0x%02x", buf[0]); return -EINVAL; } if (buf[1] == 0) { ts_raw_info("SRAM test OK"); ret = 0; cd->test_data.info[SEC_SRAM_TEST].data[0] = SEC_TEST_OK; } else { ts_raw_info("SRAM test NG"); ret = -EINVAL; cd->test_data.info[SEC_SRAM_TEST].data[0] = SEC_TEST_NG; } cd->test_data.info[SEC_SRAM_TEST].isFinished = true; return ret; } int goodix_sram_test(struct goodix_ts_core *cd) { int ret; int retry = 2; cd->test_data.info[SEC_SRAM_TEST].item_name = SRAM_TEST_ITEM; restart: if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { ret = goodix_brl_sram_test(cd); } else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { ret = goodix_brld_sram_test(cd); } else { ret = goodix_brl_sram_test(cd); ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); } if (ret && retry--) { ts_info("sram test failed, retry[%d]", 2 - retry); cd->hw_ops->reset(cd, 200); goto restart; } return ret; } #define SHORT_TEST_FINISH_FLAG 0x88 static int goodix_brl_short_test(struct goodix_ts_core *cd) { int ret = 0; int retry; u16 test_time; u8 status; unsigned int time_reg = cd->short_test_time_reg; unsigned int result_reg = cd->short_test_status_reg; ret = goodix_brl_short_test_prepare(cd); if (ret < 0) { ts_err("Failed enter short test mode"); return ret; } sec_delay(300); /* get short test time */ ret = cd->hw_ops->read(cd, time_reg, (u8 *)&test_time, 2); if (ret < 0) { ts_err("Failed to get test_time, default %dms", DEFAULT_TEST_TIME_MS); test_time = DEFAULT_TEST_TIME_MS; } else { if (test_time > MAX_TEST_TIME_MS) { ts_info("test time too long %d > %d", test_time, MAX_TEST_TIME_MS); test_time = MAX_TEST_TIME_MS; } /* if predict time is 0, means fast test pass */ ts_info("get test time %dms", test_time); } status = 0; cd->hw_ops->write(cd, SHORT_TEST_RUN_REG, &status, 1); /* wait short test finish */ if (test_time > 0) sec_delay(test_time); retry = 50; while (retry--) { ret = cd->hw_ops->read(cd, result_reg, &status, 1); if (!ret && status == SHORT_TEST_FINISH_FLAG) break; sec_delay(50); } if (retry < 0) { ts_err("short test failed, status:0x%02x", status); return -EINVAL; } /* start analysis short result */ ts_info("short_test finished, start analysis"); ret = goodix_short_analysis(cd); return ret; } int goodix_short_test(struct goodix_ts_core *cd) { int ret; int retry = 2; cd->test_data.info[SEC_SHORT_TEST].item_name = SHORT_TEST_ITEM; restart: ret = goodix_brl_short_test(cd); if (ret && retry--) { ts_info("short test failed, retry[%d]", 2 - retry); cd->hw_ops->reset(cd, 200); goto restart; } return ret; } #define GOODIX_TOUCH_EVENT 0x80 #define DISCARD_FRAMES_NUM 3 int goodix_cache_rawdata(struct goodix_ts_core *cd, int test_type, u8 freq) { int ret; int retry = 20; struct goodix_ts_cmd temp_cmd; unsigned int freq_cmd = cd->switch_freq_cmd; uint32_t flag_addr; uint32_t raw_addr; uint32_t diff_addr; uint32_t selfraw_addr; uint32_t selfdiff_addr; int tx = 0; int rx = 0; u8 val; int i; if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW || test_type == RAWDATA_TEST_TYPE_MUTUAL_DIFF) { memset(&temp_cmd, 0x00, sizeof(struct goodix_ts_cmd)); /* switch test config */ temp_cmd.len = 5; temp_cmd.cmd = cd->switch_cfg_cmd; temp_cmd.data[0] = 1; ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("switch test config failed"); return ret; } sec_delay(20); } memset(&temp_cmd, 0x00, sizeof(struct goodix_ts_cmd)); tx = cd->ic_info.parm.drv_num; rx = cd->ic_info.parm.sen_num; if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { flag_addr = cd->ic_info.misc.touch_data_addr; raw_addr = cd->ic_info.misc.mutual_rawdata_addr; diff_addr = cd->ic_info.misc.mutual_diffdata_addr; selfraw_addr = cd->ic_info.misc.self_rawdata_addr; selfdiff_addr = cd->ic_info.misc.self_diffdata_addr; temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x84; temp_cmd.len = 5; } else if (cd->bus->ic_type == IC_TYPE_BERLIN_D) { flag_addr = cd->ic_info.misc.frame_data_addr; raw_addr = 0x104B2; selfraw_addr = cd->ic_info.misc.frame_data_addr + cd->ic_info.misc.frame_data_head_len + cd->ic_info.misc.fw_attr_len + cd->ic_info.misc.fw_log_len + cd->ic_info.misc.mutual_struct_len + 10; diff_addr = raw_addr; selfdiff_addr = selfraw_addr; if (test_type == RAWDATA_TEST_TYPE_SELF_RAW || test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x84; temp_cmd.len = 5; } else { temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x82; temp_cmd.len = 5; } } else { ts_err("abnormal tsp ic type(%d)", cd->bus->ic_type); flag_addr = cd->ic_info.misc.touch_data_addr; raw_addr = cd->ic_info.misc.mutual_rawdata_addr; diff_addr = cd->ic_info.misc.mutual_diffdata_addr; selfraw_addr = cd->ic_info.misc.self_rawdata_addr; selfdiff_addr = cd->ic_info.misc.self_diffdata_addr; temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x84; temp_cmd.len = 5; } ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("switch rawdata mode failed, exit!"); return ret; } if (freq == FREQ_HIGH) { temp_cmd.len = 6; temp_cmd.cmd = freq_cmd; temp_cmd.data[0] = 0x36; temp_cmd.data[1] = 0x13; cd->hw_ops->send_cmd(cd, &temp_cmd); sec_delay(50); } else if (freq == FREQ_LOW) { temp_cmd.len = 6; temp_cmd.cmd = freq_cmd; temp_cmd.data[0] = 0x67; temp_cmd.data[1] = 0x06; cd->hw_ops->send_cmd(cd, &temp_cmd); sec_delay(50); } /* discard the first few frames */ for (i = 0; i < DISCARD_FRAMES_NUM; i++) { sec_delay(20); val = 0; ret = ts_test_write(cd, flag_addr, &val, 1); if (ret < 0) { ts_err("clean touch event failed, exit!"); return ret; } } while (retry--) { sec_delay(5); ret = ts_test_read(cd, flag_addr, &val, 1); if (!ret && (val & GOODIX_TOUCH_EVENT)) break; } if (retry < 0) { ts_err("rawdata is not ready val:0x%02x, exit!", val); ret = -EIO; return ret; } if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { if (freq == FREQ_HIGH) { cd->test_data.high_freq_rawdata.size = tx * rx; ret = ts_test_read(cd, raw_addr, (u8 *)cd->test_data.high_freq_rawdata.data, cd->test_data.high_freq_rawdata.size * 2); goodix_rotate_abcd2cbad(tx, rx, cd->test_data.high_freq_rawdata.data); goodix_data_statistics(cd->test_data.high_freq_rawdata.data, cd->test_data.high_freq_rawdata.size, &cd->test_data.high_freq_rawdata.max, &cd->test_data.high_freq_rawdata.min); cd->test_data.high_freq_rawdata.delta = cd->test_data.high_freq_rawdata.max - cd->test_data.high_freq_rawdata.min; } else if (freq == FREQ_LOW) { cd->test_data.low_freq_rawdata.size = tx * rx; ret = ts_test_read(cd, raw_addr, (u8 *)cd->test_data.low_freq_rawdata.data, cd->test_data.low_freq_rawdata.size * 2); goodix_rotate_abcd2cbad(tx, rx, cd->test_data.low_freq_rawdata.data); goodix_data_statistics(cd->test_data.low_freq_rawdata.data, cd->test_data.low_freq_rawdata.size, &cd->test_data.low_freq_rawdata.max, &cd->test_data.low_freq_rawdata.min); cd->test_data.low_freq_rawdata.delta = cd->test_data.low_freq_rawdata.max - cd->test_data.low_freq_rawdata.min; } else { /* read raw data */ cd->test_data.rawdata.size = tx * rx; ret = ts_test_read(cd, raw_addr, (u8 *)cd->test_data.rawdata.data, cd->test_data.rawdata.size * 2); if (ret < 0) { ts_err("obtian rawdata failed, exit!"); return ret; } goodix_rotate_abcd2cbad(tx, rx, cd->test_data.rawdata.data); goodix_data_statistics(cd->test_data.rawdata.data, cd->test_data.rawdata.size, &cd->test_data.rawdata.max, &cd->test_data.rawdata.min); cd->test_data.rawdata.delta = cd->test_data.rawdata.max - cd->test_data.rawdata.min; } } else if (test_type == RAWDATA_TEST_TYPE_MUTUAL_DIFF) { /* read diff data */ cd->test_data.diffdata.size = tx * rx; ret = ts_test_read(cd, diff_addr, (u8 *)cd->test_data.diffdata.data, cd->test_data.diffdata.size * 2); if (ret < 0) { ts_err("obtian diffdata failed, exit!"); return ret; } goodix_rotate_abcd2cbad(tx, rx, cd->test_data.diffdata.data); goodix_data_statistics(cd->test_data.diffdata.data, cd->test_data.diffdata.size, &cd->test_data.diffdata.max, &cd->test_data.diffdata.min); } else if (test_type == RAWDATA_TEST_TYPE_SELF_RAW) { /* read selfraw */ cd->test_data.selfraw.size = tx + rx; ret = ts_test_read(cd, selfraw_addr, (u8 *)cd->test_data.selfraw.data, cd->test_data.selfraw.size * 2); if (ret < 0) { ts_err("obtian selfraw data failed, exit!"); return ret; } goodix_data_statistics(cd->test_data.selfraw.data, tx, &cd->test_data.selfraw.tx_max, &cd->test_data.selfraw.tx_min); goodix_data_statistics(cd->test_data.selfraw.data + tx, rx, &cd->test_data.selfraw.rx_max, &cd->test_data.selfraw.rx_min); } else if (test_type == RAWDATA_TEST_TYPE_SELF_DIFF) { /* read selfdiff */ cd->test_data.selfdiff.size = tx + rx; ret = ts_test_read(cd, selfdiff_addr, (u8 *)cd->test_data.selfdiff.data, cd->test_data.selfdiff.size * 2); if (ret < 0) { ts_err("obtian selfdiff data failed, exit!"); return ret; } goodix_data_statistics(cd->test_data.selfdiff.data, cd->test_data.selfdiff.size, &cd->test_data.selfdiff.tx_max, &cd->test_data.selfdiff.tx_min); } else { ts_err("invalid test_type %d", test_type); return -EINVAL; } ts_raw_info("test_type:%d, done", test_type); return 0; } int goodix_read_realtime(struct goodix_ts_core *cd, int test_type) { int ret; int retry = 20; struct goodix_ts_cmd temp_cmd; uint32_t flag_addr; uint32_t raw_addr; uint32_t diff_addr; int tx = cd->ic_info.parm.drv_num; int rx = cd->ic_info.parm.sen_num; u8 val; if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { /* switch test config */ temp_cmd.len = 5; temp_cmd.cmd = cd->switch_cfg_cmd; temp_cmd.data[0] = 1; ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("switch test config failed"); return ret; } sec_delay(20); } if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { flag_addr = cd->ic_info.misc.touch_data_addr; raw_addr = cd->ic_info.misc.mutual_rawdata_addr; diff_addr = cd->ic_info.misc.mutual_diffdata_addr; temp_cmd.cmd = 0x01; temp_cmd.len = 4; } else { flag_addr = cd->ic_info.misc.frame_data_addr; raw_addr = 0x104B2; diff_addr = raw_addr; if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x81; temp_cmd.len = 5; } else { temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0x82; temp_cmd.len = 5; } } ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("switch rawdata mode failed, exit!"); goto exit; } val = 0; ts_test_write(cd, flag_addr, &val, 1); while (retry--) { sec_delay(5); ret = ts_test_read(cd, flag_addr, &val, 1); if (!ret && (val & GOODIX_TOUCH_EVENT)) break; } if (retry < 0) { ts_err("rawdata is not ready val:0x%02x, exit!", val); ret = -EIO; goto exit; } if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { memset(cd->test_data.rawdata.data, 0, sizeof(cd->test_data.rawdata.data)); cd->test_data.rawdata.size = tx * rx; ts_test_read(cd, raw_addr, (u8 *)cd->test_data.rawdata.data, cd->test_data.rawdata.size * 2); goodix_rotate_abcd2cbad(tx, rx, cd->test_data.rawdata.data); } else if (test_type == RAWDATA_TEST_TYPE_MUTUAL_DIFF) { memset(cd->test_data.diffdata.data, 0, sizeof(cd->test_data.diffdata.data)); cd->test_data.diffdata.size = tx * rx; ts_test_read(cd, diff_addr, (u8 *)cd->test_data.diffdata.data, cd->test_data.diffdata.size * 2); goodix_rotate_abcd2cbad(tx, rx, cd->test_data.diffdata.data); } else { ts_err("invalid test_type %d", test_type); ret = -EINVAL; } val = 0; ts_test_write(cd, flag_addr, &val, 1); exit: if (cd->bus->ic_type == IC_TYPE_BERLIN_B) { temp_cmd.len = 4; temp_cmd.cmd = 0x00; } else { temp_cmd.len = 5; temp_cmd.cmd = 0x90; temp_cmd.data[0] = 0; } cd->hw_ops->send_cmd(cd, &temp_cmd); if (test_type == RAWDATA_TEST_TYPE_MUTUAL_RAW) { temp_cmd.len = 5; temp_cmd.cmd = cd->switch_cfg_cmd; temp_cmd.data[0] = 0; cd->hw_ops->send_cmd(cd, &temp_cmd); } return ret; } int goodix_snr_test(struct goodix_ts_core *cd, int type, int frames) { int ret; int retry = 50; struct goodix_ts_cmd temp_cmd; unsigned char temp_buf[58]; int i; unsigned char snr_cmd = cd->snr_cmd; unsigned int snr_addr = cd->production_test_addr; if (type == SNR_TEST_NON_TOUCH) { ts_info("run_snr_non_touched, %d", frames); temp_cmd.len = 7; temp_cmd.cmd = snr_cmd; temp_cmd.data[0] = 0x01; temp_cmd.data[1] = (u8)frames; temp_cmd.data[2] = (u8)(frames >> 8); memset(cd->test_data.snr_result, 0, sizeof(cd->test_data.snr_result)); } else if (type == SNR_TEST_TOUCH) { ts_info("run_snr_touched, %d", frames); temp_cmd.len = 7; temp_cmd.cmd = snr_cmd; temp_cmd.data[0] = 0x02; temp_cmd.data[1] = (u8)frames; temp_cmd.data[2] = (u8)(frames >> 8); } else { ts_err("abnormal type(%d)", type); return -EINVAL; } ret = cd->hw_ops->send_cmd(cd, &temp_cmd); if (ret < 0) { ts_err("send snr_test cmd failed"); return ret; } sec_delay(frames * 10); while (retry--) { sec_delay(100); ret = cd->hw_ops->read(cd, snr_addr, temp_buf, 2); if (ret < 0) { ts_err("read snr status failed"); return ret; } if (type == SNR_TEST_NON_TOUCH) { if (temp_buf[0] == 0xAA && temp_buf[1] == 0xAA) break; } else if (type == SNR_TEST_TOUCH) { if (temp_buf[0] == 0xBB && temp_buf[1] == 0xBB) break; } } if (retry < 0) { ts_err("can't read valid snr status:%x%x", temp_buf[0], temp_buf[1]); return -EINVAL; } if (type == SNR_TEST_NON_TOUCH) { ts_info("run_snr_non_touched, frames(%d): OK", frames); return 0; } else if (type == SNR_TEST_TOUCH) { ret = cd->hw_ops->read(cd, snr_addr, temp_buf, sizeof(temp_buf)); if (ret < 0) { ts_err("read snr touched data failed"); return ret; } if (checksum_cmp(temp_buf + 2, sizeof(temp_buf) - 2, CHECKSUM_MODE_U8_LE)) { ts_err("snr touched data checksum error"); ts_err("data:%*ph", (int)sizeof(temp_buf) - 2, temp_buf + 2); return -EINVAL; } for (i = 0; i < 9; i++) { cd->test_data.snr_result[i * 3] = (s16)le16_to_cpup((__le16 *)&temp_buf[2 + i * 6]); cd->test_data.snr_result[i * 3 + 1] = (s16)le16_to_cpup((__le16 *)&temp_buf[4 + i * 6]); cd->test_data.snr_result[i * 3 + 2] = (s16)le16_to_cpup((__le16 *)&temp_buf[6 + i * 6]); ts_raw_info("run_snr_touched, frame(%d) #%d: avg:%d snr1:%d snr2:%d", frames, i, cd->test_data.snr_result[i * 3], cd->test_data.snr_result[i * 3 + 1], cd->test_data.snr_result[i * 3 + 2]); } return 0; } ts_err("abnormal type(%d)", type); return -EINVAL; } static int goodix_auto_test(struct goodix_ts_core *cd) { int i = 0; /* disable irq */ cd->hw_ops->irq_enable(cd, false); memset(&cd->test_data, 0, sizeof(cd->test_data)); for (i = RAWDATA_TEST_TYPE_MUTUAL_RAW; i <= RAWDATA_TEST_TYPE_SELF_DIFF; i++) { /* cache rawdata */ goodix_cache_rawdata(cd, i, FREQ_NORMAL); } cd->hw_ops->reset(cd, 100); /* jitter test */ goodix_jitter_test(cd, JITTER_100_FRAMES); goodix_jitter_test(cd, JITTER_1000_FRAMES); /* open test */ goodix_open_test(cd); /* SRAM test */ goodix_sram_test(cd); cd->hw_ops->reset(cd, 100); /* short test */ goodix_short_test(cd); /* reset IC */ cd->hw_ops->reset(cd, 100); cd->hw_ops->irq_enable(cd, true); return 0; } static int rawdata_proc_show(struct seq_file *m, void *v) { struct goodix_ts_core *cd; int tx = 0; int rx = 0; int i; if (!m || !v) { ts_err("rawdata_proc_show, input null ptr"); return -EIO; } cd = m->private; if (!cd) { ts_err("can't get core data"); return -EIO; } goodix_auto_test(cd); tx = cd->ic_info.parm.drv_num; rx = cd->ic_info.parm.sen_num; seq_printf(m, "TX:%d RX:%d\n", tx, rx); seq_printf(m, "mutual_rawdata[%d, %d, %d]:\n", cd->test_data.rawdata.max, cd->test_data.rawdata.min, cd->test_data.rawdata.delta); for (i = 0; i < cd->test_data.rawdata.size; i++) { seq_printf(m, "%d,", cd->test_data.rawdata.data[i]); if ((i + 1) % tx == 0) seq_printf(m, "\n"); } /* print high freq rawdata */ seq_printf(m, "high_freq_rawdata[%d, %d, %d]:\n", cd->test_data.high_freq_rawdata.max, cd->test_data.high_freq_rawdata.min, cd->test_data.high_freq_rawdata.delta); for (i = 0; i < cd->test_data.high_freq_rawdata.size; i++) { seq_printf(m, "%d,", cd->test_data.high_freq_rawdata.data[i]); if ((i + 1) % tx == 0) seq_printf(m, "\n"); } /* print low freq rawdata */ seq_printf(m, "low_freq_rawdata[%d, %d, %d]:\n", cd->test_data.low_freq_rawdata.max, cd->test_data.low_freq_rawdata.min, cd->test_data.low_freq_rawdata.delta); for (i = 0; i < cd->test_data.low_freq_rawdata.size; i++) { seq_printf(m, "%d,", cd->test_data.low_freq_rawdata.data[i]); if ((i + 1) % tx == 0) seq_printf(m, "\n"); } seq_printf(m, "mutual_diffdata[%d, %d]:\n", cd->test_data.diffdata.max, cd->test_data.diffdata.min); for (i = 0; i < cd->test_data.diffdata.size; i++) { seq_printf(m, "%3d,", cd->test_data.diffdata.data[i]); if ((i + 1) % tx == 0) seq_printf(m, "\n"); } seq_printf(m, "self_rawdata_tx[%d, %d]:\n", cd->test_data.selfraw.tx_max, cd->test_data.selfraw.tx_min); for (i = 0; i < tx; i++) seq_printf(m, "%d,", cd->test_data.selfraw.data[i]); seq_printf(m, "\nself_rawdata_rx[%d, %d]:\n", cd->test_data.selfraw.rx_max, cd->test_data.selfraw.rx_min); for (i = tx; i < cd->test_data.selfraw.size; i++) seq_printf(m, "%d,", cd->test_data.selfraw.data[i]); seq_printf(m, "\n"); seq_printf(m, "self_diffdata[%d, %d]:\n", cd->test_data.selfdiff.tx_max, cd->test_data.selfdiff.tx_min); for (i = 0; i < cd->test_data.selfdiff.size; i++) seq_printf(m, "%d,", cd->test_data.selfdiff.data[i]); seq_printf(m, "\n"); for (i = 0; i < SEC_TEST_MAX_ITEM; i++) { if (!cd->test_data.info[i].isFinished) continue; seq_printf(m, "[%d]%s: ", i, cd->test_data.info[i].item_name); if (i == SEC_JITTER1_TEST) { seq_printf(m, "mutualDiff[%d %d] ", cd->test_data.info[i].data[1], cd->test_data.info[i].data[0]); seq_printf(m, "selfDiff[%d %d]\n", cd->test_data.info[i].data[3], cd->test_data.info[i].data[2]); } else if (i == SEC_JITTER2_TEST) { seq_printf(m, "min_matrix[%d %d] ", cd->test_data.info[i].data[0], cd->test_data.info[i].data[1]); seq_printf(m, "max_matrix[%d %d] ", cd->test_data.info[i].data[2], cd->test_data.info[i].data[3]); seq_printf(m, "avg_matrix[%d %d]\n", cd->test_data.info[i].data[4], cd->test_data.info[i].data[5]); } else { seq_printf(m, "%s\n", cd->test_data.info[i].data[0] == SEC_TEST_OK ? "OK" : "NG"); } } return 0; } static int rawdata_proc_open(struct inode *inode, struct file *file) { return single_open_size(file, rawdata_proc_show, PDE_DATA(inode), PAGE_SIZE * 10); } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) static const struct proc_ops rawdata_proc_fops = { .proc_open = rawdata_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; #else static const struct file_operations rawdata_proc_fops = { .open = rawdata_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif int inspect_module_init(void) { struct proc_dir_entry *proc_entry; proc_entry = proc_create_data(PROC_NODE_NAME, 0664, NULL, &rawdata_proc_fops, goodix_modules.core_data); if (!proc_entry) ts_err("failed to create proc entry: %s", PROC_NODE_NAME); module_initialized = true; ts_info("inspect module init success"); return 0; } void inspect_module_exit(void) { ts_info("inspect module exit"); if (!module_initialized) return; remove_proc_entry(PROC_NODE_NAME, NULL); module_initialized = false; }