#include <linux/serio.h>
#include <linux/workqueue.h>
+#define DRIVER_DESC "AT and PS/2 keyboard driver"
+
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
-MODULE_DESCRIPTION("AT and PS/2 keyboard driver");
+MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
static int atkbd_set = 2;
module_param_named(softrepeat, atkbd_softrepeat, bool, 0);
MODULE_PARM_DESC(softrepeat, "Use software keyboard repeat");
+static int atkbd_softraw = 1;
+module_param_named(softraw, atkbd_softraw, bool, 0);
+MODULE_PARM_DESC(softraw, "Use software generated rawmode");
+
static int atkbd_scroll;
module_param_named(scroll, atkbd_scroll, bool, 0);
MODULE_PARM_DESC(scroll, "Enable scroll-wheel on MS Office and similar keyboards");
{ ATKBD_SCR_CLICK, 0x60 },
};
+#define ATKBD_FLAG_ACK 0 /* Waiting for ACK/NAK */
+#define ATKBD_FLAG_CMD 1 /* Waiting for command to finish */
+#define ATKBD_FLAG_CMD1 2 /* First byte of command response */
+#define ATKBD_FLAG_ENABLED 3 /* Waining for init to finish */
+
/*
* The atkbd control structure
*/
struct atkbd {
- unsigned char keycode[512];
- struct input_dev dev;
- struct serio *serio;
+ /* Written only during init */
char name[64];
char phys[32];
- unsigned short id;
+ struct serio *serio;
+ struct input_dev dev;
+
unsigned char set;
- unsigned int translated:1;
- unsigned int extra:1;
- unsigned int write:1;
+ unsigned short id;
+ unsigned char keycode[512];
+ unsigned char translated;
+ unsigned char extra;
+ unsigned char write;
+
+ /* Protected by FLAG_ACK */
+ unsigned char nak;
+ /* Protected by FLAG_CMD */
unsigned char cmdbuf[4];
unsigned char cmdcnt;
- volatile signed char ack;
- unsigned char emul;
- unsigned int resend:1;
- unsigned int release:1;
- unsigned int bat_xl:1;
- unsigned int enabled:1;
+ /* Accessed only from interrupt */
+ unsigned char emul;
+ unsigned char resend;
+ unsigned char release;
+ unsigned char bat_xl;
unsigned int last;
unsigned long time;
+
+ /* Ensures that only one command is executing at a time */
+ struct semaphore cmd_sem;
+
+ /* Used to signal completion from interrupt handler */
+ wait_queue_head_t wait;
+
+ /* Flags */
+ unsigned long flags;
+};
+
+/* Work structure to schedule execution of a command */
+struct atkbd_work {
+ struct work_struct work;
+ struct atkbd *atkbd;
+ int command;
+ unsigned char param[0];
};
+
static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value)
{
input_regs(dev, regs);
#if !defined(__i386__) && !defined (__x86_64__)
if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) {
- printk("atkbd.c: frame/parity error: %02x\n", flags);
+ printk(KERN_WARNING "atkbd.c: frame/parity error: %02x\n", flags);
serio_write(serio, ATKBD_CMD_RESEND);
atkbd->resend = 1;
goto out;
atkbd->resend = 0;
#endif
- if (!atkbd->ack)
+ if (test_bit(ATKBD_FLAG_ACK, &atkbd->flags)) {
switch (code) {
case ATKBD_RET_ACK:
- atkbd->ack = 1;
- goto out;
+ atkbd->nak = 0;
+ if (atkbd->cmdcnt) {
+ set_bit(ATKBD_FLAG_CMD, &atkbd->flags);
+ set_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
+ }
+ clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
+ wake_up_interruptible(&atkbd->wait);
+ break;
case ATKBD_RET_NAK:
- atkbd->ack = -1;
- goto out;
+ atkbd->nak = 1;
+ clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
+ wake_up_interruptible(&atkbd->wait);
+ break;
}
+ goto out;
+ }
+
+ if (test_bit(ATKBD_FLAG_CMD, &atkbd->flags)) {
+
+ if (atkbd->cmdcnt)
+ atkbd->cmdbuf[--atkbd->cmdcnt] = code;
- if (atkbd->cmdcnt) {
- atkbd->cmdbuf[--atkbd->cmdcnt] = code;
+ if (test_and_clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags) && atkbd->cmdcnt)
+ wake_up_interruptible(&atkbd->wait);
+
+ if (!atkbd->cmdcnt) {
+ clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
+ wake_up_interruptible(&atkbd->wait);
+ }
goto out;
}
- if (!atkbd->enabled)
+ if (!test_bit(ATKBD_FLAG_ENABLED, &atkbd->flags))
goto out;
+ input_event(&atkbd->dev, EV_MSC, MSC_RAW, code);
+
if (atkbd->translated) {
if (atkbd->emul ||
switch (code) {
case ATKBD_RET_BAT:
+ clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
serio_rescan(atkbd->serio);
goto out;
case ATKBD_RET_EMUL0:
code |= (atkbd->set != 3) ? 0x80 : 0x100;
}
+ if (atkbd->keycode[code] != ATKBD_KEY_NULL)
+ input_event(&atkbd->dev, EV_MSC, MSC_SCAN, code);
+
switch (atkbd->keycode[code]) {
case ATKBD_KEY_NULL:
break;
* acknowledge. It doesn't handle resends according to the keyboard
* protocol specs, because if these are needed, the keyboard needs
* replacement anyway, and they only make a mess in the protocol.
+ *
+ * atkbd_sendbyte() can only be called from a process context
*/
static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
{
- int timeout = 20000; /* 200 msec */
- atkbd->ack = 0;
-
#ifdef ATKBD_DEBUG
printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte);
#endif
- if (serio_write(atkbd->serio, byte))
- return -1;
+ atkbd->nak = 1;
+ set_bit(ATKBD_FLAG_ACK, &atkbd->flags);
- while (!atkbd->ack && timeout--) udelay(10);
+ if (serio_write(atkbd->serio, byte) == 0)
+ wait_event_interruptible_timeout(atkbd->wait,
+ !test_bit(ATKBD_FLAG_ACK, &atkbd->flags),
+ msecs_to_jiffies(200));
- return -(atkbd->ack <= 0);
+ clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
+ return -atkbd->nak;
}
/*
* atkbd_command() sends a command, and its parameters to the keyboard,
* then waits for the response and puts it in the param array.
+ *
+ * atkbd_command() can only be called from a process context
*/
static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
{
- int timeout = 500000; /* 500 msec */
+ int timeout;
int send = (command >> 12) & 0xf;
int receive = (command >> 8) & 0xf;
+ int rc = -1;
int i;
- atkbd->cmdcnt = receive;
+ timeout = msecs_to_jiffies(command == ATKBD_CMD_RESET_BAT ? 4000 : 500);
- if (command == ATKBD_CMD_RESET_BAT)
- timeout = 2000000; /* 2 sec */
+ down(&atkbd->cmd_sem);
+ clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
if (receive && param)
for (i = 0; i < receive; i++)
atkbd->cmdbuf[(receive - 1) - i] = param[i];
+ atkbd->cmdcnt = receive;
+
if (command & 0xff)
if (atkbd_sendbyte(atkbd, command & 0xff))
- return (atkbd->cmdcnt = 0) - 1;
+ goto out;
for (i = 0; i < send; i++)
if (atkbd_sendbyte(atkbd, param[i]))
- return (atkbd->cmdcnt = 0) - 1;
-
- while (atkbd->cmdcnt && timeout--) {
-
- if (atkbd->cmdcnt == 1 &&
- command == ATKBD_CMD_RESET_BAT && timeout > 100000)
- timeout = 100000;
+ goto out;
- if (atkbd->cmdcnt == 1 && command == ATKBD_CMD_GETID &&
- atkbd->cmdbuf[1] != 0xab && atkbd->cmdbuf[1] != 0xac) {
+ timeout = wait_event_interruptible_timeout(atkbd->wait,
+ !test_bit(ATKBD_FLAG_CMD1, &atkbd->flags), timeout);
+
+ if (atkbd->cmdcnt && timeout > 0) {
+ if (command == ATKBD_CMD_RESET_BAT && jiffies_to_msecs(timeout) > 100)
+ timeout = msecs_to_jiffies(100);
+
+ if (command == ATKBD_CMD_GETID &&
+ atkbd->cmdbuf[receive - 1] != 0xab && atkbd->cmdbuf[receive - 1] != 0xac) {
+ /*
+ * Device behind the port is not a keyboard
+ * so we don't need to wait for the 2nd byte
+ * of ID response.
+ */
+ clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
atkbd->cmdcnt = 0;
- break;
}
- udelay(1);
+ wait_event_interruptible_timeout(atkbd->wait,
+ !test_bit(ATKBD_FLAG_CMD, &atkbd->flags), timeout);
}
if (param)
for (i = 0; i < receive; i++)
param[i] = atkbd->cmdbuf[(receive - 1) - i];
- if (command == ATKBD_CMD_RESET_BAT && atkbd->cmdcnt == 1)
- atkbd->cmdcnt = 0;
+ if (atkbd->cmdcnt && (command != ATKBD_CMD_RESET_BAT || atkbd->cmdcnt != 1))
+ goto out;
+
+ rc = 0;
+
+out:
+ clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
+ clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
+ up(&atkbd->cmd_sem);
+
+ return rc;
+}
+
+/*
+ * atkbd_execute_scheduled_command() sends a command, previously scheduled by
+ * atkbd_schedule_command(), to the keyboard.
+ */
+
+static void atkbd_execute_scheduled_command(void *data)
+{
+ struct atkbd_work *atkbd_work = data;
+
+ atkbd_command(atkbd_work->atkbd, atkbd_work->param, atkbd_work->command);
+
+ kfree(atkbd_work);
+}
+
+/*
+ * atkbd_schedule_command() allows to schedule delayed execution of a keyboard
+ * command and can be used to issue a command from an interrupt or softirq
+ * context.
+ */
+
+static int atkbd_schedule_command(struct atkbd *atkbd, unsigned char *param, int command)
+{
+ struct atkbd_work *atkbd_work;
+ int send = (command >> 12) & 0xf;
+ int receive = (command >> 8) & 0xf;
+
+ if (!test_bit(ATKBD_FLAG_ENABLED, &atkbd->flags))
+ return -1;
+
+ if (!(atkbd_work = kmalloc(sizeof(struct atkbd_work) + max(send, receive), GFP_ATOMIC)))
+ return -1;
+
+ memset(atkbd_work, 0, sizeof(struct atkbd_work));
+ atkbd_work->atkbd = atkbd;
+ atkbd_work->command = command;
+ memcpy(atkbd_work->param, param, send);
+ INIT_WORK(&atkbd_work->work, atkbd_execute_scheduled_command, atkbd_work);
- if (atkbd->cmdcnt) {
- atkbd->cmdcnt = 0;
+ if (!schedule_work(&atkbd_work->work)) {
+ kfree(atkbd_work);
return -1;
}
return 0;
}
+
/*
* Event callback from the input module. Events that change the state of
* the hardware are processed here.
param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
| (test_bit(LED_NUML, dev->led) ? 2 : 0)
| (test_bit(LED_CAPSL, dev->led) ? 4 : 0);
- atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS);
+ atkbd_schedule_command(atkbd, param, ATKBD_CMD_SETLEDS);
if (atkbd->extra) {
param[0] = 0;
| (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
| (test_bit(LED_MISC, dev->led) ? 0x10 : 0)
| (test_bit(LED_MUTE, dev->led) ? 0x20 : 0);
- atkbd_command(atkbd, param, ATKBD_CMD_EX_SETLEDS);
+ atkbd_schedule_command(atkbd, param, ATKBD_CMD_EX_SETLEDS);
}
return 0;
dev->rep[REP_PERIOD] = period[i];
dev->rep[REP_DELAY] = delay[j];
param[0] = i | (j << 5);
- atkbd_command(atkbd, param, ATKBD_CMD_SETREP);
+ atkbd_schedule_command(atkbd, param, ATKBD_CMD_SETREP);
return 0;
}
static void atkbd_disconnect(struct serio *serio)
{
struct atkbd *atkbd = serio->private;
+
+ clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+ synchronize_kernel();
+ flush_scheduled_work();
+
input_unregister_device(&atkbd->dev);
serio_close(serio);
kfree(atkbd);
* to the input module.
*/
-static void atkbd_connect(struct serio *serio, struct serio_dev *dev)
+static void atkbd_connect(struct serio *serio, struct serio_driver *drv)
{
struct atkbd *atkbd;
int i;
return;
memset(atkbd, 0, sizeof(struct atkbd));
+ init_MUTEX(&atkbd->cmd_sem);
+ init_waitqueue_head(&atkbd->wait);
+
switch (serio->type & SERIO_TYPE) {
case SERIO_8042_XL:
return;
}
+ if (!atkbd->write)
+ atkbd_softrepeat = 1;
+ if (atkbd_softrepeat)
+ atkbd_softraw = 1;
+
if (atkbd->write) {
- atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP);
+ atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP) | BIT(EV_MSC);
atkbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL);
- } else atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+ } else atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
+ atkbd->dev.mscbit[0] = atkbd_softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN);
if (!atkbd_softrepeat) {
atkbd->dev.rep[REP_DELAY] = 250;
atkbd->dev.rep[REP_PERIOD] = 33;
- }
+ } else atkbd_softraw = 1;
- atkbd->ack = 1;
atkbd->serio = serio;
init_input_dev(&atkbd->dev);
serio->private = atkbd;
- if (serio_open(serio, dev)) {
+ if (serio_open(serio, drv)) {
kfree(atkbd);
return;
}
atkbd->id = 0xab00;
}
- atkbd->enabled = 1;
-
if (atkbd->extra) {
atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) | BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
sprintf(atkbd->name, "AT Set 2 Extra keyboard");
input_register_device(&atkbd->dev);
+ set_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+
printk(KERN_INFO "input: %s on %s\n", atkbd->name, serio->phys);
}
static int atkbd_reconnect(struct serio *serio)
{
struct atkbd *atkbd = serio->private;
- struct serio_dev *dev = serio->dev;
+ struct serio_driver *drv = serio->drv;
unsigned char param[1];
- if (!dev) {
+ if (!drv) {
printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n");
return -1;
}
return -1;
}
+ set_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+
return 0;
}
-static struct serio_dev atkbd_dev = {
- .interrupt = atkbd_interrupt,
- .connect = atkbd_connect,
- .reconnect = atkbd_reconnect,
- .disconnect = atkbd_disconnect,
- .cleanup = atkbd_cleanup,
+static struct serio_driver atkbd_drv = {
+ .driver = {
+ .name = "atkbd",
+ },
+ .description = DRIVER_DESC,
+ .interrupt = atkbd_interrupt,
+ .connect = atkbd_connect,
+ .reconnect = atkbd_reconnect,
+ .disconnect = atkbd_disconnect,
+ .cleanup = atkbd_cleanup,
};
int __init atkbd_init(void)
{
- serio_register_device(&atkbd_dev);
+ serio_register_driver(&atkbd_drv);
return 0;
}
void __exit atkbd_exit(void)
{
- serio_unregister_device(&atkbd_dev);
+ serio_unregister_driver(&atkbd_drv);
}
module_init(atkbd_init);