Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
183
drivers/input/serio/Kconfig
Normal file
183
drivers/input/serio/Kconfig
Normal file
@@ -0,0 +1,183 @@
|
||||
#
|
||||
# Input core configuration
|
||||
#
|
||||
config SERIO
|
||||
tristate "Serial I/O support" if EMBEDDED || !X86
|
||||
default y
|
||||
---help---
|
||||
Say Yes here if you have any input device that uses serial I/O to
|
||||
communicate with the system. This includes the
|
||||
* standard AT keyboard and PS/2 mouse *
|
||||
as well as serial mice, Sun keyboards, some joysticks and 6dof
|
||||
devices and more.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called serio.
|
||||
|
||||
if SERIO
|
||||
|
||||
config SERIO_I8042
|
||||
tristate "i8042 PC Keyboard controller" if EMBEDDED || !X86
|
||||
default y
|
||||
depends on !PARISC && (!ARM || ARCH_SHARK || FOOTBRIDGE_HOST) && !M68K
|
||||
---help---
|
||||
i8042 is the chip over which the standard AT keyboard and PS/2
|
||||
mouse are connected to the computer. If you use these devices,
|
||||
you'll need to say Y here.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called i8042.
|
||||
|
||||
config SERIO_SERPORT
|
||||
tristate "Serial port line discipline"
|
||||
default y
|
||||
---help---
|
||||
Say Y here if you plan to use an input device (mouse, joystick,
|
||||
tablet, 6dof) that communicates over the RS232 serial (COM) port.
|
||||
|
||||
More information is available: <file:Documentation/input/input.txt>
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called serport.
|
||||
|
||||
config SERIO_CT82C710
|
||||
tristate "ct82c710 Aux port controller"
|
||||
depends on X86
|
||||
---help---
|
||||
Say Y here if you have a Texas Instruments TravelMate notebook
|
||||
equipped with the ct82c710 chip and want to use a mouse connected
|
||||
to the "QuickPort".
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ct82c710.
|
||||
|
||||
config SERIO_Q40KBD
|
||||
tristate "Q40 keyboard controller"
|
||||
depends on Q40
|
||||
|
||||
config SERIO_PARKBD
|
||||
tristate "Parallel port keyboard adapter"
|
||||
depends on PARPORT
|
||||
---help---
|
||||
Say Y here if you built a simple parallel port adapter to attach
|
||||
an additional AT keyboard, XT keyboard or PS/2 mouse.
|
||||
|
||||
More information is available: <file:Documentation/input/input.txt>
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called parkbd.
|
||||
|
||||
config SERIO_RPCKBD
|
||||
tristate "Acorn RiscPC keyboard controller"
|
||||
depends on ARCH_ACORN || ARCH_CLPS7500
|
||||
default y
|
||||
help
|
||||
Say Y here if you have the Acorn RiscPC and want to use an AT
|
||||
keyboard connected to its keyboard controller.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rpckbd.
|
||||
|
||||
config SERIO_AMBAKMI
|
||||
tristate "AMBA KMI keyboard controller"
|
||||
depends on ARM_AMBA
|
||||
|
||||
config SERIO_SA1111
|
||||
tristate "Intel SA1111 keyboard controller"
|
||||
depends on SA1111
|
||||
|
||||
config SERIO_GSCPS2
|
||||
tristate "HP GSC PS/2 keyboard and PS/2 mouse controller"
|
||||
depends on GSC
|
||||
default y
|
||||
help
|
||||
This driver provides support for the PS/2 ports on PA-RISC machines
|
||||
over which HP PS/2 keyboards and PS/2 mice may be connected.
|
||||
If you use these devices, you'll need to say Y here.
|
||||
|
||||
It's safe to enable this driver, so if unsure, say Y.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called gscps2.
|
||||
|
||||
config HP_SDC
|
||||
tristate "HP System Device Controller i8042 Support"
|
||||
depends on (GSC || HP300) && SERIO
|
||||
default y
|
||||
---help---
|
||||
This option enables support for the "System Device
|
||||
Controller", an i8042 carrying microcode to manage a
|
||||
few miscellaneous devices on some Hewlett Packard systems.
|
||||
The SDC itself contains a 10ms resolution timer/clock capable
|
||||
of delivering interrupts on a periodic and one-shot basis.
|
||||
The SDC may also be connected to a battery-backed real-time
|
||||
clock, a basic audio waveform generator, and an HP-HIL Master
|
||||
Link Controller serving up to seven input devices.
|
||||
|
||||
By itself this option is rather useless, but enabling it will
|
||||
enable selection of drivers for the abovementioned devices.
|
||||
It is, however, incompatible with the old, reliable HIL keyboard
|
||||
driver, and the new HIL driver is experimental, so if you plan
|
||||
to use a HIL keyboard as your primary keyboard, you may wish
|
||||
to keep using that driver until the new HIL drivers have had
|
||||
more testing.
|
||||
|
||||
config HIL_MLC
|
||||
tristate "HIL MLC Support (needed for HIL input devices)"
|
||||
depends on HP_SDC
|
||||
|
||||
config SERIO_PCIPS2
|
||||
tristate "PCI PS/2 keyboard and PS/2 mouse controller"
|
||||
depends on PCI
|
||||
help
|
||||
Say Y here if you have a Mobility Docking station with PS/2
|
||||
keyboard and mice ports.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called pcips2.
|
||||
|
||||
config SERIO_MACEPS2
|
||||
tristate "SGI O2 MACE PS/2 controller"
|
||||
depends on SGI_IP32
|
||||
help
|
||||
Say Y here if you have SGI O2 workstation and want to use its
|
||||
PS/2 ports.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called maceps2.
|
||||
|
||||
config SERIO_LIBPS2
|
||||
tristate "PS/2 driver library" if EMBEDDED
|
||||
help
|
||||
Say Y here if you are using a driver for device connected
|
||||
to a PS/2 port, such as PS/2 mouse or standard AT keyboard.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called libps2.
|
||||
|
||||
config SERIO_RAW
|
||||
tristate "Raw access to serio ports"
|
||||
help
|
||||
Say Y here if you want to have raw access to serio ports, such as
|
||||
AUX ports on i8042 keyboard controller. Each serio port that is
|
||||
bound to this driver will be accessible via a char device with
|
||||
major 10 and dynamically allocated minor. The driver will try
|
||||
allocating minor 1 (that historically corresponds to /dev/psaux)
|
||||
first. To bind this driver to a serio port use sysfs interface:
|
||||
|
||||
echo -n "serio_raw" > /sys/bus/serio/devices/serioX/drvctl
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called serio_raw.
|
||||
|
||||
endif
|
||||
22
drivers/input/serio/Makefile
Normal file
22
drivers/input/serio/Makefile
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# Makefile for the input core drivers.
|
||||
#
|
||||
|
||||
# Each configuration option enables a list of files.
|
||||
|
||||
obj-$(CONFIG_SERIO) += serio.o
|
||||
obj-$(CONFIG_SERIO_I8042) += i8042.o
|
||||
obj-$(CONFIG_SERIO_PARKBD) += parkbd.o
|
||||
obj-$(CONFIG_SERIO_SERPORT) += serport.o
|
||||
obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o
|
||||
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
|
||||
obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
|
||||
obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
|
||||
obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
|
||||
obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
|
||||
obj-$(CONFIG_HP_SDC) += hp_sdc.o
|
||||
obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o
|
||||
obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o
|
||||
obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o
|
||||
obj-$(CONFIG_SERIO_LIBPS2) += libps2.o
|
||||
obj-$(CONFIG_SERIO_RAW) += serio_raw.o
|
||||
224
drivers/input/serio/ambakmi.c
Normal file
224
drivers/input/serio/ambakmi.c
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* linux/drivers/input/serio/ambakmi.c
|
||||
*
|
||||
* Copyright (C) 2000-2003 Deep Blue Solutions Ltd.
|
||||
* Copyright (C) 2002 Russell King.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/amba/kmi.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
|
||||
#define KMI_BASE (kmi->base)
|
||||
|
||||
struct amba_kmi_port {
|
||||
struct serio *io;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
unsigned int irq;
|
||||
unsigned int divisor;
|
||||
unsigned int open;
|
||||
};
|
||||
|
||||
static irqreturn_t amba_kmi_int(int irq, void *dev_id)
|
||||
{
|
||||
struct amba_kmi_port *kmi = dev_id;
|
||||
unsigned int status = readb(KMIIR);
|
||||
int handled = IRQ_NONE;
|
||||
|
||||
while (status & KMIIR_RXINTR) {
|
||||
serio_interrupt(kmi->io, readb(KMIDATA), 0);
|
||||
status = readb(KMIIR);
|
||||
handled = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
static int amba_kmi_write(struct serio *io, unsigned char val)
|
||||
{
|
||||
struct amba_kmi_port *kmi = io->port_data;
|
||||
unsigned int timeleft = 10000; /* timeout in 100ms */
|
||||
|
||||
while ((readb(KMISTAT) & KMISTAT_TXEMPTY) == 0 && timeleft--)
|
||||
udelay(10);
|
||||
|
||||
if (timeleft)
|
||||
writeb(val, KMIDATA);
|
||||
|
||||
return timeleft ? 0 : SERIO_TIMEOUT;
|
||||
}
|
||||
|
||||
static int amba_kmi_open(struct serio *io)
|
||||
{
|
||||
struct amba_kmi_port *kmi = io->port_data;
|
||||
unsigned int divisor;
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(kmi->clk);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
divisor = clk_get_rate(kmi->clk) / 8000000 - 1;
|
||||
writeb(divisor, KMICLKDIV);
|
||||
writeb(KMICR_EN, KMICR);
|
||||
|
||||
ret = request_irq(kmi->irq, amba_kmi_int, 0, "kmi-pl050", kmi);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "kmi: failed to claim IRQ%d\n", kmi->irq);
|
||||
writeb(0, KMICR);
|
||||
goto clk_disable;
|
||||
}
|
||||
|
||||
writeb(KMICR_EN | KMICR_RXINTREN, KMICR);
|
||||
|
||||
return 0;
|
||||
|
||||
clk_disable:
|
||||
clk_disable(kmi->clk);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void amba_kmi_close(struct serio *io)
|
||||
{
|
||||
struct amba_kmi_port *kmi = io->port_data;
|
||||
|
||||
writeb(0, KMICR);
|
||||
|
||||
free_irq(kmi->irq, kmi);
|
||||
clk_disable(kmi->clk);
|
||||
}
|
||||
|
||||
static int amba_kmi_probe(struct amba_device *dev, void *id)
|
||||
{
|
||||
struct amba_kmi_port *kmi;
|
||||
struct serio *io;
|
||||
int ret;
|
||||
|
||||
ret = amba_request_regions(dev, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
kmi = kmalloc(sizeof(struct amba_kmi_port), GFP_KERNEL);
|
||||
io = kmalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!kmi || !io) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memset(kmi, 0, sizeof(struct amba_kmi_port));
|
||||
memset(io, 0, sizeof(struct serio));
|
||||
|
||||
io->id.type = SERIO_8042;
|
||||
io->write = amba_kmi_write;
|
||||
io->open = amba_kmi_open;
|
||||
io->close = amba_kmi_close;
|
||||
strlcpy(io->name, dev->dev.bus_id, sizeof(io->name));
|
||||
strlcpy(io->phys, dev->dev.bus_id, sizeof(io->phys));
|
||||
io->port_data = kmi;
|
||||
io->dev.parent = &dev->dev;
|
||||
|
||||
kmi->io = io;
|
||||
kmi->base = ioremap(dev->res.start, KMI_SIZE);
|
||||
if (!kmi->base) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
kmi->clk = clk_get(&dev->dev, "KMIREFCLK");
|
||||
if (IS_ERR(kmi->clk)) {
|
||||
ret = PTR_ERR(kmi->clk);
|
||||
goto unmap;
|
||||
}
|
||||
|
||||
kmi->irq = dev->irq[0];
|
||||
amba_set_drvdata(dev, kmi);
|
||||
|
||||
serio_register_port(kmi->io);
|
||||
return 0;
|
||||
|
||||
unmap:
|
||||
iounmap(kmi->base);
|
||||
out:
|
||||
kfree(kmi);
|
||||
kfree(io);
|
||||
amba_release_regions(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amba_kmi_remove(struct amba_device *dev)
|
||||
{
|
||||
struct amba_kmi_port *kmi = amba_get_drvdata(dev);
|
||||
|
||||
amba_set_drvdata(dev, NULL);
|
||||
|
||||
serio_unregister_port(kmi->io);
|
||||
clk_put(kmi->clk);
|
||||
iounmap(kmi->base);
|
||||
kfree(kmi);
|
||||
amba_release_regions(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amba_kmi_resume(struct amba_device *dev)
|
||||
{
|
||||
struct amba_kmi_port *kmi = amba_get_drvdata(dev);
|
||||
|
||||
/* kick the serio layer to rescan this port */
|
||||
serio_reconnect(kmi->io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct amba_id amba_kmi_idtable[] = {
|
||||
{
|
||||
.id = 0x00041050,
|
||||
.mask = 0x000fffff,
|
||||
},
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
static struct amba_driver ambakmi_driver = {
|
||||
.drv = {
|
||||
.name = "kmi-pl050",
|
||||
},
|
||||
.id_table = amba_kmi_idtable,
|
||||
.probe = amba_kmi_probe,
|
||||
.remove = amba_kmi_remove,
|
||||
.resume = amba_kmi_resume,
|
||||
};
|
||||
|
||||
static int __init amba_kmi_init(void)
|
||||
{
|
||||
return amba_driver_register(&ambakmi_driver);
|
||||
}
|
||||
|
||||
static void __exit amba_kmi_exit(void)
|
||||
{
|
||||
amba_driver_unregister(&ambakmi_driver);
|
||||
}
|
||||
|
||||
module_init(amba_kmi_init);
|
||||
module_exit(amba_kmi_exit);
|
||||
|
||||
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
|
||||
MODULE_DESCRIPTION("AMBA KMI controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
262
drivers/input/serio/ct82c710.c
Normal file
262
drivers/input/serio/ct82c710.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* $Id: ct82c710.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* Copyright (c) 1999-2001 Vojtech Pavlik
|
||||
*/
|
||||
|
||||
/*
|
||||
* 82C710 C&T mouse port chip driver for Linux
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* Should you need to contact me, the author, you can do so either by
|
||||
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
|
||||
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION("82C710 C&T mouse port chip driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*
|
||||
* ct82c710 interface
|
||||
*/
|
||||
|
||||
#define CT82C710_DEV_IDLE 0x01 /* Device Idle */
|
||||
#define CT82C710_RX_FULL 0x02 /* Device Char received */
|
||||
#define CT82C710_TX_IDLE 0x04 /* Device XMIT Idle */
|
||||
#define CT82C710_RESET 0x08 /* Device Reset */
|
||||
#define CT82C710_INTS_ON 0x10 /* Device Interrupt On */
|
||||
#define CT82C710_ERROR_FLAG 0x20 /* Device Error */
|
||||
#define CT82C710_CLEAR 0x40 /* Device Clear */
|
||||
#define CT82C710_ENABLE 0x80 /* Device Enable */
|
||||
|
||||
#define CT82C710_IRQ 12
|
||||
|
||||
#define CT82C710_DATA ct82c710_iores.start
|
||||
#define CT82C710_STATUS (ct82c710_iores.start + 1)
|
||||
|
||||
static struct serio *ct82c710_port;
|
||||
static struct platform_device *ct82c710_device;
|
||||
static struct resource ct82c710_iores;
|
||||
|
||||
/*
|
||||
* Interrupt handler for the 82C710 mouse port. A character
|
||||
* is waiting in the 82C710.
|
||||
*/
|
||||
|
||||
static irqreturn_t ct82c710_interrupt(int cpl, void *dev_id)
|
||||
{
|
||||
return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for device to send output char and flush any input char.
|
||||
*/
|
||||
|
||||
static int ct82c170_wait(void)
|
||||
{
|
||||
int timeout = 60000;
|
||||
|
||||
while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE))
|
||||
!= (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) {
|
||||
|
||||
if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA);
|
||||
|
||||
udelay(1);
|
||||
timeout--;
|
||||
}
|
||||
|
||||
return !timeout;
|
||||
}
|
||||
|
||||
static void ct82c710_close(struct serio *serio)
|
||||
{
|
||||
if (ct82c170_wait())
|
||||
printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
|
||||
|
||||
outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS);
|
||||
|
||||
if (ct82c170_wait())
|
||||
printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
|
||||
|
||||
free_irq(CT82C710_IRQ, NULL);
|
||||
}
|
||||
|
||||
static int ct82c710_open(struct serio *serio)
|
||||
{
|
||||
unsigned char status;
|
||||
|
||||
if (request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL))
|
||||
return -1;
|
||||
|
||||
status = inb_p(CT82C710_STATUS);
|
||||
|
||||
status |= (CT82C710_ENABLE | CT82C710_RESET);
|
||||
outb_p(status, CT82C710_STATUS);
|
||||
|
||||
status &= ~(CT82C710_RESET);
|
||||
outb_p(status, CT82C710_STATUS);
|
||||
|
||||
status |= CT82C710_INTS_ON;
|
||||
outb_p(status, CT82C710_STATUS); /* Enable interrupts */
|
||||
|
||||
while (ct82c170_wait()) {
|
||||
printk(KERN_ERR "ct82c710: Device busy in open()\n");
|
||||
status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON);
|
||||
outb_p(status, CT82C710_STATUS);
|
||||
free_irq(CT82C710_IRQ, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to the 82C710 mouse device.
|
||||
*/
|
||||
|
||||
static int ct82c710_write(struct serio *port, unsigned char c)
|
||||
{
|
||||
if (ct82c170_wait()) return -1;
|
||||
outb_p(c, CT82C710_DATA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if we can find a 82C710 device. Read mouse address.
|
||||
*/
|
||||
|
||||
static int __init ct82c710_detect(void)
|
||||
{
|
||||
outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */
|
||||
outb_p(0xaa, 0x3fa); /* Inverse of 55 */
|
||||
outb_p(0x36, 0x3fa); /* Address the chip */
|
||||
outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */
|
||||
outb_p(0x1b, 0x2fa); /* Inverse of e4 */
|
||||
outb_p(0x0f, 0x390); /* Write index */
|
||||
if (inb_p(0x391) != 0xe4) /* Config address found? */
|
||||
return -ENODEV; /* No: no 82C710 here */
|
||||
|
||||
outb_p(0x0d, 0x390); /* Write index */
|
||||
ct82c710_iores.start = inb_p(0x391) << 2; /* Get mouse I/O address */
|
||||
ct82c710_iores.end = ct82c710_iores.start + 1;
|
||||
ct82c710_iores.flags = IORESOURCE_IO;
|
||||
outb_p(0x0f, 0x390);
|
||||
outb_p(0x0f, 0x391); /* Close config mode */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit ct82c710_probe(struct platform_device *dev)
|
||||
{
|
||||
ct82c710_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!ct82c710_port)
|
||||
return -ENOMEM;
|
||||
|
||||
ct82c710_port->id.type = SERIO_8042;
|
||||
ct82c710_port->dev.parent = &dev->dev;
|
||||
ct82c710_port->open = ct82c710_open;
|
||||
ct82c710_port->close = ct82c710_close;
|
||||
ct82c710_port->write = ct82c710_write;
|
||||
strlcpy(ct82c710_port->name, "C&T 82c710 mouse port",
|
||||
sizeof(ct82c710_port->name));
|
||||
snprintf(ct82c710_port->phys, sizeof(ct82c710_port->phys),
|
||||
"isa%16llx/serio0", (unsigned long long)CT82C710_DATA);
|
||||
|
||||
serio_register_port(ct82c710_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit ct82c710_remove(struct platform_device *dev)
|
||||
{
|
||||
serio_unregister_port(ct82c710_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ct82c710_driver = {
|
||||
.driver = {
|
||||
.name = "ct82c710",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ct82c710_probe,
|
||||
.remove = __devexit_p(ct82c710_remove),
|
||||
};
|
||||
|
||||
|
||||
static int __init ct82c710_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = ct82c710_detect();
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = platform_driver_register(&ct82c710_driver);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
ct82c710_device = platform_device_alloc("ct82c710", -1);
|
||||
if (!ct82c710_device) {
|
||||
error = -ENOMEM;
|
||||
goto err_unregister_driver;
|
||||
}
|
||||
|
||||
error = platform_device_add_resources(ct82c710_device, &ct82c710_iores, 1);
|
||||
if (error)
|
||||
goto err_free_device;
|
||||
|
||||
error = platform_device_add(ct82c710_device);
|
||||
if (error)
|
||||
goto err_free_device;
|
||||
|
||||
serio_register_port(ct82c710_port);
|
||||
|
||||
printk(KERN_INFO "serio: C&T 82c710 mouse port at %#llx irq %d\n",
|
||||
(unsigned long long)CT82C710_DATA, CT82C710_IRQ);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_device:
|
||||
platform_device_put(ct82c710_device);
|
||||
err_unregister_driver:
|
||||
platform_driver_unregister(&ct82c710_driver);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit ct82c710_exit(void)
|
||||
{
|
||||
platform_device_unregister(ct82c710_device);
|
||||
platform_driver_unregister(&ct82c710_driver);
|
||||
}
|
||||
|
||||
module_init(ct82c710_init);
|
||||
module_exit(ct82c710_exit);
|
||||
465
drivers/input/serio/gscps2.c
Normal file
465
drivers/input/serio/gscps2.c
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* drivers/input/serio/gscps2.c
|
||||
*
|
||||
* Copyright (c) 2004-2006 Helge Deller <deller@gmx.de>
|
||||
* Copyright (c) 2002 Laurent Canet <canetl@esiee.fr>
|
||||
* Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org>
|
||||
*
|
||||
* Pieces of code based on linux-2.4's hp_mouse.c & hp_keyb.c
|
||||
* Copyright (c) 1999 Alex deVries <alex@onefishtwo.ca>
|
||||
* Copyright (c) 1999-2000 Philipp Rumpf <prumpf@tux.org>
|
||||
* Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr>
|
||||
* Copyright (c) 2000-2001 Thomas Marteau <marteaut@esiee.fr>
|
||||
*
|
||||
* HP GSC PS/2 port driver, found in PA/RISC Workstations
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* TODO:
|
||||
* - Dino testing (did HP ever shipped a machine on which this port
|
||||
* was usable/enabled ?)
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/pci_ids.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/parisc-device.h>
|
||||
|
||||
MODULE_AUTHOR("Laurent Canet <canetl@esiee.fr>, Thibaut Varene <varenet@parisc-linux.org>, Helge Deller <deller@gmx.de>");
|
||||
MODULE_DESCRIPTION("HP GSC PS2 port driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl);
|
||||
|
||||
#define PFX "gscps2.c: "
|
||||
|
||||
/*
|
||||
* Driver constants
|
||||
*/
|
||||
|
||||
/* various constants */
|
||||
#define ENABLE 1
|
||||
#define DISABLE 0
|
||||
|
||||
#define GSC_DINO_OFFSET 0x0800 /* offset for DINO controller versus LASI one */
|
||||
|
||||
/* PS/2 IO port offsets */
|
||||
#define GSC_ID 0x00 /* device ID offset (see: GSC_ID_XXX) */
|
||||
#define GSC_RESET 0x00 /* reset port offset */
|
||||
#define GSC_RCVDATA 0x04 /* receive port offset */
|
||||
#define GSC_XMTDATA 0x04 /* transmit port offset */
|
||||
#define GSC_CONTROL 0x08 /* see: Control register bits */
|
||||
#define GSC_STATUS 0x0C /* see: Status register bits */
|
||||
|
||||
/* Control register bits */
|
||||
#define GSC_CTRL_ENBL 0x01 /* enable interface */
|
||||
#define GSC_CTRL_LPBXR 0x02 /* loopback operation */
|
||||
#define GSC_CTRL_DIAG 0x20 /* directly control clock/data line */
|
||||
#define GSC_CTRL_DATDIR 0x40 /* data line direct control */
|
||||
#define GSC_CTRL_CLKDIR 0x80 /* clock line direct control */
|
||||
|
||||
/* Status register bits */
|
||||
#define GSC_STAT_RBNE 0x01 /* Receive Buffer Not Empty */
|
||||
#define GSC_STAT_TBNE 0x02 /* Transmit Buffer Not Empty */
|
||||
#define GSC_STAT_TERR 0x04 /* Timeout Error */
|
||||
#define GSC_STAT_PERR 0x08 /* Parity Error */
|
||||
#define GSC_STAT_CMPINTR 0x10 /* Composite Interrupt = irq on any port */
|
||||
#define GSC_STAT_DATSHD 0x40 /* Data Line Shadow */
|
||||
#define GSC_STAT_CLKSHD 0x80 /* Clock Line Shadow */
|
||||
|
||||
/* IDs returned by GSC_ID port register */
|
||||
#define GSC_ID_KEYBOARD 0 /* device ID values */
|
||||
#define GSC_ID_MOUSE 1
|
||||
|
||||
|
||||
static irqreturn_t gscps2_interrupt(int irq, void *dev);
|
||||
|
||||
#define BUFFER_SIZE 0x0f
|
||||
|
||||
/* GSC PS/2 port device struct */
|
||||
struct gscps2port {
|
||||
struct list_head node;
|
||||
struct parisc_device *padev;
|
||||
struct serio *port;
|
||||
spinlock_t lock;
|
||||
char *addr;
|
||||
u8 act, append; /* position in buffer[] */
|
||||
struct {
|
||||
u8 data;
|
||||
u8 str;
|
||||
} buffer[BUFFER_SIZE+1];
|
||||
int id;
|
||||
};
|
||||
|
||||
/*
|
||||
* Various HW level routines
|
||||
*/
|
||||
|
||||
#define gscps2_readb_input(x) readb((x)+GSC_RCVDATA)
|
||||
#define gscps2_readb_control(x) readb((x)+GSC_CONTROL)
|
||||
#define gscps2_readb_status(x) readb((x)+GSC_STATUS)
|
||||
#define gscps2_writeb_control(x, y) writeb((x), (y)+GSC_CONTROL)
|
||||
|
||||
|
||||
/*
|
||||
* wait_TBE() - wait for Transmit Buffer Empty
|
||||
*/
|
||||
|
||||
static int wait_TBE(char *addr)
|
||||
{
|
||||
int timeout = 25000; /* device is expected to react within 250 msec */
|
||||
while (gscps2_readb_status(addr) & GSC_STAT_TBNE) {
|
||||
if (!--timeout)
|
||||
return 0; /* This should not happen */
|
||||
udelay(10);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gscps2_flush() - flush the receive buffer
|
||||
*/
|
||||
|
||||
static void gscps2_flush(struct gscps2port *ps2port)
|
||||
{
|
||||
while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE)
|
||||
gscps2_readb_input(ps2port->addr);
|
||||
ps2port->act = ps2port->append = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* gscps2_writeb_output() - write a byte to the port
|
||||
*
|
||||
* returns 1 on sucess, 0 on error
|
||||
*/
|
||||
|
||||
static inline int gscps2_writeb_output(struct gscps2port *ps2port, u8 data)
|
||||
{
|
||||
unsigned long flags;
|
||||
char *addr = ps2port->addr;
|
||||
|
||||
if (!wait_TBE(addr)) {
|
||||
printk(KERN_DEBUG PFX "timeout - could not write byte %#x\n", data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE)
|
||||
/* wait */;
|
||||
|
||||
spin_lock_irqsave(&ps2port->lock, flags);
|
||||
writeb(data, addr+GSC_XMTDATA);
|
||||
spin_unlock_irqrestore(&ps2port->lock, flags);
|
||||
|
||||
/* this is ugly, but due to timing of the port it seems to be necessary. */
|
||||
mdelay(6);
|
||||
|
||||
/* make sure any received data is returned as fast as possible */
|
||||
/* this is important e.g. when we set the LEDs on the keyboard */
|
||||
gscps2_interrupt(0, NULL);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gscps2_enable() - enables or disables the port
|
||||
*/
|
||||
|
||||
static void gscps2_enable(struct gscps2port *ps2port, int enable)
|
||||
{
|
||||
unsigned long flags;
|
||||
u8 data;
|
||||
|
||||
/* now enable/disable the port */
|
||||
spin_lock_irqsave(&ps2port->lock, flags);
|
||||
gscps2_flush(ps2port);
|
||||
data = gscps2_readb_control(ps2port->addr);
|
||||
if (enable)
|
||||
data |= GSC_CTRL_ENBL;
|
||||
else
|
||||
data &= ~GSC_CTRL_ENBL;
|
||||
gscps2_writeb_control(data, ps2port->addr);
|
||||
spin_unlock_irqrestore(&ps2port->lock, flags);
|
||||
wait_TBE(ps2port->addr);
|
||||
gscps2_flush(ps2port);
|
||||
}
|
||||
|
||||
/*
|
||||
* gscps2_reset() - resets the PS/2 port
|
||||
*/
|
||||
|
||||
static void gscps2_reset(struct gscps2port *ps2port)
|
||||
{
|
||||
char *addr = ps2port->addr;
|
||||
unsigned long flags;
|
||||
|
||||
/* reset the interface */
|
||||
spin_lock_irqsave(&ps2port->lock, flags);
|
||||
gscps2_flush(ps2port);
|
||||
writeb(0xff, addr+GSC_RESET);
|
||||
gscps2_flush(ps2port);
|
||||
spin_unlock_irqrestore(&ps2port->lock, flags);
|
||||
}
|
||||
|
||||
static LIST_HEAD(ps2port_list);
|
||||
|
||||
/**
|
||||
* gscps2_interrupt() - Interruption service routine
|
||||
*
|
||||
* This function reads received PS/2 bytes and processes them on
|
||||
* all interfaces.
|
||||
* The problematic part here is, that the keyboard and mouse PS/2 port
|
||||
* share the same interrupt and it's not possible to send data if any
|
||||
* one of them holds input data. To solve this problem we try to receive
|
||||
* the data as fast as possible and handle the reporting to the upper layer
|
||||
* later.
|
||||
*/
|
||||
|
||||
static irqreturn_t gscps2_interrupt(int irq, void *dev)
|
||||
{
|
||||
struct gscps2port *ps2port;
|
||||
|
||||
list_for_each_entry(ps2port, &ps2port_list, node) {
|
||||
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&ps2port->lock, flags);
|
||||
|
||||
while ( (ps2port->buffer[ps2port->append].str =
|
||||
gscps2_readb_status(ps2port->addr)) & GSC_STAT_RBNE ) {
|
||||
ps2port->buffer[ps2port->append].data =
|
||||
gscps2_readb_input(ps2port->addr);
|
||||
ps2port->append = ((ps2port->append+1) & BUFFER_SIZE);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&ps2port->lock, flags);
|
||||
|
||||
} /* list_for_each_entry */
|
||||
|
||||
/* all data was read from the ports - now report the data to upper layer */
|
||||
|
||||
list_for_each_entry(ps2port, &ps2port_list, node) {
|
||||
|
||||
while (ps2port->act != ps2port->append) {
|
||||
|
||||
unsigned int rxflags;
|
||||
u8 data, status;
|
||||
|
||||
/* Did new data arrived while we read existing data ?
|
||||
If yes, exit now and let the new irq handler start over again */
|
||||
if (gscps2_readb_status(ps2port->addr) & GSC_STAT_CMPINTR)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
status = ps2port->buffer[ps2port->act].str;
|
||||
data = ps2port->buffer[ps2port->act].data;
|
||||
|
||||
ps2port->act = ((ps2port->act+1) & BUFFER_SIZE);
|
||||
rxflags = ((status & GSC_STAT_TERR) ? SERIO_TIMEOUT : 0 ) |
|
||||
((status & GSC_STAT_PERR) ? SERIO_PARITY : 0 );
|
||||
|
||||
serio_interrupt(ps2port->port, data, rxflags);
|
||||
|
||||
} /* while() */
|
||||
|
||||
} /* list_for_each_entry */
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gscps2_write() - send a byte out through the aux interface.
|
||||
*/
|
||||
|
||||
static int gscps2_write(struct serio *port, unsigned char data)
|
||||
{
|
||||
struct gscps2port *ps2port = port->port_data;
|
||||
|
||||
if (!gscps2_writeb_output(ps2port, data)) {
|
||||
printk(KERN_DEBUG PFX "sending byte %#x failed.\n", data);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* gscps2_open() is called when a port is opened by the higher layer.
|
||||
* It resets and enables the port.
|
||||
*/
|
||||
|
||||
static int gscps2_open(struct serio *port)
|
||||
{
|
||||
struct gscps2port *ps2port = port->port_data;
|
||||
|
||||
gscps2_reset(ps2port);
|
||||
|
||||
/* enable it */
|
||||
gscps2_enable(ps2port, ENABLE);
|
||||
|
||||
gscps2_interrupt(0, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* gscps2_close() disables the port
|
||||
*/
|
||||
|
||||
static void gscps2_close(struct serio *port)
|
||||
{
|
||||
struct gscps2port *ps2port = port->port_data;
|
||||
gscps2_enable(ps2port, DISABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* gscps2_probe() - Probes PS2 devices
|
||||
* @return: success/error report
|
||||
*/
|
||||
|
||||
static int __init gscps2_probe(struct parisc_device *dev)
|
||||
{
|
||||
struct gscps2port *ps2port;
|
||||
struct serio *serio;
|
||||
unsigned long hpa = dev->hpa.start;
|
||||
int ret;
|
||||
|
||||
if (!dev->irq)
|
||||
return -ENODEV;
|
||||
|
||||
/* Offset for DINO PS/2. Works with LASI even */
|
||||
if (dev->id.sversion == 0x96)
|
||||
hpa += GSC_DINO_OFFSET;
|
||||
|
||||
ps2port = kmalloc(sizeof(struct gscps2port), GFP_KERNEL);
|
||||
serio = kmalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!ps2port || !serio) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_nomem;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&dev->dev, ps2port);
|
||||
|
||||
memset(ps2port, 0, sizeof(struct gscps2port));
|
||||
memset(serio, 0, sizeof(struct serio));
|
||||
ps2port->port = serio;
|
||||
ps2port->padev = dev;
|
||||
ps2port->addr = ioremap_nocache(hpa, GSC_STATUS + 4);
|
||||
spin_lock_init(&ps2port->lock);
|
||||
|
||||
gscps2_reset(ps2port);
|
||||
ps2port->id = readb(ps2port->addr + GSC_ID) & 0x0f;
|
||||
|
||||
snprintf(serio->name, sizeof(serio->name), "GSC PS/2 %s",
|
||||
(ps2port->id == GSC_ID_KEYBOARD) ? "keyboard" : "mouse");
|
||||
strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys));
|
||||
serio->id.type = SERIO_8042;
|
||||
serio->write = gscps2_write;
|
||||
serio->open = gscps2_open;
|
||||
serio->close = gscps2_close;
|
||||
serio->port_data = ps2port;
|
||||
serio->dev.parent = &dev->dev;
|
||||
|
||||
ret = -EBUSY;
|
||||
if (request_irq(dev->irq, gscps2_interrupt, IRQF_SHARED, ps2port->port->name, ps2port))
|
||||
goto fail_miserably;
|
||||
|
||||
if (ps2port->id != GSC_ID_KEYBOARD && ps2port->id != GSC_ID_MOUSE) {
|
||||
printk(KERN_WARNING PFX "Unsupported PS/2 port at 0x%08lx (id=%d) ignored\n",
|
||||
hpa, ps2port->id);
|
||||
ret = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (!request_mem_region(hpa, GSC_STATUS + 4, ps2port->port.name))
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
printk(KERN_INFO "serio: %s port at 0x%p irq %d @ %s\n",
|
||||
ps2port->port->name,
|
||||
ps2port->addr,
|
||||
ps2port->padev->irq,
|
||||
ps2port->port->phys);
|
||||
|
||||
serio_register_port(ps2port->port);
|
||||
|
||||
list_add_tail(&ps2port->node, &ps2port_list);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
free_irq(dev->irq, ps2port);
|
||||
|
||||
fail_miserably:
|
||||
iounmap(ps2port->addr);
|
||||
release_mem_region(dev->hpa.start, GSC_STATUS + 4);
|
||||
|
||||
fail_nomem:
|
||||
kfree(ps2port);
|
||||
kfree(serio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* gscps2_remove() - Removes PS2 devices
|
||||
* @return: success/error report
|
||||
*/
|
||||
|
||||
static int __devexit gscps2_remove(struct parisc_device *dev)
|
||||
{
|
||||
struct gscps2port *ps2port = dev_get_drvdata(&dev->dev);
|
||||
|
||||
serio_unregister_port(ps2port->port);
|
||||
free_irq(dev->irq, ps2port);
|
||||
gscps2_flush(ps2port);
|
||||
list_del(&ps2port->node);
|
||||
iounmap(ps2port->addr);
|
||||
#if 0
|
||||
release_mem_region(dev->hpa, GSC_STATUS + 4);
|
||||
#endif
|
||||
dev_set_drvdata(&dev->dev, NULL);
|
||||
kfree(ps2port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct parisc_device_id gscps2_device_tbl[] = {
|
||||
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00084 }, /* LASI PS/2 */
|
||||
#ifdef DINO_TESTED
|
||||
{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00096 }, /* DINO PS/2 */
|
||||
#endif
|
||||
{ 0, } /* 0 terminated list */
|
||||
};
|
||||
|
||||
static struct parisc_driver parisc_ps2_driver = {
|
||||
.name = "gsc_ps2",
|
||||
.id_table = gscps2_device_tbl,
|
||||
.probe = gscps2_probe,
|
||||
.remove = gscps2_remove,
|
||||
};
|
||||
|
||||
static int __init gscps2_init(void)
|
||||
{
|
||||
register_parisc_driver(&parisc_ps2_driver);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit gscps2_exit(void)
|
||||
{
|
||||
unregister_parisc_driver(&parisc_ps2_driver);
|
||||
}
|
||||
|
||||
|
||||
module_init(gscps2_init);
|
||||
module_exit(gscps2_exit);
|
||||
|
||||
955
drivers/input/serio/hil_mlc.c
Normal file
955
drivers/input/serio/hil_mlc.c
Normal file
@@ -0,0 +1,955 @@
|
||||
/*
|
||||
* HIL MLC state machine and serio interface driver
|
||||
*
|
||||
* Copyright (c) 2001 Brian S. Julin
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions, and the following disclaimer,
|
||||
* without modification.
|
||||
* 2. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* Alternatively, this software may be distributed under the terms of the
|
||||
* GNU General Public License ("GPL").
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
*
|
||||
* References:
|
||||
* HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A
|
||||
*
|
||||
*
|
||||
* Driver theory of operation:
|
||||
*
|
||||
* Some access methods and an ISR is defined by the sub-driver
|
||||
* (e.g. hp_sdc_mlc.c). These methods are expected to provide a
|
||||
* few bits of logic in addition to raw access to the HIL MLC,
|
||||
* specifically, the ISR, which is entirely registered by the
|
||||
* sub-driver and invoked directly, must check for record
|
||||
* termination or packet match, at which point a semaphore must
|
||||
* be cleared and then the hil_mlcs_tasklet must be scheduled.
|
||||
*
|
||||
* The hil_mlcs_tasklet processes the state machine for all MLCs
|
||||
* each time it runs, checking each MLC's progress at the current
|
||||
* node in the state machine, and moving the MLC to subsequent nodes
|
||||
* in the state machine when appropriate. It will reschedule
|
||||
* itself if output is pending. (This rescheduling should be replaced
|
||||
* at some point with a sub-driver-specific mechanism.)
|
||||
*
|
||||
* A timer task prods the tasklet once per second to prevent
|
||||
* hangups when attached devices do not return expected data
|
||||
* and to initiate probes of the loop for new devices.
|
||||
*/
|
||||
|
||||
#include <linux/hil_mlc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
|
||||
MODULE_DESCRIPTION("HIL MLC serio");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
|
||||
EXPORT_SYMBOL(hil_mlc_register);
|
||||
EXPORT_SYMBOL(hil_mlc_unregister);
|
||||
|
||||
#define PREFIX "HIL MLC: "
|
||||
|
||||
static LIST_HEAD(hil_mlcs);
|
||||
static DEFINE_RWLOCK(hil_mlcs_lock);
|
||||
static struct timer_list hil_mlcs_kicker;
|
||||
static int hil_mlcs_probe;
|
||||
|
||||
static void hil_mlcs_process(unsigned long unused);
|
||||
DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0);
|
||||
|
||||
|
||||
/* #define HIL_MLC_DEBUG */
|
||||
|
||||
/********************** Device info/instance management **********************/
|
||||
|
||||
static void hil_mlc_clear_di_map (hil_mlc *mlc, int val) {
|
||||
int j;
|
||||
for (j = val; j < 7 ; j++) {
|
||||
mlc->di_map[j] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void hil_mlc_clear_di_scratch (hil_mlc *mlc) {
|
||||
memset(&(mlc->di_scratch), 0, sizeof(mlc->di_scratch));
|
||||
}
|
||||
|
||||
static void hil_mlc_copy_di_scratch (hil_mlc *mlc, int idx) {
|
||||
memcpy(&(mlc->di[idx]), &(mlc->di_scratch), sizeof(mlc->di_scratch));
|
||||
}
|
||||
|
||||
static int hil_mlc_match_di_scratch (hil_mlc *mlc) {
|
||||
int idx;
|
||||
|
||||
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
|
||||
int j, found;
|
||||
|
||||
/* In-use slots are not eligible. */
|
||||
found = 0;
|
||||
for (j = 0; j < 7 ; j++) {
|
||||
if (mlc->di_map[j] == idx) found++;
|
||||
}
|
||||
if (found) continue;
|
||||
if (!memcmp(mlc->di + idx,
|
||||
&(mlc->di_scratch),
|
||||
sizeof(mlc->di_scratch))) break;
|
||||
}
|
||||
return((idx >= HIL_MLC_DEVMEM) ? -1 : idx);
|
||||
}
|
||||
|
||||
static int hil_mlc_find_free_di(hil_mlc *mlc) {
|
||||
int idx;
|
||||
/* TODO: Pick all-zero slots first, failing that,
|
||||
* randomize the slot picked among those eligible.
|
||||
*/
|
||||
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
|
||||
int j, found;
|
||||
found = 0;
|
||||
for (j = 0; j < 7 ; j++) {
|
||||
if (mlc->di_map[j] == idx) found++;
|
||||
}
|
||||
if (!found) break;
|
||||
}
|
||||
return(idx); /* Note: It is guaranteed at least one above will match */
|
||||
}
|
||||
|
||||
static inline void hil_mlc_clean_serio_map(hil_mlc *mlc) {
|
||||
int idx;
|
||||
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
|
||||
int j, found;
|
||||
found = 0;
|
||||
for (j = 0; j < 7 ; j++) {
|
||||
if (mlc->di_map[j] == idx) found++;
|
||||
}
|
||||
if (!found) mlc->serio_map[idx].di_revmap = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void hil_mlc_send_polls(hil_mlc *mlc) {
|
||||
int did, i, cnt;
|
||||
struct serio *serio;
|
||||
struct serio_driver *drv;
|
||||
|
||||
i = cnt = 0;
|
||||
did = (mlc->ipacket[0] & HIL_PKT_ADDR_MASK) >> 8;
|
||||
serio = did ? mlc->serio[mlc->di_map[did - 1]] : NULL;
|
||||
drv = (serio != NULL) ? serio->drv : NULL;
|
||||
|
||||
while (mlc->icount < 15 - i) {
|
||||
hil_packet p;
|
||||
p = mlc->ipacket[i];
|
||||
if (did != (p & HIL_PKT_ADDR_MASK) >> 8) {
|
||||
if (drv == NULL || drv->interrupt == NULL) goto skip;
|
||||
|
||||
drv->interrupt(serio, 0, 0);
|
||||
drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
|
||||
drv->interrupt(serio, HIL_PKT_CMD >> 8, 0);
|
||||
drv->interrupt(serio, HIL_CMD_POL + cnt, 0);
|
||||
skip:
|
||||
did = (p & HIL_PKT_ADDR_MASK) >> 8;
|
||||
serio = did ? mlc->serio[mlc->di_map[did-1]] : NULL;
|
||||
drv = (serio != NULL) ? serio->drv : NULL;
|
||||
cnt = 0;
|
||||
}
|
||||
cnt++; i++;
|
||||
if (drv == NULL || drv->interrupt == NULL) continue;
|
||||
drv->interrupt(serio, (p >> 24), 0);
|
||||
drv->interrupt(serio, (p >> 16) & 0xff, 0);
|
||||
drv->interrupt(serio, (p >> 8) & ~HIL_PKT_ADDR_MASK, 0);
|
||||
drv->interrupt(serio, p & 0xff, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** State engine *********************************/
|
||||
|
||||
#define HILSEN_SCHED 0x000100 /* Schedule the tasklet */
|
||||
#define HILSEN_BREAK 0x000200 /* Wait until next pass */
|
||||
#define HILSEN_UP 0x000400 /* relative node#, decrement */
|
||||
#define HILSEN_DOWN 0x000800 /* relative node#, increment */
|
||||
#define HILSEN_FOLLOW 0x001000 /* use retval as next node# */
|
||||
|
||||
#define HILSEN_MASK 0x0000ff
|
||||
#define HILSEN_START 0
|
||||
#define HILSEN_RESTART 1
|
||||
#define HILSEN_DHR 9
|
||||
#define HILSEN_DHR2 10
|
||||
#define HILSEN_IFC 14
|
||||
#define HILSEN_HEAL0 16
|
||||
#define HILSEN_HEAL 18
|
||||
#define HILSEN_ACF 21
|
||||
#define HILSEN_ACF2 22
|
||||
#define HILSEN_DISC0 25
|
||||
#define HILSEN_DISC 27
|
||||
#define HILSEN_MATCH 40
|
||||
#define HILSEN_OPERATE 41
|
||||
#define HILSEN_PROBE 44
|
||||
#define HILSEN_DSR 52
|
||||
#define HILSEN_REPOLL 55
|
||||
#define HILSEN_IFCACF 58
|
||||
#define HILSEN_END 60
|
||||
|
||||
#define HILSEN_NEXT (HILSEN_DOWN | 1)
|
||||
#define HILSEN_SAME (HILSEN_DOWN | 0)
|
||||
#define HILSEN_LAST (HILSEN_UP | 1)
|
||||
|
||||
#define HILSEN_DOZE (HILSEN_SAME | HILSEN_SCHED | HILSEN_BREAK)
|
||||
#define HILSEN_SLEEP (HILSEN_SAME | HILSEN_BREAK)
|
||||
|
||||
static int hilse_match(hil_mlc *mlc, int unused) {
|
||||
int rc;
|
||||
rc = hil_mlc_match_di_scratch(mlc);
|
||||
if (rc == -1) {
|
||||
rc = hil_mlc_find_free_di(mlc);
|
||||
if (rc == -1) goto err;
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
printk(KERN_DEBUG PREFIX "new in slot %i\n", rc);
|
||||
#endif
|
||||
hil_mlc_copy_di_scratch(mlc, rc);
|
||||
mlc->di_map[mlc->ddi] = rc;
|
||||
mlc->serio_map[rc].di_revmap = mlc->ddi;
|
||||
hil_mlc_clean_serio_map(mlc);
|
||||
serio_rescan(mlc->serio[rc]);
|
||||
return -1;
|
||||
}
|
||||
mlc->di_map[mlc->ddi] = rc;
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
printk(KERN_DEBUG PREFIX "same in slot %i\n", rc);
|
||||
#endif
|
||||
mlc->serio_map[rc].di_revmap = mlc->ddi;
|
||||
hil_mlc_clean_serio_map(mlc);
|
||||
return 0;
|
||||
err:
|
||||
printk(KERN_ERR PREFIX "Residual device slots exhausted, close some serios!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* An LCV used to prevent runaway loops, forces 5 second sleep when reset. */
|
||||
static int hilse_init_lcv(hil_mlc *mlc, int unused) {
|
||||
struct timeval tv;
|
||||
|
||||
do_gettimeofday(&tv);
|
||||
|
||||
if(mlc->lcv == 0) goto restart; /* First init, no need to dally */
|
||||
if(tv.tv_sec - mlc->lcv_tv.tv_sec < 5) return -1;
|
||||
restart:
|
||||
mlc->lcv_tv = tv;
|
||||
mlc->lcv = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_inc_lcv(hil_mlc *mlc, int lim) {
|
||||
if (mlc->lcv++ >= lim) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int hilse_set_lcv(hil_mlc *mlc, int val) {
|
||||
mlc->lcv = val;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Management of the discovered device index (zero based, -1 means no devs) */
|
||||
static int hilse_set_ddi(hil_mlc *mlc, int val) {
|
||||
mlc->ddi = val;
|
||||
hil_mlc_clear_di_map(mlc, val + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_dec_ddi(hil_mlc *mlc, int unused) {
|
||||
mlc->ddi--;
|
||||
if (mlc->ddi <= -1) {
|
||||
mlc->ddi = -1;
|
||||
hil_mlc_clear_di_map(mlc, 0);
|
||||
return -1;
|
||||
}
|
||||
hil_mlc_clear_di_map(mlc, mlc->ddi + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_inc_ddi(hil_mlc *mlc, int unused) {
|
||||
if (mlc->ddi >= 6) {
|
||||
BUG();
|
||||
return -1;
|
||||
}
|
||||
mlc->ddi++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_take_idd(hil_mlc *mlc, int unused) {
|
||||
int i;
|
||||
|
||||
/* Help the state engine:
|
||||
* Is this a real IDD response or just an echo?
|
||||
*
|
||||
* Real IDD response does not start with a command.
|
||||
*/
|
||||
if (mlc->ipacket[0] & HIL_PKT_CMD) goto bail;
|
||||
/* Should have the command echoed further down. */
|
||||
for (i = 1; i < 16; i++) {
|
||||
if (((mlc->ipacket[i] & HIL_PKT_ADDR_MASK) ==
|
||||
(mlc->ipacket[0] & HIL_PKT_ADDR_MASK)) &&
|
||||
(mlc->ipacket[i] & HIL_PKT_CMD) &&
|
||||
((mlc->ipacket[i] & HIL_PKT_DATA_MASK) == HIL_CMD_IDD))
|
||||
break;
|
||||
}
|
||||
if (i > 15) goto bail;
|
||||
/* And the rest of the packets should still be clear. */
|
||||
while (++i < 16) {
|
||||
if (mlc->ipacket[i]) break;
|
||||
}
|
||||
if (i < 16) goto bail;
|
||||
for (i = 0; i < 16; i++) {
|
||||
mlc->di_scratch.idd[i] =
|
||||
mlc->ipacket[i] & HIL_PKT_DATA_MASK;
|
||||
}
|
||||
/* Next step is to see if RSC supported */
|
||||
if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_RSC)
|
||||
return HILSEN_NEXT;
|
||||
if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD)
|
||||
return HILSEN_DOWN | 4;
|
||||
return 0;
|
||||
bail:
|
||||
mlc->ddi--;
|
||||
return -1; /* This should send us off to ACF */
|
||||
}
|
||||
|
||||
static int hilse_take_rsc(hil_mlc *mlc, int unused) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
mlc->di_scratch.rsc[i] =
|
||||
mlc->ipacket[i] & HIL_PKT_DATA_MASK;
|
||||
}
|
||||
/* Next step is to see if EXD supported (IDD has already been read) */
|
||||
if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD)
|
||||
return HILSEN_NEXT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_take_exd(hil_mlc *mlc, int unused) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
mlc->di_scratch.exd[i] =
|
||||
mlc->ipacket[i] & HIL_PKT_DATA_MASK;
|
||||
}
|
||||
/* Next step is to see if RNM supported. */
|
||||
if (mlc->di_scratch.exd[0] & HIL_EXD_HEADER_RNM)
|
||||
return HILSEN_NEXT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_take_rnm(hil_mlc *mlc, int unused) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
mlc->di_scratch.rnm[i] =
|
||||
mlc->ipacket[i] & HIL_PKT_DATA_MASK;
|
||||
}
|
||||
do {
|
||||
char nam[17];
|
||||
snprintf(nam, 16, "%s", mlc->di_scratch.rnm);
|
||||
nam[16] = '\0';
|
||||
printk(KERN_INFO PREFIX "Device name gotten: %s\n", nam);
|
||||
} while (0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hilse_operate(hil_mlc *mlc, int repoll) {
|
||||
|
||||
if (mlc->opercnt == 0) hil_mlcs_probe = 0;
|
||||
mlc->opercnt = 1;
|
||||
|
||||
hil_mlc_send_polls(mlc);
|
||||
|
||||
if (!hil_mlcs_probe) return 0;
|
||||
hil_mlcs_probe = 0;
|
||||
mlc->opercnt = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define FUNC(funct, funct_arg, zero_rc, neg_rc, pos_rc) \
|
||||
{ HILSE_FUNC, { .func = funct }, funct_arg, zero_rc, neg_rc, pos_rc },
|
||||
#define OUT(pack) \
|
||||
{ HILSE_OUT, { .packet = pack }, 0, HILSEN_NEXT, HILSEN_DOZE, 0 },
|
||||
#define CTS \
|
||||
{ HILSE_CTS, { .packet = 0 }, 0, HILSEN_NEXT | HILSEN_SCHED | HILSEN_BREAK, HILSEN_DOZE, 0 },
|
||||
#define EXPECT(comp, to, got, got_wrong, timed_out) \
|
||||
{ HILSE_EXPECT, { .packet = comp }, to, got, got_wrong, timed_out },
|
||||
#define EXPECT_LAST(comp, to, got, got_wrong, timed_out) \
|
||||
{ HILSE_EXPECT_LAST, { .packet = comp }, to, got, got_wrong, timed_out },
|
||||
#define EXPECT_DISC(comp, to, got, got_wrong, timed_out) \
|
||||
{ HILSE_EXPECT_DISC, { .packet = comp }, to, got, got_wrong, timed_out },
|
||||
#define IN(to, got, got_error, timed_out) \
|
||||
{ HILSE_IN, { .packet = 0 }, to, got, got_error, timed_out },
|
||||
#define OUT_DISC(pack) \
|
||||
{ HILSE_OUT_DISC, { .packet = pack }, 0, 0, 0, 0 },
|
||||
#define OUT_LAST(pack) \
|
||||
{ HILSE_OUT_LAST, { .packet = pack }, 0, 0, 0, 0 },
|
||||
|
||||
struct hilse_node hil_mlc_se[HILSEN_END] = {
|
||||
|
||||
/* 0 HILSEN_START */
|
||||
FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0)
|
||||
|
||||
/* 1 HILSEN_RESTART */
|
||||
FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
|
||||
OUT(HIL_CTRL_ONLY) /* Disable APE */
|
||||
CTS
|
||||
|
||||
#define TEST_PACKET(x) \
|
||||
(HIL_PKT_CMD | (x << HIL_PKT_ADDR_SHIFT) | x << 4 | x)
|
||||
|
||||
OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0x5))
|
||||
EXPECT(HIL_ERR_INT | TEST_PACKET(0x5),
|
||||
2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART)
|
||||
OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0xa))
|
||||
EXPECT(HIL_ERR_INT | TEST_PACKET(0xa),
|
||||
2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART)
|
||||
OUT(HIL_CTRL_ONLY | 0) /* Disable test mode */
|
||||
|
||||
/* 9 HILSEN_DHR */
|
||||
FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0)
|
||||
|
||||
/* 10 HILSEN_DHR2 */
|
||||
FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
|
||||
FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0)
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_DHR)
|
||||
IN(300000, HILSEN_DHR2, HILSEN_DHR2, HILSEN_NEXT)
|
||||
|
||||
/* 14 HILSEN_IFC */
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_IFC)
|
||||
EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT,
|
||||
20000, HILSEN_DISC, HILSEN_DHR2, HILSEN_NEXT )
|
||||
|
||||
/* If devices are there, they weren't in PUP or other loopback mode.
|
||||
* We're more concerned at this point with restoring operation
|
||||
* to devices than discovering new ones, so we try to salvage
|
||||
* the loop configuration by closing off the loop.
|
||||
*/
|
||||
|
||||
/* 16 HILSEN_HEAL0 */
|
||||
FUNC(hilse_dec_ddi, 0, HILSEN_NEXT, HILSEN_ACF, 0)
|
||||
FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, 0, 0)
|
||||
|
||||
/* 18 HILSEN_HEAL */
|
||||
OUT_LAST(HIL_CMD_ELB)
|
||||
EXPECT_LAST(HIL_CMD_ELB | HIL_ERR_INT,
|
||||
20000, HILSEN_REPOLL, HILSEN_DSR, HILSEN_NEXT)
|
||||
FUNC(hilse_dec_ddi, 0, HILSEN_HEAL, HILSEN_NEXT, 0)
|
||||
|
||||
/* 21 HILSEN_ACF */
|
||||
FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_DOZE, 0)
|
||||
|
||||
/* 22 HILSEN_ACF2 */
|
||||
FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1)
|
||||
IN(20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
|
||||
|
||||
/* 25 HILSEN_DISC0 */
|
||||
OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB)
|
||||
EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_ELB | HIL_ERR_INT,
|
||||
20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
|
||||
|
||||
/* Only enter here if response just received */
|
||||
/* 27 HILSEN_DISC */
|
||||
OUT_DISC(HIL_PKT_CMD | HIL_CMD_IDD)
|
||||
EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_IDD | HIL_ERR_INT,
|
||||
20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_START)
|
||||
FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, HILSEN_START, 0)
|
||||
FUNC(hilse_take_idd, 0, HILSEN_MATCH, HILSEN_IFCACF, HILSEN_FOLLOW)
|
||||
OUT_LAST(HIL_PKT_CMD | HIL_CMD_RSC)
|
||||
EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RSC | HIL_ERR_INT,
|
||||
30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
|
||||
FUNC(hilse_take_rsc, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW)
|
||||
OUT_LAST(HIL_PKT_CMD | HIL_CMD_EXD)
|
||||
EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_EXD | HIL_ERR_INT,
|
||||
30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
|
||||
FUNC(hilse_take_exd, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW)
|
||||
OUT_LAST(HIL_PKT_CMD | HIL_CMD_RNM)
|
||||
EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RNM | HIL_ERR_INT,
|
||||
30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
|
||||
FUNC(hilse_take_rnm, 0, HILSEN_MATCH, 0, 0)
|
||||
|
||||
/* 40 HILSEN_MATCH */
|
||||
FUNC(hilse_match, 0, HILSEN_NEXT, HILSEN_NEXT, /* TODO */ 0)
|
||||
|
||||
/* 41 HILSEN_OPERATE */
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_POL)
|
||||
EXPECT(HIL_PKT_CMD | HIL_CMD_POL | HIL_ERR_INT,
|
||||
20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
|
||||
FUNC(hilse_operate, 0, HILSEN_OPERATE, HILSEN_IFC, HILSEN_NEXT)
|
||||
|
||||
/* 44 HILSEN_PROBE */
|
||||
OUT_LAST(HIL_PKT_CMD | HIL_CMD_EPT)
|
||||
IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT)
|
||||
OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB)
|
||||
IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT)
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1)
|
||||
IN(10000, HILSEN_DISC0, HILSEN_DSR, HILSEN_NEXT)
|
||||
OUT_LAST(HIL_PKT_CMD | HIL_CMD_ELB)
|
||||
IN(10000, HILSEN_OPERATE, HILSEN_DSR, HILSEN_DSR)
|
||||
|
||||
/* 52 HILSEN_DSR */
|
||||
FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0)
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_DSR)
|
||||
IN(20000, HILSEN_DHR, HILSEN_DHR, HILSEN_IFC)
|
||||
|
||||
/* 55 HILSEN_REPOLL */
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_RPL)
|
||||
EXPECT(HIL_PKT_CMD | HIL_CMD_RPL | HIL_ERR_INT,
|
||||
20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
|
||||
FUNC(hilse_operate, 1, HILSEN_OPERATE, HILSEN_IFC, HILSEN_PROBE)
|
||||
|
||||
/* 58 HILSEN_IFCACF */
|
||||
OUT(HIL_PKT_CMD | HIL_CMD_IFC)
|
||||
EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT,
|
||||
20000, HILSEN_ACF2, HILSEN_DHR2, HILSEN_HEAL)
|
||||
|
||||
/* 60 HILSEN_END */
|
||||
};
|
||||
|
||||
static inline void hilse_setup_input(hil_mlc *mlc, struct hilse_node *node) {
|
||||
|
||||
switch (node->act) {
|
||||
case HILSE_EXPECT_DISC:
|
||||
mlc->imatch = node->object.packet;
|
||||
mlc->imatch |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT);
|
||||
break;
|
||||
case HILSE_EXPECT_LAST:
|
||||
mlc->imatch = node->object.packet;
|
||||
mlc->imatch |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT);
|
||||
break;
|
||||
case HILSE_EXPECT:
|
||||
mlc->imatch = node->object.packet;
|
||||
break;
|
||||
case HILSE_IN:
|
||||
mlc->imatch = 0;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
mlc->istarted = 1;
|
||||
mlc->intimeout = node->arg;
|
||||
do_gettimeofday(&(mlc->instart));
|
||||
mlc->icount = 15;
|
||||
memset(mlc->ipacket, 0, 16 * sizeof(hil_packet));
|
||||
BUG_ON(down_trylock(&(mlc->isem)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
static int doze = 0;
|
||||
static int seidx; /* For debug */
|
||||
static int kick = 1;
|
||||
#endif
|
||||
|
||||
static int hilse_donode (hil_mlc *mlc) {
|
||||
struct hilse_node *node;
|
||||
int nextidx = 0;
|
||||
int sched_long = 0;
|
||||
unsigned long flags;
|
||||
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
if (mlc->seidx && (mlc->seidx != seidx) && mlc->seidx != 41 && mlc->seidx != 42 && mlc->seidx != 43) {
|
||||
printk(KERN_DEBUG PREFIX "z%i \n%s {%i}", doze, kick ? "K" : "", mlc->seidx);
|
||||
doze = 0;
|
||||
}
|
||||
kick = 0;
|
||||
|
||||
seidx = mlc->seidx;
|
||||
#endif
|
||||
node = hil_mlc_se + mlc->seidx;
|
||||
|
||||
switch (node->act) {
|
||||
int rc;
|
||||
hil_packet pack;
|
||||
|
||||
case HILSE_FUNC:
|
||||
if (node->object.func == NULL) break;
|
||||
rc = node->object.func(mlc, node->arg);
|
||||
nextidx = (rc > 0) ? node->ugly :
|
||||
((rc < 0) ? node->bad : node->good);
|
||||
if (nextidx == HILSEN_FOLLOW) nextidx = rc;
|
||||
break;
|
||||
case HILSE_EXPECT_LAST:
|
||||
case HILSE_EXPECT_DISC:
|
||||
case HILSE_EXPECT:
|
||||
case HILSE_IN:
|
||||
/* Already set up from previous HILSE_OUT_* */
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
rc = mlc->in(mlc, node->arg);
|
||||
if (rc == 2) {
|
||||
nextidx = HILSEN_DOZE;
|
||||
sched_long = 1;
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
break;
|
||||
}
|
||||
if (rc == 1) nextidx = node->ugly;
|
||||
else if (rc == 0) nextidx = node->good;
|
||||
else nextidx = node->bad;
|
||||
mlc->istarted = 0;
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
break;
|
||||
case HILSE_OUT_LAST:
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
pack = node->object.packet;
|
||||
pack |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT);
|
||||
goto out;
|
||||
case HILSE_OUT_DISC:
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
pack = node->object.packet;
|
||||
pack |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT);
|
||||
goto out;
|
||||
case HILSE_OUT:
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
pack = node->object.packet;
|
||||
out:
|
||||
if (mlc->istarted) goto out2;
|
||||
/* Prepare to receive input */
|
||||
if ((node + 1)->act & HILSE_IN)
|
||||
hilse_setup_input(mlc, node + 1);
|
||||
|
||||
out2:
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
|
||||
if (down_trylock(&mlc->osem)) {
|
||||
nextidx = HILSEN_DOZE;
|
||||
break;
|
||||
}
|
||||
up(&mlc->osem);
|
||||
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
if (!(mlc->ostarted)) {
|
||||
mlc->ostarted = 1;
|
||||
mlc->opacket = pack;
|
||||
mlc->out(mlc);
|
||||
nextidx = HILSEN_DOZE;
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
break;
|
||||
}
|
||||
mlc->ostarted = 0;
|
||||
do_gettimeofday(&(mlc->instart));
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
nextidx = HILSEN_NEXT;
|
||||
break;
|
||||
case HILSE_CTS:
|
||||
nextidx = mlc->cts(mlc) ? node->bad : node->good;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
nextidx = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
if (nextidx == HILSEN_DOZE) doze++;
|
||||
#endif
|
||||
|
||||
while (nextidx & HILSEN_SCHED) {
|
||||
struct timeval tv;
|
||||
|
||||
if (!sched_long) goto sched;
|
||||
|
||||
do_gettimeofday(&tv);
|
||||
tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec);
|
||||
tv.tv_usec -= mlc->instart.tv_usec;
|
||||
if (tv.tv_usec >= mlc->intimeout) goto sched;
|
||||
tv.tv_usec = (mlc->intimeout - tv.tv_usec) * HZ / 1000000;
|
||||
if (!tv.tv_usec) goto sched;
|
||||
mod_timer(&hil_mlcs_kicker, jiffies + tv.tv_usec);
|
||||
break;
|
||||
sched:
|
||||
tasklet_schedule(&hil_mlcs_tasklet);
|
||||
break;
|
||||
}
|
||||
if (nextidx & HILSEN_DOWN) mlc->seidx += nextidx & HILSEN_MASK;
|
||||
else if (nextidx & HILSEN_UP) mlc->seidx -= nextidx & HILSEN_MASK;
|
||||
else mlc->seidx = nextidx & HILSEN_MASK;
|
||||
|
||||
if (nextidx & HILSEN_BREAK) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************** tasklet context functions **************************/
|
||||
static void hil_mlcs_process(unsigned long unused) {
|
||||
struct list_head *tmp;
|
||||
|
||||
read_lock(&hil_mlcs_lock);
|
||||
list_for_each(tmp, &hil_mlcs) {
|
||||
struct hil_mlc *mlc = list_entry(tmp, hil_mlc, list);
|
||||
while (hilse_donode(mlc) == 0) {
|
||||
#ifdef HIL_MLC_DEBUG
|
||||
if (mlc->seidx != 41 &&
|
||||
mlc->seidx != 42 &&
|
||||
mlc->seidx != 43)
|
||||
printk(KERN_DEBUG PREFIX " + ");
|
||||
#endif
|
||||
};
|
||||
}
|
||||
read_unlock(&hil_mlcs_lock);
|
||||
}
|
||||
|
||||
/************************* Keepalive timer task *********************/
|
||||
|
||||
void hil_mlcs_timer (unsigned long data) {
|
||||
hil_mlcs_probe = 1;
|
||||
tasklet_schedule(&hil_mlcs_tasklet);
|
||||
/* Re-insert the periodic task. */
|
||||
if (!timer_pending(&hil_mlcs_kicker))
|
||||
mod_timer(&hil_mlcs_kicker, jiffies + HZ);
|
||||
}
|
||||
|
||||
/******************** user/kernel context functions **********************/
|
||||
|
||||
static int hil_mlc_serio_write(struct serio *serio, unsigned char c) {
|
||||
struct hil_mlc_serio_map *map;
|
||||
struct hil_mlc *mlc;
|
||||
struct serio_driver *drv;
|
||||
uint8_t *idx, *last;
|
||||
|
||||
map = serio->port_data;
|
||||
if (map == NULL) {
|
||||
BUG();
|
||||
return -EIO;
|
||||
}
|
||||
mlc = map->mlc;
|
||||
if (mlc == NULL) {
|
||||
BUG();
|
||||
return -EIO;
|
||||
}
|
||||
mlc->serio_opacket[map->didx] |=
|
||||
((hil_packet)c) << (8 * (3 - mlc->serio_oidx[map->didx]));
|
||||
|
||||
if (mlc->serio_oidx[map->didx] >= 3) {
|
||||
/* for now only commands */
|
||||
if (!(mlc->serio_opacket[map->didx] & HIL_PKT_CMD))
|
||||
return -EIO;
|
||||
switch (mlc->serio_opacket[map->didx] & HIL_PKT_DATA_MASK) {
|
||||
case HIL_CMD_IDD:
|
||||
idx = mlc->di[map->didx].idd;
|
||||
goto emu;
|
||||
case HIL_CMD_RSC:
|
||||
idx = mlc->di[map->didx].rsc;
|
||||
goto emu;
|
||||
case HIL_CMD_EXD:
|
||||
idx = mlc->di[map->didx].exd;
|
||||
goto emu;
|
||||
case HIL_CMD_RNM:
|
||||
idx = mlc->di[map->didx].rnm;
|
||||
goto emu;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mlc->serio_oidx[map->didx] = 0;
|
||||
mlc->serio_opacket[map->didx] = 0;
|
||||
}
|
||||
|
||||
mlc->serio_oidx[map->didx]++;
|
||||
return -EIO;
|
||||
emu:
|
||||
drv = serio->drv;
|
||||
if (drv == NULL) {
|
||||
BUG();
|
||||
return -EIO;
|
||||
}
|
||||
last = idx + 15;
|
||||
while ((last != idx) && (*last == 0)) last--;
|
||||
|
||||
while (idx != last) {
|
||||
drv->interrupt(serio, 0, 0);
|
||||
drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
|
||||
drv->interrupt(serio, 0, 0);
|
||||
drv->interrupt(serio, *idx, 0);
|
||||
idx++;
|
||||
}
|
||||
drv->interrupt(serio, 0, 0);
|
||||
drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
|
||||
drv->interrupt(serio, HIL_PKT_CMD >> 8, 0);
|
||||
drv->interrupt(serio, *idx, 0);
|
||||
|
||||
mlc->serio_oidx[map->didx] = 0;
|
||||
mlc->serio_opacket[map->didx] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hil_mlc_serio_open(struct serio *serio) {
|
||||
struct hil_mlc_serio_map *map;
|
||||
struct hil_mlc *mlc;
|
||||
|
||||
if (serio_get_drvdata(serio) != NULL)
|
||||
return -EBUSY;
|
||||
|
||||
map = serio->port_data;
|
||||
if (map == NULL) {
|
||||
BUG();
|
||||
return -ENODEV;
|
||||
}
|
||||
mlc = map->mlc;
|
||||
if (mlc == NULL) {
|
||||
BUG();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hil_mlc_serio_close(struct serio *serio) {
|
||||
struct hil_mlc_serio_map *map;
|
||||
struct hil_mlc *mlc;
|
||||
|
||||
map = serio->port_data;
|
||||
if (map == NULL) {
|
||||
BUG();
|
||||
return;
|
||||
}
|
||||
mlc = map->mlc;
|
||||
if (mlc == NULL) {
|
||||
BUG();
|
||||
return;
|
||||
}
|
||||
|
||||
serio_set_drvdata(serio, NULL);
|
||||
serio->drv = NULL;
|
||||
/* TODO wake up interruptable */
|
||||
}
|
||||
|
||||
static struct serio_device_id hil_mlc_serio_id = {
|
||||
.type = SERIO_HIL_MLC,
|
||||
.proto = SERIO_HIL,
|
||||
.extra = SERIO_ANY,
|
||||
.id = SERIO_ANY,
|
||||
};
|
||||
|
||||
int hil_mlc_register(hil_mlc *mlc) {
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
if (mlc == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mlc->istarted = 0;
|
||||
mlc->ostarted = 0;
|
||||
|
||||
rwlock_init(&mlc->lock);
|
||||
init_MUTEX(&(mlc->osem));
|
||||
|
||||
init_MUTEX(&(mlc->isem));
|
||||
mlc->icount = -1;
|
||||
mlc->imatch = 0;
|
||||
|
||||
mlc->opercnt = 0;
|
||||
|
||||
init_MUTEX_LOCKED(&(mlc->csem));
|
||||
|
||||
hil_mlc_clear_di_scratch(mlc);
|
||||
hil_mlc_clear_di_map(mlc, 0);
|
||||
for (i = 0; i < HIL_MLC_DEVMEM; i++) {
|
||||
struct serio *mlc_serio;
|
||||
hil_mlc_copy_di_scratch(mlc, i);
|
||||
mlc_serio = kzalloc(sizeof(*mlc_serio), GFP_KERNEL);
|
||||
mlc->serio[i] = mlc_serio;
|
||||
mlc_serio->id = hil_mlc_serio_id;
|
||||
mlc_serio->write = hil_mlc_serio_write;
|
||||
mlc_serio->open = hil_mlc_serio_open;
|
||||
mlc_serio->close = hil_mlc_serio_close;
|
||||
mlc_serio->port_data = &(mlc->serio_map[i]);
|
||||
mlc->serio_map[i].mlc = mlc;
|
||||
mlc->serio_map[i].didx = i;
|
||||
mlc->serio_map[i].di_revmap = -1;
|
||||
mlc->serio_opacket[i] = 0;
|
||||
mlc->serio_oidx[i] = 0;
|
||||
serio_register_port(mlc_serio);
|
||||
}
|
||||
|
||||
mlc->tasklet = &hil_mlcs_tasklet;
|
||||
|
||||
write_lock_irqsave(&hil_mlcs_lock, flags);
|
||||
list_add_tail(&mlc->list, &hil_mlcs);
|
||||
mlc->seidx = HILSEN_START;
|
||||
write_unlock_irqrestore(&hil_mlcs_lock, flags);
|
||||
|
||||
tasklet_schedule(&hil_mlcs_tasklet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hil_mlc_unregister(hil_mlc *mlc) {
|
||||
struct list_head *tmp;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (mlc == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
write_lock_irqsave(&hil_mlcs_lock, flags);
|
||||
list_for_each(tmp, &hil_mlcs) {
|
||||
if (list_entry(tmp, hil_mlc, list) == mlc)
|
||||
goto found;
|
||||
}
|
||||
|
||||
/* not found in list */
|
||||
write_unlock_irqrestore(&hil_mlcs_lock, flags);
|
||||
tasklet_schedule(&hil_mlcs_tasklet);
|
||||
return -ENODEV;
|
||||
|
||||
found:
|
||||
list_del(tmp);
|
||||
write_unlock_irqrestore(&hil_mlcs_lock, flags);
|
||||
|
||||
for (i = 0; i < HIL_MLC_DEVMEM; i++) {
|
||||
serio_unregister_port(mlc->serio[i]);
|
||||
mlc->serio[i] = NULL;
|
||||
}
|
||||
|
||||
tasklet_schedule(&hil_mlcs_tasklet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************************** Module interface *************************/
|
||||
|
||||
static int __init hil_mlc_init(void)
|
||||
{
|
||||
init_timer(&hil_mlcs_kicker);
|
||||
hil_mlcs_kicker.expires = jiffies + HZ;
|
||||
hil_mlcs_kicker.function = &hil_mlcs_timer;
|
||||
add_timer(&hil_mlcs_kicker);
|
||||
|
||||
tasklet_enable(&hil_mlcs_tasklet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit hil_mlc_exit(void)
|
||||
{
|
||||
del_timer(&hil_mlcs_kicker);
|
||||
|
||||
tasklet_disable(&hil_mlcs_tasklet);
|
||||
tasklet_kill(&hil_mlcs_tasklet);
|
||||
}
|
||||
|
||||
module_init(hil_mlc_init);
|
||||
module_exit(hil_mlc_exit);
|
||||
1053
drivers/input/serio/hp_sdc.c
Normal file
1053
drivers/input/serio/hp_sdc.c
Normal file
File diff suppressed because it is too large
Load Diff
360
drivers/input/serio/hp_sdc_mlc.c
Normal file
360
drivers/input/serio/hp_sdc_mlc.c
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Access to HP-HIL MLC through HP System Device Controller.
|
||||
*
|
||||
* Copyright (c) 2001 Brian S. Julin
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions, and the following disclaimer,
|
||||
* without modification.
|
||||
* 2. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* Alternatively, this software may be distributed under the terms of the
|
||||
* GNU General Public License ("GPL").
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
*
|
||||
* References:
|
||||
* HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A
|
||||
* System Device Controller Microprocessor Firmware Theory of Operation
|
||||
* for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/hil_mlc.h>
|
||||
#include <linux/hp_sdc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
#define PREFIX "HP SDC MLC: "
|
||||
|
||||
static hil_mlc hp_sdc_mlc;
|
||||
|
||||
MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
|
||||
MODULE_DESCRIPTION("Glue for onboard HIL MLC in HP-PARISC machines");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
|
||||
struct hp_sdc_mlc_priv_s {
|
||||
int emtestmode;
|
||||
hp_sdc_transaction trans;
|
||||
u8 tseq[16];
|
||||
int got5x;
|
||||
} hp_sdc_mlc_priv;
|
||||
|
||||
/************************* Interrupt context ******************************/
|
||||
static void hp_sdc_mlc_isr (int irq, void *dev_id,
|
||||
uint8_t status, uint8_t data) {
|
||||
int idx;
|
||||
hil_mlc *mlc = &hp_sdc_mlc;
|
||||
|
||||
write_lock(&(mlc->lock));
|
||||
if (mlc->icount < 0) {
|
||||
printk(KERN_WARNING PREFIX "HIL Overflow!\n");
|
||||
up(&mlc->isem);
|
||||
goto out;
|
||||
}
|
||||
idx = 15 - mlc->icount;
|
||||
if ((status & HP_SDC_STATUS_IRQMASK) == HP_SDC_STATUS_HILDATA) {
|
||||
mlc->ipacket[idx] |= data | HIL_ERR_INT;
|
||||
mlc->icount--;
|
||||
if (hp_sdc_mlc_priv.got5x) goto check;
|
||||
if (!idx) goto check;
|
||||
if ((mlc->ipacket[idx-1] & HIL_PKT_ADDR_MASK) !=
|
||||
(mlc->ipacket[idx] & HIL_PKT_ADDR_MASK)) {
|
||||
mlc->ipacket[idx] &= ~HIL_PKT_ADDR_MASK;
|
||||
mlc->ipacket[idx] |= (mlc->ipacket[idx-1]
|
||||
& HIL_PKT_ADDR_MASK);
|
||||
}
|
||||
goto check;
|
||||
}
|
||||
/* We know status is 5X */
|
||||
if (data & HP_SDC_HIL_ISERR) goto err;
|
||||
mlc->ipacket[idx] =
|
||||
(data & HP_SDC_HIL_R1MASK) << HIL_PKT_ADDR_SHIFT;
|
||||
hp_sdc_mlc_priv.got5x = 1;
|
||||
goto out;
|
||||
|
||||
check:
|
||||
hp_sdc_mlc_priv.got5x = 0;
|
||||
if (mlc->imatch == 0) goto done;
|
||||
if ((mlc->imatch == (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL))
|
||||
&& (mlc->ipacket[idx] == (mlc->imatch | idx))) goto done;
|
||||
if (mlc->ipacket[idx] == mlc->imatch) goto done;
|
||||
goto out;
|
||||
|
||||
err:
|
||||
printk(KERN_DEBUG PREFIX "err code %x\n", data);
|
||||
switch (data) {
|
||||
case HP_SDC_HIL_RC_DONE:
|
||||
printk(KERN_WARNING PREFIX "Bastard SDC reconfigured loop!\n");
|
||||
break;
|
||||
case HP_SDC_HIL_ERR:
|
||||
mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_PERR |
|
||||
HIL_ERR_FERR | HIL_ERR_FOF;
|
||||
break;
|
||||
case HP_SDC_HIL_TO:
|
||||
mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_LERR;
|
||||
break;
|
||||
case HP_SDC_HIL_RC:
|
||||
printk(KERN_WARNING PREFIX "Bastard SDC decided to reconfigure loop!\n");
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING PREFIX "Unkown HIL Error status (%x)!\n", data);
|
||||
break;
|
||||
}
|
||||
/* No more data will be coming due to an error. */
|
||||
done:
|
||||
tasklet_schedule(mlc->tasklet);
|
||||
up(&(mlc->isem));
|
||||
out:
|
||||
write_unlock(&(mlc->lock));
|
||||
}
|
||||
|
||||
|
||||
/******************** Tasklet or userspace context functions ****************/
|
||||
|
||||
static int hp_sdc_mlc_in (hil_mlc *mlc, suseconds_t timeout) {
|
||||
unsigned long flags;
|
||||
struct hp_sdc_mlc_priv_s *priv;
|
||||
int rc = 2;
|
||||
|
||||
priv = mlc->priv;
|
||||
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
|
||||
/* Try to down the semaphore */
|
||||
if (down_trylock(&(mlc->isem))) {
|
||||
struct timeval tv;
|
||||
if (priv->emtestmode) {
|
||||
mlc->ipacket[0] =
|
||||
HIL_ERR_INT | (mlc->opacket &
|
||||
(HIL_PKT_CMD |
|
||||
HIL_PKT_ADDR_MASK |
|
||||
HIL_PKT_DATA_MASK));
|
||||
mlc->icount = 14;
|
||||
/* printk(KERN_DEBUG PREFIX ">[%x]\n", mlc->ipacket[0]); */
|
||||
goto wasup;
|
||||
}
|
||||
do_gettimeofday(&tv);
|
||||
tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec);
|
||||
if (tv.tv_usec - mlc->instart.tv_usec > mlc->intimeout) {
|
||||
/* printk("!%i %i",
|
||||
tv.tv_usec - mlc->instart.tv_usec,
|
||||
mlc->intimeout);
|
||||
*/
|
||||
rc = 1;
|
||||
up(&(mlc->isem));
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
wasup:
|
||||
up(&(mlc->isem));
|
||||
rc = 0;
|
||||
goto done;
|
||||
done:
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hp_sdc_mlc_cts (hil_mlc *mlc) {
|
||||
struct hp_sdc_mlc_priv_s *priv;
|
||||
unsigned long flags;
|
||||
|
||||
priv = mlc->priv;
|
||||
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
|
||||
/* Try to down the semaphores -- they should be up. */
|
||||
if (down_trylock(&(mlc->isem))) {
|
||||
BUG();
|
||||
goto busy;
|
||||
}
|
||||
if (down_trylock(&(mlc->osem))) {
|
||||
BUG();
|
||||
up(&(mlc->isem));
|
||||
goto busy;
|
||||
}
|
||||
up(&(mlc->isem));
|
||||
up(&(mlc->osem));
|
||||
|
||||
if (down_trylock(&(mlc->csem))) {
|
||||
if (priv->trans.act.semaphore != &(mlc->csem)) goto poll;
|
||||
goto busy;
|
||||
}
|
||||
if (!(priv->tseq[4] & HP_SDC_USE_LOOP)) goto done;
|
||||
|
||||
poll:
|
||||
priv->trans.act.semaphore = &(mlc->csem);
|
||||
priv->trans.actidx = 0;
|
||||
priv->trans.idx = 1;
|
||||
priv->trans.endidx = 5;
|
||||
priv->tseq[0] =
|
||||
HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE;
|
||||
priv->tseq[1] = HP_SDC_CMD_READ_USE;
|
||||
priv->tseq[2] = 1;
|
||||
priv->tseq[3] = 0;
|
||||
priv->tseq[4] = 0;
|
||||
hp_sdc_enqueue_transaction(&(priv->trans));
|
||||
busy:
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
return 1;
|
||||
done:
|
||||
priv->trans.act.semaphore = &(mlc->osem);
|
||||
up(&(mlc->csem));
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hp_sdc_mlc_out (hil_mlc *mlc) {
|
||||
struct hp_sdc_mlc_priv_s *priv;
|
||||
unsigned long flags;
|
||||
|
||||
priv = mlc->priv;
|
||||
|
||||
write_lock_irqsave(&(mlc->lock), flags);
|
||||
|
||||
/* Try to down the semaphore -- it should be up. */
|
||||
if (down_trylock(&(mlc->osem))) {
|
||||
BUG();
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (mlc->opacket & HIL_DO_ALTER_CTRL) goto do_control;
|
||||
|
||||
do_data:
|
||||
if (priv->emtestmode) {
|
||||
up(&(mlc->osem));
|
||||
goto done;
|
||||
}
|
||||
/* Shouldn't be sending commands when loop may be busy */
|
||||
if (down_trylock(&(mlc->csem))) {
|
||||
BUG();
|
||||
goto done;
|
||||
}
|
||||
up(&(mlc->csem));
|
||||
|
||||
priv->trans.actidx = 0;
|
||||
priv->trans.idx = 1;
|
||||
priv->trans.act.semaphore = &(mlc->osem);
|
||||
priv->trans.endidx = 6;
|
||||
priv->tseq[0] =
|
||||
HP_SDC_ACT_DATAREG | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_SEMAPHORE;
|
||||
priv->tseq[1] = 0x7;
|
||||
priv->tseq[2] =
|
||||
(mlc->opacket &
|
||||
(HIL_PKT_ADDR_MASK | HIL_PKT_CMD))
|
||||
>> HIL_PKT_ADDR_SHIFT;
|
||||
priv->tseq[3] =
|
||||
(mlc->opacket & HIL_PKT_DATA_MASK)
|
||||
>> HIL_PKT_DATA_SHIFT;
|
||||
priv->tseq[4] = 0; /* No timeout */
|
||||
if (priv->tseq[3] == HIL_CMD_DHR) priv->tseq[4] = 1;
|
||||
priv->tseq[5] = HP_SDC_CMD_DO_HIL;
|
||||
goto enqueue;
|
||||
|
||||
do_control:
|
||||
priv->emtestmode = mlc->opacket & HIL_CTRL_TEST;
|
||||
|
||||
/* we cannot emulate this, it should not be used. */
|
||||
BUG_ON((mlc->opacket & (HIL_CTRL_APE | HIL_CTRL_IPF)) == HIL_CTRL_APE);
|
||||
|
||||
if ((mlc->opacket & HIL_CTRL_ONLY) == HIL_CTRL_ONLY) goto control_only;
|
||||
if (mlc->opacket & HIL_CTRL_APE) {
|
||||
BUG(); /* Should not send command/data after engaging APE */
|
||||
goto done;
|
||||
}
|
||||
/* Disengaging APE this way would not be valid either since
|
||||
* the loop must be allowed to idle.
|
||||
*
|
||||
* So, it works out that we really never actually send control
|
||||
* and data when using SDC, we just send the data.
|
||||
*/
|
||||
goto do_data;
|
||||
|
||||
control_only:
|
||||
priv->trans.actidx = 0;
|
||||
priv->trans.idx = 1;
|
||||
priv->trans.act.semaphore = &(mlc->osem);
|
||||
priv->trans.endidx = 4;
|
||||
priv->tseq[0] =
|
||||
HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE;
|
||||
priv->tseq[1] = HP_SDC_CMD_SET_LPC;
|
||||
priv->tseq[2] = 1;
|
||||
// priv->tseq[3] = (mlc->ddc + 1) | HP_SDC_LPS_ACSUCC;
|
||||
priv->tseq[3] = 0;
|
||||
if (mlc->opacket & HIL_CTRL_APE) {
|
||||
priv->tseq[3] |= HP_SDC_LPC_APE_IPF;
|
||||
down_trylock(&(mlc->csem));
|
||||
}
|
||||
enqueue:
|
||||
hp_sdc_enqueue_transaction(&(priv->trans));
|
||||
done:
|
||||
write_unlock_irqrestore(&(mlc->lock), flags);
|
||||
}
|
||||
|
||||
static int __init hp_sdc_mlc_init(void)
|
||||
{
|
||||
hil_mlc *mlc = &hp_sdc_mlc;
|
||||
|
||||
printk(KERN_INFO PREFIX "Registering the System Domain Controller's HIL MLC.\n");
|
||||
|
||||
hp_sdc_mlc_priv.emtestmode = 0;
|
||||
hp_sdc_mlc_priv.trans.seq = hp_sdc_mlc_priv.tseq;
|
||||
hp_sdc_mlc_priv.trans.act.semaphore = &(mlc->osem);
|
||||
hp_sdc_mlc_priv.got5x = 0;
|
||||
|
||||
mlc->cts = &hp_sdc_mlc_cts;
|
||||
mlc->in = &hp_sdc_mlc_in;
|
||||
mlc->out = &hp_sdc_mlc_out;
|
||||
|
||||
if (hil_mlc_register(mlc)) {
|
||||
printk(KERN_WARNING PREFIX "Failed to register MLC structure with hil_mlc\n");
|
||||
goto err0;
|
||||
}
|
||||
mlc->priv = &hp_sdc_mlc_priv;
|
||||
|
||||
if (hp_sdc_request_hil_irq(&hp_sdc_mlc_isr)) {
|
||||
printk(KERN_WARNING PREFIX "Request for raw HIL ISR hook denied\n");
|
||||
goto err1;
|
||||
}
|
||||
return 0;
|
||||
err1:
|
||||
if (hil_mlc_unregister(mlc)) {
|
||||
printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n"
|
||||
"This is bad. Could cause an oops.\n");
|
||||
}
|
||||
err0:
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static void __exit hp_sdc_mlc_exit(void)
|
||||
{
|
||||
hil_mlc *mlc = &hp_sdc_mlc;
|
||||
if (hp_sdc_release_hil_irq(&hp_sdc_mlc_isr)) {
|
||||
printk(KERN_ERR PREFIX "Failed to release the raw HIL ISR hook.\n"
|
||||
"This is bad. Could cause an oops.\n");
|
||||
}
|
||||
if (hil_mlc_unregister(mlc)) {
|
||||
printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n"
|
||||
"This is bad. Could cause an oops.\n");
|
||||
}
|
||||
}
|
||||
|
||||
module_init(hp_sdc_mlc_init);
|
||||
module_exit(hp_sdc_mlc_exit);
|
||||
90
drivers/input/serio/i8042-io.h
Normal file
90
drivers/input/serio/i8042-io.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef _I8042_IO_H
|
||||
#define _I8042_IO_H
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Names.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "isa0060/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "isa0060/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "isa0060/serio%d"
|
||||
|
||||
/*
|
||||
* IRQs.
|
||||
*/
|
||||
|
||||
#ifdef __alpha__
|
||||
# define I8042_KBD_IRQ 1
|
||||
# define I8042_AUX_IRQ (RTC_PORT(0) == 0x170 ? 9 : 12) /* Jensen is special */
|
||||
#elif defined(__arm__)
|
||||
/* defined in include/asm-arm/arch-xxx/irqs.h */
|
||||
#include <asm/irq.h>
|
||||
#elif defined(CONFIG_SUPERH64)
|
||||
#include <asm/irq.h>
|
||||
#else
|
||||
# define I8042_KBD_IRQ 1
|
||||
# define I8042_AUX_IRQ 12
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Register numbers.
|
||||
*/
|
||||
|
||||
#define I8042_COMMAND_REG 0x64
|
||||
#define I8042_STATUS_REG 0x64
|
||||
#define I8042_DATA_REG 0x60
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return inb(I8042_DATA_REG);
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return inb(I8042_STATUS_REG);
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
outb(val, I8042_DATA_REG);
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
outb(val, I8042_COMMAND_REG);
|
||||
}
|
||||
|
||||
static inline int i8042_platform_init(void)
|
||||
{
|
||||
/*
|
||||
* On some platforms touching the i8042 data register region can do really
|
||||
* bad things. Because of this the region is always reserved on such boxes.
|
||||
*/
|
||||
#if defined(CONFIG_PPC_MERGE)
|
||||
if (check_legacy_ioport(I8042_DATA_REG))
|
||||
return -ENODEV;
|
||||
#endif
|
||||
#if !defined(__sh__) && !defined(__alpha__) && !defined(__mips__)
|
||||
if (!request_region(I8042_DATA_REG, 16, "i8042"))
|
||||
return -EBUSY;
|
||||
#endif
|
||||
|
||||
i8042_reset = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
#if !defined(__sh__) && !defined(__alpha__)
|
||||
release_region(I8042_DATA_REG, 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* _I8042_IO_H */
|
||||
76
drivers/input/serio/i8042-ip22io.h
Normal file
76
drivers/input/serio/i8042-ip22io.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef _I8042_IP22_H
|
||||
#define _I8042_IP22_H
|
||||
|
||||
#include <asm/sgi/ioc.h>
|
||||
#include <asm/sgi/ip22.h>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Names.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "hpc3ps2/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "hpc3ps2/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "hpc3ps2/serio%d"
|
||||
|
||||
/*
|
||||
* IRQs.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_IRQ SGI_KEYBD_IRQ
|
||||
#define I8042_AUX_IRQ SGI_KEYBD_IRQ
|
||||
|
||||
/*
|
||||
* Register numbers.
|
||||
*/
|
||||
|
||||
#define I8042_COMMAND_REG ((unsigned long)&sgioc->kbdmouse.command)
|
||||
#define I8042_STATUS_REG ((unsigned long)&sgioc->kbdmouse.command)
|
||||
#define I8042_DATA_REG ((unsigned long)&sgioc->kbdmouse.data)
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return sgioc->kbdmouse.data;
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return sgioc->kbdmouse.command;
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
sgioc->kbdmouse.data = val;
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
sgioc->kbdmouse.command = val;
|
||||
}
|
||||
|
||||
static inline int i8042_platform_init(void)
|
||||
{
|
||||
#if 0
|
||||
/* XXX sgi_kh is a virtual address */
|
||||
if (!request_mem_region(sgi_kh, sizeof(struct hpc_keyb), "i8042"))
|
||||
return -EBUSY;
|
||||
#endif
|
||||
|
||||
i8042_reset = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
#if 0
|
||||
release_mem_region(JAZZ_KEYBOARD_ADDRESS, sizeof(struct hpc_keyb));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* _I8042_IP22_H */
|
||||
69
drivers/input/serio/i8042-jazzio.h
Normal file
69
drivers/input/serio/i8042-jazzio.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef _I8042_JAZZ_H
|
||||
#define _I8042_JAZZ_H
|
||||
|
||||
#include <asm/jazz.h>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Names.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "R4030/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "R4030/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "R4030/serio%d"
|
||||
|
||||
/*
|
||||
* IRQs.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_IRQ JAZZ_KEYBOARD_IRQ
|
||||
#define I8042_AUX_IRQ JAZZ_MOUSE_IRQ
|
||||
|
||||
#define I8042_COMMAND_REG ((unsigned long)&jazz_kh->command)
|
||||
#define I8042_STATUS_REG ((unsigned long)&jazz_kh->command)
|
||||
#define I8042_DATA_REG ((unsigned long)&jazz_kh->data)
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return jazz_kh->data;
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return jazz_kh->command;
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
jazz_kh->data = val;
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
jazz_kh->command = val;
|
||||
}
|
||||
|
||||
static inline int i8042_platform_init(void)
|
||||
{
|
||||
#if 0
|
||||
/* XXX JAZZ_KEYBOARD_ADDRESS is a virtual address */
|
||||
if (!request_mem_region(JAZZ_KEYBOARD_ADDRESS, 2, "i8042"))
|
||||
return -EBUSY;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
#if 0
|
||||
release_mem_region(JAZZ_KEYBOARD_ADDRESS, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* _I8042_JAZZ_H */
|
||||
136
drivers/input/serio/i8042-ppcio.h
Normal file
136
drivers/input/serio/i8042-ppcio.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#ifndef _I8042_PPCIO_H
|
||||
#define _I8042_PPCIO_H
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_WALNUT)
|
||||
|
||||
#define I8042_KBD_IRQ 25
|
||||
#define I8042_AUX_IRQ 26
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "walnutps2/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "walnutps2/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "walnutps2/serio%d"
|
||||
|
||||
extern void *kb_cs;
|
||||
extern void *kb_data;
|
||||
|
||||
#define I8042_COMMAND_REG (*(int *)kb_cs)
|
||||
#define I8042_DATA_REG (*(int *)kb_data)
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return readb(kb_data);
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return readb(kb_cs);
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
writeb(val, kb_data);
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
writeb(val, kb_cs);
|
||||
}
|
||||
|
||||
static inline int i8042_platform_init(void)
|
||||
{
|
||||
i8042_reset = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_SPRUCE)
|
||||
|
||||
#define I8042_KBD_IRQ 22
|
||||
#define I8042_AUX_IRQ 21
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "spruceps2/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "spruceps2/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "spruceps2/serio%d"
|
||||
|
||||
#define I8042_COMMAND_REG 0xff810000
|
||||
#define I8042_DATA_REG 0xff810001
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
unsigned long kbd_data;
|
||||
|
||||
__raw_writel(0x00000088, 0xff500008);
|
||||
eieio();
|
||||
|
||||
__raw_writel(0x03000000, 0xff50000c);
|
||||
eieio();
|
||||
|
||||
asm volatile("lis 7,0xff88 \n\
|
||||
lswi 6,7,0x8 \n\
|
||||
mr %0,6"
|
||||
: "=r" (kbd_data) :: "6", "7");
|
||||
|
||||
__raw_writel(0x00000000, 0xff50000c);
|
||||
eieio();
|
||||
|
||||
return (unsigned char)(kbd_data >> 24);
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
unsigned long kbd_status;
|
||||
|
||||
__raw_writel(0x00000088, 0xff500008);
|
||||
eieio();
|
||||
|
||||
__raw_writel(0x03000000, 0xff50000c);
|
||||
eieio();
|
||||
|
||||
asm volatile("lis 7,0xff88 \n\
|
||||
ori 7,7,0x8 \n\
|
||||
lswi 6,7,0x8 \n\
|
||||
mr %0,6"
|
||||
: "=r" (kbd_status) :: "6", "7");
|
||||
|
||||
__raw_writel(0x00000000, 0xff50000c);
|
||||
eieio();
|
||||
|
||||
return (unsigned char)(kbd_status >> 24);
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
*((unsigned char *)0xff810000) = (char)val;
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
*((unsigned char *)0xff810001) = (char)val;
|
||||
}
|
||||
|
||||
static inline int i8042_platform_init(void)
|
||||
{
|
||||
i8042_reset = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "i8042-io.h"
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* _I8042_PPCIO_H */
|
||||
147
drivers/input/serio/i8042-sparcio.h
Normal file
147
drivers/input/serio/i8042-sparcio.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifndef _I8042_SPARCIO_H
|
||||
#define _I8042_SPARCIO_H
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/oplib.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/of_device.h>
|
||||
|
||||
static int i8042_kbd_irq = -1;
|
||||
static int i8042_aux_irq = -1;
|
||||
#define I8042_KBD_IRQ i8042_kbd_irq
|
||||
#define I8042_AUX_IRQ i8042_aux_irq
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "sparcps2/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "sparcps2/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "sparcps2/serio%d"
|
||||
|
||||
static void __iomem *kbd_iobase;
|
||||
static struct resource *kbd_res;
|
||||
|
||||
#define I8042_COMMAND_REG (kbd_iobase + 0x64UL)
|
||||
#define I8042_DATA_REG (kbd_iobase + 0x60UL)
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return readb(kbd_iobase + 0x60UL);
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return readb(kbd_iobase + 0x64UL);
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
writeb(val, kbd_iobase + 0x60UL);
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
writeb(val, kbd_iobase + 0x64UL);
|
||||
}
|
||||
|
||||
#define OBP_PS2KBD_NAME1 "kb_ps2"
|
||||
#define OBP_PS2KBD_NAME2 "keyboard"
|
||||
#define OBP_PS2MS_NAME1 "kdmouse"
|
||||
#define OBP_PS2MS_NAME2 "mouse"
|
||||
|
||||
static int __devinit sparc_i8042_probe(struct of_device *op, const struct of_device_id *match)
|
||||
{
|
||||
struct device_node *dp = op->node;
|
||||
|
||||
dp = dp->child;
|
||||
while (dp) {
|
||||
if (!strcmp(dp->name, OBP_PS2KBD_NAME1) ||
|
||||
!strcmp(dp->name, OBP_PS2KBD_NAME2)) {
|
||||
struct of_device *kbd = of_find_device_by_node(dp);
|
||||
unsigned int irq = kbd->irqs[0];
|
||||
if (irq == 0xffffffff)
|
||||
irq = op->irqs[0];
|
||||
i8042_kbd_irq = irq;
|
||||
kbd_iobase = of_ioremap(&kbd->resource[0],
|
||||
0, 8, "kbd");
|
||||
kbd_res = &kbd->resource[0];
|
||||
} else if (!strcmp(dp->name, OBP_PS2MS_NAME1) ||
|
||||
!strcmp(dp->name, OBP_PS2MS_NAME2)) {
|
||||
struct of_device *ms = of_find_device_by_node(dp);
|
||||
unsigned int irq = ms->irqs[0];
|
||||
if (irq == 0xffffffff)
|
||||
irq = op->irqs[0];
|
||||
i8042_aux_irq = irq;
|
||||
}
|
||||
|
||||
dp = dp->sibling;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit sparc_i8042_remove(struct of_device *op)
|
||||
{
|
||||
of_iounmap(kbd_res, kbd_iobase, 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id sparc_i8042_match[] = {
|
||||
{
|
||||
.name = "8042",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sparc_i8042_match);
|
||||
|
||||
static struct of_platform_driver sparc_i8042_driver = {
|
||||
.name = "i8042",
|
||||
.match_table = sparc_i8042_match,
|
||||
.probe = sparc_i8042_probe,
|
||||
.remove = __devexit_p(sparc_i8042_remove),
|
||||
};
|
||||
|
||||
static int __init i8042_platform_init(void)
|
||||
{
|
||||
#ifndef CONFIG_PCI
|
||||
return -ENODEV;
|
||||
#else
|
||||
struct device_node *root = of_find_node_by_path("/");
|
||||
|
||||
if (!strcmp(root->name, "SUNW,JavaStation-1")) {
|
||||
/* Hardcoded values for MrCoffee. */
|
||||
i8042_kbd_irq = i8042_aux_irq = 13 | 0x20;
|
||||
kbd_iobase = ioremap(0x71300060, 8);
|
||||
if (!kbd_iobase)
|
||||
return -ENODEV;
|
||||
} else {
|
||||
int err = of_register_driver(&sparc_i8042_driver,
|
||||
&of_bus_type);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (i8042_kbd_irq == -1 ||
|
||||
i8042_aux_irq == -1) {
|
||||
if (kbd_iobase) {
|
||||
of_iounmap(kbd_res, kbd_iobase, 8);
|
||||
kbd_iobase = (void __iomem *) NULL;
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
i8042_reset = 1;
|
||||
|
||||
return 0;
|
||||
#endif /* CONFIG_PCI */
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_PCI
|
||||
struct device_node *root = of_find_node_by_path("/");
|
||||
|
||||
if (strcmp(root->name, "SUNW,JavaStation-1"))
|
||||
of_unregister_driver(&sparc_i8042_driver);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* _I8042_SPARCIO_H */
|
||||
438
drivers/input/serio/i8042-x86ia64io.h
Normal file
438
drivers/input/serio/i8042-x86ia64io.h
Normal file
@@ -0,0 +1,438 @@
|
||||
#ifndef _I8042_X86IA64IO_H
|
||||
#define _I8042_X86IA64IO_H
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Names.
|
||||
*/
|
||||
|
||||
#define I8042_KBD_PHYS_DESC "isa0060/serio0"
|
||||
#define I8042_AUX_PHYS_DESC "isa0060/serio1"
|
||||
#define I8042_MUX_PHYS_DESC "isa0060/serio%d"
|
||||
|
||||
/*
|
||||
* IRQs.
|
||||
*/
|
||||
|
||||
#if defined(__ia64__)
|
||||
# define I8042_MAP_IRQ(x) isa_irq_to_vector((x))
|
||||
#else
|
||||
# define I8042_MAP_IRQ(x) (x)
|
||||
#endif
|
||||
|
||||
#define I8042_KBD_IRQ i8042_kbd_irq
|
||||
#define I8042_AUX_IRQ i8042_aux_irq
|
||||
|
||||
static int i8042_kbd_irq;
|
||||
static int i8042_aux_irq;
|
||||
|
||||
/*
|
||||
* Register numbers.
|
||||
*/
|
||||
|
||||
#define I8042_COMMAND_REG i8042_command_reg
|
||||
#define I8042_STATUS_REG i8042_command_reg
|
||||
#define I8042_DATA_REG i8042_data_reg
|
||||
|
||||
static int i8042_command_reg = 0x64;
|
||||
static int i8042_data_reg = 0x60;
|
||||
|
||||
|
||||
static inline int i8042_read_data(void)
|
||||
{
|
||||
return inb(I8042_DATA_REG);
|
||||
}
|
||||
|
||||
static inline int i8042_read_status(void)
|
||||
{
|
||||
return inb(I8042_STATUS_REG);
|
||||
}
|
||||
|
||||
static inline void i8042_write_data(int val)
|
||||
{
|
||||
outb(val, I8042_DATA_REG);
|
||||
}
|
||||
|
||||
static inline void i8042_write_command(int val)
|
||||
{
|
||||
outb(val, I8042_COMMAND_REG);
|
||||
}
|
||||
|
||||
#if defined(__i386__)
|
||||
|
||||
#include <linux/dmi.h>
|
||||
|
||||
static struct dmi_system_id __initdata i8042_dmi_noloop_table[] = {
|
||||
{
|
||||
.ident = "Compaq Proliant 8500",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "8500"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Compaq Proliant DL760",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "DL760"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "OQO Model 01",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "OQO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ZEPTO"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "00"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* Some Fujitsu notebooks are having trouble with touchpads if
|
||||
* active multiplexing mode is activated. Luckily they don't have
|
||||
* external PS/2 ports so we can safely disable it.
|
||||
* ... apparently some Toshibas don't like MUX mode either and
|
||||
* die horrible death on reboot.
|
||||
*/
|
||||
static struct dmi_system_id __initdata i8042_dmi_nomux_table[] = {
|
||||
{
|
||||
.ident = "Fujitsu Lifebook P7010/P7010D",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "P7010"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu Lifebook P7010",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "0000000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu Lifebook P5020D",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P Series"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu Lifebook S2000",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S Series"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu Lifebook S6230",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S6230"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu T70H",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "FMVLT70H"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu-Siemens Lifebook T3010",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T3010"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Fujitsu-Siemens Lifebook E4010",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E4010"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Toshiba P10",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P10"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Toshiba Equium A110",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "EQUIUM A110"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Alienware Sentia",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ALIENWARE"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Sentia"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Sharp Actius MM20",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "SHARP"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "PC-MM20 Series"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Sony Vaio FS-115b",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FS115B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Amoi M636/A737",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Amoi Electronics CO.,LTD."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "M636/A737 platform"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
#include <linux/pnp.h>
|
||||
|
||||
static int i8042_pnp_kbd_registered;
|
||||
static unsigned int i8042_pnp_kbd_devices;
|
||||
static int i8042_pnp_aux_registered;
|
||||
static unsigned int i8042_pnp_aux_devices;
|
||||
|
||||
static int i8042_pnp_command_reg;
|
||||
static int i8042_pnp_data_reg;
|
||||
static int i8042_pnp_kbd_irq;
|
||||
static int i8042_pnp_aux_irq;
|
||||
|
||||
static char i8042_pnp_kbd_name[32];
|
||||
static char i8042_pnp_aux_name[32];
|
||||
|
||||
static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
|
||||
{
|
||||
if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
|
||||
i8042_pnp_data_reg = pnp_port_start(dev,0);
|
||||
|
||||
if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
|
||||
i8042_pnp_command_reg = pnp_port_start(dev, 1);
|
||||
|
||||
if (pnp_irq_valid(dev,0))
|
||||
i8042_pnp_kbd_irq = pnp_irq(dev, 0);
|
||||
|
||||
strncpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name));
|
||||
if (strlen(pnp_dev_name(dev))) {
|
||||
strncat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
|
||||
strncat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
|
||||
}
|
||||
|
||||
i8042_pnp_kbd_devices++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
|
||||
{
|
||||
if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
|
||||
i8042_pnp_data_reg = pnp_port_start(dev,0);
|
||||
|
||||
if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
|
||||
i8042_pnp_command_reg = pnp_port_start(dev, 1);
|
||||
|
||||
if (pnp_irq_valid(dev, 0))
|
||||
i8042_pnp_aux_irq = pnp_irq(dev, 0);
|
||||
|
||||
strncpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name));
|
||||
if (strlen(pnp_dev_name(dev))) {
|
||||
strncat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
|
||||
strncat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
|
||||
}
|
||||
|
||||
i8042_pnp_aux_devices++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pnp_device_id pnp_kbd_devids[] = {
|
||||
{ .id = "PNP0303", .driver_data = 0 },
|
||||
{ .id = "PNP030b", .driver_data = 0 },
|
||||
{ .id = "", },
|
||||
};
|
||||
|
||||
static struct pnp_driver i8042_pnp_kbd_driver = {
|
||||
.name = "i8042 kbd",
|
||||
.id_table = pnp_kbd_devids,
|
||||
.probe = i8042_pnp_kbd_probe,
|
||||
};
|
||||
|
||||
static struct pnp_device_id pnp_aux_devids[] = {
|
||||
{ .id = "PNP0f03", .driver_data = 0 },
|
||||
{ .id = "PNP0f0b", .driver_data = 0 },
|
||||
{ .id = "PNP0f0e", .driver_data = 0 },
|
||||
{ .id = "PNP0f12", .driver_data = 0 },
|
||||
{ .id = "PNP0f13", .driver_data = 0 },
|
||||
{ .id = "PNP0f19", .driver_data = 0 },
|
||||
{ .id = "PNP0f1c", .driver_data = 0 },
|
||||
{ .id = "SYN0801", .driver_data = 0 },
|
||||
{ .id = "", },
|
||||
};
|
||||
|
||||
static struct pnp_driver i8042_pnp_aux_driver = {
|
||||
.name = "i8042 aux",
|
||||
.id_table = pnp_aux_devids,
|
||||
.probe = i8042_pnp_aux_probe,
|
||||
};
|
||||
|
||||
static void i8042_pnp_exit(void)
|
||||
{
|
||||
if (i8042_pnp_kbd_registered) {
|
||||
i8042_pnp_kbd_registered = 0;
|
||||
pnp_unregister_driver(&i8042_pnp_kbd_driver);
|
||||
}
|
||||
|
||||
if (i8042_pnp_aux_registered) {
|
||||
i8042_pnp_aux_registered = 0;
|
||||
pnp_unregister_driver(&i8042_pnp_aux_driver);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init i8042_pnp_init(void)
|
||||
{
|
||||
char kbd_irq_str[4] = { 0 }, aux_irq_str[4] = { 0 };
|
||||
int err;
|
||||
|
||||
if (i8042_nopnp) {
|
||||
printk(KERN_INFO "i8042: PNP detection disabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = pnp_register_driver(&i8042_pnp_kbd_driver);
|
||||
if (!err)
|
||||
i8042_pnp_kbd_registered = 1;
|
||||
|
||||
err = pnp_register_driver(&i8042_pnp_aux_driver);
|
||||
if (!err)
|
||||
i8042_pnp_aux_registered = 1;
|
||||
|
||||
if (!i8042_pnp_kbd_devices && !i8042_pnp_aux_devices) {
|
||||
i8042_pnp_exit();
|
||||
#if defined(__ia64__)
|
||||
return -ENODEV;
|
||||
#else
|
||||
printk(KERN_INFO "PNP: No PS/2 controller found. Probing ports directly.\n");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (i8042_pnp_kbd_devices)
|
||||
snprintf(kbd_irq_str, sizeof(kbd_irq_str),
|
||||
"%d", i8042_pnp_kbd_irq);
|
||||
if (i8042_pnp_aux_devices)
|
||||
snprintf(aux_irq_str, sizeof(aux_irq_str),
|
||||
"%d", i8042_pnp_aux_irq);
|
||||
|
||||
printk(KERN_INFO "PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %s%s%s\n",
|
||||
i8042_pnp_kbd_name, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
|
||||
i8042_pnp_aux_name,
|
||||
i8042_pnp_data_reg, i8042_pnp_command_reg,
|
||||
kbd_irq_str, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
|
||||
aux_irq_str);
|
||||
|
||||
#if defined(__ia64__)
|
||||
if (!i8042_pnp_kbd_devices)
|
||||
i8042_nokbd = 1;
|
||||
if (!i8042_pnp_aux_devices)
|
||||
i8042_noaux = 1;
|
||||
#endif
|
||||
|
||||
if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) &&
|
||||
i8042_pnp_data_reg != i8042_data_reg) || !i8042_pnp_data_reg) {
|
||||
printk(KERN_WARNING "PNP: PS/2 controller has invalid data port %#x; using default %#x\n",
|
||||
i8042_pnp_data_reg, i8042_data_reg);
|
||||
i8042_pnp_data_reg = i8042_data_reg;
|
||||
}
|
||||
|
||||
if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) &&
|
||||
i8042_pnp_command_reg != i8042_command_reg) || !i8042_pnp_command_reg) {
|
||||
printk(KERN_WARNING "PNP: PS/2 controller has invalid command port %#x; using default %#x\n",
|
||||
i8042_pnp_command_reg, i8042_command_reg);
|
||||
i8042_pnp_command_reg = i8042_command_reg;
|
||||
}
|
||||
|
||||
if (!i8042_nokbd && !i8042_pnp_kbd_irq) {
|
||||
printk(KERN_WARNING "PNP: PS/2 controller doesn't have KBD irq; using default %d\n", i8042_kbd_irq);
|
||||
i8042_pnp_kbd_irq = i8042_kbd_irq;
|
||||
}
|
||||
|
||||
if (!i8042_noaux && !i8042_pnp_aux_irq) {
|
||||
printk(KERN_WARNING "PNP: PS/2 controller doesn't have AUX irq; using default %d\n", i8042_aux_irq);
|
||||
i8042_pnp_aux_irq = i8042_aux_irq;
|
||||
}
|
||||
|
||||
i8042_data_reg = i8042_pnp_data_reg;
|
||||
i8042_command_reg = i8042_pnp_command_reg;
|
||||
i8042_kbd_irq = i8042_pnp_kbd_irq;
|
||||
i8042_aux_irq = i8042_pnp_aux_irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
static inline int i8042_pnp_init(void) { return 0; }
|
||||
static inline void i8042_pnp_exit(void) { }
|
||||
#endif
|
||||
|
||||
static int __init i8042_platform_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
/*
|
||||
* On ix86 platforms touching the i8042 data register region can do really
|
||||
* bad things. Because of this the region is always reserved on ix86 boxes.
|
||||
*
|
||||
* if (!request_region(I8042_DATA_REG, 16, "i8042"))
|
||||
* return -EBUSY;
|
||||
*/
|
||||
|
||||
i8042_kbd_irq = I8042_MAP_IRQ(1);
|
||||
i8042_aux_irq = I8042_MAP_IRQ(12);
|
||||
|
||||
retval = i8042_pnp_init();
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
#if defined(__ia64__)
|
||||
i8042_reset = 1;
|
||||
#endif
|
||||
|
||||
#if defined(__i386__)
|
||||
if (dmi_check_system(i8042_dmi_noloop_table))
|
||||
i8042_noloop = 1;
|
||||
|
||||
if (dmi_check_system(i8042_dmi_nomux_table))
|
||||
i8042_nomux = 1;
|
||||
#endif
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static inline void i8042_platform_exit(void)
|
||||
{
|
||||
i8042_pnp_exit();
|
||||
}
|
||||
|
||||
#endif /* _I8042_X86IA64IO_H */
|
||||
1238
drivers/input/serio/i8042.c
Normal file
1238
drivers/input/serio/i8042.c
Normal file
File diff suppressed because it is too large
Load Diff
123
drivers/input/serio/i8042.h
Normal file
123
drivers/input/serio/i8042.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifndef _I8042_H
|
||||
#define _I8042_H
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (c) 1999-2002 Vojtech Pavlik
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Arch-dependent inline functions and defines.
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_MACH_JAZZ)
|
||||
#include "i8042-jazzio.h"
|
||||
#elif defined(CONFIG_SGI_IP22)
|
||||
#include "i8042-ip22io.h"
|
||||
#elif defined(CONFIG_PPC)
|
||||
#include "i8042-ppcio.h"
|
||||
#elif defined(CONFIG_SPARC)
|
||||
#include "i8042-sparcio.h"
|
||||
#elif defined(CONFIG_X86) || defined(CONFIG_IA64)
|
||||
#include "i8042-x86ia64io.h"
|
||||
#else
|
||||
#include "i8042-io.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is in 50us units, the time we wait for the i8042 to react. This
|
||||
* has to be long enough for the i8042 itself to timeout on sending a byte
|
||||
* to a non-existent mouse.
|
||||
*/
|
||||
|
||||
#define I8042_CTL_TIMEOUT 10000
|
||||
|
||||
/*
|
||||
* Status register bits.
|
||||
*/
|
||||
|
||||
#define I8042_STR_PARITY 0x80
|
||||
#define I8042_STR_TIMEOUT 0x40
|
||||
#define I8042_STR_AUXDATA 0x20
|
||||
#define I8042_STR_KEYLOCK 0x10
|
||||
#define I8042_STR_CMDDAT 0x08
|
||||
#define I8042_STR_MUXERR 0x04
|
||||
#define I8042_STR_IBF 0x02
|
||||
#define I8042_STR_OBF 0x01
|
||||
|
||||
/*
|
||||
* Control register bits.
|
||||
*/
|
||||
|
||||
#define I8042_CTR_KBDINT 0x01
|
||||
#define I8042_CTR_AUXINT 0x02
|
||||
#define I8042_CTR_IGNKEYLOCK 0x08
|
||||
#define I8042_CTR_KBDDIS 0x10
|
||||
#define I8042_CTR_AUXDIS 0x20
|
||||
#define I8042_CTR_XLATE 0x40
|
||||
|
||||
/*
|
||||
* Commands.
|
||||
*/
|
||||
|
||||
#define I8042_CMD_CTL_RCTR 0x0120
|
||||
#define I8042_CMD_CTL_WCTR 0x1060
|
||||
#define I8042_CMD_CTL_TEST 0x01aa
|
||||
|
||||
#define I8042_CMD_KBD_DISABLE 0x00ad
|
||||
#define I8042_CMD_KBD_ENABLE 0x00ae
|
||||
#define I8042_CMD_KBD_TEST 0x01ab
|
||||
#define I8042_CMD_KBD_LOOP 0x11d2
|
||||
|
||||
#define I8042_CMD_AUX_DISABLE 0x00a7
|
||||
#define I8042_CMD_AUX_ENABLE 0x00a8
|
||||
#define I8042_CMD_AUX_TEST 0x01a9
|
||||
#define I8042_CMD_AUX_SEND 0x10d4
|
||||
#define I8042_CMD_AUX_LOOP 0x11d3
|
||||
|
||||
#define I8042_CMD_MUX_PFX 0x0090
|
||||
#define I8042_CMD_MUX_SEND 0x1090
|
||||
|
||||
/*
|
||||
* Return codes.
|
||||
*/
|
||||
|
||||
#define I8042_RET_CTL_TEST 0x55
|
||||
|
||||
/*
|
||||
* Expected maximum internal i8042 buffer size. This is used for flushing
|
||||
* the i8042 buffers.
|
||||
*/
|
||||
|
||||
#define I8042_BUFFER_SIZE 16
|
||||
|
||||
/*
|
||||
* Number of AUX ports on controllers supporting active multiplexing
|
||||
* specification
|
||||
*/
|
||||
|
||||
#define I8042_NUM_MUX_PORTS 4
|
||||
|
||||
/*
|
||||
* Debug.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG
|
||||
static unsigned long i8042_start_time;
|
||||
#define dbg_init() do { i8042_start_time = jiffies; } while (0)
|
||||
#define dbg(format, arg...) \
|
||||
do { \
|
||||
if (i8042_debug) \
|
||||
printk(KERN_DEBUG __FILE__ ": " format " [%d]\n" , \
|
||||
## arg, (int) (jiffies - i8042_start_time)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define dbg_init() do { } while (0)
|
||||
#define dbg(format, arg...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#endif /* _I8042_H */
|
||||
387
drivers/input/serio/libps2.c
Normal file
387
drivers/input/serio/libps2.c
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* PS/2 driver library
|
||||
*
|
||||
* Copyright (c) 1999-2002 Vojtech Pavlik
|
||||
* Copyright (c) 2004 Dmitry Torokhov
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/libps2.h>
|
||||
|
||||
#define DRIVER_DESC "PS/2 driver library"
|
||||
|
||||
MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
|
||||
MODULE_DESCRIPTION("PS/2 driver library");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* Work structure to schedule execution of a command */
|
||||
struct ps2work {
|
||||
struct work_struct work;
|
||||
struct ps2dev *ps2dev;
|
||||
int command;
|
||||
unsigned char param[0];
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* ps2_sendbyte() sends a byte to the device and waits for acknowledge.
|
||||
* It doesn't handle retransmission, though it could - because if there
|
||||
* is a need for retransmissions device has to be replaced anyway.
|
||||
*
|
||||
* ps2_sendbyte() can only be called from a process context.
|
||||
*/
|
||||
|
||||
int ps2_sendbyte(struct ps2dev *ps2dev, unsigned char byte, int timeout)
|
||||
{
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->nak = 1;
|
||||
ps2dev->flags |= PS2_FLAG_ACK;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
|
||||
if (serio_write(ps2dev->serio, byte) == 0)
|
||||
wait_event_timeout(ps2dev->wait,
|
||||
!(ps2dev->flags & PS2_FLAG_ACK),
|
||||
msecs_to_jiffies(timeout));
|
||||
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags &= ~PS2_FLAG_ACK;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
|
||||
return -ps2dev->nak;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_sendbyte);
|
||||
|
||||
/*
|
||||
* ps2_drain() waits for device to transmit requested number of bytes
|
||||
* and discards them.
|
||||
*/
|
||||
|
||||
void ps2_drain(struct ps2dev *ps2dev, int maxbytes, int timeout)
|
||||
{
|
||||
if (maxbytes > sizeof(ps2dev->cmdbuf)) {
|
||||
WARN_ON(1);
|
||||
maxbytes = sizeof(ps2dev->cmdbuf);
|
||||
}
|
||||
|
||||
mutex_lock(&ps2dev->cmd_mutex);
|
||||
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags = PS2_FLAG_CMD;
|
||||
ps2dev->cmdcnt = maxbytes;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
|
||||
wait_event_timeout(ps2dev->wait,
|
||||
!(ps2dev->flags & PS2_FLAG_CMD),
|
||||
msecs_to_jiffies(timeout));
|
||||
mutex_unlock(&ps2dev->cmd_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_drain);
|
||||
|
||||
/*
|
||||
* ps2_is_keyboard_id() checks received ID byte against the list of
|
||||
* known keyboard IDs.
|
||||
*/
|
||||
|
||||
int ps2_is_keyboard_id(char id_byte)
|
||||
{
|
||||
static const char keyboard_ids[] = {
|
||||
0xab, /* Regular keyboards */
|
||||
0xac, /* NCD Sun keyboard */
|
||||
0x2b, /* Trust keyboard, translated */
|
||||
0x5d, /* Trust keyboard */
|
||||
0x60, /* NMB SGI keyboard, translated */
|
||||
0x47, /* NMB SGI keyboard */
|
||||
};
|
||||
|
||||
return memchr(keyboard_ids, id_byte, sizeof(keyboard_ids)) != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_is_keyboard_id);
|
||||
|
||||
/*
|
||||
* ps2_adjust_timeout() is called after receiving 1st byte of command
|
||||
* response and tries to reduce remaining timeout to speed up command
|
||||
* completion.
|
||||
*/
|
||||
|
||||
static int ps2_adjust_timeout(struct ps2dev *ps2dev, int command, int timeout)
|
||||
{
|
||||
switch (command) {
|
||||
case PS2_CMD_RESET_BAT:
|
||||
/*
|
||||
* Device has sent the first response byte after
|
||||
* reset command, reset is thus done, so we can
|
||||
* shorten the timeout.
|
||||
* The next byte will come soon (keyboard) or not
|
||||
* at all (mouse).
|
||||
*/
|
||||
if (timeout > msecs_to_jiffies(100))
|
||||
timeout = msecs_to_jiffies(100);
|
||||
break;
|
||||
|
||||
case PS2_CMD_GETID:
|
||||
/*
|
||||
* Microsoft Natural Elite keyboard responds to
|
||||
* the GET ID command as it were a mouse, with
|
||||
* a single byte. Fail the command so atkbd will
|
||||
* use alternative probe to detect it.
|
||||
*/
|
||||
if (ps2dev->cmdbuf[1] == 0xaa) {
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags = 0;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If device behind the port is not a keyboard there
|
||||
* won't be 2nd byte of ID response.
|
||||
*/
|
||||
if (!ps2_is_keyboard_id(ps2dev->cmdbuf[1])) {
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags = ps2dev->cmdcnt = 0;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
timeout = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/*
|
||||
* ps2_command() sends a command and its parameters to the mouse,
|
||||
* then waits for the response and puts it in the param array.
|
||||
*
|
||||
* ps2_command() can only be called from a process context
|
||||
*/
|
||||
|
||||
int ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command)
|
||||
{
|
||||
int timeout;
|
||||
int send = (command >> 12) & 0xf;
|
||||
int receive = (command >> 8) & 0xf;
|
||||
int rc = -1;
|
||||
int i;
|
||||
|
||||
if (receive > sizeof(ps2dev->cmdbuf)) {
|
||||
WARN_ON(1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (send && !param) {
|
||||
WARN_ON(1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
mutex_lock(&ps2dev->cmd_mutex);
|
||||
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags = command == PS2_CMD_GETID ? PS2_FLAG_WAITID : 0;
|
||||
ps2dev->cmdcnt = receive;
|
||||
if (receive && param)
|
||||
for (i = 0; i < receive; i++)
|
||||
ps2dev->cmdbuf[(receive - 1) - i] = param[i];
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
|
||||
/*
|
||||
* Some devices (Synaptics) peform the reset before
|
||||
* ACKing the reset command, and so it can take a long
|
||||
* time before the ACK arrrives.
|
||||
*/
|
||||
if (ps2_sendbyte(ps2dev, command & 0xff,
|
||||
command == PS2_CMD_RESET_BAT ? 1000 : 200))
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < send; i++)
|
||||
if (ps2_sendbyte(ps2dev, param[i], 200))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* The reset command takes a long time to execute.
|
||||
*/
|
||||
timeout = msecs_to_jiffies(command == PS2_CMD_RESET_BAT ? 4000 : 500);
|
||||
|
||||
timeout = wait_event_timeout(ps2dev->wait,
|
||||
!(ps2dev->flags & PS2_FLAG_CMD1), timeout);
|
||||
|
||||
if (ps2dev->cmdcnt && timeout > 0) {
|
||||
|
||||
timeout = ps2_adjust_timeout(ps2dev, command, timeout);
|
||||
wait_event_timeout(ps2dev->wait,
|
||||
!(ps2dev->flags & PS2_FLAG_CMD), timeout);
|
||||
}
|
||||
|
||||
if (param)
|
||||
for (i = 0; i < receive; i++)
|
||||
param[i] = ps2dev->cmdbuf[(receive - 1) - i];
|
||||
|
||||
if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1))
|
||||
goto out;
|
||||
|
||||
rc = 0;
|
||||
|
||||
out:
|
||||
serio_pause_rx(ps2dev->serio);
|
||||
ps2dev->flags = 0;
|
||||
serio_continue_rx(ps2dev->serio);
|
||||
|
||||
mutex_unlock(&ps2dev->cmd_mutex);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_command);
|
||||
|
||||
/*
|
||||
* ps2_execute_scheduled_command() sends a command, previously scheduled by
|
||||
* ps2_schedule_command(), to a PS/2 device (keyboard, mouse, etc.)
|
||||
*/
|
||||
|
||||
static void ps2_execute_scheduled_command(struct work_struct *work)
|
||||
{
|
||||
struct ps2work *ps2work = container_of(work, struct ps2work, work);
|
||||
|
||||
ps2_command(ps2work->ps2dev, ps2work->param, ps2work->command);
|
||||
kfree(ps2work);
|
||||
}
|
||||
|
||||
/*
|
||||
* ps2_schedule_command() allows to schedule delayed execution of a PS/2
|
||||
* command and can be used to issue a command from an interrupt or softirq
|
||||
* context.
|
||||
*/
|
||||
|
||||
int ps2_schedule_command(struct ps2dev *ps2dev, unsigned char *param, int command)
|
||||
{
|
||||
struct ps2work *ps2work;
|
||||
int send = (command >> 12) & 0xf;
|
||||
int receive = (command >> 8) & 0xf;
|
||||
|
||||
if (!(ps2work = kmalloc(sizeof(struct ps2work) + max(send, receive), GFP_ATOMIC)))
|
||||
return -1;
|
||||
|
||||
memset(ps2work, 0, sizeof(struct ps2work));
|
||||
ps2work->ps2dev = ps2dev;
|
||||
ps2work->command = command;
|
||||
memcpy(ps2work->param, param, send);
|
||||
INIT_WORK(&ps2work->work, ps2_execute_scheduled_command);
|
||||
|
||||
if (!schedule_work(&ps2work->work)) {
|
||||
kfree(ps2work);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_schedule_command);
|
||||
|
||||
/*
|
||||
* ps2_init() initializes ps2dev structure
|
||||
*/
|
||||
|
||||
void ps2_init(struct ps2dev *ps2dev, struct serio *serio)
|
||||
{
|
||||
mutex_init(&ps2dev->cmd_mutex);
|
||||
lockdep_set_subclass(&ps2dev->cmd_mutex, serio->depth);
|
||||
init_waitqueue_head(&ps2dev->wait);
|
||||
ps2dev->serio = serio;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_init);
|
||||
|
||||
/*
|
||||
* ps2_handle_ack() is supposed to be used in interrupt handler
|
||||
* to properly process ACK/NAK of a command from a PS/2 device.
|
||||
*/
|
||||
|
||||
int ps2_handle_ack(struct ps2dev *ps2dev, unsigned char data)
|
||||
{
|
||||
switch (data) {
|
||||
case PS2_RET_ACK:
|
||||
ps2dev->nak = 0;
|
||||
break;
|
||||
|
||||
case PS2_RET_NAK:
|
||||
ps2dev->nak = 1;
|
||||
break;
|
||||
|
||||
/*
|
||||
* Workaround for mice which don't ACK the Get ID command.
|
||||
* These are valid mouse IDs that we recognize.
|
||||
*/
|
||||
case 0x00:
|
||||
case 0x03:
|
||||
case 0x04:
|
||||
if (ps2dev->flags & PS2_FLAG_WAITID) {
|
||||
ps2dev->nak = 0;
|
||||
break;
|
||||
}
|
||||
/* Fall through */
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (!ps2dev->nak && ps2dev->cmdcnt)
|
||||
ps2dev->flags |= PS2_FLAG_CMD | PS2_FLAG_CMD1;
|
||||
|
||||
ps2dev->flags &= ~PS2_FLAG_ACK;
|
||||
wake_up(&ps2dev->wait);
|
||||
|
||||
if (data != PS2_RET_ACK)
|
||||
ps2_handle_response(ps2dev, data);
|
||||
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_handle_ack);
|
||||
|
||||
/*
|
||||
* ps2_handle_response() is supposed to be used in interrupt handler
|
||||
* to properly store device's response to a command and notify process
|
||||
* waiting for completion of the command.
|
||||
*/
|
||||
|
||||
int ps2_handle_response(struct ps2dev *ps2dev, unsigned char data)
|
||||
{
|
||||
if (ps2dev->cmdcnt)
|
||||
ps2dev->cmdbuf[--ps2dev->cmdcnt] = data;
|
||||
|
||||
if (ps2dev->flags & PS2_FLAG_CMD1) {
|
||||
ps2dev->flags &= ~PS2_FLAG_CMD1;
|
||||
if (ps2dev->cmdcnt)
|
||||
wake_up(&ps2dev->wait);
|
||||
}
|
||||
|
||||
if (!ps2dev->cmdcnt) {
|
||||
ps2dev->flags &= ~PS2_FLAG_CMD;
|
||||
wake_up(&ps2dev->wait);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_handle_response);
|
||||
|
||||
void ps2_cmd_aborted(struct ps2dev *ps2dev)
|
||||
{
|
||||
if (ps2dev->flags & PS2_FLAG_ACK)
|
||||
ps2dev->nak = 1;
|
||||
|
||||
if (ps2dev->flags & (PS2_FLAG_ACK | PS2_FLAG_CMD))
|
||||
wake_up(&ps2dev->wait);
|
||||
|
||||
ps2dev->flags = 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ps2_cmd_aborted);
|
||||
211
drivers/input/serio/maceps2.c
Normal file
211
drivers/input/serio/maceps2.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* SGI O2 MACE PS2 controller driver for linux
|
||||
*
|
||||
* Copyright (C) 2002 Vivien Chappelier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/ip32/mace.h>
|
||||
#include <asm/ip32/ip32_ints.h>
|
||||
|
||||
MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org");
|
||||
MODULE_DESCRIPTION("SGI O2 MACE PS2 controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define MACE_PS2_TIMEOUT 10000 /* in 50us unit */
|
||||
|
||||
#define PS2_STATUS_CLOCK_SIGNAL BIT(0) /* external clock signal */
|
||||
#define PS2_STATUS_CLOCK_INHIBIT BIT(1) /* clken output signal */
|
||||
#define PS2_STATUS_TX_INPROGRESS BIT(2) /* transmission in progress */
|
||||
#define PS2_STATUS_TX_EMPTY BIT(3) /* empty transmit buffer */
|
||||
#define PS2_STATUS_RX_FULL BIT(4) /* full receive buffer */
|
||||
#define PS2_STATUS_RX_INPROGRESS BIT(5) /* reception in progress */
|
||||
#define PS2_STATUS_ERROR_PARITY BIT(6) /* parity error */
|
||||
#define PS2_STATUS_ERROR_FRAMING BIT(7) /* framing error */
|
||||
|
||||
#define PS2_CONTROL_TX_CLOCK_DISABLE BIT(0) /* inhibit clock signal after TX */
|
||||
#define PS2_CONTROL_TX_ENABLE BIT(1) /* transmit enable */
|
||||
#define PS2_CONTROL_TX_INT_ENABLE BIT(2) /* enable transmit interrupt */
|
||||
#define PS2_CONTROL_RX_INT_ENABLE BIT(3) /* enable receive interrupt */
|
||||
#define PS2_CONTROL_RX_CLOCK_ENABLE BIT(4) /* pause reception if set to 0 */
|
||||
#define PS2_CONTROL_RESET BIT(5) /* reset */
|
||||
|
||||
struct maceps2_data {
|
||||
struct mace_ps2port *port;
|
||||
int irq;
|
||||
};
|
||||
|
||||
static struct maceps2_data port_data[2];
|
||||
static struct serio *maceps2_port[2];
|
||||
static struct platform_device *maceps2_device;
|
||||
|
||||
static int maceps2_write(struct serio *dev, unsigned char val)
|
||||
{
|
||||
struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port;
|
||||
unsigned int timeout = MACE_PS2_TIMEOUT;
|
||||
|
||||
do {
|
||||
if (port->status & PS2_STATUS_TX_EMPTY) {
|
||||
port->tx = val;
|
||||
return 0;
|
||||
}
|
||||
udelay(50);
|
||||
} while (timeout--);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static irqreturn_t maceps2_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct serio *dev = dev_id;
|
||||
struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port;
|
||||
unsigned long byte;
|
||||
|
||||
if (port->status & PS2_STATUS_RX_FULL) {
|
||||
byte = port->rx;
|
||||
serio_interrupt(dev, byte & 0xff, 0);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int maceps2_open(struct serio *dev)
|
||||
{
|
||||
struct maceps2_data *data = (struct maceps2_data *)dev->port_data;
|
||||
|
||||
if (request_irq(data->irq, maceps2_interrupt, 0, "PS2 port", dev)) {
|
||||
printk(KERN_ERR "Could not allocate PS/2 IRQ\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Reset port */
|
||||
data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET;
|
||||
udelay(100);
|
||||
|
||||
/* Enable interrupts */
|
||||
data->port->control = PS2_CONTROL_RX_CLOCK_ENABLE |
|
||||
PS2_CONTROL_TX_ENABLE |
|
||||
PS2_CONTROL_RX_INT_ENABLE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void maceps2_close(struct serio *dev)
|
||||
{
|
||||
struct maceps2_data *data = (struct maceps2_data *)dev->port_data;
|
||||
|
||||
data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET;
|
||||
udelay(100);
|
||||
free_irq(data->irq, dev);
|
||||
}
|
||||
|
||||
|
||||
static struct serio * __devinit maceps2_allocate_port(int idx)
|
||||
{
|
||||
struct serio *serio;
|
||||
|
||||
serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (serio) {
|
||||
serio->id.type = SERIO_8042;
|
||||
serio->write = maceps2_write;
|
||||
serio->open = maceps2_open;
|
||||
serio->close = maceps2_close;
|
||||
snprintf(serio->name, sizeof(serio->name), "MACE PS/2 port%d", idx);
|
||||
snprintf(serio->phys, sizeof(serio->phys), "mace/serio%d", idx);
|
||||
serio->port_data = &port_data[idx];
|
||||
serio->dev.parent = &maceps2_device->dev;
|
||||
}
|
||||
|
||||
return serio;
|
||||
}
|
||||
|
||||
static int __devinit maceps2_probe(struct platform_device *dev)
|
||||
{
|
||||
maceps2_port[0] = maceps2_allocate_port(0);
|
||||
maceps2_port[1] = maceps2_allocate_port(1);
|
||||
if (!maceps2_port[0] || !maceps2_port[1]) {
|
||||
kfree(maceps2_port[0]);
|
||||
kfree(maceps2_port[1]);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
serio_register_port(maceps2_port[0]);
|
||||
serio_register_port(maceps2_port[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit maceps2_remove(struct platform_device *dev)
|
||||
{
|
||||
serio_unregister_port(maceps2_port[0]);
|
||||
serio_unregister_port(maceps2_port[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver maceps2_driver = {
|
||||
.driver = {
|
||||
.name = "maceps2",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = maceps2_probe,
|
||||
.remove = __devexit_p(maceps2_remove),
|
||||
};
|
||||
|
||||
static int __init maceps2_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = platform_driver_register(&maceps2_driver);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
maceps2_device = platform_device_alloc("maceps2", -1);
|
||||
if (!maceps2_device) {
|
||||
error = -ENOMEM;
|
||||
goto err_unregister_driver;
|
||||
}
|
||||
|
||||
port_data[0].port = &mace->perif.ps2.keyb;
|
||||
port_data[0].irq = MACEISA_KEYB_IRQ;
|
||||
port_data[1].port = &mace->perif.ps2.mouse;
|
||||
port_data[1].irq = MACEISA_MOUSE_IRQ;
|
||||
|
||||
error = platform_device_add(maceps2_device);
|
||||
if (error)
|
||||
goto err_free_device;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_device:
|
||||
platform_device_put(maceps2_device);
|
||||
err_unregister_driver:
|
||||
platform_driver_unregister(&maceps2_driver);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit maceps2_exit(void)
|
||||
{
|
||||
platform_device_unregister(maceps2_device);
|
||||
platform_driver_unregister(&maceps2_driver);
|
||||
}
|
||||
|
||||
module_init(maceps2_init);
|
||||
module_exit(maceps2_exit);
|
||||
217
drivers/input/serio/parkbd.c
Normal file
217
drivers/input/serio/parkbd.c
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Parallel port to Keyboard port adapter driver for Linux
|
||||
*
|
||||
* Copyright (c) 1999-2004 Vojtech Pavlik
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To connect an AT or XT keyboard to the parallel port, a fairly simple adapter
|
||||
* can be made:
|
||||
*
|
||||
* Parallel port Keyboard port
|
||||
*
|
||||
* +5V --------------------- +5V (4)
|
||||
*
|
||||
* ______
|
||||
* +5V -------|______|--.
|
||||
* |
|
||||
* ACK (10) ------------|
|
||||
* |--- KBD CLOCK (5)
|
||||
* STROBE (1) ---|<|----'
|
||||
*
|
||||
* ______
|
||||
* +5V -------|______|--.
|
||||
* |
|
||||
* BUSY (11) -----------|
|
||||
* |--- KBD DATA (1)
|
||||
* AUTOFD (14) --|<|----'
|
||||
*
|
||||
* GND (18-25) ------------- GND (3)
|
||||
*
|
||||
* The diodes can be fairly any type, and the resistors should be somewhere
|
||||
* around 5 kOhm, but the adapter will likely work without the resistors,
|
||||
* too.
|
||||
*
|
||||
* The +5V source can be taken either from USB, from mouse or keyboard ports,
|
||||
* or from a joystick port. Unfortunately, the parallel port of a PC doesn't
|
||||
* have a +5V pin, and feeding the keyboard from signal pins is out of question
|
||||
* with 300 mA power reqirement of a typical AT keyboard.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/parport.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
|
||||
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static unsigned int parkbd_pp_no;
|
||||
module_param_named(port, parkbd_pp_no, int, 0);
|
||||
MODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)");
|
||||
|
||||
static unsigned int parkbd_mode = SERIO_8042;
|
||||
module_param_named(mode, parkbd_mode, uint, 0);
|
||||
MODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)");
|
||||
|
||||
#define PARKBD_CLOCK 0x01 /* Strobe & Ack */
|
||||
#define PARKBD_DATA 0x02 /* AutoFd & Busy */
|
||||
|
||||
static int parkbd_buffer;
|
||||
static int parkbd_counter;
|
||||
static unsigned long parkbd_last;
|
||||
static int parkbd_writing;
|
||||
static unsigned long parkbd_start;
|
||||
|
||||
static struct pardevice *parkbd_dev;
|
||||
static struct serio *parkbd_port;
|
||||
|
||||
static int parkbd_readlines(void)
|
||||
{
|
||||
return (parport_read_status(parkbd_dev->port) >> 6) ^ 2;
|
||||
}
|
||||
|
||||
static void parkbd_writelines(int data)
|
||||
{
|
||||
parport_write_control(parkbd_dev->port, (~data & 3) | 0x10);
|
||||
}
|
||||
|
||||
static int parkbd_write(struct serio *port, unsigned char c)
|
||||
{
|
||||
unsigned char p;
|
||||
|
||||
if (!parkbd_mode) return -1;
|
||||
|
||||
p = c ^ (c >> 4);
|
||||
p = p ^ (p >> 2);
|
||||
p = p ^ (p >> 1);
|
||||
|
||||
parkbd_counter = 0;
|
||||
parkbd_writing = 1;
|
||||
parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600;
|
||||
|
||||
parkbd_writelines(2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parkbd_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
|
||||
if (parkbd_writing) {
|
||||
|
||||
if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) {
|
||||
parkbd_counter = 0;
|
||||
parkbd_buffer = 0;
|
||||
parkbd_writing = 0;
|
||||
parkbd_writelines(3);
|
||||
return;
|
||||
}
|
||||
|
||||
parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2);
|
||||
|
||||
if (parkbd_counter == 11) {
|
||||
parkbd_counter = 0;
|
||||
parkbd_buffer = 0;
|
||||
parkbd_writing = 0;
|
||||
parkbd_writelines(3);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) {
|
||||
parkbd_counter = 0;
|
||||
parkbd_buffer = 0;
|
||||
}
|
||||
|
||||
parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++;
|
||||
|
||||
if (parkbd_counter == parkbd_mode + 10)
|
||||
serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0);
|
||||
}
|
||||
|
||||
parkbd_last = jiffies;
|
||||
}
|
||||
|
||||
static int parkbd_getport(void)
|
||||
{
|
||||
struct parport *pp;
|
||||
|
||||
pp = parport_find_number(parkbd_pp_no);
|
||||
|
||||
if (pp == NULL) {
|
||||
printk(KERN_ERR "parkbd: no such parport\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
parkbd_dev = parport_register_device(pp, "parkbd", NULL, NULL, parkbd_interrupt, PARPORT_DEV_EXCL, NULL);
|
||||
parport_put_port(pp);
|
||||
|
||||
if (!parkbd_dev)
|
||||
return -ENODEV;
|
||||
|
||||
if (parport_claim(parkbd_dev)) {
|
||||
parport_unregister_device(parkbd_dev);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
parkbd_start = jiffies;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct serio * __init parkbd_allocate_serio(void)
|
||||
{
|
||||
struct serio *serio;
|
||||
|
||||
serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (serio) {
|
||||
serio->id.type = parkbd_mode;
|
||||
serio->write = parkbd_write,
|
||||
strlcpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name));
|
||||
snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name);
|
||||
}
|
||||
|
||||
return serio;
|
||||
}
|
||||
|
||||
static int __init parkbd_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = parkbd_getport();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
parkbd_port = parkbd_allocate_serio();
|
||||
if (!parkbd_port) {
|
||||
parport_release(parkbd_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
parkbd_writelines(3);
|
||||
|
||||
serio_register_port(parkbd_port);
|
||||
|
||||
printk(KERN_INFO "serio: PARKBD %s adapter on %s\n",
|
||||
parkbd_mode ? "AT" : "XT", parkbd_dev->port->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit parkbd_exit(void)
|
||||
{
|
||||
parport_release(parkbd_dev);
|
||||
serio_unregister_port(parkbd_port);
|
||||
parport_unregister_device(parkbd_dev);
|
||||
}
|
||||
|
||||
module_init(parkbd_init);
|
||||
module_exit(parkbd_exit);
|
||||
234
drivers/input/serio/pcips2.c
Normal file
234
drivers/input/serio/pcips2.c
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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 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);
|
||||
} 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);
|
||||
|
||||
ret = request_irq(ps2if->dev->irq, pcips2_interrupt, IRQF_SHARED,
|
||||
"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);
|
||||
189
drivers/input/serio/q40kbd.c
Normal file
189
drivers/input/serio/q40kbd.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* $Id: q40kbd.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* Copyright (c) 2000-2001 Vojtech Pavlik
|
||||
*
|
||||
* Based on the work of:
|
||||
* Richard Zidlicky <Richard.Zidlicky@stud.informatik.uni-erlangen.de>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Q40 PS/2 keyboard controller driver for Linux/m68k
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* Should you need to contact me, the author, you can do so either by
|
||||
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
|
||||
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/q40_master.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/q40ints.h>
|
||||
|
||||
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
DEFINE_SPINLOCK(q40kbd_lock);
|
||||
static struct serio *q40kbd_port;
|
||||
static struct platform_device *q40kbd_device;
|
||||
|
||||
static irqreturn_t q40kbd_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&q40kbd_lock, flags);
|
||||
|
||||
if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))
|
||||
serio_interrupt(q40kbd_port, master_inb(KEYCODE_REG), 0);
|
||||
|
||||
master_outb(-1, KEYBOARD_UNLOCK_REG);
|
||||
|
||||
spin_unlock_irqrestore(&q40kbd_lock, flags);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* q40kbd_flush() flushes all data that may be in the keyboard buffers
|
||||
*/
|
||||
|
||||
static void q40kbd_flush(void)
|
||||
{
|
||||
int maxread = 100;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&q40kbd_lock, flags);
|
||||
|
||||
while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)))
|
||||
master_inb(KEYCODE_REG);
|
||||
|
||||
spin_unlock_irqrestore(&q40kbd_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* q40kbd_open() is called when a port is open by the higher layer.
|
||||
* It allocates the interrupt and enables in in the chip.
|
||||
*/
|
||||
|
||||
static int q40kbd_open(struct serio *port)
|
||||
{
|
||||
q40kbd_flush();
|
||||
|
||||
if (request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, "q40kbd", NULL)) {
|
||||
printk(KERN_ERR "q40kbd.c: Can't get irq %d.\n", Q40_IRQ_KEYBOARD);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* off we go */
|
||||
master_outb(-1, KEYBOARD_UNLOCK_REG);
|
||||
master_outb(1, KEY_IRQ_ENABLE_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void q40kbd_close(struct serio *port)
|
||||
{
|
||||
master_outb(0, KEY_IRQ_ENABLE_REG);
|
||||
master_outb(-1, KEYBOARD_UNLOCK_REG);
|
||||
free_irq(Q40_IRQ_KEYBOARD, NULL);
|
||||
|
||||
q40kbd_flush();
|
||||
}
|
||||
|
||||
static int __devinit q40kbd_probe(struct platform_device *dev)
|
||||
{
|
||||
q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!q40kbd_port)
|
||||
return -ENOMEM;
|
||||
|
||||
q40kbd_port->id.type = SERIO_8042;
|
||||
q40kbd_port->open = q40kbd_open;
|
||||
q40kbd_port->close = q40kbd_close;
|
||||
q40kbd_port->dev.parent = &dev->dev;
|
||||
strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));
|
||||
strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));
|
||||
|
||||
serio_register_port(q40kbd_port);
|
||||
printk(KERN_INFO "serio: Q40 kbd registered\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit q40kbd_remove(struct platform_device *dev)
|
||||
{
|
||||
serio_unregister_port(q40kbd_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver q40kbd_driver = {
|
||||
.driver = {
|
||||
.name = "q40kbd",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = q40kbd_probe,
|
||||
.remove = __devexit_p(q40kbd_remove),
|
||||
};
|
||||
|
||||
static int __init q40kbd_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!MACH_IS_Q40)
|
||||
return -EIO;
|
||||
|
||||
error = platform_driver_register(&q40kbd_driver);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
q40kbd_device = platform_device_alloc("q40kbd", -1);
|
||||
if (!q40kbd_device)
|
||||
goto err_unregister_driver;
|
||||
|
||||
error = platform_device_add(q40kbd_device);
|
||||
if (error)
|
||||
goto err_free_device;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_device:
|
||||
platform_device_put(q40kbd_device);
|
||||
err_unregister_driver:
|
||||
platform_driver_unregister(&q40kbd_driver);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit q40kbd_exit(void)
|
||||
{
|
||||
platform_device_unregister(q40kbd_device);
|
||||
platform_driver_unregister(&q40kbd_driver);
|
||||
}
|
||||
|
||||
module_init(q40kbd_init);
|
||||
module_exit(q40kbd_exit);
|
||||
157
drivers/input/serio/rpckbd.c
Normal file
157
drivers/input/serio/rpckbd.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* $Id: rpckbd.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* Copyright (c) 2000-2001 Vojtech Pavlik
|
||||
* Copyright (c) 2002 Russell King
|
||||
*/
|
||||
|
||||
/*
|
||||
* Acorn RiscPC PS/2 keyboard controller driver for Linux/ARM
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* Should you need to contact me, the author, you can do so either by
|
||||
* e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
|
||||
* Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/hardware/iomd.h>
|
||||
#include <asm/system.h>
|
||||
|
||||
MODULE_AUTHOR("Vojtech Pavlik, Russell King");
|
||||
MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int rpckbd_write(struct serio *port, unsigned char val)
|
||||
{
|
||||
while (!(iomd_readb(IOMD_KCTRL) & (1 << 7)))
|
||||
cpu_relax();
|
||||
|
||||
iomd_writeb(val, IOMD_KARTTX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t rpckbd_rx(int irq, void *dev_id)
|
||||
{
|
||||
struct serio *port = dev_id;
|
||||
unsigned int byte;
|
||||
int handled = IRQ_NONE;
|
||||
|
||||
while (iomd_readb(IOMD_KCTRL) & (1 << 5)) {
|
||||
byte = iomd_readb(IOMD_KARTRX);
|
||||
|
||||
serio_interrupt(port, byte, 0);
|
||||
handled = IRQ_HANDLED;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
static irqreturn_t rpckbd_tx(int irq, void *dev_id)
|
||||
{
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int rpckbd_open(struct serio *port)
|
||||
{
|
||||
/* Reset the keyboard state machine. */
|
||||
iomd_writeb(0, IOMD_KCTRL);
|
||||
iomd_writeb(8, IOMD_KCTRL);
|
||||
iomd_readb(IOMD_KARTRX);
|
||||
|
||||
if (request_irq(IRQ_KEYBOARDRX, rpckbd_rx, 0, "rpckbd", port) != 0) {
|
||||
printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (request_irq(IRQ_KEYBOARDTX, rpckbd_tx, 0, "rpckbd", port) != 0) {
|
||||
printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ\n");
|
||||
free_irq(IRQ_KEYBOARDRX, NULL);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rpckbd_close(struct serio *port)
|
||||
{
|
||||
free_irq(IRQ_KEYBOARDRX, port);
|
||||
free_irq(IRQ_KEYBOARDTX, port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate and initialize serio structure for subsequent registration
|
||||
* with serio core.
|
||||
*/
|
||||
static int __devinit rpckbd_probe(struct platform_device *dev)
|
||||
{
|
||||
struct serio *serio;
|
||||
|
||||
serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!serio)
|
||||
return -ENOMEM;
|
||||
|
||||
serio->id.type = SERIO_8042;
|
||||
serio->write = rpckbd_write;
|
||||
serio->open = rpckbd_open;
|
||||
serio->close = rpckbd_close;
|
||||
serio->dev.parent = &dev->dev;
|
||||
strlcpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name));
|
||||
strlcpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys));
|
||||
|
||||
platform_set_drvdata(dev, serio);
|
||||
serio_register_port(serio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit rpckbd_remove(struct platform_device *dev)
|
||||
{
|
||||
struct serio *serio = platform_get_drvdata(dev);
|
||||
serio_unregister_port(serio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rpckbd_driver = {
|
||||
.probe = rpckbd_probe,
|
||||
.remove = __devexit_p(rpckbd_remove),
|
||||
.driver = {
|
||||
.name = "kart",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init rpckbd_init(void)
|
||||
{
|
||||
return platform_driver_register(&rpckbd_driver);
|
||||
}
|
||||
|
||||
static void __exit rpckbd_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rpckbd_driver);
|
||||
}
|
||||
|
||||
module_init(rpckbd_init);
|
||||
module_exit(rpckbd_exit);
|
||||
358
drivers/input/serio/sa1111ps2.c
Normal file
358
drivers/input/serio/sa1111ps2.c
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* linux/drivers/input/serio/sa1111ps2.c
|
||||
*
|
||||
* Copyright (C) 2002 Russell King
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
|
||||
#include <asm/hardware/sa1111.h>
|
||||
|
||||
struct ps2if {
|
||||
struct serio *io;
|
||||
struct sa1111_dev *dev;
|
||||
void __iomem *base;
|
||||
unsigned int open;
|
||||
spinlock_t lock;
|
||||
unsigned int head;
|
||||
unsigned int tail;
|
||||
unsigned char buf[4];
|
||||
};
|
||||
|
||||
/*
|
||||
* Read all bytes waiting in the PS2 port. There should be
|
||||
* at the most one, but we loop for safety. If there was a
|
||||
* framing error, we have to manually clear the status.
|
||||
*/
|
||||
static irqreturn_t ps2_rxint(int irq, void *dev_id)
|
||||
{
|
||||
struct ps2if *ps2if = dev_id;
|
||||
unsigned int scancode, flag, status;
|
||||
|
||||
status = sa1111_readl(ps2if->base + SA1111_PS2STAT);
|
||||
while (status & PS2STAT_RXF) {
|
||||
if (status & PS2STAT_STP)
|
||||
sa1111_writel(PS2STAT_STP, ps2if->base + SA1111_PS2STAT);
|
||||
|
||||
flag = (status & PS2STAT_STP ? SERIO_FRAME : 0) |
|
||||
(status & PS2STAT_RXP ? 0 : SERIO_PARITY);
|
||||
|
||||
scancode = sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff;
|
||||
|
||||
if (hweight8(scancode) & 1)
|
||||
flag ^= SERIO_PARITY;
|
||||
|
||||
serio_interrupt(ps2if->io, scancode, flag);
|
||||
|
||||
status = sa1111_readl(ps2if->base + SA1111_PS2STAT);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Completion of ps2 write
|
||||
*/
|
||||
static irqreturn_t ps2_txint(int irq, void *dev_id)
|
||||
{
|
||||
struct ps2if *ps2if = dev_id;
|
||||
unsigned int status;
|
||||
|
||||
spin_lock(&ps2if->lock);
|
||||
status = sa1111_readl(ps2if->base + SA1111_PS2STAT);
|
||||
if (ps2if->head == ps2if->tail) {
|
||||
disable_irq(irq);
|
||||
/* done */
|
||||
} else if (status & PS2STAT_TXE) {
|
||||
sa1111_writel(ps2if->buf[ps2if->tail], ps2if->base + SA1111_PS2DATA);
|
||||
ps2if->tail = (ps2if->tail + 1) & (sizeof(ps2if->buf) - 1);
|
||||
}
|
||||
spin_unlock(&ps2if->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a byte to the PS2 port. We have to wait for the
|
||||
* port to indicate that the transmitter is empty.
|
||||
*/
|
||||
static int ps2_write(struct serio *io, unsigned char val)
|
||||
{
|
||||
struct ps2if *ps2if = io->port_data;
|
||||
unsigned long flags;
|
||||
unsigned int head;
|
||||
|
||||
spin_lock_irqsave(&ps2if->lock, flags);
|
||||
|
||||
/*
|
||||
* If the TX register is empty, we can go straight out.
|
||||
*/
|
||||
if (sa1111_readl(ps2if->base + SA1111_PS2STAT) & PS2STAT_TXE) {
|
||||
sa1111_writel(val, ps2if->base + SA1111_PS2DATA);
|
||||
} else {
|
||||
if (ps2if->head == ps2if->tail)
|
||||
enable_irq(ps2if->dev->irq[1]);
|
||||
head = (ps2if->head + 1) & (sizeof(ps2if->buf) - 1);
|
||||
if (head != ps2if->tail) {
|
||||
ps2if->buf[ps2if->head] = val;
|
||||
ps2if->head = head;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&ps2if->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ps2_open(struct serio *io)
|
||||
{
|
||||
struct ps2if *ps2if = io->port_data;
|
||||
int ret;
|
||||
|
||||
sa1111_enable_device(ps2if->dev);
|
||||
|
||||
ret = request_irq(ps2if->dev->irq[0], ps2_rxint, 0,
|
||||
SA1111_DRIVER_NAME(ps2if->dev), ps2if);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n",
|
||||
ps2if->dev->irq[0], ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_irq(ps2if->dev->irq[1], ps2_txint, 0,
|
||||
SA1111_DRIVER_NAME(ps2if->dev), ps2if);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n",
|
||||
ps2if->dev->irq[1], ret);
|
||||
free_irq(ps2if->dev->irq[0], ps2if);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ps2if->open = 1;
|
||||
|
||||
enable_irq_wake(ps2if->dev->irq[0]);
|
||||
|
||||
sa1111_writel(PS2CR_ENA, ps2if->base + SA1111_PS2CR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ps2_close(struct serio *io)
|
||||
{
|
||||
struct ps2if *ps2if = io->port_data;
|
||||
|
||||
sa1111_writel(0, ps2if->base + SA1111_PS2CR);
|
||||
|
||||
disable_irq_wake(ps2if->dev->irq[0]);
|
||||
|
||||
ps2if->open = 0;
|
||||
|
||||
free_irq(ps2if->dev->irq[1], ps2if);
|
||||
free_irq(ps2if->dev->irq[0], ps2if);
|
||||
|
||||
sa1111_disable_device(ps2if->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the input buffer.
|
||||
*/
|
||||
static void __init ps2_clear_input(struct ps2if *ps2if)
|
||||
{
|
||||
int maxread = 100;
|
||||
|
||||
while (maxread--) {
|
||||
if ((sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff) == 0xff)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
ps2_test_one(struct ps2if *ps2if, unsigned int mask)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
sa1111_writel(PS2CR_ENA | mask, ps2if->base + SA1111_PS2CR);
|
||||
|
||||
udelay(2);
|
||||
|
||||
val = sa1111_readl(ps2if->base + SA1111_PS2STAT);
|
||||
return val & (PS2STAT_KBC | PS2STAT_KBD);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the keyboard interface. We basically check to make sure that
|
||||
* we can drive each line to the keyboard independently of each other.
|
||||
*/
|
||||
static int __init ps2_test(struct ps2if *ps2if)
|
||||
{
|
||||
unsigned int stat;
|
||||
int ret = 0;
|
||||
|
||||
stat = ps2_test_one(ps2if, PS2CR_FKC);
|
||||
if (stat != PS2STAT_KBD) {
|
||||
printk("PS/2 interface test failed[1]: %02x\n", stat);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
stat = ps2_test_one(ps2if, 0);
|
||||
if (stat != (PS2STAT_KBC | PS2STAT_KBD)) {
|
||||
printk("PS/2 interface test failed[2]: %02x\n", stat);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
stat = ps2_test_one(ps2if, PS2CR_FKD);
|
||||
if (stat != PS2STAT_KBC) {
|
||||
printk("PS/2 interface test failed[3]: %02x\n", stat);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
sa1111_writel(0, ps2if->base + SA1111_PS2CR);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add one device to this driver.
|
||||
*/
|
||||
static int ps2_probe(struct sa1111_dev *dev)
|
||||
{
|
||||
struct ps2if *ps2if;
|
||||
struct serio *serio;
|
||||
int ret;
|
||||
|
||||
ps2if = kmalloc(sizeof(struct ps2if), GFP_KERNEL);
|
||||
serio = kmalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!ps2if || !serio) {
|
||||
ret = -ENOMEM;
|
||||
goto free;
|
||||
}
|
||||
|
||||
memset(ps2if, 0, sizeof(struct ps2if));
|
||||
memset(serio, 0, sizeof(struct serio));
|
||||
|
||||
serio->id.type = SERIO_8042;
|
||||
serio->write = ps2_write;
|
||||
serio->open = ps2_open;
|
||||
serio->close = ps2_close;
|
||||
strlcpy(serio->name, dev->dev.bus_id, 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;
|
||||
sa1111_set_drvdata(dev, ps2if);
|
||||
|
||||
spin_lock_init(&ps2if->lock);
|
||||
|
||||
/*
|
||||
* Request the physical region for this PS2 port.
|
||||
*/
|
||||
if (!request_mem_region(dev->res.start,
|
||||
dev->res.end - dev->res.start + 1,
|
||||
SA1111_DRIVER_NAME(dev))) {
|
||||
ret = -EBUSY;
|
||||
goto free;
|
||||
}
|
||||
|
||||
/*
|
||||
* Our parent device has already mapped the region.
|
||||
*/
|
||||
ps2if->base = dev->mapbase;
|
||||
|
||||
sa1111_enable_device(ps2if->dev);
|
||||
|
||||
/* Incoming clock is 8MHz */
|
||||
sa1111_writel(0, ps2if->base + SA1111_PS2CLKDIV);
|
||||
sa1111_writel(127, ps2if->base + SA1111_PS2PRECNT);
|
||||
|
||||
/*
|
||||
* Flush any pending input.
|
||||
*/
|
||||
ps2_clear_input(ps2if);
|
||||
|
||||
/*
|
||||
* Test the keyboard interface.
|
||||
*/
|
||||
ret = ps2_test(ps2if);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Flush any pending input.
|
||||
*/
|
||||
ps2_clear_input(ps2if);
|
||||
|
||||
sa1111_disable_device(ps2if->dev);
|
||||
serio_register_port(ps2if->io);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
sa1111_disable_device(ps2if->dev);
|
||||
release_mem_region(dev->res.start,
|
||||
dev->res.end - dev->res.start + 1);
|
||||
free:
|
||||
sa1111_set_drvdata(dev, NULL);
|
||||
kfree(ps2if);
|
||||
kfree(serio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove one device from this driver.
|
||||
*/
|
||||
static int ps2_remove(struct sa1111_dev *dev)
|
||||
{
|
||||
struct ps2if *ps2if = sa1111_get_drvdata(dev);
|
||||
|
||||
serio_unregister_port(ps2if->io);
|
||||
release_mem_region(dev->res.start,
|
||||
dev->res.end - dev->res.start + 1);
|
||||
sa1111_set_drvdata(dev, NULL);
|
||||
|
||||
kfree(ps2if);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Our device driver structure
|
||||
*/
|
||||
static struct sa1111_driver ps2_driver = {
|
||||
.drv = {
|
||||
.name = "sa1111-ps2",
|
||||
},
|
||||
.devid = SA1111_DEVID_PS2,
|
||||
.probe = ps2_probe,
|
||||
.remove = ps2_remove,
|
||||
};
|
||||
|
||||
static int __init ps2_init(void)
|
||||
{
|
||||
return sa1111_driver_register(&ps2_driver);
|
||||
}
|
||||
|
||||
static void __exit ps2_exit(void)
|
||||
{
|
||||
sa1111_driver_unregister(&ps2_driver);
|
||||
}
|
||||
|
||||
module_init(ps2_init);
|
||||
module_exit(ps2_exit);
|
||||
|
||||
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
|
||||
MODULE_DESCRIPTION("SA1111 PS2 controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
1042
drivers/input/serio/serio.c
Normal file
1042
drivers/input/serio/serio.c
Normal file
File diff suppressed because it is too large
Load Diff
401
drivers/input/serio/serio_raw.c
Normal file
401
drivers/input/serio/serio_raw.c
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Raw serio device providing access to a raw byte stream from underlying
|
||||
* serio port. Closely emulates behavior of pre-2.6 /dev/psaux device
|
||||
*
|
||||
* Copyright (c) 2004 Dmitry Torokhov
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#define DRIVER_DESC "Raw serio driver"
|
||||
|
||||
MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define SERIO_RAW_QUEUE_LEN 64
|
||||
struct serio_raw {
|
||||
unsigned char queue[SERIO_RAW_QUEUE_LEN];
|
||||
unsigned int tail, head;
|
||||
|
||||
char name[16];
|
||||
unsigned int refcnt;
|
||||
struct serio *serio;
|
||||
struct miscdevice dev;
|
||||
wait_queue_head_t wait;
|
||||
struct list_head list;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
struct serio_raw_list {
|
||||
struct fasync_struct *fasync;
|
||||
struct serio_raw *serio_raw;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(serio_raw_mutex);
|
||||
static LIST_HEAD(serio_raw_list);
|
||||
static unsigned int serio_raw_no;
|
||||
|
||||
/*********************************************************************
|
||||
* Interface with userspace (file operations) *
|
||||
*********************************************************************/
|
||||
|
||||
static int serio_raw_fasync(int fd, struct file *file, int on)
|
||||
{
|
||||
struct serio_raw_list *list = file->private_data;
|
||||
int retval;
|
||||
|
||||
retval = fasync_helper(fd, file, on, &list->fasync);
|
||||
return retval < 0 ? retval : 0;
|
||||
}
|
||||
|
||||
static struct serio_raw *serio_raw_locate(int minor)
|
||||
{
|
||||
struct serio_raw *serio_raw;
|
||||
|
||||
list_for_each_entry(serio_raw, &serio_raw_list, node) {
|
||||
if (serio_raw->dev.minor == minor)
|
||||
return serio_raw;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int serio_raw_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct serio_raw *serio_raw;
|
||||
struct serio_raw_list *list;
|
||||
int retval = 0;
|
||||
|
||||
retval = mutex_lock_interruptible(&serio_raw_mutex);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!(serio_raw = serio_raw_locate(iminor(inode)))) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!serio_raw->serio) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(list = kzalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) {
|
||||
retval = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
list->serio_raw = serio_raw;
|
||||
file->private_data = list;
|
||||
|
||||
serio_raw->refcnt++;
|
||||
list_add_tail(&list->node, &serio_raw->list);
|
||||
|
||||
out:
|
||||
mutex_unlock(&serio_raw_mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int serio_raw_cleanup(struct serio_raw *serio_raw)
|
||||
{
|
||||
if (--serio_raw->refcnt == 0) {
|
||||
misc_deregister(&serio_raw->dev);
|
||||
list_del_init(&serio_raw->node);
|
||||
kfree(serio_raw);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serio_raw_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct serio_raw_list *list = file->private_data;
|
||||
struct serio_raw *serio_raw = list->serio_raw;
|
||||
|
||||
mutex_lock(&serio_raw_mutex);
|
||||
|
||||
serio_raw_fasync(-1, file, 0);
|
||||
serio_raw_cleanup(serio_raw);
|
||||
|
||||
mutex_unlock(&serio_raw_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c)
|
||||
{
|
||||
unsigned long flags;
|
||||
int empty;
|
||||
|
||||
spin_lock_irqsave(&serio_raw->serio->lock, flags);
|
||||
|
||||
empty = serio_raw->head == serio_raw->tail;
|
||||
if (!empty) {
|
||||
*c = serio_raw->queue[serio_raw->tail];
|
||||
serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&serio_raw->serio->lock, flags);
|
||||
|
||||
return !empty;
|
||||
}
|
||||
|
||||
static ssize_t serio_raw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct serio_raw_list *list = file->private_data;
|
||||
struct serio_raw *serio_raw = list->serio_raw;
|
||||
char c;
|
||||
ssize_t retval = 0;
|
||||
|
||||
if (!serio_raw->serio)
|
||||
return -ENODEV;
|
||||
|
||||
if (serio_raw->head == serio_raw->tail && (file->f_flags & O_NONBLOCK))
|
||||
return -EAGAIN;
|
||||
|
||||
retval = wait_event_interruptible(list->serio_raw->wait,
|
||||
serio_raw->head != serio_raw->tail || !serio_raw->serio);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!serio_raw->serio)
|
||||
return -ENODEV;
|
||||
|
||||
while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) {
|
||||
if (put_user(c, buffer++))
|
||||
return -EFAULT;
|
||||
retval++;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t serio_raw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct serio_raw_list *list = file->private_data;
|
||||
ssize_t written = 0;
|
||||
int retval;
|
||||
unsigned char c;
|
||||
|
||||
retval = mutex_lock_interruptible(&serio_raw_mutex);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!list->serio_raw->serio) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (count > 32)
|
||||
count = 32;
|
||||
|
||||
while (count--) {
|
||||
if (get_user(c, buffer++)) {
|
||||
retval = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (serio_write(list->serio_raw->serio, c)) {
|
||||
retval = -EIO;
|
||||
goto out;
|
||||
}
|
||||
written++;
|
||||
};
|
||||
|
||||
out:
|
||||
mutex_unlock(&serio_raw_mutex);
|
||||
return written;
|
||||
}
|
||||
|
||||
static unsigned int serio_raw_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct serio_raw_list *list = file->private_data;
|
||||
|
||||
poll_wait(file, &list->serio_raw->wait, wait);
|
||||
|
||||
if (list->serio_raw->head != list->serio_raw->tail)
|
||||
return POLLIN | POLLRDNORM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations serio_raw_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = serio_raw_open,
|
||||
.release = serio_raw_release,
|
||||
.read = serio_raw_read,
|
||||
.write = serio_raw_write,
|
||||
.poll = serio_raw_poll,
|
||||
.fasync = serio_raw_fasync,
|
||||
};
|
||||
|
||||
|
||||
/*********************************************************************
|
||||
* Interface with serio port *
|
||||
*********************************************************************/
|
||||
|
||||
static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data,
|
||||
unsigned int dfl)
|
||||
{
|
||||
struct serio_raw *serio_raw = serio_get_drvdata(serio);
|
||||
struct serio_raw_list *list;
|
||||
unsigned int head = serio_raw->head;
|
||||
|
||||
/* we are holding serio->lock here so we are prootected */
|
||||
serio_raw->queue[head] = data;
|
||||
head = (head + 1) % SERIO_RAW_QUEUE_LEN;
|
||||
if (likely(head != serio_raw->tail)) {
|
||||
serio_raw->head = head;
|
||||
list_for_each_entry(list, &serio_raw->list, node)
|
||||
kill_fasync(&list->fasync, SIGIO, POLL_IN);
|
||||
wake_up_interruptible(&serio_raw->wait);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int serio_raw_connect(struct serio *serio, struct serio_driver *drv)
|
||||
{
|
||||
struct serio_raw *serio_raw;
|
||||
int err;
|
||||
|
||||
if (!(serio_raw = kzalloc(sizeof(struct serio_raw), GFP_KERNEL))) {
|
||||
printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mutex_lock(&serio_raw_mutex);
|
||||
|
||||
snprintf(serio_raw->name, sizeof(serio_raw->name), "serio_raw%d", serio_raw_no++);
|
||||
serio_raw->refcnt = 1;
|
||||
serio_raw->serio = serio;
|
||||
INIT_LIST_HEAD(&serio_raw->list);
|
||||
init_waitqueue_head(&serio_raw->wait);
|
||||
|
||||
serio_set_drvdata(serio, serio_raw);
|
||||
|
||||
err = serio_open(serio, drv);
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
list_add_tail(&serio_raw->node, &serio_raw_list);
|
||||
|
||||
serio_raw->dev.minor = PSMOUSE_MINOR;
|
||||
serio_raw->dev.name = serio_raw->name;
|
||||
serio_raw->dev.parent = &serio->dev;
|
||||
serio_raw->dev.fops = &serio_raw_fops;
|
||||
|
||||
err = misc_register(&serio_raw->dev);
|
||||
if (err) {
|
||||
serio_raw->dev.minor = MISC_DYNAMIC_MINOR;
|
||||
err = misc_register(&serio_raw->dev);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
printk(KERN_INFO "serio_raw: failed to register raw access device for %s\n",
|
||||
serio->phys);
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "serio_raw: raw access enabled on %s (%s, minor %d)\n",
|
||||
serio->phys, serio_raw->name, serio_raw->dev.minor);
|
||||
goto out;
|
||||
|
||||
out_close:
|
||||
serio_close(serio);
|
||||
list_del_init(&serio_raw->node);
|
||||
out_free:
|
||||
serio_set_drvdata(serio, NULL);
|
||||
kfree(serio_raw);
|
||||
out:
|
||||
mutex_unlock(&serio_raw_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int serio_raw_reconnect(struct serio *serio)
|
||||
{
|
||||
struct serio_raw *serio_raw = serio_get_drvdata(serio);
|
||||
struct serio_driver *drv = serio->drv;
|
||||
|
||||
if (!drv || !serio_raw) {
|
||||
printk(KERN_DEBUG "serio_raw: reconnect request, but serio is disconnected, ignoring...\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Nothing needs to be done here, we just need this method to
|
||||
* keep the same device.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void serio_raw_disconnect(struct serio *serio)
|
||||
{
|
||||
struct serio_raw *serio_raw;
|
||||
|
||||
mutex_lock(&serio_raw_mutex);
|
||||
|
||||
serio_raw = serio_get_drvdata(serio);
|
||||
|
||||
serio_close(serio);
|
||||
serio_set_drvdata(serio, NULL);
|
||||
|
||||
serio_raw->serio = NULL;
|
||||
if (!serio_raw_cleanup(serio_raw))
|
||||
wake_up_interruptible(&serio_raw->wait);
|
||||
|
||||
mutex_unlock(&serio_raw_mutex);
|
||||
}
|
||||
|
||||
static struct serio_device_id serio_raw_serio_ids[] = {
|
||||
{
|
||||
.type = SERIO_8042,
|
||||
.proto = SERIO_ANY,
|
||||
.id = SERIO_ANY,
|
||||
.extra = SERIO_ANY,
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(serio, serio_raw_serio_ids);
|
||||
|
||||
static struct serio_driver serio_raw_drv = {
|
||||
.driver = {
|
||||
.name = "serio_raw",
|
||||
},
|
||||
.description = DRIVER_DESC,
|
||||
.id_table = serio_raw_serio_ids,
|
||||
.interrupt = serio_raw_interrupt,
|
||||
.connect = serio_raw_connect,
|
||||
.reconnect = serio_raw_reconnect,
|
||||
.disconnect = serio_raw_disconnect,
|
||||
.manual_bind = 1,
|
||||
};
|
||||
|
||||
static int __init serio_raw_init(void)
|
||||
{
|
||||
return serio_register_driver(&serio_raw_drv);
|
||||
}
|
||||
|
||||
static void __exit serio_raw_exit(void)
|
||||
{
|
||||
serio_unregister_driver(&serio_raw_drv);
|
||||
}
|
||||
|
||||
module_init(serio_raw_init);
|
||||
module_exit(serio_raw_exit);
|
||||
250
drivers/input/serio/serport.c
Normal file
250
drivers/input/serio/serport.c
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Input device TTY line discipline
|
||||
*
|
||||
* Copyright (c) 1999-2002 Vojtech Pavlik
|
||||
*
|
||||
* This is a module that converts a tty line into a much simpler
|
||||
* 'serial io port' abstraction that the input device drivers use.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/serio.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION("Input device TTY line discipline");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_LDISC(N_MOUSE);
|
||||
|
||||
#define SERPORT_BUSY 1
|
||||
#define SERPORT_ACTIVE 2
|
||||
#define SERPORT_DEAD 3
|
||||
|
||||
struct serport {
|
||||
struct tty_struct *tty;
|
||||
wait_queue_head_t wait;
|
||||
struct serio *serio;
|
||||
struct serio_device_id id;
|
||||
spinlock_t lock;
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* Callback functions from the serio code.
|
||||
*/
|
||||
|
||||
static int serport_serio_write(struct serio *serio, unsigned char data)
|
||||
{
|
||||
struct serport *serport = serio->port_data;
|
||||
return -(serport->tty->driver->write(serport->tty, &data, 1) != 1);
|
||||
}
|
||||
|
||||
static int serport_serio_open(struct serio *serio)
|
||||
{
|
||||
struct serport *serport = serio->port_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&serport->lock, flags);
|
||||
set_bit(SERPORT_ACTIVE, &serport->flags);
|
||||
spin_unlock_irqrestore(&serport->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void serport_serio_close(struct serio *serio)
|
||||
{
|
||||
struct serport *serport = serio->port_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&serport->lock, flags);
|
||||
clear_bit(SERPORT_ACTIVE, &serport->flags);
|
||||
set_bit(SERPORT_DEAD, &serport->flags);
|
||||
spin_unlock_irqrestore(&serport->lock, flags);
|
||||
|
||||
wake_up_interruptible(&serport->wait);
|
||||
}
|
||||
|
||||
/*
|
||||
* serport_ldisc_open() is the routine that is called upon setting our line
|
||||
* discipline on a tty. It prepares the serio struct.
|
||||
*/
|
||||
|
||||
static int serport_ldisc_open(struct tty_struct *tty)
|
||||
{
|
||||
struct serport *serport;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
serport = kzalloc(sizeof(struct serport), GFP_KERNEL);
|
||||
if (!serport)
|
||||
return -ENOMEM;
|
||||
|
||||
serport->tty = tty;
|
||||
spin_lock_init(&serport->lock);
|
||||
init_waitqueue_head(&serport->wait);
|
||||
|
||||
tty->disc_data = serport;
|
||||
tty->receive_room = 256;
|
||||
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* serport_ldisc_close() is the opposite of serport_ldisc_open()
|
||||
*/
|
||||
|
||||
static void serport_ldisc_close(struct tty_struct *tty)
|
||||
{
|
||||
struct serport *serport = (struct serport *) tty->disc_data;
|
||||
|
||||
kfree(serport);
|
||||
}
|
||||
|
||||
/*
|
||||
* serport_ldisc_receive() is called by the low level tty driver when characters
|
||||
* are ready for us. We forward the characters, one by one to the 'interrupt'
|
||||
* routine.
|
||||
*/
|
||||
|
||||
static void serport_ldisc_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
|
||||
{
|
||||
struct serport *serport = (struct serport*) tty->disc_data;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&serport->lock, flags);
|
||||
|
||||
if (!test_bit(SERPORT_ACTIVE, &serport->flags))
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
serio_interrupt(serport->serio, cp[i], 0);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&serport->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* serport_ldisc_read() just waits indefinitely if everything goes well.
|
||||
* However, when the serio driver closes the serio port, it finishes,
|
||||
* returning 0 characters.
|
||||
*/
|
||||
|
||||
static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, unsigned char __user * buf, size_t nr)
|
||||
{
|
||||
struct serport *serport = (struct serport*) tty->disc_data;
|
||||
struct serio *serio;
|
||||
char name[64];
|
||||
|
||||
if (test_and_set_bit(SERPORT_BUSY, &serport->flags))
|
||||
return -EBUSY;
|
||||
|
||||
serport->serio = serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
|
||||
if (!serio)
|
||||
return -ENOMEM;
|
||||
|
||||
strlcpy(serio->name, "Serial port", sizeof(serio->name));
|
||||
snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty, name));
|
||||
serio->id = serport->id;
|
||||
serio->id.type = SERIO_RS232;
|
||||
serio->write = serport_serio_write;
|
||||
serio->open = serport_serio_open;
|
||||
serio->close = serport_serio_close;
|
||||
serio->port_data = serport;
|
||||
|
||||
serio_register_port(serport->serio);
|
||||
printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty, name));
|
||||
|
||||
wait_event_interruptible(serport->wait, test_bit(SERPORT_DEAD, &serport->flags));
|
||||
serio_unregister_port(serport->serio);
|
||||
serport->serio = NULL;
|
||||
|
||||
clear_bit(SERPORT_DEAD, &serport->flags);
|
||||
clear_bit(SERPORT_BUSY, &serport->flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* serport_ldisc_ioctl() allows to set the port protocol, and device ID
|
||||
*/
|
||||
|
||||
static int serport_ldisc_ioctl(struct tty_struct * tty, struct file * file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct serport *serport = (struct serport*) tty->disc_data;
|
||||
unsigned long type;
|
||||
|
||||
if (cmd == SPIOCSTYPE) {
|
||||
if (get_user(type, (unsigned long __user *) arg))
|
||||
return -EFAULT;
|
||||
|
||||
serport->id.proto = type & 0x000000ff;
|
||||
serport->id.id = (type & 0x0000ff00) >> 8;
|
||||
serport->id.extra = (type & 0x00ff0000) >> 16;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void serport_ldisc_write_wakeup(struct tty_struct * tty)
|
||||
{
|
||||
struct serport *serport = (struct serport *) tty->disc_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&serport->lock, flags);
|
||||
if (test_bit(SERPORT_ACTIVE, &serport->flags))
|
||||
serio_drv_write_wakeup(serport->serio);
|
||||
spin_unlock_irqrestore(&serport->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* The line discipline structure.
|
||||
*/
|
||||
|
||||
static struct tty_ldisc serport_ldisc = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "input",
|
||||
.open = serport_ldisc_open,
|
||||
.close = serport_ldisc_close,
|
||||
.read = serport_ldisc_read,
|
||||
.ioctl = serport_ldisc_ioctl,
|
||||
.receive_buf = serport_ldisc_receive,
|
||||
.write_wakeup = serport_ldisc_write_wakeup
|
||||
};
|
||||
|
||||
/*
|
||||
* The functions for insering/removing us as a module.
|
||||
*/
|
||||
|
||||
static int __init serport_init(void)
|
||||
{
|
||||
int retval;
|
||||
retval = tty_register_ldisc(N_MOUSE, &serport_ldisc);
|
||||
if (retval)
|
||||
printk(KERN_ERR "serport.c: Error registering line discipline.\n");
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __exit serport_exit(void)
|
||||
{
|
||||
tty_unregister_ldisc(N_MOUSE);
|
||||
}
|
||||
|
||||
module_init(serport_init);
|
||||
module_exit(serport_exit);
|
||||
Reference in New Issue
Block a user