diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-25 07:59:01 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-25 07:59:01 -0700 |
commit | 3a99c6319064af3f2e18eb929f638d555dbf7a62 (patch) | |
tree | e611927f41142123dc8efed7e07a3a91151edb01 /drivers/input/serio | |
parent | 1dfd166e93f98892aa4427069a23ed73259983c8 (diff) | |
parent | 49327ad2bbbaf1945d5ba431522201574219d150 (diff) | |
download | linux-stable-3a99c6319064af3f2e18eb929f638d555dbf7a62.tar.gz linux-stable-3a99c6319064af3f2e18eb929f638d555dbf7a62.tar.bz2 linux-stable-3a99c6319064af3f2e18eb929f638d555dbf7a62.zip |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (75 commits)
Input: wacom - specify Cinitq supported tools
Input: ab8500-ponkey - fix IRQ freeing in error path
Input: adp5588-keys - use more obvious i2c_device_id name string
Input: ad7877 - switch to using threaded IRQ
Input: ad7877 - use attribute group to control visibility of attributes
Input: serio - add support for PS2Mult multiplexer protocol
Input: wacom - properly enable runtime PM
Input: ad7877 - filter events where pressure is beyond the maximum
Input: ad7877 - implement EV_KEY:BTN_TOUCH reporting
Input: ad7877 - implement specified chip select behavior
Input: hp680_ts_input - use cancel_delayed_work_sync()
Input: mousedev - correct lockdep annotation
Input: ads7846 - switch to using threaded IRQ
Input: serio - support multiple child devices per single parent
Input: synaptics - simplify pass-through port handling
Input: add ROHM BU21013 touch panel controller support
Input: omap4-keypad - wake-up on events & long presses
Input: omap4-keypad - fix interrupt line configuration
Input: omap4-keypad - SYSCONFIG register configuration
Input: omap4-keypad - use platform device helpers
...
Diffstat (limited to 'drivers/input/serio')
-rw-r--r-- | drivers/input/serio/Kconfig | 9 | ||||
-rw-r--r-- | drivers/input/serio/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/serio/i8042.c | 2 | ||||
-rw-r--r-- | drivers/input/serio/ps2mult.c | 318 | ||||
-rw-r--r-- | drivers/input/serio/serio.c | 125 |
5 files changed, 410 insertions, 45 deletions
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index 3bfe8fafc6ad..6256233d2bfb 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -226,4 +226,13 @@ config SERIO_AMS_DELTA To compile this driver as a module, choose M here; the module will be called ams_delta_serio. +config SERIO_PS2MULT + tristate "TQC PS/2 multiplexer" + help + Say Y here if you have the PS/2 line multiplexer like the one + present on TQC boads. + + To compile this driver as a module, choose M here: the + module will be called ps2mult. + endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 84c80bf7185e..dbbe37616c92 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o obj-$(CONFIG_HP_SDC) += hp_sdc.o obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o +obj-$(CONFIG_SERIO_PS2MULT) += ps2mult.o obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o obj-$(CONFIG_SERIO_LIBPS2) += libps2.o obj-$(CONFIG_SERIO_RAW) += serio_raw.o diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index f58513160480..18db5a8c7478 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -1063,7 +1063,7 @@ static long i8042_panic_blink(int state) #ifdef CONFIG_X86 static void i8042_dritek_enable(void) { - char param = 0x90; + unsigned char param = 0x90; int error; error = i8042_command(¶m, 0x1059); diff --git a/drivers/input/serio/ps2mult.c b/drivers/input/serio/ps2mult.c new file mode 100644 index 000000000000..6bce22e4e495 --- /dev/null +++ b/drivers/input/serio/ps2mult.c @@ -0,0 +1,318 @@ +/* + * TQC PS/2 Multiplexer driver + * + * Copyright (C) 2010 Dmitry Eremin-Solenikov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/serio.h> + +MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>"); +MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver"); +MODULE_LICENSE("GPL"); + +#define PS2MULT_KB_SELECTOR 0xA0 +#define PS2MULT_MS_SELECTOR 0xA1 +#define PS2MULT_ESCAPE 0x7D +#define PS2MULT_BSYNC 0x7E +#define PS2MULT_SESSION_START 0x55 +#define PS2MULT_SESSION_END 0x56 + +struct ps2mult_port { + struct serio *serio; + unsigned char sel; + bool registered; +}; + +#define PS2MULT_NUM_PORTS 2 +#define PS2MULT_KBD_PORT 0 +#define PS2MULT_MOUSE_PORT 1 + +struct ps2mult { + struct serio *mx_serio; + struct ps2mult_port ports[PS2MULT_NUM_PORTS]; + + spinlock_t lock; + struct ps2mult_port *in_port; + struct ps2mult_port *out_port; + bool escape; +}; + +/* First MUST come PS2MULT_NUM_PORTS selectors */ +static const unsigned char ps2mult_controls[] = { + PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR, + PS2MULT_ESCAPE, PS2MULT_BSYNC, + PS2MULT_SESSION_START, PS2MULT_SESSION_END, +}; + +static const struct serio_device_id ps2mult_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_PS2MULT, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids); + +static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port) +{ + struct serio *mx_serio = psm->mx_serio; + + serio_write(mx_serio, port->sel); + psm->out_port = port; + dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel); +} + +static int ps2mult_serio_write(struct serio *serio, unsigned char data) +{ + struct serio *mx_port = serio->parent; + struct ps2mult *psm = serio_get_drvdata(mx_port); + struct ps2mult_port *port = serio->port_data; + bool need_escape; + unsigned long flags; + + spin_lock_irqsave(&psm->lock, flags); + + if (psm->out_port != port) + ps2mult_select_port(psm, port); + + need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls)); + + dev_dbg(&serio->dev, + "write: %s%02x\n", need_escape ? "ESC " : "", data); + + if (need_escape) + serio_write(mx_port, PS2MULT_ESCAPE); + + serio_write(mx_port, data); + + spin_unlock_irqrestore(&psm->lock, flags); + + return 0; +} + +static int ps2mult_serio_start(struct serio *serio) +{ + struct ps2mult *psm = serio_get_drvdata(serio->parent); + struct ps2mult_port *port = serio->port_data; + unsigned long flags; + + spin_lock_irqsave(&psm->lock, flags); + port->registered = true; + spin_unlock_irqrestore(&psm->lock, flags); + + return 0; +} + +static void ps2mult_serio_stop(struct serio *serio) +{ + struct ps2mult *psm = serio_get_drvdata(serio->parent); + struct ps2mult_port *port = serio->port_data; + unsigned long flags; + + spin_lock_irqsave(&psm->lock, flags); + port->registered = false; + spin_unlock_irqrestore(&psm->lock, flags); +} + +static int ps2mult_create_port(struct ps2mult *psm, int i) +{ + struct serio *mx_serio = psm->mx_serio; + struct serio *serio; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + strlcpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), + "%s/port%d", mx_serio->phys, i); + serio->id.type = SERIO_8042; + serio->write = ps2mult_serio_write; + serio->start = ps2mult_serio_start; + serio->stop = ps2mult_serio_stop; + serio->parent = psm->mx_serio; + serio->port_data = &psm->ports[i]; + + psm->ports[i].serio = serio; + + return 0; +} + +static void ps2mult_reset(struct ps2mult *psm) +{ + unsigned long flags; + + spin_lock_irqsave(&psm->lock, flags); + + serio_write(psm->mx_serio, PS2MULT_SESSION_END); + serio_write(psm->mx_serio, PS2MULT_SESSION_START); + + ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]); + + spin_unlock_irqrestore(&psm->lock, flags); +} + +static int ps2mult_connect(struct serio *serio, struct serio_driver *drv) +{ + struct ps2mult *psm; + int i; + int error; + + if (!serio->write) + return -EINVAL; + + psm = kzalloc(sizeof(*psm), GFP_KERNEL); + if (!psm) + return -ENOMEM; + + spin_lock_init(&psm->lock); + psm->mx_serio = serio; + + for (i = 0; i < PS2MULT_NUM_PORTS; i++) { + psm->ports[i].sel = ps2mult_controls[i]; + error = ps2mult_create_port(psm, i); + if (error) + goto err_out; + } + + psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT]; + + serio_set_drvdata(serio, psm); + error = serio_open(serio, drv); + if (error) + goto err_out; + + ps2mult_reset(psm); + + for (i = 0; i < PS2MULT_NUM_PORTS; i++) { + struct serio *s = psm->ports[i].serio; + + dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys); + serio_register_port(s); + } + + return 0; + +err_out: + while (--i >= 0) + kfree(psm->ports[i].serio); + kfree(serio); + return error; +} + +static void ps2mult_disconnect(struct serio *serio) +{ + struct ps2mult *psm = serio_get_drvdata(serio); + + /* Note that serio core already take care of children ports */ + serio_write(serio, PS2MULT_SESSION_END); + serio_close(serio); + kfree(psm); + + serio_set_drvdata(serio, NULL); +} + +static int ps2mult_reconnect(struct serio *serio) +{ + struct ps2mult *psm = serio_get_drvdata(serio); + + ps2mult_reset(psm); + + return 0; +} + +static irqreturn_t ps2mult_interrupt(struct serio *serio, + unsigned char data, unsigned int dfl) +{ + struct ps2mult *psm = serio_get_drvdata(serio); + struct ps2mult_port *in_port; + unsigned long flags; + + dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl); + + spin_lock_irqsave(&psm->lock, flags); + + if (psm->escape) { + psm->escape = false; + in_port = psm->in_port; + if (in_port->registered) + serio_interrupt(in_port->serio, data, dfl); + goto out; + } + + switch (data) { + case PS2MULT_ESCAPE: + dev_dbg(&serio->dev, "ESCAPE\n"); + psm->escape = true; + break; + + case PS2MULT_BSYNC: + dev_dbg(&serio->dev, "BSYNC\n"); + psm->in_port = psm->out_port; + break; + + case PS2MULT_SESSION_START: + dev_dbg(&serio->dev, "SS\n"); + break; + + case PS2MULT_SESSION_END: + dev_dbg(&serio->dev, "SE\n"); + break; + + case PS2MULT_KB_SELECTOR: + dev_dbg(&serio->dev, "KB\n"); + psm->in_port = &psm->ports[PS2MULT_KBD_PORT]; + break; + + case PS2MULT_MS_SELECTOR: + dev_dbg(&serio->dev, "MS\n"); + psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT]; + break; + + default: + in_port = psm->in_port; + if (in_port->registered) + serio_interrupt(in_port->serio, data, dfl); + break; + } + + out: + spin_unlock_irqrestore(&psm->lock, flags); + return IRQ_HANDLED; +} + +static struct serio_driver ps2mult_drv = { + .driver = { + .name = "ps2mult", + }, + .description = "TQC PS/2 Multiplexer driver", + .id_table = ps2mult_serio_ids, + .interrupt = ps2mult_interrupt, + .connect = ps2mult_connect, + .disconnect = ps2mult_disconnect, + .reconnect = ps2mult_reconnect, +}; + +static int __init ps2mult_init(void) +{ + return serio_register_driver(&ps2mult_drv); +} + +static void __exit ps2mult_exit(void) +{ + serio_unregister_driver(&ps2mult_drv); +} + +module_init(ps2mult_init); +module_exit(ps2mult_exit); diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c index c3b626e9eae7..405bf214527c 100644 --- a/drivers/input/serio/serio.c +++ b/drivers/input/serio/serio.c @@ -37,7 +37,6 @@ #include <linux/slab.h> #include <linux/kthread.h> #include <linux/mutex.h> -#include <linux/freezer.h> MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); MODULE_DESCRIPTION("Serio abstraction core"); @@ -56,7 +55,7 @@ static struct bus_type serio_bus; static void serio_add_port(struct serio *serio); static int serio_reconnect_port(struct serio *serio); static void serio_disconnect_port(struct serio *serio); -static void serio_reconnect_chain(struct serio *serio); +static void serio_reconnect_subtree(struct serio *serio); static void serio_attach_driver(struct serio_driver *drv); static int serio_connect_driver(struct serio *serio, struct serio_driver *drv) @@ -152,7 +151,7 @@ static void serio_find_driver(struct serio *serio) enum serio_event_type { SERIO_RESCAN_PORT, SERIO_RECONNECT_PORT, - SERIO_RECONNECT_CHAIN, + SERIO_RECONNECT_SUBTREE, SERIO_REGISTER_PORT, SERIO_ATTACH_DRIVER, }; @@ -292,8 +291,8 @@ static void serio_handle_event(void) serio_find_driver(event->object); break; - case SERIO_RECONNECT_CHAIN: - serio_reconnect_chain(event->object); + case SERIO_RECONNECT_SUBTREE: + serio_reconnect_subtree(event->object); break; case SERIO_ATTACH_DRIVER: @@ -330,12 +329,10 @@ static void serio_remove_pending_events(void *object) } /* - * Destroy child serio port (if any) that has not been fully registered yet. + * Locate child serio port (if any) that has not been fully registered yet. * - * Note that we rely on the fact that port can have only one child and therefore - * only one child registration request can be pending. Additionally, children - * are registered by driver's connect() handler so there can't be a grandchild - * pending registration together with a child. + * Children are registered by driver's connect() handler so there can't be a + * grandchild pending registration together with a child. */ static struct serio *serio_get_pending_child(struct serio *parent) { @@ -449,7 +446,7 @@ static ssize_t serio_rebind_driver(struct device *dev, struct device_attribute * if (!strncmp(buf, "none", count)) { serio_disconnect_port(serio); } else if (!strncmp(buf, "reconnect", count)) { - serio_reconnect_chain(serio); + serio_reconnect_subtree(serio); } else if (!strncmp(buf, "rescan", count)) { serio_disconnect_port(serio); serio_find_driver(serio); @@ -516,6 +513,8 @@ static void serio_init_port(struct serio *serio) __module_get(THIS_MODULE); INIT_LIST_HEAD(&serio->node); + INIT_LIST_HEAD(&serio->child_node); + INIT_LIST_HEAD(&serio->children); spin_lock_init(&serio->lock); mutex_init(&serio->drv_mutex); device_initialize(&serio->dev); @@ -538,12 +537,13 @@ static void serio_init_port(struct serio *serio) */ static void serio_add_port(struct serio *serio) { + struct serio *parent = serio->parent; int error; - if (serio->parent) { - serio_pause_rx(serio->parent); - serio->parent->child = serio; - serio_continue_rx(serio->parent); + if (parent) { + serio_pause_rx(parent); + list_add_tail(&serio->child_node, &parent->children); + serio_continue_rx(parent); } list_add_tail(&serio->node, &serio_list); @@ -559,15 +559,14 @@ static void serio_add_port(struct serio *serio) } /* - * serio_destroy_port() completes deregistration process and removes + * serio_destroy_port() completes unregistration process and removes * port from the system */ static void serio_destroy_port(struct serio *serio) { struct serio *child; - child = serio_get_pending_child(serio); - if (child) { + while ((child = serio_get_pending_child(serio)) != NULL) { serio_remove_pending_events(child); put_device(&child->dev); } @@ -577,7 +576,7 @@ static void serio_destroy_port(struct serio *serio) if (serio->parent) { serio_pause_rx(serio->parent); - serio->parent->child = NULL; + list_del_init(&serio->child_node); serio_continue_rx(serio->parent); serio->parent = NULL; } @@ -609,46 +608,82 @@ static int serio_reconnect_port(struct serio *serio) } /* - * Reconnect serio port and all its children (re-initialize attached devices) + * Reconnect serio port and all its children (re-initialize attached + * devices). */ -static void serio_reconnect_chain(struct serio *serio) +static void serio_reconnect_subtree(struct serio *root) { + struct serio *s = root; + int error; + do { - if (serio_reconnect_port(serio)) { - /* Ok, old children are now gone, we are done */ - break; + error = serio_reconnect_port(s); + if (!error) { + /* + * Reconnect was successful, move on to do the + * first child. + */ + if (!list_empty(&s->children)) { + s = list_first_entry(&s->children, + struct serio, child_node); + continue; + } } - serio = serio->child; - } while (serio); + + /* + * Either it was a leaf node or reconnect failed and it + * became a leaf node. Continue reconnecting starting with + * the next sibling of the parent node. + */ + while (s != root) { + struct serio *parent = s->parent; + + if (!list_is_last(&s->child_node, &parent->children)) { + s = list_entry(s->child_node.next, + struct serio, child_node); + break; + } + + s = parent; + } + } while (s != root); } /* * serio_disconnect_port() unbinds a port from its driver. As a side effect - * all child ports are unbound and destroyed. + * all children ports are unbound and destroyed. */ static void serio_disconnect_port(struct serio *serio) { - struct serio *s, *parent; + struct serio *s = serio; + + /* + * Children ports should be disconnected and destroyed + * first; we travel the tree in depth-first order. + */ + while (!list_empty(&serio->children)) { + + /* Locate a leaf */ + while (!list_empty(&s->children)) + s = list_first_entry(&s->children, + struct serio, child_node); - if (serio->child) { /* - * Children ports should be disconnected and destroyed - * first, staring with the leaf one, since we don't want - * to do recursion + * Prune this leaf node unless it is the one we + * started with. */ - for (s = serio; s->child; s = s->child) - /* empty */; - - do { - parent = s->parent; + if (s != serio) { + struct serio *parent = s->parent; device_release_driver(&s->dev); serio_destroy_port(s); - } while ((s = parent) != serio); + + s = parent; + } } /* - * Ok, no children left, now disconnect this port + * OK, no children left, now disconnect this port. */ device_release_driver(&serio->dev); } @@ -661,7 +696,7 @@ EXPORT_SYMBOL(serio_rescan); void serio_reconnect(struct serio *serio) { - serio_queue_event(serio, NULL, SERIO_RECONNECT_CHAIN); + serio_queue_event(serio, NULL, SERIO_RECONNECT_SUBTREE); } EXPORT_SYMBOL(serio_reconnect); @@ -689,14 +724,16 @@ void serio_unregister_port(struct serio *serio) EXPORT_SYMBOL(serio_unregister_port); /* - * Safely unregisters child port if one is present. + * Safely unregisters children ports if they are present. */ void serio_unregister_child_port(struct serio *serio) { + struct serio *s, *next; + mutex_lock(&serio_mutex); - if (serio->child) { - serio_disconnect_port(serio->child); - serio_destroy_port(serio->child); + list_for_each_entry_safe(s, next, &serio->children, child_node) { + serio_disconnect_port(s); + serio_destroy_port(s); } mutex_unlock(&serio_mutex); } |