+ 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);