Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Input core configuration
#
config SERIO
tristate "Serial I/O support"
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.
config ARCH_MIGHT_HAVE_PC_SERIO
bool
help
Select this config option from the architecture Kconfig if
the architecture might use a PC serio device (i8042) to
communicate with keyboard, mouse, etc.
if SERIO
config SERIO_I8042
tristate "i8042 PC Keyboard controller"
default y
depends on ARCH_MIGHT_HAVE_PC_SERIO
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
depends on TTY
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.rst>
If unsure, say Y.
To compile this driver as a module, choose M here: the
module will be called serport.
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.rst>
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
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
depends on HAS_IOPORT
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_SGI_IOC3
tristate "SGI IOC3 PS/2 controller"
depends on SGI_MFD_IOC3
help
Say Y here if you have an SGI Onyx2, SGI Octane or IOC3 PCI card
and you want to attach and use a keyboard, mouse, or both.
To compile this driver as a module, choose M here: the
module will be called ioc3kbd.
config SERIO_LIBPS2
tristate "PS/2 driver library"
depends on SERIO_I8042 || SERIO_I8042=n
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.
config SERIO_ALTERA_PS2
tristate "Altera UP PS/2 controller"
depends on HAS_IOMEM
help
Say Y here if you have Altera University Program PS/2 ports.
To compile this driver as a module, choose M here: the
module will be called altera_ps2.
config SERIO_AMS_DELTA
tristate "Amstrad Delta (E3) mailboard support"
depends on MACH_AMS_DELTA
default y
help
Say Y here if you have an E3 and want to use its mailboard,
or any standard AT keyboard connected to the mailboard port.
When used for the E3 mailboard, a non-standard key table
must be loaded from userspace, possibly using udev extras
provided keymap helper utility.
To compile this driver as a module, choose M here;
the module will be called ams_delta_serio.
config SERIO_PS2MULT
tristate "TQC PS/2 multiplexer"
help
Say Y here if you have the PS/2 line multiplexer like the one
present on TQC boards.
To compile this driver as a module, choose M here: the
module will be called ps2mult.
config SERIO_ARC_PS2
tristate "ARC PS/2 support"
depends on HAS_IOMEM
help
Say Y here if you have an ARC FPGA platform with a PS/2
controller in it.
To compile this driver as a module, choose M here; the module
will be called arc_ps2.
config SERIO_APBPS2
tristate "GRLIB APBPS2 PS/2 keyboard/mouse controller"
depends on OF && HAS_IOMEM
help
Say Y here if you want support for GRLIB APBPS2 peripherals used
to connect to PS/2 keyboard and/or mouse.
To compile this driver as a module, choose M here: the module will
be called apbps2.
config SERIO_OLPC_APSP
tristate "OLPC AP-SP input support"
depends on ARCH_MMP || COMPILE_TEST
help
Say Y here if you want support for the keyboard and touchpad included
in the OLPC XO-1.75 and XO-4 laptops.
To compile this driver as a module, choose M here: the module will
be called olpc_apsp.
config HYPERV_KEYBOARD
tristate "Microsoft Synthetic Keyboard driver"
depends on HYPERV_VMBUS
default HYPERV_VMBUS
help
Select this option to enable the Hyper-V Keyboard driver.
To compile this driver as a module, choose M here: the module will
be called hyperv_keyboard.
config SERIO_SUN4I_PS2
tristate "Allwinner A10 PS/2 controller support"
depends on ARCH_SUNXI || COMPILE_TEST
help
This selects support for the PS/2 Host Controller on
Allwinner A10.
To compile this driver as a module, choose M here: the
module will be called sun4i-ps2.
config SERIO_GPIO_PS2
tristate "GPIO PS/2 bit banging driver"
depends on GPIOLIB
help
Say Y here if you want PS/2 bit banging support via GPIO.
To compile this driver as a module, choose M here: the
module will be called ps2-gpio.
If you are unsure, say N.
config USERIO
tristate "User space serio port driver support"
help
Say Y here if you want to support user level drivers for serio
subsystem accessible under char device 10:240 - /dev/userio. Using
this facility userspace programs can implement serio ports that
will be used by the standard in-kernel serio consumer drivers,
such as psmouse and atkbd.
To compile this driver as a module, choose M here: the module will be
called userio.
If you are unsure, say N.
endif
+33
View File
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-2.0
#
# 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_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_PS2MULT) += ps2mult.o
obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o
obj-$(CONFIG_SERIO_SGI_IOC3) += ioc3kbd.o
obj-$(CONFIG_SERIO_LIBPS2) += libps2.o
obj-$(CONFIG_SERIO_RAW) += serio_raw.o
obj-$(CONFIG_SERIO_AMS_DELTA) += ams_delta_serio.o
obj-$(CONFIG_SERIO_ALTERA_PS2) += altera_ps2.o
obj-$(CONFIG_SERIO_ARC_PS2) += arc_ps2.o
obj-$(CONFIG_SERIO_APBPS2) += apbps2.o
obj-$(CONFIG_SERIO_OLPC_APSP) += olpc_apsp.o
obj-$(CONFIG_HYPERV_KEYBOARD) += hyperv-keyboard.o
obj-$(CONFIG_SERIO_SUN4I_PS2) += sun4i-ps2.o
obj-$(CONFIG_SERIO_GPIO_PS2) += ps2-gpio.o
obj-$(CONFIG_USERIO) += userio.o
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Altera University Program PS2 controller driver
*
* Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
*
* Based on sa1111ps2.c, which is:
* Copyright (C) 2002 Russell King
*/
#include <linux/module.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of.h>
#define DRV_NAME "altera_ps2"
struct ps2if {
struct serio *io;
void __iomem *base;
};
/*
* Read all bytes waiting in the PS2 port. There should be
* at the most one, but we loop for safety.
*/
static irqreturn_t altera_ps2_rxint(int irq, void *dev_id)
{
struct ps2if *ps2if = dev_id;
unsigned int status;
irqreturn_t handled = IRQ_NONE;
while ((status = readl(ps2if->base)) & 0xffff0000) {
serio_interrupt(ps2if->io, status & 0xff, 0);
handled = IRQ_HANDLED;
}
return handled;
}
/*
* Write a byte to the PS2 port.
*/
static int altera_ps2_write(struct serio *io, unsigned char val)
{
struct ps2if *ps2if = io->port_data;
writel(val, ps2if->base);
return 0;
}
static int altera_ps2_open(struct serio *io)
{
struct ps2if *ps2if = io->port_data;
/* clear fifo */
while (readl(ps2if->base) & 0xffff0000)
/* empty */;
writel(1, ps2if->base + 4); /* enable rx irq */
return 0;
}
static void altera_ps2_close(struct serio *io)
{
struct ps2if *ps2if = io->port_data;
writel(0, ps2if->base + 4); /* disable rx irq */
}
/*
* Add one device to this driver.
*/
static int altera_ps2_probe(struct platform_device *pdev)
{
struct ps2if *ps2if;
struct serio *serio;
int error, irq;
ps2if = devm_kzalloc(&pdev->dev, sizeof(*ps2if), GFP_KERNEL);
if (!ps2if)
return -ENOMEM;
ps2if->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(ps2if->base))
return PTR_ERR(ps2if->base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENXIO;
error = devm_request_irq(&pdev->dev, irq, altera_ps2_rxint, 0,
pdev->name, ps2if);
if (error) {
dev_err(&pdev->dev, "could not request IRQ %d\n", irq);
return error;
}
serio = kzalloc_obj(*serio);
if (!serio)
return -ENOMEM;
serio->id.type = SERIO_8042;
serio->write = altera_ps2_write;
serio->open = altera_ps2_open;
serio->close = altera_ps2_close;
strscpy(serio->name, dev_name(&pdev->dev), sizeof(serio->name));
strscpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys));
serio->port_data = ps2if;
serio->dev.parent = &pdev->dev;
ps2if->io = serio;
dev_info(&pdev->dev, "base %p, irq %d\n", ps2if->base, irq);
serio_register_port(ps2if->io);
platform_set_drvdata(pdev, ps2if);
return 0;
}
/*
* Remove one device from this driver.
*/
static void altera_ps2_remove(struct platform_device *pdev)
{
struct ps2if *ps2if = platform_get_drvdata(pdev);
serio_unregister_port(ps2if->io);
}
#ifdef CONFIG_OF
static const struct of_device_id altera_ps2_match[] = {
{ .compatible = "ALTR,ps2-1.0", },
{ .compatible = "altr,ps2-1.0", },
{},
};
MODULE_DEVICE_TABLE(of, altera_ps2_match);
#endif /* CONFIG_OF */
/*
* Our device driver structure
*/
static struct platform_driver altera_ps2_driver = {
.probe = altera_ps2_probe,
.remove = altera_ps2_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(altera_ps2_match),
},
};
module_platform_driver(altera_ps2_driver);
MODULE_DESCRIPTION("Altera University Program PS2 controller driver");
MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
+209
View File
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* linux/drivers/input/serio/ambakmi.c
*
* Copyright (C) 2000-2003 Deep Blue Solutions Ltd.
* Copyright (C) 2002 Russell King.
*/
#include <linux/module.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_prepare_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, IRQF_SHARED, "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_unprepare(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_unprepare(kmi->clk);
}
static int amba_kmi_probe(struct amba_device *dev,
const struct amba_id *id)
{
struct amba_kmi_port *kmi;
struct serio *io;
int ret;
ret = amba_request_regions(dev, NULL);
if (ret)
return ret;
kmi = kzalloc_obj(*kmi);
io = kzalloc_obj(*io);
if (!kmi || !io) {
ret = -ENOMEM;
goto out;
}
io->id.type = SERIO_8042;
io->write = amba_kmi_write;
io->open = amba_kmi_open;
io->close = amba_kmi_close;
strscpy(io->name, dev_name(&dev->dev), sizeof(io->name));
strscpy(io->phys, dev_name(&dev->dev), sizeof(io->phys));
io->port_data = kmi;
io->dev.parent = &dev->dev;
kmi->io = io;
kmi->base = ioremap(dev->res.start, resource_size(&dev->res));
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 void amba_kmi_remove(struct amba_device *dev)
{
struct amba_kmi_port *kmi = amba_get_drvdata(dev);
serio_unregister_port(kmi->io);
clk_put(kmi->clk);
iounmap(kmi->base);
kfree(kmi);
amba_release_regions(dev);
}
static int amba_kmi_resume(struct device *dev)
{
struct amba_kmi_port *kmi = dev_get_drvdata(dev);
/* kick the serio layer to rescan this port */
serio_reconnect(kmi->io);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(amba_kmi_dev_pm_ops, NULL, amba_kmi_resume);
static const struct amba_id amba_kmi_idtable[] = {
{
.id = 0x00041050,
.mask = 0x000fffff,
},
{ 0, 0 }
};
MODULE_DEVICE_TABLE(amba, amba_kmi_idtable);
static struct amba_driver ambakmi_driver = {
.drv = {
.name = "kmi-pl050",
.pm = pm_sleep_ptr(&amba_kmi_dev_pm_ops),
},
.id_table = amba_kmi_idtable,
.probe = amba_kmi_probe,
.remove = amba_kmi_remove,
};
module_amba_driver(ambakmi_driver);
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
MODULE_DESCRIPTION("AMBA KMI controller driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Amstrad E3 (Delta) keyboard port driver
*
* Copyright (c) 2006 Matt Callow
* Copyright (c) 2010 Janusz Krzysztofik
*
* Thanks to Cliff Lawson for his help
*
* The Amstrad Delta keyboard (aka mailboard) uses normal PC-AT style serial
* transmission. The keyboard port is formed of two GPIO lines, for clock
* and data. Due to strict timing requirements of the interface,
* the serial data stream is read and processed by a FIQ handler.
* The resulting words are fetched by this driver from a circular buffer.
*
* Standard AT keyboard driver (atkbd) is used for handling the keyboard data.
* However, when used with the E3 mailboard that producecs non-standard
* scancodes, a custom key table must be prepared and loaded from userspace.
*/
#include <linux/irq.h>
#include <linux/platform_data/ams-delta-fiq.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/module.h>
#define DRIVER_NAME "ams-delta-serio"
MODULE_AUTHOR("Matt Callow");
MODULE_DESCRIPTION("AMS Delta (E3) keyboard port driver");
MODULE_LICENSE("GPL");
struct ams_delta_serio {
struct serio *serio;
struct regulator *vcc;
unsigned int *fiq_buffer;
};
static int check_data(struct serio *serio, int data)
{
int i, parity = 0;
/* check valid stop bit */
if (!(data & 0x400)) {
dev_warn(&serio->dev, "invalid stop bit, data=0x%X\n", data);
return SERIO_FRAME;
}
/* calculate the parity */
for (i = 1; i < 10; i++) {
if (data & (1 << i))
parity++;
}
/* it should be odd */
if (!(parity & 0x01)) {
dev_warn(&serio->dev,
"parity check failed, data=0x%X parity=0x%X\n", data,
parity);
return SERIO_PARITY;
}
return 0;
}
static irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id)
{
struct ams_delta_serio *priv = dev_id;
int *circ_buff = &priv->fiq_buffer[FIQ_CIRC_BUFF];
int data, dfl;
u8 scancode;
priv->fiq_buffer[FIQ_IRQ_PEND] = 0;
/*
* Read data from the circular buffer, check it
* and then pass it on the serio
*/
while (priv->fiq_buffer[FIQ_KEYS_CNT] > 0) {
data = circ_buff[priv->fiq_buffer[FIQ_HEAD_OFFSET]++];
priv->fiq_buffer[FIQ_KEYS_CNT]--;
if (priv->fiq_buffer[FIQ_HEAD_OFFSET] ==
priv->fiq_buffer[FIQ_BUF_LEN])
priv->fiq_buffer[FIQ_HEAD_OFFSET] = 0;
dfl = check_data(priv->serio, data);
scancode = (u8) (data >> 1) & 0xFF;
serio_interrupt(priv->serio, scancode, dfl);
}
return IRQ_HANDLED;
}
static int ams_delta_serio_open(struct serio *serio)
{
struct ams_delta_serio *priv = serio->port_data;
/* enable keyboard */
return regulator_enable(priv->vcc);
}
static void ams_delta_serio_close(struct serio *serio)
{
struct ams_delta_serio *priv = serio->port_data;
/* disable keyboard */
regulator_disable(priv->vcc);
}
static int ams_delta_serio_init(struct platform_device *pdev)
{
struct ams_delta_serio *priv;
struct serio *serio;
int irq, err;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->fiq_buffer = pdev->dev.platform_data;
if (!priv->fiq_buffer)
return -EINVAL;
priv->vcc = devm_regulator_get(&pdev->dev, "vcc");
if (IS_ERR(priv->vcc)) {
err = PTR_ERR(priv->vcc);
dev_err(&pdev->dev, "regulator request failed (%d)\n", err);
/*
* When running on a non-dt platform and requested regulator
* is not available, devm_regulator_get() never returns
* -EPROBE_DEFER as it is not able to justify if the regulator
* may still appear later. On the other hand, the board can
* still set full constriants flag at late_initcall in order
* to instruct devm_regulator_get() to returnn a dummy one
* if sufficient. Hence, if we get -ENODEV here, let's convert
* it to -EPROBE_DEFER and wait for the board to decide or
* let Deferred Probe infrastructure handle this error.
*/
if (err == -ENODEV)
err = -EPROBE_DEFER;
return err;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENXIO;
err = devm_request_irq(&pdev->dev, irq, ams_delta_serio_interrupt,
IRQ_TYPE_EDGE_RISING, DRIVER_NAME, priv);
if (err < 0) {
dev_err(&pdev->dev, "IRQ request failed (%d)\n", err);
return err;
}
serio = kzalloc_obj(*serio);
if (!serio)
return -ENOMEM;
priv->serio = serio;
serio->id.type = SERIO_8042;
serio->open = ams_delta_serio_open;
serio->close = ams_delta_serio_close;
strscpy(serio->name, "AMS DELTA keyboard adapter", sizeof(serio->name));
strscpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys));
serio->dev.parent = &pdev->dev;
serio->port_data = priv;
serio_register_port(serio);
platform_set_drvdata(pdev, priv);
dev_info(&serio->dev, "%s\n", serio->name);
return 0;
}
static void ams_delta_serio_exit(struct platform_device *pdev)
{
struct ams_delta_serio *priv = platform_get_drvdata(pdev);
serio_unregister_port(priv->serio);
}
static struct platform_driver ams_delta_serio_driver = {
.probe = ams_delta_serio_init,
.remove = ams_delta_serio_exit,
.driver = {
.name = DRIVER_NAME
},
};
module_platform_driver(ams_delta_serio_driver);
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2013 Aeroflex Gaisler
*
* This driver supports the APBPS2 PS/2 core available in the GRLIB
* VHDL IP core library.
*
* Full documentation of the APBPS2 core can be found here:
* http://www.gaisler.com/products/grlib/grip.pdf
*
* See "Documentation/devicetree/bindings/input/ps2keyb-mouse-apbps2.txt" for
* information on open firmware properties.
*
* Contributors: Daniel Hellstrom <daniel@gaisler.com>
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/io.h>
struct apbps2_regs {
u32 __iomem data; /* 0x00 */
u32 __iomem status; /* 0x04 */
u32 __iomem ctrl; /* 0x08 */
u32 __iomem reload; /* 0x0c */
};
#define APBPS2_STATUS_DR (1<<0)
#define APBPS2_STATUS_PE (1<<1)
#define APBPS2_STATUS_FE (1<<2)
#define APBPS2_STATUS_KI (1<<3)
#define APBPS2_STATUS_RF (1<<4)
#define APBPS2_STATUS_TF (1<<5)
#define APBPS2_STATUS_TCNT (0x1f<<22)
#define APBPS2_STATUS_RCNT (0x1f<<27)
#define APBPS2_CTRL_RE (1<<0)
#define APBPS2_CTRL_TE (1<<1)
#define APBPS2_CTRL_RI (1<<2)
#define APBPS2_CTRL_TI (1<<3)
struct apbps2_priv {
struct serio *io;
struct apbps2_regs __iomem *regs;
};
static int apbps2_idx;
static irqreturn_t apbps2_isr(int irq, void *dev_id)
{
struct apbps2_priv *priv = dev_id;
unsigned long status, data, rxflags;
irqreturn_t ret = IRQ_NONE;
while ((status = ioread32be(&priv->regs->status)) & APBPS2_STATUS_DR) {
data = ioread32be(&priv->regs->data);
rxflags = (status & APBPS2_STATUS_PE) ? SERIO_PARITY : 0;
rxflags |= (status & APBPS2_STATUS_FE) ? SERIO_FRAME : 0;
/* Clear error bits */
if (rxflags)
iowrite32be(0, &priv->regs->status);
serio_interrupt(priv->io, data, rxflags);
ret = IRQ_HANDLED;
}
return ret;
}
static int apbps2_write(struct serio *io, unsigned char val)
{
struct apbps2_priv *priv = io->port_data;
unsigned int tleft = 10000; /* Timeout in 100ms */
/* Delay until PS/2 controller has room for more chars */
while ((ioread32be(&priv->regs->status) & APBPS2_STATUS_TF) && tleft--)
udelay(10);
if ((ioread32be(&priv->regs->status) & APBPS2_STATUS_TF) == 0) {
iowrite32be(val, &priv->regs->data);
iowrite32be(APBPS2_CTRL_RE | APBPS2_CTRL_RI | APBPS2_CTRL_TE,
&priv->regs->ctrl);
return 0;
}
return -ETIMEDOUT;
}
static int apbps2_open(struct serio *io)
{
struct apbps2_priv *priv = io->port_data;
int limit;
/* Clear error flags */
iowrite32be(0, &priv->regs->status);
/* Clear old data if available (unlikely) */
limit = 1024;
while ((ioread32be(&priv->regs->status) & APBPS2_STATUS_DR) && --limit)
ioread32be(&priv->regs->data);
/* Enable receiver and its interrupt */
iowrite32be(APBPS2_CTRL_RE | APBPS2_CTRL_RI, &priv->regs->ctrl);
return 0;
}
static void apbps2_close(struct serio *io)
{
struct apbps2_priv *priv = io->port_data;
/* Stop interrupts at PS/2 HW level */
iowrite32be(0, &priv->regs->ctrl);
}
/* Initialize one APBPS2 PS/2 core */
static int apbps2_of_probe(struct platform_device *ofdev)
{
struct apbps2_priv *priv;
int irq, err;
u32 freq_hz;
priv = devm_kzalloc(&ofdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&ofdev->dev, "memory allocation failed\n");
return -ENOMEM;
}
/* Find device address */
priv->regs = devm_platform_ioremap_resource(ofdev, 0);
if (IS_ERR(priv->regs))
return PTR_ERR(priv->regs);
/* Reset hardware, disable interrupt */
iowrite32be(0, &priv->regs->ctrl);
/* IRQ */
irq = platform_get_irq(ofdev, 0);
if (irq < 0)
return irq;
err = devm_request_irq(&ofdev->dev, irq, apbps2_isr,
IRQF_SHARED, "apbps2", priv);
if (err) {
dev_err(&ofdev->dev, "request IRQ%d failed\n", irq);
return err;
}
/* Get core frequency */
if (of_property_read_u32(ofdev->dev.of_node, "freq", &freq_hz)) {
dev_err(&ofdev->dev, "unable to get core frequency\n");
return -EINVAL;
}
/* Set reload register to core freq in kHz/10 */
iowrite32be(freq_hz / 10000, &priv->regs->reload);
priv->io = kzalloc_obj(*priv->io);
if (!priv->io)
return -ENOMEM;
priv->io->id.type = SERIO_8042;
priv->io->open = apbps2_open;
priv->io->close = apbps2_close;
priv->io->write = apbps2_write;
priv->io->port_data = priv;
strscpy(priv->io->name, "APBPS2 PS/2", sizeof(priv->io->name));
snprintf(priv->io->phys, sizeof(priv->io->phys),
"apbps2_%d", apbps2_idx++);
dev_info(&ofdev->dev, "irq = %d, base = 0x%p\n", irq, priv->regs);
serio_register_port(priv->io);
platform_set_drvdata(ofdev, priv);
return 0;
}
static void apbps2_of_remove(struct platform_device *of_dev)
{
struct apbps2_priv *priv = platform_get_drvdata(of_dev);
serio_unregister_port(priv->io);
}
static const struct of_device_id apbps2_of_match[] = {
{ .name = "GAISLER_APBPS2", },
{ .name = "01_060", },
{}
};
MODULE_DEVICE_TABLE(of, apbps2_of_match);
static struct platform_driver apbps2_of_driver = {
.driver = {
.name = "grlib-apbps2",
.of_match_table = apbps2_of_match,
},
.probe = apbps2_of_probe,
.remove = apbps2_of_remove,
};
module_platform_driver(apbps2_of_driver);
MODULE_AUTHOR("Aeroflex Gaisler AB.");
MODULE_DESCRIPTION("GRLIB APBPS2 PS/2 serial I/O");
MODULE_LICENSE("GPL");
+269
View File
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
*
* Driver is originally developed by Pavel Sokolov <psokolov@synopsys.com>
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#define ARC_PS2_PORTS 2
#define ARC_ARC_PS2_ID 0x0001f609
#define STAT_TIMEOUT 128
#define PS2_STAT_RX_FRM_ERR (1)
#define PS2_STAT_RX_BUF_OVER (1 << 1)
#define PS2_STAT_RX_INT_EN (1 << 2)
#define PS2_STAT_RX_VAL (1 << 3)
#define PS2_STAT_TX_ISNOT_FUL (1 << 4)
#define PS2_STAT_TX_INT_EN (1 << 5)
struct arc_ps2_port {
void __iomem *data_addr;
void __iomem *status_addr;
struct serio *io;
};
struct arc_ps2_data {
struct arc_ps2_port port[ARC_PS2_PORTS];
void __iomem *addr;
unsigned int frame_error;
unsigned int buf_overflow;
unsigned int total_int;
};
static void arc_ps2_check_rx(struct arc_ps2_data *arc_ps2,
struct arc_ps2_port *port)
{
unsigned int timeout = 1000;
unsigned int flag, status;
unsigned char data;
do {
status = ioread32(port->status_addr);
if (!(status & PS2_STAT_RX_VAL))
return;
data = ioread32(port->data_addr) & 0xff;
flag = 0;
arc_ps2->total_int++;
if (status & PS2_STAT_RX_FRM_ERR) {
arc_ps2->frame_error++;
flag |= SERIO_PARITY;
} else if (status & PS2_STAT_RX_BUF_OVER) {
arc_ps2->buf_overflow++;
flag |= SERIO_FRAME;
}
serio_interrupt(port->io, data, flag);
} while (--timeout);
dev_err(&port->io->dev, "PS/2 hardware stuck\n");
}
static irqreturn_t arc_ps2_interrupt(int irq, void *dev)
{
struct arc_ps2_data *arc_ps2 = dev;
int i;
for (i = 0; i < ARC_PS2_PORTS; i++)
arc_ps2_check_rx(arc_ps2, &arc_ps2->port[i]);
return IRQ_HANDLED;
}
static int arc_ps2_write(struct serio *io, unsigned char val)
{
unsigned status;
struct arc_ps2_port *port = io->port_data;
int timeout = STAT_TIMEOUT;
do {
status = ioread32(port->status_addr);
cpu_relax();
if (status & PS2_STAT_TX_ISNOT_FUL) {
iowrite32(val & 0xff, port->data_addr);
return 0;
}
} while (--timeout);
dev_err(&io->dev, "write timeout\n");
return -ETIMEDOUT;
}
static int arc_ps2_open(struct serio *io)
{
struct arc_ps2_port *port = io->port_data;
iowrite32(PS2_STAT_RX_INT_EN, port->status_addr);
return 0;
}
static void arc_ps2_close(struct serio *io)
{
struct arc_ps2_port *port = io->port_data;
iowrite32(ioread32(port->status_addr) & ~PS2_STAT_RX_INT_EN,
port->status_addr);
}
static void __iomem *arc_ps2_calc_addr(struct arc_ps2_data *arc_ps2,
int index, bool status)
{
void __iomem *addr;
addr = arc_ps2->addr + 4 + 4 * index;
if (status)
addr += ARC_PS2_PORTS * 4;
return addr;
}
static void arc_ps2_inhibit_ports(struct arc_ps2_data *arc_ps2)
{
void __iomem *addr;
u32 val;
int i;
for (i = 0; i < ARC_PS2_PORTS; i++) {
addr = arc_ps2_calc_addr(arc_ps2, i, true);
val = ioread32(addr);
val &= ~(PS2_STAT_RX_INT_EN | PS2_STAT_TX_INT_EN);
iowrite32(val, addr);
}
}
static int arc_ps2_create_port(struct platform_device *pdev,
struct arc_ps2_data *arc_ps2,
int index)
{
struct arc_ps2_port *port = &arc_ps2->port[index];
struct serio *io;
io = kzalloc_obj(*io);
if (!io)
return -ENOMEM;
io->id.type = SERIO_8042;
io->write = arc_ps2_write;
io->open = arc_ps2_open;
io->close = arc_ps2_close;
snprintf(io->name, sizeof(io->name), "ARC PS/2 port%d", index);
snprintf(io->phys, sizeof(io->phys), "arc/serio%d", index);
io->port_data = port;
port->io = io;
port->data_addr = arc_ps2_calc_addr(arc_ps2, index, false);
port->status_addr = arc_ps2_calc_addr(arc_ps2, index, true);
dev_dbg(&pdev->dev, "port%d is allocated (data = 0x%p, status = 0x%p)\n",
index, port->data_addr, port->status_addr);
serio_register_port(port->io);
return 0;
}
static int arc_ps2_probe(struct platform_device *pdev)
{
struct arc_ps2_data *arc_ps2;
int irq;
int error, id, i;
irq = platform_get_irq_byname(pdev, "arc_ps2_irq");
if (irq < 0)
return -EINVAL;
arc_ps2 = devm_kzalloc(&pdev->dev, sizeof(*arc_ps2), GFP_KERNEL);
if (!arc_ps2) {
dev_err(&pdev->dev, "out of memory\n");
return -ENOMEM;
}
arc_ps2->addr = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(arc_ps2->addr))
return PTR_ERR(arc_ps2->addr);
dev_info(&pdev->dev, "irq = %d, address = 0x%p, ports = %i\n",
irq, arc_ps2->addr, ARC_PS2_PORTS);
id = ioread32(arc_ps2->addr);
if (id != ARC_ARC_PS2_ID) {
dev_err(&pdev->dev, "device id does not match\n");
return -ENXIO;
}
arc_ps2_inhibit_ports(arc_ps2);
error = devm_request_irq(&pdev->dev, irq, arc_ps2_interrupt,
0, "arc_ps2", arc_ps2);
if (error) {
dev_err(&pdev->dev, "Could not allocate IRQ\n");
return error;
}
for (i = 0; i < ARC_PS2_PORTS; i++) {
error = arc_ps2_create_port(pdev, arc_ps2, i);
if (error) {
while (--i >= 0)
serio_unregister_port(arc_ps2->port[i].io);
return error;
}
}
platform_set_drvdata(pdev, arc_ps2);
return 0;
}
static void arc_ps2_remove(struct platform_device *pdev)
{
struct arc_ps2_data *arc_ps2 = platform_get_drvdata(pdev);
int i;
for (i = 0; i < ARC_PS2_PORTS; i++)
serio_unregister_port(arc_ps2->port[i].io);
dev_dbg(&pdev->dev, "interrupt count = %i\n", arc_ps2->total_int);
dev_dbg(&pdev->dev, "frame error count = %i\n", arc_ps2->frame_error);
dev_dbg(&pdev->dev, "buffer overflow count = %i\n",
arc_ps2->buf_overflow);
}
#ifdef CONFIG_OF
static const struct of_device_id arc_ps2_match[] = {
{ .compatible = "snps,arc_ps2" },
{ },
};
MODULE_DEVICE_TABLE(of, arc_ps2_match);
#endif
static struct platform_driver arc_ps2_driver = {
.driver = {
.name = "arc_ps2",
.of_match_table = of_match_ptr(arc_ps2_match),
},
.probe = arc_ps2_probe,
.remove = arc_ps2_remove,
};
module_platform_driver(arc_ps2_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pavel Sokolov <psokolov@synopsys.com>");
MODULE_DESCRIPTION("ARC PS/2 Driver");
+480
View File
@@ -0,0 +1,480 @@
/*
* 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/slab.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 <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");
#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 __iomem *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 __iomem *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 success, 0 on error
*/
static inline int gscps2_writeb_output(struct gscps2port *ps2port, u8 data)
{
char __iomem *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(addr) & GSC_STAT_RBNE)
/* wait */;
scoped_guard(spinlock_irqsave, &ps2port->lock)
writeb(data, addr+GSC_XMTDATA);
/* 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)
{
u8 data;
/* now enable/disable the port */
scoped_guard(spinlock_irqsave, &ps2port->lock) {
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);
}
wait_TBE(ps2port->addr);
gscps2_flush(ps2port);
}
/*
* gscps2_reset() - resets the PS/2 port
*/
static void gscps2_reset(struct gscps2port *ps2port)
{
/* reset the interface */
guard(spinlock_irqsave)(&ps2port->lock);
gscps2_flush(ps2port);
writeb(0xff, ps2port->addr + GSC_RESET);
gscps2_flush(ps2port);
}
static LIST_HEAD(ps2port_list);
static void gscps2_read_data(struct gscps2port *ps2port)
{
u8 status;
do {
status = gscps2_readb_status(ps2port->addr);
if (!(status & GSC_STAT_RBNE))
break;
ps2port->buffer[ps2port->append].str = status;
ps2port->buffer[ps2port->append].data =
gscps2_readb_input(ps2port->addr);
ps2port->append = (ps2port->append + 1) & BUFFER_SIZE;
} while (true);
}
static bool gscps2_report_data(struct gscps2port *ps2port)
{
unsigned int rxflags;
u8 data, status;
while (ps2port->act != ps2port->append) {
/*
* 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 true;
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);
}
return false;
}
/**
* gscps2_interrupt() - Interruption service routine
* @irq: interrupt number which triggered (unused)
* @dev: device pointer (unused)
*
* 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) {
guard(spinlock_irqsave)(&ps2port->lock);
gscps2_read_data(ps2port);
} /* 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) {
if (gscps2_report_data(ps2port)) {
/* More data ready - break early to restart interrupt */
break;
}
}
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
* @dev: pointer to parisc_device struct which will be probed
*
* @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 = kzalloc_obj(*ps2port);
serio = kzalloc_obj(*serio);
if (!ps2port || !serio) {
ret = -ENOMEM;
goto fail_nomem;
}
dev_set_drvdata(&dev->dev, ps2port);
ps2port->port = serio;
ps2port->padev = dev;
ps2port->addr = ioremap(hpa, GSC_STATUS + 4);
if (!ps2port->addr) {
ret = -ENOMEM;
goto fail_nomem;
}
spin_lock_init(&ps2port->lock);
gscps2_reset(ps2port);
ps2port->id = readb(ps2port->addr + GSC_ID) & 0x0f;
snprintf(serio->name, sizeof(serio->name), "gsc-ps2-%s",
(ps2port->id == GSC_ID_KEYBOARD) ? "keyboard" : "mouse");
strscpy(serio->phys, dev_name(&dev->dev), 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
pr_info("serio: %s port at 0x%08lx irq %d @ %s\n",
ps2port->port->name,
hpa,
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
* @dev: pointer to parisc_device which shall be removed
*
* @return: success/error report
*/
static void __exit 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);
}
static const struct parisc_device_id gscps2_device_tbl[] __initconst = {
{ 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 */
};
MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl);
static struct parisc_driver parisc_ps2_driver __refdata = {
.name = "gsc_ps2",
.id_table = gscps2_device_tbl,
.probe = gscps2_probe,
.remove = __exit_p(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);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,355 @@
/*
* 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 <linux/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");
static 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 || !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 "Unknown 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)
{
struct hp_sdc_mlc_priv_s *priv;
int rc = 2;
priv = mlc->priv;
/* Try to down the semaphore */
if (down_trylock(&mlc->isem)) {
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;
}
if (time_after(jiffies, mlc->instart + 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;
done:
return rc;
}
static int hp_sdc_mlc_cts(hil_mlc *mlc)
{
struct hp_sdc_mlc_priv_s *priv;
priv = mlc->priv;
/* Try to down the semaphores -- they should be up. */
BUG_ON(down_trylock(&mlc->isem));
BUG_ON(down_trylock(&mlc->osem));
up(&mlc->isem);
up(&mlc->osem);
if (down_trylock(&mlc->csem)) {
if (priv->trans.act.semaphore != &mlc->csem)
goto poll;
else
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;
return __hp_sdc_enqueue_transaction(&priv->trans);
busy:
return 1;
done:
priv->trans.act.semaphore = &mlc->osem;
up(&mlc->csem);
return 0;
}
static int hp_sdc_mlc_out(hil_mlc *mlc)
{
struct hp_sdc_mlc_priv_s *priv;
priv = mlc->priv;
/* Try to down the semaphore -- it should be up. */
BUG_ON(down_trylock(&mlc->osem));
if (mlc->opacket & HIL_DO_ALTER_CTRL)
goto do_control;
do_data:
if (priv->emtestmode) {
up(&mlc->osem);
return 0;
}
/* Shouldn't be sending commands when loop may be busy */
BUG_ON(down_trylock(&mlc->csem));
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;
/* Should not send command/data after engaging APE */
BUG_ON(mlc->opacket & HIL_CTRL_APE);
/* 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;
BUG_ON(down_trylock(&mlc->csem));
}
enqueue:
return hp_sdc_enqueue_transaction(&priv->trans);
}
static int __init hp_sdc_mlc_init(void)
{
hil_mlc *mlc = &hp_sdc_mlc;
int err;
#ifdef __mc68000__
if (!MACH_IS_HP300)
return -ENODEV;
#endif
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;
mlc->priv = &hp_sdc_mlc_priv;
err = hil_mlc_register(mlc);
if (err) {
printk(KERN_WARNING PREFIX "Failed to register MLC structure with hil_mlc\n");
return err;
}
if (hp_sdc_request_hil_irq(&hp_sdc_mlc_isr)) {
printk(KERN_WARNING PREFIX "Request for raw HIL ISR hook denied\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");
return -EBUSY;
}
return 0;
}
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);
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013, Microsoft Corporation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/completion.h>
#include <linux/hyperv.h>
#include <linux/serio.h>
#include <linux/slab.h>
/*
* Current version 1.0
*
*/
#define SYNTH_KBD_VERSION_MAJOR 1
#define SYNTH_KBD_VERSION_MINOR 0
#define SYNTH_KBD_VERSION (SYNTH_KBD_VERSION_MINOR | \
(SYNTH_KBD_VERSION_MAJOR << 16))
/*
* Message types in the synthetic input protocol
*/
enum synth_kbd_msg_type {
SYNTH_KBD_PROTOCOL_REQUEST = 1,
SYNTH_KBD_PROTOCOL_RESPONSE = 2,
SYNTH_KBD_EVENT = 3,
SYNTH_KBD_LED_INDICATORS = 4,
};
/*
* Basic message structures.
*/
struct synth_kbd_msg_hdr {
__le32 type;
};
struct synth_kbd_msg {
struct synth_kbd_msg_hdr header;
char data[]; /* Enclosed message */
};
union synth_kbd_version {
__le32 version;
};
/*
* Protocol messages
*/
struct synth_kbd_protocol_request {
struct synth_kbd_msg_hdr header;
union synth_kbd_version version_requested;
};
#define PROTOCOL_ACCEPTED BIT(0)
struct synth_kbd_protocol_response {
struct synth_kbd_msg_hdr header;
__le32 proto_status;
};
#define IS_UNICODE BIT(0)
#define IS_BREAK BIT(1)
#define IS_E0 BIT(2)
#define IS_E1 BIT(3)
struct synth_kbd_keystroke {
struct synth_kbd_msg_hdr header;
__le16 make_code;
__le16 reserved0;
__le32 info; /* Additional information */
};
#define HK_MAXIMUM_MESSAGE_SIZE 256
#define KBD_VSC_SEND_RING_BUFFER_SIZE VMBUS_RING_SIZE(36 * 1024)
#define KBD_VSC_RECV_RING_BUFFER_SIZE VMBUS_RING_SIZE(36 * 1024)
#define XTKBD_EMUL0 0xe0
#define XTKBD_EMUL1 0xe1
#define XTKBD_RELEASE 0x80
/*
* Represents a keyboard device
*/
struct hv_kbd_dev {
struct hv_device *hv_dev;
struct serio *hv_serio;
struct synth_kbd_protocol_request protocol_req;
struct synth_kbd_protocol_response protocol_resp;
/* Synchronize the request/response if needed */
struct completion wait_event;
spinlock_t lock; /* protects 'started' field */
bool started;
};
static void hv_kbd_on_receive(struct hv_device *hv_dev,
struct synth_kbd_msg *msg, u32 msg_length)
{
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
struct synth_kbd_keystroke *ks_msg;
u32 msg_type = __le32_to_cpu(msg->header.type);
u32 info;
u16 scan_code;
switch (msg_type) {
case SYNTH_KBD_PROTOCOL_RESPONSE:
/*
* Validate the information provided by the host.
* If the host is giving us a bogus packet,
* drop the packet (hoping the problem
* goes away).
*/
if (msg_length < sizeof(struct synth_kbd_protocol_response)) {
dev_err(&hv_dev->device,
"Illegal protocol response packet (len: %d)\n",
msg_length);
break;
}
memcpy(&kbd_dev->protocol_resp, msg,
sizeof(struct synth_kbd_protocol_response));
complete(&kbd_dev->wait_event);
break;
case SYNTH_KBD_EVENT:
/*
* Validate the information provided by the host.
* If the host is giving us a bogus packet,
* drop the packet (hoping the problem
* goes away).
*/
if (msg_length < sizeof(struct synth_kbd_keystroke)) {
dev_err(&hv_dev->device,
"Illegal keyboard event packet (len: %d)\n",
msg_length);
break;
}
ks_msg = (struct synth_kbd_keystroke *)msg;
info = __le32_to_cpu(ks_msg->info);
/*
* Inject the information through the serio interrupt.
*/
scoped_guard(spinlock_irqsave, &kbd_dev->lock) {
if (kbd_dev->started) {
if (info & IS_E0)
serio_interrupt(kbd_dev->hv_serio,
XTKBD_EMUL0, 0);
if (info & IS_E1)
serio_interrupt(kbd_dev->hv_serio,
XTKBD_EMUL1, 0);
scan_code = __le16_to_cpu(ks_msg->make_code);
if (info & IS_BREAK)
scan_code |= XTKBD_RELEASE;
serio_interrupt(kbd_dev->hv_serio,
scan_code, 0);
}
}
/*
* Only trigger a wakeup on key down, otherwise
* "echo freeze > /sys/power/state" can't really enter the
* state because the Enter-UP can trigger a wakeup at once.
*/
if (!(info & IS_BREAK))
pm_wakeup_hard_event(&hv_dev->device);
break;
default:
dev_err(&hv_dev->device,
"unhandled message type %d\n", msg_type);
}
}
static void hv_kbd_handle_received_packet(struct hv_device *hv_dev,
struct vmpacket_descriptor *desc,
u32 bytes_recvd,
u64 req_id)
{
struct synth_kbd_msg *msg;
u32 msg_sz;
switch (desc->type) {
case VM_PKT_COMP:
break;
case VM_PKT_DATA_INBAND:
/*
* We have a packet that has "inband" data. The API used
* for retrieving the packet guarantees that the complete
* packet is read. So, minimally, we should be able to
* parse the payload header safely (assuming that the host
* can be trusted. Trusting the host seems to be a
* reasonable assumption because in a virtualized
* environment there is not whole lot you can do if you
* don't trust the host.
*
* Nonetheless, let us validate if the host can be trusted
* (in a trivial way). The interesting aspect of this
* validation is how do you recover if we discover that the
* host is not to be trusted? Simply dropping the packet, I
* don't think is an appropriate recovery. In the interest
* of failing fast, it may be better to crash the guest.
* For now, I will just drop the packet!
*/
msg_sz = bytes_recvd - (desc->offset8 << 3);
if (msg_sz <= sizeof(struct synth_kbd_msg_hdr)) {
/*
* Drop the packet and hope
* the problem magically goes away.
*/
dev_err(&hv_dev->device,
"Illegal packet (type: %d, tid: %llx, size: %d)\n",
desc->type, req_id, msg_sz);
break;
}
msg = (void *)desc + (desc->offset8 << 3);
hv_kbd_on_receive(hv_dev, msg, msg_sz);
break;
default:
dev_err(&hv_dev->device,
"unhandled packet type %d, tid %llx len %d\n",
desc->type, req_id, bytes_recvd);
break;
}
}
static void hv_kbd_on_channel_callback(void *context)
{
struct vmpacket_descriptor *desc;
struct hv_device *hv_dev = context;
u32 bytes_recvd;
u64 req_id;
foreach_vmbus_pkt(desc, hv_dev->channel) {
bytes_recvd = desc->len8 * 8;
req_id = desc->trans_id;
hv_kbd_handle_received_packet(hv_dev, desc, bytes_recvd,
req_id);
}
}
static int hv_kbd_connect_to_vsp(struct hv_device *hv_dev)
{
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
struct synth_kbd_protocol_request *request;
struct synth_kbd_protocol_response *response;
u32 proto_status;
int error;
reinit_completion(&kbd_dev->wait_event);
request = &kbd_dev->protocol_req;
memset(request, 0, sizeof(struct synth_kbd_protocol_request));
request->header.type = __cpu_to_le32(SYNTH_KBD_PROTOCOL_REQUEST);
request->version_requested.version = __cpu_to_le32(SYNTH_KBD_VERSION);
error = vmbus_sendpacket(hv_dev->channel, request,
sizeof(struct synth_kbd_protocol_request),
(unsigned long)request,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (error)
return error;
if (!wait_for_completion_timeout(&kbd_dev->wait_event, 10 * HZ))
return -ETIMEDOUT;
response = &kbd_dev->protocol_resp;
proto_status = __le32_to_cpu(response->proto_status);
if (!(proto_status & PROTOCOL_ACCEPTED)) {
dev_err(&hv_dev->device,
"synth_kbd protocol request failed (version %d)\n",
SYNTH_KBD_VERSION);
return -ENODEV;
}
return 0;
}
static int hv_kbd_start(struct serio *serio)
{
struct hv_kbd_dev *kbd_dev = serio->port_data;
guard(spinlock_irqsave)(&kbd_dev->lock);
kbd_dev->started = true;
return 0;
}
static void hv_kbd_stop(struct serio *serio)
{
struct hv_kbd_dev *kbd_dev = serio->port_data;
guard(spinlock_irqsave)(&kbd_dev->lock);
kbd_dev->started = false;
}
static int hv_kbd_probe(struct hv_device *hv_dev,
const struct hv_vmbus_device_id *dev_id)
{
struct hv_kbd_dev *kbd_dev;
struct serio *hv_serio;
int error;
kbd_dev = kzalloc_obj(*kbd_dev);
hv_serio = kzalloc_obj(*hv_serio);
if (!kbd_dev || !hv_serio) {
error = -ENOMEM;
goto err_free_mem;
}
kbd_dev->hv_dev = hv_dev;
kbd_dev->hv_serio = hv_serio;
spin_lock_init(&kbd_dev->lock);
init_completion(&kbd_dev->wait_event);
hv_set_drvdata(hv_dev, kbd_dev);
hv_serio->dev.parent = &hv_dev->device;
hv_serio->id.type = SERIO_8042_XL;
hv_serio->port_data = kbd_dev;
strscpy(hv_serio->name, dev_name(&hv_dev->device),
sizeof(hv_serio->name));
strscpy(hv_serio->phys, dev_name(&hv_dev->device),
sizeof(hv_serio->phys));
hv_serio->start = hv_kbd_start;
hv_serio->stop = hv_kbd_stop;
error = vmbus_open(hv_dev->channel,
KBD_VSC_SEND_RING_BUFFER_SIZE,
KBD_VSC_RECV_RING_BUFFER_SIZE,
NULL, 0,
hv_kbd_on_channel_callback,
hv_dev);
if (error)
goto err_free_mem;
error = hv_kbd_connect_to_vsp(hv_dev);
if (error)
goto err_close_vmbus;
serio_register_port(kbd_dev->hv_serio);
device_init_wakeup(&hv_dev->device, true);
return 0;
err_close_vmbus:
vmbus_close(hv_dev->channel);
err_free_mem:
kfree(hv_serio);
kfree(kbd_dev);
return error;
}
static void hv_kbd_remove(struct hv_device *hv_dev)
{
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
serio_unregister_port(kbd_dev->hv_serio);
vmbus_close(hv_dev->channel);
kfree(kbd_dev);
hv_set_drvdata(hv_dev, NULL);
}
static int hv_kbd_suspend(struct hv_device *hv_dev)
{
vmbus_close(hv_dev->channel);
return 0;
}
static int hv_kbd_resume(struct hv_device *hv_dev)
{
int ret;
ret = vmbus_open(hv_dev->channel,
KBD_VSC_SEND_RING_BUFFER_SIZE,
KBD_VSC_RECV_RING_BUFFER_SIZE,
NULL, 0,
hv_kbd_on_channel_callback,
hv_dev);
if (ret == 0)
ret = hv_kbd_connect_to_vsp(hv_dev);
return ret;
}
static const struct hv_vmbus_device_id id_table[] = {
/* Keyboard guid */
{ HV_KBD_GUID, },
{ },
};
MODULE_DEVICE_TABLE(vmbus, id_table);
static struct hv_driver hv_kbd_drv = {
.name = KBUILD_MODNAME,
.id_table = id_table,
.probe = hv_kbd_probe,
.remove = hv_kbd_remove,
.suspend = hv_kbd_suspend,
.resume = hv_kbd_resume,
.driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
static int __init hv_kbd_init(void)
{
return vmbus_driver_register(&hv_kbd_drv);
}
static void __exit hv_kbd_exit(void)
{
vmbus_driver_unregister(&hv_kbd_drv);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic Keyboard Driver");
module_init(hv_kbd_init);
module_exit(hv_kbd_exit);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _I8042_IO_H
#define _I8042_IO_H
/*
* 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(__arm__)
/* defined in include/asm-arm/arch-xxx/irqs.h */
#include <asm/irq.h>
#elif defined(CONFIG_PPC)
extern int of_i8042_kbd_irq;
extern int of_i8042_aux_irq;
# define I8042_KBD_IRQ of_i8042_kbd_irq
# define I8042_AUX_IRQ of_i8042_aux_irq
#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)
if (check_legacy_ioport(I8042_DATA_REG))
return -ENODEV;
#endif
#if !defined(__sh__) && !defined(__alpha__)
if (!request_region(I8042_DATA_REG, 16, "i8042"))
return -EBUSY;
#endif
i8042_reset = I8042_RESET_ALWAYS;
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 */
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _I8042_IP22_H
#define _I8042_IP22_H
#include <asm/sgi/ioc.h>
#include <asm/sgi/ip22.h>
/*
* 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 = I8042_RESET_ALWAYS;
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 */
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _I8042_JAZZ_H
#define _I8042_JAZZ_H
#include <asm/jazz.h>
/*
* 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 */
@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _I8042_SNIRM_H
#define _I8042_SNIRM_H
#include <asm/sni.h>
/*
* Names.
*/
#define I8042_KBD_PHYS_DESC "onboard/serio0"
#define I8042_AUX_PHYS_DESC "onboard/serio1"
#define I8042_MUX_PHYS_DESC "onboard/serio%d"
/*
* IRQs.
*/
static int i8042_kbd_irq;
static int i8042_aux_irq;
#define I8042_KBD_IRQ i8042_kbd_irq
#define I8042_AUX_IRQ i8042_aux_irq
static void __iomem *kbd_iobase;
#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);
}
static inline int i8042_platform_init(void)
{
/* RM200 is strange ... */
if (sni_brd_type == SNI_BRD_RM200) {
kbd_iobase = ioremap(0x16000000, 4);
i8042_kbd_irq = 33;
i8042_aux_irq = 44;
} else {
kbd_iobase = ioremap(0x14000000, 4);
i8042_kbd_irq = 1;
i8042_aux_irq = 12;
}
if (!kbd_iobase)
return -ENOMEM;
return 0;
}
static inline void i8042_platform_exit(void)
{
}
#endif /* _I8042_SNIRM_H */
@@ -0,0 +1,160 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _I8042_SPARCIO_H
#define _I8042_SPARCIO_H
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <asm/io.h>
#include <asm/oplib.h>
#include <asm/prom.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;
#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);
}
#ifdef CONFIG_PCI
static struct resource *kbd_res;
#define OBP_PS2KBD_NAME1 "kb_ps2"
#define OBP_PS2KBD_NAME2 "keyboard"
#define OBP_PS2MS_NAME1 "kdmouse"
#define OBP_PS2MS_NAME2 "mouse"
static int sparc_i8042_probe(struct platform_device *op)
{
struct device_node *dp;
for_each_child_of_node(op->dev.of_node, dp) {
if (of_node_name_eq(dp, OBP_PS2KBD_NAME1) ||
of_node_name_eq(dp, OBP_PS2KBD_NAME2)) {
struct platform_device *kbd = of_find_device_by_node(dp);
unsigned int irq = kbd->archdata.irqs[0];
if (irq == 0xffffffff)
irq = op->archdata.irqs[0];
i8042_kbd_irq = irq;
kbd_iobase = of_ioremap(&kbd->resource[0],
0, 8, "kbd");
kbd_res = &kbd->resource[0];
} else if (of_node_name_eq(dp, OBP_PS2MS_NAME1) ||
of_node_name_eq(dp, OBP_PS2MS_NAME2)) {
struct platform_device *ms = of_find_device_by_node(dp);
unsigned int irq = ms->archdata.irqs[0];
if (irq == 0xffffffff)
irq = op->archdata.irqs[0];
i8042_aux_irq = irq;
}
}
return 0;
}
static void sparc_i8042_remove(struct platform_device *op)
{
of_iounmap(kbd_res, kbd_iobase, 8);
}
static const struct of_device_id sparc_i8042_match[] = {
{
.name = "8042",
},
{},
};
MODULE_DEVICE_TABLE(of, sparc_i8042_match);
static struct platform_driver sparc_i8042_driver = {
.driver = {
.name = "i8042",
.of_match_table = sparc_i8042_match,
},
.probe = sparc_i8042_probe,
.remove = sparc_i8042_remove,
};
static bool i8042_is_mr_coffee(void)
{
struct device_node *root __free(device_node) = of_find_node_by_path("/");
const char *name = of_get_property(root, "name", NULL);
return name && !strcmp(name, "SUNW,JavaStation-1");
}
static int __init i8042_platform_init(void)
{
if (i8042_is_mr_coffee()) {
/* 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 = platform_driver_register(&sparc_i8042_driver);
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 = I8042_RESET_ALWAYS;
return 0;
}
static inline void i8042_platform_exit(void)
{
if (!i8042_is_mr_coffee())
platform_driver_unregister(&sparc_i8042_driver);
}
#else /* !CONFIG_PCI */
static int __init i8042_platform_init(void)
{
return -ENODEV;
}
static inline void i8042_platform_exit(void)
{
}
#endif /* !CONFIG_PCI */
#endif /* _I8042_SPARCIO_H */
File diff suppressed because it is too large Load Diff
+91
View File
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _I8042_H
#define _I8042_H
/*
* Copyright (c) 1999-2002 Vojtech Pavlik
*/
/*
* Arch-dependent inline functions and defines.
*/
#if defined(CONFIG_MACH_JAZZ)
#include "i8042-jazzio.h"
#elif defined(CONFIG_SGI_HAS_I8042)
#include "i8042-ip22io.h"
#elif defined(CONFIG_SNI_RM)
#include "i8042-snirm.h"
#elif defined(CONFIG_SPARC)
#include "i8042-sparcio.h"
#elif defined(CONFIG_X86) || defined(CONFIG_LOONGARCH)
#include "i8042-acpipnpio.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
/*
* 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 KBUILD_MODNAME ": [%d] " format, \
(int) (jiffies - i8042_start_time), ##arg); \
} while (0)
#define filter_dbg(filter, data, format, args...) \
do { \
if (!i8042_debug) \
break; \
\
if (!filter || i8042_unmask_kbd_data) \
dbg("%02x " format, data, ##args); \
else \
dbg("** " format, ##args); \
} while (0)
#else
#define dbg_init() do { } while (0)
#define dbg(format, arg...) \
do { \
if (0) \
printk(KERN_DEBUG pr_fmt(format), ##arg); \
} while (0)
#define filter_dbg(filter, data, format, args...) do { } while (0)
#endif
#endif /* _I8042_H */
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SGI IOC3 PS/2 controller driver for linux
*
* Copyright (C) 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de>
*
* Based on code Copyright (C) 2005 Stanislaw Skowronek <skylark@unaligned.org>
* Copyright (C) 2009 Johannes Dickgreber <tanzy@gmx.de>
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/serio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <asm/sn/ioc3.h>
struct ioc3kbd_data {
struct ioc3_serioregs __iomem *regs;
struct serio *kbd, *aux;
bool kbd_exists, aux_exists;
int irq;
};
static int ioc3kbd_wait(struct ioc3_serioregs __iomem *regs, u32 mask)
{
unsigned long timeout = 0;
while ((readl(&regs->km_csr) & mask) && (timeout < 250)) {
udelay(50);
timeout++;
}
return (timeout >= 250) ? -ETIMEDOUT : 0;
}
static int ioc3kbd_write(struct serio *dev, u8 val)
{
struct ioc3kbd_data *d = dev->port_data;
int ret;
ret = ioc3kbd_wait(d->regs, KM_CSR_K_WRT_PEND);
if (ret)
return ret;
writel(val, &d->regs->k_wd);
return 0;
}
static int ioc3kbd_start(struct serio *dev)
{
struct ioc3kbd_data *d = dev->port_data;
d->kbd_exists = true;
return 0;
}
static void ioc3kbd_stop(struct serio *dev)
{
struct ioc3kbd_data *d = dev->port_data;
d->kbd_exists = false;
}
static int ioc3aux_write(struct serio *dev, u8 val)
{
struct ioc3kbd_data *d = dev->port_data;
int ret;
ret = ioc3kbd_wait(d->regs, KM_CSR_M_WRT_PEND);
if (ret)
return ret;
writel(val, &d->regs->m_wd);
return 0;
}
static int ioc3aux_start(struct serio *dev)
{
struct ioc3kbd_data *d = dev->port_data;
d->aux_exists = true;
return 0;
}
static void ioc3aux_stop(struct serio *dev)
{
struct ioc3kbd_data *d = dev->port_data;
d->aux_exists = false;
}
static void ioc3kbd_process_data(struct serio *dev, u32 data)
{
if (data & KM_RD_VALID_0)
serio_interrupt(dev, (data >> KM_RD_DATA_0_SHIFT) & 0xff, 0);
if (data & KM_RD_VALID_1)
serio_interrupt(dev, (data >> KM_RD_DATA_1_SHIFT) & 0xff, 0);
if (data & KM_RD_VALID_2)
serio_interrupt(dev, (data >> KM_RD_DATA_2_SHIFT) & 0xff, 0);
}
static irqreturn_t ioc3kbd_intr(int itq, void *dev_id)
{
struct ioc3kbd_data *d = dev_id;
u32 data_k, data_m;
data_k = readl(&d->regs->k_rd);
if (d->kbd_exists)
ioc3kbd_process_data(d->kbd, data_k);
data_m = readl(&d->regs->m_rd);
if (d->aux_exists)
ioc3kbd_process_data(d->aux, data_m);
return IRQ_HANDLED;
}
static int ioc3kbd_probe(struct platform_device *pdev)
{
struct ioc3_serioregs __iomem *regs;
struct device *dev = &pdev->dev;
struct ioc3kbd_data *d;
struct serio *sk, *sa;
int irq, ret;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENXIO;
d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
sk = kzalloc_obj(*sk);
if (!sk)
return -ENOMEM;
sa = kzalloc_obj(*sa);
if (!sa) {
kfree(sk);
return -ENOMEM;
}
sk->id.type = SERIO_8042;
sk->write = ioc3kbd_write;
sk->start = ioc3kbd_start;
sk->stop = ioc3kbd_stop;
snprintf(sk->name, sizeof(sk->name), "IOC3 keyboard %d", pdev->id);
snprintf(sk->phys, sizeof(sk->phys), "ioc3/serio%dkbd", pdev->id);
sk->port_data = d;
sk->dev.parent = dev;
sa->id.type = SERIO_8042;
sa->write = ioc3aux_write;
sa->start = ioc3aux_start;
sa->stop = ioc3aux_stop;
snprintf(sa->name, sizeof(sa->name), "IOC3 auxiliary %d", pdev->id);
snprintf(sa->phys, sizeof(sa->phys), "ioc3/serio%daux", pdev->id);
sa->port_data = d;
sa->dev.parent = dev;
d->regs = regs;
d->kbd = sk;
d->aux = sa;
d->irq = irq;
platform_set_drvdata(pdev, d);
serio_register_port(d->kbd);
serio_register_port(d->aux);
ret = request_irq(irq, ioc3kbd_intr, IRQF_SHARED, "ioc3-kbd", d);
if (ret) {
dev_err(dev, "could not request IRQ %d\n", irq);
serio_unregister_port(d->kbd);
serio_unregister_port(d->aux);
return ret;
}
/* enable ports */
writel(KM_CSR_K_CLAMP_3 | KM_CSR_M_CLAMP_3, &regs->km_csr);
return 0;
}
static void ioc3kbd_remove(struct platform_device *pdev)
{
struct ioc3kbd_data *d = platform_get_drvdata(pdev);
free_irq(d->irq, d);
serio_unregister_port(d->kbd);
serio_unregister_port(d->aux);
}
static const struct platform_device_id ioc3kbd_id_table[] = {
{ "ioc3-kbd", },
{ }
};
MODULE_DEVICE_TABLE(platform, ioc3kbd_id_table);
static struct platform_driver ioc3kbd_driver = {
.probe = ioc3kbd_probe,
.remove = ioc3kbd_remove,
.id_table = ioc3kbd_id_table,
.driver = {
.name = "ioc3-kbd",
},
};
module_platform_driver(ioc3kbd_driver);
MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>");
MODULE_DESCRIPTION("SGI IOC3 serio driver");
MODULE_LICENSE("GPL");
+620
View File
@@ -0,0 +1,620 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PS/2 driver library
*
* Copyright (c) 1999-2002 Vojtech Pavlik
* Copyright (c) 2004 Dmitry Torokhov
*/
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/kmsan-checks.h>
#include <linux/serio.h>
#include <linux/i8042.h>
#include <linux/libps2.h>
#define DRIVER_DESC "PS/2 driver library"
#define PS2_CMD_SETSCALE11 0x00e6
#define PS2_CMD_SETRES 0x10e8
#define PS2_CMD_EX_SETLEDS 0x20eb
#define PS2_CMD_SETLEDS 0x10ed
#define PS2_CMD_GETID 0x02f2
#define PS2_CMD_SETREP 0x10f3 /* Set repeat rate/set report rate */
#define PS2_CMD_RESET_BAT 0x02ff
#define PS2_RET_BAT 0xaa
#define PS2_RET_ID 0x00
#define PS2_RET_ACK 0xfa
#define PS2_RET_NAK 0xfe
#define PS2_RET_ERR 0xfc
#define PS2_FLAG_ACK BIT(0) /* Waiting for ACK/NAK */
#define PS2_FLAG_CMD BIT(1) /* Waiting for a command to finish */
#define PS2_FLAG_CMD1 BIT(2) /* Waiting for the first byte of command response */
#define PS2_FLAG_WAITID BIT(3) /* Command executing is GET ID */
#define PS2_FLAG_NAK BIT(4) /* Last transmission was NAKed */
#define PS2_FLAG_PASS_NOACK BIT(5) /* Pass non-ACK byte to receive handler */
static int ps2_do_sendbyte(struct ps2dev *ps2dev, u8 byte,
unsigned int timeout, unsigned int max_attempts)
__releases(&ps2dev->serio->lock) __acquires(&ps2dev->serio->lock)
{
int attempt = 0;
int error;
lockdep_assert_held(&ps2dev->serio->lock);
do {
ps2dev->nak = 1;
ps2dev->flags |= PS2_FLAG_ACK;
serio_continue_rx(ps2dev->serio);
error = serio_write(ps2dev->serio, byte);
if (error)
dev_dbg(&ps2dev->serio->dev,
"failed to write %#02x: %d\n", byte, error);
else
wait_event_timeout(ps2dev->wait,
!(ps2dev->flags & PS2_FLAG_ACK),
msecs_to_jiffies(timeout));
serio_pause_rx(ps2dev->serio);
} while (ps2dev->nak == PS2_RET_NAK && ++attempt < max_attempts);
ps2dev->flags &= ~PS2_FLAG_ACK;
if (!error) {
switch (ps2dev->nak) {
case 0:
break;
case PS2_RET_NAK:
error = -EAGAIN;
break;
case PS2_RET_ERR:
error = -EPROTO;
break;
default:
error = -EIO;
break;
}
}
if (error || attempt > 1)
dev_dbg(&ps2dev->serio->dev,
"%02x - %d (%x), attempt %d\n",
byte, error, ps2dev->nak, attempt);
return error;
}
/**
* ps2_sendbyte - sends a byte to the device and wait for acknowledgement
* @ps2dev: a PS/2 device to send the data to
* @byte: data to be sent to the device
* @timeout: timeout for sending the data and receiving an acknowledge
*
* The function doesn't handle retransmission, the caller is expected to handle
* it when needed.
*
* ps2_sendbyte() can only be called from a process context.
*/
int ps2_sendbyte(struct ps2dev *ps2dev, u8 byte, unsigned int timeout)
{
int retval;
guard(serio_pause_rx)(ps2dev->serio);
retval = ps2_do_sendbyte(ps2dev, byte, timeout, 1);
dev_dbg(&ps2dev->serio->dev, "%02x - %x\n", byte, ps2dev->nak);
return retval;
}
EXPORT_SYMBOL(ps2_sendbyte);
/**
* ps2_begin_command - mark beginning of execution of a complex command
* @ps2dev: a PS/2 device executing the command
*
* Serializes a complex/compound command. Once command is finished
* ps2_end_command() should be called.
*/
void ps2_begin_command(struct ps2dev *ps2dev)
{
struct mutex *m = ps2dev->serio->ps2_cmd_mutex ?: &ps2dev->cmd_mutex;
mutex_lock(m);
}
EXPORT_SYMBOL(ps2_begin_command);
/**
* ps2_end_command - mark end of execution of a complex command
* @ps2dev: a PS/2 device executing the command
*/
void ps2_end_command(struct ps2dev *ps2dev)
{
struct mutex *m = ps2dev->serio->ps2_cmd_mutex ?: &ps2dev->cmd_mutex;
mutex_unlock(m);
}
EXPORT_SYMBOL(ps2_end_command);
/**
* ps2_drain - waits for device to transmit requested number of bytes
* and discards them
* @ps2dev: the PS/2 device that should be drained
* @maxbytes: maximum number of bytes to be drained
* @timeout: time to drain the device
*/
void ps2_drain(struct ps2dev *ps2dev, size_t maxbytes, unsigned int timeout)
{
if (WARN_ON(maxbytes > sizeof(ps2dev->cmdbuf)))
maxbytes = sizeof(ps2dev->cmdbuf);
ps2_begin_command(ps2dev);
scoped_guard(serio_pause_rx, ps2dev->serio) {
ps2dev->flags = PS2_FLAG_CMD;
ps2dev->cmdcnt = maxbytes;
}
wait_event_timeout(ps2dev->wait,
!(ps2dev->flags & PS2_FLAG_CMD),
msecs_to_jiffies(timeout));
ps2_end_command(ps2dev);
}
EXPORT_SYMBOL(ps2_drain);
/**
* ps2_is_keyboard_id - checks received ID byte against the list of
* known keyboard IDs
* @id_byte: data byte that should be checked
*/
bool ps2_is_keyboard_id(u8 id_byte)
{
static const u8 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,
unsigned int command, unsigned 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) {
scoped_guard(serio_pause_rx, ps2dev->serio)
ps2dev->flags = 0;
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])) {
scoped_guard(serio_pause_rx, ps2dev->serio)
ps2dev->flags = ps2dev->cmdcnt = 0;
timeout = 0;
}
break;
default:
break;
}
return timeout;
}
/**
* __ps2_command - send a command to PS/2 device
* @ps2dev: the PS/2 device that should execute the command
* @param: a buffer containing parameters to be sent along with the command,
* or place where the results of the command execution will be deposited,
* or both
* @command: command word that encodes the command itself, as well as number of
* additional parameter bytes that should be sent to the device and expected
* length of the command response
*
* Not serialized. Callers should use ps2_begin_command() and ps2_end_command()
* to ensure proper serialization for complex commands.
*/
int __ps2_command(struct ps2dev *ps2dev, u8 *param, unsigned int command)
{
unsigned int timeout;
unsigned int send = (command >> 12) & 0xf;
unsigned int receive = (command >> 8) & 0xf;
int rc;
int i;
u8 send_param[16];
if (WARN_ON(receive > sizeof(ps2dev->cmdbuf)))
return -EINVAL;
if (WARN_ON(send && !param))
return -EINVAL;
memcpy(send_param, param, send);
/*
* Not using guard notation because we need to break critical
* section below while waiting for the response.
*/
serio_pause_rx(ps2dev->serio);
ps2dev->cmdcnt = receive;
switch (command) {
case PS2_CMD_GETID:
/*
* Some mice do not ACK the "get ID" command, prepare to
* handle this.
*/
ps2dev->flags = PS2_FLAG_WAITID;
break;
case PS2_CMD_SETLEDS:
case PS2_CMD_EX_SETLEDS:
case PS2_CMD_SETREP:
ps2dev->flags = PS2_FLAG_PASS_NOACK;
break;
default:
ps2dev->flags = 0;
break;
}
if (receive) {
/* Indicate that we expect response to the command. */
ps2dev->flags |= PS2_FLAG_CMD | PS2_FLAG_CMD1;
if (param)
for (i = 0; i < receive; i++)
ps2dev->cmdbuf[(receive - 1) - i] = param[i];
}
/*
* Some devices (Synaptics) perform the reset before
* ACKing the reset command, and so it can take a long
* time before the ACK arrives.
*/
timeout = command == PS2_CMD_RESET_BAT ? 1000 : 200;
rc = ps2_do_sendbyte(ps2dev, command & 0xff, timeout, 2);
if (rc)
goto out_reset_flags;
/* Send command parameters, if any. */
for (i = 0; i < send; i++) {
rc = ps2_do_sendbyte(ps2dev, param[i], 200, 2);
if (rc)
goto out_reset_flags;
}
serio_continue_rx(ps2dev->serio);
/*
* 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 && !(ps2dev->flags & PS2_FLAG_CMD1)) {
timeout = ps2_adjust_timeout(ps2dev, command, timeout);
wait_event_timeout(ps2dev->wait,
!(ps2dev->flags & PS2_FLAG_CMD), timeout);
}
serio_pause_rx(ps2dev->serio);
if (param) {
for (i = 0; i < receive; i++)
param[i] = ps2dev->cmdbuf[(receive - 1) - i];
kmsan_unpoison_memory(param, receive);
}
if (ps2dev->cmdcnt &&
(command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) {
rc = -EPROTO;
goto out_reset_flags;
}
rc = 0;
out_reset_flags:
ps2dev->flags = 0;
serio_continue_rx(ps2dev->serio);
dev_dbg(&ps2dev->serio->dev,
"%02x [%*ph] - %x/%08lx [%*ph]\n",
command & 0xff, send, send_param,
ps2dev->nak, ps2dev->flags,
receive, param ?: send_param);
/*
* ps_command() handles resends itself, so do not leak -EAGAIN
* to the callers.
*/
return rc != -EAGAIN ? rc : -EPROTO;
}
EXPORT_SYMBOL(__ps2_command);
/**
* ps2_command - send a command to PS/2 device
* @ps2dev: the PS/2 device that should execute the command
* @param: a buffer containing parameters to be sent along with the command,
* or place where the results of the command execution will be deposited,
* or both
* @command: command word that encodes the command itself, as well as number of
* additional parameter bytes that should be sent to the device and expected
* length of the command response
*
* Note: ps2_command() serializes the command execution so that only one
* command can be executed at a time for either individual port or the entire
* 8042 controller.
*/
int ps2_command(struct ps2dev *ps2dev, u8 *param, unsigned int command)
{
int rc;
ps2_begin_command(ps2dev);
rc = __ps2_command(ps2dev, param, command);
ps2_end_command(ps2dev);
return rc;
}
EXPORT_SYMBOL(ps2_command);
/**
* ps2_sliced_command - sends an extended PS/2 command to a mouse
* @ps2dev: the PS/2 device that should execute the command
* @command: command byte
*
* The command is sent using "sliced" syntax understood by advanced devices,
* such as Logitech or Synaptics touchpads. The command is encoded as:
* 0xE6 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where (rr*64)+(ss*16)+(tt*4)+uu
* is the command.
*/
int ps2_sliced_command(struct ps2dev *ps2dev, u8 command)
{
int i;
int retval;
ps2_begin_command(ps2dev);
retval = __ps2_command(ps2dev, NULL, PS2_CMD_SETSCALE11);
if (retval)
goto out;
for (i = 6; i >= 0; i -= 2) {
u8 d = (command >> i) & 3;
retval = __ps2_command(ps2dev, &d, PS2_CMD_SETRES);
if (retval)
break;
}
out:
dev_dbg(&ps2dev->serio->dev, "%02x - %d\n", command, retval);
ps2_end_command(ps2dev);
return retval;
}
EXPORT_SYMBOL(ps2_sliced_command);
/**
* ps2_init - initializes ps2dev structure
* @ps2dev: structure to be initialized
* @serio: serio port associated with the PS/2 device
* @pre_receive_handler: validation handler to check basic communication state
* @receive_handler: main protocol handler
*
* Prepares ps2dev structure for use in drivers for PS/2 devices.
*/
void ps2_init(struct ps2dev *ps2dev, struct serio *serio,
ps2_pre_receive_handler_t pre_receive_handler,
ps2_receive_handler_t receive_handler)
{
ps2dev->pre_receive_handler = pre_receive_handler;
ps2dev->receive_handler = receive_handler;
mutex_init(&ps2dev->cmd_mutex);
lockdep_set_subclass(&ps2dev->cmd_mutex, serio->depth);
init_waitqueue_head(&ps2dev->wait);
ps2dev->serio = serio;
serio_set_drvdata(serio, ps2dev);
}
EXPORT_SYMBOL(ps2_init);
/*
* ps2_handle_response() stores device's response to a command and notifies
* the process waiting for completion of the command. Note that there is a
* distinction between waiting for the first byte of the response, and
* waiting for subsequent bytes. It is done so that callers could shorten
* timeouts once first byte of response is received.
*/
static void ps2_handle_response(struct ps2dev *ps2dev, u8 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);
}
}
/*
* ps2_handle_ack() processes ACK/NAK of a command from a PS/2 device,
* possibly applying workarounds for mice not acknowledging the "get ID"
* command.
*/
static void ps2_handle_ack(struct ps2dev *ps2dev, u8 data)
{
switch (data) {
case PS2_RET_ACK:
ps2dev->nak = 0;
break;
case PS2_RET_NAK:
ps2dev->flags |= PS2_FLAG_NAK;
ps2dev->nak = PS2_RET_NAK;
break;
case PS2_RET_ERR:
if (ps2dev->flags & PS2_FLAG_NAK) {
ps2dev->flags &= ~PS2_FLAG_NAK;
ps2dev->nak = PS2_RET_ERR;
break;
}
fallthrough;
/*
* 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;
}
fallthrough;
default:
/*
* Do not signal errors if we get unexpected reply while
* waiting for an ACK to the initial (first) command byte:
* the device might not be quiesced yet and continue
* delivering data. For certain commands (such as set leds and
* set repeat rate) that can be used during normal device
* operation, we even pass this data byte to the normal receive
* handler.
* Note that we reset PS2_FLAG_WAITID flag, so the workaround
* for mice not acknowledging the Get ID command only triggers
* on the 1st byte; if device spews data we really want to see
* a real ACK from it.
*/
dev_dbg(&ps2dev->serio->dev, "unexpected %#02x\n", data);
if (ps2dev->flags & PS2_FLAG_PASS_NOACK)
ps2dev->receive_handler(ps2dev, data);
ps2dev->flags &= ~(PS2_FLAG_WAITID | PS2_FLAG_PASS_NOACK);
return;
}
if (!ps2dev->nak)
ps2dev->flags &= ~PS2_FLAG_NAK;
ps2dev->flags &= ~PS2_FLAG_ACK;
if (!ps2dev->nak && data != PS2_RET_ACK)
ps2_handle_response(ps2dev, data);
else
wake_up(&ps2dev->wait);
}
/*
* Clears state of PS/2 device after communication error by resetting majority
* of flags and waking up waiters, if any.
*/
static void ps2_cleanup(struct ps2dev *ps2dev)
{
unsigned long old_flags = ps2dev->flags;
/* reset all flags except last nak */
ps2dev->flags &= PS2_FLAG_NAK;
if (old_flags & PS2_FLAG_ACK)
ps2dev->nak = 1;
if (old_flags & (PS2_FLAG_ACK | PS2_FLAG_CMD))
wake_up(&ps2dev->wait);
}
/**
* ps2_interrupt - common interrupt handler for PS/2 devices
* @serio: serio port for the device
* @data: a data byte received from the device
* @flags: flags such as %SERIO_PARITY or %SERIO_TIMEOUT indicating state of
* the data transfer
*
* ps2_interrupt() invokes pre-receive handler, optionally handles command
* acknowledgement and response from the device, and finally passes the data
* to the main protocol handler for future processing.
*/
irqreturn_t ps2_interrupt(struct serio *serio, u8 data, unsigned int flags) {
struct ps2dev *ps2dev = serio_get_drvdata(serio);
enum ps2_disposition rc;
rc = ps2dev->pre_receive_handler(ps2dev, data, flags);
switch (rc) {
case PS2_ERROR:
ps2_cleanup(ps2dev);
break;
case PS2_IGNORE:
break;
case PS2_PROCESS:
if (ps2dev->flags & PS2_FLAG_ACK)
ps2_handle_ack(ps2dev, data);
else if (ps2dev->flags & PS2_FLAG_CMD)
ps2_handle_response(ps2dev, data);
else
ps2dev->receive_handler(ps2dev, data);
break;
}
return IRQ_HANDLED;
}
EXPORT_SYMBOL(ps2_interrupt);
MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
MODULE_DESCRIPTION("PS/2 driver library");
MODULE_LICENSE("GPL");
+204
View File
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SGI O2 MACE PS2 controller driver for linux
*
* Copyright (C) 2002 Vivien Chappelier
*/
#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/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 *maceps2_allocate_port(int idx)
{
struct serio *serio;
serio = kzalloc_obj(*serio);
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 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 void maceps2_remove(struct platform_device *dev)
{
serio_unregister_port(maceps2_port[0]);
serio_unregister_port(maceps2_port[1]);
}
static struct platform_driver maceps2_driver = {
.driver = {
.name = "maceps2",
},
.probe = maceps2_probe,
.remove = 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);
+268
View File
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OLPC serio driver for multiplexed input from Marvell MMP security processor
*
* Copyright (C) 2011-2013 One Laptop Per Child
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/serio.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/delay.h>
/*
* The OLPC XO-1.75 and XO-4 laptops do not have a hardware PS/2 controller.
* Instead, the OLPC firmware runs a bit-banging PS/2 implementation on an
* otherwise-unused slow processor which is included in the Marvell MMP2/MMP3
* SoC, known as the "Security Processor" (SP) or "Wireless Trusted Module"
* (WTM). This firmware then reports its results via the WTM registers,
* which we read from the Application Processor (AP, i.e. main CPU) in this
* driver.
*
* On the hardware side we have a PS/2 mouse and an AT keyboard, the data
* is multiplexed through this system. We create a serio port for each one,
* and demultiplex the data accordingly.
*/
/* WTM register offsets */
#define SECURE_PROCESSOR_COMMAND 0x40
#define COMMAND_RETURN_STATUS 0x80
#define COMMAND_FIFO_STATUS 0xc4
#define PJ_RST_INTERRUPT 0xc8
#define PJ_INTERRUPT_MASK 0xcc
/*
* The upper byte of SECURE_PROCESSOR_COMMAND and COMMAND_RETURN_STATUS is
* used to identify which port (device) is being talked to. The lower byte
* is the data being sent/received.
*/
#define PORT_MASK 0xff00
#define DATA_MASK 0x00ff
#define PORT_SHIFT 8
#define KEYBOARD_PORT 0
#define TOUCHPAD_PORT 1
/* COMMAND_FIFO_STATUS */
#define CMD_CNTR_MASK 0x7 /* Number of pending/unprocessed commands */
#define MAX_PENDING_CMDS 4 /* from device specs */
/* PJ_RST_INTERRUPT */
#define SP_COMMAND_COMPLETE_RESET 0x1
/* PJ_INTERRUPT_MASK */
#define INT_0 (1 << 0)
/* COMMAND_FIFO_STATUS */
#define CMD_STS_MASK 0x100
struct olpc_apsp {
struct device *dev;
struct serio *kbio;
struct serio *padio;
void __iomem *base;
int open_count;
int irq;
};
static int olpc_apsp_write(struct serio *port, unsigned char val)
{
struct olpc_apsp *priv = port->port_data;
unsigned int i;
u32 which = 0;
if (port == priv->padio)
which = TOUCHPAD_PORT << PORT_SHIFT;
else
which = KEYBOARD_PORT << PORT_SHIFT;
dev_dbg(priv->dev, "olpc_apsp_write which=%x val=%x\n", which, val);
for (i = 0; i < 50; i++) {
u32 sts = readl(priv->base + COMMAND_FIFO_STATUS);
if ((sts & CMD_CNTR_MASK) < MAX_PENDING_CMDS) {
writel(which | val,
priv->base + SECURE_PROCESSOR_COMMAND);
return 0;
}
/* SP busy. This has not been seen in practice. */
mdelay(1);
}
dev_dbg(priv->dev, "olpc_apsp_write timeout, status=%x\n",
readl(priv->base + COMMAND_FIFO_STATUS));
return -ETIMEDOUT;
}
static irqreturn_t olpc_apsp_rx(int irq, void *dev_id)
{
struct olpc_apsp *priv = dev_id;
unsigned int w, tmp;
struct serio *serio;
/*
* Write 1 to PJ_RST_INTERRUPT to acknowledge and clear the interrupt
* Write 0xff00 to SECURE_PROCESSOR_COMMAND.
*/
tmp = readl(priv->base + PJ_RST_INTERRUPT);
if (!(tmp & SP_COMMAND_COMPLETE_RESET)) {
dev_warn(priv->dev, "spurious interrupt?\n");
return IRQ_NONE;
}
w = readl(priv->base + COMMAND_RETURN_STATUS);
dev_dbg(priv->dev, "olpc_apsp_rx %x\n", w);
if (w >> PORT_SHIFT == KEYBOARD_PORT)
serio = priv->kbio;
else
serio = priv->padio;
serio_interrupt(serio, w & DATA_MASK, 0);
/* Ack and clear interrupt */
writel(tmp | SP_COMMAND_COMPLETE_RESET, priv->base + PJ_RST_INTERRUPT);
writel(PORT_MASK, priv->base + SECURE_PROCESSOR_COMMAND);
pm_wakeup_event(priv->dev, 1000);
return IRQ_HANDLED;
}
static int olpc_apsp_open(struct serio *port)
{
struct olpc_apsp *priv = port->port_data;
unsigned int tmp;
unsigned long l;
if (priv->open_count++ == 0) {
l = readl(priv->base + COMMAND_FIFO_STATUS);
if (!(l & CMD_STS_MASK)) {
dev_err(priv->dev, "SP cannot accept commands.\n");
return -EIO;
}
/* Enable interrupt 0 by clearing its bit */
tmp = readl(priv->base + PJ_INTERRUPT_MASK);
writel(tmp & ~INT_0, priv->base + PJ_INTERRUPT_MASK);
}
return 0;
}
static void olpc_apsp_close(struct serio *port)
{
struct olpc_apsp *priv = port->port_data;
unsigned int tmp;
if (--priv->open_count == 0) {
/* Disable interrupt 0 */
tmp = readl(priv->base + PJ_INTERRUPT_MASK);
writel(tmp | INT_0, priv->base + PJ_INTERRUPT_MASK);
}
}
static int olpc_apsp_probe(struct platform_device *pdev)
{
struct serio *kb_serio, *pad_serio;
struct olpc_apsp *priv;
int error;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(priv->base)) {
dev_err(&pdev->dev, "Failed to map WTM registers\n");
return PTR_ERR(priv->base);
}
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0)
return priv->irq;
/* KEYBOARD */
kb_serio = kzalloc_obj(*kb_serio);
if (!kb_serio)
return -ENOMEM;
kb_serio->id.type = SERIO_8042_XL;
kb_serio->write = olpc_apsp_write;
kb_serio->open = olpc_apsp_open;
kb_serio->close = olpc_apsp_close;
kb_serio->port_data = priv;
kb_serio->dev.parent = &pdev->dev;
strscpy(kb_serio->name, "sp keyboard", sizeof(kb_serio->name));
strscpy(kb_serio->phys, "sp/serio0", sizeof(kb_serio->phys));
priv->kbio = kb_serio;
serio_register_port(kb_serio);
/* TOUCHPAD */
pad_serio = kzalloc_obj(*pad_serio);
if (!pad_serio) {
error = -ENOMEM;
goto err_pad;
}
pad_serio->id.type = SERIO_8042;
pad_serio->write = olpc_apsp_write;
pad_serio->open = olpc_apsp_open;
pad_serio->close = olpc_apsp_close;
pad_serio->port_data = priv;
pad_serio->dev.parent = &pdev->dev;
strscpy(pad_serio->name, "sp touchpad", sizeof(pad_serio->name));
strscpy(pad_serio->phys, "sp/serio1", sizeof(pad_serio->phys));
priv->padio = pad_serio;
serio_register_port(pad_serio);
error = request_irq(priv->irq, olpc_apsp_rx, 0, "olpc-apsp", priv);
if (error) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
goto err_irq;
}
device_init_wakeup(priv->dev, 1);
platform_set_drvdata(pdev, priv);
dev_dbg(&pdev->dev, "probed successfully.\n");
return 0;
err_irq:
serio_unregister_port(pad_serio);
err_pad:
serio_unregister_port(kb_serio);
return error;
}
static void olpc_apsp_remove(struct platform_device *pdev)
{
struct olpc_apsp *priv = platform_get_drvdata(pdev);
free_irq(priv->irq, priv);
serio_unregister_port(priv->kbio);
serio_unregister_port(priv->padio);
}
static const struct of_device_id olpc_apsp_dt_ids[] = {
{ .compatible = "olpc,ap-sp", },
{}
};
MODULE_DEVICE_TABLE(of, olpc_apsp_dt_ids);
static struct platform_driver olpc_apsp_driver = {
.probe = olpc_apsp_probe,
.remove = olpc_apsp_remove,
.driver = {
.name = "olpc-apsp",
.of_match_table = olpc_apsp_dt_ids,
},
};
MODULE_DESCRIPTION("OLPC AP-SP serio driver");
MODULE_LICENSE("GPL");
module_platform_driver(olpc_apsp_driver);
+222
View File
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parallel port to Keyboard port adapter driver for Linux
*
* Copyright (c) 1999-2004 Vojtech Pavlik
*/
/*
* 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/slab.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(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(struct parport *pp)
{
struct pardev_cb parkbd_parport_cb;
memset(&parkbd_parport_cb, 0, sizeof(parkbd_parport_cb));
parkbd_parport_cb.irq_func = parkbd_interrupt;
parkbd_parport_cb.flags = PARPORT_FLAG_EXCL;
parkbd_dev = parport_register_dev_model(pp, "parkbd",
&parkbd_parport_cb, 0);
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 *parkbd_allocate_serio(void)
{
struct serio *serio;
serio = kzalloc_obj(*serio);
if (serio) {
serio->id.type = parkbd_mode;
serio->write = parkbd_write;
strscpy(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 void parkbd_attach(struct parport *pp)
{
if (pp->number != parkbd_pp_no) {
pr_debug("Not using parport%d.\n", pp->number);
return;
}
if (parkbd_getport(pp))
return;
parkbd_port = parkbd_allocate_serio();
if (!parkbd_port) {
parport_release(parkbd_dev);
parport_unregister_device(parkbd_dev);
return;
}
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;
}
static void parkbd_detach(struct parport *port)
{
if (!parkbd_port || port->number != parkbd_pp_no)
return;
parport_release(parkbd_dev);
serio_unregister_port(parkbd_port);
parport_unregister_device(parkbd_dev);
parkbd_port = NULL;
}
static struct parport_driver parkbd_parport_driver = {
.name = "parkbd",
.match_port = parkbd_attach,
.detach = parkbd_detach,
};
module_parport_driver(parkbd_parport_driver);
+217
View File
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/drivers/input/serio/pcips2.c
*
* Copyright (C) 2003 Russell King, All Rights Reserved.
*
* 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/slab.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 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 = kzalloc_obj(*ps2if);
serio = kzalloc_obj(*serio);
if (!ps2if || !serio) {
ret = -ENOMEM;
goto release;
}
serio->id.type = SERIO_8042;
serio->write = pcips2_write;
serio->open = pcips2_open;
serio->close = pcips2_close;
strscpy(serio->name, pci_name(dev), sizeof(serio->name));
strscpy(serio->phys, dev_name(&dev->dev), 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 pcips2_remove(struct pci_dev *dev)
{
struct pcips2_data *ps2if = pci_get_drvdata(dev);
serio_unregister_port(ps2if->io);
kfree(ps2if);
pci_release_regions(dev);
pci_disable_device(dev);
}
static const 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, }
};
MODULE_DEVICE_TABLE(pci, pcips2_ids);
static struct pci_driver pcips2_driver = {
.name = "pcips2",
.id_table = pcips2_ids,
.probe = pcips2_probe,
.remove = pcips2_remove,
};
module_pci_driver(pcips2_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
MODULE_DESCRIPTION("PCI PS/2 keyboard/mouse driver");
+504
View File
@@ -0,0 +1,504 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* GPIO based serio bus driver for bit banging the PS/2 protocol
*
* Author: Danilo Krummrich <danilokrummrich@dk-develop.de>
*/
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/preempt.h>
#include <linux/property.h>
#include <linux/of.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/timekeeping.h>
#define DRIVER_NAME "ps2-gpio"
#define PS2_MODE_RX 0
#define PS2_MODE_TX 1
#define PS2_START_BIT 0
#define PS2_DATA_BIT0 1
#define PS2_DATA_BIT1 2
#define PS2_DATA_BIT2 3
#define PS2_DATA_BIT3 4
#define PS2_DATA_BIT4 5
#define PS2_DATA_BIT5 6
#define PS2_DATA_BIT6 7
#define PS2_DATA_BIT7 8
#define PS2_PARITY_BIT 9
#define PS2_STOP_BIT 10
#define PS2_ACK_BIT 11
#define PS2_DEV_RET_ACK 0xfa
#define PS2_DEV_RET_NACK 0xfe
#define PS2_CMD_RESEND 0xfe
/*
* The PS2 protocol specifies a clock frequency between 10kHz and 16.7kHz,
* therefore the maximal interrupt interval should be 100us and the minimum
* interrupt interval should be ~60us. Let's allow +/- 20us for frequency
* deviations and interrupt latency.
*
* The data line must be sampled after ~30us to 50us after the falling edge,
* since the device updates the data line at the rising edge.
*
* ___ ______ ______ ______ ___
* \ / \ / \ / \ /
* \ / \ / \ / \ /
* \______/ \______/ \______/ \______/
*
* |-----------------| |--------|
* 60us/100us 30us/50us
*/
#define PS2_CLK_FREQ_MIN_HZ 10000
#define PS2_CLK_FREQ_MAX_HZ 16700
#define PS2_CLK_MIN_INTERVAL_US ((1000 * 1000) / PS2_CLK_FREQ_MAX_HZ)
#define PS2_CLK_MAX_INTERVAL_US ((1000 * 1000) / PS2_CLK_FREQ_MIN_HZ)
#define PS2_IRQ_MIN_INTERVAL_US (PS2_CLK_MIN_INTERVAL_US - 20)
#define PS2_IRQ_MAX_INTERVAL_US (PS2_CLK_MAX_INTERVAL_US + 20)
struct ps2_gpio_data {
struct device *dev;
struct serio *serio;
unsigned char mode;
struct gpio_desc *gpio_clk;
struct gpio_desc *gpio_data;
bool write_enable;
int irq;
ktime_t t_irq_now;
ktime_t t_irq_last;
struct {
unsigned char cnt;
unsigned char byte;
} rx;
struct {
unsigned char cnt;
unsigned char byte;
ktime_t t_xfer_start;
ktime_t t_xfer_end;
struct completion complete;
struct mutex mutex;
struct delayed_work work;
} tx;
};
static int ps2_gpio_open(struct serio *serio)
{
struct ps2_gpio_data *drvdata = serio->port_data;
drvdata->t_irq_last = 0;
drvdata->tx.t_xfer_end = 0;
enable_irq(drvdata->irq);
return 0;
}
static void ps2_gpio_close(struct serio *serio)
{
struct ps2_gpio_data *drvdata = serio->port_data;
flush_delayed_work(&drvdata->tx.work);
disable_irq(drvdata->irq);
}
static int __ps2_gpio_write(struct serio *serio, unsigned char val)
{
struct ps2_gpio_data *drvdata = serio->port_data;
disable_irq_nosync(drvdata->irq);
gpiod_direction_output(drvdata->gpio_clk, 0);
drvdata->mode = PS2_MODE_TX;
drvdata->tx.byte = val;
schedule_delayed_work(&drvdata->tx.work, usecs_to_jiffies(200));
return 0;
}
static int ps2_gpio_write(struct serio *serio, unsigned char val)
{
struct ps2_gpio_data *drvdata = serio->port_data;
int ret = 0;
if (in_task()) {
guard(mutex)(&drvdata->tx.mutex);
__ps2_gpio_write(serio, val);
if (!wait_for_completion_timeout(&drvdata->tx.complete,
msecs_to_jiffies(10000)))
ret = SERIO_TIMEOUT;
} else {
__ps2_gpio_write(serio, val);
}
return ret;
}
static void ps2_gpio_tx_work_fn(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct ps2_gpio_data *drvdata = container_of(dwork,
struct ps2_gpio_data,
tx.work);
drvdata->tx.t_xfer_start = ktime_get();
enable_irq(drvdata->irq);
gpiod_direction_output(drvdata->gpio_data, 0);
gpiod_direction_input(drvdata->gpio_clk);
}
static irqreturn_t ps2_gpio_irq_rx(struct ps2_gpio_data *drvdata)
{
unsigned char byte, cnt;
int data;
int rxflags = 0;
s64 us_delta;
byte = drvdata->rx.byte;
cnt = drvdata->rx.cnt;
drvdata->t_irq_now = ktime_get();
/*
* We need to consider spurious interrupts happening right after
* a TX xfer finished.
*/
us_delta = ktime_us_delta(drvdata->t_irq_now, drvdata->tx.t_xfer_end);
if (unlikely(us_delta < PS2_IRQ_MIN_INTERVAL_US))
goto end;
us_delta = ktime_us_delta(drvdata->t_irq_now, drvdata->t_irq_last);
if (us_delta > PS2_IRQ_MAX_INTERVAL_US && cnt) {
dev_err(drvdata->dev,
"RX: timeout, probably we missed an interrupt\n");
goto err;
} else if (unlikely(us_delta < PS2_IRQ_MIN_INTERVAL_US)) {
/* Ignore spurious IRQs. */
goto end;
}
drvdata->t_irq_last = drvdata->t_irq_now;
data = gpiod_get_value(drvdata->gpio_data);
if (unlikely(data < 0)) {
dev_err(drvdata->dev, "RX: failed to get data gpio val: %d\n",
data);
goto err;
}
switch (cnt) {
case PS2_START_BIT:
/* start bit should be low */
if (unlikely(data)) {
dev_err(drvdata->dev, "RX: start bit should be low\n");
goto err;
}
break;
case PS2_DATA_BIT0:
case PS2_DATA_BIT1:
case PS2_DATA_BIT2:
case PS2_DATA_BIT3:
case PS2_DATA_BIT4:
case PS2_DATA_BIT5:
case PS2_DATA_BIT6:
case PS2_DATA_BIT7:
/* processing data bits */
if (data)
byte |= (data << (cnt - 1));
break;
case PS2_PARITY_BIT:
/* check odd parity */
if (!((hweight8(byte) & 1) ^ data)) {
rxflags |= SERIO_PARITY;
dev_warn(drvdata->dev, "RX: parity error\n");
if (!drvdata->write_enable)
goto err;
}
break;
case PS2_STOP_BIT:
/* stop bit should be high */
if (unlikely(!data)) {
dev_err(drvdata->dev, "RX: stop bit should be high\n");
goto err;
}
/*
* Do not send spurious ACK's and NACK's when write fn is
* not provided.
*/
if (!drvdata->write_enable) {
if (byte == PS2_DEV_RET_NACK)
goto err;
else if (byte == PS2_DEV_RET_ACK)
break;
}
serio_interrupt(drvdata->serio, byte, rxflags);
dev_dbg(drvdata->dev, "RX: sending byte 0x%x\n", byte);
cnt = byte = 0;
goto end; /* success */
default:
dev_err(drvdata->dev, "RX: got out of sync with the device\n");
goto err;
}
cnt++;
goto end; /* success */
err:
cnt = byte = 0;
__ps2_gpio_write(drvdata->serio, PS2_CMD_RESEND);
end:
drvdata->rx.cnt = cnt;
drvdata->rx.byte = byte;
return IRQ_HANDLED;
}
static irqreturn_t ps2_gpio_irq_tx(struct ps2_gpio_data *drvdata)
{
unsigned char byte, cnt;
int data;
s64 us_delta;
cnt = drvdata->tx.cnt;
byte = drvdata->tx.byte;
drvdata->t_irq_now = ktime_get();
/*
* There might be pending IRQs since we disabled IRQs in
* __ps2_gpio_write(). We can expect at least one clock period until
* the device generates the first falling edge after releasing the
* clock line.
*/
us_delta = ktime_us_delta(drvdata->t_irq_now,
drvdata->tx.t_xfer_start);
if (unlikely(us_delta < PS2_CLK_MIN_INTERVAL_US))
goto end;
us_delta = ktime_us_delta(drvdata->t_irq_now, drvdata->t_irq_last);
if (us_delta > PS2_IRQ_MAX_INTERVAL_US && cnt > 1) {
dev_err(drvdata->dev,
"TX: timeout, probably we missed an interrupt\n");
goto err;
} else if (unlikely(us_delta < PS2_IRQ_MIN_INTERVAL_US)) {
/* Ignore spurious IRQs. */
goto end;
}
drvdata->t_irq_last = drvdata->t_irq_now;
switch (cnt) {
case PS2_START_BIT:
/* should never happen */
dev_err(drvdata->dev,
"TX: start bit should have been sent already\n");
goto err;
case PS2_DATA_BIT0:
case PS2_DATA_BIT1:
case PS2_DATA_BIT2:
case PS2_DATA_BIT3:
case PS2_DATA_BIT4:
case PS2_DATA_BIT5:
case PS2_DATA_BIT6:
case PS2_DATA_BIT7:
data = byte & BIT(cnt - 1);
gpiod_set_value(drvdata->gpio_data, data);
break;
case PS2_PARITY_BIT:
/* do odd parity */
data = !(hweight8(byte) & 1);
gpiod_set_value(drvdata->gpio_data, data);
break;
case PS2_STOP_BIT:
/* release data line to generate stop bit */
gpiod_direction_input(drvdata->gpio_data);
break;
case PS2_ACK_BIT:
data = gpiod_get_value(drvdata->gpio_data);
if (data) {
dev_warn(drvdata->dev, "TX: received NACK, retry\n");
goto err;
}
drvdata->tx.t_xfer_end = ktime_get();
drvdata->mode = PS2_MODE_RX;
complete(&drvdata->tx.complete);
cnt = 1;
goto end; /* success */
default:
/*
* Probably we missed the stop bit. Therefore we release data
* line and try again.
*/
gpiod_direction_input(drvdata->gpio_data);
dev_err(drvdata->dev, "TX: got out of sync with the device\n");
goto err;
}
cnt++;
goto end; /* success */
err:
cnt = 1;
gpiod_direction_input(drvdata->gpio_data);
__ps2_gpio_write(drvdata->serio, drvdata->tx.byte);
end:
drvdata->tx.cnt = cnt;
return IRQ_HANDLED;
}
static irqreturn_t ps2_gpio_irq(int irq, void *dev_id)
{
struct ps2_gpio_data *drvdata = dev_id;
return drvdata->mode ? ps2_gpio_irq_tx(drvdata) :
ps2_gpio_irq_rx(drvdata);
}
static int ps2_gpio_get_props(struct device *dev,
struct ps2_gpio_data *drvdata)
{
enum gpiod_flags gflags;
/* Enforce open drain, since this is required by the PS/2 bus. */
gflags = GPIOD_IN | GPIOD_FLAGS_BIT_OPEN_DRAIN;
drvdata->gpio_data = devm_gpiod_get(dev, "data", gflags);
if (IS_ERR(drvdata->gpio_data)) {
dev_err(dev, "failed to request data gpio: %ld",
PTR_ERR(drvdata->gpio_data));
return PTR_ERR(drvdata->gpio_data);
}
drvdata->gpio_clk = devm_gpiod_get(dev, "clk", gflags);
if (IS_ERR(drvdata->gpio_clk)) {
dev_err(dev, "failed to request clock gpio: %ld",
PTR_ERR(drvdata->gpio_clk));
return PTR_ERR(drvdata->gpio_clk);
}
drvdata->write_enable = device_property_read_bool(dev,
"write-enable");
return 0;
}
static int ps2_gpio_probe(struct platform_device *pdev)
{
struct ps2_gpio_data *drvdata;
struct serio *serio;
struct device *dev = &pdev->dev;
int error;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
serio = kzalloc_obj(*serio);
if (!drvdata || !serio) {
error = -ENOMEM;
goto err_free_serio;
}
error = ps2_gpio_get_props(dev, drvdata);
if (error)
goto err_free_serio;
if (gpiod_cansleep(drvdata->gpio_data) ||
gpiod_cansleep(drvdata->gpio_clk)) {
dev_err(dev, "GPIO data or clk are connected via slow bus\n");
error = -EINVAL;
goto err_free_serio;
}
drvdata->irq = platform_get_irq(pdev, 0);
if (drvdata->irq < 0) {
error = drvdata->irq;
goto err_free_serio;
}
error = devm_request_irq(dev, drvdata->irq, ps2_gpio_irq,
IRQF_NO_THREAD | IRQF_NO_AUTOEN, DRIVER_NAME,
drvdata);
if (error) {
dev_err(dev, "failed to request irq %d: %d\n",
drvdata->irq, error);
goto err_free_serio;
}
serio->id.type = SERIO_8042;
serio->open = ps2_gpio_open;
serio->close = ps2_gpio_close;
/*
* Write can be enabled in platform/dt data, but possibly it will not
* work because of the tough timings.
*/
serio->write = drvdata->write_enable ? ps2_gpio_write : NULL;
serio->port_data = drvdata;
serio->dev.parent = dev;
strscpy(serio->name, dev_name(dev), sizeof(serio->name));
strscpy(serio->phys, dev_name(dev), sizeof(serio->phys));
drvdata->serio = serio;
drvdata->dev = dev;
drvdata->mode = PS2_MODE_RX;
/*
* Tx count always starts at 1, as the start bit is sent implicitly by
* host-to-device communication initialization.
*/
drvdata->tx.cnt = 1;
INIT_DELAYED_WORK(&drvdata->tx.work, ps2_gpio_tx_work_fn);
init_completion(&drvdata->tx.complete);
mutex_init(&drvdata->tx.mutex);
serio_register_port(serio);
platform_set_drvdata(pdev, drvdata);
return 0; /* success */
err_free_serio:
kfree(serio);
return error;
}
static void ps2_gpio_remove(struct platform_device *pdev)
{
struct ps2_gpio_data *drvdata = platform_get_drvdata(pdev);
serio_unregister_port(drvdata->serio);
}
#if defined(CONFIG_OF)
static const struct of_device_id ps2_gpio_match[] = {
{ .compatible = "ps2-gpio", },
{ },
};
MODULE_DEVICE_TABLE(of, ps2_gpio_match);
#endif
static struct platform_driver ps2_gpio_driver = {
.probe = ps2_gpio_probe,
.remove = ps2_gpio_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(ps2_gpio_match),
},
};
module_platform_driver(ps2_gpio_driver);
MODULE_AUTHOR("Danilo Krummrich <danilokrummrich@dk-develop.de>");
MODULE_DESCRIPTION("GPIO PS2 driver");
MODULE_LICENSE("GPL v2");
+293
View File
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TQC PS/2 Multiplexer driver
*
* Copyright (C) 2010 Dmitry Eremin-Solenikov
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/serio.h>
MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
MODULE_LICENSE("GPL");
#define PS2MULT_KB_SELECTOR 0xA0
#define PS2MULT_MS_SELECTOR 0xA1
#define PS2MULT_ESCAPE 0x7D
#define PS2MULT_BSYNC 0x7E
#define PS2MULT_SESSION_START 0x55
#define PS2MULT_SESSION_END 0x56
struct ps2mult_port {
struct serio *serio;
unsigned char sel;
bool registered;
};
#define PS2MULT_NUM_PORTS 2
#define PS2MULT_KBD_PORT 0
#define PS2MULT_MOUSE_PORT 1
struct ps2mult {
struct serio *mx_serio;
struct ps2mult_port ports[PS2MULT_NUM_PORTS];
spinlock_t lock;
struct ps2mult_port *in_port;
struct ps2mult_port *out_port;
bool escape;
};
/* First MUST come PS2MULT_NUM_PORTS selectors */
static const unsigned char ps2mult_controls[] = {
PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
PS2MULT_ESCAPE, PS2MULT_BSYNC,
PS2MULT_SESSION_START, PS2MULT_SESSION_END,
};
static const struct serio_device_id ps2mult_serio_ids[] = {
{
.type = SERIO_RS232,
.proto = SERIO_PS2MULT,
.id = SERIO_ANY,
.extra = SERIO_ANY,
},
{ 0 }
};
MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
{
struct serio *mx_serio = psm->mx_serio;
serio_write(mx_serio, port->sel);
psm->out_port = port;
dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
}
static int ps2mult_serio_write(struct serio *serio, unsigned char data)
{
struct serio *mx_port = serio->parent;
struct ps2mult *psm = serio_get_drvdata(mx_port);
struct ps2mult_port *port = serio->port_data;
bool need_escape;
guard(spinlock_irqsave)(&psm->lock);
if (psm->out_port != port)
ps2mult_select_port(psm, port);
need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
dev_dbg(&serio->dev,
"write: %s%02x\n", need_escape ? "ESC " : "", data);
if (need_escape)
serio_write(mx_port, PS2MULT_ESCAPE);
serio_write(mx_port, data);
return 0;
}
static int ps2mult_serio_start(struct serio *serio)
{
struct ps2mult *psm = serio_get_drvdata(serio->parent);
struct ps2mult_port *port = serio->port_data;
guard(spinlock_irqsave)(&psm->lock);
port->registered = true;
return 0;
}
static void ps2mult_serio_stop(struct serio *serio)
{
struct ps2mult *psm = serio_get_drvdata(serio->parent);
struct ps2mult_port *port = serio->port_data;
guard(spinlock_irqsave)(&psm->lock);
port->registered = false;
}
static int ps2mult_create_port(struct ps2mult *psm, int i)
{
struct serio *mx_serio = psm->mx_serio;
struct serio *serio;
serio = kzalloc_obj(*serio);
if (!serio)
return -ENOMEM;
strscpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
snprintf(serio->phys, sizeof(serio->phys),
"%s/port%d", mx_serio->phys, i);
serio->id.type = SERIO_8042;
serio->write = ps2mult_serio_write;
serio->start = ps2mult_serio_start;
serio->stop = ps2mult_serio_stop;
serio->parent = psm->mx_serio;
serio->port_data = &psm->ports[i];
psm->ports[i].serio = serio;
return 0;
}
static void ps2mult_reset(struct ps2mult *psm)
{
guard(spinlock_irqsave)(&psm->lock);
serio_write(psm->mx_serio, PS2MULT_SESSION_END);
serio_write(psm->mx_serio, PS2MULT_SESSION_START);
ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
}
static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
{
struct ps2mult *psm;
int i;
int error;
if (!serio->write)
return -EINVAL;
psm = kzalloc_obj(*psm);
if (!psm)
return -ENOMEM;
spin_lock_init(&psm->lock);
psm->mx_serio = serio;
for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
psm->ports[i].sel = ps2mult_controls[i];
error = ps2mult_create_port(psm, i);
if (error)
goto err_out;
}
psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
serio_set_drvdata(serio, psm);
error = serio_open(serio, drv);
if (error)
goto err_out;
ps2mult_reset(psm);
for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
struct serio *s = psm->ports[i].serio;
dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
serio_register_port(s);
}
return 0;
err_out:
while (--i >= 0)
kfree(psm->ports[i].serio);
kfree(psm);
return error;
}
static void ps2mult_disconnect(struct serio *serio)
{
struct ps2mult *psm = serio_get_drvdata(serio);
/* Note that serio core already take care of children ports */
serio_write(serio, PS2MULT_SESSION_END);
serio_close(serio);
kfree(psm);
serio_set_drvdata(serio, NULL);
}
static int ps2mult_reconnect(struct serio *serio)
{
struct ps2mult *psm = serio_get_drvdata(serio);
ps2mult_reset(psm);
return 0;
}
static irqreturn_t ps2mult_interrupt(struct serio *serio,
unsigned char data, unsigned int dfl)
{
struct ps2mult *psm = serio_get_drvdata(serio);
struct ps2mult_port *in_port;
dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
guard(spinlock_irqsave)(&psm->lock);
if (psm->escape) {
psm->escape = false;
in_port = psm->in_port;
if (in_port->registered)
serio_interrupt(in_port->serio, data, dfl);
goto out;
}
switch (data) {
case PS2MULT_ESCAPE:
dev_dbg(&serio->dev, "ESCAPE\n");
psm->escape = true;
break;
case PS2MULT_BSYNC:
dev_dbg(&serio->dev, "BSYNC\n");
psm->in_port = psm->out_port;
break;
case PS2MULT_SESSION_START:
dev_dbg(&serio->dev, "SS\n");
break;
case PS2MULT_SESSION_END:
dev_dbg(&serio->dev, "SE\n");
break;
case PS2MULT_KB_SELECTOR:
dev_dbg(&serio->dev, "KB\n");
psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
break;
case PS2MULT_MS_SELECTOR:
dev_dbg(&serio->dev, "MS\n");
psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
break;
default:
in_port = psm->in_port;
if (in_port->registered)
serio_interrupt(in_port->serio, data, dfl);
break;
}
out:
return IRQ_HANDLED;
}
static struct serio_driver ps2mult_drv = {
.driver = {
.name = "ps2mult",
},
.description = "TQC PS/2 Multiplexer driver",
.id_table = ps2mult_serio_ids,
.interrupt = ps2mult_interrupt,
.connect = ps2mult_connect,
.disconnect = ps2mult_disconnect,
.reconnect = ps2mult_reconnect,
};
module_serio_driver(ps2mult_drv);
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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
*/
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/bitops.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/q40_master.h>
#include <asm/irq.h>
#include <asm/q40ints.h>
#define DRV_NAME "q40kbd"
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
struct q40kbd {
struct serio *port;
spinlock_t lock;
};
static irqreturn_t q40kbd_interrupt(int irq, void *dev_id)
{
struct q40kbd *q40kbd = dev_id;
guard(spinlock_irqsave)(&q40kbd->lock);
if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))
serio_interrupt(q40kbd->port, master_inb(KEYCODE_REG), 0);
master_outb(-1, KEYBOARD_UNLOCK_REG);
return IRQ_HANDLED;
}
/*
* q40kbd_flush() flushes all data that may be in the keyboard buffers
*/
static void q40kbd_flush(struct q40kbd *q40kbd)
{
int maxread = 100;
guard(spinlock_irqsave)(&q40kbd->lock);
while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)))
master_inb(KEYCODE_REG);
}
static void q40kbd_stop(void)
{
master_outb(0, KEY_IRQ_ENABLE_REG);
master_outb(-1, KEYBOARD_UNLOCK_REG);
}
/*
* 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)
{
struct q40kbd *q40kbd = port->port_data;
q40kbd_flush(q40kbd);
/* 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)
{
struct q40kbd *q40kbd = port->port_data;
q40kbd_stop();
q40kbd_flush(q40kbd);
}
static int q40kbd_probe(struct platform_device *pdev)
{
struct q40kbd *q40kbd;
struct serio *port;
int error;
q40kbd = kzalloc_obj(*q40kbd);
port = kzalloc_obj(*port);
if (!q40kbd || !port) {
error = -ENOMEM;
goto err_free_mem;
}
q40kbd->port = port;
spin_lock_init(&q40kbd->lock);
port->id.type = SERIO_8042;
port->open = q40kbd_open;
port->close = q40kbd_close;
port->port_data = q40kbd;
port->dev.parent = &pdev->dev;
strscpy(port->name, "Q40 Kbd Port", sizeof(port->name));
strscpy(port->phys, "Q40", sizeof(port->phys));
q40kbd_stop();
error = request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0,
DRV_NAME, q40kbd);
if (error) {
dev_err(&pdev->dev, "Can't get irq %d.\n", Q40_IRQ_KEYBOARD);
goto err_free_mem;
}
serio_register_port(q40kbd->port);
platform_set_drvdata(pdev, q40kbd);
printk(KERN_INFO "serio: Q40 kbd registered\n");
return 0;
err_free_mem:
kfree(port);
kfree(q40kbd);
return error;
}
static void q40kbd_remove(struct platform_device *pdev)
{
struct q40kbd *q40kbd = platform_get_drvdata(pdev);
/*
* q40kbd_close() will be called as part of unregistering
* and will ensure that IRQ is turned off, so it is safe
* to unregister port first and free IRQ later.
*/
serio_unregister_port(q40kbd->port);
free_irq(Q40_IRQ_KEYBOARD, q40kbd);
kfree(q40kbd);
}
static struct platform_driver q40kbd_driver = {
.driver = {
.name = "q40kbd",
},
.remove = q40kbd_remove,
};
module_platform_driver_probe(q40kbd_driver, q40kbd_probe);
+152
View File
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2000-2001 Vojtech Pavlik
* Copyright (c) 2002 Russell King
*/
/*
* Acorn RiscPC PS/2 keyboard controller driver for Linux/ARM
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/serio.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <mach/hardware.h>
#include <asm/hardware/iomd.h>
MODULE_AUTHOR("Vojtech Pavlik, Russell King");
MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:kart");
struct rpckbd_data {
int tx_irq;
int rx_irq;
};
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)
{
struct rpckbd_data *rpckbd = port->port_data;
/* Reset the keyboard state machine. */
iomd_writeb(0, IOMD_KCTRL);
iomd_writeb(8, IOMD_KCTRL);
iomd_readb(IOMD_KARTRX);
if (request_irq(rpckbd->rx_irq, rpckbd_rx, 0, "rpckbd", port) != 0) {
printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ\n");
return -EBUSY;
}
if (request_irq(rpckbd->tx_irq, rpckbd_tx, 0, "rpckbd", port) != 0) {
printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ\n");
free_irq(rpckbd->rx_irq, port);
return -EBUSY;
}
return 0;
}
static void rpckbd_close(struct serio *port)
{
struct rpckbd_data *rpckbd = port->port_data;
free_irq(rpckbd->rx_irq, port);
free_irq(rpckbd->tx_irq, port);
}
/*
* Allocate and initialize serio structure for subsequent registration
* with serio core.
*/
static int rpckbd_probe(struct platform_device *dev)
{
struct rpckbd_data *rpckbd;
struct serio *serio;
int tx_irq, rx_irq;
rx_irq = platform_get_irq(dev, 0);
if (rx_irq < 0)
return rx_irq;
tx_irq = platform_get_irq(dev, 1);
if (tx_irq < 0)
return tx_irq;
serio = kzalloc_obj(*serio);
rpckbd = kzalloc_obj(*rpckbd);
if (!serio || !rpckbd) {
kfree(rpckbd);
kfree(serio);
return -ENOMEM;
}
rpckbd->rx_irq = rx_irq;
rpckbd->tx_irq = tx_irq;
serio->id.type = SERIO_8042;
serio->write = rpckbd_write;
serio->open = rpckbd_open;
serio->close = rpckbd_close;
serio->dev.parent = &dev->dev;
serio->port_data = rpckbd;
strscpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name));
strscpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys));
platform_set_drvdata(dev, serio);
serio_register_port(serio);
return 0;
}
static void rpckbd_remove(struct platform_device *dev)
{
struct serio *serio = platform_get_drvdata(dev);
struct rpckbd_data *rpckbd = serio->port_data;
serio_unregister_port(serio);
kfree(rpckbd);
}
static struct platform_driver rpckbd_driver = {
.probe = rpckbd_probe,
.remove = rpckbd_remove,
.driver = {
.name = "kart",
},
};
module_platform_driver(rpckbd_driver);
+384
View File
@@ -0,0 +1,384 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/drivers/input/serio/sa1111ps2.c
*
* Copyright (C) 2002 Russell King
*/
#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/hardware/sa1111.h>
#define PS2CR 0x0000
#define PS2STAT 0x0004
#define PS2DATA 0x0008
#define PS2CLKDIV 0x000c
#define PS2PRECNT 0x0010
#define PS2CR_ENA 0x08
#define PS2CR_FKD 0x02
#define PS2CR_FKC 0x01
#define PS2STAT_STP 0x0100
#define PS2STAT_TXE 0x0080
#define PS2STAT_TXB 0x0040
#define PS2STAT_RXF 0x0020
#define PS2STAT_RXB 0x0010
#define PS2STAT_ENA 0x0008
#define PS2STAT_RXP 0x0004
#define PS2STAT_KBD 0x0002
#define PS2STAT_KBC 0x0001
struct ps2if {
struct serio *io;
struct sa1111_dev *dev;
void __iomem *base;
int rx_irq;
int tx_irq;
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 = readl_relaxed(ps2if->base + PS2STAT);
while (status & PS2STAT_RXF) {
if (status & PS2STAT_STP)
writel_relaxed(PS2STAT_STP, ps2if->base + PS2STAT);
flag = (status & PS2STAT_STP ? SERIO_FRAME : 0) |
(status & PS2STAT_RXP ? 0 : SERIO_PARITY);
scancode = readl_relaxed(ps2if->base + PS2DATA) & 0xff;
if (hweight8(scancode) & 1)
flag ^= SERIO_PARITY;
serio_interrupt(ps2if->io, scancode, flag);
status = readl_relaxed(ps2if->base + 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;
guard(spinlock)(&ps2if->lock);
status = readl_relaxed(ps2if->base + PS2STAT);
if (ps2if->head == ps2if->tail) {
disable_irq_nosync(irq);
/* done */
} else if (status & PS2STAT_TXE) {
writel_relaxed(ps2if->buf[ps2if->tail], ps2if->base + PS2DATA);
ps2if->tail = (ps2if->tail + 1) & (sizeof(ps2if->buf) - 1);
}
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 int head;
guard(spinlock_irqsave)(&ps2if->lock);
/*
* If the TX register is empty, we can go straight out.
*/
if (readl_relaxed(ps2if->base + PS2STAT) & PS2STAT_TXE) {
writel_relaxed(val, ps2if->base + PS2DATA);
} else {
if (ps2if->head == ps2if->tail)
enable_irq(ps2if->tx_irq);
head = (ps2if->head + 1) & (sizeof(ps2if->buf) - 1);
if (head != ps2if->tail) {
ps2if->buf[ps2if->head] = val;
ps2if->head = head;
}
}
return 0;
}
static int ps2_open(struct serio *io)
{
struct ps2if *ps2if = io->port_data;
int ret;
ret = sa1111_enable_device(ps2if->dev);
if (ret)
return ret;
ret = request_irq(ps2if->rx_irq, ps2_rxint, 0,
SA1111_DRIVER_NAME(ps2if->dev), ps2if);
if (ret) {
printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n",
ps2if->rx_irq, ret);
sa1111_disable_device(ps2if->dev);
return ret;
}
ret = request_irq(ps2if->tx_irq, ps2_txint, 0,
SA1111_DRIVER_NAME(ps2if->dev), ps2if);
if (ret) {
printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n",
ps2if->tx_irq, ret);
free_irq(ps2if->rx_irq, ps2if);
sa1111_disable_device(ps2if->dev);
return ret;
}
ps2if->open = 1;
enable_irq_wake(ps2if->rx_irq);
writel_relaxed(PS2CR_ENA, ps2if->base + PS2CR);
return 0;
}
static void ps2_close(struct serio *io)
{
struct ps2if *ps2if = io->port_data;
writel_relaxed(0, ps2if->base + PS2CR);
disable_irq_wake(ps2if->rx_irq);
ps2if->open = 0;
free_irq(ps2if->tx_irq, ps2if);
free_irq(ps2if->rx_irq, ps2if);
sa1111_disable_device(ps2if->dev);
}
/*
* Clear the input buffer.
*/
static void ps2_clear_input(struct ps2if *ps2if)
{
int maxread = 100;
while (maxread--) {
if ((readl_relaxed(ps2if->base + PS2DATA) & 0xff) == 0xff)
break;
}
}
static unsigned int ps2_test_one(struct ps2if *ps2if,
unsigned int mask)
{
unsigned int val;
writel_relaxed(PS2CR_ENA | mask, ps2if->base + PS2CR);
udelay(10);
val = readl_relaxed(ps2if->base + 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 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;
}
writel_relaxed(0, ps2if->base + 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 = kzalloc_obj(*ps2if);
serio = kzalloc_obj(*serio);
if (!ps2if || !serio) {
ret = -ENOMEM;
goto free;
}
serio->id.type = SERIO_8042;
serio->write = ps2_write;
serio->open = ps2_open;
serio->close = ps2_close;
strscpy(serio->name, dev_name(&dev->dev), sizeof(serio->name));
strscpy(serio->phys, dev_name(&dev->dev), 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);
ps2if->rx_irq = sa1111_get_irq(dev, 0);
if (ps2if->rx_irq <= 0) {
ret = ps2if->rx_irq ? : -ENXIO;
goto free;
}
ps2if->tx_irq = sa1111_get_irq(dev, 1);
if (ps2if->tx_irq <= 0) {
ret = ps2if->tx_irq ? : -ENXIO;
goto free;
}
/*
* 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 */
writel_relaxed(0, ps2if->base + PS2CLKDIV);
writel_relaxed(127, ps2if->base + 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, resource_size(&dev->res));
free:
sa1111_set_drvdata(dev, NULL);
kfree(ps2if);
kfree(serio);
return ret;
}
/*
* Remove one device from this driver.
*/
static void ps2_remove(struct sa1111_dev *dev)
{
struct ps2if *ps2if = sa1111_get_drvdata(dev);
serio_unregister_port(ps2if->io);
release_mem_region(dev->res.start, resource_size(&dev->res));
sa1111_set_drvdata(dev, NULL);
kfree(ps2if);
}
/*
* Our device driver structure
*/
static struct sa1111_driver ps2_driver = {
.drv = {
.name = "sa1111-ps2",
.owner = THIS_MODULE,
},
.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");
File diff suppressed because it is too large Load Diff
+418
View File
@@ -0,0 +1,418 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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
*/
#include <linux/kref.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/serio.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[20];
struct kref kref;
struct serio *serio;
struct miscdevice dev;
wait_queue_head_t wait;
struct list_head client_list;
struct list_head node;
bool dead;
};
struct serio_raw_client {
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);
/*********************************************************************
* Interface with userspace (file operations) *
*********************************************************************/
static int serio_raw_fasync(int fd, struct file *file, int on)
{
struct serio_raw_client *client = file->private_data;
return fasync_helper(fd, file, on, &client->fasync);
}
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_client *client;
scoped_guard(mutex_intr, &serio_raw_mutex) {
serio_raw = serio_raw_locate(iminor(inode));
if (!serio_raw)
return -ENODEV;
if (serio_raw->dead)
return -ENODEV;
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
client->serio_raw = serio_raw;
file->private_data = client;
kref_get(&serio_raw->kref);
scoped_guard(serio_pause_rx, serio_raw->serio)
list_add_tail(&client->node, &serio_raw->client_list);
return 0;
}
return -EINTR;
}
static void serio_raw_free(struct kref *kref)
{
struct serio_raw *serio_raw =
container_of(kref, struct serio_raw, kref);
put_device(&serio_raw->serio->dev);
kfree(serio_raw);
}
static int serio_raw_release(struct inode *inode, struct file *file)
{
struct serio_raw_client *client = file->private_data;
struct serio_raw *serio_raw = client->serio_raw;
scoped_guard(serio_pause_rx, serio_raw->serio)
list_del(&client->node);
kfree(client);
kref_put(&serio_raw->kref, serio_raw_free);
return 0;
}
static bool serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c)
{
guard(serio_pause_rx)(serio_raw->serio);
if (serio_raw->head == serio_raw->tail)
return false; /* queue is empty */
*c = serio_raw->queue[serio_raw->tail];
serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN;
return true;
}
static ssize_t serio_raw_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct serio_raw_client *client = file->private_data;
struct serio_raw *serio_raw = client->serio_raw;
char c;
ssize_t read = 0;
int error;
for (;;) {
if (serio_raw->dead)
return -ENODEV;
if (serio_raw->head == serio_raw->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
if (count == 0)
break;
while (read < count && serio_raw_fetch_byte(serio_raw, &c)) {
if (put_user(c, buffer++))
return -EFAULT;
read++;
}
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(serio_raw->wait,
serio_raw->head != serio_raw->tail ||
serio_raw->dead);
if (error)
return error;
}
}
return read;
}
static ssize_t serio_raw_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct serio_raw_client *client = file->private_data;
struct serio_raw *serio_raw = client->serio_raw;
int written = 0;
unsigned char c;
scoped_guard(mutex_intr, &serio_raw_mutex) {
if (serio_raw->dead)
return -ENODEV;
if (count > 32)
count = 32;
while (count--) {
if (get_user(c, buffer++))
return -EFAULT;
if (serio_write(serio_raw->serio, c)) {
/* Either signal error or partial write */
return written ?: -EIO;
}
written++;
}
return written;
}
return -EINTR;
}
static __poll_t serio_raw_poll(struct file *file, poll_table *wait)
{
struct serio_raw_client *client = file->private_data;
struct serio_raw *serio_raw = client->serio_raw;
__poll_t mask;
poll_wait(file, &serio_raw->wait, wait);
mask = serio_raw->dead ? EPOLLHUP | EPOLLERR : EPOLLOUT | EPOLLWRNORM;
if (serio_raw->head != serio_raw->tail)
mask |= EPOLLIN | EPOLLRDNORM;
return mask;
}
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,
.llseek = noop_llseek,
};
/*********************************************************************
* 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_client *client;
unsigned int head = serio_raw->head;
/* we are holding serio->lock here so we are protected */
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(client, &serio_raw->client_list, node)
kill_fasync(&client->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)
{
static atomic_t serio_raw_no = ATOMIC_INIT(-1);
struct serio_raw *serio_raw;
int err;
serio_raw = kzalloc_obj(*serio_raw);
if (!serio_raw) {
dev_dbg(&serio->dev, "can't allocate memory for a device\n");
return -ENOMEM;
}
snprintf(serio_raw->name, sizeof(serio_raw->name),
"serio_raw%u", atomic_inc_return(&serio_raw_no));
kref_init(&serio_raw->kref);
INIT_LIST_HEAD(&serio_raw->client_list);
init_waitqueue_head(&serio_raw->wait);
serio_raw->serio = serio;
get_device(&serio->dev);
serio_set_drvdata(serio, serio_raw);
err = serio_open(serio, drv);
if (err)
goto err_free;
err = mutex_lock_killable(&serio_raw_mutex);
if (err)
goto err_close;
list_add_tail(&serio_raw->node, &serio_raw_list);
mutex_unlock(&serio_raw_mutex);
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) {
dev_err(&serio->dev,
"failed to register raw access device for %s\n",
serio->phys);
goto err_unlink;
}
dev_info(&serio->dev, "raw access enabled on %s (%s, minor %d)\n",
serio->phys, serio_raw->name, serio_raw->dev.minor);
return 0;
err_unlink:
list_del_init(&serio_raw->node);
err_close:
serio_close(serio);
err_free:
serio_set_drvdata(serio, NULL);
kref_put(&serio_raw->kref, serio_raw_free);
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) {
dev_dbg(&serio->dev,
"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;
}
/*
* Wake up users waiting for IO so they can disconnect from
* dead device.
*/
static void serio_raw_hangup(struct serio_raw *serio_raw)
{
struct serio_raw_client *client;
scoped_guard(serio_pause_rx, serio_raw->serio) {
list_for_each_entry(client, &serio_raw->client_list, node)
kill_fasync(&client->fasync, SIGIO, POLL_HUP);
}
wake_up_interruptible(&serio_raw->wait);
}
static void serio_raw_disconnect(struct serio *serio)
{
struct serio_raw *serio_raw = serio_get_drvdata(serio);
misc_deregister(&serio_raw->dev);
scoped_guard(mutex, &serio_raw_mutex) {
serio_raw->dead = true;
list_del_init(&serio_raw->node);
}
serio_raw_hangup(serio_raw);
serio_close(serio);
kref_put(&serio_raw->kref, serio_raw_free);
serio_set_drvdata(serio, NULL);
}
static const struct serio_device_id serio_raw_serio_ids[] = {
{
.type = SERIO_8042,
.proto = SERIO_ANY,
.id = SERIO_ANY,
.extra = SERIO_ANY,
},
{
.type = SERIO_8042_XL,
.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 = true,
};
module_serio_driver(serio_raw_drv);
+298
View File
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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.
*/
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/tty.h>
#include <linux/compat.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->ops->write(serport->tty, &data, 1) != 1);
}
static int serport_serio_open(struct serio *serio)
{
struct serport *serport = serio->port_data;
guard(spinlock_irqsave)(&serport->lock);
set_bit(SERPORT_ACTIVE, &serport->flags);
return 0;
}
static void serport_serio_close(struct serio *serio)
{
struct serport *serport = serio->port_data;
guard(spinlock_irqsave)(&serport->lock);
clear_bit(SERPORT_ACTIVE, &serport->flags);
}
/*
* 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_obj(*serport);
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 = 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 and flags, one by one to the
* 'interrupt' routine.
*/
static void serport_ldisc_receive(struct tty_struct *tty, const u8 *cp,
const u8 *fp, size_t count)
{
struct serport *serport = tty->disc_data;
unsigned int ch_flags = 0;
int i;
guard(spinlock_irqsave)(&serport->lock);
if (!test_bit(SERPORT_ACTIVE, &serport->flags))
return;
for (i = 0; i < count; i++) {
if (fp) {
switch (fp[i]) {
case TTY_FRAME:
ch_flags = SERIO_FRAME;
break;
case TTY_PARITY:
ch_flags = SERIO_PARITY;
break;
default:
ch_flags = 0;
break;
}
}
serio_interrupt(serport->serio, cp[i], ch_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,
u8 *kbuf, size_t nr, void **cookie,
unsigned long offset)
{
struct serport *serport = tty->disc_data;
struct serio *serio;
if (test_and_set_bit(SERPORT_BUSY, &serport->flags))
return -EBUSY;
serport->serio = serio = kzalloc_obj(*serio);
if (!serio)
return -ENOMEM;
strscpy(serio->name, "Serial port", sizeof(serio->name));
snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty));
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->dev.parent = tty->dev;
serio_register_port(serport->serio);
printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty));
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;
}
static void serport_set_type(struct tty_struct *tty, unsigned long type)
{
struct serport *serport = tty->disc_data;
serport->id.proto = type & 0x000000ff;
serport->id.id = (type & 0x0000ff00) >> 8;
serport->id.extra = (type & 0x00ff0000) >> 16;
}
/*
* serport_ldisc_ioctl() allows to set the port protocol, and device ID
*/
static int serport_ldisc_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
if (cmd == SPIOCSTYPE) {
unsigned long type;
if (get_user(type, (unsigned long __user *) arg))
return -EFAULT;
serport_set_type(tty, type);
return 0;
}
return -EINVAL;
}
#ifdef CONFIG_COMPAT
#define COMPAT_SPIOCSTYPE _IOW('q', 0x01, compat_ulong_t)
static int serport_ldisc_compat_ioctl(struct tty_struct *tty,
unsigned int cmd, unsigned long arg)
{
if (cmd == COMPAT_SPIOCSTYPE) {
void __user *uarg = compat_ptr(arg);
compat_ulong_t compat_type;
if (get_user(compat_type, (compat_ulong_t __user *)uarg))
return -EFAULT;
serport_set_type(tty, compat_type);
return 0;
}
return -EINVAL;
}
#endif
static void serport_ldisc_hangup(struct tty_struct *tty)
{
struct serport *serport = tty->disc_data;
scoped_guard(spinlock_irqsave, &serport->lock)
set_bit(SERPORT_DEAD, &serport->flags);
wake_up_interruptible(&serport->wait);
}
static void serport_ldisc_write_wakeup(struct tty_struct * tty)
{
struct serport *serport = tty->disc_data;
guard(spinlock_irqsave)(&serport->lock);
if (test_bit(SERPORT_ACTIVE, &serport->flags))
serio_drv_write_wakeup(serport->serio);
}
/*
* The line discipline structure.
*/
static struct tty_ldisc_ops serport_ldisc = {
.owner = THIS_MODULE,
.num = N_MOUSE,
.name = "input",
.open = serport_ldisc_open,
.close = serport_ldisc_close,
.read = serport_ldisc_read,
.ioctl = serport_ldisc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = serport_ldisc_compat_ioctl,
#endif
.receive_buf = serport_ldisc_receive,
.hangup = serport_ldisc_hangup,
.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(&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(&serport_ldisc);
}
module_init(serport_init);
module_exit(serport_exit);
+331
View File
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Allwinner A10 PS2 host controller
*
* Author: Vishnu Patekar <vishnupatekar0510@gmail.com>
* Aaron.maoye <leafy.myeh@newbietech.com>
*/
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "sun4i-ps2"
/* register offset definitions */
#define PS2_REG_GCTL 0x00 /* PS2 Module Global Control Reg */
#define PS2_REG_DATA 0x04 /* PS2 Module Data Reg */
#define PS2_REG_LCTL 0x08 /* PS2 Module Line Control Reg */
#define PS2_REG_LSTS 0x0C /* PS2 Module Line Status Reg */
#define PS2_REG_FCTL 0x10 /* PS2 Module FIFO Control Reg */
#define PS2_REG_FSTS 0x14 /* PS2 Module FIFO Status Reg */
#define PS2_REG_CLKDR 0x18 /* PS2 Module Clock Divider Reg*/
/* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */
#define PS2_GCTL_INTFLAG BIT(4)
#define PS2_GCTL_INTEN BIT(3)
#define PS2_GCTL_RESET BIT(2)
#define PS2_GCTL_MASTER BIT(1)
#define PS2_GCTL_BUSEN BIT(0)
/* PS2 LINE CONTROL REGISTER */
#define PS2_LCTL_NOACK BIT(18)
#define PS2_LCTL_TXDTOEN BIT(8)
#define PS2_LCTL_STOPERREN BIT(3)
#define PS2_LCTL_ACKERREN BIT(2)
#define PS2_LCTL_PARERREN BIT(1)
#define PS2_LCTL_RXDTOEN BIT(0)
/* PS2 LINE STATUS REGISTER */
#define PS2_LSTS_TXTDO BIT(8)
#define PS2_LSTS_STOPERR BIT(3)
#define PS2_LSTS_ACKERR BIT(2)
#define PS2_LSTS_PARERR BIT(1)
#define PS2_LSTS_RXTDO BIT(0)
#define PS2_LINE_ERROR_BIT \
(PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | \
PS2_LSTS_PARERR | PS2_LSTS_RXTDO)
/* PS2 FIFO CONTROL REGISTER */
#define PS2_FCTL_TXRST BIT(17)
#define PS2_FCTL_RXRST BIT(16)
#define PS2_FCTL_TXUFIEN BIT(10)
#define PS2_FCTL_TXOFIEN BIT(9)
#define PS2_FCTL_TXRDYIEN BIT(8)
#define PS2_FCTL_RXUFIEN BIT(2)
#define PS2_FCTL_RXOFIEN BIT(1)
#define PS2_FCTL_RXRDYIEN BIT(0)
/* PS2 FIFO STATUS REGISTER */
#define PS2_FSTS_TXUF BIT(10)
#define PS2_FSTS_TXOF BIT(9)
#define PS2_FSTS_TXRDY BIT(8)
#define PS2_FSTS_RXUF BIT(2)
#define PS2_FSTS_RXOF BIT(1)
#define PS2_FSTS_RXRDY BIT(0)
#define PS2_FIFO_ERROR_BIT \
(PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_RXUF | PS2_FSTS_RXOF)
#define PS2_SAMPLE_CLK 1000000
#define PS2_SCLK 125000
struct sun4i_ps2data {
struct serio *serio;
struct device *dev;
/* IO mapping base */
void __iomem *reg_base;
/* clock management */
struct clk *clk;
/* irq */
spinlock_t lock;
int irq;
};
static irqreturn_t sun4i_ps2_interrupt(int irq, void *dev_id)
{
struct sun4i_ps2data *drvdata = dev_id;
u32 intr_status;
u32 fifo_status;
unsigned char byte;
unsigned int rxflags = 0;
u32 rval;
guard(spinlock)(&drvdata->lock);
/* Get the PS/2 interrupts and clear them */
intr_status = readl(drvdata->reg_base + PS2_REG_LSTS);
fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS);
/* Check line status register */
if (intr_status & PS2_LINE_ERROR_BIT) {
rxflags = (intr_status & PS2_LINE_ERROR_BIT) ? SERIO_FRAME : 0;
rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_PARITY : 0;
rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_TIMEOUT : 0;
rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR |
PS2_LSTS_PARERR | PS2_LSTS_RXTDO;
writel(rval, drvdata->reg_base + PS2_REG_LSTS);
}
/* Check FIFO status register */
if (fifo_status & PS2_FIFO_ERROR_BIT) {
rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY |
PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY;
writel(rval, drvdata->reg_base + PS2_REG_FSTS);
}
rval = (fifo_status >> 16) & 0x3;
while (rval--) {
byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff;
serio_interrupt(drvdata->serio, byte, rxflags);
}
writel(intr_status, drvdata->reg_base + PS2_REG_LSTS);
writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS);
return IRQ_HANDLED;
}
static int sun4i_ps2_open(struct serio *serio)
{
struct sun4i_ps2data *drvdata = serio->port_data;
u32 src_clk = 0;
u32 clk_scdf;
u32 clk_pcdf;
u32 rval;
/* Set line control and enable interrupt */
rval = PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN
| PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN;
writel(rval, drvdata->reg_base + PS2_REG_LCTL);
/* Reset FIFO */
rval = PS2_FCTL_TXRST | PS2_FCTL_RXRST | PS2_FCTL_TXUFIEN
| PS2_FCTL_TXOFIEN | PS2_FCTL_RXUFIEN
| PS2_FCTL_RXOFIEN | PS2_FCTL_RXRDYIEN;
writel(rval, drvdata->reg_base + PS2_REG_FCTL);
src_clk = clk_get_rate(drvdata->clk);
/* Set clock divider register */
clk_scdf = src_clk / PS2_SAMPLE_CLK - 1;
clk_pcdf = PS2_SAMPLE_CLK / PS2_SCLK - 1;
rval = (clk_scdf << 8) | clk_pcdf;
writel(rval, drvdata->reg_base + PS2_REG_CLKDR);
/* Set global control register */
rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER
| PS2_GCTL_BUSEN;
guard(spinlock_irqsave)(&drvdata->lock);
writel(rval, drvdata->reg_base + PS2_REG_GCTL);
return 0;
}
static void sun4i_ps2_close(struct serio *serio)
{
struct sun4i_ps2data *drvdata = serio->port_data;
u32 rval;
/* Shut off the interrupt */
rval = readl(drvdata->reg_base + PS2_REG_GCTL);
writel(rval & ~(PS2_GCTL_INTEN), drvdata->reg_base + PS2_REG_GCTL);
synchronize_irq(drvdata->irq);
}
static int sun4i_ps2_write(struct serio *serio, unsigned char val)
{
unsigned long expire = jiffies + msecs_to_jiffies(10000);
struct sun4i_ps2data *drvdata = serio->port_data;
do {
if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) {
writel(val, drvdata->reg_base + PS2_REG_DATA);
return 0;
}
} while (time_before(jiffies, expire));
return SERIO_TIMEOUT;
}
static int sun4i_ps2_probe(struct platform_device *pdev)
{
struct resource *res; /* IO mem resources */
struct sun4i_ps2data *drvdata;
struct serio *serio;
struct device *dev = &pdev->dev;
int error;
drvdata = kzalloc_obj(*drvdata);
serio = kzalloc_obj(*serio);
if (!drvdata || !serio) {
error = -ENOMEM;
goto err_free_mem;
}
spin_lock_init(&drvdata->lock);
/* IO */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "failed to locate registers\n");
error = -ENXIO;
goto err_free_mem;
}
drvdata->reg_base = ioremap(res->start, resource_size(res));
if (!drvdata->reg_base) {
dev_err(dev, "failed to map registers\n");
error = -ENOMEM;
goto err_free_mem;
}
drvdata->clk = clk_get(dev, NULL);
if (IS_ERR(drvdata->clk)) {
error = PTR_ERR(drvdata->clk);
dev_err(dev, "couldn't get clock %d\n", error);
goto err_ioremap;
}
error = clk_prepare_enable(drvdata->clk);
if (error) {
dev_err(dev, "failed to enable clock %d\n", error);
goto err_clk;
}
serio->id.type = SERIO_8042;
serio->write = sun4i_ps2_write;
serio->open = sun4i_ps2_open;
serio->close = sun4i_ps2_close;
serio->port_data = drvdata;
serio->dev.parent = dev;
strscpy(serio->name, dev_name(dev), sizeof(serio->name));
strscpy(serio->phys, dev_name(dev), sizeof(serio->phys));
/* shutoff interrupt */
writel(0, drvdata->reg_base + PS2_REG_GCTL);
/* Get IRQ for the device */
drvdata->irq = platform_get_irq(pdev, 0);
if (drvdata->irq < 0) {
error = drvdata->irq;
goto err_disable_clk;
}
drvdata->serio = serio;
drvdata->dev = dev;
error = request_irq(drvdata->irq, sun4i_ps2_interrupt, 0,
DRIVER_NAME, drvdata);
if (error) {
dev_err(drvdata->dev, "failed to allocate interrupt %d: %d\n",
drvdata->irq, error);
goto err_disable_clk;
}
serio_register_port(serio);
platform_set_drvdata(pdev, drvdata);
return 0; /* success */
err_disable_clk:
clk_disable_unprepare(drvdata->clk);
err_clk:
clk_put(drvdata->clk);
err_ioremap:
iounmap(drvdata->reg_base);
err_free_mem:
kfree(serio);
kfree(drvdata);
return error;
}
static void sun4i_ps2_remove(struct platform_device *pdev)
{
struct sun4i_ps2data *drvdata = platform_get_drvdata(pdev);
serio_unregister_port(drvdata->serio);
free_irq(drvdata->irq, drvdata);
clk_disable_unprepare(drvdata->clk);
clk_put(drvdata->clk);
iounmap(drvdata->reg_base);
kfree(drvdata);
}
static const struct of_device_id sun4i_ps2_match[] = {
{ .compatible = "allwinner,sun4i-a10-ps2", },
{ },
};
MODULE_DEVICE_TABLE(of, sun4i_ps2_match);
static struct platform_driver sun4i_ps2_driver = {
.probe = sun4i_ps2_probe,
.remove = sun4i_ps2_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = sun4i_ps2_match,
},
};
module_platform_driver(sun4i_ps2_driver);
MODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510@gmail.com>");
MODULE_AUTHOR("Aaron.maoye <leafy.myeh@newbietech.com>");
MODULE_DESCRIPTION("Allwinner A10/Sun4i PS/2 driver");
MODULE_LICENSE("GPL v2");
+313
View File
@@ -0,0 +1,313 @@
/*
* userio kernel serio device emulation module
* Copyright (C) 2015 Red Hat
* Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser
* General Public License for more details.
*/
#include <linux/circ_buf.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <uapi/linux/userio.h>
#define USERIO_NAME "userio"
#define USERIO_BUFSIZE 16
static struct miscdevice userio_misc;
struct userio_device {
struct serio *serio;
struct mutex mutex;
bool running;
u8 head;
u8 tail;
spinlock_t buf_lock;
unsigned char buf[USERIO_BUFSIZE];
wait_queue_head_t waitq;
};
/**
* userio_device_write - Write data from serio to a userio device in userspace
* @id: The serio port for the userio device
* @val: The data to write to the device
*/
static int userio_device_write(struct serio *id, unsigned char val)
{
struct userio_device *userio = id->port_data;
scoped_guard(spinlock_irqsave, &userio->buf_lock) {
userio->buf[userio->head] = val;
userio->head = (userio->head + 1) % USERIO_BUFSIZE;
if (userio->head == userio->tail)
dev_warn(userio_misc.this_device,
"Buffer overflowed, userio client isn't keeping up");
}
wake_up_interruptible(&userio->waitq);
return 0;
}
static int userio_char_open(struct inode *inode, struct file *file)
{
struct userio_device *userio __free(kfree) =
kzalloc_obj(*userio);
if (!userio)
return -ENOMEM;
mutex_init(&userio->mutex);
spin_lock_init(&userio->buf_lock);
init_waitqueue_head(&userio->waitq);
userio->serio = kzalloc_obj(*userio->serio);
if (!userio->serio)
return -ENOMEM;
userio->serio->write = userio_device_write;
userio->serio->port_data = userio;
file->private_data = no_free_ptr(userio);
return 0;
}
static int userio_char_release(struct inode *inode, struct file *file)
{
struct userio_device *userio = file->private_data;
if (userio->running) {
/*
* Don't free the serio port here, serio_unregister_port()
* does it for us.
*/
serio_unregister_port(userio->serio);
} else {
kfree(userio->serio);
}
kfree(userio);
return 0;
}
static size_t userio_fetch_data(struct userio_device *userio, u8 *buf,
size_t count, size_t *copylen)
{
size_t available, len;
guard(spinlock_irqsave)(&userio->buf_lock);
available = CIRC_CNT_TO_END(userio->head, userio->tail,
USERIO_BUFSIZE);
len = min(available, count);
if (len) {
memcpy(buf, &userio->buf[userio->tail], len);
userio->tail = (userio->tail + len) % USERIO_BUFSIZE;
}
*copylen = len;
return available;
}
static ssize_t userio_char_read(struct file *file, char __user *user_buffer,
size_t count, loff_t *ppos)
{
struct userio_device *userio = file->private_data;
int error;
size_t available, copylen;
u8 buf[USERIO_BUFSIZE];
/*
* By the time we get here, the data that was waiting might have
* been taken by another thread. Grab the buffer lock and check if
* there's still any data waiting, otherwise repeat this process
* until we have data (unless the file descriptor is non-blocking
* of course).
*/
for (;;) {
available = userio_fetch_data(userio, buf, count, &copylen);
if (available)
break;
/* buffer was/is empty */
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
return 0;
error = wait_event_interruptible(userio->waitq,
userio->head != userio->tail);
if (error)
return error;
}
if (copylen)
if (copy_to_user(user_buffer, buf, copylen))
return -EFAULT;
return copylen;
}
static int userio_execute_cmd(struct userio_device *userio,
const struct userio_cmd *cmd)
{
switch (cmd->type) {
case USERIO_CMD_REGISTER:
if (!userio->serio->id.type) {
dev_warn(userio_misc.this_device,
"No port type given on /dev/userio\n");
return -EINVAL;
}
if (userio->running) {
dev_warn(userio_misc.this_device,
"Begin command sent, but we're already running\n");
return -EBUSY;
}
userio->running = true;
serio_register_port(userio->serio);
break;
case USERIO_CMD_SET_PORT_TYPE:
if (userio->running) {
dev_warn(userio_misc.this_device,
"Can't change port type on an already running userio instance\n");
return -EBUSY;
}
userio->serio->id.type = cmd->data;
break;
case USERIO_CMD_SET_PORT_EXTRA:
if (userio->running) {
dev_warn(userio_misc.this_device,
"Can't change port extra on an already running userio instance\n");
return -EBUSY;
}
userio->serio->id.extra = cmd->data;
break;
case USERIO_CMD_SET_PORT_ID:
if (userio->running) {
dev_warn(userio_misc.this_device,
"Can't change port id on an already running userio instance\n");
return -EBUSY;
}
userio->serio->id.id = cmd->data;
break;
case USERIO_CMD_SET_PORT_PROTO:
if (userio->running) {
dev_warn(userio_misc.this_device,
"Can't change port proto on an already running userio instance\n");
return -EBUSY;
}
userio->serio->id.proto = cmd->data;
break;
case USERIO_CMD_SEND_INTERRUPT:
if (!userio->running) {
dev_warn(userio_misc.this_device,
"The device must be registered before sending interrupts\n");
return -ENODEV;
}
serio_interrupt(userio->serio, cmd->data, 0);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static ssize_t userio_char_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct userio_device *userio = file->private_data;
struct userio_cmd cmd;
int error;
if (count != sizeof(cmd)) {
dev_warn(userio_misc.this_device, "Invalid payload size\n");
return -EINVAL;
}
if (copy_from_user(&cmd, buffer, sizeof(cmd)))
return -EFAULT;
scoped_cond_guard(mutex_intr, return -EINTR, &userio->mutex) {
error = userio_execute_cmd(userio, &cmd);
if (error)
return error;
}
return count;
}
static __poll_t userio_char_poll(struct file *file, poll_table *wait)
{
struct userio_device *userio = file->private_data;
poll_wait(file, &userio->waitq, wait);
if (userio->head != userio->tail)
return EPOLLIN | EPOLLRDNORM;
return 0;
}
static const struct file_operations userio_fops = {
.owner = THIS_MODULE,
.open = userio_char_open,
.release = userio_char_release,
.read = userio_char_read,
.write = userio_char_write,
.poll = userio_char_poll,
};
static struct miscdevice userio_misc = {
.fops = &userio_fops,
.minor = USERIO_MINOR,
.name = USERIO_NAME,
};
module_driver(userio_misc, misc_register, misc_deregister);
MODULE_ALIAS_MISCDEV(USERIO_MINOR);
MODULE_ALIAS("devname:" USERIO_NAME);
MODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>");
MODULE_DESCRIPTION("Virtual Serio Device Support");
MODULE_LICENSE("GPL");