blob: 75bf9438eacd0d6cd94831ff983f9e5ecd322cc1 [file] [log] [blame]
/*
* CORERIVER TOUCHCORE touchkey driver
*
* Copyright (C) 2012 Samsung Electronics Co.Ltd
* Author: Taeyoon Yoon <tyoony.yoon@samsung.com>
*
* 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.
*
*/
/*#define LED_DEBUG*/
#define ISP_DEBUG
/*#define ISP_VERBOSE_DEBUG*/
/*#define ISP_VERY_VERBOSE_DEBUG*/
#include <linux/delay.h>
#include <linux/earlysuspend.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input/tc360-touchkey.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <mach/pinmux.h>
#define TC360_CUR_FW_VER 0x09
#define TC360_FW_NAME "tc360_09.fw"
#define TC360_FW_BUILTIN_PATH "coreriver"
#define TC360_FW_HEADER_PATH
#define TC360_FW_IN_SDCARD_PATH "/mnt/sdcard/firmware/coreriver"
#define TC360_FW_EX_SDCARD_PATH "/mnt/sdcard/external_sd/firmware/coreriver"
#define TC360_FW_FLASH_RETRY 5
#define TC360_POWERON_DELAY 50
#define TC360_DCR_RD_RETRY 50
#define TC360_KEY_DATA 0x00
#define TC360_KEY_INDEX_MASK 0x03
#define TC360_KEY_PRESS_MASK 0x08
#define TC360_ISP_ACK 1
#define TC360_ISP_SP_SIGNAL (0b010101011111000)
#define TC360_NUM_OF_ISP_SP_SIGNAL 15
#if 0
#define TC360_CMD_LED_ON 0x10
#define TC360_CMD_LED_OFF 0x20
#define TC360_CMD_SLEEP 0x80
#else
#define TC360_CMD_LED_ON 0x01
#define TC360_CMD_LED_OFF 0x02
#define TC360_CMD_SLEEP 0x08
#endif
#define TC360_FW_ER_MAX_LEN 0x8000
#define TC360_FW_WT_MAX_LEN 0x3000
#define TOUCHKEY_LDO_EN -1
#define TOUCHKEY_INT 26
#define TOUCHKEY_SCL 92
#define TOUCHKEY_SDA 91
#define I2C_RETRY_CNT 2
enum {
FW_BUILT_IN = 0,
FW_HEADER,
FW_IN_SDCARD,
FW_EX_SDCARD,
};
enum {
HAVE_LATEST_FW = 1,
FW_UPDATE_RUNNING,
};
enum {
STATE_NORMAL = 1,
STATE_FLASH,
STATE_FLASH_FAIL,
};
#if defined(SEC_FAC_TK)
#define TO_STRING(x) #x
enum {
DOWNLOADING = 1,
FAIL,
PASS,
};
struct fdata_struct {
struct device *dummy_dev;
u8 fw_flash_status;
u8 pressed[2];
};
#endif
struct tc360_data {
struct i2c_client *client;
struct input_dev *input_dev;
char phys[32];
struct tc360_platform_data *pdata;
struct early_suspend early_suspend;
struct mutex lock;
bool enabled;
u8 suspend_type;
u32 scl;
u32 sda;
int udelay;
int num_key;
int *keycodes;
/* variables for fw update*/
const struct firmware *fw;
const u8 *fw_data;
int fw_len;
u8 cur_fw_path;
struct workqueue_struct *fw_wq;
struct work_struct fw_work;
struct wake_lock fw_wake_lock;
u8 fw_flash_state;
/* variables for LED*/
struct led_classdev led;
struct workqueue_struct *led_wq;
struct work_struct led_work;
u8 led_brightness;
#if defined(SEC_FAC_TK)
struct fdata_struct *fdata;
#endif
};
static int prev_value;
static int IsTouchkeyPowerOn = 1;
#ifdef CONFIG_HAS_EARLYSUSPEND
static void tc360_early_suspend(struct early_suspend *h);
static void tc360_late_resume(struct early_suspend *h);
#endif
static void GPIO_pullup_setting(bool v_setval)
{
struct pin_config GPIO00_config, GPIO01_config;
GPIO00_config.name = PN_GPIO00;
GPIO01_config.name = PN_GPIO01;
pinmux_get_pin_config(&GPIO00_config);
pinmux_get_pin_config(&GPIO01_config);
if (v_setval) { /* pull-up */
GPIO00_config.reg.b.pull_up = 1;
GPIO01_config.reg.b.pull_up = 1;
} else { /* no-pull */
GPIO00_config.reg.b.pull_up = 0;
GPIO01_config.reg.b.pull_up = 0;
}
pinmux_set_pin_config(&GPIO00_config);
pinmux_set_pin_config(&GPIO01_config);
}
void touchkey_led_on(struct tc360_data *data, bool on)
{
#if 0
printk("touchkey_led_on = %d\n", on);
if (on) {
gpio_set_value(TOUCHKEY_LDO_EN, 1);
} else {
gpio_set_value(TOUCHKEY_LDO_EN, 0);
}
#endif
}
static irqreturn_t tc360_interrupt(int irq, void *dev_id)
{
struct tc360_data *data = dev_id;
struct i2c_client *client = data->client;
u8 buf = 0xA5;
u8 key_index;
bool press;
int ret;
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = &buf,
.len = 1,
};
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
dev_err(&client->dev, "failed to read key data (%d)\n", ret);
goto out;
}
key_index = buf & TC360_KEY_INDEX_MASK;
switch (key_index) {
case 0:
dev_err(&client->dev, "no button press interrupt(%#x)\n", buf);
break;
case 1 ... 2:
press = !(buf & TC360_KEY_PRESS_MASK);
dev_err(&client->dev, "key[%3d] is %s\n", data->keycodes[key_index - 1], (press) ? "pressed" : "releaseed");
input_report_key(data->input_dev, data->keycodes[key_index - 1], press);
input_sync(data->input_dev);
data->fdata->pressed[key_index - 1] = press;
break;
default:
dev_err(&client->dev, "wrong interrupt(%#x)\n", buf);
break;
}
dev_info(&client->dev, "tc360_interrupt\n");
out:
return IRQ_HANDLED;
}
static inline void setsda(struct tc360_data *data, int state)
{
if (state)
gpio_direction_input(data->sda);
else
gpio_direction_output(data->sda, 0);
}
static inline void setscl(struct tc360_data *data, int state)
{
if (state)
gpio_direction_input(data->scl);
else
gpio_direction_output(data->scl, 0);
}
static inline int getsda(struct tc360_data *data)
{
return gpio_get_value(data->sda);
}
static inline int getscl(struct tc360_data *data)
{
return gpio_get_value(data->scl);
}
static inline void sdalo(struct tc360_data *data)
{
setsda(data, 0);
udelay((data->udelay + 1) / 2);
}
static inline void sdahi(struct tc360_data *data)
{
setsda(data, 1);
udelay((data->udelay + 1) / 2);
}
static inline void scllo(struct tc360_data *data)
{
setscl(data, 0);
udelay((data->udelay + 1) / 2);
}
static int sclhi(struct tc360_data *data)
{
#if defined(ISP_VERY_VERBOSE_DEBUG) || defined(ISP_VERBOSE_DEBUG)
struct i2c_client *client = data->client;
#endif
unsigned long start;
int timeout = HZ / 20;
setscl(data, 1);
start = jiffies;
while (!getscl(data)) {
if (time_after(jiffies, start + timeout))
return -ETIMEDOUT;
}
if (jiffies != start)
#if defined(ISP_VERY_VERBOSE_DEBUG)
dev_err(&client->dev, "%s: needed %ld jiffies for SCL to go "
"high\n", __func__, jiffies - start);
#endif
udelay(data->udelay);
return 0;
}
static void isp_start(struct tc360_data *data)
{
setsda(data, 0);
udelay(data->udelay);
scllo(data);
}
static void isp_stop(struct tc360_data *data)
{
sdalo(data);
sclhi(data);
setsda(data, 1);
udelay(data->udelay);
}
static int isp_recvdbyte(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int i;
u8 indata = 0;
sdahi(data);
for (i = 0; i < 8 ; i++) {
if (sclhi(data) < 0) { /* timed out */
dev_err(&client->dev, "%s: timeout at bit "
"#%d\n", __func__, 7 - i);
return -ETIMEDOUT;
}
indata = indata << 1;
if (getsda(data))
indata |= 0x01;
setscl(data, 0);
udelay(i == 7 ? data->udelay / 2 : data->udelay);
}
return indata;
}
static int isp_sendbyte(struct tc360_data *data, u8 c)
{
struct i2c_client *client = data->client;
int i;
int sb;
int ack = 0;
/* assert: scl is low */
for (i = 7; i >= 0; i--) {
sb = (c >> i) & 0x1;
setsda(data, sb);
udelay((data->udelay + 1) / 2);
if (sclhi(data) < 0) { /* timed out */
dev_err(&client->dev, "%s: %#x, timeout at bit #%d\n",
__func__, (int)c, i);
return -ETIMEDOUT;
}
scllo(data);
}
sdahi(data);
if (sclhi(data) < 0) { /* timed out */
dev_err(&client->dev, "%s: %#x, timeout at bit #%d\n",
__func__, (int)c, i);
return -ETIMEDOUT;
}
ack = !getsda(data);
scllo(data);
#if defined(ISP_VERY_VERBOSE_DEBUG)
dev_info(&client->dev, "%s: %#x %s\n", __func__, (int)c,
ack ? "A" : "NA");
#endif
return ack;
}
static int isp_master_recv(struct tc360_data *data, u8 addr, u8 *val)
{
struct i2c_client *client = data->client;
int ret;
int retries = 2;
retry:
isp_start(data);
ret = isp_sendbyte(data, addr);
if (ret != TC360_ISP_ACK) {
dev_err(&client->dev, "%s: %#x %s\n", __func__, addr, "NA");
if (retries-- > 0) {
dev_err(&client->dev, "%s: retry (%d)\n", __func__,
retries);
goto retry;
}
return -EIO;
}
*val = isp_recvdbyte(data);
isp_stop(data);
return 0;
}
static int isp_master_send(struct tc360_data *data, u8 msg_1, u8 msg_2)
{
struct i2c_client *client = data->client;
int ret;
int retries = 2;
retry:
isp_start(data);
ret = isp_sendbyte(data, msg_1);
if (ret != TC360_ISP_ACK) {
dev_err(&client->dev, "%s: %#x %s\n", __func__, msg_1, "NA");
if (retries-- > 0) {
dev_err(&client->dev, "%s: retry (%d)\n", __func__,
retries);
goto retry;
}
return -EIO;
}
ret = isp_sendbyte(data, msg_2);
if (ret != TC360_ISP_ACK) {
dev_err(&client->dev, "%s: %#x %s\n", __func__, msg_2, "NA");
if (retries-- > 0) {
dev_err(&client->dev, "%s: retry (%d)\n", __func__,
retries);
goto retry;
}
return -EIO;
}
isp_stop(data);
return 0;
}
static void isp_sp_signal(struct tc360_data *data)
{
int i;
unsigned long flags;
local_irq_save(flags);
for (i = TC360_NUM_OF_ISP_SP_SIGNAL - 1; i >= 0; i--) {
int sb = (TC360_ISP_SP_SIGNAL >> i) & 0x1;
setscl(data, sb);
udelay(3);
setsda(data, 0);
udelay(10);
setsda(data, 1);
udelay(10);
if (i == 5)
udelay(30);
}
sclhi(data);
local_irq_restore(flags);
}
static int raw_dbgir3(struct tc360_data *data, u8 data2, u8 data1, u8 data0)
{
struct i2c_client *client = data->client;
int ret = 0;
ret = ret | isp_master_send(data, 0xc2, data2);
ret = ret | isp_master_send(data, 0xc4, data1);
ret = ret | isp_master_send(data, 0xc6, data0);
ret = ret | isp_master_send(data, 0xc0, 0x80);
if (ret < 0) {
dev_err(&client->dev, "fail to dbgir3 %#x,%#x,%#x (%d)\n",
data2, data1, data0, ret);
return ret;
}
return 0;
}
static int raw_dbgir2(struct tc360_data *data, u8 data1, u8 data0)
{
struct i2c_client *client = data->client;
int ret = 0;
ret = ret | isp_master_send(data, 0xc2, data1);
ret = ret | isp_master_send(data, 0xc4, data0);
ret = ret | isp_master_send(data, 0xc0, 0x80);
if (ret < 0) {
dev_err(&client->dev, "fail to dbgir2 %#x,%#x (%d)\n",
data1, data0, ret);
return ret;
}
return 0;
}
static int raw_spchl(struct tc360_data *data, u8 data1, u8 data0)
{
struct i2c_client *client = data->client;
int ret = 0;
ret = ret | isp_master_send(data, 0xd0, data1);
ret = ret | isp_master_send(data, 0xd2, data0);
if (ret < 0) {
dev_err(&client->dev, "fail to spchl %#x,%#x (%d)\n",
data1, data0, ret);
return ret;
}
return 0;
}
static int isp_common_set(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
ret = ret | raw_dbgir3(data, 0x75 , 0x8f, 0x25);
ret = ret | raw_dbgir3(data, 0x75 , 0xc6, 0x0e);
ret = ret | raw_dbgir3(data, 0x75 , 0xf7, 0xc1);
ret = ret | raw_dbgir3(data, 0x75 , 0xf7, 0x1e);
ret = ret | raw_dbgir3(data, 0x75 , 0xf7, 0xec);
ret = ret | raw_dbgir3(data, 0x75 , 0xf7, 0x81);
if (ret < 0) {
dev_err(&client->dev, "fail to %s (%d)\n", __func__, ret);
return ret;
}
return 0;
}
static int isp_ers_timing_set(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
ret = ret | raw_dbgir3(data, 0x75, 0xf2, 0x20);
ret = ret | raw_dbgir3(data, 0x75, 0xf3, 0xa1);
ret = ret | raw_dbgir3(data, 0x75, 0xf4, 0x07);
ret = ret | raw_dbgir3(data, 0x75, 0xf1, 0x80);
if (ret < 0) {
dev_err(&client->dev, "fail to %s (%d)\n", __func__, ret);
return ret;
}
return 0;
}
static int isp_pgm_timing_set(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
/*value modified for speed-up*/ /*coreriver*/
ret = ret | raw_dbgir3(data, 0x75, 0xf2, 0xE8);
ret = ret | raw_dbgir3(data, 0x75, 0xf3, 0x03);
ret = ret | raw_dbgir3(data, 0x75, 0xf4, 0x00);
if (ret < 0) {
dev_err(&client->dev, "fail to %s (%d)\n", __func__, ret);
return ret;
}
return 0;
}
static void reset_for_isp(struct tc360_data *data)
{
/*sequence modified*/ /*coreriver*/
gpio_direction_output(data->pdata->gpio_int, 0);
gpio_direction_output(data->pdata->gpio_scl, 0);
gpio_direction_output(data->pdata->gpio_sda, 0);
data->pdata->power(false);
msleep(TC360_POWERON_DELAY);
data->pdata->power(true);
gpio_direction_output(data->pdata->gpio_sda, 1);
gpio_direction_output(data->pdata->gpio_scl, 1);
gpio_direction_input(data->pdata->gpio_int);
}
static int tc360_erase_fw(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret;
u16 addr = 0;
int dcr_rd_cnt;
u8 val;
reset_for_isp(data);
msleep(3);
isp_sp_signal(data);
ret = isp_common_set(data);
if (ret < 0) {
dev_err(&client->dev, "fail to %s (%d)\n", __func__, ret);
return ret;
}
while (addr < TC360_FW_ER_MAX_LEN) {
#if defined(ISP_DEBUG)
dev_info(&client->dev, "fw erase addr=x0%4x\n", addr);
#endif
ret = isp_ers_timing_set(data);
raw_dbgir3(data, 0x90, (u8)(addr >> 8), (u8)(addr & 0xff));
isp_master_send(data, 0xf8, 0x01);
isp_master_send(data, 0xc8, 0xff);
isp_master_send(data, 0xca, 0x42);
raw_dbgir3(data, 0x02, 0xff, 0x3a);
raw_spchl(data, 0xff, 0x3a);
isp_master_send(data, 0xc0, 0x14);
val = 0;
dcr_rd_cnt = TC360_DCR_RD_RETRY;
do {
isp_master_recv(data, 0xc1, &val);
if (dcr_rd_cnt-- < 0) {
dev_err(&client->dev, "%s: fail to update "
"dcr\n", __func__);
return -ENOSYS;
}
usleep_range(10000, 15000);
} while (val != 0x12);
#if defined(ISP_VERBOSE_DEBUG)
dev_info(&client->dev, "dcr_rd_cnt=%d\n", dcr_rd_cnt);
#endif
addr += 0x400;
}
return 0;
}
static int tc360_write_fw(struct tc360_data *data)
{
struct i2c_client *client = data->client;
u16 addr = 0;
int dcr_rd_cnt;
u8 val;
reset_for_isp(data);
msleep(3);
isp_sp_signal(data);
isp_common_set(data);
while (addr < data->fw_len) {
#if defined(ISP_DEBUG)
dev_info(&client->dev, "fw write addr=%#x\n", addr);
#endif
isp_pgm_timing_set(data);
raw_dbgir3(data, 0x90, (u8)(addr >> 8), (u8)(addr & 0xff));
isp_master_send(data, 0xf8, 0x01);
isp_master_send(data, 0xc8, 0xff);
isp_master_send(data, 0xca, 0x20);
raw_dbgir3(data, 0x02, 0xff, 0x1e);
raw_spchl(data, 0xff, 0x1e);
do {
u8 __fw_data = data->fw_data[addr++];
raw_dbgir2(data, 0x74, __fw_data);
raw_dbgir3(data, 0x75, 0xf1, 0x80);
isp_master_send(data, 0xc0, 0x14);
val = 0;
dcr_rd_cnt = TC360_DCR_RD_RETRY;
do {
isp_master_recv(data, 0xc1, &val);
if (dcr_rd_cnt-- < 0) {
dev_err(&client->dev, "%s: fail to "
"update dcr\n", __func__);
return -ENOSYS;
}
usleep_range(900, 1000);
} while (val != 0x12);
#if defined(ISP_VERBOSE_DEBUG)
dev_info(&client->dev, "dcr_rd_cnt=%d\n", dcr_rd_cnt);
#endif
/* increase address */
isp_master_send(data, 0xc0, 0x08);
} while (addr % 0x20);
}
return 0;
}
static int tc360_verify_fw(struct tc360_data *data)
{
struct i2c_client *client = data->client;
u16 addr = 0;
int dcr_rd_cnt;
u8 val;
reset_for_isp(data);
msleep(3);
isp_sp_signal(data);
isp_common_set(data);
while (addr < data->fw_len) {
#if defined(ISP_DEBUG)
dev_info(&client->dev, "fw read addr=%#x\n", addr);
#endif
isp_master_send(data, 0xf8, 0x01);
isp_master_send(data, 0xc8, 0xff);
isp_master_send(data, 0xca, 0x2a);
raw_dbgir3(data, 0x90, (u8)(addr >> 8), (u8)(addr & 0xff));
raw_dbgir3(data, 0x02, 0xff, 0x28);
raw_spchl(data, 0xff, 0x28);
do {
isp_master_send(data, 0xc0, 0x14);
val = 0;
dcr_rd_cnt = TC360_DCR_RD_RETRY;
do {
isp_master_recv(data, 0xc1, &val);
if (dcr_rd_cnt-- < 0) {
dev_err(&client->dev, "%s: fail to "
"update dcr\n", __func__);
return -ENOSYS;
}
usleep_range(900, 1000);
} while (val != 0x12);
#if defined(ISP_VERBOSE_DEBUG)
dev_info(&client->dev, "dcr_rd_cnt=%d\n", dcr_rd_cnt);
#endif
isp_master_send(data, 0xc0, 0x08);
isp_master_recv(data, 0xd9, &val);
if (data->fw_data[addr] != val) {
dev_err(&client->dev, "fail to verify at "
"%#x (%#x)\n", addr,
data->fw_data[addr]);
return -EIO;
}
addr++;
} while (addr % 0x20);
}
return 0;
}
static int tc360_get_fw_ver(struct tc360_data *data)
{
struct i2c_client *client = data->client;
u8 rbuf[2];
int ret;
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = 2,
};
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read fw ver (%d)\n", ret);
return rbuf[1];
}
static void tc360_fw_update_worker(struct work_struct *work)
{
struct tc360_data *data = container_of(work, struct tc360_data,
fw_work);
struct i2c_client *client = data->client;
int retries;
int ret;
wake_lock(&data->fw_wake_lock);
retries = TC360_FW_FLASH_RETRY;
erase_fw:
ret = tc360_erase_fw(data);
if (ret < 0) {
dev_err(&client->dev, "fail to erase fw (%d)\n", ret);
if (retries-- > 0) {
dev_info(&client->dev, "retry esasing fw (%d)\n",
retries);
goto erase_fw;
} else {
goto err;
}
}
dev_info(&client->dev, "succeed to erase fw\n");
retries = TC360_FW_FLASH_RETRY;
write_fw:
ret = tc360_write_fw(data);
if (ret < 0) {
dev_err(&client->dev, "fail to write fw (%d)\n", ret);
if (retries-- > 0) {
dev_info(&client->dev, "retry writing fw (%d)\n",
retries);
goto write_fw;
} else {
goto err;
}
}
dev_info(&client->dev, "succeed to write fw\n");
retries = TC360_FW_FLASH_RETRY;
verify_fw:
ret = tc360_verify_fw(data);
if (ret < 0) {
dev_err(&client->dev, "fail to verify fw (%d)\n", ret);
if (retries-- > 0) {
dev_info(&client->dev, "retry verifing fw (%d)\n",
retries);
goto verify_fw;
} else {
goto err;
}
}
ret = 0;
dev_info(&client->dev, "succeed to verify fw\n");
if (data->cur_fw_path == FW_BUILT_IN)
release_firmware(data->fw);
else if (data->cur_fw_path == FW_IN_SDCARD ||
data->cur_fw_path == FW_EX_SDCARD)
kfree(data->fw_data);
data->pdata->power(false);
msleep(TC360_POWERON_DELAY);
data->pdata->power(true);
msleep(TC360_POWERON_DELAY * 2);
data->fw_flash_state = STATE_FLASH;
enable_irq(client->irq);
data->enabled = true;
wake_unlock(&data->fw_wake_lock);
dev_info(&client->dev, "succeed to fw flash. current version is %#x\n",
tc360_get_fw_ver(data));
#if defined(SEC_FAC_TK)
if (data->fdata->fw_flash_status == DOWNLOADING)
data->fdata->fw_flash_status = PASS;
#endif
return;
err:
wake_unlock(&data->fw_wake_lock);
#if defined(SEC_FAC_TK)
if (data->fdata->fw_flash_status == DOWNLOADING) {
dev_err(&client->dev, "fail to fw flash.\n");
data->fdata->fw_flash_status = FAIL;
return;
}
#endif
/* early suspend is not removed for debugging. */
/*
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&data->early_suspend);
#endif
*/
data->fw_flash_state = STATE_FLASH_FAIL;
wake_lock_destroy(&data->fw_wake_lock);
free_irq(client->irq, data);
gpio_free(data->pdata->gpio_int);
led_classdev_unregister(&data->led);
destroy_workqueue(data->led_wq);
data->led_wq = NULL;
/* fw_wq (workqueue for firmware flash) will be destroyed in suspend function */
/* destroy_workqueue(data->fw_wq); */
input_unregister_device(data->input_dev);
input_free_device(data->input_dev);
data->pdata->setup_power(NULL, false);
/* data is not deallocated for debugging. */
/* kfree(data); */
dev_err(&client->dev, "fail to fw flash. driver is removed\n");
}
static int load_fw_built_in(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret;
char *fw_name = kasprintf(GFP_KERNEL, "%s/%s",
TC360_FW_BUILTIN_PATH, TC360_FW_NAME);
ret = request_firmware(&data->fw, fw_name, &client->dev);
if (ret) {
dev_err(&client->dev, "error requesting built-in firmware (%d)"
"\n", ret);
goto out;
}
data->fw_data = data->fw->data;
data->fw_len = data->fw->size;
dev_err(&client->dev, "the fw is loaded (size=%d)\n", data->fw_len);
out:
kfree(fw_name);
return ret;
}
static int load_fw_header(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
/*
* to do : implemete
*/
dev_info(&client->dev, "%s:", __func__);
return ret;
}
static int load_fw_in_sdcard(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
/*
* to do : implemete
*/
dev_info(&client->dev, "%s:", __func__);
return ret;
}
static int load_fw_ex_sdcard(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret = 0;
/*
* to do : implemete
*/
dev_info(&client->dev, "%s:", __func__);
return ret;
}
static int tc360_load_fw(struct tc360_data *data, u8 fw_path)
{
struct i2c_client *client = data->client;
int ret;
switch (fw_path) {
case FW_BUILT_IN:
ret = load_fw_built_in(data);
break;
case FW_HEADER:
ret = load_fw_header(data);
break;
case FW_IN_SDCARD:
ret = load_fw_in_sdcard(data);
break;
case FW_EX_SDCARD:
ret = load_fw_ex_sdcard(data);
break;
default:
dev_err(&client->dev, "%s: invalid fw path (%d)\n",
__func__, fw_path);
return -ENOENT;
}
if (ret < 0) {
dev_err(&client->dev, "fail to load fw in %d (%d)\n",
fw_path, ret);
return ret;
}
return 0;
}
static int tc360_unload_fw(struct tc360_data *data, u8 fw_path)
{
struct i2c_client *client = data->client;
int ret;
switch (fw_path) {
case FW_BUILT_IN:
release_firmware(data->fw);
break;
case FW_HEADER:
break;
case FW_IN_SDCARD:
case FW_EX_SDCARD:
kfree(data->fw_data);
break;
default:
dev_err(&client->dev, "%s: invalid fw path (%d)\n",
__func__, fw_path);
return -ENOENT;
}
if (ret < 0) {
dev_err(&client->dev, "fail to unload fw in %d (%d)\n",
fw_path, ret);
return ret;
}
return 0;
}
static int tc360_flash_fw(struct tc360_data *data, u8 fw_path, bool force)
{
struct i2c_client *client = data->client;
int ret;
int fw_ver;
ret = tc360_load_fw(data, fw_path);
if (ret < 0) {
dev_err(&client->dev, "fail to load fw (%d)\n", ret);
return ret;
}
data->cur_fw_path = fw_path;
/* firmware version compare */
fw_ver = tc360_get_fw_ver(data);
if (fw_ver == TC360_CUR_FW_VER && !force) {
ret = HAVE_LATEST_FW;
dev_info(&client->dev, "IC aleady have latest firmware (%#x)\n",
fw_ver);
goto out;
}
dev_info(&client->dev, "fw update to %#x (from %#x) (%s)\n",
TC360_CUR_FW_VER, fw_ver, (force) ? "force" : "ver mismatch");
queue_work(data->fw_wq, &data->fw_work);
return FW_UPDATE_RUNNING;
out:
data->fw_flash_state = STATE_NORMAL;
tc360_unload_fw(data, fw_path);
return ret;
}
static int tc360_initialize(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret;
ret = tc360_flash_fw(data, FW_BUILT_IN, false);
if (ret < 0)
dev_err(&client->dev, "fail to flash fw (%d)\n", ret);
return ret;
}
static void tc360_led_worker(struct work_struct *work)
{
struct tc360_data *data = container_of(work, struct tc360_data,
led_work);
struct i2c_client *client = data->client;
u8 buf;
int ret;
u8 br;
int cnt = 0;
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.buf = &buf,
.len = 1,
};
#if defined(LED_DEBUG)
dev_err(&client->dev, "%s: turn %s LED\n", __func__,
(br == LED_OFF) ? "off" : "on");
#endif
br = data->led_brightness;
if (br == LED_OFF)
buf = TC360_CMD_LED_OFF;
else /* LED_FULL*/
buf = TC360_CMD_LED_ON;
while (!data->enabled) {
usleep_range(100000, 200000);
dev_err(&client->dev, "%s: wiating for device (%d)\n",
__func__, cnt++);
if (cnt > 30) {
dev_err(&client->dev, "%s: fail to tc360 led %s\n",
__func__,
(br == LED_OFF) ? "OFF" : "ON");
return;
}
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to wt led data (%d)\n", ret);
}
static void tc360_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct tc360_data *data =
container_of(led_cdev, struct tc360_data, led);
struct i2c_client *client = data->client;
if (unlikely(wake_lock_active(&data->fw_wake_lock))) {
dev_info(&client->dev, "fw is being updated."
"led control is ignored.\n");
return;
}
data->led_brightness = value;
printk("[TouchKey] data->led_brightness=%d, prev_value=%d\n", data->led_brightness, prev_value);
if (value >= 100 && prev_value == 0 && IsTouchkeyPowerOn) {
touchkey_led_on(data, 1);
} else if (value == 0 && prev_value != 0) {
touchkey_led_on(data, 0);
}
prev_value = value;
#if 0
if (data->led_wq)
queue_work(data->led_wq, &data->led_work);
#endif
}
#if defined(SEC_FAC_TK)
static ssize_t fac_fw_ver_ic_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int ver;
int ret;
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ver = tc360_get_fw_ver(data);
if (ver < 0) {
dev_err(&client->dev, "%s: fail to read fw ver (%d)\n.",
__func__, ver);
ret = sprintf(buf, "%s\n", "error");
goto out;
}
dev_info(&client->dev, "%s: %#x\n", __func__, (u8)ver);
ret = sprintf(buf, "%#x\n", (u8)ver);
out:
return ret;
}
static ssize_t fac_fw_ver_src_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int ret;
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = sprintf(buf, "%#x\n", TC360_CUR_FW_VER);
dev_info(&client->dev, "%s: %#x\n", __func__, TC360_CUR_FW_VER);
out:
return ret;
}
static ssize_t fac_fw_update_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int ret;
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
/* to do : check data is valid */
data->fdata->fw_flash_status = DOWNLOADING;
disable_irq(client->irq);
ret = tc360_flash_fw(data, FW_BUILT_IN, true);
if (ret < 0) {
data->fdata->fw_flash_status = FAIL;
goto out;
}
ret = 0;
out:
return ret;
}
static ssize_t fac_fw_update_store(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int ret;
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
if (*buf != 's' && *buf != 'S') {
dev_err(&client->dev, "%s: invalid parameter\n.", __func__);
ret = -EINVAL;
goto out;
}
/* to do : check data is valid */
data->fdata->fw_flash_status = DOWNLOADING;
disable_irq(client->irq);
data->enabled = false;
ret = tc360_flash_fw(data, FW_BUILT_IN, true);
if (ret < 0) {
data->fdata->fw_flash_status = FAIL;
goto out;
}
ret = 0;
out:
return ret;
}
static ssize_t fac_fw_update_status_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int ret;
switch (data->fdata->fw_flash_status) {
case DOWNLOADING:
ret = sprintf(buf, "%s\n", TO_STRING(DOWNLOADING));
break;
case FAIL:
ret = sprintf(buf, "%s\n", TO_STRING(FAIL));
break;
case PASS:
ret = sprintf(buf, "%s\n", TO_STRING(PASS));
break;
default:
dev_err(&client->dev, "%s: invalid status\n", __func__);
ret = 0;
goto out;
}
dev_info(&client->dev, "%s: %#x\n", __func__,
data->fdata->fw_flash_status);
out:
return ret;
}
static ssize_t fac_read_diff_menu_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int menu_sensitivity;
int ret;
u8 rbuf[20];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = ARRAY_SIZE(rbuf),
};
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read (%d)\n", ret);
menu_sensitivity = rbuf[6] << 8 | rbuf[7];
ret = sprintf(buf, "%d\n", menu_sensitivity);
dev_info(&client->dev, "%s: %d\n", __func__, menu_sensitivity);
out:
return ret;
}
static ssize_t fac_read_diff_back_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int back_sensitivity;
int ret;
u8 rbuf[20];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = ARRAY_SIZE(rbuf),
};
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read (%d)\n", ret);
back_sensitivity = rbuf[14] << 8 | rbuf[15];
ret = sprintf(buf, "%d\n", back_sensitivity);
dev_info(&client->dev, "%s: %d\n", __func__, back_sensitivity);
out:
return ret;
}
static ssize_t fac_read_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int Threshold;
int ret;
u8 rbuf[20];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = ARRAY_SIZE(rbuf),
};
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read (%d)\n", ret);
Threshold = rbuf[4] << 8 | rbuf[5];
ret = sprintf(buf, "%d\n", Threshold);
dev_info(&client->dev, "%s: %d\n", __func__, Threshold);
out:
return ret;
}
static ssize_t fac_read_raw_menu_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int menu_raw;
int ret;
u8 rbuf[20];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = ARRAY_SIZE(rbuf),
};
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read (%d)\n", ret);
menu_raw = rbuf[10] << 8 | rbuf[11];
ret = sprintf(buf, "%d\n", menu_raw);
dev_info(&client->dev, "%s: %d\n", __func__, menu_raw);
out:
return ret;
}
static ssize_t fac_read_raw_back_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tc360_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
int back_raw;
int ret;
u8 rbuf[20];
struct i2c_msg msg = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = rbuf,
.len = ARRAY_SIZE(rbuf),
};
if (!data->enabled) {
dev_err(&client->dev, "%s: device is disabled\n.", __func__);
ret = -EPERM;
goto out;
}
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "failed to read (%d)\n", ret);
back_raw = rbuf[18] << 8 | rbuf[19];
ret = sprintf(buf, "%d\n", back_raw);
dev_info(&client->dev, "%s: %d\n", __func__, back_raw);
out:
return ret;
}
static DEVICE_ATTR(touchkey_firm_version_panel, S_IRUGO | S_IWUSR | S_IWGRP,
fac_fw_ver_ic_show, NULL);
static DEVICE_ATTR(touchkey_firm_version_phone, S_IRUGO | S_IWUSR | S_IWGRP,
fac_fw_ver_src_show, NULL);
static DEVICE_ATTR(touchkey_firm_update, S_IRUGO | S_IWUSR | S_IWGRP,
fac_fw_update_show, fac_fw_update_store);
static DEVICE_ATTR(touchkey_firm_update_status, S_IRUGO,
fac_fw_update_status_show, NULL);
static DEVICE_ATTR(touchkey_menu, S_IRUGO,
fac_read_diff_menu_show, NULL);
static DEVICE_ATTR(touchkey_back, S_IRUGO,
fac_read_diff_back_show, NULL);
static DEVICE_ATTR(touchkey_threshold, S_IRUGO,
fac_read_threshold_show, NULL);
static DEVICE_ATTR(touchkey_raw_data0, S_IRUGO, fac_read_raw_menu_show, NULL);
static DEVICE_ATTR(touchkey_raw_data1, S_IRUGO, fac_read_raw_back_show, NULL);
static struct attribute *fac_attributes[] = {
&dev_attr_touchkey_firm_version_panel.attr,
&dev_attr_touchkey_firm_version_phone.attr,
&dev_attr_touchkey_firm_update.attr,
&dev_attr_touchkey_firm_update_status.attr,
&dev_attr_touchkey_menu.attr,
&dev_attr_touchkey_back.attr,
&dev_attr_touchkey_threshold.attr,
&dev_attr_touchkey_raw_data0.attr,
&dev_attr_touchkey_raw_data1.attr,
NULL,
};
static struct attribute_group fac_attr_group = {
.attrs = fac_attributes,
};
#endif
static int tc360_init_interface(struct tc360_data *data)
{
struct i2c_client *client = data->client;
int ret;
#if defined(SEC_FAC_TK)
data->fdata->dummy_dev = device_create(sec_class, NULL, (dev_t)NULL,
data, "sec_touchkey");
if (IS_ERR(data->fdata->dummy_dev)) {
dev_err(&client->dev, "Failed to create fac tsp temp dev\n");
ret = -ENODEV;
data->fdata->dummy_dev = NULL;
goto err_create_sec_class_dev;
}
ret = sysfs_create_group(&data->fdata->dummy_dev->kobj,
&fac_attr_group);
if (ret) {
dev_err(&client->dev, "%s: failed to create fac_attr_group "
"(%d)\n", __func__, ret);
ret = (ret > 0) ? -ret : ret;
goto err_create_fac_attr_group;
}
#endif
return 0;
#if defined(SEC_FAC_TK)
err_create_fac_attr_group:
device_destroy(sec_class, (dev_t)NULL);
err_create_sec_class_dev:
#endif
return ret;
}
static void tc360_destroy_interface(struct tc360_data *data)
{
#if defined(SEC_FAC_TK)
sysfs_remove_group(&data->fdata->dummy_dev->kobj,
&fac_attr_group);
device_destroy(sec_class, (dev_t)NULL);
#endif
}
static int __devinit tc360_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct tc360_data *data;
struct input_dev *input_dev;
int ret;
int i;
if (client->dev.platform_data) {
struct tc360_platform_data *pdata =
client->dev.platform_data;
}
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
return -EIO;
data = kzalloc(sizeof(struct tc360_data), GFP_KERNEL);
if (!data) {
dev_err(&client->dev, "failed to alloc memory\n");
ret = -ENOMEM;
goto err_data_alloc;
}
#if defined(SEC_FAC_TK)
data->fdata = kzalloc(sizeof(struct fdata_struct), GFP_KERNEL);
if (!data->fdata) {
dev_err(&client->dev, "failed to alloc memory for fdata\n");
ret = -ENOMEM;
goto err_data_alloc_fdata;
}
#endif
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(&client->dev, "failed to allocate input device\n");
ret = -ENOMEM;
goto err_input_devalloc;
}
data->client = client;
data->pdata = client->dev.platform_data;
data->input_dev = input_dev;
i2c_set_clientdata(client, data);
data->num_key = data->pdata->num_key;
dev_info(&client->dev, "number of keys= %d\n", data->num_key);
data->keycodes = data->pdata->keycodes;
#if 0
for (i = 0; i < data->num_key; i++)
dev_info(&client->dev, "keycode[%d]= %3d\n", i,
data->keycodes[i]);
#endif
data->pdata = client->dev.platform_data;
data->suspend_type = data->pdata->suspend_type;
data->scl = data->pdata->gpio_scl;
data->sda = data->pdata->gpio_sda;
data->udelay = data->pdata->udelay;
mutex_init(&data->lock);
wake_lock_init(&data->fw_wake_lock, WAKE_LOCK_SUSPEND,
"tc360_fw_wake_lock");
client->irq = client->irq;
snprintf(data->phys, sizeof(data->phys), "%s/input0",
dev_name(&client->dev));
input_dev->name = "sec_touchkey";
input_dev->phys = data->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->dev.parent = &client->dev;
input_dev->keycode = data->keycodes;
input_dev->keycodesize = sizeof(data->keycodes[0]);
input_dev->keycodemax = data->num_key;
set_bit(EV_ABS, input_dev->evbit);
set_bit(EV_LED, input_dev->evbit);
set_bit(LED_MISC, input_dev->ledbit);
for (i = 0; i < data->num_key; i++) {
input_set_capability(input_dev, EV_KEY, data->keycodes[i]);
set_bit(data->keycodes[i], input_dev->keybit);
}
i2c_set_clientdata(client, data);
input_set_drvdata(input_dev, data);
ret = input_register_device(data->input_dev);
if (ret) {
dev_err(&client->dev, "fail to register input_dev (%d).\n",
ret);
goto err_register_input_dev;
}
ret = tc360_init_interface(data);
if (ret < 0) {
dev_err(&client->dev, "failed to init interface (%d)\n", ret);
goto err_init_interface;
}
data->led_wq = create_singlethread_workqueue(client->name);
if (!data->led_wq) {
dev_err(&client->dev, "fail to create workqueue for led_wq\n");
ret = -ENOMEM;
goto err_create_led_wq;
}
INIT_WORK(&data->led_work, tc360_led_worker);
data->led.name = "button-backlight";
data->led.brightness = LED_OFF;
data->led.max_brightness = LED_FULL;
data->led.brightness_set = tc360_led_set;
ret = led_classdev_register(&client->dev, &data->led);
if (ret) {
dev_err(&client->dev, "fail to register led_classdev (%d).\n",
ret);
goto err_register_led;
}
data->fw_wq = create_singlethread_workqueue(client->name);
if (!data->fw_wq) {
dev_err(&client->dev, "fail to create workqueue for fw_wq\n");
ret = -ENOMEM;
goto err_create_fw_wq;
}
INIT_WORK(&data->fw_work, tc360_fw_update_worker);
if (client->irq) {
dev_info(&client->dev, "Register touchkey irq.\n");
ret = request_threaded_irq(client->irq, NULL, tc360_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
TC360_DEVICE, data);
if (ret) {
dev_err(&client->dev, "fail to request irq (%d).\n",
client->irq);
goto err_request_irq;
}
}
#if 0 /* auto update */
disable_irq(client->irq);
#endif
ret = data->pdata->setup_power(&client->dev, true);
if (ret < 0) {
dev_err(&client->dev, "fail to setup power (%d).\n", ret);
goto err_setup_power;
}
data->pdata->power(true);
msleep(TC360_POWERON_DELAY);
/*
* The tc360_initialize function create singlethread_workqueue. If the
* probe function is failed while the thread is running, the driver
* cause kernel panic. So, the tc360_initialize function must be on
* the bottom on probe function.
*/
#if 0 /* auto update */
ret = tc360_initialize(data);
if (ret < 0) {
dev_err(&client->dev, "fail to tc360 initialize (%d).\n",
ret);
goto err_initialize;
}
switch (ret) {
case HAVE_LATEST_FW:
data->enabled = true;
enable_irq(client->irq);
break;
case FW_UPDATE_RUNNING:
/*
* now fw update thread is running.
* this thread will enable interrupt
*/
data->enabled = false;
break;
}
#else
data->enabled = true;
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
data->early_suspend.suspend = tc360_early_suspend;
data->early_suspend.resume = tc360_late_resume;
/* register_early_suspend(&data->early_suspend); */
#endif
dev_info(&client->dev, "successfully probed.\n");
return 0;
err_initialize:
data->pdata->setup_power(NULL, false);
err_setup_power:
free_irq(client->irq, data);
err_request_irq:
destroy_workqueue(data->fw_wq);
err_create_fw_wq:
led_classdev_unregister(&data->led);
err_register_led:
destroy_workqueue(data->led_wq);
err_create_led_wq:
tc360_destroy_interface(data);
err_init_interface:
input_unregister_device(input_dev);
err_register_input_dev:
input_free_device(input_dev);
err_input_devalloc:
#if defined(SEC_FAC_TK)
kfree(data->fdata);
err_data_alloc_fdata:
#endif
kfree(data);
err_data_alloc:
return ret;
}
static int __devexit tc360_remove(struct i2c_client *client)
{
struct tc360_data *data = i2c_get_clientdata(client);
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&data->early_suspend);
#endif
free_irq(client->irq, data);
gpio_free(data->pdata->gpio_int);
led_classdev_unregister(&data->led);
destroy_workqueue(data->led_wq);
destroy_workqueue(data->fw_wq);
input_unregister_device(data->input_dev);
input_free_device(data->input_dev);
data->pdata->setup_power(NULL, false);
#if defined(SEC_FAC_TK)
kfree(data->fdata);
#endif
kfree(data);
return 0;
}
#if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND)
static int tc360_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct tc360_data *data = i2c_get_clientdata(client);
int i;
int ret;
mutex_lock(&data->lock);
#if 0
/* now the firmware is being updated. IC will keep power state. */
if (unlikely(wake_lock_active(&data->fw_wake_lock))) {
dev_info(&data->client->dev, "%s, now fw updating. suspend "
"control is ignored.\n", __func__);
goto out;
}
/* fw flash is failed. and the driver is removed */
if (unlikely(data->fw_flash_state == STATE_FLASH_FAIL)) {
dev_info(&data->client->dev, "%s: fail to fw flaash. "
"driver is removed\n", __func__);
if (data->fw_wq) {
dev_err(&client->dev, "%s: destroy wq for fw flash\n",
__func__);
destroy_workqueue(data->fw_wq);
data->fw_wq = NULL;
}
goto out;
}
if (unlikely(!data->enabled)) {
dev_info(&client->dev, "%s, already disabled.\n", __func__);
goto out;
}
data->enabled = false;
disable_irq(client->irq);
printk("[TouchKey] disable_irq...\n");
/* report not released key */
for (i = 0; i < data->num_key; i++)
input_report_key(data->input_dev, data->keycodes[i], 0);
input_sync(data->input_dev);
data->pdata->power(false);
IsTouchkeyPowerOn = 0;
/* GPIO_pullup_setting(0);*/ /*no pull*/
gpio_direction_output(data->pdata->gpio_sda, 0);
gpio_direction_output(data->pdata->gpio_scl, 0);
#endif
out:
mutex_unlock(&data->lock);
dev_info(&client->dev, "%s (%#x)\n", __func__, data->fw_flash_state);
return 0;
}
static int tc360_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct tc360_data *data = i2c_get_clientdata(client);
mutex_lock(&data->lock);
if (unlikely(wake_lock_active(&data->fw_wake_lock))) {
dev_info(&data->client->dev, "%s, now fw updating. resume "
"control is ignored.\n", __func__);
goto out;
}
/* fw flash is failed. and the driver is removed */
if (unlikely(data->fw_flash_state == STATE_FLASH_FAIL)) {
dev_info(&data->client->dev, "%s: fail to fw flaash. "
"driver is removed\n", __func__);
goto out;
}
if (unlikely(data->enabled)) {
dev_info(&client->dev, "%s, already enabled.\n", __func__);
goto out;
}
printk(KERN_DEBUG "[TouchKey] resume\n");
/* GPIO_pullup_setting(1);*/ /*pull up*/
data->pdata->power(true);
IsTouchkeyPowerOn = 1;
if (data->led_brightness == 100)
touchkey_led_on(data, 1);
printk("[TouchKey] enable_irq...\n");
out:
enable_irq(client->irq);
data->enabled = true;
mutex_unlock(&data->lock);
dev_info(&client->dev, "%s\n", __func__);
return 0;
}
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
static void tc360_early_suspend(struct early_suspend *h)
{
struct tc360_data *data;
data = container_of(h, struct tc360_data, early_suspend);
tc360_suspend(&data->client->dev);
}
static void tc360_late_resume(struct early_suspend *h)
{
struct tc360_data *data;
data = container_of(h, struct tc360_data, early_suspend);
tc360_resume(&data->client->dev);
}
#endif
#if defined(CONFIG_PM) || defined(CONFIG_HAS_EARLYSUSPEND)
static const struct dev_pm_ops tc360_pm_ops = {
.suspend = tc360_suspend,
.resume = tc360_resume,
};
#endif
static const struct i2c_device_id tc360_id[] = {
{ TC360_DEVICE, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tc360_id);
static struct i2c_driver tc360_driver = {
.probe = tc360_probe,
.remove = __devexit_p(tc360_remove),
.driver = {
.name = TC360_DEVICE,
.owner = THIS_MODULE,
#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND)
.pm = &tc360_pm_ops,
#endif
},
.id_table = tc360_id,
};
static int __devinit tc360_init(void)
{
int ret = 0;
int rc;
printk(KERN_INFO "[TouchKey] %s\n", __func__);
rc = gpio_request(TOUCHKEY_INT, "touchkey_int");
if (rc < 0) {
printk(KERN_ERR "unable to request GPIO pin %d\n", TOUCHKEY_INT);
}
gpio_direction_input(TOUCHKEY_INT);
gpio_direction_output(TOUCHKEY_SCL , 1);
gpio_direction_output(TOUCHKEY_SDA , 1);
return i2c_add_driver(&tc360_driver);
}
static void __exit tc360_exit(void)
{
i2c_del_driver(&tc360_driver);
}
late_initcall(tc360_init);
module_exit(tc360_exit);
MODULE_DESCRIPTION("CORERIVER TC360 touchkey driver");
MODULE_AUTHOR("Taeyoon Yoon <tyoony.yoon@samsung.com>");
MODULE_LICENSE("GPL");