blob: 0a62e1b4ba6b88bbf90d045142657cef7c8608f3 [file] [log] [blame]
/*
* Copyright (c) 2010 Viragelogic
*/
/*
* DMC FIT-10 touchscreen driver for Linux
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/init.h>
#include <linux/wait.h>
#ifdef CONFIG_TOUCHSCREEN_DMC_FIT_10_DEBUG
#define DMC_FIT10_DEBUG 1
#endif
#undef DMC_FIT10_DO_IDLE_MODE
#define DMC_FIT10_DEFAULT_SR 5
#define DMC_FIT10_DEFAULT_CM 2
#define DRIVER_DESC "DMC FIT-10 touchscreen driver"
#define DMC_FIT10_MAX_SIZE (8)
#define DMC_FIT10_RCV_STOPPED (0)
#define DMC_FIT10_RCV_ACK (1)
#define DMC_FIT10_RCV_COORD_PIN (2)
#define DMC_FIT10_RCV_COORD_DATA (3)
#define DMC_FIT10_ACK (0x6)
#define DMC_FIT10_NACK (0x15)
#define DMC_FIT10_SR_30PS (0x40)
#define DMC_FIT10_SR_50PS (0x41)
#define DMC_FIT10_SR_80PS (0x42)
#define DMC_FIT10_SR_100PS (0x43)
#define DMC_FIT10_SR_130PS (0x44)
#define DMC_FIT10_SR_150PS (0x45)
#define DMC_FIT10_SR_1OT (0x50)
#ifdef DMC_FIT10_DO_IDLE_MODE
typedef struct
{
int sr;
char description[32];
}dmc_fit10_sr_t;
dmc_fit10_sr_t s_rates[] = {
{DMC_FIT10_SR_30PS, "30 cords p/s"},
{DMC_FIT10_SR_50PS, "50 cords p/s"},
{DMC_FIT10_SR_80PS, "80 cords p/s"},
{DMC_FIT10_SR_100PS, "100 cords p/s"},
{DMC_FIT10_SR_130PS, "130 cords p/s"},
{DMC_FIT10_SR_150PS, "150 cords p/s"},
{DMC_FIT10_SR_1OT, "one cord per touch"}
};
#endif
#define DMC_FIT10_MODE_IDLE (0x5)
#define DMC_FIT10_MODE_CORD1 (0x1)
#define DMC_FIT10_MODE_CORD2 (0x21)
#define DMC_FIT10_MODE_CORD3 (0x31)
#define DMC_FIT10_MODE_END_CORD (0x02)
#define DMC_FIT10_IS_PIN_UP(x) \
((x) == 0x10 || (x) == 0x50 || \
(x) == 0x90 || (x) == 0xD0)
#define DMC_FIT10_IS_PIN_DOWN(x) \
((x) == 0x11 || (x) == 0x51 || \
(x) == 0x91 || (x) == 0xD1)
typedef struct dcm_fit10_cmd
{
unsigned receiver_state;
unsigned size;
unsigned char data[4];
}dcm_fit10_cmd_t;
typedef struct
{
dcm_fit10_cmd_t cmd;
char description[64];
}dmc_fit10_mode_t;
static dmc_fit10_mode_t cord_modes[] = {
{{DMC_FIT10_RCV_COORD_PIN, 1, {DMC_FIT10_MODE_CORD1}},
"1: pen-down ID + cord + pen-up ID + power saving"},
{{DMC_FIT10_RCV_COORD_PIN, 1, {DMC_FIT10_MODE_CORD2}},
"2: pen-up ID + cord + pen-down ID + pen-up ID"},
{{DMC_FIT10_RCV_COORD_PIN, 1, {DMC_FIT10_MODE_CORD3}},
"3: pen-down ID + cord + pen-up ID",}
};
DECLARE_WAIT_QUEUE_HEAD(answer_w_q);
/* unpack 10-bit coordinates */
#define DMC_FIT10_CORD_UNPACK(x, ptr) \
{ \
(x) = ((unsigned)*(ptr)) << 8; \
ptr++; \
(x) |= *(ptr); \
ptr++; \
}
typedef struct{
int acks;
int coords;
int errors;
} dmc_fit10_counters;
struct dmc_fit10 {
struct input_dev *dev;
struct serio *serio;
unsigned char data[DMC_FIT10_MAX_SIZE];
int data_fill;
char phys[32];
volatile int receiver_state;
dmc_fit10_counters counters;
};
static int dmc_fit10_send_command(struct dmc_fit10* dmc_fit10,
dcm_fit10_cmd_t *command)
{
int i;
dmc_fit10->receiver_state = command->receiver_state;
dmc_fit10->data_fill = 0;
for(i = 0; i < command->size; i++)
serio_write(dmc_fit10->serio, command->data[i]);
if(command->receiver_state == DMC_FIT10_RCV_ACK)
{
wait_event(answer_w_q,
(dmc_fit10->receiver_state ==
DMC_FIT10_RCV_STOPPED));
#ifdef DMC_FIT10_DEBUG
switch(dmc_fit10->data[0])
{
case DMC_FIT10_ACK:
case DMC_FIT10_NACK:
break;
default:
printk(KERN_ERR "DMC FIT-10: unknown response: %x\n",
dmc_fit10->data[0]);
}
#endif
return dmc_fit10->data[0];
}
return 0;
}
static irqreturn_t dmc_fit10_interrupt(struct serio *serio,
unsigned char data, unsigned int flags)
{
struct dmc_fit10* dmc_fit10 = serio_get_drvdata(serio);
struct input_dev *dev = dmc_fit10->dev;
switch(dmc_fit10->receiver_state)
{
case DMC_FIT10_RCV_ACK:
dmc_fit10->data[0] = data;
dmc_fit10->receiver_state = DMC_FIT10_RCV_STOPPED;
wake_up_all(&answer_w_q);
#ifdef DMC_FIT10_DEBUG
dmc_fit10->counters.acks++;
#endif
break;
case DMC_FIT10_RCV_COORD_PIN:
if(DMC_FIT10_IS_PIN_DOWN(data))
dmc_fit10->receiver_state = DMC_FIT10_RCV_COORD_DATA;
else if(DMC_FIT10_IS_PIN_UP(data))
{
input_report_key(dev, BTN_TOUCH, 0);
input_sync(dev);
}
#ifdef DMC_FIT10_DEBUG
else
dmc_fit10->counters.errors++;
#endif
break;
case DMC_FIT10_RCV_COORD_DATA:
dmc_fit10->data[dmc_fit10->data_fill] = data;
dmc_fit10->data_fill ++;
if(dmc_fit10->data_fill > 3)
{
int x = 0, y = 0;
unsigned char *ptr = &dmc_fit10->data[0];
DMC_FIT10_CORD_UNPACK(x, ptr);
DMC_FIT10_CORD_UNPACK(y, ptr);
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
input_report_key(dev, BTN_TOUCH, 1);
input_sync(dev);
dmc_fit10->data_fill = 0;
dmc_fit10->receiver_state = DMC_FIT10_RCV_COORD_PIN;
#ifdef DMC_FIT10_DEBUG
dmc_fit10->counters.coords++;
#endif
}
break;
#ifdef DMC_FIT10_DEBUG
default:
dmc_fit10->counters.errors++;
#endif
}
return IRQ_HANDLED;
}
static void dmc_fit10_disconnect(struct serio *serio)
{
struct dmc_fit10 *dmc_fit10 = serio_get_drvdata(serio);
int err;
static dcm_fit10_cmd_t end_cord_mode = {DMC_FIT10_RCV_ACK,
1, {DMC_FIT10_MODE_END_CORD}};
err = dmc_fit10_send_command(dmc_fit10, &end_cord_mode);
if(err != DMC_FIT10_ACK)
printk(KERN_DEBUG "DMC FIT-10: failed to switch "
"to idle mode, response: 0x%x\n", err);
else
printk(KERN_DEBUG "DMC FIT-10: switched back to idle mode\n");
#ifdef DMC_FIT10_DEBUG
printk(KERN_DEBUG "DMC FIT-10: counters: acks - %i, coords - %i, errors - %i\n",
dmc_fit10->counters.acks, dmc_fit10->counters.coords,
dmc_fit10->counters.errors);
#endif
input_get_device(dmc_fit10->dev);
input_unregister_device(dmc_fit10->dev);
serio_close(serio);
serio_set_drvdata(serio, NULL);
input_put_device(dmc_fit10->dev);
kfree(dmc_fit10);
}
static int dmc_fit10_connect(struct serio *serio, struct serio_driver *drv)
{
struct dmc_fit10 *dmc_fit10;
struct input_dev *input_dev;
int err;
#ifdef DMC_FIT10_DO_IDLE_MODE
static dcm_fit10_cmd_t idle_mode = {DMC_FIT10_RCV_ACK,
2, {DMC_FIT10_MODE_IDLE}};
#endif
dmc_fit10 = kzalloc(sizeof(struct dmc_fit10), GFP_KERNEL);
input_dev = input_allocate_device();
if (!dmc_fit10 || !input_dev) {
err = -ENOMEM;
goto no_mem;
}
dmc_fit10->serio = serio;
dmc_fit10->dev = input_dev;
snprintf(dmc_fit10->phys, sizeof(serio->phys), "%s/input0", serio->phys);
input_dev->name = "DMC FIT-10 touchscreen";
input_dev->phys = dmc_fit10->phys;
input_dev->id.bustype = BUS_RS232;
input_dev->id.vendor = SERIO_DMC_FIT10;
input_dev->id.product = 0x0;
input_dev->id.version = 0x0100;
input_dev->dev.parent = &serio->dev;
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, 1023, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
serio_set_drvdata(serio, dmc_fit10);
err = serio_open(serio, drv);
if (err)
{
printk(KERN_ERR "DMC FIT-10: open serial port failed: %x\n",
err);
goto open_failed;
}
err = input_register_device(dmc_fit10->dev);
if (err)
{
printk(KERN_ERR "DMC FIT-10: register input device failed: %x\n",
err);
goto register_failed;
}
#ifdef DMC_FIT10_DO_IDLE_MODE
idle_mode.data[1] = s_rates[DMC_FIT10_DEFAULT_SR].sr;
err = dmc_fit10_send_command(dmc_fit10, &idle_mode);
if(err != DMC_FIT10_ACK)
{
printk(KERN_ERR "DMC FIT-10: failed to switch to idle"
"mode, response - %x\n",err);
err = EIO;
goto register_failed;
}
printk(KERN_DEBUG "DMC FIT-10: switched to idle mode, sample rate: %s\n",
s_rates[DMC_FIT10_DEFAULT_SR].description);
#endif
dmc_fit10_send_command(dmc_fit10, &cord_modes[DMC_FIT10_DEFAULT_CM].cmd);
printk(KERN_DEBUG "DMC FIT-10: switched to cord mode %s\n",
cord_modes[DMC_FIT10_DEFAULT_CM].description);
return 0;
register_failed:
serio_close(serio);
open_failed:
serio_set_drvdata(serio, NULL);
no_mem:
input_free_device(input_dev);
kfree(dmc_fit10);
return err;
}
/*
* The serio driver structure.
*/
static struct serio_device_id dmc_fit10_serio_ids[] = {
{
.type = SERIO_RS232,
.proto = SERIO_DMC_FIT10,
.id = SERIO_ANY,
.extra = SERIO_ANY,
},
{ 0 }
};
MODULE_DEVICE_TABLE(serio, dmc_fit10_serio_ids);
static struct serio_driver dmc_fit10_drv = {
.driver = {
.name = "dmc_fit10",
},
.description = DRIVER_DESC,
.id_table = dmc_fit10_serio_ids,
.interrupt = dmc_fit10_interrupt,
.connect = dmc_fit10_connect,
.disconnect = dmc_fit10_disconnect,
};
/*
* The functions for inserting/removing us as a module.
*/
static int __init dmc_fit10_init(void)
{
return serio_register_driver(&dmc_fit10_drv);
}
static void __exit dmc_fit10_exit(void)
{
serio_unregister_driver(&dmc_fit10_drv);
}
module_init(dmc_fit10_init);
module_exit(dmc_fit10_exit);
MODULE_AUTHOR("Pavel Sokolov <pavel.sokolov@viragelogic.com>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");