@@ -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
@@ -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
new file mode 100644
@@ -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");