📄 amba_kmi_keyb.c
字号:
/* * linux/drivers/char/amba_kmi_keyb.c * * AMBA Keyboard and Mouse Interface Driver * * Copyright (C) 2000 Deep Blue Solutions Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * This keyboard driver drives a PS/2 keyboard and mouse connected * to the KMI interfaces. The KMI interfaces are nothing more than * a uart; there is no inteligence in them to do keycode translation. * We leave all that up to the keyboard itself. * * FIXES: * dirk.uffmann@nokia.com: enabled PS/2 reconnection */#include <linux/config.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/interrupt.h> /* for in_interrupt */#include <linux/timer.h>#include <linux/init.h>#include <linux/delay.h> /* for udelay */#include <linux/kbd_kern.h> /* for keyboard_tasklet */#include <linux/kbd_ll.h>#include <asm/io.h>#include <asm/hardware/amba_kmi.h>#include <asm/mach/amba_kmi.h>#include <asm/keyboard.h>//#define DEBUG(s) printk s#define DEBUG(s) do { } while (0)#define CONFIG_AMBA_PS2_RECONNECT#define KMI_BASE (kmi->base)#define KMI_RESET 0x00#define KMI_RESET_POR 0x01#define KMI_RESET_DONE 0x02#define KMI_NO_ACK 0xffff#define PS2_O_RESET 0xff#define PS2_O_RESEND 0xfe#define PS2_O_DISABLE 0xf5#define PS2_O_ENABLE 0xf4#define PS2_O_ECHO 0xee/* * Keyboard */#define PS2_O_SET_DEFAULT 0xf6#define PS2_O_SET_RATE_DELAY 0xf3#define PS2_O_SET_SCANSET 0xf0#define PS2_O_INDICATORS 0xed/* * Mouse */#define PS2_O_SET_SAMPLE 0xf3#define PS2_O_SET_STREAM 0xea#define PS2_O_SET_RES 0xe8#define PS2_O_SET_SCALE21 0xe7#define PS2_O_SET_SCALE11 0xe6#define PS2_O_REQ_STATUS 0xe9/* * Responses */#define PS2_I_RESEND 0xfe#define PS2_I_DIAGFAIL 0xfc#define PS2_I_ACK 0xfa#define PS2_I_BREAK 0xf0#define PS2_I_ECHO 0xee#define PS2_I_BAT_OK 0xaastatic char *kmi_type[] = { "Keyboard", "Mouse" };static struct kmi_info *kmi_keyb;static struct kmi_info *kmi_mouse;static inline void __kmi_send(struct kmi_info *kmi, u_int val){ u_int status; do { status = __raw_readb(KMISTAT); } while (!(status & KMISTAT_TXEMPTY)); kmi->resend_count += 1; __raw_writeb(val, KMIDATA);}static void kmi_send(struct kmi_info *kmi, u_int val){ kmi->last_tx = val; kmi->resend_count = -1; __kmi_send(kmi, val);}static u_int kmi_send_and_wait(struct kmi_info *kmi, u_int val, u_int timeo){ DECLARE_WAITQUEUE(wait, current); if (kmi->present == 0) return KMI_NO_ACK; kmi->res = KMI_NO_ACK; kmi->last_tx = val; kmi->resend_count = -1; if (current->pid != 0 && !in_interrupt()) { add_wait_queue(&kmi->wait_q, &wait); set_current_state(TASK_UNINTERRUPTIBLE); __kmi_send(kmi, val); schedule_timeout(timeo); current->state = TASK_RUNNING; remove_wait_queue(&kmi->wait_q, &wait); } else { int i; __kmi_send(kmi, val); for (i = 0; i < 1000; i++) { if (kmi->res != KMI_NO_ACK) break; udelay(100); } } return kmi->res;}/* * This lot should probably be separated into a separate file... */#ifdef CONFIG_KMI_MOUSE#include <linux/fs.h> /* for struct file_ops */#include <linux/poll.h> /* for poll_table */#include <linux/miscdevice.h> /* for struct miscdev */#include <linux/random.h> /* for add_mouse_randomness */#include <linux/slab.h> /* for kmalloc */#include <linux/smp_lock.h> /* for {un,}lock_kernel */#include <linux/spinlock.h>#include <asm/uaccess.h>#define BUF_SZ 2048static spinlock_t kmi_mouse_lock;static int kmi_mouse_count;static struct queue { u_int head; u_int tail; struct fasync_struct *fasync; unsigned char buf[BUF_SZ];} *queue;#define queue_empty() (queue->head == queue->tail)static u_char get_from_queue(void){ unsigned long flags; u_char res; spin_lock_irqsave(&kmi_mouse_lock, flags); res = queue->buf[queue->tail]; queue->tail = (queue->tail + 1) & (BUF_SZ-1); spin_unlock_irqrestore(&kmi_mouse_lock, flags); return res;}static ssize_tkmi_mouse_read(struct file *file, char *buf, size_t count, loff_t *ppos){ ssize_t i = count; if (queue_empty()) { int ret; if (file->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(kmi_mouse->wait_q, !queue_empty()); if (ret) return ret; } while (i > 0 && !queue_empty()) { u_char c; c = get_from_queue(); put_user(c, buf++); i--; } if (count - i) file->f_dentry->d_inode->i_atime = CURRENT_TIME; return count - i;}static ssize_tkmi_mouse_write(struct file *file, const char *buf, size_t count, loff_t *ppos){ ssize_t retval = 0; if (count > 32) count = 32; do { char c; get_user(c, buf++); kmi_send_and_wait(kmi_mouse, c, HZ); retval++; } while (--count); if (retval) file->f_dentry->d_inode->i_mtime = CURRENT_TIME; return retval;}static unsigned intkmi_mouse_poll(struct file *file, poll_table *wait){ poll_wait(file, &kmi_mouse->wait_q, wait); return (!queue_empty()) ? POLLIN | POLLRDNORM : 0;}static intkmi_mouse_release(struct inode *inode, struct file *file){ lock_kernel(); fasync_helper(-1, file, 0, &queue->fasync); if (--kmi_mouse_count == 0) kmi_send_and_wait(kmi_mouse, PS2_O_DISABLE, HZ); unlock_kernel(); return 0;}static intkmi_mouse_open(struct inode *inode, struct file *file){ if (kmi_mouse_count++) return 0; queue->head = queue->tail = 0; kmi_send_and_wait(kmi_mouse, PS2_O_ENABLE, HZ); return 0;}static intkmi_mouse_fasync(int fd, struct file *filp, int on){ int retval = fasync_helper(fd, filp, on, &queue->fasync); if (retval > 0) retval = 0; return retval;}static struct file_operations ps_fops = { read: kmi_mouse_read, write: kmi_mouse_write, poll: kmi_mouse_poll, open: kmi_mouse_open, release: kmi_mouse_release, fasync: kmi_mouse_fasync,};static struct miscdevice ps_mouse = { minor: PSMOUSE_MINOR, name: "psaux", fops: &ps_fops,};static u_char kmi_mse_init_string[] = { PS2_O_DISABLE, PS2_O_SET_SAMPLE, 100, PS2_O_SET_RES, 3, PS2_O_SET_SCALE21};/* * The "normal" mouse scancode processing */static void kmi_mse_intr(struct kmi_info *kmi, u_int val, struct pt_regs *regs){ u_int head; add_mouse_randomness(val);#ifdef CONFIG_AMBA_PS2_RECONNECT /* Try to detect a hot-plug event on the PS/2 mouse port */ switch (kmi->hotplug_state) { case 0: /* Maybe we lost contact... */ if (val == PS2_I_BAT_OK) { kmi->hotplug_state++; DEBUG(("%s: Saw 0xAA. Going to hotplug state %d\n", kmi->name, kmi->hotplug_state)); } break; case 1: /* Again, maybe (but only maybe) we lost contact... */ if (val == 0) { kmi->hotplug_state++; kmi_send(kmi, PS2_O_REQ_STATUS); DEBUG(("%s: Got 0xAA 0x00. Sent Status Request\n", kmi->name)); } else { kmi->hotplug_state = 0; DEBUG(("%s: No 0x00 followed 0xAA. No reconnect.\n", kmi->name)); } break; case 2: /* Eat up acknowledge */ if (val == PS2_I_ACK) kmi->hotplug_state++; else { kmi->hotplug_state = 0; DEBUG(("%s: didn't get ack (0x%2.2x)\n", kmi->name, val)); } break; case 3: /* check if data reporting is still enabled, then no POR has happend */ kmi->reconnect = !(val & 1<<5); DEBUG(("%s: Data reporting disabled?: (%d)\n", kmi->name, kmi->reconnect)); kmi->hotplug_state++; DEBUG(("%s: Going to hotplug state %d\n", kmi->name, kmi->hotplug_state)); break; case 4: /* Eat up one status byte */ kmi->hotplug_state++; DEBUG(("%s: Going to hotplug state %d\n", kmi->name, kmi->hotplug_state)); break; case 5: /* Eat up another status byte */ if (kmi->reconnect) { kmi->config_num = 0; kmi_send(kmi, kmi_mse_init_string[kmi->config_num]); kmi->config_num++; kmi->hotplug_state++; DEBUG(("%s: Sending byte %d of PS/2 init string.\n", kmi->name, kmi->config_num)); } else { kmi->hotplug_state = 0; DEBUG(("%s: False Alarm...\n", kmi->name)); } break; case 6: if (val == PS2_I_ACK && kmi->config_num < sizeof(kmi_mse_init_string)) { kmi_send(kmi, kmi_mse_init_string[kmi->config_num]); kmi->config_num++; DEBUG(("%s: Sending byte %d of PS/2 init string.\n", kmi->name, kmi->config_num)); } else { if (val == PS2_I_ACK) { DEBUG(("%s: Now enable the mouse again...\n", kmi->name)); queue->head = queue->tail = 0; kmi_send(kmi, PS2_O_ENABLE); kmi->hotplug_state++; } else { kmi->hotplug_state = 0; DEBUG(("%s: didn't get ack (0x%2.2x)\n", kmi->name, val)); } } break; case 7: /* Eat up last acknowledge from enable */ if (val == PS2_I_ACK) printk(KERN_ERR "%s: reconnected\n", kmi->name); else DEBUG(("%s: didn't get ack (0x%2.2x)\n", kmi->name, val)); kmi->hotplug_state = 0; break; } /* switch (kmi->hotplug_state) */ /* while inside hotplug mechanism, don't misinterpret values */ if (kmi->hotplug_state > 2) return;#endif /* We are waiting for the mouse to respond to a kmi_send_and_wait() */ if (kmi->res == KMI_NO_ACK) { if (val == PS2_I_RESEND) { if (kmi->resend_count < 5) __kmi_send(kmi, kmi->last_tx); else { printk(KERN_ERR "%s: too many resends\n", kmi->name); return; } } if (val == PS2_I_ACK) { kmi->res = val; wake_up(&kmi->wait_q); } return; } /* The mouse autonomously send new data, so wake up mouse_read() */ if (queue) { head = queue->head; queue->buf[head] = val; head = (head + 1) & (BUF_SZ - 1); if (head != queue->tail) { queue->head = head; kill_fasync(&queue->fasync, SIGIO, POLL_IN); wake_up_interruptible(&kmi->wait_q); } }}static int kmi_init_mouse(struct kmi_info *kmi){ u_int ret, i; if (kmi->present) { kmi->rx = kmi_mse_intr; for (i = 0; i < sizeof(kmi_mse_init_string); i++) { ret = kmi_send_and_wait(kmi, kmi_mse_init_string[i], HZ); if (ret != PS2_I_ACK) printk("%s: didn't get ack (0x%2.2x)\n", kmi->name, ret); } } queue = kmalloc(sizeof(*queue), GFP_KERNEL); if (queue) { memset(queue, 0, sizeof(*queue)); misc_register(&ps_mouse); ret = 0; } else ret = -ENOMEM; return ret;}#endif /* CONFIG_KMI_MOUSE *//* * The "program" we send to the keyboard to set it up how we want it: * - default typematic delays * - scancode set 1 */static u_char kmi_kbd_init_string[] = { PS2_O_DISABLE, PS2_O_SET_DEFAULT, PS2_O_SET_SCANSET, 0x01, PS2_O_ENABLE};static void kmi_kbd_intr(struct kmi_info *kmi, u_int val, struct pt_regs *regs);static int __kmi_init_keyboard(struct kmi_info *kmi){ u_int ret, i; if (!kmi->present) return 0; kmi->rx = kmi_kbd_intr; for (i = 0; i < sizeof(kmi_kbd_init_string); i++) { ret = kmi_send_and_wait(kmi, kmi_kbd_init_string[i], HZ); if (ret != PS2_I_ACK) printk("%s: didn't ack (0x%2.2x)\n", kmi->name, ret); } return 0;}static void kmi_kbd_init_tasklet(unsigned long k){ struct kmi_info *kmi = (struct kmi_info *)k; __kmi_init_keyboard(kmi);}static DECLARE_TASKLET_DISABLED(kmikbd_init_tasklet, kmi_kbd_init_tasklet, 0);/* * The "normal" keyboard scancode processing */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -