Re: [PATCH v2] input: mouse: add qci touchpad driver

Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]
From: Dmitry Torokhov
Date: Thursday, August 12, 2010 - 7:49 pm

On Thu, Aug 12, 2010 at 01:58:18PM -0400, Neil Leeder wrote:

Actually, since this is not a new touchpad but simply a PS/2 interface
it should be implemented as a serio driver, not input device driver.

Could you please tell me if the following works for you? Note that it
expects IRQ to be set up properly (edge vs. level trigger) by the
platform code

Thanks.

-- 
Dmitry

Input: wpce775x-serio - add driver for WPCE775x PS/2 interface

From: Dmitry Torokhov <dmitry.torokhov@gmail.com>

This is a driver for PS/2 interface part of Nuvoton WPCE775x family of
Advanced Embedded Controllers.

Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
---

 drivers/input/serio/Kconfig          |   12 +
 drivers/input/serio/Makefile         |    1 
 drivers/input/serio/wpce775x_serio.c |  377 ++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+), 1 deletions(-)
 create mode 100644 drivers/input/serio/wpce775x_serio.c


diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 3bfe8fa..259e550 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -215,7 +215,7 @@ config SERIO_AMS_DELTA
 	depends on MACH_AMS_DELTA
 	default y
 	select AMS_DELTA_FIQ
-	---help---
+	help
 	  Say Y here if you have an E3 and want to use its mailboard,
 	  or any standard AT keyboard connected to the mailboard port.
 
@@ -226,4 +226,14 @@ config SERIO_AMS_DELTA
 	  To compile this driver as a module, choose M here;
 	  the module will be called ams_delta_serio.
 
+config SERIO_WPCE775X
+	tristate "WPCE775x EC PS/2 interface support"
+	depends on I2C
+	help
+	  Say Y here if you have a system with Nuvoton WPCE775x Advanced
+	  Embedded Controller chip and want to use its PS/2 interface part.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wpce775x_serio.
+
 endif
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 84c80bf..e442550 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -24,3 +24,4 @@ obj-$(CONFIG_SERIO_RAW)		+= serio_raw.o
 obj-$(CONFIG_SERIO_AMS_DELTA)	+= ams_delta_serio.o
 obj-$(CONFIG_SERIO_XILINX_XPS_PS2)	+= xilinx_ps2.o
 obj-$(CONFIG_SERIO_ALTERA_PS2)	+= altera_ps2.o
+obj-$(CONFIG_SERIO_WPCE775X)	+= wpce775x_serio.o
diff --git a/drivers/input/serio/wpce775x_serio.c b/drivers/input/serio/wpce775x_serio.c
new file mode 100644
index 0000000..6c8dcee
--- /dev/null
+++ b/drivers/input/serio/wpce775x_serio.c
@@ -0,0 +1,377 @@
+/*
+ * Driver for PS/2 Interface part of WPCE775x Embedded Controller
+ *
+ * Copyright (c) 2010 Dmitry Torokhov
+ * Copyright (C) 2009 Quanta Computer Inc.
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ * Author: Hsin Wu <hsin.wu@quantatw.com>
+ * Author: Austin Lai <austin.lai@quantatw.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/serio.h>
+#include <linux/delay.h>
+
+struct wpce775x_ps2if {
+	struct i2c_client *client;
+	struct serio *serio;
+	unsigned int gpio;
+	unsigned int irq;
+	struct work_struct recv_work;
+	struct work_struct send_work;
+	spinlock_t send_lock;
+	unsigned char data_in[16];
+	unsigned char data_queued[8];
+	unsigned char data_out[8];
+	unsigned int out_len;
+	struct mutex pm_mutex;
+	bool opened;		/* protected by pm_mutex */
+	bool suspended;		/* protected by pm_mutex */
+};
+
+static DEFINE_MUTEX(wpce775x_wq_mutex);
+static struct workqueue_struct *wpce775x_wq;
+static int wpce775x_count;
+
+static void wpce775x_recv(struct work_struct *work)
+{
+	struct wpce775x_ps2if *ps2if =
+			container_of(work, struct wpce775x_ps2if, recv_work);
+	int count;
+	int i;
+
+	count = i2c_master_recv(ps2if->client, ps2if->data_in,
+				sizeof(ps2if->data_in));
+	if (count < 0) {
+		dev_err(&ps2if->client->dev,
+			"failed to read data, error %d", count);
+	} else {
+		for (i = 0; i < count; i++)
+			serio_interrupt(ps2if->serio, ps2if->data_in[1], 0);
+	}
+}
+
+static void wpce775x_send(struct work_struct *work)
+{
+	struct wpce775x_ps2if *ps2if =
+			container_of(work, struct wpce775x_ps2if, send_work);
+	unsigned long flags;
+	unsigned int len;
+	int error;
+
+	spin_lock_irqsave(&ps2if->send_lock, flags);
+
+	len = ps2if->out_len;
+	memcpy(ps2if->data_queued, ps2if->data_out, len);
+	ps2if->out_len = 0;
+
+	spin_unlock_irqrestore(&ps2if->send_lock, flags);
+
+	error = i2c_master_send(ps2if->client, ps2if->data_queued, len);
+	if (error < 0)
+		dev_err(&ps2if->client->dev,
+			"failed to send data, error: %d", error);
+}
+
+static int wpce775x_write(struct serio *serio, unsigned char data)
+{
+	struct wpce775x_ps2if *ps2if = serio->port_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ps2if->send_lock, flags);
+
+	ps2if->data_out[ps2if->out_len++] = data;
+	queue_work(wpce775x_wq, &ps2if->send_work);
+
+	spin_unlock_irqrestore(&ps2if->send_lock, flags);
+
+	return 0;
+}
+
+static irqreturn_t wpce775x_interrupt(int irq, void *dev_id)
+{
+	struct wpce775x_ps2if *ps2if = dev_id;
+
+	queue_work(wpce775x_wq, &ps2if->recv_work);
+
+	return IRQ_HANDLED;
+}
+
+static int wpce775x_start_wq(struct wpce775x_ps2if *ps2if)
+{
+	int retval;
+
+	retval = mutex_lock_interruptible(&wpce775x_wq_mutex);
+	if (retval)
+		return retval;
+
+	if (wpce775x_count == 0) {
+		wpce775x_wq = create_singlethread_workqueue("wpce775x-serio");
+		if (!wpce775x_wq) {
+			dev_err(&ps2if->client->dev,
+				"Failed to create wpce775x-serio workqueue\n");
+			retval = -ENOMEM;
+			goto out;
+		}
+	}
+
+	wpce775x_count++;
+
+out:
+	mutex_unlock(&wpce775x_wq_mutex);
+	return retval;
+}
+
+static void wpce775x_stop_wq(struct wpce775x_ps2if *ps2if)
+{
+	mutex_lock(&wpce775x_wq_mutex);
+
+	if (!--wpce775x_count)
+		destroy_workqueue(wpce775x_wq);
+
+	mutex_unlock(&wpce775x_wq_mutex);
+}
+
+static void wpce775x_enable(struct wpce775x_ps2if *ps2if)
+{
+	enable_irq(ps2if->irq);
+}
+
+static void wpce775x_disable(struct wpce775x_ps2if *ps2if)
+{
+	disable_irq(ps2if->irq);
+
+	cancel_work_sync(&ps2if->recv_work);
+	cancel_work_sync(&ps2if->send_work);
+}
+
+static int wpce775x_open(struct serio *serio)
+{
+	struct wpce775x_ps2if *ps2if = serio->port_data;
+	int retval;
+
+	retval = mutex_lock_interruptible(&ps2if->pm_mutex);
+	if (retval)
+		return retval;
+
+	retval = wpce775x_start_wq(ps2if);
+	if (retval)
+		goto out;
+
+	if (!ps2if->suspended)
+		wpce775x_enable(ps2if);
+
+	ps2if->opened = true;
+
+out:
+	mutex_unlock(&ps2if->pm_mutex);
+	return retval;
+}
+
+static void wpce775x_close(struct serio *serio)
+{
+	struct wpce775x_ps2if *ps2if = serio->port_data;
+
+	mutex_lock(&ps2if->pm_mutex);
+
+	if (!ps2if->suspended)
+		wpce775x_disable(ps2if);
+
+	wpce775x_stop_wq(ps2if);
+
+	ps2if->opened = false;
+
+	mutex_unlock(&ps2if->pm_mutex);
+}
+
+#ifdef CONFIG_PM
+static int wpce775x_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct wpce775x_ps2if *ps2if = i2c_get_clientdata(client);
+
+	mutex_lock(&ps2if->pm_mutex);
+
+	if (!ps2if->suspended && ps2if->opened)
+		wpce775x_disable(ps2if);
+
+	ps2if->suspended = true;
+
+	mutex_unlock(&ps2if->pm_mutex);
+
+	return 0;
+}
+
+static int wpce775x_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct wpce775x_ps2if *ps2if = i2c_get_clientdata(client);
+
+	mutex_lock(&ps2if->pm_mutex);
+
+	if (ps2if->suspended && ps2if->opened)
+		wpce775x_enable(ps2if);
+
+	ps2if->suspended = false;
+
+	mutex_unlock(&ps2if->pm_mutex);
+
+	return 0;
+}
+
+static const struct dev_pm_ops wpce775x_pm_ops = {
+	.suspend  = wpce775x_suspend,
+	.resume   = wpce775x_resume,
+};
+
+#endif
+
+static int __devinit wpce775x_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct wpce775x_ps2if *ps2if;
+	struct serio *serio;
+	int irq;
+	int error;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev, "missing required i2c functionality\n");
+		return -ENODEV;
+	}
+
+	ps2if = kzalloc(sizeof(struct wpce775x_ps2if), GFP_KERNEL);
+	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!ps2if || !serio) {
+		error = -ENOMEM;
+		goto err_free_mem;
+	}
+
+	serio->id.type = SERIO_8042;
+	serio->write = wpce775x_write;
+	serio->open = wpce775x_open;
+	serio->close = wpce775x_close;
+	serio->port_data = ps2if;
+	serio->dev.parent = &client->dev;
+
+	strlcpy(serio->name, "WPCE775x PS/2 Interface", sizeof(serio->name));
+
+	ps2if->client = client;
+	ps2if->gpio = client->irq;
+	ps2if->serio = serio;
+
+	INIT_WORK(&ps2if->send_work, wpce775x_send);
+	INIT_WORK(&ps2if->recv_work, wpce775x_recv);
+	spin_lock_init(&ps2if->send_lock);
+	mutex_init(&ps2if->pm_mutex);
+
+	error = gpio_request(ps2if->gpio, "wpce775x-serio");
+	if (error) {
+		dev_err(&client->dev,
+			"Failed to request GPIO %d\n", ps2if->gpio);
+		goto err_free_mem;
+	}
+
+	error = gpio_direction_input(ps2if->gpio);
+	if (error) {
+		dev_err(&client->dev,
+			"Failed to configure GPIO %d as input\n", ps2if->gpio);
+		goto err_free_gpio;
+	}
+
+	irq = gpio_to_irq(ps2if->gpio);
+	if (irq < 0) {
+		error = irq;
+		dev_err(&client->dev,
+			"Unable to get irq number for GPIO %d\n", ps2if->gpio);
+		goto err_free_gpio;
+	}
+
+	ps2if->irq = irq;
+
+	error = request_irq(ps2if->irq, wpce775x_interrupt, 0,
+				client->name, ps2if);
+	if (error) {
+		dev_err(&client->dev, "Unable to claim IRQ %d\n", ps2if->irq);
+		goto err_free_gpio;
+	}
+
+	disable_irq(client->irq);
+
+	dev_info(&client->dev, "WPCE775x PS/2 interface, irq %d\n", ps2if->irq);
+
+	serio_register_port(ps2if->serio);
+	i2c_set_clientdata(client, ps2if);
+
+	return 0;
+
+err_free_gpio:
+	gpio_free(ps2if->gpio);
+err_free_mem:
+	kfree(serio);
+	kfree(ps2if);
+	return error;
+}
+
+static int __devexit wpce775x_remove(struct i2c_client *client)
+{
+	struct wpce775x_ps2if *ps2if = i2c_get_clientdata(client);
+
+	serio_unregister_port(ps2if->serio);
+	free_irq(ps2if->irq, ps2if);
+	gpio_free(ps2if->gpio);
+	kfree(ps2if);
+
+	return 0;
+}
+
+static const struct i2c_device_id wpce775x_idtable[] = {
+	{ "wpce775x-ps2" , 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, wpce775x_idtable);
+
+static struct i2c_driver i2ctp_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name  = "wpce775x-ps2",
+#ifdef CONFIG_PM
+		.pm = &wpce775x_pm_ops,
+#endif
+	},
+	.probe	  = wpce775x_probe,
+	.remove	  = __devexit_p(wpce775x_remove),
+	.id_table = wpce775x_idtable,
+};
+
+static int __init wpce775x_init(void)
+{
+	return i2c_add_driver(&i2ctp_driver);
+}
+module_init(wpce775x_init);
+
+static void __exit wpce775x_exit(void)
+{
+	i2c_del_driver(&i2ctp_driver);
+}
+module_exit(wpce775x_exit);
+
+MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
+MODULE_DESCRIPTION("Nuvoton WPCE775x Embedded Controller driver (PS/2 interface part)");
+MODULE_LICENSE("GPL v2");
--
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]

Messages in current thread:
[PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Thu Aug 12, 9:49 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Stepan Moskovchenko, (Thu Aug 12, 10:25 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Thu Aug 12, 10:58 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Dmitry Torokhov, (Thu Aug 12, 7:49 pm)
RE: [PATCH v2] input: mouse: add qci touchpad driver, Datta, Shubhrajyoti, (Fri Aug 13, 1:36 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Trilok Soni, (Fri Aug 13, 2:34 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Fri Aug 13, 2:56 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Dmitry Torokhov, (Fri Aug 13, 5:54 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Tue Aug 17, 10:14 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Dmitry Torokhov, (Tue Aug 17, 11:05 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Wed Aug 18, 2:38 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Dmitry Torokhov, (Wed Aug 18, 10:33 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Thu Aug 19, 3:19 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Matthew Garrett, (Thu Aug 19, 4:35 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Fri Aug 20, 12:16 pm)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Wed Aug 25, 11:26 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Dmitry Torokhov, (Thu Aug 26, 7:49 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Thu Aug 26, 11:45 am)
Re: [PATCH v2] input: mouse: add qci touchpad driver, Neil Leeder, (Thu Aug 26, 2:00 pm)