373 lines
9.0 KiB
C
Executable File
373 lines
9.0 KiB
C
Executable File
/*
|
|
* GSC3280 SoC ADC(SPI0) Controller Driver
|
|
*
|
|
* Copyright (C) 2013 BLX IC Design Corp.,Ltd.
|
|
* Author: Davied, apple_guet@126.com
|
|
*
|
|
*/
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/input.h>
|
|
#include <linux/major.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/random.h>
|
|
#include <linux/time.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
|
|
|
|
#ifdef CONFIG_GSC3280_TS_DEV_DEBUG
|
|
#define DBG(msg...) do { \
|
|
printk(KERN_INFO msg); \
|
|
} while (0)
|
|
#else
|
|
#define DBG(msg...) do { } while(0)
|
|
#endif
|
|
|
|
|
|
#define TSDEV_MINOR_BASE 128
|
|
#define TSDEV_MINOR_MAX 32
|
|
/* First 16 devices are h3600_ts compatible; second 16 are h3600_tsraw */
|
|
#define TSDEV_MINOR_MASK ((TSDEV_MINOR_MAX >> 1) - 1) //15
|
|
#define TSDEV_BUFFER_SIZE 64
|
|
|
|
|
|
struct ts_dev {
|
|
char name[8];
|
|
u8 exist;
|
|
u8 minor;
|
|
int open;
|
|
volatile short x;
|
|
volatile short y;
|
|
volatile short z;
|
|
struct device dev;
|
|
struct list_head list;
|
|
struct input_handle handle;
|
|
};
|
|
struct ts_event {
|
|
short z;
|
|
short x;
|
|
short y;
|
|
short msec;
|
|
};
|
|
struct tsdev_client {
|
|
volatile u8 head;
|
|
volatile u8 tail;
|
|
spinlock_t lock;
|
|
struct ts_dev *tsdev;
|
|
struct list_head node;
|
|
struct fasync_struct *fasync;
|
|
struct ts_event event[TSDEV_BUFFER_SIZE];
|
|
};
|
|
|
|
#ifndef CONFIG_INPUT_TSDEV_SCREEN_X
|
|
#define CONFIG_INPUT_TSDEV_SCREEN_X 800
|
|
#endif
|
|
#ifndef CONFIG_INPUT_TSDEV_SCREEN_Y
|
|
#define CONFIG_INPUT_TSDEV_SCREEN_Y 480
|
|
#endif
|
|
|
|
static const int gXres = CONFIG_INPUT_TSDEV_SCREEN_X;
|
|
static const int gYres = CONFIG_INPUT_TSDEV_SCREEN_Y;
|
|
|
|
static struct ts_dev *tsdev_table[TSDEV_MINOR_MAX >> 1];
|
|
static DECLARE_WAIT_QUEUE_HEAD(ts_wait_queue);
|
|
|
|
|
|
static int tsdev_fasync(int fd, struct file *filp, int on)
|
|
{
|
|
int ret = 0;
|
|
struct tsdev_client *client = filp->private_data;
|
|
|
|
ret = fasync_helper(fd, filp, on, &client->fasync);
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
static int tsdev_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsdev_client *client = NULL;
|
|
int ret = 0, i = iminor(inode) - TSDEV_MINOR_BASE;
|
|
|
|
if ((i >= TSDEV_MINOR_MAX) || (!tsdev_table[i & TSDEV_MINOR_MASK])) {
|
|
DBG("!!!!tsdev minor error!\n");
|
|
return -ENODEV;
|
|
}
|
|
client = kzalloc(sizeof(struct tsdev_client), GFP_KERNEL);
|
|
if (!client) {
|
|
DBG("!!!!kzalloc error!\n");
|
|
return -ENOMEM;
|
|
}
|
|
i &= TSDEV_MINOR_MASK;
|
|
client->tsdev = tsdev_table[i];
|
|
list_add_tail(&client->node, &client->tsdev->list);
|
|
client->head = client->tail = 0;
|
|
spin_lock_init(&client->lock);
|
|
filp->private_data = client;
|
|
if (!client->tsdev->open++)
|
|
if (client->tsdev->exist)
|
|
ret = input_open_device(&client->tsdev->handle);
|
|
return ret;
|
|
}
|
|
|
|
static void tsdev_free(struct device *dev)
|
|
{
|
|
struct ts_dev *ts = container_of(dev, struct ts_dev, dev);
|
|
|
|
input_put_device(ts->handle.dev);
|
|
tsdev_table[ts->minor] = NULL;
|
|
kfree(ts);
|
|
}
|
|
|
|
static int tsdev_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsdev_client *client = filp->private_data;
|
|
|
|
tsdev_fasync(-1, filp, 0);
|
|
list_del(&client->node);
|
|
if (!--client->tsdev->open) {
|
|
if (client->tsdev->exist)
|
|
input_close_device(&client->tsdev->handle);
|
|
else
|
|
tsdev_free(&(client->tsdev->dev));
|
|
}
|
|
kfree(client);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tsdev_read(struct file *filp, char __user *buffer, size_t count, loff_t * ppos)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags = 0;
|
|
struct tsdev_client *client = filp->private_data;
|
|
|
|
if ((client->head == client->tail) && (filp->f_flags & O_NONBLOCK) ) {
|
|
DBG("no data or O_NONBLOCK error!!!!\n");
|
|
return -EAGAIN;
|
|
}
|
|
ret = wait_event_interruptible(ts_wait_queue, (client->head != client->tail));
|
|
if (ret)
|
|
return ret;
|
|
spin_lock_irqsave(&client->lock, flags);
|
|
while ((client->head != client->tail) && (ret + sizeof (struct ts_event) <= count)) {
|
|
if (copy_to_user (buffer + ret, client->event + client->tail, sizeof (struct ts_event))) {
|
|
spin_unlock_irqrestore(&client->lock, flags);
|
|
return ret;
|
|
}
|
|
client->tail = (client->tail + 1) & (TSDEV_BUFFER_SIZE - 1);
|
|
ret += sizeof (struct ts_event);
|
|
}
|
|
spin_unlock_irqrestore(&client->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/* No kernel lock - fine */
|
|
static unsigned int tsdev_poll(struct file *filp, poll_table * wait)
|
|
{
|
|
unsigned long flags = 0;
|
|
struct tsdev_client *client = filp->private_data;
|
|
|
|
poll_wait(filp, &ts_wait_queue, wait);
|
|
spin_lock_irqsave(&client->lock, flags);
|
|
if (client->head != client->tail) {
|
|
spin_unlock_irqrestore(&client->lock, flags);
|
|
return POLLIN | POLLRDNORM;
|
|
}
|
|
spin_unlock_irqrestore(&client->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct file_operations tsdev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tsdev_open,
|
|
.release = tsdev_release,
|
|
.read = tsdev_read,
|
|
.poll = tsdev_poll,
|
|
.fasync = tsdev_fasync,
|
|
};
|
|
|
|
|
|
static void tsdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
|
|
{
|
|
unsigned long flags = 0;
|
|
struct tsdev_client *client = NULL;
|
|
struct ts_dev *ts = handle->private;
|
|
|
|
switch (type) {
|
|
case EV_ABS:
|
|
switch (code) {
|
|
case ABS_X:
|
|
ts->x = value;
|
|
break;
|
|
case ABS_Y:
|
|
ts->y = value;
|
|
break;
|
|
case ABS_PRESSURE:
|
|
ts->z = value;
|
|
break;
|
|
}
|
|
break;
|
|
case EV_REL:
|
|
switch (code) {
|
|
case REL_X:
|
|
ts->x += value;
|
|
if (ts->x < 0)
|
|
ts->x = 0;
|
|
else if (ts->x > gXres)
|
|
ts->x = gXres;
|
|
break;
|
|
case REL_Y:
|
|
ts->y += value;
|
|
if (ts->y < 0)
|
|
ts->y = 0;
|
|
else if (ts->y > gYres)
|
|
ts->y = gYres;
|
|
break;
|
|
}
|
|
break;
|
|
case EV_KEY:
|
|
if ((code == BTN_TOUCH) || (code == BTN_MOUSE)) {
|
|
switch (value) {
|
|
case 0:
|
|
ts->z = 0;
|
|
break;
|
|
case 1:
|
|
ts->z = 1;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (type != EV_SYN)
|
|
return;
|
|
list_for_each_entry(client, &ts->list, node) {
|
|
if (client) {
|
|
spin_lock_irqsave(&client->lock, flags);
|
|
client->event[client->head].z = ts->z;
|
|
client->event[client->head].x = ts->x;
|
|
client->event[client->head].y = ts->y;
|
|
client->head = (client->head + 1) & (TSDEV_BUFFER_SIZE - 1);
|
|
kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
|
spin_unlock_irqrestore(&client->lock, flags);
|
|
}
|
|
}
|
|
wake_up_interruptible(&ts_wait_queue);
|
|
}
|
|
|
|
static int tsdev_connect(struct input_handler *handler, struct input_dev *dev,
|
|
const struct input_device_id *id)
|
|
{
|
|
int minor = 0, ret = 0;
|
|
struct ts_dev *ts = NULL;
|
|
|
|
for (minor = 0; ((minor < (TSDEV_MINOR_MAX >> 1)) && tsdev_table[minor]); minor++) {
|
|
;
|
|
}
|
|
if (minor >= (TSDEV_MINOR_MAX >> 1)) {
|
|
DBG("!!!!You have too many touchscreens!\n");
|
|
return -EBUSY;
|
|
}
|
|
ts = kzalloc(sizeof(struct ts_dev), GFP_KERNEL);
|
|
if (!ts) {
|
|
DBG("!!!!kmalloc error!\n");
|
|
return -ENOMEM;
|
|
}
|
|
INIT_LIST_HEAD(&ts->list);
|
|
dev_set_name(&ts->dev, "ts%d", minor);
|
|
ts->exist = 1;
|
|
ts->minor = minor;
|
|
ts->handle.dev = input_get_device(dev);
|
|
ts->handle.name = dev_name(&ts->dev);;
|
|
ts->handle.handler = handler;
|
|
ts->handle.private = ts;
|
|
ts->dev.class = &input_class;
|
|
if (dev) {
|
|
ts->dev.parent = &dev->dev;
|
|
}
|
|
ts->dev.devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor);
|
|
ts->dev.release = tsdev_free;
|
|
device_initialize(&ts->dev);
|
|
ret = input_register_handle(&ts->handle);
|
|
if (ret) {
|
|
DBG("!!!!register handler tsdev error!\n");
|
|
return -EFAULT;
|
|
}
|
|
tsdev_table[minor] = ts;
|
|
ret = device_add(&ts->dev);
|
|
if (ret) {
|
|
DBG("!!!!add tsdev class error!\n");
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void tsdev_disconnect(struct input_handle *handle)
|
|
{
|
|
struct ts_dev *ts = handle->private;
|
|
|
|
ts->exist = 0;
|
|
device_del(&ts->dev);
|
|
if (ts->minor != (TSDEV_MINOR_MAX >> 1))
|
|
input_unregister_handle(&ts->handle);
|
|
put_device(&ts->dev);
|
|
}
|
|
|
|
static struct input_device_id tsdev_ids[] = {
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT
|
|
| INPUT_DEVICE_ID_MATCH_RELBIT,
|
|
.evbit = {BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
|
|
.keybit = {[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT)},
|
|
.relbit = {BIT_MASK(REL_X) | BIT_MASK(REL_Y)},
|
|
},/* A mouse like device, at least one button, two relative axes */
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT
|
|
| INPUT_DEVICE_ID_MATCH_ABSBIT,
|
|
.evbit = {BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS)},
|
|
.keybit = {[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH)},
|
|
.absbit = {BIT_MASK(ABS_X) | BIT_MASK(ABS_Y)},
|
|
},/* A tablet like device, at least touch detection, two absolute axes */
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT,
|
|
.evbit = {BIT_MASK(EV_ABS)},
|
|
.absbit = {BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | BIT_MASK(ABS_PRESSURE)},
|
|
},/* A tablet like device with several gradations of pressure */
|
|
{},/* Terminating entry */
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(input, tsdev_ids);
|
|
|
|
static struct input_handler tsdev_handler = {
|
|
.event = tsdev_event,
|
|
.connect = tsdev_connect,
|
|
.disconnect = tsdev_disconnect,
|
|
.fops = &tsdev_fops,
|
|
.minor = TSDEV_MINOR_BASE,
|
|
.name = "tsdev",
|
|
.id_table = tsdev_ids,
|
|
};
|
|
|
|
static int __init tsdev_init(void)
|
|
{
|
|
DBG("####################\n");
|
|
input_register_handler(&tsdev_handler);
|
|
DBG("gXres = %d, gYres = %d\n", gXres, gYres);
|
|
printk(KERN_INFO "gsc3280 ts dev init\n");
|
|
DBG("####################\n");
|
|
return 0;
|
|
}
|
|
static void __exit tsdev_exit(void)
|
|
{
|
|
input_unregister_handler(&tsdev_handler);
|
|
}
|
|
module_init(tsdev_init);
|
|
module_exit(tsdev_exit);
|
|
|
|
MODULE_AUTHOR("Davied <apple_guet@126.com>");
|
|
MODULE_DESCRIPTION("Input driver to loongson gsc3280 touchscreen");
|
|
MODULE_LICENSE("GPL");
|
|
|