linux/drivers/input/serio/ps2mult.c
<<
>>
Prefs
   1/*
   2 * TQC PS/2 Multiplexer driver
   3 *
   4 * Copyright (C) 2010 Dmitry Eremin-Solenikov
   5 *
   6 * This program is free software; you can redistribute it and/or modify it
   7 * under the terms of the GNU General Public License version 2 as published by
   8 * the Free Software Foundation.
   9 */
  10
  11
  12#include <linux/kernel.h>
  13#include <linux/slab.h>
  14#include <linux/module.h>
  15#include <linux/serio.h>
  16
  17MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
  18MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
  19MODULE_LICENSE("GPL");
  20
  21#define PS2MULT_KB_SELECTOR             0xA0
  22#define PS2MULT_MS_SELECTOR             0xA1
  23#define PS2MULT_ESCAPE                  0x7D
  24#define PS2MULT_BSYNC                   0x7E
  25#define PS2MULT_SESSION_START           0x55
  26#define PS2MULT_SESSION_END             0x56
  27
  28struct ps2mult_port {
  29        struct serio *serio;
  30        unsigned char sel;
  31        bool registered;
  32};
  33
  34#define PS2MULT_NUM_PORTS       2
  35#define PS2MULT_KBD_PORT        0
  36#define PS2MULT_MOUSE_PORT      1
  37
  38struct ps2mult {
  39        struct serio *mx_serio;
  40        struct ps2mult_port ports[PS2MULT_NUM_PORTS];
  41
  42        spinlock_t lock;
  43        struct ps2mult_port *in_port;
  44        struct ps2mult_port *out_port;
  45        bool escape;
  46};
  47
  48/* First MUST come PS2MULT_NUM_PORTS selectors */
  49static const unsigned char ps2mult_controls[] = {
  50        PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
  51        PS2MULT_ESCAPE, PS2MULT_BSYNC,
  52        PS2MULT_SESSION_START, PS2MULT_SESSION_END,
  53};
  54
  55static const struct serio_device_id ps2mult_serio_ids[] = {
  56        {
  57                .type   = SERIO_RS232,
  58                .proto  = SERIO_PS2MULT,
  59                .id     = SERIO_ANY,
  60                .extra  = SERIO_ANY,
  61        },
  62        { 0 }
  63};
  64
  65MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
  66
  67static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
  68{
  69        struct serio *mx_serio = psm->mx_serio;
  70
  71        serio_write(mx_serio, port->sel);
  72        psm->out_port = port;
  73        dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
  74}
  75
  76static int ps2mult_serio_write(struct serio *serio, unsigned char data)
  77{
  78        struct serio *mx_port = serio->parent;
  79        struct ps2mult *psm = serio_get_drvdata(mx_port);
  80        struct ps2mult_port *port = serio->port_data;
  81        bool need_escape;
  82        unsigned long flags;
  83
  84        spin_lock_irqsave(&psm->lock, flags);
  85
  86        if (psm->out_port != port)
  87                ps2mult_select_port(psm, port);
  88
  89        need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
  90
  91        dev_dbg(&serio->dev,
  92                "write: %s%02x\n", need_escape ? "ESC " : "", data);
  93
  94        if (need_escape)
  95                serio_write(mx_port, PS2MULT_ESCAPE);
  96
  97        serio_write(mx_port, data);
  98
  99        spin_unlock_irqrestore(&psm->lock, flags);
 100
 101        return 0;
 102}
 103
 104static int ps2mult_serio_start(struct serio *serio)
 105{
 106        struct ps2mult *psm = serio_get_drvdata(serio->parent);
 107        struct ps2mult_port *port = serio->port_data;
 108        unsigned long flags;
 109
 110        spin_lock_irqsave(&psm->lock, flags);
 111        port->registered = true;
 112        spin_unlock_irqrestore(&psm->lock, flags);
 113
 114        return 0;
 115}
 116
 117static void ps2mult_serio_stop(struct serio *serio)
 118{
 119        struct ps2mult *psm = serio_get_drvdata(serio->parent);
 120        struct ps2mult_port *port = serio->port_data;
 121        unsigned long flags;
 122
 123        spin_lock_irqsave(&psm->lock, flags);
 124        port->registered = false;
 125        spin_unlock_irqrestore(&psm->lock, flags);
 126}
 127
 128static int ps2mult_create_port(struct ps2mult *psm, int i)
 129{
 130        struct serio *mx_serio = psm->mx_serio;
 131        struct serio *serio;
 132
 133        serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
 134        if (!serio)
 135                return -ENOMEM;
 136
 137        strlcpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
 138        snprintf(serio->phys, sizeof(serio->phys),
 139                 "%s/port%d", mx_serio->phys, i);
 140        serio->id.type = SERIO_8042;
 141        serio->write = ps2mult_serio_write;
 142        serio->start = ps2mult_serio_start;
 143        serio->stop = ps2mult_serio_stop;
 144        serio->parent = psm->mx_serio;
 145        serio->port_data = &psm->ports[i];
 146
 147        psm->ports[i].serio = serio;
 148
 149        return 0;
 150}
 151
 152static void ps2mult_reset(struct ps2mult *psm)
 153{
 154        unsigned long flags;
 155
 156        spin_lock_irqsave(&psm->lock, flags);
 157
 158        serio_write(psm->mx_serio, PS2MULT_SESSION_END);
 159        serio_write(psm->mx_serio, PS2MULT_SESSION_START);
 160
 161        ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
 162
 163        spin_unlock_irqrestore(&psm->lock, flags);
 164}
 165
 166static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
 167{
 168        struct ps2mult *psm;
 169        int i;
 170        int error;
 171
 172        if (!serio->write)
 173                return -EINVAL;
 174
 175        psm = kzalloc(sizeof(*psm), GFP_KERNEL);
 176        if (!psm)
 177                return -ENOMEM;
 178
 179        spin_lock_init(&psm->lock);
 180        psm->mx_serio = serio;
 181
 182        for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
 183                psm->ports[i].sel = ps2mult_controls[i];
 184                error = ps2mult_create_port(psm, i);
 185                if (error)
 186                        goto err_out;
 187        }
 188
 189        psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
 190
 191        serio_set_drvdata(serio, psm);
 192        error = serio_open(serio, drv);
 193        if (error)
 194                goto err_out;
 195
 196        ps2mult_reset(psm);
 197
 198        for (i = 0; i <  PS2MULT_NUM_PORTS; i++) {
 199                struct serio *s = psm->ports[i].serio;
 200
 201                dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
 202                serio_register_port(s);
 203        }
 204
 205        return 0;
 206
 207err_out:
 208        while (--i >= 0)
 209                kfree(psm->ports[i].serio);
 210        kfree(psm);
 211        return error;
 212}
 213
 214static void ps2mult_disconnect(struct serio *serio)
 215{
 216        struct ps2mult *psm = serio_get_drvdata(serio);
 217
 218        /* Note that serio core already take care of children ports */
 219        serio_write(serio, PS2MULT_SESSION_END);
 220        serio_close(serio);
 221        kfree(psm);
 222
 223        serio_set_drvdata(serio, NULL);
 224}
 225
 226static int ps2mult_reconnect(struct serio *serio)
 227{
 228        struct ps2mult *psm = serio_get_drvdata(serio);
 229
 230        ps2mult_reset(psm);
 231
 232        return 0;
 233}
 234
 235static irqreturn_t ps2mult_interrupt(struct serio *serio,
 236                                     unsigned char data, unsigned int dfl)
 237{
 238        struct ps2mult *psm = serio_get_drvdata(serio);
 239        struct ps2mult_port *in_port;
 240        unsigned long flags;
 241
 242        dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
 243
 244        spin_lock_irqsave(&psm->lock, flags);
 245
 246        if (psm->escape) {
 247                psm->escape = false;
 248                in_port = psm->in_port;
 249                if (in_port->registered)
 250                        serio_interrupt(in_port->serio, data, dfl);
 251                goto out;
 252        }
 253
 254        switch (data) {
 255        case PS2MULT_ESCAPE:
 256                dev_dbg(&serio->dev, "ESCAPE\n");
 257                psm->escape = true;
 258                break;
 259
 260        case PS2MULT_BSYNC:
 261                dev_dbg(&serio->dev, "BSYNC\n");
 262                psm->in_port = psm->out_port;
 263                break;
 264
 265        case PS2MULT_SESSION_START:
 266                dev_dbg(&serio->dev, "SS\n");
 267                break;
 268
 269        case PS2MULT_SESSION_END:
 270                dev_dbg(&serio->dev, "SE\n");
 271                break;
 272
 273        case PS2MULT_KB_SELECTOR:
 274                dev_dbg(&serio->dev, "KB\n");
 275                psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
 276                break;
 277
 278        case PS2MULT_MS_SELECTOR:
 279                dev_dbg(&serio->dev, "MS\n");
 280                psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
 281                break;
 282
 283        default:
 284                in_port = psm->in_port;
 285                if (in_port->registered)
 286                        serio_interrupt(in_port->serio, data, dfl);
 287                break;
 288        }
 289
 290 out:
 291        spin_unlock_irqrestore(&psm->lock, flags);
 292        return IRQ_HANDLED;
 293}
 294
 295static struct serio_driver ps2mult_drv = {
 296        .driver         = {
 297                .name   = "ps2mult",
 298        },
 299        .description    = "TQC PS/2 Multiplexer driver",
 300        .id_table       = ps2mult_serio_ids,
 301        .interrupt      = ps2mult_interrupt,
 302        .connect        = ps2mult_connect,
 303        .disconnect     = ps2mult_disconnect,
 304        .reconnect      = ps2mult_reconnect,
 305};
 306
 307module_serio_driver(ps2mult_drv);
 308