| /* |
| * wacom_i2c.c - Wacom Digitizer Controller (I2C bus) |
| * |
| * |
| * 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., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/i2c.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/firmware.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/of_gpio.h> |
| #include <linux/sec_sysfs.h> |
| #include <linux/usb/manager/usb_typec_manager_notifier.h> |
| |
| #include "wacom.h" |
| |
| #define WACOM_FW_SIZE 131072 |
| |
| #define COM_QUERY_RETRY 3 |
| #define WACOM_I2C_RETRY 3 |
| |
| #define WACOM_FW_PATH_SDCARD "/sdcard/FIRMWARE/WACOM/wacom_firm.fw" |
| |
| |
| static struct wacom_features wacom_feature_EMR = { |
| .comstat = COM_QUERY, |
| .fw_version = 0x0, |
| .update_status = FW_UPDATE_PASS, |
| }; |
| |
| struct wacom_i2c *wacom_get_drv_data(void *data) |
| { |
| static void *drv_data; |
| |
| if (unlikely(data)) |
| drv_data = data; |
| |
| return (struct wacom_i2c *)drv_data; |
| } |
| |
| int coordc; |
| |
| void forced_release(struct wacom_i2c *wac_i2c) |
| { |
| input_info(true, &wac_i2c->client->dev, "%s\n", __func__); |
| |
| if (wac_i2c->dex_mode & DEX_MODE_MOUSE) { |
| input_report_key(wac_i2c->input_dev, BTN_LEFT, 0); |
| input_report_rel(wac_i2c->input_dev, REL_X, 0); |
| input_report_rel(wac_i2c->input_dev, REL_Y, 0); |
| input_report_key(wac_i2c->input_dev, BTN_RIGHT, 0); |
| } else { |
| input_report_abs(wac_i2c->input_dev, ABS_X, 0); |
| input_report_abs(wac_i2c->input_dev, ABS_Y, 0); |
| input_report_key(wac_i2c->input_dev, BTN_STYLUS, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, 0); |
| } |
| |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_abs(wac_i2c->input_dev, ABS_DISTANCE, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOOL_RUBBER, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOOL_PEN, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| wac_i2c->pen_prox = 0; |
| wac_i2c->pen_pressed = 0; |
| wac_i2c->side_pressed = 0; |
| wac_i2c->mcount = 0; |
| wac_i2c->virtual_tracking = EPEN_POS_NONE; |
| /*wac_i2c->pen_pdct = PDCT_NOSIGNAL; */ |
| } |
| |
| int wacom_i2c_send(struct wacom_i2c *wac_i2c, |
| const char *buf, int count, bool mode) |
| { |
| struct i2c_client *client = mode ? wac_i2c->client_boot : wac_i2c->client; |
| int retry = WACOM_I2C_RETRY; |
| int ret; |
| |
| do { |
| if (!wac_i2c->power_enable) { |
| input_err(true, &wac_i2c->client->dev, "%s: Power status off\n", |
| __func__); |
| return -EIO; |
| } |
| |
| ret = i2c_master_send(client, buf, count); |
| if (ret == count) |
| break; |
| |
| if (retry < WACOM_I2C_RETRY) { |
| input_err(true, &wac_i2c->client->dev, "%s: I2C retry(%d)\n", |
| __func__, WACOM_I2C_RETRY - retry); |
| wac_i2c->i2c_fail_count++; |
| } |
| } while (--retry); |
| |
| return ret; |
| } |
| |
| int wacom_i2c_recv(struct wacom_i2c *wac_i2c, char *buf, int count, bool mode) |
| { |
| struct i2c_client *client = mode ? wac_i2c->client_boot : wac_i2c->client; |
| int retry = WACOM_I2C_RETRY; |
| int ret; |
| |
| do { |
| if (!wac_i2c->power_enable) { |
| input_err(true, &wac_i2c->client->dev, "%s: Power status off\n", |
| __func__); |
| return -EIO; |
| } |
| |
| ret = i2c_master_recv(client, buf, count); |
| if (ret == count) |
| break; |
| |
| if (retry < WACOM_I2C_RETRY) { |
| input_err(true, &wac_i2c->client->dev, "%s: I2C retry(%d)\n", |
| __func__, WACOM_I2C_RETRY - retry); |
| wac_i2c->i2c_fail_count++; |
| } |
| } while (--retry); |
| |
| return ret; |
| } |
| |
| int wacom_checksum(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int ret = 0, retry = 10; |
| int i = 0; |
| u8 buf[5] = { 0, }; |
| |
| buf[0] = COM_CHECKSUM; |
| |
| while (retry--) { |
| ret = wacom_i2c_send(wac_i2c, &buf[0], 1, |
| WACOM_I2C_MODE_NORMAL); |
| if (ret < 0) { |
| input_err(true, &client->dev, "i2c fail, retry, %d\n", |
| __LINE__); |
| continue; |
| } |
| |
| msleep(200); |
| |
| ret = wacom_i2c_recv(wac_i2c, buf, 5, WACOM_I2C_MODE_NORMAL); |
| if (ret < 0) { |
| input_err(true, &client->dev, "i2c fail, retry, %d\n", |
| __LINE__); |
| continue; |
| } |
| |
| if (buf[0] == 0x1f) |
| break; |
| |
| input_info(true, &client->dev, "buf[0]: 0x%x, checksum retry\n", |
| buf[0]); |
| } |
| |
| if (ret >= 0) { |
| input_info(true, &client->dev, |
| "received checksum %x, %x, %x, %x, %x\n", buf[0], |
| buf[1], buf[2], buf[3], buf[4]); |
| } |
| |
| for (i = 0; i < 5; ++i) { |
| if (buf[i] != wac_i2c->fw_chksum[i]) { |
| input_info(true, &client->dev, |
| "checksum fail %dth %x %x\n", i, buf[i], |
| wac_i2c->fw_chksum[i]); |
| break; |
| } |
| } |
| |
| wac_i2c->checksum_result = (i == 5); |
| |
| return wac_i2c->checksum_result; |
| } |
| |
| int wacom_i2c_query(struct wacom_i2c *wac_i2c) |
| { |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| struct wacom_features *wac_feature = wac_i2c->wac_feature; |
| struct i2c_client *client = wac_i2c->client; |
| u8 data[COM_QUERY_BUFFER] = { 0, }; |
| u8 *query = data + COM_QUERY_POS; |
| int read_size = COM_QUERY_BUFFER; |
| int ret; |
| int i; |
| int max_x, max_y, pressure, height, x_tilt, y_tilt; |
| |
| for (i = 0; i < COM_QUERY_RETRY; i++) { |
| ret = wacom_i2c_recv(wac_i2c, data, read_size, |
| WACOM_I2C_MODE_NORMAL); |
| if (ret < 0) { |
| input_err(true, &client->dev, "%s: failed to recv\n", |
| __func__); |
| continue; |
| } |
| |
| input_info(true, &client->dev, |
| "%s: %dth ret of wacom query=%d\n", __func__, i, |
| ret); |
| |
| if (read_size != ret) { |
| input_err(true, &client->dev, |
| "%s: read size error %d of %d\n", __func__, |
| ret, read_size); |
| continue; |
| } |
| |
| if (query[EPEN_REG_HEADER] == 0x0f) { |
| wac_feature->fw_version = |
| ((u16)query[EPEN_REG_FWVER1] << 8) + |
| query[EPEN_REG_FWVER2]; |
| break; |
| } |
| } |
| |
| input_info(true, &client->dev, |
| "%X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X\n", |
| query[0], query[1], query[2], query[3], query[4], |
| query[5], query[6], query[7], query[8], query[9], query[10], |
| query[11], query[12], query[13], query[14]); |
| |
| if (ret < 0) { |
| input_err(true, &client->dev, "%s: failed to read query\n", |
| __func__); |
| wac_feature->fw_version = 0; |
| wac_i2c->query_status = false; |
| |
| return ret; |
| } |
| |
| wac_i2c->query_status = true; |
| |
| max_x = ((u16)query[EPEN_REG_X1] << 8) + query[EPEN_REG_X2]; |
| max_y = ((u16)query[EPEN_REG_Y1] << 8) + query[EPEN_REG_Y2]; |
| pressure = |
| ((u16)query[EPEN_REG_PRESSURE1] << 8) + query[EPEN_REG_PRESSURE2]; |
| x_tilt = query[EPEN_REG_TILT_X]; |
| y_tilt = query[EPEN_REG_TILT_Y]; |
| height = query[EPEN_REG_HEIGHT]; |
| |
| if (!pdata->use_dt_coord) { |
| pdata->max_x = max_x; |
| pdata->max_y = max_y; |
| } |
| pdata->max_pressure = pressure; |
| pdata->max_x_tilt = x_tilt; |
| pdata->max_y_tilt = y_tilt; |
| pdata->max_height = height; |
| |
| input_info(true, &client->dev, "use_dt_coord=%d, max_x=%d max_y=%d, max_pressure=%d\n", |
| pdata->use_dt_coord, pdata->max_x, pdata->max_y, pdata->max_pressure); |
| input_info(true, &client->dev, "fw_version=0x%X\n", |
| wac_feature->fw_version); |
| input_info(true, &client->dev, "mpu %#x, bl %#x, tx %d, ty %d, h %d\n", |
| query[EPEN_REG_MPUVER], query[EPEN_REG_BLVER], x_tilt, |
| y_tilt, height); |
| |
| return 0; |
| } |
| |
| int wacom_i2c_set_sense_mode(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int retval; |
| char data[4] = { 0, 0, 0, 0 }; |
| |
| input_info(true, &wac_i2c->client->dev, "%s cmd mod(%d)\n", __func__, |
| wac_i2c->wcharging_mode); |
| |
| if (wac_i2c->wcharging_mode == 1) |
| data[0] = COM_LOW_SENSE_MODE; |
| else if (wac_i2c->wcharging_mode == 3) |
| data[0] = COM_LOW_SENSE_MODE2; |
| else { |
| /* it must be 0 */ |
| data[0] = COM_NORMAL_SENSE_MODE; |
| } |
| |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to send wacom i2c mode, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| msleep(60); |
| |
| data[0] = COM_SAMPLERATE_STOP; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send1, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| msleep(60); |
| |
| #if 0 /* temp block not to receive gabage irq by cmd */ |
| data[1] = COM_REQUEST_SENSE_MODE; |
| retval = wacom_i2c_send(wac_i2c, &data[1], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send2, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| msleep(60); |
| |
| retval = wacom_i2c_recv(wac_i2c, &data[2], 2, WACOM_I2C_MODE_NORMAL); |
| if (retval != 2) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c recv, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| input_info(true, &client->dev, "%s: mode:%X, %X\n", __func__, data[2], |
| data[3]); |
| |
| data[0] = COM_SAMPLERATE_STOP; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send3, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| msleep(60); |
| #endif |
| data[0] = COM_SAMPLERATE_START; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send4, %d\n", __func__, |
| retval); |
| return retval; |
| } |
| |
| msleep(60); |
| |
| return data[3]; |
| } |
| |
| void wacom_select_survey_mode(struct wacom_i2c *wac_i2c, bool enable) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| |
| if (enable) { |
| if (wac_i2c->epen_blocked || |
| (wac_i2c->battery_saving_mode && !(wac_i2c->function_result & EPEN_EVENT_PEN_OUT))) { |
| if (wac_i2c->pdata->use_garage) { |
| input_info(true, &client->dev, |
| "%s: %s & garage on. garage only mode\n", |
| __func__, |
| wac_i2c->epen_blocked ? "epen blocked" : "ps on & pen in"); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_GARAGE_ONLY); |
| } else { |
| input_info(true, &client->dev, |
| "%s: %s & garage off. power off\n", __func__, |
| wac_i2c->epen_blocked ? "epen blocked" : "ps on & pen in"); |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| wacom_power(wac_i2c, false); |
| |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| } |
| } else if (wac_i2c->survey_mode) { |
| input_info(true, &client->dev, "%s: exit aop mode\n", |
| __func__); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_NONE); |
| } else { |
| input_info(true, &client->dev, "%s: power on\n", |
| __func__); |
| |
| wacom_power(wac_i2c, true); |
| msleep(100); |
| |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| } |
| } else { |
| if (wac_i2c->epen_blocked || |
| (wac_i2c->battery_saving_mode && !(wac_i2c->function_result & EPEN_EVENT_PEN_OUT))) { |
| if (wac_i2c->pdata->use_garage) { |
| input_info(true, &client->dev, |
| "%s: %s & garage on. garage only mode\n", |
| __func__, |
| wac_i2c->epen_blocked ? "epen blocked" : "ps on & pen in"); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_GARAGE_ONLY); |
| } else { |
| input_info(true, &client->dev, |
| "%s: %s & garage off. power off\n", __func__, |
| wac_i2c->epen_blocked ? "epen blocked" : "ps on & pen in"); |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| wacom_power(wac_i2c, false); |
| |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| } |
| } else if (!(wac_i2c->function_set & EPEN_SETMODE_AOP)) { |
| if (wac_i2c->pdata->use_garage) { |
| input_info(true, &client->dev, |
| "%s: aop off & garage on. garage only mode\n", |
| __func__); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_GARAGE_ONLY); |
| } else { |
| input_info(true, &client->dev, |
| "%s: aop off & garage off. power off\n", |
| __func__); |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| wacom_power(wac_i2c, false); |
| |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| } |
| } else { |
| /* aop on => (aod : screen off memo = 1:1 or 1:0 or 0:1) |
| * double tab & hover + button event will be occurred, |
| * but some of them will be skipped at reporting by mode |
| */ |
| input_info(true, &client->dev, |
| "%s: aop on. aop mode\n", __func__); |
| |
| if (!wac_i2c->power_enable) { |
| input_info(true, &client->dev, "%s: power on\n", |
| __func__); |
| |
| wacom_power(wac_i2c, true); |
| msleep(100); |
| |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| } |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_GARAGE_AOP); |
| } |
| } |
| |
| if (wac_i2c->power_enable) { |
| input_info(true, &client->dev, "%s: screen %s, survey mode:%d, result:%d\n", |
| __func__, enable ? "on" : "off", |
| wac_i2c->survey_mode, |
| wac_i2c->function_result & EPEN_EVENT_SURVEY); |
| |
| if ((wac_i2c->function_result & EPEN_EVENT_SURVEY) != wac_i2c->survey_mode) { |
| input_err(true, &client->dev, "%s: survey mode cmd failed\n", |
| __func__); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, |
| wac_i2c->survey_mode); |
| } |
| } |
| } |
| |
| void wacom_i2c_set_survey_mode(struct wacom_i2c *wac_i2c, int mode) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int retval; |
| char data[4] = { 0, 0, 0, 0 }; |
| |
| switch (mode) { |
| case EPEN_SURVEY_MODE_NONE: |
| data[0] = COM_SURVEYEXIT; |
| break; |
| case EPEN_SURVEY_MODE_GARAGE_ONLY: |
| if (!wac_i2c->pdata->use_garage) { |
| input_err(true, &client->dev, |
| "%s: garage mode is not supported\n", __func__); |
| return; |
| } |
| |
| data[0] = COM_SURVEY_TARGET_GARAGEONLY; |
| break; |
| case EPEN_SURVEY_MODE_GARAGE_AOP: |
| data[0] = COM_SURVEYSCAN; |
| break; |
| default: |
| input_err(true, &client->dev, |
| "%s: wrong param %d\n", __func__, mode); |
| return; |
| } |
| |
| wac_i2c->survey_mode = mode; |
| input_info(true, &client->dev, "%s: ps %s & mode : %d cmd(0x%2X)\n", __func__, |
| wac_i2c->battery_saving_mode ? "on" : "off", mode, data[0]); |
| |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send survey, %d\n", |
| __func__, retval); |
| wac_i2c->reset_flag = true; |
| |
| return; |
| } |
| |
| if (mode) |
| msleep(35); |
| |
| wac_i2c->reset_flag = false; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| wac_i2c->function_result |= mode; |
| |
| return; |
| } |
| |
| static int keycode[] = { |
| KEY_RECENT, KEY_BACK, |
| }; |
| |
| void forced_release_key(struct wacom_i2c *wac_i2c) |
| { |
| input_info(true, &wac_i2c->client->dev, "%s : [%x/%x]!\n", |
| __func__, wac_i2c->soft_key_pressed[0], |
| wac_i2c->soft_key_pressed[1]); |
| |
| input_report_key(wac_i2c->input_dev, KEY_RECENT, 0); |
| input_report_key(wac_i2c->input_dev, KEY_BACK, 0); |
| input_sync(wac_i2c->input_dev); |
| } |
| |
| void wacom_i2c_softkey(struct wacom_i2c *wac_i2c, s16 key, s16 pressed) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| if (wac_i2c->block_softkey && pressed) { |
| cancel_delayed_work_sync(&wac_i2c->softkey_block_work); |
| input_info(true, &client->dev, "block p\n"); |
| return; |
| } else if (wac_i2c->block_softkey && !pressed) { |
| input_info(true, &client->dev, "block r\n"); |
| wac_i2c->block_softkey = false; |
| return; |
| } |
| #endif |
| input_report_key(wac_i2c->input_dev, keycode[key], pressed); |
| input_sync(wac_i2c->input_dev); |
| |
| wac_i2c->soft_key_pressed[key] = pressed; |
| if (pressed) |
| cancel_delayed_work_sync(&wac_i2c->fullscan_check_work); |
| |
| #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) |
| input_info(true, &client->dev, "%d %s\n", |
| keycode[key], !(!pressed) ? "PRESS" : "RELEASE"); |
| #else |
| input_info(true, &client->dev, "softkey %s\n", |
| !(!pressed) ? "PRESS" : "RELEASE"); |
| #endif |
| } |
| |
| void forced_release_fullscan(struct wacom_i2c *wac_i2c) |
| { |
| input_info(true, &wac_i2c->client->dev, "%s full scan OUT\n", __func__); |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| wac_i2c->fullscan_mode = false; |
| } |
| |
| int wacom_get_scan_mode(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int retval; |
| char data[COM_COORD_NUM + 1] = { 0, }; |
| int i, retry = 3, temp = 0, ret = 0; |
| |
| input_info(true, &wac_i2c->client->dev, "%s\n", __func__); |
| |
| data[0] = COM_SAMPLERATE_STOP; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send (stop), %d\n", |
| __func__, retval); |
| return -EIO; |
| } |
| |
| data[0] = COM_REQUESTSCANMODE; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send (request data), %d\n", |
| __func__, retval); |
| return -EIO; |
| } |
| msleep(35); |
| |
| while (retry--) { |
| retval = wacom_i2c_recv(wac_i2c, data, COM_COORD_NUM + 1, |
| WACOM_I2C_MODE_NORMAL); |
| if (retval < 0) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send survey, %d\n", |
| __func__, retval); |
| } |
| temp = 0; |
| for (i = 0; i <= COM_COORD_NUM; i++) |
| temp += data[i]; |
| |
| input_info(true, &client->dev, |
| "%x %x %x %x %x, %x %x %x %x %x, %x %x %x\n", |
| data[0], data[1], data[2], data[3], data[4], data[5], |
| data[6], data[7], data[8], data[9], data[10], |
| data[11], data[12]); |
| |
| if (temp == 0) { /* unlock */ |
| ret = EPEN_GLOBAL_SCAN_MODE; |
| break; |
| } else if (data[12] == 0x01) { /* send noise mode to tsp */ |
| ret = EPEN_HIGH_NOISE_MODE; |
| break; |
| } |
| msleep(10); |
| } |
| |
| input_info(true, &client->dev, |
| "data[0] = %x, data[12] =%x, retry(%d) ret(%d)\n", data[0], |
| data[12], 3 - retry, ret); |
| |
| data[0] = COM_SAMPLERATE_133; |
| retval = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (retval != 1) { |
| input_err(true, &client->dev, |
| "%s: failed to read wacom i2c send (start), %d\n", |
| __func__, retval); |
| return -EIO; |
| } |
| |
| return ret; |
| } |
| |
| bool wacom_get_status_data(struct wacom_i2c *wac_i2c, char *data) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int retval; |
| int retry = 5; |
| bool ret = false; |
| |
| input_info(true, &wac_i2c->client->dev, "%s\n", __func__); |
| |
| while (retry--) { |
| retval = |
| wacom_i2c_recv(wac_i2c, data, COM_COORD_NUM + 1, |
| WACOM_I2C_MODE_NORMAL); |
| if (retval < 0) { |
| input_err(true, &client->dev, |
| "%s: failed to read status data, %d\n", |
| __func__, retval); |
| } |
| |
| if (data[10] != data[11]) { |
| input_err(true, &client->dev, |
| "%s: invalid status data\n", __func__); |
| break; |
| } |
| |
| /* AOP status : BB BB = Button+Hover, DD DD = Double Tab gesture |
| * Noise status : 11 11 = High noise, 22 22 = Low noise |
| */ |
| if ((data[10] == AOP_BUTTON_HOVER) || (data[10] == AOP_DOUBLE_TAB) || |
| (data[10] == WACOM_NOISE_HIGH) || (data[10] == WACOM_NOISE_LOW)) { |
| ret = true; |
| break; |
| } |
| |
| /* READ IRQ status */ |
| if (wacom_get_irq_state(wac_i2c) <= 0) |
| break; |
| |
| msleep(10); |
| } |
| |
| input_info(true, &client->dev, |
| "data[10] = %x data[11] = %x, retry(%d), %s\n", data[10], |
| data[11], 5 - retry, ret ? "success" : "failed"); |
| |
| return ret; |
| } |
| |
| #ifdef LCD_FREQ_SYNC |
| void wacom_i2c_lcd_freq_check(struct wacom_i2c *wac_i2c, u8 *data) |
| { |
| u32 lcd_freq = 0; |
| |
| if (wac_i2c->lcd_freq_wait == false) { |
| lcd_freq = ((u16) data[10] << 8) + (u16) data[11]; |
| wac_i2c->lcd_freq = 2000000000 / (lcd_freq + 1); |
| wac_i2c->lcd_freq_wait = true; |
| schedule_work(&wac_i2c->lcd_freq_work); |
| } |
| } |
| #endif |
| |
| void wacom_fullscan_check_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, fullscan_check_work.work); |
| int ret; |
| |
| input_info(true, &wac_i2c->client->dev, |
| "fullscan_check_work irq-cnt(%d)\n", coordc); |
| if (coordc <= 2) { |
| ret = wacom_get_scan_mode(wac_i2c); |
| if (ret < 0) { |
| input_info(true, &wac_i2c->client->dev, "work - stay scan mode\n"); |
| } else if (ret == EPEN_GLOBAL_SCAN_MODE) { |
| input_info(true, &wac_i2c->client->dev, "work - full scan OUT\n"); |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| wac_i2c->fullscan_mode = false; |
| } else if (ret == EPEN_HIGH_NOISE_MODE) { |
| input_info(true, &wac_i2c->client->dev, "work - wacom noise mode\n"); |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_HIGH_NOISE_MODE); */ |
| } else { |
| input_err(true, &wac_i2c->client->dev, "unexpected status\n"); |
| } |
| } |
| } |
| |
| int wacom_i2c_coord(struct wacom_i2c *wac_i2c) |
| { |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| struct i2c_client *client = wac_i2c->client; |
| u8 data[COM_COORD_NUM + 1] = { 0, }; |
| bool prox = false; |
| bool rdy = false; |
| int ret = 0, tsp = 0; |
| int stylus; |
| static int old_x = 0, old_y = 0; |
| s16 x, y, pressure; |
| s16 tmp; |
| u8 gain = 0; |
| s16 softkey, pressed, keycode; |
| s8 tilt_x = 0; |
| s8 tilt_y = 0; |
| s8 retry = 3; |
| |
| while (retry--) { |
| ret = wacom_i2c_recv(wac_i2c, data, COM_COORD_NUM + 1, |
| WACOM_I2C_MODE_NORMAL); |
| if (ret >= 0) |
| break; |
| |
| input_err(true, &client->dev, |
| "%s failed to read i2c.retry %d.L%d\n", __func__, |
| retry, __LINE__); |
| } |
| |
| if (ret < 0) { |
| input_err(true, &client->dev, "i2c err, exit %s\n", __func__); |
| forced_release(wac_i2c); |
| wac_i2c->reset_flag = true; |
| if (work_busy(&wac_i2c->resume_work.work) == 0) { |
| input_err(true, &client->dev, |
| "%s schedule resume work\n", __func__); |
| cancel_delayed_work(&wac_i2c->resume_work); |
| schedule_delayed_work(&wac_i2c->resume_work, |
| msecs_to_jiffies(EPEN_RESUME_DELAY)); |
| } else { |
| input_err(true, &client->dev, |
| "%s resume work is busy\n", __func__); |
| } |
| return 0; |
| } |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| #if 0 |
| input_info(true, &client->dev, |
| "%x, %x, %x, %x, %x, %x, %x %x %x %x %x %x %x\n", data[0], |
| data[1], data[2], data[3], data[4], data[5], data[6], |
| data[7], data[8], data[9], data[10], data[11], data[12]); |
| #endif |
| #endif |
| |
| rdy = data[0] & 0x80; |
| |
| #ifdef LCD_FREQ_SYNC |
| if (!rdy && !data[1] && !data[2] && !data[3] && !data[4]) { |
| if (likely(wac_i2c->use_lcd_freq_sync)) { |
| if (unlikely(!wac_i2c->pen_prox)) |
| wacom_i2c_lcd_freq_check(wac_i2c, data); |
| } |
| } |
| #endif |
| if (coordc < 100) |
| coordc++; |
| |
| tsp = data[12] & 0x01; |
| |
| if (wac_i2c->pdata->table_swap && data[0] == TABLE_SWAP_DATA && |
| data[2] == 0 && data[3] == 0 && data[4] == 0) { |
| wac_i2c->dp_connect_state = data[1]; |
| input_info(true, &client->dev, |
| "%s: usb typec DP %sconnected\n", |
| __func__, wac_i2c->dp_connect_state ? "" : "dis"); |
| } |
| |
| if (!rdy && tsp && wac_i2c->fullscan_mode == false) { |
| input_info(true, &client->dev, |
| "%x, %x, %x, %x, %x, %x, %x %x %x %x %x %x %x coordc(%d)\n", |
| data[0], data[1], data[2], data[3], data[4], data[5], |
| data[6], data[7], data[8], data[9], data[10], |
| data[11], data[12], coordc); |
| if (data[10] == HSYNC_COUNTER_UMAGIC && data[11] == HSYNC_COUNTER_LMAGIC) { |
| input_info(true, &client->dev, |
| "x-y scan IN, rdy(%d) tsp(%d)\n", rdy, tsp); |
| wac_i2c->fullscan_mode = true; |
| if (wac_i2c->wacom_noise_state != WACOM_NOISE_HIGH) { |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_LOCAL_SCAN_MODE); */ |
| } else { |
| input_info(true, &client->dev, "high noise mode, skip tsp (F3 1)\n"); |
| } |
| |
| coordc = 0; |
| cancel_delayed_work_sync(&wac_i2c->fullscan_check_work); |
| schedule_delayed_work(&wac_i2c->fullscan_check_work, |
| msecs_to_jiffies(2000)); |
| } |
| } |
| |
| if (data[0] == 0x0F) { |
| input_info(true, &client->dev, |
| "%x %x %x %x %x, %x %x %x %x %x, %x %x %x\n", |
| data[0], data[1], data[2], data[3], data[4], data[5], |
| data[6], data[7], data[8], data[9], data[10], |
| data[11], data[12]); |
| if ((data[10] == WACOM_NOISE_HIGH) && (data[11] == WACOM_NOISE_HIGH)) { |
| input_err(true, &wac_i2c->client->dev, "11 11 high-noise mode\n"); |
| wac_i2c->wacom_noise_state = WACOM_NOISE_HIGH; |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_HIGH_NOISE_MODE); */ |
| } else if ((data[10] == WACOM_NOISE_LOW) && (data[11] == WACOM_NOISE_LOW)) { |
| input_err(true, &wac_i2c->client->dev, "22 22 low-noise mode\n"); |
| wac_i2c->wacom_noise_state = WACOM_NOISE_LOW; |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| } |
| } |
| |
| if (rdy) { |
| /* checking softkey */ |
| softkey = !(!(data[12] & 0x80)); |
| if (unlikely(softkey)) { |
| if (unlikely(wac_i2c->pen_prox)) |
| forced_release(wac_i2c); |
| |
| pressed = !(!(data[12] & 0x40)); |
| keycode = (data[12] & 0x30) >> 4; |
| |
| if (!wac_i2c->pdata->use_virtual_softkey) |
| wacom_i2c_softkey(wac_i2c, keycode, pressed); |
| else |
| input_info(true, &client->dev, "wacom use virtual softkey\n"); |
| |
| return 0; |
| } |
| |
| prox = !(!(data[0] & 0x10)); |
| stylus = !(!(data[0] & 0x20)); |
| x = ((u16) data[1] << 8) + (u16) data[2]; |
| y = ((u16) data[3] << 8) + (u16) data[4]; |
| pressure = ((u16) data[5] << 8) + (u16) data[6]; |
| gain = data[7]; |
| tilt_x = (s8) data[9]; |
| tilt_y = -(s8) data[8]; |
| |
| /* origin */ |
| x = x - pdata->origin[0]; |
| y = y - pdata->origin[1]; |
| |
| /* change axis from wacom to lcd */ |
| if (pdata->x_invert) |
| x = pdata->max_x - x; |
| if (pdata->y_invert) |
| y = pdata->max_y - y; |
| |
| if (pdata->xy_switch) { |
| tmp = x; |
| x = y; |
| y = tmp; |
| } |
| |
| if (wac_i2c->keyboard_cover_mode == true) { |
| if (y > KEYBOARD_COVER_BOUNDARY) |
| wac_i2c->keyboard_area = true; |
| else |
| wac_i2c->keyboard_area = false; |
| |
| if (wac_i2c->keyboard_area == true && |
| wac_i2c->virtual_tracking == EPEN_POS_NONE) { |
| /* input_info(true, &client->dev, |
| *"skip - approching on keyboard area prox(%d)\n", prox); |
| */ |
| return 0; |
| } else if (wac_i2c->keyboard_area == true && |
| wac_i2c->virtual_tracking == EPEN_POS_COVER) { |
| /*input_info(true, &client->dev, |
| *"skip keyboard area prox(%d)\n", prox); |
| */ |
| return 0; |
| } |
| |
| /*input_info(true, &client->dev, |
| *"go through, prox(%d) y(%d) area(%d)\n", prox,y,wac_i2c->keyboard_area); |
| */ |
| } |
| |
| /* validation check */ |
| if (unlikely |
| (x < 0 || y < 0 || x > pdata->max_y || y > pdata->max_x)) { |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| input_info(true, &client->dev, "raw data x=%d, y=%d\n", |
| x, y); |
| #endif |
| return 0; |
| } |
| |
| if (wac_i2c->dex_mode & DEX_MODE_EDGE_CROP) { |
| int max_x = wac_i2c->pdata->max_x; |
| if (pdata->xy_switch) |
| max_x = wac_i2c->pdata->max_y; |
| |
| if (wac_i2c->dex_mode & DEX_MODE_MOUSE) { |
| if (x < max_x / 10) |
| x = max_x / 10; |
| else if (x > max_x * 9 / 10) |
| x = max_x * 9 / 10; |
| } else { |
| if (x < max_x / 10) |
| x = 0; |
| else if (x > max_x * 9 / 10) |
| x = max_x; |
| else |
| x = x * 5 / 4 - max_x / 8; |
| } |
| } |
| |
| if (wac_i2c->dex_mode & DEX_MODE_IRIS) { |
| int max_y = wac_i2c->pdata->max_y; |
| if (pdata->xy_switch) |
| max_y = wac_i2c->pdata->max_x; |
| |
| if (y < max_y / 2) { |
| x = old_x; |
| y = old_y; |
| } |
| } |
| |
| if (!wac_i2c->pen_prox) { |
| if (!wac_i2c->pdata->use_garage) { |
| /* check pdct */ |
| if (unlikely(wac_i2c->pen_pdct == PDCT_NOSIGNAL)) { |
| input_info(true, &client->dev, |
| "pdct is not active\n"); |
| return 0; |
| } |
| } |
| |
| wac_i2c->pen_prox = 1; |
| wac_i2c->virtual_tracking = EPEN_POS_VIEW; |
| |
| if (wac_i2c->dex_mode & DEX_MODE_MOUSE) { |
| input_report_rel(wac_i2c->input_dev, REL_X, 0); |
| input_report_rel(wac_i2c->input_dev, REL_Y, 0); |
| old_x = x; |
| old_y = y; |
| input_sync(wac_i2c->input_dev); |
| } |
| |
| if (data[0] & 0x40) |
| wac_i2c->tool = BTN_TOOL_RUBBER; |
| else |
| wac_i2c->tool = BTN_TOOL_PEN; |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| input_info(true, &client->dev, "hover in(%s)\n", |
| wac_i2c->tool == |
| BTN_TOOL_PEN ? "pen" : "rubber"); |
| #else |
| input_info(true, &client->dev, "hover in\n"); |
| #endif |
| return 0; |
| } |
| |
| if (wac_i2c->keyboard_cover_mode == true && |
| wac_i2c->keyboard_area == true && |
| wac_i2c->virtual_tracking == EPEN_POS_VIEW) { |
| input_report_key(wac_i2c->input_dev, BTN_STYLUS, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, 0); |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_abs(wac_i2c->input_dev, ABS_DISTANCE, 0); |
| input_report_key(wac_i2c->input_dev, |
| BTN_TOOL_RUBBER, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOOL_PEN, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| if (wac_i2c->pen_pressed) { |
| cancel_delayed_work_sync |
| (&wac_i2c->softkey_block_work); |
| wac_i2c->block_softkey = true; |
| schedule_delayed_work |
| (&wac_i2c->softkey_block_work, |
| SOFTKEY_BLOCK_DURATION); |
| } |
| #endif |
| input_info(true, &client->dev, |
| "virtual hover out by keyboard cover\n"); |
| wac_i2c->pen_prox = 0; |
| wac_i2c->pen_pressed = 0; |
| wac_i2c->side_pressed = 0; |
| wac_i2c->virtual_tracking = EPEN_POS_COVER; |
| return 0; |
| } |
| |
| /* report info */ |
| if (wac_i2c->dex_mode & DEX_MODE_MOUSE) { |
| input_report_key(wac_i2c->input_dev, BTN_LEFT, prox); |
| input_report_rel(wac_i2c->input_dev, REL_X, |
| x / wac_i2c->pdata->dex_rate - |
| (old_x / wac_i2c->pdata->dex_rate)); |
| input_report_rel(wac_i2c->input_dev, REL_Y, |
| y / wac_i2c->pdata->dex_rate - |
| (old_y / wac_i2c->pdata->dex_rate)); |
| input_report_key(wac_i2c->input_dev, BTN_RIGHT, stylus); |
| |
| } else { |
| input_report_abs(wac_i2c->input_dev, ABS_X, x); |
| input_report_abs(wac_i2c->input_dev, ABS_Y, y); |
| input_report_key(wac_i2c->input_dev, BTN_STYLUS, stylus); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, prox); |
| } |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, pressure); |
| input_report_abs(wac_i2c->input_dev, ABS_DISTANCE, gain); |
| input_report_abs(wac_i2c->input_dev, ABS_TILT_X, tilt_x); |
| input_report_abs(wac_i2c->input_dev, ABS_TILT_Y, tilt_y); |
| input_report_key(wac_i2c->input_dev, wac_i2c->tool, 1); |
| input_sync(wac_i2c->input_dev); |
| old_x = x; |
| old_y = y; |
| wac_i2c->mcount++; |
| |
| /* log */ |
| if (prox && !wac_i2c->pen_pressed) { |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| input_info(true, &client->dev, |
| "[P] x:%d, y:%d, mc:%d, pre:%d, tool:%x ps:%d dex:%d dp:%d\n", |
| x, y, wac_i2c->mcount, pressure, wac_i2c->tool, |
| wac_i2c->battery_saving_mode, wac_i2c->dex_mode, wac_i2c->dp_connect_state); |
| #else |
| input_info(true, &client->dev, "[P] mc:%d ps:%d dex:%d dp:%d\n", |
| wac_i2c->mcount, wac_i2c->battery_saving_mode, |
| wac_i2c->dex_mode, wac_i2c->dp_connect_state); |
| #endif |
| } else if (!prox && wac_i2c->pen_pressed) { |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| input_info(true, &client->dev, |
| "[R] x:%d, y:%d, mc:%d pre:%d, tool:%x dex:%d dp:%d [0x%x]\n", |
| x, y, wac_i2c->mcount, pressure, wac_i2c->tool, |
| wac_i2c->dex_mode, wac_i2c->dp_connect_state, |
| wac_i2c->wac_feature->fw_version); |
| #else |
| input_info(true, &client->dev, "[R] mc:%d dex:%d dp:%d [0x%x]\n", |
| wac_i2c->mcount, wac_i2c->dex_mode, wac_i2c->dp_connect_state, |
| wac_i2c->wac_feature->fw_version); |
| #endif |
| } |
| wac_i2c->pen_pressed = prox; |
| |
| /* check side */ |
| if (stylus && !wac_i2c->side_pressed) |
| input_info(true, &client->dev, "side on\n"); |
| else if (!stylus && wac_i2c->side_pressed) |
| input_info(true, &client->dev, "side off\n"); |
| |
| wac_i2c->side_pressed = stylus; |
| } else { |
| if (wac_i2c->pen_prox) { |
| if (wac_i2c->dex_mode & DEX_MODE_MOUSE) { |
| input_report_key(wac_i2c->input_dev, BTN_LEFT, 0); |
| input_report_key(wac_i2c->input_dev, BTN_RIGHT, 0); |
| } else { |
| input_report_key(wac_i2c->input_dev, BTN_STYLUS, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, 0); |
| } |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_abs(wac_i2c->input_dev, ABS_DISTANCE, 0); |
| input_report_key(wac_i2c->input_dev, |
| BTN_TOOL_RUBBER, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOOL_PEN, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| if (wac_i2c->pen_pressed) { |
| cancel_delayed_work_sync |
| (&wac_i2c->softkey_block_work); |
| wac_i2c->block_softkey = true; |
| schedule_delayed_work |
| (&wac_i2c->softkey_block_work, |
| SOFTKEY_BLOCK_DURATION); |
| } |
| #endif |
| |
| if (wac_i2c->pen_pressed) { |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| x = ((u16) data[1] << 8) + (u16) data[2]; |
| y = ((u16) data[3] << 8) + (u16) data[4]; |
| /* origin */ |
| x = x - pdata->origin[0]; |
| y = y - pdata->origin[1]; |
| |
| /* change axis from wacom to lcd */ |
| if (pdata->x_invert) |
| x = pdata->max_x - x; |
| if (pdata->y_invert) |
| y = pdata->max_y - y; |
| |
| if (pdata->xy_switch) { |
| tmp = x; |
| x = y; |
| y = tmp; |
| } |
| |
| input_info(true, &client->dev, |
| "[R] x:%d, y:%d, mc:%d dex:%d dp:%d [0x%x] & hover out\n", |
| x, y, wac_i2c->mcount, wac_i2c->dex_mode, |
| wac_i2c->dp_connect_state, wac_i2c->wac_feature->fw_version); |
| #else |
| input_info(true, &client->dev, "[R] mc:%d dex:%d dp:%d [0x%x] & hover out\n", |
| wac_i2c->mcount, wac_i2c->dex_mode, |
| wac_i2c->dp_connect_state, wac_i2c->wac_feature->fw_version); |
| #endif |
| } else { |
| input_info(true, &client->dev, "hover out mc:%d\n", wac_i2c->mcount); |
| } |
| |
| } |
| |
| if (!tsp) { |
| input_info(true, &client->dev, "full scan out (00 00)\n"); |
| if (wac_i2c->wacom_noise_state != WACOM_NOISE_HIGH) { |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| } else { |
| input_info(true, &client->dev, "high noise mode, skip tsp (F3 0)\n"); |
| } |
| |
| cancel_delayed_work_sync(&wac_i2c->fullscan_check_work); |
| wac_i2c->fullscan_mode = false; |
| } |
| |
| wac_i2c->pen_prox = 0; |
| wac_i2c->pen_pressed = 0; |
| wac_i2c->side_pressed = 0; |
| wac_i2c->mcount = 0; |
| wac_i2c->virtual_tracking = EPEN_POS_NONE; |
| } |
| |
| return 0; |
| } |
| |
| int wacom_power(struct wacom_i2c *wac_i2c, bool on) |
| { |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| struct i2c_client *client = wac_i2c->client; |
| static struct timeval off_time = { 0, 0 }; |
| struct timeval cur_time = { 0, 0 }; |
| int retval = 0; |
| static struct regulator *vddo; |
| |
| input_info(true, &client->dev, "power %s\n", |
| on ? "enabled" : "disabled"); |
| |
| if (!pdata) { |
| input_err(true, &client->dev, "%s, pdata is null\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (wac_i2c->power_enable == on) { |
| input_info(true, &client->dev, "pwr already %s\n", |
| on ? "enabled" : "disabled"); |
| return 0; |
| } |
| |
| if (on) { |
| long sec, usec; |
| |
| do_gettimeofday(&cur_time); |
| sec = cur_time.tv_sec - off_time.tv_sec; |
| usec = cur_time.tv_usec - off_time.tv_usec; |
| if (!sec) { |
| usec = EPEN_OFF_TIME_LIMIT - usec; |
| if (usec > 500) { |
| usleep_range(usec, usec); |
| input_info(true, &client->dev, |
| "%s, pwr on usleep %d\n", __func__, |
| (int)usec); |
| } |
| } |
| } |
| |
| if (!vddo) { |
| vddo = devm_regulator_get(&client->dev, "vddo"); |
| |
| if (IS_ERR(vddo)) { |
| input_err(true, &client->dev, |
| "%s: could not get vddo, rc = %ld\n", |
| __func__, PTR_ERR(vddo)); |
| vddo = NULL; |
| return -ENODEV; |
| } |
| retval = regulator_set_voltage(vddo, 3300000, 3300000); |
| if (retval) |
| input_err(true, &client->dev, |
| "%s: unable to set vddo voltage to 3.3V\n", |
| __func__); |
| input_err(true, &client->dev, "%s: 3.3V is enabled %s\n", |
| __func__, |
| regulator_is_enabled(vddo) ? "TRUE" : "FALSE"); |
| |
| } |
| |
| if (on) { |
| retval = regulator_enable(vddo); |
| if (retval) { |
| input_err(true, &client->dev, |
| "%s: Fail to enable regulator vddo[%d]\n", |
| __func__, retval); |
| } |
| input_err(true, &client->dev, "%s: vddo is enabled[OK]\n", |
| __func__); |
| } else { |
| if (regulator_is_enabled(vddo)) { |
| retval = regulator_disable(vddo); |
| if (retval) { |
| input_err(true, &client->dev, |
| "%s: Fail to disable regulator vddo[%d]\n", |
| __func__, retval); |
| |
| } |
| input_err(true, &client->dev, |
| "%s: vddo is disabled[OK]\n", __func__); |
| } else { |
| input_err(true, &client->dev, |
| "%s: vddo is already disabled\n", __func__); |
| } |
| } |
| |
| wac_i2c->power_enable = on; |
| |
| return 0; |
| } |
| |
| void wacom_reset_hw(struct wacom_i2c *wac_i2c) |
| { |
| wacom_power(wac_i2c, false); |
| /* recommended delay in spec */ |
| msleep(100); |
| wacom_power(wac_i2c, true); |
| |
| msleep(200); |
| } |
| |
| void wacom_compulsory_flash_mode(struct wacom_i2c *wac_i2c, bool enable) |
| { |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| |
| if (pdata) |
| gpio_direction_output(pdata->fwe_gpio, enable); |
| } |
| |
| int wacom_get_irq_state(struct wacom_i2c *wac_i2c) |
| { |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| int level; |
| |
| if (!pdata) |
| return -EINVAL; |
| |
| level = gpio_get_value(pdata->irq_gpio); |
| |
| if (pdata->irq_type & (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_LOW)) |
| return !level; |
| |
| return level; |
| } |
| |
| void wacom_enable_irq(struct wacom_i2c *wac_i2c, bool enable) |
| { |
| static int depth; |
| |
| mutex_lock(&wac_i2c->irq_lock); |
| if (enable) { |
| if (depth) { |
| --depth; |
| enable_irq(wac_i2c->irq); |
| } |
| } else { |
| if (!depth) { |
| ++depth; |
| disable_irq(wac_i2c->irq); |
| } |
| } |
| mutex_unlock(&wac_i2c->irq_lock); |
| |
| #ifdef WACOM_IRQ_DEBUG |
| input_info(true, &wac_i2c->client->dev, |
| "%s: Enable %d, depth %d\n", __func__, (int)enable, depth); |
| #endif |
| } |
| |
| void wacom_enable_pdct_irq(struct wacom_i2c *wac_i2c, bool enable) |
| { |
| static int depth; |
| |
| mutex_lock(&wac_i2c->irq_lock); |
| if (enable) { |
| if (depth) { |
| --depth; |
| enable_irq(wac_i2c->irq_pdct); |
| } |
| } else { |
| if (!depth) { |
| ++depth; |
| disable_irq(wac_i2c->irq_pdct); |
| } |
| } |
| mutex_unlock(&wac_i2c->irq_lock); |
| |
| #ifdef WACOM_IRQ_DEBUG |
| input_info(true, &wac_i2c->client->dev, |
| "%s: Enable %d, depth %d\n", __func__, (int)enable, depth); |
| #endif |
| } |
| |
| static void wacom_enable_irq_wake(struct wacom_i2c *wac_i2c, bool enable) |
| { |
| static int depth; |
| |
| if (enable) { |
| if (depth++ == 0) { |
| enable_irq_wake(wac_i2c->irq); |
| |
| if (wac_i2c->pdata->use_garage) |
| enable_irq_wake(wac_i2c->irq_pdct); |
| } |
| } else { |
| if (depth == 0) { |
| input_info(true, &wac_i2c->client->dev, |
| "Unbalanced IRQ wake disable\n"); |
| } else if (--depth == 0) { |
| disable_irq_wake(wac_i2c->irq); |
| |
| if (wac_i2c->pdata->use_garage) |
| disable_irq_wake(wac_i2c->irq_pdct); |
| } |
| } |
| } |
| |
| void wacom_wakeup_sequence(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| |
| mutex_lock(&wac_i2c->lock); |
| |
| input_info(true, &client->dev, |
| "%s: start, garage %s, ps %s, pen %s, epen %s, cover %s, count(%u,%u)[0x%x]\n", |
| __func__, wac_i2c->pdata->use_garage ? "used" : "unused", |
| wac_i2c->battery_saving_mode ? "on" : "off", |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", |
| wac_i2c->epen_blocked ? "blocked" : "unblocked", |
| wac_i2c->keyboard_cover_mode ? "on" : "off", |
| wac_i2c->i2c_fail_count, wac_i2c->abnormal_reset_count, |
| wac_i2c->wac_feature->fw_version); |
| |
| #ifdef CONFIG_SEC_FACTORY |
| if (wac_i2c->fac_garage_mode) |
| input_info(true, &client->dev, "%s: garage mode\n", __func__); |
| #endif |
| |
| if (wake_lock_active(&wac_i2c->fw_wakelock)) { |
| input_info(true, &client->dev, |
| "fw wake lock active. pass %s\n", __func__); |
| goto out_power_on; |
| } |
| |
| if (wac_i2c->screen_on) { |
| input_info(true, &client->dev, |
| "already enabled. pass %s\n", __func__); |
| goto out_power_on; |
| } |
| |
| cancel_delayed_work_sync(&wac_i2c->resume_work); |
| schedule_delayed_work(&wac_i2c->resume_work, |
| msecs_to_jiffies(EPEN_RESUME_DELAY)); |
| |
| if (device_may_wakeup(&client->dev)) |
| wacom_enable_irq_wake(wac_i2c, false); |
| |
| wac_i2c->screen_on = true; |
| |
| out_power_on: |
| mutex_unlock(&wac_i2c->lock); |
| |
| input_info(true, &client->dev, "%s: end\n", __func__); |
| } |
| |
| void wacom_sleep_sequence(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| int retry = 1; |
| |
| mutex_lock(&wac_i2c->lock); |
| |
| input_info(true, &client->dev, |
| "%s: start, garage %s, ps %s, pen %s, epen %s, cover %s, count(%u,%u), set(0x%x), ret(0x%x)[0x%x]\n", |
| __func__, wac_i2c->pdata->use_garage ? "used" : "unused", |
| wac_i2c->battery_saving_mode ? "on" : "off", |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", |
| wac_i2c->epen_blocked ? "blocked" : "unblocked", |
| wac_i2c->keyboard_cover_mode ? "on" : "off", |
| wac_i2c->i2c_fail_count, wac_i2c->abnormal_reset_count, |
| wac_i2c->function_set, wac_i2c->function_result, |
| wac_i2c->wac_feature->fw_version); |
| |
| #ifdef CONFIG_SEC_FACTORY |
| if (wac_i2c->fac_garage_mode) |
| input_info(true, &client->dev, "%s: garage mode\n", __func__); |
| |
| #endif |
| |
| if (wake_lock_active(&wac_i2c->fw_wakelock)) { |
| input_info(true, &client->dev, |
| "fw wake lock active. pass %s\n", __func__); |
| |
| goto out_power_off; |
| } |
| |
| if (!wac_i2c->screen_on) { |
| input_info(true, &client->dev, |
| "already disabled. pass %s\n", __func__); |
| goto out_power_off; |
| } |
| |
| forced_release_fullscan(wac_i2c); |
| |
| cancel_delayed_work_sync(&wac_i2c->resume_work); |
| cancel_delayed_work_sync(&wac_i2c->fullscan_check_work); |
| |
| #ifdef LCD_FREQ_SYNC |
| cancel_work_sync(&wac_i2c->lcd_freq_work); |
| cancel_delayed_work_sync(&wac_i2c->lcd_freq_done_work); |
| wac_i2c->lcd_freq_wait = false; |
| #endif |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| cancel_delayed_work_sync(&wac_i2c->softkey_block_work); |
| wac_i2c->block_softkey = false; |
| #endif |
| |
| reset: |
| if (wac_i2c->reset_flag) { |
| input_info(true, &client->dev, |
| "%s: IC reset start\n", __func__); |
| |
| wac_i2c->abnormal_reset_count++; |
| wac_i2c->reset_flag = false; |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| |
| wacom_reset_hw(wac_i2c); |
| |
| wac_i2c->pen_pdct = |
| gpio_get_value(wac_i2c->pdata->pdct_gpio); |
| |
| input_info(true, &client->dev, |
| "%s : IC reset end, pdct(%d)\n", __func__, |
| wac_i2c->pen_pdct); |
| |
| if (wac_i2c->pdata->use_garage) { |
| if (wac_i2c->pen_pdct) |
| wac_i2c->function_result &= ~EPEN_EVENT_PEN_OUT; |
| else |
| wac_i2c->function_result |= EPEN_EVENT_PEN_OUT; |
| } |
| |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| } |
| |
| wacom_select_survey_mode(wac_i2c, false); |
| |
| /* release pen, if it is pressed */ |
| if (wac_i2c->pen_pressed || wac_i2c->side_pressed || wac_i2c->pen_prox) |
| forced_release(wac_i2c); |
| |
| if (!wac_i2c->pdata->use_virtual_softkey) { |
| if (wac_i2c->soft_key_pressed[0] || wac_i2c->soft_key_pressed[1]) |
| forced_release_key(wac_i2c); |
| } |
| |
| if (wac_i2c->reset_flag && retry--) |
| goto reset; |
| |
| if (device_may_wakeup(&client->dev)) |
| wacom_enable_irq_wake(wac_i2c, true); |
| |
| wac_i2c->screen_on = false; |
| |
| out_power_off: |
| mutex_unlock(&wac_i2c->lock); |
| |
| input_info(true, &client->dev, "%s end\n", __func__); |
| } |
| |
| static irqreturn_t wacom_interrupt(int irq, void *dev_id) |
| { |
| struct wacom_i2c *wac_i2c = dev_id; |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| s16 x, y, pressure; |
| s16 tmp; |
| u8 gain = 0; |
| s8 tilt_x = 0; |
| s8 tilt_y = 0; |
| int ret = 0; |
| char data[COM_COORD_NUM + 1] = { 0, }; |
| |
| if (!wac_i2c->screen_on && wac_i2c->survey_mode) { |
| input_info(true, &wac_i2c->client->dev, |
| "%s: lcd off & survey mode on\n", __func__); |
| |
| /* in LPM, waiting blsp block resume */ |
| if (wac_i2c->pm_suspend) { |
| wake_lock_timeout(&wac_i2c->wakelock, |
| msecs_to_jiffies(3 * MSEC_PER_SEC)); |
| /* waiting for blsp block resuming, if not occurs |
| * i2c error |
| */ |
| ret = |
| wait_for_completion_interruptible_timeout( |
| &wac_i2c->resume_done, |
| msecs_to_jiffies(3 * MSEC_PER_SEC)); |
| if (ret == 0) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: LPM: pm resume is not handled [timeout]\n", |
| __func__); |
| return IRQ_HANDLED; |
| } |
| } |
| |
| ret = wacom_get_status_data(wac_i2c, data); |
| if (!ret) |
| return IRQ_HANDLED; |
| |
| if (wac_i2c->function_set & EPEN_SETMODE_AOP) { |
| if (data[10] == AOP_BUTTON_HOVER) { |
| if (wac_i2c->function_set & EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO) { |
| input_info(true, &wac_i2c->client->dev, "Hover & Side Button detected\n"); |
| |
| input_report_key(wac_i2c->input_dev, |
| KEY_WAKEUP_UNLOCK, 1); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_key(wac_i2c->input_dev, |
| KEY_WAKEUP_UNLOCK, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| x = ((u16) data[1] << 8) + (u16) data[2]; |
| y = ((u16) data[3] << 8) + (u16) data[4]; |
| |
| /* origin */ |
| x = x - pdata->origin[0]; |
| y = y - pdata->origin[1]; |
| /* change axis from wacom to lcd */ |
| if (pdata->x_invert) |
| x = pdata->max_x - x; |
| |
| if (pdata->y_invert) |
| y = pdata->max_y - y; |
| |
| if (pdata->xy_switch) { |
| tmp = x; |
| x = y; |
| y = tmp; |
| } |
| |
| wac_i2c->survey_pos.id = EPEN_POS_ID_SCREEN_OF_MEMO; |
| wac_i2c->survey_pos.x = x; |
| wac_i2c->survey_pos.y = y; |
| } else { |
| input_info(true, &wac_i2c->client->dev, |
| "AOP detected but skip report, screen_off_memo disabled\n"); |
| } |
| } else if (data[10] == AOP_DOUBLE_TAB) { |
| if (wac_i2c->function_set & EPEN_SETMODE_AOP_OPTION_AOD) { |
| input_info(true, &wac_i2c->client->dev, "Double Tab detected in AOD\n"); |
| |
| x = ((u16) data[1] << 8) + (u16) data[2]; |
| y = ((u16) data[3] << 8) + (u16) data[4]; |
| pressure = ((u16) data[5] << 8) + (u16) data[6]; |
| gain = data[7]; |
| tilt_x = (s8) data[9]; |
| tilt_y = -(s8) data[8]; |
| |
| /* origin */ |
| x = x - pdata->origin[0]; |
| y = y - pdata->origin[1]; |
| /* change axis from wacom to lcd */ |
| if (pdata->x_invert) |
| x = pdata->max_x - x; |
| |
| if (pdata->y_invert) |
| y = pdata->max_y - y; |
| |
| if (pdata->xy_switch) { |
| tmp = x; |
| x = y; |
| y = tmp; |
| } |
| |
| if (data[0] & 0x40) |
| wac_i2c->tool = BTN_TOOL_RUBBER; |
| else |
| wac_i2c->tool = BTN_TOOL_PEN; |
| |
| /* make press / release event for AOP double tab gesture */ |
| input_report_abs(wac_i2c->input_dev, ABS_X, x); |
| input_report_abs(wac_i2c->input_dev, ABS_Y, y); |
| input_report_key(wac_i2c->input_dev, wac_i2c->tool, 1); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, pressure); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, 1); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_abs(wac_i2c->input_dev, ABS_PRESSURE, 0); |
| input_report_key(wac_i2c->input_dev, BTN_TOUCH, 0); |
| input_sync(wac_i2c->input_dev); |
| |
| input_report_key(wac_i2c->input_dev, wac_i2c->tool, 0); |
| input_sync(wac_i2c->input_dev); |
| #ifndef CONFIG_SAMSUNG_PRODUCT_SHIP |
| input_info(true, &wac_i2c->client->dev, |
| "x(%d), y(%d) P / R event\n", x, y); |
| #else |
| input_info(true, &wac_i2c->client->dev, "P / R event\n"); |
| #endif |
| } else if (wac_i2c->function_set & EPEN_SETMODE_AOP_OPTION_AOT) { |
| input_info(true, &wac_i2c->client->dev, "Double Tab detected\n"); |
| |
| input_report_key(wac_i2c->input_dev, KEY_HOMEPAGE, 1); |
| input_sync(wac_i2c->input_dev); |
| input_report_key(wac_i2c->input_dev, KEY_HOMEPAGE, 0); |
| input_sync(wac_i2c->input_dev); |
| } else { |
| input_info(true, &wac_i2c->client->dev, |
| "AOP Double Tab detected but skip report, aod & aot disabled\n"); |
| } |
| } else { |
| input_info(true, &wac_i2c->client->dev, "unknown AOP status\n"); |
| } |
| } else { |
| input_info(true, &wac_i2c->client->dev, |
| "AOP Disabled \n"); |
| } |
| |
| if (data[0] == 0x0F) { |
| if (data[10] == WACOM_NOISE_HIGH) { |
| input_err(true, &wac_i2c->client->dev, "11 11 high-noise mode\n"); |
| wac_i2c->wacom_noise_state = WACOM_NOISE_HIGH; |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_HIGH_NOISE_MODE); */ |
| } else if (data[10] == WACOM_NOISE_LOW) { |
| input_err(true, &wac_i2c->client->dev, "22 22 low-noise mode\n"); |
| wac_i2c->wacom_noise_state = WACOM_NOISE_LOW; |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| wacom_i2c_coord(wac_i2c); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t wacom_interrupt_pdct(int irq, void *dev_id) |
| { |
| struct wacom_i2c *wac_i2c = dev_id; |
| struct i2c_client *client = wac_i2c->client; |
| int ret; |
| |
| if (wac_i2c->query_status == false) |
| return IRQ_HANDLED; |
| |
| wac_i2c->pen_pdct = gpio_get_value(wac_i2c->pdata->pdct_gpio); |
| |
| if (wac_i2c->pdata->use_garage) { |
| #if defined(CONFIG_SAMSUNG_PRODUCT_SHIP) |
| input_info(true, &client->dev, "%s: pen is %s garage\n", |
| __func__, wac_i2c->pen_pdct ? "IN " : "OUT of"); |
| #else |
| input_info(true, &client->dev, "%s: pen is %s garage(%d)\n", |
| __func__, wac_i2c->pen_pdct ? "IN" : "OUT of", |
| (wacom_get_irq_state(wac_i2c) > 0)); |
| #endif |
| if (wac_i2c->pen_pdct) |
| wac_i2c->function_result &= ~EPEN_EVENT_PEN_OUT; |
| else |
| wac_i2c->function_result |= EPEN_EVENT_PEN_OUT; |
| |
| if (!wac_i2c->screen_on) { |
| /* in LPM, waiting blsp block resume */ |
| if (wac_i2c->pm_suspend) { |
| wake_lock_timeout(&wac_i2c->wakelock, |
| msecs_to_jiffies(3 * MSEC_PER_SEC)); |
| /* waiting for blsp block resuming, if not occurs |
| * i2c error |
| */ |
| ret = |
| wait_for_completion_interruptible_timeout( |
| &wac_i2c->resume_done, |
| msecs_to_jiffies(3 * MSEC_PER_SEC)); |
| if (ret == 0) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: LPM: pm resume is not handled [timeout]\n", |
| __func__); |
| return IRQ_HANDLED; |
| } |
| } |
| } |
| |
| input_report_switch(wac_i2c->input_dev_pen, SW_PEN_INSERT, |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT)); |
| input_sync(wac_i2c->input_dev_pen); |
| |
| if (wac_i2c->epen_blocked || |
| (wac_i2c->battery_saving_mode && !(wac_i2c->function_result & EPEN_EVENT_PEN_OUT))) { |
| input_info(true, &client->dev, |
| "%s: %s & garage on. garage only mode\n", __func__, |
| wac_i2c->epen_blocked ? "epen blocked" : "ps on & pen in"); |
| wacom_i2c_set_survey_mode(wac_i2c, |
| EPEN_SURVEY_MODE_GARAGE_ONLY); |
| } else if (wac_i2c->screen_on && wac_i2c->survey_mode) { |
| input_info(true, &client->dev, |
| "%s: ps %s & pen %s & lcd on. normal mode\n", |
| __func__, |
| wac_i2c->battery_saving_mode? "on" : "off", |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in"); |
| |
| wacom_i2c_set_survey_mode(wac_i2c, EPEN_SURVEY_MODE_NONE); |
| } else { |
| input_info(true, &client->dev, |
| "%s: ps %s & pen %s & lcd %s. keep current mode(%s)\n", |
| __func__, |
| wac_i2c->battery_saving_mode? "on" : "off", |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", |
| wac_i2c->screen_on ? "on" : "off", |
| wac_i2c->function_result & EPEN_EVENT_SURVEY ? "survey" : "normal"); |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void pen_insert_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, pen_insert_dwork.work); |
| |
| if (wac_i2c->pdata->use_garage) { |
| wac_i2c->pen_pdct = gpio_get_value(wac_i2c->pdata->pdct_gpio); |
| |
| input_info(true, &wac_i2c->client->dev, "%s: pdct(%d)\n", |
| __func__, wac_i2c->pen_pdct); |
| |
| if (wac_i2c->pen_pdct) |
| wac_i2c->function_result &= ~EPEN_EVENT_PEN_OUT; |
| else |
| wac_i2c->function_result |= EPEN_EVENT_PEN_OUT; |
| } |
| |
| input_report_switch(wac_i2c->input_dev_pen, SW_PEN_INSERT, |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT)); |
| input_sync(wac_i2c->input_dev_pen); |
| |
| input_info(true, &wac_i2c->client->dev, "%s : pen is %s\n", __func__, |
| (wac_i2c->function_result & EPEN_EVENT_PEN_OUT) ? "OUT" : "IN"); |
| } |
| |
| static void init_pen_insert(struct wacom_i2c *wac_i2c) |
| { |
| INIT_DELAYED_WORK(&wac_i2c->pen_insert_dwork, pen_insert_work); |
| |
| /* update the current status */ |
| schedule_delayed_work(&wac_i2c->pen_insert_dwork, HZ * 5); |
| } |
| |
| static int wacom_i2c_input_open(struct input_dev *dev) |
| { |
| struct wacom_i2c *wac_i2c = input_get_drvdata(dev); |
| int ret = 0; |
| |
| input_info(true, &wac_i2c->client->dev, "%s(%s)\n", __func__, |
| dev->name); |
| |
| wacom_wakeup_sequence(wac_i2c); |
| |
| return ret; |
| } |
| |
| static void wacom_i2c_input_close(struct input_dev *dev) |
| { |
| struct wacom_i2c *wac_i2c = input_get_drvdata(dev); |
| |
| input_info(true, &wac_i2c->client->dev, "%s(%s)\n", __func__, |
| dev->name); |
| |
| wacom_sleep_sequence(wac_i2c); |
| } |
| |
| static void wacom_i2c_set_input_values(struct wacom_i2c *wac_i2c, |
| struct input_dev *input_dev, u8 propbit) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| struct wacom_g5_platform_data *pdata = wac_i2c->pdata; |
| /* Set input values before registering input device */ |
| |
| input_dev->id.bustype = BUS_I2C; |
| input_dev->dev.parent = &client->dev; |
| |
| input_dev->open = wacom_i2c_input_open; |
| input_dev->close = wacom_i2c_input_close; |
| |
| input_set_capability(input_dev, EV_KEY, BTN_TOOL_PEN); |
| input_set_capability(input_dev, EV_KEY, BTN_TOOL_RUBBER); |
| |
| input_set_abs_params(input_dev, ABS_PRESSURE, 0, pdata->max_pressure, |
| 0, 0); |
| input_set_abs_params(input_dev, ABS_DISTANCE, 0, pdata->max_height, |
| 0, 0); |
| input_set_abs_params(input_dev, ABS_TILT_X, -pdata->max_x_tilt, |
| pdata->max_x_tilt, 0, 0); |
| input_set_abs_params(input_dev, ABS_TILT_Y, -pdata->max_y_tilt, |
| pdata->max_y_tilt, 0, 0); |
| |
| /* AOP */ |
| input_set_capability(input_dev, EV_KEY, KEY_WAKEUP_UNLOCK); |
| input_set_capability(input_dev, EV_KEY, KEY_HOMEPAGE); |
| |
| if (propbit & DEX_MODE_MOUSE) { |
| input_set_capability(input_dev, EV_REL, REL_X); |
| input_set_capability(input_dev, EV_REL, REL_Y); |
| |
| input_set_capability(input_dev, EV_KEY, BTN_LEFT); |
| input_set_capability(input_dev, EV_KEY, BTN_RIGHT); |
| /* input_set_capability(input_dev, EV_KEY, BTN_MIDDLE); */ |
| } else { |
| int max_x, max_y; |
| |
| input_set_capability(input_dev, EV_SW, SW_PEN_INSERT); |
| input_set_capability(input_dev, EV_KEY, BTN_TOUCH); |
| input_set_capability(input_dev, EV_KEY, BTN_STYLUS); |
| |
| /* input_set_capability(input_dev, EV_KEY, KEY_UNKNOWN); */ |
| /* input_set_capability(input_dev, EV_KEY, BTN_TOOL_SPEN_SCAN); */ |
| /* input_set_capability(input_dev, EV_KEY, KEY_PEN_PDCT); */ |
| /* input_set_capability(input_dev, EV_KEY, BTN_STYLUS2); */ |
| /* input_set_capability(input_dev, EV_KEY, ABS_MISC); */ |
| |
| /* softkey */ |
| if (!pdata->use_virtual_softkey) { |
| input_set_capability(input_dev, EV_KEY, KEY_RECENT); |
| input_set_capability(input_dev, EV_KEY, KEY_BACK); |
| } |
| |
| max_x = pdata->max_x; |
| max_y = pdata->max_y; |
| |
| if (pdata->xy_switch) { |
| input_set_abs_params(input_dev, ABS_X, 0, max_y, 4, 0); |
| input_set_abs_params(input_dev, ABS_Y, 0, max_x, 4, 0); |
| } else { |
| input_set_abs_params(input_dev, ABS_X, 0, max_x, 4, 0); |
| input_set_abs_params(input_dev, ABS_Y, 0, max_y, 4, 0); |
| } |
| } |
| |
| input_set_drvdata(input_dev, wac_i2c); |
| } |
| |
| static void wacom_i2c_resume_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, resume_work.work); |
| struct i2c_client *client = wac_i2c->client; |
| u8 irq_state = 0; |
| int retry = 1; |
| int ret = 0; |
| |
| reset: |
| if (wac_i2c->reset_flag) { |
| input_info(true, &client->dev, |
| "%s: IC reset start\n", __func__); |
| |
| wac_i2c->abnormal_reset_count++; |
| wac_i2c->reset_flag = false; |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result &= ~EPEN_EVENT_SURVEY; |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| |
| wacom_reset_hw(wac_i2c); |
| |
| wac_i2c->pen_pdct = |
| gpio_get_value(wac_i2c->pdata->pdct_gpio); |
| |
| input_info(true, &client->dev, |
| "%s: IC reset end, pdct(%d)\n", __func__, |
| wac_i2c->pen_pdct); |
| |
| if (wac_i2c->pdata->use_garage) { |
| if (wac_i2c->pen_pdct) |
| wac_i2c->function_result &= ~EPEN_EVENT_PEN_OUT; |
| else |
| wac_i2c->function_result |= EPEN_EVENT_PEN_OUT; |
| } |
| |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| } |
| |
| wacom_select_survey_mode(wac_i2c, true); |
| |
| if (wac_i2c->reset_flag && retry--) |
| goto reset; |
| |
| if (wac_i2c->wcharging_mode) |
| wacom_i2c_set_sense_mode(wac_i2c); |
| |
| if (wac_i2c->tsp_noise_mode < 0) { |
| /* wac_i2c->tsp_noise_mode = set_spen_mode(EPEN_GLOBAL_SCAN_MODE); */ |
| } |
| |
| irq_state = wacom_get_irq_state(wac_i2c); |
| if (unlikely(irq_state > 0)) { |
| u8 data[COM_COORD_NUM + 1] = { 0, }; |
| |
| input_info(true, &client->dev, "%s: irq was enabled\n", |
| __func__); |
| |
| ret = wacom_i2c_recv(wac_i2c, data, COM_COORD_NUM + 1, |
| WACOM_I2C_MODE_NORMAL); |
| if (ret < 0) { |
| input_err(true, &client->dev, |
| "%s: failed to receive\n", __func__, |
| __LINE__); |
| } |
| |
| input_info(true, &client->dev, |
| "%x %x %x %x %x, %x %x %x %x %x, %x %x %x\n", |
| data[0], data[1], data[2], data[3], data[4], data[5], |
| data[6], data[7], data[8], data[9], data[10], |
| data[11], data[12]); |
| } |
| |
| ret = gpio_get_value(wac_i2c->pdata->pdct_gpio); |
| |
| input_info(true, &client->dev, |
| "%s: i(%d) p(%d) set(0x%x) ret(0x%x)\n", |
| __func__, irq_state, ret, wac_i2c->function_set, |
| wac_i2c->function_result); |
| } |
| |
| #ifdef LCD_FREQ_SYNC |
| #define SYSFS_WRITE_LCD "/sys/class/lcd/panel/ldi_fps" |
| static void wacom_i2c_sync_lcd_freq(struct wacom_i2c *wac_i2c) |
| { |
| int ret = 0; |
| mm_segment_t old_fs; |
| struct file *write_node; |
| char freq[12] = { 0, }; |
| int lcd_freq = wac_i2c->lcd_freq; |
| |
| mutex_lock(&wac_i2c->freq_write_lock); |
| |
| snprintf(freq, 12, "%d", lcd_freq); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| |
| /* write_node = filp_open(SYSFS_WRITE_LCD, O_RDONLY | O_SYNC, 0664); */ |
| write_node = filp_open(SYSFS_WRITE_LCD, O_RDWR | O_SYNC, 0664); |
| if (IS_ERR(write_node)) { |
| ret = PTR_ERR(write_node); |
| input_err(true, &wac_i2c->client->dev, |
| "%s: node file open fail, %d\n", __func__, ret); |
| goto err_open_node; |
| } |
| |
| ret = write_node->f_op->write(write_node, (char __user *)freq, |
| strlen(freq), &write_node->f_pos); |
| if (ret != strlen(freq)) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: Can't write node data\n", __func__); |
| } |
| input_info(true, &wac_i2c->client->dev, "%s write freq %s\n", |
| __func__, freq); |
| |
| filp_close(write_node, current->files); |
| |
| err_open_node: |
| set_fs(old_fs); |
| mutex_unlock(&wac_i2c->freq_write_lock); |
| |
| schedule_delayed_work(&wac_i2c->lcd_freq_done_work, HZ * 5); |
| } |
| |
| static void wacom_i2c_finish_lcd_freq_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, lcd_freq_done_work.work); |
| |
| wac_i2c->lcd_freq_wait = false; |
| |
| input_info(true, &wac_i2c->client->dev, "%s\n", __func__); |
| } |
| |
| static void wacom_i2c_lcd_freq_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, lcd_freq_work); |
| |
| wacom_i2c_sync_lcd_freq(wac_i2c); |
| } |
| #endif |
| |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| static void wacom_i2c_block_softkey_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, softkey_block_work.work); |
| |
| wac_i2c->block_softkey = false; |
| } |
| #endif |
| |
| int load_fw_built_in(struct wacom_i2c *wac_i2c, int fw_index) |
| { |
| int retry = 3; |
| int ret; |
| const char *fw_load_path = NULL; |
| |
| input_info(true, &wac_i2c->client->dev, "load_fw_built_in (%d)\n",fw_index); |
| |
| #ifdef CONFIG_SEC_FACTORY |
| if (fw_index == FW_FACTORY_PROC) |
| fw_load_path = wac_i2c->pdata->fw_fac_path; |
| else |
| #endif |
| fw_load_path = wac_i2c->pdata->fw_path; |
| |
| if (fw_load_path == NULL) { |
| input_err(true, wac_i2c->dev, |
| "Unable to open firmware. fw_path is NULL\n"); |
| return -EINVAL; |
| } |
| |
| while (retry--) { |
| ret = |
| request_firmware(&wac_i2c->firm_data, |
| fw_load_path, |
| &wac_i2c->client->dev); |
| if (ret < 0) { |
| input_err(true, &wac_i2c->client->dev, |
| "Unable to open firmware. ret %d retry %d\n", |
| ret, retry); |
| continue; |
| } |
| break; |
| } |
| |
| if (ret < 0) |
| return ret; |
| |
| wac_i2c->fw_img = (struct fw_image *)wac_i2c->firm_data->data; |
| |
| return ret; |
| } |
| |
| int load_fw_sdcard(struct wacom_i2c *wac_i2c) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| struct file *fp; |
| mm_segment_t old_fs; |
| long fsize, nread; |
| int ret = 0; |
| unsigned int nSize; |
| unsigned long nSize2; |
| u8 *ums_data; |
| |
| nSize = WACOM_FW_SIZE; |
| nSize2 = nSize + sizeof(struct fw_image); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| |
| fp = filp_open(WACOM_FW_PATH_SDCARD, O_RDONLY, S_IRUSR); |
| |
| if (IS_ERR(fp)) { |
| input_err(true, &client->dev, "failed to open %s.\n", |
| WACOM_FW_PATH_SDCARD); |
| ret = -ENOENT; |
| set_fs(old_fs); |
| return ret; |
| } |
| |
| fsize = fp->f_path.dentry->d_inode->i_size; |
| input_info(true, &client->dev, "start, file path %s, size %ld Bytes\n", |
| WACOM_FW_PATH_SDCARD, fsize); |
| |
| if ((fsize != nSize) && (fsize != nSize2)) { |
| input_err(true, &client->dev, |
| "UMS firmware size is different\n"); |
| ret = -EFBIG; |
| goto out; |
| } |
| |
| ums_data = kmalloc(fsize, GFP_KERNEL); |
| if (!ums_data) { |
| input_err(true, &client->dev, "%s, kmalloc failed\n", __func__); |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| nread = vfs_read(fp, (char __user *)ums_data, fsize, &fp->f_pos); |
| input_info(true, &client->dev, "nread %ld Bytes\n", nread); |
| if (nread != fsize) { |
| input_err(true, &client->dev, |
| "failed to read firmware file, nread %ld Bytes\n", |
| nread); |
| ret = -EIO; |
| kfree(ums_data); |
| goto out; |
| } |
| |
| filp_close(fp, current->files); |
| set_fs(old_fs); |
| |
| wac_i2c->fw_img = (struct fw_image *)ums_data; |
| |
| return 0; |
| |
| out: |
| filp_close(fp, current->files); |
| set_fs(old_fs); |
| return ret; |
| } |
| |
| int wacom_i2c_load_fw(struct wacom_i2c *wac_i2c, u8 fw_path) |
| { |
| int ret = 0; |
| struct fw_image *fw_img; |
| struct i2c_client *client = wac_i2c->client; |
| |
| switch (fw_path) { |
| case FW_BUILT_IN: |
| ret = load_fw_built_in(wac_i2c,FW_BUILT_IN); |
| break; |
| #ifdef CONFIG_SEC_FACTORY |
| case FW_FACTORY_PROC: |
| ret = load_fw_built_in(wac_i2c,FW_FACTORY_PROC); |
| break; |
| #endif |
| case FW_IN_SDCARD: |
| ret = load_fw_sdcard(wac_i2c); |
| break; |
| default: |
| input_info(true, &client->dev, "unknown path(%d)\n", fw_path); |
| goto err_load_fw; |
| } |
| |
| if (ret < 0) |
| goto err_load_fw; |
| |
| fw_img = wac_i2c->fw_img; |
| |
| /* header check */ |
| if (fw_img->hdr_ver == 1 && fw_img->hdr_len == sizeof(struct fw_image)) { |
| wac_i2c->fw_data = (u8 *) fw_img->data; |
| #if !defined(CONFIG_SEC_FACTORY) |
| if (fw_path == FW_BUILT_IN) { |
| #else |
| if ((fw_path == FW_BUILT_IN) || (fw_path == FW_FACTORY_PROC)) { |
| #endif |
| wac_i2c->fw_ver_file = fw_img->fw_ver1; |
| memcpy(wac_i2c->fw_chksum, fw_img->checksum, 5); |
| } |
| } else { |
| input_err(true, &client->dev, "no hdr\n"); |
| wac_i2c->fw_data = (u8 *) fw_img; |
| } |
| |
| return ret; |
| |
| err_load_fw: |
| wac_i2c->fw_data = NULL; |
| return ret; |
| } |
| |
| void wacom_i2c_unload_fw(struct wacom_i2c *wac_i2c) |
| { |
| switch (wac_i2c->fw_update_way) { |
| case FW_BUILT_IN: |
| #ifdef CONFIG_SEC_FACTORY |
| case FW_FACTORY_PROC: |
| #endif |
| release_firmware(wac_i2c->firm_data); |
| break; |
| case FW_IN_SDCARD: |
| kfree(wac_i2c->fw_img); |
| break; |
| default: |
| break; |
| } |
| |
| wac_i2c->fw_img = NULL; |
| wac_i2c->fw_update_way = FW_NONE; |
| wac_i2c->firm_data = NULL; |
| wac_i2c->fw_data = NULL; |
| } |
| |
| int wacom_fw_update(struct wacom_i2c *wac_i2c, u8 fw_update_way, bool bforced) |
| { |
| struct i2c_client *client = wac_i2c->client; |
| u32 fw_ver_ic = wac_i2c->wac_feature->fw_version; |
| int ret; |
| |
| input_info(true, &client->dev, "%s\n", __func__); |
| |
| if (wake_lock_active(&wac_i2c->fw_wakelock)) { |
| input_info(true, &client->dev, |
| "update is already running. pass\n"); |
| return 0; |
| } |
| |
| mutex_lock(&wac_i2c->update_lock); |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| |
| /* release pen, if it is pressed */ |
| if (wac_i2c->pen_pressed || wac_i2c->side_pressed || wac_i2c->pen_prox) |
| forced_release(wac_i2c); |
| |
| ret = wacom_i2c_load_fw(wac_i2c, fw_update_way); |
| if (ret < 0) { |
| input_info(true, &client->dev, "failed to load fw data\n"); |
| wac_i2c->wac_feature->update_status = FW_UPDATE_FAIL; |
| goto err_update_load_fw; |
| } |
| wac_i2c->fw_update_way = fw_update_way; |
| |
| /* firmware info */ |
| input_info(true, &client->dev, |
| "wacom fw ver : 0x%x, new fw ver : 0x%x\n", |
| wac_i2c->wac_feature->fw_version, wac_i2c->fw_ver_file); |
| |
| if (!bforced) { |
| if (fw_ver_ic == wac_i2c->fw_ver_file) { |
| input_info(true, &client->dev, "pass fw update\n"); |
| wac_i2c->do_crc_check = true; |
| /* need to check crc */ |
| } else if (fw_ver_ic > wac_i2c->fw_ver_file) { |
| input_info(true, &client->dev, |
| "dont need to update fw\n"); |
| goto out_update_fw; |
| } |
| |
| /* ic < file then update */ |
| } |
| |
| cancel_work_sync(&wac_i2c->update_work); |
| schedule_work(&wac_i2c->update_work); |
| mutex_unlock(&wac_i2c->update_lock); |
| |
| return 0; |
| |
| out_update_fw: |
| wacom_i2c_unload_fw(wac_i2c); |
| err_update_load_fw: |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| mutex_unlock(&wac_i2c->update_lock); |
| |
| return 0; |
| } |
| |
| static int wacom_i2c_remove(struct i2c_client *client); |
| |
| static void wacom_i2c_update_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = |
| container_of(work, struct wacom_i2c, update_work); |
| struct i2c_client *client = wac_i2c->client; |
| struct wacom_features *feature = wac_i2c->wac_feature; |
| int ret = 0; |
| int retry = 3; |
| |
| if (wac_i2c->fw_update_way == FW_NONE) |
| goto end_fw_update; |
| |
| wake_lock(&wac_i2c->fw_wakelock); |
| |
| /* CRC Check */ |
| if (wac_i2c->do_crc_check) { |
| wac_i2c->do_crc_check = false; |
| |
| ret = wacom_checksum(wac_i2c); |
| if (ret) { |
| input_info(true, &client->dev, "crc ok, do not update\n"); |
| goto err_update_fw; |
| } |
| |
| input_info(true, &client->dev, "crc err, do update\n"); |
| } |
| |
| feature->update_status = FW_UPDATE_RUNNING; |
| |
| while (retry--) { |
| ret = wacom_i2c_flash(wac_i2c); |
| if (ret) { |
| input_info(true, &client->dev, |
| "failed to flash fw(%d)\n", ret); |
| continue; |
| } |
| break; |
| } |
| if (ret) { |
| feature->update_status = FW_UPDATE_FAIL; |
| feature->fw_version = 0; |
| goto err_update_fw; |
| } |
| |
| ret = wacom_i2c_query(wac_i2c); |
| if (ret < 0) { |
| input_info(true, &client->dev, "failed to query to IC(%d)\n", |
| ret); |
| feature->update_status = FW_UPDATE_FAIL; |
| goto err_update_fw; |
| } |
| |
| feature->update_status = FW_UPDATE_PASS; |
| |
| err_update_fw: |
| wake_unlock(&wac_i2c->fw_wakelock); |
| end_fw_update: |
| wacom_i2c_unload_fw(wac_i2c); |
| |
| ret = wacom_open_test(wac_i2c); |
| if (ret) |
| input_err(true, &client->dev, "open test check failed\n"); |
| |
| wacom_enable_irq(wac_i2c, true); |
| wacom_enable_pdct_irq(wac_i2c, true); |
| |
| if (feature->update_status == FW_UPDATE_FAIL) { |
| input_err(true, &client->dev, |
| "%s : failed to download FW & unload wacom driver\n", __func__); |
| wacom_i2c_remove(wac_i2c->client); |
| } |
| } |
| |
| static void wacom_usb_typec_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = container_of(work, struct wacom_i2c, usb_typec_work.work); |
| char data[5] = { 0 }; |
| int ret; |
| |
| if (wac_i2c->dp_connect_state == wac_i2c->dp_connect_cmd) |
| return; |
| |
| if (!wac_i2c->power_enable) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: powered off now\n", __func__); |
| return; |
| } |
| |
| if (wake_lock_active(&wac_i2c->fw_wakelock)) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: fw update is running\n", __func__); |
| return; |
| } |
| |
| data[0] = COM_SAMPLERATE_STOP; |
| ret = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (ret != 1) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: failed to send stop cmd %d\n", |
| __func__, ret); |
| return; |
| } |
| |
| msleep(50); |
| |
| if (wac_i2c->dp_connect_cmd) |
| data[0] = COM_SPECIAL_COMPENSATION; |
| else |
| data[0] = COM_NORMAL_COMPENSATION; |
| |
| ret = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (ret != 1) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: failed to send table swap cmd %d\n", |
| __func__, ret); |
| return; |
| } |
| |
| msleep(30); |
| |
| data[0] = COM_SAMPLERATE_STOP; |
| ret = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (ret != 1) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: failed to send stop cmd %d\n", |
| __func__, ret); |
| return; |
| } |
| |
| data[0] = COM_SAMPLERATE_133; |
| ret = wacom_i2c_send(wac_i2c, &data[0], 1, WACOM_I2C_MODE_NORMAL); |
| if (ret != 1) { |
| input_err(true, &wac_i2c->client->dev, |
| "%s: failed to send start cmd, %d\n", |
| __func__, ret); |
| return; |
| } |
| |
| input_info(true, &wac_i2c->client->dev, "%s: %s\n", |
| __func__, wac_i2c->dp_connect_cmd ? "on" : "off"); |
| } |
| |
| static int wacom_usb_typec_notification_cb(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct wacom_i2c *wac_i2c = container_of(nb, struct wacom_i2c, typec_nb); |
| CC_NOTI_TYPEDEF usb_typec_info = *(CC_NOTI_TYPEDEF *)data; |
| |
| if (usb_typec_info.src != CCIC_NOTIFY_DEV_CCIC || |
| usb_typec_info.dest != CCIC_NOTIFY_DEV_DP || |
| usb_typec_info.id != CCIC_NOTIFY_ID_DP_CONNECT) |
| goto out; |
| |
| input_info(true, &wac_i2c->client->dev, |
| "%s: %sed (vid:0x%04X pid:0x%04X)\n", |
| __func__, usb_typec_info.sub1 ? "attach" : "detach", |
| usb_typec_info.sub2, usb_typec_info.sub3); |
| |
| switch (usb_typec_info.sub1) { |
| case CCIC_NOTIFY_ATTACH: |
| if (usb_typec_info.sub2 != 0x04E8 || |
| usb_typec_info.sub3 != 0xA020) |
| goto out; |
| break; |
| case CCIC_NOTIFY_DETACH: |
| break; |
| default: |
| input_err(true, &wac_i2c->client->dev, |
| "%s: invalid value %d\n", __func__, usb_typec_info.sub1); |
| goto out; |
| } |
| |
| cancel_delayed_work(&wac_i2c->usb_typec_work); |
| wac_i2c->dp_connect_cmd = usb_typec_info.sub1; |
| schedule_delayed_work(&wac_i2c->usb_typec_work, msecs_to_jiffies(1)); |
| out: |
| return 0; |
| } |
| |
| static void wacom_usb_typec_nb_register_work(struct work_struct *work) |
| { |
| struct wacom_i2c *wac_i2c = container_of(work, struct wacom_i2c, |
| typec_nb_reg_work.work); |
| int ret; |
| static int count; |
| |
| if (!wac_i2c || count > 100) |
| return; |
| |
| ret = manager_notifier_register(&wac_i2c->typec_nb, |
| wacom_usb_typec_notification_cb, |
| MANAGER_NOTIFY_CCIC_DP); |
| if (ret) { |
| count++; |
| schedule_delayed_work(&wac_i2c->typec_nb_reg_work, msecs_to_jiffies(10)); |
| } else { |
| input_err(true, &wac_i2c->client->dev, "%s: success\n", __func__); |
| } |
| } |
| |
| static int wacom_request_gpio(struct i2c_client *client, |
| struct wacom_g5_platform_data *pdata) |
| { |
| int ret; |
| |
| ret = devm_gpio_request(&client->dev, pdata->irq_gpio, "wacom_irq"); |
| if (ret) { |
| input_err(true, &client->dev, |
| "unable to request gpio for irq [%d]\n", |
| pdata->irq_gpio); |
| return ret; |
| } |
| |
| ret = devm_gpio_request(&client->dev, pdata->pdct_gpio, "wacom_pdct"); |
| if (ret) { |
| input_err(true, &client->dev, |
| "unable to request gpio for pdct [%d]\n", |
| pdata->pdct_gpio); |
| return ret; |
| } |
| |
| ret = devm_gpio_request(&client->dev, pdata->fwe_gpio, "wacom_fwe"); |
| if (ret) { |
| input_err(true, &client->dev, |
| "unable to request gpio for fwe [%d]\n", |
| pdata->fwe_gpio); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static struct wacom_g5_platform_data *wacom_parse_dt(struct i2c_client *client) |
| { |
| struct wacom_g5_platform_data *pdata; |
| struct device *dev = &client->dev; |
| struct device_node *np = dev->of_node; |
| u32 tmp[5] = { 0, }; |
| bool flag; |
| int ret; |
| |
| if (!np) |
| return ERR_PTR(-ENODEV); |
| |
| pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return ERR_PTR(-ENOMEM); |
| |
| pdata->irq_gpio = of_get_named_gpio(np, "wacom,irq-gpio", 0); |
| if (!gpio_is_valid(pdata->irq_gpio)) { |
| input_err(true, &client->dev, "failed to get irq-gpio\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| pdata->pdct_gpio = of_get_named_gpio(np, "wacom,pdct-gpio", 0); |
| if (!gpio_is_valid(pdata->pdct_gpio)) { |
| input_err(true, &client->dev, "failed to get pdct-gpio\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| pdata->fwe_gpio = of_get_named_gpio(np, "wacom,fwe-gpio", 0); |
| if (!gpio_is_valid(pdata->fwe_gpio)) { |
| input_err(true, &client->dev, "failed to get fwe-gpio\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| /* get features */ |
| ret = of_property_read_u32(np, "wacom,irq_type", tmp); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read trigger type %d\n", ret); |
| return ERR_PTR(-EINVAL); |
| } |
| pdata->irq_type = tmp[0]; |
| |
| ret = of_property_read_u32(np, "wacom,boot_addr", tmp); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read boot address %d\n", ret); |
| return ERR_PTR(-EINVAL); |
| } |
| pdata->boot_addr = tmp[0]; |
| |
| ret = of_property_read_u32_array(np, "wacom,origin", pdata->origin, 2); |
| if (ret) { |
| input_err(true, dev, "failed to read origin %d\n", ret); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| ret = of_property_read_u32_array(np, "wacom,max_coords", tmp, 2); |
| if (ret != -EINVAL) { |
| if (ret) { |
| input_err(true, dev, |
| "failed to read max coords %d\n", ret); |
| } |
| pdata->max_x = tmp[0]; |
| pdata->max_y = tmp[1]; |
| } |
| pdata->use_dt_coord = of_property_read_bool(np, "wacom,use_dt_coord"); |
| |
| ret = of_property_read_u32(np, "wacom,max_pressure", tmp); |
| if (ret != -EINVAL) { |
| if (ret) { |
| input_err(true, dev, |
| "failed to read max pressure %d\n", ret); |
| } |
| pdata->max_pressure = tmp[0]; |
| } |
| |
| ret = of_property_read_u32(np, "wacom,max_x_tilt", tmp); |
| if (ret != -EINVAL) { |
| if (ret) { |
| input_err(true, dev, |
| "failed to read max x tilt %d\n", ret); |
| } |
| pdata->max_x_tilt = tmp[0]; |
| } |
| |
| ret = of_property_read_u32(np, "wacom,max_y_tilt", tmp); |
| if (ret != -EINVAL) { |
| if (ret) { |
| input_err(true, dev, |
| "failed to read max y tilt %d\n", ret); |
| } |
| pdata->max_y_tilt = tmp[0]; |
| } |
| |
| ret = of_property_read_u32(np, "wacom,max_height", tmp); |
| if (ret != -EINVAL) { |
| if (ret) { |
| input_err(true, dev, |
| "failed to read max height %d\n", ret); |
| } |
| pdata->max_height = tmp[0]; |
| } |
| |
| ret = of_property_read_u32_array(np, "wacom,invert", tmp, 3); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read inverts %d\n", ret); |
| return ERR_PTR(-EINVAL); |
| } |
| pdata->x_invert = tmp[0]; |
| pdata->y_invert = tmp[1]; |
| pdata->xy_switch = tmp[2]; |
| |
| ret = of_property_read_u32(np, "wacom,ic_type", &pdata->ic_type); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read ic_type %d\n", ret); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| ret = of_property_read_string(np, "wacom,fw_path", &pdata->fw_path); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read fw_path %d\n", ret); |
| } |
| #ifdef CONFIG_SEC_FACTORY |
| ret = of_property_read_string(np, "wacom,fw_fac_path", |
| &pdata->fw_fac_path); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read fw_fac_path %d\n", ret); |
| } |
| #endif |
| ret = of_property_read_string_index(np, "wacom,project_name", 0, |
| &pdata->project_name); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read project name %d\n", ret); |
| } |
| |
| ret = of_property_read_string_index(np, "wacom,project_name", 1, |
| &pdata->model_name); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read model name %d\n", ret); |
| } |
| |
| flag = of_property_read_bool(np, "wacom,use_virtual_softkey"); |
| pdata->use_virtual_softkey = flag; |
| |
| flag = of_property_read_bool(np, "wacom,use_garage"); |
| pdata->use_garage = flag; |
| |
| flag = of_property_read_bool(np, "wacom,support_dex_mode"); |
| pdata->support_dex = flag; |
| |
| if (pdata->support_dex) { |
| ret = of_property_read_u32(np, "wacom,dex_rate", |
| &pdata->dex_rate); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to read dex_rate %d, default value is 10\n", |
| ret); |
| pdata->dex_rate = 10; |
| } |
| } |
| |
| pdata->support_aot = of_property_read_bool(np, "wacom,support_aot_mode"); |
| pdata->table_swap = of_property_read_bool(np, "wacom,table_swap_for_dex_station"); |
| |
| input_info(true, &client->dev, |
| "boot_addr: 0x%X, origin: (%d,%d), max_coords: (%d,%d), " |
| "max_pressure: %d, max_height: %d, max_tilt: (%d,%d) " |
| "project_name: (%s,%s), invert: (%d,%d,%d), fw_path: %s, " |
| "ic_type: %d, %s virtual softkey, %s garage, support_dex:%d, " |
| "dex_rate:%d, table_swap:%d, support_aot:%d\n", |
| pdata->boot_addr, pdata->origin[0], pdata->origin[1], |
| pdata->max_x, pdata->max_y, pdata->max_pressure, |
| pdata->max_height, pdata->max_x_tilt, pdata->max_y_tilt, |
| pdata->project_name, pdata->model_name, pdata->x_invert, |
| pdata->y_invert, pdata->xy_switch, pdata->fw_path, |
| pdata->ic_type, |
| pdata->use_virtual_softkey ? "enabled" : "disabled", |
| pdata->use_garage ? "enabled" : "disabled", |
| pdata->support_dex, pdata->dex_rate, pdata->table_swap, pdata->support_aot); |
| |
| return pdata; |
| } |
| #else |
| static struct wacom_g5_platform_data *wacom_parse_dt(struct i2c_client *client) |
| { |
| input_err(true, &client->dev, "no platform data specified\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| #endif |
| |
| static int wacom_i2c_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct wacom_g5_platform_data *pdata = dev_get_platdata(&client->dev); |
| struct wacom_i2c *wac_i2c; |
| struct input_dev *input; |
| int ret = 0; |
| |
| ret = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); |
| if (!ret) { |
| input_err(true, &client->dev, |
| "I2C functionality not supported\n"); |
| return -EIO; |
| } |
| |
| wac_i2c = devm_kzalloc(&client->dev, sizeof(*wac_i2c), GFP_KERNEL); |
| if (!wac_i2c) |
| return -ENOMEM; |
| |
| if (!pdata) { |
| pdata = wacom_parse_dt(client); |
| if (IS_ERR(pdata)) { |
| input_err(true, &client->dev, "failed to parse dt\n"); |
| return PTR_ERR(pdata); |
| } |
| } |
| |
| ret = wacom_request_gpio(client, pdata); |
| if (ret) { |
| input_err(true, &client->dev, "failed to request gpio\n"); |
| return ret; |
| } |
| |
| /* using managed input device */ |
| input = devm_input_allocate_device(&client->dev); |
| if (!input) { |
| input_err(true, &client->dev, |
| "failed to allocate input device\n"); |
| return -ENOMEM; |
| } |
| |
| if (pdata->support_dex) { |
| /* using new input device for relative coordinate for DeX */ |
| wac_i2c->input_dev_pad |
| = devm_input_allocate_device(&client->dev); |
| if (!wac_i2c->input_dev_pad) { |
| input_err(true, &client->dev, |
| "failed to allocate input device pad\n"); |
| return -ENOMEM; |
| } |
| |
| wac_i2c->input_dev_virtual |
| = devm_input_allocate_device(&client->dev); |
| if (!wac_i2c->input_dev_virtual) { |
| input_err(true, &client->dev, |
| "failed to allocate input device virtual\n"); |
| return -ENOMEM; |
| } |
| } |
| |
| /* using 2 slave address. one is normal mode, another is boot mode for |
| * fw update. |
| */ |
| wac_i2c->client_boot = i2c_new_dummy(client->adapter, pdata->boot_addr); |
| if (!wac_i2c->client_boot) { |
| input_err(true, &client->dev, |
| "failed to register sub client[0x%x]\n", |
| pdata->boot_addr); |
| return -ENOMEM; |
| } |
| |
| wac_i2c->client = client; |
| wac_i2c->pdata = pdata; |
| wac_i2c->input_dev = input; |
| wac_i2c->irq = gpio_to_irq(pdata->irq_gpio); |
| wac_i2c->irq_pdct = gpio_to_irq(pdata->pdct_gpio); |
| wac_i2c->pen_pdct = PDCT_NOSIGNAL; |
| wac_i2c->fw_img = NULL; |
| wac_i2c->fw_update_way = FW_NONE; |
| wac_i2c->fullscan_mode = false; |
| wac_i2c->wacom_noise_state = WACOM_NOISE_LOW; |
| wac_i2c->tsp_noise_mode = EPEN_GLOBAL_SCAN_MODE; |
| wac_i2c->wac_feature = &wacom_feature_EMR; |
| wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE; |
| wac_i2c->function_result = EPEN_EVENT_PEN_OUT; |
| wac_i2c->battery_saving_mode = 0; /* it needs */ |
| /* Consider about factory, it may be need to as default 1 */ |
| wac_i2c->reset_flag = false; |
| wac_i2c->pm_suspend = false; |
| |
| wacom_get_drv_data(wac_i2c); |
| |
| /*Set client data */ |
| i2c_set_clientdata(client, wac_i2c); |
| i2c_set_clientdata(wac_i2c->client_boot, wac_i2c); |
| |
| /* compensation to protect from flash mode */ |
| wacom_compulsory_flash_mode(wac_i2c, true); |
| wacom_compulsory_flash_mode(wac_i2c, false); |
| |
| /* Power on */ |
| wacom_power(wac_i2c, true); |
| wac_i2c->screen_on = true; |
| /* need to delay for query */ |
| msleep(100); |
| |
| wacom_i2c_query(wac_i2c); |
| |
| input->name = "sec_e-pen"; |
| wacom_i2c_set_input_values(wac_i2c, input, DEX_MODE_STYLUS); |
| |
| if (pdata->support_dex) { |
| wac_i2c->input_dev_pad->name = "sec_e-pen-pad"; |
| wacom_i2c_set_input_values(wac_i2c, wac_i2c->input_dev_pad, |
| DEX_MODE_MOUSE); |
| wac_i2c->input_dev_virtual->name = "sec_virtual-e-pen"; |
| wacom_i2c_set_input_values(wac_i2c, wac_i2c->input_dev_virtual, |
| DEX_MODE_STYLUS); |
| } |
| wac_i2c->input_dev_pen = input; |
| |
| /*Initializing for semaphor */ |
| mutex_init(&wac_i2c->lock); |
| mutex_init(&wac_i2c->update_lock); |
| mutex_init(&wac_i2c->irq_lock); |
| |
| wake_lock_init(&wac_i2c->fw_wakelock, WAKE_LOCK_SUSPEND, "wacom"); |
| wake_lock_init(&wac_i2c->wakelock, WAKE_LOCK_SUSPEND, "wacom_wakelock"); |
| |
| INIT_DELAYED_WORK(&wac_i2c->resume_work, wacom_i2c_resume_work); |
| INIT_DELAYED_WORK(&wac_i2c->fullscan_check_work, |
| wacom_fullscan_check_work); |
| |
| init_completion(&wac_i2c->resume_done); |
| |
| #ifdef LCD_FREQ_SYNC |
| mutex_init(&wac_i2c->freq_write_lock); |
| INIT_WORK(&wac_i2c->lcd_freq_work, wacom_i2c_lcd_freq_work); |
| INIT_DELAYED_WORK(&wac_i2c->lcd_freq_done_work, |
| wacom_i2c_finish_lcd_freq_work); |
| wac_i2c->use_lcd_freq_sync = true; |
| #endif |
| #ifdef WACOM_USE_SOFTKEY_BLOCK |
| INIT_DELAYED_WORK(&wac_i2c->softkey_block_work, |
| wacom_i2c_block_softkey_work); |
| wac_i2c->block_softkey = false; |
| #endif |
| INIT_WORK(&wac_i2c->update_work, wacom_i2c_update_work); |
| |
| ret = input_register_device(input); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to register input device\n"); |
| /* managed input devices need not be explicitly unregistred or freed. */ |
| goto err_register_input_dev; |
| |
| } |
| |
| if (pdata->support_dex) { |
| ret = input_register_device(wac_i2c->input_dev_pad); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to register input device pad\n"); |
| goto err_register_input_dev; |
| } |
| |
| ret = input_register_device(wac_i2c->input_dev_virtual); |
| if (ret) { |
| input_err(true, &client->dev, |
| "failed to register input device virtual\n"); |
| goto err_register_input_dev; |
| } |
| } |
| |
| /*Request IRQ */ |
| ret = devm_request_threaded_irq(&client->dev, wac_i2c->irq, NULL, |
| wacom_interrupt, IRQF_ONESHOT | |
| pdata->irq_type, |
| "sec_epen_irq", wac_i2c); |
| if (ret < 0) { |
| input_err(true, &client->dev, |
| "failed to request irq(%d) - %d\n", wac_i2c->irq, |
| ret); |
| goto err_request_irq; |
| } |
| input_info(true, &client->dev, "init irq %d\n", wac_i2c->irq); |
| |
| ret = devm_request_threaded_irq(&client->dev, wac_i2c->irq_pdct, NULL, |
| wacom_interrupt_pdct, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT | |
| IRQF_TRIGGER_RISING, |
| "sec_epen_pdct", wac_i2c); |
| if (ret < 0) { |
| input_err(true, &client->dev, |
| "failed to request pdct irq(%d) - %d\n", |
| wac_i2c->irq_pdct, ret); |
| goto err_request_pdct_irq; |
| } |
| input_info(true, &client->dev, "init pdct %d\n", wac_i2c->irq_pdct); |
| |
| init_pen_insert(wac_i2c); |
| |
| ret = wacom_sec_init(wac_i2c); |
| if (ret) |
| goto err_sec_init; |
| |
| wacom_fw_update(wac_i2c, FW_BUILT_IN, false); |
| |
| device_init_wakeup(&client->dev, true); |
| |
| if (wac_i2c->pdata->table_swap) { |
| INIT_DELAYED_WORK(&wac_i2c->usb_typec_work, wacom_usb_typec_work); |
| INIT_DELAYED_WORK(&wac_i2c->typec_nb_reg_work, |
| wacom_usb_typec_nb_register_work); |
| schedule_delayed_work(&wac_i2c->typec_nb_reg_work, msecs_to_jiffies(10)); |
| } |
| |
| input_info(true, &client->dev, "probe done\n"); |
| |
| return 0; |
| |
| err_sec_init: |
| err_request_pdct_irq: |
| err_request_irq: |
| err_register_input_dev: |
| wake_lock_destroy(&wac_i2c->fw_wakelock); |
| wake_lock_destroy(&wac_i2c->wakelock); |
| #ifdef LCD_FREQ_SYNC |
| mutex_destroy(&wac_i2c->freq_write_lock); |
| #endif |
| mutex_destroy(&wac_i2c->irq_lock); |
| mutex_destroy(&wac_i2c->update_lock); |
| mutex_destroy(&wac_i2c->lock); |
| |
| i2c_unregister_device(wac_i2c->client_boot); |
| |
| input_err(true, &client->dev, "failed to probe\n"); |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_PM |
| static int wacom_i2c_suspend(struct device *dev) |
| { |
| struct wacom_i2c *wac_i2c = dev_get_drvdata(dev); |
| |
| wac_i2c->pm_suspend = true; |
| reinit_completion(&wac_i2c->resume_done); |
| #ifndef USE_OPEN_CLOSE |
| if (wac_i2c->input_dev->users) |
| wacom_sleep_sequence(wac_i2c); |
| #endif |
| |
| return 0; |
| } |
| |
| static int wacom_i2c_resume(struct device *dev) |
| { |
| struct wacom_i2c *wac_i2c = dev_get_drvdata(dev); |
| |
| wac_i2c->pm_suspend = false; |
| complete_all(&wac_i2c->resume_done); |
| #ifndef USE_OPEN_CLOSE |
| if (wac_i2c->input_dev->users) |
| wacom_wakeup_sequence(wac_i2c); |
| #endif |
| |
| return 0; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(wacom_pm_ops, wacom_i2c_suspend, wacom_i2c_resume); |
| #endif |
| |
| static void wacom_i2c_shutdown(struct i2c_client *client) |
| { |
| struct wacom_i2c *wac_i2c = i2c_get_clientdata(client); |
| |
| if (!wac_i2c) |
| return; |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| |
| wacom_power(wac_i2c, false); |
| |
| input_info(true, &wac_i2c->client->dev, "%s\n", __func__); |
| } |
| |
| static int wacom_i2c_remove(struct i2c_client *client) |
| { |
| struct wacom_i2c *wac_i2c = i2c_get_clientdata(client); |
| |
| input_info(true, &wac_i2c->client->dev, "%s called!\n", __func__); |
| |
| device_init_wakeup(&client->dev, false); |
| |
| wacom_enable_irq(wac_i2c, false); |
| wacom_enable_pdct_irq(wac_i2c, false); |
| |
| wacom_power(wac_i2c, false); |
| |
| wake_lock_destroy(&wac_i2c->fw_wakelock); |
| wake_lock_destroy(&wac_i2c->wakelock); |
| mutex_destroy(&wac_i2c->irq_lock); |
| mutex_destroy(&wac_i2c->update_lock); |
| mutex_destroy(&wac_i2c->lock); |
| |
| wacom_sec_remove(wac_i2c); |
| |
| i2c_unregister_device(wac_i2c->client_boot); |
| |
| if (wac_i2c->pdata->support_dex) { |
| input_unregister_device(wac_i2c->input_dev_pad); |
| input_unregister_device(wac_i2c->input_dev_virtual); |
| wac_i2c->input_dev = wac_i2c->input_dev_pen; |
| } |
| |
| input_unregister_device(wac_i2c->input_dev); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id wacom_i2c_id[] = { |
| {"wacom_w90xx", 0}, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, wacom_i2c_id); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id wacom_dt_ids[] = { |
| {.compatible = "wacom,w90xx"}, |
| {} |
| }; |
| #endif |
| |
| static struct i2c_driver wacom_i2c_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "wacom_w90xx", |
| #ifdef CONFIG_PM |
| .pm = &wacom_pm_ops, |
| #endif |
| .of_match_table = of_match_ptr(wacom_dt_ids), |
| }, |
| .probe = wacom_i2c_probe, |
| .remove = wacom_i2c_remove, |
| .shutdown = wacom_i2c_shutdown, |
| .id_table = wacom_i2c_id, |
| }; |
| |
| static int __init wacom_i2c_init(void) |
| { |
| int ret = 0; |
| |
| #ifdef CONFIG_BATTERY_SAMSUNG |
| if (lpcharge) { |
| pr_info("%s: %s: Do not load driver due to : lpm %d\n", |
| SECLOG, __func__, lpcharge); |
| return ret; |
| } |
| #endif |
| ret = i2c_add_driver(&wacom_i2c_driver); |
| if (ret) |
| pr_err("%s: %s: failed to add i2c driver\n", SECLOG, __func__); |
| |
| return ret; |
| } |
| |
| static void __exit wacom_i2c_exit(void) |
| { |
| i2c_del_driver(&wacom_i2c_driver); |
| } |
| |
| module_init(wacom_i2c_init); |
| module_exit(wacom_i2c_exit); |
| |
| MODULE_AUTHOR("Samsung"); |
| MODULE_DESCRIPTION("Driver for Wacom Digitizer Controller"); |
| MODULE_LICENSE("GPL"); |