529 lines
15 KiB
C
Executable file
529 lines
15 KiB
C
Executable file
/* drivers/input/sec_input/stm/stm_dump.c
|
|
*
|
|
* Copyright (C) 2011 Samsung Electronics Co., Ltd.
|
|
*
|
|
* Core file for Samsung TSC driver
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "stm_dev.h"
|
|
#include "stm_reg.h"
|
|
|
|
static ssize_t get_cmoffset_dump_v2(struct stm_ts_data *ts, char *buf, u8 position)
|
|
{
|
|
u8 *rbuff;
|
|
int ret, i, j, cm_num, value, size = 8 + ts->tx_count * ts->rx_count;
|
|
u32 signature;
|
|
u8 address[4] = { 0 };
|
|
char buff[80] = { 0 };
|
|
|
|
rbuff = kzalloc(size, GFP_KERNEL);
|
|
if (!rbuff) {
|
|
input_err(true, &ts->client->dev, "%s: alloc failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ts->stm_ts_systemreset(ts, 50);
|
|
|
|
/* read cm2 & cm3 */
|
|
for (cm_num = 2; cm_num < 4; ++cm_num) {
|
|
input_info(true, &ts->client->dev, "%s: CM%d read start! buf size:%lu\n", __func__, cm_num, strlen(buf));
|
|
|
|
// Set Test mode : prepare save test data & fail history
|
|
address[0] = 0xE4;
|
|
address[1] = 0x02;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0) {
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, timeout, %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true);
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
// set factory level for read data
|
|
address[0] = 0x74;
|
|
address[1] = position;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0) {
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, timeout, %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
// set data typr for read data
|
|
address[0] = 0x7D;
|
|
if (cm_num == OFFSET_FAC_DATA_CM2) {
|
|
signature = STM_TS_CM2_SIGNATURE;
|
|
address[1] = 0x01;
|
|
} else if (cm_num == OFFSET_FAC_DATA_CM3) {
|
|
signature = STM_TS_CM3_SIGNATURE;
|
|
address[1] = 0x02;
|
|
}
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0) {
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, timeout, %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
memset(rbuff, 0x00, size);
|
|
|
|
address[0] = 0x75;
|
|
ret = ts->stm_ts_read(ts, &address[0], 1, rbuff, size);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: read failed ret = %d\n", __func__, ret);
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, failed to read data, %d", ret);
|
|
continue;
|
|
} else {
|
|
input_info(true, &ts->client->dev, "%s: read size = %d, ret = %d\n", __func__, size, ret);
|
|
}
|
|
|
|
if (position == OFFSET_FW_SDC) {
|
|
snprintf(buff, sizeof(buff), "%s", "SDC ");
|
|
} else if (position == OFFSET_FW_SUB) {
|
|
snprintf(buff, sizeof(buff), "%s", "SUB ");
|
|
} else if (position == OFFSET_FW_MAIN) {
|
|
snprintf(buff, sizeof(buff), "%s", "MAIN ");
|
|
}
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
|
|
// data parsing
|
|
/* check Header */
|
|
value = rbuff[3] << 24 | rbuff[2] << 16 | rbuff[1] << 8 | rbuff[0];
|
|
if (value == 0) {
|
|
snprintf(buff, sizeof(buff), "CM%d Data empty!\n", cm_num);
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
|
|
input_err(true, &ts->client->dev, "%s: CM%d Data empty\n", __func__, cm_num);
|
|
continue;
|
|
} else if (value != signature) {
|
|
snprintf(buff, sizeof(buff), "CM%d : signature mismatched %08X != %08X\n", cm_num, signature, value);
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
|
|
input_err(true, &ts->client->dev, "%s: CM%d pos[%d] signature is mismatched %08X != %08X\n",
|
|
__func__, cm_num, position, signature, value);
|
|
|
|
continue;
|
|
} else {
|
|
snprintf(buff, sizeof(buff), "CM%d try cnt:%d\n", cm_num, rbuff[5]);
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
|
|
input_info(true, &ts->client->dev, "%s: CM%d try cnt:%d value %d signature:%8X\n",
|
|
__func__, cm_num, rbuff[5], value, signature);
|
|
}
|
|
|
|
for (i = 0; i < ts->tx_count; i++) {
|
|
for (j = 0; j < ts->rx_count; j++) {
|
|
snprintf(buff, sizeof(buff), " %3d", (rbuff[8 + i * ts->rx_count + j]) << 3);
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
}
|
|
snprintf(buff, sizeof(buff), "\n");
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
}
|
|
input_info(true, &ts->client->dev, "%s: CM%d read end!\n", __func__, cm_num);
|
|
}
|
|
|
|
out:
|
|
input_err(true, &ts->client->dev, "%s: pos:%d, buf size:%zu\n", __func__, position, strlen(buf));
|
|
|
|
// clear factory level
|
|
address[0] = 0x74;
|
|
address[1] = 0;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0)
|
|
goto err_out;
|
|
|
|
// clear data type
|
|
address[0] = 0x7D;
|
|
address[1] = 0x00;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0)
|
|
goto err_out;
|
|
|
|
// Set Normal mode : save test data & fail history
|
|
address[0] = 0xE4;
|
|
address[1] = 0x00;
|
|
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 2, 0);
|
|
if (ret < 0)
|
|
goto err_out;
|
|
|
|
err_out:
|
|
/* reinit */
|
|
ts->stm_ts_systemreset(ts, 0);
|
|
|
|
stm_ts_set_scanmode(ts, ts->scan_mode);
|
|
|
|
kfree(rbuff);
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
|
|
static ssize_t get_cmoffset_dump_v1(struct stm_ts_data *ts, char *buf, u8 position)
|
|
{
|
|
u8 *rbuff;
|
|
int ret, i, j, size = ts->tx_count * ts->rx_count;
|
|
u32 signature;
|
|
u8 address[4] = { 0 };
|
|
|
|
rbuff = kzalloc(size, GFP_KERNEL);
|
|
if (!rbuff) {
|
|
input_err(true, &ts->client->dev, "%s: alloc failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true);
|
|
|
|
stm_ts_release_all_finger(ts);
|
|
|
|
/* Request SEC factory debug data from flash */
|
|
address[0] = 0xA4;
|
|
address[1] = 0x06;
|
|
address[2] = 0x92;
|
|
address[3] = position;
|
|
ret = stm_ts_wait_for_echo_event(ts, address, 4, 0);
|
|
if (ret < 0) {
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, failed to request data, %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
/* read header info */
|
|
address[0] = 0xA6;
|
|
address[1] = 0x00;
|
|
address[2] = 0x00;
|
|
ret = ts->stm_ts_read(ts, &address[0], 3, rbuff, 8);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: read header failed. ret: %d\n", __func__, ret);
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, failed to read header, %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
signature = rbuff[3] << 24 | rbuff[2] << 16 | rbuff[1] << 8 | rbuff[0];
|
|
input_info(true, &ts->client->dev,
|
|
"%s: position:%d, signature:%08X (%X), validation:%X, try count:%X\n",
|
|
__func__, position, signature, SEC_OFFSET_SIGNATURE, rbuff[4], rbuff[5]);
|
|
|
|
if (signature != SEC_OFFSET_SIGNATURE) {
|
|
input_err(true, &ts->client->dev, "%s: cmoffset[%d], signature is mismatched\n",
|
|
__func__, position);
|
|
snprintf(buf, ts->proc_cmoffset_size, "signature mismatched %08X\n", signature);
|
|
goto out;
|
|
}
|
|
|
|
/* read history data */
|
|
address[0] = 0xA6;
|
|
address[1] = 0x00;
|
|
address[2] = (u8)ts->rx_count;
|
|
ret = ts->stm_ts_read(ts, &address[0], 3, rbuff, size);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev,
|
|
"%s: read data failed. ret: %d\n", __func__, ret);
|
|
snprintf(buf, ts->proc_cmoffset_size, "NG, failed to read data %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
memset(buf, 0x00, ts->proc_cmoffset_size);
|
|
for (i = 0; i < ts->tx_count; i++) {
|
|
char buff[4] = { 0 };
|
|
|
|
for (j = 0; j < ts->rx_count; j++) {
|
|
snprintf(buff, sizeof(buff), " %d", rbuff[i * ts->tx_count + j]);
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
}
|
|
snprintf(buff, sizeof(buff), "\n");
|
|
strlcat(buf, buff, ts->proc_cmoffset_size);
|
|
}
|
|
out:
|
|
input_err(true, &ts->client->dev, "%s: pos:%d, buf size:%zu\n", __func__, position, strlen(buf));
|
|
|
|
kfree(rbuff);
|
|
return strlen(buf);
|
|
}
|
|
|
|
|
|
static ssize_t stm_ts_tsp_cmoffset_all_read(struct file *file, char __user *buf,
|
|
size_t len, loff_t *offset)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(ptsp);
|
|
static ssize_t retlen = 0;
|
|
ssize_t retlen_sdc = 0, retlen_sub = 0, retlen_main = 0;
|
|
ssize_t count;
|
|
loff_t pos = *offset;
|
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
int ret;
|
|
#endif
|
|
|
|
if (!ts) {
|
|
pr_err("%s %s: dev is null\n", SECLOG, __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (pos == 0) {
|
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
ret = get_cmoffset_dump(ts, ts->cmoffset_sdc_proc, OFFSET_FW_SDC);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: SDC fail use boot time value\n", __func__);
|
|
ret = get_cmoffset_dump(ts, ts->cmoffset_sub_proc, OFFSET_FW_SUB);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: SUB fail use boot time value\n", __func__);
|
|
ret = get_cmoffset_dump(ts, ts->cmoffset_main_proc, OFFSET_FW_MAIN);
|
|
if (ret < 0)
|
|
input_err(true, &ts->client->dev,
|
|
"%s: MAIN fail use boot time value\n", __func__);
|
|
#endif
|
|
|
|
retlen_sdc = strlen(ts->cmoffset_sdc_proc);
|
|
retlen_sub = strlen(ts->cmoffset_sub_proc);
|
|
retlen_main = strlen(ts->cmoffset_main_proc);
|
|
|
|
ts->cmoffset_all_proc = kzalloc(ts->proc_cmoffset_all_size, GFP_KERNEL);
|
|
if (!ts->cmoffset_all_proc) {
|
|
input_err(true, &ts->client->dev, "%s: kzalloc fail (cmoffset_all_proc)\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
strlcat(ts->cmoffset_all_proc, ts->cmoffset_sdc_proc, ts->proc_cmoffset_all_size);
|
|
strlcat(ts->cmoffset_all_proc, ts->cmoffset_sub_proc, ts->proc_cmoffset_all_size);
|
|
strlcat(ts->cmoffset_all_proc, ts->cmoffset_main_proc, ts->proc_cmoffset_all_size);
|
|
|
|
retlen = strlen(ts->cmoffset_all_proc);
|
|
|
|
input_info(true, &ts->client->dev, "%s: retlen[%ld], retlen_sdc[%ld], retlen_sub[%ld], retlen_main[%ld]\n",
|
|
__func__, retlen, retlen_sdc, retlen_sub, retlen_main);
|
|
}
|
|
|
|
if (pos >= retlen)
|
|
return 0;
|
|
|
|
count = min(len, (size_t)(retlen - pos));
|
|
|
|
input_info(true, &ts->client->dev, "%s: total:%ld count:%ld\n", __func__, retlen, count);
|
|
|
|
if (copy_to_user(buf, ts->cmoffset_all_proc + pos, count)) {
|
|
input_err(true, &ts->client->dev, "%s: copy_to_user error!\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
*offset += count;
|
|
|
|
if (count < len) {
|
|
input_info(true, &ts->client->dev, "%s: print all & free cmoffset_all_proc\n", __func__);
|
|
if (ts->cmoffset_all_proc)
|
|
kfree(ts->cmoffset_all_proc);
|
|
retlen = 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE)
|
|
void stm_ts_check_rawdata(struct work_struct *work)
|
|
{
|
|
struct stm_ts_data *ts = container_of(work, struct stm_ts_data, check_rawdata.work);
|
|
|
|
if (ts->tsp_dump_lock == 1) {
|
|
input_err(true, &ts->client->dev, "%s: ignored ## already checking..\n", __func__);
|
|
return;
|
|
}
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF) {
|
|
input_err(true, &ts->client->dev, "%s: ignored ## IC is power off\n", __func__);
|
|
return;
|
|
}
|
|
|
|
stm_ts_run_rawdata_all(ts);
|
|
}
|
|
|
|
void stm_ts_dump_tsp_log(struct device *dev)
|
|
{
|
|
struct stm_ts_data *ts = dev_get_drvdata(dev);
|
|
|
|
pr_info("%s: %s %s: start\n", STM_TS_I2C_NAME, SECLOG, __func__);
|
|
|
|
if (!ts) {
|
|
pr_err("%s: %s %s: ignored ## tsp probe fail!!\n", STM_TS_I2C_NAME, SECLOG, __func__);
|
|
return;
|
|
}
|
|
|
|
schedule_delayed_work(&ts->check_rawdata, msecs_to_jiffies(100));
|
|
}
|
|
|
|
void stm_ts_sponge_dump_flush(struct stm_ts_data *ts, int dump_area)
|
|
{
|
|
int i, ret;
|
|
unsigned char *sec_spg_dat;
|
|
input_info(true, &ts->client->dev, "%s: ++\n", __func__);
|
|
|
|
sec_spg_dat = vmalloc(SEC_TS_MAX_SPONGE_DUMP_BUFFER);
|
|
if (!sec_spg_dat) {
|
|
input_err(true, &ts->client->dev, "%s : Failed!!\n", __func__);
|
|
return;
|
|
}
|
|
memset(sec_spg_dat, 0, SEC_TS_MAX_SPONGE_DUMP_BUFFER);
|
|
|
|
input_info(true, &ts->client->dev, "%s: dump area=%d\n", __func__, dump_area);
|
|
|
|
/* check dump area */
|
|
if (dump_area == 0) {
|
|
sec_spg_dat[0] = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT;
|
|
sec_spg_dat[1] = 0;
|
|
} else {
|
|
sec_spg_dat[0] = (u8)ts->sponge_dump_border & 0xff;
|
|
sec_spg_dat[1] = (u8)(ts->sponge_dump_border >> 8);
|
|
}
|
|
|
|
/* dump all events at once */
|
|
|
|
if (ts->sponge_dump_event * ts->sponge_dump_format > SEC_TS_MAX_SPONGE_DUMP_BUFFER) {
|
|
input_err(true, &ts->client->dev, "%s: wrong sponge dump read size (%d)\n",
|
|
__func__, ts->sponge_dump_event * ts->sponge_dump_format);
|
|
vfree(sec_spg_dat);
|
|
return;
|
|
}
|
|
|
|
ret = ts->stm_ts_read_sponge(ts, sec_spg_dat, ts->sponge_dump_event * ts->sponge_dump_format);
|
|
if (ret < 0) {
|
|
input_err(true, &ts->client->dev, "%s: Failed to read sponge\n", __func__);
|
|
vfree(sec_spg_dat);
|
|
return;
|
|
}
|
|
|
|
for (i = 0 ; i < ts->sponge_dump_event ; i++) {
|
|
int e_offset = i * ts->sponge_dump_format;
|
|
char buff[30] = {0, };
|
|
u16 edata[5];
|
|
edata[0] = (sec_spg_dat[1 + e_offset] & 0xFF) << 8 | (sec_spg_dat[0 + e_offset] & 0xFF);
|
|
edata[1] = (sec_spg_dat[3 + e_offset] & 0xFF) << 8 | (sec_spg_dat[2 + e_offset] & 0xFF);
|
|
edata[2] = (sec_spg_dat[5 + e_offset] & 0xFF) << 8 | (sec_spg_dat[4 + e_offset] & 0xFF);
|
|
edata[3] = (sec_spg_dat[7 + e_offset] & 0xFF) << 8 | (sec_spg_dat[6 + e_offset] & 0xFF);
|
|
edata[4] = (sec_spg_dat[9 + e_offset] & 0xFF) << 8 | (sec_spg_dat[8 + e_offset] & 0xFF);
|
|
|
|
if (edata[0] || edata[1] || edata[2] || edata[3] || edata[4]) {
|
|
snprintf(buff, sizeof(buff), "%03d: %04x%04x%04x%04x%04x\n",
|
|
i + (ts->sponge_dump_event * dump_area),
|
|
edata[0], edata[1], edata[2], edata[3], edata[4]);
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG)
|
|
sec_tsp_sponge_log(buff);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
vfree(sec_spg_dat);
|
|
input_info(true, &ts->client->dev, "%s: --\n", __func__);
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(stm_ts_sponge_dump_flush);
|
|
#endif
|
|
|
|
static int stm_ts_init_check(struct stm_ts_data *ts)
|
|
{
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_POWER_OFF) {
|
|
input_err(true, &ts->client->dev, "%s: Touch is stopped\n", __func__);
|
|
return -EPERM;
|
|
}
|
|
|
|
if (ts->plat_data->power_state == SEC_INPUT_STATE_LPM) {
|
|
input_err(true, &ts->client->dev, "%s: Touch is LP mode\n", __func__);
|
|
return -EPERM;
|
|
}
|
|
|
|
if (ts->reset_is_on_going) {
|
|
input_err(true, &ts->client->dev, "%s: Reset is ongoing!\n", __func__);
|
|
return -EPERM;
|
|
}
|
|
|
|
if (ts->sec.cmd_is_running) {
|
|
input_err(true, &ts->client->dev, "%s: cmd is running\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t get_cmoffset_dump(struct stm_ts_data *ts, char *buf, u8 position)
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
ret = stm_ts_init_check(ts);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (ts->plat_data->dump_ic_ver == STM_TS_GET_CMOFFSET_DUMP_V1) {
|
|
return get_cmoffset_dump_v1(ts, buf, position);
|
|
} else if (ts->plat_data->dump_ic_ver == STM_TS_GET_CMOFFSET_DUMP_V2) {
|
|
return get_cmoffset_dump_v2(ts, buf, position);
|
|
}
|
|
|
|
return strlen(buf);
|
|
|
|
}
|
|
static ssize_t stm_ts_tsp_cmoffset_read(struct file *file, char __user *buf,
|
|
size_t len, loff_t *offset)
|
|
{
|
|
pr_info("[sec_input] %s called offset:%d\n", __func__, (int)*offset);
|
|
return stm_ts_tsp_cmoffset_all_read(file, buf, len, offset);
|
|
}
|
|
|
|
static sec_input_proc_ops(THIS_MODULE, tsp_cmoffset_all_file_ops, stm_ts_tsp_cmoffset_read, NULL);
|
|
|
|
void stm_ts_init_proc(struct stm_ts_data *ts)
|
|
{
|
|
struct proc_dir_entry *entry_cmoffset_all;
|
|
|
|
ts->proc_cmoffset_size = (ts->tx_count * ts->rx_count * 4 + 100) * 4;
|
|
ts->proc_cmoffset_all_size = ts->proc_cmoffset_size * 2;
|
|
|
|
ts->cmoffset_sdc_proc = kzalloc(ts->proc_cmoffset_size, GFP_KERNEL);
|
|
if (!ts->cmoffset_sdc_proc)
|
|
return;
|
|
|
|
ts->cmoffset_sub_proc = kzalloc(ts->proc_cmoffset_size, GFP_KERNEL);
|
|
if (!ts->cmoffset_sub_proc)
|
|
goto err_alloc_sub;
|
|
|
|
ts->cmoffset_main_proc = kzalloc(ts->proc_cmoffset_size, GFP_KERNEL);
|
|
if (!ts->cmoffset_main_proc)
|
|
goto err_alloc_main;
|
|
|
|
if (ts->plat_data->support_dual_foldable == SUB_TOUCH)
|
|
entry_cmoffset_all = proc_create("tsp_cmoffset_all_sub", S_IFREG | 0444, NULL, &tsp_cmoffset_all_file_ops);
|
|
else
|
|
entry_cmoffset_all = proc_create("tsp_cmoffset_all", S_IFREG | 0444, NULL, &tsp_cmoffset_all_file_ops);
|
|
|
|
if (!entry_cmoffset_all) {
|
|
input_err(true, &ts->client->dev, "%s: failed to create /proc/tsp_cmoffset_all\n", __func__);
|
|
goto err_cmoffset_proc_create;
|
|
}
|
|
|
|
input_info(true, &ts->client->dev, "%s: done\n", __func__);
|
|
return;
|
|
|
|
err_cmoffset_proc_create:
|
|
kfree(ts->cmoffset_main_proc);
|
|
err_alloc_main:
|
|
kfree(ts->cmoffset_sub_proc);
|
|
err_alloc_sub:
|
|
kfree(ts->cmoffset_sdc_proc);
|
|
|
|
ts->cmoffset_sdc_proc = NULL;
|
|
ts->cmoffset_sub_proc = NULL;
|
|
ts->cmoffset_main_proc = NULL;
|
|
|
|
input_err(true, &ts->client->dev, "%s: failed\n", __func__);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|