blob: e0647b449233ed09e199554772bbb721fd1341fd [file] [log] [blame]
/*
* drivers/input/touchscreen/scn0700.c
*
* Copyright (C) 2004-2012, Ambarella, Inc.
* Long Zhao <longzhao@ambarella.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.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/i2c/scn0700.h>
#ifdef CONFIG_DEBUG_TOUCHSCREEN
#define SCN0700_DEBUG(format, arg...) printk(format , ## arg)
#else
#define SCN0700_DEBUG(format, arg...)
#endif
static int int_mode = 0x0A;
module_param(int_mode, int, 0644);
MODULE_PARM_DESC(int_mode, "Set INT mode.");
#define MAX_Z 16
#define MAX_FINGERS 2
typedef enum {
MSI_TOUCHING = 0x00,
MSI_OLD_TOUCHING,
MSI_X1_LOW,
MSI_X1_HIGH,
MSI_Y1_LOW,
MSI_Y1_HIGH,
MSI_X2_LOW,
MSI_X2_HIGH,
MSI_Y2_LOW,
MSI_Y2_HIGH,
MSI_POWER_MODE = 0x14,
MSI_INT_MODE = 0x15,
} scn0700_sub_addr_t;
#define MODE_ACTIVE (0x00)
#define MODE_SLEEP (0x01)
#define MODE_DEEP_SLEEP (0x02)
#define MODE_FREEZE (0x03)
#define MODE_AUTO_SWITCH (0xA4)
#define INT_ASSERT_PERIOD (0x08)
#define INT_ASSERT_MOVING (0x09)
#define INT_ASSERT_TOUCH (0x0A)
#define NUM_DATA 10
struct scn0700 {
char phys[32];
struct input_dev *input;
struct i2c_client *client;
struct workqueue_struct *workqueue;
struct work_struct report_worker;
u8 reg_data[NUM_DATA];
int irq;
struct scn0700_fix_data fix;
int (*get_pendown_state)(void);
void (*clear_penirq)(void);
};
static int scn0700_set_power(struct scn0700 *msi, u8 mode)
{
if (i2c_smbus_write_i2c_block_data(msi->client,
MSI_POWER_MODE, 1, &mode)) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static int scn0700_config_irq(struct scn0700 *msi, u8 mode)
{
if (i2c_smbus_write_i2c_block_data(msi->client,
MSI_INT_MODE, 1, &mode)) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static inline int scn0700_read_all(struct scn0700 *msi)
{
if (i2c_smbus_read_i2c_block_data(msi->client,
MSI_TOUCHING, NUM_DATA, msi->reg_data) != NUM_DATA) {
printk("I2C Error: %s\n", __func__);
return -EIO;
} else {
return 0;
}
}
static void scn0700_send_event(struct scn0700 *msi)
{
struct input_dev *input = msi->input;
static int prev_touch = 0;
u8 i, t_num;
int x, y;
int event = 0;
t_num = msi->reg_data[MSI_TOUCHING];
/* Button Pressed */
if (!prev_touch && t_num) {
input_report_key(input, BTN_TOUCH, t_num);
SCN0700_DEBUG("Finger Pressed\n");
scn0700_config_irq(msi, INT_ASSERT_PERIOD);
SCN0700_DEBUG("Switch int mode to period\n");
}
SCN0700_DEBUG("prev_touch=%d\n", prev_touch);
/* Button Released */
if (prev_touch && !t_num) {
event = 1;
input_report_abs(input, ABS_PRESSURE, 0);
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_MT_TOUCH_MAJOR, 0);
input_mt_sync(input);
SCN0700_DEBUG("Finger Released\n\n\n");
scn0700_config_irq(msi, INT_ASSERT_TOUCH);
SCN0700_DEBUG("Switch int mode to one-touch\n");
}
for(i=0; i<t_num; i++) {
x = (msi->reg_data[MSI_X1_HIGH + 4*i]<<8) | msi->reg_data[MSI_X1_LOW + 4*i];
y = (msi->reg_data[MSI_Y1_HIGH + 4*i]<<8) | msi->reg_data[MSI_Y1_LOW + 4*i];
SCN0700_DEBUG("Finger%d Raw: (%d, %d)\n", i+1, x, y);
if (x < msi->fix.x_min) {
x = msi->fix.x_min;
}
if (x > msi->fix.x_max) {
x = msi->fix.x_max;
}
if (y < msi->fix.y_min) {
y = msi->fix.y_min;
}
if (y > msi->fix.y_max) {
y = msi->fix.y_max;
}
if (msi->fix.x_invert) {
x = msi->fix.x_max - x + msi->fix.x_min;
}
if (msi->fix.y_invert) {
y = msi->fix.y_max - y + msi->fix.y_min;
}
SCN0700_DEBUG("Finger%d Calibrated: (%d, %d)\n", i+1, x, y);
event = 1;
if (t_num == 1) {
input_report_abs(input, ABS_PRESSURE, MAX_Z);
input_report_abs(input, ABS_X, x);
input_report_abs(input, ABS_Y, y);
SCN0700_DEBUG("Report single point\n");
}
input_report_abs(input, ABS_MT_TOUCH_MAJOR, MAX_Z);
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
input_mt_sync(input);
SCN0700_DEBUG("Report multi point\n");
}
if (event) {
input_sync(input);
}
prev_touch = t_num;
}
static irqreturn_t scn0700_irq(int irq, void *handle)
{
struct scn0700 *msi = handle;
if (msi->get_pendown_state && !msi->get_pendown_state())
goto scn0700_irq_exit;
if (msi->clear_penirq) {
msi->clear_penirq();
}
queue_work(msi->workqueue, &msi->report_worker);
scn0700_irq_exit:
return IRQ_HANDLED;
}
static void scn0700_report_worker(struct work_struct *work)
{
struct scn0700 *msi;
#ifdef CONFIG_DEBUG_TOUCHSCREEN
int T_num, T_num_old;
int X1, Y1, X2, Y2;
#endif
msi = container_of(work, struct scn0700, report_worker);
scn0700_read_all(msi);
#ifdef CONFIG_DEBUG_TOUCHSCREEN
X1 = (msi->reg_data[MSI_X1_HIGH]<<8) | msi->reg_data[MSI_X1_LOW];
Y1 = (msi->reg_data[MSI_Y1_HIGH]<<8) | msi->reg_data[MSI_Y1_LOW];
X2 = (msi->reg_data[MSI_X2_HIGH]<<8) | msi->reg_data[MSI_X2_LOW];
Y2 = (msi->reg_data[MSI_Y2_HIGH]<<8) | msi->reg_data[MSI_Y2_LOW];
T_num = msi->reg_data[MSI_TOUCHING];
T_num_old = msi->reg_data[MSI_OLD_TOUCHING];
SCN0700_DEBUG("(X1, Y1)=(%d, %d)\n", X1, Y1);
SCN0700_DEBUG("(X2, Y2)=(%d, %d)\n", X2, Y2);
SCN0700_DEBUG("T_num=%d\n", T_num);
SCN0700_DEBUG("T_num_old=%d\n", T_num_old);
#endif
scn0700_send_event(msi);
}
static int scn0700_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct input_dev *input_dev;
struct scn0700 *msi;
struct scn0700_platform_data *pdata;
int err;
pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "platform data is required!\n");
return -EINVAL;
}
pdata->init_platform_hw();
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE |
I2C_FUNC_SMBUS_READ_BYTE))
return -EIO;
msi = kzalloc(sizeof(struct scn0700), GFP_KERNEL);
input_dev = input_allocate_device();
if (!msi || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
msi->client = client;
i2c_set_clientdata(client, msi);
msi->input = input_dev;
msi->get_pendown_state = pdata->get_pendown_state;
msi->clear_penirq = pdata->clear_penirq;
snprintf(msi->phys, sizeof(msi->phys),
"%s/input0", dev_name(&client->dev));
err = scn0700_set_power(msi, MODE_ACTIVE);
if (err)
goto err_free_mem;
msi->fix = pdata->fix;
err = scn0700_config_irq(msi, int_mode);
if (err)
goto err_free_mem;
err = scn0700_read_all(msi);
if (err)
goto err_free_mem;
input_dev->name = "Data Image SCN0700 Touchscreen";
input_dev->phys = msi->phys;
input_dev->id.bustype = BUS_I2C;
set_bit(EV_SYN, input_dev->evbit);
set_bit(EV_KEY, input_dev->evbit);
set_bit(BTN_TOUCH, input_dev->keybit);
set_bit(EV_ABS, input_dev->evbit);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_Z, 0, 0);
input_set_abs_params(input_dev, ABS_X, msi->fix.x_min, msi->fix.x_max, 0, 0);
input_set_abs_params(input_dev, ABS_Y, msi->fix.y_min, msi->fix.y_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, MAX_Z, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_X, msi->fix.x_min, msi->fix.x_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, msi->fix.y_min, msi->fix.y_max, 0, 0);
msi->workqueue = create_singlethread_workqueue("scn0700");
INIT_WORK(&msi->report_worker, scn0700_report_worker);
msi->irq = client->irq;
err = request_irq(msi->irq, scn0700_irq, IRQF_TRIGGER_FALLING,
client->dev.driver->name, msi);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", msi->irq);
goto err_free_mem;
}
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
dev_info(&client->dev, "%s is probed!\n", input_dev->name);
return 0;
err_free_irq:
free_irq(msi->irq, msi);
err_free_mem:
input_free_device(input_dev);
kfree(msi);
return err;
}
static int scn0700_remove(struct i2c_client *client)
{
struct scn0700 *msi = i2c_get_clientdata(client);
struct scn0700_platform_data *pdata = client->dev.platform_data;
pdata->exit_platform_hw();
destroy_workqueue(msi->workqueue);
free_irq(msi->irq, msi);
input_unregister_device(msi->input);
kfree(msi);
return 0;
}
static struct i2c_device_id scn0700_idtable[] = {
{ "scn0700", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, scn0700_idtable);
static struct i2c_driver scn0700_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "scn0700"
},
.id_table = scn0700_idtable,
.probe = scn0700_probe,
.remove = scn0700_remove,
};
static int __init scn0700_init(void)
{
return i2c_add_driver(&scn0700_driver);
}
static void __exit scn0700_exit(void)
{
i2c_del_driver(&scn0700_driver);
}
module_init(scn0700_init);
module_exit(scn0700_exit);
MODULE_AUTHOR("Long Zhao <longzhao@ambarella.com>");
MODULE_DESCRIPTION("Data Image SCN0700 TouchScreen Driver");
MODULE_LICENSE("GPL");