forked from Minki/linux
b0ecc73094
On parisc machines, which don't have HIL, removing the hilkbd module panics the kernel. Fix this by adding proper implementations for the probe and remove functions to the parisc_driver structure. A few functions were renamed to clean up the code and make it easier readable. Disable the MODULE_DEVICE_TABLE() macro on parisc since the kernel module autoloader should instead prefer the hp_sdc driver which takes care of full HIL support, including HIL mouse and HIL tablets. [dtor@mail.ru: fix some section markups] Signed-off-by: Helge Deller <deller@gmx.de> Acked-by: Geert Uytterhoeven <geert@linux-m68k.org> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
398 lines
8.6 KiB
C
398 lines
8.6 KiB
C
/*
|
|
* linux/drivers/hil/hilkbd.c
|
|
*
|
|
* Copyright (C) 1998 Philip Blundell <philb@gnu.org>
|
|
* Copyright (C) 1999 Matthew Wilcox <willy@bofh.ai>
|
|
* Copyright (C) 1999-2007 Helge Deller <deller@gmx.de>
|
|
*
|
|
* Very basic HP Human Interface Loop (HIL) driver.
|
|
* This driver handles the keyboard on HP300 (m68k) and on some
|
|
* HP700 (parisc) series machines.
|
|
*
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License version 2. See the file COPYING in the main directory of this
|
|
* archive for more details.
|
|
*/
|
|
|
|
#include <linux/pci_ids.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/input.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/hil.h>
|
|
#include <linux/io.h>
|
|
#include <linux/spinlock.h>
|
|
#include <asm/irq.h>
|
|
#ifdef CONFIG_HP300
|
|
#include <asm/hwtest.h>
|
|
#endif
|
|
|
|
|
|
MODULE_AUTHOR("Philip Blundell, Matthew Wilcox, Helge Deller");
|
|
MODULE_DESCRIPTION("HIL keyboard driver (basic functionality)");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
|
|
#if defined(CONFIG_PARISC)
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/hardware.h>
|
|
#include <asm/parisc-device.h>
|
|
static unsigned long hil_base; /* HPA for the HIL device */
|
|
static unsigned int hil_irq;
|
|
#define HILBASE hil_base /* HPPA (parisc) port address */
|
|
#define HIL_DATA 0x800
|
|
#define HIL_CMD 0x801
|
|
#define HIL_IRQ hil_irq
|
|
#define hil_readb(p) gsc_readb(p)
|
|
#define hil_writeb(v,p) gsc_writeb((v),(p))
|
|
|
|
#elif defined(CONFIG_HP300)
|
|
|
|
#define HILBASE 0xf0428000UL /* HP300 (m68k) port address */
|
|
#define HIL_DATA 0x1
|
|
#define HIL_CMD 0x3
|
|
#define HIL_IRQ 2
|
|
#define hil_readb(p) readb(p)
|
|
#define hil_writeb(v,p) writeb((v),(p))
|
|
|
|
#else
|
|
#error "HIL is not supported on this platform"
|
|
#endif
|
|
|
|
|
|
|
|
/* HIL helper functions */
|
|
|
|
#define hil_busy() (hil_readb(HILBASE + HIL_CMD) & HIL_BUSY)
|
|
#define hil_data_available() (hil_readb(HILBASE + HIL_CMD) & HIL_DATA_RDY)
|
|
#define hil_status() (hil_readb(HILBASE + HIL_CMD))
|
|
#define hil_command(x) do { hil_writeb((x), HILBASE + HIL_CMD); } while (0)
|
|
#define hil_read_data() (hil_readb(HILBASE + HIL_DATA))
|
|
#define hil_write_data(x) do { hil_writeb((x), HILBASE + HIL_DATA); } while (0)
|
|
|
|
/* HIL constants */
|
|
|
|
#define HIL_BUSY 0x02
|
|
#define HIL_DATA_RDY 0x01
|
|
|
|
#define HIL_SETARD 0xA0 /* set auto-repeat delay */
|
|
#define HIL_SETARR 0xA2 /* set auto-repeat rate */
|
|
#define HIL_SETTONE 0xA3 /* set tone generator */
|
|
#define HIL_CNMT 0xB2 /* clear nmi */
|
|
#define HIL_INTON 0x5C /* Turn on interrupts. */
|
|
#define HIL_INTOFF 0x5D /* Turn off interrupts. */
|
|
|
|
#define HIL_READKBDSADR 0xF9
|
|
#define HIL_WRITEKBDSADR 0xE9
|
|
|
|
static unsigned int hphilkeyb_keycode[HIL_KEYCODES_SET1_TBLSIZE] __read_mostly =
|
|
{ HIL_KEYCODES_SET1 };
|
|
|
|
/* HIL structure */
|
|
static struct {
|
|
struct input_dev *dev;
|
|
|
|
unsigned int curdev;
|
|
|
|
unsigned char s;
|
|
unsigned char c;
|
|
int valid;
|
|
|
|
unsigned char data[16];
|
|
unsigned int ptr;
|
|
spinlock_t lock;
|
|
|
|
void *dev_id; /* native bus device */
|
|
} hil_dev;
|
|
|
|
|
|
static void poll_finished(void)
|
|
{
|
|
int down;
|
|
int key;
|
|
unsigned char scode;
|
|
|
|
switch (hil_dev.data[0]) {
|
|
case 0x40:
|
|
down = (hil_dev.data[1] & 1) == 0;
|
|
scode = hil_dev.data[1] >> 1;
|
|
key = hphilkeyb_keycode[scode];
|
|
input_report_key(hil_dev.dev, key, down);
|
|
break;
|
|
}
|
|
hil_dev.curdev = 0;
|
|
}
|
|
|
|
|
|
static inline void handle_status(unsigned char s, unsigned char c)
|
|
{
|
|
if (c & 0x8) {
|
|
/* End of block */
|
|
if (c & 0x10)
|
|
poll_finished();
|
|
} else {
|
|
if (c & 0x10) {
|
|
if (hil_dev.curdev)
|
|
poll_finished(); /* just in case */
|
|
hil_dev.curdev = c & 7;
|
|
hil_dev.ptr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static inline void handle_data(unsigned char s, unsigned char c)
|
|
{
|
|
if (hil_dev.curdev) {
|
|
hil_dev.data[hil_dev.ptr++] = c;
|
|
hil_dev.ptr &= 15;
|
|
}
|
|
}
|
|
|
|
|
|
/* handle HIL interrupts */
|
|
static irqreturn_t hil_interrupt(int irq, void *handle)
|
|
{
|
|
unsigned char s, c;
|
|
|
|
s = hil_status();
|
|
c = hil_read_data();
|
|
|
|
switch (s >> 4) {
|
|
case 0x5:
|
|
handle_status(s, c);
|
|
break;
|
|
case 0x6:
|
|
handle_data(s, c);
|
|
break;
|
|
case 0x4:
|
|
hil_dev.s = s;
|
|
hil_dev.c = c;
|
|
mb();
|
|
hil_dev.valid = 1;
|
|
break;
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
/* send a command to the HIL */
|
|
static void hil_do(unsigned char cmd, unsigned char *data, unsigned int len)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&hil_dev.lock, flags);
|
|
while (hil_busy())
|
|
/* wait */;
|
|
hil_command(cmd);
|
|
while (len--) {
|
|
while (hil_busy())
|
|
/* wait */;
|
|
hil_write_data(*(data++));
|
|
}
|
|
spin_unlock_irqrestore(&hil_dev.lock, flags);
|
|
}
|
|
|
|
|
|
/* initialize HIL */
|
|
static int __devinit hil_keyb_init(void)
|
|
{
|
|
unsigned char c;
|
|
unsigned int i, kbid;
|
|
wait_queue_head_t hil_wait;
|
|
int err;
|
|
|
|
if (hil_dev.dev)
|
|
return -ENODEV; /* already initialized */
|
|
|
|
init_waitqueue_head(&hil_wait);
|
|
spin_lock_init(&hil_dev.lock);
|
|
|
|
hil_dev.dev = input_allocate_device();
|
|
if (!hil_dev.dev)
|
|
return -ENOMEM;
|
|
|
|
err = request_irq(HIL_IRQ, hil_interrupt, 0, "hil", hil_dev.dev_id);
|
|
if (err) {
|
|
printk(KERN_ERR "HIL: Can't get IRQ\n");
|
|
goto err1;
|
|
}
|
|
|
|
/* Turn on interrupts */
|
|
hil_do(HIL_INTON, NULL, 0);
|
|
|
|
/* Look for keyboards */
|
|
hil_dev.valid = 0; /* clear any pending data */
|
|
hil_do(HIL_READKBDSADR, NULL, 0);
|
|
|
|
wait_event_interruptible_timeout(hil_wait, hil_dev.valid, 3 * HZ);
|
|
if (!hil_dev.valid)
|
|
printk(KERN_WARNING "HIL: timed out, assuming no keyboard present\n");
|
|
|
|
c = hil_dev.c;
|
|
hil_dev.valid = 0;
|
|
if (c == 0) {
|
|
kbid = -1;
|
|
printk(KERN_WARNING "HIL: no keyboard present\n");
|
|
} else {
|
|
kbid = ffz(~c);
|
|
printk(KERN_INFO "HIL: keyboard found at id %d\n", kbid);
|
|
}
|
|
|
|
/* set it to raw mode */
|
|
c = 0;
|
|
hil_do(HIL_WRITEKBDSADR, &c, 1);
|
|
|
|
for (i = 0; i < HIL_KEYCODES_SET1_TBLSIZE; i++)
|
|
if (hphilkeyb_keycode[i] != KEY_RESERVED)
|
|
__set_bit(hphilkeyb_keycode[i], hil_dev.dev->keybit);
|
|
|
|
hil_dev.dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
|
|
hil_dev.dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
|
|
BIT_MASK(LED_SCROLLL);
|
|
hil_dev.dev->keycodemax = HIL_KEYCODES_SET1_TBLSIZE;
|
|
hil_dev.dev->keycodesize= sizeof(hphilkeyb_keycode[0]);
|
|
hil_dev.dev->keycode = hphilkeyb_keycode;
|
|
hil_dev.dev->name = "HIL keyboard";
|
|
hil_dev.dev->phys = "hpkbd/input0";
|
|
|
|
hil_dev.dev->id.bustype = BUS_HIL;
|
|
hil_dev.dev->id.vendor = PCI_VENDOR_ID_HP;
|
|
hil_dev.dev->id.product = 0x0001;
|
|
hil_dev.dev->id.version = 0x0010;
|
|
|
|
err = input_register_device(hil_dev.dev);
|
|
if (err) {
|
|
printk(KERN_ERR "HIL: Can't register device\n");
|
|
goto err2;
|
|
}
|
|
|
|
printk(KERN_INFO "input: %s, ID %d at 0x%08lx (irq %d) found and attached\n",
|
|
hil_dev.dev->name, kbid, HILBASE, HIL_IRQ);
|
|
|
|
return 0;
|
|
|
|
err2:
|
|
hil_do(HIL_INTOFF, NULL, 0);
|
|
free_irq(HIL_IRQ, hil_dev.dev_id);
|
|
err1:
|
|
input_free_device(hil_dev.dev);
|
|
hil_dev.dev = NULL;
|
|
return err;
|
|
}
|
|
|
|
static void __devexit hil_keyb_exit(void)
|
|
{
|
|
if (HIL_IRQ)
|
|
free_irq(HIL_IRQ, hil_dev.dev_id);
|
|
|
|
/* Turn off interrupts */
|
|
hil_do(HIL_INTOFF, NULL, 0);
|
|
|
|
input_unregister_device(hil_dev.dev);
|
|
hil_dev.dev = NULL;
|
|
}
|
|
|
|
#if defined(CONFIG_PARISC)
|
|
static int __devinit hil_probe_chip(struct parisc_device *dev)
|
|
{
|
|
/* Only allow one HIL keyboard */
|
|
if (hil_dev.dev)
|
|
return -ENODEV;
|
|
|
|
if (!dev->irq) {
|
|
printk(KERN_WARNING "HIL: IRQ not found for HIL bus at 0x%p\n",
|
|
(void *)dev->hpa.start);
|
|
return -ENODEV;
|
|
}
|
|
|
|
hil_base = dev->hpa.start;
|
|
hil_irq = dev->irq;
|
|
hil_dev.dev_id = dev;
|
|
|
|
printk(KERN_INFO "Found HIL bus at 0x%08lx, IRQ %d\n", hil_base, hil_irq);
|
|
|
|
return hil_keyb_init();
|
|
}
|
|
|
|
static int __devexit hil_remove_chip(struct parisc_device *dev)
|
|
{
|
|
hil_keyb_exit();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct parisc_device_id hil_tbl[] = {
|
|
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00073 },
|
|
{ 0, }
|
|
};
|
|
|
|
#if 0
|
|
/* Disabled to avoid conflicts with the HP SDC HIL drivers */
|
|
MODULE_DEVICE_TABLE(parisc, hil_tbl);
|
|
#endif
|
|
|
|
static struct parisc_driver hil_driver = {
|
|
.name = "hil",
|
|
.id_table = hil_tbl,
|
|
.probe = hil_probe_chip,
|
|
.remove = __devexit_p(hil_remove_chip),
|
|
};
|
|
|
|
static int __init hil_init(void)
|
|
{
|
|
return register_parisc_driver(&hil_driver);
|
|
}
|
|
|
|
static void __exit hil_exit(void)
|
|
{
|
|
unregister_parisc_driver(&hil_driver);
|
|
}
|
|
|
|
#else /* !CONFIG_PARISC */
|
|
|
|
static int __init hil_init(void)
|
|
{
|
|
int error;
|
|
|
|
/* Only allow one HIL keyboard */
|
|
if (hil_dev.dev)
|
|
return -EBUSY;
|
|
|
|
if (!MACH_IS_HP300)
|
|
return -ENODEV;
|
|
|
|
if (!hwreg_present((void *)(HILBASE + HIL_DATA))) {
|
|
printk(KERN_ERR "HIL: hardware register was not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!request_region(HILBASE + HIL_DATA, 2, "hil")) {
|
|
printk(KERN_ERR "HIL: IOPORT region already used\n");
|
|
return -EIO;
|
|
}
|
|
|
|
error = hil_keyb_init();
|
|
if (error) {
|
|
release_region(HILBASE + HIL_DATA, 2);
|
|
return error;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit hil_exit(void)
|
|
{
|
|
hil_keyb_exit();
|
|
release_region(HILBASE + HIL_DATA, 2);
|
|
}
|
|
|
|
#endif /* CONFIG_PARISC */
|
|
|
|
module_init(hil_init);
|
|
module_exit(hil_exit);
|