 65ac9f7a23
			
		
	
	
	65ac9f7a23
	
	
	
		
			
			This patch converts the drivers in drivers/input/* to use module_serio_driver() macro which makes the code smaller and a bit simpler. Signed-off-by: Axel Lin <axel.lin@gmail.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
		
			
				
	
	
		
			307 lines
		
	
	
	
		
			6.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
	
		
			6.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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(psm);
 | |
| 	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,
 | |
| };
 | |
| 
 | |
| module_serio_driver(ps2mult_drv);
 |