| /* drivers/input/touchscreen/sec_ts.c |
| * |
| * Copyright (C) 2011 Samsung Electronics Co., Ltd. |
| * http://www.samsungsemi.com/ |
| * |
| * Core file for Samsung TSC driver |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| struct sec_ts_data *tsp_info; |
| |
| #include "sec_ts.h" |
| |
| /* Switch GPIO values */ |
| #define SEC_SWITCH_GPIO_VALUE_SLPI_MASTER 1 |
| #define SEC_SWITCH_GPIO_VALUE_AP_MASTER 0 |
| |
| struct sec_ts_data *ts_dup; |
| |
| #ifndef CONFIG_SEC_SYSFS |
| /* Declare extern sec_class */ |
| struct class *sec_class; |
| #endif |
| |
| #ifdef USE_POWER_RESET_WORK |
| static void sec_ts_reset_work(struct work_struct *work); |
| #endif |
| static void sec_ts_fw_update_work(struct work_struct *work); |
| static void sec_ts_suspend_work(struct work_struct *work); |
| static void sec_ts_resume_work(struct work_struct *work); |
| static void sec_ts_charger_work(struct work_struct *work); |
| |
| #ifdef USE_OPEN_CLOSE |
| static int sec_ts_input_open(struct input_dev *dev); |
| static void sec_ts_input_close(struct input_dev *dev); |
| #endif |
| |
| int sec_ts_read_information(struct sec_ts_data *ts); |
| |
| #ifndef I2C_INTERFACE |
| int sec_ts_spi_delay(u8 reg) |
| { |
| switch (reg) { |
| case SEC_TS_READ_TOUCH_RAWDATA: |
| return 400; |
| case SEC_TS_CMD_HEATMAP_READ: |
| return 500; |
| case SEC_TS_READ_ALL_EVENT: |
| return 500; |
| case SEC_TS_READ_CSRAM_RTDP_DATA: |
| return 500; |
| case SEC_TS_CAAT_READ_STORED_DATA: |
| return 500; |
| case SEC_TS_CMD_FLASH_READ_DATA: |
| return 1800; |
| case SEC_TS_READ_FIRMWARE_INTEGRITY: |
| return 20*1000; |
| case SEC_TS_READ_SELFTEST_RESULT: |
| return 3500; |
| default: return 100; |
| } |
| } |
| |
| int sec_ts_spi_post_delay(u8 reg) |
| { |
| switch (reg) { |
| case SEC_TS_READ_TOUCH_RAWDATA: |
| case SEC_TS_CMD_FLASH_READ_DATA: |
| case SEC_TS_READ_SELFTEST_RESULT: |
| return 500; |
| default: return 0; |
| } |
| } |
| #endif |
| |
| int sec_ts_write(struct sec_ts_data *ts, u8 reg, u8 *data, int len) |
| { |
| u8 *buf; |
| int ret; |
| unsigned char retry; |
| #ifdef I2C_INTERFACE |
| struct i2c_msg msg; |
| #else |
| struct spi_message msg; |
| struct spi_transfer transfer[1] = { { 0 } }; |
| unsigned int i; |
| unsigned int spi_len = 0; |
| unsigned char checksum = 0x0; |
| #endif |
| |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF\n", __func__); |
| goto err; |
| } |
| |
| #ifdef I2C_INTERFACE |
| if (len + 1 > sizeof(ts->io_write_buf)) { |
| input_err(true, &ts->client->dev, |
| "%s: len is larger than buffer size\n", __func__); |
| return -EINVAL; |
| } |
| #else |
| /* add 3 zero stuffing tx bytes at last */ |
| if (SEC_TS_SPI_HEADER_SIZE + 1 + len + SEC_TS_SPI_CHECKSUM_SIZE + 3 > |
| sizeof(ts->io_write_buf)) { |
| input_err(true, &ts->client->dev, |
| "%s: len is larger than buffer size\n", __func__); |
| return -EINVAL; |
| } |
| #endif |
| |
| mutex_lock(&ts->io_mutex); |
| |
| buf = ts->io_write_buf; |
| #ifdef I2C_INTERFACE |
| buf[0] = reg; |
| memcpy(buf + 1, data, len); |
| |
| msg.addr = ts->client->addr; |
| msg.flags = 0; |
| msg.len = len + 1; |
| msg.buf = buf; |
| #else |
| |
| buf[0] = SEC_TS_SPI_SYNC_CODE; |
| buf[1] = ((len + 1) >> 8) & 0xFF; |
| buf[2] = (len + 1) & 0xFF; |
| buf[3] = 0x00; |
| buf[4] = 0x00; |
| buf[5] = reg; |
| memcpy(buf + SEC_TS_SPI_HEADER_SIZE + 1, data, len); |
| |
| spi_len = SEC_TS_SPI_HEADER_SIZE + 1 + len; |
| // spi_len = SPI header size(5)+register(1)+data size(len) |
| for (i = 0; i < spi_len ; i++) |
| checksum += buf[i]; |
| buf[spi_len++] = checksum; |
| // spi_len += checksum(1) |
| |
| spi_message_init(&msg); |
| |
| /* add 3 zero stuffing tx bytes at last */ |
| memset(ts->io_write_buf + spi_len, 0x00, 3); |
| /* spi transfer size should be multiple of 4 |
| **/ |
| spi_len = (spi_len + 3) & ~3; |
| transfer[0].len = spi_len; |
| transfer[0].tx_buf = buf; |
| transfer[0].rx_buf = NULL; |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| #ifdef SEC_TS_DEBUG_IO |
| input_info(true, &ts->client->dev, "%s: ", __func__); |
| // for (i = 0; i < SEC_TS_SPI_HEADER_SIZE + 1 + len + 1; i++) |
| for (i = 0; i < 8; i++) |
| input_info(true, &ts->client->dev, "%X ", buf[i]); |
| input_info(true, &ts->client->dev, "\n"); |
| #endif |
| |
| #endif |
| |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| #ifdef I2C_INTERFACE |
| if ((ret = i2c_transfer(ts->client->adapter, &msg, 1)) == 1) |
| break; |
| #else |
| if ((ret = spi_sync(ts->client, &msg)) == 0) |
| break; |
| #endif |
| |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| |
| mutex_unlock(&ts->io_mutex); |
| |
| if (retry == SEC_TS_IO_RETRY_CNT) { |
| input_err(true, &ts->client->dev, |
| "%s: write over retry limit\n", __func__); |
| ret = -EIO; |
| #ifdef USE_POR_AFTER_I2C_RETRY |
| if (ts->probe_done && !ts->reset_is_on_going) |
| schedule_delayed_work(&ts->reset_work, |
| msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); |
| #endif |
| } |
| |
| #ifdef I2C_INTERFACE |
| if (ret == 1) |
| #else |
| if (ret == 0) |
| #endif |
| return 0; |
| err: |
| return -EIO; |
| } |
| |
| static int sec_ts_read_internal(struct sec_ts_data *ts, u8 reg, |
| u8 *data, int len, bool dma_safe) |
| { |
| u8 *buf; |
| int ret; |
| unsigned char retry; |
| #ifdef I2C_INTERFASCE |
| struct i2c_msg msg[2]; |
| #else |
| struct spi_message msg; |
| struct spi_transfer transfer[1] = { { 0 } }; |
| unsigned int i; |
| unsigned int spi_write_len = 0, spi_read_len = 0; |
| unsigned char write_checksum = 0x0, read_checksum = 0x0; |
| int copy_size = 0, copy_cur = 0; |
| #endif |
| int remain = len; |
| |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF\n", __func__); |
| goto err; |
| } |
| |
| #ifndef I2C_INTERFACE |
| /* add 3 zero stuffing tx bytes at last */ |
| if (SEC_TS_SPI_HEADER_SIZE + 1 + SEC_TS_SPI_CHECKSUM_SIZE + 3 > |
| sizeof(ts->io_write_buf)) { |
| input_err(true, &ts->client->dev, |
| "%s: len is larger than buffer size\n", __func__); |
| return -EINVAL; |
| } |
| #endif |
| |
| if (len > sizeof(ts->io_read_buf) && dma_safe == false) { |
| input_err(true, &ts->client->dev, |
| "%s: len %d over pre-allocated size %d\n", |
| __func__, len, IO_PREALLOC_READ_BUF_SZ); |
| return -ENOSPC; |
| } |
| |
| mutex_lock(&ts->io_mutex); |
| |
| buf = ts->io_write_buf; |
| #ifdef I2C_INTERFACE |
| buf[0] = reg; |
| |
| msg[0].addr = ts->client->addr; |
| msg[0].flags = 0; |
| msg[0].len = 1; |
| msg[0].buf = buf; |
| |
| msg[1].addr = ts->client->addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].len = len; |
| if (dma_safe == false) |
| msg[1].buf = ts->io_read_buf; |
| else |
| msg[1].buf = data; |
| #else |
| |
| buf[0] = SEC_TS_SPI_SYNC_CODE; |
| buf[1] = 0x00; |
| buf[2] = 0x01; |
| buf[3] = (len >> 8) & 0xFF; |
| buf[4] = len & 0xFF; |
| buf[5] = reg; |
| |
| spi_write_len = SEC_TS_SPI_HEADER_SIZE + 1; |
| for (i = 0; i < spi_write_len; i++) |
| write_checksum += buf[i]; |
| buf[spi_write_len] = write_checksum; |
| spi_write_len += SEC_TS_SPI_CHECKSUM_SIZE; |
| /* add 3 zero stuffing tx bytes at last */ |
| memset(ts->io_write_buf + spi_write_len, 0x00, 3); |
| spi_write_len = (spi_write_len + 3) & ~3; |
| |
| spi_read_len = len + |
| SEC_TS_SPI_READ_HEADER_SIZE + SEC_TS_SPI_CHECKSUM_SIZE; |
| spi_read_len = (spi_read_len + 3) & ~3; |
| #endif |
| if (len <= ts->io_burstmax) { |
| #ifdef I2C_INTERFACE |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| ret = i2c_transfer(ts->client->adapter, msg, 2); |
| if (ret == 2) |
| break; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| if (ret == 2 && dma_safe == false) |
| memcpy(data, |
| ts->io_read_buf[SEC_TS_SPI_READ_HEADER_SIZE], |
| len); |
| #else |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| spi_message_init(&msg); |
| // spi transfer size should be multiple of 4 |
| transfer[0].len = spi_write_len; |
| transfer[0].tx_buf = buf; |
| transfer[0].rx_buf = NULL; |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| ret = spi_sync(ts->client, &msg); |
| #ifdef SEC_TS_DEBUG_IO |
| input_info(true, &ts->client->dev, |
| "%s: spi write buf %X %X %X %X %X %X %X\n", |
| __func__, buf[0], buf[1], buf[2], |
| buf[3], buf[4], buf[5], buf[6]); |
| #endif |
| // write fail |
| if (ret != 0) { |
| ret = -EIO; |
| |
| input_err(true, &ts->client->dev, |
| "%s: spi write retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == |
| SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry == SEC_TS_IO_RETRY_CNT - 1) { |
| input_err(true, &ts->client->dev, |
| "%s: write reg retry over retry limit, skip read\n", |
| __func__); |
| goto skip_spi_read; |
| } |
| |
| continue; |
| } |
| |
| usleep_range(sec_ts_spi_delay(reg), |
| sec_ts_spi_delay(reg) + 1); |
| |
| // read sequence start |
| spi_message_init(&msg); |
| transfer[0].len = spi_read_len; |
| transfer[0].tx_buf = NULL; |
| transfer[0].rx_buf = ts->io_read_buf; |
| spi_message_add_tail(&transfer[0], &msg); |
| ret = spi_sync(ts->client, &msg); |
| |
| for (i = 0, read_checksum = 0x0; |
| i < (SEC_TS_SPI_READ_HEADER_SIZE + len); |
| i++) |
| read_checksum += ts->io_read_buf[i]; |
| |
| #ifdef SEC_TS_DEBUG_IO |
| input_info(true, &ts->client->dev, "%s: ", __func__); |
| // for (i = 0; i < spi_read_len; i++) |
| for (i = 0; i < 8; i++) |
| input_info(true, &ts->client->dev, |
| "%X ", |
| ts->io_read_buf[i]); |
| input_info(true, &ts->client->dev, |
| "\n%s: checksum = %X", |
| __func__, read_checksum); |
| #endif |
| // read fail |
| if (ret != 0 || |
| ts->io_read_buf[0] != SEC_TS_SPI_SYNC_CODE || |
| reg != ts->io_read_buf[5] || |
| // ts->io_read_buf[6] != SEC_TS_SPI_CMD_OK || |
| read_checksum != |
| ts->io_read_buf[ |
| SEC_TS_SPI_READ_HEADER_SIZE + |
| len]) { |
| |
| ret = -EIO; |
| |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == |
| SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| continue; |
| } else |
| break; |
| } |
| if (ret == 0) |
| memcpy(data, ts->io_read_buf + |
| SEC_TS_SPI_READ_HEADER_SIZE, len); |
| |
| usleep_range(sec_ts_spi_post_delay(reg), |
| sec_ts_spi_post_delay(reg) + 1); |
| |
| #endif //I2C_INTERFACE |
| } else { |
| /* |
| * read buffer is 256 byte. do not support long buffer over |
| * than 256. So, try to separate reading data about 256 bytes. |
| **/ |
| #ifdef I2C_INTERFACE |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| ret = i2c_transfer(ts->client->adapter, msg, 1); |
| if (ret == 1) |
| break; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| |
| do { |
| if (remain > ts->io_burstmax) |
| msg[1].len = ts->io_burstmax; |
| else |
| msg[1].len = remain; |
| |
| remain -= ts->io_burstmax; |
| |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| ret = i2c_transfer(ts->client->adapter, |
| &msg[1], 1); |
| if (ret == 1) |
| break; |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == |
| SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| |
| msg[1].buf += msg[1].len; |
| |
| } while (remain > 0); |
| |
| if (ret == 1 && dma_safe == false) |
| memcpy(data, ts->io_read_buf, len); |
| #else |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| spi_message_init(&msg); |
| // spi transfer size should be multiple of 4 |
| transfer[0].len = spi_write_len; |
| transfer[0].tx_buf = buf; |
| transfer[0].rx_buf = NULL; |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| ret = spi_sync(ts->client, &msg); |
| |
| // write fail |
| if (ret != 0) { |
| ret = -EIO; |
| |
| input_err(true, &ts->client->dev, |
| "%s: spi write retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| |
| if (ts->power_status == |
| SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry == SEC_TS_IO_RETRY_CNT - 1) { |
| input_err(true, &ts->client->dev, |
| "%s: write reg retry over retry limit, skip read\n", |
| __func__); |
| goto skip_spi_read; |
| } |
| |
| continue; |
| } |
| |
| usleep_range(sec_ts_spi_delay(reg), |
| sec_ts_spi_delay(reg) + 1); |
| |
| copy_size = 0; |
| remain = spi_read_len; |
| do { |
| if (remain > ts->io_burstmax) |
| copy_cur = ts->io_burstmax; |
| else |
| copy_cur = remain; |
| |
| spi_message_init(&msg); |
| |
| transfer[0].len = copy_cur; |
| transfer[0].tx_buf = NULL; |
| transfer[0].rx_buf = |
| &ts->io_read_buf[copy_size]; |
| // CS needs to stay low until read seq. is done |
| transfer[0].cs_change = |
| (remain > ts->io_burstmax) ? 1 : 0; |
| |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| copy_size += copy_cur; |
| remain -= copy_cur; |
| |
| ret = spi_sync(ts->client, &msg); |
| #ifdef SEC_TS_DEBUG_IO |
| input_info(true, &ts->client->dev, |
| "%s: ", __func__); |
| for (i = 0; i < 8; i++) |
| input_info(true, |
| &ts->client->dev, "%X ", |
| ts->io_read_buf[i]); |
| input_info(true, &ts->client->dev, |
| "\n%s: checksum = %X", |
| __func__, read_checksum); |
| #endif |
| |
| if (ret != 0) { |
| ret = -EIO; |
| |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status |
| == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, |
| &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| break; |
| } |
| } while (remain > 0); |
| if (ret != 0) { // read fail, retry |
| ret = -EIO; |
| continue; |
| } |
| |
| for (i = 0, read_checksum = 0x0; |
| i < SEC_TS_SPI_READ_HEADER_SIZE + len; i++) |
| read_checksum += ts->io_read_buf[i]; |
| //read success |
| if (ts->io_read_buf[0] == SEC_TS_SPI_SYNC_CODE && |
| // ts->io_read_buf[6] == SEC_TS_SPI_CMD_OK && |
| reg == ts->io_read_buf[5] && |
| read_checksum == |
| ts->io_read_buf[SEC_TS_SPI_READ_HEADER_SIZE + |
| len]) |
| break; |
| //read data fail |
| else if (ts->io_read_buf[6] |
| == SEC_TS_SPI_CMD_UNKNOWN || |
| ts->io_read_buf[6] |
| == SEC_TS_SPI_CMD_BAD_PARAM) { |
| input_info(true, &ts->client->dev, |
| "%s: CMD_NG cmd(M) = %X, cmd(S) = %X, cmd_result = %X\n", |
| __func__, reg, ts->io_read_buf[5], |
| ts->io_read_buf[6]); |
| ret = -EIO; |
| continue; |
| } else { |
| input_info(true, &ts->client->dev, |
| "%s: spi fail, ret %d, sync code %X, reg(M) %X, reg(S) %X, cmd_result %X, chksum(M) %X, chksum(S) %X\n", |
| __func__, ret, ts->io_read_buf[0], |
| reg, ts->io_read_buf[5], |
| ts->io_read_buf[6], read_checksum, |
| ts->io_read_buf[ |
| SEC_TS_SPI_READ_HEADER_SIZE + |
| len]); |
| ret = -EIO; |
| continue; |
| } |
| } |
| if (ret == 0) |
| memcpy(data, ts->io_read_buf + |
| SEC_TS_SPI_READ_HEADER_SIZE, len); |
| |
| usleep_range(sec_ts_spi_post_delay(reg), |
| sec_ts_spi_post_delay(reg) + 1); |
| #endif |
| } |
| skip_spi_read: |
| mutex_unlock(&ts->io_mutex); |
| |
| if (retry == SEC_TS_IO_RETRY_CNT) { |
| input_err(true, &ts->client->dev, |
| "%s: read reg(%#x) over retry limit, comm_err_count %d, io_err_count %d\n", |
| __func__, reg, ts->comm_err_count, ts->io_err_count); |
| ret = -EIO; |
| ts->io_err_count++; |
| #ifdef USE_POR_AFTER_I2C_RETRY |
| if (ts->probe_done && !ts->reset_is_on_going) |
| schedule_delayed_work(&ts->reset_work, |
| msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); |
| #endif |
| |
| } else |
| ts->io_err_count = 0; |
| |
| /* do hw reset if continuously failed over SEC_TS_IO_RESET_CNT times */ |
| if (ts->io_err_count >= SEC_TS_IO_RESET_CNT) { |
| ts->io_err_count = 0; |
| sec_ts_hw_reset(ts); |
| } |
| |
| return ret; |
| |
| err: |
| return -EIO; |
| } |
| |
| static int sec_ts_write_burst_internal(struct sec_ts_data *ts, |
| u8 *data, int len, bool dma_safe) |
| { |
| int ret; |
| int retry; |
| #ifndef I2C_INTERFACE |
| struct spi_message msg; |
| struct spi_transfer transfer[1] = { { 0 } }; |
| unsigned int i; |
| unsigned int spi_len = 0; |
| unsigned char checksum = 0x0; |
| #endif |
| |
| #ifdef I2C_INTERFACE |
| if (len > sizeof(ts->io_write_buf) && dma_safe == false) { |
| input_err(true, &ts->client->dev, |
| "%s: len %d over pre-allocated size %d\n", |
| __func__, len, sizeof(ts->io_write_buf)); |
| return -ENOSPC; |
| } |
| #else |
| /* add 3 zero stuffing tx bytes at last */ |
| if (SEC_TS_SPI_HEADER_SIZE + len + SEC_TS_SPI_CHECKSUM_SIZE + 3 > |
| sizeof(ts->io_write_buf)) { |
| input_err(true, &ts->client->dev, |
| "%s: len is larger than buffer size\n", __func__); |
| return -EINVAL; |
| } |
| #endif |
| |
| mutex_lock(&ts->io_mutex); |
| #ifdef I2C_INTERFACE |
| if (dma_safe == false) { |
| memcpy(ts->io_write_buf, data, len); |
| data = ts->io_write_buf; |
| } |
| #else |
| |
| ts->io_write_buf[0] = SEC_TS_SPI_SYNC_CODE; |
| ts->io_write_buf[1] = (len >> 8) & 0xFF; |
| ts->io_write_buf[2] = len & 0xFF; |
| ts->io_write_buf[3] = 0x0; |
| ts->io_write_buf[4] = 0x0; |
| |
| memcpy(ts->io_write_buf + SEC_TS_SPI_HEADER_SIZE, data, len); |
| |
| |
| spi_len = SEC_TS_SPI_HEADER_SIZE + len; |
| for (i = 0; i < spi_len; i++) |
| checksum += ts->io_write_buf[i]; |
| |
| ts->io_write_buf[spi_len] = checksum; |
| spi_len += SEC_TS_SPI_CHECKSUM_SIZE; |
| |
| spi_message_init(&msg); |
| |
| /* add 3 zero stuffing tx bytes at last */ |
| memset(ts->io_write_buf + spi_len, 0x00, 3); |
| spi_len = (spi_len + 3) & ~3; |
| transfer[0].len = spi_len; |
| transfer[0].tx_buf = ts->io_write_buf; |
| transfer[0].rx_buf = NULL; |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| #ifdef SEC_TS_DEBUG_IO |
| input_info(true, &ts->client->dev, "%s:\n", __func__); |
| for (i = 0; i < spi_len; i++) |
| input_info(true, &ts->client->dev, "%X ", ts->io_write_buf[i]); |
| input_info(true, &ts->client->dev, "\n"); |
| #endif |
| |
| #endif |
| |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| #ifdef I2C_INTERFACE |
| if ((ret = i2c_master_send(ts->client, data, len)) == len) |
| break; |
| #else |
| if ((ret = spi_sync(ts->client, &msg)) == 0) |
| break; |
| #endif |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| |
| mutex_unlock(&ts->io_mutex); |
| if (retry == SEC_TS_IO_RETRY_CNT) { |
| input_err(true, &ts->client->dev, |
| "%s: write over retry limit\n", __func__); |
| ret = -EIO; |
| } |
| |
| return ret; |
| } |
| |
| static int sec_ts_read_bulk_internal(struct sec_ts_data *ts, |
| u8 *data, int len, bool dma_safe) |
| { |
| int ret; |
| unsigned char retry; |
| int remain = len; |
| #ifdef I2C_INTERFACE |
| struct i2c_msg msg; |
| #else |
| struct spi_message msg; |
| struct spi_transfer transfer[1] = { { 0 } }; |
| unsigned int i; |
| unsigned int spi_len = 0; |
| unsigned char checksum = 0x0; |
| int copy_size = 0, copy_cur = 0; |
| int retry_msg = 0; |
| #endif |
| |
| if (len > sizeof(ts->io_read_buf) && dma_safe == false) { |
| input_err(true, &ts->client->dev, |
| "%s: len %d over pre-allocated size %d\n", __func__, |
| len, sizeof(ts->io_read_buf)); |
| return -ENOSPC; |
| } |
| |
| mutex_lock(&ts->io_mutex); |
| |
| #ifdef I2C_INTERFACE |
| msg.addr = ts->client->addr; |
| msg.flags = I2C_M_RD; |
| msg.len = len; |
| if (dma_safe == false) |
| msg.buf = ts->io_read_buf; |
| else |
| msg.buf = data; |
| |
| do { |
| if (remain > ts->io_burstmax) |
| msg.len = ts->io_burstmax; |
| else |
| msg.len = remain; |
| |
| remain -= ts->io_burstmax; |
| |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| ret = i2c_transfer(ts->client->adapter, &msg, 1); |
| if (ret == 1) |
| break; |
| usleep_range(1 * 1000, 1 * 1000); |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", |
| __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| |
| if (retry == SEC_TS_IO_RETRY_CNT) { |
| input_err(true, &ts->client->dev, |
| "%s: read over retry limit\n", __func__); |
| ret = -EIO; |
| break; |
| } |
| |
| msg.buf += msg.len; |
| |
| } while (remain > 0); |
| |
| if (ret == 1 && dma_safe == false) |
| memcpy(data, ts->io_read_buf, len); |
| #else |
| retry_message: |
| remain = spi_len = (SEC_TS_SPI_READ_HEADER_SIZE + len + |
| SEC_TS_SPI_CHECKSUM_SIZE + 3) & ~3; |
| do { |
| if (remain > ts->io_burstmax) |
| copy_cur = ts->io_burstmax; |
| else |
| copy_cur = remain; |
| |
| spi_message_init(&msg); |
| transfer[0].len = copy_cur; |
| transfer[0].tx_buf = NULL; |
| transfer[0].rx_buf = &ts->io_read_buf[copy_size]; |
| /* CS needs to stay low until read seq. is done |
| */ |
| transfer[0].cs_change = (remain > ts->io_burstmax) ? 1 : 0; |
| |
| spi_message_add_tail(&transfer[0], &msg); |
| |
| copy_size += copy_cur; |
| remain -= copy_cur; |
| |
| for (retry = 0; retry < SEC_TS_IO_RETRY_CNT; retry++) { |
| ret = spi_sync(ts->client, &msg); |
| if (ret == 0) |
| break; |
| |
| usleep_range(1 * 1000, 1 * 1000); |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: POWER_STATUS : OFF, retry:%d\n", |
| __func__, retry); |
| mutex_unlock(&ts->io_mutex); |
| goto err; |
| } |
| |
| if (retry > 1) { |
| input_err(true, &ts->client->dev, |
| "%s: retry %d\n", __func__, retry + 1); |
| ts->comm_err_count++; |
| } |
| } |
| } while (remain > 0); |
| |
| for (i = 0, checksum = 0; i < SEC_TS_SPI_READ_HEADER_SIZE + len; i++) |
| checksum += ts->io_read_buf[i]; |
| |
| if (ret == 0 && ts->io_read_buf[0] == SEC_TS_SPI_SYNC_CODE && |
| checksum == ts->io_read_buf[SEC_TS_SPI_READ_HEADER_SIZE + len]) |
| memcpy(data, ts->io_read_buf + SEC_TS_SPI_READ_HEADER_SIZE, |
| len); |
| else { |
| input_info(true, &ts->client->dev, |
| "%s: spi fail, ret %d, sync code %X, reg(S) %X, chksum(M) %X, chksum(S) %X\n", |
| __func__, ret, ts->io_read_buf[0], ts->io_read_buf[5], |
| checksum, |
| ts->io_read_buf[SEC_TS_SPI_READ_HEADER_SIZE + len]); |
| if (retry_msg++ < SEC_TS_IO_RETRY_CNT) |
| goto retry_message; |
| } |
| #endif |
| mutex_unlock(&ts->io_mutex); |
| |
| #ifdef I2C_INTERFACE |
| if (ret == 1) |
| #else |
| if (ret == 0) |
| #endif |
| return 0; |
| err: |
| return -EIO; |
| } |
| |
| /* Wrapper API for read and write */ |
| int sec_ts_read(struct sec_ts_data *ts, u8 reg, u8 *data, int len) |
| { |
| return sec_ts_read_internal(ts, reg, data, len, false); |
| } |
| |
| int sec_ts_read_heap(struct sec_ts_data *ts, u8 reg, u8 *data, int len) |
| { |
| return sec_ts_read_internal(ts, reg, data, len, true); |
| } |
| |
| int sec_ts_write_burst(struct sec_ts_data *ts, u8 *data, int len) |
| { |
| return sec_ts_write_burst_internal(ts, data, len, false); |
| } |
| |
| int sec_ts_write_burst_heap(struct sec_ts_data *ts, u8 *data, int len) |
| { |
| return sec_ts_write_burst_internal(ts, data, len, true); |
| } |
| |
| int sec_ts_read_bulk(struct sec_ts_data *ts, u8 *data, int len) |
| { |
| return sec_ts_read_bulk_internal(ts, data, len, false); |
| } |
| |
| int sec_ts_read_bulk_heap(struct sec_ts_data *ts, u8 *data, int len) |
| { |
| return sec_ts_read_bulk_internal(ts, data, len, true); |
| } |
| |
| static int sec_ts_read_from_customlib(struct sec_ts_data *ts, u8 *data, int len) |
| { |
| int ret; |
| |
| ret = sec_ts_write(ts, SEC_TS_CMD_CUSTOMLIB_READ_PARAM, data, 2); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: fail to read custom library command\n", __func__); |
| |
| ret = sec_ts_read(ts, SEC_TS_CMD_CUSTOMLIB_READ_PARAM, (u8 *)data, len); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: fail to read custom library command\n", __func__); |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_TOUCHSCREEN_DUMP_MODE) |
| #include <linux/sec_debug.h> |
| extern struct tsp_dump_callbacks dump_callbacks; |
| static struct delayed_work *p_ghost_check; |
| |
| static void sec_ts_check_rawdata(struct work_struct *work) |
| { |
| struct sec_ts_data *ts = container_of(work, struct sec_ts_data, |
| ghost_check.work); |
| |
| if (ts->tsp_dump_lock == 1) { |
| input_err(true, &ts->client->dev, |
| "%s: ignored ## already checking..\n", __func__); |
| return; |
| } |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: ignored ## IC is power off\n", __func__); |
| return; |
| } |
| |
| ts->tsp_dump_lock = 1; |
| input_info(true, &ts->client->dev, "%s: start ##\n", __func__); |
| sec_ts_run_rawdata_all((void *)ts, false); |
| msleep(100); |
| |
| input_info(true, &ts->client->dev, "%s: done ##\n", __func__); |
| ts->tsp_dump_lock = 0; |
| |
| } |
| |
| static void dump_tsp_log(void) |
| { |
| pr_info("%s: %s %s: start\n", SEC_TS_NAME, SECLOG, __func__); |
| |
| #ifdef CONFIG_BATTERY_SAMSUNG |
| if (lpcharge == 1) { |
| pr_err("%s: %s %s: ignored ## lpm charging Mode!!\n", |
| SEC_TS_NAME, SECLOG, __func__); |
| return; |
| } |
| #endif |
| |
| if (p_ghost_check == NULL) { |
| pr_err("%s: %s %s: ignored ## tsp probe fail!!\n", |
| SEC_TS_NAME, SECLOG, __func__); |
| return; |
| } |
| schedule_delayed_work(p_ghost_check, msecs_to_jiffies(100)); |
| } |
| #endif |
| |
| |
| void sec_ts_delay(unsigned int ms) |
| { |
| if (ms < 20) |
| usleep_range(ms * 1000, ms * 1000); |
| else |
| msleep(ms); |
| } |
| |
| int sec_ts_wait_for_ready(struct sec_ts_data *ts, unsigned int ack) |
| { |
| return sec_ts_wait_for_ready_with_count(ts, ack, |
| SEC_TS_WAIT_RETRY_CNT); |
| } |
| |
| int sec_ts_wait_for_ready_with_count(struct sec_ts_data *ts, unsigned int ack, |
| unsigned int count) |
| { |
| int rc = -1; |
| int retry = 0; |
| u8 tBuff[SEC_TS_EVENT_BUFF_SIZE] = {0,}; |
| |
| while (retry < count) { |
| if (sec_ts_read(ts, SEC_TS_READ_ONE_EVENT, tBuff, |
| SEC_TS_EVENT_BUFF_SIZE) >= 0) { |
| if (((tBuff[0] >> 2) & 0xF) == TYPE_STATUS_EVENT_INFO) { |
| if (tBuff[1] == ack) { |
| rc = 0; |
| break; |
| } |
| } else if (((tBuff[0] >> 2) & 0xF) == |
| TYPE_STATUS_EVENT_VENDOR_INFO) { |
| if (tBuff[1] == ack) { |
| rc = 0; |
| break; |
| } |
| } |
| } |
| sec_ts_delay(20); |
| retry++; |
| } |
| if (retry == count) |
| input_err(true, &ts->client->dev, "%s: Time Over\n", |
| __func__); |
| |
| input_info(true, &ts->client->dev, |
| "%s: %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X [%d]\n", |
| __func__, tBuff[0], tBuff[1], tBuff[2], tBuff[3], |
| tBuff[4], tBuff[5], tBuff[6], tBuff[7], retry); |
| |
| return rc; |
| } |
| |
| int sec_ts_read_calibration_report(struct sec_ts_data *ts) |
| { |
| int ret; |
| |
| memset(ts->cali_report, 0, sizeof(ts->cali_report)); |
| ret = sec_ts_read(ts, SEC_TS_READ_CALIBRATION_REPORT, |
| ts->cali_report, sizeof(ts->cali_report)); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: failed to read, %d\n", __func__, ret); |
| return ret; |
| } |
| |
| input_info(true, &ts->client->dev, |
| "%s: count:%d, pass count:%d, fail count:%d, status:%X, param version:%X %X %X %X\n", |
| __func__, ts->cali_report_try_cnt, ts->cali_report_pass_cnt, |
| ts->cali_report_fail_cnt, ts->cali_report_status, |
| ts->cali_report_param_ver[0], ts->cali_report_param_ver[1], |
| ts->cali_report_param_ver[2], ts->cali_report_param_ver[3]); |
| |
| return ts->cali_report_status; |
| } |
| |
| static void sec_ts_reinit(struct sec_ts_data *ts) |
| { |
| u8 w_data[2] = {0x00, 0x00}; |
| int ret = 0; |
| |
| input_info(true, &ts->client->dev, |
| "%s : charger=0x%x, Cover=0x%x, Power mode=0x%x\n", |
| __func__, ts->charger_mode, ts->touch_functions, |
| ts->lowpower_status); |
| |
| /* charger mode */ |
| if (ts->charger_mode != SEC_TS_BIT_CHARGER_MODE_NO) { |
| w_data[0] = ts->charger_mode; |
| ret = ts->sec_ts_write(ts, SET_TS_CMD_SET_CHARGER_MODE, |
| (u8 *)&w_data[0], 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command(0x%x)", |
| __func__, SET_TS_CMD_SET_CHARGER_MODE); |
| } |
| |
| /* Cover mode */ |
| if (ts->touch_functions & SEC_TS_BIT_SETFUNC_COVER) { |
| w_data[0] = ts->cover_cmd; |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_COVERTYPE, |
| (u8 *)&w_data[0], 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command(0x%x)", |
| __func__, SEC_TS_CMD_SET_COVERTYPE); |
| |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_TOUCHFUNCTION, |
| (u8 *)&(ts->touch_functions), 2); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command(0x%x)", |
| __func__, SEC_TS_CMD_SET_TOUCHFUNCTION); |
| } |
| |
| #ifdef SEC_TS_SUPPORT_CUSTOMLIB |
| if (ts->use_customlib) |
| sec_ts_set_custom_library(ts); |
| #endif |
| |
| /* Power mode */ |
| if (ts->lowpower_status == TO_LOWPOWER_MODE) { |
| w_data[0] = (ts->lowpower_mode & |
| SEC_TS_MODE_LOWPOWER_FLAG) >> 1; |
| ret = sec_ts_write(ts, SEC_TS_CMD_WAKEUP_GESTURE_MODE, |
| (u8 *)&w_data[0], 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command(0x%x)", |
| __func__, SEC_TS_CMD_WAKEUP_GESTURE_MODE); |
| |
| w_data[0] = TO_LOWPOWER_MODE; |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_POWER_MODE, |
| (u8 *)&w_data[0], 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command(0x%x)", |
| __func__, SEC_TS_CMD_SET_POWER_MODE); |
| |
| sec_ts_delay(50); |
| |
| if (ts->lowpower_mode & SEC_TS_MODE_CUSTOMLIB_AOD) { |
| int i, ret; |
| u8 data[10] = {0x02, 0}; |
| |
| for (i = 0; i < 4; i++) { |
| data[i * 2 + 2] = ts->rect_data[i] & 0xFF; |
| data[i * 2 + 3] = |
| (ts->rect_data[i] >> 8) & 0xFF; |
| } |
| |
| ret = ts->sec_ts_write(ts, |
| SEC_TS_CMD_CUSTOMLIB_WRITE_PARAM, |
| &data[0], 10); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to write offset\n", |
| __func__); |
| |
| ret = ts->sec_ts_write(ts, |
| SEC_TS_CMD_CUSTOMLIB_NOTIFY_PACKET, NULL, 0); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send notify\n", |
| __func__); |
| |
| } |
| |
| } else { |
| |
| sec_ts_set_grip_type(ts, ONLY_EDGE_HANDLER); |
| |
| if (ts->dex_mode) { |
| input_info(true, &ts->client->dev, |
| "%s: set dex mode\n", __func__); |
| ret = ts->sec_ts_write(ts, SEC_TS_CMD_SET_DEX_MODE, |
| &ts->dex_mode, 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: failed to set dex mode %x\n", |
| __func__, ts->dex_mode); |
| } |
| |
| if (ts->brush_mode) { |
| input_info(true, &ts->client->dev, |
| "%s: set brush mode\n", __func__); |
| ret = ts->sec_ts_write(ts, SEC_TS_CMD_SET_BRUSH_MODE, |
| &ts->brush_mode, 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: failed to set brush mode\n", |
| __func__); |
| } |
| |
| if (ts->touchable_area) { |
| input_info(true, &ts->client->dev, |
| "%s: set 16:9 mode\n", __func__); |
| ret = ts->sec_ts_write(ts, |
| SEC_TS_CMD_SET_TOUCHABLE_AREA, |
| &ts->touchable_area, 1); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: failed to set 16:9 mode\n", |
| __func__); |
| } |
| |
| } |
| } |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP) |
| /* Update a state machine used to toggle control of the touch IC's motion |
| * filter. |
| */ |
| static void update_motion_filter(struct sec_ts_data *ts) |
| { |
| /* Motion filter timeout, in milliseconds */ |
| const u32 mf_timeout_ms = 500; |
| u8 next_state; |
| /* Count the active touches */ |
| u8 touches = hweight32(ts->tid_touch_state); |
| |
| if (ts->use_default_mf) |
| return; |
| |
| /* Determine the next filter state. The motion filter is enabled by |
| * default and it is disabled while a single finger is touching the |
| * screen. If another finger is touched down or if a timeout expires, |
| * the motion filter is reenabled and remains enabled until all fingers |
| * are lifted. |
| */ |
| next_state = ts->mf_state; |
| switch (ts->mf_state) { |
| case SEC_TS_MF_FILTERED: |
| if (touches == 1) { |
| next_state = SEC_TS_MF_UNFILTERED; |
| ts->mf_downtime = ktime_get(); |
| } |
| break; |
| case SEC_TS_MF_UNFILTERED: |
| if (touches == 0) { |
| next_state = SEC_TS_MF_FILTERED; |
| } else if (touches > 1 || |
| ktime_after(ktime_get(), |
| ktime_add_ms(ts->mf_downtime, |
| mf_timeout_ms))) { |
| next_state = SEC_TS_MF_FILTERED_LOCKED; |
| } |
| break; |
| case SEC_TS_MF_FILTERED_LOCKED: |
| if (touches == 0) |
| next_state = SEC_TS_MF_FILTERED; |
| break; |
| } |
| |
| /* Send command to update filter state */ |
| if ((next_state == SEC_TS_MF_UNFILTERED) != |
| (ts->mf_state == SEC_TS_MF_UNFILTERED)) { |
| int ret; |
| u8 para; |
| |
| pr_debug("%s: setting motion filter = %s.\n", __func__, |
| (next_state == SEC_TS_MF_UNFILTERED) ? |
| "false" : "true"); |
| para = (next_state == SEC_TS_MF_UNFILTERED) ? 0x01 : 0x00; |
| ret = ts->sec_ts_write(ts, SEC_TS_CMD_SET_CONT_REPORT, |
| ¶, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: write reg %#x para %#x failed, returned %i\n", |
| __func__, SEC_TS_CMD_SET_CONT_REPORT, para, ret); |
| } |
| } |
| ts->mf_state = next_state; |
| } |
| |
| static bool read_heatmap_raw(struct v4l2_heatmap *v4l2) |
| { |
| struct sec_ts_data *ts = container_of(v4l2, struct sec_ts_data, v4l2); |
| const struct sec_ts_plat_data *pdata = ts->plat_data; |
| int result; |
| int max_x = v4l2->format.width; |
| int max_y = v4l2->format.height; |
| |
| if (ts->tsp_dump_lock == 1) { |
| input_info(true, &ts->client->dev, |
| "%s: drop this because raw data reading by others\n", |
| __func__); |
| return false; |
| } |
| |
| if (pdata->heatmap_mode == HEATMAP_PARTIAL) { |
| strength_t heatmap_value; |
| int heatmap_x, heatmap_y; |
| /* index for through the heatmap buffer read over the bus */ |
| unsigned int local_i; |
| /* final position of the heatmap value in the full frame */ |
| unsigned int frame_i; |
| unsigned int num_elements; |
| u8 enable; |
| struct heatmap_report report = {0}; |
| |
| result = sec_ts_read(ts, |
| SEC_TS_CMD_HEATMAP_ENABLE, &enable, 1); |
| if (result < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read reg %#x failed, returned %i\n", |
| __func__, SEC_TS_CMD_HEATMAP_ENABLE, result); |
| return false; |
| } |
| |
| if (!enable) { |
| enable = 1; |
| result = sec_ts_write(ts, |
| SEC_TS_CMD_HEATMAP_ENABLE, &enable, 1); |
| if (result < 0) |
| input_err(true, &ts->client->dev, |
| "%s: enable local heatmap failed, returned %i\n", |
| __func__, result); |
| /* |
| * After local heatmap enabled, it takes `1/SCAN_RATE` |
| * time to make data ready. But, we don't want to wait |
| * here to cause overhead. Just drop this and wait for |
| * next reading. |
| */ |
| return false; |
| } |
| |
| result = sec_ts_read(ts, SEC_TS_CMD_HEATMAP_READ, |
| (uint8_t *) &report, sizeof(report)); |
| if (result < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read failed, returned %i\n", |
| __func__, result); |
| return false; |
| } |
| |
| num_elements = report.size_x * report.size_y; |
| if (num_elements > LOCAL_HEATMAP_WIDTH * LOCAL_HEATMAP_HEIGHT) { |
| input_err(true, &ts->client->dev, |
| "Unexpected heatmap size: %i x %i", |
| report.size_x, report.size_y); |
| return false; |
| } |
| |
| /* |
| * Set all to zero, will only write to non-zero locations |
| * in the loop. |
| */ |
| memset(v4l2->frame, 0, v4l2->format.sizeimage); |
| /* populate the data buffer, rearranging into final locations */ |
| for (local_i = 0; local_i < num_elements; local_i++) { |
| /* big-endian order raw data into heatmap data type */ |
| be16_to_cpus(&report.data[local_i]); |
| heatmap_value = report.data[local_i]; |
| |
| if (heatmap_value == 0) { |
| /* |
| * Already initialized to zero. More |
| * importantly, samples around edges may go out |
| * of bounds. |
| * If their value is zero, this is ok. |
| */ |
| continue; |
| } |
| heatmap_x = report.offset_x + (local_i % report.size_x); |
| heatmap_y = report.offset_y + (local_i / report.size_x); |
| |
| if (heatmap_x < 0 || heatmap_x >= max_x || |
| heatmap_y < 0 || heatmap_y >= max_y) { |
| input_err(true, &ts->client->dev, |
| "Invalid x or y: (%i, %i), value=%i, ending loop\n", |
| heatmap_x, heatmap_y, |
| heatmap_value); |
| return false; |
| } |
| frame_i = heatmap_y * max_x + heatmap_x; |
| v4l2->frame[frame_i] = heatmap_value; |
| } |
| } else if (pdata->heatmap_mode == HEATMAP_FULL) { |
| int i, j, index = 0; |
| int ret = 0; |
| u8 type; |
| |
| if (!ts->heatmap_buff) { |
| ts->heatmap_buff = kmalloc( |
| sizeof(strength_t) * max_x * max_y, GFP_KERNEL); |
| if (!ts->heatmap_buff) { |
| input_err(true, &ts->client->dev, |
| "%s: alloc heatmap_buff failed\n", __func__); |
| return false; |
| } |
| } |
| |
| ret = sec_ts_read(ts, |
| SEC_TS_CMD_MUTU_RAW_TYPE, &ts->ms_frame_type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read rawdata type failed\n", |
| __func__); |
| return false; |
| } |
| |
| /* Check raw type is TYPE_SIGNAL_DATA */ |
| if (ts->ms_frame_type != TYPE_SIGNAL_DATA) { |
| input_info(true, &ts->client->dev, |
| "%s: ms_frame_type change from %#x\n", |
| __func__, ts->ms_frame_type); |
| |
| /* Check raw type is TYPE_INVALID_DATA */ |
| if (ts->ms_frame_type != TYPE_INVALID_DATA) { |
| type = TYPE_INVALID_DATA; |
| ret = sec_ts_write(ts, |
| SEC_TS_CMD_MUTU_RAW_TYPE, &type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: recover rawdata type failed\n", |
| __func__); |
| return false; |
| } |
| ts->ms_frame_type = type; |
| } |
| |
| /* Set raw type to TYPE_SIGNAL_DATA */ |
| type = TYPE_SIGNAL_DATA; |
| ret = sec_ts_write(ts, SEC_TS_CMD_MUTU_RAW_TYPE, |
| &type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Set rawdata type failed\n", |
| __func__); |
| return false; |
| } |
| ts->ms_frame_type = type; |
| |
| /* |
| * If raw type change, need to wait 50 ms to read data |
| * back. But, we don't wanto to wait here to cause |
| * overhead. Just drop this and wait for next reading. |
| */ |
| return false; |
| } |
| |
| ret = sec_ts_read_heap(ts, SEC_TS_READ_TOUCH_RAWDATA, |
| (u8 *)ts->heatmap_buff, |
| sizeof(strength_t) * max_x * max_y); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Read delta frame failed\n", __func__); |
| return false; |
| } |
| |
| /* big-endian order raw data into heatmap data type */ |
| for (i = max_y - 1; i >= 0; i--) |
| for (j = max_x - 1; j >= 0 ; j--) |
| v4l2->frame[index++] = be16_to_cpup( |
| ts->heatmap_buff + (j * max_y) + i); |
| } else |
| return false; |
| |
| return true; |
| } |
| #endif |
| |
| #ifdef SEC_TS_SUPPORT_CUSTOMLIB |
| /* WARNING: touch_offload does not currently support the custom library |
| * interface! |
| * TODO: when custom library support is enabled, ensure that the output is |
| * routed through touch_offload. |
| */ |
| static void sec_ts_handle_lib_status_event(struct sec_ts_data *ts, |
| struct sec_ts_event_status *p_event_status) |
| { |
| if ((p_event_status->stype == TYPE_STATUS_EVENT_CUSTOMLIB_INFO) && |
| (p_event_status->status_id == SEC_TS_EVENT_CUSTOMLIB_FORCE_KEY)) { |
| if (ts->power_status == SEC_TS_STATE_POWER_ON) { |
| if (p_event_status->status_data_1 & |
| SEC_TS_CUSTOMLIB_EVENT_PRESSURE_TOUCHED) { |
| ts->all_force_count++; |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_PRESSURE_TOUCHED; |
| } else { |
| if (ts->scrub_id == |
| CUSTOMLIB_EVENT_TYPE_AOD_HOMEKEY_PRESS) { |
| input_report_key(ts->input_dev, |
| KEY_HOMEPAGE, |
| 0); |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_AOD_HOMEKEY_RELEASE; |
| } else { |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_PRESSURE_RELEASED; |
| } |
| } |
| |
| input_report_key(ts->input_dev, |
| KEY_BLACK_UI_GESTURE, 1); |
| } else { |
| if (p_event_status->status_data_1 & |
| SEC_TS_CUSTOMLIB_EVENT_PRESSURE_RELEASED) { |
| input_report_key(ts->input_dev, |
| KEY_HOMEPAGE, 0); |
| input_report_key(ts->input_dev, |
| KEY_BLACK_UI_GESTURE, 1); |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_AOD_HOMEKEY_RLS_NO_HAPTIC; |
| input_sync(ts->input_dev); |
| haptic_homekey_release(); |
| } else { |
| input_report_key(ts->input_dev, |
| KEY_HOMEPAGE, 1); |
| input_sync(ts->input_dev); |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_AOD_HOMEKEY_PRESS; |
| haptic_homekey_press(); |
| ts->all_force_count++; |
| } |
| } |
| |
| ts->scrub_x = |
| ((p_event_status->status_data_4 >> 4) & 0xF) << 8 | |
| (p_event_status->status_data_3 & 0xFF); |
| ts->scrub_y = |
| ((p_event_status->status_data_4 >> 0) & 0xF) << 8 | |
| (p_event_status->status_data_2 & 0xFF); |
| |
| input_info(true, &ts->client->dev, "%s: PRESSURE[%d]\n", |
| __func__, ts->scrub_id); |
| |
| input_sync(ts->input_dev); |
| input_report_key(ts->input_dev, KEY_BLACK_UI_GESTURE, 0); |
| } |
| } |
| #endif |
| |
| static void sec_ts_handle_coord_event(struct sec_ts_data *ts, |
| struct sec_ts_event_coordinate *p_event_coord) |
| { |
| u8 t_id; |
| |
| if (ts->input_closed) { |
| input_err(true, &ts->client->dev, "%s: device is closed\n", |
| __func__); |
| return; |
| } |
| |
| t_id = (p_event_coord->tid - 1); |
| |
| if (t_id < MAX_SUPPORT_TOUCH_COUNT + MAX_SUPPORT_HOVER_COUNT) { |
| ts->coord[t_id].id = t_id; |
| ts->coord[t_id].action = p_event_coord->tchsta; |
| ts->coord[t_id].x = (p_event_coord->x_11_4 << 4) | |
| (p_event_coord->x_3_0); |
| ts->coord[t_id].y = (p_event_coord->y_11_4 << 4) | |
| (p_event_coord->y_3_0); |
| ts->coord[t_id].z = p_event_coord->z & |
| SEC_TS_PRESSURE_MAX; |
| ts->coord[t_id].ttype = p_event_coord->ttype_3_2 << 2 | |
| p_event_coord->ttype_1_0 << 0; |
| ts->coord[t_id].major = p_event_coord->major; |
| ts->coord[t_id].minor = p_event_coord->minor; |
| |
| if (!ts->coord[t_id].palm && |
| (ts->coord[t_id].ttype == SEC_TS_TOUCHTYPE_PALM)) |
| ts->coord[t_id].palm_count++; |
| |
| ts->coord[t_id].palm = |
| (ts->coord[t_id].ttype == SEC_TS_TOUCHTYPE_PALM); |
| |
| ts->coord[t_id].grip = |
| (ts->coord[t_id].ttype == SEC_TS_TOUCHTYPE_GRIP); |
| |
| ts->coord[t_id].left_event = p_event_coord->left_event; |
| |
| if (ts->coord[t_id].z <= 0) |
| ts->coord[t_id].z = 1; |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| ts->offload.coords[t_id].x = ts->coord[t_id].x; |
| ts->offload.coords[t_id].y = ts->coord[t_id].y; |
| ts->offload.coords[t_id].major = ts->coord[t_id].major; |
| ts->offload.coords[t_id].minor = ts->coord[t_id].minor; |
| ts->offload.coords[t_id].pressure = ts->coord[t_id].z; |
| ts->offload.coords[t_id].rotation = 0; |
| #endif |
| |
| if ((ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_NORMAL) || |
| (ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_PALM) || |
| (ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_GRIP) || |
| (ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_WET) || |
| (ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_GLOVE)) { |
| |
| if (ts->coord[t_id].action == |
| SEC_TS_COORDINATE_ACTION_RELEASE) { |
| |
| do_gettimeofday(&ts->time_released[t_id]); |
| |
| if (ts->time_longest < |
| (ts->time_released[t_id].tv_sec - |
| ts->time_pressed[t_id].tv_sec)) |
| ts->time_longest = |
| (ts->time_released[t_id].tv_sec |
| - ts->time_pressed[t_id].tv_sec); |
| |
| if (ts->touch_count > 0) |
| ts->touch_count--; |
| if (ts->touch_count == 0 || |
| ts->tid_touch_state == 0) { |
| ts->check_multi = 0; |
| } |
| __clear_bit(t_id, &ts->tid_palm_state); |
| __clear_bit(t_id, &ts->tid_grip_state); |
| __clear_bit(t_id, &ts->tid_touch_state); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| ts->offload.coords[t_id].status = |
| COORD_STATUS_INACTIVE; |
| if (!ts->offload.offload_running) { |
| #endif |
| input_mt_slot(ts->input_dev, t_id); |
| if (ts->plat_data->support_mt_pressure) |
| input_report_abs(ts->input_dev, |
| ABS_MT_PRESSURE, 0); |
| input_mt_report_slot_state(ts->input_dev, |
| MT_TOOL_FINGER, 0); |
| |
| if (ts->touch_count == 0 || |
| ts->tid_touch_state == 0) { |
| input_report_key(ts->input_dev, |
| BTN_TOUCH, 0); |
| input_report_key(ts->input_dev, |
| BTN_TOOL_FINGER, 0); |
| } |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| } |
| #endif |
| } else if (ts->coord[t_id].action == |
| SEC_TS_COORDINATE_ACTION_PRESS) { |
| do_gettimeofday(&ts->time_pressed[t_id]); |
| |
| ts->touch_count++; |
| if ((ts->touch_count > 4) && |
| (ts->check_multi == 0)) { |
| ts->check_multi = 1; |
| ts->multi_count++; |
| } |
| ts->all_finger_count++; |
| |
| ts->max_z_value = max_t(unsigned int, |
| ts->coord[t_id].z, |
| ts->max_z_value); |
| ts->min_z_value = min_t(unsigned int, |
| ts->coord[t_id].z, |
| ts->min_z_value); |
| ts->sum_z_value += |
| (unsigned int)ts->coord[t_id].z; |
| |
| __set_bit(t_id, &ts->tid_touch_state); |
| __clear_bit(t_id, &ts->tid_palm_state); |
| __clear_bit(t_id, &ts->tid_grip_state); |
| if (ts->coord[t_id].palm) |
| __set_bit(t_id, &ts->tid_palm_state); |
| else if (ts->coord[t_id].grip) |
| __set_bit(t_id, &ts->tid_grip_state); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| ts->offload.coords[t_id].status = |
| COORD_STATUS_FINGER; |
| if (!ts->offload.offload_running) { |
| #endif |
| input_mt_slot(ts->input_dev, t_id); |
| if (ts->coord[t_id].palm) |
| input_mt_report_slot_state( |
| ts->input_dev, MT_TOOL_PALM, 1); |
| else if (ts->coord[t_id].grip) |
| input_mt_report_slot_state( |
| ts->input_dev, MT_TOOL_PALM, 1); |
| else |
| input_mt_report_slot_state( |
| ts->input_dev, |
| MT_TOOL_FINGER, 1); |
| |
| input_report_key(ts->input_dev, BTN_TOUCH, 1); |
| input_report_key(ts->input_dev, |
| BTN_TOOL_FINGER, 1); |
| |
| input_report_abs(ts->input_dev, |
| ABS_MT_POSITION_X, ts->coord[t_id].x); |
| input_report_abs(ts->input_dev, |
| ABS_MT_POSITION_Y, ts->coord[t_id].y); |
| input_report_abs(ts->input_dev, |
| ABS_MT_TOUCH_MAJOR, |
| ts->coord[t_id].major); |
| input_report_abs(ts->input_dev, |
| ABS_MT_TOUCH_MINOR, |
| ts->coord[t_id].minor); |
| #ifdef ABS_MT_CUSTOM |
| if (ts->brush_mode) |
| input_report_abs(ts->input_dev, |
| ABS_MT_CUSTOM, |
| (ts->coord[t_id].z << 1) | |
| ts->coord[t_id].palm); |
| else |
| input_report_abs(ts->input_dev, |
| ABS_MT_CUSTOM, |
| (BRUSH_Z_DATA << 1) | |
| ts->coord[t_id].palm); |
| #endif |
| if (ts->plat_data->support_mt_pressure) |
| input_report_abs(ts->input_dev, |
| ABS_MT_PRESSURE, |
| ts->coord[t_id].z); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| } |
| #endif |
| } else if (ts->coord[t_id].action == |
| SEC_TS_COORDINATE_ACTION_MOVE) { |
| |
| ts->coord[t_id].mcount++; |
| |
| #ifdef SW_GLOVE |
| if ((ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_GLOVE) && |
| !ts->touchkey_glove_mode_status) { |
| ts->touchkey_glove_mode_status = true; |
| } else if ((ts->coord[t_id].ttype != |
| SEC_TS_TOUCHTYPE_GLOVE) && |
| ts->touchkey_glove_mode_status) { |
| ts->touchkey_glove_mode_status = false; |
| } |
| #endif |
| __set_bit(t_id, &ts->tid_touch_state); |
| __clear_bit(t_id, &ts->tid_palm_state); |
| __clear_bit(t_id, &ts->tid_grip_state); |
| if (ts->coord[t_id].palm) |
| __set_bit(t_id, &ts->tid_palm_state); |
| else if (ts->coord[t_id].grip) |
| __set_bit(t_id, &ts->tid_grip_state); |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| ts->offload.coords[t_id].status = |
| COORD_STATUS_FINGER; |
| if (!ts->offload.offload_running) { |
| #endif |
| #ifdef SW_GLOVE |
| if ((ts->coord[t_id].ttype == |
| SEC_TS_TOUCHTYPE_GLOVE) && |
| !ts->touchkey_glove_mode_status) { |
| input_report_switch(ts->input_dev, |
| SW_GLOVE, 1); |
| } else if ((ts->coord[t_id].ttype != |
| SEC_TS_TOUCHTYPE_GLOVE) && |
| ts->touchkey_glove_mode_status) { |
| input_report_switch(ts->input_dev, |
| SW_GLOVE, 0); |
| } |
| #endif |
| input_mt_slot(ts->input_dev, t_id); |
| if (ts->coord[t_id].palm) |
| input_mt_report_slot_state( |
| ts->input_dev, MT_TOOL_PALM, 1); |
| else if (ts->coord[t_id].grip) |
| input_mt_report_slot_state( |
| ts->input_dev, MT_TOOL_PALM, 1); |
| else |
| input_mt_report_slot_state( |
| ts->input_dev, |
| MT_TOOL_FINGER, 1); |
| |
| input_report_key(ts->input_dev, BTN_TOUCH, 1); |
| input_report_key(ts->input_dev, |
| BTN_TOOL_FINGER, 1); |
| |
| input_report_abs(ts->input_dev, |
| ABS_MT_POSITION_X, ts->coord[t_id].x); |
| input_report_abs(ts->input_dev, |
| ABS_MT_POSITION_Y, ts->coord[t_id].y); |
| input_report_abs(ts->input_dev, |
| ABS_MT_TOUCH_MAJOR, |
| ts->coord[t_id].major); |
| input_report_abs(ts->input_dev, |
| ABS_MT_TOUCH_MINOR, |
| ts->coord[t_id].minor); |
| #ifdef ABS_MT_CUSTOM |
| if (ts->brush_mode) |
| input_report_abs(ts->input_dev, |
| ABS_MT_CUSTOM, |
| (ts->coord[t_id].z << 1) | |
| ts->coord[t_id].palm); |
| else |
| input_report_abs(ts->input_dev, |
| ABS_MT_CUSTOM, |
| (BRUSH_Z_DATA << 1) | |
| ts->coord[t_id].palm); |
| #endif |
| if (ts->plat_data->support_mt_pressure) |
| input_report_abs(ts->input_dev, |
| ABS_MT_PRESSURE, |
| ts->coord[t_id].z); |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| } |
| #endif |
| } else |
| input_dbg(true, &ts->client->dev, |
| "%s: do not support coordinate action(%d)\n", |
| __func__, ts->coord[t_id].action); |
| } else |
| input_dbg(true, &ts->client->dev, |
| "%s: do not support coordinate type(%d)\n", |
| __func__, ts->coord[t_id].ttype); |
| } else |
| input_err(true, &ts->client->dev, |
| "%s: tid(%d) is out of range\n", |
| __func__, t_id); |
| |
| if (t_id < MAX_SUPPORT_TOUCH_COUNT + MAX_SUPPORT_HOVER_COUNT) { |
| if (ts->coord[t_id].action == SEC_TS_COORDINATE_ACTION_PRESS) { |
| input_dbg(false, &ts->client->dev, |
| "%s[P] tID:%d x:%d y:%d z:%d major:%d minor:%d tc:%d type:%X\n", |
| ts->dex_name, |
| t_id, ts->coord[t_id].x, |
| ts->coord[t_id].y, ts->coord[t_id].z, |
| ts->coord[t_id].major, |
| ts->coord[t_id].minor, |
| ts->touch_count, |
| ts->coord[t_id].ttype); |
| |
| } else if (ts->coord[t_id].action == |
| SEC_TS_COORDINATE_ACTION_RELEASE) { |
| input_dbg(false, &ts->client->dev, |
| "%s[R] tID:%d mc:%d tc:%d lx:%d ly:%d v:%02X%02X cal:%02X(%02X) id(%d,%d) p:%d\n", |
| ts->dex_name, |
| t_id, ts->coord[t_id].mcount, |
| ts->touch_count, |
| ts->coord[t_id].x, ts->coord[t_id].y, |
| ts->plat_data->img_version_of_ic[2], |
| ts->plat_data->img_version_of_ic[3], |
| ts->cal_status, ts->nv, ts->tspid_val, |
| ts->tspicid_val, |
| ts->coord[t_id].palm_count); |
| |
| ts->coord[t_id].mcount = 0; |
| ts->coord[t_id].palm_count = 0; |
| } |
| } |
| } |
| |
| #ifdef SEC_TS_SUPPORT_CUSTOMLIB |
| static void sec_ts_handle_gesture_event(struct sec_ts_data *ts, |
| struct sec_ts_gesture_status *p_gesture_status) |
| { |
| if ((p_gesture_status->eid == 0x02) && |
| (p_gesture_status->stype == 0x00)) { |
| u8 customlib[3] = { 0 }; |
| |
| ret = sec_ts_read_from_customlib(ts, customlib, 3); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: fail to read custom library data\n", |
| __func__); |
| |
| input_info(true, &ts->client->dev, |
| "%s: Custom Library, %x, %x, %x\n", |
| __func__, customlib[0], customlib[1], customlib[2]); |
| |
| if (p_gesture_status->gesture_id == SEC_TS_GESTURE_CODE_SPAY || |
| p_gesture_status->gesture_id == |
| SEC_TS_GESTURE_CODE_DOUBLE_TAP) { |
| /* will be fixed to data structure */ |
| if (customlib[1] & SEC_TS_MODE_CUSTOMLIB_AOD) { |
| u8 data[5] = { 0x0A, 0x00, 0x00, 0x00, 0x00 }; |
| |
| ret = sec_ts_read_from_customlib(ts, data, 5); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: fail to read custom library data\n", |
| __func__); |
| |
| if (data[4] & SEC_TS_AOD_GESTURE_DOUBLETAB) |
| ts->scrub_id = |
| CUSTOMLIB_EVENT_TYPE_AOD_DOUBLETAB; |
| |
| ts->scrub_x = (data[1] & 0xFF) << 8 | |
| (data[0] & 0xFF); |
| ts->scrub_y = (data[3] & 0xFF) << 8 | |
| (data[2] & 0xFF); |
| input_info(true, &ts->client->dev, |
| "%s: aod: %d\n", |
| __func__, ts->scrub_id); |
| ts->all_aod_tap_count++; |
| } |
| if (customlib[1] & SEC_TS_MODE_CUSTOMLIB_SPAY) { |
| ts->scrub_id = CUSTOMLIB_EVENT_TYPE_SPAY; |
| input_info(true, &ts->client->dev, |
| "%s: SPAY: %d\n", |
| __func__, ts->scrub_id); |
| ts->all_spay_count++; |
| } |
| input_report_key(ts->input_dev, |
| KEY_BLACK_UI_GESTURE, 1); |
| input_sync(ts->input_dev); |
| input_report_key(ts->input_dev, |
| KEY_BLACK_UI_GESTURE, 0); |
| } |
| } |
| } |
| #endif |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| |
| static void sec_ts_populate_coordinate_channel(struct sec_ts_data *ts, |
| struct touch_offload_frame *frame, |
| int channel) |
| { |
| int j; |
| |
| struct TouchOffloadDataCoord *dc = |
| (struct TouchOffloadDataCoord *)frame->channel_data[channel]; |
| memset(dc, 0, frame->channel_data_size[channel]); |
| dc->header.channel_type = TOUCH_DATA_TYPE_COORD; |
| dc->header.channel_size = TOUCH_OFFLOAD_FRAME_SIZE_COORD; |
| |
| for (j = 0; j < MAX_COORDS; j++) { |
| dc->coords[j].x = ts->offload.coords[j].x; |
| dc->coords[j].y = ts->offload.coords[j].y; |
| dc->coords[j].major = ts->offload.coords[j].major; |
| dc->coords[j].minor = ts->offload.coords[j].minor; |
| dc->coords[j].pressure = ts->offload.coords[j].pressure; |
| dc->coords[j].rotation = ts->offload.coords[j].rotation; |
| dc->coords[j].status = ts->offload.coords[j].status; |
| } |
| } |
| |
| static void sec_ts_populate_mutual_channel(struct sec_ts_data *ts, |
| struct touch_offload_frame *frame, |
| int channel) |
| { |
| uint32_t frame_index = 0; |
| int32_t x, y; |
| uint16_t heatmap_value; |
| int ret = 0; |
| u8 target_data_type, type; |
| struct TouchOffloadData2d *mutual_strength = |
| (struct TouchOffloadData2d *)frame->channel_data[channel]; |
| |
| switch (frame->channel_type[channel] & ~TOUCH_SCAN_TYPE_MUTUAL) { |
| case TOUCH_DATA_TYPE_RAW: |
| target_data_type = TYPE_DECODED_DATA; |
| break; |
| case TOUCH_DATA_TYPE_FILTERED: |
| target_data_type = TYPE_REMV_AMB_DATA; |
| break; |
| case TOUCH_DATA_TYPE_STRENGTH: |
| target_data_type = TYPE_SIGNAL_DATA; |
| break; |
| case TOUCH_DATA_TYPE_BASELINE: |
| target_data_type = TYPE_AMBIENT_DATA; |
| break; |
| } |
| |
| mutual_strength->tx_size = ts->tx_count; |
| mutual_strength->rx_size = ts->rx_count; |
| mutual_strength->header.channel_type = frame->channel_type[channel]; |
| mutual_strength->header.channel_size = |
| TOUCH_OFFLOAD_FRAME_SIZE_2D(mutual_strength->rx_size, |
| mutual_strength->tx_size); |
| |
| ret = sec_ts_read(ts, |
| SEC_TS_CMD_MUTU_RAW_TYPE, &ts->ms_frame_type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read rawdata type failed\n", |
| __func__); |
| return; |
| } |
| |
| /* Check raw type is correct */ |
| if (ts->ms_frame_type != target_data_type) { |
| input_info(true, &ts->client->dev, |
| "%s: ms_frame_type change from %#x\n", |
| __func__, ts->ms_frame_type); |
| |
| /* Check raw type is TYPE_INVALID_DATA */ |
| if (ts->ms_frame_type != TYPE_INVALID_DATA) { |
| type = TYPE_INVALID_DATA; |
| ret = sec_ts_write(ts, |
| SEC_TS_CMD_MUTU_RAW_TYPE, &type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: recover rawdata type failed\n", |
| __func__); |
| return; |
| } |
| ts->ms_frame_type = type; |
| } |
| |
| /* Set the targeted data type */ |
| ret = sec_ts_write(ts, SEC_TS_CMD_MUTU_RAW_TYPE, |
| &target_data_type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Set rawdata type failed\n", |
| __func__); |
| return; |
| } |
| ts->ms_frame_type = target_data_type; |
| |
| /* |
| * If raw type change, need to wait 50 ms to read data |
| * back. But, we don't wanto to wait here to cause |
| * overhead. Just drop this and wait for next reading. |
| */ |
| |
| return; |
| } |
| |
| ret = sec_ts_read_heap(ts, SEC_TS_READ_TOUCH_RAWDATA, |
| (u8 *)ts->heatmap_buff, |
| mutual_strength->header.channel_size); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Read mutual frame failed\n", __func__); |
| return; |
| } |
| |
| for (y = mutual_strength->rx_size - 1; y >= 0; y--) { |
| for (x = mutual_strength->tx_size - 1; x >= 0; x--) { |
| heatmap_value = |
| ts->heatmap_buff[x * mutual_strength->rx_size + y]; |
| ((uint16_t *) |
| mutual_strength->data)[frame_index++] = |
| be16_to_cpu(heatmap_value); |
| } |
| } |
| } |
| |
| static void sec_ts_populate_self_channel(struct sec_ts_data *ts, |
| struct touch_offload_frame *frame, |
| int channel) |
| { |
| uint32_t frame_index = 0; |
| int32_t x, y; |
| uint16_t heatmap_value; |
| int ret = 0; |
| u8 target_data_type, type; |
| struct TouchOffloadData1d *self_strength = |
| (struct TouchOffloadData1d *)frame->channel_data[channel]; |
| |
| switch (frame->channel_type[channel] & ~TOUCH_SCAN_TYPE_SELF) { |
| case TOUCH_DATA_TYPE_RAW: |
| target_data_type = TYPE_DECODED_DATA; |
| break; |
| case TOUCH_DATA_TYPE_FILTERED: |
| target_data_type = TYPE_REMV_AMB_DATA; |
| break; |
| case TOUCH_DATA_TYPE_STRENGTH: |
| target_data_type = TYPE_SIGNAL_DATA; |
| break; |
| case TOUCH_DATA_TYPE_BASELINE: |
| target_data_type = TYPE_AMBIENT_DATA; |
| break; |
| } |
| |
| self_strength->tx_size = ts->tx_count; |
| self_strength->rx_size = ts->rx_count; |
| self_strength->header.channel_type = frame->channel_type[channel]; |
| self_strength->header.channel_size = |
| TOUCH_OFFLOAD_FRAME_SIZE_1D(self_strength->rx_size, |
| self_strength->tx_size); |
| |
| ret = sec_ts_read(ts, |
| SEC_TS_CMD_SELF_RAW_TYPE, &ts->ss_frame_type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read rawdata type failed\n", |
| __func__); |
| return; |
| } |
| |
| /* Check raw type is TYPE_SIGNAL_DATA */ |
| if (ts->ss_frame_type != target_data_type) { |
| input_info(true, &ts->client->dev, |
| "%s: ss_frame_type change from %#x\n", |
| __func__, ts->ss_frame_type); |
| |
| /* Check raw type is TYPE_INVALID_DATA */ |
| if (ts->ss_frame_type != TYPE_INVALID_DATA) { |
| type = TYPE_INVALID_DATA; |
| ret = sec_ts_write(ts, |
| SEC_TS_CMD_SELF_RAW_TYPE, &type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: recover rawdata type failed\n", |
| __func__); |
| return; |
| } |
| ts->ss_frame_type = type; |
| } |
| |
| /* Set the targeted data type */ |
| ret = sec_ts_write(ts, SEC_TS_CMD_SELF_RAW_TYPE, |
| &target_data_type, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Set rawdata type failed\n", |
| __func__); |
| return; |
| } |
| ts->ss_frame_type = target_data_type; |
| |
| /* |
| * If raw type change, need to wait 50 ms to read data |
| * back. But, we don't wanto to wait here to cause |
| * overhead. Just drop this and wait for next reading. |
| */ |
| |
| return; |
| } |
| |
| ret = sec_ts_read_heap(ts, SEC_TS_READ_TOUCH_SELF_RAWDATA, |
| (u8 *)ts->heatmap_buff, |
| self_strength->header.channel_size); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Read self frame failed\n", __func__); |
| return; |
| } |
| |
| for (x = self_strength->tx_size - 1; x >= 0; x--) { |
| heatmap_value = ts->heatmap_buff[x]; |
| ((uint16_t *) |
| self_strength->data)[frame_index++] = |
| be16_to_cpu(heatmap_value); |
| } |
| for (y = self_strength->rx_size - 1; y >= 0; y--) { |
| heatmap_value = ts->heatmap_buff[self_strength->tx_size + y]; |
| ((uint16_t *) |
| self_strength->data)[frame_index++] = |
| be16_to_cpu(heatmap_value); |
| } |
| } |
| |
| static void sec_ts_populate_driver_status_channel(struct sec_ts_data *ts, |
| struct touch_offload_frame *frame, |
| int channel) |
| { |
| struct TouchOffloadDriverStatus *ds = |
| (struct TouchOffloadDriverStatus *)frame->channel_data[channel]; |
| memset(ds, 0, frame->channel_data_size[channel]); |
| ds->header.channel_type = (u32)CONTEXT_CHANNEL_TYPE_DRIVER_STATUS; |
| ds->header.channel_size = sizeof(struct TouchOffloadDriverStatus); |
| |
| ds->contents.screen_state = 1; |
| ds->screen_state = (ts->power_status == SEC_TS_STATE_POWER_ON) ? 1 : 0; |
| |
| /* Display refresh rate not currently available in this driver */ |
| ds->display_refresh_rate = 60; |
| ds->contents.display_refresh_rate = 0; |
| } |
| |
| static void sec_ts_populate_frame(struct sec_ts_data *ts, |
| struct touch_offload_frame *frame) |
| { |
| static u64 index; |
| int i; |
| |
| frame->header.index = index++; |
| frame->header.timestamp = ts->timestamp; |
| |
| if (!ts->heatmap_buff) { |
| ts->heatmap_buff = kmalloc( |
| ts->rx_count * ts->rx_count * 2, GFP_KERNEL); |
| } |
| |
| /* Populate all channels */ |
| for (i = 0; i < frame->num_channels; i++) { |
| if (frame->channel_type[i] == TOUCH_DATA_TYPE_COORD) |
| sec_ts_populate_coordinate_channel(ts, frame, i); |
| else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_MUTUAL) != 0) |
| sec_ts_populate_mutual_channel(ts, frame, i); |
| else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_SELF) != 0) |
| sec_ts_populate_self_channel(ts, frame, i); |
| else if ((frame->channel_type[i] == |
| CONTEXT_CHANNEL_TYPE_DRIVER_STATUS) != 0) |
| sec_ts_populate_driver_status_channel(ts, frame, i); |
| else if ((frame->channel_type[i] == |
| CONTEXT_CHANNEL_TYPE_STYLUS_STATUS) != 0) { |
| /* Stylus context is not required by this driver */ |
| input_err(true, &ts->client->dev, |
| "%s: Driver does not support stylus status", |
| __func__); |
| } |
| } |
| } |
| |
| int sec_ts_enable_grip(struct sec_ts_data *ts, bool enable) |
| { |
| u8 value = enable ? 1 : 0; |
| int ret; |
| int final_result = 0; |
| |
| /* Set grip */ |
| ret = ts->sec_ts_write(ts, SEC_TS_CMD_SET_GRIP_DETEC, &value, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: SEC_TS_CMD_SET_GRIP_DETEC failed with ret=%d\n", |
| __func__, ret); |
| final_result = ret; |
| } |
| |
| /* Set deadzone */ |
| value = enable ? 1 : 0; |
| ret = ts->sec_ts_write(ts, SEC_TS_CMD_EDGE_DEADZONE, &value, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: SEC_TS_CMD_EDGE_DEADZONE failed with ret=%d\n", |
| __func__, ret); |
| final_result = ret; |
| } |
| |
| return final_result; |
| } |
| |
| static void sec_ts_offload_set_running(struct sec_ts_data *ts, bool running) |
| { |
| if (ts->offload.offload_running != running) { |
| ts->offload.offload_running = running; |
| if (running && ts->offload.config.filter_grip) { |
| pr_info("%s: disabling FW grip.\n", __func__); |
| sec_ts_enable_grip(ts, false); |
| } else { |
| pr_info("%s: enabling FW grip.\n", __func__); |
| sec_ts_enable_grip(ts, true); |
| } |
| } |
| } |
| |
| #endif /* CONFIG_TOUCHSCREEN_OFFLOAD */ |
| |
| #define MAX_EVENT_COUNT 32 |
| static void sec_ts_read_event(struct sec_ts_data *ts) |
| { |
| int ret; |
| u8 event_id; |
| u8 left_event_count; |
| u8 read_event_buff[MAX_EVENT_COUNT][SEC_TS_EVENT_BUFF_SIZE] = { { 0 } }; |
| u8 *event_buff; |
| struct sec_ts_gesture_status *p_gesture_status; |
| struct sec_ts_event_status *p_event_status; |
| int curr_pos; |
| int remain_event_count = 0; |
| bool processed_pointer_event = false; |
| unsigned long last_tid_palm_state = ts->tid_palm_state; |
| unsigned long last_tid_grip_state = ts->tid_grip_state; |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| struct touch_offload_frame *frame = NULL; |
| #endif |
| |
| if (ts->power_status == SEC_TS_STATE_LPM) { |
| |
| pm_wakeup_event(&ts->client->dev, 3 * MSEC_PER_SEC); |
| /* waiting for blsp block resuming, if not occurs error */ |
| ret = wait_for_completion_interruptible_timeout( |
| &ts->resume_done, |
| msecs_to_jiffies(3 * MSEC_PER_SEC)); |
| if (ret == 0) { |
| input_err(true, &ts->client->dev, |
| "%s: LPM: pm resume is not handled\n", |
| __func__); |
| return; |
| } |
| |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: LPM: -ERESTARTSYS if interrupted, %d\n", |
| __func__, ret); |
| return; |
| } |
| |
| input_info(true, &ts->client->dev, |
| "%s: run LPM interrupt handler, %d\n", __func__, ret); |
| /* run lpm interrupt handler */ |
| } |
| |
| ret = event_id = curr_pos = remain_event_count = 0; |
| /* repeat READ_ONE_EVENT until buffer is empty(No event) */ |
| ret = sec_ts_read(ts, SEC_TS_READ_ONE_EVENT, |
| (u8 *)read_event_buff[0], SEC_TS_EVENT_BUFF_SIZE); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read one event failed\n", __func__); |
| return; |
| } |
| |
| if (ts->temp == 0x01) |
| input_info(true, &ts->client->dev, |
| "ONE: %02X %02X %02X %02X %02X %02X %02X %02X\n", |
| read_event_buff[0][0], read_event_buff[0][1], |
| read_event_buff[0][2], read_event_buff[0][3], |
| read_event_buff[0][4], read_event_buff[0][5], |
| read_event_buff[0][6], read_event_buff[0][7]); |
| |
| if (read_event_buff[0][0] == 0) { |
| input_info(true, &ts->client->dev, |
| "%s: event buffer is empty\n", __func__); |
| return; |
| } |
| |
| left_event_count = read_event_buff[0][7] & 0x3F; |
| remain_event_count = left_event_count; |
| |
| if (left_event_count > MAX_EVENT_COUNT - 1 || |
| left_event_count == 0xFF) { |
| input_err(true, &ts->client->dev, |
| "%s: event buffer overflow %d\n", |
| __func__, left_event_count); |
| |
| /* write clear event stack command |
| * when read_event_count > MAX_EVENT_COUNT |
| **/ |
| ret = sec_ts_write(ts, SEC_TS_CMD_CLEAR_EVENT_STACK, NULL, 0); |
| if (ret < 0) |
| input_err(true, &ts->client->dev, |
| "%s: write clear event failed\n", __func__); |
| return; |
| } |
| |
| if (left_event_count > 0) { |
| ret = sec_ts_read(ts, SEC_TS_READ_ALL_EVENT, |
| (u8 *)read_event_buff[1], |
| sizeof(u8) * (SEC_TS_EVENT_BUFF_SIZE) * |
| (left_event_count)); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: read one event failed\n", __func__); |
| return; |
| } |
| } |
| |
| do { |
| event_buff = read_event_buff[curr_pos]; |
| event_id = event_buff[0] & 0x3; |
| |
| if (ts->temp == 0x01) |
| input_info(true, &ts->client->dev, |
| "ALL: %02X %02X %02X %02X %02X %02X %02X %02X\n", |
| event_buff[0], event_buff[1], event_buff[2], |
| event_buff[3], event_buff[4], event_buff[5], |
| event_buff[6], event_buff[7]); |
| |
| switch (event_id) { |
| case SEC_TS_STATUS_EVENT: |
| p_event_status = |
| (struct sec_ts_event_status *)event_buff; |
| |
| /* tchsta == 0 && ttype == 0 && eid == 0 : buffer empty |
| **/ |
| if (p_event_status->stype > 0) { |
| /* Demote 'vendor' messages */ |
| if (p_event_status->stype == |
| TYPE_STATUS_EVENT_VENDOR_INFO) { |
| u8 status_id = |
| p_event_status->status_id; |
| u8 status_data_1 = |
| p_event_status->status_data_1; |
| |
| input_dbg(true, &ts->client->dev, |
| "%s: STATUS %x %x %x %x %x %x %x %x\n", |
| __func__, event_buff[0], |
| event_buff[1], event_buff[2], |
| event_buff[3], event_buff[4], |
| event_buff[5], event_buff[6], |
| event_buff[7]); |
| |
| switch (status_id) { |
| case SEC_TS_EVENT_STATUS_ID_WLC: |
| input_info(true, |
| &ts->client->dev, |
| "STATUS: wlc mode change to %x\n", |
| status_data_1); |
| break; |
| |
| case SEC_TS_EVENT_STATUS_ID_NOISE: |
| input_info(true, |
| &ts->client->dev, |
| "STATUS: noise mode change to %x\n", |
| status_data_1); |
| break; |
| |
| case SEC_TS_EVENT_STATUS_ID_GRIP: |
| input_info(true, |
| &ts->client->dev, |
| "STATUS: detect grip %s!\n", |
| (status_data_1) ? |
| "enter" : "leave"); |
| break; |
| |
| case SEC_TS_EVENT_STATUS_ID_PALM: |
| input_info(true, |
| &ts->client->dev, |
| "STATUS: detect palm!\n"); |
| break; |
| |
| default: |
| break; |
| } |
| } else |
| input_info(true, &ts->client->dev, |
| "%s: STATUS %x %x %x %x %x %x %x %x\n", |
| __func__, event_buff[0], |
| event_buff[1], event_buff[2], |
| event_buff[3], event_buff[4], |
| event_buff[5], event_buff[6], |
| event_buff[7]); |
| } |
| |
| if ((p_event_status->stype == |
| TYPE_STATUS_EVENT_INFO) && |
| (p_event_status->status_id == |
| SEC_TS_ACK_BOOT_COMPLETE)) { |
| u8 status_data_1 = |
| p_event_status->status_data_1; |
| |
| switch (status_data_1) { |
| case 0x20: |
| /* watchdog reset !? */ |
| sec_ts_locked_release_all_finger(ts); |
| ret = sec_ts_write(ts, |
| SEC_TS_CMD_SENSE_ON, NULL, 0); |
| if (ret < 0) |
| input_err(true, |
| &ts->client->dev, |
| "%s: fail to write Sense_on\n", |
| __func__); |
| sec_ts_reinit(ts); |
| break; |
| case 0x40: |
| input_info(true, &ts->client->dev, |
| "%s: sw_reset done\n", |
| __func__); |
| sec_ts_locked_release_all_finger(ts); |
| complete_all(&ts->boot_completed); |
| break; |
| case 0x10: |
| input_info(true, &ts->client->dev, |
| "%s: hw_reset done\n", |
| __func__); |
| sec_ts_locked_release_all_finger(ts); |
| complete_all(&ts->boot_completed); |
| break; |
| default: |
| break; |
| } |
| |
| } |
| |
| /* event queue full-> all finger release */ |
| if ((p_event_status->stype == TYPE_STATUS_EVENT_ERR) && |
| (p_event_status->status_id == |
| SEC_TS_ERR_EVENT_QUEUE_FULL)) { |
| input_err(true, &ts->client->dev, |
| "%s: IC Event Queue is full\n", |
| __func__); |
| sec_ts_locked_release_all_finger(ts); |
| } |
| |
| if ((p_event_status->stype == |
| TYPE_STATUS_EVENT_ERR) && |
| (p_event_status->status_id == |
| SEC_TS_ERR_EVENT_ESD)) { |
| input_err(true, &ts->client->dev, |
| "%s: ESD detected. run reset\n", |
| __func__); |
| #ifdef USE_RESET_DURING_POWER_ON |
| schedule_work(&ts->reset_work.work); |
| #endif |
| } |
| |
| if ((p_event_status->stype == |
| TYPE_STATUS_EVENT_INFO) && |
| (p_event_status->status_id == |
| SEC_TS_ACK_WET_MODE)) { |
| ts->wet_mode = p_event_status->status_data_1; |
| input_info(true, &ts->client->dev, |
| "%s: water wet mode %d\n", |
| __func__, ts->wet_mode); |
| if (ts->wet_mode) |
| ts->wet_count++; |
| |
| } |
| |
| #ifdef SEC_TS_SUPPORT_CUSTOMLIB |
| mutex_lock(&ts->eventlock); |
| sec_ts_handle_lib_status_event(ts, p_event_status); |
| mutex_unlock(&ts->eventlock); |
| #endif |
| break; |
| |
| case SEC_TS_COORDINATE_EVENT: |
| processed_pointer_event = true; |
| mutex_lock(&ts->eventlock); |
| sec_ts_handle_coord_event(ts, |
| (struct sec_ts_event_coordinate *)event_buff); |
| mutex_unlock(&ts->eventlock); |
| break; |
| |
| case SEC_TS_GESTURE_EVENT: |
| p_gesture_status = |
| (struct sec_ts_gesture_status *)event_buff; |
| #ifdef SEC_TS_SUPPORT_CUSTOMLIB |
| mutex_lock(&ts->eventlock); |
| sec_ts_handle_gesture_event(ts, p_gesture_status); |
| mutex_unlock(&ts->eventlock); |
| #endif |
| break; |
| |
| default: |
| input_err(true, &ts->client->dev, |
| "%s: unknown event %x %x %x %x %x %x\n", |
| __func__, |
| event_buff[0], event_buff[1], event_buff[2], |
| event_buff[3], event_buff[4], event_buff[5]); |
| break; |
| } |
| curr_pos++; |
| remain_event_count--; |
| } while (remain_event_count >= 0); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| if (!ts->offload.offload_running) { |
| #endif |
| mutex_lock(&ts->eventlock); |
| input_sync(ts->input_dev); |
| mutex_unlock(&ts->eventlock); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| } |
| |
| if (processed_pointer_event) { |
| ret = touch_offload_reserve_frame(&ts->offload, &frame); |
| if (ret != 0) { |
| input_dbg(true, &ts->client->dev, |
| "Could not reserve a frame: ret=%d.\n", ret); |
| |
| /* Stop offload when there are no buffers available */ |
| sec_ts_offload_set_running(ts, false); |
| } else { |
| sec_ts_offload_set_running(ts, true); |
| |
| sec_ts_populate_frame(ts, frame); |
| |
| ret = touch_offload_queue_frame(&ts->offload, frame); |
| if (ret != 0) { |
| pr_err("%s: Failed to queue reserved frame: ret=%d.\n", |
| __func__, ret); |
| } |
| } |
| } |
| #endif |
| |
| /* TODO: If the mutual strength heatmap was already read into the touch |
| * offload interface, use it here instead of reading again. |
| */ |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP) |
| if (processed_pointer_event) { |
| heatmap_read(&ts->v4l2, ktime_to_ns(ts->timestamp)); |
| |
| /* palm */ |
| if (last_tid_palm_state == 0 && |
| ts->tid_palm_state >= 1) { |
| input_info(true, &ts->client->dev, |
| "COORD: detect palm enter(tid 0x0 -> %#x)\n", |
| ts->tid_palm_state); |
| } |
| if (last_tid_palm_state >= 1 && |
| ts->tid_palm_state == 0) { |
| input_info(true, &ts->client->dev, |
| "COORD: detect palm leave(tid %#x -> 0x0), tid_touch %#x\n", |
| last_tid_palm_state, ts->tid_touch_state); |
| if (ts->touch_count || ts->tid_touch_state) { |
| ts->palms_leaved_once = true; |
| input_dbg(true, &ts->client->dev, |
| "COORD: wait all finger(s) release after palm entered\n"); |
| } |
| } |
| /* grip */ |
| if (last_tid_grip_state == 0 && |
| ts->tid_grip_state >= 1) { |
| input_info(true, &ts->client->dev, |
| "COORD: detect grip enter(tid 0x0 -> %#x)\n", |
| ts->tid_grip_state); |
| } |
| if (last_tid_grip_state >= 1 && |
| ts->tid_grip_state == 0) { |
| input_info(true, &ts->client->dev, |
| "COORD: detect grip leave(tid %#x -> 0x0), tid_touch %#x\n", |
| last_tid_grip_state, ts->tid_touch_state); |
| if (ts->touch_count || ts->tid_touch_state) { |
| ts->grips_leaved_once = true; |
| input_dbg(true, &ts->client->dev, |
| "COORD: wait all finger(s) release after grip entered\n"); |
| } |
| } |
| if ((ts->touch_count == 0 || ts->tid_touch_state == 0) && |
| (ts->palms_leaved_once || ts->grips_leaved_once)) { |
| ts->palms_leaved_once = false; |
| ts->grips_leaved_once = false; |
| input_info(true, &ts->client->dev, |
| "COORD: all fingers released with palm(s)/grip(s) leaved once\n"); |
| } |
| } |
| #endif |
| } |
| |
| static irqreturn_t sec_ts_isr(int irq, void *handle) |
| { |
| struct sec_ts_data *ts = (struct sec_ts_data *)handle; |
| |
| ts->timestamp = ktime_get(); |
| #if !IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| input_set_timestamp(ts->input_dev, ts->timestamp); |
| #endif |
| |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static irqreturn_t sec_ts_irq_thread(int irq, void *ptr) |
| { |
| struct sec_ts_data *ts = (struct sec_ts_data *)ptr; |
| |
| if (sec_ts_set_bus_ref(ts, SEC_TS_BUS_REF_IRQ, true) < 0) { |
| /* Interrupt during bus suspend */ |
| input_info(true, &ts->client->dev, |
| "%s: Skipping stray interrupt since bus is suspended(power_status: %d)\n", |
| __func__, ts->power_status); |
| return IRQ_HANDLED; |
| } |
| |
| /* prevent CPU from entering deep sleep */ |
| pm_qos_update_request(&ts->pm_qos_req, 100); |
| pm_wakeup_event(&ts->client->dev, MSEC_PER_SEC); |
| |
| sec_ts_read_event(ts); |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP) |
| /* Disable the firmware motion filter during single touch */ |
| update_motion_filter(ts); |
| #endif |
| |
| pm_qos_update_request(&ts->pm_qos_req, PM_QOS_DEFAULT_VALUE); |
| |
| sec_ts_set_bus_ref(ts, SEC_TS_BUS_REF_IRQ, false); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) |
| static void sec_ts_offload_report(void *handle, |
| struct TouchOffloadIocReport *report) |
| { |
| struct sec_ts_data *ts = (struct sec_ts_data *)handle; |
| bool touch_down = 0; |
| int i; |
| |
| mutex_lock(&ts->eventlock); |
| |
| input_set_timestamp(ts->input_dev, report->timestamp); |
| |
| for (i = 0; i < MAX_COORDS; i++) { |
| if (report->coords[i].status != COORD_STATUS_INACTIVE) { |
| int mt_tool = MT_TOOL_FINGER; |
| |
| input_mt_slot(ts->input_dev, i); |
| touch_down = 1; |
| input_report_key(ts->input_dev, BTN_TOUCH, |
| touch_down); |
| |
| if (report->coords[i].status == COORD_STATUS_EDGE || |
| report->coords[i].status == COORD_STATUS_PALM || |
| report->coords[i].status == COORD_STATUS_CANCEL) |
| mt_tool = MT_TOOL_PALM; |
| |
| input_mt_report_slot_state(ts->input_dev, |
| mt_tool, 1); |
| input_report_abs(ts->input_dev, ABS_MT_POSITION_X, |
| report->coords[i].x); |
| input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, |
| report->coords[i].y); |
| input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, |
| report->coords[i].major); |
| input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, |
| report->coords[i].minor); |
| if (ts->plat_data->support_mt_pressure) |
| input_report_abs(ts->input_dev, |
| ABS_MT_PRESSURE, |
| report->coords[i].pressure); |
| input_report_abs(ts->input_dev, ABS_MT_ORIENTATION, |
| report->coords[i].rotation); |
| } else { |
| input_mt_slot(ts->input_dev, i); |
| input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); |
| input_mt_report_slot_state(ts->input_dev, |
| MT_TOOL_FINGER, 0); |
| input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, |
| -1); |
| input_report_abs(ts->input_dev, ABS_MT_ORIENTATION, 0); |
| } |
| } |
| |
| input_report_key(ts->input_dev, BTN_TOUCH, touch_down); |
| |
| input_sync(ts->input_dev); |
| |
| mutex_unlock(&ts->eventlock); |
| } |
| #endif /* CONFIG_TOUCHSCREEN_OFFLOAD */ |
| |
| int get_tsp_status(void) |
| { |
| return 0; |
| } |
| EXPORT_SYMBOL(get_tsp_status); |
| |
| int sec_ts_glove_mode_enables(struct sec_ts_data *ts, int mode) |
| { |
| int ret; |
| |
| if (mode) |
| ts->touch_functions = (ts->touch_functions | |
| SEC_TS_BIT_SETFUNC_GLOVE | |
| SEC_TS_DEFAULT_ENABLE_BIT_SETFUNC); |
| else |
| ts->touch_functions = ((ts->touch_functions & |
| (~SEC_TS_BIT_SETFUNC_GLOVE)) | |
| SEC_TS_DEFAULT_ENABLE_BIT_SETFUNC); |
| |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: pwr off, glove:%d, status:%x\n", __func__, |
| mode, ts->touch_functions); |
| goto glove_enable_err; |
| } |
| |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_TOUCHFUNCTION, |
| (u8 *)&ts->touch_functions, 2); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command", __func__); |
| goto glove_enable_err; |
| } |
| |
| input_info(true, &ts->client->dev, |
| "%s: glove:%d, status:%x\n", __func__, |
| mode, ts->touch_functions); |
| |
| return 0; |
| |
| glove_enable_err: |
| return -EIO; |
| } |
| EXPORT_SYMBOL(sec_ts_glove_mode_enables); |
| |
| int sec_ts_set_cover_type(struct sec_ts_data *ts, bool enable) |
| { |
| int ret; |
| |
| input_info(true, &ts->client->dev, "%s: %d\n", |
| __func__, ts->cover_type); |
| |
| |
| switch (ts->cover_type) { |
| case SEC_TS_VIEW_WIRELESS: |
| case SEC_TS_VIEW_COVER: |
| case SEC_TS_VIEW_WALLET: |
| case SEC_TS_FLIP_WALLET: |
| case SEC_TS_LED_COVER: |
| case SEC_TS_MONTBLANC_COVER: |
| case SEC_TS_CLEAR_FLIP_COVER: |
| case SEC_TS_QWERTY_KEYBOARD_EUR: |
| case SEC_TS_QWERTY_KEYBOARD_KOR: |
| ts->cover_cmd = (u8)ts->cover_type; |
| break; |
| case SEC_TS_CHARGER_COVER: |
| case SEC_TS_COVER_NOTHING1: |
| case SEC_TS_COVER_NOTHING2: |
| default: |
| ts->cover_cmd = 0; |
| input_err(true, &ts->client->dev, |
| "%s: not chage touch state, %d\n", |
| __func__, ts->cover_type); |
| break; |
| } |
| |
| if (enable) |
| ts->touch_functions = (ts->touch_functions | |
| SEC_TS_BIT_SETFUNC_COVER | |
| SEC_TS_DEFAULT_ENABLE_BIT_SETFUNC); |
| else |
| ts->touch_functions = ((ts->touch_functions & |
| (~SEC_TS_BIT_SETFUNC_COVER)) | |
| SEC_TS_DEFAULT_ENABLE_BIT_SETFUNC); |
| |
| if (ts->power_status == SEC_TS_STATE_POWER_OFF) { |
| input_err(true, &ts->client->dev, |
| "%s: pwr off, close:%d, status:%x\n", __func__, |
| enable, ts->touch_functions); |
| goto cover_enable_err; |
| } |
| |
| if (enable) { |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_COVERTYPE, |
| &ts->cover_cmd, 1); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send covertype command: %d", |
| __func__, ts->cover_cmd); |
| goto cover_enable_err; |
| } |
| } |
| |
| ret = sec_ts_write(ts, SEC_TS_CMD_SET_TOUCHFUNCTION, |
| (u8 *)&(ts->touch_functions), 2); |
| if (ret < 0) { |
| input_err(true, &ts->client->dev, |
| "%s: Failed to send command", __func__); |
| goto cover_enable_err; |
| } |
| |
| input_info(true, &ts->client->dev, |
| "%s: close:%d, status:%x\n", __func__, |
| enable, ts->touch_functions); |
| |
| return 0; |
| |
| cover_enable_err: |
| return -EIO; |
| |
| |
| } |
| EXPORT_SYMBOL(sec_ts_set_cover_type); |
| |
| void sec_ts_set_grip_type(struct sec_ts_data *ts, u8 set_type) |
| { |
| u8 mode = G_NONE; |
| |
| input_info(true, &ts->client->dev, |
| "%s: re-init grip(%d), edh:%d, edg:%d, lan:%d\n", __func__, |
| set_type, ts->grip_edgehandler_direction, ts->grip_edge_range, |
| ts->grip_landscape_mode); |
| |
| /* edge handler */ |
| if (ts->grip_edgehandler_direction != 0) |
| mode |= G_SET_EDGE_HANDLER; |
| |
| if (set_type == GRIP_ALL_DATA) { |
| /* edge */ |
| if (ts->grip_edge_range != 60) |
| mode |= G_SET_EDGE_ZONE; |
| |
| /* dead zone */ |
| if (ts->grip_landscape_mode == 1) /* default 0 mode, 32 */ |
| mode |= G_SET_LANDSCAPE_MODE; |
| else |
| mode |= G_SET_NORMAL_MODE; |
| } |
| |
| if (mode) |
| set_grip_data_to_ic(ts, mode); |
| |
| } |
| |
| /* for debugging--------------------------------------------------------------*/ |
| |
| static int sec_ts_pinctrl_configure(struct sec_ts_data *ts, bool enable) |
| { |
| struct pinctrl_state *state; |
| |
| input_info(true, &ts->client->dev, "%s: %s\n", |
| __func__, enable ? "ACTIVE" : "SUSPEND"); |
| |
| if (enable) { |
| state = pinctrl_lookup_state(ts->plat_data->pinctrl, |
| "on_state"); |
|