2005-04-17 02:20:36 +04:00
/*
* linux / drivers / input / serio / pcips2 . c
*
* Copyright ( C ) 2003 Russell King , All Rights Reserved .
*
* 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 .
*
* I ' m not sure if this is a generic PS / 2 PCI interface or specific to
* the Mobility Electronics docking station .
*/
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/input.h>
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/serio.h>
# include <linux/delay.h>
# include <asm/io.h>
# define PS2_CTRL (0)
# define PS2_STATUS (1)
# define PS2_DATA (2)
# define PS2_CTRL_CLK (1<<0)
# define PS2_CTRL_DAT (1<<1)
# define PS2_CTRL_TXIRQ (1<<2)
# define PS2_CTRL_ENABLE (1<<3)
# define PS2_CTRL_RXIRQ (1<<4)
# define PS2_STAT_CLK (1<<0)
# define PS2_STAT_DAT (1<<1)
# define PS2_STAT_PARITY (1<<2)
# define PS2_STAT_RXFULL (1<<5)
# define PS2_STAT_TXBUSY (1<<6)
# define PS2_STAT_TXEMPTY (1<<7)
struct pcips2_data {
struct serio * io ;
unsigned int base ;
struct pci_dev * dev ;
} ;
static int pcips2_write ( struct serio * io , unsigned char val )
{
struct pcips2_data * ps2if = io - > port_data ;
unsigned int stat ;
do {
stat = inb ( ps2if - > base + PS2_STATUS ) ;
cpu_relax ( ) ;
} while ( ! ( stat & PS2_STAT_TXEMPTY ) ) ;
outb ( val , ps2if - > base + PS2_DATA ) ;
return 0 ;
}
static irqreturn_t pcips2_interrupt ( int irq , void * devid , struct pt_regs * regs )
{
struct pcips2_data * ps2if = devid ;
unsigned char status , scancode ;
int handled = 0 ;
do {
unsigned int flag ;
status = inb ( ps2if - > base + PS2_STATUS ) ;
if ( ! ( status & PS2_STAT_RXFULL ) )
break ;
handled = 1 ;
scancode = inb ( ps2if - > base + PS2_DATA ) ;
if ( status = = 0xff & & scancode = = 0xff )
break ;
flag = ( status & PS2_STAT_PARITY ) ? 0 : SERIO_PARITY ;
if ( hweight8 ( scancode ) & 1 )
flag ^ = SERIO_PARITY ;
serio_interrupt ( ps2if - > io , scancode , flag , regs ) ;
} while ( 1 ) ;
return IRQ_RETVAL ( handled ) ;
}
static void pcips2_flush_input ( struct pcips2_data * ps2if )
{
unsigned char status , scancode ;
do {
status = inb ( ps2if - > base + PS2_STATUS ) ;
if ( ! ( status & PS2_STAT_RXFULL ) )
break ;
scancode = inb ( ps2if - > base + PS2_DATA ) ;
if ( status = = 0xff & & scancode = = 0xff )
break ;
} while ( 1 ) ;
}
static int pcips2_open ( struct serio * io )
{
struct pcips2_data * ps2if = io - > port_data ;
int ret , val = 0 ;
outb ( PS2_CTRL_ENABLE , ps2if - > base ) ;
pcips2_flush_input ( ps2if ) ;
2006-07-02 06:29:38 +04:00
ret = request_irq ( ps2if - > dev - > irq , pcips2_interrupt , IRQF_SHARED ,
2005-04-17 02:20:36 +04:00
" pcips2 " , ps2if ) ;
if ( ret = = 0 )
val = PS2_CTRL_ENABLE | PS2_CTRL_RXIRQ ;
outb ( val , ps2if - > base ) ;
return ret ;
}
static void pcips2_close ( struct serio * io )
{
struct pcips2_data * ps2if = io - > port_data ;
outb ( 0 , ps2if - > base ) ;
free_irq ( ps2if - > dev - > irq , ps2if ) ;
}
static int __devinit pcips2_probe ( struct pci_dev * dev , const struct pci_device_id * id )
{
struct pcips2_data * ps2if ;
struct serio * serio ;
int ret ;
ret = pci_enable_device ( dev ) ;
if ( ret )
goto out ;
ret = pci_request_regions ( dev , " pcips2 " ) ;
if ( ret )
goto disable ;
ps2if = kmalloc ( sizeof ( struct pcips2_data ) , GFP_KERNEL ) ;
serio = kmalloc ( sizeof ( struct serio ) , GFP_KERNEL ) ;
if ( ! ps2if | | ! serio ) {
ret = - ENOMEM ;
goto release ;
}
memset ( ps2if , 0 , sizeof ( struct pcips2_data ) ) ;
memset ( serio , 0 , sizeof ( struct serio ) ) ;
serio - > id . type = SERIO_8042 ;
serio - > write = pcips2_write ;
serio - > open = pcips2_open ;
serio - > close = pcips2_close ;
strlcpy ( serio - > name , pci_name ( dev ) , sizeof ( serio - > name ) ) ;
strlcpy ( serio - > phys , dev - > dev . bus_id , sizeof ( serio - > phys ) ) ;
serio - > port_data = ps2if ;
serio - > dev . parent = & dev - > dev ;
ps2if - > io = serio ;
ps2if - > dev = dev ;
ps2if - > base = pci_resource_start ( dev , 0 ) ;
pci_set_drvdata ( dev , ps2if ) ;
serio_register_port ( ps2if - > io ) ;
return 0 ;
release :
kfree ( ps2if ) ;
kfree ( serio ) ;
pci_release_regions ( dev ) ;
disable :
pci_disable_device ( dev ) ;
out :
return ret ;
}
static void __devexit pcips2_remove ( struct pci_dev * dev )
{
struct pcips2_data * ps2if = pci_get_drvdata ( dev ) ;
serio_unregister_port ( ps2if - > io ) ;
pci_set_drvdata ( dev , NULL ) ;
kfree ( ps2if ) ;
pci_release_regions ( dev ) ;
pci_disable_device ( dev ) ;
}
static struct pci_device_id pcips2_ids [ ] = {
{
. vendor = 0x14f2 , /* MOBILITY */
. device = 0x0123 , /* Keyboard */
. subvendor = PCI_ANY_ID ,
. subdevice = PCI_ANY_ID ,
. class = PCI_CLASS_INPUT_KEYBOARD < < 8 ,
. class_mask = 0xffff00 ,
} ,
{
. vendor = 0x14f2 , /* MOBILITY */
. device = 0x0124 , /* Mouse */
. subvendor = PCI_ANY_ID ,
. subdevice = PCI_ANY_ID ,
. class = PCI_CLASS_INPUT_MOUSE < < 8 ,
. class_mask = 0xffff00 ,
} ,
{ 0 , }
} ;
static struct pci_driver pcips2_driver = {
. name = " pcips2 " ,
. id_table = pcips2_ids ,
. probe = pcips2_probe ,
. remove = __devexit_p ( pcips2_remove ) ,
} ;
static int __init pcips2_init ( void )
{
return pci_register_driver ( & pcips2_driver ) ;
}
static void __exit pcips2_exit ( void )
{
pci_unregister_driver ( & pcips2_driver ) ;
}
module_init ( pcips2_init ) ;
module_exit ( pcips2_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Russell King <rmk@arm.linux.org.uk> " ) ;
MODULE_DESCRIPTION ( " PCI PS/2 keyboard/mouse driver " ) ;
MODULE_DEVICE_TABLE ( pci , pcips2_ids ) ;