blob: 01e6b828c5ca3196851e995563833486045fa03e [file] [log] [blame]
/*
* fts.c
*
* FTS Capacitive touch screen controller (FingerTipS)
*
* Copyright (C) 2016, STMicroelectronics Limited.
* Authors: AMG(Analog Mems Group)
*
* 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.
*
* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE
* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT.
* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM
* THE
* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS.
*/
/*!
* \file fts.c
* \brief It is the main file which contains all the most important functions
* generally used by a device driver the driver
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/hrtimer.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/fb.h>
#include <linux/spi/spi.h>
#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
#include <drm/drm_panel.h>
#endif
#include "fts.h"
#include "fts_lib/fts_flash.h"
#include "fts_lib/fts_test.h"
#include "fts_lib/fts_error.h"
extern struct sys_info system_info;
static int system_reseted_up;
static int system_reseted_down;
#ifdef CONFIG_PM
static const struct dev_pm_ops fts_pm_ops;
#endif
char fts_ts_phys[64];
extern struct test_to_do tests;
#define event_id(_e) (EVT_ID_##_e >> 4)
#define handler_name(_h) fts_##_h##_event_handler
#define install_handler(_i, _evt, _hnd) \
(_i->event_dispatch_table[event_id(_evt)] = handler_name(_hnd))
#ifdef KERNEL_ABOVE_2_6_38
#define TYPE_B_PROTOCOL
#endif
/* Refer to 2.1.4 Status Event Summary */
static char *event_type_str[EVT_TYPE_STATUS_MAX_NUM] = {
[EVT_TYPE_STATUS_ECHO] = "Echo",
[EVT_TYPE_STATUS_GPIO_CHAR_DET] = "GPIO Charger Detect",
[EVT_TYPE_STATUS_FRAME_DROP] = "Frame Drop",
[EVT_TYPE_STATUS_FORCE_CAL] = "Force Cal",
[EVT_TYPE_STATUS_WATER] = "Water Mode",
[EVT_TYPE_STATUS_NOISE] = "Noise Status",
[EVT_TYPE_STATUS_PALM_TOUCH] = "Palm Status",
[EVT_TYPE_STATUS_GRIP_TOUCH] = "Grip Status",
[EVT_TYPE_STATUS_GOLDEN_RAW_ERR] = "Golden Raw Data Abnormal",
[EVT_TYPE_STATUS_INV_GESTURE] = "Invalid Gesture",
[EVT_TYPE_STATUS_HIGH_SENS] = "High Sensitivity Mode",
};
static void fts_pinctrl_setup(struct fts_ts_info *info, bool active);
/**
* Set the value of system_reseted_up flag
* @param val value to write in the flag
*/
void set_system_reseted_up(int val)
{
system_reseted_up = val;
}
/**
* Return the value of system_resetted_down.
* @return the flag value: 0 if not set, 1 if set
*/
int is_system_resetted_down(void)
{
return system_reseted_down;
}
/**
* Return the value of system_resetted_up.
* @return the flag value: 0 if not set, 1 if set
*/
int is_system_resetted_up(void)
{
return system_reseted_up;
}
/**
* Set the value of system_reseted_down flag
* @param val value to write in the flag
*/
void set_system_reseted_down(int val)
{
system_reseted_down = val;
}
/* Set the interrupt state
* @param enable Indicates whether interrupts should enabled.
* @return OK if success
*/
int fts_set_interrupt(struct fts_ts_info *info, bool enable)
{
if (info->client == NULL) {
dev_err(info->dev, "Error: Cannot get client irq.\n");
return ERROR_OP_NOT_ALLOW;
}
if (enable == info->irq_enabled) {
dev_dbg(info->dev, "Interrupt is already set (enable = %d).\n", enable);
return OK;
}
if (enable && !info->resume_bit) {
dev_err(info->dev, "Error: Interrupt can't enable in suspend mode.\n");
return ERROR_OP_NOT_ALLOW;
}
mutex_lock(&info->fts_int_mutex);
info->irq_enabled = enable;
if (enable) {
enable_irq(info->client->irq);
dev_dbg(info->dev, "Interrupt enabled.\n");
} else {
disable_irq_nosync(info->client->irq);
dev_dbg(info->dev, "Interrupt disabled.\n");
}
mutex_unlock(&info->fts_int_mutex);
return OK;
}
#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
/**
* Release all the touches in the linux input subsystem
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
*/
void release_all_touches(struct fts_ts_info *info)
{
unsigned int type = MT_TOOL_FINGER;
int i;
mutex_lock(&info->input_report_mutex);
for (i = 0; i < TOUCH_ID_MAX + PEN_ID_MAX ; i++) {
type = i < TOUCH_ID_MAX ? MT_TOOL_FINGER : MT_TOOL_PEN;
input_mt_slot(info->input_dev, i);
input_report_abs(info->input_dev, ABS_MT_PRESSURE, 0);
input_mt_report_slot_state(info->input_dev, type, 0);
input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1);
}
input_report_key(info->input_dev, BTN_TOUCH, 0);
input_sync(info->input_dev);
mutex_unlock(&info->input_report_mutex);
info->touch_id = 0;
}
#endif
/**
* The function handle the switching of the mode in the IC enabling/disabling
* the sensing and the features set from the host
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param force if 1, the enabling/disabling command will be send even
* if the feature was already enabled/disabled otherwise it will judge if
* the feature changed status or the IC had a system reset
* @return OK if success or an error code which specify the type of error
*encountered
*/
static int fts_mode_handler(struct fts_ts_info *info, int force)
{
int res = OK;
u8 data = 0;
/* disable irq wake because resuming from gesture mode */
if ((info->mode == SCAN_MODE_LOW_POWER) && (info->resume_bit == 1))
disable_irq_wake(info->client->irq);
info->mode = SCAN_MODE_HIBERNATE;
pr_info("%s: Mode Handler starting...\n", __func__);
switch (info->resume_bit) {
case 0: /* screen down */
pr_info("%s: Screen OFF...\n", __func__);
/* do sense off in order to avoid the flooding of the fifo with
* touch events if someone is touching the panel during suspend
*/
data = SCAN_MODE_HIBERNATE;
res = fts_write_fw_reg(SCAN_MODE_ADDR, &data, 1);
if (res == OK)
info->mode = SCAN_MODE_HIBERNATE;
set_system_reseted_down(0);
break;
case 1: /* screen up */
pr_info("%s: Screen ON...\n", __func__);
data = SCAN_MODE_ACTIVE;
res = fts_write_fw_reg(SCAN_MODE_ADDR, &data, 1);
if (res == OK)
info->mode = SCAN_MODE_ACTIVE;
set_system_reseted_up(0);
break;
default:
pr_err("%s: invalid resume_bit value = %d! ERROR %08X\n",
__func__, info->resume_bit, ERROR_OP_NOT_ALLOW);
res = ERROR_OP_NOT_ALLOW;
}
/*TODO : For all the gesture related modes */
pr_info("%s: Mode Handler finished! res = %08X mode = %08X\n",
__func__, res, info->mode);
return res;
}
/**
* Bottom Half Interrupt Handler function
* This handler is called each time there is at least one new event in the FIFO
* and the interrupt pin of the IC goes low. It will read all the events from
* the FIFO and dispatch them to the proper event handler according the event
* ID
*/
static irqreturn_t fts_interrupt_handler(int irq, void *handle)
{
struct fts_ts_info *info = handle;
int error = 0, count = 0;
unsigned char event_id;
unsigned char total_events = 0;
unsigned char *evt_data;
bool has_pointer_event = false;
int event_start_idx = -1;
memset(info->evt_data, 0, EVENT_DATA_SIZE);
for (count = 0; count < MAX_FIFO_EVENT; count++) {
error = fts_read_fw_reg(FIFO_READ_ADDR,
&info->evt_data[count * FIFO_EVENT_SIZE], FIFO_EVENT_SIZE);
if (error != OK) {
pr_err("%s: Failed to read fifo event (error=%d)",
__func__, error);
break;
}
if (info->evt_data[count * FIFO_EVENT_SIZE] == EVT_ID_NOEVENT)
break;
total_events++;
udelay(100);
}
evt_data = &info->evt_data[0];
if (evt_data[0] == EVT_ID_NOEVENT)
goto exit;
if (total_events == MAX_FIFO_EVENT)
pr_info("%s: Warnning: total_events = MAX_FIFO_EVENT(%d)",
__func__, MAX_FIFO_EVENT);
/*
* Parsing all the events ID and specifically handle the
* EVT_ID_CONTROLLER_READY and EVT_ID_ERROR at first.
*/
for (count = 0; count < total_events; count++) {
evt_data = &info->evt_data[count * FIFO_EVENT_SIZE];
switch (evt_data[0]) {
case EVT_ID_CONTROLLER_READY:
case EVT_ID_ERROR:
event_id = evt_data[0] >> 4;
/* Ensure event ID is within bounds */
if (event_id < NUM_EVT_ID)
info->event_dispatch_table[event_id](info, (evt_data));
has_pointer_event = false;
event_start_idx = count;
break;
case EVT_ID_ENTER_POINT:
case EVT_ID_MOTION_POINT:
case EVT_ID_LEAVE_POINT:
has_pointer_event = true;
break;
default:
break;
}
}
/* Only lock input report when there is pointer event. */
if (has_pointer_event) {
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_input_lock(info->gti);
goog_input_set_timestamp(info->gti, info->input_dev, info->timestamp);
#else
mutex_lock(&info->input_report_mutex);
input_set_timestamp(info->input_dev, info->timestamp);
#endif
}
/*
* Handle the remaining events except for
* EVT_ID_CONTROLLER_READY and EVT_ID_ERROR.
*/
for (count = max(event_start_idx + 1, 0); count < total_events; count++) {
evt_data = &info->evt_data[count * FIFO_EVENT_SIZE];
event_id = evt_data[0] >> 4;
/* Ensure event ID is within bounds */
if (event_id < NUM_EVT_ID)
info->event_dispatch_table[event_id](info, (evt_data));
}
if (has_pointer_event) {
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
if (info->touch_id == 0)
goog_input_report_key(info->gti, info->input_dev, BTN_TOUCH, 0);
goog_input_sync(info->gti, info->input_dev);
goog_input_unlock(info->gti);
#else
if (info->touch_id == 0)
input_report_key(info->input_dev, BTN_TOUCH, 0);
input_sync(info->input_dev);
mutex_unlock(&info->input_report_mutex);
#endif
}
exit:
return IRQ_HANDLED;
}
/**
* Top half Interrupt handler function
* Respond to the interrupt and schedule the bottom half interrupt handler
* in its work queue
* @see fts_event_handler()
*/
static irqreturn_t fts_isr(int irq, void *handle)
{
struct fts_ts_info *info = handle;
info->timestamp = ktime_get();
return IRQ_WAKE_THREAD;
}
/**
* Event Handler for no events (EVT_ID_NOEVENT)
*/
static void fts_nop_event_handler(struct fts_ts_info *info,
unsigned char *event)
{
pr_info("%s: Doing nothing for event = %02X %02X %02X %02X %02X %02X %02X %02X\n",
__func__, event[0], event[1], event[2], event[3],
event[4], event[5], event[6], event[7]);
}
/**
* Event handler for enter and motion events (EVT_ID_ENTER_POINT,
* EVT_ID_MOTION_POINT )
* report to the linux input system touches with their coordinated and
* additional informations
*/
static void fts_enter_pointer_event_handler(struct fts_ts_info *info, unsigned
char *event)
{
struct fts_hw_platform_data *bdata = info->board;
unsigned char touch_id;
unsigned int touch_condition = 1, tool = MT_TOOL_FINGER;
int x, y, z, distance, major, minor;
u8 touch_type;
if (!info->resume_bit)
goto no_report;
touch_type = event[1] & 0x0F;
touch_id = (event[1] & 0xF0) >> 4;
x = (((int)event[3] & 0x0F) << 8) | (event[2]);
y = ((int)event[4] << 4) | ((event[3] & 0xF0) >> 4);
z = (int)(event[5]);
distance = 0; /* if the tool is touching the display the distance
* should be 0 */
major = (int)(event[6]);
minor = (int)(event[7]);
if (x == X_AXIS_MAX)
x--;
if (y == Y_AXIS_MAX)
y--;
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_input_mt_slot(info->gti, info->input_dev, touch_id);
#else
input_mt_slot(info->input_dev, touch_id);
#endif
switch (touch_type) {
/* TODO: customer can implement a different strategy for each kind of
* touch */
case TOUCH_TYPE_FINGER:
case TOUCH_TYPE_GLOVE:
case TOUCH_TYPE_LARGE:
pr_debug("%s: touch type = %d!\n", __func__, touch_type);
tool = MT_TOOL_FINGER;
touch_condition = 1;
__set_bit(touch_id, &info->touch_id);
break;
case TOUCH_TYPE_FINGER_HOVER:
pr_debug("%s: touch type = %d!\n", __func__, touch_type);
tool = MT_TOOL_FINGER;
touch_condition = 0; /* need to hover */
z = 0; /* no pressure */
__set_bit(touch_id, &info->touch_id);
distance = DISTANCE_MAX; /* check with fw report the
* hovering distance */
break;
default:
pr_err("%s: Invalid touch type = %d! No Report...\n",
__func__, touch_type);
goto no_report;
}
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_input_report_key(info->gti, info->input_dev, BTN_TOUCH, touch_condition);
goog_input_mt_report_slot_state(info->gti, info->input_dev, tool, 1);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_POSITION_X, x);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_POSITION_Y, y);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_TOUCH_MAJOR,
major * bdata->mm2px);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_TOUCH_MINOR,
minor * bdata->mm2px);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_PRESSURE, z);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_DISTANCE, distance);
#else
input_report_key(info->input_dev, BTN_TOUCH, touch_condition);
input_mt_report_slot_state(info->input_dev, tool, 1);
input_report_abs(info->input_dev, ABS_MT_POSITION_X, x);
input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR,
major * bdata->mm2px);
input_report_abs(info->input_dev, ABS_MT_TOUCH_MINOR,
minor * bdata->mm2px);
input_report_abs(info->input_dev, ABS_MT_PRESSURE, z);
input_report_abs(info->input_dev, ABS_MT_DISTANCE, distance);
#endif
no_report:
return;
}
/**
* Event handler for leave event (EVT_ID_LEAVE_POINT )
* Report to the linux input system that one touch left the display
*/
static void fts_leave_pointer_event_handler(struct fts_ts_info *info, unsigned
char *event)
{
unsigned char touch_id;
unsigned int tool = MT_TOOL_FINGER;
u8 touch_type;
touch_type = event[1] & 0x0F;
touch_id = (event[1] & 0xF0) >> 4;
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_input_mt_slot(info->gti, info->input_dev, touch_id);
#else
input_mt_slot(info->input_dev, touch_id);
#endif
switch (touch_type) {
case TOUCH_TYPE_FINGER:
case TOUCH_TYPE_GLOVE:
case TOUCH_TYPE_LARGE:
case TOUCH_TYPE_FINGER_HOVER:
pr_debug("%s: touch type = %d!\n", __func__, touch_type);
tool = MT_TOOL_FINGER;
__clear_bit(touch_id, &info->touch_id);
break;
default:
pr_err("%s: Invalid touch type = %d! No Report...\n",
__func__, touch_type);
return;
}
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_PRESSURE, 0);
goog_input_mt_report_slot_state(info->gti, info->input_dev, tool, 0);
goog_input_report_abs(info->gti, info->input_dev, ABS_MT_TRACKING_ID, -1);
#else
input_report_abs(info->input_dev, ABS_MT_PRESSURE, 0);
input_mt_report_slot_state(info->input_dev, tool, 0);
input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1);
#endif
}
/**
* Perform a system reset of the IC.
* If the reset pin is associated to a gpio, the function execute an hw reset
* (toggling of reset pin) otherwise send an hw command to the IC
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param poll_event varaiable to enable polling for controller ready event
* @return OK if success or an error code which specify the type of error
*/
int fts_system_reset(struct fts_ts_info *info, int poll_event)
{
int res = 0;
u8 data = SYSTEM_RESET_VAL;
int event_to_search = EVT_ID_CONTROLLER_READY;
u8 read_data[8] = { 0x00 };
int add = 0x001C;
uint8_t int_data = 0x01;
if (info->board->reset_gpio == GPIO_NOT_DEFINED) {
res = fts_write_u8ux(FTS_CMD_HW_REG_W, HW_ADDR_SIZE, SYS_RST_ADDR,
&data, 1);
if (res < OK) {
pr_err("%s: ERROR %08X\n", __func__, res);
return res;
}
} else {
gpio_set_value(info->board->reset_gpio, 0);
msleep(20);
gpio_set_value(info->board->reset_gpio, 1);
res = OK;
}
if (poll_event) {
res = poll_for_event(&event_to_search, 1, read_data,
TIMEOUT_GENERAL);
if (res < OK)
pr_err("%s: ERROR %08X\n", __func__, res);
} else
msleep(100);
#ifdef FTS_GPIO6_UNUSED
res = fts_write_read_u8ux(FTS_CMD_HW_REG_R, HW_ADDR_SIZE,
FLASH_CTRL_ADDR, &data, 1, DUMMY_BYTE);
if (res < OK) {
pr_err("%s: ERROR %08X\n", __func__, res);
return res;
}
data |= 0x80;
res = fts_write_u8ux(FTS_CMD_HW_REG_W, HW_ADDR_SIZE,
FLASH_CTRL_ADDR, &data, 1);
if (res < OK) {
pr_err("%s: ERROR %08X\n", __func__, res);
return res;
}
#endif
res = fts_write_fw_reg(add, &int_data, 1);
if (res < OK)
pr_err("%s: ERROR %08X\n", __func__, res);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
if (info->gti)
goog_notify_fw_status_changed(info->gti, GTI_FW_STATUS_RESET,
NULL);
#endif
return res;
}
#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler
/*!< remap the motion event handler to the same function which handle the enter
* event */
/**
* Event handler for error events (EVT_ID_ERROR)
* Handle unexpected error events implementing recovery strategy and
* restoring the sensing status that the IC had before the error occured
*/
static void fts_error_event_handler(struct fts_ts_info *info, unsigned
char *event)
{
int error = 0;
pr_warn("%s: Received event %02X %02X %02X %02X %02X %02X %02X %02X\n",
__func__, event[0], event[1], event[2], event[3], event[4],
event[5], event[6], event[7]);
switch (event[1]) {
case EVT_TYPE_ERROR_HARD_FAULT:
case EVT_TYPE_ERROR_MEMORY_MANAGE:
case EVT_TYPE_ERROR_BUS_FAULT:
case EVT_TYPE_ERROR_USAGE_FAULT:
case EVT_TYPE_ERROR_WATCHDOG:
case EVT_TYPE_ERROR_INIT_ERROR:
case EVT_TYPE_ERROR_TASK_STACK_OVERFLOW:
case EVT_TYPE_ERROR_MEMORY_OVERFLOW:
{
/* before reset clear all slots */
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
info->touch_id = 0;
#else
release_all_touches(info);
#endif
fts_set_interrupt(info, false);
error = fts_system_reset(info, 1);
error |= fts_mode_handler(info, 0);
error |= fts_set_interrupt(info, true);
if (error < OK)
pr_err("%s: Cannot reset the device ERROR %08X\n",
__func__, error);
}
break;
}
}
/**
* Event handler for controller ready event (EVT_ID_CONTROLLER_READY)
* Handle controller events received after unexpected reset of the IC updating
* the resets flag and restoring the proper sensing status
*/
static void fts_controller_ready_event_handler(struct fts_ts_info *info,
unsigned char *event)
{
int error;
pr_info("%s: controller event %02X %02X %02X %02X %02X %02X %02X %02X\n",
__func__, event[0], event[1], event[2], event[3], event[4],
event[5], event[6], event[7]);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
info->touch_id = 0;
#else
release_all_touches(info);
#endif
set_system_reseted_up(1);
set_system_reseted_down(1);
error = fts_mode_handler(info, 0);
if (error < OK)
pr_err("%s: Cannot restore the device status ERROR %08X\n",
__func__, error);
}
#define log_status_event(force, evt_ptr) \
do { \
u8 type = evt_ptr[1]; \
if (force) \
pr_info("%s: %s =" \
" %02X %02X %02X %02X %02X %02X\n", \
__func__, event_type_str[type], \
evt_ptr[2], evt_ptr[3], evt_ptr[4], \
evt_ptr[5], evt_ptr[6], evt_ptr[7]); \
else \
pr_debug("%s: %s =" \
" %02X %02X %02X %02X %02X %02X\n", \
__func__, event_type_str[type], \
evt_ptr[2], evt_ptr[3], evt_ptr[4], \
evt_ptr[5], evt_ptr[6], evt_ptr[7]); \
} while (0)
#define log_status_event2(force, sub_str, evt_ptr) \
do { \
u8 type = evt_ptr[1]; \
if (force) \
pr_info("%s: %s - %s =" \
" %02X %02X %02X %02X %02X %02X\n", \
__func__, event_type_str[type], sub_str, \
evt_ptr[2], evt_ptr[3], evt_ptr[4], \
evt_ptr[5], evt_ptr[6], evt_ptr[7]); \
else \
pr_debug("%s: %s - %s =" \
" %02X %02X %02X %02X %02X %02X\n", \
__func__, event_type_str[type], sub_str, \
evt_ptr[2], evt_ptr[3], evt_ptr[4], \
evt_ptr[5], evt_ptr[6], evt_ptr[7]); \
} while (0)
/**
* Event handler for status events (EVT_ID_STATUS_UPDATE)
* Handle status update events
*/
static void fts_status_event_handler(struct fts_ts_info *info, u8 *event)
{
switch (event[1]) {
case EVT_TYPE_STATUS_ECHO:
log_status_event(0, event);
break;
case EVT_TYPE_STATUS_GPIO_CHAR_DET:
case EVT_TYPE_STATUS_FRAME_DROP:
case EVT_TYPE_STATUS_GOLDEN_RAW_ERR:
case EVT_TYPE_STATUS_INV_GESTURE:
log_status_event(1, event);
break;
case EVT_TYPE_STATUS_FORCE_CAL:
switch (event[2]) {
case 0x01:
log_status_event2(1, "sense on", event);
break;
case 0x02:
log_status_event2(1, "host command", event);
break;
case 0x10:
log_status_event2(1, "frame drop", event);
break;
case 0x11:
log_status_event2(1, "pure raw", event);
break;
case 0x20:
log_status_event2(1, "ss detect negative strength", event);
break;
case 0x30:
log_status_event2(1, "invalid mutual", event);
break;
case 0x31:
log_status_event2(1, "invalid self", event);
break;
case 0x32:
log_status_event2(1, "invalid self islands", event);
break;
default:
log_status_event2(1, "unknown event", event);
break;
}
break;
case EVT_TYPE_STATUS_WATER:
case EVT_TYPE_STATUS_HIGH_SENS:
if (event[2] == 1)
log_status_event2(1, "entry", event);
else
log_status_event2(1, "exit", event);
break;
case EVT_TYPE_STATUS_NOISE:
{
static u8 noise_level;
static u8 scanning_frequency;
if (noise_level != event[2] || scanning_frequency != event[3]) {
log_status_event2(1, "changed", event);
pr_info("%s: level:[%02X->%02X],freq:[%02X->%02X]\n",
__func__, noise_level, event[2],
scanning_frequency, event[3]);
noise_level = event[2];
scanning_frequency = event[3];
} else
log_status_event(0, event);
}
break;
case EVT_TYPE_STATUS_PALM_TOUCH:
switch (event[2]) {
case 0x01:
log_status_event2(0, "entry", event);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_notify_fw_status_changed(info->gti, GTI_FW_STATUS_PALM_ENTER,
NULL);
#endif
break;
case 0x02:
log_status_event2(0, "exit", event);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_notify_fw_status_changed(info->gti, GTI_FW_STATUS_PALM_EXIT,
NULL);
#endif
break;
default:
log_status_event2(1, "unknown event", event);
break;
}
break;
case EVT_TYPE_STATUS_GRIP_TOUCH:
{
u8 grip_touch_status;
grip_touch_status = (event[2] & 0xF0) >> 4;
switch (grip_touch_status) {
case 0x01:
log_status_event2(0, "entry", event);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_notify_fw_status_changed(info->gti, GTI_FW_STATUS_GRIP_ENTER,
NULL);
#endif
break;
case 0x02:
log_status_event2(0, "exit", event);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goog_notify_fw_status_changed(info->gti, GTI_FW_STATUS_GRIP_EXIT,
NULL);
#endif
break;
default:
log_status_event2(1, "unknown event", event);
break;
}
}
break;
default:
pr_err("%s: Unknown status event (%02X) ="
" %02X %02X %02X %02X %02X %02X\n",
__func__, event[1], event[2], event[3],
event[4], event[5], event[6], event[7]);
break;
}
}
/**
* Event handler for enter and motion events (EVT_ID_ENTER_PEN,
* EVT_ID_MOTION_PEN)
* report to the linux input system pen touches with their coordinated and
* additional informations
*/
static void fts_enter_pen_event_handler(struct fts_ts_info *info, unsigned
char *event)
{
unsigned char pen_id;
unsigned int touch_condition = 1, tool = MT_TOOL_PEN;
int x, y, pressure, tilt_x, tilt_y;
if (!info->resume_bit)
goto no_report;
pen_id = (event[0] & 0x0C) >> 2;
pen_id = pen_id + TOUCH_ID_MAX;
x = (((int)event[2] & 0x0F) << 8) | (event[1]);
y = ((int)event[3] << 4) | ((event[2] & 0xF0) >> 4);
tilt_x = (int)(event[4]);
tilt_y = (int)(event[5]);
pressure = (((int)event[7] & 0x0F) << 8) | (event[6]);
input_mt_slot(info->input_dev, pen_id);
touch_condition = 1;
__set_bit(pen_id, &info->touch_id);
input_report_key(info->input_dev, BTN_TOUCH, touch_condition);
input_mt_report_slot_state(info->input_dev, tool, 1);
input_report_abs(info->input_dev, ABS_MT_POSITION_X, x);
input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y);
input_report_abs(info->input_dev, ABS_TILT_X, tilt_x);
input_report_abs(info->input_dev, ABS_TILT_Y, tilt_y);
input_report_abs(info->input_dev, ABS_MT_PRESSURE, pressure);
no_report:
return;
}
#define fts_motion_pen_event_handler fts_enter_pen_event_handler
/*!< remap the pen motion event handler to the same function which handle the
* enter event */
/**
* Event handler for leave event (EVT_ID_LEAVE_PEN )
* Report to the linux input system that pen touch left the display
*/
static void fts_leave_pen_event_handler(struct fts_ts_info *info, unsigned
char *event)
{
unsigned char pen_id;
unsigned int tool = MT_TOOL_PEN;
pen_id = (event[0] & 0x0C) >> 2;
pen_id = pen_id + TOUCH_ID_MAX;
input_mt_slot(info->input_dev, pen_id);
__clear_bit(pen_id, &info->touch_id);
input_report_abs(info->input_dev, ABS_MT_PRESSURE, 0);
input_mt_report_slot_state(info->input_dev, tool, 0);
input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1);
}
/**
* Initialize the dispatch table with the event handlers for any possible event
* ID
* Set IRQ pin behavior (level triggered low)
* Register top half interrupt handler function.
* @see fts_interrupt_handler()
*/
static int fts_interrupt_install(struct fts_ts_info *info)
{
int i, error = 0;
info->event_dispatch_table = kzalloc(sizeof(event_dispatch_handler_t) *
NUM_EVT_ID, GFP_KERNEL);
if (!info->event_dispatch_table) {
pr_err("%s: OOM allocating event dispatch table\n", __func__);
return -ENOMEM;
}
for (i = 0; i < NUM_EVT_ID; i++)
info->event_dispatch_table[i] = fts_nop_event_handler;
install_handler(info, ENTER_POINT, enter_pointer);
install_handler(info, LEAVE_POINT, leave_pointer);
install_handler(info, MOTION_POINT, motion_pointer);
install_handler(info, ERROR, error);
install_handler(info, CONTROLLER_READY, controller_ready);
install_handler(info, STATUS_UPDATE, status);
install_handler(info, ENTER_PEN, enter_pen);
install_handler(info, LEAVE_PEN, leave_pen);
install_handler(info, MOTION_PEN, motion_pen);
/* disable interrupts in any case */
error = fts_set_interrupt(info, false);
if (error) return error;
pr_info("%s: Interrupt Mode\n", __func__);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
if (goog_request_threaded_irq(info->gti, info->client->irq, fts_isr,
#else
if (request_threaded_irq(info->client->irq, fts_isr,
#endif
fts_interrupt_handler, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
FTS_TS_DRV_NAME, info)) {
pr_err("%s: Request irq failed\n", __func__);
kfree(info->event_dispatch_table);
error = -EBUSY;
}
info->irq_enabled = true;
return error;
}
/**
* Clean the dispatch table and the free the IRQ.
* This function is called when the driver need to be removed
*/
static void fts_interrupt_uninstall(struct fts_ts_info *info) {
fts_set_interrupt(info, false);
kfree(info->event_dispatch_table);
free_irq(info->client->irq, info);
}
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
static int gti_default_handler(void *private_data, enum gti_cmd_type cmd_type,
struct gti_union_cmd_data *cmd)
{
int res = 0;
static bool grip_enabled;
static bool palm_enabled;
switch (cmd_type) {
case GTI_CMD_GET_GRIP_MODE:
cmd->grip_cmd.setting = (grip_enabled) ?
GTI_GRIP_ENABLE : GTI_GRIP_DISABLE;
res = 0;
pr_info("grip %s.\n", (grip_enabled) ? "enable" : "disable");
break;
case GTI_CMD_GET_PALM_MODE:
cmd->palm_cmd.setting = (palm_enabled) ?
GTI_PALM_ENABLE : GTI_PALM_DISABLE;
res = 0;
pr_info("palm %s.\n", (palm_enabled) ? "enable" : "disable");
break;
case GTI_CMD_NOTIFY_DISPLAY_STATE:
case GTI_CMD_NOTIFY_DISPLAY_VREFRESH:
case GTI_CMD_SET_SCREEN_PROTECTOR_MODE:
res = -EOPNOTSUPP;
break;
case GTI_CMD_SET_CONTINUOUS_REPORT: {
#define CONTINUOUS_ENABLE 0x01
#define CONTINUOUS_DISABLE 0x00
uint8_t spi_buf[5] = {0xB2, 0x00, 0x30, 0x10, CONTINUOUS_DISABLE};
if (cmd->continuous_report_cmd.setting == GTI_CONTINUOUS_REPORT_ENABLE)
spi_buf[4] = CONTINUOUS_ENABLE;
if (fts_write(spi_buf, sizeof(spi_buf)))
res = -EIO;
pr_debug("%s continuous report %s.\n",
(spi_buf[4] == CONTINUOUS_ENABLE) ? "Enable" : "Disable",
!res ? "successfully" : "unsuccessfully");
}
break;
case GTI_CMD_SET_GRIP_MODE: {
#define GRIP_ENABLE 0x01
#define GRIP_DISABLE 0x00
uint8_t spi_buf[5] = {0xB2, 0x00, 0x30, 0x12, GRIP_DISABLE};
if (cmd->grip_cmd.setting == GTI_GRIP_ENABLE)
spi_buf[4] = GRIP_ENABLE;
if (fts_write(spi_buf, sizeof(spi_buf)))
res = -EIO;
else
grip_enabled = spi_buf[4] == GRIP_ENABLE ? true : false;
pr_info("%s FW grip %s, status(%d).\n",
(spi_buf[4] == GRIP_ENABLE) ? "Enable" : "Disable",
!res ? "successfully" : "unsuccessfully",
grip_enabled);
}
break;
case GTI_CMD_SET_PALM_MODE: {
#define PALM_ENABLE 0x03
#define PALM_DISABLE 0x00
uint8_t spi_buf[5] = {0xB2, 0x00, 0x30, 0x11, PALM_DISABLE};
if (cmd->palm_cmd.setting == GTI_PALM_ENABLE)
spi_buf[4] = PALM_ENABLE;
if (fts_write(spi_buf, sizeof(spi_buf)))
res = -EIO;
else
palm_enabled = spi_buf[4] == PALM_ENABLE ? true : false;
pr_info("%s FW palm %s, status(%d).\n",
(spi_buf[4] == PALM_ENABLE) ? "Enable" : "Disable",
!res ? "successfully" : "unsuccessfully",
palm_enabled);
}
break;
case GTI_CMD_SET_HEATMAP_ENABLED:
/* Heatmap is always enabled. */
res = 0;
break;
default:
res = -ESRCH;
break;
}
return res;
}
/**
* Read a MS Frame from frame buffer memory
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param type type of MS frame to read
* @return zero if success or an error code which specify the type of error
*/
int goog_get_ms_frame(struct fts_ts_info *info, ms_frame_type_t type)
{
u16 offset;
int res = 0;
if (!info->fw_ms_data) {
return -ENOMEM;
}
switch (type) {
case MS_RAW:
offset = system_info.u16_ms_scr_raw_addr;
break;
case MS_STRENGTH:
offset = system_info.u16_ms_scr_strength_addr;
break;
case MS_FILTER:
offset = system_info.u16_ms_scr_filter_addr;
break;
case MS_BASELINE:
offset = system_info.u16_ms_scr_baseline_addr;
break;
default:
pr_err("%s: Invalid MS type %d\n", __func__, type);
return -EINVAL;
}
pr_debug("%s: type = %d Offset = 0x%04X\n", __func__, type, offset);
res = get_frame_data(offset, info->mutual_data_size, info->fw_ms_data);
if (res < OK) {
pr_err("%s: error while reading sense data ERROR %08X\n",
__func__, res);
return -EIO;
}
/* if you want to access one node i,j,
* compute the offset like: offset = i*columns + j = > frame[i, j] */
pr_debug("%s: Frame acquired!\n", __func__);
return res;
/* return the number of data put inside frame */
}
/**
* Read a SS Frame from frame buffer
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param type type of SS frame to read
* @return zero if success or an error code which specify the type of error
*/
int goog_get_ss_frame(struct fts_ts_info *info, ss_frame_type_t type)
{
u16 self_force_offset = 0;
u16 self_sense_offset = 0;
int res = 0;
int force_len, sense_len, tmp_force_len, tmp_sense_len;
int16_t *ss_ptr;
if (!info->self_data) {
return -ENOMEM;
}
tmp_force_len = force_len = system_info.u8_scr_tx_len;
tmp_sense_len = sense_len = system_info.u8_scr_rx_len;
if (force_len == 0x00 || sense_len == 0x00 ||
force_len == 0xFF || sense_len == 0xFF) {
pr_err("%s: number of channels not initialized\n", __func__);
return -EINVAL;
}
switch (type) {
case SS_RAW:
self_force_offset = system_info.u16_ss_tch_tx_raw_addr;
self_sense_offset = system_info.u16_ss_tch_rx_raw_addr;
break;
case SS_FILTER:
self_force_offset = system_info.u16_ss_tch_tx_filter_addr;
self_sense_offset = system_info.u16_ss_tch_rx_filter_addr;
break;
case SS_BASELINE:
self_force_offset = system_info.u16_ss_tch_tx_baseline_addr;
self_sense_offset = system_info.u16_ss_tch_rx_baseline_addr;
break;
case SS_STRENGTH:
self_force_offset = system_info.u16_ss_tch_tx_strength_addr;
self_sense_offset = system_info.u16_ss_tch_rx_strength_addr;
break;
case SS_DETECT_RAW:
self_force_offset = system_info.u16_ss_det_tx_raw_addr;
self_sense_offset = system_info.u16_ss_det_rx_raw_addr;
tmp_force_len = (self_force_offset == 0) ? 0 : force_len;
tmp_sense_len = (self_sense_offset == 0) ? 0 : sense_len;
break;
case SS_DETECT_STRENGTH:
self_force_offset = system_info.u16_ss_det_tx_strength_addr;
self_sense_offset = system_info.u16_ss_det_rx_strength_addr;
tmp_force_len = (self_force_offset == 0) ? 0 : force_len;
tmp_sense_len = (self_sense_offset == 0) ? 0 : sense_len;
break;
case SS_DETECT_BASELINE:
self_force_offset = system_info.u16_ss_det_tx_baseline_addr;
self_sense_offset = system_info.u16_ss_det_rx_baseline_addr;
tmp_force_len = (self_force_offset == 0) ? 0 : force_len;
tmp_sense_len = (self_sense_offset == 0) ? 0 : sense_len;
break;
case SS_DETECT_FILTER:
self_force_offset = system_info.u16_ss_det_tx_filter_addr;
self_sense_offset = system_info.u16_ss_det_rx_filter_addr;
tmp_force_len = (self_force_offset == 0) ? 0 : force_len;
tmp_sense_len = (self_sense_offset == 0) ? 0 : sense_len;
break;
default:
pr_err("%s: Invalid SS type = %d\n", __func__, type);
return -EINVAL;
}
pr_debug("%s: type = %d Force_len = %d Sense_len = %d"
" Offset_force = 0x%04X Offset_sense = 0x%04X\n",
__func__, type, tmp_force_len, tmp_sense_len,
self_force_offset, self_sense_offset);
if (self_force_offset) {
ss_ptr = &info->self_data[tmp_sense_len];
res = get_frame_data(self_force_offset,
tmp_force_len * BYTES_PER_NODE, ss_ptr);
if (res < OK) {
pr_err("%s: error while reading force data ERROR %08X\n",
__func__, res);
return -EIO;
}
}
if (self_sense_offset) {
ss_ptr = info->self_data;
res = get_frame_data(self_sense_offset,
tmp_sense_len * BYTES_PER_NODE, ss_ptr);
if (res < OK) {
pr_err("%s: error while reading sense data ERROR %08X\n",
__func__, res);
return -EIO;
}
}
pr_debug("%s: Frame acquired!\n", __func__);
return res;
}
static int get_fw_version(void *private_data, struct gti_fw_version_cmd *cmd)
{
int cmd_buf_size = sizeof(cmd->buffer);
ssize_t buf_idx = 0;
pr_info("%s\n", __func__);
buf_idx += scnprintf(cmd->buffer + buf_idx, cmd_buf_size - buf_idx,
"\nREG Revision: 0x%04X\n", system_info.u16_reg_ver);
buf_idx += scnprintf(cmd->buffer + buf_idx, cmd_buf_size - buf_idx,
"FW Version: 0x%04X\n", system_info.u16_fw_ver);
buf_idx += scnprintf(cmd->buffer + buf_idx, cmd_buf_size - buf_idx,
"SVN Revision: 0x%04X\n", system_info.u16_svn_rev);
buf_idx += scnprintf(cmd->buffer + buf_idx, cmd_buf_size - buf_idx,
"Config Afe Ver: 0x%04X\n", system_info.u8_cfg_afe_ver);
return 0;
}
static int get_mutual_sensor_data(void *private_data, struct gti_sensor_data_cmd *cmd)
{
struct fts_ts_info *info = private_data;
int res = 0;
uint32_t frame_index = 0;
uint16_t x, y;
int tx_size = system_info.u8_scr_tx_len;
int rx_size = system_info.u8_scr_rx_len;
int cmd_type = 0;
cmd->buffer = NULL;
cmd->size = 0;
if (cmd->type & TOUCH_DATA_TYPE_STRENGTH)
cmd_type = MS_STRENGTH;
else if (cmd->type & TOUCH_DATA_TYPE_BASELINE)
cmd_type = MS_BASELINE;
else if (cmd->type & TOUCH_DATA_TYPE_RAW)
cmd_type = MS_RAW;
else {
pr_err("%s: Invalid command type(0x%X).\n", __func__, cmd->type);
return -EINVAL;
}
res = goog_get_ms_frame(info, cmd_type);
if (res < 0) {
pr_err("%s: failed with res=0x%08X.\n", __func__, res);
return res;
}
for (y = 0; y < rx_size; y++) {
for (x = 0; x < tx_size; x++) {
/* swap tx and rx direction. */
info->mutual_data[frame_index++] =
info->fw_ms_data[y * tx_size + x];
}
}
cmd->buffer = (u8 *)info->mutual_data;
cmd->size = info->mutual_data_size;
return res;
}
static int get_self_sensor_data(void *private_data, struct gti_sensor_data_cmd *cmd)
{
struct fts_ts_info *info = private_data;
int res = 0;
int cmd_type = 0;
cmd->buffer = (u8 *)info->self_data;
cmd->size = info->self_data_size;
if (cmd->type & TOUCH_DATA_TYPE_STRENGTH)
cmd_type = SS_STRENGTH;
else if (cmd->type & TOUCH_DATA_TYPE_BASELINE)
cmd_type = SS_BASELINE;
else if (cmd->type & TOUCH_DATA_TYPE_RAW)
cmd_type = SS_RAW;
else {
pr_err("%s: Invalid command type(0x%X).\n", __func__, cmd->type);
return -EINVAL;
}
res = goog_get_ss_frame(info, cmd_type);
if (res < 0) {
pr_err("%s: failed with res=0x%08X.\n", __func__, res);
return res;
}
cmd->buffer = (u8 *)info->self_data;
cmd->size = info->self_data_size;
return res;
}
#endif
#ifdef CONFIG_PM
/**
* Resume function which perform a system reset, clean all the touches
*from the linux input system and prepare the ground for enabling the sensing
*/
static void fts_resume(struct fts_ts_info *info)
{
if (!info->sensor_sleep) return;
pr_info("%s\n", __func__);
pm_stay_awake(info->dev);
fts_pinctrl_setup(info, true);
fts_system_reset(info, 1);
info->resume_bit = 1;
fts_mode_handler(info, 0);
fts_set_interrupt(info, true);
info->sensor_sleep = false;
}
/**
* Suspend function which clean all the touches from Linux input system
*and prepare the ground to disabling the sensing or enter in gesture mode
*/
static void fts_suspend(struct fts_ts_info *info)
{
if (info->sensor_sleep) return;
pr_info("%s\n", __func__);
info->sensor_sleep = true;
fts_set_interrupt(info, false);
info->resume_bit = 0;
fts_mode_handler(info, 0);
fts_pinctrl_setup(info, false);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
info->touch_id = 0;
#else
release_all_touches(info);
#endif
pm_relax(info->dev);
}
#endif
/**
* Complete the boot up process, initializing the sensing of the IC according
* to the current setting chosen by the host
* Register the notifier for the suspend/resume actions and the event handler
* @return OK if success or an error code which specify the type of error
*/
static int fts_init_sensing(struct fts_ts_info *info)
{
int error = 0;
int add = 0x001C;
uint8_t int_data = 0x01;
int res = 0;
error |= fts_interrupt_install(info);
pr_info("%s: Sensing on..\n", __func__);
error |= fts_mode_handler(info, 0);
error |= fts_set_interrupt(info, true); /* enable the interrupt */
res = fts_write_fw_reg(add, &int_data, 1);
if (res < OK) {
pr_err("%s: ERROR %08X\n", __func__, res);
}
if (error < OK)
pr_err("%s: Init error (ERROR = %08X)\n", __func__, error);
return error;
}
/**
* Implement the fw update and initialization flow of the IC that should be
*executed at every boot up.
* The function perform a fw update of the IC in case of crc error or a new
*fw version and then understand if the IC need to be re-initialized again.
* @return OK if success or an error code which specify the type of error
* encountered
*/
static int fts_chip_init(struct fts_ts_info *info)
{
int res = OK;
int i = 0;
struct force_update_flag force_burn;
force_burn.code_update = 0;
force_burn.panel_init = 0;
for (; i < FLASH_MAX_SECTIONS; i++)
force_burn.section_update[i] = 0;
pr_info("%s: [1]: FW UPDATE..\n", __func__);
res = flash_update(info, &force_burn);
if (res != OK) {
pr_err("%s: [1]: FW UPDATE FAILED.. res = %d\n", __func__, res);
return res;
}
if (force_burn.panel_init) {
pr_info("%s: [2]: MP TEST..\n", __func__);
res = fts_production_test_main(info, LIMITS_FILE, 0, &tests, 0);
if (res != OK)
pr_err("%s: [2]: MP TEST FAILED.. res = %d\n",
__func__, res);
}
pr_info("%s: [3]: TOUCH INIT..\n", __func__);
res = fts_init_sensing(info);
if (res != OK) {
pr_err("%s: [3]: TOUCH INIT FAILED.. res = %d\n", __func__, res);
return res;
}
return res;
}
#ifndef FW_UPDATE_ON_PROBE
/**
* Function called by the delayed workthread executed after the probe in
* order to perform the fw update flow
* @see fts_chip_init()
*/
static void flash_update_auto(struct work_struct *work)
{
struct delayed_work *fwu_work = container_of(work, struct delayed_work,
work);
struct fts_ts_info *info = container_of(fwu_work, struct fts_ts_info,
fwu_work);
fts_chip_init(info);
}
#endif
/**
* This function try to attempt to communicate with the IC for the first time
* during the boot up process in order to read the necessary info for the
* following stages.
* The function execute a system reset, read fundamental info (system info)
* @return OK if success or an error code which specify the type of error
*/
static int fts_init(struct fts_ts_info *info)
{
int res = 0;
u8 data[3] = { 0 };
u16 chip_id = 0;
int retry_cnt = 0;
open_channel(info->client);
init_test_to_do();
#ifndef I2C_INTERFACE
#ifdef SPI4_WIRE
pr_info("%s: Configuring SPI4..\n", __func__);
res = configure_spi4();
if (res < OK) {
pr_err("%s: Error configuring IC in spi4 mode: %08X\n",
__func__, res);
return res;
}
#endif
#endif
do {
res = fts_write_read_u8ux(FTS_CMD_HW_REG_R, HW_ADDR_SIZE,
CHIP_ID_ADDRESS, data, 2, DUMMY_BYTE);
if (res < OK) {
pr_err("%s: Bus Connection issue: %08X\n", __func__, res);
return res;
}
chip_id = (u16)((data[0] << 8) + data[1]);
pr_info("%s: Chip id: 0x%04X, retry: %d\n", __func__, chip_id, retry_cnt);
if (chip_id != CHIP_ID) {
pr_err("%s: Wrong Chip detected.. Expected|Detected: 0x%04X|0x%04X\n",
__func__, CHIP_ID, chip_id);
if (retry_cnt >= MAX_PROBE_RETRY)
return ERROR_WRONG_CHIP_ID;
}
res = fts_system_reset(info, 1);
if (res < OK) {
if (res == ERROR_BUS_W) {
pr_err("%s: Bus Connection issue\n", __func__);
return res;
}
/*
* other errors are because of no FW,
* so we continue to flash
*/
}
retry_cnt++;
} while (chip_id != CHIP_ID);
res = read_sys_info();
if (res < 0)
pr_err("%s: Couldnot read sys info.. No FW..\n", __func__);
return OK;
}
/**
* From the name of the power regulator get/put the actual regulator structs
* (copying their references into fts_ts_info variable)
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param get if 1, the regulators are get otherwise they are put (released)
* back to the system
* @return OK if success or an error code which specify the type of error
*/
static int fts_get_reg(struct fts_ts_info *info, bool get)
{
int ret_val;
if (!get) {
ret_val = 0;
goto regulator_put;
}
if (of_property_read_bool(info->dev->of_node, "vdd-supply")) {
info->vdd_reg = regulator_get(info->dev, "vdd");
if (IS_ERR(info->vdd_reg)) {
pr_err("%s: Failed to get power regulator\n", __func__);
ret_val = -EPROBE_DEFER;
goto regulator_put;
}
}
if (of_property_read_bool(info->dev->of_node, "avdd-supply")) {
info->avdd_reg = regulator_get(info->dev, "avdd");
if (IS_ERR(info->avdd_reg)) {
pr_err("%s: Failed to get bus pullup regulator\n",
__func__);
ret_val = -EPROBE_DEFER;
goto regulator_put;
}
}
return OK;
regulator_put:
if (info->vdd_reg) {
regulator_put(info->vdd_reg);
info->vdd_reg = NULL;
}
if (info->avdd_reg) {
regulator_put(info->avdd_reg);
info->avdd_reg = NULL;
}
return ret_val;
}
/**
* Enable or disable the power regulators
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param enable if 1, the power regulators are turned on otherwise they are
* turned off
* @return OK if success or an error code which specify the type of error
*/
static int fts_enable_reg(struct fts_ts_info *info, bool enable)
{
int ret_val;
if (!enable) {
ret_val = 0;
goto disable_pwr_reg;
}
if (info->vdd_reg) {
ret_val = regulator_enable(info->vdd_reg);
if (ret_val < 0) {
pr_err("%s: Failed to enable bus regulator\n", __func__);
goto exit;
}
}
if (info->avdd_reg) {
ret_val = regulator_enable(info->avdd_reg);
if (ret_val < 0) {
pr_err("%s: Failed to enable power regulator\n",
__func__);
goto disable_bus_reg;
}
}
return OK;
disable_pwr_reg:
if (info->avdd_reg)
regulator_disable(info->avdd_reg);
disable_bus_reg:
if (info->vdd_reg)
regulator_disable(info->vdd_reg);
exit:
return ret_val;
}
/**
* Configure a GPIO according to the parameters
* @param gpio gpio number
* @param config if true, the gpio is set up otherwise it is free
* @param dir direction of the gpio, 0 = in, 1 = out
* @param state initial value (if the direction is in, this parameter is
* ignored)
* return error code
*/
static int fts_gpio_setup(int gpio, bool config, int dir, int state)
{
int ret_val = 0;
unsigned char buf[16];
if (config) {
scnprintf(buf, 16, "fts_gpio_%u\n", gpio);
ret_val = gpio_request(gpio, buf);
if (ret_val) {
pr_err("%s: Failed to get gpio %d (code: %d)",
__func__, gpio, ret_val);
return ret_val;
}
if (dir == 0)
ret_val = gpio_direction_input(gpio);
else
ret_val = gpio_direction_output(gpio, state);
if (ret_val) {
pr_err("%s: Failed to set gpio %d direction",
__func__, gpio);
return ret_val;
}
} else
gpio_free(gpio);
return ret_val;
}
/**
* Setup the IRQ and RESET (if present) gpios.
* If the Reset Gpio is present it will perform a cycle HIGH-LOW-HIGH in order
*to assure that the IC has been reset properly
*/
static int fts_set_gpio(struct fts_ts_info *info)
{
int ret_val;
struct fts_hw_platform_data *bdata = info->board;
ret_val = fts_gpio_setup(bdata->irq_gpio, true, 0, 0);
if (ret_val < 0) {
pr_err("%s: Failed to configure irq GPIO\n", __func__);
goto err_gpio_irq;
}
if (bdata->reset_gpio >= 0) {
ret_val = fts_gpio_setup(bdata->reset_gpio, true, 1, 0);
if (ret_val < 0) {
pr_err("%s: Failed to configure reset GPIO\n", __func__);
goto err_gpio_reset;
}
}
if (bdata->reset_gpio >= 0) {
gpio_set_value(bdata->reset_gpio, 0);
msleep(20);
gpio_set_value(bdata->reset_gpio, 1);
}
return OK;
err_gpio_reset:
fts_gpio_setup(bdata->irq_gpio, false, 0, 0);
bdata->reset_gpio = GPIO_NOT_DEFINED;
err_gpio_irq:
return ret_val;
}
/** Set pin state to active or suspend
* @param active 1 for active while 0 for suspend
*/
static void fts_pinctrl_setup(struct fts_ts_info *info, bool active)
{
int retval;
if (info->ts_pinctrl) {
/*
* Pinctrl setup is optional.
* If pinctrl is found, set pins to active/suspend state.
* Otherwise, go on without showing error messages.
*/
retval = pinctrl_select_state(info->ts_pinctrl, active ?
info->pinctrl_state_active :
info->pinctrl_state_suspend);
if (retval < 0) {
dev_err(info->dev, "Failed to select %s pinstate %d\n", active ?
PINCTRL_STATE_ACTIVE : PINCTRL_STATE_SUSPEND,
retval);
}
} else {
dev_warn(info->dev, "ts_pinctrl is NULL\n");
}
}
/**
* Get/put the touch pinctrl from the specific names. If pinctrl is used, the
* active and suspend pin control names and states are necessary.
* @param info pointer to fts_ts_info which contains info about the device and
* its hw setup
* @param get if 1, the pinctrl is get otherwise it is put (released) back to
* the system
* @return OK if success or an error code which specify the type of error
*/
static int fts_pinctrl_get(struct fts_ts_info *info, bool get)
{
int retval;
if (!get) {
retval = 0;
goto pinctrl_put;
}
info->ts_pinctrl = devm_pinctrl_get(info->dev);
if (IS_ERR_OR_NULL(info->ts_pinctrl)) {
retval = PTR_ERR(info->ts_pinctrl);
dev_info(info->dev, "Target does not use pinctrl %d\n", retval);
goto err_pinctrl_get;
}
info->pinctrl_state_active
= pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_ACTIVE);
if (IS_ERR_OR_NULL(info->pinctrl_state_active)) {
retval = PTR_ERR(info->pinctrl_state_active);
dev_err(info->dev, "Can not lookup %s pinstate %d\n",
PINCTRL_STATE_ACTIVE, retval);
goto err_pinctrl_lookup;
}
info->pinctrl_state_suspend
= pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_SUSPEND);
if (IS_ERR_OR_NULL(info->pinctrl_state_suspend)) {
retval = PTR_ERR(info->pinctrl_state_suspend);
dev_err(info->dev, "Can not lookup %s pinstate %d\n",
PINCTRL_STATE_SUSPEND, retval);
goto err_pinctrl_lookup;
}
info->pinctrl_state_release
= pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_RELEASE);
if (IS_ERR_OR_NULL(info->pinctrl_state_release)) {
retval = PTR_ERR(info->pinctrl_state_release);
dev_warn(info->dev, "Can not lookup %s pinstate %d\n",
PINCTRL_STATE_RELEASE, retval);
}
return OK;
err_pinctrl_lookup:
devm_pinctrl_put(info->ts_pinctrl);
err_pinctrl_get:
info->ts_pinctrl = NULL;
pinctrl_put:
if (info->ts_pinctrl) {
if (IS_ERR_OR_NULL(info->pinctrl_state_release)) {
devm_pinctrl_put(info->ts_pinctrl);
info->ts_pinctrl = NULL;
} else {
if (pinctrl_select_state(
info->ts_pinctrl,
info->pinctrl_state_release))
dev_warn(info->dev, "Failed to select release pinstate\n");
}
}
return retval;
}
/**
* Retrieve and parse the hw information from the device tree node defined in
* the system.
* the most important information to obtain are: IRQ and RESET gpio numbers,
* power regulator names
* In the device file node is possible to define additional optional
*information that can be parsed here.
*/
static int parse_dt(struct device *dev, struct fts_hw_platform_data *bdata)
{
int retval;
int index;
struct of_phandle_args panelmap;
struct device_node *np = dev->of_node;
struct drm_panel *panel = NULL;
if (of_property_read_bool(np, "st,panel_map")) {
for (index = 0 ;; index++) {
retval = of_parse_phandle_with_fixed_args(np,
"st,panel_map",
1,
index,
&panelmap);
if (retval)
return -EPROBE_DEFER;
panel = of_drm_find_panel(panelmap.np);
of_node_put(panelmap.np);
if (!IS_ERR_OR_NULL(panel)) {
bdata->panel = panel;
break;
}
}
}
bdata->irq_gpio = of_get_named_gpio_flags(np, "st,irq-gpio", 0, NULL);
pr_info("%s: irq_gpio = %d\n", __func__, bdata->irq_gpio);
if (of_property_read_bool(np, "st,reset-gpio")) {
bdata->reset_gpio = of_get_named_gpio_flags(np,
"st,reset-gpio", 0, NULL);
pr_info("%s: reset_gpio = %d\n", __func__, bdata->reset_gpio);
} else
bdata->reset_gpio = GPIO_NOT_DEFINED;
if (of_property_read_u8(np, "st,mm2px", &bdata->mm2px)) {
pr_err("%s: Unable to get mm2px, please check dts", __func__);
bdata->mm2px = 1;
} else {
pr_info("%s: mm2px = %d", __func__, bdata->mm2px);
}
return OK;
}
/**
* Probe function, called when the driver it is matched with a device with the
*same name compatible name
* This function allocate, initialize and define all the most important
*function and flow that are used by the driver to operate with the IC.
* It allocates device variables, initialize queues and schedule works,
*registers the IRQ handler, suspend/resume callbacks, registers the device to
*the linux input subsystem etc.
*/
#ifdef I2C_INTERFACE
static int fts_probe(struct i2c_client *client, const struct i2c_device_id
*idp)
{
#else
static int fts_probe(struct spi_device *client)
{
#endif
struct fts_ts_info *info = NULL;
struct fts_hw_platform_data *bdata = NULL;
int error = 0;
struct device_node *dp = client->dev.of_node;
int ret_val;
u16 bus_type;
u8 input_dev_free_flag = 0;
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
struct gti_optional_configuration *options;
#endif
pr_info("%s: driver probe begin!\n", __func__);
pr_info("%s: driver ver. %s\n", __func__, FTS_TS_DRV_VERSION);
info = kzalloc(sizeof(struct fts_ts_info), GFP_KERNEL);
if (!info) {
dev_err(&client->dev, "Out of memory... Impossible to allocate struct info!\n");
error = -ENOMEM;
goto probe_error_exit_0;
}
#ifdef I2C_INTERFACE
pr_info("%s: I2C interface...\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
pr_err("%s: Unsupported I2C functionality\n", __func__);
error = -EIO;
goto probe_error_exit_1;
}
pr_info("%s: I2C address: %x\n", __func__, client->addr);
bus_type = BUS_I2C;
#else
client->mode = SPI_MODE_0;
#ifndef SPI4_WIRE
client->mode |= SPI_3WIRE;
#endif
if (client->controller->rt == false) {
client->rt = true;
ret_val = spi_setup(client);
if (ret_val < 0) {
pr_err("%s: setup SPI rt failed(%d)\n", __func__, ret_val);
error = -EIO;
goto probe_error_exit_1;
}
}
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
info->dma_mode = goog_check_spi_dma_enabled(client);
#endif
pr_info("%s: SPI interface: dma_mode %d.\n", __func__, info->dma_mode);
bus_type = BUS_SPI;
#endif
pr_info("%s: SET Device driver INFO:\n", __func__);
info->client = client;
info->dev = &info->client->dev;
dev_set_drvdata(info->dev, info);
if (dp) {
info->board = devm_kzalloc(&client->dev,
sizeof(struct fts_hw_platform_data),
GFP_KERNEL);
if (!info->board) {
pr_err("%s: ERROR:info.board kzalloc failed\n",
__func__);
goto probe_error_exit_1;
}
parse_dt(&client->dev, info->board);
bdata = info->board;
}
pr_info("%s: SET Regulators:\n", __func__);
error = fts_get_reg(info, true);
if (error < 0) {
pr_err("%s: ERROR:Failed to get regulators\n",
__func__);
goto probe_error_exit_1;
}
ret_val = fts_enable_reg(info, true);
if (ret_val < 0) {
pr_err("%s: ERROR Failed to enable regulators\n",
__func__);
goto probe_error_exit_2;
}
pr_info("%s: SET GPIOS_Test:\n", __func__);
ret_val = fts_set_gpio(info);
if (ret_val < 0) {
pr_err("%s: ERROR Failed to set up GPIO's\n",
__func__);
goto probe_error_exit_2;
}
info->client->irq = gpio_to_irq(info->board->irq_gpio);
info->dev = &info->client->dev;
dev_info(info->dev, "SET Pinctrl:\n");
ret_val = fts_pinctrl_get(info, true);
if (!ret_val)
fts_pinctrl_setup(info, true);
mutex_init(&info->fts_int_mutex);
pr_info("%s: SET Input Device Property:\n", __func__);
info->input_dev = input_allocate_device();
if (!info->input_dev) {
pr_err("%s: ERROR: No such input device defined!\n", __func__);
error = -ENODEV;
goto probe_error_exit_2;
}
info->input_dev->dev.parent = &client->dev;
info->input_dev->name = FTS_TS_DRV_NAME;
scnprintf(fts_ts_phys, sizeof(fts_ts_phys), "%s/input0",
info->input_dev->name);
info->input_dev->phys = fts_ts_phys;
info->input_dev->uniq = "fts";
info->input_dev->id.bustype = bus_type;
info->input_dev->id.vendor = 0x0001;
info->input_dev->id.product = 0x0002;
info->input_dev->id.version = 0x0100;
__set_bit(EV_SYN, info->input_dev->evbit);
__set_bit(EV_KEY, info->input_dev->evbit);
__set_bit(EV_ABS, info->input_dev->evbit);
__set_bit(BTN_TOUCH, info->input_dev->keybit);
input_mt_init_slots(info->input_dev, TOUCH_ID_MAX + PEN_ID_MAX,
INPUT_MT_DIRECT);
input_set_abs_params(info->input_dev, ABS_MT_POSITION_X, X_AXIS_MIN,
X_AXIS_MAX, 0, 0);
input_set_abs_params(info->input_dev, ABS_MT_POSITION_Y, Y_AXIS_MIN,
Y_AXIS_MAX, 0, 0);
input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MAJOR,
ABS_MAJOR_MIN(bdata->mm2px), ABS_MAJOR_MAX(bdata->mm2px), 0, 0);
input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MINOR,
ABS_MINOR_MIN(bdata->mm2px), ABS_MINOR_MAX(bdata->mm2px), 0, 0);
input_set_abs_params(info->input_dev, ABS_MT_PRESSURE, PRESSURE_MIN,
PRESSURE_MAX, 0, 0);
input_set_abs_params(info->input_dev, ABS_MT_DISTANCE, DISTANCE_MIN,
DISTANCE_MAX, 0, 0);
input_set_abs_params(info->input_dev, ABS_TILT_X, DISTANCE_MIN,
DISTANCE_MAX, 0, 0);
input_set_abs_params(info->input_dev, ABS_TILT_Y, DISTANCE_MIN,
DISTANCE_MAX, 0, 0);
error = input_register_device(info->input_dev);
if (error) {
pr_err("%s: ERROR: No such input device\n", __func__);
error = -ENODEV;
goto probe_error_exit_5;
}
input_dev_free_flag = 1;
info->resume_bit = 1;
ret_val = fts_init(info);
if (ret_val < OK) {
pr_err("%s: Initialization fails.. exiting..\n", __func__);
if (ret_val == ERROR_WRONG_CHIP_ID)
error = -EPROBE_DEFER;
else
error = -EIO;
goto probe_error_exit_6;
}
ret_val = fts_proc_init(info);
if (ret_val < OK)
pr_err("%s: Cannot create /proc filenode..\n", __func__);
#if defined(FW_UPDATE_ON_PROBE) && defined(FW_H_FILE)
ret_val = fts_chip_init(info);
if (ret_val < OK) {
pr_err("%s: Flashing FW/Production Test/Touch Init Failed..\n",
__func__);
goto probe_error_exit_6;
}
#else
pr_info("%s: SET Auto Fw Update:\n", __func__);
info->fwu_workqueue = alloc_workqueue("fts-fwu-queue", WQ_UNBOUND |
WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1);
if (!info->fwu_workqueue) {
pr_err("%s: ERROR: Cannot create fwu work thread\n", __func__);
goto probe_error_exit_6;
}
INIT_DELAYED_WORK(&info->fwu_work, flash_update_auto);
#endif
#ifndef FW_UPDATE_ON_PROBE
queue_delayed_work(info->fwu_workqueue, &info->fwu_work,
msecs_to_jiffies(1000));
#endif
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
if (system_info.u8_scr_tx_len > 0 && system_info.u8_scr_rx_len > 0) {
info->mutual_data_size =
system_info.u8_scr_tx_len * system_info.u8_scr_rx_len *
sizeof(int16_t);
info->mutual_data = (short *)kmalloc(info->mutual_data_size,
GFP_KERNEL);
if (!info->mutual_data) {
pr_err("%s: Failed to allocate mutual_data.\n", __func__);
goto probe_error_exit_6;
}
info->self_data_size =
(system_info.u8_scr_tx_len + system_info.u8_scr_rx_len) *
sizeof(int16_t);
info->self_data = kmalloc(info->self_data_size, GFP_KERNEL);
if (!info->self_data) {
pr_err("%s: Failed to allocate self data.\n", __func__);
goto probe_error_exit_6;
}
info->fw_ms_data = (short *)kmalloc(info->mutual_data_size,
GFP_KERNEL);
if (!info->fw_ms_data) {
pr_err("%s: Failed to allocate fw mutual_data.\n", __func__);
goto probe_error_exit_6;
}
} else {
pr_err("%s: Incorrect system information ForceLen=%d SenseLen=%d.\n",
__func__, system_info.u8_scr_tx_len, system_info.u8_scr_rx_len);
goto probe_error_exit_6;
}
options = devm_kzalloc(info->dev, sizeof(struct gti_optional_configuration), GFP_KERNEL);
if (!options) {
pr_err("%s: GTI optional configuration kzalloc failed.\n",
__func__);
goto probe_error_exit_6;
}
options->get_fw_version = get_fw_version;
options->get_mutual_sensor_data = get_mutual_sensor_data;
options->get_self_sensor_data = get_self_sensor_data;
info->gti = goog_touch_interface_probe(
info, info->dev, info->input_dev, gti_default_handler, options);
ret_val = goog_pm_register_notification(info->gti, &fts_pm_ops);
if (ret_val < 0) {
pr_err("%s: Failed to register gti pm", __func__);
goto probe_error_exit_7;
}
#endif
pr_info("%s: Probe Finished!\n", __func__);
return OK;
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
probe_error_exit_7:
devm_kfree(info->dev, options);
#endif
probe_error_exit_6:
input_unregister_device(info->input_dev);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
kfree(info->mutual_data);
kfree(info->self_data);
kfree(info->fw_ms_data);
#endif
probe_error_exit_5:
if (!input_dev_free_flag)
input_free_device(info->input_dev);
probe_error_exit_2:
fts_enable_reg(info, false);
fts_get_reg(info, false);
probe_error_exit_1:
kfree(info);
probe_error_exit_0:
pr_err("%s: Probe Failed!\n", __func__);
return error;
}
/**
* Clear and free all the resources associated to the driver.
* This function is called when the driver need to be removed.
*/
#ifdef I2C_INTERFACE
static int fts_remove(struct i2c_client *client)
{
#else
static int fts_remove(struct spi_device *client)
{
#endif
struct fts_ts_info *info = dev_get_drvdata(&(client->dev));
fts_proc_remove();
fts_interrupt_uninstall(info);
input_unregister_device(info->input_dev);
#ifndef FW_UPDATE_ON_PROBE
destroy_workqueue(info->fwu_workqueue);
#endif
fts_enable_reg(info, false);
fts_get_reg(info, false);
#if IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
kfree(info->mutual_data);
kfree(info->self_data);
kfree(info->fw_ms_data);
#endif
kfree(info);
return OK;
}
#ifdef CONFIG_PM
static int fts_pm_suspend(struct device *dev)
{
struct fts_ts_info *info = dev_get_drvdata(dev);
fts_suspend(info);
return 0;
}
static int fts_pm_resume(struct device *dev)
{
struct fts_ts_info *info = dev_get_drvdata(dev);
fts_resume(info);
return 0;
}
static SIMPLE_DEV_PM_OPS(fts_pm_ops, fts_pm_suspend, fts_pm_resume);
#endif
static struct of_device_id fts_of_match_table[] = {
{
.compatible = "st,fst2",
},
{},
};
#ifdef I2C_INTERFACE
static const struct i2c_device_id fts_device_id[] = {
{ FTS_TS_DRV_NAME, 0 },
{}
};
static struct i2c_driver fts_i2c_driver = {
.driver = {
.name = FTS_TS_DRV_NAME,
.of_match_table = fts_of_match_table,
#if IS_ENABLED(CONFIG_PM) && !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
.pm = &fts_pm_ops,
#endif
},
.probe = fts_probe,
.remove = fts_remove,
.id_table = fts_device_id,
};
#else
static struct spi_driver fts_spi_driver = {
.driver = {
.name = FTS_TS_DRV_NAME,
.of_match_table = fts_of_match_table,
#if IS_ENABLED(CONFIG_PM) && !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
.pm = &fts_pm_ops,
#endif
.owner = THIS_MODULE,
},
.probe = fts_probe,
.remove = fts_remove,
};
#endif
static int __init fts_driver_init(void)
{
#ifdef I2C_INTERFACE
return i2c_add_driver(&fts_i2c_driver);
#else
return spi_register_driver(&fts_spi_driver);
#endif
}
static void __exit fts_driver_exit(void)
{
#ifdef I2C_INTERFACE
i2c_del_driver(&fts_i2c_driver);
#else
spi_unregister_driver(&fts_spi_driver);
#endif
}
MODULE_DESCRIPTION("STMicroelectronics MultiTouch IC Driver");
MODULE_AUTHOR("STMicroelectronics");
MODULE_LICENSE("GPL");
late_initcall(fts_driver_init);
module_exit(fts_driver_exit);