/* * GSC3280 SoC ADC(SPI0) Controller Driver * * Copyright (C) 2013 BLX IC Design Corp.,Ltd. * Author: Davied, apple_guet@126.com * */ #include #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("Input driver to loongson gsc3280 touchscreen"); MODULE_LICENSE("GPL");