1100 lines
24 KiB
C
1100 lines
24 KiB
C
|
/*
|
||
|
* 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 "ilitek.h"
|
||
|
|
||
|
struct touch_bus_info {
|
||
|
struct spi_driver bus_driver;
|
||
|
struct ilitek_hwif_info *hwif;
|
||
|
};
|
||
|
|
||
|
struct ilitek_tddi_dev *idev;
|
||
|
static u8 spi_ice_buf[1*K];
|
||
|
|
||
|
#if SPI_DMA_TRANSFER_SPLIT
|
||
|
#define DMA_TRANSFER_MAX_CHUNK 4 // number of chunks to be transferred.
|
||
|
#define DMA_TRANSFER_MAX_LEN 4096 // length of a chunk.
|
||
|
|
||
|
int ilitek_spi_write_then_read_split(struct spi_device *spi,
|
||
|
const void *txbuf, unsigned n_tx,
|
||
|
void *rxbuf, unsigned n_rx)
|
||
|
{
|
||
|
int status = -1;
|
||
|
int xfercnt = 0, xferlen = 0, xferloop = 0;
|
||
|
int duplex_len = 0;
|
||
|
u8 cmd = 0x0;
|
||
|
struct spi_message message;
|
||
|
struct spi_transfer xfer[DMA_TRANSFER_MAX_CHUNK];
|
||
|
|
||
|
|
||
|
spi_message_init(&message);
|
||
|
memset(xfer, 0, sizeof(xfer));
|
||
|
memset(idev->spi_tx, 0x0, SPI_TX_BUF_SIZE);
|
||
|
memset(idev->spi_rx, 0x0, SPI_RX_BUF_SIZE);
|
||
|
|
||
|
if (n_rx > SPI_RX_BUF_SIZE) {
|
||
|
ipio_err("Rx length is over than dma buf, abort\n");
|
||
|
status = -ENOMEM;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
if (xferloop > DMA_TRANSFER_MAX_CHUNK) {
|
||
|
ILI_ERR("xferloop = %d > %d\n", xferloop, DMA_TRANSFER_MAX_CHUNK);
|
||
|
status = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
xferlen = n_tx;
|
||
|
memcpy(idev->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 = idev->spi_tx + xfercnt * DMA_TRANSFER_MAX_LEN;
|
||
|
spi_message_add_tail(&xfer[xfercnt], &message);
|
||
|
xferlen = n_tx - (xfercnt+1) * DMA_TRANSFER_MAX_LEN;
|
||
|
}
|
||
|
status = spi_sync(spi, &message);
|
||
|
break;
|
||
|
case SPI_READ:
|
||
|
if (n_tx > DMA_TRANSFER_MAX_LEN) {
|
||
|
ipio_err("Tx length must be lower than transfer length (%d).\n", DMA_TRANSFER_MAX_LEN);
|
||
|
status = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
memcpy(idev->spi_tx, txbuf, n_tx);
|
||
|
|
||
|
duplex_len = n_tx + n_rx;
|
||
|
|
||
|
if (duplex_len % DMA_TRANSFER_MAX_LEN)
|
||
|
xferloop = (duplex_len / DMA_TRANSFER_MAX_LEN) + 1;
|
||
|
else
|
||
|
xferloop = duplex_len / DMA_TRANSFER_MAX_LEN;
|
||
|
|
||
|
if (xferloop > DMA_TRANSFER_MAX_CHUNK) {
|
||
|
ILI_ERR("xferloop = %d > %d\n", xferloop, DMA_TRANSFER_MAX_CHUNK);
|
||
|
status = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
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 = idev->spi_tx;
|
||
|
xfer[xfercnt].rx_buf = idev->spi_rx + xfercnt * DMA_TRANSFER_MAX_LEN;
|
||
|
spi_message_add_tail(&xfer[xfercnt], &message);
|
||
|
xferlen = duplex_len - (xfercnt + 1) * DMA_TRANSFER_MAX_LEN;
|
||
|
}
|
||
|
status = spi_sync(spi, &message);
|
||
|
if (status == 0)
|
||
|
memcpy((u8 *)rxbuf, &idev->spi_rx[1], n_rx);
|
||
|
break;
|
||
|
default:
|
||
|
ipio_info("Unknown command 0x%x\n", cmd);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (status != 0)
|
||
|
ipio_err("spi transfer failed\n");
|
||
|
return status;
|
||
|
}
|
||
|
#else
|
||
|
int ilitek_spi_write_then_read_direct(struct spi_device *spi,
|
||
|
const void *txbuf, unsigned n_tx,
|
||
|
void *rxbuf, unsigned n_rx)
|
||
|
{
|
||
|
int status = -1;
|
||
|
int duplex_len = 0;
|
||
|
u8 cmd;
|
||
|
struct spi_message message;
|
||
|
struct spi_transfer xfer;
|
||
|
|
||
|
if (n_rx > SPI_RX_BUF_SIZE) {
|
||
|
ipio_err("Rx length is over than dma buf, abort\n");
|
||
|
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);
|
||
|
status = spi_sync(spi, &message);
|
||
|
break;
|
||
|
case SPI_READ:
|
||
|
duplex_len = n_tx + n_rx;
|
||
|
if ((duplex_len > SPI_TX_BUF_SIZE) ||
|
||
|
(duplex_len > SPI_RX_BUF_SIZE)) {
|
||
|
ipio_err("duplex_len is over than dma buf, abort\n");
|
||
|
status = -ENOMEM;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
memset(idev->spi_tx, 0x0, SPI_TX_BUF_SIZE);
|
||
|
memset(idev->spi_rx, 0x0, SPI_RX_BUF_SIZE);
|
||
|
|
||
|
xfer.len = duplex_len;
|
||
|
memcpy(idev->spi_tx, txbuf, n_tx);
|
||
|
xfer.tx_buf = idev->spi_tx;
|
||
|
xfer.rx_buf = idev->spi_rx;
|
||
|
|
||
|
spi_message_add_tail(&xfer, &message);
|
||
|
status = spi_sync(spi, &message);
|
||
|
if (status != 0)
|
||
|
break;
|
||
|
|
||
|
memcpy((u8 *)rxbuf, &idev->spi_rx[1], n_rx);
|
||
|
break;
|
||
|
default:
|
||
|
ipio_info("Unknown command 0x%x\n", cmd);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (status != 0)
|
||
|
ipio_err("spi transfer failed\n");
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int core_rx_lock_check(int *ret_size)
|
||
|
{
|
||
|
int i, count = 1;
|
||
|
u8 txbuf[5] = {0};
|
||
|
u8 rxbuf[4] = {0};
|
||
|
u16 status = 0, lock = 0x5AA5;
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
txbuf[0] = SPI_WRITE;
|
||
|
txbuf[1] = 0x25;
|
||
|
txbuf[2] = 0x94;
|
||
|
txbuf[3] = 0x0;
|
||
|
txbuf[4] = 0x2;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 5, txbuf, 0) < 0) {
|
||
|
ipio_err("spi write (0x25,0x94,0x0,0x2) error\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
memset(txbuf, 0, sizeof(txbuf));
|
||
|
memset(rxbuf, 0, sizeof(rxbuf));
|
||
|
txbuf[0] = SPI_READ;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 1, rxbuf, 4) < 0) {
|
||
|
ipio_err("spi read error\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
status = (rxbuf[2] << 8) + rxbuf[3];
|
||
|
*ret_size = (rxbuf[0] << 8) + rxbuf[1];
|
||
|
|
||
|
ipio_debug("Rx lock = 0x%x, size = %d\n", status, *ret_size);
|
||
|
|
||
|
if (status == lock)
|
||
|
return 0;
|
||
|
|
||
|
mdelay(1);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
ipio_info("Rx buf is locked, 0x%x\n", status);
|
||
|
return SPI_IS_LOCKED;
|
||
|
}
|
||
|
|
||
|
static int core_tx_unlock_check(void)
|
||
|
{
|
||
|
int i, count = 100;
|
||
|
u8 txbuf[5] = {0};
|
||
|
u8 rxbuf[4] = {0};
|
||
|
u16 status = 0, unlock = 0x9881;
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
txbuf[0] = SPI_WRITE;
|
||
|
txbuf[1] = 0x25;
|
||
|
txbuf[2] = 0x0;
|
||
|
txbuf[3] = 0x0;
|
||
|
txbuf[4] = 0x2;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 5, txbuf, 0) < 0) {
|
||
|
ipio_err("spi write (0x25,0x0,0x0,0x2) error\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
memset(txbuf, 0, sizeof(txbuf));
|
||
|
memset(rxbuf, 0, sizeof(rxbuf));
|
||
|
txbuf[0] = SPI_READ;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 1, rxbuf, 4) < 0) {
|
||
|
ipio_err("spi read error\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
status = (rxbuf[2] << 8) + rxbuf[3];
|
||
|
|
||
|
ipio_debug("Tx unlock = 0x%x\n", status);
|
||
|
|
||
|
if (status == unlock)
|
||
|
return 0;
|
||
|
|
||
|
mdelay(1);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
ipio_err("Tx buf is locked, 0x%x\n", status);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_unlock_read(u8 *data, int size)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 txbuf[64] = { 0 };
|
||
|
|
||
|
/* set read address */
|
||
|
txbuf[0] = SPI_WRITE;
|
||
|
txbuf[1] = 0x25;
|
||
|
txbuf[2] = 0x98;
|
||
|
txbuf[3] = 0x0;
|
||
|
txbuf[4] = 0x2;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 5, txbuf, 0) < 0) {
|
||
|
ipio_info("spi write (0x25,0x98,0x00,0x2) error\n");
|
||
|
ret = -EIO;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* read data */
|
||
|
memset(txbuf, 0, sizeof(txbuf));
|
||
|
txbuf[0] = SPI_READ;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 1, data, size) < 0) {
|
||
|
ret = -EIO;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* write data unlock */
|
||
|
txbuf[0] = SPI_WRITE;
|
||
|
txbuf[1] = 0x25;
|
||
|
txbuf[2] = 0x94;
|
||
|
txbuf[3] = 0x0;
|
||
|
txbuf[4] = 0x2;
|
||
|
txbuf[5] = (size & 0xFF00) >> 8;
|
||
|
txbuf[6] = size & 0xFF;
|
||
|
txbuf[7] = (char)0x98;
|
||
|
txbuf[8] = (char)0x81;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 9, txbuf, 0) < 0) {
|
||
|
ipio_err("spi write unlock (0x9881) error, ret = %d\n", ret);
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_lock_write(u8 *data, int size)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int safe_size = size;
|
||
|
u8 check_sum = 0, wsize = 0;
|
||
|
|
||
|
if ((size + 5) >= sizeof(spi_ice_buf)) {
|
||
|
ipio_err("Size(%d) is greater than spi_ice_buf, abort\n", size + 5);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
memset(spi_ice_buf, 0x0, sizeof(spi_ice_buf));
|
||
|
|
||
|
/* Write data */
|
||
|
spi_ice_buf[0] = SPI_WRITE;
|
||
|
spi_ice_buf[1] = 0x25;
|
||
|
spi_ice_buf[2] = 0x4;
|
||
|
spi_ice_buf[3] = 0x0;
|
||
|
spi_ice_buf[4] = 0x2;
|
||
|
|
||
|
/* Calcuate checsum and fill it in the last byte */
|
||
|
check_sum = ilitek_calc_packet_checksum(data, size);
|
||
|
ipio_memcpy(spi_ice_buf + 5, data, size, safe_size + 4);
|
||
|
spi_ice_buf[5 + size] = check_sum;
|
||
|
size++;
|
||
|
wsize = size;
|
||
|
if (wsize % 4 != 0)
|
||
|
wsize += 4 - (wsize % 4);
|
||
|
|
||
|
if (idev->spi_write_then_read(idev->spi, spi_ice_buf, wsize + 5, spi_ice_buf, 0) < 0) {
|
||
|
ipio_info("spi write (0x25,0x4,0x00,0x2) error\n");
|
||
|
ret = -EIO;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* write data lock */
|
||
|
spi_ice_buf[0] = SPI_WRITE;
|
||
|
spi_ice_buf[1] = 0x25;
|
||
|
spi_ice_buf[2] = 0x0;
|
||
|
spi_ice_buf[3] = 0x0;
|
||
|
spi_ice_buf[4] = 0x2;
|
||
|
spi_ice_buf[5] = (size & 0xFF00) >> 8;
|
||
|
spi_ice_buf[6] = size & 0xFF;
|
||
|
spi_ice_buf[7] = (char)0x5A;
|
||
|
spi_ice_buf[8] = (char)0xA5;
|
||
|
if (idev->spi_write_then_read(idev->spi, spi_ice_buf, 9, spi_ice_buf, 0) < 0) {
|
||
|
ipio_err("spi write lock (0x5AA5) error, ret = %d\n", ret);
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int ilitek_spi_check_ack(void)
|
||
|
{
|
||
|
u8 ack = 0;
|
||
|
u8 write = SPI_WRITE;
|
||
|
|
||
|
if (idev->spi_write_then_read(idev->spi, &write, 1, &ack, 1) < 0) {
|
||
|
ipio_err("spi write 0x82 error\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
ipio_debug("spi ack = %x\n", ack);
|
||
|
return ack;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_disable(void)
|
||
|
{
|
||
|
int ack, retry = 3;
|
||
|
u8 ex_ice[5] = {0x82, 0x1B, 0x62, 0x10, 0x18};
|
||
|
|
||
|
while (retry > 0) {
|
||
|
if (idev->spi_write_then_read(idev->spi, ex_ice, sizeof(ex_ice), NULL, 0) < 0) {
|
||
|
ipio_err("spi write ice mode disable failed\n");
|
||
|
retry--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!idev->fix_ice)
|
||
|
break;
|
||
|
|
||
|
ack = idev->spi_ack();
|
||
|
if (ack == SPI_ACK) {
|
||
|
break;
|
||
|
} else if (ack == SPI_WRITE) {
|
||
|
ipio_err("SPI ACK error (0x%x)\n", ack);
|
||
|
return DO_SPI_RECOVER;
|
||
|
} else {
|
||
|
retry--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (retry <= 0) {
|
||
|
ipio_err("Failed to exit ice mode with spi comm\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_enable(void)
|
||
|
{
|
||
|
int retry = 3, ret = 0;
|
||
|
u8 en_ice[5] = {0x82, 0x1F, 0x62, 0x10, 0x18};
|
||
|
u8 pid_cmd[5] = {0};
|
||
|
u32 pid = 0;
|
||
|
|
||
|
while (retry > 0) {
|
||
|
if (idev->spi_write_then_read(idev->spi, en_ice, sizeof(en_ice), NULL, 0) < 0) {
|
||
|
ipio_err("write ice mode cmd error\n");
|
||
|
retry--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!idev->fix_ice)
|
||
|
break;
|
||
|
|
||
|
pid_cmd[0] = SPI_WRITE;
|
||
|
pid_cmd[1] = 0x25;
|
||
|
pid_cmd[2] = ((idev->chip->pid_addr & 0x000000FF) >> 0);
|
||
|
pid_cmd[3] = ((idev->chip->pid_addr & 0x0000FF00) >> 8);
|
||
|
pid_cmd[4] = ((idev->chip->pid_addr & 0x00FF0000) >> 16);
|
||
|
|
||
|
if (idev->spi_write_then_read(idev->spi, pid_cmd, sizeof(pid_cmd), NULL, 0) < 0) {
|
||
|
ipio_err("write pid cmd error\n");
|
||
|
retry--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
pid_cmd[0] = SPI_READ;
|
||
|
if (idev->spi_write_then_read(idev->spi, &pid_cmd[0], sizeof(u8), &pid, sizeof(pid)) < 0) {
|
||
|
ipio_err("write pid cmd error\n");
|
||
|
retry--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ipio_debug("check pid = 0x%x\n", pid);
|
||
|
|
||
|
if (pid == idev->chip->pid)
|
||
|
break;
|
||
|
|
||
|
retry--;
|
||
|
}
|
||
|
|
||
|
if (retry <= 0) {
|
||
|
ipio_err("Failed to enter ice mode with spi comm\n");
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int core_spi_slave_mode_write(u8 *data, int size)
|
||
|
{
|
||
|
int ret = 0, index = 0;
|
||
|
int safe_size = size, wsize = 0;
|
||
|
u8 *txbuf = NULL;
|
||
|
|
||
|
txbuf = kcalloc(size + 9, sizeof(u8), GFP_KERNEL);
|
||
|
if (ERR_ALLOC_MEM(txbuf)) {
|
||
|
ipio_err("Failed to allocate txbuf\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* Write data */
|
||
|
txbuf[index++] = SPI_WRITE;
|
||
|
|
||
|
ipio_memcpy(txbuf + index, data, size, safe_size + 9);
|
||
|
|
||
|
wsize = size + index;
|
||
|
if (wsize % 4 != 0)
|
||
|
wsize += 4 - (wsize % 4);
|
||
|
|
||
|
ret = idev->spi_write_then_read(idev->spi, txbuf, wsize, txbuf, 0);
|
||
|
if (ret < 0)
|
||
|
ipio_info("spi slave write error\n");
|
||
|
|
||
|
out:
|
||
|
ipio_kfree((void **)&txbuf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_write(u8 *data, int len)
|
||
|
{
|
||
|
int ack = 0, ret = 0;
|
||
|
|
||
|
ack = idev->spi_ack();
|
||
|
if (ack != SPI_ACK) {
|
||
|
ipio_err("SPI ACK error (0x%x)\n", ack);
|
||
|
return DO_SPI_RECOVER;
|
||
|
}
|
||
|
|
||
|
ret = core_spi_ice_mode_enable();
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
/* send data and change lock status to 0x5AA5. */
|
||
|
ret = core_spi_ice_mode_lock_write(data, len);
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
/*
|
||
|
* Check FW if they already received the data we sent.
|
||
|
* They change lock status from 0x5AA5 to 0x9881 if they did.
|
||
|
*/
|
||
|
ret = core_tx_unlock_check();
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
out:
|
||
|
if (core_spi_ice_mode_disable() < 0)
|
||
|
ret = -EIO;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int core_spi_slave_mode_read(u8 *data, int len)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 txbuf = 0 ;
|
||
|
u8 *buf = NULL;
|
||
|
u8 shift = 2;
|
||
|
u32 slave_len = 0;
|
||
|
|
||
|
slave_len = len + shift;
|
||
|
|
||
|
buf = kcalloc(slave_len, sizeof(u8), GFP_KERNEL);
|
||
|
if (ERR_ALLOC_MEM(buf)) {
|
||
|
ipio_err("Failed to allocate buf mem\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* read data */
|
||
|
txbuf = SPI_READ;
|
||
|
ret = idev->spi_write_then_read(idev->spi, &txbuf, 1, buf, slave_len);
|
||
|
if (ret < 0) {
|
||
|
ipio_err("SPI read error in slave mode\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (buf[0] != SPI_ACK) {
|
||
|
ipio_err("SPI ACK error (0x%x)\n", buf[0]);
|
||
|
ret = DO_SPI_RECOVER;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ipio_memcpy(data, (buf + shift), len, len);
|
||
|
|
||
|
out:
|
||
|
ipio_kfree((void **)&buf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int core_spi_ice_mode_read(u8 *data, int len)
|
||
|
{
|
||
|
int size = 0, ret = 0, ack = 0;
|
||
|
|
||
|
ack = idev->spi_ack();
|
||
|
if (ack != SPI_ACK) {
|
||
|
ipio_err("SPI ACK error (0x%x)\n", ack);
|
||
|
return DO_SPI_RECOVER;
|
||
|
}
|
||
|
|
||
|
ret = core_spi_ice_mode_enable();
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
/*
|
||
|
* Check FW if they already send their data to rxbuf.
|
||
|
* They change lock status from 0x9881 to 0x5AA5 if they did.
|
||
|
*/
|
||
|
ret = core_rx_lock_check(&size);
|
||
|
if (ret < 0 || ret == SPI_IS_LOCKED)
|
||
|
goto out;
|
||
|
|
||
|
if (len < size && (!idev->fw_uart_en && !idev->gesture_demo_ctrl)) {
|
||
|
ipio_info("WARRING! size(%d) > len(%d), use len to get data\n", size, len);
|
||
|
size = len;
|
||
|
}
|
||
|
|
||
|
/* receive data from rxbuf and change lock status to 0x9881. */
|
||
|
ret = core_spi_ice_mode_unlock_read(data, size);
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
out:
|
||
|
if (core_spi_ice_mode_disable() < 0)
|
||
|
ret = -EIO;
|
||
|
|
||
|
if (ret == SPI_IS_LOCKED)
|
||
|
return ret;
|
||
|
|
||
|
return (ret >= 0) ? size : ret;
|
||
|
}
|
||
|
|
||
|
static int ilitek_spi_pll_clk_wakeup(u8 cmd)
|
||
|
{
|
||
|
u8 wakeup[11] = {0x82, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3};
|
||
|
|
||
|
ipio_debug("write dummy to wake up spi pll clk\n");
|
||
|
if (idev->spi_write_then_read(idev->spi, wakeup, sizeof(wakeup), NULL, 0) < 0) {
|
||
|
ipio_err("spi write wake up cmd failed\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int core_spi_write(u8 *data, int len)
|
||
|
{
|
||
|
int ret = 0, retry = 5;
|
||
|
int safe_size = len;
|
||
|
int (*core_spi_mode_write)(u8 *data, int len);
|
||
|
|
||
|
if (!atomic_read(&idev->ice_stat)) {
|
||
|
if (idev->spi_mode == SPI_ICE_MODE)
|
||
|
core_spi_mode_write = core_spi_ice_mode_write;
|
||
|
else
|
||
|
core_spi_mode_write = core_spi_slave_mode_write;
|
||
|
|
||
|
do {
|
||
|
ret = core_spi_mode_write(data, len);
|
||
|
if (ret >= 0)
|
||
|
break;
|
||
|
} while (--retry > 0);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if ((len + 1) >= sizeof(spi_ice_buf)) {
|
||
|
ipio_err("Size(%d) is greater than spi_ice_buf, abort\n", len + 1);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
memset(spi_ice_buf, 0x0, sizeof(spi_ice_buf));
|
||
|
|
||
|
spi_ice_buf[0] = SPI_WRITE;
|
||
|
ipio_memcpy(spi_ice_buf + 1, data, len, safe_size + 1);
|
||
|
|
||
|
if (idev->spi_write_then_read(idev->spi, spi_ice_buf, len + 1, spi_ice_buf, 0) < 0) {
|
||
|
ipio_err("spi write data error in ice mode\n");
|
||
|
ret = -EIO;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int core_spi_read(u8 *rxbuf, int len)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u8 txbuf[1] = {0};
|
||
|
int (*core_spi_mode_read)(u8 *rxbuf, int len);
|
||
|
|
||
|
if (atomic_read(&idev->ice_stat) == DISABLE) {
|
||
|
if (idev->spi_mode == SPI_ICE_MODE)
|
||
|
core_spi_mode_read = core_spi_ice_mode_read;
|
||
|
else
|
||
|
core_spi_mode_read = core_spi_slave_mode_read;
|
||
|
|
||
|
ret = core_spi_mode_read(rxbuf, len);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
txbuf[0] = SPI_READ;
|
||
|
if (idev->spi_write_then_read(idev->spi, txbuf, 1, rxbuf, len) < 0) {
|
||
|
ipio_err("spi read data error in ice mode\n");
|
||
|
ret = -EIO;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int ilitek_spi_write(void *buf, int len)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!len) {
|
||
|
ipio_err("spi write len is invaild\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = core_spi_write(buf, len);
|
||
|
if (ret < 0) {
|
||
|
if (atomic_read(&idev->tp_reset) == START) {
|
||
|
ret = 0;
|
||
|
goto out;
|
||
|
}
|
||
|
ipio_err("spi write error, ret = %d\n", ret);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* If ilitek_spi_read success ,this format will return read length */
|
||
|
static int ilitek_spi_read(void *buf, int len)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!len) {
|
||
|
ipio_err("spi read len is invaild\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = core_spi_read(buf, len);
|
||
|
if (ret < 0) {
|
||
|
if (atomic_read(&idev->tp_reset) == START) {
|
||
|
ret = 0;
|
||
|
goto out;
|
||
|
}
|
||
|
ipio_err("spi read error, ret = %d\n", ret);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int ilitek_spi_slave_wrapper(u8 *txbuf, u32 wlen, u8 *rxbuf, u32 rlen, bool slave_int_check, bool int_check)
|
||
|
{
|
||
|
u8 temp[2];
|
||
|
int ret = 0, operate = -1;
|
||
|
bool ice = atomic_read(&idev->ice_stat);
|
||
|
|
||
|
ipio_debug("slave mode read write\n");
|
||
|
|
||
|
if (wlen > 0) {
|
||
|
if (ERR_ALLOC_MEM(txbuf)) {
|
||
|
ipio_err("txbuf is null\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rlen > 0) {
|
||
|
if (ERR_ALLOC_MEM(rxbuf)) {
|
||
|
ipio_err("rxbuf is null\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (wlen > 0 && rlen > 0)
|
||
|
operate = RW_SYNC;
|
||
|
else if (wlen > 0)
|
||
|
operate = W_ONLY;
|
||
|
else
|
||
|
operate = R_ONLY;
|
||
|
|
||
|
ipio_debug("wlen: %d, rlen: %d, slave_int_check: %d, int_check: %d\n", wlen, rlen, slave_int_check, int_check);
|
||
|
|
||
|
idev->detect_int_stat = ilitek_tddi_ic_check_int_stat;
|
||
|
|
||
|
switch (operate) {
|
||
|
case RW_SYNC:
|
||
|
case W_ONLY:
|
||
|
|
||
|
#if (PLL_CLK_WAKEUP_TP_RESUME == ENABLE)
|
||
|
if(idev->pll_clk_wakeup == true) {
|
||
|
#else
|
||
|
if ((idev->pll_clk_wakeup == true) && idev->tp_suspend) {
|
||
|
#endif
|
||
|
ret = idev->pll_clk_wakeup_func(txbuf[0]);
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* 1. 0xf6 wlen */
|
||
|
if (!ice) {
|
||
|
temp[0] = P5_X_READ_DATA_CTRL;
|
||
|
temp[1] = wlen;
|
||
|
|
||
|
ipio_debug("cmd = 0x%x, len = %d\n", txbuf[0], rlen);
|
||
|
if (idev->write(temp, sizeof(temp)) < 0) {
|
||
|
ipio_err("write pre cmd fail\n");
|
||
|
ret = -1;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
SPI_MODE_DELAY;
|
||
|
}
|
||
|
|
||
|
if (slave_int_check)
|
||
|
atomic_set(&idev->cmd_int_check, ENABLE);
|
||
|
|
||
|
ilitek_dump_data(txbuf, 8, wlen, 0, "dump CMD W");
|
||
|
|
||
|
/* 2. write */
|
||
|
ret = idev->write(txbuf, wlen);
|
||
|
if (ret < 0) {
|
||
|
ipio_err("wrapper write error\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* 3. wait INT */
|
||
|
if (slave_int_check) {
|
||
|
if (idev->detect_int_stat() < 0) {
|
||
|
ipio_err("ERROR! Check INT timeout\n");
|
||
|
ret = -ETIME;
|
||
|
goto out;
|
||
|
}
|
||
|
} else {
|
||
|
msleep(1);
|
||
|
}
|
||
|
|
||
|
if (operate == W_ONLY)
|
||
|
break;
|
||
|
|
||
|
case R_ONLY:
|
||
|
/* 4. Read */
|
||
|
ret = idev->read(rxbuf, rlen);
|
||
|
if (ret < 0) {
|
||
|
ipio_err("wrapper read error, ret = %d\n", ret);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ret = rlen;
|
||
|
ilitek_dump_data(rxbuf, 8, rlen, 0, "dump CMD R");
|
||
|
|
||
|
break;
|
||
|
default:
|
||
|
ipio_err("Unknown operation\n");
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (slave_int_check)
|
||
|
atomic_set(&idev->cmd_int_check, DISABLE);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int 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)) {
|
||
|
ipio_err("Invaild clk freq, set default clk freq\n");
|
||
|
num = 7;
|
||
|
}
|
||
|
|
||
|
ipio_info("spi clock = %d\n", freq[num]);
|
||
|
|
||
|
idev->spi->mode = SPI_MODE_0;
|
||
|
idev->spi->bits_per_word = 8;
|
||
|
idev->spi->max_speed_hz = freq[num];
|
||
|
|
||
|
if (spi_setup(idev->spi) < 0) {
|
||
|
ipio_err("Failed to setup spi device\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
ipio_info("name = %s, bus_num = %d,cs = %d, mode = %d, speed = %d\n",
|
||
|
idev->spi->modalias,
|
||
|
idev->spi->master->bus_num,
|
||
|
idev->spi->chip_select,
|
||
|
idev->spi->mode,
|
||
|
idev->spi->max_speed_hz);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ilitek_spi_probe(struct spi_device *spi)
|
||
|
{
|
||
|
struct touch_bus_info *info =
|
||
|
container_of(to_spi_driver(spi->dev.driver),
|
||
|
struct touch_bus_info, bus_driver);
|
||
|
|
||
|
ipio_info("ilitek spi probe\n");
|
||
|
|
||
|
tpd_gpio_mode_set();
|
||
|
|
||
|
if (!spi) {
|
||
|
ipio_err("spi device is NULL\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
|
||
|
ipio_err("Full duplex not supported by master\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
idev = devm_kzalloc(&spi->dev, sizeof(struct ilitek_tddi_dev), GFP_KERNEL);
|
||
|
if (ERR_ALLOC_MEM(idev)) {
|
||
|
ipio_err("Failed to allocate idev memory, %ld\n", PTR_ERR(idev));
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
idev->update_buf = kzalloc(MAX_HEX_FILE_SIZE, GFP_KERNEL | GFP_DMA);
|
||
|
if (ERR_ALLOC_MEM(idev->update_buf)) {
|
||
|
ipio_err("Failed to allocate update_buf\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* Used for receiving touch data only, do not mix up with others. */
|
||
|
idev->tr_buf = kzalloc(TR_BUF_SIZE, GFP_ATOMIC);
|
||
|
if (ERR_ALLOC_MEM(idev->tr_buf)) {
|
||
|
ipio_err("failed to allocate touch report buffer\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
idev->spi_tx = kzalloc(SPI_TX_BUF_SIZE, GFP_KERNEL | GFP_DMA);
|
||
|
if (ERR_ALLOC_MEM(idev->spi_tx)) {
|
||
|
ipio_err("Failed to allocate spi tx buffer\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
idev->spi_rx = kzalloc(SPI_RX_BUF_SIZE, GFP_KERNEL | GFP_DMA);
|
||
|
if (ERR_ALLOC_MEM(idev->spi_rx)) {
|
||
|
ipio_err("Failed to allocate spi rx buffer\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
idev->gcoord = kzalloc(sizeof(struct gesture_coordinate), GFP_KERNEL);
|
||
|
if (ERR_ALLOC_MEM(idev->gcoord)) {
|
||
|
ipio_err("Failed to allocate gresture coordinate buffer\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
idev->i2c = NULL;
|
||
|
idev->spi = spi;
|
||
|
idev->dev = &spi->dev;
|
||
|
idev->hwif = info->hwif;
|
||
|
idev->phys = "SPI";
|
||
|
|
||
|
idev->write = ilitek_spi_write;
|
||
|
idev->read = ilitek_spi_read;
|
||
|
idev->spi_mode = SPI_MODE;
|
||
|
idev->wrapper = ilitek_wrapper;
|
||
|
idev->pll_clk_wakeup_func = ilitek_spi_pll_clk_wakeup;
|
||
|
idev->pll_clk_wakeup = true;
|
||
|
#if SPI_DMA_TRANSFER_SPLIT
|
||
|
idev->spi_write_then_read = ilitek_spi_write_then_read_split;
|
||
|
#else
|
||
|
idev->spi_write_then_read = ilitek_spi_write_then_read_direct;
|
||
|
#endif
|
||
|
|
||
|
idev->spi_speed = ilitek_tddi_ic_spi_speed_ctrl;
|
||
|
idev->spi_ack = ilitek_spi_check_ack;
|
||
|
idev->actual_tp_mode = P5_X_FW_AP_MODE;
|
||
|
idev->tp_data_len = P5_X_DEMO_MODE_PACKET_LEN;
|
||
|
idev->tp_data_mode = AP_MODE;
|
||
|
|
||
|
if (TDDI_RST_BIND)
|
||
|
idev->reset = TP_IC_WHOLE_RST;
|
||
|
else
|
||
|
idev->reset = TP_HW_RST_ONLY;
|
||
|
|
||
|
idev->rst_edge_delay = 5;
|
||
|
idev->fw_open = FILP_OPEN;
|
||
|
idev->fw_upgrade_mode = UPGRADE_IRAM;
|
||
|
idev->mp_move_code = ilitek_tddi_move_mp_code_iram;
|
||
|
idev->gesture_move_code = ilitek_tddi_move_gesture_code_iram;
|
||
|
idev->esd_recover = ilitek_tddi_wq_esd_spi_check;
|
||
|
idev->ges_recover = ilitek_tddi_touch_esd_gesture_iram;
|
||
|
idev->gesture_mode = DATA_FORMAT_GESTURE_INFO;
|
||
|
idev->gesture_demo_ctrl = DISABLE;
|
||
|
idev->wtd_ctrl = ON;
|
||
|
idev->report = ENABLE;
|
||
|
idev->netlink = DISABLE;
|
||
|
idev->dnp = DISABLE;
|
||
|
idev->mp_retry = DISABLE;
|
||
|
idev->irq_tirgger_type = IRQF_TRIGGER_FALLING;
|
||
|
idev->info_from_hex = ENABLE;
|
||
|
idev->wait_int_timeout = AP_INT_TIMEOUT;
|
||
|
|
||
|
#if ENABLE_GESTURE
|
||
|
idev->gesture = DISABLE;
|
||
|
idev->ges_sym.double_tap = DOUBLE_TAP;
|
||
|
idev->ges_sym.alphabet_line_2_top = ALPHABET_LINE_2_TOP;
|
||
|
idev->ges_sym.alphabet_line_2_bottom = ALPHABET_LINE_2_BOTTOM;
|
||
|
idev->ges_sym.alphabet_line_2_left = ALPHABET_LINE_2_LEFT;
|
||
|
idev->ges_sym.alphabet_line_2_right = ALPHABET_LINE_2_RIGHT;
|
||
|
idev->ges_sym.alphabet_m = ALPHABET_M;
|
||
|
idev->ges_sym.alphabet_w = ALPHABET_W;
|
||
|
idev->ges_sym.alphabet_c = ALPHABET_C;
|
||
|
idev->ges_sym.alphabet_E = ALPHABET_E;
|
||
|
idev->ges_sym.alphabet_V = ALPHABET_V;
|
||
|
idev->ges_sym.alphabet_O = ALPHABET_O;
|
||
|
idev->ges_sym.alphabet_S = ALPHABET_S;
|
||
|
idev->ges_sym.alphabet_Z = ALPHABET_Z;
|
||
|
idev->ges_sym.alphabet_V_down = ALPHABET_V_DOWN;
|
||
|
idev->ges_sym.alphabet_V_left = ALPHABET_V_LEFT;
|
||
|
idev->ges_sym.alphabet_V_right = ALPHABET_V_RIGHT;
|
||
|
idev->ges_sym.alphabet_two_line_2_bottom = ALPHABET_TWO_LINE_2_BOTTOM;
|
||
|
idev->ges_sym.alphabet_F = ALPHABET_F;
|
||
|
idev->ges_sym.alphabet_AT = ALPHABET_AT;
|
||
|
#endif
|
||
|
|
||
|
if (core_spi_setup(SPI_CLK) < 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
return info->hwif->plat_probe();
|
||
|
}
|
||
|
|
||
|
static int ilitek_spi_remove(struct spi_device *spi)
|
||
|
{
|
||
|
ipio_info();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct spi_device_id tp_spi_id[] = {
|
||
|
{TDDI_DEV_ID, 0},
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
int ilitek_tddi_interface_dev_init(struct ilitek_hwif_info *hwif)
|
||
|
{
|
||
|
struct touch_bus_info *info;
|
||
|
|
||
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||
|
if (!info) {
|
||
|
ipio_err("faied to allocate spi_driver\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
if (hwif->bus_type != BUS_SPI) {
|
||
|
ipio_err("Not SPI dev\n");
|
||
|
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.remove = ilitek_spi_remove;
|
||
|
info->bus_driver.id_table = tp_spi_id;
|
||
|
|
||
|
info->hwif = hwif;
|
||
|
return spi_register_driver(&info->bus_driver);
|
||
|
}
|
||
|
|
||
|
void ilitek_tddi_interface_dev_exit(struct ilitek_tddi_dev *idev)
|
||
|
{
|
||
|
struct touch_bus_info *info = (struct touch_bus_info *)idev->hwif->info;
|
||
|
|
||
|
ipio_info("remove spi dev\n");
|
||
|
kfree(idev->update_buf);
|
||
|
kfree(idev->spi_tx);
|
||
|
kfree(idev->spi_rx);
|
||
|
spi_unregister_driver(&info->bus_driver);
|
||
|
ipio_kfree((void **)&info);
|
||
|
}
|