kernel_samsung_a34x-permissive/drivers/input/touchscreen/ili9881x/ili9881x_spi.c

692 lines
19 KiB
C
Raw Normal View History

/*
* ILITEK Touch IC driver
*
* Copyright (C) 2011 ILI Technology Corporation.
*
* Author: Dicky Chiang <dicky_chiang@ilitek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "ili9881x.h"
struct touch_bus_info {
struct spi_driver bus_driver;
struct ilitek_hwif_info *hwif;
};
struct ilitek_ts_data *ilits;
#if SPI_DMA_TRANSFER_SPLIT
#define DMA_TRANSFER_MAX_CHUNK 64 // number of chunks to be transferred.
#define DMA_TRANSFER_MAX_LEN 1024 // length of a chunk.
int ili_spi_write_then_read_split(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
int status = -1, duplex_len = 0;
int xfercnt = 0, xferlen = 0, xferloop = 0;
int offset = 0;
u8 cmd = 0;
struct spi_message message;
struct spi_transfer *xfer;
if (ilits->power_status == POWER_OFF_STATUS) {
input_err(true, ilits->dev, "%s failed(power off state).\n", __func__);
return -1;
}
xfer = kzalloc(DMA_TRANSFER_MAX_CHUNK * sizeof(struct spi_transfer), GFP_KERNEL);
if (n_rx > SPI_RX_BUF_SIZE) {
input_err(true, ilits->dev, "%s Rx length is greater than spi local buf, abort\n", __func__);
status = -ENOMEM;
goto out;
}
spi_message_init(&message);
memset(ilits->spi_tx, 0x0, SPI_TX_BUF_SIZE);
memset(ilits->spi_rx, 0x0, SPI_RX_BUF_SIZE);
if ((n_tx > 0) && (n_rx > 0))
cmd = SPI_READ;
else
cmd = SPI_WRITE;
switch (cmd) {
case SPI_WRITE:
if (n_tx % DMA_TRANSFER_MAX_LEN)
xferloop = (n_tx / DMA_TRANSFER_MAX_LEN) + 1;
else
xferloop = n_tx / DMA_TRANSFER_MAX_LEN;
xferlen = n_tx;
memcpy(ilits->spi_tx, (u8 *)txbuf, xferlen);
for (xfercnt = 0; xfercnt < xferloop; xfercnt++) {
if (xferlen > DMA_TRANSFER_MAX_LEN)
xferlen = DMA_TRANSFER_MAX_LEN;
xfer[xfercnt].len = xferlen;
xfer[xfercnt].tx_buf = ilits->spi_tx + xfercnt * DMA_TRANSFER_MAX_LEN;
spi_message_add_tail(&xfer[xfercnt], &message);
xferlen = n_tx - (xfercnt+1) * DMA_TRANSFER_MAX_LEN;
}
if (ilits->cs_gpio > 0) {
gpio_direction_output(ilits->cs_gpio, 0);
status = spi_sync(spi, &message);
gpio_direction_output(ilits->cs_gpio, 1);
} else {
status = spi_sync(spi, &message);
}
break;
case SPI_READ:
if (n_tx > DMA_TRANSFER_MAX_LEN) {
input_err(true, ilits->dev, "%s Tx length must be lower than dma length (%d).\n",
__func__, DMA_TRANSFER_MAX_LEN);
status = -EINVAL;
break;
}
if (!atomic_read(&ilits->ice_stat))
offset = 2;
memcpy(ilits->spi_tx, txbuf, n_tx);
duplex_len = n_tx + n_rx + offset;
if (duplex_len % DMA_TRANSFER_MAX_LEN)
xferloop = (duplex_len / DMA_TRANSFER_MAX_LEN) + 1;
else
xferloop = duplex_len / DMA_TRANSFER_MAX_LEN;
xferlen = duplex_len;
for (xfercnt = 0; xfercnt < xferloop; xfercnt++) {
if (xferlen > DMA_TRANSFER_MAX_LEN)
xferlen = DMA_TRANSFER_MAX_LEN;
xfer[xfercnt].len = xferlen;
xfer[xfercnt].tx_buf = ilits->spi_tx;
xfer[xfercnt].rx_buf = ilits->spi_rx + xfercnt * DMA_TRANSFER_MAX_LEN;
spi_message_add_tail(&xfer[xfercnt], &message);
xferlen = duplex_len - (xfercnt + 1) * DMA_TRANSFER_MAX_LEN;
}
if (ilits->cs_gpio > 0) {
gpio_direction_output(ilits->cs_gpio, 0);
status = spi_sync(spi, &message);
gpio_direction_output(ilits->cs_gpio, 1);
} else {
status = spi_sync(spi, &message);
}
if (status == 0) {
if (ilits->spi_rx[1] != SPI_ACK && !atomic_read(&ilits->ice_stat)) {
status = DO_SPI_RECOVER;
input_err(true, ilits->dev, "%s Do spi recovery: rxbuf[1] = 0x%x, ice = %d\n",
__func__, ilits->spi_rx[1], atomic_read(&ilits->ice_stat));
break;
}
memcpy((u8 *)rxbuf, ilits->spi_rx + offset + 1, n_rx);
} else {
input_err(true, ilits->dev, "%s spi read fail, status = %d\n", __func__, status);
}
break;
default:
input_info(true, ilits->dev, "%s Unknown command 0x%x\n", __func__, cmd);
break;
}
out:
ipio_kfree((void **)&xfer);
return status;
}
#else
int ili_spi_write_then_read_direct(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
int status = -1, duplex_len = 0;
int offset = 0;
u8 cmd;
struct spi_message message;
struct spi_transfer xfer;
if (ilits->power_status == POWER_OFF_STATUS) {
input_err(true, ilits->dev, "%s failed(power off state).\n", __func__);
return -1;
}
if (n_rx > SPI_RX_BUF_SIZE) {
input_err(true, ilits->dev, "%s Rx length is greater than spi local buf, abort\n", __func__);
status = -ENOMEM;
goto out;
}
spi_message_init(&message);
memset(&xfer, 0, sizeof(xfer));
if ((n_tx > 0) && (n_rx > 0))
cmd = SPI_READ;
else
cmd = SPI_WRITE;
switch (cmd) {
case SPI_WRITE:
xfer.len = n_tx;
xfer.tx_buf = txbuf;
spi_message_add_tail(&xfer, &message);
if (ilits->cs_gpio > 0) {
gpio_direction_output(ilits->cs_gpio, 0);
status = spi_sync(spi, &message);
gpio_direction_output(ilits->cs_gpio, 1);
} else {
status = spi_sync(spi, &message);
}
break;
case SPI_READ:
if (!atomic_read(&ilits->ice_stat))
offset = 2;
duplex_len = n_tx + n_rx + offset;
if ((duplex_len > SPI_TX_BUF_SIZE) ||
(duplex_len > SPI_RX_BUF_SIZE)) {
input_err(true, ilits->dev, "%s duplex_len is over than dma buf, abort\n", __func__);
status = -ENOMEM;
break;
}
memset(ilits->spi_tx, 0x0, SPI_TX_BUF_SIZE);
memset(ilits->spi_rx, 0x0, SPI_RX_BUF_SIZE);
xfer.len = duplex_len;
memcpy(ilits->spi_tx, txbuf, n_tx);
xfer.tx_buf = ilits->spi_tx;
xfer.rx_buf = ilits->spi_rx;
spi_message_add_tail(&xfer, &message);
if (ilits->cs_gpio > 0) {
gpio_direction_output(ilits->cs_gpio, 0);
status = spi_sync(spi, &message);
gpio_direction_output(ilits->cs_gpio, 1);
} else {
status = spi_sync(spi, &message);
}
if (status == 0) {
if (ilits->spi_rx[1] != SPI_ACK && !atomic_read(&ilits->ice_stat)) {
status = DO_SPI_RECOVER;
input_err(true, ilits->dev, "%s Do spi recovery: rxbuf[1] = 0x%x, ice = %d\n",
__func__, ilits->spi_rx[1], atomic_read(&ilits->ice_stat));
break;
}
memcpy((u8 *)rxbuf, ilits->spi_rx + offset + 1, n_rx);
} else {
input_err(true, ilits->dev, "%s spi read fail, status = %d\n", __func__, status);
}
break;
default:
input_info(true, ilits->dev, "%s Unknown command 0x%x\n", __func__, cmd);
break;
}
out:
return status;
}
#endif
static int ili_spi_mp_pre_cmd(u8 cdc)
{
u8 pre[5] = {0};
if (ilits->power_status == POWER_OFF_STATUS) {
input_err(true, ilits->dev, "%s failed(power off state).\n", __func__);
return -1;
}
if (!atomic_read(&ilits->mp_stat) || cdc != P5_X_SET_CDC_INIT ||
ilits->chip->core_ver >= CORE_VER_1430)
return 0;
ILI_DBG("%s mp test with pre commands\n", __func__);
pre[0] = SPI_WRITE;
pre[1] = 0x0;// dummy byte
pre[2] = 0x2;// Write len byte
pre[3] = P5_X_READ_DATA_CTRL;
pre[4] = P5_X_GET_CDC_DATA;
if (ilits->spi_write_then_read(ilits->spi, pre, 5, NULL, 0) < 0) {
input_err(true, ilits->dev, "%s Failed to write pre commands\n", __func__);
return -1;
}
pre[0] = SPI_WRITE;
pre[1] = 0x0;// dummy byte
pre[2] = 0x1;// Write len byte
pre[3] = P5_X_GET_CDC_DATA;
if (ilits->spi_write_then_read(ilits->spi, pre, 4, NULL, 0) < 0) {
input_err(true, ilits->dev, "%s Failed to write pre commands\n", __func__);
return -1;
}
return 0;
}
static int ili_spi_pll_clk_wakeup(void)
{
int index = 0;
u8 wdata[32] = {0};
u8 wakeup[9] = {0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3};
u32 wlen = sizeof(wakeup);
if (ilits->power_status == POWER_OFF_STATUS) {
input_err(true, ilits->dev, "%s failed(power off state).\n", __func__);
return -1;
}
wdata[0] = SPI_WRITE;
wdata[1] = wlen >> 8;
wdata[2] = wlen & 0xff;
index = 3;
wlen += index;
ipio_memcpy(&wdata[index], wakeup, wlen, wlen);
input_info(true, ilits->dev, "%s Write dummy to wake up spi pll clk\n", __func__);
if (ilits->spi_write_then_read(ilits->spi, wdata, wlen, NULL, 0) < 0) {
input_info(true, ilits->dev, "%s spi slave write error\n", __func__);
return -1;
}
return 0;
}
static int ili_spi_wrapper(u8 *txbuf, u32 wlen, u8 *rxbuf, u32 rlen, bool spi_irq, bool i2c_irq)
{
int ret = 0;
int mode = 0, index = 0;
u8 wdata[32] = {0};
u8 checksum = 0;
bool ice = atomic_read(&ilits->ice_stat);
if (ilits->power_status == POWER_OFF_STATUS) {
input_err(true, ilits->dev, "%s failed(power off state).\n", __func__);
return -1;
}
if (wlen > 0) {
if (!txbuf) {
input_err(true, ilits->dev, "%s txbuf is null\n", __func__);
return -ENOMEM;
}
/* 3 bytes data consist of length and header */
if ((wlen + 4) > sizeof(wdata)) {
input_err(true, ilits->dev, "%s WARNING! wlen(%d) > wdata(%d), using wdata length to transfer\n",
__func__, wlen, (int)sizeof(wdata));
return -ENOMEM;
}
}
if (rlen > 0) {
if (!rxbuf) {
input_err(true, ilits->dev, "%s rxbuf is null\n", __func__);
return -ENOMEM;
}
}
if (rlen > 0 && !wlen)
mode = SPI_READ;
else
mode = SPI_WRITE;
if (ilits->int_pulse)
ilits->detect_int_stat = ili_ic_check_int_pulse;
else
ilits->detect_int_stat = ili_ic_check_int_level;
if (spi_irq)
atomic_set(&ilits->cmd_int_check, ENABLE);
switch (mode) {
case SPI_WRITE:
#if (PLL_CLK_WAKEUP_TP_RESUME == ENABLE)
if (ilits->pll_clk_wakeup == true) {
#else
if ((ilits->pll_clk_wakeup == true) && (ilits->tp_suspend == true)) {
#endif
ret = ili_spi_pll_clk_wakeup();
if (ret < 0) {
input_err(true, ilits->dev, "%s Wakeup pll clk error\n", __func__);
break;
}
}
if (ice) {
wdata[0] = SPI_WRITE;
index = 1;
} else {
wdata[0] = SPI_WRITE;
wdata[1] = wlen >> 8;
wdata[2] = wlen & 0xff;
index = 3;
}
wlen += index;
ipio_memcpy(&wdata[index], txbuf, wlen, wlen);
/*
* NOTE: If TP driver is doing MP test and commanding 0xF1 to FW, we add a checksum
* to the last index and plus 1 with size.
*/
if (atomic_read(&ilits->mp_stat) && wdata[index] == P5_X_SET_CDC_INIT) {
checksum = ili_calc_packet_checksum(&wdata[index], wlen - index);
wdata[wlen] = checksum;
wlen++;
wdata[1] = (wlen - index) >> 8;
wdata[2] = (wlen - index) & 0xff;
ili_dump_data(wdata, 8, wlen, 0, "mp cdc cmd with checksum");
}
ret = ilits->spi_write_then_read(ilits->spi, wdata, wlen, txbuf, 0);
if (ret < 0) {
input_info(true, ilits->dev, "%s spi-wrapper write error\n", __func__);
break;
}
/* Won't break if it needs to read data following with writing. */
if (!rlen)
break;
case SPI_READ:
if (!ice && spi_irq) {
/* Check INT triggered by FW when sending cmds. */
if (ilits->detect_int_stat(false) < 0) {
input_err(true, ilits->dev, "%s ERROR! Check INT timeout\n", __func__);
ret = -ETIME;
break;
}
}
ret = ili_spi_mp_pre_cmd(wdata[3]);
if (ret < 0)
input_err(true, ilits->dev, "%s spi-wrapper mp pre cmd error\n", __func__);
wdata[0] = SPI_READ;
ret = ilits->spi_write_then_read(ilits->spi, wdata, 1, rxbuf, rlen);
if (ret < 0)
input_err(true, ilits->dev, "%s spi-wrapper read error\n", __func__);
break;
/*
* DEADCODE : prevent major issue.
* default:
* input_err(true, ilits->dev, "%s Unknown spi mode (%d)\n", __func__, mode);
* ret = -EINVAL;
* break;
*/
}
if (spi_irq)
atomic_set(&ilits->cmd_int_check, DISABLE);
return ret;
}
int ili_core_spi_setup(int num)
{
u32 freq[] = {
TP_SPI_CLK_1M,
TP_SPI_CLK_2M,
TP_SPI_CLK_3M,
TP_SPI_CLK_4M,
TP_SPI_CLK_5M,
TP_SPI_CLK_6M,
TP_SPI_CLK_7M,
TP_SPI_CLK_8M,
TP_SPI_CLK_9M,
TP_SPI_CLK_10M,
TP_SPI_CLK_11M,
TP_SPI_CLK_12M,
TP_SPI_CLK_13M,
TP_SPI_CLK_14M,
TP_SPI_CLK_15M
};
if (num >= (sizeof(freq) / sizeof(freq[0]))) {
input_err(true, ilits->dev, "%s Invaild clk freq, set default clk freq\n", __func__);
num = 7;
}
input_info(true, ilits->dev, "%s spi clock = %d\n", __func__, freq[num]);
ilits->spi->mode = ilits->spi_mode;
ilits->spi->bits_per_word = 8;
ilits->spi->max_speed_hz = freq[num];
if (spi_setup(ilits->spi) < 0) {
input_err(true, ilits->dev, "%s Failed to setup spi device\n", __func__);
return -ENODEV;
}
input_info(true, ilits->dev, "%s name = %s, bus_num = %d,cs = %d, mode = %d, speed = %d\n",
__func__, ilits->spi->modalias, ilits->spi->master->bus_num, ilits->spi->chip_select,
ilits->spi->mode, ilits->spi->max_speed_hz);
return 0;
}
static int ilitek_spi_probe(struct spi_device *spi)
{
struct touch_bus_info *info;
input_info(true, &spi->dev, "%s ilitek spi probe\n", __func__);
if (!spi) {
input_err(true, &spi->dev, "%s spi device is NULL\n", __func__);
return -ENODEV;
}
info = container_of(to_spi_driver(spi->dev.driver), struct touch_bus_info, bus_driver);
ilits = devm_kzalloc(&spi->dev, sizeof(struct ilitek_ts_data), GFP_KERNEL);
if (ERR_ALLOC_MEM(ilits)) {
input_err(true, &spi->dev, "%s Failed to allocate ts memory, %ld\n", __func__, PTR_ERR(ilits));
return -ENOMEM;
}
if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
input_err(true, &spi->dev, "%s Full duplex not supported by master\n", __func__);
return -EIO;
}
ilits->update_buf = kzalloc(MAX_HEX_FILE_SIZE, GFP_KERNEL | GFP_DMA);
if (ERR_ALLOC_MEM(ilits->update_buf)) {
input_err(true, &spi->dev, "%s fw kzalloc error\n", __func__);
return -ENOMEM;
}
/* Used for receiving touch data only, do not mix up with others. */
ilits->tr_buf = kzalloc(TR_BUF_SIZE, GFP_ATOMIC);
if (ERR_ALLOC_MEM(ilits->tr_buf)) {
input_err(true, &spi->dev, "%s failed to allocate touch report buffer\n", __func__);
return -ENOMEM;
}
ilits->spi_tx = kzalloc(SPI_TX_BUF_SIZE, GFP_KERNEL | GFP_DMA);
if (ERR_ALLOC_MEM(ilits->spi_tx)) {
input_err(true, &spi->dev, "%s Failed to allocate spi tx buffer\n", __func__);
return -ENOMEM;
}
ilits->spi_rx = kzalloc(SPI_RX_BUF_SIZE, GFP_KERNEL | GFP_DMA);
if (ERR_ALLOC_MEM(ilits->spi_rx)) {
input_err(true, &spi->dev, "%s Failed to allocate spi rx buffer\n", __func__);
return -ENOMEM;
}
ilits->gcoord = kzalloc(sizeof(struct gesture_coordinate), GFP_KERNEL);
if (ERR_ALLOC_MEM(ilits->gcoord)) {
input_err(true, &spi->dev, "%s Failed to allocate gresture coordinate buffer\n", __func__);
return -ENOMEM;
}
ilits->i2c = NULL;
ilits->spi = spi;
ilits->dev = &spi->dev;
ilits->hwif = info->hwif;
ilits->phys = "SPI";
ilits->wrapper = ili_spi_wrapper;
ilits->detect_int_stat = ili_ic_check_int_pulse;
ilits->int_pulse = true;
ilits->mp_retry = false;
ilits->tp_suspend = false;
ilits->power_status = POWER_ON_STATUS;
ilits->screen_off_sate = TP_RESUME;
#if SPI_DMA_TRANSFER_SPLIT
ilits->spi_write_then_read = ili_spi_write_then_read_split;
#else
ilits->spi_write_then_read = ili_spi_write_then_read_direct;
#endif
ilits->actual_tp_mode = P5_X_FW_AP_MODE;
ilits->tp_data_format = DATA_FORMAT_DEMO;
ilits->tp_data_len = P5_X_DEMO_MODE_PACKET_LEN;
#if AXIS_PACKET
ilits->tp_data_len = P5_X_DEMO_MODE_PACKET_INFO_LEN + P5_X_DEMO_MODE_PACKET_LEN +
P5_X_DEMO_MODE_AXIS_LEN + P5_X_DEMO_MODE_STATE_INFO;
#endif
ilits->tp_data_mode = AP_MODE;
if (TDDI_RST_BIND)
ilits->reset = TP_IC_WHOLE_RST;
else
ilits->reset = TP_HW_RST_ONLY;
ilits->rst_edge_delay = 11;
ilits->fw_open = REQUEST_FIRMWARE;
ilits->fw_upgrade_mode = UPGRADE_IRAM;
ilits->mp_move_code = ili_move_mp_code_iram;
ilits->gesture_move_code = ili_move_gesture_code_iram;
ilits->esd_recover = ili_wq_esd_spi_check;
ilits->ges_recover = ili_touch_esd_gesture_iram;
ilits->gesture_mode = DATA_FORMAT_GESTURE_INFO;
ilits->gesture_demo_ctrl = DISABLE;
ilits->wtd_ctrl = OFF;
ilits->report = ENABLE;
ilits->dnp = DISABLE;
ilits->irq_tirgger_type = IRQF_TRIGGER_FALLING;
ilits->info_from_hex = ENABLE;
ilits->wait_int_timeout = AP_INT_TIMEOUT;
ilits->prox_lp_scan_mode = false;
ilits->started_prox_intensity = false;
ilits->incell_power_state = false;
ilits->prox_face_mode = false;
ilits->dead_zone_enabled = true; //default true at fw
ilits->sip_mode_enabled = false;
ilits->prox_lp_scan_mode_enabled = false;
ilits->sleep_handler_mode = TP_RESUME;
#if ENABLE_GESTURE
ilits->gesture = DISABLE;
ilits->ges_sym.double_tap = DOUBLE_TAP;
ilits->ges_sym.alphabet_line_2_top = ALPHABET_LINE_2_TOP;
ilits->ges_sym.alphabet_line_2_bottom = ALPHABET_LINE_2_BOTTOM;
ilits->ges_sym.alphabet_line_2_left = ALPHABET_LINE_2_LEFT;
ilits->ges_sym.alphabet_line_2_right = ALPHABET_LINE_2_RIGHT;
ilits->ges_sym.alphabet_m = ALPHABET_M;
ilits->ges_sym.alphabet_w = ALPHABET_W;
ilits->ges_sym.alphabet_c = ALPHABET_C;
ilits->ges_sym.alphabet_E = ALPHABET_E;
ilits->ges_sym.alphabet_V = ALPHABET_V;
ilits->ges_sym.alphabet_O = ALPHABET_O;
ilits->ges_sym.alphabet_S = ALPHABET_S;
ilits->ges_sym.alphabet_Z = ALPHABET_Z;
ilits->ges_sym.alphabet_V_down = ALPHABET_V_DOWN;
ilits->ges_sym.alphabet_V_left = ALPHABET_V_LEFT;
ilits->ges_sym.alphabet_V_right = ALPHABET_V_RIGHT;
ilits->ges_sym.alphabet_two_line_2_bottom = ALPHABET_TWO_LINE_2_BOTTOM;
ilits->ges_sym.alphabet_F = ALPHABET_F;
ilits->ges_sym.alphabet_AT = ALPHABET_AT;
#endif
if (ili_core_spi_setup(SPI_CLK) < 0)
return -EINVAL;
return info->hwif->plat_probe();
}
static void ilitek_spi_shutdown(struct spi_device *spi)
{
input_info(true, ilits->dev, "%s\n", __func__);
ilits->hwif->plat_shutdown();
return;
}
static int ilitek_spi_remove(struct spi_device *spi)
{
input_info(true, ilits->dev, "%s\n", __func__);
return 0;
}
static struct spi_device_id tp_spi_id[] = {
{TDDI_DEV_ID, 0},
{},
};
int ili_interface_dev_init(struct ilitek_hwif_info *hwif)
{
struct touch_bus_info *info;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
printk(KERN_ERR "[sec_input] %s faied to allocate spi_driver\n", __func__);
return -ENOMEM;
}
if (hwif->bus_type != BUS_SPI) {
printk(KERN_ERR "[sec_input] %s Not SPI dev\n", __func__);
ipio_kfree((void **)&info);
return -EINVAL;
}
hwif->info = info;
info->bus_driver.driver.name = hwif->name;
info->bus_driver.driver.owner = hwif->owner;
info->bus_driver.driver.of_match_table = hwif->of_match_table;
info->bus_driver.driver.pm = hwif->pm;
info->bus_driver.probe = ilitek_spi_probe;
info->bus_driver.shutdown = ilitek_spi_shutdown;
info->bus_driver.remove = ilitek_spi_remove;
info->bus_driver.id_table = tp_spi_id;
info->hwif = hwif;
return spi_register_driver(&info->bus_driver);
}
void ili_interface_dev_exit(struct ilitek_ts_data *ts)
{
struct touch_bus_info *info = (struct touch_bus_info *)ilits->hwif->info;
input_info(true, ilits->dev, "%s remove spi dev\n", __func__);
kfree(ilits->update_buf);
kfree(ilits->spi_tx);
kfree(ilits->spi_rx);
// spi_unregister_driver(&info->bus_driver);
ipio_kfree((void **)&info);
}