aboutsummaryrefslogtreecommitdiff
path: root/drivers/input/misc/a3g_button.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/misc/a3g_button.c')
-rw-r--r--drivers/input/misc/a3g_button.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/drivers/input/misc/a3g_button.c b/drivers/input/misc/a3g_button.c
new file mode 100644
index 00000000000..978ed41af9a
--- /dev/null
+++ b/drivers/input/misc/a3g_button.c
@@ -0,0 +1,643 @@
+/*
+ * Button driver to support Apollo 3G board
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/leds.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include <linux/suspend.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/signal.h>
+#include <linux/freezer.h>
+#include <linux/ioport.h>
+
+#include <linux/netlink.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/major.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+
+
+extern void __iomem *led_port;
+
+#define DRV_NAME "a3g-button"
+
+#define EV_PRESSED 1
+#define EV_RELEASED 0
+#define RESET_BTN 0 /* reset button */
+
+#define _3G_BTN_BIT 0x04
+#define POL_RATE_MSECS 200
+#define MAX_EV_LEN (sizeof(struct input_event))
+
+int btn_ev_flag = 0;
+struct task_struct * btn_threadptr;
+
+struct input_event btn_event;
+
+static struct resource a3g_res = {
+ .name = "cs2",
+ .start = 0x4e0100000ULL,
+ .end = 0x4e0100300ULL,
+ /*.flags = IORESOURCE_IO,*/
+ .flags = IORESOURCE_MEM,
+ };
+
+
+static void __iomem *button_port = NULL;
+
+
+struct btndev_hw_data {
+ int abs_event;
+ unsigned long buttons;
+};
+
+struct btndev {
+ int exist;
+ int open;
+ int minor;
+ struct input_handle handle;
+ wait_queue_head_t wait;
+ struct list_head client_list;
+ spinlock_t client_lock; /* protects client_list */
+ struct mutex mutex;
+ struct device dev;
+
+ struct list_head mixdev_node;
+ int mixdev_open;
+
+ struct btndev_hw_data packet;
+ unsigned int pkt_count;
+};
+
+struct btndev * btndev_ptr;
+
+#define BTNDEV_MINOR_BASE 31
+#define BTNDEV_MINORS 31
+#define BTNDEV_ZERO 0
+
+
+struct btndev_motion {
+ unsigned long buttons;
+};
+
+#define PACKET_QUEUE_LEN 16
+struct btndev_client {
+ struct fasync_struct *fasync;
+ struct btndev *btndev;
+ struct list_head node;
+
+ struct btndev_motion packets[PACKET_QUEUE_LEN];
+ unsigned int head, tail;
+ spinlock_t packet_lock;
+
+ signed char ps2[6];
+ unsigned char ready, buffer, bufsiz;
+ unsigned char imexseq, impsseq;
+ unsigned long last_buttons;
+};
+
+
+
+struct btndev *btndev_table[BTNDEV_MINORS];
+static DEFINE_MUTEX(btndev_table_mutex);
+
+static const struct input_device_id btndev_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) },
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(input, btndev_ids);
+static void btndev_remove_chrdev(struct btndev *btndev) {
+ mutex_lock(&btndev_table_mutex);
+ btndev_table[btndev->minor] = NULL;
+ mutex_unlock(&btndev_table_mutex);
+}
+
+
+/*********************************************************/
+static void btndev_mark_dead(struct btndev *btndev) {
+ mutex_lock(&btndev->mutex);
+ btndev->exist = 0;
+ mutex_unlock(&btndev->mutex);
+}
+
+/*********************************************************/
+static void btndev_cleanup(struct btndev *btndev)
+{
+ struct input_handle *handle = &btndev->handle;
+
+ btndev_mark_dead(btndev);
+ btndev_remove_chrdev(btndev);
+
+ if (btndev->open)
+ input_close_device(handle);
+}
+
+/**********************************************************/
+static void btndev_free(struct device * dev) {
+ struct btndev * btndevptr = container_of( dev, struct btndev, dev);
+ input_put_device( btndevptr->handle.dev);
+ kfree(btndevptr);
+}
+/**********************************************************/
+static int btndev_install_chrdev(struct btndev *btndev) {
+ btndev_table[btndev->minor] = btndev;
+ return 0;
+}
+
+/**********************************************************/
+static struct btndev *btndev_create(struct input_dev *dev,
+ struct input_handler *handler,
+ int minor)
+{
+ struct btndev *btndev;
+ int error;
+ btndev = kzalloc(sizeof(struct btndev), GFP_KERNEL);
+ if (!btndev) {
+ error = -ENOMEM;
+ goto err_out;
+ }
+
+
+ INIT_LIST_HEAD(&btndev->client_list);
+ INIT_LIST_HEAD(&btndev->mixdev_node);
+ spin_lock_init(&btndev->client_lock);
+ mutex_init(&btndev->mutex);
+ lockdep_set_subclass(&btndev->mutex, 0);
+ init_waitqueue_head(&btndev->wait);
+
+ dev_set_name(&btndev->dev, "event%d", minor);
+
+ btndev->minor = minor;
+ btndev->exist = 1;
+ btndev->handle.dev = input_get_device(dev);
+ btndev->handle.name = dev_name(&btndev->dev);
+ btndev->handle.handler = handler;
+ btndev->handle.private = btndev;
+
+ btndev->dev.class = &input_class;
+ if (dev)
+ btndev->dev.parent = &dev->dev;
+ btndev->dev.devt = MKDEV(INPUT_MAJOR, BTNDEV_MINOR_BASE + minor);
+ btndev->dev.release = btndev_free;
+ device_initialize(&btndev->dev);
+
+ error = input_register_handle(&(btndev->handle));
+ if (error) {
+ goto err_free_btndev;
+ }
+
+ error = btndev_install_chrdev(btndev);
+ if (error)
+ goto err_unregister_handle;
+
+ error = device_add(&btndev->dev);
+ if (error)
+ goto err_cleanup_btndev;
+
+ return btndev;
+
+ err_cleanup_btndev:
+ btndev_cleanup(btndev);
+ err_unregister_handle:
+ input_unregister_handle(&btndev->handle);
+ err_free_btndev:
+ put_device(&btndev->dev);
+ err_out:
+ return ERR_PTR(error);
+
+}
+
+
+/**********************************************************/
+static void btndev_destroy(struct btndev *btndev) {
+ device_del(&btndev->dev);
+ btndev_cleanup(btndev);
+ input_unregister_handle(&btndev->handle);
+ put_device(&btndev->dev);
+}
+
+
+/**********************************************************/
+static void btndev_event(struct input_handle *handle,
+ unsigned int type, unsigned int code, int value) {
+ switch( type ) {
+
+ case EV_KEY:
+ break;
+ case EV_REL:
+ break;
+ default:
+ break;
+ }
+}
+
+
+static int btndev_open_device(struct btndev *btndev) {
+ int retval;
+
+ retval = mutex_lock_interruptible(&btndev->mutex);
+ if (retval)
+ return retval;
+
+ if (!btndev->exist)
+ retval = -ENODEV;
+ else if (!btndev->open++) {
+ retval = input_open_device(&btndev->handle);
+ if (retval)
+ btndev->open--;
+ }
+
+ mutex_unlock(&btndev->mutex);
+ return retval;
+}
+
+
+/**********************************************************/
+static void btndev_attach_client(struct btndev *btndev, struct btndev_client *client) {
+ spin_lock(&btndev->client_lock);
+ list_add_tail_rcu(&client->node, &btndev->client_list);
+ spin_unlock(&btndev->client_lock);
+ synchronize_rcu();
+}
+
+/**********************************************************/
+static void btndev_detach_client(struct btndev *btndev,
+ struct btndev_client *client)
+{
+ spin_lock(&btndev->client_lock);
+ list_del_rcu(&client->node);
+ spin_unlock(&btndev->client_lock);
+ synchronize_rcu();
+}
+
+static int btndev_release(struct inode *inode, struct file *file) {
+ return 0;
+}
+
+/**********************************************************/
+static int btndev_open(struct inode *inode, struct file *file)
+{
+ struct btndev_client *client;
+ struct btndev *btndev;
+ int error;
+ int i;
+
+ i = iminor(inode) - BTNDEV_MINOR_BASE;
+
+ if (i >= BTNDEV_MINORS) {
+ printk(KERN_ERR "*** error btndev_open()\n");
+ return -ENODEV;
+ }
+
+ error = mutex_lock_interruptible(&btndev_table_mutex);
+ if (error) {
+ return error;
+ }
+ btndev = btndev_table[i];
+ if (btndev)
+ get_device(&btndev->dev);
+ mutex_unlock(&btndev_table_mutex);
+
+ if (!btndev) {
+ return -ENODEV;
+ }
+
+ client = kzalloc(sizeof(struct btndev_client), GFP_KERNEL);
+ if (!client) {
+ error = -ENOMEM;
+ goto err_put_btndev;
+ }
+
+ spin_lock_init(&client->packet_lock);
+ client->btndev = btndev;
+ btndev_attach_client(btndev, client);
+
+ error = btndev_open_device(btndev);
+ if (error)
+ goto err_free_client;
+
+ file->private_data = client;
+ return 0;
+
+ err_free_client:
+ btndev_detach_client(btndev, client);
+ kfree(client);
+ err_put_btndev:
+ put_device(&btndev->dev);
+ return error;
+}
+
+static ssize_t btndev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) {
+ return 0;
+}
+
+static int btndev_fasync(int fd, struct file *file, int on) {
+ struct btndev_client *client = file->private_data;
+
+ return fasync_helper(fd, file, on, &client->fasync);
+}
+
+static ssize_t btndev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) {
+ struct btndev_client *client = file->private_data;
+ struct btndev *btndev = client->btndev;
+ signed char data[MAX_EV_LEN];
+ int retval = 0;
+/*
+ if (!client->ready && !client->buffer && btndev->exist &&
+ (file->f_flags & O_NONBLOCK) && !btn_ev_flag) {
+*/
+ if (file->f_flags & O_NONBLOCK) {
+ /*
+ * This read is from VFT, copy data to user buffer anyway.
+ *
+ */
+ spin_lock_irq(&client->packet_lock);
+ if (count > MAX_EV_LEN)
+ count = MAX_EV_LEN;
+ memcpy(data, (char*)&btn_event, count);
+ /* client->buffer -= count; */
+ spin_unlock_irq(&client->packet_lock);
+ if (copy_to_user(buffer, data, count))
+ return -EFAULT;
+
+ return -EAGAIN;
+ }
+ retval = wait_event_interruptible(btndev->wait,
+ !btndev->exist ||
+ client->ready ||
+ btn_ev_flag );
+ if( btn_ev_flag ) {
+ btn_ev_flag = 0;
+ }
+ if (retval)
+ return retval;
+ if (!btndev->exist)
+ return -ENODEV;
+ spin_lock_irq(&client->packet_lock);
+ if (count > MAX_EV_LEN)
+ count = MAX_EV_LEN;
+ memcpy(data, (char*)&btn_event, count);
+ client->buffer -= count;
+ spin_unlock_irq(&client->packet_lock);
+ if (copy_to_user(buffer, data, count))
+ return -EFAULT;
+
+ return MAX_EV_LEN;
+}
+
+
+static unsigned int btndev_poll(struct file *file, poll_table *wait) {
+
+ return 0;
+}
+
+
+
+static int btndev_connect( struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id){
+
+ return 0;
+ }
+
+
+static void btndev_disconnect(struct input_handle *handle) {
+
+}
+
+
+static const struct file_operations btndev_fops = {
+ .owner = THIS_MODULE,
+ .read = btndev_read,
+ .write = btndev_write,
+ .poll = btndev_poll,
+ .open = btndev_open,
+ .release = btndev_release,
+ .fasync = btndev_fasync,
+};
+
+
+static struct input_handler btndev_handler = {
+ .event = btndev_event,
+ .connect = btndev_connect,
+ .disconnect = btndev_disconnect,
+ .fops = &btndev_fops,
+ .minor = BTNDEV_MINOR_BASE,
+ .name = "btndev",
+ .id_table = btndev_ids,
+};
+
+
+
+
+
+
+/**************************************************/
+u8 enable_cs2( void ){
+ u8 val, reg;
+
+ reg = readb(led_port);
+ val = (reg | 0x02);
+ writeb(val, led_port);
+ return reg;
+}
+
+/**************************************************/
+void disable_cs2( u8 val ){
+ val &= ~0x02;
+ writeb(val, led_port);
+}
+
+/**************************************************/
+static int is_button_pressed(void) {
+ u8 saved, val = EV_REL;
+
+ if( led_port ) {
+ saved = enable_cs2();
+ val = readb(button_port);
+ disable_cs2( saved );
+ }
+ return ((val & _3G_BTN_BIT) == _3G_BTN_BIT) ? EV_REL : EV_KEY ;
+}
+
+
+/**************************************************/
+
+static struct input_dev * input_dev;
+
+static int button_dev_init(struct platform_device *parent_pdev)
+{
+ int error;
+
+
+ input_dev = input_allocate_device();
+ if( !input_dev ) {
+ printk(KERN_ERR "*** a3g-button: unable to allocate input_dev\n");
+ return -ENOMEM;
+ }
+
+ input_dev->name = "rst_button";
+ input_dev->phys = "a3g/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability( input_dev, EV_MSC, MSC_SCAN);
+ __set_bit( EV_KEY, input_dev->evbit);
+ __set_bit( EV_REL, input_dev->relbit);
+ error = input_register_device(input_dev);
+
+ if( error ) {
+ input_free_device( input_dev );
+ printk(KERN_ERR "*** a3g-button: error input_register_device()\n");
+ return error;
+ }
+
+ error = input_register_handler( &btndev_handler);
+ if( error ) {
+ printk(KERN_ERR "*** a3g-button: error input_register_handler()\n");
+ input_free_device( input_dev );
+ /*btndev_destroy(btndev_ptr);*/
+ return error;
+ }
+
+ btndev_ptr = btndev_create( input_dev , &btndev_handler, BTNDEV_ZERO);
+ if( IS_ERR(btndev_ptr) ) {
+ input_free_device( input_dev );
+ printk(KERN_ERR "*** a3g-button: error btndev_create()\n");
+ return PTR_ERR( btndev_ptr );
+ }
+
+ return 0;
+}
+
+
+
+static int btn_thread( void * data ) {
+
+ struct task_struct * tsk = current;
+ struct sched_param param = { .sched_priority = 1};
+ int btn_last_state, btn_cur_state;
+ btn_cur_state = btn_last_state = EV_REL;
+
+
+ sched_setscheduler(tsk, SCHED_FIFO, &param);
+ set_freezable();
+
+ if( button_port ) {
+ btn_last_state = is_button_pressed();
+ }
+ while( !kthread_should_stop() ) {
+
+ if( button_port ) {
+ msleep(POL_RATE_MSECS);
+ if( button_port ) {
+ btn_cur_state = is_button_pressed();
+ }
+ if( btn_last_state != btn_cur_state ){
+/*
+ printk( KERN_INFO "state changed from %d to %d\n",
+ btn_last_state, btn_cur_state);
+*/
+ btn_last_state = btn_cur_state;
+ do_gettimeofday(&btn_event.time);
+ btn_event.type = RESET_BTN;
+ btn_event.code = btn_cur_state;
+ btn_event.value = btn_cur_state;
+ btn_ev_flag = 1; /* wake up event read */
+ if( btndev_ptr ) {
+ wake_up_interruptible(&(btndev_ptr->wait));
+ }
+
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/**************************************************/
+static int __init a3g_button_init(void) {
+
+ resource_size_t res_size;
+ struct resource *phys_res = &a3g_res;
+ int retval;
+
+ res_size = resource_size(phys_res);
+
+ if( !request_mem_region(phys_res->start, res_size, phys_res->name) ) {
+ printk(KERN_DEBUG "**** error request_mem_region()\n");
+ return -1;
+ }
+
+ button_port = ioremap(phys_res->start, res_size);
+ if (button_port == NULL) {
+ release_mem_region(phys_res->start, res_size);
+ printk(KERN_DEBUG "*** Error ioremap()");
+ return -1;
+ }
+
+
+ retval = button_dev_init(NULL);
+ if( retval != 0 ) {
+ printk( KERN_ERR "*** failed button_dev_init() %d\n", retval);
+ iounmap(button_port);
+ button_port = NULL;
+ release_mem_region(a3g_res.start, (a3g_res.end - a3g_res.start + 1));
+ return -1;
+ }
+
+ btn_threadptr = kthread_run( btn_thread, NULL, "btn_t");
+
+ retval = (btn_threadptr == NULL) ? -1 : 0;
+
+ return retval;
+
+ /* return platform_driver_register( &a3g_button_driver ); */
+}
+
+/**************************************************/
+static void __exit a3g_button_exit(void) {
+ /*platform_driver_unregister( &a3g_button_driver);*/
+ if( button_port ) {
+ iounmap(button_port);
+ button_port = NULL;
+ release_mem_region(a3g_res.start, (a3g_res.end - a3g_res.start + 1));
+ }
+
+ btndev_destroy( btndev_ptr );
+ if( btn_threadptr ) {
+ kthread_stop(btn_threadptr);
+ btn_threadptr = NULL;
+ }
+
+ input_unregister_handler( &btndev_handler);
+ input_unregister_device(input_dev);
+}
+
+module_init( a3g_button_init );
+module_exit( a3g_button_exit );
+
+MODULE_AUTHOR("Hai Le");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Support for reset button on Apollo3G board");
+MODULE_ALIAS("platform:" DRV_NAME);