blob: e14ac262d56fd69a2556b40058d69137ad9a9717 [file] [log] [blame]
/*
* drivers/input/touchscreen/cy8ctmg.c
*
* Copyright (C) 2004-2009, Ambarella, Inc.
* Zhenwu Xue <zwxue@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/cy8ctmg.h>
#define X_RES 320
#define Y_RES 480
#define MAX_X (X_RES - 1)
#define MAX_Y (Y_RES - 1)
typedef enum {
CY_MODE = 0,
CY_REVERSE_MODE,
CY_X1_HIGH,
CY_X1_LOW,
CY_Y1_HIGH,
CY_Y1_LOW,
CY_X2_HIGH,
CY_X2_LOW,
CY_Y2_HIGH,
CY_Y2_LOW,
CY_FINGER_TOUCH,
CY_GESTURE_CODE,
CY_GESTURE_PARA_HIGH,
CY_GESTURE_PARA_LOW = 0x0d,
} cy8ctmg_sub_addr_t;
#define SCAN_PROCEEDING 0x00
#define SCAN_COMPLETE 0x08
#define SEND_INT_WHEN_TOUCHED 0x00
#define SEND_INT_WHEN_SCANNED 0x04
#define SCAN_NORMAL 0x00
#define SCAN_IDLE 0x01
#define SCAN_SLEEP 0x02
#define SCAN_DISABLED 0x03
#define CY_MODE_SCAN_BASED (SCAN_PROCEEDING | SEND_INT_WHEN_SCANNED | SCAN_NORMAL)
#define CY_SCAN_DATA() \
cy8ctmg_write_byte(ts, CY_MODE, CY_MODE_SCAN_BASED); \
cy8ctmg_write_byte(ts, CY_REVERSE_MODE, ~CY_MODE_SCAN_BASED);
struct cy8ctmg {
char phys[32];
struct input_dev *input;
struct i2c_client *client;
struct workqueue_struct *workqueue;
struct work_struct report_worker;
int irq;
struct cy8ctmg_fix_data fix;
int (*get_pendown_state)(void);
void (*clear_penirq)(void);
};
static inline int cy8ctmg_write_byte(struct cy8ctmg *cy, u8 addr, u8 data)
{
s32 error;
error = i2c_smbus_write_byte_data(cy->client, addr, data);
if (error < 0)
dev_err(&cy->client->dev, "i2c io error: %d\n", error);
return error;
}
static inline int cy8ctmg_set_sub_addr(struct cy8ctmg *cy, u8 sub_addr)
{
s32 error;
error = i2c_smbus_write_byte(cy->client, sub_addr);
if (error < 0)
dev_err(&cy->client->dev, "i2c io error: %d\n", error);
return error;
}
static inline int cy8ctmg_read_byte(struct cy8ctmg *cy)
{
s32 data;
data = i2c_smbus_read_byte(cy->client);
if (data < 0)
dev_err(&cy->client->dev, "i2c io error: %d\n", data);
return data;
}
static void cy8ctmg_send_event(void *cy, int touched)
{
struct cy8ctmg *ts = cy;
struct input_dev *input = ts->input;
static int prev_touch = 0;
static int curr_touch = 0;
int event = 0;
curr_touch = touched;
/* Button Pressed */
if (!prev_touch && curr_touch) {
event = 1;
input_report_key(ts->input, BTN_TOUCH, 1);
}
/* Button Released */
if (prev_touch && !curr_touch) {
event = 1;
input_report_key(ts->input, BTN_TOUCH, 0);
}
if (curr_touch) {
u8 x1h, x1l, y1h, y1l;
u32 x1, y1;
cy8ctmg_set_sub_addr(ts, CY_X1_HIGH);
x1h = cy8ctmg_read_byte(ts);
cy8ctmg_set_sub_addr(ts, CY_X1_LOW);
x1l = cy8ctmg_read_byte(ts);
cy8ctmg_set_sub_addr(ts, CY_Y1_HIGH);
y1h = cy8ctmg_read_byte(ts);
cy8ctmg_set_sub_addr(ts, CY_Y1_LOW);
y1l = cy8ctmg_read_byte(ts);
x1 = (x1h << 8) | x1l;
y1 = (y1h << 8) | y1l;
if (ts->fix.x_rescale) {
x1 = (x1 > ts->fix.x_min) ? (x1 - ts->fix.x_min) : 0;
x1 *= X_RES;
x1 /= (ts->fix.x_max - ts->fix.x_min);
if (x1 >= MAX_X)
x1 = MAX_X;
}
if (ts->fix.y_rescale) {
y1 = (y1 > ts->fix.y_min) ? (y1 - ts->fix.y_min) : 0;
y1 *= Y_RES;
y1 /= (ts->fix.y_max - ts->fix.y_min);
if (y1 >= MAX_Y)
y1 = MAX_Y;
}
if (ts->fix.x_invert)
x1 = MAX_X - x1;
if (ts->fix.y_invert)
y1 = MAX_Y - y1;
event = 1;
input_report_abs(input, ABS_X, x1);
input_report_abs(input, ABS_Y, y1);
}
if (event)
input_sync(input);
prev_touch = curr_touch;
}
static irqreturn_t cy8ctmg_irq(int irq, void *handle)
{
struct cy8ctmg *ts = handle;
if (ts->clear_penirq)
ts->clear_penirq();
queue_work(ts->workqueue, &ts->report_worker);
return IRQ_HANDLED;
}
static void cy8ctmg_report_worker(struct work_struct *work)
{
struct cy8ctmg *ts = container_of(work, struct cy8ctmg, report_worker);
int touched;
cy8ctmg_set_sub_addr(ts, CY_FINGER_TOUCH);
touched = cy8ctmg_read_byte(ts);
cy8ctmg_send_event(ts, touched);
msleep(80);
CY_SCAN_DATA()
}
static int cy8ctmg_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct input_dev *input_dev;
struct cy8ctmg *ts;
struct cy8ctmg_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;
ts = kzalloc(sizeof(struct cy8ctmg), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
ts->client = client;
i2c_set_clientdata(client, ts);
ts->input = input_dev;
ts->fix = pdata->fix;
ts->clear_penirq = pdata->clear_penirq;
snprintf(ts->phys, sizeof(ts->phys),
"%s/input0", dev_name(&client->dev));
input_dev->name = "CY8CTMG Touchscreen";
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, MAX_X, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MAX_Y, 0, 0);
ts->workqueue = create_singlethread_workqueue("cy8ctmg");
INIT_WORK(&ts->report_worker, cy8ctmg_report_worker);
ts->irq = client->irq;
err = request_irq(ts->irq, cy8ctmg_irq, 0, client->dev.driver->name, ts);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", ts->irq);
goto err_free_mem;
}
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
dev_info(&client->dev, "registered with irq (%d)\n", ts->irq);
CY_SCAN_DATA()
return 0;
err_free_irq:
free_irq(ts->irq, ts);
err_free_mem:
input_free_device(input_dev);
kfree(ts);
return err;
}
static int cy8ctmg_remove(struct i2c_client *client)
{
struct cy8ctmg *ts = i2c_get_clientdata(client);
struct cy8ctmg_platform_data *pdata = client->dev.platform_data;
pdata->exit_platform_hw();
destroy_workqueue(ts->workqueue);
free_irq(ts->irq, ts);
input_unregister_device(ts->input);
kfree(ts);
return 0;
}
static struct i2c_device_id cy8ctmg_idtable[] = {
{ "cy8ctmg", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, cy8ctmg_idtable);
static struct i2c_driver cy8ctmg_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "cy8ctmg"
},
.id_table = cy8ctmg_idtable,
.probe = cy8ctmg_probe,
.remove = cy8ctmg_remove,
};
static int __init cy8ctmg_init(void)
{
return i2c_add_driver(&cy8ctmg_driver);
}
static void __exit cy8ctmg_exit(void)
{
i2c_del_driver(&cy8ctmg_driver);
}
module_init(cy8ctmg_init);
module_exit(cy8ctmg_exit);
MODULE_AUTHOR("Zhenwu Xue <zwxue@ambarella.com>");
MODULE_DESCRIPTION("CY8CTMG TouchScreen Driver");
MODULE_LICENSE("GPL");