mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
3055 lines
76 KiB
C
3055 lines
76 KiB
C
/*
|
|
* Copyright (C) 2010, Samsung Electronics Co. Ltd. All Rights Reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/mt.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c/mxt540e.h>
|
|
#include <asm/unaligned.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#define OBJECT_TABLE_START_ADDRESS 7
|
|
#define OBJECT_TABLE_ELEMENT_SIZE 6
|
|
|
|
#define CMD_RESET_OFFSET 0
|
|
#define CMD_BACKUP_OFFSET 1
|
|
#define CMD_CALIBRATE_OFFSET 2
|
|
#define CMD_REPORTATLL_OFFSET 3
|
|
#define CMD_DEBUG_CTRL_OFFSET 4
|
|
#define CMD_DIAGNOSTIC_OFFSET 5
|
|
|
|
#define DETECT_MSG_MASK 0x80
|
|
#define PRESS_MSG_MASK 0x40
|
|
#define RELEASE_MSG_MASK 0x20
|
|
#define MOVE_MSG_MASK 0x10
|
|
#define SUPPRESS_MSG_MASK 0x02
|
|
|
|
/* Version */
|
|
#define MXT540E_VER_10 0x10
|
|
|
|
/* Slave addresses */
|
|
#define MXT540E_APP_LOW 0x4C
|
|
#define MXT540E_APP_HIGH 0x4D
|
|
#define MXT540E_BOOT_LOW 0x26
|
|
#define MXT540E_BOOT_HIGH 0x27
|
|
|
|
/* FIRMWARE NAME */
|
|
#define MXT540E_FW_NAME "tsp_atmel/mXT540E.fw"
|
|
|
|
#define MXT540E_BOOT_VALUE 0xa5
|
|
#define MXT540E_BACKUP_VALUE 0x55
|
|
|
|
/* Bootloader mode status */
|
|
#define MXT540E_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */
|
|
#define MXT540E_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */
|
|
#define MXT540E_FRAME_CRC_CHECK 0x02
|
|
#define MXT540E_FRAME_CRC_FAIL 0x03
|
|
#define MXT540E_FRAME_CRC_PASS 0x04
|
|
#define MXT540E_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */
|
|
#define MXT540E_BOOT_STATUS_MASK 0x3f
|
|
|
|
/* Command to unlock bootloader */
|
|
#define MXT540E_UNLOCK_CMD_MSB 0xaa
|
|
#define MXT540E_UNLOCK_CMD_LSB 0xdc
|
|
|
|
#define ID_BLOCK_SIZE 7
|
|
|
|
#define DRIVER_FILTER
|
|
|
|
#define MXT540E_STATE_INACTIVE -1
|
|
#define MXT540E_STATE_RELEASE 0
|
|
#define MXT540E_STATE_PRESS 1
|
|
#define MXT540E_STATE_MOVE 2
|
|
|
|
#define MAX_FINGER_NUM 10
|
|
|
|
#define MEDIANERROR_MAX_BAT 5
|
|
#define MEDIANERROR_MAX_TA 10
|
|
|
|
struct object_t {
|
|
u8 object_type;
|
|
u16 i2c_address;
|
|
u8 size;
|
|
u8 instances;
|
|
u8 num_report_ids;
|
|
} __packed;
|
|
|
|
struct finger_info {
|
|
s16 x;
|
|
s16 y;
|
|
s16 z;
|
|
u16 w;
|
|
s8 state;
|
|
int16_t component;
|
|
};
|
|
|
|
struct median_error_t {
|
|
u8 err_cnt_bat;
|
|
u8 err_cnt_ta;
|
|
u8 setting_flag;
|
|
u8 table_cnt;
|
|
u8 table_ta[4];
|
|
u8 table_bat[4];
|
|
};
|
|
|
|
struct report_id_map_t {
|
|
u8 object_type; /*!< Object type. */
|
|
u8 instance; /*!< Instance number. */
|
|
};
|
|
|
|
u8 max_report_id;
|
|
struct report_id_map_t *rid_map;
|
|
static bool rid_map_alloc;
|
|
|
|
struct mxt540e_data {
|
|
struct i2c_client *client;
|
|
struct input_dev *input_dev;
|
|
struct median_error_t *median_error;
|
|
struct object_t *objects;
|
|
struct delayed_work config_dwork;
|
|
struct delayed_work resume_check_dwork;
|
|
struct delayed_work cal_check_dwork;
|
|
const u8 *power_cfg;
|
|
const u8 *t48_config_batt_e;
|
|
const u8 *t48_config_chrg_e;
|
|
u16 msg_proc;
|
|
u16 cmd_proc;
|
|
u16 msg_object_size;
|
|
u32 x_dropbits:2;
|
|
u32 y_dropbits:2;
|
|
u32 finger_mask;
|
|
u8 irqf_trigger_type;
|
|
u8 objects_len;
|
|
u8 tsp_version;
|
|
u8 tsp_build;
|
|
u8 family_id;
|
|
u8 finger_type;
|
|
u8 chrgtime_batt;
|
|
u8 chrgtime_charging;
|
|
u8 atchcalst;
|
|
u8 atchcalsthr;
|
|
u8 tchthr_batt;
|
|
u8 tchthr_charging;
|
|
u8 actvsyncsperx_batt;
|
|
u8 actvsyncsperx_charging;
|
|
u8 calcfg_batt_e;
|
|
u8 calcfg_charging_e;
|
|
u8 atchfrccalthr_e;
|
|
u8 atchfrccalratio_e;
|
|
void (*power_on) (struct device *);
|
|
void (*power_off) (struct device *);
|
|
void (*register_cb) (void *);
|
|
void (*read_ta_status) (void *);
|
|
int num_fingers;
|
|
int gpio_read_done;
|
|
struct finger_info fingers[];
|
|
};
|
|
|
|
struct mxt540e_data *copy_data;
|
|
static int mxt540e_enabled;
|
|
static bool g_debug_switch;
|
|
static u8 tsp_version_disp;
|
|
static u8 threshold;
|
|
static int firm_status_data;
|
|
/* static bool deepsleep; */
|
|
static int check_resume_err;
|
|
static int check_resume_err_count;
|
|
static int check_calibrate;
|
|
static int config_dwork_flag;
|
|
int16_t sumsize;
|
|
int touch_is_pressed;
|
|
EXPORT_SYMBOL(touch_is_pressed);
|
|
|
|
struct class *sec_class;
|
|
struct device *sec_touchscreen;
|
|
|
|
static u8 firmware_latest = 0x13;
|
|
static u8 build_latest = 0xAA;
|
|
|
|
struct device *mxt540e_noise_test;
|
|
/*
|
|
top_left, top_right, center, bottom_left, bottom_right
|
|
*/
|
|
unsigned int test_node[5] = { 443, 53, 253, 422, 32 };
|
|
uint16_t qt_refrence_node[540] = { 0 };
|
|
uint16_t qt_delta_node[540] = { 0 };
|
|
|
|
static int read_mem(struct mxt540e_data *data, u16 reg, u8 len, u8 *buf)
|
|
{
|
|
int ret;
|
|
u16 le_reg = cpu_to_le16(reg);
|
|
struct i2c_msg msg[2] = {
|
|
{
|
|
.addr = data->client->addr,
|
|
.flags = 0,
|
|
.len = 2,
|
|
.buf = (u8 *) &le_reg,
|
|
},
|
|
{
|
|
.addr = data->client->addr,
|
|
.flags = I2C_M_RD,
|
|
.len = len,
|
|
.buf = buf,
|
|
},
|
|
};
|
|
|
|
ret = i2c_transfer(data->client->adapter, msg, 2);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return ret == 2 ? 0 : -EIO;
|
|
|
|
}
|
|
|
|
static int write_mem(struct mxt540e_data *data, u16 reg, u8 len, const u8 *buf)
|
|
{
|
|
int ret;
|
|
u8 tmp[len + 2];
|
|
|
|
put_unaligned_le16(cpu_to_le16(reg), tmp);
|
|
memcpy(tmp + 2, buf, len);
|
|
|
|
ret = i2c_master_send(data->client, tmp, sizeof(tmp));
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return ret == sizeof(tmp) ? 0 : -EIO;
|
|
}
|
|
|
|
static int mxt540e_reset(struct mxt540e_data *data)
|
|
{
|
|
u8 buf = 1u;
|
|
return write_mem(data, data->cmd_proc + CMD_RESET_OFFSET, 1, &buf);
|
|
}
|
|
|
|
static int mxt540e_backup(struct mxt540e_data *data)
|
|
{
|
|
u8 buf = 0x55u;
|
|
return write_mem(data, data->cmd_proc + CMD_BACKUP_OFFSET, 1, &buf);
|
|
}
|
|
|
|
static int get_object_info(struct mxt540e_data *data, u8 object_type,
|
|
u16 *size, u16 *address)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < data->objects_len; i++) {
|
|
if (data->objects[i].object_type == object_type) {
|
|
*size = data->objects[i].size + 1;
|
|
*address = data->objects[i].i2c_address;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int write_config(struct mxt540e_data *data, u8 type, const u8 * cfg)
|
|
{
|
|
int ret;
|
|
u16 address = 0;
|
|
u16 size = 0;
|
|
|
|
ret = get_object_info(data, type, &size, &address);
|
|
|
|
if (size == 0 && address == 0)
|
|
return 0;
|
|
else
|
|
return write_mem(data, address, size, cfg);
|
|
}
|
|
|
|
static int check_instance(struct mxt540e_data *data, u8 object_type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < data->objects_len; i++) {
|
|
if (data->objects[i].object_type == object_type)
|
|
return data->objects[i].instances;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int init_write_config(struct mxt540e_data *data, u8 type, const u8 * cfg)
|
|
{
|
|
int ret;
|
|
u16 address = 0;
|
|
u16 size = 0;
|
|
u8 *temp;
|
|
int instance_num;
|
|
|
|
ret = get_object_info(data, type, &size, &address);
|
|
|
|
if ((size == 0) || (address == 0))
|
|
return 0;
|
|
|
|
ret = write_mem(data, address, size, cfg);
|
|
instance_num = check_instance(data, type);
|
|
if (instance_num > 0) {
|
|
printk(KERN_DEBUG "[TSP] exist instance%d objects (%d)\n",
|
|
instance_num, type);
|
|
temp = kmalloc(size * instance_num * sizeof(u8), GFP_KERNEL);
|
|
memset(temp, 0, size * instance_num);
|
|
ret |= write_mem(data, address + size,
|
|
size * instance_num, temp);
|
|
if (ret < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n", __func__,
|
|
__LINE__);
|
|
kfree(temp);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static u32 crc24(u32 crc, u8 byte1, u8 byte2)
|
|
{
|
|
static const u32 crcpoly = 0x80001B;
|
|
u32 res;
|
|
u16 data_word;
|
|
|
|
data_word = (((u16) byte2) << 8) | byte1;
|
|
res = (crc << 1) ^ (u32) data_word;
|
|
|
|
if (res & 0x1000000)
|
|
res ^= crcpoly;
|
|
|
|
return res;
|
|
}
|
|
|
|
static int calculate_infoblock_crc(struct mxt540e_data *data,
|
|
u32 *crc_pointer)
|
|
{
|
|
u32 crc = 0;
|
|
u8 mem[7 + data->objects_len * 6];
|
|
int status;
|
|
int i;
|
|
|
|
status = read_mem(data, 0, sizeof(mem), mem);
|
|
|
|
if (status)
|
|
return status;
|
|
|
|
for (i = 0; i < sizeof(mem) - 1; i += 2)
|
|
crc = crc24(crc, mem[i], mem[i + 1]);
|
|
|
|
*crc_pointer = crc24(crc, mem[i], 0) & 0x00FFFFFF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* mxt540e reconfigration */
|
|
static void mxt_reconfigration_normal(struct work_struct *work)
|
|
{
|
|
int error, id;
|
|
u16 size;
|
|
|
|
struct mxt540e_data *data =
|
|
container_of(work, struct mxt540e_data, config_dwork.work);
|
|
u16 obj_address = 0;
|
|
if (mxt540e_enabled) {
|
|
disable_irq(data->client->irq);
|
|
|
|
for (id = 0; id < MAX_FINGER_NUM; ++id) {
|
|
if (data->fingers[id].state == MXT540E_STATE_INACTIVE)
|
|
continue;
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
printk(KERN_DEBUG "[TSP] touch pressed!! %s didn't execute!!\n",
|
|
__func__);
|
|
enable_irq(data->client->irq);
|
|
return;
|
|
}
|
|
|
|
get_object_info(data, GEN_ACQUISITIONCONFIG_T8, &size,
|
|
&obj_address);
|
|
error = write_mem(data, obj_address + 8, 1,
|
|
&data->atchfrccalthr_e);
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n", __func__,
|
|
__LINE__);
|
|
error =
|
|
write_mem(data, obj_address + 9, 1,
|
|
&data->atchfrccalratio_e);
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n", __func__,
|
|
__LINE__);
|
|
printk(KERN_DEBUG "[TSP] %s execute!!\n", __func__);
|
|
enable_irq(data->client->irq);
|
|
}
|
|
config_dwork_flag = 0;
|
|
return;
|
|
}
|
|
|
|
static void resume_check_dworker(struct work_struct *work)
|
|
{
|
|
check_resume_err = 0;
|
|
check_resume_err_count = 0;
|
|
}
|
|
|
|
static void cal_check_dworker(struct work_struct *work)
|
|
{
|
|
struct mxt540e_data *data =
|
|
container_of(work, struct mxt540e_data, cal_check_dwork.work);
|
|
int error;
|
|
u16 size;
|
|
u8 value;
|
|
u16 obj_address = 0;
|
|
if (mxt540e_enabled) {
|
|
check_calibrate = 0;
|
|
get_object_info(data, GEN_POWERCONFIG_T7, &size, &obj_address);
|
|
value = 50;
|
|
error = write_mem(data, obj_address + 2, 1, &value);
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n", __func__,
|
|
__LINE__);
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint8_t calibrate_chip(struct mxt540e_data *data)
|
|
{
|
|
u8 cal_data = 1;
|
|
int ret = 0;
|
|
/* send calibration command to the chip */
|
|
ret = write_mem(data, data->cmd_proc + CMD_CALIBRATE_OFFSET, 1,
|
|
&cal_data);
|
|
|
|
if (!ret) {
|
|
printk(KERN_DEBUG "[TSP] calibration success!!!\n");
|
|
if (check_resume_err == 2) {
|
|
check_resume_err = 1;
|
|
schedule_delayed_work(&data->resume_check_dwork,
|
|
msecs_to_jiffies(2500));
|
|
} else if (check_resume_err == 1) {
|
|
cancel_delayed_work(&data->resume_check_dwork);
|
|
schedule_delayed_work(&data->resume_check_dwork,
|
|
msecs_to_jiffies(2500));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void mxt540e_ta_probe(int ta_status)
|
|
{
|
|
u16 obj_address;
|
|
u16 size;
|
|
u8 value;
|
|
int error;
|
|
struct mxt540e_data *data = copy_data;
|
|
|
|
if (!mxt540e_enabled) {
|
|
printk(KERN_ERR "mxt540e_enabled is 0\n");
|
|
return;
|
|
}
|
|
|
|
data->median_error->err_cnt_ta = 0;
|
|
data->median_error->err_cnt_bat = 0;
|
|
data->median_error->setting_flag = 1;
|
|
data->median_error->table_cnt = 0;
|
|
|
|
error = 0;
|
|
obj_address = 0;
|
|
if (ta_status) {
|
|
get_object_info(data, SPT_CTECONFIG_T46, &size, &obj_address);
|
|
value = data->actvsyncsperx_charging;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
get_object_info(data, PROCG_NOISESUPPRESSION_T48, &size,
|
|
&obj_address);
|
|
error |=
|
|
write_config(data, data->t48_config_chrg_e[0],
|
|
data->t48_config_chrg_e + 1);
|
|
threshold = data->tchthr_charging;
|
|
} else {
|
|
get_object_info(data, TOUCH_MULTITOUCHSCREEN_T9, &size,
|
|
&obj_address);
|
|
value = 192;
|
|
error |= write_mem(data, obj_address + 6, 1, &value);
|
|
value = 50;
|
|
error |= write_mem(data, obj_address + 7, 1, &value);
|
|
value = 80;
|
|
error |= write_mem(data, obj_address + 13, 1, &value);
|
|
get_object_info(data, SPT_GENERICDATA_T57, &size, &obj_address);
|
|
value = 25;
|
|
error |= write_mem(data, obj_address + 1, 1, &value);
|
|
get_object_info(data, SPT_CTECONFIG_T46, &size, &obj_address);
|
|
value = data->actvsyncsperx_batt;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
get_object_info(data, PROCG_NOISESUPPRESSION_T48, &size,
|
|
&obj_address);
|
|
error |=
|
|
write_config(data, data->t48_config_batt_e[0],
|
|
data->t48_config_batt_e + 1);
|
|
threshold = data->tchthr_batt;
|
|
}
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s Error!!\n", __func__);
|
|
};
|
|
|
|
#if defined(DRIVER_FILTER)
|
|
static void equalize_coordinate(bool detect, u8 id, u16 *px, u16 *py)
|
|
{
|
|
static int tcount[MAX_FINGER_NUM] = { 0, };
|
|
static u16 pre_x[MAX_FINGER_NUM][4] = { {0}, };
|
|
static u16 pre_y[MAX_FINGER_NUM][4] = { {0}, };
|
|
int coff[4] = { 0, };
|
|
int distance = 0;
|
|
|
|
if (detect)
|
|
tcount[id] = 0;
|
|
|
|
pre_x[id][tcount[id] % 4] = *px;
|
|
pre_y[id][tcount[id] % 4] = *py;
|
|
|
|
if (tcount[id] > 3) {
|
|
distance =
|
|
abs(pre_x[id][(tcount[id] - 1) % 4] - *px) +
|
|
abs(pre_y[id][(tcount[id] - 1) % 4] - *py);
|
|
|
|
coff[0] = (u8)(2 + distance / 5);
|
|
if (coff[0] < 8) {
|
|
coff[0] = max(2, coff[0]);
|
|
coff[1] = min((8 - coff[0]), (coff[0] >> 1) + 1);
|
|
coff[2] = min((8 - coff[0] - coff[1]),
|
|
(coff[1] >> 1) + 1);
|
|
coff[3] = 8 - coff[0] - coff[1] - coff[2];
|
|
|
|
*px = (u16)((*px * (coff[0]) +
|
|
pre_x[id][(tcount[id] - 1) % 4] * (coff[1]) +
|
|
pre_x[id][(tcount[id] - 2) % 4] * (coff[2]) +
|
|
pre_x[id][(tcount[id] - 3) % 4] * (coff[3]))
|
|
/ 8);
|
|
*py = (u16)((*py * (coff[0]) +
|
|
pre_y[id][(tcount[id] - 1) % 4] * (coff[1]) +
|
|
pre_y[id][(tcount[id] - 2) % 4] * (coff[2]) +
|
|
pre_y[id][(tcount[id] - 3) % 4] * (coff[3]))
|
|
/ 8);
|
|
} else {
|
|
*px = (u16)((*px * 4 + pre_x[id][(tcount[id] - 1) % 4])
|
|
/ 5);
|
|
*py = (u16)((*py * 4 + pre_y[id][(tcount[id] - 1) % 4])
|
|
/ 5);
|
|
}
|
|
}
|
|
tcount[id]++;
|
|
}
|
|
#endif /* DRIVER_FILTER */
|
|
|
|
uint8_t reportid_to_type(struct mxt540e_data *data, u8 report_id, u8 * instance)
|
|
{
|
|
struct report_id_map_t *report_id_map;
|
|
report_id_map = rid_map;
|
|
|
|
if (report_id <= max_report_id) {
|
|
*instance = report_id_map[report_id].instance;
|
|
return report_id_map[report_id].object_type;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_init_touch_driver(struct mxt540e_data *data)
|
|
{
|
|
struct object_t *object_table;
|
|
struct report_id_map_t *report_id_map_t;
|
|
u32 read_crc = 0;
|
|
u32 calc_crc;
|
|
u16 crc_address;
|
|
u16 dummy;
|
|
int i, j;
|
|
u8 id[ID_BLOCK_SIZE];
|
|
int ret;
|
|
u8 type_count = 0;
|
|
u8 tmp;
|
|
int current_report_id, start_report_id;
|
|
|
|
ret = read_mem(data, 0, sizeof(id), id);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
dev_info(&data->client->dev, "family = %#02x, variant = %#02x, version "
|
|
"= %#02x, build = %d\n", id[0], id[1], id[2], id[3]);
|
|
printk(KERN_ERR "family = %#02x, variant = %#02x, version "
|
|
"= %#02x, build = %d\n", id[0], id[1], id[2], id[3]);
|
|
dev_dbg(&data->client->dev, "matrix X size = %d\n", id[4]);
|
|
dev_dbg(&data->client->dev, "matrix Y size = %d\n", id[5]);
|
|
|
|
data->family_id = id[0];
|
|
data->tsp_version = id[2];
|
|
data->tsp_build = id[3];
|
|
data->objects_len = id[6];
|
|
|
|
tsp_version_disp = data->tsp_version;
|
|
|
|
object_table = kmalloc(data->objects_len * sizeof(*object_table),
|
|
GFP_KERNEL);
|
|
if (!object_table)
|
|
return -ENOMEM;
|
|
|
|
ret = read_mem(data, OBJECT_TABLE_START_ADDRESS,
|
|
data->objects_len * sizeof(*object_table),
|
|
(u8 *) object_table);
|
|
if (ret)
|
|
goto err;
|
|
|
|
max_report_id = 0;
|
|
|
|
for (i = 0; i < data->objects_len; i++) {
|
|
object_table[i].i2c_address =
|
|
le16_to_cpu(object_table[i].i2c_address);
|
|
max_report_id +=
|
|
object_table[i].num_report_ids *
|
|
(object_table[i].instances + 1);
|
|
tmp = 0;
|
|
if (object_table[i].num_report_ids) {
|
|
tmp = type_count + 1;
|
|
type_count += object_table[i].num_report_ids *
|
|
(object_table[i].instances + 1);
|
|
}
|
|
switch (object_table[i].object_type) {
|
|
case TOUCH_MULTITOUCHSCREEN_T9:
|
|
data->finger_type = tmp;
|
|
dev_dbg(&data->client->dev, "Finger type = %d\n",
|
|
data->finger_type);
|
|
break;
|
|
case GEN_MESSAGEPROCESSOR_T5:
|
|
data->msg_object_size = object_table[i].size + 1;
|
|
dev_dbg(&data->client->dev, "Message object size = "
|
|
"%d\n", data->msg_object_size);
|
|
break;
|
|
}
|
|
}
|
|
if (rid_map_alloc) {
|
|
rid_map_alloc = false;
|
|
kfree(rid_map);
|
|
}
|
|
rid_map = kmalloc((sizeof(*report_id_map_t) * max_report_id + 1),
|
|
GFP_KERNEL);
|
|
if (!rid_map) {
|
|
kfree(object_table);
|
|
return -ENOMEM;
|
|
}
|
|
rid_map_alloc = true;
|
|
rid_map[0].instance = 0;
|
|
rid_map[0].object_type = 0;
|
|
current_report_id = 1;
|
|
|
|
for (i = 0; i < data->objects_len; i++) {
|
|
if (object_table[i].num_report_ids != 0) {
|
|
for (j = 0; j <= object_table[i].instances; j++) {
|
|
for (start_report_id = current_report_id;
|
|
current_report_id <
|
|
(start_report_id +
|
|
object_table[i].num_report_ids);
|
|
current_report_id++) {
|
|
rid_map[current_report_id].instance = j;
|
|
rid_map[current_report_id].object_type =
|
|
object_table[i].object_type;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
data->objects = object_table;
|
|
|
|
/* Verify CRC */
|
|
crc_address = OBJECT_TABLE_START_ADDRESS +
|
|
data->objects_len * OBJECT_TABLE_ELEMENT_SIZE;
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
#error The following code will likely break on a big endian machine
|
|
#endif
|
|
ret = read_mem(data, crc_address, 3, (u8 *) &read_crc);
|
|
if (ret)
|
|
goto err;
|
|
|
|
read_crc = le32_to_cpu(read_crc);
|
|
|
|
ret = calculate_infoblock_crc(data, &calc_crc);
|
|
if (ret)
|
|
goto err;
|
|
|
|
if (read_crc != calc_crc) {
|
|
dev_err(&data->client->dev, "CRC error\n");
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
ret = get_object_info(data, GEN_MESSAGEPROCESSOR_T5, &dummy,
|
|
&data->msg_proc);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = get_object_info(data, GEN_COMMANDPROCESSOR_T6, &dummy,
|
|
&data->cmd_proc);
|
|
if (ret)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
kfree(object_table);
|
|
return ret;
|
|
}
|
|
|
|
static void resume_cal_err_func(struct mxt540e_data *data)
|
|
{
|
|
int i;
|
|
bool ta_status;
|
|
int count;
|
|
u8 id[ID_BLOCK_SIZE];
|
|
int ret;
|
|
int retry;
|
|
|
|
printk(KERN_DEBUG "[TSP] %s\n", __func__);
|
|
cancel_delayed_work(&data->config_dwork);
|
|
cancel_delayed_work(&data->resume_check_dwork);
|
|
cancel_delayed_work(&data->cal_check_dwork);
|
|
data->power_off(&data->client->dev);
|
|
|
|
count = 0;
|
|
for (i = 0; i < data->num_fingers; i++) {
|
|
if (data->fingers[i].state == MXT540E_STATE_INACTIVE)
|
|
continue;
|
|
data->fingers[i].z = 0;
|
|
data->fingers[i].state = MXT540E_STATE_INACTIVE;
|
|
|
|
input_mt_slot(data->input_dev, i);
|
|
input_mt_report_slot_state(data->input_dev,
|
|
MT_TOOL_FINGER, false);
|
|
|
|
#if 0
|
|
#if defined(CONFIG_SHAPE_TOUCH)
|
|
if (get_sec_debug_level() != 0)
|
|
printk(KERN_DEBUG
|
|
"[TSP] id[%d],x=%d,y=%d,z=%d,w=%d,com=%d\n", i,
|
|
data->fingers[i].x, data->fingers[i].y,
|
|
data->fingers[i].z, data->fingers[i].w,
|
|
data->fingers[i].component);
|
|
else
|
|
printk(KERN_DEBUG "[TSP] id[%d] status:%d\n", i,
|
|
data->fingers[i].z);
|
|
#else
|
|
if (get_sec_debug_level() != 0)
|
|
printk(KERN_DEBUG "[TSP] id[%d],x=%d,y=%d,z=%d,w=%d\n",
|
|
i, data->fingers[i].x, data->fingers[i].y,
|
|
data->fingers[i].z, data->fingers[i].w);
|
|
else
|
|
printk(KERN_DEBUG "[TSP] id[%d] status:%d\n", i,
|
|
data->fingers[i].z);
|
|
#endif
|
|
#else
|
|
if (data->fingers[i].z == 0)
|
|
printk(KERN_DEBUG "[TSP] released\n");
|
|
else
|
|
printk(KERN_DEBUG "[TSP] pressed\n");
|
|
#endif
|
|
count++;
|
|
}
|
|
|
|
if (count)
|
|
input_sync(data->input_dev);
|
|
touch_is_pressed = 0;
|
|
|
|
msleep(50);
|
|
data->power_on(&data->client->dev);
|
|
|
|
ret = 0;
|
|
retry = 3;
|
|
ret = read_mem(data, 0, sizeof(id), id);
|
|
if (ret) {
|
|
while (retry--) {
|
|
printk(KERN_DEBUG "[TSP] chip boot failed. retry(%d)\n",
|
|
retry);
|
|
|
|
data->power_off(&data->client->dev);
|
|
msleep(200);
|
|
data->power_on(&data->client->dev);
|
|
|
|
ret = read_mem(data, 0, sizeof(id), id);
|
|
if (ret == 0 || retry <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d\n", ta_status);
|
|
mxt540e_ta_probe(ta_status);
|
|
}
|
|
check_resume_err = 2;
|
|
calibrate_chip(data);
|
|
check_calibrate = 3;
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
config_dwork_flag = 3;
|
|
}
|
|
|
|
static void median_filter_err_func(struct mxt540e_data *data)
|
|
{
|
|
struct median_error_t *median_error = data->median_error;
|
|
u16 obj_address = 0;
|
|
u16 size;
|
|
u8 value;
|
|
int error = 0;
|
|
bool ta_status = 0;
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d\n", ta_status);
|
|
|
|
if (ta_status) {
|
|
get_object_info(data, PROCG_NOISESUPPRESSION_T48,
|
|
&size, &obj_address);
|
|
#if 0
|
|
value = 33;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
#else
|
|
if (median_error->err_cnt_ta >= MEDIANERROR_MAX_TA) {
|
|
median_error->err_cnt_ta = 0;
|
|
|
|
median_error->table_cnt++;
|
|
if (median_error->table_cnt > 3)
|
|
median_error->table_cnt = 0;
|
|
|
|
value = median_error->table_ta
|
|
[median_error->table_cnt];
|
|
error |= write_mem(data, obj_address + 3,
|
|
1, &value);
|
|
printk(KERN_DEBUG "[TSP] median base_Freq_ta %d\n",
|
|
value);
|
|
} else {
|
|
median_error->err_cnt_ta++;
|
|
printk(KERN_DEBUG "[TSP] median error_cnt_ta %d\n",
|
|
median_error->err_cnt_ta);
|
|
}
|
|
|
|
if (median_error->setting_flag) {
|
|
median_error->setting_flag = 0;
|
|
value = median_error->table_ta[0];
|
|
error |= write_mem(data, obj_address + 3,
|
|
1, &value);
|
|
}
|
|
#endif
|
|
value = 1;
|
|
error |= write_mem(data, obj_address + 8, 1, &value);
|
|
|
|
value = 2;
|
|
error |= write_mem(data, obj_address + 9, 1, &value);
|
|
|
|
value = 100;
|
|
error |= write_mem(data, obj_address + 17, 1, &value);
|
|
|
|
value = 20;
|
|
error |= write_mem(data, obj_address + 22, 1, &value);
|
|
|
|
value = 2;
|
|
error |= write_mem(data, obj_address + 23, 1, &value);
|
|
|
|
value = 46;
|
|
error |= write_mem(data, obj_address + 25, 1, &value);
|
|
|
|
value = 80;
|
|
error |= write_mem(data, obj_address + 34, 1, &value);
|
|
|
|
value = 35;
|
|
error |= write_mem(data, obj_address + 35, 1, &value);
|
|
|
|
value = 15;
|
|
error |= write_mem(data, obj_address + 37, 1, &value);
|
|
|
|
value = 5;
|
|
error |= write_mem(data, obj_address + 38, 1, &value);
|
|
|
|
value = 65;
|
|
error |= write_mem(data, obj_address + 39, 1, &value);
|
|
|
|
value = 30;
|
|
error |= write_mem(data, obj_address + 41, 1, &value);
|
|
|
|
value = 50;
|
|
error |= write_mem(data, obj_address + 42, 1, &value);
|
|
|
|
value = 7;
|
|
error |= write_mem(data, obj_address + 45, 1, &value);
|
|
|
|
value = 7;
|
|
error |= write_mem(data, obj_address + 46, 1, &value);
|
|
|
|
value = 40;
|
|
error |= write_mem(data, obj_address + 50, 1, &value);
|
|
|
|
value = 32;
|
|
error |= write_mem(data, obj_address + 51, 1, &value);
|
|
|
|
value = 15;
|
|
error |= write_mem(data, obj_address + 52, 1, &value);
|
|
|
|
get_object_info(data, SPT_CTECONFIG_T46,
|
|
&size, &obj_address);
|
|
value = 32;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
|
|
get_object_info(data, SPT_GENERICDATA_T57,
|
|
&size, &obj_address);
|
|
value = 22;
|
|
error |= write_mem(data, obj_address + 1, 1, &value);
|
|
} else {
|
|
get_object_info(data, TOUCH_MULTITOUCHSCREEN_T9,
|
|
&size, &obj_address);
|
|
value = 160;
|
|
error |= write_mem(data, obj_address + 6, 1, &value);
|
|
|
|
value = 45;
|
|
error |= write_mem(data, obj_address + 7, 1, &value);
|
|
|
|
value = 80;
|
|
error |= write_mem(data, obj_address + 13, 1, &value);
|
|
|
|
value = 3;
|
|
error |= write_mem(data, obj_address + 22, 1, &value);
|
|
|
|
value = 2;
|
|
error |= write_mem(data, obj_address + 24, 1, &value);
|
|
|
|
get_object_info(data, PROCG_NOISESUPPRESSION_T48,
|
|
&size, &obj_address);
|
|
value = 242;
|
|
error |= write_mem(data, obj_address + 2, 1, &value);
|
|
#if 0
|
|
value = 20;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
#else
|
|
if (median_error->err_cnt_bat >= MEDIANERROR_MAX_BAT) {
|
|
median_error->err_cnt_bat = 0;
|
|
|
|
median_error->table_cnt++;
|
|
if (median_error->table_cnt > 3)
|
|
median_error->table_cnt = 0;
|
|
|
|
value = median_error->table_bat
|
|
[median_error->table_cnt];
|
|
error |= write_mem(data, obj_address + 3,
|
|
1, &value);
|
|
printk(KERN_DEBUG "[TSP] median base_freq_bat %d\n",
|
|
value);
|
|
} else {
|
|
median_error->err_cnt_bat++;
|
|
printk(KERN_DEBUG "[TSP] median error_cnt_bat %d\n",
|
|
median_error->err_cnt_bat);
|
|
}
|
|
|
|
if (median_error->setting_flag) {
|
|
median_error->setting_flag = 0;
|
|
value = median_error->table_bat[0];
|
|
error |= write_mem(data, obj_address + 3,
|
|
1, &value);
|
|
}
|
|
#endif
|
|
value = 100;
|
|
error |= write_mem(data, obj_address + 17, 1, &value);
|
|
|
|
value = 25;
|
|
error |= write_mem(data, obj_address + 22, 1, &value);
|
|
|
|
value = 46;
|
|
error |= write_mem(data, obj_address + 25, 1, &value);
|
|
|
|
value = 112;
|
|
error |= write_mem(data, obj_address + 34, 1, &value);
|
|
|
|
value = 35;
|
|
error |= write_mem(data, obj_address + 35, 1, &value);
|
|
|
|
value = 0;
|
|
error |= write_mem(data, obj_address + 39, 1, &value);
|
|
|
|
value = 40;
|
|
error |= write_mem(data, obj_address + 42, 1, &value);
|
|
|
|
get_object_info(data, SPT_CTECONFIG_T46,
|
|
&size, &obj_address);
|
|
value = 32;
|
|
error |= write_mem(data, obj_address + 3, 1, &value);
|
|
|
|
get_object_info(data, SPT_GENERICDATA_T57,
|
|
&size, &obj_address);
|
|
value = 15;
|
|
error |= write_mem(data, obj_address + 1, 1, &value);
|
|
}
|
|
if (error)
|
|
printk(KERN_ERR "[TSP] fail median filter err setting\n");
|
|
else
|
|
printk(KERN_DEBUG "[TSP] success median filter err setting\n");
|
|
|
|
} else {
|
|
get_object_info(data, PROCG_NOISESUPPRESSION_T48,
|
|
&size, &obj_address);
|
|
value = 0;
|
|
error |= write_mem(data, obj_address + 2, 1, &value);
|
|
msleep(20);
|
|
value = data->calcfg_batt_e;
|
|
error |= write_mem(data, obj_address + 2, 1, &value);
|
|
if (error)
|
|
printk(KERN_ERR "[TSP] failed to reenable CHRGON\n");
|
|
else
|
|
printk(KERN_DEBUG "[TSP] success reenable CHRGON\n");
|
|
}
|
|
|
|
}
|
|
|
|
static void calibration_check_func(struct mxt540e_data *data)
|
|
{
|
|
u16 obj_address = 0;
|
|
u16 size;
|
|
u8 value;
|
|
int error;
|
|
|
|
if (check_calibrate == 3)
|
|
check_calibrate = 0;
|
|
else if (check_calibrate == 1) {
|
|
cancel_delayed_work(&data->cal_check_dwork);
|
|
schedule_delayed_work(&data->cal_check_dwork,
|
|
msecs_to_jiffies(1400));
|
|
} else {
|
|
check_calibrate = 1;
|
|
value = 6;
|
|
get_object_info(data, GEN_POWERCONFIG_T7,
|
|
&size, &obj_address);
|
|
error = write_mem(data, obj_address + 2, 1, &value);
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n",
|
|
__func__, __LINE__);
|
|
schedule_delayed_work(&data->cal_check_dwork,
|
|
msecs_to_jiffies(1400));
|
|
}
|
|
|
|
if (config_dwork_flag == 3)
|
|
config_dwork_flag = 1;
|
|
else if (config_dwork_flag == 1) {
|
|
cancel_delayed_work(&data->config_dwork);
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
} else {
|
|
config_dwork_flag = 1;
|
|
get_object_info(data, GEN_ACQUISITIONCONFIG_T8,
|
|
&size, &obj_address);
|
|
value = 8;
|
|
error = write_mem(data, obj_address + 8, 1, &value);
|
|
value = 180;
|
|
error |= write_mem(data, obj_address + 9, 1, &value);
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] %s, %d Error!!\n",
|
|
__func__, __LINE__);
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
}
|
|
|
|
}
|
|
|
|
static void report_input_data(struct mxt540e_data *data)
|
|
{
|
|
int i;
|
|
int count = 0;
|
|
int report_count = 0;
|
|
int press_count = 0;
|
|
int move_count = 0;
|
|
|
|
for (i = 0; i < data->num_fingers; i++) {
|
|
if (data->fingers[i].state == MXT540E_STATE_INACTIVE)
|
|
continue;
|
|
|
|
if (data->fingers[i].state == MXT540E_STATE_RELEASE) {
|
|
input_mt_slot(data->input_dev, i);
|
|
input_mt_report_slot_state(data->input_dev,
|
|
MT_TOOL_FINGER, false);
|
|
} else {
|
|
input_mt_slot(data->input_dev, i);
|
|
input_mt_report_slot_state(data->input_dev,
|
|
MT_TOOL_FINGER, true);
|
|
input_report_abs(data->input_dev, ABS_MT_POSITION_X,
|
|
data->fingers[i].x);
|
|
input_report_abs(data->input_dev, ABS_MT_POSITION_Y,
|
|
data->fingers[i].y);
|
|
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR,
|
|
data->fingers[i].z);
|
|
input_report_abs(data->input_dev, ABS_MT_PRESSURE,
|
|
data->fingers[i].w);
|
|
dev_dbg(&data->client->dev, "%s X : %d\n", __func__, data->fingers[i].x);
|
|
dev_dbg(&data->client->dev, "%s Y: %d\n", __func__, data->fingers[i].y);
|
|
dev_dbg(&data->client->dev, "%s Z: %d\n", __func__, data->fingers[i].z);
|
|
dev_dbg(&data->client->dev, "%s W: %d\n", __func__, data->fingers[i].w);
|
|
#if 0
|
|
input_report_abs(data->input_dev, ABS_MT_COMPONENT,
|
|
data->fingers[i].component);
|
|
input_report_abs(data->input_dev, ABS_MT_SUMSIZE,
|
|
sumsize);
|
|
#endif
|
|
}
|
|
report_count++;
|
|
|
|
if (data->fingers[i].state == MXT540E_STATE_PRESS
|
|
|| data->fingers[i].state == MXT540E_STATE_RELEASE) {
|
|
#if 0
|
|
printk(KERN_DEBUG
|
|
"[TSP] id[%d],x=%d,y=%d,z=%d,w=%d,com=%d\n", i,
|
|
data->fingers[i].x, data->fingers[i].y,
|
|
data->fingers[i].z, data->fingers[i].w,
|
|
data->fingers[i].component);
|
|
#else
|
|
#endif
|
|
}
|
|
|
|
if (check_resume_err != 0) {
|
|
if (data->fingers[i].state == MXT540E_STATE_MOVE)
|
|
move_count++;
|
|
if (data->fingers[i].state == MXT540E_STATE_PRESS)
|
|
press_count++;
|
|
}
|
|
|
|
if (data->fingers[i].state == MXT540E_STATE_RELEASE) {
|
|
data->fingers[i].state = MXT540E_STATE_INACTIVE;
|
|
} else {
|
|
data->fingers[i].state = MXT540E_STATE_MOVE;
|
|
count++;
|
|
}
|
|
}
|
|
if (report_count > 0)
|
|
input_sync(data->input_dev);
|
|
|
|
if (count)
|
|
touch_is_pressed = 1;
|
|
else
|
|
touch_is_pressed = 0;
|
|
|
|
if (count == 0) {
|
|
sumsize = 0;
|
|
}
|
|
data->finger_mask = 0;
|
|
|
|
if (check_resume_err != 0) {
|
|
if ((press_count > 0) && (move_count > 0)) {
|
|
check_resume_err_count++;
|
|
if (check_resume_err_count > 4) {
|
|
check_resume_err_count = 0;
|
|
resume_cal_err_func(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (check_calibrate == 1) {
|
|
if (touch_is_pressed)
|
|
cancel_delayed_work(&data->cal_check_dwork);
|
|
else
|
|
schedule_delayed_work(&data->cal_check_dwork,
|
|
msecs_to_jiffies(1400));
|
|
}
|
|
}
|
|
|
|
static int mxt540e_irq_state(struct mxt540e_data *data)
|
|
{
|
|
if (data->irqf_trigger_type == IRQF_TRIGGER_HIGH)
|
|
return gpio_get_value(data->gpio_read_done);
|
|
else
|
|
return !gpio_get_value(data->gpio_read_done);
|
|
}
|
|
|
|
static irqreturn_t mxt540e_irq_thread(int irq, void *ptr)
|
|
{
|
|
struct mxt540e_data *data = ptr;
|
|
int id;
|
|
u8 msg[data->msg_object_size];
|
|
u8 touch_message_flag = 0;
|
|
u8 object_type, instance;
|
|
|
|
do {
|
|
touch_message_flag = 0;
|
|
if (read_mem(data, data->msg_proc, sizeof(msg), msg)) {
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
object_type = reportid_to_type(data, msg[0], &instance);
|
|
|
|
if (object_type == GEN_COMMANDPROCESSOR_T6) {
|
|
if (msg[1] == 0x00) { /* normal mode */
|
|
printk(KERN_DEBUG "[TSP] normal mode\n");
|
|
}
|
|
if ((msg[1] & 0x04) == 0x04) { /* I2C checksum error */
|
|
printk(KERN_DEBUG "[TSP] I2C checksum error\n");
|
|
}
|
|
if ((msg[1] & 0x08) == 0x08) { /* config error */
|
|
printk(KERN_DEBUG "[TSP] config error\n");
|
|
}
|
|
if ((msg[1] & 0x10) == 0x10) { /* calibration */
|
|
printk(KERN_DEBUG "[TSP] calibration is on going\n");
|
|
calibration_check_func(data);
|
|
}
|
|
if ((msg[1] & 0x20) == 0x20) { /* signal error */
|
|
printk(KERN_DEBUG "[TSP] signal error\n");
|
|
}
|
|
if ((msg[1] & 0x40) == 0x40) { /* overflow */
|
|
printk(KERN_DEBUG "[TSP] overflow detected\n");
|
|
}
|
|
if ((msg[1] & 0x80) == 0x80) { /* reset */
|
|
printk(KERN_DEBUG "[TSP] reset is ongoing\n");
|
|
}
|
|
}
|
|
|
|
if (object_type == PROCI_TOUCHSUPPRESSION_T42) {
|
|
if ((msg[1] & 0x01) == 0x00) { /* Palm release */
|
|
printk(KERN_DEBUG "[TSP] palm touch released\n");
|
|
touch_is_pressed = 0;
|
|
} else if ((msg[1] & 0x01) == 0x01) { /* Palm Press */
|
|
printk(KERN_DEBUG "[TSP] palm touch detected\n");
|
|
touch_is_pressed = 1;
|
|
touch_message_flag = 1;
|
|
}
|
|
}
|
|
|
|
if (object_type == SPT_GENERICDATA_T57)
|
|
sumsize = msg[1] + (msg[2] << 8);
|
|
|
|
if (object_type == PROCG_NOISESUPPRESSION_T48) {
|
|
if (msg[4] == 5) { /* Median filter error */
|
|
printk(KERN_DEBUG "[TSP] Median filter Error\n");
|
|
median_filter_err_func(data);
|
|
}
|
|
}
|
|
|
|
if (object_type == TOUCH_MULTITOUCHSCREEN_T9) {
|
|
id = msg[0] - data->finger_type;
|
|
|
|
/* If not a touch event, then keep going */
|
|
if (id < 0 || id >= data->num_fingers)
|
|
continue;
|
|
|
|
if (data->finger_mask & (1U << id))
|
|
report_input_data(data);
|
|
|
|
if (msg[1] & RELEASE_MSG_MASK) {
|
|
data->fingers[id].z = 0;
|
|
data->fingers[id].w = msg[5];
|
|
data->finger_mask |= 1U << id;
|
|
data->fingers[id].state = MXT540E_STATE_RELEASE;
|
|
} else if ((msg[1] & DETECT_MSG_MASK) &&
|
|
(msg[1] & (PRESS_MSG_MASK | MOVE_MSG_MASK))) {
|
|
touch_message_flag = 1;
|
|
data->fingers[id].component = msg[7];
|
|
data->fingers[id].z = msg[6];
|
|
data->fingers[id].w = msg[5];
|
|
data->fingers[id].x =
|
|
((msg[2] << 4) | (msg[4] >> 4)) >>
|
|
data->x_dropbits;
|
|
data->fingers[id].y =
|
|
((msg[3] << 4) | (msg[4] & 0xF)) >>
|
|
data->y_dropbits;
|
|
data->finger_mask |= 1U << id;
|
|
#if defined(DRIVER_FILTER)
|
|
if (msg[1] & PRESS_MSG_MASK) {
|
|
equalize_coordinate(1, id,
|
|
&data->fingers[id].x,
|
|
&data->fingers[id].y);
|
|
data->fingers[id].state =
|
|
MXT540E_STATE_PRESS;
|
|
} else if (msg[1] & MOVE_MSG_MASK) {
|
|
equalize_coordinate(0, id,
|
|
&data->fingers[id].x,
|
|
&data->fingers[id].y);
|
|
}
|
|
#else
|
|
if (msg[1] & PRESS_MSG_MASK) {
|
|
data->fingers[id].state =
|
|
MXT540E_STATE_PRESS;
|
|
}
|
|
#endif
|
|
|
|
data->fingers[id].component = msg[7];
|
|
|
|
|
|
} else if ((msg[1] & SUPPRESS_MSG_MASK)
|
|
&& (data->fingers[id].state !=
|
|
MXT540E_STATE_INACTIVE)) {
|
|
data->fingers[id].z = 0;
|
|
data->fingers[id].w = msg[5];
|
|
data->fingers[id].state = MXT540E_STATE_RELEASE;
|
|
data->finger_mask |= 1U << id;
|
|
} else {
|
|
dev_dbg(&data->client->dev,
|
|
"Unknown state %#02x %#02x\n", msg[0],
|
|
msg[1]);
|
|
continue;
|
|
}
|
|
}
|
|
} while (mxt540e_irq_state(data));
|
|
|
|
if (data->finger_mask)
|
|
report_input_data(data);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#if 0
|
|
static void mxt540e_deepsleep(struct mxt540e_data *data)
|
|
{
|
|
u8 power_cfg[3] = { 0, };
|
|
write_config(data, GEN_POWERCONFIG_T7, power_cfg);
|
|
deepsleep = 1;
|
|
}
|
|
#endif
|
|
static void mxt540e_wakeup(struct mxt540e_data *data)
|
|
{
|
|
write_config(data, GEN_POWERCONFIG_T7, data->power_cfg);
|
|
}
|
|
|
|
static int mxt540e_internal_suspend(struct mxt540e_data *data)
|
|
{
|
|
int i;
|
|
cancel_delayed_work(&data->config_dwork);
|
|
cancel_delayed_work(&data->resume_check_dwork);
|
|
cancel_delayed_work(&data->cal_check_dwork);
|
|
|
|
for (i = 0; i < data->num_fingers; i++) {
|
|
if (data->fingers[i].state == MXT540E_STATE_INACTIVE)
|
|
continue;
|
|
data->fingers[i].z = 0;
|
|
data->fingers[i].state = MXT540E_STATE_RELEASE;
|
|
}
|
|
report_input_data(data);
|
|
/*
|
|
if (!deepsleep)
|
|
data->power_off_with_oleddet();
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_internal_resume(struct mxt540e_data *data)
|
|
{
|
|
/*
|
|
if (!deepsleep)
|
|
data->power_on_with_oleddet();
|
|
else
|
|
*/
|
|
mxt540e_wakeup(data);
|
|
#if 0
|
|
calibrate_chip(data);
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void Mxt540e_force_released(void)
|
|
{
|
|
struct mxt540e_data *data = copy_data;
|
|
int i;
|
|
|
|
if (!mxt540e_enabled) {
|
|
printk(KERN_ERR "[TSP] mxt540e_enabled is 0\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < data->num_fingers; i++) {
|
|
if (data->fingers[i].state == MXT540E_STATE_INACTIVE)
|
|
continue;
|
|
data->fingers[i].z = 0;
|
|
data->fingers[i].state = MXT540E_STATE_RELEASE;
|
|
}
|
|
report_input_data(data);
|
|
calibrate_chip(data);
|
|
};
|
|
EXPORT_SYMBOL(Mxt540e_force_released);
|
|
|
|
static ssize_t mxt540e_debug_setting(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
g_debug_switch = !g_debug_switch;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t mxt540e_object_setting(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct mxt540e_data *data = dev_get_drvdata(dev);
|
|
unsigned int object_type;
|
|
unsigned int object_register;
|
|
unsigned int register_value;
|
|
u8 value;
|
|
u8 val;
|
|
int ret;
|
|
u16 address;
|
|
u16 size;
|
|
sscanf(buf, "%u%u%u", &object_type, &object_register, ®ister_value);
|
|
printk(KERN_ERR "[TSP] object type T%d", object_type);
|
|
printk(KERN_ERR "[TSP] object register ->Byte%d\n", object_register);
|
|
printk(KERN_ERR "[TSP] register value %d\n", register_value);
|
|
ret = get_object_info(data, (u8) object_type, &size, &address);
|
|
if (ret) {
|
|
printk(KERN_ERR "[TSP] fail to get object_info\n");
|
|
return count;
|
|
}
|
|
|
|
size = 1;
|
|
value = (u8) register_value;
|
|
write_mem(data, address + (u16) object_register, size, &value);
|
|
read_mem(data, address + (u16) object_register, (u8) size, &val);
|
|
|
|
printk(KERN_ERR "[TSP] T%d Byte%d is %d\n", object_type,
|
|
object_register, val);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t mxt540e_object_show(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct mxt540e_data *data = dev_get_drvdata(dev);
|
|
unsigned int object_type;
|
|
u8 val;
|
|
int ret;
|
|
u16 address;
|
|
u16 size;
|
|
u16 i;
|
|
sscanf(buf, "%u", &object_type);
|
|
printk(KERN_DEBUG "[TSP] object type T%d\n", object_type);
|
|
ret = get_object_info(data, (u8) object_type, &size, &address);
|
|
if (ret) {
|
|
printk(KERN_ERR "[TSP] fail to get object_info\n");
|
|
return count;
|
|
}
|
|
for (i = 0; i < size; i++) {
|
|
read_mem(data, address + i, 1, &val);
|
|
printk(KERN_DEBUG "[TSP] Byte %u --> %u\n", i, val);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int get_tsp_status(void)
|
|
{
|
|
return touch_is_pressed;
|
|
}
|
|
|
|
void diagnostic_chip(u8 mode)
|
|
{
|
|
int error;
|
|
u16 t6_address = 0;
|
|
u16 size_one;
|
|
int ret;
|
|
|
|
ret = get_object_info(copy_data, GEN_COMMANDPROCESSOR_T6,
|
|
&size_one, &t6_address);
|
|
|
|
size_one = 1;
|
|
error = write_mem(copy_data, t6_address + 5, (u8) size_one, &mode);
|
|
|
|
if (error < 0)
|
|
printk(KERN_ERR "[TSP] error %s: write_object\n", __func__);
|
|
}
|
|
|
|
uint8_t read_uint16_t(struct mxt540e_data *data, uint16_t address,
|
|
uint16_t *buf)
|
|
{
|
|
uint8_t status;
|
|
uint8_t temp[2];
|
|
|
|
status = read_mem(data, address, 2, temp);
|
|
*buf = ((uint16_t) temp[1] << 8) + (uint16_t) temp[0];
|
|
|
|
return status;
|
|
}
|
|
|
|
void read_dbg_data(uint8_t dbg_mode, uint16_t node, uint16_t *dbg_data)
|
|
{
|
|
u8 read_page, read_point;
|
|
uint8_t mode, page;
|
|
u16 size;
|
|
u16 diagnostic_addr = 0;
|
|
|
|
if (!mxt540e_enabled) {
|
|
printk(KERN_ERR "[TSP ]read_dbg_data. mxt540e_enabled is 0\n");
|
|
return;
|
|
}
|
|
|
|
get_object_info(copy_data, DEBUG_DIAGNOSTIC_T37, &size,
|
|
&diagnostic_addr);
|
|
|
|
read_page = node / 64;
|
|
node %= 64;
|
|
read_point = (node * 2) + 2;
|
|
|
|
/* Page Num Clear */
|
|
diagnostic_chip(MXT_CTE_MODE);
|
|
msleep(20);
|
|
|
|
do {
|
|
if (read_mem(copy_data, diagnostic_addr, 1, &mode)) {
|
|
printk(KERN_INFO "[TSP] READ_MEM_FAILED\n");
|
|
return;
|
|
}
|
|
} while (mode != MXT_CTE_MODE);
|
|
|
|
diagnostic_chip(dbg_mode);
|
|
msleep(20);
|
|
|
|
do {
|
|
if (read_mem(copy_data, diagnostic_addr, 1, &mode)) {
|
|
printk(KERN_INFO "[TSP] READ_MEM_FAILED\n");
|
|
return;
|
|
}
|
|
} while (mode != dbg_mode);
|
|
|
|
for (page = 1; page <= read_page; page++) {
|
|
diagnostic_chip(MXT_PAGE_UP);
|
|
msleep(20);
|
|
do {
|
|
if (read_mem(copy_data, diagnostic_addr + 1, 1,
|
|
&mode)) {
|
|
printk(KERN_INFO "[TSP] READ_MEM_FAILED\n");
|
|
return;
|
|
}
|
|
} while (mode != page);
|
|
}
|
|
|
|
if (read_uint16_t(copy_data, diagnostic_addr + read_point, dbg_data)) {
|
|
printk(KERN_INFO "[TSP] READ_MEM_FAILED\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
#define MIN_VALUE 19744
|
|
#define MAX_VALUE 28864
|
|
|
|
int read_all_data(uint16_t dbg_mode)
|
|
{
|
|
u8 read_page, read_point;
|
|
u16 max_value = MIN_VALUE, min_value = MAX_VALUE;
|
|
u16 object_address = 0;
|
|
u8 data_buffer[2] = { 0 };
|
|
u8 node = 0;
|
|
int state = 0;
|
|
int num = 0;
|
|
int ret;
|
|
u16 size;
|
|
|
|
/* Page Num Clear */
|
|
diagnostic_chip(MXT_CTE_MODE);
|
|
msleep(30);
|
|
|
|
diagnostic_chip(dbg_mode);
|
|
msleep(30);
|
|
|
|
ret = get_object_info(copy_data, DEBUG_DIAGNOSTIC_T37,
|
|
&size, &object_address);
|
|
msleep(50);
|
|
|
|
for (read_page = 0; read_page < 9; read_page++) {
|
|
for (node = 0; node < 64; node++) {
|
|
read_point = (node * 2) + 2;
|
|
read_mem(copy_data, object_address + (u16) read_point,
|
|
2, data_buffer);
|
|
qt_refrence_node[num] =
|
|
((uint16_t) data_buffer[1] << 8) +
|
|
(uint16_t) data_buffer[0];
|
|
#ifdef CONFIG_MACH_Q1_BD
|
|
/* q1 use x=16 line, y=26 line */
|
|
if ((num % 30 == 26) || (num % 30 == 27)
|
|
|| (num % 30 == 28) || (num % 30 == 29)) {
|
|
num++;
|
|
if (num == 480)
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
#endif
|
|
if ((qt_refrence_node[num] < MIN_VALUE)
|
|
|| (qt_refrence_node[num] > MAX_VALUE)) {
|
|
state = 1;
|
|
printk(KERN_ERR
|
|
"[TSP] Mxt540E qt_refrence_node[%3d] = %5d\n",
|
|
num, qt_refrence_node[num]);
|
|
}
|
|
|
|
if (data_buffer[0] != 0) {
|
|
if (qt_refrence_node[num] > max_value)
|
|
max_value = qt_refrence_node[num];
|
|
if (qt_refrence_node[num] < min_value)
|
|
min_value = qt_refrence_node[num];
|
|
}
|
|
num++;
|
|
#ifdef CONFIG_MACH_Q1_BD
|
|
if (num == 480)
|
|
break;
|
|
#endif
|
|
/* all node => 18 * 30 = 540 => (8page * 64) + 28 */
|
|
if ((read_page == 8) && (node == 28))
|
|
break;
|
|
}
|
|
diagnostic_chip(MXT_PAGE_UP);
|
|
msleep(35);
|
|
#ifdef CONFIG_MACH_Q1_BD
|
|
if (num == 480)
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if ((max_value - min_value) > 4500) {
|
|
printk(KERN_ERR
|
|
"[TSP] diff = %d, max_value = %d, min_value = %d\n",
|
|
(max_value - min_value), max_value, min_value);
|
|
state = 1;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
int read_all_delta_data(uint16_t dbg_mode)
|
|
{
|
|
u8 read_page, read_point;
|
|
u16 object_address = 0;
|
|
u8 data_buffer[2] = { 0 };
|
|
u8 node = 0;
|
|
int state = 0;
|
|
int num = 0;
|
|
int ret;
|
|
u16 size;
|
|
|
|
/* Page Num Clear */
|
|
diagnostic_chip(MXT_CTE_MODE);
|
|
msleep(30);
|
|
|
|
diagnostic_chip(dbg_mode);
|
|
msleep(30);
|
|
|
|
ret = get_object_info(copy_data, DEBUG_DIAGNOSTIC_T37,
|
|
&size, &object_address);
|
|
msleep(50);
|
|
|
|
for (read_page = 0; read_page < 9; read_page++) {
|
|
for (node = 0; node < 64; node++) {
|
|
read_point = (node * 2) + 2;
|
|
read_mem(copy_data, object_address + (u16) read_point,
|
|
2, data_buffer);
|
|
qt_delta_node[num] =
|
|
((uint16_t) data_buffer[1] << 8) +
|
|
(uint16_t) data_buffer[0];
|
|
|
|
num++;
|
|
|
|
/* all node => 18 * 30 = 540 => (8page * 64) + 28 */
|
|
if ((read_page == 8) && (node == 28))
|
|
break;
|
|
}
|
|
diagnostic_chip(MXT_PAGE_UP);
|
|
msleep(35);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
static int mxt540e_check_bootloader(struct i2c_client *client,
|
|
unsigned int state)
|
|
{
|
|
u8 val;
|
|
u8 temp;
|
|
|
|
recheck:
|
|
if (i2c_master_recv(client, &val, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (val & 0x20) {
|
|
|
|
if (i2c_master_recv(client, &temp, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (i2c_master_recv(client, &temp, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
val &= ~0x20;
|
|
}
|
|
|
|
if ((val & 0xF0) == MXT540E_APP_CRC_FAIL) {
|
|
printk(KERN_DEBUG "[TOUCH] MXT540E_APP_CRC_FAIL\n");
|
|
if (i2c_master_recv(client, &val, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (val & 0x20) {
|
|
if (i2c_master_recv(client, &temp, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (i2c_master_recv(client, &temp, 1) != 1) {
|
|
dev_err(&client->dev, "%s: i2c recv failed\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
val &= ~0x20;
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
case MXT540E_WAITING_BOOTLOAD_CMD:
|
|
case MXT540E_WAITING_FRAME_DATA:
|
|
val &= ~MXT540E_BOOT_STATUS_MASK;
|
|
break;
|
|
case MXT540E_FRAME_CRC_PASS:
|
|
if (val == MXT540E_FRAME_CRC_CHECK)
|
|
goto recheck;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (val != state) {
|
|
dev_err(&client->dev, "Unvalid bootloader mode state\n");
|
|
printk(KERN_ERR "[TSP] Unvalid bootloader mode state\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_unlock_bootloader(struct i2c_client *client)
|
|
{
|
|
u8 buf[2];
|
|
|
|
buf[0] = MXT540E_UNLOCK_CMD_LSB;
|
|
buf[1] = MXT540E_UNLOCK_CMD_MSB;
|
|
|
|
if (i2c_master_send(client, buf, 2) != 2) {
|
|
dev_err(&client->dev, "%s: i2c send failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_fw_write(struct i2c_client *client,
|
|
const u8 *data, unsigned int frame_size)
|
|
{
|
|
if (i2c_master_send(client, data, frame_size) != frame_size) {
|
|
dev_err(&client->dev, "%s: i2c send failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_load_fw(struct device *dev, const char *fn)
|
|
{
|
|
struct mxt540e_data *data = copy_data;
|
|
struct i2c_client *client = copy_data->client;
|
|
const struct firmware *fw = NULL;
|
|
unsigned int frame_size;
|
|
unsigned int pos = 0;
|
|
int ret;
|
|
u16 obj_address = 0;
|
|
u16 size_one;
|
|
u8 value;
|
|
unsigned int object_register;
|
|
int check_frame_crc_error = 0;
|
|
int check_wating_frame_data_error = 0;
|
|
|
|
printk(KERN_DEBUG "[TSP] mxt540e_load_fw start!!!\n");
|
|
|
|
ret = request_firmware(&fw, fn, &client->dev);
|
|
if (ret) {
|
|
dev_err(dev, "Unable to open firmware %s\n", fn);
|
|
printk(KERN_ERR "[TSP] Unable to open firmware %s\n", fn);
|
|
return ret;
|
|
}
|
|
|
|
/* Change to the bootloader mode */
|
|
object_register = 0;
|
|
value = (u8) MXT540E_BOOT_VALUE;
|
|
ret = get_object_info(data, GEN_COMMANDPROCESSOR_T6,
|
|
&size_one, &obj_address);
|
|
if (ret) {
|
|
printk(KERN_ERR "[TSP] fail to get object_info\n");
|
|
release_firmware(fw);
|
|
return ret;
|
|
}
|
|
size_one = 1;
|
|
write_mem(data, obj_address + (u16) object_register, (u8) size_one,
|
|
&value);
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
|
|
/* Change to slave address of bootloader */
|
|
#if 0
|
|
printk("Client add : 0x%x\n", client->addr);
|
|
if (client->addr == MXT540E_APP_LOW)
|
|
client->addr = MXT540E_BOOT_LOW;
|
|
else
|
|
client->addr = MXT540E_BOOT_HIGH;
|
|
#endif
|
|
|
|
ret = mxt540e_check_bootloader(client, MXT540E_WAITING_BOOTLOAD_CMD);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* Unlock bootloader */
|
|
mxt540e_unlock_bootloader(client);
|
|
|
|
while (pos < fw->size) {
|
|
ret = mxt540e_check_bootloader(client,
|
|
MXT540E_WAITING_FRAME_DATA);
|
|
if (ret) {
|
|
check_wating_frame_data_error++;
|
|
if (check_wating_frame_data_error > 10) {
|
|
printk(KERN_ERR
|
|
"[TSP] firm update fail. wating_frame_data err\n");
|
|
goto out;
|
|
} else {
|
|
printk(KERN_ERR
|
|
"[TSP]check_wating_frame_data_error = %d, "
|
|
"retry\n",
|
|
check_wating_frame_data_error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
|
|
|
|
/* We should add 2 at frame size as the the firmware data is not
|
|
* included the CRC bytes.
|
|
*/
|
|
frame_size += 2;
|
|
|
|
/* Write one frame to device */
|
|
mxt540e_fw_write(client, fw->data + pos, frame_size);
|
|
|
|
ret = mxt540e_check_bootloader(client, MXT540E_FRAME_CRC_PASS);
|
|
if (ret) {
|
|
check_frame_crc_error++;
|
|
if (check_frame_crc_error > 10) {
|
|
printk(KERN_ERR
|
|
"[TSP] firm update fail. frame_crc err\n");
|
|
goto out;
|
|
} else {
|
|
printk(KERN_ERR
|
|
"[TSP]check_frame_crc_error = %d, "
|
|
"retry\n",
|
|
check_frame_crc_error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pos += frame_size;
|
|
|
|
dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size);
|
|
printk(KERN_DEBUG "[TSP] Updated %d bytes / %zd bytes\n",
|
|
pos, fw->size);
|
|
|
|
msleep(20);
|
|
}
|
|
|
|
out:
|
|
release_firmware(fw);
|
|
|
|
/* Change to slave address of application */
|
|
#if 0
|
|
if (client->addr == MXT540E_BOOT_LOW)
|
|
client->addr = MXT540E_APP_LOW;
|
|
else
|
|
client->addr = MXT540E_APP_HIGH;
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
static int mxt540e_load_fw_bootmode(struct device *dev, const char *fn)
|
|
{
|
|
struct i2c_client *client = copy_data->client;
|
|
const struct firmware *fw = NULL;
|
|
unsigned int frame_size;
|
|
unsigned int pos = 0;
|
|
int ret;
|
|
int check_frame_crc_error = 0;
|
|
int check_wating_frame_data_error = 0;
|
|
|
|
printk(KERN_DEBUG "[TSP] mxt540e_load_fw start!!!\n");
|
|
|
|
ret = request_firmware(&fw, fn, &client->dev);
|
|
if (ret) {
|
|
dev_err(dev, "Unable to open firmware %s\n", fn);
|
|
printk(KERN_ERR "[TSP] Unable to open firmware %s\n", fn);
|
|
return ret;
|
|
}
|
|
|
|
/* Unlock bootloader */
|
|
mxt540e_unlock_bootloader(client);
|
|
|
|
while (pos < fw->size) {
|
|
ret = mxt540e_check_bootloader(client,
|
|
MXT540E_WAITING_FRAME_DATA);
|
|
if (ret) {
|
|
check_wating_frame_data_error++;
|
|
if (check_wating_frame_data_error > 10) {
|
|
printk(KERN_ERR
|
|
"[TSP] firm update fail. wating_frame_data err\n");
|
|
goto out;
|
|
} else {
|
|
printk(KERN_ERR
|
|
"[TSP]check_wating_frame_data_error = %d, "
|
|
"retry\n",
|
|
check_wating_frame_data_error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
|
|
|
|
/* We should add 2 at frame size as the the firmware data is not
|
|
* included the CRC bytes.
|
|
*/
|
|
frame_size += 2;
|
|
|
|
/* Write one frame to device */
|
|
mxt540e_fw_write(client, fw->data + pos, frame_size);
|
|
|
|
ret = mxt540e_check_bootloader(client, MXT540E_FRAME_CRC_PASS);
|
|
if (ret) {
|
|
check_frame_crc_error++;
|
|
if (check_frame_crc_error > 10) {
|
|
printk(KERN_ERR
|
|
"[TSP] firm update fail. frame_crc err\n");
|
|
goto out;
|
|
} else {
|
|
printk(KERN_ERR
|
|
"[TSP]check_frame_crc_error = %d, "
|
|
"retry\n",
|
|
check_frame_crc_error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pos += frame_size;
|
|
|
|
dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size);
|
|
printk(KERN_DEBUG "[TSP] Updated %d bytes / %zd bytes\n",
|
|
pos, fw->size);
|
|
|
|
msleep(20);
|
|
}
|
|
|
|
out:
|
|
release_firmware(fw);
|
|
|
|
/* Change to slave address of application */
|
|
#if 0
|
|
if (client->addr == MXT540E_BOOT_LOW)
|
|
client->addr = MXT540E_APP_LOW;
|
|
else
|
|
client->addr = MXT540E_APP_HIGH;
|
|
#endif
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static ssize_t set_refer0_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_reference = 0;
|
|
read_dbg_data(MXT_REFERENCE_MODE, test_node[0], &mxt_reference);
|
|
return sprintf(buf, "%u\n", mxt_reference);
|
|
}
|
|
|
|
static ssize_t set_refer1_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_reference = 0;
|
|
read_dbg_data(MXT_REFERENCE_MODE, test_node[1], &mxt_reference);
|
|
return sprintf(buf, "%u\n", mxt_reference);
|
|
}
|
|
|
|
static ssize_t set_refer2_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_reference = 0;
|
|
read_dbg_data(MXT_REFERENCE_MODE, test_node[2], &mxt_reference);
|
|
return sprintf(buf, "%u\n", mxt_reference);
|
|
}
|
|
|
|
static ssize_t set_refer3_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_reference = 0;
|
|
read_dbg_data(MXT_REFERENCE_MODE, test_node[3], &mxt_reference);
|
|
return sprintf(buf, "%u\n", mxt_reference);
|
|
}
|
|
|
|
static ssize_t set_refer4_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_reference = 0;
|
|
read_dbg_data(MXT_REFERENCE_MODE, test_node[4], &mxt_reference);
|
|
return sprintf(buf, "%u\n", mxt_reference);
|
|
}
|
|
|
|
static ssize_t set_delta0_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_delta = 0;
|
|
read_dbg_data(MXT_DELTA_MODE, test_node[0], &mxt_delta);
|
|
if (mxt_delta < 32767)
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
else
|
|
mxt_delta = 65535 - mxt_delta;
|
|
|
|
if (mxt_delta)
|
|
return sprintf(buf, "-%u\n", mxt_delta);
|
|
else
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
}
|
|
|
|
static ssize_t set_delta1_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_delta = 0;
|
|
read_dbg_data(MXT_DELTA_MODE, test_node[1], &mxt_delta);
|
|
if (mxt_delta < 32767)
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
else
|
|
mxt_delta = 65535 - mxt_delta;
|
|
|
|
if (mxt_delta)
|
|
return sprintf(buf, "-%u\n", mxt_delta);
|
|
else
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
}
|
|
|
|
static ssize_t set_delta2_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_delta = 0;
|
|
read_dbg_data(MXT_DELTA_MODE, test_node[2], &mxt_delta);
|
|
if (mxt_delta < 32767)
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
else
|
|
mxt_delta = 65535 - mxt_delta;
|
|
|
|
if (mxt_delta)
|
|
return sprintf(buf, "-%u\n", mxt_delta);
|
|
else
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
}
|
|
|
|
static ssize_t set_delta3_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_delta = 0;
|
|
read_dbg_data(MXT_DELTA_MODE, test_node[3], &mxt_delta);
|
|
if (mxt_delta < 32767)
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
else
|
|
mxt_delta = 65535 - mxt_delta;
|
|
|
|
if (mxt_delta)
|
|
return sprintf(buf, "-%u\n", mxt_delta);
|
|
else
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
}
|
|
|
|
static ssize_t set_delta4_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
uint16_t mxt_delta = 0;
|
|
read_dbg_data(MXT_DELTA_MODE, test_node[4], &mxt_delta);
|
|
if (mxt_delta < 32767)
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
else
|
|
mxt_delta = 65535 - mxt_delta;
|
|
|
|
if (mxt_delta)
|
|
return sprintf(buf, "-%u\n", mxt_delta);
|
|
else
|
|
return sprintf(buf, "%u\n", mxt_delta);
|
|
}
|
|
|
|
static ssize_t set_threshold_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%u\n", threshold);
|
|
}
|
|
|
|
static ssize_t set_all_refer_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int status = 0;
|
|
|
|
status = read_all_data(MXT_REFERENCE_MODE);
|
|
|
|
return sprintf(buf, "%u\n", status);
|
|
}
|
|
|
|
static int index_reference;
|
|
|
|
ssize_t disp_all_refdata_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%u\n", qt_refrence_node[index_reference]);
|
|
}
|
|
|
|
ssize_t disp_all_refdata_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
int reference;
|
|
|
|
sscanf(buf, "%u", &reference);
|
|
printk(KERN_DEBUG "%u\n", reference);
|
|
index_reference = reference;
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t set_all_delta_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int status = 0;
|
|
|
|
status = read_all_delta_data(MXT_DELTA_MODE);
|
|
|
|
return sprintf(buf, "%u\n", status);
|
|
}
|
|
|
|
static int index_delta;
|
|
|
|
ssize_t disp_all_deltadata_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
if (qt_delta_node[index_delta] < 32767)
|
|
return sprintf(buf, "%u\n", qt_delta_node[index_delta]);
|
|
else
|
|
qt_delta_node[index_delta] = 65535 - qt_delta_node[index_delta];
|
|
|
|
return sprintf(buf, "-%u\n", qt_delta_node[index_delta]);
|
|
}
|
|
|
|
ssize_t disp_all_deltadata_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
int delta;
|
|
|
|
sscanf(buf, "%u", &delta);
|
|
printk(KERN_DEBUG "%u\n", delta);
|
|
index_delta = delta;
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t set_firm_version_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
return sprintf(buf, "%#02x\n", tsp_version_disp);
|
|
|
|
}
|
|
|
|
static ssize_t set_module_off_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mxt540e_data *data = copy_data;
|
|
int count;
|
|
|
|
mxt540e_enabled = 0;
|
|
touch_is_pressed = 0;
|
|
|
|
disable_irq(data->client->irq);
|
|
mxt540e_internal_suspend(data);
|
|
|
|
count = sprintf(buf, "tspoff\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_module_on_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mxt540e_data *data = copy_data;
|
|
int count;
|
|
|
|
bool ta_status = 0;
|
|
|
|
mxt540e_internal_resume(data);
|
|
enable_irq(data->client->irq);
|
|
|
|
mxt540e_enabled = 1;
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d", ta_status);
|
|
mxt540e_ta_probe(ta_status);
|
|
}
|
|
calibrate_chip(data);
|
|
|
|
count = sprintf(buf, "tspon\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t set_mxt_firm_update_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct mxt540e_data *data = dev_get_drvdata(dev);
|
|
int error = 0;
|
|
printk(KERN_DEBUG "[TSP] set_mxt_update_show start!!\n");
|
|
if (*buf != 'S' && *buf != 'F') {
|
|
printk(KERN_ERR "Invalid values\n");
|
|
dev_err(dev, "Invalid values\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
disable_irq(data->client->irq);
|
|
firm_status_data = 1;
|
|
if (*buf != 'F' && data->tsp_version >= firmware_latest
|
|
&& data->tsp_build != build_latest) {
|
|
printk(KERN_ERR "[TSP] mxt540E has latest firmware\n");
|
|
firm_status_data = 2;
|
|
enable_irq(data->client->irq);
|
|
return size;
|
|
}
|
|
printk(KERN_DEBUG "[TSP] mxt540E_fm_update\n");
|
|
error = mxt540e_load_fw(dev, MXT540E_FW_NAME);
|
|
|
|
if (error) {
|
|
dev_err(dev, "The firmware update failed(%d)\n", error);
|
|
firm_status_data = 3;
|
|
printk(KERN_ERR "[TSP]The firmware update failed(%d)\n", error);
|
|
return error;
|
|
} else {
|
|
dev_dbg(dev, "The firmware update succeeded\n");
|
|
firm_status_data = 2;
|
|
printk(KERN_DEBUG "[TSP] The firmware update succeeded\n");
|
|
|
|
/* Wait for reset */
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
|
|
mxt540e_init_touch_driver(data);
|
|
}
|
|
|
|
enable_irq(data->client->irq);
|
|
error = mxt540e_backup(data);
|
|
if (error) {
|
|
printk(KERN_ERR "[TSP]mxt540e_backup fail!!!\n");
|
|
return error;
|
|
}
|
|
|
|
/* reset the touch IC. */
|
|
error = mxt540e_reset(data);
|
|
if (error) {
|
|
printk(KERN_ERR "[TSP]mxt540e_reset fail!!!\n");
|
|
return error;
|
|
}
|
|
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t set_mxt_firm_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
int count;
|
|
printk(KERN_DEBUG "Enter firmware_status_show by Factory command\n");
|
|
|
|
if (firm_status_data == 1)
|
|
count = sprintf(buf, "DOWNLOADING\n");
|
|
else if (firm_status_data == 2)
|
|
count = sprintf(buf, "PASS\n");
|
|
else if (firm_status_data == 3)
|
|
count = sprintf(buf, "FAIL\n");
|
|
else
|
|
count = sprintf(buf, "PASS\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t key_threshold_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%u\n", threshold);
|
|
}
|
|
|
|
static ssize_t key_threshold_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
/*TO DO IT */
|
|
unsigned int object_register = 7;
|
|
u8 value;
|
|
u8 val;
|
|
int ret;
|
|
u16 address = 0;
|
|
u16 size_one;
|
|
int num;
|
|
if (sscanf(buf, "%d", &num) == 1) {
|
|
threshold = num;
|
|
printk(KERN_DEBUG "threshold value %d\n", threshold);
|
|
ret = get_object_info(copy_data, TOUCH_MULTITOUCHSCREEN_T9,
|
|
&size_one, &address);
|
|
size_one = 1;
|
|
value = (u8) threshold;
|
|
write_mem(copy_data, address + (u16) object_register, size_one,
|
|
&value);
|
|
read_mem(copy_data, address + (u16) object_register,
|
|
(u8) size_one, &val);
|
|
printk(KERN_ERR "T9 Byte%d is %d\n", object_register, val);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static ssize_t set_mxt_firm_version_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
pr_info("Atmel Latest firmware version is %d\n", firmware_latest);
|
|
return sprintf(buf, "%#02x\n", firmware_latest);
|
|
}
|
|
|
|
static ssize_t set_mxt_firm_version_read_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct mxt540e_data *data = dev_get_drvdata(dev);
|
|
return sprintf(buf, "%#02x\n", data->tsp_version);
|
|
}
|
|
|
|
static ssize_t mxt_touchtype_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
char temp[15];
|
|
|
|
sprintf(temp, "ATMEL,MXT540E\n");
|
|
strcat(buf, temp);
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
static DEVICE_ATTR(set_refer0, S_IRUGO, set_refer0_mode_show, NULL);
|
|
static DEVICE_ATTR(set_delta0, S_IRUGO, set_delta0_mode_show, NULL);
|
|
static DEVICE_ATTR(set_refer1, S_IRUGO, set_refer1_mode_show, NULL);
|
|
static DEVICE_ATTR(set_delta1, S_IRUGO, set_delta1_mode_show, NULL);
|
|
static DEVICE_ATTR(set_refer2, S_IRUGO, set_refer2_mode_show, NULL);
|
|
static DEVICE_ATTR(set_delta2, S_IRUGO, set_delta2_mode_show, NULL);
|
|
static DEVICE_ATTR(set_refer3, S_IRUGO, set_refer3_mode_show, NULL);
|
|
static DEVICE_ATTR(set_delta3, S_IRUGO, set_delta3_mode_show, NULL);
|
|
static DEVICE_ATTR(set_refer4, S_IRUGO, set_refer4_mode_show, NULL);
|
|
static DEVICE_ATTR(set_delta4, S_IRUGO, set_delta4_mode_show, NULL);
|
|
static DEVICE_ATTR(set_all_refer, S_IRUGO, set_all_refer_mode_show, NULL);
|
|
static DEVICE_ATTR(disp_all_refdata, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
disp_all_refdata_show, disp_all_refdata_store);
|
|
static DEVICE_ATTR(set_all_delta, S_IRUGO, set_all_delta_mode_show, NULL);
|
|
static DEVICE_ATTR(disp_all_deltadata, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
disp_all_deltadata_show, disp_all_deltadata_store);
|
|
static DEVICE_ATTR(set_threshold, S_IRUGO, set_threshold_mode_show, NULL);
|
|
static DEVICE_ATTR(set_firm_version, S_IRUGO,
|
|
set_firm_version_show, NULL);
|
|
static DEVICE_ATTR(set_module_off, S_IRUGO,
|
|
set_module_off_show, NULL);
|
|
static DEVICE_ATTR(set_module_on, S_IRUGO,
|
|
set_module_on_show, NULL);
|
|
static DEVICE_ATTR(tsp_firm_update, S_IWUSR | S_IWGRP, NULL,
|
|
set_mxt_firm_update_store); /* firmware update */
|
|
static DEVICE_ATTR(tsp_firm_update_status, S_IRUGO,
|
|
set_mxt_firm_status_show, NULL);
|
|
static DEVICE_ATTR(tsp_threshold, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
key_threshold_show, key_threshold_store);
|
|
static DEVICE_ATTR(tsp_firm_version_phone, S_IRUGO,
|
|
set_mxt_firm_version_show, NULL); /* PHONE */
|
|
static DEVICE_ATTR(tsp_firm_version_panel, S_IRUGO,
|
|
set_mxt_firm_version_read_show, NULL);
|
|
static DEVICE_ATTR(mxt_touchtype, S_IRUGO,
|
|
mxt_touchtype_show, NULL);
|
|
static DEVICE_ATTR(object_show, S_IWUSR | S_IWGRP, NULL, mxt540e_object_show);
|
|
static DEVICE_ATTR(object_write, S_IWUSR | S_IWGRP, NULL,
|
|
mxt540e_object_setting);
|
|
static DEVICE_ATTR(dbg_switch, S_IWUSR | S_IWGRP, NULL, mxt540e_debug_setting);
|
|
|
|
static struct attribute *mxt540e_attrs[] = {
|
|
&dev_attr_object_show.attr,
|
|
&dev_attr_object_write.attr,
|
|
&dev_attr_dbg_switch.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group mxt540e_attr_group = {
|
|
.attrs = mxt540e_attrs,
|
|
};
|
|
|
|
static const struct of_device_id mxt540e_i2c_dt_ids[];
|
|
|
|
static int mxt540e_get_platdata(struct i2c_client *client)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
struct mxt540e_platform_data *pdata;
|
|
struct device_node *np = dev->of_node;
|
|
const struct of_device_id *match;
|
|
|
|
if (!np)
|
|
return -ENOENT;
|
|
|
|
match = of_match_node(mxt540e_i2c_dt_ids, dev->of_node);
|
|
client->dev.platform_data = (struct mxt540e_platform_data *)match->data;
|
|
pdata = client->dev.platform_data;
|
|
|
|
if (of_property_read_u32(np, "max_finger_touches", &pdata->max_finger_touches)) {
|
|
dev_err(dev, "failed to get min_x property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "min_x", &pdata->min_x)) {
|
|
dev_err(dev, "failed to get min_x property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "max_x", &pdata->max_x)) {
|
|
dev_err(dev, "failed to get max_x property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "min_y", &pdata->min_y)) {
|
|
dev_err(dev, "failed to get min_y property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "max_y", &pdata->max_y)) {
|
|
dev_err(dev, "failed to get max_y property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "min_z", &pdata->min_z)) {
|
|
dev_err(dev, "failed to get min_z property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "max_z", &pdata->max_z)) {
|
|
dev_err(dev, "failed to get max_z property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "min_w", &pdata->min_w)) {
|
|
dev_err(dev, "failed to get min_w property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "max_w", &pdata->max_w)) {
|
|
dev_err(dev, "failed to get max_w property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pdata->gpio_read_done = of_get_gpio(np, 0);
|
|
if (!gpio_is_valid(pdata->gpio_read_done)) {
|
|
dev_err(dev, "invalid gpio: %d\n", pdata->gpio_read_done);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxt540e_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct mxt540e_platform_data *pdata;
|
|
struct mxt540e_data *data;
|
|
struct input_dev *input_dev;
|
|
struct median_error_t *median_error;
|
|
int ret;
|
|
int i;
|
|
bool ta_status = 0;
|
|
u8 **tsp_config;
|
|
int retry = 3;
|
|
|
|
touch_is_pressed = 0;
|
|
|
|
if (mxt540e_get_platdata(client)) {
|
|
dev_err(&client->dev, "failed to get platdata\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pdata = dev_get_platdata(&client->dev);
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "missing platform data\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (pdata->max_finger_touches <= 0)
|
|
return -EINVAL;
|
|
|
|
data = kzalloc(sizeof(*data) + pdata->max_finger_touches *
|
|
sizeof(*data->fingers), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->num_fingers = pdata->max_finger_touches;
|
|
data->power_on = pdata->power_on;
|
|
data->power_off = pdata->power_off;
|
|
data->register_cb = pdata->register_cb;
|
|
data->read_ta_status = pdata->read_ta_status;
|
|
|
|
data->client = client;
|
|
i2c_set_clientdata(client, data);
|
|
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev) {
|
|
ret = -ENOMEM;
|
|
dev_err(&client->dev, "input device allocation failed\n");
|
|
goto err_alloc_dev;
|
|
}
|
|
data->input_dev = input_dev;
|
|
input_set_drvdata(input_dev, data);
|
|
input_dev->name = "mxt540e_i2c";
|
|
|
|
set_bit(EV_SYN, input_dev->evbit);
|
|
set_bit(EV_ABS, input_dev->evbit);
|
|
set_bit(EV_KEY, input_dev->evbit);
|
|
set_bit(MT_TOOL_FINGER, input_dev->keybit);
|
|
set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
|
|
|
|
input_mt_init_slots(input_dev, MAX_FINGER_NUM, 0);
|
|
|
|
input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->min_x,
|
|
pdata->max_x, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->min_y,
|
|
pdata->max_y, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, pdata->min_z,
|
|
pdata->max_z, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_PRESSURE, pdata->min_w,
|
|
pdata->max_w, 0, 0);
|
|
/*
|
|
input_set_abs_params(input_dev, ABS_MT_COMPONENT, 0, 255, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_SUMSIZE, 0, 16 * 26, 0, 0);
|
|
*/
|
|
ret = input_register_device(input_dev);
|
|
if (ret) {
|
|
input_free_device(input_dev);
|
|
goto err_reg_dev;
|
|
}
|
|
|
|
data->gpio_read_done = pdata->gpio_read_done;
|
|
|
|
data->power_on(&data->client->dev);
|
|
|
|
copy_data = data;
|
|
#if 0
|
|
if (client->addr == MXT540E_APP_LOW)
|
|
client->addr = MXT540E_BOOT_LOW;
|
|
else
|
|
client->addr = MXT540E_BOOT_HIGH;
|
|
|
|
printk("Client add : 0x%x\n", client->addr);
|
|
#endif
|
|
#if 0
|
|
ret = mxt540e_check_bootloader(client, MXT540E_WAITING_BOOTLOAD_CMD);
|
|
if (ret >= 0) {
|
|
printk(KERN_DEBUG "[TSP] boot mode. firm update excute\n");
|
|
mxt540e_load_fw_bootmode(NULL, MXT540E_FW_NAME);
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
}
|
|
#endif
|
|
#if 0
|
|
else {
|
|
if (client->addr == MXT540E_BOOT_LOW)
|
|
client->addr = MXT540E_APP_LOW;
|
|
else
|
|
client->addr = MXT540E_APP_HIGH;
|
|
}
|
|
#endif
|
|
|
|
data->register_cb(mxt540e_ta_probe);
|
|
|
|
while (retry--) {
|
|
ret = mxt540e_init_touch_driver(data);
|
|
|
|
if (ret == 0 || retry <= 0)
|
|
break;
|
|
|
|
printk(KERN_DEBUG
|
|
"[TSP] chip initialization failed. retry(%d)\n", retry);
|
|
|
|
data->power_off(&data->client->dev);
|
|
msleep(300);
|
|
data->power_on(&data->client->dev);
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(&client->dev, "chip initialization failed\n");
|
|
goto err_init_drv;
|
|
}
|
|
|
|
/* median filter error tunning */
|
|
median_error = kmalloc(sizeof(*median_error), GFP_KERNEL);
|
|
median_error->err_cnt_bat = 0;
|
|
median_error->err_cnt_ta = 0;
|
|
median_error->setting_flag = 0;
|
|
median_error->table_cnt = 0;
|
|
median_error->table_ta[0] = 33;
|
|
median_error->table_ta[1] = 20;
|
|
median_error->table_ta[2] = 15;
|
|
median_error->table_ta[3] = 0;
|
|
median_error->table_bat[0] = 20;
|
|
median_error->table_bat[1] = 10;
|
|
median_error->table_bat[2] = 30;
|
|
median_error->table_bat[3] = 10;
|
|
data->median_error = median_error;
|
|
|
|
if (data->family_id == 0xA1) { /* tsp_family_id - 0xA1 : MXT-540E */
|
|
tsp_config = (u8 **) pdata->config_e;
|
|
data->t48_config_batt_e = pdata->t48_config_batt_e;
|
|
data->t48_config_chrg_e = pdata->t48_config_chrg_e;
|
|
data->irqf_trigger_type = pdata->irqf_trigger_type;
|
|
data->chrgtime_batt = pdata->chrgtime_batt;
|
|
data->chrgtime_charging = pdata->chrgtime_charging;
|
|
data->tchthr_batt = pdata->tchthr_batt;
|
|
data->tchthr_charging = pdata->tchthr_charging;
|
|
data->calcfg_batt_e = pdata->calcfg_batt_e;
|
|
data->calcfg_charging_e = pdata->calcfg_charging_e;
|
|
data->atchfrccalthr_e = pdata->atchfrccalthr_e;
|
|
data->atchfrccalratio_e = pdata->atchfrccalratio_e;
|
|
data->actvsyncsperx_batt = pdata->actvsyncsperx_batt;
|
|
data->actvsyncsperx_charging = pdata->actvsyncsperx_charging;
|
|
|
|
printk(KERN_DEBUG "[TSP] TSP chip is MXT540E\n");
|
|
#if 0
|
|
if ((data->tsp_version < firmware_latest)
|
|
|| (data->tsp_build != build_latest)) {
|
|
printk(KERN_DEBUG "[TSP] mxt540E force firmware update\n");
|
|
if (mxt540e_load_fw(NULL, MXT540E_FW_NAME)) {
|
|
printk(KERN_ERR "[TSP] firm update fail\n");
|
|
goto err_config;
|
|
} else {
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
mxt540e_init_touch_driver(data);
|
|
}
|
|
}
|
|
#endif
|
|
INIT_DELAYED_WORK(&data->config_dwork,
|
|
mxt_reconfigration_normal);
|
|
INIT_DELAYED_WORK(&data->resume_check_dwork,
|
|
resume_check_dworker);
|
|
INIT_DELAYED_WORK(&data->cal_check_dwork, cal_check_dworker);
|
|
} else {
|
|
printk(KERN_ERR "ERROR : There is no valid TSP ID\n");
|
|
goto err_config;
|
|
}
|
|
|
|
for (i = 0; tsp_config[i][0] != RESERVED_T255; i++) {
|
|
ret = init_write_config(data, tsp_config[i][0],
|
|
tsp_config[i] + 1);
|
|
if (ret)
|
|
goto err_config;
|
|
|
|
if (tsp_config[i][0] == GEN_POWERCONFIG_T7)
|
|
data->power_cfg = tsp_config[i] + 1;
|
|
|
|
if (tsp_config[i][0] == TOUCH_MULTITOUCHSCREEN_T9) {
|
|
/* Are x and y inverted? */
|
|
if (tsp_config[i][10] & 0x1) {
|
|
data->x_dropbits =
|
|
(!(tsp_config[i][22] & 0xC)) << 1;
|
|
data->y_dropbits =
|
|
(!(tsp_config[i][20] & 0xC)) << 1;
|
|
} else {
|
|
data->x_dropbits =
|
|
(!(tsp_config[i][20] & 0xC)) << 1;
|
|
data->y_dropbits =
|
|
(!(tsp_config[i][22] & 0xC)) << 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = mxt540e_backup(data);
|
|
if (ret)
|
|
goto err_backup;
|
|
|
|
/* reset the touch IC. */
|
|
ret = mxt540e_reset(data);
|
|
if (ret)
|
|
goto err_reset;
|
|
|
|
msleep(MXT540E_SW_RESET_TIME);
|
|
|
|
mxt540e_enabled = 1;
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d\n", ta_status);
|
|
mxt540e_ta_probe(ta_status);
|
|
}
|
|
check_resume_err = 2;
|
|
calibrate_chip(data);
|
|
schedule_delayed_work(&data->config_dwork, HZ * 30);
|
|
|
|
for (i = 0; i < data->num_fingers; i++)
|
|
data->fingers[i].state = MXT540E_STATE_INACTIVE;
|
|
|
|
ret = request_threaded_irq(client->irq, NULL, mxt540e_irq_thread,
|
|
data->irqf_trigger_type | IRQF_ONESHOT,
|
|
"mxt540e_ts", data);
|
|
if (ret < 0)
|
|
goto err_irq;
|
|
|
|
sec_class = class_create(THIS_MODULE, "sec");
|
|
|
|
ret = sysfs_create_group(&client->dev.kobj, &mxt540e_attr_group);
|
|
if (ret)
|
|
printk(KERN_ERR "[TSP] sysfs_create_group()is falled\n");
|
|
|
|
sec_touchscreen =
|
|
device_create(sec_class, NULL, 0, NULL, "sec_touchscreen");
|
|
|
|
dev_set_drvdata(sec_touchscreen, data);
|
|
|
|
if (IS_ERR(sec_touchscreen))
|
|
printk(KERN_ERR
|
|
"[TSP] Failed to create device(sec_touchscreen)!\n");
|
|
|
|
if (device_create_file(sec_touchscreen, &dev_attr_tsp_firm_update) < 0)
|
|
printk(KERN_ERR "[TSP] Failed to create device file(%s)!\n",
|
|
dev_attr_tsp_firm_update.attr.name);
|
|
|
|
if (device_create_file
|
|
(sec_touchscreen, &dev_attr_tsp_firm_update_status) < 0)
|
|
printk(KERN_ERR "[TSP] Failed to create device file(%s)!\n",
|
|
dev_attr_tsp_firm_update_status.attr.name);
|
|
|
|
if (device_create_file(sec_touchscreen, &dev_attr_tsp_threshold) < 0)
|
|
printk(KERN_ERR "[TSP] Failed to create device file(%s)!\n",
|
|
dev_attr_tsp_threshold.attr.name);
|
|
|
|
if (device_create_file
|
|
(sec_touchscreen, &dev_attr_tsp_firm_version_phone) < 0)
|
|
printk(KERN_ERR "[TSP] Failed to create device file(%s)!\n",
|
|
dev_attr_tsp_firm_version_phone.attr.name);
|
|
|
|
if (device_create_file
|
|
(sec_touchscreen, &dev_attr_tsp_firm_version_panel) < 0)
|
|
printk(KERN_ERR "[TSP] Failed to create device file(%s)!\n",
|
|
dev_attr_tsp_firm_version_panel.attr.name);
|
|
|
|
if (device_create_file(sec_touchscreen, &dev_attr_mxt_touchtype) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_mxt_touchtype.attr.name);
|
|
|
|
mxt540e_noise_test =
|
|
device_create(sec_class, NULL, 0, NULL, "tsp_noise_test");
|
|
|
|
if (IS_ERR(mxt540e_noise_test))
|
|
printk(KERN_ERR
|
|
"Failed to create device(mxt540e_noise_test)!\n");
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_refer0) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_refer0.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_delta0) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_delta0.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_refer1) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_refer1.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_delta1) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_delta1.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_refer2) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_refer2.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_delta2) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_delta2.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_refer3) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_refer3.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_delta3) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_delta3.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_refer4) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_refer4.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_delta4) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_delta4.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_all_refer) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_all_refer.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_disp_all_refdata) <
|
|
0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_disp_all_refdata.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_all_delta) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_all_delta.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_disp_all_deltadata)
|
|
< 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_disp_all_deltadata.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_threshold) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_threshold.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_firm_version) <
|
|
0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_firm_version.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_module_off) <
|
|
0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_module_off.attr.name);
|
|
|
|
if (device_create_file(mxt540e_noise_test, &dev_attr_set_module_on) < 0)
|
|
printk(KERN_ERR "Failed to create device file(%s)!\n",
|
|
dev_attr_set_module_on.attr.name);
|
|
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
|
|
data->early_suspend.suspend = mxt540e_early_suspend;
|
|
data->early_suspend.resume = mxt540e_late_resume;
|
|
register_early_suspend(&data->early_suspend);
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
err_irq:
|
|
err_reset:
|
|
err_backup:
|
|
err_config:
|
|
kfree(data->objects);
|
|
err_init_drv:
|
|
gpio_free(data->gpio_read_done);
|
|
/* err_gpio_req:
|
|
data->power_off();
|
|
input_unregister_device(input_dev); */
|
|
err_reg_dev:
|
|
err_alloc_dev:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
static int mxt540e_remove(struct i2c_client *client)
|
|
{
|
|
struct mxt540e_data *data = i2c_get_clientdata(client);
|
|
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
unregister_early_suspend(&data->early_suspend);
|
|
#endif
|
|
free_irq(client->irq, data);
|
|
kfree(data->objects);
|
|
gpio_free(data->gpio_read_done);
|
|
data->power_off(&data->client->dev);
|
|
input_unregister_device(data->input_dev);
|
|
kfree(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
#define mxt540e_suspend NULL
|
|
#define mxt540e_resume NULL
|
|
|
|
static void mxt540e_early_suspend(struct early_suspend *h)
|
|
{
|
|
struct mxt540e_data *data = container_of(h, struct mxt540e_data,
|
|
early_suspend);
|
|
if (mxt540e_enabled) {
|
|
printk(KERN_DEBUG "[TSP] %s\n", __func__);
|
|
mxt540e_enabled = 0;
|
|
touch_is_pressed = 0;
|
|
|
|
disable_irq(data->client->irq);
|
|
mxt540e_internal_suspend(data);
|
|
} else {
|
|
printk(KERN_DEBUG "[TSP] %s, but already off\n", __func__);
|
|
}
|
|
}
|
|
|
|
static void mxt540e_late_resume(struct early_suspend *h)
|
|
{
|
|
struct mxt540e_data *data = container_of(h, struct mxt540e_data,
|
|
early_suspend);
|
|
bool ta_status = 0;
|
|
u8 id[ID_BLOCK_SIZE];
|
|
int ret = 0;
|
|
int retry = 3;
|
|
|
|
if (mxt540e_enabled == 0) {
|
|
printk(KERN_DEBUG "[TSP] %s\n", __func__);
|
|
mxt540e_internal_resume(data);
|
|
|
|
mxt540e_enabled = 1;
|
|
|
|
ret = read_mem(data, 0, sizeof(id), id);
|
|
if (ret) {
|
|
while (retry--) {
|
|
printk(KERN_DEBUG "[TSP] chip boot failed."
|
|
"retry(%d)\n", retry);
|
|
|
|
data->power_off(&data->client->dev);
|
|
msleep(200);
|
|
data->power_on(&data->client->dev);
|
|
|
|
ret = read_mem(data, 0, sizeof(id), id);
|
|
if (ret == 0 || retry <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d\n", ta_status);
|
|
mxt540e_ta_probe(ta_status);
|
|
}
|
|
if (deepsleep)
|
|
deepsleep = 0;
|
|
|
|
check_resume_err = 2;
|
|
calibrate_chip(data);
|
|
check_calibrate = 3;
|
|
schedule_delayed_work(&data->config_dwork, HZ * 5);
|
|
config_dwork_flag = 3;
|
|
enable_irq(data->client->irq);
|
|
} else {
|
|
printk(KERN_DEBUG "[TSP] %s, but already on\n", __func__);
|
|
}
|
|
}
|
|
#else
|
|
static int mxt540e_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct mxt540e_data *data = i2c_get_clientdata(client);
|
|
|
|
mxt540e_enabled = 0;
|
|
touch_is_pressed = 0;
|
|
disable_irq(data->client->irq);
|
|
return mxt540e_internal_suspend(data);
|
|
}
|
|
|
|
static int mxt540e_resume(struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
bool ta_status = 0;
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct mxt540e_data *data = i2c_get_clientdata(client);
|
|
|
|
ret = mxt540e_internal_resume(data);
|
|
|
|
mxt540e_enabled = 1;
|
|
|
|
if (data->read_ta_status) {
|
|
data->read_ta_status(&ta_status);
|
|
printk(KERN_DEBUG "[TSP] ta_status is %d\n", ta_status);
|
|
mxt540e_ta_probe(ta_status);
|
|
}
|
|
enable_irq(data->client->irq);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops mxt540e_pm_ops = {
|
|
.suspend = mxt540e_suspend,
|
|
.resume = mxt540e_resume,
|
|
};
|
|
#endif
|
|
|
|
static struct i2c_device_id mxt540e_idtable[] = {
|
|
{MXT540E_DEV_NAME, 0},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, mxt540e_idtable);
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct mxt540e_platform_data sec_mxt540e_data = {
|
|
.config_e = mxt540e_config,
|
|
.irqf_trigger_type = IRQF_TRIGGER_HIGH,
|
|
.chrgtime_batt = MXT540E_CHRGTIME_BATT,
|
|
.chrgtime_charging = MXT540E_CHRGTIME_CHRG,
|
|
.tchthr_batt = MXT540E_THRESHOLD_BATT,
|
|
.tchthr_charging = MXT540E_THRESHOLD_CHRG,
|
|
.actvsyncsperx_batt = MXT540E_ACTVSYNCSPERX_BATT,
|
|
.actvsyncsperx_charging = MXT540E_ACTVSYNCSPERX_CHRG,
|
|
.calcfg_batt_e = MXT540E_CALCFG_BATT,
|
|
.calcfg_charging_e = MXT540E_CALCFG_CHRG,
|
|
.atchfrccalthr_e = MXT540E_ATCHFRCCALTHR_NORMAL,
|
|
.atchfrccalratio_e = MXT540E_ATCHFRCCALRATIO_NORMAL,
|
|
.t48_config_batt_e = t48_config_e,
|
|
.t48_config_chrg_e = t48_config_chrg_e,
|
|
.power_on = mxt540e_power_on,
|
|
.power_off = mxt540e_power_off,
|
|
.register_cb = tsp_register_callback,
|
|
.read_ta_status = tsp_read_ta_status,
|
|
};
|
|
|
|
static const struct of_device_id mxt540e_i2c_dt_ids[] = {
|
|
{ .compatible = "atmel,mxt540e",
|
|
.data = &sec_mxt540e_data,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mxt540e_i2c_dt_ids);
|
|
#endif
|
|
|
|
static struct i2c_driver mxt540e_i2c_driver = {
|
|
.id_table = mxt540e_idtable,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = MXT540E_DEV_NAME,
|
|
#ifdef CONFIG_PM
|
|
.pm = &mxt540e_pm_ops,
|
|
#endif
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = of_match_ptr(mxt540e_i2c_dt_ids),
|
|
#endif
|
|
},
|
|
.probe = mxt540e_probe,
|
|
.remove = mxt540e_remove,
|
|
};
|
|
|
|
static int __init mxt540e_init(void)
|
|
{
|
|
return i2c_add_driver(&mxt540e_i2c_driver);
|
|
}
|
|
|
|
static void __exit mxt540e_exit(void)
|
|
{
|
|
i2c_del_driver(&mxt540e_i2c_driver);
|
|
}
|
|
|
|
module_init(mxt540e_init);
|
|
module_exit(mxt540e_exit);
|
|
|
|
MODULE_DESCRIPTION("Atmel MaXTouch 540E driver");
|
|
MODULE_AUTHOR("Heetae Ahn <heetae82.ahn@samsung.com>");
|
|
MODULE_LICENSE("GPL");
|